diff --git a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts index 4832ac2f..428a26e7 100644 --- a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts @@ -1,8 +1,9 @@ +import ErrorEvent from 'src/event/events/ErrorEvent'; import Event from '../../event/Event'; import DOMException from '../../exception/DOMException'; import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import HTMLElement from '../html-element/HTMLElement'; -import IHTMLMediaElement from './IHTMLMediaElement'; +import IHTMLMediaElement, { IMediaError } from './IHTMLMediaElement'; /** * HTML Base Element. @@ -11,12 +12,25 @@ import IHTMLMediaElement from './IHTMLMediaElement'; * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base. */ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaElement { + // Public Properties + public readonly readyState = 0; + public readonly networkState = 0; + public readonly error: IMediaError = null; + public readonly ended = false; + public readonly duration = NaN; + public readonly textTracks = []; + + // Events + public onerror: (event: ErrorEvent) => void = null; + #volume = 1; #paused = true; - #duration = NaN; #currentTime = 0; #playbackRate = 1; #defaultPlaybackRate = 1; + #muted = false; + #defaultMuted = false; + #preservesPitch = true; /** * Returns autoplay. * @@ -88,7 +102,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns Muted. */ public get muted(): boolean { - return this.getAttributeNS(null, 'muted') !== null; + return this.#muted; } /** @@ -97,7 +111,31 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param muted Muted. */ public set muted(muted: boolean) { - if (!muted) { + this.muted = Boolean(muted); + if (!muted && !this.#defaultMuted) { + this.removeAttributeNS(null, 'muted'); + } else { + this.setAttributeNS(null, 'muted', ''); + } + } + + /** + * Returns defaultMuted. + * + * @returns DefaultMuted. + */ + public get defaultMuted(): boolean { + return this.#defaultMuted; + } + + /** + * Sets defaultMuted. + * + * @param defaultMuted DefaultMuted. + */ + public set defaultMuted(defaultMuted: boolean) { + this.#defaultMuted = Boolean(defaultMuted); + if (!this.#defaultMuted && !this.#muted) { this.removeAttributeNS(null, 'muted'); } else { this.setAttributeNS(null, 'muted', ''); @@ -201,15 +239,6 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE return this.#paused; } - /** - * Returns duration. - * - * @returns Duration. - */ - public get duration(): number { - return this.#duration; - } - /** * Returns currentTime. * @@ -237,16 +266,16 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE /** * Returns playbackRate. * - * @returns playbackRate. + * @returns PlaybackRate. */ - public get playbackRate(): number { + public get playbackRate(): number { return this.#playbackRate; } /** * Sets playbackRate. * - * @param playbackRate playbackRate. + * @param playbackRate PlaybackRate. */ public set playbackRate(playbackRate: number | string) { const parsedPlaybackRate = Number(playbackRate); @@ -261,16 +290,16 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE /** * Returns defaultPlaybackRate. * - * @returns defaultPlaybackRate. + * @returns DefaultPlaybackRate. */ - public get defaultPlaybackRate(): number { + public get defaultPlaybackRate(): number { return this.#defaultPlaybackRate; } /** * Sets defaultPlaybackRate. * - * @param defaultPlaybackRate defaultPlaybackRate. + * @param defaultPlaybackRate DefaultPlaybackRate. */ public set defaultPlaybackRate(defaultPlaybackRate: number | string) { const parsedDefaultPlaybackRate = Number(defaultPlaybackRate); @@ -282,6 +311,24 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE this.#defaultPlaybackRate = parsedDefaultPlaybackRate; } + /** + * Returns preservesPitch. + * + * @returns PlaybackRate. + */ + public get preservesPitch(): boolean { + return this.#preservesPitch; + } + + /** + * Sets preservesPitch. + * + * @param preservesPitch PreservesPitch. + */ + public set preservesPitch(preservesPitch: boolean) { + this.#preservesPitch = Boolean(preservesPitch); + } + /** * */ @@ -305,6 +352,18 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE return ''; } + /** + * + */ + public load(): void {} + + /** + * + */ + public captureStream(): object { + return {}; + } + /** * Clones a node. * diff --git a/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts b/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts index 48ae30b4..fa545d3f 100644 --- a/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts @@ -6,51 +6,75 @@ import IHTMLElement from '../html-element/IHTMLElement'; * Reference: * https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement. */ + +export interface IMediaError { + code: number; + message: string; +} export default interface IHTMLMediaElement extends IHTMLElement { readonly currentSrc: string; readonly duration: number; + readonly ended: boolean; + readonly error: IMediaError | null; + readonly networkState: number; + readonly readyState: number; + readonly textTracks: object[]; autoplay: boolean; controls: boolean; + crossOrigin: string; // Only anonymus and 'use-credentials' is valid loop: boolean; muted: boolean; paused: boolean; volume: number | string; src: string; - crossOrigin: string; // Only anonymus and 'use-credentials' is valid currentTime: number | string; playbackRate: number | string; defaultPlaybackRate: number | string; + defaultMuted: boolean; + preservesPitch: boolean; - - // AddTextTrack; // Buffered; // TODO tameranges - // CaptureStream; // TODO - // ControlsList: string; // TODO - // DefaultMuted: boolean; // TODO - // DisableRemotePlayback: boolean; // TODO - // Ended: boolean; // TODO readonly - // Error; // TODO object - // NetworkState; // TODO // Played: // TODO timeranges + // Seekable: // TODO timeranges + + // CaptureStream; // TODO https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/captureStream /** - * The HTMLMediaElement.pause() method will pause playback of the media, if the media is already in a paused state this method will have no effect. + * The HTMLMediaElement.pause() method will pause playback of the media, if the media is already in a paused state + * this method will have no effect. */ pause(): void; /** - * The HTMLMediaElement play() method attempts to begin playback of the media. It returns a Promise which is resolved when playback has been successfully started. + * The HTMLMediaElement play() method attempts to begin playback of the media. It returns a Promise + * which is resolved when playback has been successfully started. */ play(): Promise; /** - * The HTMLMediaElement method canPlayType() reports how likely it is that the current browser will be able to play media of a given MIME type. + * The HTMLMediaElement method canPlayType() reports how likely it is that the current browser will be able to play + * media of a given MIME type. * Https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType * possible return value: "" | "probably" | "maybe". */ canPlayType(_type: string): string; + /** + * The HTMLMediaElement method load() resets the media element to its initial state and begins the process of + * selecting a media source and loading the media in preparation for playback to begin at the beginning. + * Https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/load. + */ + load(): void; + + /** + * A MediaStream object which can be used as a source for audio and/or video data by other media processing code, + * or as a source for WebRTC. + * Https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/captureStream. + */ + + captureStream(): object; + /** * Clones a node. * diff --git a/packages/happy-dom/test/nodes/html-media-element/HTMLMediaElement.test.ts b/packages/happy-dom/test/nodes/html-media-element/HTMLMediaElement.test.ts index 557f1e90..ac5178f9 100644 --- a/packages/happy-dom/test/nodes/html-media-element/HTMLMediaElement.test.ts +++ b/packages/happy-dom/test/nodes/html-media-element/HTMLMediaElement.test.ts @@ -60,6 +60,27 @@ describe('HTMLMediaElement', () => { }); } + describe(`get defaultMuted()`, () => { + it('Returns value.', () => { + expect(element.defaultMuted).toBe(false); + element.defaultMuted = true; + expect(element.defaultMuted).toBe(true); + }); + }); + + describe(`set defaultMuted()`, () => { + it('Sets attribute value.', () => { + element.defaultMuted = true; + expect(element.getAttribute('muted')).toBe(''); + }); + + it('Remove attribute value.', () => { + element.defaultMuted = true; + element.defaultMuted = false; + expect(element.getAttribute('muted')).toBeNull(); + }); + }); + describe('canplay event', () => { it('Should dispatch after src set', () => { let dispatchedEvent: Event = null; @@ -78,11 +99,11 @@ describe('HTMLMediaElement', () => { describe('currentSrc', () => { it('Returns the current src', () => { - const src = 'https://src' + const src = 'https://src'; element.src = src; expect(element.currentSrc).toBe(src); - }) - }) + }); + }); describe('paused', () => { it('Default is true', () => { @@ -146,6 +167,12 @@ describe('HTMLMediaElement', () => { }); }); + describe('enden', () => { + it('Returns false', () => { + expect(element.ended).toBeFalsy(); + }); + }); + describe('CrossOrigin', () => { for (const crossOrigin of ['', null, 'use-credentials', 'anonymous']) { it(`Set ${crossOrigin} as a valid crossOrigin`, () => { @@ -163,7 +190,7 @@ describe('HTMLMediaElement', () => { }); describe('duration', () => { - it('Return 0 by default', () => { + it('Return NaN by default', () => { expect(element.duration).toBe(NaN); }); }); @@ -239,4 +266,35 @@ describe('HTMLMediaElement', () => { ); }); }); + + describe('error', () => { + it('Return null by default', () => { + expect(element.error).toBeNull(); + }); + }); + + describe('networkState', () => { + it('Return 0 by default', () => { + expect(element.networkState).toBe(0); + }); + }); + + describe('preservesPitch', () => { + it('Return true by default', () => { + expect(element.preservesPitch).toBe(true); + }); + + for (const property of [null, undefined, false]) { + it(`Set false with ${property}`, () => { + element.preservesPitch = property; + expect(element.preservesPitch).toBe(false); + }); + } + }); + + describe('readyState', () => { + it('Return 0 by default', () => { + expect(element.readyState).toBe(0); + }); + }); });