@gjsify/dom-elements 0.1.13 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import { Node } from "./node.js";
2
2
  import { HTMLElement } from "./html-element.js";
3
3
  import { HTMLImageElement } from "./html-image-element.js";
4
+ import { HTMLVideoElement } from "./html-video-element.js";
4
5
  import { HTMLCanvasElement } from "./html-canvas-element.js";
5
6
  import { Text } from "./text.js";
6
7
  import { Comment } from "./comment.js";
@@ -36,6 +37,8 @@ class Document extends Node {
36
37
  switch (tag) {
37
38
  case "img":
38
39
  return new HTMLImageElement();
40
+ case "video":
41
+ return new HTMLVideoElement();
39
42
  case "canvas":
40
43
  return new HTMLCanvasElement();
41
44
  default: {
@@ -0,0 +1,11 @@
1
+ const NS_PER_SECOND = 1e9;
2
+ function secondsToGstTime(seconds) {
3
+ return BigInt(Math.round(seconds * NS_PER_SECOND));
4
+ }
5
+ function gstTimeToSeconds(nanoseconds) {
6
+ return Number(nanoseconds) / NS_PER_SECOND;
7
+ }
8
+ export {
9
+ gstTimeToSeconds,
10
+ secondsToGstTime
11
+ };
@@ -0,0 +1,125 @@
1
+ import { Event } from "@gjsify/dom-events";
2
+ import { HTMLElement } from "./html-element.js";
3
+ const HAVE_NOTHING = 0;
4
+ const HAVE_METADATA = 1;
5
+ const HAVE_CURRENT_DATA = 2;
6
+ const HAVE_FUTURE_DATA = 3;
7
+ const HAVE_ENOUGH_DATA = 4;
8
+ const NETWORK_EMPTY = 0;
9
+ const NETWORK_IDLE = 1;
10
+ const NETWORK_LOADING = 2;
11
+ const NETWORK_NO_SOURCE = 3;
12
+ class HTMLMediaElement extends HTMLElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ // -- Source --
16
+ this._src = "";
17
+ this._srcObject = null;
18
+ // -- Playback state --
19
+ this.currentTime = 0;
20
+ this.duration = NaN;
21
+ this.paused = true;
22
+ this.ended = false;
23
+ this.volume = 1;
24
+ this.muted = false;
25
+ this.defaultMuted = false;
26
+ this.loop = false;
27
+ this.autoplay = false;
28
+ this.preload = "";
29
+ this.playbackRate = 1;
30
+ this.defaultPlaybackRate = 1;
31
+ // -- Readiness --
32
+ this.readyState = HAVE_NOTHING;
33
+ this.networkState = NETWORK_EMPTY;
34
+ }
35
+ // -- Buffered/seekable stubs --
36
+ get buffered() {
37
+ return { length: 0, start: () => 0, end: () => 0 };
38
+ }
39
+ get seekable() {
40
+ return { length: 0, start: () => 0, end: () => 0 };
41
+ }
42
+ get played() {
43
+ return { length: 0, start: () => 0, end: () => 0 };
44
+ }
45
+ // -- src property --
46
+ get src() {
47
+ return this._src;
48
+ }
49
+ set src(value) {
50
+ this._src = value;
51
+ this._srcObject = null;
52
+ this.dispatchEvent(new Event("srcchange"));
53
+ }
54
+ // -- srcObject property (MediaStream) --
55
+ get srcObject() {
56
+ return this._srcObject;
57
+ }
58
+ set srcObject(stream) {
59
+ this._srcObject = stream;
60
+ this._src = "";
61
+ this.dispatchEvent(new Event("srcobjectchange"));
62
+ }
63
+ // -- Playback control --
64
+ play() {
65
+ this.paused = false;
66
+ this.ended = false;
67
+ this.dispatchEvent(new Event("play"));
68
+ return Promise.resolve();
69
+ }
70
+ pause() {
71
+ this.paused = true;
72
+ this.dispatchEvent(new Event("pause"));
73
+ }
74
+ load() {
75
+ this.readyState = HAVE_NOTHING;
76
+ this.networkState = NETWORK_LOADING;
77
+ this.dispatchEvent(new Event("loadstart"));
78
+ }
79
+ canPlayType(_type) {
80
+ return "";
81
+ }
82
+ static {
83
+ // -- Static constants --
84
+ this.HAVE_NOTHING = HAVE_NOTHING;
85
+ }
86
+ static {
87
+ this.HAVE_METADATA = HAVE_METADATA;
88
+ }
89
+ static {
90
+ this.HAVE_CURRENT_DATA = HAVE_CURRENT_DATA;
91
+ }
92
+ static {
93
+ this.HAVE_FUTURE_DATA = HAVE_FUTURE_DATA;
94
+ }
95
+ static {
96
+ this.HAVE_ENOUGH_DATA = HAVE_ENOUGH_DATA;
97
+ }
98
+ static {
99
+ this.NETWORK_EMPTY = NETWORK_EMPTY;
100
+ }
101
+ static {
102
+ this.NETWORK_IDLE = NETWORK_IDLE;
103
+ }
104
+ static {
105
+ this.NETWORK_LOADING = NETWORK_LOADING;
106
+ }
107
+ static {
108
+ this.NETWORK_NO_SOURCE = NETWORK_NO_SOURCE;
109
+ }
110
+ get [Symbol.toStringTag]() {
111
+ return "HTMLMediaElement";
112
+ }
113
+ }
114
+ export {
115
+ HAVE_CURRENT_DATA,
116
+ HAVE_ENOUGH_DATA,
117
+ HAVE_FUTURE_DATA,
118
+ HAVE_METADATA,
119
+ HAVE_NOTHING,
120
+ HTMLMediaElement,
121
+ NETWORK_EMPTY,
122
+ NETWORK_IDLE,
123
+ NETWORK_LOADING,
124
+ NETWORK_NO_SOURCE
125
+ };
@@ -0,0 +1,118 @@
1
+ import { HTMLMediaElement } from "./html-media-element.js";
2
+ import { Event } from "@gjsify/dom-events";
3
+ import * as PropertySymbol from "./property-symbol.js";
4
+ import { NamespaceURI } from "./namespace-uri.js";
5
+ import { secondsToGstTime, gstTimeToSeconds } from "./gst-time.js";
6
+ const GST_STATE_PLAYING = 4;
7
+ const GST_STATE_PAUSED = 3;
8
+ const GST_FORMAT_TIME = 3;
9
+ const GST_SEEK_FLAG_FLUSH = 1;
10
+ const GST_SEEK_FLAG_KEY_UNIT = 4;
11
+ const GST_SEEK_TYPE_SET = 1;
12
+ const GST_SEEK_TYPE_NONE = 0;
13
+ class HTMLVideoElement extends HTMLMediaElement {
14
+ constructor() {
15
+ super();
16
+ /** Set by VideoBridge after every pipeline swap. Null when no media is loaded. */
17
+ this._pipeline = null;
18
+ this._videoWidth = 0;
19
+ this._videoHeight = 0;
20
+ this.poster = "";
21
+ this[PropertySymbol.tagName] = "VIDEO";
22
+ this[PropertySymbol.localName] = "video";
23
+ this[PropertySymbol.namespaceURI] = NamespaceURI.html;
24
+ const self = this;
25
+ Object.defineProperty(this, "paused", {
26
+ get() {
27
+ if (!self._pipeline) return true;
28
+ const [, state] = self._pipeline.get_state(0n);
29
+ return state !== GST_STATE_PLAYING;
30
+ },
31
+ configurable: true,
32
+ enumerable: true
33
+ });
34
+ Object.defineProperty(this, "currentTime", {
35
+ get() {
36
+ if (!self._pipeline) return 0;
37
+ const [ok, pos] = self._pipeline.query_position(GST_FORMAT_TIME);
38
+ return ok ? gstTimeToSeconds(pos) : 0;
39
+ },
40
+ set(seconds) {
41
+ self._pipeline?.seek(
42
+ 1,
43
+ GST_FORMAT_TIME,
44
+ GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
45
+ GST_SEEK_TYPE_SET,
46
+ secondsToGstTime(seconds),
47
+ GST_SEEK_TYPE_NONE,
48
+ -1n
49
+ );
50
+ },
51
+ configurable: true,
52
+ enumerable: true
53
+ });
54
+ Object.defineProperty(this, "duration", {
55
+ get() {
56
+ if (!self._pipeline) return NaN;
57
+ const [ok, dur] = self._pipeline.query_duration(GST_FORMAT_TIME);
58
+ return ok && Number(dur) > 0 ? gstTimeToSeconds(dur) : NaN;
59
+ },
60
+ configurable: true,
61
+ enumerable: true
62
+ });
63
+ Object.defineProperty(this, "volume", {
64
+ get() {
65
+ return self._playbin()?.volume ?? 1;
66
+ },
67
+ set(v) {
68
+ const pb = self._playbin();
69
+ if (pb) pb.volume = Math.max(0, Math.min(1, v));
70
+ },
71
+ configurable: true,
72
+ enumerable: true
73
+ });
74
+ Object.defineProperty(this, "muted", {
75
+ get() {
76
+ return self._playbin()?.mute ?? false;
77
+ },
78
+ set(v) {
79
+ const pb = self._playbin();
80
+ if (pb) pb.mute = v;
81
+ },
82
+ configurable: true,
83
+ enumerable: true
84
+ });
85
+ }
86
+ async play() {
87
+ this._pipeline?.set_state(GST_STATE_PLAYING);
88
+ this.dispatchEvent(new Event("play"));
89
+ this.dispatchEvent(new Event("playing"));
90
+ }
91
+ pause() {
92
+ this._pipeline?.set_state(GST_STATE_PAUSED);
93
+ this.dispatchEvent(new Event("pause"));
94
+ }
95
+ /** Intrinsic width of the video (set by bridge when media metadata loads). */
96
+ get videoWidth() {
97
+ return this._videoWidth;
98
+ }
99
+ set videoWidth(value) {
100
+ this._videoWidth = value;
101
+ }
102
+ /** Intrinsic height of the video (set by bridge when media metadata loads). */
103
+ get videoHeight() {
104
+ return this._videoHeight;
105
+ }
106
+ set videoHeight(value) {
107
+ this._videoHeight = value;
108
+ }
109
+ get [Symbol.toStringTag]() {
110
+ return "HTMLVideoElement";
111
+ }
112
+ _playbin() {
113
+ return this._pipeline?.get_by_name("playbin") ?? null;
114
+ }
115
+ }
116
+ export {
117
+ HTMLVideoElement
118
+ };
package/lib/esm/index.js CHANGED
@@ -11,6 +11,8 @@ import { Element } from "./element.js";
11
11
  import { HTMLElement, CSSStyleDeclaration } from "./html-element.js";
12
12
  import { HTMLCanvasElement } from "./html-canvas-element.js";
13
13
  import { HTMLImageElement } from "./html-image-element.js";
14
+ import { HTMLMediaElement } from "./html-media-element.js";
15
+ import { HTMLVideoElement } from "./html-video-element.js";
14
16
  import { Image } from "./image.js";
15
17
  import { Document, document } from "./document.js";
16
18
  import { MutationObserver } from "./mutation-observer.js";
@@ -39,6 +41,8 @@ export {
39
41
  HTMLCanvasElement,
40
42
  HTMLElement,
41
43
  HTMLImageElement,
44
+ HTMLMediaElement,
45
+ HTMLVideoElement,
42
46
  Image,
43
47
  IntersectionObserver,
44
48
  MediaQueryList,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Convert seconds (number) to GStreamer nanoseconds (bigint).
3
+ * Rounds to the nearest nanosecond to avoid floating-point drift over
4
+ * repeated back-and-forth conversions.
5
+ */
6
+ export declare function secondsToGstTime(seconds: number): bigint;
7
+ /**
8
+ * Convert GStreamer nanoseconds to seconds (number).
9
+ * Accepts both `bigint` (the runtime type from GStreamer queries) and `number`
10
+ * (what the `@girs/gst-1.0` typings currently declare — a known GIR bug for
11
+ * `gint64` return values in `query_position` / `query_duration`).
12
+ */
13
+ export declare function gstTimeToSeconds(nanoseconds: bigint | number): number;
@@ -0,0 +1,67 @@
1
+ import { HTMLElement } from './html-element.js';
2
+ export declare const HAVE_NOTHING = 0;
3
+ export declare const HAVE_METADATA = 1;
4
+ export declare const HAVE_CURRENT_DATA = 2;
5
+ export declare const HAVE_FUTURE_DATA = 3;
6
+ export declare const HAVE_ENOUGH_DATA = 4;
7
+ export declare const NETWORK_EMPTY = 0;
8
+ export declare const NETWORK_IDLE = 1;
9
+ export declare const NETWORK_LOADING = 2;
10
+ export declare const NETWORK_NO_SOURCE = 3;
11
+ /**
12
+ * Base class for media elements (video, audio).
13
+ *
14
+ * Stores media state and dispatches DOM events. Pipeline construction is
15
+ * delegated to the bridge container via internal events.
16
+ */
17
+ export declare class HTMLMediaElement extends HTMLElement {
18
+ private _src;
19
+ private _srcObject;
20
+ currentTime: number;
21
+ duration: number;
22
+ paused: boolean;
23
+ ended: boolean;
24
+ volume: number;
25
+ muted: boolean;
26
+ defaultMuted: boolean;
27
+ loop: boolean;
28
+ autoplay: boolean;
29
+ preload: '' | 'none' | 'metadata' | 'auto';
30
+ playbackRate: number;
31
+ defaultPlaybackRate: number;
32
+ readyState: number;
33
+ networkState: number;
34
+ get buffered(): {
35
+ length: number;
36
+ start(index: number): number;
37
+ end(index: number): number;
38
+ };
39
+ get seekable(): {
40
+ length: number;
41
+ start(index: number): number;
42
+ end(index: number): number;
43
+ };
44
+ get played(): {
45
+ length: number;
46
+ start(index: number): number;
47
+ end(index: number): number;
48
+ };
49
+ get src(): string;
50
+ set src(value: string);
51
+ get srcObject(): any;
52
+ set srcObject(stream: any);
53
+ play(): Promise<void>;
54
+ pause(): void;
55
+ load(): void;
56
+ canPlayType(_type: string): '' | 'maybe' | 'probably';
57
+ static readonly HAVE_NOTHING = 0;
58
+ static readonly HAVE_METADATA = 1;
59
+ static readonly HAVE_CURRENT_DATA = 2;
60
+ static readonly HAVE_FUTURE_DATA = 3;
61
+ static readonly HAVE_ENOUGH_DATA = 4;
62
+ static readonly NETWORK_EMPTY = 0;
63
+ static readonly NETWORK_IDLE = 1;
64
+ static readonly NETWORK_LOADING = 2;
65
+ static readonly NETWORK_NO_SOURCE = 3;
66
+ get [Symbol.toStringTag](): string;
67
+ }
@@ -0,0 +1,29 @@
1
+ import type Gst from '@girs/gst-1.0';
2
+ import { HTMLMediaElement } from './html-media-element.js';
3
+ /**
4
+ * HTML Video Element.
5
+ *
6
+ * Dispatches 'srcobjectchange' when srcObject is set and 'srcchange' when src is set —
7
+ * bridge containers listen for these to wire up / tear down their pipelines.
8
+ *
9
+ * When a GStreamer pipeline is attached via `_pipeline`, play/pause/seek/volume
10
+ * delegate to it. Without a pipeline the element behaves as a pure DOM stub.
11
+ */
12
+ export declare class HTMLVideoElement extends HTMLMediaElement {
13
+ /** Set by VideoBridge after every pipeline swap. Null when no media is loaded. */
14
+ _pipeline: Gst.Pipeline | null;
15
+ private _videoWidth;
16
+ private _videoHeight;
17
+ poster: string;
18
+ constructor();
19
+ play(): Promise<void>;
20
+ pause(): void;
21
+ /** Intrinsic width of the video (set by bridge when media metadata loads). */
22
+ get videoWidth(): number;
23
+ set videoWidth(value: number);
24
+ /** Intrinsic height of the video (set by bridge when media metadata loads). */
25
+ get videoHeight(): number;
26
+ set videoHeight(value: number);
27
+ get [Symbol.toStringTag](): string;
28
+ private _playbin;
29
+ }
@@ -11,6 +11,8 @@ export { Element } from './element.js';
11
11
  export { HTMLElement, CSSStyleDeclaration } from './html-element.js';
12
12
  export { HTMLCanvasElement } from './html-canvas-element.js';
13
13
  export { HTMLImageElement } from './html-image-element.js';
14
+ export { HTMLMediaElement } from './html-media-element.js';
15
+ export { HTMLVideoElement } from './html-video-element.js';
14
16
  export { Image } from './image.js';
15
17
  export { Document, document } from './document.js';
16
18
  export { MutationObserver } from './mutation-observer.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/dom-elements",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "description": "DOM element hierarchy (Node, Element, HTMLElement, HTMLImageElement) for GJS",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -51,8 +51,9 @@
51
51
  "build:types": "tsc",
52
52
  "build:test": "yarn build:test:gjs",
53
53
  "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
54
+ "build:test:browser": "gjsify build src/test.browser.mts --app browser --outfile dist/test.browser.mjs",
54
55
  "test": "yarn build:gjsify && yarn build:test && yarn test:gjs",
55
- "test:gjs": "gjs -m test.gjs.mjs"
56
+ "test:gjs": "gjsify run test.gjs.mjs"
56
57
  },
57
58
  "keywords": [
58
59
  "gjs",
@@ -61,18 +62,19 @@
61
62
  "node"
62
63
  ],
63
64
  "dependencies": {
64
- "@girs/gdkpixbuf-2.0": "^2.0.0-4.0.0-rc.3",
65
- "@girs/gjs": "^4.0.0-rc.3",
66
- "@girs/glib-2.0": "^2.88.0-4.0.0-rc.3",
67
- "@gjsify/abort-controller": "^0.1.13",
68
- "@gjsify/canvas2d-core": "^0.1.13",
69
- "@gjsify/dom-events": "^0.1.13",
70
- "@gjsify/fetch": "^0.1.13"
65
+ "@girs/gdkpixbuf-2.0": "^2.0.0-4.0.0-rc.9",
66
+ "@girs/gjs": "^4.0.0-rc.9",
67
+ "@girs/glib-2.0": "^2.88.0-4.0.0-rc.9",
68
+ "@gjsify/abort-controller": "^0.2.0",
69
+ "@gjsify/canvas2d-core": "^0.2.0",
70
+ "@gjsify/dom-events": "^0.2.0",
71
+ "@gjsify/fetch": "^0.2.0"
71
72
  },
72
73
  "devDependencies": {
73
- "@gjsify/cli": "^0.1.13",
74
- "@gjsify/unit": "^0.1.13",
74
+ "@girs/gst-1.0": "^1.28.1-4.0.0-rc.9",
75
+ "@gjsify/cli": "^0.2.0",
76
+ "@gjsify/unit": "^0.2.0",
75
77
  "@types/node": "^25.6.0",
76
- "typescript": "^6.0.2"
78
+ "typescript": "^6.0.3"
77
79
  }
78
80
  }
