Skip to end of metadata
Go to start of metadata

Пример iOS-приложения для управления медиа-устройствами

Данный пример может использоваться как стример для публикации WebRTC-видеопотока с Web Call Server и позволяет выбрать медиа-устройства и следующие параметры для публикуемого и проигрываемого видео

  • разрешение (ширина, высота)
  • скорость передачи (bitrate)
  • FPS (Frames Per Second) - для публикуемого видео
  • quality - для проигрываемого видео

Поток может быть опубикован как с аудио и видео, так и без аудио или видео (переключатели 'Send Audio' и 'Send Video').
Аудио и видео в публикуемом потоке могут быть выключены/включены соответствующими переключателями 'Mute Audio' и 'Mute Video' во время или до начала публикации.
Видео потоки могут воспроизводиться с видео или без видео (переключатель 'Play Video').

На скриншоте ниже представлен пример во время публикации потока.
В URL в поле ввода demo.flashphoner.com - адрес WCS-сервера.
Слева отображается видео с камеры, справа воспроизводится опубликованный поток.


Вид с контролами для настроек публикации показывается при нажатии на кнопку 'Local settings'

а вид с контролами для настроек воспроизведения - при нажатии на кнопку 'Remote settings'.

Работа с кодом примера

Для разбора кода возьмем версию примера MediaDevices, которая доступна для скачивания на GitHub.

Классы видов

  • класс для основного вида приложения: ViewController (файл имплементации ViewController.swift)
  • класс для вида с настройками публикации: LocalViewController (файл имплементации LocalViewController.swift)
  • класс для вида с настройками воспроизведения: RemoteViewController (файл имплементации RemoteViewController.swift)

1. Импорт API. code

import FPWCSApi2Swift


2. Получение списка доступных медиа-устройств.

WCSApi2.getMediaDevices code

localDevices = WCSApi2.getMediaDevices()


3. Определение камеры и микрофона для использования по умолчанию

FPWCSApi2MediaDeviceList.audio FPWCSApi2MediaDeviceList.video code

        if (localDevices?.audio?.count ?? 0 > 0) {
            microphone.text = (localDevices?.audio[0] as AnyObject).label
        }
        if (localDevices?.video?.count ?? 0 > 0) {
            camera.text = (localDevices?.video[0] as AnyObject).label
        }


4. Определение параметров аудио и видео для публикуемого потока.

FPWCSApi2AudioConstraints, FPWCSApi2AudioConstraints code

    func toMediaConstraints() -> FPWCSApi2MediaConstraints {
        let ret = FPWCSApi2MediaConstraints()
        if (self.audioSend.isOn) {
            let audio = FPWCSApi2AudioConstraints()
            audio.useFEC = audioFEC.isOn
            audio.useStereo = audioStereo.isOn
            audio.bitrate = Int(audioBitrate.text ?? "0") ?? 0
            ret.audio = audio
        }
        if (self.videoSend.isOn) {
            let video = FPWCSApi2VideoConstraints()
            for device in localDevices!.video {
                if ((device as AnyObject).label == camera.text) {
                    video.deviceID = (device as AnyObject).deviceID;
                }
            }
            video.minWidth = Int(videoWidth.text ?? "0") ?? 0
            video.maxWidth = video.minWidth
            video.minHeight = Int(videoHeight.text ?? "0") ?? 0
            video.maxHeight = video.minHeight
            video.minFrameRate = Int(videoFPS.text ?? "0") ?? 0
            video.maxFrameRate = video.minFrameRate
            video.bitrate = Int(videoBitrate.text ?? "0") ?? 0
            ret.video = video;
        }
        return ret;
    }


5. Определение параметров для проигрываемого потока.

