@openplayerjs/core 3.0.1 → 3.1.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/README.md +205 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.js +549 -96
- package/dist/index.js.map +1 -1
- package/dist/types/core/captions.d.ts +16 -0
- package/dist/types/core/captions.d.ts.map +1 -0
- package/dist/types/core/index.d.ts +7 -3
- package/dist/types/core/index.d.ts.map +1 -1
- package/dist/types/core/media.d.ts +24 -1
- package/dist/types/core/media.d.ts.map +1 -1
- package/dist/types/core/surface.d.ts +72 -0
- package/dist/types/core/surface.d.ts.map +1 -0
- package/dist/types/core/utils.d.ts.map +1 -1
- package/dist/types/engines/base.d.ts +11 -15
- package/dist/types/engines/base.d.ts.map +1 -1
- package/dist/types/engines/html5.d.ts.map +1 -1
- package/dist/types/engines/iframe.d.ts +103 -0
- package/dist/types/engines/iframe.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +8 -5
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,9 +1,110 @@
|
|
|
1
1
|
const DVR_THRESHOLD = 120;
|
|
2
2
|
const EVENT_OPTIONS = { passive: false };
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a native HTMLMediaElement inside the normalized MediaSurface contract.
|
|
6
|
+
*/
|
|
7
|
+
class HtmlMediaSurface {
|
|
8
|
+
constructor(media) {
|
|
9
|
+
Object.defineProperty(this, "media", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: media
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "element", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: void 0
|
|
20
|
+
});
|
|
21
|
+
this.element = media;
|
|
22
|
+
}
|
|
23
|
+
get currentSrc() {
|
|
24
|
+
return this.media.currentSrc || this.media.src || '';
|
|
25
|
+
}
|
|
26
|
+
get currentTime() {
|
|
27
|
+
return this.media.currentTime || 0;
|
|
28
|
+
}
|
|
29
|
+
set currentTime(value) {
|
|
30
|
+
this.media.currentTime = value;
|
|
31
|
+
}
|
|
32
|
+
get duration() {
|
|
33
|
+
return this.media.duration;
|
|
34
|
+
}
|
|
35
|
+
set duration(_value) {
|
|
36
|
+
// Native media duration is read-only.
|
|
37
|
+
}
|
|
38
|
+
get volume() {
|
|
39
|
+
return this.media.volume;
|
|
40
|
+
}
|
|
41
|
+
set volume(value) {
|
|
42
|
+
this.media.volume = value;
|
|
43
|
+
}
|
|
44
|
+
get muted() {
|
|
45
|
+
return this.media.muted;
|
|
46
|
+
}
|
|
47
|
+
set muted(value) {
|
|
48
|
+
this.media.muted = value;
|
|
49
|
+
}
|
|
50
|
+
get playbackRate() {
|
|
51
|
+
return this.media.playbackRate;
|
|
52
|
+
}
|
|
53
|
+
set playbackRate(value) {
|
|
54
|
+
this.media.playbackRate = value;
|
|
55
|
+
}
|
|
56
|
+
get paused() {
|
|
57
|
+
return this.media.paused;
|
|
58
|
+
}
|
|
59
|
+
get ended() {
|
|
60
|
+
return this.media.ended;
|
|
61
|
+
}
|
|
62
|
+
load(source) {
|
|
63
|
+
if (source?.src && this.media.src !== source.src) {
|
|
64
|
+
this.media.src = source.src;
|
|
65
|
+
}
|
|
66
|
+
this.media.load();
|
|
67
|
+
}
|
|
68
|
+
play() {
|
|
69
|
+
return this.media.play();
|
|
70
|
+
}
|
|
71
|
+
pause() {
|
|
72
|
+
this.media.pause();
|
|
73
|
+
}
|
|
74
|
+
on(event, handler, options) {
|
|
75
|
+
const wrapped = ((evt) => {
|
|
76
|
+
if (event === 'error') {
|
|
77
|
+
handler((this.media.error ?? evt));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
handler(undefined);
|
|
81
|
+
});
|
|
82
|
+
this.media.addEventListener(event, wrapped, options);
|
|
83
|
+
return () => this.media.removeEventListener(event, wrapped, options);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function bridgeSurfaceEvents(surface, events) {
|
|
87
|
+
return [
|
|
88
|
+
surface.on('loadstart', () => events.emit('loadstart'), EVENT_OPTIONS),
|
|
89
|
+
surface.on('loadedmetadata', () => events.emit('loadedmetadata'), EVENT_OPTIONS),
|
|
90
|
+
surface.on('durationchange', () => events.emit('durationchange'), EVENT_OPTIONS),
|
|
91
|
+
surface.on('timeupdate', () => events.emit('timeupdate'), EVENT_OPTIONS),
|
|
92
|
+
surface.on('waiting', () => events.emit('waiting'), EVENT_OPTIONS),
|
|
93
|
+
surface.on('seeking', () => events.emit('seeking'), EVENT_OPTIONS),
|
|
94
|
+
surface.on('seeked', () => events.emit('seeked'), EVENT_OPTIONS),
|
|
95
|
+
surface.on('ended', () => events.emit('ended'), EVENT_OPTIONS),
|
|
96
|
+
surface.on('error', (error) => events.emit('error', error), EVENT_OPTIONS),
|
|
97
|
+
surface.on('play', () => events.emit('play'), EVENT_OPTIONS),
|
|
98
|
+
surface.on('playing', () => events.emit('playing'), EVENT_OPTIONS),
|
|
99
|
+
surface.on('pause', () => events.emit('pause'), EVENT_OPTIONS),
|
|
100
|
+
surface.on('volumechange', () => events.emit('volumechange'), EVENT_OPTIONS),
|
|
101
|
+
surface.on('ratechange', () => events.emit('ratechange'), EVENT_OPTIONS),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
4
105
|
class BaseMediaEngine {
|
|
5
106
|
constructor() {
|
|
6
|
-
Object.defineProperty(this, "
|
|
107
|
+
Object.defineProperty(this, "surface", {
|
|
7
108
|
enumerable: true,
|
|
8
109
|
configurable: true,
|
|
9
110
|
writable: true,
|
|
@@ -21,88 +122,94 @@ class BaseMediaEngine {
|
|
|
21
122
|
writable: true,
|
|
22
123
|
value: []
|
|
23
124
|
});
|
|
24
|
-
Object.defineProperty(this, "
|
|
125
|
+
Object.defineProperty(this, "surfaceListeners", {
|
|
25
126
|
enumerable: true,
|
|
26
127
|
configurable: true,
|
|
27
128
|
writable: true,
|
|
28
129
|
value: []
|
|
29
130
|
});
|
|
30
131
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* HTML5 event names (no payload; consumers read from player/media).
|
|
34
|
-
*/
|
|
35
|
-
bindMediaEvents(media, events) {
|
|
36
|
-
this.media = media;
|
|
132
|
+
bindSurfaceEvents(surface, events) {
|
|
133
|
+
this.surface = surface;
|
|
37
134
|
this.events = events;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
135
|
+
this.surfaceListeners = bridgeSurfaceEvents(surface, events);
|
|
136
|
+
}
|
|
137
|
+
unbindSurfaceEvents() {
|
|
138
|
+
this.surfaceListeners.forEach((off) => off());
|
|
139
|
+
this.surfaceListeners = [];
|
|
140
|
+
}
|
|
141
|
+
bindMediaEvents(media, events) {
|
|
142
|
+
const shim = {
|
|
143
|
+
get currentTime() { return media.currentTime; },
|
|
144
|
+
set currentTime(v) { media.currentTime = v; },
|
|
145
|
+
get duration() { return media.duration; },
|
|
146
|
+
set duration(_v) { },
|
|
147
|
+
get volume() { return media.volume; },
|
|
148
|
+
set volume(v) { media.volume = v; },
|
|
149
|
+
get muted() { return media.muted; },
|
|
150
|
+
set muted(v) { media.muted = v; },
|
|
151
|
+
get playbackRate() { return media.playbackRate; },
|
|
152
|
+
set playbackRate(v) { media.playbackRate = v; },
|
|
153
|
+
get paused() { return media.paused; },
|
|
154
|
+
get ended() { return media.ended; },
|
|
155
|
+
play: () => media.play(),
|
|
156
|
+
pause: () => media.pause(),
|
|
157
|
+
on: (event, handler) => {
|
|
158
|
+
const wrapped = () => handler();
|
|
159
|
+
media.addEventListener(event, wrapped);
|
|
160
|
+
return () => media.removeEventListener(event, wrapped);
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
this.surfaceListeners = bridgeSurfaceEvents(shim, events);
|
|
64
164
|
}
|
|
65
165
|
unbindMediaEvents() {
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
for (const l of this.mediaListeners) {
|
|
69
|
-
this.media.removeEventListener(l.type, l.handler, l.options);
|
|
70
|
-
}
|
|
71
|
-
this.mediaListeners = [];
|
|
166
|
+
this.unbindSurfaceEvents();
|
|
72
167
|
}
|
|
73
|
-
addMediaListener(media,
|
|
74
|
-
media.addEventListener(
|
|
75
|
-
this.
|
|
168
|
+
addMediaListener(media, event, handler, options) {
|
|
169
|
+
media.addEventListener(event, handler, options);
|
|
170
|
+
this.surfaceListeners.push(() => media.removeEventListener(event, handler, options));
|
|
76
171
|
}
|
|
77
172
|
canHandlePlayback(ctx) {
|
|
78
173
|
const owner = ctx.core.leases.owner('playback');
|
|
79
174
|
return !owner || owner === this.name;
|
|
80
175
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Commands are explicit and separate from notifications.
|
|
83
|
-
*/
|
|
84
176
|
bindCommands(ctx) {
|
|
85
|
-
const {
|
|
177
|
+
const { events } = ctx;
|
|
86
178
|
this.commands.push(events.on('cmd:seek', (t) => {
|
|
87
179
|
if (!this.canHandlePlayback(ctx))
|
|
88
180
|
return;
|
|
89
181
|
try {
|
|
90
|
-
|
|
182
|
+
ctx.surface.currentTime = t;
|
|
91
183
|
}
|
|
92
184
|
catch {
|
|
93
185
|
// ignore
|
|
94
186
|
}
|
|
95
187
|
}));
|
|
96
188
|
this.commands.push(events.on('cmd:setVolume', (v) => {
|
|
97
|
-
|
|
189
|
+
try {
|
|
190
|
+
ctx.surface.volume = v;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// ignore
|
|
194
|
+
}
|
|
98
195
|
}));
|
|
99
196
|
this.commands.push(events.on('cmd:setMuted', (m) => {
|
|
100
|
-
|
|
197
|
+
try {
|
|
198
|
+
ctx.surface.muted = m;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// ignore
|
|
202
|
+
}
|
|
101
203
|
}));
|
|
102
204
|
this.commands.push(events.on('cmd:setRate', (r) => {
|
|
103
205
|
if (!this.canHandlePlayback(ctx))
|
|
104
206
|
return;
|
|
105
|
-
|
|
207
|
+
try {
|
|
208
|
+
ctx.surface.playbackRate = r;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// ignore
|
|
212
|
+
}
|
|
106
213
|
}));
|
|
107
214
|
this.bindPlayPauseCommands(ctx);
|
|
108
215
|
}
|
|
@@ -112,25 +219,28 @@ class BaseMediaEngine {
|
|
|
112
219
|
this.commands = [];
|
|
113
220
|
}
|
|
114
221
|
bindPlayPauseCommands(ctx) {
|
|
115
|
-
const {
|
|
222
|
+
const { events } = ctx;
|
|
116
223
|
this.commands.push(events.on('cmd:play', () => {
|
|
117
224
|
if (!this.canHandlePlayback(ctx))
|
|
118
225
|
return;
|
|
119
|
-
this.playImpl(
|
|
226
|
+
this.playImpl(ctx.surface);
|
|
120
227
|
}));
|
|
121
228
|
this.commands.push(events.on('cmd:pause', () => {
|
|
122
229
|
if (!this.canHandlePlayback(ctx))
|
|
123
230
|
return;
|
|
124
|
-
this.pauseImpl(
|
|
231
|
+
this.pauseImpl(ctx.surface);
|
|
125
232
|
}));
|
|
126
233
|
}
|
|
127
|
-
playImpl(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
234
|
+
playImpl(surface) {
|
|
235
|
+
const maybePromise = surface.play();
|
|
236
|
+
if (maybePromise && typeof maybePromise.catch === 'function') {
|
|
237
|
+
void maybePromise.catch(() => {
|
|
238
|
+
// ignore
|
|
239
|
+
});
|
|
240
|
+
}
|
|
131
241
|
}
|
|
132
|
-
pauseImpl(
|
|
133
|
-
|
|
242
|
+
pauseImpl(surface) {
|
|
243
|
+
surface.pause();
|
|
134
244
|
}
|
|
135
245
|
}
|
|
136
246
|
|
|
@@ -167,28 +277,27 @@ class DefaultMediaEngine extends BaseMediaEngine {
|
|
|
167
277
|
return media.canPlayType(source.type || '') !== '';
|
|
168
278
|
}
|
|
169
279
|
attach(ctx) {
|
|
170
|
-
|
|
171
|
-
ctx.media.src = ctx.activeSource.src;
|
|
172
|
-
}
|
|
280
|
+
const surface = ctx.resetSurface();
|
|
173
281
|
try {
|
|
174
|
-
|
|
282
|
+
surface.load?.(ctx.activeSource);
|
|
175
283
|
}
|
|
176
284
|
catch {
|
|
177
285
|
// ignore
|
|
178
286
|
}
|
|
179
|
-
this.
|
|
287
|
+
this.bindSurfaceEvents(surface, ctx.events);
|
|
180
288
|
this.bindCommands(ctx);
|
|
181
|
-
// When preload="none" and play is requested, bump preload so the browser
|
|
182
|
-
// will actually fetch resource metadata and fire loadedmetadata.
|
|
183
289
|
this.commands.push(ctx.events.on('cmd:startLoad', () => {
|
|
184
290
|
const s = ctx.core.state.current;
|
|
185
291
|
if (['ready', 'playing', 'paused', 'waiting', 'seeking', 'ended'].includes(s))
|
|
186
292
|
return;
|
|
293
|
+
// ctx.media.preload is intentionally read/written here: it is a DOM attribute
|
|
294
|
+
// with no surface equivalent. The surface abstraction covers playback operations
|
|
295
|
+
// only; loading hints are DOM-level concerns that must be set directly on the element.
|
|
187
296
|
if (ctx.media.preload !== 'none')
|
|
188
297
|
return;
|
|
189
298
|
ctx.media.preload = 'metadata';
|
|
190
299
|
try {
|
|
191
|
-
|
|
300
|
+
surface.load?.(ctx.activeSource);
|
|
192
301
|
}
|
|
193
302
|
catch {
|
|
194
303
|
// ignore
|
|
@@ -197,7 +306,7 @@ class DefaultMediaEngine extends BaseMediaEngine {
|
|
|
197
306
|
}
|
|
198
307
|
detach() {
|
|
199
308
|
this.unbindCommands();
|
|
200
|
-
this.
|
|
309
|
+
this.unbindSurfaceEvents();
|
|
201
310
|
}
|
|
202
311
|
}
|
|
203
312
|
|
|
@@ -411,7 +520,15 @@ function isMobile() {
|
|
|
411
520
|
/android/i.test(window.navigator.userAgent));
|
|
412
521
|
}
|
|
413
522
|
function predictMimeType(media, url) {
|
|
414
|
-
|
|
523
|
+
let pathname;
|
|
524
|
+
try {
|
|
525
|
+
pathname = new URL(url).pathname;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// Non-URL string (e.g. a bare video ID): no extension can be determined.
|
|
529
|
+
return isAudio(media) ? 'audio/mp3' : 'video/mp4';
|
|
530
|
+
}
|
|
531
|
+
const fragments = pathname.split('.');
|
|
415
532
|
const extension = fragments.length > 1 ? fragments.pop().toLowerCase() : '';
|
|
416
533
|
// If no extension found, check if media is a vendor iframe
|
|
417
534
|
if (!extension)
|
|
@@ -505,6 +622,18 @@ class Core {
|
|
|
505
622
|
writable: true,
|
|
506
623
|
value: false
|
|
507
624
|
});
|
|
625
|
+
Object.defineProperty(this, "nativeSurface", {
|
|
626
|
+
enumerable: true,
|
|
627
|
+
configurable: true,
|
|
628
|
+
writable: true,
|
|
629
|
+
value: void 0
|
|
630
|
+
});
|
|
631
|
+
Object.defineProperty(this, "activeSurface", {
|
|
632
|
+
enumerable: true,
|
|
633
|
+
configurable: true,
|
|
634
|
+
writable: true,
|
|
635
|
+
value: void 0
|
|
636
|
+
});
|
|
508
637
|
Object.defineProperty(this, "interactionUnsubs", {
|
|
509
638
|
enumerable: true,
|
|
510
639
|
configurable: true,
|
|
@@ -623,30 +752,35 @@ class Core {
|
|
|
623
752
|
else {
|
|
624
753
|
this.media = media;
|
|
625
754
|
}
|
|
755
|
+
this.nativeSurface = new HtmlMediaSurface(this.media);
|
|
756
|
+
this.activeSurface = this.nativeSurface;
|
|
626
757
|
this.registerPlugin(new DefaultMediaEngine());
|
|
627
758
|
this.config = { ...defaultConfiguration, ...config };
|
|
628
759
|
this.media.currentTime = this.config.startTime || this.media.currentTime;
|
|
629
|
-
this._currentTime = this.config.startTime || this.
|
|
630
|
-
this._duration = this.config.duration || this.
|
|
631
|
-
const initialVolume = clamp01(this.config.startVolume ?? this.
|
|
632
|
-
this.
|
|
760
|
+
this._currentTime = this.config.startTime || this.activeSurface.currentTime;
|
|
761
|
+
this._duration = this.config.duration || this.activeSurface.duration;
|
|
762
|
+
const initialVolume = clamp01(this.config.startVolume ?? this.activeSurface.volume);
|
|
763
|
+
this.activeSurface.volume = initialVolume;
|
|
633
764
|
this._volume = initialVolume;
|
|
634
765
|
if (this.config.startVolume !== undefined) {
|
|
635
|
-
this.
|
|
766
|
+
this.activeSurface.muted = initialVolume <= 0;
|
|
636
767
|
this._muted = initialVolume <= 0;
|
|
637
768
|
}
|
|
638
769
|
else {
|
|
639
|
-
this._muted = this.
|
|
770
|
+
this._muted = this.activeSurface.muted;
|
|
640
771
|
}
|
|
641
|
-
this.
|
|
642
|
-
this._playbackRate = this.config.startPlaybackRate || this.
|
|
772
|
+
this.activeSurface.playbackRate = this.config.startPlaybackRate || this.activeSurface.playbackRate;
|
|
773
|
+
this._playbackRate = this.config.startPlaybackRate || this.activeSurface.playbackRate;
|
|
643
774
|
(this.config.plugins || []).forEach((p) => this.registerPlugin(p));
|
|
644
775
|
this.bindStateTransitions();
|
|
645
|
-
this.
|
|
776
|
+
this.bindSurfaceSync();
|
|
646
777
|
this.bindLeaseSync();
|
|
647
778
|
this.bindFirstInteraction();
|
|
648
779
|
queueMicrotask(() => this.maybeAutoLoad());
|
|
649
780
|
}
|
|
781
|
+
get surface() {
|
|
782
|
+
return this.activeSurface;
|
|
783
|
+
}
|
|
650
784
|
on(event, cb) {
|
|
651
785
|
// Keep the surface flexible (plugins may emit custom events).
|
|
652
786
|
return this.events.on(event, cb);
|
|
@@ -739,13 +873,7 @@ class Core {
|
|
|
739
873
|
const sources = this.detectedSources ?? this.readMediaSources(this.media);
|
|
740
874
|
this.detectedSources = sources;
|
|
741
875
|
const { engine, source: activeSource } = this.resolveMediaEngine(sources);
|
|
742
|
-
this.playerContext =
|
|
743
|
-
media: this.media,
|
|
744
|
-
events: this.events,
|
|
745
|
-
config: this.config,
|
|
746
|
-
activeSource,
|
|
747
|
-
core: this,
|
|
748
|
-
};
|
|
876
|
+
this.playerContext = this.createPlayerContext(activeSource);
|
|
749
877
|
this.activeEngine?.detach?.(this.playerContext);
|
|
750
878
|
this.activeEngine = engine;
|
|
751
879
|
this.emit('loadstart');
|
|
@@ -1008,35 +1136,32 @@ class Core {
|
|
|
1008
1136
|
}
|
|
1009
1137
|
});
|
|
1010
1138
|
}
|
|
1011
|
-
|
|
1139
|
+
bindSurfaceSync() {
|
|
1012
1140
|
const read = () => {
|
|
1013
|
-
// time + duration
|
|
1014
1141
|
try {
|
|
1015
|
-
this._currentTime = this.
|
|
1142
|
+
this._currentTime = this.activeSurface.currentTime || 0;
|
|
1016
1143
|
}
|
|
1017
1144
|
catch {
|
|
1018
1145
|
// ignore
|
|
1019
1146
|
}
|
|
1020
1147
|
try {
|
|
1021
|
-
const d = this.
|
|
1148
|
+
const d = this.config.duration || this.activeSurface.duration;
|
|
1022
1149
|
this._duration = d;
|
|
1023
1150
|
}
|
|
1024
1151
|
catch {
|
|
1025
1152
|
// ignore
|
|
1026
1153
|
}
|
|
1027
|
-
// volume + mute
|
|
1028
1154
|
try {
|
|
1029
|
-
this._muted = Boolean(this.
|
|
1030
|
-
const v = this.
|
|
1155
|
+
this._muted = Boolean(this.activeSurface.muted);
|
|
1156
|
+
const v = this.activeSurface.volume;
|
|
1031
1157
|
if (Number.isFinite(v))
|
|
1032
1158
|
this._volume = v;
|
|
1033
1159
|
}
|
|
1034
1160
|
catch {
|
|
1035
1161
|
// ignore
|
|
1036
1162
|
}
|
|
1037
|
-
// rate
|
|
1038
1163
|
try {
|
|
1039
|
-
const r = this.
|
|
1164
|
+
const r = this.activeSurface.playbackRate;
|
|
1040
1165
|
if (Number.isFinite(r))
|
|
1041
1166
|
this._playbackRate = r;
|
|
1042
1167
|
}
|
|
@@ -1050,6 +1175,33 @@ class Core {
|
|
|
1050
1175
|
this.events.on('volumechange', () => read());
|
|
1051
1176
|
this.events.on('ratechange', () => read());
|
|
1052
1177
|
}
|
|
1178
|
+
createPlayerContext(activeSource) {
|
|
1179
|
+
const ctx = {
|
|
1180
|
+
media: this.media,
|
|
1181
|
+
container: (this.media.parentElement ?? this.media),
|
|
1182
|
+
events: this.events,
|
|
1183
|
+
config: this.config,
|
|
1184
|
+
activeSource,
|
|
1185
|
+
core: this,
|
|
1186
|
+
surface: this.activeSurface, // overridden below with a live getter
|
|
1187
|
+
setSurface: (surface) => {
|
|
1188
|
+
this.activeSurface = surface;
|
|
1189
|
+
return this.activeSurface;
|
|
1190
|
+
},
|
|
1191
|
+
resetSurface: () => {
|
|
1192
|
+
this.activeSurface = this.nativeSurface;
|
|
1193
|
+
return this.nativeSurface;
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1196
|
+
// Replace the snapshot with a live getter so ctx.surface always reflects the
|
|
1197
|
+
// current active surface even after setSurface() is called (e.g. by an iframe engine).
|
|
1198
|
+
Object.defineProperty(ctx, 'surface', {
|
|
1199
|
+
get: () => this.activeSurface,
|
|
1200
|
+
enumerable: true,
|
|
1201
|
+
configurable: true,
|
|
1202
|
+
});
|
|
1203
|
+
return ctx;
|
|
1204
|
+
}
|
|
1053
1205
|
bindLeaseSync() {
|
|
1054
1206
|
this.leases.onChange((cap) => {
|
|
1055
1207
|
if (cap !== 'playback')
|
|
@@ -1237,5 +1389,306 @@ function getOverlayManager(player) {
|
|
|
1237
1389
|
return mgr;
|
|
1238
1390
|
}
|
|
1239
1391
|
|
|
1240
|
-
|
|
1392
|
+
// ─── IframeMediaSurface ───────────────────────────────────────────────────────
|
|
1393
|
+
/**
|
|
1394
|
+
* A normalized MediaSurface backed by an IframeMediaAdapter.
|
|
1395
|
+
*
|
|
1396
|
+
* Usage inside an engine's attach():
|
|
1397
|
+
* const surface = new IframeMediaSurface(adapter, { pollIntervalMs: 250 });
|
|
1398
|
+
* ctx.setSurface(surface);
|
|
1399
|
+
* this.bindSurfaceEvents(surface, ctx.events); // wires internal bus → EventBus
|
|
1400
|
+
* this.bindCommands(ctx);
|
|
1401
|
+
* surface.mount(ctx.container);
|
|
1402
|
+
*/
|
|
1403
|
+
class IframeMediaSurface {
|
|
1404
|
+
constructor(adapter, options = {}) {
|
|
1405
|
+
Object.defineProperty(this, "kind", {
|
|
1406
|
+
enumerable: true,
|
|
1407
|
+
configurable: true,
|
|
1408
|
+
writable: true,
|
|
1409
|
+
value: 'iframe'
|
|
1410
|
+
});
|
|
1411
|
+
Object.defineProperty(this, "element", {
|
|
1412
|
+
enumerable: true,
|
|
1413
|
+
configurable: true,
|
|
1414
|
+
writable: true,
|
|
1415
|
+
value: null
|
|
1416
|
+
});
|
|
1417
|
+
Object.defineProperty(this, "currentSrc", {
|
|
1418
|
+
enumerable: true,
|
|
1419
|
+
configurable: true,
|
|
1420
|
+
writable: true,
|
|
1421
|
+
value: void 0
|
|
1422
|
+
});
|
|
1423
|
+
Object.defineProperty(this, "internalBus", {
|
|
1424
|
+
enumerable: true,
|
|
1425
|
+
configurable: true,
|
|
1426
|
+
writable: true,
|
|
1427
|
+
value: new EventBus()
|
|
1428
|
+
});
|
|
1429
|
+
Object.defineProperty(this, "adapter", {
|
|
1430
|
+
enumerable: true,
|
|
1431
|
+
configurable: true,
|
|
1432
|
+
writable: true,
|
|
1433
|
+
value: void 0
|
|
1434
|
+
});
|
|
1435
|
+
Object.defineProperty(this, "pollIntervalMs", {
|
|
1436
|
+
enumerable: true,
|
|
1437
|
+
configurable: true,
|
|
1438
|
+
writable: true,
|
|
1439
|
+
value: void 0
|
|
1440
|
+
});
|
|
1441
|
+
Object.defineProperty(this, "pollTimer", {
|
|
1442
|
+
enumerable: true,
|
|
1443
|
+
configurable: true,
|
|
1444
|
+
writable: true,
|
|
1445
|
+
value: null
|
|
1446
|
+
});
|
|
1447
|
+
// Normalized state — updated by adapter events and polling
|
|
1448
|
+
Object.defineProperty(this, "_paused", {
|
|
1449
|
+
enumerable: true,
|
|
1450
|
+
configurable: true,
|
|
1451
|
+
writable: true,
|
|
1452
|
+
value: true
|
|
1453
|
+
});
|
|
1454
|
+
Object.defineProperty(this, "_ended", {
|
|
1455
|
+
enumerable: true,
|
|
1456
|
+
configurable: true,
|
|
1457
|
+
writable: true,
|
|
1458
|
+
value: false
|
|
1459
|
+
});
|
|
1460
|
+
Object.defineProperty(this, "_duration", {
|
|
1461
|
+
enumerable: true,
|
|
1462
|
+
configurable: true,
|
|
1463
|
+
writable: true,
|
|
1464
|
+
value: NaN
|
|
1465
|
+
});
|
|
1466
|
+
Object.defineProperty(this, "_currentTime", {
|
|
1467
|
+
enumerable: true,
|
|
1468
|
+
configurable: true,
|
|
1469
|
+
writable: true,
|
|
1470
|
+
value: 0
|
|
1471
|
+
});
|
|
1472
|
+
Object.defineProperty(this, "_volume", {
|
|
1473
|
+
enumerable: true,
|
|
1474
|
+
configurable: true,
|
|
1475
|
+
writable: true,
|
|
1476
|
+
value: 1
|
|
1477
|
+
});
|
|
1478
|
+
Object.defineProperty(this, "_muted", {
|
|
1479
|
+
enumerable: true,
|
|
1480
|
+
configurable: true,
|
|
1481
|
+
writable: true,
|
|
1482
|
+
value: false
|
|
1483
|
+
});
|
|
1484
|
+
Object.defineProperty(this, "_playbackRate", {
|
|
1485
|
+
enumerable: true,
|
|
1486
|
+
configurable: true,
|
|
1487
|
+
writable: true,
|
|
1488
|
+
value: 1
|
|
1489
|
+
});
|
|
1490
|
+
this.adapter = adapter;
|
|
1491
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 250;
|
|
1492
|
+
this.onAdapterReady = this.onAdapterReady.bind(this);
|
|
1493
|
+
this.onAdapterState = this.onAdapterState.bind(this);
|
|
1494
|
+
this.onAdapterError = this.onAdapterError.bind(this);
|
|
1495
|
+
this.adapter.on('ready', this.onAdapterReady);
|
|
1496
|
+
this.adapter.on('state', this.onAdapterState);
|
|
1497
|
+
this.adapter.on('error', this.onAdapterError);
|
|
1498
|
+
// Wire optional push events
|
|
1499
|
+
this.adapter.on('timeupdate', ((t) => this.applyTime(t)));
|
|
1500
|
+
this.adapter.on('durationchange', ((d) => this.applyDuration(d)));
|
|
1501
|
+
this.adapter.on('ratechange', ((r) => this.applyRate(r)));
|
|
1502
|
+
this.adapter.on('volumechange', ((v, m) => this.applyVolume(v, m)));
|
|
1503
|
+
this.adapter.on('ended', (() => this.applyEnded()));
|
|
1504
|
+
}
|
|
1505
|
+
// ─── MediaSurface properties (getter + setter adjacent) ──────────────────
|
|
1506
|
+
get currentTime() {
|
|
1507
|
+
return this._currentTime;
|
|
1508
|
+
}
|
|
1509
|
+
set currentTime(value) {
|
|
1510
|
+
this._currentTime = value;
|
|
1511
|
+
this.internalBus.emit('seeking');
|
|
1512
|
+
void Promise.resolve(this.adapter.seekTo(value)).then(() => {
|
|
1513
|
+
this.internalBus.emit('seeked');
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
get duration() {
|
|
1517
|
+
return this._duration;
|
|
1518
|
+
}
|
|
1519
|
+
set duration(_value) {
|
|
1520
|
+
}
|
|
1521
|
+
get volume() {
|
|
1522
|
+
return this._volume;
|
|
1523
|
+
}
|
|
1524
|
+
set volume(value) {
|
|
1525
|
+
const clamped = Math.min(1, Math.max(0, value));
|
|
1526
|
+
this.adapter.setVolume(clamped);
|
|
1527
|
+
this.applyVolume(clamped, this._muted);
|
|
1528
|
+
}
|
|
1529
|
+
get muted() {
|
|
1530
|
+
return this._muted;
|
|
1531
|
+
}
|
|
1532
|
+
set muted(value) {
|
|
1533
|
+
if (value)
|
|
1534
|
+
this.adapter.mute();
|
|
1535
|
+
else
|
|
1536
|
+
this.adapter.unmute();
|
|
1537
|
+
this.applyVolume(this._volume, value);
|
|
1538
|
+
}
|
|
1539
|
+
get playbackRate() {
|
|
1540
|
+
return this._playbackRate;
|
|
1541
|
+
}
|
|
1542
|
+
set playbackRate(value) {
|
|
1543
|
+
this.adapter.setPlaybackRate(value);
|
|
1544
|
+
this.applyRate(value);
|
|
1545
|
+
}
|
|
1546
|
+
get paused() {
|
|
1547
|
+
return this._paused;
|
|
1548
|
+
}
|
|
1549
|
+
get ended() {
|
|
1550
|
+
return this._ended;
|
|
1551
|
+
}
|
|
1552
|
+
// ─── MediaSurface methods ─────────────────────────────────────────────────
|
|
1553
|
+
load(_source) {
|
|
1554
|
+
// No-op: iframe surfaces are initialized with a fixed source at construction time.
|
|
1555
|
+
}
|
|
1556
|
+
play() {
|
|
1557
|
+
return Promise.resolve(this.adapter.play()).then(() => undefined);
|
|
1558
|
+
}
|
|
1559
|
+
pause() {
|
|
1560
|
+
void Promise.resolve(this.adapter.pause());
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Subscribe to surface events — satisfies MediaSurface.on() and is used by
|
|
1564
|
+
* bridgeSurfaceEvents / BaseMediaEngine.bindSurfaceEvents to forward events
|
|
1565
|
+
* onto the shared EventBus.
|
|
1566
|
+
*/
|
|
1567
|
+
on(event, handler, _options) {
|
|
1568
|
+
return this.internalBus.on(event, handler);
|
|
1569
|
+
}
|
|
1570
|
+
// ─── Extra lifecycle (beyond MediaSurface contract) ───────────────────────
|
|
1571
|
+
/** Mount the adapter iframe into a DOM container. Resolves once the adapter is ready. */
|
|
1572
|
+
async mount(container) {
|
|
1573
|
+
await this.adapter.mount(container);
|
|
1574
|
+
// Polling starts in onAdapterReady() after the player signals it is initialized.
|
|
1575
|
+
}
|
|
1576
|
+
destroy() {
|
|
1577
|
+
this.stopPolling();
|
|
1578
|
+
this.adapter.off('ready', this.onAdapterReady);
|
|
1579
|
+
this.adapter.off('state', this.onAdapterState);
|
|
1580
|
+
this.adapter.off('error', this.onAdapterError);
|
|
1581
|
+
this.adapter.destroy();
|
|
1582
|
+
this.internalBus.clear();
|
|
1583
|
+
}
|
|
1584
|
+
// ─── Adapter → internal bus ───────────────────────────────────────────────
|
|
1585
|
+
onAdapterReady() {
|
|
1586
|
+
this.applyDuration(this.safeNum(this.adapter.getDuration()));
|
|
1587
|
+
this.applyTime(this.safeNum(this.adapter.getCurrentTime()));
|
|
1588
|
+
if (this.adapter.getVolume)
|
|
1589
|
+
this.applyVolume(this.safeNum(this.adapter.getVolume()), this._muted);
|
|
1590
|
+
if (this.adapter.isMuted)
|
|
1591
|
+
this.applyVolume(this._volume, !!this.adapter.isMuted());
|
|
1592
|
+
if (this.adapter.getPlaybackRate)
|
|
1593
|
+
this.applyRate(this.safeNum(this.adapter.getPlaybackRate()));
|
|
1594
|
+
if (this.adapter.getElement) {
|
|
1595
|
+
this.element = this.adapter.getElement();
|
|
1596
|
+
}
|
|
1597
|
+
this.startPolling();
|
|
1598
|
+
this.internalBus.emit('loadedmetadata');
|
|
1599
|
+
}
|
|
1600
|
+
onAdapterState(s) {
|
|
1601
|
+
switch (s) {
|
|
1602
|
+
case 'loading':
|
|
1603
|
+
case 'buffering':
|
|
1604
|
+
this.internalBus.emit('waiting');
|
|
1605
|
+
break;
|
|
1606
|
+
case 'playing':
|
|
1607
|
+
this._paused = false;
|
|
1608
|
+
this._ended = false;
|
|
1609
|
+
this.internalBus.emit('play');
|
|
1610
|
+
this.internalBus.emit('playing');
|
|
1611
|
+
break;
|
|
1612
|
+
case 'paused':
|
|
1613
|
+
this._paused = true;
|
|
1614
|
+
this.internalBus.emit('pause');
|
|
1615
|
+
break;
|
|
1616
|
+
case 'ended':
|
|
1617
|
+
this._paused = true;
|
|
1618
|
+
this._ended = true;
|
|
1619
|
+
this.internalBus.emit('ended');
|
|
1620
|
+
break;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
onAdapterError(err) {
|
|
1624
|
+
this.internalBus.emit('error', err);
|
|
1625
|
+
}
|
|
1626
|
+
applyTime(t) {
|
|
1627
|
+
if (!Number.isFinite(t))
|
|
1628
|
+
return;
|
|
1629
|
+
this._currentTime = t;
|
|
1630
|
+
this.internalBus.emit('timeupdate');
|
|
1631
|
+
}
|
|
1632
|
+
applyDuration(d) {
|
|
1633
|
+
if (!Number.isFinite(d) || d <= 0)
|
|
1634
|
+
return;
|
|
1635
|
+
this._duration = d;
|
|
1636
|
+
this.internalBus.emit('durationchange');
|
|
1637
|
+
}
|
|
1638
|
+
applyRate(r) {
|
|
1639
|
+
if (!Number.isFinite(r) || r <= 0)
|
|
1640
|
+
return;
|
|
1641
|
+
this._playbackRate = r;
|
|
1642
|
+
this.internalBus.emit('ratechange');
|
|
1643
|
+
}
|
|
1644
|
+
applyVolume(v, muted) {
|
|
1645
|
+
if (!Number.isFinite(v))
|
|
1646
|
+
return;
|
|
1647
|
+
this._volume = Math.min(1, Math.max(0, v));
|
|
1648
|
+
this._muted = !!muted;
|
|
1649
|
+
this.internalBus.emit('volumechange');
|
|
1650
|
+
}
|
|
1651
|
+
applyEnded() {
|
|
1652
|
+
this._ended = true;
|
|
1653
|
+
this._paused = true;
|
|
1654
|
+
this.internalBus.emit('ended');
|
|
1655
|
+
}
|
|
1656
|
+
// ─── Polling ──────────────────────────────────────────────────────────────
|
|
1657
|
+
startPolling() {
|
|
1658
|
+
if (this.pollTimer != null)
|
|
1659
|
+
return;
|
|
1660
|
+
const tick = () => {
|
|
1661
|
+
this.applyTime(this.safeNum(this.adapter.getCurrentTime()));
|
|
1662
|
+
this.applyDuration(this.safeNum(this.adapter.getDuration()));
|
|
1663
|
+
if (this.adapter.getVolume)
|
|
1664
|
+
this.applyVolume(this.safeNum(this.adapter.getVolume()), this._muted);
|
|
1665
|
+
if (this.adapter.isMuted)
|
|
1666
|
+
this.applyVolume(this._volume, !!this.adapter.isMuted());
|
|
1667
|
+
if (this.adapter.getPlaybackRate)
|
|
1668
|
+
this.applyRate(this.safeNum(this.adapter.getPlaybackRate()));
|
|
1669
|
+
this.pollTimer = window.setTimeout(tick, this.pollIntervalMs);
|
|
1670
|
+
};
|
|
1671
|
+
this.pollTimer = window.setTimeout(tick, this.pollIntervalMs);
|
|
1672
|
+
}
|
|
1673
|
+
stopPolling() {
|
|
1674
|
+
if (this.pollTimer != null) {
|
|
1675
|
+
window.clearTimeout(this.pollTimer);
|
|
1676
|
+
this.pollTimer = null;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
safeNum(n) {
|
|
1680
|
+
const v = Number(n);
|
|
1681
|
+
return Number.isFinite(v) ? v : NaN;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
const CAPTION_KEY = Symbol.for('openplayerjs.caption.provider');
|
|
1686
|
+
function getCaptionTrackProvider(core) {
|
|
1687
|
+
return core[CAPTION_KEY] ?? null;
|
|
1688
|
+
}
|
|
1689
|
+
function setCaptionTrackProvider(core, provider) {
|
|
1690
|
+
core[CAPTION_KEY] = provider;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
export { BaseMediaEngine, Core, DVR_THRESHOLD, DefaultMediaEngine, DisposableStore, EVENT_OPTIONS, EventBus, HtmlMediaSurface, IframeMediaSurface, Lease, PluginRegistry, StateManager, formatTime, generateISODateTime, getCaptionTrackProvider, getOverlayManager, isAudio, isMobile, offset, setCaptionTrackProvider };
|
|
1241
1694
|
//# sourceMappingURL=index.js.map
|