Я создаю веб-приложение для караоке, в котором я использую 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);
});
Что я могу сделать, чтобы удалить все задержки или узнать точную задержку записанного видео?