Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

Пример стримера c доступом к медиа-устройствам

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

  • WebRTC
  • RTMFP
  • RTMP

и позволяет выбрать медиа-устройства и параметры для публикуемого видео

...

На скриншоте ниже представлен пример во время публикации потока.

Image RemovedImage Added


На странице вопроизводятся отображаются два видео элемента:

  • '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.

Flashphoner.init() код code

Code Block
languagejs
themeRDark
        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
languagejs
themeRDark
    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
languagejs
themeRDark
    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. Получение граничных параметров для публикации аудио и видео со страницы клиента

getConstraints() код code

Источники публикации:

  • камера (sendVideo)
  • микрофон (sendAudio)
  • HTML5 Canvas (sendCanvasStream)
Code Blockcode
languagejs
themeRDark
    constraints = {
        audio: $("#sendAudio").is(':checked'),
        video: $("#sendVideo").is(':checked'),
        customStream: $("#sendCanvasStream").is(':checked')
    };

Параметры аудио:

  • выбор микрофона (deviceId)
  • коррекция ошибок для кодека Opus (fec)
  • режим стерео (stereo)
  • битрейт аудио (bitrate)

...

Code Block
languagejs
themeRDark
            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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
    previewStream = session.createStream({
        name: streamName,
        display: remoteVideo,
        constraints: constraints,
        transport: transportOutput,
        stripCodecs: strippedCodecs
        ...
    });
    previewStream.play();

11. Получение от сервера события, подтверждающего успешное воспроизведение потока

StreamStatusEvent PLAYING code

Code Block
languagejs
themeRDark
    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
languagejs
themeRDark
    $("#playBtn").text("Stop").off('click').click(function () {
        $(this).prop('disabled', true);
        stream.stop();
    }).prop('disabled', false);

13. Получение от сервера события, подтверждающего остановку воспроизведения

StreamStatusEvent STOPPED code

Code Block
languagejs
themeRDark
    previewStream = session.createStream({
        ...
    }).on(STREAM_STATUS.STOPPED, function () {
        setStatus("#playStatus", STREAM_STATUS.STOPPED);
        onStopped();
        ...
    });
    previewStream.play();

14. Остановка публикации видеопотока

stream.stop() code

Code Block
languagejs
themeRDark
    $("#publishBtn").text("Stop").off('click').click(function () {
        $(this).prop('disabled', true);
        stream.stop();
    }).prop('disabled', false);

15. Получение от сервера события, подтверждающего успешную остановку публикации

StreamStatusEvent UNPUBLISHED code

Code Block
languagejs
themeRDark
    publishStream = session.createStream({
        ...
    }).on(STREAM_STATUS.UNPUBLISHED, function () {
        setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
        onUnpublished();
        ...
    });
    publishStream.publish();

16. Отключение микрофона

stream.muteAudio() code:

Code Block
languagejs
themeRDark
function muteAudio() {
    if (publishStream) {
        publishStream.muteAudio();
    }
}

17. Отключение камеры

stream.muteVideo() code:

Code Block
languagejs
themeRDark
function muteVideo() {
    if (publishStream) {
        publishStream.muteVideo();
    }
}

18. Отображение статистики при публикации потока

stream.getStats() code:

Code Block
languagejs
themeRDark
        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
languagejs
themeRDark
           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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
    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
languagejs
themeRDark
       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
languagejs
themeRDark
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
languagejs
themeRDark
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
languagejs
themeRDark
 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
languagejs
themeRDark
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
languagejs
themeRDark
!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
languagejs
themeRDark
    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);
}