@gcorevideo/player 2.20.22 → 2.21.3
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/assets/audio-selector/style.scss +48 -82
- package/assets/audio-selector/track-selector.ejs +3 -3
- package/assets/bottom-gear/bottomgear.ejs +10 -12
- package/assets/bottom-gear/gear-sub-menu.scss +0 -15
- package/assets/bottom-gear/gear.scss +3 -32
- package/assets/media-control/media-control.ejs +5 -25
- package/assets/media-control/media-control.scss +114 -34
- package/assets/media-control/width370.scss +35 -109
- package/assets/picture-in-picture/button.ejs +1 -1
- package/assets/picture-in-picture/button.scss +5 -4
- package/assets/subtitles/combobox.ejs +7 -9
- package/assets/subtitles/style.scss +8 -15
- package/dist/core.js +151 -23
- package/dist/index.css +897 -1000
- package/dist/index.js +416 -438
- package/dist/player.d.ts +19 -16
- package/dist/plugins/index.css +1454 -1557
- package/dist/plugins/index.js +826 -23550
- package/docs/api/player.audioselector.md +4 -59
- package/docs/api/player.md +1 -1
- package/docs/api/player.mediacontrol.getelement.md +5 -0
- package/docs/api/player.mediacontrol.md +14 -0
- package/docs/api/{player.audioselector.updatecurrenttrack.md → player.mediacontrol.putelement.md} +7 -7
- package/docs/api/player.mediacontrolelement.md +1 -1
- package/docs/api/{player.audioselector.starttrackswitch.md → player.pictureinpicture.attributes.md} +5 -7
- package/docs/api/player.pictureinpicture.md +45 -0
- package/lib/playback/BasePlayback.d.ts +1 -1
- package/lib/playback/BasePlayback.d.ts.map +1 -1
- package/lib/playback/BasePlayback.js +3 -1
- package/lib/playback/HTML5Video.d.ts +4 -0
- package/lib/playback/HTML5Video.d.ts.map +1 -1
- package/lib/playback/HTML5Video.js +53 -4
- package/lib/playback/dash-playback/DashPlayback.d.ts +5 -0
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +48 -4
- package/lib/playback/hls-playback/HlsPlayback.d.ts +31 -25
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +47 -14
- package/lib/playback.types.d.ts +5 -0
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/plugins/audio-selector/AudioSelector.d.ts +12 -11
- package/lib/plugins/audio-selector/AudioSelector.d.ts.map +1 -1
- package/lib/plugins/audio-selector/AudioSelector.js +65 -185
- package/lib/plugins/bottom-gear/BottomGear.d.ts +2 -2
- package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
- package/lib/plugins/bottom-gear/BottomGear.js +12 -10
- package/lib/plugins/level-selector/LevelSelector.js +1 -1
- package/lib/plugins/media-control/MediaControl.d.ts +3 -4
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +23 -13
- package/lib/plugins/picture-in-picture/PictureInPicture.d.ts +3 -0
- package/lib/plugins/picture-in-picture/PictureInPicture.d.ts.map +1 -1
- package/lib/plugins/picture-in-picture/PictureInPicture.js +6 -1
- package/lib/plugins/playback-rate/PlaybackRate.d.ts +1 -0
- package/lib/plugins/playback-rate/PlaybackRate.d.ts.map +1 -1
- package/lib/plugins/playback-rate/PlaybackRate.js +1 -0
- package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
- package/lib/plugins/source-controller/SourceController.js +0 -1
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +0 -2
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +1 -18
- package/lib/plugins/subtitles/Subtitles.d.ts +21 -19
- package/lib/plugins/subtitles/Subtitles.d.ts.map +1 -1
- package/lib/plugins/subtitles/Subtitles.js +121 -151
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +2 -0
- package/package.json +1 -1
- package/src/playback/BasePlayback.ts +4 -1
- package/src/playback/HTML5Video.ts +57 -4
- package/src/playback/dash-playback/DashPlayback.ts +64 -6
- package/src/playback/hls-playback/HlsPlayback.ts +82 -40
- package/src/playback.types.ts +6 -0
- package/src/plugins/audio-selector/AudioSelector.ts +84 -278
- package/src/plugins/bottom-gear/BottomGear.ts +14 -11
- package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +1 -3
- package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +14 -37
- package/src/plugins/level-selector/LevelSelector.ts +1 -1
- package/src/plugins/media-control/MediaControl.ts +54 -32
- package/src/plugins/picture-in-picture/PictureInPicture.ts +7 -1
- package/src/plugins/playback-rate/PlaybackRate.ts +1 -0
- package/src/plugins/source-controller/SourceController.ts +0 -1
- package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +1 -20
- package/src/plugins/subtitles/Subtitles.ts +144 -179
- package/src/testUtils.ts +2 -0
- package/src/typings/globals.d.ts +19 -0
- package/temp/player.api.json +102 -143
- package/tsconfig.tsbuildinfo +1 -1
- package/assets/media-control/plugins.scss +0 -94
- package/docs/api/player.audioselector.highlightcurrenttrack.md +0 -18
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Events, UICorePlugin, Browser, template,
|
|
1
|
+
import { Events, UICorePlugin, Browser, template, $ } from '@clappr/core';
|
|
2
2
|
import { reportError, trace } from '@gcorevideo/utils';
|
|
3
3
|
import assert from 'assert';
|
|
4
4
|
import { CLAPPR_VERSION } from '../../build.js';
|
|
@@ -11,7 +11,6 @@ import { isFullscreen } from '../utils.js';
|
|
|
11
11
|
const VERSION = '2.19.14';
|
|
12
12
|
const LOCAL_STORAGE_SUBTITLES_ID = 'gplayer.plugins.subtitles.selected';
|
|
13
13
|
const T = 'plugins.subtitles';
|
|
14
|
-
const NO_TRACK = { language: 'off' };
|
|
15
14
|
/**
|
|
16
15
|
* `PLUGIN` that provides a UI to select the subtitles when available.
|
|
17
16
|
* @beta
|
|
@@ -21,10 +20,7 @@ const NO_TRACK = { language: 'off' };
|
|
|
21
20
|
*
|
|
22
21
|
* - {@link MediaControl}
|
|
23
22
|
*
|
|
24
|
-
* Configuration options
|
|
25
|
-
*
|
|
26
|
-
* - subtitles.language - The language of the subtitles to select by default.
|
|
27
|
-
*
|
|
23
|
+
* Configuration options - {@link SubtitlesPluginSettings}
|
|
28
24
|
* @example
|
|
29
25
|
* ```ts
|
|
30
26
|
* import { Subtitles } from '@gcorevideo/player'
|
|
@@ -40,11 +36,10 @@ const NO_TRACK = { language: 'off' };
|
|
|
40
36
|
* ```
|
|
41
37
|
*/
|
|
42
38
|
export class Subtitles extends UICorePlugin {
|
|
43
|
-
currentLevel = null;
|
|
44
39
|
isPreselectedApplied = false;
|
|
45
40
|
isShowing = false;
|
|
46
|
-
track =
|
|
47
|
-
tracks =
|
|
41
|
+
track = null;
|
|
42
|
+
tracks = [];
|
|
48
43
|
$string = null;
|
|
49
44
|
/**
|
|
50
45
|
* @internal
|
|
@@ -71,7 +66,7 @@ export class Subtitles extends UICorePlugin {
|
|
|
71
66
|
*/
|
|
72
67
|
get attributes() {
|
|
73
68
|
return {
|
|
74
|
-
class:
|
|
69
|
+
class: 'media-control-subtitles',
|
|
75
70
|
'data-subtitles': '',
|
|
76
71
|
};
|
|
77
72
|
}
|
|
@@ -80,28 +75,34 @@ export class Subtitles extends UICorePlugin {
|
|
|
80
75
|
*/
|
|
81
76
|
get events() {
|
|
82
77
|
return {
|
|
83
|
-
'click [data-subtitles-select]': '
|
|
84
|
-
'click [data-subtitles-button]': '
|
|
78
|
+
'click [data-subtitles-select]': 'onItemSelect',
|
|
79
|
+
'click [data-subtitles-button]': 'toggleMenu',
|
|
85
80
|
};
|
|
86
81
|
}
|
|
87
82
|
get preselectedLanguage() {
|
|
88
|
-
return this.core.options.subtitles?.language ?? '
|
|
83
|
+
return this.core.options.subtitles?.language ?? '';
|
|
89
84
|
}
|
|
90
85
|
/**
|
|
91
86
|
* @internal
|
|
92
87
|
*/
|
|
93
88
|
bindEvents() {
|
|
89
|
+
this.listenTo(this.core, Events.CORE_READY, this.onCoreReady);
|
|
90
|
+
this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize);
|
|
91
|
+
this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onContainerChanged);
|
|
92
|
+
}
|
|
93
|
+
onCoreReady() {
|
|
94
|
+
trace(`${T} onCoreReady`);
|
|
94
95
|
const mediaControl = this.core.getPlugin('media_control');
|
|
95
96
|
assert(mediaControl, 'media_control plugin is required');
|
|
96
|
-
this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize);
|
|
97
|
-
this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.bindPlaybackEvents);
|
|
98
97
|
this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.render);
|
|
99
|
-
this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, this.
|
|
98
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, this.hideMenu);
|
|
100
99
|
}
|
|
101
|
-
|
|
100
|
+
onContainerChanged() {
|
|
101
|
+
trace(`${T} onContainerChanged`);
|
|
102
102
|
this.listenTo(this.core.activeContainer, Events.CONTAINER_FULLSCREEN, this.playerResize);
|
|
103
|
-
this.listenToOnce(this.core.activePlayback, Events.PLAYBACK_PLAY, this.getTracks);
|
|
104
103
|
this.listenTo(this.core.activeContainer, 'container:advertisement:start', this.onStartAd);
|
|
104
|
+
this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_AVAILABLE, this.onSubtitleAvailable);
|
|
105
|
+
this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_CHANGED, this.onSubtitleChanged);
|
|
105
106
|
// fix for iOS
|
|
106
107
|
const video = this.core.activePlayback.el;
|
|
107
108
|
assert(video, 'video element is required');
|
|
@@ -116,20 +117,50 @@ export class Subtitles extends UICorePlugin {
|
|
|
116
117
|
}
|
|
117
118
|
});
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
onSubtitleAvailable() {
|
|
121
|
+
trace(`${T} onSubtitleAvailable`);
|
|
122
|
+
this.applyTracks();
|
|
123
|
+
}
|
|
124
|
+
onSubtitleChanged({ id }) {
|
|
125
|
+
trace(`${T} onSubtitleChanged`, { id });
|
|
126
|
+
if (id === -1) {
|
|
127
|
+
this.clearSubtitleText();
|
|
128
|
+
}
|
|
129
|
+
for (const track of this.tracks) {
|
|
130
|
+
if (track.id === id) {
|
|
131
|
+
track.track.mode = 'showing';
|
|
132
|
+
this.setSubtitleText(this.getSubtitleText(track.track));
|
|
133
|
+
track.track.oncuechange = (e) => {
|
|
134
|
+
try {
|
|
135
|
+
if (track.track.activeCues?.length) {
|
|
136
|
+
const html = track.track.activeCues[0].getCueAsHTML();
|
|
137
|
+
this.setSubtitleText(html);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.clearSubtitleText();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
reportError(error);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
127
147
|
}
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
else {
|
|
149
|
+
track.track.oncuechange = null;
|
|
150
|
+
track.track.mode = 'hidden';
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
153
|
}
|
|
154
|
+
applyTracks() {
|
|
155
|
+
try {
|
|
156
|
+
this.tracks = this.core.activePlayback.closedCaptionsTracks;
|
|
157
|
+
this.applyPreselectedSubtitles();
|
|
158
|
+
this.render();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
reportError(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
133
164
|
onStartAd() {
|
|
134
165
|
if (this.isShowing && this.core.activeContainer) {
|
|
135
166
|
this.hide();
|
|
@@ -141,10 +172,11 @@ export class Subtitles extends UICorePlugin {
|
|
|
141
172
|
this.stopListening(this.core.activeContainer, 'container:advertisement:finish', this.onFinishAd);
|
|
142
173
|
}
|
|
143
174
|
playerResize() {
|
|
175
|
+
trace(`${T} playerResize`);
|
|
144
176
|
const shouldShow = this.core.activeContainer &&
|
|
145
177
|
isFullscreen(this.core.activeContainer.el) &&
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
178
|
+
this.track &&
|
|
179
|
+
this.track.track.mode &&
|
|
148
180
|
Browser.isiOS &&
|
|
149
181
|
this.isShowing;
|
|
150
182
|
if (shouldShow) {
|
|
@@ -166,7 +198,7 @@ export class Subtitles extends UICorePlugin {
|
|
|
166
198
|
this.$string.hide();
|
|
167
199
|
if (this.tracks) {
|
|
168
200
|
for (const t of this.tracks) {
|
|
169
|
-
t.mode = 'hidden';
|
|
201
|
+
t.track.mode = 'hidden';
|
|
170
202
|
}
|
|
171
203
|
}
|
|
172
204
|
}
|
|
@@ -178,23 +210,20 @@ export class Subtitles extends UICorePlugin {
|
|
|
178
210
|
this.renderIcon();
|
|
179
211
|
if (this.core.activeContainer &&
|
|
180
212
|
isFullscreen(this.core.activeContainer.el) &&
|
|
181
|
-
this.
|
|
182
|
-
this.
|
|
213
|
+
this.track &&
|
|
214
|
+
this.track.track.mode &&
|
|
183
215
|
Browser.isiOS) {
|
|
184
216
|
this.$string.hide();
|
|
185
|
-
this.
|
|
217
|
+
this.track.track.mode = 'showing';
|
|
186
218
|
}
|
|
187
219
|
else {
|
|
188
220
|
this.$string.show();
|
|
189
221
|
}
|
|
190
222
|
}
|
|
191
223
|
shouldRender() {
|
|
192
|
-
return
|
|
224
|
+
return this.tracks.length > 0;
|
|
193
225
|
}
|
|
194
226
|
resizeFont() {
|
|
195
|
-
if (!this.core.activeContainer) {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
227
|
if (!this.$string) {
|
|
199
228
|
return;
|
|
200
229
|
}
|
|
@@ -211,133 +240,74 @@ export class Subtitles extends UICorePlugin {
|
|
|
211
240
|
if (!this.shouldRender()) {
|
|
212
241
|
return this;
|
|
213
242
|
}
|
|
214
|
-
trace(`${T} render`, {
|
|
215
|
-
tracks: this.tracks?.length,
|
|
216
|
-
track: this.track?.language,
|
|
217
|
-
});
|
|
218
243
|
const mediaControl = this.core.getPlugin('media_control');
|
|
219
|
-
assert(mediaControl, 'media_control plugin is required');
|
|
220
244
|
this.$el.html(Subtitles.template({ tracks: this.tracks }));
|
|
221
245
|
this.core.activeContainer.$el.find('.subtitle-string').remove();
|
|
222
246
|
this.$string = $(Subtitles.templateString());
|
|
223
247
|
this.resizeFont();
|
|
224
|
-
this.core.activeContainer.$el.append(this.$string
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
ss.append(this.el);
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
mediaControl.getRightPanel().append(this.el);
|
|
231
|
-
}
|
|
232
|
-
this.updateCurrentLevel(this.track);
|
|
233
|
-
this.highlightCurrentSubtitles();
|
|
234
|
-
this.applyPreselectedSubtitles();
|
|
248
|
+
this.core.activeContainer.$el.append(this.$string);
|
|
249
|
+
mediaControl.putElement('subtitlesSelector', this.$el);
|
|
250
|
+
this.updateSelection();
|
|
235
251
|
this.renderIcon();
|
|
236
252
|
return this;
|
|
237
253
|
}
|
|
238
|
-
|
|
239
|
-
this.tracks
|
|
240
|
-
this.render();
|
|
254
|
+
findById(id) {
|
|
255
|
+
return this.tracks.find((track) => track.id === id) ?? null;
|
|
241
256
|
}
|
|
242
|
-
|
|
243
|
-
if (this.tracks) {
|
|
244
|
-
for (const track of this.tracks) {
|
|
245
|
-
if (track.language === id) {
|
|
246
|
-
return track; // TODO TrackInfo?
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
selectLevel(id) {
|
|
257
|
+
selectItem(item) {
|
|
252
258
|
this.clearSubtitleText();
|
|
253
|
-
this.track =
|
|
254
|
-
this.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (id) {
|
|
263
|
-
localStorage.setItem(LOCAL_STORAGE_SUBTITLES_ID, id);
|
|
264
|
-
this.selectLevel(id);
|
|
265
|
-
}
|
|
259
|
+
this.track = item;
|
|
260
|
+
this.hideMenu();
|
|
261
|
+
this.updateSelection();
|
|
262
|
+
}
|
|
263
|
+
onItemSelect(event) {
|
|
264
|
+
const id = event.target.dataset.subtitlesSelect ?? '-1';
|
|
265
|
+
trace(`${T} onItemSelect`, { id });
|
|
266
|
+
localStorage.setItem(LOCAL_STORAGE_SUBTITLES_ID, id);
|
|
267
|
+
this.selectItem(this.findById(Number(id)));
|
|
266
268
|
return false;
|
|
267
269
|
}
|
|
268
270
|
applyPreselectedSubtitles() {
|
|
269
271
|
if (!this.isPreselectedApplied) {
|
|
270
272
|
this.isPreselectedApplied = true;
|
|
273
|
+
if (!this.preselectedLanguage) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
271
276
|
setTimeout(() => {
|
|
272
|
-
this.
|
|
273
|
-
}, 300);
|
|
277
|
+
this.selectItem(this.tracks.find((t) => t.track.language === this.preselectedLanguage) ?? null);
|
|
278
|
+
}, 300); // TODO why delay?
|
|
274
279
|
}
|
|
275
280
|
}
|
|
276
|
-
|
|
277
|
-
trace(`${T} onShowLevelSelectMenu`);
|
|
278
|
-
this.toggleContextMenu();
|
|
279
|
-
}
|
|
280
|
-
hideSelectLevelMenu() {
|
|
281
|
+
hideMenu() {
|
|
281
282
|
;
|
|
282
283
|
this.$('[data-subtitles] ul').hide();
|
|
283
284
|
}
|
|
284
|
-
|
|
285
|
+
toggleMenu() {
|
|
286
|
+
;
|
|
285
287
|
this.$('[data-subtitles] ul').toggle();
|
|
286
288
|
}
|
|
287
|
-
|
|
288
|
-
return this.$(
|
|
289
|
+
itemElement(id) {
|
|
290
|
+
return this.$(`ul li a[data-subtitles-select="${id}"]`).parent();
|
|
289
291
|
}
|
|
290
|
-
|
|
291
|
-
return this.$('[data-subtitles]
|
|
292
|
-
}
|
|
293
|
-
startLevelSwitch() {
|
|
294
|
-
this.buttonElement().addClass('changing');
|
|
295
|
-
}
|
|
296
|
-
stopLevelSwitch() {
|
|
297
|
-
this.buttonElement().removeClass('changing');
|
|
292
|
+
allItemElements() {
|
|
293
|
+
return this.$('[data-subtitles] li');
|
|
298
294
|
}
|
|
299
295
|
selectSubtitles() {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (cues && cues.length) {
|
|
312
|
-
for (const cue of cues) {
|
|
313
|
-
if (currentTime >= cue.startTime && currentTime <= cue.endTime) {
|
|
314
|
-
subtitleText +=
|
|
315
|
-
cue.getCueAsHTML().textContent + '\n';
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
this.setSubtitleText(subtitleText);
|
|
320
|
-
track.oncuechange = (e) => {
|
|
321
|
-
try {
|
|
322
|
-
if (track.activeCues?.length) {
|
|
323
|
-
const html = track.activeCues[0].getCueAsHTML();
|
|
324
|
-
this.setSubtitleText(html);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
this.clearSubtitleText();
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
// console.error(error);
|
|
332
|
-
reportError(error);
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
continue;
|
|
296
|
+
const trackId = this.track ? this.track.id : -1;
|
|
297
|
+
this.core.activePlayback.closedCaptionsTrackId = trackId;
|
|
298
|
+
}
|
|
299
|
+
getSubtitleText(track) {
|
|
300
|
+
const currentTime = this.core.activePlayback?.getCurrentTime() ?? 0;
|
|
301
|
+
const cues = track.cues;
|
|
302
|
+
const lines = [];
|
|
303
|
+
if (cues && cues.length) {
|
|
304
|
+
for (const cue of cues) {
|
|
305
|
+
if (currentTime >= cue.startTime && currentTime <= cue.endTime) {
|
|
306
|
+
lines.push(cue.getCueAsHTML().textContent);
|
|
336
307
|
}
|
|
337
|
-
this.tracks[i].oncuechange = null;
|
|
338
|
-
this.tracks[i].mode = 'hidden';
|
|
339
308
|
}
|
|
340
309
|
}
|
|
310
|
+
return lines.join('\n');
|
|
341
311
|
}
|
|
342
312
|
setSubtitleText(text) {
|
|
343
313
|
this.$string.find('p').html(text);
|
|
@@ -345,9 +315,8 @@ export class Subtitles extends UICorePlugin {
|
|
|
345
315
|
clearSubtitleText() {
|
|
346
316
|
this.setSubtitleText('');
|
|
347
317
|
}
|
|
348
|
-
|
|
349
|
-
this.
|
|
350
|
-
if (track.language === 'off') {
|
|
318
|
+
updateSelection() {
|
|
319
|
+
if (!this.track) {
|
|
351
320
|
this.hide();
|
|
352
321
|
}
|
|
353
322
|
else {
|
|
@@ -357,20 +326,21 @@ export class Subtitles extends UICorePlugin {
|
|
|
357
326
|
this.highlightCurrentSubtitles();
|
|
358
327
|
}
|
|
359
328
|
highlightCurrentSubtitles() {
|
|
360
|
-
this.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
329
|
+
this.allItemElements()
|
|
330
|
+
.removeClass('current')
|
|
331
|
+
.find('a')
|
|
332
|
+
.removeClass('gcore-skin-active');
|
|
333
|
+
trace(`${T} highlightCurrentSubtitles`, {
|
|
334
|
+
track: this.track?.id,
|
|
335
|
+
});
|
|
336
|
+
const currentLevelElement = this.itemElement(this.track ? this.track.id : -1);
|
|
337
|
+
currentLevelElement
|
|
338
|
+
.addClass('current')
|
|
339
|
+
.find('a')
|
|
340
|
+
.addClass('gcore-skin-active');
|
|
367
341
|
}
|
|
368
342
|
renderIcon() {
|
|
369
343
|
const icon = this.isShowing ? subtitlesOnIcon : subtitlesOffIcon;
|
|
370
|
-
this.
|
|
371
|
-
.getPlugin('media_control')
|
|
372
|
-
.getElement('subtitlesSelector')
|
|
373
|
-
?.find('span.subtitle-text')
|
|
374
|
-
.html(icon);
|
|
344
|
+
this.$el.find('span.subtitle-text').html(icon);
|
|
375
345
|
}
|
|
376
346
|
}
|
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;AAElC;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,MAAM;IAErC,SAAS,CAAC,OAAO,EAAE,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG;gBAFjB,OAAO,EAAE,GAAG,EACb,IAAI,EAAE,GAAG,EACR,WAAW,CAAC,EAAE,GAAG,YAAA;IAK7B,IAAI,IAAI,WAEP;IAED,OAAO;IAEP,IAAI;IAEJ,KAAK;IAEL,IAAI;IAEJ,OAAO;IAEP,IAAI;IAEJ,cAAc;IAEd,WAAW;IAIX,QAAQ;IAER,OAAO;IAEP,eAAe;IAIf,kBAAkB;IAIlB,cAAc;IAId,qBAAqB;IAIrB,IAAI;IAEJ,MAAM;IAEN,MAAM;IAEN,SAAS;IAET,eAAe;IAIf,WAAW;IAIX,QAAQ;IAIR,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGtC;AAED,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAA2B;;;;;;;;;;;;;;;;EAqBvC;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;mBA2C7B,MAAM,WAAW,GAAG,EAAE;EAIxC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,GAA0B;;;;;;;;;;EAcvE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,
|
|
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;AAElC;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,MAAM;IAErC,SAAS,CAAC,OAAO,EAAE,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG;gBAFjB,OAAO,EAAE,GAAG,EACb,IAAI,EAAE,GAAG,EACR,WAAW,CAAC,EAAE,GAAG,YAAA;IAK7B,IAAI,IAAI,WAEP;IAED,OAAO;IAEP,IAAI;IAEJ,KAAK;IAEL,IAAI;IAEJ,OAAO;IAEP,IAAI;IAEJ,cAAc;IAEd,WAAW;IAIX,QAAQ;IAER,OAAO;IAEP,eAAe;IAIf,kBAAkB;IAIlB,cAAc;IAId,qBAAqB;IAIrB,IAAI;IAEJ,MAAM;IAEN,MAAM;IAEN,SAAS;IAET,eAAe;IAIf,WAAW;IAIX,QAAQ;IAIR,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGtC;AAED,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAA2B;;;;;;;;;;;;;;;;EAqBvC;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;mBA2C7B,MAAM,WAAW,GAAG,EAAE;EAIxC;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,GAA0B;;;;;;;;;;EAcvE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAqB/C"}
|
package/lib/testUtils.js
CHANGED
|
@@ -167,6 +167,8 @@ export function createMockMediaControl(core) {
|
|
|
167
167
|
// @ts-ignore
|
|
168
168
|
mediaControl.getElement = vi.fn().mockImplementation((name) => elements[name]);
|
|
169
169
|
// @ts-ignore
|
|
170
|
+
mediaControl.putElement = vi.fn();
|
|
171
|
+
// @ts-ignore
|
|
170
172
|
mediaControl.getLeftPanel = vi.fn().mockImplementation(() => mediaControl.$el.find('.media-control-left-panel'));
|
|
171
173
|
// @ts-ignore
|
|
172
174
|
mediaControl.getRightPanel = vi.fn().mockImplementation(() => mediaControl.$el.find('.media-control-right-panel'));
|
package/package.json
CHANGED
|
@@ -5,9 +5,12 @@ import { PlaybackErrorCode } from '../playback.types.js'
|
|
|
5
5
|
/**
|
|
6
6
|
* This class adds common behaviors to all playback modules.
|
|
7
7
|
* @internal
|
|
8
|
-
* TODO use custom HTML5Video playback with this layer applied
|
|
9
8
|
*/
|
|
10
9
|
export class BasePlayback extends HTML5Video {
|
|
10
|
+
get isHTML5Video() {
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
|
|
11
14
|
createError(errorData: any, options?: ErrorOptions) {
|
|
12
15
|
const i18n =
|
|
13
16
|
this.i18n ||
|
|
@@ -5,6 +5,7 @@ import { PlaybackErrorCode } from '../playback.types.js'
|
|
|
5
5
|
import { BasePlayback } from './BasePlayback.js'
|
|
6
6
|
import { trace } from '@gcorevideo/utils'
|
|
7
7
|
import { TimerId } from '../utils/types.js'
|
|
8
|
+
import { AudioTrack } from '@clappr/core/types/base/playback/playback.js'
|
|
8
9
|
|
|
9
10
|
const T = 'playback.html5_video'
|
|
10
11
|
|
|
@@ -32,10 +33,6 @@ export default class HTML5Video extends BasePlayback {
|
|
|
32
33
|
!errorData.UI &&
|
|
33
34
|
errorData.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
|
|
34
35
|
) {
|
|
35
|
-
// errorData.UI = {
|
|
36
|
-
// title: i18n.t('no_broadcast'),
|
|
37
|
-
// message: errorData.message,
|
|
38
|
-
// }
|
|
39
36
|
errorData.code = PlaybackErrorCode.MediaSourceUnavailable
|
|
40
37
|
}
|
|
41
38
|
return super.createError(errorData, { ...options, useCodePrefix: false })
|
|
@@ -96,4 +93,60 @@ export default class HTML5Video extends BasePlayback {
|
|
|
96
93
|
this.stallTimerId = null
|
|
97
94
|
}
|
|
98
95
|
}
|
|
96
|
+
|
|
97
|
+
get audioTracks(): AudioTrack[] {
|
|
98
|
+
const tracks = (this.el as HTMLMediaElement).audioTracks
|
|
99
|
+
const supported = !!tracks
|
|
100
|
+
trace(`${T} get audioTracks`, { supported })
|
|
101
|
+
const retval: AudioTrack[] = []
|
|
102
|
+
if (supported) {
|
|
103
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
104
|
+
const track = tracks[i]
|
|
105
|
+
retval.push({
|
|
106
|
+
id: track.id,
|
|
107
|
+
label: track.label,
|
|
108
|
+
language: track.language,
|
|
109
|
+
kind: track.kind as 'main' | 'description', // TODO check
|
|
110
|
+
} as AudioTrack)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return retval
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// @ts-expect-error
|
|
117
|
+
get currentAudioTrack() {
|
|
118
|
+
const tracks = (this.el as HTMLMediaElement).audioTracks
|
|
119
|
+
const supported = !!tracks
|
|
120
|
+
trace(`${T} get currentAudioTrack`, {
|
|
121
|
+
supported,
|
|
122
|
+
})
|
|
123
|
+
if (supported) {
|
|
124
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
125
|
+
const track = tracks[i]
|
|
126
|
+
if (track.enabled) {
|
|
127
|
+
return {
|
|
128
|
+
id: track.id,
|
|
129
|
+
label: track.label,
|
|
130
|
+
language: track.language,
|
|
131
|
+
kind: track.kind,
|
|
132
|
+
} as AudioTrack
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
switchAudioTrack(id: string) {
|
|
140
|
+
const tracks = (this.el as HTMLMediaElement).audioTracks
|
|
141
|
+
const supported = !!tracks
|
|
142
|
+
trace(`${T} switchAudioTrack`, {
|
|
143
|
+
supported,
|
|
144
|
+
})
|
|
145
|
+
if (supported) {
|
|
146
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
147
|
+
const track = tracks[i]
|
|
148
|
+
track.enabled = track.id === id
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
99
152
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// license
|
|
1
|
+
// This code is derived on works by Globo.com.
|
|
2
|
+
// This code is distributed under the terms of the Apache License 2.0.
|
|
3
|
+
// Original code's license can be found on
|
|
4
|
+
// https://github.com/clappr/clappr/blob/8752995ea439321ac7ca3cd35e8c64de7a3c3d17/LICENSE
|
|
4
5
|
|
|
5
6
|
import { Events, Log, Playback, PlayerError, Utils, $ } from '@clappr/core'
|
|
6
7
|
import { trace } from '@gcorevideo/utils'
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
import { isDashSource } from '../../utils/mediaSources.js'
|
|
26
27
|
import { BasePlayback } from '../BasePlayback.js'
|
|
27
28
|
import { PlaybackEvents } from '../types.js'
|
|
29
|
+
import { AudioTrack } from '@clappr/core/types/base/playback/playback.js'
|
|
28
30
|
|
|
29
31
|
const AUTO = -1
|
|
30
32
|
|
|
@@ -45,7 +47,6 @@ type LocalTimeCorrelation = {
|
|
|
45
47
|
|
|
46
48
|
const T = 'playback.dash'
|
|
47
49
|
|
|
48
|
-
// @ts-expect-error
|
|
49
50
|
export default class DashPlayback extends BasePlayback {
|
|
50
51
|
_levels: QualityLevel[] | null = null
|
|
51
52
|
|
|
@@ -271,6 +272,8 @@ export default class DashPlayback extends BasePlayback {
|
|
|
271
272
|
const newLevel = this.getLevel(evt.newQuality)
|
|
272
273
|
this.onLevelSwitch(newLevel)
|
|
273
274
|
})
|
|
275
|
+
|
|
276
|
+
this.checkAudioTracks()
|
|
274
277
|
})
|
|
275
278
|
|
|
276
279
|
this._dash.on(
|
|
@@ -300,8 +303,20 @@ export default class DashPlayback extends BasePlayback {
|
|
|
300
303
|
},
|
|
301
304
|
)
|
|
302
305
|
|
|
303
|
-
this._dash.on(
|
|
304
|
-
|
|
306
|
+
this._dash.on(
|
|
307
|
+
DASHJS.MediaPlayer.events.PLAYBACK_RATE_CHANGED,
|
|
308
|
+
(e: DASHJS.PlaybackRateChangedEvent) => {
|
|
309
|
+
this.trigger(PlaybackEvents.PLAYBACK_RATE_CHANGED, e.playbackRate)
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
this._dash.on(DASHJS.MediaPlayer.events.TRACK_CHANGE_RENDERED, (e: any) => {
|
|
314
|
+
if ((e as DASHJS.TrackChangeRenderedEvent).mediaType === 'audio') {
|
|
315
|
+
this.trigger(
|
|
316
|
+
Events.PLAYBACK_AUDIO_CHANGED,
|
|
317
|
+
toClapprTrack(e.newMediaInfo),
|
|
318
|
+
)
|
|
319
|
+
}
|
|
305
320
|
})
|
|
306
321
|
}
|
|
307
322
|
|
|
@@ -642,6 +657,40 @@ export default class DashPlayback extends BasePlayback {
|
|
|
642
657
|
setPlaybackRate(rate: number) {
|
|
643
658
|
this._dash?.setPlaybackRate(rate)
|
|
644
659
|
}
|
|
660
|
+
|
|
661
|
+
get audioTracks(): AudioTrack[] {
|
|
662
|
+
assert.ok(this._dash, 'DASH.js MediaPlayer is not initialized')
|
|
663
|
+
const tracks = this._dash.getTracksFor('audio')
|
|
664
|
+
trace(`${T} get audioTracks`, { tracks })
|
|
665
|
+
return tracks.map(toClapprTrack)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// @ts-expect-error
|
|
669
|
+
get currentAudioTrack(): AudioTrack | null {
|
|
670
|
+
trace(`${T} get currentAudioTrack`)
|
|
671
|
+
assert.ok(this._dash, 'DASH.js MediaPlayer is not initialized')
|
|
672
|
+
const t = this._dash.getCurrentTrackFor('audio')
|
|
673
|
+
if (!t) {
|
|
674
|
+
return null
|
|
675
|
+
}
|
|
676
|
+
return toClapprTrack(t)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
switchAudioTrack(id: string): void {
|
|
680
|
+
assert.ok(this._dash, 'DASH.js MediaPlayer is not initialized')
|
|
681
|
+
const tracks = this._dash.getTracksFor('audio')
|
|
682
|
+
const track = tracks.find((t) => t.id === id)
|
|
683
|
+
assert.ok(track, 'Invalid audio track ID')
|
|
684
|
+
this._dash.setCurrentTrack(track)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
private checkAudioTracks() {
|
|
688
|
+
// @ts-ignore
|
|
689
|
+
const tracks = this._dash.getTracksFor('audio')
|
|
690
|
+
if (tracks.length) {
|
|
691
|
+
this.trigger(Events.PLAYBACK_AUDIO_AVAILABLE, tracks.map(toClapprTrack))
|
|
692
|
+
}
|
|
693
|
+
}
|
|
645
694
|
}
|
|
646
695
|
|
|
647
696
|
DashPlayback.canPlay = function (resource, mimeType) {
|
|
@@ -656,3 +705,12 @@ DashPlayback.canPlay = function (resource, mimeType) {
|
|
|
656
705
|
const ctor = ms || mms || wms
|
|
657
706
|
return typeof ctor === 'function'
|
|
658
707
|
}
|
|
708
|
+
|
|
709
|
+
function toClapprTrack(t: DASHJS.MediaInfo): AudioTrack {
|
|
710
|
+
return {
|
|
711
|
+
id: t.id,
|
|
712
|
+
kind: t.roles && t.roles?.length > 0 ? t.roles[0] : 'main', // TODO
|
|
713
|
+
label: t.labels.map((l) => l.text).join(' '), // TODO
|
|
714
|
+
language: t.lang,
|
|
715
|
+
} as AudioTrack
|
|
716
|
+
}
|