JS代码:
const mp4url = './lightning2.mp4';
const mp4box = MP4Box.createFile();
const getExtradata = () => {
const entry = mp4box.moov.traks[0].mdia.minf.stbl.stsd.entries[0];
const box = entry.avcC ?? entry.hvcC ?? entry.vpcC;
if (box != null) {
const stream = new DataStream(
undefined,
0,
DataStream.BIG_ENDIAN
)
box.write(stream)
return new Uint8Array(stream.buffer.slice(8))
}
};
let videoTrack = null;
let videoDecoder = null;
const videoFrames = [];
let nbSampleTotal = 0;
let countSample = 0;
mp4box.onReady = function (info) {
videoTrack = info.videoTracks[0];
if (videoTrack != null) {
mp4box.setExtractionOptions(videoTrack.id, 'video', {
nbSamples: 100
})
}
const videoW = videoTrack.track_width;
const videoH = videoTrack.track_height;
videoDecoder = new VideoDecoder({
output: (videoFrame) => {
createImageBitmap(videoFrame).then((img) => {
videoFrames.push({
img,
duration: videoFrame.duration,
timestamp: videoFrame.timestamp,
});
videoFrame.close();
if (videoFrames.length == nbSampleTotal) {
render();
}
});
},
error: (err) => {
console.error('videoDecoder错误:', err);
},
});
nbSampleTotal = videoTrack.nb_samples;
videoDecoder.configure({
codec: videoTrack.codec,
codedWidth: videoW,
codedHeight: videoH,
description: getExtradata(),
});
mp4box.start();
};
mp4box.onSamples = function (trackId, ref, samples) {
if (videoTrack.id === trackId) {
mp4box.stop();
countSample += samples.length;
for (const sample of samples) {
const type = sample.is_sync ? 'key' : 'delta';
const chunk = new EncodedVideoChunk({
type,
timestamp: sample.cts,
duration: sample.duration,
data: sample.data,
});
videoDecoder.decode(chunk);
}
if (countSample === nbSampleTotal) {
videoDecoder.flush();
}
}
};
fetch(mp4url).then(res => res.arrayBuffer()).then(buffer => {
buffer.fileStart = 0;
mp4box.appendBuffer(buffer);
mp4box.flush();
});
const canvas = document.getElementById('canvas');
const view = canvas.transferControlToOffscreen()
const viewWidth = 480;
const viewHeight = 480;
const app = new PIXI.Application({
view,
width: viewWidth,
height: viewHeight,
resolution: 1,
});
const background = PIXI.Sprite.from('./cloudy.jpg');
background.width = viewWidth;
background.height = viewHeight;
app.stage.addChild(background);
const imgContainer = new PIXI.Container();
app.stage.addChild(imgContainer);
const render = () => {
const spriteFrames = videoFrames.map(obj => {
obj.sprite = PIXI.Sprite.from(obj.img);
obj.sprite.x = 0;
obj.sprite.y = 0;
obj.sprite.width = viewWidth;
obj.sprite.height = viewHeight;
obj.sprite.blendMode = PIXI.BLEND_MODES.SCREEN
return obj;
});
let index = 0;
const draw = () => {
const { sprite, duration } = spriteFrames[index];
imgContainer.removeChildren();
imgContainer.addChild(sprite);
index++;
if (index === videoFrames.length) {
index = 0;
}
setTimeout(draw, duration);
}
draw();
}