Skip to content

Commit

Permalink
feat: media audioTracks
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Jan 20, 2022
1 parent c57874e commit a39e07b
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 171 deletions.
82 changes: 82 additions & 0 deletions src/lib/web-worker/media/audio-track.ts
Original file line number Diff line number Diff line change
@@ -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);
}
12 changes: 11 additions & 1 deletion src/lib/web-worker/media/media-element.ts
Original file line number Diff line number Diff line change
@@ -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<any> = {
const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType<any> = {
buffered: {
get() {
if (!this[TimeRangesKey]) {
Expand All @@ -15,6 +16,7 @@ export const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType<any> = {
return this[TimeRangesKey];
},
},

readyState: {
get() {
if (this[ReadyStateKey] === 4) {
Expand All @@ -32,4 +34,12 @@ export const HTMLMediaDescriptorMap: PropertyDescriptorMap & ThisType<any> = {
},
};

if (hasAudioTracks) {
HTMLMediaDescriptorMap.audioTracks = {
get(this: any) {
return new AudioTrackList(this);
},
};
}

definePrototypePropertyDescriptor(self.HTMLMediaElement, HTMLMediaDescriptorMap);
167 changes: 2 additions & 165 deletions src/lib/web-worker/media/media-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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<string, boolean>();

type SourceBufferTask = ['appendBuffer', any[], any] | ['remove', any[]];
Loading

0 comments on commit a39e07b

Please sign in to comment.