FPWCSApi2AudioConstraints, FPWCSApi2AudioConstraints  code

    func toMediaConstraints() -> FPWCSApi2MediaConstraints {
        let ret = FPWCSApi2MediaConstraints();
        ret.audio = FPWCSApi2AudioConstraints();
        if (playVideo.isOn) {
            let video = FPWCSApi2VideoConstraints();
            video.minWidth = Int(videoWidth.text ?? "0") ?? 0
            video.maxWidth = video.minWidth
            video.minHeight = Int(videoHeight.text ?? "0") ?? 0
            video.maxHeight = video.minWidth
            video.bitrate = Int(videoBitrate.text ?? "0") ?? 0
            video.quality = Int(videoQuality.text ?? "0") ?? 0
            ret.video = video;
        }
        return ret;
    }


6. Локальное тестирование микрофона и камеры

WCSApi2.getMediaAccess  code

    @IBAction func testPressed(_ sender: Any) {
        if (testButton.title(for: .normal) == "TEST") {
            let constraints = FPWCSApi2MediaConstraints(audio: true, video: true)!
            do {
                try WCSApi2.getMediaAccess(constraints, localDisplay.videoView)
            } catch {
                print(error)
            }
            testButton.setTitle("RELEASE", for: .normal)
        } else {
            WCSApi2.releaseLocalMedia(display: localDisplay.videoView);
            testButton.setTitle("TEST", for: .normal)
        }
    }


7. Создание сессии и подключение к серверу.

WCSSession, WCSSession.connect  code

В параметрах сессии указываются:

  • URL WCS-сервера
  • имя серверного приложения defaultApp
    @IBAction func connectPressed(_ sender: Any) {
        changeViewState(connectButton, false)
        if (connectButton.title(for: .normal) == "CONNECT") {
            if (session == nil) {
                let options = FPWCSApi2SessionOptions()
                options.urlServer = urlField.text
                options.appKey = "defaultApp"
                do {
                session = try WCSSession(options)
                } catch {
                    print(error);
                }
            }
            ...
            changeViewState(urlField, false)
            session?.connect()
        }
    }


8. Публикация потока.

WCSSession.createStream, WCSStream.publish  code

Методу createStream передаются параметры:

  • имя публикуемого потока
  • вид для локального отображения
  • параметры аудио и видео
    @IBAction func publishPressed(_ sender: Any) {
        changeViewState(publishButton,false)
        if (publishButton.title(for: .normal) == "PUBLISH") {
            let options = FPWCSApi2StreamOptions()
            options.name = publishName.text
            options.display = localDisplay.videoView
            options.constraints = localMediaConstrains;
            do {
                try publishStream = session!.createStream(options)
            } catch {
                print(error);
            }
            ...
            do {
                try publishStream?.publish()
            } catch {
                print(error);
            }
        }        
    }


9. Воспроизведение видеопотока после публикации

WCSSession.createStream, WCSStream.play  code

Методу createStream передаются параметры:

  • имя воспроизводимого потока
  • вид для отображения потока
  • параметры видео и аудио
    @IBAction func playPressed(_ sender: Any) {
        changeViewState(playButton,false)
        if (playButton.title(for: .normal) == "PLAY") {
            let options = FPWCSApi2StreamOptions()
            options.name = playName.text;
            options.display = remoteDisplay.videoView;
            options.constraints = remoteMediaConstrains;
            do {
            playStream = try session!.createStream(options)
            } catch {
                print(error);
            }
            ...
            do {
                try playStream?.play()
            } catch {
                print(error);
            }
        }
    }


10. Остановка воспроизведения потока.

WCSStream.stop  code

    @IBAction func playPressed(_ sender: Any) {
        changeViewState(playButton,false)
        if (playButton.title(for: .normal) == "PLAY") {
            ...
        } else{
            do {
                try playStream?.stop();
            } catch {
                print(error);
            }
        }
    }


11. Остановка публикации потока.

WCSStream.stop  code

    @IBAction func publishPressed(_ sender: Any) {
        changeViewState(publishButton,false)
        if (publishButton.title(for: .normal) == "PUBLISH") {
            ...
        } else {
            do {
                try publishStream?.stop();
            } catch {
                print(error);
            }
        }
        
    }
  • No labels