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 428a26e7e..d00331471 100644 --- a/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts @@ -5,6 +5,23 @@ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum'; import HTMLElement from '../html-element/HTMLElement'; import IHTMLMediaElement, { IMediaError } from './IHTMLMediaElement'; +/** + * + * This implementation coming from jsdom + * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/nodes/HTMLMediaElement-impl.js#L7 + * + */ +function getTimeRangeDummy(): object { + return { + length: 0, + start() { + return 0; + }, + end() { + return 0; + } + }; +} /** * HTML Base Element. * @@ -13,15 +30,27 @@ import IHTMLMediaElement, { IMediaError } from './IHTMLMediaElement'; */ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaElement { // Public Properties - public readonly readyState = 0; - public readonly networkState = 0; + public readonly buffered = getTimeRangeDummy(); + public readonly duration = NaN; public readonly error: IMediaError = null; public readonly ended = false; - public readonly duration = NaN; + public readonly networkState = 0; + public readonly readyState = 0; public readonly textTracks = []; + public readonly videoTracks = []; + public readonly seeking = false; + public readonly seekable = getTimeRangeDummy(); + public readonly played = getTimeRangeDummy(); // Events + public onabort: (event: Event) => void = null; + public oncanplay: (event: Event) => void = null; + public oncanplaythrough: (event: Event) => void = null; + public ondurationchange: (event: Event) => void = null; + public onemptied: (event: Event) => void = null; + public onended: (event: Event) => void = null; public onerror: (event: ErrorEvent) => void = null; + public onloadeddata: (event: Event) => void = null; #volume = 1; #paused = true; @@ -102,7 +131,15 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @returns Muted. */ public get muted(): boolean { - return this.#muted; + if (this.#muted) { + return this.#muted; + } + + if (!this.#defaultMuted) { + return this.getAttributeNS(null, 'muted') !== null; + } + + return false; } /** @@ -111,7 +148,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param muted Muted. */ public set muted(muted: boolean) { - this.muted = Boolean(muted); + this.#muted = !!muted; if (!muted && !this.#defaultMuted) { this.removeAttributeNS(null, 'muted'); } else { @@ -134,7 +171,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE * @param defaultMuted DefaultMuted. */ public set defaultMuted(defaultMuted: boolean) { - this.#defaultMuted = Boolean(defaultMuted); + this.#defaultMuted = !!defaultMuted; if (!this.#defaultMuted && !this.#muted) { this.removeAttributeNS(null, 'muted'); } else { @@ -160,6 +197,7 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE this.setAttributeNS(null, 'src', src); if (Boolean(src)) { this.dispatchEvent(new Event('canplay', { bubbles: false, cancelable: false })); + this.dispatchEvent(new Event('durationchange', { bubbles: false, cancelable: false })); } } @@ -230,15 +268,6 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE } } - /** - * Returns paused. - * - * @returns Paused. - */ - public get paused(): boolean { - return this.#paused; - } - /** * Returns currentTime. * @@ -330,14 +359,42 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE } /** + * Returns preload. + * + * @returns preload. + */ + public get preload(): string { + return this.getAttributeNS(null, 'preload') || 'auto'; + } + + /** + * Sets preload. * + * @param preload preload. + */ + public set preload(preload: string) { + this.setAttributeNS(null, 'preload', preload); + } + + /** + * Returns paused. + * + * @returns Paused. + */ + public get paused(): boolean { + return this.#paused; + } + + /** + * Pause played media */ public pause(): void { this.#paused = true; + this.dispatchEvent(new Event('pause', { bubbles: false, cancelable: false })); } /** - * + * Start playing media */ public async play(): Promise { this.#paused = false; @@ -353,9 +410,11 @@ export default class HTMLMediaElement extends HTMLElement implements IHTMLMediaE } /** - * + * Load media */ - public load(): void {} + public load(): void { + this.dispatchEvent(new Event('emptied', { bubbles: false, cancelable: false })); + } /** * 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 fa545d3fa..d0e1ff0d2 100644 --- a/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts +++ b/packages/happy-dom/src/nodes/html-media-element/IHTMLMediaElement.ts @@ -17,40 +17,34 @@ export default interface IHTMLMediaElement extends IHTMLElement { readonly ended: boolean; readonly error: IMediaError | null; readonly networkState: number; + readonly played: object; // TimeRanges https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges readonly readyState: number; + readonly seekable: object; // TimeRanges https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges + readonly seeking: boolean; readonly textTracks: object[]; + readonly videoTracks: object[]; + readonly buffered: object; // TimeRanges https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges autoplay: boolean; controls: boolean; crossOrigin: string; // Only anonymus and 'use-credentials' is valid + currentTime: number | string; + defaultMuted: boolean; + defaultPlaybackRate: number | string; loop: boolean; muted: boolean; paused: boolean; - volume: number | string; - src: string; - currentTime: number | string; playbackRate: number | string; - defaultPlaybackRate: number | string; - defaultMuted: boolean; + preload: string; preservesPitch: boolean; - - // Buffered; // TODO tameranges - // 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. - */ - - pause(): void; + src: string; + volume: number | string; /** - * The HTMLMediaElement play() method attempts to begin playback of the media. It returns a Promise - * which is resolved when playback has been successfully started. + * 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 */ - play(): Promise; + captureStream(): object; /** * The HTMLMediaElement method canPlayType() reports how likely it is that the current browser will be able to play @@ -68,12 +62,16 @@ export default interface IHTMLMediaElement extends IHTMLElement { 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. + * 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; - captureStream(): object; + /** + * 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; /** * 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 ac5178f9e..58212f97b 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 @@ -21,7 +21,7 @@ describe('HTMLMediaElement', () => { jest.restoreAllMocks(); }); - for (const property of ['autoplay', 'controls', 'loop', 'muted']) { + for (const property of ['autoplay', 'controls', 'loop']) { describe(`get ${property}()`, () => { it('Returns attribute value.', () => { expect(element[property]).toBe(false); @@ -44,7 +44,7 @@ describe('HTMLMediaElement', () => { }); } - for (const property of ['src']) { + for (const property of ['src', 'preload']) { describe(`get ${property}()`, () => { it(`Returns the "${property}" attribute.`, () => { element.setAttribute(property, 'test'); @@ -81,6 +81,38 @@ describe('HTMLMediaElement', () => { }); }); + describe(`get muted()`, () => { + it('Returns value.', () => { + element.setAttribute('muted', ''); + expect(element.muted).toBe(true); + }); + it('Returns setter value.', () => { + element.muted = true; + expect(element.muted).toBe(true); + }); + }); + + describe(`set muted()`, () => { + it('Sets attribute value.', () => { + element.muted = true; + expect(element.getAttribute('muted')).toBe(''); + }); + + it('Remove attribute value.', () => { + element.setAttribute('muted', ''); + element.muted = false; + expect(element.getAttribute('muted')).toBeNull(); + }); + + it('Keep attribute value, if default muted true', () => { + element.setAttribute('muted', ''); + element.defaultMuted = true; + element.muted = false; + expect(element.getAttribute('muted')).toBe(''); + expect(element.muted).toBe(false); + }); + }); + describe('canplay event', () => { it('Should dispatch after src set', () => { let dispatchedEvent: Event = null; @@ -297,4 +329,16 @@ describe('HTMLMediaElement', () => { expect(element.readyState).toBe(0); }); }); + + describe('load', () => { + it('Dispatch emptied event', () => { + let dispatchedEvent: Event = null; + element.addEventListener('emptied', (event: Event) => (dispatchedEvent = event)); + + element.load(); + + expect(dispatchedEvent.cancelable).toBe(false); + expect(dispatchedEvent.bubbles).toBe(false); + }); + }); });