@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.
Files changed (89) hide show
  1. package/assets/audio-selector/style.scss +48 -82
  2. package/assets/audio-selector/track-selector.ejs +3 -3
  3. package/assets/bottom-gear/bottomgear.ejs +10 -12
  4. package/assets/bottom-gear/gear-sub-menu.scss +0 -15
  5. package/assets/bottom-gear/gear.scss +3 -32
  6. package/assets/media-control/media-control.ejs +5 -25
  7. package/assets/media-control/media-control.scss +114 -34
  8. package/assets/media-control/width370.scss +35 -109
  9. package/assets/picture-in-picture/button.ejs +1 -1
  10. package/assets/picture-in-picture/button.scss +5 -4
  11. package/assets/subtitles/combobox.ejs +7 -9
  12. package/assets/subtitles/style.scss +8 -15
  13. package/dist/core.js +151 -23
  14. package/dist/index.css +897 -1000
  15. package/dist/index.js +416 -438
  16. package/dist/player.d.ts +19 -16
  17. package/dist/plugins/index.css +1454 -1557
  18. package/dist/plugins/index.js +826 -23550
  19. package/docs/api/player.audioselector.md +4 -59
  20. package/docs/api/player.md +1 -1
  21. package/docs/api/player.mediacontrol.getelement.md +5 -0
  22. package/docs/api/player.mediacontrol.md +14 -0
  23. package/docs/api/{player.audioselector.updatecurrenttrack.md → player.mediacontrol.putelement.md} +7 -7
  24. package/docs/api/player.mediacontrolelement.md +1 -1
  25. package/docs/api/{player.audioselector.starttrackswitch.md → player.pictureinpicture.attributes.md} +5 -7
  26. package/docs/api/player.pictureinpicture.md +45 -0
  27. package/lib/playback/BasePlayback.d.ts +1 -1
  28. package/lib/playback/BasePlayback.d.ts.map +1 -1
  29. package/lib/playback/BasePlayback.js +3 -1
  30. package/lib/playback/HTML5Video.d.ts +4 -0
  31. package/lib/playback/HTML5Video.d.ts.map +1 -1
  32. package/lib/playback/HTML5Video.js +53 -4
  33. package/lib/playback/dash-playback/DashPlayback.d.ts +5 -0
  34. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  35. package/lib/playback/dash-playback/DashPlayback.js +48 -4
  36. package/lib/playback/hls-playback/HlsPlayback.d.ts +31 -25
  37. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  38. package/lib/playback/hls-playback/HlsPlayback.js +47 -14
  39. package/lib/playback.types.d.ts +5 -0
  40. package/lib/playback.types.d.ts.map +1 -1
  41. package/lib/plugins/audio-selector/AudioSelector.d.ts +12 -11
  42. package/lib/plugins/audio-selector/AudioSelector.d.ts.map +1 -1
  43. package/lib/plugins/audio-selector/AudioSelector.js +65 -185
  44. package/lib/plugins/bottom-gear/BottomGear.d.ts +2 -2
  45. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  46. package/lib/plugins/bottom-gear/BottomGear.js +12 -10
  47. package/lib/plugins/level-selector/LevelSelector.js +1 -1
  48. package/lib/plugins/media-control/MediaControl.d.ts +3 -4
  49. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  50. package/lib/plugins/media-control/MediaControl.js +23 -13
  51. package/lib/plugins/picture-in-picture/PictureInPicture.d.ts +3 -0
  52. package/lib/plugins/picture-in-picture/PictureInPicture.d.ts.map +1 -1
  53. package/lib/plugins/picture-in-picture/PictureInPicture.js +6 -1
  54. package/lib/plugins/playback-rate/PlaybackRate.d.ts +1 -0
  55. package/lib/plugins/playback-rate/PlaybackRate.d.ts.map +1 -1
  56. package/lib/plugins/playback-rate/PlaybackRate.js +1 -0
  57. package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
  58. package/lib/plugins/source-controller/SourceController.js +0 -1
  59. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +0 -2
  60. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
  61. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +1 -18
  62. package/lib/plugins/subtitles/Subtitles.d.ts +21 -19
  63. package/lib/plugins/subtitles/Subtitles.d.ts.map +1 -1
  64. package/lib/plugins/subtitles/Subtitles.js +121 -151
  65. package/lib/testUtils.d.ts.map +1 -1
  66. package/lib/testUtils.js +2 -0
  67. package/package.json +1 -1
  68. package/src/playback/BasePlayback.ts +4 -1
  69. package/src/playback/HTML5Video.ts +57 -4
  70. package/src/playback/dash-playback/DashPlayback.ts +64 -6
  71. package/src/playback/hls-playback/HlsPlayback.ts +82 -40
  72. package/src/playback.types.ts +6 -0
  73. package/src/plugins/audio-selector/AudioSelector.ts +84 -278
  74. package/src/plugins/bottom-gear/BottomGear.ts +14 -11
  75. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +1 -3
  76. package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +14 -37
  77. package/src/plugins/level-selector/LevelSelector.ts +1 -1
  78. package/src/plugins/media-control/MediaControl.ts +54 -32
  79. package/src/plugins/picture-in-picture/PictureInPicture.ts +7 -1
  80. package/src/plugins/playback-rate/PlaybackRate.ts +1 -0
  81. package/src/plugins/source-controller/SourceController.ts +0 -1
  82. package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +1 -20
  83. package/src/plugins/subtitles/Subtitles.ts +144 -179
  84. package/src/testUtils.ts +2 -0
  85. package/src/typings/globals.d.ts +19 -0
  86. package/temp/player.api.json +102 -143
  87. package/tsconfig.tsbuildinfo +1 -1
  88. package/assets/media-control/plugins.scss +0 -94
  89. package/docs/api/player.audioselector.highlightcurrenttrack.md +0 -18
