@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.
@@ -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(): any[];
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
- addCue(cue: any): void;
29
- removeCue(cue: any): void;
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() { return this._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
- // Stub for standard event handler property
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
+ }