happy-dom
Version:
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
540 lines (487 loc) • 11.9 kB
text/typescript
import ErrorEvent from '../../event/events/ErrorEvent.js';
import Event from '../../event/Event.js';
import DOMException from '../../exception/DOMException.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import HTMLElement from '../html-element/HTMLElement.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import TimeRange from './TimeRange.js';
interface IMediaError {
code: number;
message: string;
}
/**
* HTML Media Element.
*
* Reference:
* https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement.
*
*/
export default class HTMLMediaElement extends HTMLElement {
// Public properties
public cloneNode: (deep?: boolean) => HTMLMediaElement;
// Events
public onabort: (event: Event) => void | null = null;
public oncanplay: (event: Event) => void | null = null;
public oncanplaythrough: (event: Event) => void | null = null;
public ondurationchange: (event: Event) => void | null = null;
public onemptied: (event: Event) => void | null = null;
public onended: (event: Event) => void | null = null;
public onerror: (event: ErrorEvent) => void | null = null;
public onloadeddata: (event: Event) => void | null = null;
public onloadedmetadata: (event: Event) => void | null = null;
public onloadstart: (event: Event) => void | null = null;
public onpause: (event: Event) => void | null = null;
public onplay: (event: Event) => void | null = null;
public onplaying: (event: Event) => void | null = null;
public onprogress: (event: Event) => void | null = null;
public onratechange: (event: Event) => void | null = null;
public onresize: (event: Event) => void | null = null;
public onseeked: (event: Event) => void | null = null;
public onseeking: (event: Event) => void | null = null;
public onstalled: (event: Event) => void | null = null;
public onsuspend: (event: Event) => void | null = null;
public ontimeupdate: (event: Event) => void | null = null;
public onvolumechange: (event: Event) => void | null = null;
public onwaiting: (event: Event) => void | null = null;
// Internal Properties
public [PropertySymbol.volume] = 1;
public [PropertySymbol.paused] = true;
public [PropertySymbol.currentTime] = 0;
public [PropertySymbol.playbackRate] = 1;
public [PropertySymbol.defaultPlaybackRate] = 1;
public [PropertySymbol.muted] = false;
public [PropertySymbol.defaultMuted] = false;
public [PropertySymbol.preservesPitch] = true;
public [PropertySymbol.buffered] = new TimeRange();
public [PropertySymbol.duration] = NaN;
public [PropertySymbol.error]: IMediaError = null;
public [PropertySymbol.ended] = false;
public [PropertySymbol.networkState] = 0;
public [PropertySymbol.readyState] = 0;
public [PropertySymbol.textTracks]: object[] = [];
public [PropertySymbol.videoTracks]: object[] = [];
public [PropertySymbol.seeking] = false;
public [PropertySymbol.seekable] = new TimeRange();
public [PropertySymbol.played] = new TimeRange();
/**
* Returns buffered.
*
* @returns Buffered.
*/
public get buffered(): object {
return this[PropertySymbol.buffered];
}
/**
* Returns duration.
*
* @returns Duration.
*/
public get duration(): number {
return this[PropertySymbol.duration];
}
/**
* Returns error.
*
* @returns Error.
*/
public get error(): IMediaError {
return this[PropertySymbol.error];
}
/**
* Returns ended.
*
* @returns Ended.
*/
public get ended(): boolean {
return this[PropertySymbol.ended];
}
/**
* Returns networkState.
*
* @returns NetworkState.
*/
public get networkState(): number {
return this[PropertySymbol.networkState];
}
/**
* Returns readyState.
*
* @returns ReadyState.
*/
public get readyState(): number {
return this[PropertySymbol.readyState];
}
/**
* Returns textTracks.
*
* @returns TextTracks.
*/
public get textTracks(): object[] {
return this[PropertySymbol.textTracks];
}
/**
* Returns videoTracks.
*
* @returns VideoTracks.
*/
public get videoTracks(): object[] {
return this[PropertySymbol.videoTracks];
}
/**
* Returns seeking.
*
* @returns Seeking.
*/
public get seeking(): boolean {
return this[PropertySymbol.seeking];
}
/**
* Returns seekable.
*
* @returns Seekable.
*/
public get seekable(): object {
return this[PropertySymbol.seekable];
}
/**
* Returns played.
*
* @returns Played.
*/
public get played(): object {
return this[PropertySymbol.played];
}
/**
* Returns autoplay.
*
* @returns Autoplay.
*/
public get autoplay(): boolean {
return this.getAttribute('autoplay') !== null;
}
/**
* Sets autoplay.
*
* @param autoplay Autoplay.
*/
public set autoplay(autoplay: boolean) {
if (!autoplay) {
this.removeAttribute('autoplay');
} else {
this.setAttribute('autoplay', '');
}
}
/**
* Returns controls.
*
* @returns Controls.
*/
public get controls(): boolean {
return this.getAttribute('controls') !== null;
}
/**
* Sets controls.
*
* @param controls Controls.
*/
public set controls(controls: boolean) {
if (!controls) {
this.removeAttribute('controls');
} else {
this.setAttribute('controls', '');
}
}
/**
* Returns loop.
*
* @returns Loop.
*/
public get loop(): boolean {
return this.getAttribute('loop') !== null;
}
/**
* Sets loop.
*
* @param loop Loop.
*/
public set loop(loop: boolean) {
if (!loop) {
this.removeAttribute('loop');
} else {
this.setAttribute('loop', '');
}
}
/**
* Returns muted.
*
* @returns Muted.
*/
public get muted(): boolean {
if (this[PropertySymbol.muted]) {
return this[PropertySymbol.muted];
}
if (!this[PropertySymbol.defaultMuted]) {
return this.getAttribute('muted') !== null;
}
return false;
}
/**
* Sets muted.
*
* @param muted Muted.
*/
public set muted(muted: boolean) {
this[PropertySymbol.muted] = !!muted;
if (!muted && !this[PropertySymbol.defaultMuted]) {
this.removeAttribute('muted');
} else {
this.setAttribute('muted', '');
}
}
/**
* Returns defaultMuted.
*
* @returns DefaultMuted.
*/
public get defaultMuted(): boolean {
return this[PropertySymbol.defaultMuted];
}
/**
* Sets defaultMuted.
*
* @param defaultMuted DefaultMuted.
*/
public set defaultMuted(defaultMuted: boolean) {
this[PropertySymbol.defaultMuted] = !!defaultMuted;
if (!this[PropertySymbol.defaultMuted] && !this[PropertySymbol.muted]) {
this.removeAttribute('muted');
} else {
this.setAttribute('muted', '');
}
}
/**
* Returns src.
*
* @returns Src.
*/
public get src(): string {
return this.getAttribute('src') || '';
}
/**
* Sets src.
*
* @param src Src.
*/
public set src(src: string) {
this.setAttribute('src', src);
if (Boolean(src)) {
this.dispatchEvent(new Event('canplay', { bubbles: false, cancelable: false }));
this.dispatchEvent(new Event('durationchange', { bubbles: false, cancelable: false }));
}
}
/**
* Returns currentSrc.
*
* @returns CurrentrSrc.
*/
public get currentSrc(): string {
return this.src;
}
/**
* Returns volume.
*
* @returns Volume.
*/
public get volume(): number {
return this[PropertySymbol.volume];
}
/**
* Sets volume.
*
* @param volume Volume.
*/
public set volume(volume: number | string) {
const parsedVolume = Number(volume);
if (isNaN(parsedVolume)) {
throw new TypeError(
`Failed to set the 'volume' property on 'HTMLMediaElement': The provided double value is non-finite.`
);
}
if (parsedVolume < 0 || parsedVolume > 1) {
throw new DOMException(
`Failed to set the 'volume' property on 'HTMLMediaElement': The volume provided (${parsedVolume}) is outside the range [0, 1].`,
DOMExceptionNameEnum.indexSizeError
);
}
// TODO: volumechange event https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/volumechange_event
this[PropertySymbol.volume] = parsedVolume;
}
/**
* Returns crossOrigin.
*
* @returns CrossOrigin.
*/
public get crossOrigin(): string {
return this.getAttribute('crossorigin');
}
/**
* Sets crossOrigin.
*
* @param crossOrigin CrossOrigin.
*/
public set crossOrigin(crossOrigin: string | null) {
if (crossOrigin === null) {
return;
}
if (['', 'use-credentials', 'anonymous'].includes(crossOrigin)) {
this.setAttribute('crossorigin', crossOrigin);
} else {
this.setAttribute('crossorigin', 'anonymous');
}
}
/**
* Returns currentTime.
*
* @returns CurrentTime.
*/
public get currentTime(): number {
return this[PropertySymbol.currentTime];
}
/**
* Sets currentTime.
*
* @param currentTime CurrentTime.
*/
public set currentTime(currentTime: number | string) {
const parsedCurrentTime = Number(currentTime);
if (isNaN(parsedCurrentTime)) {
throw new TypeError(
`Failed to set the 'currentTime' property on 'HTMLMediaElement': The provided double value is non-finite.`
);
}
this[PropertySymbol.currentTime] = parsedCurrentTime;
}
/**
* Returns playbackRate.
*
* @returns PlaybackRate.
*/
public get playbackRate(): number {
return this[PropertySymbol.playbackRate];
}
/**
* Sets playbackRate.
*
* @param playbackRate PlaybackRate.
*/
public set playbackRate(playbackRate: number | string) {
const parsedPlaybackRate = Number(playbackRate);
if (isNaN(parsedPlaybackRate)) {
throw new TypeError(
`Failed to set the 'playbackRate' property on 'HTMLMediaElement': The provided double value is non-finite.`
);
}
this[PropertySymbol.playbackRate] = parsedPlaybackRate;
}
/**
* Returns defaultPlaybackRate.
*
* @returns DefaultPlaybackRate.
*/
public get defaultPlaybackRate(): number {
return this[PropertySymbol.defaultPlaybackRate];
}
/**
* Sets defaultPlaybackRate.
*
* @param defaultPlaybackRate DefaultPlaybackRate.
*/
public set defaultPlaybackRate(defaultPlaybackRate: number | string) {
const parsedDefaultPlaybackRate = Number(defaultPlaybackRate);
if (isNaN(parsedDefaultPlaybackRate)) {
throw new TypeError(
`Failed to set the 'defaultPlaybackRate' property on 'HTMLMediaElement': The provided double value is non-finite.`
);
}
this[PropertySymbol.defaultPlaybackRate] = parsedDefaultPlaybackRate;
}
/**
* Returns preservesPitch.
*
* @returns PlaybackRate.
*/
public get preservesPitch(): boolean {
return this[PropertySymbol.preservesPitch];
}
/**
* Sets preservesPitch.
*
* @param preservesPitch PreservesPitch.
*/
public set preservesPitch(preservesPitch: boolean) {
this[PropertySymbol.preservesPitch] = Boolean(preservesPitch);
}
/**
* Returns preload.
*
* @returns preload.
*/
public get preload(): string {
return this.getAttribute('preload') || 'auto';
}
/**
* Sets preload.
*
* @param preload preload.
*/
public set preload(preload: string) {
this.setAttribute('preload', preload);
}
/**
* Returns paused.
*
* @returns Paused.
*/
public get paused(): boolean {
return this[PropertySymbol.paused];
}
/**
* Pause played media.
*/
public pause(): void {
this[PropertySymbol.paused] = true;
this.dispatchEvent(new Event('pause', { bubbles: false, cancelable: false }));
}
/**
* Start playing media.
*/
public async play(): Promise<void> {
this[PropertySymbol.paused] = false;
return Promise.resolve();
}
/**
*
* @param _type
*/
public canPlayType(_type: string): string {
return '';
}
/**
* Load media.
*/
public load(): void {
this.dispatchEvent(new Event('emptied', { bubbles: false, cancelable: false }));
}
/**
*
*/
public captureStream(): object {
return {};
}
/**
* @override
*/
public override [PropertySymbol.cloneNode](deep = false): HTMLMediaElement {
return <HTMLMediaElement>super[PropertySymbol.cloneNode](deep);
}
}