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-сервере по следующему пути:

...

Здесь host - адрес WCS-сервера.

Работа с кодом примера

Для разбора кода возьмем версию файла manager.js с хэшем 66cc393 ecbadc3, которая находится здесь и доступна для скачивания в соответствующей сборке 2.0.5.28.2753.133.212.

1. Инициализация API.

Flashphoner.init() code

Code Block
languagejs
themeRDark
        Flashphoner.init({
            screenSharingExtensionId: extensionId,
            flashMediaProviderSwfLocation: '../../../../media-provider.swf',mediaProvidersReadyCallback: function (mediaProviders) {
            mediaProvidersReadyCallback: function (mediaProviders    if (Flashphoner.isUsingTemasys()) {
                //hide remote video if current media provider is Flash
                if (mediaProviders[0] == "Flash") { $("#audioInputForm").hide();
                    $("#fecForm#videoInputForm").hide();
                    $("#stereoForm").hide();}
            }
          $("#sendAudioBitrateForm").hide();
         })

2. Получение списка доступных медиа-устройств ввода

Flashphoner.getMediaDevices() code

При получении списка медиа-устройств заполняются выпадающие списки микрофонов и камер на странице клиента.

Code Block
languagejs
themeRDark
    Flashphoner.getMediaDevices(null, true).then(function (list) {
             $("#cpuOveruseDetectionForm").hide();list.audio.forEach(function (device) {
                }...
        });
        iflist.video.forEach(function (Flashphoner.isUsingTemasys(device)) {
            ...
        $("#audioInputForm").hide(});
        ...
    }).catch(function (error) {
        $("#videoInputForm#notifyFlash").hide();
    text("Failed to get media devices");
            }
            }
        })

...

});

3. Получение списка доступных медиа-устройств вводавывода звука

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  ...media devices");
     });

4. Получение граничных параметров для публикации аудио и видео со страницы клиента

getConstraints() code

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

  • камера (sendVideo)
  • микрофон (sendAudio)
Code Block
languagejs
themeRDark
   });
 constraints = {
     ...
    }audio: $("#sendAudio").catch(function (error) {
is(':checked'),
        video: $("#notifyFlash#sendVideo").text("Failed to get media devices");is(':checked'),
    });

3. Получение списка доступных медиа-устройств вывода звука

Flashphoner.getMediaDevices() code

...

;

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

  • выбор микрофона (deviceId)
  • коррекция ошибок для кодека Opus (fec)
  • режим стерео (stereo)
  • битрейт аудио (bitrate)
Code Block
languagejs
themeRDark
    if Flashphoner.getMediaDevices(null, true, MEDIA_DEVICE_KIND.OUTPUT).then(function (list(constraints.audio) {
        listconstraints.audio.forEach(function (device) = {
            ...deviceId: $('#audioInput').val()
        });
        ...if ($("#fec").is(':checked'))
    }).catch(function (error) {
      constraints.audio.fec = $("#notifyFlash#fec").text("Failed to get media devices");
is(':checked');
         });

4. Получение граничных параметров для публикации аудио и видео со страницы клиента

getConstraints() code

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

  • камера (sendVideo)
  • микрофон (sendAudio)
Code Block
languagejs
themeRDark
if ($("#sendStereoAudio").is(':checked'))
    constraints = {
      constraints.audio.stereo = audio: $("#sendAudio#sendStereoAudio").is(':checked'),;
        video:if (parseInt($("#sendVideo").is(':checked'),('#sendAudioBitrate').val()) > 0)
            constraints.audio.bitrate = parseInt($('#sendAudioBitrate').val());
    };

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

  • выбор микрофона камеры (deviceId)
  • коррекция ошибок для кодека Opus (fec)
  • режим стерео (stereo)
  • битрейт аудио (bitrateразмеры при публикации (width, height)
  • минимальный и максимальный битрейт видео (minBitrate, maxBitrate)
  • FPS (frameRate)
Code Block
languagejs
themeRDark
    if (constraints.audio) {
        constraints.audiovideo = {
                deviceId: {exact: $('#audioInput#videoInput').val()},
        };
        ifwidth: parseInt($("#fec"'#sendWidth').isval(':checked')),
               constraints.audio.fec = $("#fec").is(':checked'); height: parseInt($('#sendHeight').val())
        if ($("#sendStereoAudio").is(':checked'))    };
            constraints.audio.stereo = $("#sendStereoAudio").is(':checked');
        if (parseInt($('#sendAudioBitrate').val()) > 0)if (Browser.isSafariWebRTC() && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") {
                constraints.audiovideo.bitratedeviceId = parseInt({exact: $('#sendAudioBitrate#videoInput').val());
    }

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

  • выбор камеры (deviceId)
  • размеры при публикации (width, height)
  • минимальный и максимальный битрейт видео (minBitrate, maxBitrate)
  • FPS (frameRate)
Code Block
languagejs
themeRDark
};
            constraints.video = {}
                deviceId: {exact: if (parseInt($('#videoInput#sendVideoMinBitrate').val()},) > 0)
                width: constraints.video.minBitrate = parseInt($('#sendWidth#sendVideoMinBitrate').val()),;
                height: if (parseInt($('#sendHeight#sendVideoMaxBitrate').val()) > 0)
            }    constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val());
            if (Browser.isSafariWebRTCparseInt($('#fps').val()) && Browser.isiOS() && Flashphoner.getMediaProviders()[0] === "WebRTC") {> 0)
                constraints.video.deviceIdframeRate = {exact: parseInt($('#videoInput#fps').val()};
            }
            if (parseInt($('#sendVideoMinBitrate').val()) > 0)
        );


