Пример стримера c доступом к медиа-устройствам
Данный стример может использоваться для публикации следующих типов потоков с Web Call Server
- WebRTC
- RTMFP
- RTMP
и позволяет выбрать медиа-устройства и параметры для публикуемого видео
- камера
- микрофон
- FPS (Frames Per Second)
- разрешение (ширина, высота)
На скриншоте ниже представлен пример во время публикации потока.
На странице вопроизводятся два видео:
- 'Local' - видео с камеры
- 'Preview' - видео, которое приходит с сервера
Код примера
Код данного примера находится на WCS-сервере по следующему пути:
/usr/local/FlashphonerWebCallServer/client2/examples/demo/streaming/media_devices_manager
manager.css - файл стилей
media_device_manager.html - страница стримера
manager.js - скрипт, обеспечивающий работу стримера
Тестировать данный пример можно по следующему адресу:
https://host:8888/client2/examples/demo/streaming/media_devices_manager/media_device_manager.html
Здесь host - адрес WCS-сервера.
Работа с кодом примера
Для разбора кода возьмем версию файла manager.js, которая находится здесь и доступна для скачивания в соответствующей сборке 0.5.28.2747.
1. Инициализация API.
Flashphoner.init() код
Flashphoner.init({ flashMediaProviderSwfLocation: '../../../../media-provider.swf', mediaProvidersReadyCallback: function (mediaProviders) { //hide remote video if current media provider is Flash if (mediaProviders[0] == "Flash") { $("#fecForm").hide(); $("#stereoForm").hide(); $("#sendAudioBitrateForm").hide(); $("#cpuOveruseDetectionForm").hide(); } if (Flashphoner.isUsingTemasys()) { $("#audioInputForm").hide(); $("#videoInputForm").hide(); } } })
2. Получение списка доступных медиа-устройств. код
Flashphoner.getMediaDevices() код
При получении списка медиа-устройств заполняются выпадающие списки на странице клиента.
Flashphoner.getMediaDevices(null, true).then(function (list) { list.audio.forEach(function (device) { var audio = document.getElementById("audioInput"); var i; var deviceInList = false; for (i = 0; i < audio.options.length; i++) { if (audio.options[i].value == device.id) { deviceInList = true; break; } } if (!deviceInList) { var option = document.createElement("option"); option.text = device.label || device.id; option.value = device.id; audio.appendChild(option); } }); list.video.forEach(function (device) { console.log(device); var video = document.getElementById("videoInput"); var i; var deviceInList = false; for (i = 0; i < video.options.length; i++) { if (video.options[i].value == device.id) { deviceInList = true; break; } } if (!deviceInList) { var option = document.createElement("option"); option.text = device.label || device.id; option.value = device.id; if (option.text.toLowerCase().indexOf("back") >= 0 && video.children.length > 0) { video.insertBefore(option, video.children[0]); } else { video.appendChild(option); } } }); $("#url").val(setURL() + "/" + createUUID(8)); //set initial button callback onStopped(); if (list.audio.length === 0) { $("#sendAudio").prop('checked', false).prop('disabled', true); } if (list.video.length === 0) { $("#sendVideo").prop('checked', false).prop('disabled', true); } }).catch(function (error) { $("#notifyFlash").text("Failed to get media devices"); });
3. Получение граничных параметров для аудио и видео со страницы клиента
getConstraints() код
function getConstraints() { constraints = { audio: $("#sendAudio").is(':checked'), video: $("#sendVideo").is(':checked'), customStream: $("#sendCanvasStream").is(':checked') }; if (constraints.audio) { constraints.audio = { deviceId: $('#audioInput').val() }; if ($("#fec").is(':checked')) constraints.audio.fec = $("#fec").is(':checked'); if ($("#sendStereoAudio").is(':checked')) constraints.audio.stereo = $("#sendStereoAudio").is(':checked'); if (parseInt($('#sendAudioBitrate').val()) > 0) constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val()); } if (constraints.video) { if (constraints.customStream) { constraints.customStream = canvas.captureStream(30); constraints.video = false; } else { constraints.video = { deviceId: {exact: $('#videoInput').val()}, width: parseInt($('#sendWidth').val()), height: parseInt($('#sendHeight').val()) }; if (Browser.isSafariWebRTC() && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") { constraints.video.width = {min: parseInt($('#sendWidth').val()), max: 640}; constraints.video.height = {min: parseInt($('#sendHeight').val()), max: 480}; } if (parseInt($('#sendVideoMinBitrate').val()) > 0) constraints.video.minBitrate = parseInt($('#sendVideoMinBitrate').val()); if (parseInt($('#sendVideoMaxBitrate').val()) > 0) constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val()); if (parseInt($('#fps').val()) > 0) constraints.video.frameRate = parseInt($('#fps').val()); } } return constraints; }
4. Получение доступа к медиаустройствам для локального тестирования
Flashphoner.getMediaAccess() код
В метод передаются граничные параметры для аудио и видео (constrains), а также localVideo - div-элемент, в котором будет отображаться видео с выбранной камеры.
Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disp) { $("#testBtn").text("Release").off('click').click(function () { $(this).prop('disabled', true); stopTest(); }).prop('disabled', false); 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) { var stream = localVideo.children[i].srcObject; audioContextForTest = new AudioContext(); var microphone = audioContextForTest.createMediaStreamSource(stream); var javascriptNode = audioContextForTest.createScriptProcessor(1024, 1, 1); microphone.connect(javascriptNode); javascriptNode.connect(audioContextForTest.destination); javascriptNode.onaudioprocess = function (event) { var inpt_L = event.inputBuffer.getChannelData(0); var sum_L = 0.0; for (var i = 0; i < inpt_L.length; ++i) { sum_L += inpt_L[i] * inpt_L[i]; } $("#micLevel").text(Math.floor(Math.sqrt(sum_L / inpt_L.length) * 100)); } } } } else if (Flashphoner.getMediaProviders()[0] == "Flash") { micLevelInterval = setInterval(function () { $("#micLevel").text(disp.children[0].getMicrophoneLevel()); }, 500); } testStarted = true; }).catch(function (error) { $("#testBtn").prop('disabled', false); testStarted = false; });
5. Подключение к серверу.
Flashphoner.createSession() код
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) { //session connected, start streaming startStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function () { setStatus(SESSION_STATUS.DISCONNECTED); onStopped(); }).on(SESSION_STATUS.FAILED, function () { setStatus(SESSION_STATUS.FAILED); onStopped(); });
6. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED код
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) { //session connected, start streaming startStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function () { setStatus(SESSION_STATUS.DISCONNECTED); onStopped(); }).on(SESSION_STATUS.FAILED, function () { setStatus(SESSION_STATUS.FAILED); onStopped(); });
7. Публикация видеопотока
session.createStream(), publishStream.publish() код
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.id()); //resize local if resolution is available if (video.videoWidth > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); } enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked")) { muteVideo(); } if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } //remove resize listener in case this video was cached earlier video.removeEventListener('resize', resizeLocalVideo); video.addEventListener('resize', resizeLocalVideo); setStatus(STREAM_STATUS.PUBLISHING); //play preview var constraints = { audio: $("#playAudio").is(':checked'), video: $("#playVideo").is(':checked') }; if (constraints.video) { constraints.video = { width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0, quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 }; } previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play(); }).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();
8. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING код
При получении данного события создается превью-видеопоток при помощи createStream() и вызывается play() для его воспроизведения.
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.id()); //resize local if resolution is available if (video.videoWidth > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); } enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked")) { muteVideo(); } if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } //remove resize listener in case this video was cached earlier video.removeEventListener('resize', resizeLocalVideo); video.addEventListener('resize', resizeLocalVideo); setStatus(STREAM_STATUS.PUBLISHING); //play preview var constraints = { audio: $("#playAudio").is(':checked'), video: $("#playVideo").is(':checked') }; if (constraints.video) { constraints.video = { width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0, quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 }; } previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play(); }).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();
9. Остановка воспроизведения превью-видеопотока.
previewStream.stop() код
$("#publishBtn").text("Stop").off('click').click(function () { $(this).prop('disabled', true); previewStream.stop(); }).prop('disabled', false);
10. Получение от сервера события, подтверждающего остановку воспроизведения
StreamStatusEvent STOPPED код
previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play();
11. Остановка публикации видеопотока после остановки воспроизведения превью-потока.
publishStream.stop() код
previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play();
12. Получение от сервера события, подтверждающего успешную остановку публикации
StreamStatusEvent UNPUBLISHED код
publishStream = session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true, constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { $("#testBtn").prop('disabled', true); var video = document.getElementById(publishStream.id()); //resize local if resolution is available if (video.videoWidth > 0 && video.videoHeight > 0) { resizeLocalVideo({target: video}); } enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked")) { muteVideo(); } if ($("#muteAudioToggle").is(":checked")) { muteAudio(); } //remove resize listener in case this video was cached earlier video.removeEventListener('resize', resizeLocalVideo); video.addEventListener('resize', resizeLocalVideo); setStatus(STREAM_STATUS.PUBLISHING); //play preview var constraints = { audio: $("#playAudio").is(':checked'), video: $("#playVideo").is(':checked') }; if (constraints.video) { constraints.video = { width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0, height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0, bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0, quality: (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 }; } previewStream = session.createStream({ name: streamName, display: remoteVideo, constraints: constraints }).on(STREAM_STATUS.PLAYING, function (previewStream) { document.getElementById(previewStream.id()).addEventListener('resize', function (event) { $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight); resizeVideo(event.target); }); //enable stop button onStarted(publishStream, previewStream); //wait for incoming stream if (Flashphoner.getMediaProviders()[0] == "WebRTC") { setTimeout(function () { detectSpeech(previewStream); }, 3000); } drawSquare(); }).on(STREAM_STATUS.STOPPED, function () { publishStream.stop(); }).on(STREAM_STATUS.FAILED, function () { //preview failed, stop publishStream if (publishStream.status() == STREAM_STATUS.PUBLISHING) { setStatus(STREAM_STATUS.FAILED); publishStream.stop(); } }); previewStream.play(); }).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();