@gcorevideo/player 2.28.35 → 2.29.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 +22 -1
- package/assets/{subtitles → cc}/style.scss +5 -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 -28
- package/dist/index.css +384 -402
- package/dist/index.embed.js +54 -84
- package/dist/index.js +122 -219
- 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/playback/BasePlayback.d.ts +1 -0
- package/lib/playback/BasePlayback.d.ts.map +1 -1
- package/lib/playback/BasePlayback.js +3 -0
- package/lib/playback/dash-playback/DashPlayback.d.ts +3 -1
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +9 -22
- package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +4 -0
- 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/subtitles/ClosedCaptions.d.ts +1 -1
- package/lib/plugins/subtitles/ClosedCaptions.d.ts.map +1 -1
- package/lib/plugins/subtitles/ClosedCaptions.js +32 -22
- package/lib/testUtils.d.ts +1 -0
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +3 -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/playback/BasePlayback.ts +4 -0
- package/src/playback/dash-playback/DashPlayback.ts +11 -29
- package/src/playback/hls-playback/HlsPlayback.ts +5 -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 +35 -21
- package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +73 -112
- package/src/plugins/subtitles/__tests__/__snapshots__/ClosedCaptions.test.ts.snap +3 -3
- package/src/testUtils.ts +3 -0
- package/src/utils/mediaSources.ts +10 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.multicamera.activebyid.md +0 -67
- /package/assets/{subtitles → cc}/combobox.ejs +0 -0
- /package/assets/{subtitles → cc}/string.ejs +0 -0
|
@@ -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'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClosedCaptions.d.ts","sourceRoot":"","sources":["../../../src/plugins/subtitles/ClosedCaptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,YAAY,EAAwB,MAAM,cAAc,CAAA;AAOzE,OAAO,
|
|
1
|
+
{"version":3,"file":"ClosedCaptions.d.ts","sourceRoot":"","sources":["../../../src/plugins/subtitles/ClosedCaptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,YAAY,EAAwB,MAAM,cAAc,CAAA;AAOzE,OAAO,+BAA+B,CAAA;AAmBtC;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,iBAAiB,CAAQ;IAEjC,OAAO,CAAC,MAAM,CAAQ;IAEtB,OAAO,CAAC,IAAI,CAAQ;IAEpB,OAAO,CAAC,kBAAkB,CAAa;IAEvC,OAAO,CAAC,KAAK,CAA6B;IAE1C,OAAO,CAAC,MAAM,CAAsB;IAEpC,OAAO,CAAC,KAAK,CAA2B;IAExC;;OAEG;IACH,IAAI,IAAI,WAEP;IAED;;OAEG;IACH,IAAI,gBAAgB;;MAEnB;IAED;;OAEG;IACH,MAAM,KAAK,OAAO,WAEjB;IAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEhE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAuB;IAE3D;;OAEG;IACH,IAAa,UAAU;;MAItB;IAED;;OAEG;IACH,IAAa,MAAM;;;MAKlB;IAED,OAAO,KAAK,mBAAmB,GAI9B;IAED,OAAO,CAAC,qBAAqB;IAO7B;;OAEG;IACM,UAAU;IAWnB,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,IAAI;IAcJ;;OAEG;IACH,IAAI;IAgBJ,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACM,MAAM;IAmCf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,sBAAsB;IAwB9B,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,KAAK;IAOb,OAAO,KAAK,kBAAkB,GAO7B;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,KAAK,YAAY,GAEvB;IAED,OAAO,KAAK,YAAY,QAEvB;IAED,OAAO,KAAK,aAAa,QAMxB;IAED,OAAO,CAAC,SAAS,CAA+C;CACjE"}
|
|
@@ -2,18 +2,15 @@ import { Events, UICorePlugin, Browser, template, $ } from '@clappr/core';
|
|
|
2
2
|
import { reportError } from '@gcorevideo/utils';
|
|
3
3
|
import assert from 'assert';
|
|
4
4
|
import { CLAPPR_VERSION } from '../../build.js';
|
|
5
|
-
import '../../../assets/
|
|
5
|
+
import '../../../assets/cc/style.scss';
|
|
6
6
|
import subtitlesOffIcon from '../../../assets/icons/new/subtitles-off.svg';
|
|
7
7
|
import subtitlesOnIcon from '../../../assets/icons/new/subtitles-on.svg';
|
|
8
|
-
import comboboxHTML from '../../../assets/
|
|
9
|
-
import stringHTML from '../../../assets/
|
|
8
|
+
import comboboxHTML from '../../../assets/cc/combobox.ejs';
|
|
9
|
+
import stringHTML from '../../../assets/cc/string.ejs';
|
|
10
10
|
import { isFullscreen } from '../utils/fullscreen.js';
|
|
11
11
|
import { ExtendedEvents } from '../media-control/MediaControl.js';
|
|
12
12
|
import { mediaControlClickaway } from '../../utils/clickaway.js';
|
|
13
13
|
const VERSION = '2.19.14';
|
|
14
|
-
// TODO review
|
|
15
|
-
// const LOCAL_STORAGE_CC_ID = 'gplayer.plugins.cc.selected'
|
|
16
|
-
const T = 'plugins.cc';
|
|
17
14
|
/**
|
|
18
15
|
* `PLUGIN` that provides a UI to select the subtitles when available.
|
|
19
16
|
* @public
|
|
@@ -115,8 +112,12 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
115
112
|
const mediaControl = this.core.getPlugin('media_control');
|
|
116
113
|
assert(mediaControl, 'media_control plugin is required');
|
|
117
114
|
this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.mount);
|
|
115
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_SHOW, () => {
|
|
116
|
+
this.$line?.removeClass('media-control-cc-pulled');
|
|
117
|
+
});
|
|
118
118
|
this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, () => {
|
|
119
119
|
this.hideMenu();
|
|
120
|
+
this.$line?.addClass('media-control-cc-pulled');
|
|
120
121
|
});
|
|
121
122
|
this.listenTo(mediaControl, ExtendedEvents.MEDIACONTROL_MENU_COLLAPSE, (from) => {
|
|
122
123
|
if (from !== this.name) {
|
|
@@ -174,23 +175,27 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
174
175
|
this.activateTrack(id);
|
|
175
176
|
}
|
|
176
177
|
activateTrack(id) {
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
const isManaged = this.core.activePlayback?.name === 'hls';
|
|
179
|
+
this.core.activePlayback.setTextTrack(id);
|
|
180
|
+
if (isManaged) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!this.core.activePlayback?.el.textTracks) {
|
|
179
184
|
return;
|
|
180
185
|
}
|
|
181
|
-
for (const track of this.
|
|
182
|
-
if (
|
|
186
|
+
for (const [index, track] of Array.from(this.core.activePlayback?.el.textTracks ?? []).entries()) {
|
|
187
|
+
if (index === id) {
|
|
183
188
|
if (this.useNativeSubtitles) {
|
|
184
|
-
track.
|
|
189
|
+
track.mode = 'showing';
|
|
185
190
|
}
|
|
186
191
|
else {
|
|
187
|
-
track.
|
|
192
|
+
track.mode = 'hidden';
|
|
188
193
|
}
|
|
189
|
-
this.setSubtitleText(this.getSubtitleText(track
|
|
190
|
-
track.
|
|
194
|
+
this.setSubtitleText(this.getSubtitleText(track));
|
|
195
|
+
track.oncuechange = () => {
|
|
191
196
|
try {
|
|
192
|
-
if (track.
|
|
193
|
-
const html = track.
|
|
197
|
+
if (track.activeCues?.length) {
|
|
198
|
+
const html = track.activeCues[0].getCueAsHTML();
|
|
194
199
|
this.setSubtitleText(html);
|
|
195
200
|
}
|
|
196
201
|
else {
|
|
@@ -203,8 +208,8 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
203
208
|
};
|
|
204
209
|
}
|
|
205
210
|
else {
|
|
206
|
-
track.
|
|
207
|
-
track.
|
|
211
|
+
track.oncuechange = () => { };
|
|
212
|
+
track.mode = 'disabled';
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
215
|
}
|
|
@@ -247,8 +252,10 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
247
252
|
this.$el.find('#gplayer-cc-menu').hide();
|
|
248
253
|
this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false');
|
|
249
254
|
this.$line.hide();
|
|
250
|
-
for (const track of this.
|
|
251
|
-
track.
|
|
255
|
+
for (const track of this.core.activePlayback.el.textTracks) {
|
|
256
|
+
if (track.mode === 'showing') {
|
|
257
|
+
track.mode = 'hidden';
|
|
258
|
+
}
|
|
252
259
|
}
|
|
253
260
|
}
|
|
254
261
|
/**
|
|
@@ -260,7 +267,6 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
260
267
|
if (this.core.activeContainer &&
|
|
261
268
|
isFullscreen(this.core.activeContainer.el) &&
|
|
262
269
|
this.currentTrack &&
|
|
263
|
-
// this.currentTrack.track.mode &&
|
|
264
270
|
(Browser.isiOS || this.useNativeSubtitles)) {
|
|
265
271
|
this.$line.hide();
|
|
266
272
|
this.currentTrack.track.mode = 'showing';
|
|
@@ -302,6 +308,10 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
302
308
|
this.resizeFont();
|
|
303
309
|
this.clampPopup();
|
|
304
310
|
this.core.activeContainer.$el.append(this.$line);
|
|
311
|
+
const mc = this.core.getPlugin('media_control');
|
|
312
|
+
if (!mc?.isVisible()) {
|
|
313
|
+
this.$line?.addClass('media-control-cc-pulled');
|
|
314
|
+
}
|
|
305
315
|
this.updateSelection();
|
|
306
316
|
this.renderIcon();
|
|
307
317
|
return this;
|
|
@@ -407,7 +417,7 @@ export class ClosedCaptions extends UICorePlugin {
|
|
|
407
417
|
this.setSubtitleText('');
|
|
408
418
|
}
|
|
409
419
|
updateSelection() {
|
|
410
|
-
if (
|
|
420
|
+
if (this.core.activePlayback.closedCaptionsTrackId === -1) {
|
|
411
421
|
this.hide();
|
|
412
422
|
}
|
|
413
423
|
else {
|
package/lib/testUtils.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export declare function createMockPlayback(name?: string, options?: Record<strin
|
|
|
65
65
|
canAutoPlay: import("vitest").Mock<(...args: any[]) => any>;
|
|
66
66
|
onResize: import("vitest").Mock<(...args: any[]) => any>;
|
|
67
67
|
setPlaybackRate: import("vitest").Mock<(...args: any[]) => any>;
|
|
68
|
+
setTextTrack: import("vitest").Mock<(...args: any[]) => any>;
|
|
68
69
|
switchAudioTrack: import("vitest").Mock<(...args: any[]) => any>;
|
|
69
70
|
trigger: <T extends string | symbol>(event: T, ...args: any[]) => boolean;
|
|
70
71
|
};
|
package/lib/testUtils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,MAAM,MAAM,eAAe,CAAA;AAGlC,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAAkC;;;;;;;;;;;;;;;;;;EAuB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,SAAS,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,MAAM,MAAM,eAAe,CAAA;AAGlC,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAAkC;;;;;;;;;;;;;;;;;;EAuB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,SAAS,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwCtC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,QAAQ,GAAE,GAAgD;;;;;;;;;;;;;;;;;;;;;;;;;EA8B3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBA2B/C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,GAAG,OAe7C"}
|
package/lib/testUtils.js
CHANGED
|
@@ -71,6 +71,7 @@ export function createMockPlayback(name = 'mock', options = {}) {
|
|
|
71
71
|
canAutoPlay: vi.fn().mockImplementation(() => true),
|
|
72
72
|
onResize: vi.fn().mockImplementation(() => true),
|
|
73
73
|
setPlaybackRate: vi.fn(),
|
|
74
|
+
setTextTrack: vi.fn(),
|
|
74
75
|
switchAudioTrack: vi.fn(),
|
|
75
76
|
trigger: emitter.emit,
|
|
76
77
|
});
|
|
@@ -122,6 +123,8 @@ export function createMockMediaControl(core) {
|
|
|
122
123
|
// @ts-ignore
|
|
123
124
|
mediaControl.getAvailablePopupHeight = vi.fn().mockReturnValue(286);
|
|
124
125
|
// @ts-ignore
|
|
126
|
+
mediaControl.isVisible = vi.fn().mockReturnValue(true);
|
|
127
|
+
// @ts-ignore
|
|
125
128
|
mediaControl.toggleElement = vi.fn();
|
|
126
129
|
// @ts-ignore
|
|
127
130
|
mediaControl.setKeepVisible = vi.fn();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { PlayerMediaSource, PlayerMediaSourceDesc, TransportPreference } from '../types';
|
|
2
|
+
export declare const MIME_TYPES_HLS: string[];
|
|
3
|
+
export declare const MIME_TYPE_HLS: string;
|
|
4
|
+
export declare const MIME_TYPE_DASH = "application/dash+xml";
|
|
2
5
|
export declare function buildMediaSourcesList(sources: PlayerMediaSourceDesc[], priorityTransport?: TransportPreference): PlayerMediaSourceDesc[];
|
|
3
6
|
export declare function unwrapSource(s: PlayerMediaSource): string;
|
|
4
7
|
export declare function wrapSource(s: PlayerMediaSource): PlayerMediaSourceDesc;
|
|
8
|
+
export declare function guessMimeType(s: string): string | undefined;
|
|
5
9
|
export declare function isDashSource(source: string, mimeType?: string): boolean;
|
|
6
10
|
export declare function isHlsSource(source: string, mimeType?: string): boolean;
|
|
7
11
|
//# sourceMappingURL=mediaSources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediaSources.d.ts","sourceRoot":"","sources":["../../src/utils/mediaSources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"mediaSources.d.ts","sourceRoot":"","sources":["../../src/utils/mediaSources.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,UAAU,CAAA;AAGjB,eAAO,MAAM,cAAc,UAA6D,CAAA;AACxF,eAAO,MAAM,aAAa,QAAoB,CAAA;AAC9C,eAAO,MAAM,cAAc,yBAAyB,CAAA;AAGpD,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,qBAAqB,EAAE,EAChC,iBAAiB,GAAE,mBAA4B,GAC9C,qBAAqB,EAAE,CAqCzB;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,iBAAiB,GAAG,MAAM,CAEzD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,iBAAiB,GAAG,qBAAqB,CAEtE;AAGD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAO3D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAK7D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAO5D"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Loader } from '@clappr/core';
|
|
2
2
|
import { trace } from '@gcorevideo/utils';
|
|
3
|
+
export const MIME_TYPES_HLS = ['application/x-mpegurl', 'application/vnd.apple.mpegurl'];
|
|
4
|
+
export const MIME_TYPE_HLS = MIME_TYPES_HLS[0];
|
|
5
|
+
export const MIME_TYPE_DASH = 'application/dash+xml';
|
|
3
6
|
// TODO rewrite using the Playback classes and canPlay static methods
|
|
4
7
|
export function buildMediaSourcesList(sources, priorityTransport = 'dash') {
|
|
5
8
|
const playbacks = Loader.registeredPlaybacks;
|
|
@@ -40,24 +43,23 @@ export function unwrapSource(s) {
|
|
|
40
43
|
export function wrapSource(s) {
|
|
41
44
|
return typeof s === 'string' ? { source: s, mimeType: guessMimeType(s) } : s;
|
|
42
45
|
}
|
|
43
|
-
function guessMimeType(s) {
|
|
46
|
+
export function guessMimeType(s) {
|
|
44
47
|
if (s.endsWith('.mpd')) {
|
|
45
|
-
return
|
|
48
|
+
return MIME_TYPE_DASH;
|
|
46
49
|
}
|
|
47
50
|
if (s.endsWith('.m3u8')) {
|
|
48
|
-
|
|
49
|
-
return 'application/x-mpegurl';
|
|
51
|
+
return MIME_TYPE_HLS;
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
export function isDashSource(source, mimeType) {
|
|
53
55
|
if (mimeType) {
|
|
54
|
-
return mimeType ===
|
|
56
|
+
return mimeType === MIME_TYPE_DASH; // TODO consider video/mp4
|
|
55
57
|
}
|
|
56
58
|
return source.endsWith('.mpd');
|
|
57
59
|
}
|
|
58
60
|
export function isHlsSource(source, mimeType) {
|
|
59
61
|
if (mimeType) {
|
|
60
|
-
return
|
|
62
|
+
return MIME_TYPES_HLS.includes(mimeType.toLowerCase());
|
|
61
63
|
}
|
|
62
64
|
return source.endsWith('.m3u8');
|
|
63
65
|
}
|
package/package.json
CHANGED