@gcorevideo/player 2.28.36 → 2.30.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 +108 -0
- package/assets/media-control/media-control.scss +8 -6
- package/assets/multi-camera/multicamera.ejs +27 -23
- package/assets/multi-camera/style.scss +7 -34
- package/assets/style/main.scss +2 -2
- package/dist/core.js +24 -7
- package/dist/index.css +324 -346
- package/dist/index.embed.js +24 -46
- package/dist/index.js +471 -245
- package/docs/api/player.md +22 -9
- package/docs/api/player.mediacontrol.setkeepvisible.md +56 -0
- package/docs/api/player.multicamera.md +0 -28
- package/docs/api/player.multiccamerasourceinfo.md +27 -0
- package/docs/api/{player.multicamera.unbindevents.md → player.multisourcesmode.md} +4 -7
- package/docs/api/player.sourcecontroller.md +0 -37
- package/lib/Player.d.ts +9 -0
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +11 -0
- package/lib/index.plugins.d.ts +1 -0
- package/lib/index.plugins.d.ts.map +1 -1
- package/lib/index.plugins.js +1 -0
- package/lib/playback/dash-playback/DashPlayback.d.ts +2 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +5 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/types.d.ts +9 -0
- package/lib/playback/types.d.ts.map +1 -1
- package/lib/playback.types.d.ts +0 -6
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/multi-camera/MultiCamera.d.ts +21 -4
- package/lib/plugins/multi-camera/MultiCamera.d.ts.map +1 -1
- package/lib/plugins/multi-camera/MultiCamera.js +70 -134
- package/lib/plugins/source-controller/SourceController.d.ts +0 -39
- package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
- package/lib/plugins/source-controller/SourceController.js +0 -39
- package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts +119 -0
- package/lib/plugins/token-refresh/TokenRefreshPlugin.d.ts.map +1 -0
- package/lib/plugins/token-refresh/TokenRefreshPlugin.js +318 -0
- package/lib/plugins/token-refresh/index.d.ts +2 -0
- package/lib/plugins/token-refresh/index.d.ts.map +1 -0
- package/lib/plugins/token-refresh/index.js +1 -0
- package/lib/utils/mediaSources.d.ts +4 -0
- package/lib/utils/mediaSources.d.ts.map +1 -1
- package/lib/utils/mediaSources.js +8 -6
- package/package.json +1 -1
- package/src/Player.ts +12 -0
- package/src/index.plugins.ts +1 -0
- package/src/playback/dash-playback/DashPlayback.ts +7 -3
- package/src/playback/hls-playback/HlsPlayback.ts +1 -1
- package/src/playback/types.ts +10 -0
- package/src/playback.types.ts +0 -6
- package/src/plugins/multi-camera/MultiCamera.ts +103 -166
- package/src/plugins/source-controller/SourceController.ts +0 -39
- package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
- package/src/plugins/token-refresh/TokenRefreshPlugin.ts +425 -0
- package/src/plugins/token-refresh/index.ts +5 -0
- package/src/utils/mediaSources.ts +10 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.multicamera.activebyid.md +0 -67
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Browser, Events, Playback, template, UICorePlugin, } from '@clappr/core';
|
|
2
2
|
import { reportError, trace } from '@gcorevideo/utils';
|
|
3
|
+
import { guessMimeType, MIME_TYPE_DASH } from '../../utils/mediaSources.js';
|
|
3
4
|
import { CLAPPR_VERSION } from '../../build.js';
|
|
4
5
|
import pluginHtml from '../../../assets/multi-camera/multicamera.ejs';
|
|
5
6
|
import '../../../assets/multi-camera/style.scss';
|
|
6
7
|
import streamsIcon from '../../../assets/icons/old/streams.svg';
|
|
7
|
-
import streamsMomentoIcon from '../../../assets/icons/old/language.svg';
|
|
8
|
-
import streamsWhiteNightsIcon from '../../../assets/icons/old/wn.svg';
|
|
9
8
|
const VERSION = '0.0.1';
|
|
10
9
|
const T = 'plugins.multicamera';
|
|
11
10
|
/**
|
|
@@ -53,6 +52,8 @@ export class MultiCamera extends UICorePlugin {
|
|
|
53
52
|
// Don't mutate the options, TODO check if some plugin observes the options.multicamera
|
|
54
53
|
this.multicamera = this.options.multisources.map((item) => ({ ...item }));
|
|
55
54
|
this.noActiveStreams = this.multicamera.every((item) => !item.live);
|
|
55
|
+
// TODO filter out non-live
|
|
56
|
+
this.core.options.sources = expandMediaSource(this.multicamera[0]);
|
|
56
57
|
}
|
|
57
58
|
bindEvents() {
|
|
58
59
|
this.listenTo(this.core, Events.CORE_READY, this.bindPlaybackEvents);
|
|
@@ -61,15 +62,10 @@ export class MultiCamera extends UICorePlugin {
|
|
|
61
62
|
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_HIDE, this.hideSelectLevelMenu);
|
|
62
63
|
}
|
|
63
64
|
unBindEvents() {
|
|
64
|
-
|
|
65
|
-
this.stopListening(this.core, Events.
|
|
66
|
-
|
|
67
|
-
this.stopListening(this.core.mediaControl, Events.
|
|
68
|
-
// @ts-ignore
|
|
69
|
-
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_RENDERED);
|
|
70
|
-
// @ts-ignore
|
|
71
|
-
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_HIDE);
|
|
72
|
-
// @ts-ignore
|
|
65
|
+
this.stopListening(this.core, Events.CORE_READY, this.bindPlaybackEvents);
|
|
66
|
+
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.reload);
|
|
67
|
+
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this.render);
|
|
68
|
+
this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_HIDE, this.hideSelectLevelMenu);
|
|
73
69
|
this.stopListening(this.core.activePlayback, Events.PLAYBACK_PLAY, this.onPlay);
|
|
74
70
|
}
|
|
75
71
|
onPlay() {
|
|
@@ -85,80 +81,53 @@ export class MultiCamera extends UICorePlugin {
|
|
|
85
81
|
this.bindPlaybackEvents();
|
|
86
82
|
}
|
|
87
83
|
shouldRender() {
|
|
88
|
-
if (
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
if (!this.core.activePlayback) {
|
|
84
|
+
if (this.noActiveStreams) {
|
|
92
85
|
return false;
|
|
93
86
|
}
|
|
94
87
|
return this.multicamera.length >= 2;
|
|
95
88
|
}
|
|
96
89
|
render() {
|
|
97
|
-
if (this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
// const mediaControl = this.core.getPlugin('media_control')
|
|
110
|
-
if (this.currentTime &&
|
|
111
|
-
// TODO check the last active playback type instead
|
|
112
|
-
// !mediaControl.$el.hasClass('live') &&
|
|
113
|
-
this.core.getPlaybackType() !== Playback.LIVE) {
|
|
114
|
-
if (this.currentTime < this.core.activePlayback.getDuration()) {
|
|
115
|
-
this.core.activePlayback.seek(this.currentTime);
|
|
116
|
-
}
|
|
117
|
-
this.currentTime = 0;
|
|
118
|
-
// if (mediaControl.$el.hasClass('dvr')) {
|
|
119
|
-
// this.core.activeContainer.dvrInUse = true;
|
|
120
|
-
// }
|
|
121
|
-
}
|
|
122
|
-
// TODO current source
|
|
123
|
-
this.$el.html(this.template({
|
|
124
|
-
streams: this.multicamera,
|
|
125
|
-
multisources_mode: this.options.multisourcesMode,
|
|
126
|
-
}));
|
|
127
|
-
if ((numActiveSources <= 1 &&
|
|
128
|
-
this.options.multisourcesMode !== 'show_all') ||
|
|
129
|
-
this.options.multisourcesMode === 'one_first') {
|
|
130
|
-
this.$el.hide();
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
this.$el.show();
|
|
134
|
-
}
|
|
135
|
-
if (this.core.mediaControl.$multiCameraSelector &&
|
|
136
|
-
this.core.mediaControl.$multiCameraSelector.length > 0) {
|
|
137
|
-
this.core.mediaControl.$multiCameraSelector.append(this.el);
|
|
90
|
+
if (!this.core.activeContainer || !this.core.activePlayback) {
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
if (!this.shouldRender()) {
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
let numActiveSources = 0;
|
|
97
|
+
const currentSource = this.core.activePlayback?.sourceMedia;
|
|
98
|
+
for (const item of this.multicamera) {
|
|
99
|
+
if (item.live) {
|
|
100
|
+
numActiveSources++;
|
|
138
101
|
}
|
|
139
|
-
|
|
140
|
-
this.
|
|
102
|
+
if (!this.currentCamera && item.source === currentSource) {
|
|
103
|
+
this.currentCamera = item;
|
|
141
104
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.append(streamsWhiteNightsIcon);
|
|
148
|
-
}
|
|
149
|
-
else if (~window.location.href.indexOf('momentosolutions.gcdn.co')) {
|
|
150
|
-
this.core.mediaControl.$multiCameraSelector
|
|
151
|
-
.find('span.multicamera-icon')
|
|
152
|
-
.append(streamsMomentoIcon);
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
this.core.mediaControl.$multiCameraSelector
|
|
156
|
-
.find('span.multicamera-icon')
|
|
157
|
-
.append(streamsIcon);
|
|
158
|
-
}
|
|
105
|
+
}
|
|
106
|
+
if (this.currentTime &&
|
|
107
|
+
this.core.getPlaybackType() !== Playback.LIVE) {
|
|
108
|
+
if (this.currentTime < this.core.activePlayback.getDuration()) {
|
|
109
|
+
this.core.activePlayback.seek(this.currentTime);
|
|
159
110
|
}
|
|
160
|
-
this.
|
|
111
|
+
this.currentTime = 0;
|
|
161
112
|
}
|
|
113
|
+
this.$el.html(this.template({
|
|
114
|
+
streams: this.multicamera,
|
|
115
|
+
multisources_mode: this.options.multisourcesMode,
|
|
116
|
+
}));
|
|
117
|
+
if ((numActiveSources < 2 &&
|
|
118
|
+
this.options.multisourcesMode !== 'show_all') ||
|
|
119
|
+
this.options.multisourcesMode === 'one_first') {
|
|
120
|
+
this.$el.hide();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.$el.show();
|
|
124
|
+
}
|
|
125
|
+
const mediaControl = this.core.getPlugin('media_control');
|
|
126
|
+
mediaControl.slot('multicamera', this.$el);
|
|
127
|
+
this.$el
|
|
128
|
+
.find('span.multicamera-icon')
|
|
129
|
+
.html(streamsIcon);
|
|
130
|
+
this.highlightCurrentLevel();
|
|
162
131
|
return this;
|
|
163
132
|
}
|
|
164
133
|
onCameraSelect(event) {
|
|
@@ -171,33 +140,6 @@ export class MultiCamera extends UICorePlugin {
|
|
|
171
140
|
event.stopPropagation();
|
|
172
141
|
return false;
|
|
173
142
|
}
|
|
174
|
-
activeById(id, active) {
|
|
175
|
-
this.setLiveStatus(id, active);
|
|
176
|
-
if (!this.currentCamera && !this.noActiveStreams) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
if (this.noActiveStreams && !active) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (this.currentCamera) {
|
|
183
|
-
if (this.options.multisourcesMode === 'only_live') {
|
|
184
|
-
this.behaviorLive(id, active);
|
|
185
|
-
}
|
|
186
|
-
if (this.options.multisourcesMode === 'one_first') {
|
|
187
|
-
this.behaviorOne(id, active);
|
|
188
|
-
}
|
|
189
|
-
if (this.options.multisourcesMode === 'show_all') {
|
|
190
|
-
this.behaviorAll(id, active);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
if (this.noActiveStreams && active) {
|
|
195
|
-
this.changeById(id);
|
|
196
|
-
this.noActiveStreams = false;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
this.render();
|
|
200
|
-
}
|
|
201
143
|
setLiveStatus(id, active) {
|
|
202
144
|
try {
|
|
203
145
|
const index = this.findIndexById(id);
|
|
@@ -260,7 +202,6 @@ export class MultiCamera extends UICorePlugin {
|
|
|
260
202
|
this.currentCamera = null;
|
|
261
203
|
this.noActiveStreams = true;
|
|
262
204
|
this.core.trigger('core:multicamera:no_active_translation');
|
|
263
|
-
// this.changeById(this.multicamera[nextIndex].id);
|
|
264
205
|
}
|
|
265
206
|
showError() {
|
|
266
207
|
this.core.activePlayback.pause();
|
|
@@ -283,7 +224,7 @@ export class MultiCamera extends UICorePlugin {
|
|
|
283
224
|
}
|
|
284
225
|
hideError() {
|
|
285
226
|
try {
|
|
286
|
-
this.core.
|
|
227
|
+
this.core.getPlugin('media_control')?.enableControlButton();
|
|
287
228
|
}
|
|
288
229
|
catch (error) {
|
|
289
230
|
reportError(error);
|
|
@@ -293,14 +234,9 @@ export class MultiCamera extends UICorePlugin {
|
|
|
293
234
|
trace(`${T} changeById`, { id });
|
|
294
235
|
queueMicrotask(() => {
|
|
295
236
|
const playbackOptions = this.core.options.playback || {};
|
|
296
|
-
// TODO figure out
|
|
237
|
+
// TODO figure out if it's needed
|
|
297
238
|
playbackOptions.recycleVideo = Browser.isMobile;
|
|
298
|
-
this.currentCamera = this.findElementById(id)
|
|
299
|
-
trace(`${T} changeById`, {
|
|
300
|
-
id,
|
|
301
|
-
currentCamera: this.currentCamera,
|
|
302
|
-
multicamera: this.multicamera,
|
|
303
|
-
});
|
|
239
|
+
this.currentCamera = this.findElementById(id);
|
|
304
240
|
if (!this.currentCamera) {
|
|
305
241
|
return;
|
|
306
242
|
}
|
|
@@ -322,26 +258,16 @@ export class MultiCamera extends UICorePlugin {
|
|
|
322
258
|
this.core.configure({
|
|
323
259
|
playback: playbackOptions,
|
|
324
260
|
source: this.currentCamera.source, // TODO ensure that the preferred transport is used
|
|
325
|
-
video360: {
|
|
326
|
-
// TODO
|
|
327
|
-
projection: this.currentCamera.projection,
|
|
328
|
-
},
|
|
329
261
|
fullscreenDisable,
|
|
330
262
|
autoPlay: this.playing,
|
|
331
263
|
disableCanAutoPlay: true,
|
|
332
264
|
});
|
|
333
|
-
this.core.activeContainer
|
|
265
|
+
this.core.activeContainer?.enableMediaControl();
|
|
334
266
|
});
|
|
335
267
|
this.toggleContextMenu();
|
|
336
268
|
}
|
|
337
|
-
getCamerasList() {
|
|
338
|
-
return this.multicamera;
|
|
339
|
-
}
|
|
340
|
-
getCurrentCamera() {
|
|
341
|
-
return this.currentCamera;
|
|
342
|
-
}
|
|
343
269
|
findElementById(id) {
|
|
344
|
-
return this.multicamera.find((element) => element.id === id);
|
|
270
|
+
return this.multicamera.find((element) => element.id === id) ?? null;
|
|
345
271
|
}
|
|
346
272
|
findIndexById(id) {
|
|
347
273
|
return this.multicamera.findIndex((element) => element.id === id);
|
|
@@ -351,20 +277,14 @@ export class MultiCamera extends UICorePlugin {
|
|
|
351
277
|
}
|
|
352
278
|
hideSelectLevelMenu() {
|
|
353
279
|
;
|
|
354
|
-
this.$('
|
|
280
|
+
this.$('ul').hide();
|
|
355
281
|
}
|
|
356
282
|
toggleContextMenu() {
|
|
357
283
|
;
|
|
358
|
-
this.$('
|
|
359
|
-
}
|
|
360
|
-
// private buttonElement(): ZeptoResult {
|
|
361
|
-
// return this.$('.multicamera button');
|
|
362
|
-
// }
|
|
363
|
-
// private buttonElementText(): ZeptoResult {
|
|
364
|
-
// return this.$('.multicamera button .quality-text');
|
|
365
|
-
// }
|
|
284
|
+
this.$('ul').toggle();
|
|
285
|
+
}
|
|
366
286
|
levelElement(id) {
|
|
367
|
-
return this.$('.multicamera
|
|
287
|
+
return this.$('ul .multicamera-item' +
|
|
368
288
|
(id !== undefined
|
|
369
289
|
? '[data-multicamera-selector-select="' + id + '"]'
|
|
370
290
|
: ''));
|
|
@@ -376,3 +296,19 @@ export class MultiCamera extends UICorePlugin {
|
|
|
376
296
|
this.levelElement(this.currentCamera.id).addClass('multicamera-active');
|
|
377
297
|
}
|
|
378
298
|
}
|
|
299
|
+
function expandMediaSource(source) {
|
|
300
|
+
const result = [{
|
|
301
|
+
source: source.source,
|
|
302
|
+
mimeType: guessMimeType(source.source),
|
|
303
|
+
}];
|
|
304
|
+
if (source.source_dash) {
|
|
305
|
+
result.push({
|
|
306
|
+
source: source.source_dash,
|
|
307
|
+
mimeType: MIME_TYPE_DASH,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (source.hls_mpegts_url) {
|
|
311
|
+
result.push(source.hls_mpegts_url);
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
@@ -4,45 +4,6 @@ import { type PlayerMediaSourceDesc } from '../../types.js';
|
|
|
4
4
|
* `PLUGIN` that is managing the automatic failover between media sources.
|
|
5
5
|
* @public
|
|
6
6
|
* @remarks
|
|
7
|
-
* Have a look at the {@link https://miro.com/app/board/uXjVLiN15tY=/?share_link_id=390327585787 | source failover diagram} for the details
|
|
8
|
-
* on how sources ordering and selection works. Below is a simplified diagram:
|
|
9
|
-
*
|
|
10
|
-
* ```markdown
|
|
11
|
-
* sources_list:
|
|
12
|
-
* - a.mpd | +--------------------+
|
|
13
|
-
* - b.m3u8 |--->| init |
|
|
14
|
-
* - ... | |--------------------|
|
|
15
|
-
* | current_source = 0 |
|
|
16
|
-
* +--------------------+
|
|
17
|
-
* |
|
|
18
|
-
* | source = a.mpd
|
|
19
|
-
* | playback = dash.js
|
|
20
|
-
* v
|
|
21
|
-
* +------------------+
|
|
22
|
-
* +-->| load source |
|
|
23
|
-
* | +---------|--------+
|
|
24
|
-
* | v
|
|
25
|
-
* | +------------------+
|
|
26
|
-
* | | play |
|
|
27
|
-
* | +---------|--------+
|
|
28
|
-
* | |
|
|
29
|
-
* | v
|
|
30
|
-
* | +-----------------------+
|
|
31
|
-
* | | on playback_error |
|
|
32
|
-
* | |-----------------------|
|
|
33
|
-
* | | current_source = |
|
|
34
|
-
* | | (current_source + 1) |
|
|
35
|
-
* | | % len sources_list |
|
|
36
|
-
* | | |
|
|
37
|
-
* | | delay 1..3s |
|
|
38
|
-
* | +---------------|-------+
|
|
39
|
-
* | |
|
|
40
|
-
* | source=b.m3u8 |
|
|
41
|
-
* | playback=hls.js |
|
|
42
|
-
* +-------------------+
|
|
43
|
-
*
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
7
|
* @example
|
|
47
8
|
* ```ts
|
|
48
9
|
* import { SourceController } from '@gcorevideo/player'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SourceController.d.ts","sourceRoot":"","sources":["../../../src/plugins/source-controller/SourceController.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,KAAK,IAAI,IAAI,UAAU,EACxB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAsB3D
|
|
1
|
+
{"version":3,"file":"SourceController.d.ts","sourceRoot":"","sources":["../../../src/plugins/source-controller/SourceController.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,UAAU,EACV,KAAK,IAAI,IAAI,UAAU,EACxB,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AAsB3D;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAwC9C,OAAO,CAAC,WAAW,CAA8B;IAEjD,OAAO,CAAC,kBAAkB,CAAI;IAE9B,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO,CAAC,MAAM,CAAQ;IAEtB,OAAO,CAAC,QAAQ,CAAQ;IAExB,OAAO,CAAC,SAAS,CAAQ;IAEzB,OAAO,CAAC,IAAI,CAAiB;IAE7B;;OAEG;IACH,IAAI,IAAI,WAEP;IAED;;OAEG;IACH,IAAI,gBAAgB;;MAEnB;IAED;;OAEG;gBACS,IAAI,EAAE,UAAU;IAY5B;;;;;;;OAOG;IACH,cAAc,CAAC,WAAW,EAAE,qBAAqB,EAAE;IAQnD;;OAEG;IACM,UAAU;IAWnB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,wBAAwB;IAgBhC,OAAO,CAAC,2BAA2B;IAiDnC,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;IAe1B;;OAEG;IACH,MAAM,KAAK,OAAO,WAEjB;CACF"}
|
|
@@ -15,45 +15,6 @@ function noSync(cb) {
|
|
|
15
15
|
* `PLUGIN` that is managing the automatic failover between media sources.
|
|
16
16
|
* @public
|
|
17
17
|
* @remarks
|
|
18
|
-
* Have a look at the {@link https://miro.com/app/board/uXjVLiN15tY=/?share_link_id=390327585787 | source failover diagram} for the details
|
|
19
|
-
* on how sources ordering and selection works. Below is a simplified diagram:
|
|
20
|
-
*
|
|
21
|
-
* ```markdown
|
|
22
|
-
* sources_list:
|
|
23
|
-
* - a.mpd | +--------------------+
|
|
24
|
-
* - b.m3u8 |--->| init |
|
|
25
|
-
* - ... | |--------------------|
|
|
26
|
-
* | current_source = 0 |
|
|
27
|
-
* +--------------------+
|
|
28
|
-
* |
|
|
29
|
-
* | source = a.mpd
|
|
30
|
-
* | playback = dash.js
|
|
31
|
-
* v
|
|
32
|
-
* +------------------+
|
|
33
|
-
* +-->| load source |
|
|
34
|
-
* | +---------|--------+
|
|
35
|
-
* | v
|
|
36
|
-
* | +------------------+
|
|
37
|
-
* | | play |
|
|
38
|
-
* | +---------|--------+
|
|
39
|
-
* | |
|
|
40
|
-
* | v
|
|
41
|
-
* | +-----------------------+
|
|
42
|
-
* | | on playback_error |
|
|
43
|
-
* | |-----------------------|
|
|
44
|
-
* | | current_source = |
|
|
45
|
-
* | | (current_source + 1) |
|
|
46
|
-
* | | % len sources_list |
|
|
47
|
-
* | | |
|
|
48
|
-
* | | delay 1..3s |
|
|
49
|
-
* | +---------------|-------+
|
|
50
|
-
* | |
|
|
51
|
-
* | source=b.m3u8 |
|
|
52
|
-
* | playback=hls.js |
|
|
53
|
-
* +-------------------+
|
|
54
|
-
*
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
18
|
* @example
|
|
58
19
|
* ```ts
|
|
59
20
|
* import { SourceController } from '@gcorevideo/player'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { CorePlugin } from '@clappr/core';
|
|
2
|
+
/**
|
|
3
|
+
* Response shape expected from your token-refresh API endpoint.
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export interface TokenResponse {
|
|
7
|
+
/** Plain (non-IP-bound) secure token */
|
|
8
|
+
token: string;
|
|
9
|
+
/** IP-bound secure token */
|
|
10
|
+
token_ip: string;
|
|
11
|
+
/** Client IP address (informational) */
|
|
12
|
+
client_ip: string;
|
|
13
|
+
/** Unix timestamp (seconds) when both tokens expire */
|
|
14
|
+
expires: number;
|
|
15
|
+
/** Ready-to-use HLS master playlist URL with plain token embedded */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Ready-to-use HLS master playlist URL with IP-bound token embedded */
|
|
18
|
+
url_ip: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Configuration options for {@link TokenRefreshPlugin}.
|
|
22
|
+
* @public
|
|
23
|
+
*/
|
|
24
|
+
export interface TokenRefreshOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Async function called each time a fresh token is needed.
|
|
27
|
+
* Must return a {@link TokenResponse}.
|
|
28
|
+
*/
|
|
29
|
+
getToken: () => Promise<TokenResponse>;
|
|
30
|
+
/**
|
|
31
|
+
* When `true`, the IP-bound variant (`token_ip` / `url_ip`) is used.
|
|
32
|
+
* Defaults to `false`.
|
|
33
|
+
*/
|
|
34
|
+
ipBound?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Seconds before the token expiry timestamp to request a fresh token.
|
|
37
|
+
* Defaults to `5`.
|
|
38
|
+
*/
|
|
39
|
+
refreshLeadSeconds?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Optional callback invoked after every successful token refresh.
|
|
42
|
+
*/
|
|
43
|
+
onTokenRefreshed?: (data: TokenResponse) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `PLUGIN` — automatic token refresh for Gcore protected-content streams.
|
|
47
|
+
*
|
|
48
|
+
* Supports all three playback engines:
|
|
49
|
+
*
|
|
50
|
+
* | Engine | Mechanism | Interruption |
|
|
51
|
+
* |--------|-----------|--------------|
|
|
52
|
+
* | **hls.js** | Custom loader rewrites every request URL | None |
|
|
53
|
+
* | **dash.js** | `addRequestInterceptor` rewrites every request URL | None |
|
|
54
|
+
* | **Native `<video>`** (Safari ≤ iOS 14.4) | Source reload + seek restore | Brief |
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
* @remarks
|
|
58
|
+
* Register the plugin once before creating any player instance:
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { Player, TokenRefreshPlugin } from '@gcorevideo/player'
|
|
61
|
+
* Player.registerPlugin(TokenRefreshPlugin)
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Then pass `tokenRefresh` in `PlayerConfig`:
|
|
65
|
+
* ```ts
|
|
66
|
+
* const player = new Player({
|
|
67
|
+
* sources: [{ source: initialUrl, mimeType: 'application/x-mpegURL' }],
|
|
68
|
+
* tokenRefresh: {
|
|
69
|
+
* getToken: () => fetch('https://…/token').then(r => r.json()),
|
|
70
|
+
* ipBound: false,
|
|
71
|
+
* refreshLeadSeconds: 5,
|
|
72
|
+
* onTokenRefreshed: (data) => console.log('new token expires', data.expires),
|
|
73
|
+
* },
|
|
74
|
+
* })
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* Safari native — opt-in Service Worker for fully seamless refresh:
|
|
79
|
+
* ```js
|
|
80
|
+
* // Register token-refresh-sw.js (see example/ directory)
|
|
81
|
+
* // and omit tokenRefresh config — the SW handles rewriting.
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare class TokenRefreshPlugin extends CorePlugin {
|
|
85
|
+
/** @internal */
|
|
86
|
+
static get type(): 'core';
|
|
87
|
+
/** @internal */
|
|
88
|
+
get name(): string;
|
|
89
|
+
/** @internal */
|
|
90
|
+
get supportedVersion(): {
|
|
91
|
+
min: string;
|
|
92
|
+
};
|
|
93
|
+
/** Token state extracted from the currently-managed source URL */
|
|
94
|
+
private originalState;
|
|
95
|
+
/** Latest token state (updated after each refresh) */
|
|
96
|
+
private currentState;
|
|
97
|
+
/** Scheduled refresh timer handle */
|
|
98
|
+
private refreshTimer;
|
|
99
|
+
/** Playback time (seconds) to restore after a native-video source reload */
|
|
100
|
+
private savedPosition;
|
|
101
|
+
/** True when using native HTML5 Video playback (no request interception) */
|
|
102
|
+
private isNativePlayback;
|
|
103
|
+
/** Set in destroy(); short-circuits late timer callbacks and getToken() resolutions */
|
|
104
|
+
private destroyed;
|
|
105
|
+
/** @internal */
|
|
106
|
+
bindEvents(): void;
|
|
107
|
+
/** @internal */
|
|
108
|
+
destroy(): void;
|
|
109
|
+
private onContainersCreated;
|
|
110
|
+
private injectHlsLoader;
|
|
111
|
+
private injectDashInterceptor;
|
|
112
|
+
private reloadNativeSource;
|
|
113
|
+
private onActiveContainerChangedForNative;
|
|
114
|
+
private scheduleRefresh;
|
|
115
|
+
private performRefresh;
|
|
116
|
+
private get opts();
|
|
117
|
+
private clearTimer;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=TokenRefreshPlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenRefreshPlugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/token-refresh/TokenRefreshPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,UAAU,EAAU,MAAM,cAAc,CAAA;AAQrE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;OAEG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAA;CACjD;AAgED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,gBAAgB;IAChB,MAAM,KAAK,IAAI,IAAI,MAAM,CAExB;IAED,gBAAgB;IAChB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gBAAgB;IAChB,IAAI,gBAAgB;;MAEnB;IAED,kEAAkE;IAClE,OAAO,CAAC,aAAa,CAA0B;IAC/C,sDAAsD;IACtD,OAAO,CAAC,YAAY,CAA0B;IAC9C,qCAAqC;IACrC,OAAO,CAAC,YAAY,CAA6C;IACjE,4EAA4E;IAC5E,OAAO,CAAC,aAAa,CAAsB;IAC3C,4EAA4E;IAC5E,OAAO,CAAC,gBAAgB,CAAQ;IAChC,uFAAuF;IACvF,OAAO,CAAC,SAAS,CAAQ;IAEzB,gBAAgB;IACP,UAAU,IAAI,IAAI;IAQ3B,gBAAgB;IACP,OAAO,IAAI,IAAI;IAMxB,OAAO,CAAC,mBAAmB;IA8D3B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,qBAAqB;YAmBf,kBAAkB;IA+ChC,OAAO,CAAC,iCAAiC;IAiBzC,OAAO,CAAC,eAAe;YAmBT,cAAc;IAmC5B,OAAO,KAAK,IAAI,GAEf;IAED,OAAO,CAAC,UAAU;CAMnB"}
|