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);
+ });
+ });
});