...
WCS позволяет настраивать камеру и микрофон в браузере. Рассмотрим, как и какими параметрами можно управлять при захвате аудио- и видеопотока, на примере веб-приложения Media Devices:
Настройки микрофона
1.Выбор микрофона из списка
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.INPUT).then(function (list) { list.audio.forEach(function (device) { var audio = document.getElementById("audioInput");... }); var i;... }).catch(function (error) { var deviceInList = false; for (i = 0; i < audio.options.length; i++ $("#notifyFlash").text("Failed to get media devices"); }); |
2. Переключение микрофона во время трансляции
code:
Code Block | ||||
---|---|---|---|---|
| ||||
$("#switchMicBtn").click(function (){ stream.switchMic().then(function(id) { if (audio.options[i].value == device.id) {$('#audioInput option:selected').prop('selected', false); $("#audioInput option[value='"+ deviceInList = trueid +"']").prop('selected', true); }).catch(function(e) { break; console.log("Error " + e); }); }).prop('disabled', !($('#sendAudio').is(':checked'))); |
3. Регулировка усиления микрофона (работает только в браузере Chrome)
code:
Code Block | ||||
---|---|---|---|---|
| ||||
}$("#micGainControl").slider({ range: "min", if (!deviceInList) { min: 0, max: 100, var option = document.createElement("option"); value: currentGainValue, step: 10, option.text = device.label || device.id; animate: true, slide: function (event, option.value = device.id;ui) { currentGainValue = audio.appendChild(option)ui.value; }if(previewStream) { }); ...publishStream.setMicrophoneGain(currentGainValue); }).catch(function (error) { } $("#notifyFlash").text("Failed to get media devices");} }); |
2. Регулировка усиления микрофона (работает только в браузере Chrome)код4. Включение коррекции ошибок (только для кодека Opus)
code:
Code Block | ||||
---|---|---|---|---|
| ||||
$("#micGainControl").slider({ if (constraints.audio) { range: "min", constraints.audio = { min: 0, maxdeviceId: 100,$('#audioInput').val() value: currentGainValue, }; step: 10,if ($("#fec").is(':checked')) animate: true, constraints.audio.fec slide: function (event, ui) {= $("#fec").is(':checked'); currentGainValue = ui.value;... } |
5. Установка стерео / моно режима.
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if if(previewStream(constraints.audio) { constraints.audio = { publishStream.setMicrophoneGain(currentGainValue); }deviceId: $('#audioInput').val() }; }); |
3. Включение коррекции ошибок (только для кодека Opus)
код:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.audio) { ... constraints.audio = { if ($("#sendStereoAudio").is(':checked')) constraints.audio.stereo = deviceId: $('#audioInput'"#sendStereoAudio").valis(':checked'); };... } |
6. Установка битрейта звука в бит/с
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if ($("#fec").is(':checked'))constraints.audio) { constraints.audio.fec = $("#fec").is(':checked');{ if ($("#sendStereoAudio").is(':checked')) deviceId: $('#audioInput').val() }; constraints .audio.stereo = $("#sendStereoAudio").is(':checked'); if (parseInt($('#sendAudioBitrate').val()) > 0) constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val()); } |
4. Установка стерео / моно режима7. Отключение микрофона.
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.audio$("#muteAudioToggle").is(":checked")) { constraints.audio = { muteAudio(); deviceId: $('#audioInput').val()} |
Настройки камеры
1. Выбор камеры
code:
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.INPUT).then(function (list) { };... iflist.video.forEach(function ($("#fec").is(':checked')) device) { constraints.audio.fec = $("#fec").is(':checked'); if ($("#sendStereoAudio").is(':checked'))}); }).catch(function (error) { constraints.audio.stereo = $("#sendStereoAudio#notifyFlash").is(':checked'); text("Failed to get media devices"); if (parseInt($('#sendAudioBitrate').val()) > 0) constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val()); } |
...
код:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.audio}); |
Если необходимо выбрать только камеру, не запрашивая доступ к аудиоустройствам, необходимо вызвать функцию getMediaDevices() с явным указанием ограничений
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.INPUT, {video: true, audio: false}).then(function (list) { ... list.video.forEach(function (device) { constraints.audio = {... }); deviceId: $('#audioInput').val() }).catch(function (error) { }$("#notifyFlash").text("Failed to get media devices"); }); |
2. Переключение камер.
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if ($$("#switchBtn").text("#fecSwitch").isoff(':checkedclick').click(function () { constraints.audio.fec = $("#fec").is(':checked');stream.switchCam().then(function(id) { if ($("#sendStereoAudio"'#videoInput option:selected').isprop(':checkedselected', false)); constraints.audio.stereo = $("#sendStereoAudio$("#videoInput option[value='"+ id +"']").isprop(':checkedselected', true); if (parseInt($('#sendAudioBitrate').val()) > 0)}).catch(function(e) { constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val()console.log("Error " + e); } |
6. Отключение микрофона.
код:
Code Block | ||||
---|---|---|---|---|
| ||||
}); if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } |
Настройки камеры
1. Выбор камеры
код:
Code Block |
---|
}); |
Переключение камеры может осуществляться "на лету", во время трансляции потока. Переключение работает в следующем порядке:
- На ПК камеры переключаются в том порядке, в каком они определены в менеджере устройств операционной системы.
- На Android при использовании браузера Chrome по умолчанию выбирается фронтальная камера, при использовании браузера Firefox - тыловая камера
- На iOS в браузере Safari по умолчанию выбирается фронтальная камера, но в выпадающем списке при выборе камеры первой указана тыловая камера.
3. Установка разрешения видео
code:
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true).then(function (list) constraints.video = { ... list.video.forEach(function (device) { deviceId: $('#videoInput').val(), console.log(device);width: parseInt($('#sendWidth').val()), var video = document.getElementById("videoInput");height: parseInt($('#sendHeight').val()) var i}; if (Browser.isSafariWebRTC() && var deviceInList = false;Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") { for (iconstraints.video.deviceId = 0; i < video.options.length; i++) { {exact: $('#videoInput').val()}; } |
4. Установка FPS
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.video) { if (video.options[i].value == device.id) { if (parseInt($('#fps').val()) deviceInList = true;> 0) constraints.video.frameRate break= parseInt($('#fps').val()); } |
5. Установка битрейта видео в кбит/с
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.video) { } ... } if (parseInt($('#sendVideoMinBitrate').val()) > 0) if (!deviceInList) { constraints.video.minBitrate = parseInt($('#sendVideoMinBitrate').val()); var option = document.createElement("option"); if (parseInt($('#sendVideoMaxBitrate').val()) > 0) option.textconstraints.video.maxBitrate = device.label || device.idparseInt($('#sendVideoMaxBitrate').val()); option.value = device.id;... } |
6. Установка CPU Overuse Detection
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if (option.text.toLowerCase(!$("#cpuOveruseDetection").indexOf("back") >= 0 && video.children.length > 0is(':checked')) { mediaConnectionConstraints = { video.insertBefore(option, video.children[0]); "mandatory": { } else {googCpuOveruseDetection: false } video.appendChild(option);} } |
7. Отключение камеры
code:
Code Block | ||||
---|---|---|---|---|
| ||||
if }($("#muteVideoToggle").is(":checked")) { } }muteVideo(); }).catch(function (error) { $("#notifyFlash").text("Failed to get media devices"); }); |
2. Переключение камер.
...
} |
Тестирование захвата с камеры и микрофона локально
Локальное тестирование захвата с микрофона и камеры предназначено для того, чтобы проверить работоспособность микрофона и камеры в браузере, не отправляя поток на сервер.
code:
Code Block | ||||
---|---|---|---|---|
| ||||
function $startTest("#switchBtn").text("Switch) { Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disp) { $("#testBtn").text("Release").off('click').click(function () { publishStream.switchCam $(this).prop('disabled', true); stopTest(); }).prop('disabled', $('#sendCanvasStream').is(':checked')); |
Переключение камеры может осуществляться "на лету", во время трансляции потока. Переключение работает в следующем порядке:
- На ПК камеры переключаются в том порядке, в каком они определены в менеджере устройств операционной системы.
- На Android при использовании браузера Chrome по умолчанию выбирается фронтальная камера, при использовании браузера Firefox - тыловая камера
- На iOS в браузере Safari по умолчанию выбирается фронтальная камера, но в выпадающем списке при выборе камеры первой указана тыловая камера.
3. Установка разрешения видео
код:
Code Block | ||||
---|---|---|---|---|
| ||||
function resizeLocalVideo(event) { var requested = constraints.video; if (requested.width != event.target.videoWidth || requested.height != event.target.videoHeightfalse); window.AudioContext = window.AudioContext || window.webkitAudioContext; if (Flashphoner.getMediaProviders()[0] == "WebRTC" && window.AudioContext) { for (i = 0; i < localVideo.children.length; i++) { if (localVideo.children[i] && localVideo.children[i].id.indexOf("-LOCAL_CACHED_VIDEO") != -1) { console.warn("Camera does not support requested resolution, actual resolution is " + event.target.videoWidth +var "x"stream += event.target.videoHeight)localVideo.children[i].srcObject; } $("#publishResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); } |
4. Установка FPS
код:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.video) { audioContextForTest = new AudioContext(); if (constraints.customStream) { var constraints.customStreammicrophone = canvasaudioContextForTest.captureStreamcreateMediaStreamSource(30stream); constraints.video = false; var javascriptNode } else { = audioContextForTest.createScriptProcessor(1024, 1, 1); constraints.video = { microphone.connect(javascriptNode); deviceId: {exact: $('#videoInput').val()}, javascriptNode.connect(audioContextForTest.destination); width: parseInt($('#sendWidth').val()), javascriptNode.onaudioprocess = function (event) { height: parseInt($('#sendHeight').val()) var inpt_L = event.inputBuffer.getChannelData(0); }; if (Browser.isSafariWebRTC() && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") { var sum_L = 0.0; constraints.video.width = {min:for parseInt($('#sendWidth').val()), max: 640};(var i = 0; i < inpt_L.length; ++i) { constraints.video.height = {min: parseInt($('#sendHeight').val()), max: 480}; sum_L += inpt_L[i] }* inpt_L[i]; if (parseInt($('#sendVideoMinBitrate').val()) > 0) } constraints.video.minBitrate = parseInt($('#sendVideoMinBitrate').val()); if (parseInt($('#sendVideoMaxBitrate').val()) > 0)$("#micLevel").text(Math.floor(Math.sqrt(sum_L / inpt_L.length) * 100)); constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val()); } if (parseInt($('#fps').val()) > 0) } } } constraints.video.frameRate = parseInt($('#fps').val());else if (Flashphoner.getMediaProviders()[0] == "Flash") { } micLevelInterval } |
...
код:
Code Block | ||||
---|---|---|---|---|
| ||||
if (constraints.video= setInterval(function () { if (constraints.customStream) { constraints.customStream = canvas.captureStream(30$("#micLevel").text(disp.children[0].getMicrophoneLevel()); constraints.video = false}, 500); } else { testStarted constraints.video = {= true; deviceId: {exact: $('#videoInput').val()}, }).catch(function (error) { width: parseInt($('#sendWidth'"#testBtn").val()),prop('disabled', false); testStarted = false; height: parseInt($('#sendHeight').val()) };}); } |
Замена отдельных параметров SDP
При публикации потока предусмотрена возможность замены параметров SDP. В поле 'SDP replace' указывается шаблон поиска параметра, который нужно заменить, в поле 'with' указывается новое значение параметра.
Для замены параметров SDP используется callback-функция, которая должна быть указана при создании потока в параметре sdpHook метода createStream():
создание потока code
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ name: streamName, if (Browser.isSafariWebRTC() && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") { display: localVideo, cacheLocalResources: true, constraints: constraints.video.width = {min: parseInt($('#sendWidth').val()), max: 640}; , mediaConnectionConstraints: mediaConnectionConstraints, sdpHook: rewriteSdp, constraints..video.height = {min: parseInt($('#sendHeight').val()), max: 480}; }) |
функция rewriteSdp code
Code Block | ||||
---|---|---|---|---|
| ||||
function rewriteSdp(sdp) { var sdpStringFind }= $("#sdpStringFind").val().replace('\\r\\n','\r\n'); var sdpStringReplace if (parseInt($('#sendVideoMinBitrate'= $("#sdpStringReplace").val().replace('\\r\\n','\r\n'); > 0) if (sdpStringFind != 0 && sdpStringReplace != 0) { constraints.video.minBitrate var newSDP = parseInt($('#sendVideoMinBitrate').valsdp.sdpString.toString()); newSDP = if (parseInt($('#sendVideoMaxBitrate').val()) > 0)newSDP.replace(new RegExp(sdpStringFind,"g"), sdpStringReplace); return newSDP; } constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val()); if (parseInt($('#fps').val()) > 0) constraints.video.frameRate = parseInt($('#fps').val()); } } |
6. Установка CPU Overuse Detection
код:
Code Block | ||||
---|---|---|---|---|
| ||||
if (!$("#cpuOveruseDetection").is(':checked')) {
mediaConnectionConstraints = {
"mandatory": {
googCpuOveruseDetection: false
}
}
} |
7. Отключение камеры
код:
Code Block |
---|
return sdp.sdpString;
} |
Увеличение битрейта публикуемого видео в браузере Chrome
Замена параметров SDP позволяет увеличить битрейт публикуемого видео. Для этого необходимо при публикации H264 заменить параметр 'a' по шаблону
Code Block | ||
---|---|---|
| ||
a=fmtp:(.*) (.*) |
на
Code Block | ||
---|---|---|
| ||
a=fmtp:$1 $2;x-google-min-bitrate=2500 |
Здесь 2500 - битрейт в килобитах в секунду.
Подобным образом можно указать битрейт видео на старте (атрибут x-google-start-bitrate) и ограничить максимальный битрейт (атрибут x-google-max-bitrate). Отметим, что, если указать только минимальный битрейт, то выше 2500 кбит/с битрейт поднять не удается, возможно, по умолчанию в Chrome зафиксирован максимальный битрейт на уровне 2500 кбит/с. Если необходимо использовать более высокие значения, например, для трансляции потока высокого разрешения, должны быть указаны и минимальное, и максимальное значения:
Code Block | ||
---|---|---|
| ||
a=fmtp:$1 $2;x-google-max-bitrate=7000;x-google-min-bitrate=3000 |
В этом случае браузер будет держать битрейт при публикации потока в пределах от 3000 до 7000 кбит/с.
При публикации потока VP8 необходимо заменить
Code Block | ||
---|---|---|
| ||
a=rtpmap:(.*) VP8/90000\r\n |
на
Code Block | ||
---|---|---|
| ||
a=rtpmap:$1 VP8/90000\r\na=fmtp:$1 x-google-min-bitrate=3000;x-google-max-bitrate=7000\r\n |
Возможность управления битрейтом доступна только в браузере Chrome.
Задание пропускной способности канала
Замена параметров SDP позволяет задать пропускную способность канала при публикации потока. Для этого необходимо при публикации заменить параметр 'c' по шаблону
Code Block | ||
---|---|---|
| ||
c=IN (.*)\r\n |
на
Code Block | ||
---|---|---|
| ||
c=IN $1\r\nb=AS:10000\r\n |
Установка используемых кодеков
При публикации потока предусмотрена возможность убрать из WebRTC SDP кодеки, которые не должны использоваться при публикации данного потока, например:
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ if ($("#muteVideoToggle").is(":checked")) ... stripCodecs: "h264,H264,flv,mpv" }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { ... muteVideo(}); } |
Тестирование захвата с камеры и микрофона локально
Локальное тестирование захвата с микрофона и камеры предназначено для того, чтобы проверить работоспособность микрофона и камеры в браузере, не отправляя поток на сервер.
...
publishStream.publish(); |
Данная возможность полезна, в частности, для обхода багов браузера с каким-либо кодеком. Например, если в браузере не работает H.264, можно отключить его и перейти на VP8 при работе по WebRTC.
Управление выводом звука
При воспроизведении потока можно выбрать (и переключить "на лету") устройство вывода звука в браузерах Chrome и MS Edge.
code:
Code Block | ||||
---|---|---|---|---|
| ||||
function startTest() { if (Browser.isSafariWebRTC())Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.OUTPUT).then(function (list) { Flashphonerlist.audio.playFirstVideoforEach(localVideo,function true(device); { Flashphoner.playFirstVideo(remoteVideo, false); ... }); Flashphoner.getMediaAccess(getConstraints(), localVideo).then}).catch(function (disperror) { $("#testBtn"'#audioOutputForm').textremove("Release").off('click').click(function () { ); $(this).prop('disabled', true); stopTest(); }).prop('disabled', false); window.AudioContext = window.AudioContext || window.webkitAudioContext; }); |
Отметим, что в браузерах Firefox и Safari нельзя получить список устройств вывода, поэтому данная функция а них не работает
Отображение WebRTC-статистики
При публикации и воспроизведении потока клиентское приложение может получить WebRTC-статистику в соответствии со стандартом. Эта статистика может быть отображена в браузере, например:
Отметим, что в браузере Safari отображается только статистика аудио.
1. Отображение статистики при публикации потока
stream.getStats() code:
Code Block | ||||
---|---|---|---|---|
| ||||
ifpublishStream.getStats(function (Flashphoner.getMediaProviders()[0] == "WebRTC"stats) { if (stats && windowstats.AudioContextoutboundStream) { for (i = 0; i < localVideo.children.length; i++if (stats.outboundStream.video) { if (localVideo.children[i] && localVideo.children[i].id.indexOf("-LOCAL_CACHED_VIDEO") != -1) { showStat(stats.outboundStream.video, "outVideoStat"); varlet streamvBitrate = localVideo.children[i].srcObject(stats.outboundStream.video.bytesSent - videoBytesSent) * 8; audioContextForTest = new AudioContext(); if ($('#outVideoStatBitrate').length == 0) { var microphone = audioContextForTest.createMediaStreamSource(stream); let html = "<div>Bitrate: " + "<span id='outVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>"; var javascriptNode = audioContextForTest.createScriptProcessor(1024, 1, 1); microphone.connect(javascriptNode$("#outVideoStat").append(html); javascriptNode.connect(audioContextForTest.destination); } else { javascriptNode.onaudioprocess = function (event) { $('#outVideoStatBitrate').text(vBitrate); } var inpt_LvideoBytesSent = eventstats.outboundStream.inputBuffer.getChannelData(0)video.bytesSent; ... var sum_L = 0.0; } for (var i = 0; i < inpt_L.length; ++iif (stats.outboundStream.audio) { sum_L += inpt_L[i] * inpt_L[i]showStat(stats.outboundStream.audio, "outAudioStat"); let aBitrate = } (stats.outboundStream.audio.bytesSent - audioBytesSent) * 8; if ($("#micLevel").text(Math.floor(Math.sqrt(sum_L / inpt_L.length) * 100)); '#outAudioStatBitrate').length == 0) { } let html = "<div>Bitrate: " + "<span id='outAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" }+ "</div>"; } } else if $(Flashphoner.getMediaProviders()[0] == "Flash") { "#outAudioStat").append(html); micLevelInterval = setInterval(function ()} else { $("#micLevel"'#outAudioStatBitrate').text(disp.children[0].getMicrophoneLevel())aBitrate); }, 500); } testStarted = true; }).catch(function (error) { audioBytesSent $("#testBtn").prop('disabled', false); = stats.outboundStream.audio.bytesSent; testStarted = false; }); drawSquare(); } |
Установка используемых кодеков
При публикации потока предусмотрена возможность убрать из WebRTC SDP кодеки, которые не должны использоваться при публикации данного потока, например:
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ } name: streamName, display: localVideo,... cacheLocalResources: true, }); |
2. Отображение статистики при воспроизведении потока
stream.getStats() code:
Code Block | ||||
---|---|---|---|---|
| ||||
constraints: constraints,previewStream.getStats(function (stats) { mediaConnectionConstraints: mediaConnectionConstraints, if (stats && stats.inboundStream) { stripCodecs: "h264,H264,flv,mpv" }).on(STREAM_STATUS.PUBLISHING, function (publishStream if (stats.inboundStream.video) { $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.id())showStat(stats.inboundStream.video, "inVideoStat"); //resize local if resolution is available let ifvBitrate = (stats.inboundStream.video.videoWidthbytesReceived > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); - videoBytesReceived) * 8; } enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked"))'#inVideoStatBitrate').length == 0) { muteVideo(); } let html = if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } //remove resize listener in case this video was cached earlier"<div>Bitrate: " + "<span id='inVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>"; $("#inVideoStat").append(html); } else { $('#inVideoStatBitrate').text(vBitrate); } videoBytesReceived = stats.inboundStream.video.bytesReceived; ... } if (stats.inboundStream.audio) { showStat(stats.inboundStream.audio, "inAudioStat"); let aBitrate = (stats.inboundStream.audio.bytesReceived - audioBytesReceived) * 8; if ($('#inAudioStatBitrate').length == 0) { let html = "<div style='font-weight: bold'>Bitrate: " + "<span id='inAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>"; $("#inAudioStat").append(html); } else { $('#inAudioStatBitrate').text(aBitrate); } audioBytesReceived = stats.inboundStream.audio.bytesReceived; } ... } }); |
Управление параметрами картинки при публикации потока
При публикации видео потока с помощью граничных условий (constraints) можно управлять разрешением картинки и частотой кадров
Управление разрешением картинки
Разрешение картинки можно указать точно
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{width:320,height:240}} |
Однако, в некоторых случаях требуется указать диапазон для ширины и высоты
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{width:{min:160,max:320},height:{min:120,max:240}}} |
Для некоторых браузеров, например, iOS Safari, необходимо указать точные значения в виде диапазона (в последних версиях это обрабатывается на уровне WebSDK)
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{width:{min:320,max:320},height:{min:240,max:240}}} |
Управление частотой кадров
Частота кадров может быть указана точно
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{frameRate:30} |
или в виде диапазона
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{frameRate:{min:15,max:30}} |
В некоторых случаях, например, если веб-камера поддерживает 24 fps, при точном указании 30 fps публикация может завершиться ошибкой. В таком случае нужно задать частоту кадров как идеальную
Code Block | ||||
---|---|---|---|---|
| ||||
constraints = {audio:true, video:{frameRate:{ideal:30}} |
Переключение между потоками с веб-камеры и с экрана во время трансляции
При организации вебинаров возникает необходимость переключаться между потоками, захваченными с веб-камеры ведущего и с экрана. во время трансляции. В идеале, переключение должно быть бесшовным и с сохранением звуковой дорожки с микрофона ведущего. В последних версиях WebSDK реализована такая возможность для браузеров Chrome и Firefox, рассмотрим пример использования в приложении Media Devices.
1. Во время трансляции потока с выбранных камеры и микрофона, при установке переключателя 'Screen share' в положение 'on' будет вызвана функция switchToScreen
stream.switchToScreen code
Code Block | ||||
---|---|---|---|---|
| ||||
function switchToScreen() { if (publishStream) { video.removeEventListener$('#switchBtn').prop('resizedisabled', resizeLocalVideotrue); video.addEventListener$('#videoInput').prop('resizedisabled', resizeLocalVideotrue); setStatus(STREAM_STATUS.PUBLISHING); //play preview var constraints = publishStream.switchToScreen($('#mediaSource').val()).catch(function () { audio: $("#playAudio#screenShareToggle").isremoveAttr(':"checked'"),; video: $("#playVideo"'#switchBtn').isprop(':checkeddisabled', false); }; if (constraints.video) { constraints.video = { $('#videoInput').prop('disabled', false); }); width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0,} } |
В данную функцию передается источник потока (экран).
2. Затем пользователь в браузере Chrome при помощи расширения, а в браузере Firefox средствами браузера должен выбрать весь экран или окно программы для трансляции:
3. На сервер транслируется поток с экрана
При этом источник трансляции звука не меняется.
4. Для возврата к трансляции потока с веб-камеры вызывается функция switch ToCam
stream.switchToCam code
Code Block | ||||
---|---|---|---|---|
| ||||
function switchToCam() { if (publishStream) { publishStream.switchToCam(); bitrate: (!$("#receiveDefaultBitrate"'#switchBtn').is(":checked")) ? $("#receiveBitrate").val() : 0,prop('disabled', false); quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 }; } }).on(STREAM_STATUS.UNPUBLISHED, function () { setStatus(STREAM_STATUS.UNPUBLISHED); //enable start button onStopped(); }).on(STREAM_STATUS.FAILED, function () { setStatus(STREAM_STATUS.FAILED); //enable start button onStopped(); }); publishStream.publish(); |
...
$('#videoInput').prop('disabled', false);
}
} |
Ограничения
1. Переключение трансляции работает только в браузерах Chrome и Firefox.
2. Невозможно переключить веб-камеру в то время, когда транслируется экран.
3. Для публикации экрана в браузере Chrome необходимо расширение.
4. Переключение работает только в том случае, если первым опубликован поток с веб-камеры.
Известные проблемы
1. Не работает переключение микрофона в браузере Safari.
Симптомы: не переключается микрофон при помощи метода switchMic() WCS WebSDK.
Решение: использовать другой браузер, поскольку Safari всегда использует микрофон sound input, выбранный в настройках звука системы sound menu (для входа необходимо зажать клавишу Option (Alt) и щелкнуть по иконке звука в меню). После выбора другого микрофона в sound menu требуется перезагрузка Mac.
Если не работает микрофон Logitech USB camera (когда выбран в sound input), может помочь изменение format / sample rate в Audio MIDI Setup и перезагрузка.
2. iOS Safari зависает на воспроизведении, если публикующий переключает камеру.
Симптомы: при переключении камеры воспроизведение публикуемого потока в браузере iOS Safari зависает.
Решение: включить транскодинг при помощи параметра в файле flashphoner.properties
Code Block | ||||
---|---|---|---|---|
| ||||
disable_streaming_proxy=true |
или указав фиксированное разрешение для плеера при воспроизведении
Code Block | ||||
---|---|---|---|---|
| ||||
session.createStream({constraints:{audio:true,video:{width:320,height:240}}}).play(); |