Table of Contents |
---|
Пример стримера c доступом к медиа-устройствам
Данный стример может использоваться для публикации следующих типов потоков с и проигрывания WebRTC потоков на Web Call Server
- WebRTC
- RTMFP
- RTMP
и позволяет выбрать медиа-устройства и параметры для публикуемого видео
...
На скриншоте ниже представлен пример во время публикации потока.
На странице вопроизводятся отображаются два видео элемента:
- 'Local' - видео с камеры
- 'PreviewPlayer' - видео, которое приходит с сервера
Код примера
Код данного примера находится на WCS-сервере по следующему пути:
...
Тестировать данный пример можно по следующему адресу:
https://host:8888/client2/examples/demo/streaming/media_devices_manager/media_device_manager.html
Здесь host - адрес WCS-сервера.
Работа с кодом примера
Для разбора кода возьмем версию файла manager.js с хэшем ecbadc3, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.5212.28.2747.
1. Инициализация API.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.init({ flashMediaProviderSwfLocationscreenSharingExtensionId: '../../../../media-provider.swf'extensionId, mediaProvidersReadyCallback: function (mediaProviders) { //hide remote video if current media provider is Flash if (mediaProviders[0] == "Flash") { if (Flashphoner.isUsingTemasys()) { $("#fecForm#audioInputForm").hide(); $("#stereoForm#videoInputForm").hide(); $("#sendAudioBitrateForm").hide();} } $("#cpuOveruseDetectionForm").hide(); }) |
2. Получение списка доступных медиа-устройств ввода
Flashphoner.getMediaDevices() code
При получении списка медиа-устройств заполняются выпадающие списки микрофонов и камер на странице клиента.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true).then(function (list) { } list.audio.forEach(function (device) { if (Flashphoner.isUsingTemasys()) {... }); $("#audioInputForm").hide();list.video.forEach(function (device) { ... $("#videoInputForm").hide(}); ... }).catch(function }(error) { } $("#notifyFlash").text("Failed to get media devices"); }); |
23. Получение списка доступных медиа-устройств . кодвывода звука
Flashphoner.getMediaDevices() код code
При получении списка медиа-устройств заполняются выпадающие списки выпадающий список устройств вывода звука на странице клиента.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.OUTPUT).then(function (list) { list.audio.forEach(function (device) { ... }); list.video.forEach... }).catch(function (deviceerror) { $("#notifyFlash").text("Failed to get ... }); ... }).catch(function (error) { $("#notifyFlash").text("Failed to get media devices")media devices"); }); |
34. Получение граничных параметров для публикации аудио и видео со страницы клиента
Источники публикации:
- камера (sendVideo)
- микрофон (sendAudio)
- HTML5 Canvas (sendCanvasStream)
Code Blockcode | ||||
---|---|---|---|---|
| ||||
constraints = { audio: $("#sendAudio").is(':checked'), video: $("#sendVideo").is(':checked'), customStream: $("#sendCanvasStream").is(':checked') }; |
Параметры аудио:
- выбор микрофона (deviceId)
- коррекция ошибок для кодека Opus (fec)
- режим стерео (stereo)
- битрейт аудио (bitrate)
...
Code Block | ||||
---|---|---|---|---|
| ||||
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.widthdeviceId = {minexact: parseInt($('#sendWidth#videoInput').val()), max: 640}; } constraints.video.height = {min:if (parseInt($('#sendHeight#sendVideoMinBitrate').val()), max: 480};> 0) } 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()); |
45. Получение доступа к медиаустройствам для локального тестирования
Flashphoner.getMediaAccess() код code
В метод передаются граничные параметры для аудио и видео (constrains), а также localVideo - div-элемент, в котором будет отображаться видео с выбранной камеры.
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disp) { $("#testBtn").text("Release").off('click').click(function () { (function (disp) { $("#testBtn").text("Release").off('click').click(function () { $(this).prop('disabled', true); stopTest(); }).prop('disabled', false); ... testStarted = true; }).catch(function (error) { $("#testBtn").prop('disabled', false); testStarted = false; }); |
6. Подключение к серверу.
Flashphoner.createSession() code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url, timeout: tm}).on(SESSION_STATUS.ESTABLISHED, function (session) {
...
}).on(SESSION_STATUS.DISCONNECTED, function () {
...
}).on(SESSION_STATUS.FAILED, function () {
...
}); |
7. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url, timeout: tm}).on(SESSION_STATUS.ESTABLISHED, function (session) {
setStatus("#connectStatus", session.status());
onConnected(session);
...
}); |
8. Публикация видеопотока
session.createStream(), publishStream.publish() code
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({
name: streamName,
display: localVideo,
cacheLocalResources: true,
constraints: constraints,
mediaConnectionConstraints: mediaConnectionConstraints,
sdpHook: rewriteSdp,
transport: transportInput,
cvoExtension: cvo,
stripCodecs: strippedCodecs,
videoContentHint: contentHint
...
});
publishStream.publish(); |
9. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING code
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({
...
}).on(STREAM_STATUS.PUBLISHING, function (stream) {
$("#testBtn").prop('disabled', true);
var video = document.getElementById(stream.id());
//resize local if resolution is available
if (video.videoWidth > 0 && video.videoHeight > 0) {
resizeLocalVideo({target: video});
}
enablePublishToggles(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);
publishStream.setMicrophoneGain(currentGainValue);
setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
onPublishing(stream);
}).on(STREAM_STATUS.UNPUBLISHED, function () {
...
}).on(STREAM_STATUS.FAILED, function () {
...
});
publishStream.publish(); |
10. Воспроизведение потока
session.createStream(), previewStream.play() code
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({
name: streamName,
display: remoteVideo,
constraints: constraints,
transport: transportOutput,
stripCodecs: strippedCodecs
...
});
previewStream.play(); |
11. Получение от сервера события, подтверждающего успешное воспроизведение потока
StreamStatusEvent PLAYING code
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({
...
}).on(STREAM_STATUS.PLAYING, function (stream) {
playConnectionQualityStat.connectionQualityUpdateTimestamp = new Date().valueOf();
setStatus("#playStatus", stream.status());
onPlaying(stream);
document.getElementById(stream.id()).addEventListener('resize', function (event) {
$("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight);
resizeVideo(event.target);
});
//wait for incoming stream
if (Flashphoner.getMediaProviders()[0] == "WebRTC") {
setTimeout(function () {
if(Browser.isChrome()) {
detectSpeechChrome(stream);
} else {
detectSpeech(stream);
}
}, 3000);
}
...
});
previewStream.play(); |
12. Остановка воспроизведения потока.
stream.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
$("#playBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
stream.stop();
}).prop('disabled', false); |
13. Получение от сервера события, подтверждающего остановку воспроизведения
StreamStatusEvent STOPPED code
Code Block | ||||
---|---|---|---|---|
| ||||
previewStream = session.createStream({
...
}).on(STREAM_STATUS.STOPPED, function () {
setStatus("#playStatus", STREAM_STATUS.STOPPED);
onStopped();
...
});
previewStream.play(); |
14. Остановка публикации видеопотока
stream.stop() code
Code Block | ||||
---|---|---|---|---|
| ||||
$("#publishBtn").text("Stop").off('click').click(function () {
$(this).prop('disabled', true);
stream.stop();
}).prop('disabled', false); |
15. Получение от сервера события, подтверждающего успешную остановку публикации
StreamStatusEvent UNPUBLISHED code
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({
...
}).on(STREAM_STATUS.UNPUBLISHED, function () {
setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
onUnpublished();
...
});
publishStream.publish(); |
16. Отключение микрофона
stream.muteAudio() code:
Code Block | ||||
---|---|---|---|---|
| ||||
function muteAudio() {
if (publishStream) {
publishStream.muteAudio();
}
} |
17. Отключение камеры
stream.muteVideo() code:
Code Block | ||||
---|---|---|---|---|
| ||||
function muteVideo() {
if (publishStream) {
publishStream.muteVideo();
}
} |
18. Отображение статистики при публикации потока
stream.getStats() code:
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream.getStats(function (stats) { if (stats && stats.outboundStream) { if (stats.outboundStream.video) { showStat(stats.outboundStream.video, "outVideoStat"); let vBitrate = (stats.outboundStream.video.bytesSent - videoBytesSent) * 8; if ($('#outVideoStatBitrate').length == 0) { let html = "<div>Bitrate: " + "<span id='outVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>"; $("#outVideoStat").append(html); } else { $('#outVideoStatBitrate').text(vBitrate); } videoBytesSent = stats.outboundStream.video.bytesSent; ... } if (stats.outboundStream.audio) { showStat(stats.outboundStream.audio, "outAudioStat"); let aBitrate = (stats.outboundStream.audio.bytesSent - audioBytesSent) * 8; if ($('#outAudioStatBitrate').length == 0) { let html = "<div>Bitrate: " + "<span id='outAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>"; $("#outAudioStat").append(html); } else { $('#outAudioStatBitrate').text(aBitrate); } audioBytesSent = stats.outboundStream.audio.bytesSent; } } ... $(this).prop('disabled', true); }); |
19. Отображение статистики при воспроизведении потока
stream.getStats() code:
Code Block | ||||
---|---|---|---|---|
| ||||
stopTest();previewStream.getStats(function (stats) { }).prop('disabled', false); if (stats && ...stats.inboundStream) { testStarted = true; }).catch(function (error) { if $("#testBtn").prop('disabled', false);(stats.inboundStream.video) { testStarted = false; }); |
5. Подключение к серверу.
Flashphoner.createSession() код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) {showStat(stats.inboundStream.video, "inVideoStat"); //session connected, start streaming startStreaming(session); let vBitrate }).on(SESSION_STATUS.DISCONNECTED, function () {= (stats.inboundStream.video.bytesReceived - videoBytesReceived) * 8; setStatus(SESSION_STATUS.DISCONNECTED); onStopped(); }).on(SESSION_STATUS.FAILED, function (if ($('#inVideoStatBitrate').length == 0) { setStatus(SESSION_STATUS.FAILED); onStopped(); }); |
6. Получение от сервера события, подтверждающего успешное соединение.
ConnectionStatusEvent ESTABLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, function (session) { //session connected, start streaming startStreaming(session); }).on(SESSION_STATUS.DISCONNECTED, function () { let html = "<div>Bitrate: " + "<span id='inVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>"; ... }$("#inVideoStat").on(SESSION_STATUS.FAILED, function () { append(html); ... }); |
7. Публикация видеопотока
session.createStream(), publishStream.publish() код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream} =else session.createStream({ name: streamName, display: localVideo, cacheLocalResources: true,$('#inVideoStatBitrate').text(vBitrate); constraints: constraints, mediaConnectionConstraints: mediaConnectionConstraints } ... }); publishStream.publish(); |
8. Получение от сервера события, подтверждающего успешную публикацию потока
StreamStatusEvent PUBLISHING код
При получении данного события создается превью-видеопоток при помощи createStream() и вызывается play() для его воспроизведения.
Code Block | ||||
---|---|---|---|---|
| ||||
publishStreamvideoBytesReceived = session.createStream({stats.inboundStream.video.bytesReceived; name: streamName, ... display: localVideo,} cacheLocalResources: true, constraints: constraints,if (stats.inboundStream.audio) { mediaConnectionConstraints: mediaConnectionConstraints }).on(STREAM_STATUS.PUBLISHING, function (publishStream) { showStat(stats.inboundStream.audio, "inAudioStat"); $("#testBtn").prop('disabled', true); varlet videoaBitrate = document.getElementById(publishStream.id()); (stats.inboundStream.audio.bytesReceived - audioBytesReceived) * 8; //resize local if resolution is available if (video.videoWidth > 0 && video.videoHeight >if ($('#inAudioStatBitrate').length == 0) { resizeLocalVideo({target: video}); } let html = "<div style='font-weight: enableMuteToggles(true); if ($("#muteVideoToggle").is(":checked")) {bold'>Bitrate: " + "<span id='inAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>"; muteVideo(); } if ($("#muteAudioToggle#inAudioStat").is(":checked")) {append(html); muteAudio(); } else { //remove resize listener in case this video was cached earlier video.removeEventListener$('resize', resizeLocalVideo#inAudioStatBitrate').text(aBitrate); video.addEventListener('resize', resizeLocalVideo); setStatus(STREAM_STATUS.PUBLISHING); } //play preview var constraintsaudioBytesReceived = { stats.inboundStream.audio.bytesReceived; audio: $("#playAudio").is(':checked'), } video: $("#playVideo").is(':checked') ... }; }); |
20. Определение речи при помощи интерфейса ScriptProcessor (любой браузер, кроме Chrome)
audioContext.createMediaStreamSource(), audioContext.createScriptProcessor() code
Code Block | ||||
---|---|---|---|---|
| ||||
function detectSpeech(stream, if (constraints.videolevel, latency) { var mediaStream constraints.video = {= document.getElementById(stream.id()).srcObject; var width: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveWidth').val()) : 0,source = audioContext.createMediaStreamSource(mediaStream); var processor = audioContext.createScriptProcessor(512); processor.onaudioprocess = handleAudio; height: (!$("#receiveDefaultSize").is(":checked")) ? parseInt($('#receiveHeight').val()) : 0,processor.connect(audioContext.destination); processor.clipping = false; processor.lastClip = 0; // threshold processor.threshold = level || 0.10; bitrate: (!$("#receiveDefaultBitrate").is(":checked")) ? $("#receiveBitrate").val() : 0, processor.latency = latency || 750; processor.isSpeech = function () { quality:if (!$("#receiveDefaultQuality").is(":checked")) ? $('#quality').val() : 0 this.clipping) return false; if ((this.lastClip + this.latency) < window.performance.now()) this.clipping = }false; } previewStream = session.createStream({return this.clipping; }; name: streamName,source.connect(processor); // Check speech every display: remoteVideo,500 ms speechIntervalID = setInterval(function () { constraints: constraints if (processor.isSpeech()) { ... }$("#talking").css('background-color', 'green'); previewStream.play(); }).on(STREAM_STATUS.UNPUBLISHED, function () else { ... }$("#talking").on(STREAM_STATUS.FAILED, function () {css('background-color', 'red'); ...} }, 500); publishStream.publish(); |
9. Остановка воспроизведения превью-видеопотока.
...
} |
Обработка аудиоданных code
Code Block | ||||
---|---|---|---|---|
| ||||
function handleAudio(event) { var buf $("#publishBtn").text("Stop").off('click').click(function () {= event.inputBuffer.getChannelData(0); var bufLength = $(this).prop('disabled', true)buf.length; var x; previewStream.stop(); }).prop('disabled', false); |
10. Получение от сервера события, подтверждающего остановку воспроизведения
StreamStatusEvent STOPPED код
Code Block | ||||
---|---|---|---|---|
| ||||
for (var i = 0; i < bufLength; i++) { previewStreamx = session.createStream(buf[i]; if (Math.abs(x) >= this.threshold) { name: streamName,this.clipping = true; display: remoteVideo, this.lastClip = window.performance.now(); constraints: constraints} }).on(STREAM_STATUS.PLAYING, function (previewStream} } |
21. Определение речи по WebRTC статистике входящего аудио потока в браузере Chrome
stream.getStats() code
Code Block | ||||
---|---|---|---|---|
| ||||
function detectSpeechChrome(stream, level, latency) { statSpeechDetector.threshold = level || 0.010; statSpeechDetector... latency = latency || 750; }).on(STREAM_STATUS.STOPPED, function () {statSpeechDetector.clipping = false; statSpeechDetector.lastClip = 0; speechIntervalID publishStream.stop= setInterval(function(); { })stream.on(STREAM_STATUS.FAILED, function (getStats(function(stat) { let audioStats = stat.inboundStream..audio; }); previewStream.playif(); |
11. Остановка публикации видеопотока после остановки воспроизведения превью-потока.
publishStream.stop() код
Code Block | ||||
---|---|---|---|---|
| ||||
!audioStats) { previewStream = session.createStream({ return; name: streamName, } display: remoteVideo, // Using audioLevel WebRTC stats parameter constraints: constraints if }).on(STREAM_STATUS.PLAYING, function (previewStream(audioStats.audioLevel >= statSpeechDetector.threshold) { ... }).on(STREAM_STATUS.STOPPED, function () {statSpeechDetector.clipping = true; publishStream.stop(); statSpeechDetector.lastClip = }).on(STREAM_STATUS.FAILED, function () {window.performance.now(); ...} }); if ((statSpeechDetector.lastClip + previewStreamstatSpeechDetector.play(); |
12. Получение от сервера события, подтверждающего успешную остановку публикации
StreamStatusEvent UNPUBLISHED код
Code Block | ||||
---|---|---|---|---|
| ||||
publishStream = session.createStream({ name: streamName,latency) < window.performance.now()) { display: localVideo, statSpeechDetector.clipping cacheLocalResources: true,= false; constraints: constraints, } mediaConnectionConstraints: mediaConnectionConstraints if }).on(STREAM_STATUS.PUBLISHING, function (publishStream(statSpeechDetector.clipping) { ... }$("#talking").on(STREAM_STATUS.UNPUBLISHED, function () { css('background-color', 'green'); setStatus(STREAM_STATUS.UNPUBLISHED); } else { //enable start button onStopped($("#talking").css('background-color', 'red'); }).on(STREAM_STATUS.FAILED, function () { } ... }); publishStream.publish(},500); } |