2 years ago
#74149
Display name
Playing mp4 via mp4box.js
There was a problem with playing mp4 video through MediaSource. For video processing, I used the mp4box.js library. According to the logs, I see that all stages of video processing go through, but when I try to play the video, it loads endlessly, and the duration of the video is not even indicated. Can anyone tell me what the problem is, show an example, or suggest another way to process and play mp4 video through JS?
components/video.vue
<template>
<div class="video">
<div class="media-popup" v-show="isOpen">
<div class="media-popup__content">
<button v-on:click="close()" class="btn btn-close">x</button>
<video
v-bind:poster="preview"
controls
crossOrigin="anonymous"
v-bind:src="src"></video>
</div>
</div>
<img
class="chat__message__content__video"
v-on:click="open()"
v-bind:src="preview">
</div>
</template>
<script>
import MP4Source from "../codecs/mp4Codec";
import {ref, onMounted} from 'vue'
export default {
props: {
video: {
required: true,
type: Object
}
},
setup(props) {
const src = new ref(null);
const preview = new ref(null);
const downloadPreview = async function () {
//
};
const fetchBufferAsync = async function (nextBufferStart, bufferSize) {
//
}
const mp4Source = new MP4Source(fetchBufferAsync);
src.value = mp4Source.src;
onMounted(() => {
downloadPreview();
});
const isOpen = new ref(false);
const close = function () {
isOpen.value = false;
};
const open = function () {
isOpen.value = true;
mp4Source.fetchNextBuffer();
};
return {
close,
open,
src,
isOpen,
preview
};
},
}
</script>
<style scoped>
video {
max-width: 300px;
}
</style>
codecs/mp4Codes.ts
import MP4Box from "mp4box";
export const BEFORE_MOOV_BUFFER_SIZE = 3 * 1024,
MOOV_BUFFER_SIZE = 512 * 1024,
DEFAULT_BUFFER_SIZE = 1024 * 1024,
DEFAULT_NB_SAMPLES = 10;
export default class MP4Source {
mediaSource: MediaSource;
fetchBufferAsync: Function;
src: string;
mp4File: object | null;
sampleNum: any;
nextBufferStart: number;
bufferSize: number;
loading: boolean;
nbSamples: number;
constructor(fetchBufferAsync: Function) {
this.loading = false;
this.nextBufferStart = 0;
this.nbSamples = DEFAULT_NB_SAMPLES;
this.bufferSize = BEFORE_MOOV_BUFFER_SIZE;
this.fetchBufferAsync = fetchBufferAsync;
this.mediaSource = new MediaSource();
this.mediaSource
.addEventListener('sourceopen', () => {
this.createMP4File();
});
this.mediaSource
.addEventListener('sourceclose', () => {
console.info('[MP4Source]', 'MSE - closed.');
});
this.src = (URL || webkitURL).createObjectURL(this.mediaSource);
}
async fetchNextBuffer(seek = false) {
if (!this.mp4File || this.nextBufferStart === undefined || this.loading) {
console.log('[MP4Source]', 'Fetch canceled(ended).');
return;
}
this.loading = true;
let bufferSize = seek ? DEFAULT_BUFFER_SIZE : this.bufferSize;
console.info('[MP4Source]',
'Fetch next buffer',
`size ${bufferSize}`,
`position ${this.nextBufferStart}`);
const nextBuffer = await this.fetchBufferAsync(this.nextBufferStart, bufferSize);
nextBuffer.fileStart = this.nextBufferStart;
console.info('[MP4Source]',
'Loaded next buffer',
`size ${nextBuffer.byteLength}`,
`(waited ${bufferSize})`,
`position ${this.nextBufferStart}`);
if (nextBuffer.byteLength) {
this.nextBufferStart = this.mp4File.appendBuffer(nextBuffer);
} else {
this.nextBufferStart = undefined;
console.info('[MP4Source]', 'Empty buffer');
}
if (nextBuffer.byteLength < bufferSize) {
this.mp4File.flush();
console.info('[MP4Source]', 'MP4 file was flushed.');
}
this.loading = false;
console.info('[MP4Source] End fetch iteration.');
await this.fetchNextBuffer();
}
addSourceBuffer(track) {
const type = `video/mp4; codecs="${track.codec}"`;
if (!MediaSource.isTypeSupported(type)) {
return null;
}
console.info('[MP4Source]', [
`SourceBuffer #${track.id}`,
`Type ${type}`
].join(' '));
const sourceBuffer = this.mediaSource.addSourceBuffer(type);
sourceBuffer.id = track.id;
sourceBuffer.pendingUpdates = [];
sourceBuffer.nbSamples = track.nb_samples;
this.mp4File.setSegmentOptions(track.id, sourceBuffer, {nbSamples: this.nbSamples});
return sourceBuffer;
}
initSourceBuffers(info) {
for (let i = 0; i < info.tracks.length; i++) {
this.addSourceBuffer(info.tracks[i]);
}
const initSegs = this.mp4File.initializeSegmentation();
for (let i = 0; i < initSegs.length; i++) {
const user = initSegs[i].user;
user.onupdateend = () => {
console.info('[MP4Source]', [
`MSE - SourceBuffer #${user.id}`,
`Source update init`,
].join(' '));
if (this.mediaSource.readyState === "open") {
user.initSegs = true;
user.onupdateend = this.onSourceUpdateEnd.bind(this);
this.onSourceUpdateEnd({target: user});
}
}
console.info('[MP4Source]', [
`MSE - SourceBuffer #${user.id}.`,
'Appending initialization data.'
].join(' '));
user.appendBuffer(initSegs[i].buffer);
}
this.mp4File.start();
}
createMP4File() {
this.mp4File = MP4Box.createFile();
this.mp4File.onError = error => {
console.error('[MP4Source]', error);
};
this.mp4File.onMoovStart = () => {
this.bufferSize = MOOV_BUFFER_SIZE;
console.info('[MP4Source]', 'MP4File moov start');
};
this.mp4File.onReady = info => {
console.info('[MP4Source]', 'MP4File ready');
this.bufferSize = DEFAULT_BUFFER_SIZE;
if (info.isFragmented) {
this.mediaSource.duration = info.fragment_duration / info.timescale;
} else {
this.mediaSource.duration = info.duration / info.timescale;
}
this.initSourceBuffers(info);
}
this.mp4File.onSegment = (id, user, buffer, sampleNum, isLast) => {
isLast &&= (sampleNum + this.nbSamples) > user.nbSamples;
if (this.mediaSource.readyState !== 'open') {
return;
}
// user.segmentIndex++;
console.info('[MP4Source]', [
`Received new segment for track ${id} up to sample #${sampleNum}`,
`segments pending append: ${user.pendingUpdates.length}`
].join(', '));
user.pendingUpdates.push({id: id, buffer: buffer, sampleNum: sampleNum, isLast});
this.onSourceUpdateEnd({target: user});
};
}
onSourceUpdateEnd(event) {
const sourceBuffer = event.target;
console.info('[MP4Source]', [
`MSE - SourceBuffer #${sourceBuffer.id}`,
`Source update end`
].join(' '));
if (sourceBuffer.initSegs && sourceBuffer.updating === false && sourceBuffer.isLast && this.mediaSource.readyState === 'open') {
this.mediaSource.endOfStream();
}
if (this.mediaSource.readyState === "open" && sourceBuffer.updating === false && sourceBuffer.pendingUpdates.length > 0) {
const update = sourceBuffer.pendingUpdates.shift();
console.info('[MP4Source]', "MSE - SourceBuffer #" + sourceBuffer.id + ". Appending new buffer, pending: " + sourceBuffer.pendingUpdates.length);
if (update.sampleNum) {
this.mp4File.releaseUsedSamples(update.id, update.sampleNum);
}
sourceBuffer.isLast = update.isLast;
sourceBuffer.appendBuffer(update.buffer);
}
}
}
javascript
html5-video
mp4
media-source
mp4box
0 Answers
Your Answer