Как рассчитать или устранить задержку между audio.play () и mediaRecorder.start ()? - PullRequest
0 голосов
/ 03 мая 2020

Я создаю веб-приложение для караоке, в котором я использую MediaRecorder для записи пользователя, поющего на минусовку, для последующего микширования. Резервная дорожка загружается и воспроизводится через наушники пользователя, поэтому ее нельзя услышать при записи видео. Когда я прихожу, чтобы потом смешать видео и минусовку, между ними возникает задержка (иногда положительная, иногда отрицательная).

Я попробовал следующее, чтобы уменьшить задержку между воспроизведением аудио и записью видео, чтобы 0ms:

  • Предварительная загрузка аудиофайла в HTML
  • Воспроизведение / приостановка / сброс аудиофайла после его загрузки до нажатия кнопки записи видео (не помогло )
  • Не запускается MediaRecorder до тех пор, пока прослушиватель событий на audio.play () не зарегистрирует, что он воспроизводится

Я также попытался вычислить смещение, чтобы можно было редактировать видео после факта:

  • Использование performance.now () для записи временного сдвига в мс. В среднем обрезка или заполнение видео на это количество миллисекунд делает его немного ближе к фоновой дорожке, но все еще не выровнено.

HTML:

<!DOCTYPE html>

<head>

</head>

<body>

<div id="container">

    <audio preload="auto" id="track">
         <source src="./tracks/happybirthday.mp3" type="audio/mpeg" name="happybirthday">
    </audio>

    <div class="row">

        <div class="col-md-5">

            <h6><b>Step 1:</b> Plug headphones into your computer, put them on, and start your camera</h6>
            <button class="btn btn-large btn-success" id="start">Start camera</button>

            <h6><b>Step 2:</b> Hit Start Recording and sing!</h6>
            <button class="btn btn-large btn-success" id="record">Start Recording</button>

            <h6><b>Step 3:</b> Stop recording when you're done</h6>
            <button class="btn btn-large btn-success" id="stop">Stop Recording</button>

            <h6><b>Step 4:</b> Listen back if you'd like to</h6>
            <button class="btn btn-large btn-success" id="play">Play Recording</button>

            <h6><b>Step 5:</b> Download your video</h6>
            <button class="btn btn-large btn-success" id="download">Download</button>

        </div>

        <div class="col-md-7 align-self-center text-center">

            <div class="row">
                <video id="gum" style="width: 720px;height: 360px;" playsinline="" autoplay="" muted=""></video>
            </div>

        </div>

    </div>

    <div style="visibility: hidden">
        <h4>Media Stream Constraints options</h4>
        <p>Echo cancellation: <input type="checkbox" id="echoCancellation"></p>
    </div>

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

</div>

<script src="stackoverflow.js" async=""></script>

</body>

</html>

JS:

var audio = document.querySelector('audio#track');

const mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);

let mediaRecorder;
let recordedBlobs;
let sourceBuffer;

const gumVideo = document.querySelector('video#gum');
const startButton = document.querySelector('button#start');
const recordButton = document.querySelector('button#record');
const stopButton = document.querySelector('button#stop');

//LOGGING VARIABLES
var logflag = false;
var listenerPlayTime;
var blobFirstAddedTime;
var offset;
var offsetname;

//START CAMERA
document.querySelector('button#start').addEventListener('click', async () => {

  const hasEchoCancellation = document.querySelector('#echoCancellation').checked;
  const constraints = {
    audio: {
      echoCancellation: false,
      //noiseSuppression: false,
      //autoGainControl: false,
      //latency: 0
    },
    video: {
      width: 1280, height: 720, facingMode: "user"
    }
  };

  await init(constraints);

  recordedBlobs = [];

  let options = {mimeType: 'video/webm;codecs=vp9'};
  if (!MediaRecorder.isTypeSupported(options.mimeType)) {
    _LTracker.push(`${options.mimeType} is not Supported`);
    console.error(`${options.mimeType} is not Supported`);
    errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
    options = {mimeType: 'video/webm;codecs=vp8'};
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      _LTracker.push(`${options.mimeType} is not Supported`);
      console.error(`${options.mimeType} is not Supported`);
      errorMsgElement.innerHTML = `${options.mimeType} is not Supported`;
      options = {mimeType: 'video/webm'};
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {
        _LTracker.push(`${options.mimeType} is not Supported`);
        console.error(`${options.mimeType} is not Supported`);
        errorMsgElement.innerHTML = `${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;
  }

  mediaRecorder.ondataavailable = handleDataAvailable;

});

function handleSourceOpen(event) {
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
}

function handleDataAvailable(event) {
  //console.log('handleDataAvailable', event);
  if (event.data && event.data.size > 0) {

    if(logflag == false){
      blobFirstAddedTime = performance.now();
    }

    recordedBlobs.push(event.data);

    logflag = true;
  }
}


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()}`;
  }
}

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

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

//CLICK START RECORDING
recordButton.addEventListener('click', () => {
  startRecording();
});

//START RECORDING
function startRecording() {
  audio.play();
}


//EVENT LISTENER ON AUDIO PLAYING STARTS MEDIARECORDER
audio.addEventListener("play", function () {
  if(mediaRecorder){
    listenerPlayTime = performance.now();
    console.log(listenerPlayTime)
    mediaRecorder.start(10); // collect 10ms of data
  }
}, false);

//CLICK STOP RECORDING
stopButton.addEventListener('click', () => {
  stopRecording();
});

//STOP RECORDING
function stopRecording() {
  mediaRecorder.stop();
  audio.pause();
  audio.currentTime = 0;
  calculateOffset();
}

//CALCULATE OFFSET
function calculateOffset(){
  var recorderStartTime = blobFirstAddedTime - 10;
  offset = recorderStartTime - listenerPlayTime;
  offsetname = offset.toFixed(0);  

  console.log("Event listener on audio.play started at:", listenerPlayTime)
  console.log("MediaRecorder started 10ms before blobFirstAddedTime:", recorderStartTime);
  console.log("Audio started", offsetname, "ms before video" );
}

//PLAY RECORDING
const playButton = document.querySelector('button#play');
playButton.addEventListener('click', () => {
  const superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  gumVideo.src = null;
  gumVideo.srcObject = null;
  gumVideo.src = window.URL.createObjectURL(superBuffer);
  gumVideo.controls = true;
  gumVideo.muted = false;
  gumVideo.play();
  gumVideo.loop = false;
});

//DOWNLOAD
const downloadButton = document.querySelector('button#download');
downloadButton.addEventListener('click', () => {
  const blob = new Blob(recordedBlobs, {type: 'video/webm'});
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');

  const filename = offsetname.concat('.webm');
  a.style.display = 'none';
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  setTimeout(() => {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
});

Что я могу сделать, чтобы удалить все задержки или узнать точную задержку записанного видео?

...