@gjsify/dom-elements 0.1.15 → 0.3.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.
- package/lib/esm/gst-time.js +11 -0
- package/lib/esm/html-video-element.js +86 -2
- package/lib/types/gst-time.d.ts +13 -0
- package/lib/types/html-video-element.d.ts +13 -4
- package/package.json +14 -12
- package/src/gst-time.ts +26 -0
- package/src/html-video-element.ts +114 -20
- package/src/test.browser.mts +686 -0
- package/tsconfig.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
+
};
|
|
@@ -1,24 +1,105 @@
|
|
|
1
1
|
import { HTMLMediaElement } from "./html-media-element.js";
|
|
2
|
+
import { Event } from "@gjsify/dom-events";
|
|
2
3
|
import * as PropertySymbol from "./property-symbol.js";
|
|
3
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;
|
|
4
13
|
class HTMLVideoElement extends HTMLMediaElement {
|
|
5
14
|
constructor() {
|
|
6
15
|
super();
|
|
16
|
+
/** Set by VideoBridge after every pipeline swap. Null when no media is loaded. */
|
|
17
|
+
this._pipeline = null;
|
|
7
18
|
this._videoWidth = 0;
|
|
8
19
|
this._videoHeight = 0;
|
|
9
20
|
this.poster = "";
|
|
10
21
|
this[PropertySymbol.tagName] = "VIDEO";
|
|
11
22
|
this[PropertySymbol.localName] = "video";
|
|
12
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
|
+
});
|
|
13
85
|
}
|
|
14
|
-
|
|
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). */
|
|
15
96
|
get videoWidth() {
|
|
16
97
|
return this._videoWidth;
|
|
17
98
|
}
|
|
18
99
|
set videoWidth(value) {
|
|
19
100
|
this._videoWidth = value;
|
|
20
101
|
}
|
|
21
|
-
/** Intrinsic height of the video (set by
|
|
102
|
+
/** Intrinsic height of the video (set by bridge when media metadata loads). */
|
|
22
103
|
get videoHeight() {
|
|
23
104
|
return this._videoHeight;
|
|
24
105
|
}
|
|
@@ -28,6 +109,9 @@ class HTMLVideoElement extends HTMLMediaElement {
|
|
|
28
109
|
get [Symbol.toStringTag]() {
|
|
29
110
|
return "HTMLVideoElement";
|
|
30
111
|
}
|
|
112
|
+
_playbin() {
|
|
113
|
+
return this._pipeline?.get_by_name("playbin") ?? null;
|
|
114
|
+
}
|
|
31
115
|
}
|
|
32
116
|
export {
|
|
33
117
|
HTMLVideoElement
|
|
@@ -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;
|
|
@@ -1,20 +1,29 @@
|
|
|
1
|
+
import type Gst from '@girs/gst-1.0';
|
|
1
2
|
import { HTMLMediaElement } from './html-media-element.js';
|
|
2
3
|
/**
|
|
3
4
|
* HTML Video Element.
|
|
4
5
|
*
|
|
5
|
-
* Dispatches 'srcobjectchange' when srcObject is set
|
|
6
|
-
*
|
|
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.
|
|
7
11
|
*/
|
|
8
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;
|
|
9
15
|
private _videoWidth;
|
|
10
16
|
private _videoHeight;
|
|
11
17
|
poster: string;
|
|
12
18
|
constructor();
|
|
13
|
-
|
|
19
|
+
play(): Promise<void>;
|
|
20
|
+
pause(): void;
|
|
21
|
+
/** Intrinsic width of the video (set by bridge when media metadata loads). */
|
|
14
22
|
get videoWidth(): number;
|
|
15
23
|
set videoWidth(value: number);
|
|
16
|
-
/** Intrinsic height of the video (set by
|
|
24
|
+
/** Intrinsic height of the video (set by bridge when media metadata loads). */
|
|
17
25
|
get videoHeight(): number;
|
|
18
26
|
set videoHeight(value: number);
|
|
19
27
|
get [Symbol.toStringTag](): string;
|
|
28
|
+
private _playbin;
|
|
20
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/dom-elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": "
|
|
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.
|
|
65
|
-
"@girs/gjs": "^4.0.0-rc.
|
|
66
|
-
"@girs/glib-2.0": "^2.88.0-4.0.0-rc.
|
|
67
|
-
"@gjsify/abort-controller": "^0.
|
|
68
|
-
"@gjsify/canvas2d-core": "^0.
|
|
69
|
-
"@gjsify/dom-events": "^0.
|
|
70
|
-
"@gjsify/fetch": "^0.
|
|
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.3.0",
|
|
69
|
+
"@gjsify/canvas2d-core": "^0.3.0",
|
|
70
|
+
"@gjsify/dom-events": "^0.3.0",
|
|
71
|
+
"@gjsify/fetch": "^0.3.0"
|
|
71
72
|
},
|
|
72
73
|
"devDependencies": {
|
|
73
|
-
"@
|
|
74
|
-
"@gjsify/
|
|
74
|
+
"@girs/gst-1.0": "^1.28.1-4.0.0-rc.9",
|
|
75
|
+
"@gjsify/cli": "^0.3.0",
|
|
76
|
+
"@gjsify/unit": "^0.3.0",
|
|
75
77
|
"@types/node": "^25.6.0",
|
|
76
|
-
"typescript": "^6.0.
|
|
78
|
+
"typescript": "^6.0.3"
|
|
77
79
|
}
|
|
78
80
|
}
|
package/src/gst-time.ts
ADDED
|
@@ -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
|
+
}
|
|
@@ -1,21 +1,44 @@
|
|
|
1
|
-
// HTMLVideoElement for GJS — video element
|
|
1
|
+
// HTMLVideoElement for GJS — video element with optional GStreamer pipeline wiring.
|
|
2
2
|
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement
|
|
3
|
-
// Reference: refs/happy-dom/packages/happy-dom/src/nodes/html-video-element/HTMLVideoElement.ts
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// The attached pipeline is set by VideoBridge after each swap. Types come from
|
|
5
|
+
// a type-only Gst import so dom-elements has no runtime dependency on GStreamer
|
|
6
|
+
// (mirrors the HTMLImageElement / GdkPixbuf split). Numeric enum values are
|
|
7
|
+
// still used at runtime because pulling the Gst module eagerly would break the
|
|
8
|
+
// "dom-elements works without gst-init" contract.
|
|
7
9
|
|
|
10
|
+
import type Gst from '@girs/gst-1.0';
|
|
8
11
|
import { HTMLMediaElement } from './html-media-element.js';
|
|
12
|
+
import { Event } from '@gjsify/dom-events';
|
|
9
13
|
import * as PropertySymbol from './property-symbol.js';
|
|
10
14
|
import { NamespaceURI } from './namespace-uri.js';
|
|
15
|
+
import { secondsToGstTime, gstTimeToSeconds } from './gst-time.js';
|
|
16
|
+
|
|
17
|
+
// Gst.State / Gst.Format / Gst.SeekFlags / Gst.SeekType numeric values, hardcoded
|
|
18
|
+
// to avoid a runtime `gi://Gst` import. Cross-checked against the GStreamer GIR.
|
|
19
|
+
const GST_STATE_PLAYING = 4;
|
|
20
|
+
const GST_STATE_PAUSED = 3;
|
|
21
|
+
const GST_FORMAT_TIME = 3;
|
|
22
|
+
const GST_SEEK_FLAG_FLUSH = 1;
|
|
23
|
+
const GST_SEEK_FLAG_KEY_UNIT = 4;
|
|
24
|
+
const GST_SEEK_TYPE_SET = 1;
|
|
25
|
+
const GST_SEEK_TYPE_NONE = 0;
|
|
26
|
+
|
|
27
|
+
type Playbin = Gst.Element & { volume?: number; mute?: boolean };
|
|
11
28
|
|
|
12
29
|
/**
|
|
13
30
|
* HTML Video Element.
|
|
14
31
|
*
|
|
15
|
-
* Dispatches 'srcobjectchange' when srcObject is set
|
|
16
|
-
*
|
|
32
|
+
* Dispatches 'srcobjectchange' when srcObject is set and 'srcchange' when src is set —
|
|
33
|
+
* bridge containers listen for these to wire up / tear down their pipelines.
|
|
34
|
+
*
|
|
35
|
+
* When a GStreamer pipeline is attached via `_pipeline`, play/pause/seek/volume
|
|
36
|
+
* delegate to it. Without a pipeline the element behaves as a pure DOM stub.
|
|
17
37
|
*/
|
|
18
38
|
export class HTMLVideoElement extends HTMLMediaElement {
|
|
39
|
+
/** Set by VideoBridge after every pipeline swap. Null when no media is loaded. */
|
|
40
|
+
_pipeline: Gst.Pipeline | null = null;
|
|
41
|
+
|
|
19
42
|
private _videoWidth = 0;
|
|
20
43
|
private _videoHeight = 0;
|
|
21
44
|
poster = '';
|
|
@@ -25,25 +48,96 @@ export class HTMLVideoElement extends HTMLMediaElement {
|
|
|
25
48
|
this[PropertySymbol.tagName] = 'VIDEO';
|
|
26
49
|
this[PropertySymbol.localName] = 'video';
|
|
27
50
|
this[PropertySymbol.namespaceURI] = NamespaceURI.html;
|
|
28
|
-
}
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this
|
|
52
|
+
// HTMLMediaElement defines paused/currentTime/duration/volume/muted as plain
|
|
53
|
+
// fields. TypeScript forbids overriding a field with an accessor in the
|
|
54
|
+
// subclass, so we install GStreamer-backed descriptors via defineProperty.
|
|
55
|
+
const self = this;
|
|
56
|
+
|
|
57
|
+
Object.defineProperty(this, 'paused', {
|
|
58
|
+
get(): boolean {
|
|
59
|
+
if (!self._pipeline) return true;
|
|
60
|
+
const [, state] = self._pipeline.get_state(0n);
|
|
61
|
+
return state !== GST_STATE_PLAYING;
|
|
62
|
+
},
|
|
63
|
+
configurable: true,
|
|
64
|
+
enumerable: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
Object.defineProperty(this, 'currentTime', {
|
|
68
|
+
get(): number {
|
|
69
|
+
if (!self._pipeline) return 0;
|
|
70
|
+
const [ok, pos] = self._pipeline.query_position(GST_FORMAT_TIME);
|
|
71
|
+
return ok ? gstTimeToSeconds(pos) : 0;
|
|
72
|
+
},
|
|
73
|
+
set(seconds: number) {
|
|
74
|
+
self._pipeline?.seek(
|
|
75
|
+
1.0,
|
|
76
|
+
GST_FORMAT_TIME,
|
|
77
|
+
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
|
|
78
|
+
GST_SEEK_TYPE_SET,
|
|
79
|
+
secondsToGstTime(seconds),
|
|
80
|
+
GST_SEEK_TYPE_NONE,
|
|
81
|
+
-1n,
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
configurable: true,
|
|
85
|
+
enumerable: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
Object.defineProperty(this, 'duration', {
|
|
89
|
+
get(): number {
|
|
90
|
+
if (!self._pipeline) return NaN;
|
|
91
|
+
const [ok, dur] = self._pipeline.query_duration(GST_FORMAT_TIME);
|
|
92
|
+
return ok && Number(dur) > 0 ? gstTimeToSeconds(dur) : NaN;
|
|
93
|
+
},
|
|
94
|
+
configurable: true,
|
|
95
|
+
enumerable: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
Object.defineProperty(this, 'volume', {
|
|
99
|
+
get(): number { return self._playbin()?.volume ?? 1.0; },
|
|
100
|
+
set(v: number) {
|
|
101
|
+
const pb = self._playbin();
|
|
102
|
+
if (pb) pb.volume = Math.max(0, Math.min(1, v));
|
|
103
|
+
},
|
|
104
|
+
configurable: true,
|
|
105
|
+
enumerable: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
Object.defineProperty(this, 'muted', {
|
|
109
|
+
get(): boolean { return self._playbin()?.mute ?? false; },
|
|
110
|
+
set(v: boolean) {
|
|
111
|
+
const pb = self._playbin();
|
|
112
|
+
if (pb) pb.mute = v;
|
|
113
|
+
},
|
|
114
|
+
configurable: true,
|
|
115
|
+
enumerable: true,
|
|
116
|
+
});
|
|
36
117
|
}
|
|
37
118
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
119
|
+
override async play(): Promise<void> {
|
|
120
|
+
this._pipeline?.set_state(GST_STATE_PLAYING);
|
|
121
|
+
this.dispatchEvent(new Event('play'));
|
|
122
|
+
this.dispatchEvent(new Event('playing'));
|
|
41
123
|
}
|
|
42
|
-
|
|
43
|
-
|
|
124
|
+
|
|
125
|
+
override pause(): void {
|
|
126
|
+
this._pipeline?.set_state(GST_STATE_PAUSED);
|
|
127
|
+
this.dispatchEvent(new Event('pause'));
|
|
44
128
|
}
|
|
45
129
|
|
|
46
|
-
|
|
47
|
-
|
|
130
|
+
/** Intrinsic width of the video (set by bridge when media metadata loads). */
|
|
131
|
+
get videoWidth(): number { return this._videoWidth; }
|
|
132
|
+
set videoWidth(value: number) { this._videoWidth = value; }
|
|
133
|
+
|
|
134
|
+
/** Intrinsic height of the video (set by bridge when media metadata loads). */
|
|
135
|
+
get videoHeight(): number { return this._videoHeight; }
|
|
136
|
+
set videoHeight(value: number) { this._videoHeight = value; }
|
|
137
|
+
|
|
138
|
+
get [Symbol.toStringTag](): string { return 'HTMLVideoElement'; }
|
|
139
|
+
|
|
140
|
+
private _playbin(): Playbin | null {
|
|
141
|
+
return (this._pipeline?.get_by_name('playbin') as Playbin | null) ?? null;
|
|
48
142
|
}
|
|
49
143
|
}
|