5. Получение доступа к медиаустройствам для локального тестирования

Flashphoner.getMediaAccess() code

В метод передаются граничные параметры для аудио и видео (constrains), а также localVideo - div-элемент, в котором будет отображаться видео с выбранной камеры.

Code Block
languagejs
themeRDark
    Flashphoner.getMediaAccess(getConstraints(), localVideo).then(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 () {
        constraints..video.minBitrate = parseInt($('#sendVideoMinBitrate').val());
.
    });
    publishStream.publish();

10. Воспроизведение потока

session.createStream(), previewStream.play() code

Code Block
languagejs
themeRDark
    previewStream = session.createStream({
      if (parseInt($('#sendVideoMaxBitrate').val()) > 0) name: streamName,
        display: remoteVideo,
        constraints.video.maxBitrate = parseInt($('#sendVideoMaxBitrate').val());
    : constraints,
        if (parseInt($('#fps').val()) > 0) transport: transportOutput,
        stripCodecs: strippedCodecs
       constraints ..video.frameRate
   = parseInt($('#fps').val());

...

Flashphoner.getMediaAccess() code

...

 });
    previewStream.play();

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

StreamStatusEvent PLAYING code

Code Block
languagejs
themeRDark
    Flashphoner.getMediaAccess(getConstraints(), localVideo).then(function (disppreviewStream = session.createStream({
        ...
    }).on(STREAM_STATUS.PLAYING, function (stream) {
        $("#testBtn").text("Release").off('click').click(function () {playConnectionQualityStat.connectionQualityUpdateTimestamp = new Date().valueOf();
            $(this).prop('disabled', truesetStatus("#playStatus", stream.status());
            stopTestonPlaying(stream);
        }document.getElementById(stream.id()).propaddEventListener('disabledresize', false);
function (event) {
            $("#playResolution").text(event..
  target.videoWidth + "x" + event.target.videoHeight);
      testStarted = true;
    }).catch(function (error) {resizeVideo(event.target);
        $("#testBtn").prop('disabled', false)});
        testStarted = false;//wait for incoming stream
    });

6. Подключение к серверу.

Flashphoner.createSession() code

Code Block
languagejs
themeRDark
    Flashphoner.createSession({urlServer: url, timeout: tm}).on(SESSION_STATUS.ESTABLISHED, function (session) {
    if (Flashphoner.getMediaProviders()[0] == "WebRTC") {
            setTimeout(function () {
              setStatus("#connectStatus", session.status  if(Browser.isChrome()); {
        onConnected(session);
    }).on(SESSION_STATUS.DISCONNECTED, function () {
        setStatus("#connectStatus", SESSION_STATUS.DISCONNECTEDdetectSpeechChrome(stream);
         onDisconnected();
       }).on(SESSION_STATUS.FAILED, function () {
 else {
           setStatus("#connectStatus", SESSION_STATUS.FAILED);
        onDisconnecteddetectSpeech(stream);
    });

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

ConnectionStatusEvent ESTABLISHED code

Code Block
languagejs
themeRDark
          Flashphoner.createSession({urlServer: url, timeout: tm}).on(SESSION_STATUS.ESTABLISHED, function (session) {
}
            setStatus("#connectStatus"}, session.status(3000));
        onConnected(session);}
           ...
    });

8. Публикация видеопотока

...


    previewStream.play();

12. Остановка воспроизведения потока.

stream.stop() code

Code Block
languagejs
themeRDark
    publishStream = session.createStream({
        name: streamName,$("#playBtn").text("Stop").off('click').click(function () {
        display: localVideo,
        cacheLocalResources: true,$(this).prop('disabled', true);
        constraints: constraints,
   stream.stop();
    }).prop('disabled', false);

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

StreamStatusEvent STOPPED code

Code Block
languagejs
themeRDark
    previewStream mediaConnectionConstraints: mediaConnectionConstraints,= session.createStream({
        sdpHook: rewriteSdp,...
        transport: transportInput,}).on(STREAM_STATUS.STOPPED, function () {
        cvoExtension: cvo,setStatus("#playStatus", STREAM_STATUS.STOPPED);
        stripCodecs: strippedCodecsonStopped();
        ...
    });
    publishStreampreviewStream.publishplay();

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

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

stream.stop() code

Code Block
languagejs
themeRDark
    publishStream = session.createStream({
        ...
    }$("#publishBtn").text("Stop").on(STREAM_STATUS.PUBLISHING, off('click').click(function (stream) {
        $("#testBtn"this).prop('disabled', true);
        var video = document.getElementById(stream.idstop());
    }).prop('disabled', false);

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

StreamStatusEvent UNPUBLISHED code

Code Block
languagejs
themeRDark
   //resize localpublishStream if resolution is available= session.createStream({
        if (video.videoWidth > 0 && video.videoHeight > 0...
    }).on(STREAM_STATUS.UNPUBLISHED, function () {
            resizeLocalVideo({target: video}setStatus("#publishStatus", STREAM_STATUS.UNPUBLISHED);
        }onUnpublished();
        enablePublishToggles(true);...
     });
   if ($("#muteVideoToggle").is(":checked")) {
    publishStream.publish();

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

stream.muteAudio() code:

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

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

stream.muteVideo() code:

Code Block
languagejs
themeRDark
function muteVideo() {
      if ($("#muteAudioToggle").is(":checked"))publishStream) {
            muteAudiopublishStream.muteVideo();
        }
}
}

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

stream.getStats() code:

Code Block
languagejs
themeRDark
        //remove resize listener in case this video was cached earlier
publishStream.getStats(function (stats) {
            if video.removeEventListener('resize', resizeLocalVideo);(stats && stats.outboundStream) {
        video.addEventListener('resize', resizeLocalVideo);
       if publishStream.setMicrophoneGain(currentGainValue);(stats.outboundStream.video) {
        setStatus("#publishStatus", STREAM_STATUS.PUBLISHING);
        onPublishing(stream);
    }).on(STREAM_STATUS.UNPUBLISHED, function () {showStat(stats.outboundStream.video, "outVideoStat");
        ...
    }).on(STREAM_STATUS.FAILED, function () {
     let vBitrate = (stats.outboundStream.video.
bytesSent - videoBytesSent) * })8;
      publishStream.publish();

10. Воспроизведение потока

session.createStream(), previewStream.play() code

Code Block
languagejs
themeRDark
    previewStream = session.createStream(              if ($('#outVideoStatBitrate').length == 0) {
        name:  streamName,
        display: remoteVideo,
     let html = constraints"<div>Bitrate: constraints,
        transport: transportOutput, " + "<span id='outVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>";
        stripCodecs: strippedCodecs
        ...
    });
    previewStream.play();

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

StreamStatusEvent PLAYING code

Code Block
languagejs
themeRDark
    previewStream = session.createStream({
$("#outVideoStat").append(html);
           ...
    }).on(STREAM_STATUS.PLAYING, function (stream) {
  } else {
    playConnectionQualityStat.connectionQualityUpdateTimestamp = new Date().valueOf();
        setStatus("#playStatus", stream.status());
        onPlaying(stream$('#outVideoStatBitrate').text(vBitrate);
        document.getElementById(stream.id()).addEventListener('resize', function (event) {
            $("#playResolution").text(event.target.videoWidth + "x" + event.target.videoHeight);
}
                    videoBytesSent  resizeVideo(event.target)= stats.outboundStream.video.bytesSent;
        });
         //wait for incoming stream...
        if (Flashphoner.getMediaProviders()[0] == "WebRTC") {
        }

         setTimeout(function () {
     if (stats.outboundStream.audio) {
         detectSpeech(stream);
            }showStat(stats.outboundStream.audio, 3000"outAudioStat");
        }
        ...
    });
let aBitrate =  previewStream.play();

12. Остановка воспроизведения потока.

stream.stop() code

Code Block
languagejs
themeRDark
    $("#playBtn").text("Stop").off('click').click(function () {(stats.outboundStream.audio.bytesSent - audioBytesSent) * 8;
        $(this).prop('disabled', true);
        stream.stop();
    }).propif ($('disabled#outAudioStatBitrate', false);

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

StreamStatusEvent STOPPED code

Code Block
languagejs
themeRDark
 .length == 0) {
   previewStream = session.createStream({
        ...
    }).on(STREAM_STATUS.STOPPED, function () {
    let html = "<div>Bitrate: setStatus("#playStatus + ", STREAM_STATUS.STOPPED);
        onStopped()<span id='outAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>";
        ...
    });
    previewStream.play();

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

stream.stop() code

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

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

StreamStatusEvent UNPUBLISHED code

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

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

code:

Code Block
languagejs
themeRDark
                audioBytesSent if ($("#muteAudioToggle").is(":checked")) {= stats.outboundStream.audio.bytesSent;
            muteAudio();
    }
    }

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

code:

Code Block
languagejs
themeRDark
        if ($("#muteVideoToggle").is(":checked")) { }
            muteVideo();...
        });

1819. Отображение статистики при публикации потокавоспроизведении потока

stream.getStats() code:

Code Block
languagejs
themeRDark
        publishStreampreviewStream.getStats(function (stats) {
            if (stats && stats.outboundStreaminboundStream) {
                if (stats.outboundStreaminboundStream.video) {
                    showStat(stats.outboundStreaminboundStream.video, "outVideoStatinVideoStat");
                    let vBitrate = (stats.outboundStreaminboundStream.video.bytesSentbytesReceived - videoBytesSentvideoBytesReceived) * 8;
                    if ($('#outVideoStatBitrate#inVideoStatBitrate').length == 0) {
                        let html = "<div>Bitrate: " + "<span id='outVideoStatBitrateinVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>";
                        $("#outVideoStat#inVideoStat").append(html);
                    } else {
                        $('#outVideoStatBitrate#inVideoStatBitrate').text(vBitrate);
                    }
                    videoBytesSentvideoBytesReceived = stats.outboundStreaminboundStream.video.bytesSentbytesReceived;
                    ...
                }

                if (stats.outboundStreaminboundStream.audio) {
                    showStat(stats.outboundStreaminboundStream.audio, "outAudioStatinAudioStat");
                    let aBitrate = (stats.outboundStreaminboundStream.audio.bytesSentbytesReceived - audioBytesSentaudioBytesReceived) * 8;
                    if ($('#outAudioStatBitrate#inAudioStatBitrate').length == 0) {
                        let html = "<div>Bitrate<div style='font-weight: bold'>Bitrate: " + "<span id='outAudioStatBitrateinAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>";
                        $("#outAudioStat#inAudioStat").append(html);
                    } else {
                        $('#outAudioStatBitrate#inAudioStatBitrate').text(aBitrate);
;
                    }
                    audioBytesReceived = stats.inboundStream.audio.bytesReceived;
         }
       }
              audioBytesSent = stats.outboundStream.audio.bytesSent;
            }
    }
       });

20. Определение речи при помощи интерфейса ScriptProcessor (любой браузер, кроме Chrome)

audioContext.createMediaStreamSource(), audioContext.createScriptProcessor() code


Code Block
languagejs
themeRDark
function detectSpeech(stream, level, latency) {
    var mediaStream = document.getElementById(stream.id()).srcObject;
  }
  var source = audioContext.createMediaStreamSource(mediaStream);
    var processor = ...audioContext.createScriptProcessor(512);
    processor.onaudioprocess = handleAudio;
  });

19. Отображение статистики при воспроизведении потока

stream.getStats() code:

Code Block
languagejs
themeRDark
  processor.connect(audioContext.destination);
    processor.clipping = false;
  previewStream.getStats(function (stats) {  processor.lastClip = 0;
    // threshold
    processor.threshold =  if (stats && stats.inboundStream) {
   level || 0.10;
    processor.latency = latency || 750;

    processor.isSpeech =
        iffunction (stats.inboundStream.video) {
) {
            if (!this.clipping) return false;
            if showStat((stats.inboundStream.video, "inVideoStat")this.lastClip + this.latency) < window.performance.now()) this.clipping = false;
            return this.clipping;
        };

 let vBitrate = (stats.inboundStream.video.bytesReceived - videoBytesReceived) * 8;
    source.connect(processor);

    // Check speech every 500 ms
    speechIntervalID = setInterval(function () {
        if ($('#inVideoStatBitrate').length == 0processor.isSpeech()) {
            $("#talking").css('background-color', 'green');
        }   let html = "<div>Bitrate: " + "<span id='inVideoStatBitrate' style='font-weight: normal'>" + vBitrate + "</span>" + "</div>"else {
            $("#talking").css('background-color', 'red');
        }
    }, 500);
}

Обработка аудиоданных code

Code Block
languagejs
themeRDark
function handleAudio(event) {
    var buf    $("#inVideoStat").append(html= event.inputBuffer.getChannelData(0);
     var bufLength = buf.length;
    var x;
    for (var i }= else0; {
i < bufLength; i++) {
        x = buf[i];
          $('#inVideoStatBitrate').text(vBitrate);if (Math.abs(x) >= this.threshold) {
            this.clipping = true;
      }
      this.lastClip = window.performance.now();
        }
     videoBytesReceived = stats.inboundStream.video.bytesReceived;
    }
}

21. Определение речи по WebRTC статистике входящего аудио потока в браузере Chrome

stream.getStats() code

Code Block
languagejs
themeRDark
function detectSpeechChrome(stream, level, latency) {
    statSpeechDetector.threshold = level || 0.010;
        ...
    statSpeechDetector.latency = latency || 750;
    statSpeechDetector.clipping = false;
    statSpeechDetector.lastClip = }0;

    speechIntervalID = setInterval(function() {
         if (stats.inboundStream.audiostream.getStats(function(stat) {
            let audioStats       showStat(stats= stat.inboundStream.audio, "inAudioStat");
                    let aBitrate = (stats.inboundStream.audio.bytesReceived - audioBytesReceived) * 8;
if(!audioStats) {
                return;
        if ($('#inAudioStatBitrate').length == 0) {}
            // Using audioLevel WebRTC stats parameter
          let html = "<div style='font-weight: bold'>Bitrate: " + "<span id='inAudioStatBitrate' style='font-weight: normal'>" + aBitrate + "</span>" + "</div>";
      if (audioStats.audioLevel >= statSpeechDetector.threshold) {
                statSpeechDetector.clipping = true;
                statSpeechDetector.lastClip  $("#inAudioStat").append(html= window.performance.now();
            }
        } else {
  if ((statSpeechDetector.lastClip + statSpeechDetector.latency) < window.performance.now()) {
                $('#inAudioStatBitrate').text(aBitrate)statSpeechDetector.clipping = false;
            }
        }
    if (statSpeechDetector.clipping) {
              audioBytesReceived = stats.inboundStream.audio.bytesReceived $("#talking").css('background-color', 'green');
            } else   }{
                ...$("#talking").css('background-color', 'red');
            }
        });
    },500);
}