Add support for switching between tracks to the player.
This commit is contained in:
parent
2608baac33
commit
894f26c5af
|
@ -242,7 +242,6 @@ export class Player {
|
|||
const segment = new Segment(track.source, init, msg.timestamp)
|
||||
|
||||
// The track is responsible for flushing the segments in order
|
||||
track.source.initialize(init)
|
||||
track.add(segment)
|
||||
|
||||
/* TODO I'm not actually sure why this code doesn't work; something trips up the MP4 parser
|
||||
|
|
|
@ -100,7 +100,8 @@ export class Segment {
|
|||
mdat.write(stream);
|
||||
}
|
||||
|
||||
this.source.appendBuffer(stream.buffer as ArrayBuffer)
|
||||
this.source.initialize(this.init)
|
||||
this.source.append(stream.buffer as ArrayBuffer)
|
||||
|
||||
return this.done
|
||||
}
|
||||
|
@ -109,6 +110,9 @@ export class Segment {
|
|||
finish() {
|
||||
this.done = true
|
||||
this.flush()
|
||||
|
||||
// Trim the buffer to 30s long after each segment.
|
||||
this.source.trim(30)
|
||||
}
|
||||
|
||||
// Extend the last sample so it reaches the provided timestamp
|
||||
|
|
|
@ -4,46 +4,56 @@ import { Init } from "./init"
|
|||
export class Source {
|
||||
sourceBuffer?: SourceBuffer;
|
||||
mediaSource: MediaSource;
|
||||
queue: Array<Uint8Array | ArrayBuffer>;
|
||||
mime: string;
|
||||
queue: Array<SourceInit | SourceData | SourceTrim>;
|
||||
init?: Init;
|
||||
|
||||
constructor(mediaSource: MediaSource) {
|
||||
this.mediaSource = mediaSource;
|
||||
this.queue = [];
|
||||
this.mime = "";
|
||||
}
|
||||
|
||||
// (re)initialize the source using the provided init segment.
|
||||
initialize(init: Init) {
|
||||
if (!this.sourceBuffer) {
|
||||
this.sourceBuffer = this.mediaSource.addSourceBuffer(init.info.mime)
|
||||
this.sourceBuffer.addEventListener('updateend', this.flush.bind(this))
|
||||
|
||||
// Add the init data to the front of the queue
|
||||
for (let i = init.raw.length - 1; i >= 0; i -= 1) {
|
||||
this.queue.unshift(init.raw[i])
|
||||
}
|
||||
|
||||
this.flush()
|
||||
} else if (init.info.mime != this.mime) {
|
||||
this.sourceBuffer.changeType(init.info.mime)
|
||||
|
||||
// Add the init data to the front of the queue
|
||||
for (let i = init.raw.length - 1; i >= 0; i -= 1) {
|
||||
this.queue.unshift(init.raw[i])
|
||||
// Check if the init segment is already in the queue.
|
||||
for (let i = this.queue.length - 1; i >= 0; i--) {
|
||||
if ((this.queue[i] as SourceInit).init == init) {
|
||||
// Already queued up.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.mime = init.info.mime
|
||||
}
|
||||
|
||||
appendBuffer(data: Uint8Array | ArrayBuffer) {
|
||||
if (!this.sourceBuffer || this.sourceBuffer.updating || this.queue.length) {
|
||||
this.queue.push(data)
|
||||
} else {
|
||||
this.sourceBuffer.appendBuffer(data)
|
||||
// Check if the init segment has already been applied.
|
||||
if (this.init == init) {
|
||||
return
|
||||
}
|
||||
|
||||
// Add the init segment to the queue so we call addSourceBuffer or changeType
|
||||
this.queue.push({
|
||||
kind: "init",
|
||||
init: init,
|
||||
})
|
||||
|
||||
for (let i = 0; i < init.raw.length; i += 1) {
|
||||
this.queue.push({
|
||||
kind: "data",
|
||||
data: init.raw[i],
|
||||
})
|
||||
}
|
||||
|
||||
this.flush()
|
||||
}
|
||||
|
||||
// Append the segment data to the buffer.
|
||||
append(data: Uint8Array | ArrayBuffer) {
|
||||
this.queue.push({
|
||||
kind: "data",
|
||||
data: data,
|
||||
})
|
||||
|
||||
this.flush()
|
||||
}
|
||||
|
||||
// Return the buffered range.
|
||||
buffered() {
|
||||
if (!this.sourceBuffer) {
|
||||
return { length: 0 }
|
||||
|
@ -52,30 +62,86 @@ export class Source {
|
|||
return this.sourceBuffer.buffered
|
||||
}
|
||||
|
||||
// Delete any media older than x seconds from the buffer.
|
||||
trim(duration: number) {
|
||||
this.queue.push({
|
||||
kind: "trim",
|
||||
trim: duration,
|
||||
})
|
||||
|
||||
this.flush()
|
||||
}
|
||||
|
||||
// Flush any queued instructions
|
||||
flush() {
|
||||
// Check if we have a mime yet
|
||||
if (!this.sourceBuffer) {
|
||||
return
|
||||
}
|
||||
while (1) {
|
||||
// Check if the buffer is currently busy.
|
||||
if (this.sourceBuffer && this.sourceBuffer.updating) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if the buffer is currently busy.
|
||||
if (this.sourceBuffer.updating) {
|
||||
return
|
||||
}
|
||||
// Process the next item in the queue.
|
||||
const next = this.queue.shift()
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
|
||||
const data = this.queue.shift()
|
||||
if (data) {
|
||||
// If there's data in the queue, flush it.
|
||||
this.sourceBuffer.appendBuffer(data)
|
||||
} else if (this.sourceBuffer.buffered.length) {
|
||||
// Otherwise with no data, trim anything older than 30s.
|
||||
const end = this.sourceBuffer.buffered.end(this.sourceBuffer.buffered.length - 1) - 30.0
|
||||
const start = this.sourceBuffer.buffered.start(0)
|
||||
switch (next.kind) {
|
||||
case "init":
|
||||
this.init = next.init;
|
||||
|
||||
// Remove any range larger than 1s.
|
||||
if (end > start && end - start > 1.0) {
|
||||
this.sourceBuffer.remove(start, end)
|
||||
if (!this.sourceBuffer) {
|
||||
// Create a new source buffer.
|
||||
this.sourceBuffer = this.mediaSource.addSourceBuffer(this.init.info.mime)
|
||||
|
||||
// Call flush automatically after each update finishes.
|
||||
this.sourceBuffer.addEventListener('updateend', this.flush.bind(this))
|
||||
} else {
|
||||
this.sourceBuffer.changeType(next.init.info.mime)
|
||||
}
|
||||
|
||||
break;
|
||||
case "data":
|
||||
if (!this.sourceBuffer) {
|
||||
throw "failed to call initailize before append"
|
||||
}
|
||||
|
||||
this.sourceBuffer.appendBuffer(next.data)
|
||||
|
||||
break;
|
||||
case "trim":
|
||||
if (!this.sourceBuffer) {
|
||||
throw "failed to call initailize before trim"
|
||||
}
|
||||
|
||||
const end = this.sourceBuffer.buffered.end(this.sourceBuffer.buffered.length - 1) - next.trim;
|
||||
const start = this.sourceBuffer.buffered.start(0)
|
||||
|
||||
if (end > start) {
|
||||
this.sourceBuffer.remove(start, end)
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw "impossible; unknown SourceItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SourceItem {}
|
||||
|
||||
class SourceInit implements SourceItem {
|
||||
kind!: "init";
|
||||
init!: Init;
|
||||
}
|
||||
|
||||
class SourceData implements SourceItem {
|
||||
kind!: "data";
|
||||
data!: Uint8Array | ArrayBuffer;
|
||||
}
|
||||
|
||||
class SourceTrim implements SourceItem {
|
||||
kind!: "trim";
|
||||
trim!: number;
|
||||
}
|
Loading…
Reference in New Issue