I am trying to read segments from the backend and append them to my source buffer, but for some reason, I keep getting an error message stating that the source buffer has been removed from the source media.
Here is my code
if ('MediaSource' in window) {
console.log('MediaSource works');
let mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', async () => {
const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); // Adjust codec as necessary
console.log(sourceBuffer);
try {
await fetchAndAppendSegments(mediaSource, sourceBuffer, videoId);
if (mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
} catch (error) {
console.error('Error during fetch and append segments:', error);
if (mediaSource.readyState === 'open') {
mediaSource.endOfStream('network');
}
}
// Attempt to start playback explicitly
document.addEventListener('click', function () {
console.log("Should play now.");
video.play().then(() => {
console.log('Playback started successfully');
}).catch(error => {
console.error('Error starting playback:', error);
});
});
});
} else {
console.error('MediaSource Extensions are not supported in your browser.');
}
And the fetch and append segments function:
async function fetchAndAppendSegments(mediaSource, sourceBuffer, videoId) {
console.log("Fetching and appending video segments");
// Fetch video metadata to get total size
const metadata = await fetchVideoMetadata(videoId);
const totalSize = metadata.size;
console.log('Total Size:', totalSize);
const SEGMENT_SIZE = Math.min(512 * 1024, totalSize / 10) * 4; // Example segment size: 512 KB or 1/10th of the total size
console.log("Segment Size:", SEGMENT_SIZE);
// Create array of segment ranges
const segments = [];
for (let start = 0; start < totalSize; start += SEGMENT_SIZE) {
const end = Math.min(start + SEGMENT_SIZE - 1, totalSize);
segments.push({ start, end });
}
let segmentIndex = 0;
// Process each segment sequentially using jQuery each
$.each(segments, async (index, segment) => {
if (mediaSource.readyState !== 'open') {
console.error('MediaSource ended prematurely');
return false; // Break out of $.each loop
}
const { start, end } = segment;
try {
console.log(`Fetching segment ${index}: ${start} to ${end}`);
const segmentData = await fetchSegment(videoId, start, end);
if (segmentData) {
console.log(`Segment ${index} fetched successfully: ${start} to ${end}`);
await appendSegment(sourceBuffer, segmentData);
console.log(`Appended segment ${index}: ${start} to ${end}`);
} else {
console.log(`No segment returned for range ${start} to ${end}, stopping.`);
return false; // Break out of $.each loop
}
} catch (error) {
console.error(`Error fetching or appending video segment ${start} to ${end}:`, error);
if (mediaSource.readyState === 'open') {
console.log('Ending MediaSource due to network error');
mediaSource.endOfStream('network');
}
return false; // Break out of $.each loop
}
segmentIndex++;
});
console.log('Final MediaSource readyState:', mediaSource.readyState);
if (mediaSource.readyState === 'open') {
mediaSource.endOfStream();
}
}
Then I have the fetch segment from backend function:
async function fetchSegment(videoId, start, end) {
const segmentUrl = `http://localhost:8080/videos/${videoId}/segment`;
try {
const response = await fetch(segmentUrl, {
headers: {
'Range': `bytes=${start}-${end}`
}
});
if (!response.ok) {
if (response.status === 416) {
console.error('Requested range not satisfiable');
return null;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log(`Fetched segment: bytes=${start}-${end}`);
return await response.arrayBuffer();
} catch (error) {
console.error(`Error fetching segment:`, error);
throw error;
}
}
And these should do the actual appending of buffers:
async function appendSegment(sourceBuffer, segment) {
console.log('Appending segment...', segment.byteLength);
if (!sourceBuffer) {
console.error("No source buffer");
return;
}
try {
await waitForSourceBuffer(sourceBuffer); // Wait until the source buffer is ready
// Check for buffer overflow
if (sourceBuffer.buffered.length > 0) {
const bufferEnd = sourceBuffer.buffered.end(sourceBuffer.buffered.length - 1);
const currentTime = video.currentTime;
if (bufferEnd - currentTime > 30) {
sourceBuffer.remove(0, bufferEnd - 30); // Remove old data if needed
await waitForSourceBuffer(sourceBuffer); // Wait for the removal to complete
}
}
console.log('Appending buffer segment.');
sourceBuffer.appendBuffer(segment);
await waitForSourceBuffer(sourceBuffer);
console.log('Buffer append completed');
} catch (error) {
console.error('Error appending segment:', error);
throw error;
}
}
function waitForSourceBuffer(sourceBuffer) {
return new Promise((resolve, reject) => {
if (!sourceBuffer.updating) {
resolve();
return;
}
const updateEndHandler = () => {
sourceBuffer.removeEventListener('updateend', updateEndHandler);
resolve();
};
const errorHandler = (e) => {
sourceBuffer.removeEventListener('error', errorHandler);
reject(new Error('SourceBuffer error: ' + e.message));
};
sourceBuffer.addEventListener('updateend', updateEndHandler);
sourceBuffer.addEventListener('error', errorHandler);
});
}
Here are the logs: