-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinsertableStreamsManager.mjs
144 lines (118 loc) · 5.05 KB
/
insertableStreamsManager.mjs
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import worker from "!!raw-loader!../../temp/worker-bundle.js";
import {InjectToWorkerMessageHandler, MESSAGE as m, CONTEXT as c} from "./messageHandler.mjs";
import {AlteredMediaStreamTrackGenerator} from "./AlteredMediaStreamTrackGenerator.mjs";
import {setupPlayer} from "../applets/videoPlayer/scripts/inject.mjs";
import {setupImpairment} from "../applets/badConnection/scripts/inject.mjs";
const debug = Function.prototype.bind.call(console.debug, console, `vch 💉️📥`);
const wmh = new InjectToWorkerMessageHandler();
/**
* Modifies a MediaStreamTrack with insertable streams
* - creates a generator and processor for the track
* - creates a worker to process the track
*/
export class InsertableStreamsManager{
generator;
processor;
reader;
writer;
worker;
/**
* Modifies a MediaStreamTrack to simulate a bad connection
* @param {MediaStreamTrack} track - the input track to modify
* @param {boolean} fakeDevice - flag to indicate if the track is for a fake device
* @returns {MediaStreamTrack} - a MediaStreamTrackGenerator disguised as a MediaStreamTrack that may be processed
*/
constructor(track, fakeDevice = false) {
this.sourceTrack = track;
let generator;
/** @type {MediaStreamTrackProcessor}*/
const processor = new MediaStreamTrackProcessor(track);
this.processor = processor;
this.reader = this.processor.readable;
// I should only be processing gUM - check if the track id is a MediaStreamTrackGenerator
if (track instanceof MediaStreamTrackGenerator) {
debug("Unexpected track type - track is MediaStreamTrackGenerator", track);
this.generator = false;
} else {
generator = new AlteredMediaStreamTrackGenerator({kind: track.kind, fakeDevice: fakeDevice}, track);
this.generator = generator;
this.writer = generator.writable;
}
this.#startWorker().then(() => {
// Applets
setupPlayer(this.sourceTrack, this.worker.name);
setupImpairment(this.sourceTrack, this.worker.name);
}).catch((error) => {
debug(`failed to start worker - returning ${track.kind} track ${track.id}`, error);
return track;
});
debug("InsertableStreamsManager created", this.reader, this.writer);
// the generator is not ready yet but that doesn't seem to matter
return generator;
}
/**
* Starts a worker to process the track
* @returns {Promise[null]} - a MediaStreamTrackGenerator
* @private
*/
#startWorker(){
const workerName = `vch-${this.sourceTrack.kind}-${this.sourceTrack.id.substr(0, 5)}`;
return new Promise((resolve, reject) => {
// Start the worker
try {
// Below needed to work around policy problems - needed for Google Meet
if (window.trustedTypes && trustedTypes.createPolicy) {
debug("Trusted types enabled");
const policy = trustedTypes.createPolicy('vch-policy', {
createScriptURL: (url) => url,
});
const workerBlobURL = URL.createObjectURL(
new Blob([worker], {type: 'application/javascript'})
);
this.worker = new Worker(policy.createScriptURL(workerBlobURL), {name: workerName});
} else {
const workerBlobURL = URL.createObjectURL(new Blob([worker], {type: 'application/javascript'}));
this.worker = new Worker(workerBlobURL, {name: workerName});
}
}
catch (error) {
debug(`Failed to create worker ${workerName}`, error);
reject("Failed to create worker", error);
}
if (!this.worker) {
debug(`Worker does not exist ${workerName}`);
reject(`Worker does not exist: ${workerName}`);
}
this.worker.name = workerName;
wmh.registerWorker(this.worker);
const data = {
reader: this.reader,
writer: this.writer,
}
wmh.sendMessage(workerName, m.WORKER_SETUP, data, [this.reader, this.writer]);
resolve();
});
}
}
/**
* Modifies each track in a MediaStream and returns a stream with the modified tracks
* @param {MediaStream} stream - the stream to modify
* @returns {MediaStream} - a MediaStream
*/
export class ProcessedMediaStream {
originalTracks;
originalStream;
constructor(stream)
{
this.originalStream = stream;
this.originalTracks = stream.getTracks();
this.tracks = [];
return Promise.all(this.originalTracks.map(async (track) => {
const generator = new InsertableStreamsManager(track);
this.tracks.push(generator);
})).then(() => {
this.alteredStream = new MediaStream(this.tracks);
return this.alteredStream
});
}
}