@@ -1,4 +1,4 @@
1
- import { Events, UICorePlugin, Browser, template, $, } from '@clappr/core';
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 = { ...NO_TRACK };
47
- tracks = null;
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: this.name,
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]': 'onLevelSelect',
84
- 'click [data-subtitles-button]': 'onShowLevelSelectMenu',
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 ?? 'off';
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.hideSelectLevelMenu);
98
+ this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, this.hideMenu);
100
99
  }
101
- bindPlaybackEvents() {
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
- getTracks() {
120
- if (this.core.activePlayback) {
121
- try {
122
- const tracks = this.core.activePlayback.el
123
- .textTracks;
124
- if (tracks.length > 0) {
125
- this.setTracks(tracks);
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
- catch (error) {
129
- reportError(error);
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.currentLevel &&
147
- this.currentLevel.mode &&
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.currentLevel &&
182
- this.currentLevel.mode &&
213
+ this.track &&
214
+ this.track.track.mode &&
183
215
  Browser.isiOS) {
184
216
  this.$string.hide();
185
- this.currentLevel.mode = 'showing';
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 !!(this.tracks && this.tracks.length > 0);
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[0]);
225
- const ss = mediaControl.getElement('subtitlesSelector');
226
- if (ss && ss.length > 0) {
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
- setTracks(tracks) {
239
- this.tracks = tracks;
240
- this.render();
254
+ findById(id) {
255
+ return this.tracks.find((track) => track.id === id) ?? null;
241
256
  }
242
- findLevelBy(id) {
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 = this.findLevelBy(id) || { ...NO_TRACK };
254
- this.hideSelectLevelMenu();
255
- if (!this.track) {
256
- this.track = { language: 'off' };
257
- }
258
- this.updateCurrentLevel(this.track);
259
- }
260
- onLevelSelect(event) {
261
- const id = event.target.dataset.subtitlesSelect;
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.selectLevel(this.preselectedLanguage);
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
- onShowLevelSelectMenu() {
277
- trace(`${T} onShowLevelSelectMenu`);
278
- this.toggleContextMenu();
279
- }
280
- hideSelectLevelMenu() {
281
+ hideMenu() {
281
282
  ;
282
283
  this.$('[data-subtitles] ul').hide();
283
284
  }
284
- toggleContextMenu() {
285
+ toggleMenu() {
286
+ ;
285
287
  this.$('[data-subtitles] ul').toggle();
286
288
  }
287
- buttonElement() {
288
- return this.$('[data-subtitles] button');
289
+ itemElement(id) {
290
+ return this.$(`ul li a[data-subtitles-select="${id}"]`).parent();
289
291
  }
290
- levelElement(id) {
291
- return this.$('[data-subtitles] ul a' + (id ? '[data-subtitles-select="' + id + '"]' : '')).parent();
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
- if (!this.currentLevel) {
301
- return;
302
- }
303
- if (this.tracks) {
304
- for (let i = 0; i < this.tracks.length; i++) {
305
- const track = this.tracks[i];
306
- if (track.language === this.currentLevel.language) {
307
- track.mode = 'showing';
308
- const currentTime = this.core.activePlayback?.getCurrentTime() ?? 0;
309
- const cues = track.cues;
310
- let subtitleText = '';
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
- updateCurrentLevel(track) {
349
- this.currentLevel = track;
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.levelElement().removeClass('current');
361
- this.levelElement().find('a').removeClass('gcore-skin-active');
362
- if (this.currentLevel) {
363
- const currentLevelElement = this.levelElement(this.currentLevel.language);
364
- currentLevelElement.addClass('current');
365
- currentLevelElement.find('a').addClass('gcore-skin-active');
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.core
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
  }
@@ -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,gBAmB/C"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcorevideo/player",
3
- "version": "2.20.22",
3
+ "version": "2.21.3",
4
4
  "description": "Gcore JavaScript video player",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -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
- // Copyright 2014 Globo.com Player authors. All rights reserved.
2
- // Use of this source code is governed by a BSD-style
3
- // license that can be found in the LICENSE file.
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(DASHJS.MediaPlayer.events.PLAYBACK_RATE_CHANGED, (e: DASHJS.PlaybackRateChangedEvent) => {
304
- this.trigger(PlaybackEvents.PLAYBACK_RATE_CHANGED, e.playbackRate)
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
+ }