Как добавить заранее заданную длину к аудио, записанному из MediaRecorder в Chrome?
Я нахожусь в процессе замены RecordRTC на встроенный MediaRecorder для записи звука в Chrome. Записанный звук затем воспроизводится в программе с аудио API. У меня проблемы с получением свойства audio.duration. Это говорит
Если видео (аудио) передается в потоковом режиме и не имеет заданной длины, возвращается "Inf" (бесконечность).
С RecordRTC мне пришлось использовать ffmpeg_asm.js для преобразования аудио из wav в ogg. Я предполагаю, что где-то в процессе RecordRTC устанавливает предопределенную длину звука. Есть ли способ установить предопределенную длину с помощью MediaRecorder?
7 ответов
Это ошибка Chrome.
FF показывает продолжительность записанного носителя, и если вы установите currentTime
записанных носителей более чем фактическим duration
, то свойство доступно в хром...
var recorder,
chunks = [],
ctx = new AudioContext(),
aud = document.getElementById('aud');
function exportAudio() {
var blob = new Blob(chunks, {
type: 'audio/ogg'
});
aud.src = URL.createObjectURL(new Blob(chunks));
aud.onloadedmetadata = function() {
// it should already be available here
log.textContent = ' duration: ' + aud.duration;
// handle chrome's bug
if (aud.duration === Infinity) {
// set it to bigger than the actual duration
aud.currentTime = 1e101;
aud.ontimeupdate = function() {
this.ontimeupdate = () => {
return;
}
log.textContent += ' after workaround: ' + aud.duration;
aud.currentTime = 0;
}
}
}
}
function getData() {
var request = new XMLHttpRequest();
request.open('GET', 'https://upload.wikimedia.org/wikipedia/commons/4/4b/011229beowulf_grendel.ogg', true);
request.responseType = 'arraybuffer';
request.onload = decodeAudio;
request.send();
}
function decodeAudio(evt) {
var audioData = this.response;
ctx.decodeAudioData(audioData, startRecording);
}
function startRecording(buffer) {
var source = ctx.createBufferSource();
source.buffer = buffer;
var dest = ctx.createMediaStreamDestination();
source.connect(dest);
recorder = new MediaRecorder(dest.stream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportAudio;
source.start(0);
recorder.start();
log.innerHTML = 'recording...'
// record only 5 seconds
setTimeout(function() {
recorder.stop();
}, 5000);
}
function saveChunks(evt) {
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
getData();
<audio id="aud" controls></audio><span id="log"></span>
Так что совет здесь будет заключаться в том, чтобы пометить отчет об ошибке так, чтобы команда Chrome заняла некоторое время, чтобы исправить это, даже если этот обходной путь может помочь...
Спасибо @Kaiido за выявление ошибки и предложение исправления.
Я подготовил пакет npm с именем get-blob-duration, который вы можете установить, чтобы получить красивую упакованную в Promise функцию для грязной работы.
Использование заключается в следующем:
// Returns Promise<Number>
getBlobDuration(blob).then(function(duration) {
console.log(duration + ' seconds');
});
Или ECMAScript 6:
// yada yada async
const duration = await getBlobDuration(blob)
console.log(duration + ' seconds')
Ошибка в Chrome, обнаруженная в 2016 году, но все еще открытая сегодня (март 2019 года), является основной причиной такого поведения. По определенным сценариям audioElement.duration
вернусь Infinity
,
Следующий код предоставляет обходной путь, чтобы избежать ошибки.
Использование: Создайте свой audioElement
и вызовите эту функцию один раз, предоставив ссылку на ваш audioElement
, Когда вернулся promise
решает, audioElement.duration
свойство должно содержать правильное значение. (Это также исправляет ту же проблему с videoElements
)
/**
* calculateMediaDuration()
* Force media element duration calculation.
* Returns a promise, that resolves when duration is calculated
**/
function calculateMediaDuration(media){
return new Promise( (resolve,reject)=>{
media.onloadedmetadata = function(){
// set the mediaElement.currentTime to a high value beyond its real duration
media.currentTime = Number.MAX_SAFE_INTEGER;
// listen to time position change
media.ontimeupdate = function(){
media.ontimeupdate = function(){};
// setting player currentTime back to 0 can be buggy too, set it first to .1 sec
media.currentTime = 0.1;
media.currentTime = 0;
// media.duration should now have its correct value, return it...
resolve(media.duration);
}
}
});
}
// USAGE EXAMPLE :
calculateMediaDuration( yourAudioElement ).then( ()=>{
console.log( yourAudioElement.duration )
});
Спасибо @colxi за фактическое решение, я добавил несколько шагов проверки (поскольку решение работало нормально, но были проблемы с длинными аудиофайлами).
Мне потребовалось около 4 часов, чтобы заставить его работать с длинными аудиофайлами, оказалось, что проверка была исправлением
function fixInfinity(media) {
return new Promise((resolve, reject) => {
//Wait for media to load metadata
media.onloadedmetadata = () => {
//Changes the current time to update ontimeupdate
media.currentTime = Number.MAX_SAFE_INTEGER;
//Check if its infinite NaN or undefined
if (ifNull(media)) {
media.ontimeupdate = () => {
//If it is not null resolve the promise and send the duration
if (!ifNull(media)) {
//If it is not null resolve the promise and send the duration
resolve(media.duration);
}
//Check if its infinite NaN or undefined //The second ontime update is a fallback if the first one fails
media.ontimeupdate = () => {
if (!ifNull(media)) {
resolve(media.duration);
}
};
};
} else {
//If media duration was never infinity return it
resolve(media.duration);
}
};
});
}
//Check if null
function ifNull(media) {
if (media.duration === Infinity || media.duration === NaN || media.duration === undefined) {
return true;
} else {
return false;
}
}
//USAGE EXAMPLE
//Get audio player on html
const AudioPlayer = document.getElementById('audio');
const getInfinity = async () => {
//Await for promise
await fixInfinity(AudioPlayer).then(val => {
//Reset audio current time
AudioPlayer.currentTime = 0;
//Log duration
console.log(val)
})
}
Я обернул пакет webm-duration-fix , чтобы решить проблему длины веб-страницы, которую можно использовать в nodejs и веб-браузерах для поддержки видеофайлов размером более 2 ГБ с небольшим использованием памяти.
Использование выглядит следующим образом:
import fixWebmDuration from 'webm-duration-fix';
const mimeType = 'video/webm\;codecs=vp9';
const blobSlice: BlobPart[] = [];
mediaRecorder = new MediaRecorder(stream, {
mimeType
});
mediaRecorder.ondataavailable = (event: BlobEvent) => {
blobSlice.push(event.data);
}
mediaRecorder.onstop = async () => {
// fix blob, support fix webm file larger than 2GB
const fixBlob = await fixWebmDuration(new Blob([...blobSlice], { type: mimeType }));
// to write locally, it is recommended to use fs.createWriteStream to reduce memory usage
const fileWriteStream = fs.createWriteStream(inputPath);
const blobReadstream = fixBlob.stream();
const blobReader = blobReadstream.getReader();
while (true) {
let { done, value } = await blobReader.read();
if (done) {
console.log('write done.');
fileWriteStream.close();
break;
}
fileWriteStream.write(value);
value = null;
}
blobSlice = [];
};
Принятый ответ - это нормально, однако, если вы используете проигрыватель Kendo Media или другие сторонние видеоплееры, которые зависят от функции ontimeupdate, это решение для вас.
const seedDuration = (player) => {
player.onloadedmetadata = () => {
// handle chrome's bug
if (player.duration === Infinity) {
player.currentTime = 1e101;
// Save the default behaviour of ontimeupdate
const onTimeUpdateHandler = player.ontimeupdate;
player.ontimeupdate = function () {
// bring back the default behaviour
this.ontimeupdate = onTimeUpdateHandler;
player.currentTime = 0;
}
}
}
}
//For kendo media player
//const player = document.getElementsByClassName('k-mediaplayer-media')[0];
const player = document.getElementById("videoPlayer");
player.src = YOUR_VIDEO_SOURCE;
seedDuration(player);
// To make sure that this block of code runs after all the executions are done
setTimeout(()=>{
player.play();
// For kendo media player
$("#YOUR_MEDIA_PLAYER_ID").data("kendoMediaPlayer").play();
},0)
// Если вы хотите полностью изменить видеофайл, вы можете использовать этот пакет «webmFixDuration». Другие методы применяются на уровне отображения только для тега видео. С помощью этого метода изменяется весь видеофайл.
пример webmFixDuration на гитхабе
mediaRecorder.onstop = async () => {
const duration = Date.now() - startTime;
const buggyBlob = new Blob(mediaParts, { type: 'video/webm' });
const fixedBlob = await webmFixDuration(buggyBlob, duration);
displayResult(fixedBlob);
};