media-tracks
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -1,40 +0,79 @@ | ||
import { audioRenditionToList } from "./audio-rendition.js"; | ||
import { RenditionEvent } from "./rendition-event.js"; | ||
import { getPrivate } from "./utils.js"; | ||
function addRendition(track, rendition) { | ||
const renditionList = getPrivate(track).media.audioRenditions; | ||
if (!getPrivate(rendition).list) { | ||
getPrivate(rendition).list = renditionList; | ||
getPrivate(rendition).track = track; | ||
} | ||
const { collection } = getPrivate(renditionList); | ||
collection.add(rendition); | ||
const index = collection.size - 1; | ||
if (!(index in AudioRenditionList.prototype)) { | ||
Object.defineProperty(AudioRenditionList.prototype, index, { | ||
get() { | ||
return getCurrentRenditions(this)[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
if (!track.enabled) | ||
return; | ||
renditionList.dispatchEvent(new RenditionEvent("addrendition", { rendition })); | ||
}); | ||
} | ||
function removeRendition(rendition) { | ||
const renditionList = getPrivate(rendition).list; | ||
const collection = getPrivate(renditionList).collection; | ||
collection.delete(rendition); | ||
queueMicrotask(() => { | ||
const track = getPrivate(rendition).track; | ||
if (!track.enabled) | ||
return; | ||
renditionList.dispatchEvent(new RenditionEvent("removerendition", { rendition })); | ||
}); | ||
} | ||
function selectedChanged(rendition) { | ||
const renditionList = getPrivate(rendition).list; | ||
if (!renditionList || getPrivate(renditionList).changeRequested) | ||
return; | ||
getPrivate(renditionList).changeRequested = true; | ||
queueMicrotask(() => { | ||
delete getPrivate(renditionList).changeRequested; | ||
const track = getPrivate(rendition).track; | ||
if (!track.enabled) | ||
return; | ||
renditionList.dispatchEvent(new Event("change")); | ||
}); | ||
} | ||
function getCurrentRenditions(renditionList) { | ||
return [...getPrivate(renditionList).collection].filter((rendition) => { | ||
return getPrivate(rendition).track.enabled; | ||
}); | ||
} | ||
class AudioRenditionList extends EventTarget { | ||
#renditions = []; | ||
#addRenditionCallback; | ||
#removeRenditionCallback; | ||
#changeCallback; | ||
constructor() { | ||
super(); | ||
getPrivate(this).collection = /* @__PURE__ */ new Set(); | ||
} | ||
[Symbol.iterator]() { | ||
return this.#renditions.values(); | ||
return getCurrentRenditions(this).values(); | ||
} | ||
get length() { | ||
return this.#renditions.length; | ||
return getCurrentRenditions(this).length; | ||
} | ||
add(rendition) { | ||
audioRenditionToList.set(rendition, this); | ||
const length = this.#renditions.push(rendition); | ||
const index = length - 1; | ||
if (!(index in AudioRenditionList.prototype)) { | ||
Object.defineProperty(AudioRenditionList.prototype, index, { | ||
get() { | ||
return this.#renditions[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
this.dispatchEvent(new RenditionEvent("addrendition", { rendition })); | ||
}); | ||
getRenditionById(id) { | ||
return getCurrentRenditions(this).find((rendition) => `${rendition.id}` === `${id}`) ?? null; | ||
} | ||
remove(rendition) { | ||
audioRenditionToList.delete(rendition); | ||
this.#renditions.splice(this.#renditions.indexOf(rendition), 1); | ||
this.dispatchEvent(new RenditionEvent("removerendition", { rendition })); | ||
get selectedIndex() { | ||
return getCurrentRenditions(this).findIndex((rendition) => rendition.selected); | ||
} | ||
getRenditionById(id) { | ||
return this.#renditions.find((rendition) => `${rendition.id}` === `${id}`) ?? null; | ||
set selectedIndex(index) { | ||
for (const [i, rendition] of getCurrentRenditions(this).entries()) { | ||
rendition.selected = i === index; | ||
} | ||
} | ||
get activeIndex() { | ||
return this.#renditions.findIndex((rendition) => rendition.active); | ||
} | ||
get onaddrendition() { | ||
@@ -58,6 +97,3 @@ return this.#addRenditionCallback; | ||
if (this.#removeRenditionCallback) { | ||
this.removeEventListener( | ||
"removerendition", | ||
this.#removeRenditionCallback | ||
); | ||
this.removeEventListener("removerendition", this.#removeRenditionCallback); | ||
this.#removeRenditionCallback = void 0; | ||
@@ -85,3 +121,6 @@ } | ||
export { | ||
AudioRenditionList | ||
AudioRenditionList, | ||
addRendition, | ||
removeRendition, | ||
selectedChanged | ||
}; |
@@ -1,3 +0,2 @@ | ||
const audioRenditionToList = /* @__PURE__ */ new Map(); | ||
const changeRequested = /* @__PURE__ */ new Map(); | ||
import { selectedChanged } from "./audio-rendition-list.js"; | ||
class AudioRendition { | ||
@@ -8,47 +7,15 @@ src; | ||
codec; | ||
#enabled = false; | ||
#active = false; | ||
get enabled() { | ||
return this.#enabled; | ||
#selected = false; | ||
get selected() { | ||
return this.#selected; | ||
} | ||
set enabled(val) { | ||
if (this.#enabled === val) | ||
set selected(val) { | ||
if (this.#selected === val) | ||
return; | ||
this.#enabled = val; | ||
const renditionList = audioRenditionToList.get(this); | ||
if (!renditionList || changeRequested.get(renditionList)) | ||
return; | ||
changeRequested.set(renditionList, true); | ||
queueMicrotask(() => { | ||
changeRequested.delete(renditionList); | ||
renditionList.dispatchEvent(new Event("change")); | ||
}); | ||
this.#selected = val; | ||
selectedChanged(this); | ||
} | ||
get active() { | ||
return this.#active; | ||
} | ||
set active(val) { | ||
if (this.#active === val) | ||
return; | ||
this.#active = val; | ||
if (val !== true) | ||
return; | ||
const renditionList = audioRenditionToList.get(this) ?? []; | ||
let hasInactivated = false; | ||
for (const rendition of renditionList) { | ||
if (rendition === this) | ||
continue; | ||
rendition.active = false; | ||
hasInactivated = true; | ||
} | ||
if (hasInactivated) { | ||
queueMicrotask(() => { | ||
renditionList.dispatchEvent(new Event("renditionchange")); | ||
}); | ||
} | ||
} | ||
} | ||
export { | ||
AudioRendition, | ||
audioRenditionToList | ||
AudioRendition | ||
}; |
@@ -1,8 +0,52 @@ | ||
import { audioTrackToList } from "./audio-track.js"; | ||
import { TrackEvent } from "./track-event.js"; | ||
import { getPrivate } from "./utils.js"; | ||
function addAudioTrack(media, track) { | ||
const trackList = media.audioTracks; | ||
if (!getPrivate(track).list) { | ||
getPrivate(track).list = trackList; | ||
getPrivate(track).media = media; | ||
} | ||
const { collection } = getPrivate(trackList); | ||
collection.add(track); | ||
const index = collection.size - 1; | ||
if (!(index in AudioTrackList.prototype)) { | ||
Object.defineProperty(AudioTrackList.prototype, index, { | ||
get() { | ||
return [...collection][index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
trackList.dispatchEvent(new TrackEvent("addtrack", { track })); | ||
}); | ||
} | ||
function removeAudioTrack(track) { | ||
const trackList = getPrivate(track).list; | ||
const collection = getPrivate(trackList).collection; | ||
collection.delete(track); | ||
queueMicrotask(() => { | ||
trackList.dispatchEvent(new TrackEvent("removetrack", { track })); | ||
}); | ||
} | ||
function enabledChanged(track) { | ||
const trackList = getPrivate(track).list; | ||
if (!trackList || getPrivate(trackList).changeRequested) | ||
return; | ||
getPrivate(trackList).changeRequested = true; | ||
queueMicrotask(() => { | ||
delete getPrivate(trackList).changeRequested; | ||
trackList.dispatchEvent(new Event("change")); | ||
}); | ||
} | ||
class AudioTrackList extends EventTarget { | ||
#tracks = []; | ||
#addTrackCallback; | ||
#removeTrackCallback; | ||
#changeCallback; | ||
constructor() { | ||
super(); | ||
getPrivate(this).collection = /* @__PURE__ */ new Set(); | ||
} | ||
get #tracks() { | ||
return getPrivate(this).collection; | ||
} | ||
[Symbol.iterator]() { | ||
@@ -12,26 +56,6 @@ return this.#tracks.values(); | ||
get length() { | ||
return this.#tracks.length; | ||
return this.#tracks.size; | ||
} | ||
add(track) { | ||
audioTrackToList.set(track, this); | ||
const length = this.#tracks.push(track); | ||
const index = length - 1; | ||
if (!(index in AudioTrackList.prototype)) { | ||
Object.defineProperty(AudioTrackList.prototype, index, { | ||
get() { | ||
return this.#tracks[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
this.dispatchEvent(new TrackEvent("addtrack", { track })); | ||
}); | ||
} | ||
remove(track) { | ||
audioTrackToList.delete(track); | ||
this.#tracks.splice(this.#tracks.indexOf(track), 1); | ||
this.dispatchEvent(new TrackEvent("removetrack", { track })); | ||
} | ||
getTrackById(id) { | ||
return this.#tracks.find((track) => track.id === id) ?? null; | ||
return [...this.#tracks].find((track) => track.id === id) ?? null; | ||
} | ||
@@ -79,3 +103,6 @@ get onaddtrack() { | ||
export { | ||
AudioTrackList | ||
AudioTrackList, | ||
addAudioTrack, | ||
enabledChanged, | ||
removeAudioTrack | ||
}; |
import { AudioRendition } from "./audio-rendition.js"; | ||
import { AudioRenditionList } from "./audio-rendition-list.js"; | ||
const audioTrackToList = /* @__PURE__ */ new Map(); | ||
const changeRequested = /* @__PURE__ */ new Map(); | ||
import { enabledChanged } from "./audio-track-list.js"; | ||
import { addRendition, removeRendition } from "./audio-rendition-list.js"; | ||
const AudioTrackKind = { | ||
@@ -20,3 +19,2 @@ alternative: "alternative", | ||
#enabled = false; | ||
#renditions = new AudioRenditionList(); | ||
addRendition(src, codec, bitrate) { | ||
@@ -27,7 +25,7 @@ const rendition = new AudioRendition(); | ||
rendition.bitrate = bitrate; | ||
this.#renditions.add(rendition); | ||
addRendition(this, rendition); | ||
return rendition; | ||
} | ||
get renditions() { | ||
return this.#renditions; | ||
removeRendition(rendition) { | ||
removeRendition(rendition); | ||
} | ||
@@ -41,10 +39,3 @@ get enabled() { | ||
this.#enabled = val; | ||
const trackList = audioTrackToList.get(this); | ||
if (!trackList || changeRequested.get(trackList)) | ||
return; | ||
changeRequested.set(trackList, true); | ||
queueMicrotask(() => { | ||
changeRequested.delete(trackList); | ||
trackList.dispatchEvent(new Event("change")); | ||
}); | ||
enabledChanged(this); | ||
} | ||
@@ -54,4 +45,3 @@ } | ||
AudioTrack, | ||
AudioTrackKind, | ||
audioTrackToList | ||
AudioTrackKind | ||
}; |
export * from "./mixin.js"; | ||
export * from "./video-track.js"; | ||
export * from "./video-track-list.js"; | ||
export * from "./video-rendition.js"; | ||
export * from "./video-rendition-list.js"; | ||
export * from "./audio-track.js"; | ||
export * from "./audio-track-list.js"; | ||
export * from "./audio-rendition.js"; | ||
export * from "./audio-rendition-list.js"; | ||
export * from "./track-event.js"; | ||
export * from "./rendition-event.js"; | ||
import { VideoTrack } from "./video-track.js"; | ||
import { VideoTrackList } from "./video-track-list.js"; | ||
import { VideoRendition } from "./video-rendition.js"; | ||
import { VideoRenditionList } from "./video-rendition-list.js"; | ||
import { AudioTrack } from "./audio-track.js"; | ||
import { AudioTrackList } from "./audio-track-list.js"; | ||
import { AudioRendition } from "./audio-rendition.js"; | ||
import { AudioRenditionList } from "./audio-rendition-list.js"; | ||
import { TrackEvent } from "./track-event.js"; | ||
import { RenditionEvent } from "./rendition-event.js"; | ||
export { | ||
AudioRendition, | ||
AudioRenditionList, | ||
AudioTrack, | ||
AudioTrackList, | ||
RenditionEvent, | ||
TrackEvent, | ||
VideoRendition, | ||
VideoRenditionList, | ||
VideoTrack, | ||
VideoTrackList | ||
}; |
import { VideoTrack } from "./video-track.js"; | ||
import { VideoTrackList } from "./video-track-list.js"; | ||
import { VideoTrackList, addVideoTrack, removeVideoTrack } from "./video-track-list.js"; | ||
import { AudioTrack } from "./audio-track.js"; | ||
import { AudioTrackList } from "./audio-track-list.js"; | ||
import { AudioTrackList, addAudioTrack, removeAudioTrack } from "./audio-track-list.js"; | ||
import { VideoRenditionList } from "./video-rendition-list.js"; | ||
import { AudioRenditionList } from "./audio-rendition-list.js"; | ||
import { getPrivate } from "./utils.js"; | ||
function MediaTracksMixin(MediaElementClass) { | ||
@@ -35,3 +36,2 @@ if (!MediaElementClass?.prototype) | ||
MediaElementClass.prototype.addVideoTrack = function(kind, label = "", language = "") { | ||
const videoTrackList = initVideoTrackList(this); | ||
const track = new VideoTrack(); | ||
@@ -41,9 +41,11 @@ track.kind = kind; | ||
track.language = language; | ||
videoTrackList.add(track); | ||
addVideoTrack(this, track); | ||
return track; | ||
}; | ||
} | ||
if (!("removeVideoTrack" in MediaElementClass.prototype)) { | ||
MediaElementClass.prototype.removeVideoTrack = removeVideoTrack; | ||
} | ||
if (!("addAudioTrack" in MediaElementClass.prototype)) { | ||
MediaElementClass.prototype.addAudioTrack = function(kind, label = "", language = "") { | ||
const audioTrackList = initAudioTrackList(this); | ||
const track = new AudioTrack(); | ||
@@ -53,19 +55,18 @@ track.kind = kind; | ||
track.language = language; | ||
audioTrackList.add(track); | ||
addAudioTrack(this, track); | ||
return track; | ||
}; | ||
} | ||
const videoTrackLists = /* @__PURE__ */ new WeakMap(); | ||
const audioTrackLists = /* @__PURE__ */ new WeakMap(); | ||
const videoRenditionLists = /* @__PURE__ */ new WeakMap(); | ||
const audioRenditionLists = /* @__PURE__ */ new WeakMap(); | ||
if (!("removeAudioTrack" in MediaElementClass.prototype)) { | ||
MediaElementClass.prototype.removeAudioTrack = removeAudioTrack; | ||
} | ||
const initVideoTrackList = (media) => { | ||
let tracks = videoTrackLists.get(media); | ||
let tracks = getPrivate(media).videoTracks; | ||
if (!tracks) { | ||
tracks = new VideoTrackList(); | ||
videoTrackLists.set(media, tracks); | ||
getPrivate(media).videoTracks = tracks; | ||
if (getNativeVideoTracks) { | ||
const nativeTracks = getNativeVideoTracks.call(media); | ||
for (const nativeTrack of nativeTracks) { | ||
tracks.add(nativeTrack); | ||
addVideoTrack(media, nativeTrack); | ||
} | ||
@@ -77,7 +78,7 @@ nativeTracks.addEventListener("change", () => { | ||
if (![...tracks].some((t) => t instanceof VideoTrack)) { | ||
tracks.add(event.track); | ||
addVideoTrack(media, event.track); | ||
} | ||
}); | ||
nativeTracks.addEventListener("removetrack", (event) => { | ||
tracks.remove(event.track); | ||
removeVideoTrack(event.track); | ||
}); | ||
@@ -89,10 +90,10 @@ } | ||
const initAudioTrackList = (media) => { | ||
let tracks = audioTrackLists.get(media); | ||
let tracks = getPrivate(media).audioTracks; | ||
if (!tracks) { | ||
tracks = new AudioTrackList(); | ||
audioTrackLists.set(media, tracks); | ||
getPrivate(media).audioTracks = tracks; | ||
if (getNativeAudioTracks) { | ||
const nativeTracks = getNativeAudioTracks.call(media); | ||
for (const nativeTrack of nativeTracks) { | ||
tracks.add(nativeTrack); | ||
addAudioTrack(media, nativeTrack); | ||
} | ||
@@ -104,7 +105,7 @@ nativeTracks.addEventListener("change", () => { | ||
if (![...tracks].some((t) => t instanceof AudioTrack)) { | ||
tracks.add(event.track); | ||
addAudioTrack(media, event.track); | ||
} | ||
}); | ||
nativeTracks.addEventListener("removetrack", (event) => { | ||
tracks.remove(event.track); | ||
removeAudioTrack(event.track); | ||
}); | ||
@@ -115,29 +116,29 @@ } | ||
}; | ||
if (globalThis.VideoTrack && !("renditions" in globalThis.VideoTrack.prototype)) { | ||
Object.defineProperty(globalThis.VideoTrack.prototype, "renditions", { | ||
if (!("videoRenditions" in MediaElementClass.prototype)) { | ||
Object.defineProperty(MediaElementClass.prototype, "videoRenditions", { | ||
get() { | ||
return initVideoRenditionList(this); | ||
return initVideoRenditions(this); | ||
} | ||
}); | ||
} | ||
if (globalThis.AudioTrack && !("renditions" in globalThis.AudioTrack.prototype)) { | ||
Object.defineProperty(globalThis.AudioTrack.prototype, "renditions", { | ||
get() { | ||
return initAudioRenditionList(this); | ||
} | ||
}); | ||
} | ||
const initVideoRenditionList = (track) => { | ||
let renditions = videoRenditionLists.get(track); | ||
const initVideoRenditions = (media) => { | ||
let renditions = getPrivate(media).videoRenditions; | ||
if (!renditions) { | ||
renditions = new VideoRenditionList(); | ||
videoRenditionLists.set(track, renditions); | ||
getPrivate(media).videoRenditions = renditions; | ||
} | ||
return renditions; | ||
}; | ||
const initAudioRenditionList = (track) => { | ||
let renditions = audioRenditionLists.get(track); | ||
if (!("audioRenditions" in MediaElementClass.prototype)) { | ||
Object.defineProperty(MediaElementClass.prototype, "audioRenditions", { | ||
get() { | ||
return initAudioRenditions(this); | ||
} | ||
}); | ||
} | ||
const initAudioRenditions = (media) => { | ||
let renditions = getPrivate(media).audioRenditions; | ||
if (!renditions) { | ||
renditions = new AudioRenditionList(); | ||
audioRenditionLists.set(track, renditions); | ||
getPrivate(media).audioRenditions = renditions; | ||
} | ||
@@ -144,0 +145,0 @@ return renditions; |
@@ -1,17 +0,29 @@ | ||
import { AudioRendition } from './audio-rendition.js'; | ||
import type { AudioTrack } from './audio-track.js'; | ||
import type { AudioRendition } from './audio-rendition.js'; | ||
export declare function addRendition(track: AudioTrack, rendition: AudioRendition): void; | ||
export declare function removeRendition(rendition: AudioRendition): void; | ||
export declare function selectedChanged(rendition: AudioRendition): void; | ||
export declare class AudioRenditionList extends EventTarget { | ||
#private; | ||
[index: number]: AudioRendition; | ||
constructor(); | ||
[Symbol.iterator](): IterableIterator<AudioRendition>; | ||
get length(): number; | ||
add(rendition: AudioRendition): void; | ||
remove(rendition: AudioRendition): void; | ||
getRenditionById(id: string): AudioRendition | null; | ||
get activeIndex(): number; | ||
get onaddrendition(): (() => void) | undefined; | ||
set onaddrendition(callback: (() => void) | undefined); | ||
get onremoverendition(): (() => void) | undefined; | ||
set onremoverendition(callback: (() => void) | undefined); | ||
get selectedIndex(): number; | ||
set selectedIndex(index: number); | ||
get onaddrendition(): ((event?: { | ||
rendition: AudioRendition; | ||
}) => void) | undefined; | ||
set onaddrendition(callback: ((event?: { | ||
rendition: AudioRendition; | ||
}) => void) | undefined); | ||
get onremoverendition(): ((event?: { | ||
rendition: AudioRendition; | ||
}) => void) | undefined; | ||
set onremoverendition(callback: ((event?: { | ||
rendition: AudioRendition; | ||
}) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} |
@@ -1,7 +0,4 @@ | ||
export declare const audioRenditionToList: Map<any, any>; | ||
/** | ||
* - There can only be 1 rendition active in a rendition list. | ||
* - The consumer should use the `enabled` setter to select 1 or multiple | ||
* - The consumer should use the `selected` setter to select 1 or multiple | ||
* renditions that the engine is allowed to play. | ||
* - The `active` setter should be used by the media engine implementation. | ||
*/ | ||
@@ -14,6 +11,4 @@ export declare class AudioRendition { | ||
codec?: string; | ||
get enabled(): boolean; | ||
set enabled(val: boolean); | ||
get active(): boolean; | ||
set active(val: boolean); | ||
get selected(): boolean; | ||
set selected(val: boolean); | ||
} |
@@ -1,16 +0,26 @@ | ||
import { AudioTrack } from './audio-track.js'; | ||
import type { AudioTrack } from './audio-track.js'; | ||
export declare function addAudioTrack(media: HTMLMediaElement, track: AudioTrack): void; | ||
export declare function removeAudioTrack(track: AudioTrack): void; | ||
export declare function enabledChanged(track: AudioTrack): void; | ||
export declare class AudioTrackList extends EventTarget { | ||
#private; | ||
[index: number]: AudioTrack; | ||
[Symbol.iterator](): IterableIterator<AudioTrack>; | ||
get length(): number; | ||
add(track: AudioTrack): void; | ||
remove(track: AudioTrack): void; | ||
constructor(); | ||
[Symbol.iterator](): any; | ||
get length(): any; | ||
getTrackById(id: string): AudioTrack | null; | ||
get onaddtrack(): (() => void) | undefined; | ||
set onaddtrack(callback: (() => void) | undefined); | ||
get onremovetrack(): (() => void) | undefined; | ||
set onremovetrack(callback: (() => void) | undefined); | ||
get onaddtrack(): ((event?: { | ||
track: AudioTrack; | ||
}) => void) | undefined; | ||
set onaddtrack(callback: ((event?: { | ||
track: AudioTrack; | ||
}) => void) | undefined); | ||
get onremovetrack(): ((event?: { | ||
track: AudioTrack; | ||
}) => void) | undefined; | ||
set onremovetrack(callback: ((event?: { | ||
track: AudioTrack; | ||
}) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} |
import { AudioRendition } from './audio-rendition.js'; | ||
import { AudioRenditionList } from './audio-rendition-list.js'; | ||
export declare const audioTrackToList: Map<any, any>; | ||
export declare const AudioTrackKind: { | ||
@@ -20,5 +18,5 @@ alternative: string; | ||
addRendition(src: string, codec?: string, bitrate?: number): AudioRendition; | ||
get renditions(): AudioRenditionList; | ||
removeRendition(rendition: AudioRendition): void; | ||
get enabled(): boolean; | ||
set enabled(val: boolean); | ||
} |
export * from './mixin.js'; | ||
export * from './video-track.js'; | ||
export * from './video-track-list.js'; | ||
export * from './video-rendition.js'; | ||
export * from './video-rendition-list.js'; | ||
export * from './audio-track.js'; | ||
export * from './audio-track-list.js'; | ||
export * from './audio-rendition.js'; | ||
export * from './audio-rendition-list.js'; | ||
export * from './track-event.js'; | ||
export * from './rendition-event.js'; | ||
export { VideoTrack } from './video-track.js'; | ||
export { VideoTrackList } from './video-track-list.js'; | ||
export { VideoRendition } from './video-rendition.js'; | ||
export { VideoRenditionList } from './video-rendition-list.js'; | ||
export { AudioTrack } from './audio-track.js'; | ||
export { AudioTrackList } from './audio-track-list.js'; | ||
export { AudioRendition } from './audio-rendition.js'; | ||
export { AudioRenditionList } from './audio-rendition-list.js'; | ||
export { TrackEvent } from './track-event.js'; | ||
export { RenditionEvent } from './rendition-event.js'; |
@@ -5,2 +5,4 @@ import { VideoTrack } from './video-track.js'; | ||
import { AudioTrackList } from './audio-track-list.js'; | ||
import { VideoRenditionList } from './video-rendition-list.js'; | ||
import { AudioRenditionList } from './audio-rendition-list.js'; | ||
type VideoTrackType = typeof VideoTrack; | ||
@@ -14,4 +16,8 @@ type AudioTrackType = typeof AudioTrack; | ||
audioTracks: AudioTrackList; | ||
addVideoTrack(kind: string, label?: string, language?: string): VideoTrack; | ||
addAudioTrack(kind: string, label?: string, language?: string): AudioTrack; | ||
addVideoTrack(kind: string, label?: string, language?: string): VideoTrack; | ||
removeVideoTrack(track: VideoTrack): void; | ||
removeAudioTrack(track: AudioTrack): void; | ||
videoRenditions: VideoRenditionList; | ||
audioRenditions: AudioRenditionList; | ||
} | ||
@@ -18,0 +24,0 @@ } |
@@ -1,17 +0,29 @@ | ||
import { VideoRendition } from './video-rendition.js'; | ||
import type { VideoTrack } from './video-track.js'; | ||
import type { VideoRendition } from './video-rendition.js'; | ||
export declare function addRendition(track: VideoTrack, rendition: VideoRendition): void; | ||
export declare function removeRendition(rendition: VideoRendition): void; | ||
export declare function selectedChanged(rendition: VideoRendition): void; | ||
export declare class VideoRenditionList extends EventTarget { | ||
#private; | ||
[index: number]: VideoRendition; | ||
constructor(); | ||
[Symbol.iterator](): IterableIterator<VideoRendition>; | ||
get length(): number; | ||
add(rendition: VideoRendition): void; | ||
remove(rendition: VideoRendition): void; | ||
getRenditionById(id: string): VideoRendition | null; | ||
get activeIndex(): number; | ||
get onaddrendition(): (() => void) | undefined; | ||
set onaddrendition(callback: (() => void) | undefined); | ||
get onremoverendition(): (() => void) | undefined; | ||
set onremoverendition(callback: (() => void) | undefined); | ||
get selectedIndex(): number; | ||
set selectedIndex(index: number); | ||
get onaddrendition(): ((event?: { | ||
rendition: VideoRendition; | ||
}) => void) | undefined; | ||
set onaddrendition(callback: ((event?: { | ||
rendition: VideoRendition; | ||
}) => void) | undefined); | ||
get onremoverendition(): ((event?: { | ||
rendition: VideoRendition; | ||
}) => void) | undefined; | ||
set onremoverendition(callback: ((event?: { | ||
rendition: VideoRendition; | ||
}) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} |
@@ -1,7 +0,4 @@ | ||
export declare const videoRenditionToList: Map<any, any>; | ||
/** | ||
* - There can only be 1 rendition active in a rendition list. | ||
* - The consumer should use the `enabled` setter to select 1 or multiple | ||
* - The consumer should use the `selected` setter to select 1 or multiple | ||
* renditions that the engine is allowed to play. | ||
* - The `active` setter should be used by the media engine implementation. | ||
*/ | ||
@@ -17,6 +14,4 @@ export declare class VideoRendition { | ||
codec?: string; | ||
get enabled(): boolean; | ||
set enabled(val: boolean); | ||
get active(): boolean; | ||
set active(val: boolean); | ||
get selected(): boolean; | ||
set selected(val: boolean); | ||
} |
@@ -1,17 +0,27 @@ | ||
import { VideoTrack } from './video-track.js'; | ||
import type { VideoTrack } from './video-track.js'; | ||
export declare function addVideoTrack(media: HTMLMediaElement, track: VideoTrack): void; | ||
export declare function removeVideoTrack(track: VideoTrack): void; | ||
export declare function selectedChanged(selected: VideoTrack): void; | ||
export declare class VideoTrackList extends EventTarget { | ||
#private; | ||
[index: number]: VideoTrack; | ||
[Symbol.iterator](): IterableIterator<VideoTrack>; | ||
get length(): number; | ||
add(track: VideoTrack): void; | ||
remove(track: VideoTrack): void; | ||
constructor(); | ||
[Symbol.iterator](): any; | ||
get length(): any; | ||
getTrackById(id: string): VideoTrack | null; | ||
get selectedIndex(): number; | ||
get onaddtrack(): (() => void) | undefined; | ||
set onaddtrack(callback: (() => void) | undefined); | ||
get onremovetrack(): (() => void) | undefined; | ||
set onremovetrack(callback: (() => void) | undefined); | ||
get onaddtrack(): ((event?: { | ||
track: VideoTrack; | ||
}) => void) | undefined; | ||
set onaddtrack(callback: ((event?: { | ||
track: VideoTrack; | ||
}) => void) | undefined); | ||
get onremovetrack(): ((event?: { | ||
track: VideoTrack; | ||
}) => void) | undefined; | ||
set onremovetrack(callback: ((event?: { | ||
track: VideoTrack; | ||
}) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} |
import { VideoRendition } from './video-rendition.js'; | ||
import { VideoRenditionList } from './video-rendition-list.js'; | ||
export declare const videoTrackToList: Map<any, any>; | ||
export declare const VideoTrackKind: { | ||
@@ -20,5 +18,5 @@ alternative: string; | ||
addRendition(src: string, width?: number, height?: number, codec?: string, bitrate?: number, frameRate?: number): VideoRendition; | ||
get renditions(): VideoRenditionList; | ||
removeRendition(rendition: VideoRendition): void; | ||
get selected(): boolean; | ||
set selected(val: boolean); | ||
} |
@@ -1,40 +0,79 @@ | ||
import { videoRenditionToList } from "./video-rendition.js"; | ||
import { RenditionEvent } from "./rendition-event.js"; | ||
import { getPrivate } from "./utils.js"; | ||
function addRendition(track, rendition) { | ||
const renditionList = getPrivate(track).media.videoRenditions; | ||
if (!getPrivate(rendition).list) { | ||
getPrivate(rendition).list = renditionList; | ||
getPrivate(rendition).track = track; | ||
} | ||
const { collection } = getPrivate(renditionList); | ||
collection.add(rendition); | ||
const index = collection.size - 1; | ||
if (!(index in VideoRenditionList.prototype)) { | ||
Object.defineProperty(VideoRenditionList.prototype, index, { | ||
get() { | ||
return getCurrentRenditions(this)[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
if (!track.selected) | ||
return; | ||
renditionList.dispatchEvent(new RenditionEvent("addrendition", { rendition })); | ||
}); | ||
} | ||
function removeRendition(rendition) { | ||
const renditionList = getPrivate(rendition).list; | ||
const collection = getPrivate(renditionList).collection; | ||
collection.delete(rendition); | ||
queueMicrotask(() => { | ||
const track = getPrivate(rendition).track; | ||
if (!track.selected) | ||
return; | ||
renditionList.dispatchEvent(new RenditionEvent("removerendition", { rendition })); | ||
}); | ||
} | ||
function selectedChanged(rendition) { | ||
const renditionList = getPrivate(rendition).list; | ||
if (!renditionList || getPrivate(renditionList).changeRequested) | ||
return; | ||
getPrivate(renditionList).changeRequested = true; | ||
queueMicrotask(() => { | ||
delete getPrivate(renditionList).changeRequested; | ||
const track = getPrivate(rendition).track; | ||
if (!track.selected) | ||
return; | ||
renditionList.dispatchEvent(new Event("change")); | ||
}); | ||
} | ||
function getCurrentRenditions(renditionList) { | ||
return [...getPrivate(renditionList).collection].filter((rendition) => { | ||
return getPrivate(rendition).track.selected; | ||
}); | ||
} | ||
class VideoRenditionList extends EventTarget { | ||
#renditions = []; | ||
#addRenditionCallback; | ||
#removeRenditionCallback; | ||
#changeCallback; | ||
constructor() { | ||
super(); | ||
getPrivate(this).collection = /* @__PURE__ */ new Set(); | ||
} | ||
[Symbol.iterator]() { | ||
return this.#renditions.values(); | ||
return getCurrentRenditions(this).values(); | ||
} | ||
get length() { | ||
return this.#renditions.length; | ||
return getCurrentRenditions(this).length; | ||
} | ||
add(rendition) { | ||
videoRenditionToList.set(rendition, this); | ||
const length = this.#renditions.push(rendition); | ||
const index = length - 1; | ||
if (!(index in VideoRenditionList.prototype)) { | ||
Object.defineProperty(VideoRenditionList.prototype, index, { | ||
get() { | ||
return this.#renditions[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
this.dispatchEvent(new RenditionEvent("addrendition", { rendition })); | ||
}); | ||
getRenditionById(id) { | ||
return getCurrentRenditions(this).find((rendition) => `${rendition.id}` === `${id}`) ?? null; | ||
} | ||
remove(rendition) { | ||
videoRenditionToList.delete(rendition); | ||
this.#renditions.splice(this.#renditions.indexOf(rendition), 1); | ||
this.dispatchEvent(new RenditionEvent("removerendition", { rendition })); | ||
get selectedIndex() { | ||
return getCurrentRenditions(this).findIndex((rendition) => rendition.selected); | ||
} | ||
getRenditionById(id) { | ||
return this.#renditions.find((rendition) => `${rendition.id}` === `${id}`) ?? null; | ||
set selectedIndex(index) { | ||
for (const [i, rendition] of getCurrentRenditions(this).entries()) { | ||
rendition.selected = i === index; | ||
} | ||
} | ||
get activeIndex() { | ||
return this.#renditions.findIndex((rendition) => rendition.active); | ||
} | ||
get onaddrendition() { | ||
@@ -58,6 +97,3 @@ return this.#addRenditionCallback; | ||
if (this.#removeRenditionCallback) { | ||
this.removeEventListener( | ||
"removerendition", | ||
this.#removeRenditionCallback | ||
); | ||
this.removeEventListener("removerendition", this.#removeRenditionCallback); | ||
this.#removeRenditionCallback = void 0; | ||
@@ -85,3 +121,6 @@ } | ||
export { | ||
VideoRenditionList | ||
VideoRenditionList, | ||
addRendition, | ||
removeRendition, | ||
selectedChanged | ||
}; |
@@ -1,3 +0,2 @@ | ||
const videoRenditionToList = /* @__PURE__ */ new Map(); | ||
const changeRequested = /* @__PURE__ */ new Map(); | ||
import { selectedChanged } from "./video-rendition-list.js"; | ||
class VideoRendition { | ||
@@ -11,47 +10,15 @@ src; | ||
codec; | ||
#enabled = false; | ||
#active = false; | ||
get enabled() { | ||
return this.#enabled; | ||
#selected = false; | ||
get selected() { | ||
return this.#selected; | ||
} | ||
set enabled(val) { | ||
if (this.#enabled === val) | ||
set selected(val) { | ||
if (this.#selected === val) | ||
return; | ||
this.#enabled = val; | ||
const renditionList = videoRenditionToList.get(this); | ||
if (!renditionList || changeRequested.get(renditionList)) | ||
return; | ||
changeRequested.set(renditionList, true); | ||
queueMicrotask(() => { | ||
changeRequested.delete(renditionList); | ||
renditionList.dispatchEvent(new Event("change")); | ||
}); | ||
this.#selected = val; | ||
selectedChanged(this); | ||
} | ||
get active() { | ||
return this.#active; | ||
} | ||
set active(val) { | ||
if (this.#active === val) | ||
return; | ||
this.#active = val; | ||
if (val !== true) | ||
return; | ||
const renditionList = videoRenditionToList.get(this) ?? []; | ||
let hasInactivated = false; | ||
for (const rendition of renditionList) { | ||
if (rendition === this) | ||
continue; | ||
rendition.active = false; | ||
hasInactivated = true; | ||
} | ||
if (hasInactivated) { | ||
queueMicrotask(() => { | ||
renditionList.dispatchEvent(new Event("renditionchange")); | ||
}); | ||
} | ||
} | ||
} | ||
export { | ||
VideoRendition, | ||
videoRenditionToList | ||
VideoRendition | ||
}; |
@@ -1,8 +0,61 @@ | ||
import { videoTrackToList } from "./video-track.js"; | ||
import { TrackEvent } from "./track-event.js"; | ||
import { getPrivate } from "./utils.js"; | ||
function addVideoTrack(media, track) { | ||
const trackList = media.videoTracks; | ||
if (!getPrivate(track).list) { | ||
getPrivate(track).list = trackList; | ||
getPrivate(track).media = media; | ||
} | ||
const { collection } = getPrivate(trackList); | ||
collection.add(track); | ||
const index = collection.size - 1; | ||
if (!(index in VideoTrackList.prototype)) { | ||
Object.defineProperty(VideoTrackList.prototype, index, { | ||
get() { | ||
return [...collection][index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
trackList.dispatchEvent(new TrackEvent("addtrack", { track })); | ||
}); | ||
} | ||
function removeVideoTrack(track) { | ||
const trackList = getPrivate(track).list; | ||
const collection = getPrivate(trackList).collection; | ||
collection.delete(track); | ||
queueMicrotask(() => { | ||
trackList.dispatchEvent(new TrackEvent("removetrack", { track })); | ||
}); | ||
} | ||
function selectedChanged(selected) { | ||
const trackList = getPrivate(selected).list ?? []; | ||
let hasUnselected = false; | ||
for (const track of trackList) { | ||
if (track === selected) | ||
continue; | ||
track.selected = false; | ||
hasUnselected = true; | ||
} | ||
if (hasUnselected) { | ||
if (getPrivate(trackList).changeRequested) | ||
return; | ||
getPrivate(trackList).changeRequested = true; | ||
queueMicrotask(() => { | ||
delete getPrivate(trackList).changeRequested; | ||
trackList.dispatchEvent(new Event("change")); | ||
}); | ||
} | ||
} | ||
class VideoTrackList extends EventTarget { | ||
#tracks = []; | ||
#addTrackCallback; | ||
#removeTrackCallback; | ||
#changeCallback; | ||
constructor() { | ||
super(); | ||
getPrivate(this).collection = /* @__PURE__ */ new Set(); | ||
} | ||
get #tracks() { | ||
return getPrivate(this).collection; | ||
} | ||
[Symbol.iterator]() { | ||
@@ -12,29 +65,9 @@ return this.#tracks.values(); | ||
get length() { | ||
return this.#tracks.length; | ||
return this.#tracks.size; | ||
} | ||
add(track) { | ||
videoTrackToList.set(track, this); | ||
const length = this.#tracks.push(track); | ||
const index = length - 1; | ||
if (!(index in VideoTrackList.prototype)) { | ||
Object.defineProperty(VideoTrackList.prototype, index, { | ||
get() { | ||
return this.#tracks[index]; | ||
} | ||
}); | ||
} | ||
queueMicrotask(() => { | ||
this.dispatchEvent(new TrackEvent("addtrack", { track })); | ||
}); | ||
} | ||
remove(track) { | ||
videoTrackToList.delete(track); | ||
this.#tracks.splice(this.#tracks.indexOf(track), 1); | ||
this.dispatchEvent(new TrackEvent("removetrack", { track })); | ||
} | ||
getTrackById(id) { | ||
return this.#tracks.find((track) => track.id === id) ?? null; | ||
return [...this.#tracks].find((track) => track.id === id) ?? null; | ||
} | ||
get selectedIndex() { | ||
return this.#tracks.findIndex((track) => track.selected); | ||
return [...this.#tracks].findIndex((track) => track.selected); | ||
} | ||
@@ -82,3 +115,6 @@ get onaddtrack() { | ||
export { | ||
VideoTrackList | ||
VideoTrackList, | ||
addVideoTrack, | ||
removeVideoTrack, | ||
selectedChanged | ||
}; |
@@ -0,5 +1,4 @@ | ||
import { selectedChanged } from "./video-track-list.js"; | ||
import { VideoRendition } from "./video-rendition.js"; | ||
import { VideoRenditionList } from "./video-rendition-list.js"; | ||
const videoTrackToList = /* @__PURE__ */ new Map(); | ||
const changeRequested = /* @__PURE__ */ new Map(); | ||
import { addRendition, removeRendition } from "./video-rendition-list.js"; | ||
const VideoTrackKind = { | ||
@@ -20,3 +19,2 @@ alternative: "alternative", | ||
#selected = false; | ||
#renditions = new VideoRenditionList(); | ||
addRendition(src, width, height, codec, bitrate, frameRate) { | ||
@@ -30,7 +28,7 @@ const rendition = new VideoRendition(); | ||
rendition.codec = codec; | ||
this.#renditions.add(rendition); | ||
addRendition(this, rendition); | ||
return rendition; | ||
} | ||
get renditions() { | ||
return this.#renditions; | ||
removeRendition(rendition) { | ||
removeRendition(rendition); | ||
} | ||
@@ -46,19 +44,3 @@ get selected() { | ||
return; | ||
const trackList = videoTrackToList.get(this) ?? []; | ||
let hasUnselected = false; | ||
for (const track of trackList) { | ||
if (track === this) | ||
continue; | ||
track.selected = false; | ||
hasUnselected = true; | ||
} | ||
if (hasUnselected) { | ||
if (changeRequested.get(trackList)) | ||
return; | ||
changeRequested.set(trackList, true); | ||
queueMicrotask(() => { | ||
changeRequested.delete(trackList); | ||
trackList.dispatchEvent(new Event("change")); | ||
}); | ||
} | ||
selectedChanged(this); | ||
} | ||
@@ -68,4 +50,3 @@ } | ||
VideoTrack, | ||
VideoTrackKind, | ||
videoTrackToList | ||
VideoTrackKind | ||
}; |
{ | ||
"name": "media-tracks", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "Polyfill audio and video tracks with renditions.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
101
README.md
@@ -27,9 +27,27 @@ # Media Tracks | ||
interface HTMLMediaElement { | ||
videoTracks: VideoTrackList; | ||
audioTracks: AudioTrackList; | ||
videoTracks: VideoTrackList; | ||
addVideoTrack(kind: string, label?: string, language?: string): VideoTrack; | ||
addAudioTrack(kind: string, label?: string, language?: string): AudioTrack; | ||
addVideoTrack(kind: string, label?: string, language?: string): VideoTrack; | ||
removeVideoTrack(track: VideoTrack): void; | ||
removeAudioTrack(track: AudioTrack): void; | ||
videoRenditions: VideoRenditionList; | ||
audioRenditions: AudioRenditionList; | ||
} | ||
} | ||
declare class VideoTrackList extends EventTarget { | ||
[index: number]: VideoTrack; | ||
[Symbol.iterator](): IterableIterator<VideoTrack>; | ||
get length(): number; | ||
getTrackById(id: string): VideoTrack | null; | ||
get selectedIndex(): number; | ||
get onaddtrack(): ((event?: { track: VideoTrack }) => void) | undefined; | ||
set onaddtrack(callback: ((event?: { track: VideoTrack }) => void) | undefined); | ||
get onremovetrack(): ((event?: { track: VideoTrack }) => void) | undefined; | ||
set onremovetrack(callback: ((event?: { track: VideoTrack }) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} | ||
declare const VideoTrackKind: { | ||
@@ -51,3 +69,3 @@ alternative: string; | ||
addRendition(src: string, width?: number, height?: number, codec?: string, bitrate?: number, frameRate?: number): VideoRendition; | ||
get renditions(): VideoRenditionList; | ||
removeRendition(rendition: AudioRendition): void; | ||
get selected(): boolean; | ||
@@ -58,12 +76,12 @@ set selected(val: boolean); | ||
declare class VideoRenditionList extends EventTarget { | ||
[index: number]: VideoRendition; | ||
[Symbol.iterator](): IterableIterator<VideoRendition>; | ||
get length(): number; | ||
add(rendition: VideoRendition): void; | ||
remove(rendition: VideoRendition): void; | ||
getRenditionById(id: string): VideoRendition | null; | ||
get activeIndex(): number; | ||
get onaddrendition(): (() => void) | undefined; | ||
set onaddrendition(callback: (() => void) | undefined); | ||
get onremoverendition(): (() => void) | undefined; | ||
set onremoverendition(callback: (() => void) | undefined); | ||
get selectedIndex(): number; | ||
set selectedIndex(index: number); | ||
get onaddrendition(): ((event?: { rendition: VideoRendition }) => void) | undefined; | ||
set onaddrendition(callback: ((event?: { rendition: VideoRendition }) => void) | undefined); | ||
get onremoverendition(): ((event?: { rendition: VideoRendition }) => void) | undefined; | ||
set onremoverendition(callback: ((event?: { rendition: VideoRendition }) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
@@ -73,3 +91,3 @@ set onchange(callback: (() => void) | undefined); | ||
export declare class VideoRendition { | ||
declare class VideoRendition { | ||
src?: string; | ||
@@ -82,8 +100,63 @@ id?: string; | ||
codec?: string; | ||
get selected(): boolean; | ||
set selected(val: boolean); | ||
} | ||
declare class AudioTrackList extends EventTarget { | ||
[index: number]: AudioTrack; | ||
[Symbol.iterator](): IterableIterator<AudioTrack>; | ||
get length(): number; | ||
getTrackById(id: string): AudioTrack | null; | ||
get onaddtrack(): ((event?: { track: AudioTrack }) => void) | undefined; | ||
set onaddtrack(callback: ((event?: { track: AudioTrack }) => void) | undefined); | ||
get onremovetrack(): ((event?: { track: AudioTrack }) => void) | undefined; | ||
set onremovetrack(callback: ((event?: { track: AudioTrack }) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} | ||
declare const AudioTrackKind: { | ||
alternative: string; | ||
descriptions: string; | ||
main: string; | ||
'main-desc': string; | ||
translation: string; | ||
commentary: string; | ||
}; | ||
declare class AudioTrack { | ||
id?: string; | ||
kind?: string; | ||
label: string; | ||
language: string; | ||
sourceBuffer?: SourceBuffer; | ||
addRendition(src: string, codec?: string, bitrate?: number): AudioRendition; | ||
removeRendition(rendition: AudioRendition): void; | ||
get enabled(): boolean; | ||
set enabled(val: boolean); | ||
get active(): boolean; | ||
set active(val: boolean); | ||
} | ||
declare class AudioRenditionList extends EventTarget { | ||
[index: number]: AudioRendition; | ||
[Symbol.iterator](): IterableIterator<AudioRendition>; | ||
get length(): number; | ||
getRenditionById(id: string): AudioRendition | null; | ||
get selectedIndex(): number; | ||
set selectedIndex(index: number); | ||
get onaddrendition(): ((event?: { rendition: VideoRendition }) => void) | undefined; | ||
set onaddrendition(callback: ((event?: { rendition: VideoRendition }) => void) | undefined); | ||
get onremoverendition(): ((event?: { rendition: VideoRendition }) => void) | undefined; | ||
set onremoverendition(callback: ((event?: { rendition: VideoRendition }) => void) | undefined); | ||
get onchange(): (() => void) | undefined; | ||
set onchange(callback: (() => void) | undefined); | ||
} | ||
declare class AudioRendition { | ||
src?: string; | ||
id?: string; | ||
bitrate?: number; | ||
codec?: string; | ||
get selected(): boolean; | ||
set selected(val: boolean); | ||
} | ||
``` | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
39839
30
1041
158