WebRTC 音频/视频不同步 - 音频延迟

问题描述 投票:0回答:1

背景: 我正在开发一个 Django 项目,使用 webRTC 进行视频/音频录制以进行用户注册。 我使用这个存储库演示)作为我的代码的基础,然后添加了我的功能。

这是我所有更改的工作流程:

  1. 用户单击按钮开始直播。
  2. 10 秒后流停止。录制时,用户可以在还剩 5 秒时看到倒计时器。
  3. 注册视频后,我将录像机 (#gum) 更改为录制的录像机 (#recorded),以便用户可以播放、暂停等。
  4. 如果用户不满意,可以点击开始录制按钮,现在有文字内容“录制新视频”。
  5. 如果用户满意并填写了表单的其余部分,则在提交表单时,我使用注册视频输入中的 blob 来保存文件。

问题: 使用

Firefox 70.0.1 (64 bits)
:在第 3 步中视频/音频同步完美。 使用
Chrome 84.0.4147.105 (64 bits)
:在步骤 3 中音频同步延迟约 3 秒。

要求: 我做错什么了吗? 请问有什么窍门可以解决吗?

我的代码如下:

HTML 代码:

    <div class="video-div">
        <h2>Etape 1</h2>
        <div class="video-content">
            <video id="gum" class="video-show" poster="{% static 'core/images/video-show-poster.png' %}"  playsinline autoplay muted></video>
            <video id="recorded" class="video-hide" playsinline></video>
               <p class="timer"></p>
        </div>
        <button class="btn start-recording" id="record">Enregistrer ma vidéo</button>
    </div>


    <div class="echo-cancellation">
        <h4>Media Stream Constraints options</h4>
        <p>Echo cancellation: <input type="checkbox" id="echoCancellation"></p>
    </div> 

    <div>
        <span id="errorMsg"></span>
    </div>

    <form id="registration-form" action="" method="POST"  enctype="multipart/form-data" novalidate>
        {% csrf_token %}
        {% for field in form_registration %}
            {% if field.name != "registration_video" %}
                <div class="form-group">
                    <div> {{ field.label_tag }} {{ field }}</div> 
                </div>
            {% endif %}
        {% endfor %}
        {% for field in form_location %}
            <div class="form-group">
                <div> {{ field.label_tag }} {{ field }}</div> 
            </div>
        {% endfor %}

        <input type="submit" value="Inscription" class="register-submit-btn btn">
    </form>

JS代码:


    <!-- Script to register user media. Adaptated to current app thanks to this resource: https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/record -->
    <script>
        
        let mediaRecorder;
        let recordedBlobs;

        const errorMsgElement = document.querySelector('span#errorMsg');
        const recordedVideo = document.querySelector('video#recorded');

        const recordButton = document.querySelector('button#record');
        recordButton.addEventListener('click', async () => {
              
            const hasEchoCancellation = document.querySelector('#echoCancellation').checked;
            const constraints = {
                audio: {
                    echoCancellation: {exact: hasEchoCancellation}
                },
                video: {
                    width: 290, height: 240
                }
            };

            console.log('Using media constraints:', constraints);
            await init(constraints);

            //Hiding the recorded video if user record a new one, show the gum video (livecam) instead.
            if(document.getElementById('gum').hasAttribute('class', 'video-hide')){

                document.getElementById('gum').classList.remove('video-hide');
                document.getElementById('gum').classList.add('video-show');
                document.getElementById('recorded').classList.remove('video-show');
                document.getElementById('recorded').classList.add('video-hide');
            }

                startRecording();
        });

        //Make all necessary action for the video recording
        function startRecording() 
        {
            recordedBlobs = [];

            let options = {mimeType: 'video/webm;codecs=vp9,opus'};

            if (!MediaRecorder.isTypeSupported(options.mimeType)) {

                //console.error(`${options.mimeType} is not supported`);
                options = {mimeType: 'video/webm;codecs=vp8,opus'};

                if (!MediaRecorder.isTypeSupported(options.mimeType)) {

                    //console.error(`${options.mimeType} is not supported`);
                    options = {mimeType: 'video/webm'};
                    
                    if (!MediaRecorder.isTypeSupported(options.mimeType)) {

                        //console.error(`${options.mimeType} is not supported`);
                        options = {mimeType: ''};
                    }
                }
            }

            try {
                mediaRecorder = new MediaRecorder(window.stream, options);

            } catch (e) {
                console.error('Exception while creating MediaRecorder:', e);
                errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`;
                return;
            }

            console.log('Created MediaRecorder', mediaRecorder, 'with options', options);

            recordButton.disabled = true;
            
            // When the recording is done we're allowing the user to register a new video
            // We're using the recordedBlob as a source to play the recordedVideo automatically
            mediaRecorder.onstop = (event) => {
                console.log('Recorder stopped: ', event);
                console.log('Recorded Blobs: ', recordedBlobs);

                document.getElementById('record').textContent = 'Nouvelle tentative';

                const superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
                recordedVideo.src = null;
                recordedVideo.srcObject = null;
                recordedVideo.src = window.URL.createObjectURL(superBuffer);
                recordedVideo.controls = true;
            };


            mediaRecorder.ondataavailable = handleDataAvailable;

            mediaRecorder.start();
            console.log('MediaRecorder started', mediaRecorder);


            //The video should last less that 10 seconds. 
            //We put a timeout so the video will last less than 10sec. And an interval to display a countdown 5 secondes before the end.
            let timerId = setInterval(countDown, 1000);

            let i = 10;

            function countDown()
            {   
                if(i <= 5 && i > 0 ) {

                    document.querySelector('.timer').style.display = 'block';
                    document.querySelector('.timer').textContent = i + ' secondes';

                } else if (i < 1){
                    clearInterval(timerId);
                    document.querySelector('.timer').textContent = '';
                    document.querySelector('.timer').style.display = 'none';
                }

                i--;
            }

            //When the 10 seconds are done, we're showing the user the recorded video.
            setTimeout(function()
            { 
                stopRecording();
                recordButton.disabled = false;


                document.getElementById('gum').classList.remove('video-show');
                document.getElementById('gum').classList.add('video-hide');
                document.getElementById('recorded').classList.remove('video-hide');
                document.getElementById('recorded').classList.add('video-show');

                stream.getTracks().forEach( 
                    function(track) 
                    {
                        track.stop();
                    }
                );



            }, 10000);
        }

        // If there is data, we push it to the recordedBlob array
        function handleDataAvailable(event)
        {
            console.log('handleDataAvailable', event);
            if (event.data && event.data.size > 0) {
                recordedBlobs.push(event.data);
            }

        }


        // We're listening to the click on submit button in order add the recordedBlob to the registration_video input thanks to the FormData class.
        // Then we're sending the request after creating it with the XMLHttpRequest class.
        const form = document.querySelector('form');
        
        form.addEventListener('submit', (evt) => {
            
            let myForm = document.querySelector('form');
            let formData = new FormData(myForm);

            if(recordedBlobs){
                formData.append('registration_video', recordedBlobs[0], encodeURIComponent("nomaleatoire.webm")); 
            } else {
                formData.append('registration_video', '');
            }
            

            //Handling form errors
            evt.preventDefault();
            const request = new XMLHttpRequest();

            request.open("POST", "/register", true);


            request.onload = function() { 
            response = JSON.parse(this.response);
            if ( response.success == true) {
                evt.target.querySelectorAll('input[type="text"], input[type="date"], input[type="number"], input[type="email"], textarea').forEach(
                    function(inputElem) { 
                        inputElem.value = '';
                        });
                if(document.querySelectorAll('.error-div')){
                    document.querySelectorAll('.error-div').forEach(
                        function(errorDiv)
                        {
                            errorDiv.remove();
                        }
                    ) 
                }

                window.location.href = "/";

                
                
            } else {

                errors = Object.assign(JSON.parse(response.errors_location), JSON.parse(response.errors_registration));


                if(document.querySelectorAll('.error-div'))
                {
                    document.querySelectorAll('.error-div').forEach(
                        function(errorDiv)
                        {
                            errorDiv.remove();
                        }
                    ) 
                }

                for (const [key, value] of Object.entries(errors)) {
                    if(key != 'registration_video') {
                        div = document.createElement("div")
                        div.setAttribute('id', 'error-div-' + key);
                        div.classList.add("error-div")
                        errorMessage = "";
                        value.forEach(errorfield => {
                            errorMessage = errorMessage +  " " + errorfield.message
                        });
                        div.textContent = errorMessage
                        inputElem = document.querySelector('#id_' + key)
                        theform = inputElem.parentElement.parentElement.parentElement;
                        theform.insertBefore(div,inputElem.parentElement.parentElement)
                    } else {
                        //Il faut afficher une erreur pour la vidéo manquante!
                        errorVideoMessage = document.createElement('div');
                        errorVideoMessage.textContent = "L'enregistrement de la vidéo est obligatoire.";
                        errorVideoMessage.classList.add('error-div', 'mt-2');

                        document.querySelector('.start-recording').before(errorVideoMessage);

                    }


                }   

            }

        };

        request.onerror = function() {
            // request failed
        };


        request.send(formData);
        
        });


        function stopRecording() 
        { 
            mediaRecorder.stop();
        }

        function handleSuccess(stream) 
        {
            recordButton.disabled = false;
            console.log('getUserMedia() got stream:', stream);
            window.stream = stream;

            const gumVideo = document.querySelector('video#gum');
            gumVideo.srcObject = stream;

        }

        async function init(constraints)
        {
            try {
                const stream = await navigator.mediaDevices.getUserMedia(constraints);
                handleSuccess(stream);
            } catch (e) {
                console.error('navigator.getUserMedia error:', e);
                errorMsgElement.innerHTML = `navigator.getUserMedia error:${e.toString()}`;
            }
        }

    </script>

google-chrome webrtc
1个回答
0
投票

有趣的是,臭名昭著的徽章和 318k plus 无法在 2024 年回答这个问题。

© www.soinside.com 2019 - 2024. All rights reserved.