diff --git a/src/lib/web-worker/media/audio-track.ts b/src/lib/web-worker/media/audio-track.ts new file mode 100644 index 00000000..82d48739 --- /dev/null +++ b/src/lib/web-worker/media/audio-track.ts @@ -0,0 +1,82 @@ +import { callMethod, getter, InstanceIdKey, setter, WinIdKey, WorkerProxy } from './bridge'; +import { CallType } from '../../types'; +import { defineCstr } from './utils'; +import { SourceBuffer } from './source-buffer'; + +export const AudioTrackList = class { + constructor(mediaElm: any) { + const audioTracks = 'audioTracks'; + const winId = mediaElm[WinIdKey]; + const instanceId = mediaElm[InstanceIdKey]; + + const instance = { + addEventListener(...args: any[]) { + callMethod( + mediaElm, + [audioTracks, 'addEventListener'], + args, + CallType.NonBlockingNoSideEffect + ); + }, + getTrackById(...args: any[]) { + return callMethod(mediaElm, [audioTracks, 'getTrackById'], args); + }, + get length(): number { + return getter(mediaElm, [audioTracks, 'length']); + }, + removeEventListener(...args: any[]) { + callMethod( + mediaElm, + [audioTracks, 'removeEventListener'], + args, + CallType.NonBlockingNoSideEffect + ); + }, + }; + + return new Proxy(instance, { + get(target: any, propName) { + if (typeof propName === 'number') { + return new AudioTrack(winId, instanceId, [audioTracks, propName]); + } + return target[propName]; + }, + }) as any; + } +}; + +const AudioTrack = class extends WorkerProxy { + get enabled() { + return getter(this, ['enabled']); + } + set enabled(value: any) { + setter(this, ['enabled'], value); + } + + get id() { + return getter(this, ['id']); + } + + get kind() { + return getter(this, ['kind']); + } + + get label() { + return getter(this, ['label']); + } + + get language() { + return getter(this, ['language']); + } + + get sourceBuffer() { + return new SourceBuffer(this); + } +}; + +export const hasAudioTracks = 'audioTracks' in (self.HTMLMediaElement.prototype as any); +if (hasAudioTracks) { + // not all browsers have audioTracks, only add if it's found + defineCstr('AudioTrackList', AudioTrackList); + defineCstr('AudioTrack', AudioTrack); +} diff --git a/src/lib/web-worker/media/media-element.ts b/src/lib/web-worker/media/media-element.ts index 350d7719..c3e8b008 100644 --- a/src/lib/web-worker/media/media-element.ts +++ b/src/lib/web-worker/media/media-element.ts @@ -1,8 +1,9 @@ +import { AudioTrackList, hasAudioTracks } from './audio-track'; import { definePrototypePropertyDescriptor, getter, InstanceIdKey, WinIdKey } from './bridge'; import { ReadyStateKey, TimeRangesKey } from './utils'; import { TimeRanges } from './time-ranges'; -export const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType = { +const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType = { buffered: { get() { if (!this[TimeRangesKey]) { @@ -15,6 +16,7 @@ export const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType = { return this[TimeRangesKey]; }, }, + readyState: { get() { if (this[ReadyStateKey] === 4) { @@ -32,4 +34,12 @@ export const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType = { }, }; +if (hasAudioTracks) { + HTMLMediaDescriptorMap.audioTracks = { + get(this: any) { + return new AudioTrackList(this); + }, + }; +} + definePrototypePropertyDescriptor(self.HTMLMediaElement, HTMLMediaDescriptorMap); diff --git a/src/lib/web-worker/media/media-source.ts b/src/lib/web-worker/media/media-source.ts index f1b8f5d9..6bf3a6f6 100644 --- a/src/lib/web-worker/media/media-source.ts +++ b/src/lib/web-worker/media/media-source.ts @@ -2,22 +2,13 @@ import { callMethod, constructGlobal, getter, - InstanceIdKey, randomId, setter, - WinIdKey, WorkerEventTargetProxy, } from './bridge'; import { CallType, InitLazyMediaConstructor } from '../../types'; -import { - defineCstr, - defineCstrName, - EMPTY_ARRAY, - MediaSourceKey, - SourceBuffersKey, - SourceBufferTasksKey, -} from './utils'; -import { TimeRanges } from './time-ranges'; +import { defineCstrName, EMPTY_ARRAY, SourceBuffersKey } from './utils'; +import { getSourceBufferIndex, SourceBuffer, SourceBufferList } from './source-buffer'; /** https://developer.mozilla.org/en-US/docs/Web/API/MediaSource */ export const createMediaSourceConstructor: InitLazyMediaConstructor = (env, win, cstrName) => { @@ -94,158 +85,4 @@ export const createMediaSourceConstructor: InitLazyMediaConstructor = (env, win, return defineCstrName(cstrName, MediaSource); }; -class SourceBufferList extends Array { - [MediaSourceKey]: MediaSource; - - constructor(mediaSource: MediaSource) { - super(); - this[MediaSourceKey] = mediaSource; - } - - addEventListener(...args: any[]) { - callMethod( - this[MediaSourceKey], - ['sourceBuffers', 'addEventListener'], - args, - CallType.NonBlockingNoSideEffect - ); - } - - removeEventListener(...args: any[]) { - callMethod( - this[MediaSourceKey], - ['sourceBuffers', 'removeEventListener'], - args, - CallType.NonBlockingNoSideEffect - ); - } -} - -class SourceBuffer extends WorkerEventTargetProxy { - [MediaSourceKey]: any; - [SourceBufferTasksKey]: SourceBufferTask[] = []; - - constructor(mediaSource: any) { - super(mediaSource[WinIdKey], mediaSource[InstanceIdKey], ['sourceBuffers']); - this[MediaSourceKey] = mediaSource; - } - - abort() { - const sbIndex = getSourceBufferIndex(this); - callMethod(this, [sbIndex, 'appendWindowStart'], EMPTY_ARRAY, CallType.Blocking); - } - - addEventListener(...args: any[]) { - const sbIndex = getSourceBufferIndex(this); - callMethod(this, [sbIndex, 'addEventListener'], args, CallType.NonBlockingNoSideEffect); - } - - appendBuffer(buf: ArrayBuffer | ArrayBufferView) { - this[SourceBufferTasksKey].push(['appendBuffer', [buf], buf]); - drainSourceBufferQueue(this); - } - - get appendWindowStart() { - const sbIndex = getSourceBufferIndex(this); - return getter(this, [sbIndex, 'appendWindowStart']); - } - set appendWindowStart(value: number) { - const sbIndex = getSourceBufferIndex(this); - setter(this, [sbIndex, 'appendWindowStart'], value); - } - - get appendWindowEnd() { - const sbIndex = getSourceBufferIndex(this); - return getter(this, [sbIndex, 'appendWindowEnd']); - } - set appendWindowEnd(value: number) { - const sbIndex = getSourceBufferIndex(this); - setter(this, [sbIndex, 'appendWindowEnd'], value); - } - - get buffered() { - const mediaSource = this[MediaSourceKey]; - const sbIndex = getSourceBufferIndex(this); - const timeRanges = new TimeRanges(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ - 'sourceBuffers', - sbIndex, - 'buffered', - ]); - return timeRanges; - } - - changeType(mimeType: string) { - const sbIndex = getSourceBufferIndex(this); - callMethod(this, [sbIndex, 'changeType'], [mimeType], CallType.NonBlocking); - } - - get mode() { - const sbIndex = getSourceBufferIndex(this); - return getter(this, [sbIndex, 'mode']); - } - set mode(value: number) { - const sbIndex = getSourceBufferIndex(this); - setter(this, [sbIndex, 'mode'], value); - } - - remove(start: number, end: number) { - this[SourceBufferTasksKey].push(['remove', [start, end]]); - drainSourceBufferQueue(this); - } - - removeEventListener(...args: any[]) { - const sbIndex = getSourceBufferIndex(this); - callMethod(this, [sbIndex, 'removeEventListener'], args, CallType.NonBlockingNoSideEffect); - } - - get timestampOffset() { - const sbIndex = getSourceBufferIndex(this); - return getter(this, [sbIndex, 'timestampOffset']); - } - set timestampOffset(value: number) { - const sbIndex = getSourceBufferIndex(this); - setter(this, [sbIndex, 'timestampOffset'], value); - } - - get updating() { - const sbIndex = getSourceBufferIndex(this); - return getter(this, [sbIndex, 'updating']); - } -} - -const drainSourceBufferQueue = (sourceBuffer: SourceBuffer) => { - if (sourceBuffer[SourceBufferTasksKey].length) { - if (!sourceBuffer.updating) { - const task = sourceBuffer[SourceBufferTasksKey].shift(); - - if (task) { - const sbIndex = getSourceBufferIndex(sourceBuffer); - callMethod( - sourceBuffer, - [sbIndex, task[0]], - task[1], - CallType.NonBlockingNoSideEffect, - undefined, - task[2] - ); - } - } - setTimeout(() => drainSourceBufferQueue(sourceBuffer), 50); - } -}; - -const getSourceBufferIndex = (sourceBuffer: SourceBuffer) => { - if (sourceBuffer) { - const mediaSource = sourceBuffer[MediaSourceKey]; - const sourceBufferList: SourceBufferList = mediaSource[SourceBuffersKey]; - return sourceBufferList.indexOf(sourceBuffer); - } - return -1; -}; - -defineCstr('SourceBufferList', SourceBufferList); -defineCstr('SourceBuffer', SourceBuffer); - const isStaticTypeSupported = new Map(); - -type SourceBufferTask = ['appendBuffer', any[], any] | ['remove', any[]]; diff --git a/src/lib/web-worker/media/source-buffer.ts b/src/lib/web-worker/media/source-buffer.ts new file mode 100644 index 00000000..557cb3f6 --- /dev/null +++ b/src/lib/web-worker/media/source-buffer.ts @@ -0,0 +1,171 @@ +import { + callMethod, + getter, + InstanceIdKey, + setter, + WinIdKey, + WorkerEventTargetProxy, +} from './bridge'; +import { CallType } from '../../types'; +import { + defineCstr, + EMPTY_ARRAY, + MediaSourceKey, + SourceBuffersKey, + SourceBufferTasksKey, +} from './utils'; +import { TimeRanges } from './time-ranges'; + +export class SourceBufferList extends Array { + [MediaSourceKey]: MediaSource; + + constructor(mediaSource: MediaSource) { + super(); + this[MediaSourceKey] = mediaSource; + } + + addEventListener(...args: any[]) { + callMethod( + this[MediaSourceKey], + ['sourceBuffers', 'addEventListener'], + args, + CallType.NonBlockingNoSideEffect + ); + } + + removeEventListener(...args: any[]) { + callMethod( + this[MediaSourceKey], + ['sourceBuffers', 'removeEventListener'], + args, + CallType.NonBlockingNoSideEffect + ); + } +} + +export class SourceBuffer extends WorkerEventTargetProxy { + [MediaSourceKey]: any; + [SourceBufferTasksKey]: SourceBufferTask[] = []; + + constructor(mediaSource: any) { + super(mediaSource[WinIdKey], mediaSource[InstanceIdKey], ['sourceBuffers']); + this[MediaSourceKey] = mediaSource; + } + + abort() { + const sbIndex = getSourceBufferIndex(this); + callMethod(this, [sbIndex, 'appendWindowStart'], EMPTY_ARRAY, CallType.Blocking); + } + + addEventListener(...args: any[]) { + const sbIndex = getSourceBufferIndex(this); + callMethod(this, [sbIndex, 'addEventListener'], args, CallType.NonBlockingNoSideEffect); + } + + appendBuffer(buf: ArrayBuffer | ArrayBufferView) { + this[SourceBufferTasksKey].push(['appendBuffer', [buf], buf]); + drainSourceBufferQueue(this); + } + + get appendWindowStart() { + const sbIndex = getSourceBufferIndex(this); + return getter(this, [sbIndex, 'appendWindowStart']); + } + set appendWindowStart(value: number) { + const sbIndex = getSourceBufferIndex(this); + setter(this, [sbIndex, 'appendWindowStart'], value); + } + + get appendWindowEnd() { + const sbIndex = getSourceBufferIndex(this); + return getter(this, [sbIndex, 'appendWindowEnd']); + } + set appendWindowEnd(value: number) { + const sbIndex = getSourceBufferIndex(this); + setter(this, [sbIndex, 'appendWindowEnd'], value); + } + + get buffered() { + const mediaSource = this[MediaSourceKey]; + const sbIndex = getSourceBufferIndex(this); + const timeRanges = new TimeRanges(mediaSource[WinIdKey], mediaSource[InstanceIdKey], [ + 'sourceBuffers', + sbIndex, + 'buffered', + ]); + return timeRanges; + } + + changeType(mimeType: string) { + const sbIndex = getSourceBufferIndex(this); + callMethod(this, [sbIndex, 'changeType'], [mimeType], CallType.NonBlocking); + } + + get mode() { + const sbIndex = getSourceBufferIndex(this); + return getter(this, [sbIndex, 'mode']); + } + set mode(value: number) { + const sbIndex = getSourceBufferIndex(this); + setter(this, [sbIndex, 'mode'], value); + } + + remove(start: number, end: number) { + this[SourceBufferTasksKey].push(['remove', [start, end]]); + drainSourceBufferQueue(this); + } + + removeEventListener(...args: any[]) { + const sbIndex = getSourceBufferIndex(this); + callMethod(this, [sbIndex, 'removeEventListener'], args, CallType.NonBlockingNoSideEffect); + } + + get timestampOffset() { + const sbIndex = getSourceBufferIndex(this); + return getter(this, [sbIndex, 'timestampOffset']); + } + set timestampOffset(value: number) { + const sbIndex = getSourceBufferIndex(this); + setter(this, [sbIndex, 'timestampOffset'], value); + } + + get updating() { + const sbIndex = getSourceBufferIndex(this); + return getter(this, [sbIndex, 'updating']); + } +} + +const drainSourceBufferQueue = (sourceBuffer: SourceBuffer) => { + if (sourceBuffer[SourceBufferTasksKey].length) { + if (!sourceBuffer.updating) { + const task = sourceBuffer[SourceBufferTasksKey].shift(); + + if (task) { + const sbIndex = getSourceBufferIndex(sourceBuffer); + callMethod( + sourceBuffer, + [sbIndex, task[0]], + task[1], + CallType.NonBlockingNoSideEffect, + undefined, + task[2] + ); + } + } + setTimeout(() => drainSourceBufferQueue(sourceBuffer), 50); + } +}; + +export const getSourceBufferIndex = (sourceBuffer: SourceBuffer) => { + if (sourceBuffer) { + const mediaSource = sourceBuffer[MediaSourceKey]; + const sourceBufferList: SourceBufferList = mediaSource[SourceBuffersKey]; + return sourceBufferList.indexOf(sourceBuffer); + } + return -1; +}; + +defineCstr('SourceBufferList', SourceBufferList); +defineCstr('SourceBuffer', SourceBuffer); + +type SourceBufferTask = ['appendBuffer', any[], any] | ['remove', any[]]; diff --git a/src/lib/web-worker/media/video.ts b/src/lib/web-worker/media/video.ts deleted file mode 100644 index 301fec7b..00000000 --- a/src/lib/web-worker/media/video.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { definePrototypePropertyDescriptor } from './bridge'; - -export const HTMLVideoDescriptorMap: PropertyDescriptorMap & ThisType = {}; - -definePrototypePropertyDescriptor(self.HTMLVideoElement, HTMLVideoDescriptorMap);