...
- разрешение (ширина, высота)
- скорость передачи (bitrate)
- FPS (Frames Per Second) - для публикуемого видео
- quality - для проигрываемого видео
Поток может быть опубикован опубликован как с аудио и видео, так и без аудио или видео (переключатели 'Send Audio' и 'Send Video').
Аудио и видео в публикуемом потоке могут быть выключены/включены соответствующими переключателями 'Mute Audio' и 'Mute Video' во время или до начала публикации.
Видео потоки могут воспроизводиться с видео или без видео (переключатель 'Play Video').
При необходимости, определенные кодеки можно исключить из публикации или воспроизведения (поле 'Strip codecs')
На скриншоте ниже представлен пример во время публикации потока.
В URL в поле ввода demo.flashphoner.com - адрес WCS-сервера.
Слева отображается видео с камеры, справа воспроизводится опубликованный поток.
Вид с контролами для настроек настройками публикации показывается при нажатии на кнопку 'Local settings'
а вид с контролами для настроек настройками воспроизведения - при нажатии на кнопку 'Remote settings'.
Работа с кодом примера
Для разбора кода возьмем версию примера MediaDevices Swift, которая доступна для скачивания на GitHub.
Классы видов
- класс для основного вида приложения: ViewController (файл имплементации ViewController.swift)
- класс для вида с настройками публикации: LocalViewController (файл имплементации LocalViewController.swift)
- класс для вида с настройками воспроизведения: RemoteViewController (файл имплементации RemoteViewController.swift)
1. Импорт API.
Code Block | ||||
---|---|---|---|---|
| ||||
import FPWCSApi2Swift |
2. Получение списка доступных медиа-устройств.
WCSApi2.getMediaDevices code
Code Block | ||||
---|---|---|---|---|
| ||||
localDevices = WCSApi2.getMediaDevices() |
...
FPWCSApi2MediaDeviceList.audio FPWCSApi2MediaDeviceList.video code
Code Block | ||||
---|---|---|---|---|
| ||||
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. Определение параметров аудио и видео для публикуемого потока.
FPWCSApi2VideoConstraints, FPWCSApi2AudioConstraints , FPWCSApi2AudioConstraints code
Code Block | ||||
---|---|---|---|---|
| ||||
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. Определение параметров для проигрываемого потока.
FPWCSApi2AudioConstraintsFPWCSApi2VideoConstraints, FPWCSApi2AudioConstraints FPWCSApi2AudioConstraints code
Code Block | ||||
---|---|---|---|---|
| ||||
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
Code Block | ||||
---|---|---|---|---|
| ||||
@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) } } |
...
WCSSession, WCSSession.connect code
В параметрах сессии указываются:
...
WCSSession.createStream, WCSStream.publish code
Методу createStream передаются параметры:
- options.name - имя публикуемого потока
- options.display - вид для локального отображения
- options.constraints - параметры аудио и видео
- options.stripCodecs - массив кодеков, которые должны быть исключены из SDP при публикации
- options.transport - используемый WebRTC транспорт
Code Block | ||||
---|---|---|---|---|
| ||||
@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 { options.stripCodecs = localStripCodecs?.split(separator: ",") options.transport = tcpTransport.isOn ? kFPWCSTransport.fpwcsTransportTCP : kFPWCSTransport.fpwcsTransportUDP; do { try publishStream = session!.createStream(options) } catch { print(error); } ... do { try publishStream?.publish() } catch { print(error); } } } |
...
WCSSession.createStream, WCSStream.play code
Методу createStream передаются параметры:
- options.name - имя воспроизводимого публикуемого потока
- вид options.display - вид для локального отображения потока
- options.constraints - параметры видео и аудиоаудио и видео
- options.stripCodecs - массив кодеков, которые должны быть исключены из SDP при публикации
- options.transport - используемый WebRTC транспорт
Code Block | ||||
---|---|---|---|---|
| ||||
@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;
options.stripCodecs = remoteStripCodecs?.split(separator: ",")
options.transport = tcpTransport.isOn ? kFPWCSTransport.fpwcsTransportTCP : kFPWCSTransport.fpwcsTransportUDP;
do {
playStream = try session!.createStream(options)
} catch {
print(error);
}
...
do {
try playStream?.play()
} catch {
print(error);
}
}
} |
...