package/src/document.ts CHANGED
@@ -5,6 +5,7 @@ import { Node } from './node.js';
5
5
  import { Element } from './element.js';
6
6
  import { HTMLElement } from './html-element.js';
7
7
  import { HTMLImageElement } from './html-image-element.js';
8
+ import { HTMLVideoElement } from './html-video-element.js';
8
9
  import { HTMLCanvasElement } from './html-canvas-element.js';
9
10
  import { Text } from './text.js';
10
11
  import { Comment } from './comment.js';
@@ -48,6 +49,7 @@ export class Document extends Node {
48
49
  const tag = tagName.toLowerCase();
49
50
  switch (tag) {
50
51
  case 'img': return new HTMLImageElement();
52
+ case 'video': return new HTMLVideoElement();
51
53
  case 'canvas': return new HTMLCanvasElement();
52
54
  default: {
53
55
  const factory = Document._elementFactories.get(tag);
@@ -0,0 +1,26 @@
1
+ // Conversions between seconds (Web video API) and GStreamer's nanosecond
2
+ // `BigInt` timebase (used by `Gst.Format.TIME` throughout GStreamer). Lives
3
+ // in @gjsify/dom-elements so HTMLVideoElement/HTMLAudioElement can use it
4
+ // directly; @gjsify/video re-exports these for consumers of the bridge
5
+ // package. Kept as pure number math — no runtime Gst import required.
6
+
7
+ const NS_PER_SECOND = 1_000_000_000;
8
+
9
+ /**
10
+ * Convert seconds (number) to GStreamer nanoseconds (bigint).
11
+ * Rounds to the nearest nanosecond to avoid floating-point drift over
12
+ * repeated back-and-forth conversions.
13
+ */
14
+ export function secondsToGstTime(seconds: number): bigint {
15
+ return BigInt(Math.round(seconds * NS_PER_SECOND));
16
+ }
17
+
18
+ /**
19
+ * Convert GStreamer nanoseconds to seconds (number).
20
+ * Accepts both `bigint` (the runtime type from GStreamer queries) and `number`
21
+ * (what the `@girs/gst-1.0` typings currently declare — a known GIR bug for
22
+ * `gint64` return values in `query_position` / `query_duration`).
23
+ */
24
+ export function gstTimeToSeconds(nanoseconds: bigint | number): number {
25
+ return Number(nanoseconds) / NS_PER_SECOND;
26
+ }
@@ -0,0 +1,123 @@
1
+ // HTMLMediaElement for GJS — base class for HTMLVideoElement and HTMLAudioElement.
2
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
3
+ // Reference: refs/happy-dom/packages/happy-dom/src/nodes/html-media-element/HTMLMediaElement.ts
4
+ //
5
+ // This is a pure DOM class — it stores media state and dispatches events but
6
+ // does NOT interact with GStreamer. The bridge container (VideoBridge etc.)
7
+ // listens for internal events and handles the actual pipeline.
8
+
9
+ import { Event } from '@gjsify/dom-events';
10
+
11
+ import { HTMLElement } from './html-element.js';
12
+
13
+ // Readiness states
14
+ export const HAVE_NOTHING = 0;
15
+ export const HAVE_METADATA = 1;
16
+ export const HAVE_CURRENT_DATA = 2;
17
+ export const HAVE_FUTURE_DATA = 3;
18
+ export const HAVE_ENOUGH_DATA = 4;
19
+
20
+ // Network states
21
+ export const NETWORK_EMPTY = 0;
22
+ export const NETWORK_IDLE = 1;
23
+ export const NETWORK_LOADING = 2;
24
+ export const NETWORK_NO_SOURCE = 3;
25
+
26
+ /**
27
+ * Base class for media elements (video, audio).
28
+ *
29
+ * Stores media state and dispatches DOM events. Pipeline construction is
30
+ * delegated to the bridge container via internal events.
31
+ */
32
+ export class HTMLMediaElement extends HTMLElement {
33
+ // -- Source --
34
+ private _src = '';
35
+ private _srcObject: any = null;
36
+
37
+ // -- Playback state --
38
+ currentTime = 0;
39
+ duration = NaN;
40
+ paused = true;
41
+ ended = false;
42
+ volume = 1;
43
+ muted = false;
44
+ defaultMuted = false;
45
+ loop = false;
46
+ autoplay = false;
47
+ preload: '' | 'none' | 'metadata' | 'auto' = '';
48
+ playbackRate = 1;
49
+ defaultPlaybackRate = 1;
50
+
51
+ // -- Readiness --
52
+ readyState = HAVE_NOTHING;
53
+ networkState = NETWORK_EMPTY;
54
+
55
+ // -- Buffered/seekable stubs --
56
+ get buffered(): { length: number; start(index: number): number; end(index: number): number } {
57
+ return { length: 0, start: () => 0, end: () => 0 };
58
+ }
59
+ get seekable(): { length: number; start(index: number): number; end(index: number): number } {
60
+ return { length: 0, start: () => 0, end: () => 0 };
61
+ }
62
+ get played(): { length: number; start(index: number): number; end(index: number): number } {
63
+ return { length: 0, start: () => 0, end: () => 0 };
64
+ }
65
+
66
+ // -- src property --
67
+ get src(): string {
68
+ return this._src;
69
+ }
70
+ set src(value: string) {
71
+ this._src = value;
72
+ this._srcObject = null;
73
+ this.dispatchEvent(new Event('srcchange'));
74
+ }
75
+
76
+ // -- srcObject property (MediaStream) --
77
+ get srcObject(): any {
78
+ return this._srcObject;
79
+ }
80
+ set srcObject(stream: any) {
81
+ this._srcObject = stream;
82
+ this._src = '';
83
+ this.dispatchEvent(new Event('srcobjectchange'));
84
+ }
85
+
86
+ // -- Playback control --
87
+ play(): Promise<void> {
88
+ this.paused = false;
89
+ this.ended = false;
90
+ this.dispatchEvent(new Event('play'));
91
+ return Promise.resolve();
92
+ }
93
+
94
+ pause(): void {
95
+ this.paused = true;
96
+ this.dispatchEvent(new Event('pause'));
97
+ }
98
+
99
+ load(): void {
100
+ this.readyState = HAVE_NOTHING;
101
+ this.networkState = NETWORK_LOADING;
102
+ this.dispatchEvent(new Event('loadstart'));
103
+ }
104
+
105
+ canPlayType(_type: string): '' | 'maybe' | 'probably' {
106
+ return '';
107
+ }
108
+
109
+ // -- Static constants --
110
+ static readonly HAVE_NOTHING = HAVE_NOTHING;
111
+ static readonly HAVE_METADATA = HAVE_METADATA;
112
+ static readonly HAVE_CURRENT_DATA = HAVE_CURRENT_DATA;
113
+ static readonly HAVE_FUTURE_DATA = HAVE_FUTURE_DATA;
114
+ static readonly HAVE_ENOUGH_DATA = HAVE_ENOUGH_DATA;
115
+ static readonly NETWORK_EMPTY = NETWORK_EMPTY;
116
+ static readonly NETWORK_IDLE = NETWORK_IDLE;
117
+ static readonly NETWORK_LOADING = NETWORK_LOADING;
118
+ static readonly NETWORK_NO_SOURCE = NETWORK_NO_SOURCE;
119
+
120
+ get [Symbol.toStringTag](): string {
121
+ return 'HTMLMediaElement';
122
+ }
123
+ }