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)
|
const segment = new Segment(track.source, init, msg.timestamp)
|
||||||
|
|
||||||
// The track is responsible for flushing the segments in order
|
// The track is responsible for flushing the segments in order
|
||||||
track.source.initialize(init)
|
|
||||||
track.add(segment)
|
track.add(segment)
|
||||||
|
|
||||||
/* TODO I'm not actually sure why this code doesn't work; something trips up the MP4 parser
|
/* 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);
|
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
|
return this.done
|
||||||
}
|
}
|
||||||
|
@ -109,6 +110,9 @@ export class Segment {
|
||||||
finish() {
|
finish() {
|
||||||
this.done = true
|
this.done = true
|
||||||
this.flush()
|
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
|
// Extend the last sample so it reaches the provided timestamp
|
||||||
|
|
|
@ -4,46 +4,56 @@ import { Init } from "./init"
|
||||||
export class Source {
|
export class Source {
|
||||||
sourceBuffer?: SourceBuffer;
|
sourceBuffer?: SourceBuffer;
|
||||||
mediaSource: MediaSource;
|
mediaSource: MediaSource;
|
||||||
queue: Array<Uint8Array | ArrayBuffer>;
|
queue: Array<SourceInit | SourceData | SourceTrim>;
|
||||||
mime: string;
|
init?: Init;
|
||||||
|
|
||||||
constructor(mediaSource: MediaSource) {
|
constructor(mediaSource: MediaSource) {
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
this.mime = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (re)initialize the source using the provided init segment.
|
||||||
initialize(init: Init) {
|
initialize(init: Init) {
|
||||||
if (!this.sourceBuffer) {
|
// Check if the init segment is already in the queue.
|
||||||
this.sourceBuffer = this.mediaSource.addSourceBuffer(init.info.mime)
|
for (let i = this.queue.length - 1; i >= 0; i--) {
|
||||||
this.sourceBuffer.addEventListener('updateend', this.flush.bind(this))
|
if ((this.queue[i] as SourceInit).init == init) {
|
||||||
|
// Already queued up.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the init data to the front of the queue
|
// Check if the init segment has already been applied.
|
||||||
for (let i = init.raw.length - 1; i >= 0; i -= 1) {
|
if (this.init == init) {
|
||||||
this.queue.unshift(init.raw[i])
|
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()
|
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])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mime = init.info.mime
|
// Append the segment data to the buffer.
|
||||||
}
|
append(data: Uint8Array | ArrayBuffer) {
|
||||||
|
this.queue.push({
|
||||||
appendBuffer(data: Uint8Array | ArrayBuffer) {
|
kind: "data",
|
||||||
if (!this.sourceBuffer || this.sourceBuffer.updating || this.queue.length) {
|
data: data,
|
||||||
this.queue.push(data)
|
})
|
||||||
} else {
|
|
||||||
this.sourceBuffer.appendBuffer(data)
|
this.flush()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the buffered range.
|
||||||
buffered() {
|
buffered() {
|
||||||
if (!this.sourceBuffer) {
|
if (!this.sourceBuffer) {
|
||||||
return { length: 0 }
|
return { length: 0 }
|
||||||
|
@ -52,30 +62,86 @@ export class Source {
|
||||||
return this.sourceBuffer.buffered
|
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() {
|
flush() {
|
||||||
// Check if we have a mime yet
|
while (1) {
|
||||||
if (!this.sourceBuffer) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the buffer is currently busy.
|
// Check if the buffer is currently busy.
|
||||||
if (this.sourceBuffer.updating) {
|
if (this.sourceBuffer && this.sourceBuffer.updating) {
|
||||||
return
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.queue.shift()
|
// Process the next item in the queue.
|
||||||
if (data) {
|
const next = this.queue.shift()
|
||||||
// If there's data in the queue, flush it.
|
if (!next) {
|
||||||
this.sourceBuffer.appendBuffer(data)
|
break;
|
||||||
} 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
|
switch (next.kind) {
|
||||||
|
case "init":
|
||||||
|
this.init = next.init;
|
||||||
|
|
||||||
|
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)
|
const start = this.sourceBuffer.buffered.start(0)
|
||||||
|
|
||||||
// Remove any range larger than 1s.
|
if (end > start) {
|
||||||
if (end > start && end - start > 1.0) {
|
|
||||||
this.sourceBuffer.remove(start, end)
|
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