Here is my fontend code with React.js
import React, { useEffect, useRef } from 'react';
const VideoPage = () => {
const videoInited = useRef(false)
useEffect(() => {
const video = document.querySelector("#stream-media-video") as HTMLMediaElement;
// Using Bento4 to parse my mp4 video file
// mp4info test.mp4 | grep Codec
const mimeCodec = 'video/mp4; codecs="avc1.64081F, mp4a.40.2"';
if ("MediaSource" in window && MediaSource.isTypeSupported(mimeCodec)) {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener("sourceopen", sourceOpen);
} else {
console.error("Unsupported MIME type or codec: ", mimeCodec);
}
function sourceOpen(_) {
console.log('[mediaSource sourceopen event trigger]: ', this.readyState); // open
const mediaSource = this;
const playBtn = document.querySelector('.play-btn');
const init = () => {
const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
// Get some video clips through xhr request (the video file is 1.5M in total, first get the first 1M part, and then get the next 0.5M part)
fetchAB("/api/media/testmp4/part-v2?start=0&end=1000000", function (buf) {
sourceBuffer.addEventListener('error', (e) => {
console.error("sourceBuffer error", e);
});
sourceBuffer.addEventListener("updateend", function (_) {
// mediaSource.endOfStream();
video.play();
videoInited.current = true;
playBtn.removeEventListener("click", init);
});
sourceBuffer.appendBuffer(buf);
// get the next 0.5M part
fetchAB("/api/media/testmp4/part-v2?start=1000000&end=2000000", function (secondBuf){
sourceBuffer.appendBuffer(secondBuf);
});
});
}
playBtn.addEventListener("click", init)
}
function fetchAB(url, cb) {
const xhr = new XMLHttpRequest();
xhr.open("get", url);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
}
}, [])
const handleClickPlay = () => {
const video = document.querySelector("video")
if (videoInited.current) {
video.play()
}
}
return (
<div>
<video
id='stream-media-video'
width="400px"
height="400px"
style={{
objectFit: 'contain',
}}
controls
/>
<div>
<button className='play-btn' onClick={handleClickPlay}>click to play video</button>
</div>
</div>
)
}
export default VideoPage
And Here is my backend code with Express.js
app.get("/api/media/testmp4/part-v2", function (req, res) {
const {
start,
end,
} = req.query;
const mp4FilePath = "test.mp4";
const _start = Number(start);
const videoSize = fs.statSync(mp4FilePath).size;
const _end = Math.min(Number(end), videoSize - 1);
// Get video clips and return them to the front end
const videoStream = fs.createReadStream(mp4FilePath, {
start: _start,
end: _end,
});
videoStream.pipe(res);
});
The progress bar in the video player shows that the remaining video clips have not been loaded and cannot continue to play. And here is my screenshot of problem.
There are no errors on the console, how can I make the video continue to play?
"I have controlled the returned file byte range on the server side:
Math.min(Number(end), videoSize - 1)
"
Looking at:
const _end = Math.min(Number(end), videoSize - 1);
Math.min will return the smallest one from those two input numbers.
Your end
is smaller than videoSize-1
so now it is being used as the limit of bytes returned back into your app for playback:
var End : 1000000
(Size-1) : 1499999
file Size : 1500000
"The progress bar in the video player shows that the remaining video clip data has not been loaded and it cannot continue to play."
To fix:
Use the known file size (to get all data, with none remaining):
fetchAB("/api/media/testmp4/part-v2?start=0&end=1500000");
IF the file size is unknown, then use a very large number (true duration is the new MIN):
fetchAB("/api/media/testmp4/part-v2?start=0&end=999999999999");