@helios-project/player 0.48.3 → 0.76.4
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/README.md +73 -2
- package/dist/bridge.js +105 -4
- package/dist/controllers.d.ts +48 -5
- package/dist/controllers.js +175 -9
- package/dist/features/audio-context-manager.d.ts +20 -0
- package/dist/features/audio-context-manager.js +81 -0
- package/dist/features/audio-fader.d.ts +13 -0
- package/dist/features/audio-fader.js +118 -0
- package/dist/features/audio-metering.d.ts +28 -0
- package/dist/features/audio-metering.js +160 -0
- package/dist/features/audio-tracks.d.ts +42 -0
- package/dist/features/audio-tracks.js +104 -0
- package/dist/features/audio-utils.d.ts +8 -1
- package/dist/features/audio-utils.js +118 -23
- package/dist/features/caption-parser.d.ts +9 -0
- package/dist/features/caption-parser.js +140 -0
- package/dist/features/dom-capture.d.ts +4 -1
- package/dist/features/dom-capture.js +178 -67
- package/dist/features/exporter.d.ts +14 -5
- package/dist/features/exporter.js +103 -21
- package/dist/features/media-session.d.ts +12 -0
- package/dist/features/media-session.js +107 -0
- package/dist/features/text-tracks.d.ts +31 -3
- package/dist/features/text-tracks.js +140 -2
- package/dist/features/video-tracks.d.ts +43 -0
- package/dist/features/video-tracks.js +107 -0
- package/dist/helios-player.bundle.mjs +4700 -2968
- package/dist/helios-player.global.js +448 -212
- package/dist/index.d.ts +177 -4
- package/dist/index.js +1598 -151
- package/package.json +3 -3
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export class HeliosMediaSession {
|
|
2
|
+
player;
|
|
3
|
+
controller;
|
|
4
|
+
unsubscribe = null;
|
|
5
|
+
constructor(player, controller) {
|
|
6
|
+
this.player = player;
|
|
7
|
+
this.controller = controller;
|
|
8
|
+
if (!('mediaSession' in navigator))
|
|
9
|
+
return;
|
|
10
|
+
this.updateMetadata();
|
|
11
|
+
this.setupHandlers();
|
|
12
|
+
// Subscribe to state changes
|
|
13
|
+
this.unsubscribe = controller.subscribe(state => this.updateState(state));
|
|
14
|
+
// Initial state update
|
|
15
|
+
this.updateState(controller.getState());
|
|
16
|
+
}
|
|
17
|
+
updateMetadata() {
|
|
18
|
+
if (!('mediaSession' in navigator))
|
|
19
|
+
return;
|
|
20
|
+
const title = this.player.getAttribute('media-title') || '';
|
|
21
|
+
const artist = this.player.getAttribute('media-artist') || '';
|
|
22
|
+
const album = this.player.getAttribute('media-album') || '';
|
|
23
|
+
let artworkSrc = this.player.getAttribute('media-artwork');
|
|
24
|
+
// Fallback to poster if artwork not set
|
|
25
|
+
if (!artworkSrc) {
|
|
26
|
+
artworkSrc = this.player.getAttribute('poster');
|
|
27
|
+
}
|
|
28
|
+
const artwork = artworkSrc ? [{ src: artworkSrc }] : [];
|
|
29
|
+
navigator.mediaSession.metadata = new MediaMetadata({
|
|
30
|
+
title,
|
|
31
|
+
artist,
|
|
32
|
+
album,
|
|
33
|
+
artwork
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
setupHandlers() {
|
|
37
|
+
if (!('mediaSession' in navigator))
|
|
38
|
+
return;
|
|
39
|
+
const actions = [
|
|
40
|
+
['play', () => this.controller.play()],
|
|
41
|
+
['pause', () => this.controller.pause()],
|
|
42
|
+
['seekbackward', (details) => this.seekRelative(-(details.seekOffset || 10))],
|
|
43
|
+
['seekforward', (details) => this.seekRelative(details.seekOffset || 10)],
|
|
44
|
+
['seekto', (details) => {
|
|
45
|
+
if (details.seekTime !== undefined) {
|
|
46
|
+
const state = this.controller.getState();
|
|
47
|
+
if (state.fps) {
|
|
48
|
+
this.controller.seek(Math.floor(details.seekTime * state.fps));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}],
|
|
52
|
+
['stop', () => {
|
|
53
|
+
this.controller.pause();
|
|
54
|
+
this.controller.seek(0);
|
|
55
|
+
}]
|
|
56
|
+
];
|
|
57
|
+
for (const [action, handler] of actions) {
|
|
58
|
+
try {
|
|
59
|
+
navigator.mediaSession.setActionHandler(action, handler);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
// Ignore unsupported actions
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
seekRelative(seconds) {
|
|
67
|
+
const state = this.controller.getState();
|
|
68
|
+
if (state.fps && state.duration) {
|
|
69
|
+
const currentSeconds = state.currentFrame / state.fps;
|
|
70
|
+
const newSeconds = Math.max(0, Math.min(state.duration, currentSeconds + seconds));
|
|
71
|
+
this.controller.seek(Math.floor(newSeconds * state.fps));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
updateState(state) {
|
|
75
|
+
if (!('mediaSession' in navigator))
|
|
76
|
+
return;
|
|
77
|
+
navigator.mediaSession.playbackState = state.isPlaying ? 'playing' : 'paused';
|
|
78
|
+
if (state.duration > 0 && state.fps > 0) {
|
|
79
|
+
try {
|
|
80
|
+
navigator.mediaSession.setPositionState({
|
|
81
|
+
duration: state.duration,
|
|
82
|
+
playbackRate: state.playbackRate || 1,
|
|
83
|
+
position: Math.min(state.duration, Math.max(0, state.currentFrame / state.fps))
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
// Log warning (e.g. invalid position)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
destroy() {
|
|
92
|
+
if ('mediaSession' in navigator) {
|
|
93
|
+
const actions = ['play', 'pause', 'seekbackward', 'seekforward', 'seekto', 'stop'];
|
|
94
|
+
actions.forEach(action => {
|
|
95
|
+
try {
|
|
96
|
+
navigator.mediaSession.setActionHandler(action, null);
|
|
97
|
+
}
|
|
98
|
+
catch (e) { }
|
|
99
|
+
});
|
|
100
|
+
navigator.mediaSession.playbackState = 'none';
|
|
101
|
+
}
|
|
102
|
+
if (this.unsubscribe) {
|
|
103
|
+
this.unsubscribe();
|
|
104
|
+
this.unsubscribe = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -3,38 +3,66 @@ export interface TrackHost {
|
|
|
3
3
|
handleTrackModeChange(track: HeliosTextTrack): void;
|
|
4
4
|
}
|
|
5
5
|
export declare class HeliosCue {
|
|
6
|
+
id: string;
|
|
7
|
+
track: HeliosTextTrack | null;
|
|
6
8
|
startTime: number;
|
|
7
9
|
endTime: number;
|
|
10
|
+
pauseOnExit: boolean;
|
|
8
11
|
text: string;
|
|
9
12
|
constructor(startTime: number, endTime: number, text: string);
|
|
10
13
|
}
|
|
11
14
|
export declare const CueClass: any;
|
|
15
|
+
export declare class HeliosTextTrackCueList implements Iterable<HeliosCue> {
|
|
16
|
+
[index: number]: HeliosCue;
|
|
17
|
+
private _cues;
|
|
18
|
+
constructor(cues: HeliosCue[]);
|
|
19
|
+
get length(): number;
|
|
20
|
+
getCueById(id: string): HeliosCue | null;
|
|
21
|
+
[Symbol.iterator](): ArrayIterator<HeliosCue>;
|
|
22
|
+
}
|
|
12
23
|
export declare class HeliosTextTrack extends EventTarget {
|
|
13
24
|
private _mode;
|
|
14
25
|
private _cues;
|
|
26
|
+
private _activeCues;
|
|
27
|
+
private _oncuechange;
|
|
15
28
|
private _kind;
|
|
16
29
|
private _label;
|
|
17
30
|
private _language;
|
|
18
31
|
private _id;
|
|
19
32
|
private _host;
|
|
33
|
+
private _cuesWrapper;
|
|
34
|
+
private _activeCuesWrapper;
|
|
20
35
|
constructor(kind: string, label: string, language: string, host: TrackHost);
|
|
21
36
|
get kind(): string;
|
|
22
37
|
get label(): string;
|
|
23
38
|
get language(): string;
|
|
24
39
|
get id(): string;
|
|
25
|
-
get cues():
|
|
40
|
+
get cues(): HeliosTextTrackCueList;
|
|
41
|
+
get activeCues(): HeliosTextTrackCueList;
|
|
42
|
+
get oncuechange(): ((event: Event) => void) | null;
|
|
43
|
+
set oncuechange(handler: ((event: Event) => void) | null);
|
|
26
44
|
get mode(): TextTrackMode;
|
|
27
45
|
set mode(value: TextTrackMode);
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
updateActiveCues(currentTime: number): void;
|
|
47
|
+
addCue(cue: HeliosCue): void;
|
|
48
|
+
removeCue(cue: HeliosCue): void;
|
|
30
49
|
}
|
|
31
50
|
export declare class HeliosTextTrackList extends EventTarget implements Iterable<HeliosTextTrack> {
|
|
32
51
|
private tracks;
|
|
52
|
+
private _onaddtrack;
|
|
53
|
+
private _onremovetrack;
|
|
54
|
+
private _onchange;
|
|
33
55
|
get length(): number;
|
|
34
56
|
[index: number]: HeliosTextTrack;
|
|
35
57
|
[Symbol.iterator](): Iterator<HeliosTextTrack>;
|
|
36
58
|
addTrack(track: HeliosTextTrack): void;
|
|
37
59
|
getTrackById(id: string): HeliosTextTrack | null;
|
|
38
60
|
removeTrack(track: HeliosTextTrack): void;
|
|
61
|
+
dispatchChangeEvent(): void;
|
|
62
|
+
get onaddtrack(): ((event: any) => void) | null;
|
|
39
63
|
set onaddtrack(handler: ((event: any) => void) | null);
|
|
64
|
+
get onremovetrack(): ((event: any) => void) | null;
|
|
65
|
+
set onremovetrack(handler: ((event: any) => void) | null);
|
|
66
|
+
get onchange(): ((event: any) => void) | null;
|
|
67
|
+
set onchange(handler: ((event: any) => void) | null);
|
|
40
68
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Shim for VTTCue if not present (e.g. JSDOM, older browsers)
|
|
2
2
|
const GlobalVTTCue = (typeof window !== 'undefined' && window.VTTCue) || null;
|
|
3
3
|
export class HeliosCue {
|
|
4
|
+
id = "";
|
|
5
|
+
track = null;
|
|
4
6
|
startTime;
|
|
5
7
|
endTime;
|
|
8
|
+
pauseOnExit = false;
|
|
6
9
|
text;
|
|
7
10
|
constructor(startTime, endTime, text) {
|
|
8
11
|
this.startTime = startTime;
|
|
@@ -11,14 +14,43 @@ export class HeliosCue {
|
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
export const CueClass = GlobalVTTCue || HeliosCue;
|
|
17
|
+
export class HeliosTextTrackCueList {
|
|
18
|
+
_cues;
|
|
19
|
+
constructor(cues) {
|
|
20
|
+
this._cues = cues;
|
|
21
|
+
return new Proxy(this, {
|
|
22
|
+
get: (target, prop) => {
|
|
23
|
+
if (typeof prop === 'string') {
|
|
24
|
+
const index = Number(prop);
|
|
25
|
+
if (Number.isInteger(index) && index >= 0) {
|
|
26
|
+
return target._cues[index];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Reflect.get(target, prop);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
get length() { return this._cues.length; }
|
|
34
|
+
getCueById(id) {
|
|
35
|
+
return this._cues.find(c => c.id === id) || null;
|
|
36
|
+
}
|
|
37
|
+
[Symbol.iterator]() {
|
|
38
|
+
return this._cues[Symbol.iterator]();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
14
41
|
export class HeliosTextTrack extends EventTarget {
|
|
15
42
|
_mode = 'disabled';
|
|
16
43
|
_cues = [];
|
|
44
|
+
_activeCues = [];
|
|
45
|
+
_oncuechange = null;
|
|
17
46
|
_kind;
|
|
18
47
|
_label;
|
|
19
48
|
_language;
|
|
20
49
|
_id;
|
|
21
50
|
_host;
|
|
51
|
+
// Cached lists to ensure stable references
|
|
52
|
+
_cuesWrapper = null;
|
|
53
|
+
_activeCuesWrapper = null;
|
|
22
54
|
constructor(kind, label, language, host) {
|
|
23
55
|
super();
|
|
24
56
|
this._kind = kind;
|
|
@@ -31,7 +63,28 @@ export class HeliosTextTrack extends EventTarget {
|
|
|
31
63
|
get label() { return this._label; }
|
|
32
64
|
get language() { return this._language; }
|
|
33
65
|
get id() { return this._id; }
|
|
34
|
-
get cues() {
|
|
66
|
+
get cues() {
|
|
67
|
+
if (!this._cuesWrapper) {
|
|
68
|
+
this._cuesWrapper = new HeliosTextTrackCueList(this._cues);
|
|
69
|
+
}
|
|
70
|
+
return this._cuesWrapper;
|
|
71
|
+
}
|
|
72
|
+
get activeCues() {
|
|
73
|
+
if (!this._activeCuesWrapper) {
|
|
74
|
+
this._activeCuesWrapper = new HeliosTextTrackCueList(this._activeCues);
|
|
75
|
+
}
|
|
76
|
+
return this._activeCuesWrapper;
|
|
77
|
+
}
|
|
78
|
+
get oncuechange() { return this._oncuechange; }
|
|
79
|
+
set oncuechange(handler) {
|
|
80
|
+
if (this._oncuechange) {
|
|
81
|
+
this.removeEventListener('cuechange', this._oncuechange);
|
|
82
|
+
}
|
|
83
|
+
this._oncuechange = handler;
|
|
84
|
+
if (handler) {
|
|
85
|
+
this.addEventListener('cuechange', handler);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
35
88
|
get mode() {
|
|
36
89
|
return this._mode;
|
|
37
90
|
}
|
|
@@ -41,7 +94,51 @@ export class HeliosTextTrack extends EventTarget {
|
|
|
41
94
|
this._host.handleTrackModeChange(this);
|
|
42
95
|
}
|
|
43
96
|
}
|
|
97
|
+
updateActiveCues(currentTime) {
|
|
98
|
+
if (this._mode === 'disabled') {
|
|
99
|
+
if (this._activeCues.length > 0) {
|
|
100
|
+
this._activeCues = [];
|
|
101
|
+
this._activeCuesWrapper = null;
|
|
102
|
+
this.dispatchEvent(new Event('cuechange'));
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const newActiveCues = this._cues.filter(cue => currentTime >= cue.startTime && currentTime < cue.endTime);
|
|
107
|
+
let changed = false;
|
|
108
|
+
if (newActiveCues.length !== this._activeCues.length) {
|
|
109
|
+
changed = true;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
for (let i = 0; i < newActiveCues.length; i++) {
|
|
113
|
+
if (newActiveCues[i] !== this._activeCues[i]) {
|
|
114
|
+
changed = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (changed) {
|
|
120
|
+
this._activeCues = newActiveCues;
|
|
121
|
+
this._activeCuesWrapper = null;
|
|
122
|
+
this.dispatchEvent(new Event('cuechange'));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
44
125
|
addCue(cue) {
|
|
126
|
+
try {
|
|
127
|
+
cue.track = this;
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// Handle native VTTCue where track is read-only
|
|
131
|
+
try {
|
|
132
|
+
Object.defineProperty(cue, 'track', {
|
|
133
|
+
value: this,
|
|
134
|
+
writable: true,
|
|
135
|
+
configurable: true
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (e2) {
|
|
139
|
+
console.warn("HeliosTextTrack: Could not set track property on cue", e2);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
45
142
|
this._cues.push(cue);
|
|
46
143
|
this._cues.sort((a, b) => a.startTime - b.startTime);
|
|
47
144
|
// If we are showing, we need to update the host (controller) immediately with new cues
|
|
@@ -52,6 +149,17 @@ export class HeliosTextTrack extends EventTarget {
|
|
|
52
149
|
removeCue(cue) {
|
|
53
150
|
const idx = this._cues.indexOf(cue);
|
|
54
151
|
if (idx !== -1) {
|
|
152
|
+
try {
|
|
153
|
+
cue.track = null;
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
try {
|
|
157
|
+
Object.defineProperty(cue, 'track', { value: null });
|
|
158
|
+
}
|
|
159
|
+
catch (e2) {
|
|
160
|
+
// Ignore
|
|
161
|
+
}
|
|
162
|
+
}
|
|
55
163
|
this._cues.splice(idx, 1);
|
|
56
164
|
if (this._mode === 'showing') {
|
|
57
165
|
this._host.handleTrackModeChange(this);
|
|
@@ -61,6 +169,9 @@ export class HeliosTextTrack extends EventTarget {
|
|
|
61
169
|
}
|
|
62
170
|
export class HeliosTextTrackList extends EventTarget {
|
|
63
171
|
tracks = [];
|
|
172
|
+
_onaddtrack = null;
|
|
173
|
+
_onremovetrack = null;
|
|
174
|
+
_onchange = null;
|
|
64
175
|
get length() { return this.tracks.length; }
|
|
65
176
|
[Symbol.iterator]() {
|
|
66
177
|
return this.tracks[Symbol.iterator]();
|
|
@@ -90,10 +201,37 @@ export class HeliosTextTrackList extends EventTarget {
|
|
|
90
201
|
this.dispatchEvent(event);
|
|
91
202
|
}
|
|
92
203
|
}
|
|
93
|
-
|
|
204
|
+
dispatchChangeEvent() {
|
|
205
|
+
this.dispatchEvent(new Event('change'));
|
|
206
|
+
}
|
|
207
|
+
get onaddtrack() { return this._onaddtrack; }
|
|
94
208
|
set onaddtrack(handler) {
|
|
209
|
+
if (this._onaddtrack) {
|
|
210
|
+
this.removeEventListener('addtrack', this._onaddtrack);
|
|
211
|
+
}
|
|
212
|
+
this._onaddtrack = handler;
|
|
95
213
|
if (handler) {
|
|
96
214
|
this.addEventListener('addtrack', handler);
|
|
97
215
|
}
|
|
98
216
|
}
|
|
217
|
+
get onremovetrack() { return this._onremovetrack; }
|
|
218
|
+
set onremovetrack(handler) {
|
|
219
|
+
if (this._onremovetrack) {
|
|
220
|
+
this.removeEventListener('removetrack', this._onremovetrack);
|
|
221
|
+
}
|
|
222
|
+
this._onremovetrack = handler;
|
|
223
|
+
if (handler) {
|
|
224
|
+
this.addEventListener('removetrack', handler);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
get onchange() { return this._onchange; }
|
|
228
|
+
set onchange(handler) {
|
|
229
|
+
if (this._onchange) {
|
|
230
|
+
this.removeEventListener('change', this._onchange);
|
|
231
|
+
}
|
|
232
|
+
this._onchange = handler;
|
|
233
|
+
if (handler) {
|
|
234
|
+
this.addEventListener('change', handler);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
99
237
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface VideoTrackHost {
|
|
2
|
+
handleVideoTrackSelectedChange(track: HeliosVideoTrack): void;
|
|
3
|
+
}
|
|
4
|
+
export declare class HeliosVideoTrack {
|
|
5
|
+
private _id;
|
|
6
|
+
private _kind;
|
|
7
|
+
private _label;
|
|
8
|
+
private _language;
|
|
9
|
+
private _selected;
|
|
10
|
+
private _host;
|
|
11
|
+
constructor(id: string, kind: string, label: string, language: string, selected: boolean, host: VideoTrackHost);
|
|
12
|
+
get id(): string;
|
|
13
|
+
get kind(): string;
|
|
14
|
+
get label(): string;
|
|
15
|
+
get language(): string;
|
|
16
|
+
get selected(): boolean;
|
|
17
|
+
set selected(value: boolean);
|
|
18
|
+
/**
|
|
19
|
+
* Internal method to update state without triggering host callback.
|
|
20
|
+
* Used when syncing from external state.
|
|
21
|
+
*/
|
|
22
|
+
_setSelectedInternal(value: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
export declare class HeliosVideoTrackList extends EventTarget implements Iterable<HeliosVideoTrack> {
|
|
25
|
+
private tracks;
|
|
26
|
+
private _onaddtrack;
|
|
27
|
+
private _onremovetrack;
|
|
28
|
+
private _onchange;
|
|
29
|
+
get length(): number;
|
|
30
|
+
get selectedIndex(): number;
|
|
31
|
+
[index: number]: HeliosVideoTrack;
|
|
32
|
+
[Symbol.iterator](): Iterator<HeliosVideoTrack>;
|
|
33
|
+
getTrackById(id: string): HeliosVideoTrack | null;
|
|
34
|
+
addTrack(track: HeliosVideoTrack): void;
|
|
35
|
+
removeTrack(track: HeliosVideoTrack): void;
|
|
36
|
+
dispatchChangeEvent(): void;
|
|
37
|
+
get onaddtrack(): ((event: any) => void) | null;
|
|
38
|
+
set onaddtrack(handler: ((event: any) => void) | null);
|
|
39
|
+
get onremovetrack(): ((event: any) => void) | null;
|
|
40
|
+
set onremovetrack(handler: ((event: any) => void) | null);
|
|
41
|
+
get onchange(): ((event: any) => void) | null;
|
|
42
|
+
set onchange(handler: ((event: any) => void) | null);
|
|
43
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export class HeliosVideoTrack {
|
|
2
|
+
_id;
|
|
3
|
+
_kind;
|
|
4
|
+
_label;
|
|
5
|
+
_language;
|
|
6
|
+
_selected;
|
|
7
|
+
_host;
|
|
8
|
+
constructor(id, kind, label, language, selected, host) {
|
|
9
|
+
this._id = id;
|
|
10
|
+
this._kind = kind;
|
|
11
|
+
this._label = label;
|
|
12
|
+
this._language = language;
|
|
13
|
+
this._selected = selected;
|
|
14
|
+
this._host = host;
|
|
15
|
+
}
|
|
16
|
+
get id() { return this._id; }
|
|
17
|
+
get kind() { return this._kind; }
|
|
18
|
+
get label() { return this._label; }
|
|
19
|
+
get language() { return this._language; }
|
|
20
|
+
get selected() { return this._selected; }
|
|
21
|
+
set selected(value) {
|
|
22
|
+
if (this._selected !== value) {
|
|
23
|
+
this._selected = value;
|
|
24
|
+
this._host.handleVideoTrackSelectedChange(this);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Internal method to update state without triggering host callback.
|
|
29
|
+
* Used when syncing from external state.
|
|
30
|
+
*/
|
|
31
|
+
_setSelectedInternal(value) {
|
|
32
|
+
this._selected = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class HeliosVideoTrackList extends EventTarget {
|
|
36
|
+
tracks = [];
|
|
37
|
+
_onaddtrack = null;
|
|
38
|
+
_onremovetrack = null;
|
|
39
|
+
_onchange = null;
|
|
40
|
+
get length() { return this.tracks.length; }
|
|
41
|
+
get selectedIndex() {
|
|
42
|
+
return this.tracks.findIndex(t => t.selected);
|
|
43
|
+
}
|
|
44
|
+
[Symbol.iterator]() {
|
|
45
|
+
return this.tracks[Symbol.iterator]();
|
|
46
|
+
}
|
|
47
|
+
getTrackById(id) {
|
|
48
|
+
return this.tracks.find(t => t.id === id) || null;
|
|
49
|
+
}
|
|
50
|
+
addTrack(track) {
|
|
51
|
+
this.tracks.push(track);
|
|
52
|
+
// Enable array-like access
|
|
53
|
+
this[this.tracks.length - 1] = track;
|
|
54
|
+
const event = new Event('addtrack');
|
|
55
|
+
event.track = track;
|
|
56
|
+
this.dispatchEvent(event);
|
|
57
|
+
}
|
|
58
|
+
removeTrack(track) {
|
|
59
|
+
const index = this.tracks.indexOf(track);
|
|
60
|
+
if (index !== -1) {
|
|
61
|
+
this.tracks.splice(index, 1);
|
|
62
|
+
// Re-index properties
|
|
63
|
+
for (let i = index; i < this.tracks.length; i++) {
|
|
64
|
+
this[i] = this.tracks[i];
|
|
65
|
+
}
|
|
66
|
+
delete this[this.tracks.length];
|
|
67
|
+
const event = new Event('removetrack');
|
|
68
|
+
event.track = track;
|
|
69
|
+
this.dispatchEvent(event);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Helper to dispatch change event
|
|
73
|
+
dispatchChangeEvent() {
|
|
74
|
+
this.dispatchEvent(new Event('change'));
|
|
75
|
+
}
|
|
76
|
+
// Standard event handler properties
|
|
77
|
+
get onaddtrack() { return this._onaddtrack; }
|
|
78
|
+
set onaddtrack(handler) {
|
|
79
|
+
if (this._onaddtrack) {
|
|
80
|
+
this.removeEventListener('addtrack', this._onaddtrack);
|
|
81
|
+
}
|
|
82
|
+
this._onaddtrack = handler;
|
|
83
|
+
if (handler) {
|
|
84
|
+
this.addEventListener('addtrack', handler);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
get onremovetrack() { return this._onremovetrack; }
|
|
88
|
+
set onremovetrack(handler) {
|
|
89
|
+
if (this._onremovetrack) {
|
|
90
|
+
this.removeEventListener('removetrack', this._onremovetrack);
|
|
91
|
+
}
|
|
92
|
+
this._onremovetrack = handler;
|
|
93
|
+
if (handler) {
|
|
94
|
+
this.addEventListener('removetrack', handler);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
get onchange() { return this._onchange; }
|
|
98
|
+
set onchange(handler) {
|
|
99
|
+
if (this._onchange) {
|
|
100
|
+
this.removeEventListener('change', this._onchange);
|
|
101
|
+
}
|
|
102
|
+
this._onchange = handler;
|
|
103
|
+
if (handler) {
|
|
104
|
+
this.addEventListener('change', handler);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|