Skip to content

Commit

Permalink
⚡ Live streaming optimization, add retry logic
Browse files Browse the repository at this point in the history
  • Loading branch information
yumexupanic committed Apr 27, 2024
1 parent ac37ff0 commit dc267f4
Showing 1 changed file with 110 additions and 27 deletions.
137 changes: 110 additions & 27 deletions lib/wsfmp4.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ class WSFMP4 {
constructor(media, options) {
this.options = Object.assign({}, {
debug: false,
retry: false,
url: '',
duration: 0,

cacheMax: 8,
live: false,
liveMaxLatency: 0
}, options);
Expand Down Expand Up @@ -34,6 +36,37 @@ class WSFMP4 {
this.frameQueue.push(data);
this._cache()
}

this.ws.onclose = (event) => {
console.error('websocket disconnected:', event.code, event.reason);
if (this.options.retry) {
if (event.code != 1000) {
let sid = setTimeout(() => {
clearTimeout(sid)
console.log('try reloading...')
this.destroy()
this._setup()
}, 1000 * 3)
}
}
};

this.ws.onerror = function (error) {
console.error('websocket error:', error);
};

// this._videoErrorListener = (e) => {
// if (this.media.error.message.indexOf('Empty') < 0) {
// console.error(`video error message => ${this.media.error.message}`)
// console.log('try reloading...')
// let sid = setTimeout(() => {
// clearTimeout(sid)
// this.destroy()
// this._setup()
// }, 1000 * 3)
// }
// }
// this.media.addEventListener('error', this._videoErrorListener)
}

_demux(info) {
Expand All @@ -47,61 +80,93 @@ class WSFMP4 {
console.error("not fragmented mp4")
return;
}
this.codecs = info.mime
this._mediasource()
}

let codecs = info.tracks.map(e => e.codec)
_mediasource() {
if (this.options.debug) {
console.log(`sourcebuffer codecs => ${codecs}`)
console.debug(`sourcebuffer codecs => ${this.codecs}`)
}
let mediasource = new MediaSource()
this.media.src = URL.createObjectURL(mediasource);
if (!MediaSource.isTypeSupported(this.codecs)) {
console.warn(`unsupported formats => ${this.codecs}`)
return
}

this.mediasource = new MediaSource()
this.media.preload = "metadata"
this.media.src = URL.createObjectURL(this.mediasource);

mediasource.addEventListener('sourceopen', () => {
this.sourceBuffer = mediasource.addSourceBuffer(`video/mp4; codecs="${codecs.join(', ')}"`);
this.mediasource.addEventListener('sourceopen', () => {
this.sourceBuffer = this.mediasource.addSourceBuffer(this.codecs);
if (this.options.duration) {
mediasource.duration = this.options.duration
}

this.sourceBuffer.onupdateend = () => {
let cacheMax = 24

let current = this.media.currentTime;
if (video.buffered.length > 0) {
let start = this.media.buffered.start(0);
let end = this.media.buffered.end(0);
let start = this.media.buffered.start(this.media.buffered.length - 1);
let end = this.media.buffered.end(this.media.buffered.length - 1);

if (this.options.debug) {
console.log(`buffer: current => ${current},buffer_start => ${start},buffer_end => ${end}`);
}

if (current < start) {
this.media.currentTime = start;
if (this.options.debug) {
console.log(`reset start playback time: current => ${current},now => ${start}`);

if (this.options.live) {
if (end - current > this.options.liveMaxLatency) {
this.media.currentTime = end;
if (!this.media.paused && this.options.live && this.media.muted) {
this.media.play().then(() => { }).catch(error => { })
}

console.log(`live streaming optimization refresh latency: latency => ${end - current},now => ${this.media.currentTime}`);
}
} else {
if (current < start) {
this.media.currentTime = start;
if (this.options.debug) {
console.log(`reset start playback time: current => ${current},now => ${start}`);
}
}
}

if (current - start > cacheMax * 2) {
this.sourceBuffer.remove(start, start + cacheMax)
if (current - start > this.options.cacheMax * 2) {
this.sourceBuffer.remove(0, current - this.options.cacheMax)
if (this.options.debug) {
console.log(`clearing the played cache: start => ${start},end => ${end},second => ${current - start}`);
}
}
}
}
this._cache();
});

if (this.options.live && this.options.liveMaxLatency) {
if (end - current > this.options.liveMaxLatency) {
this.media.currentTime = end;
console.log(`live streaming optimization refresh latency: latency => ${end - current},end => ${end}`);
let paused
document.addEventListener('visibilitychange', () => {
if (this.sourceBuffer) {
if (document.hidden) {
paused = this.media.paused
this.media.pause()
} else {
if (!paused) {
if (this.media.buffered.length) {
let end = this.media.buffered.end(this.media.buffered.length - 1)
this.media.currentTime = end
}
this.media.play().then(() => { }).catch(error => { })
}
}
}
this._cache();
});
})
}

_cache() {
if (!this.sourceBuffer || this.sourceBuffer.updating) {
if (!this.sourceBuffer || this.sourceBuffer.updating || !this.frameQueue.length) {
return;
}
let frame = null
if (this.frameQueue.length > 1) {
let freeBuffer = this.frameQueue.splice(0, this.frameQueue.length)
let length = freeBuffer.map(e => e.byteLength).reduce((a, b) => a + b, 0)
Expand All @@ -112,18 +177,36 @@ class WSFMP4 {
buffer.set(frame, offset);
offset += data.byteLength;
}
this.sourceBuffer.appendBuffer(buffer);
frame = buffer
} else {
this.sourceBuffer.appendBuffer(this.frameQueue.shift());
frame = this.frameQueue.shift()
}
}

if (frame) {
this.sourceBuffer.appendBuffer(frame);
if (this.options.debug) {
console.log(`load buffer: size => ${frame.byteLength}`);
}
}

}
destroy() {
if (this.ws) {
this.ws.close(1000)
this.ws = null
this.sourceBuffer.abort()
}
this.frameQueue = [];
this.media.pause();
this.media.currentTime = 0;
this.codecs = ""

if (this.mediasource) {
try { this.sourceBuffer.abort() } catch (e) { }
try { this.mediasource.removeSourceBuffer(this.sourceBuffer) } catch (e) { }
this.sourceBuffer = null
this.mediasource = null
}

}
}

Expand Down

0 comments on commit dc267f4

Please sign in to comment.