-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmediastreamtrackprocessor.js
More file actions
69 lines (69 loc) · 3.27 KB
/
Copy pathmediastreamtrackprocessor.js
File metadata and controls
69 lines (69 loc) · 3.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
if (!self.MediaStreamTrackProcessor) {
self.MediaStreamTrackProcessor = class MediaStreamTrackProcessor {
constructor({track}) {
if (track.kind == "video") {
this.readable = new ReadableStream({
async start(controller) {
track.addEventListener("ended", () => controller.close(), {once: true});
this.video = document.createElement("video");
this.video.srcObject = new MediaStream([track]);
await Promise.all([this.video.play(), new Promise(r => this.video.onloadedmetadata = r)]);
this.track = track;
this.canvas = new OffscreenCanvas(this.video.videoWidth, this.video.videoHeight);
this.ctx = this.canvas.getContext('2d', {desynchronized: true});
this.t1 = performance.now();
},
async pull(controller) {
if (track.readyState == "ended") return controller.close();
const fps = track.getSettings().frameRate || 30;
while (performance.now() - this.t1 < 1000 / fps) {
await new Promise(r => requestAnimationFrame(r));
if (track.readyState == "ended") return controller.close();
}
this.canvas.width = this.video.videoWidth;
this.canvas.height = this.video.videoHeight;
this.t1 = performance.now();
this.ctx.drawImage(this.video, 0, 0);
controller.enqueue(new VideoFrame(this.canvas, {timestamp: this.t1}));
}
});
} else if (track.kind == "audio") {
this.readable = new ReadableStream({
async start(controller) {
track.addEventListener("ended", () => controller.close(), {once: true});
this.ac = new AudioContext;
this.arrays = [];
function worklet() {
registerProcessor("mstp-shim", class Processor extends AudioWorkletProcessor {
process(input) { this.port.postMessage(input); return true; }
});
}
await this.ac.audioWorklet.addModule(`data:text/javascript,(${worklet.toString()})()`);
this.node = new AudioWorkletNode(this.ac, "mstp-shim");
this.ac.createMediaStreamSource(new MediaStream([track])).connect(this.node);
this.node.port.addEventListener("message", ({data}) => data[0][0] && this.arrays.push(data));
},
async pull(controller) {
if (track.readyState == "ended") return controller.close();
while (!this.arrays.length) {
await new Promise(r => this.node.port.onmessage = r);
if (track.readyState == "ended") return controller.close();
}
const [channels] = this.arrays.shift();
const joined = new Float32Array(channels.reduce((a, b) => a + b.length, 0));
channels.reduce((offset, a) => (joined.set(a, offset), offset + a.length), 0);
controller.enqueue(new AudioData({
format: "f32-planar",
sampleRate: this.ac.sampleRate,
numberOfFrames: channels[0].length,
numberOfChannels: channels.length,
timestamp: this.ac.currentTime * 1e6 | 0,
data: joined,
transfer: [joined.buffer]
}));
}
});
}
}
};
}