1 |
|
2 | (self => {
|
3 | const ContextKey = Symbol();
|
4 | const MediaSourceKey = Symbol();
|
5 | const ReadyStateKey = Symbol();
|
6 | const SourceBuffersKey = Symbol();
|
7 | const SourceBufferTasksKey = Symbol();
|
8 | const TimeRangesKey = Symbol();
|
9 | const EMPTY_ARRAY = [];
|
10 | const defineCstr = (cstrName, Cstr) => self[cstrName] = defineCstrName(cstrName, Cstr);
|
11 | const defineCstrName = (cstrName, Cstr) => Object.defineProperty(Cstr, "name", {
|
12 | value: cstrName
|
13 | });
|
14 | const [getter, setter, callMethod, constructGlobal, definePrototypePropertyDescriptor, randomId, WorkerProxy, WorkerEventTargetProxy, WinIdKey, InstanceIdKey, ApplyPathKey] = self.ptm;
|
15 | class TimeRanges extends WorkerProxy {
|
16 | start(...args) {
|
17 | return callMethod(this, [ "start" ], args);
|
18 | }
|
19 | end(...args) {
|
20 | return callMethod(this, [ "end" ], args);
|
21 | }
|
22 | get length() {
|
23 | return getter(this, [ "length" ]);
|
24 | }
|
25 | }
|
26 | defineCstr("TimeRanges", TimeRanges);
|
27 | const HTMLMediaDescriptorMap = {
|
28 | buffered: {
|
29 | get() {
|
30 | if (!this[TimeRangesKey]) {
|
31 | this[TimeRangesKey] = new TimeRanges(this[WinIdKey], this[InstanceIdKey], [ "buffered" ]);
|
32 | setTimeout((() => {
|
33 | this[TimeRangesKey] = void 0;
|
34 | }), 5e3);
|
35 | }
|
36 | return this[TimeRangesKey];
|
37 | }
|
38 | },
|
39 | readyState: {
|
40 | get() {
|
41 | if (4 === this[ReadyStateKey]) {
|
42 | return 4;
|
43 | }
|
44 | if ("number" != typeof this[ReadyStateKey]) {
|
45 | this[ReadyStateKey] = getter(this, [ "readyState" ]);
|
46 | setTimeout((() => {
|
47 | this[ReadyStateKey] = void 0;
|
48 | }), 1e3);
|
49 | }
|
50 | return this[ReadyStateKey];
|
51 | }
|
52 | }
|
53 | };
|
54 | definePrototypePropertyDescriptor(self.HTMLMediaElement, HTMLMediaDescriptorMap);
|
55 | var _a;
|
56 | class SourceBufferList extends Array {
|
57 | constructor(mediaSource) {
|
58 | super();
|
59 | this[MediaSourceKey] = mediaSource;
|
60 | }
|
61 | addEventListener(...args) {
|
62 | callMethod(this[MediaSourceKey], [ "sourceBuffers", "addEventListener" ], args, 3);
|
63 | }
|
64 | removeEventListener(...args) {
|
65 | callMethod(this[MediaSourceKey], [ "sourceBuffers", "removeEventListener" ], args, 3);
|
66 | }
|
67 | }
|
68 | class SourceBuffer extends WorkerEventTargetProxy {
|
69 | constructor(mediaSource) {
|
70 | super(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers" ]);
|
71 | this[_a] = [];
|
72 | this[MediaSourceKey] = mediaSource;
|
73 | }
|
74 | abort() {
|
75 | const sbIndex = getSourceBufferIndex(this);
|
76 | callMethod(this, [ sbIndex, "appendWindowStart" ], EMPTY_ARRAY, 1);
|
77 | }
|
78 | addEventListener(...args) {
|
79 | console.log("addEventListener");
|
80 | const sbIndex = getSourceBufferIndex(this);
|
81 | callMethod(this, [ sbIndex, "addEventListener" ], args, 3);
|
82 | }
|
83 | appendBuffer(buf) {
|
84 | this[SourceBufferTasksKey].push([ "appendBuffer", [ buf ], buf ]);
|
85 | drainSourceBufferQueue(this);
|
86 | }
|
87 | get appendWindowStart() {
|
88 | const sbIndex = getSourceBufferIndex(this);
|
89 | return getter(this, [ sbIndex, "appendWindowStart" ]);
|
90 | }
|
91 | set appendWindowStart(value) {
|
92 | const sbIndex = getSourceBufferIndex(this);
|
93 | setter(this, [ sbIndex, "appendWindowStart" ], value);
|
94 | }
|
95 | get appendWindowEnd() {
|
96 | const sbIndex = getSourceBufferIndex(this);
|
97 | return getter(this, [ sbIndex, "appendWindowEnd" ]);
|
98 | }
|
99 | set appendWindowEnd(value) {
|
100 | const sbIndex = getSourceBufferIndex(this);
|
101 | setter(this, [ sbIndex, "appendWindowEnd" ], value);
|
102 | }
|
103 | get buffered() {
|
104 | const mediaSource = this[MediaSourceKey];
|
105 | const sbIndex = getSourceBufferIndex(this);
|
106 | const timeRanges = new TimeRanges(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ "sourceBuffers", sbIndex, "buffered" ]);
|
107 | return timeRanges;
|
108 | }
|
109 | changeType(mimeType) {
|
110 | const sbIndex = getSourceBufferIndex(this);
|
111 | callMethod(this, [ sbIndex, "changeType" ], [ mimeType ], 2);
|
112 | }
|
113 | get mode() {
|
114 | const sbIndex = getSourceBufferIndex(this);
|
115 | return getter(this, [ sbIndex, "mode" ]);
|
116 | }
|
117 | set mode(value) {
|
118 | const sbIndex = getSourceBufferIndex(this);
|
119 | setter(this, [ sbIndex, "mode" ], value);
|
120 | }
|
121 | remove(start, end) {
|
122 | this[SourceBufferTasksKey].push([ "remove", [ start, end ] ]);
|
123 | drainSourceBufferQueue(this);
|
124 | }
|
125 | removeEventListener(...args) {
|
126 | const sbIndex = getSourceBufferIndex(this);
|
127 | callMethod(this, [ sbIndex, "removeEventListener" ], args, 3);
|
128 | }
|
129 | get timestampOffset() {
|
130 | const sbIndex = getSourceBufferIndex(this);
|
131 | return getter(this, [ sbIndex, "timestampOffset" ]);
|
132 | }
|
133 | set timestampOffset(value) {
|
134 | const sbIndex = getSourceBufferIndex(this);
|
135 | setter(this, [ sbIndex, "timestampOffset" ], value);
|
136 | }
|
137 | get updating() {
|
138 | const sbIndex = getSourceBufferIndex(this);
|
139 | return getter(this, [ sbIndex, "updating" ]);
|
140 | }
|
141 | }
|
142 | _a = SourceBufferTasksKey;
|
143 | const drainSourceBufferQueue = sourceBuffer => {
|
144 | if (sourceBuffer[SourceBufferTasksKey].length) {
|
145 | if (!sourceBuffer.updating) {
|
146 | const task = sourceBuffer[SourceBufferTasksKey].shift();
|
147 | if (task) {
|
148 | const sbIndex = getSourceBufferIndex(sourceBuffer);
|
149 | callMethod(sourceBuffer, [ sbIndex, task[0] ], task[1], 3, void 0, task[2]);
|
150 | }
|
151 | }
|
152 | setTimeout((() => drainSourceBufferQueue(sourceBuffer)), 50);
|
153 | }
|
154 | };
|
155 | const getSourceBufferIndex = sourceBuffer => {
|
156 | if (sourceBuffer) {
|
157 | const mediaSource = sourceBuffer[MediaSourceKey];
|
158 | const sourceBufferList = mediaSource[SourceBuffersKey];
|
159 | return sourceBufferList.indexOf(sourceBuffer);
|
160 | }
|
161 | return -1;
|
162 | };
|
163 | defineCstr("SourceBufferList", SourceBufferList);
|
164 | defineCstr("SourceBuffer", SourceBuffer);
|
165 | const isStaticTypeSupported = new Map;
|
166 | const HTMLVideoDescriptorMap = {
|
167 | currentTime: {
|
168 | get: () => 0
|
169 | },
|
170 | playbackRate: {
|
171 | get: () => 1
|
172 | }
|
173 | };
|
174 | definePrototypePropertyDescriptor(self.HTMLVideoElement, HTMLVideoDescriptorMap);
|
175 | const createContext2D = (canvasInstance, contextType, contextAttributes) => {
|
176 | const winId = canvasInstance[WinIdKey];
|
177 | const ctxInstanceId = randomId();
|
178 | const ctxInstance = {
|
179 | [WinIdKey]: winId,
|
180 | [InstanceIdKey]: ctxInstanceId,
|
181 | [ApplyPathKey]: []
|
182 | };
|
183 | const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId);
|
184 | const CanvasRenderingContext2D = {
|
185 | get: (target, propName) => "string" == typeof propName && propName in ctx ? "function" == typeof ctx[propName] ? (...args) => {
|
186 | if (propName.startsWith("create")) {
|
187 | const instanceId = randomId();
|
188 | callMethod(ctxInstance, [ propName ], args, 2, instanceId);
|
189 | if ("createImageData" === propName || "createPattern" === propName) {
|
190 | (api => {
|
191 | console.warn(`${api} not implemented`);
|
192 | })(`${propName}()`);
|
193 | return {
|
194 | setTransform: () => {}
|
195 | };
|
196 | }
|
197 | return new CanvasGradient(winId, instanceId);
|
198 | }
|
199 | const methodCallType = ctx2dGetterMethods.includes(propName) ? 1 : 2;
|
200 | return callMethod(ctxInstance, [ propName ], args, methodCallType);
|
201 | } : ctx[propName] : target[propName],
|
202 | set(target, propName, value) {
|
203 | if ("string" == typeof propName && propName in ctx) {
|
204 | ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value);
|
205 | ctx[propName] = value;
|
206 | } else {
|
207 | target[propName] = value;
|
208 | }
|
209 | return true;
|
210 | }
|
211 | };
|
212 | return new Proxy(ctx, CanvasRenderingContext2D);
|
213 | };
|
214 | const CanvasGradient = class {
|
215 | constructor(winId, instanceId) {
|
216 | this[WinIdKey] = winId;
|
217 | this[InstanceIdKey] = instanceId;
|
218 | this[ApplyPathKey] = [];
|
219 | }
|
220 | addColorStop(...args) {
|
221 | callMethod(this, [ "addColorStop" ], args, 2);
|
222 | }
|
223 | };
|
224 | defineCstr("CanvasGradient", CanvasGradient);
|
225 | defineCstr("CanvasPattern", CanvasPattern);
|
226 | const ctx2dGetterMethods = "getContextAttributes,getImageData,getLineDash,getTransform,isPointInPath,isPointInStroke,measureText".split(",");
|
227 | const createContextWebGL = (canvasInstance, contextType, contextAttributes) => {
|
228 | const winId = canvasInstance[WinIdKey];
|
229 | const ctxInstanceId = randomId();
|
230 | const ctxInstance = {
|
231 | [WinIdKey]: winId,
|
232 | [InstanceIdKey]: ctxInstanceId,
|
233 | [ApplyPathKey]: []
|
234 | };
|
235 | const ctx = callMethod(canvasInstance, [ "getContext" ], [ contextType, contextAttributes ], 1, ctxInstanceId);
|
236 | const WebGLRenderingContextHandler = {
|
237 | get: (target, propName) => "string" == typeof propName ? "function" != typeof ctx[propName] ? ctx[propName] : (...args) => callMethod(ctxInstance, [ propName ], args, getWebGlMethodCallType(propName)) : target[propName],
|
238 | set(target, propName, value) {
|
239 | if ("string" == typeof propName && propName in ctx) {
|
240 | ctx[propName] !== value && "function" != typeof value && setter(ctxInstance, [ propName ], value);
|
241 | ctx[propName] = value;
|
242 | } else {
|
243 | target[propName] = value;
|
244 | }
|
245 | return true;
|
246 | }
|
247 | };
|
248 | return new Proxy(ctx, WebGLRenderingContextHandler);
|
249 | };
|
250 | const ctxWebGLGetterMethods = "checkFramebufferStatus,makeXRCompatible".split(",");
|
251 | const getWebGlMethodCallType = methodName => methodName.startsWith("create") || methodName.startsWith("get") || methodName.startsWith("is") || ctxWebGLGetterMethods.includes(methodName) ? 1 : 2;
|
252 | const HTMLCanvasDescriptorMap = {
|
253 | getContext: {
|
254 | value(contextType, contextAttributes) {
|
255 | this[ContextKey] || (this[ContextKey] = (contextType.includes("webgl") ? createContextWebGL : createContext2D)(this, contextType, contextAttributes));
|
256 | return this[ContextKey];
|
257 | }
|
258 | }
|
259 | };
|
260 | definePrototypePropertyDescriptor(self.HTMLCanvasElement, HTMLCanvasDescriptorMap);
|
261 | const MediaCstrs = {
|
262 | Audio: env => defineCstrName("HTMLAudioElement", class {
|
263 | constructor(src) {
|
264 | const audio = env.$document$.createElement("audio");
|
265 | audio.src = src;
|
266 | return audio;
|
267 | }
|
268 | }),
|
269 | MediaSource: (env, win, cstrName) => {
|
270 | const winURL = win.URL = defineCstrName("URL", class extends URL {});
|
271 | winURL.createObjectURL = obj => callMethod(win, [ "URL", "createObjectURL" ], [ obj ]);
|
272 | winURL.revokeObjectURL = obj => callMethod(win, [ "URL", "revokeObjectURL" ], [ obj ]);
|
273 | return defineCstrName(cstrName, class extends WorkerEventTargetProxy {
|
274 | constructor() {
|
275 | super(env.$winId$, randomId());
|
276 | this[SourceBuffersKey] = new SourceBufferList(this);
|
277 | constructGlobal(this, cstrName, EMPTY_ARRAY);
|
278 | }
|
279 | get activeSourceBuffers() {
|
280 | return [];
|
281 | }
|
282 | addSourceBuffer(mimeType) {
|
283 | const sourceBuffer = new SourceBuffer(this);
|
284 | this[SourceBuffersKey].push(sourceBuffer);
|
285 | callMethod(this, [ "addSourceBuffer" ], [ mimeType ]);
|
286 | return sourceBuffer;
|
287 | }
|
288 | clearLiveSeekableRange() {
|
289 | callMethod(this, [ "clearLiveSeekableRange" ], EMPTY_ARRAY, 2);
|
290 | }
|
291 | get duration() {
|
292 | return getter(this, [ "duration" ]);
|
293 | }
|
294 | set duration(value) {
|
295 | setter(this, [ "duration" ], value);
|
296 | }
|
297 | endOfStream(endOfStreamError) {
|
298 | callMethod(this, [ "endOfStream" ], [ endOfStreamError ], 3);
|
299 | }
|
300 | get readyState() {
|
301 | return getter(this, [ "readyState" ]);
|
302 | }
|
303 | removeSourceBuffer(sourceBuffer) {
|
304 | const index = getSourceBufferIndex(sourceBuffer);
|
305 | if (index > -1) {
|
306 | this[SourceBuffersKey].splice(index, 1);
|
307 | callMethod(this, [ "removeSourceBuffer" ], [ index ], 1);
|
308 | }
|
309 | }
|
310 | setLiveSeekableRange(start, end) {
|
311 | callMethod(this, [ "setLiveSeekableRange" ], [ start, end ], 2);
|
312 | }
|
313 | get sourceBuffers() {
|
314 | return this[SourceBuffersKey];
|
315 | }
|
316 | static isTypeSupported(mimeType) {
|
317 | if (!isStaticTypeSupported.has(mimeType)) {
|
318 | const isSupported = callMethod(win, [ cstrName, "isTypeSupported" ], [ mimeType ]);
|
319 | isStaticTypeSupported.set(mimeType, isSupported);
|
320 | }
|
321 | return isStaticTypeSupported.get(mimeType);
|
322 | }
|
323 | });
|
324 | }
|
325 | };
|
326 | self.ptm = MediaCstrs;
|
327 | })(self);
|