@gcorevideo/player 2.28.30 → 2.28.36

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 (37) hide show
  1. package/README.md +22 -1
  2. package/assets/{subtitles → cc}/style.scss +5 -0
  3. package/dist/core.js +17 -23
  4. package/dist/index.css +241 -237
  5. package/dist/index.embed.js +79 -59
  6. package/dist/index.js +137 -119
  7. package/docs/api/player.closedcaptionspluginsettings.md +1 -0
  8. package/docs/api/player.md +9 -0
  9. package/docs/api/player.mediacontrol.md +16 -0
  10. package/docs/api/player.thumbnails.md +1 -1
  11. package/lib/Player.d.ts.map +1 -1
  12. package/lib/playback/BasePlayback.d.ts +1 -0
  13. package/lib/playback/BasePlayback.d.ts.map +1 -1
  14. package/lib/playback/BasePlayback.js +3 -0
  15. package/lib/playback/dash-playback/DashPlayback.d.ts +1 -0
  16. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  17. package/lib/playback/dash-playback/DashPlayback.js +9 -22
  18. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  19. package/lib/playback/hls-playback/HlsPlayback.js +4 -0
  20. package/lib/plugins/subtitles/ClosedCaptions.d.ts +7 -3
  21. package/lib/plugins/subtitles/ClosedCaptions.d.ts.map +1 -1
  22. package/lib/plugins/subtitles/ClosedCaptions.js +66 -42
  23. package/lib/testUtils.d.ts +1 -0
  24. package/lib/testUtils.d.ts.map +1 -1
  25. package/lib/testUtils.js +3 -0
  26. package/package.json +4 -1
  27. package/src/Player.ts +12 -12
  28. package/src/playback/BasePlayback.ts +4 -0
  29. package/src/playback/dash-playback/DashPlayback.ts +10 -27
  30. package/src/playback/hls-playback/HlsPlayback.ts +4 -0
  31. package/src/plugins/subtitles/ClosedCaptions.ts +75 -47
  32. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +277 -29
  33. package/src/plugins/subtitles/__tests__/__snapshots__/ClosedCaptions.test.ts.snap +3 -3
  34. package/src/testUtils.ts +3 -0
  35. package/tsconfig.tsbuildinfo +1 -1
  36. /package/assets/{subtitles → cc}/combobox.ejs +0 -0
  37. /package/assets/{subtitles → cc}/string.ejs +0 -0
@@ -61,6 +61,8 @@ export default class DashPlayback extends BasePlayback {
61
61
 
62
62
  _currentLevel: number = AUTO
63
63
 
64
+ _currentTextTrackId: number = -1
65
+
64
66
  // true when the actual duration is longer than hlsjs's live sync point
65
67
  // when this is false playableRegionDuration will be the actual duration
66
68
  // when this is true playableRegionDuration will exclude the time after the sync point
@@ -248,7 +250,11 @@ export default class DashPlayback extends BasePlayback {
248
250
  streaming: {
249
251
  text: {
250
252
  defaultEnabled: false,
251
- dispatchForManualRendering: true,
253
+ // NOTE: dispatchForManualRendering is not correctly implemented in DASH.js;
254
+ // it does not work when there are multiple text tracks.
255
+ // CUE_ENTER and CUE_EXIT events might be dispatched additionally
256
+ // for a track, other than the currently active one.
257
+ // dispatchForManualRendering: true, // TODO only when useNativeSubtitles is not true?
252
258
  },
253
259
  },
254
260
  },
@@ -319,29 +325,6 @@ export default class DashPlayback extends BasePlayback {
319
325
  this.trigger(PlaybackEvents.PLAYBACK_RATE_CHANGED, e.playbackRate)
320
326
  },
321
327
  )
322
-
323
- this._dash.on(MediaPlayer.events.TRACK_CHANGE_RENDERED, (e: any) => {
324
- if ((e as TrackChangeRenderedEvent).mediaType === 'audio') {
325
- this.trigger(
326
- Events.PLAYBACK_AUDIO_CHANGED,
327
- toClapprTrack(e.newMediaInfo),
328
- )
329
- }
330
- })
331
-
332
- this._dash.on(MediaPlayer.events.CUE_ENTER, (e: CueEnterEvent) => {
333
- this.oncueenter?.({
334
- end: e.end,
335
- id: e.id,
336
- start: e.start,
337
- text: e.text,
338
- })
339
- })
340
- this._dash.on(MediaPlayer.events.CUE_EXIT, (e: CueExitEvent) => {
341
- this.oncueexit?.({
342
- id: e.id,
343
- })
344
- })
345
328
  }
346
329
 
347
330
  render() {
@@ -725,6 +708,7 @@ export default class DashPlayback extends BasePlayback {
725
708
  }
726
709
 
727
710
  setTextTrack(id: number) {
711
+ this._currentTextTrackId = id
728
712
  this._dash?.setTextTrack(id)
729
713
  }
730
714
 
@@ -732,8 +716,7 @@ export default class DashPlayback extends BasePlayback {
732
716
  * @override
733
717
  */
734
718
  get closedCaptionsTracks() {
735
- const tt = this.getTextTracks()
736
- return tt;
719
+ return this.getTextTracks()
737
720
  }
738
721
 
739
722
  private getTextTracks() {
@@ -744,7 +727,7 @@ export default class DashPlayback extends BasePlayback {
744
727
  id: index,
745
728
  label: getTextTrackLabel(t) || "",
746
729
  language: t.lang,
747
- mode: "hidden",
730
+ mode: this._currentTextTrackId === index ? "showing" : "hidden",
748
731
  },
749
732
  })) || []
750
733
  }
@@ -1135,7 +1135,11 @@ export default class HlsPlayback extends BasePlayback {
1135
1135
  }
1136
1136
 
1137
1137
  setTextTrack(id: number) {
1138
+ if (id === this._hls!.subtitleTrack) {
1139
+ return
1140
+ }
1138
1141
  this._hls!.subtitleTrack = id
1142
+ this.cues = []
1139
1143
  }
1140
1144
 
1141
1145
  /**
@@ -1,15 +1,15 @@
1
1
  import { Events, UICorePlugin, Browser, template, $ } from '@clappr/core'
2
- import { reportError, trace } from '@gcorevideo/utils'
2
+ import { reportError } from '@gcorevideo/utils'
3
3
  import assert from 'assert'
4
4
 
5
5
  import { CLAPPR_VERSION } from '../../build.js'
6
6
  import type { TextTrackItem } from '../../internal.types.js'
7
7
 
8
- import '../../../assets/subtitles/style.scss'
8
+ import '../../../assets/cc/style.scss'
9
9
  import subtitlesOffIcon from '../../../assets/icons/new/subtitles-off.svg'
10
10
  import subtitlesOnIcon from '../../../assets/icons/new/subtitles-on.svg'
11
- import comboboxHTML from '../../../assets/subtitles/combobox.ejs'
12
- import stringHTML from '../../../assets/subtitles/string.ejs'
11
+ import comboboxHTML from '../../../assets/cc/combobox.ejs'
12
+ import stringHTML from '../../../assets/cc/string.ejs'
13
13
 
14
14
  import { isFullscreen } from '../utils/fullscreen.js'
15
15
  import type { ZeptoResult } from '../../types.js'
@@ -19,9 +19,10 @@ import { VTTCueInfo } from '../../playback.types.js'
19
19
 
20
20
  const VERSION: string = '2.19.14'
21
21
 
22
- const LOCAL_STORAGE_CC_ID = 'gplayer.plugins.cc.selected'
22
+ // TODO review
23
+ // const LOCAL_STORAGE_CC_ID = 'gplayer.plugins.cc.selected'
23
24
 
24
- const T = 'plugins.cc'
25
+ // const T = 'plugins.cc'
25
26
 
26
27
  /**
27
28
  * Configuration options for the {@link ClosedCaptions} plugin.
@@ -35,6 +36,9 @@ export type ClosedCaptionsPluginSettings = {
35
36
 
36
37
  /**
37
38
  * Whether to use builtin subtitles.
39
+ *
40
+ * native: video player element renders the subtitles
41
+ * custom: plugin manages the subtitles rendition
38
42
  */
39
43
  mode?: 'native' | 'custom'
40
44
  }
@@ -75,12 +79,14 @@ export type ClosedCaptionsPluginSettings = {
75
79
  * ```
76
80
  */
77
81
  export class ClosedCaptions extends UICorePlugin {
78
- private isPreselectedApplied = false
82
+ private isSelectedApplied = false
79
83
 
80
84
  private active = false
81
85
 
82
86
  private open = false
83
87
 
88
+ private userSelectedItemId: number = -1
89
+
84
90
  private track: TextTrackItem | null = null
85
91
 
86
92
  private tracks: TextTrackItem[] = []
@@ -162,8 +168,12 @@ export class ClosedCaptions extends UICorePlugin {
162
168
  const mediaControl = this.core.getPlugin('media_control')
163
169
  assert(mediaControl, 'media_control plugin is required')
164
170
  this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.mount)
171
+ this.listenTo(mediaControl, Events.MEDIACONTROL_SHOW, () => {
172
+ this.$line?.removeClass('media-control-cc-pulled')
173
+ })
165
174
  this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, () => {
166
175
  this.hideMenu()
176
+ this.$line?.addClass('media-control-cc-pulled')
167
177
  })
168
178
  this.listenTo(
169
179
  mediaControl,
@@ -223,11 +233,10 @@ export class ClosedCaptions extends UICorePlugin {
223
233
  }
224
234
  })
225
235
 
226
- this.isPreselectedApplied = false
236
+ this.isSelectedApplied = false
227
237
  }
228
238
 
229
239
  private onPlaybackReady() {
230
- trace(`${T} onPlaybackReady`)
231
240
  this.core.activePlayback.oncueenter = (e: VTTCueInfo) => {
232
241
  this.setSubtitleText(e.text)
233
242
  }
@@ -251,24 +260,28 @@ export class ClosedCaptions extends UICorePlugin {
251
260
  }
252
261
 
253
262
  private activateTrack(id: number) {
254
- if (['dash', 'hls'].includes(this.core.activePlayback?.name)) {
255
- this.core.activePlayback.setTextTrack(id)
263
+ const isManaged = this.core.activePlayback?.name === 'hls'
264
+ this.core.activePlayback.setTextTrack(id)
265
+ if (isManaged) {
266
+ return
267
+ }
268
+ if (!this.core.activePlayback?.el.textTracks) {
256
269
  return
257
270
  }
258
- for (const track of this.currentTracks) {
259
- if (track.id === id) {
271
+ for (const [index, track] of (Array.from(this.core.activePlayback?.el.textTracks ?? []) as TextTrack[]).entries()) {
272
+ if (index === id) {
260
273
  if (this.useNativeSubtitles) {
261
- track.track.mode = 'showing'
274
+ track.mode = 'showing'
262
275
  } else {
263
- track.track.mode = 'hidden'
276
+ track.mode = 'hidden'
264
277
  }
265
278
 
266
- this.setSubtitleText(this.getSubtitleText(track.track))
279
+ this.setSubtitleText(this.getSubtitleText(track))
267
280
 
268
- track.track.oncuechange = (e) => {
281
+ track.oncuechange = () => {
269
282
  try {
270
- if (track.track.activeCues?.length) {
271
- const html = (track.track.activeCues[0] as VTTCue).getCueAsHTML()
283
+ if (track.activeCues?.length) {
284
+ const html = (track.activeCues[0] as VTTCue).getCueAsHTML()
272
285
 
273
286
  this.setSubtitleText(html)
274
287
  } else {
@@ -279,8 +292,8 @@ export class ClosedCaptions extends UICorePlugin {
279
292
  }
280
293
  }
281
294
  } else {
282
- track.track.oncuechange = () => { }
283
- track.track.mode = 'disabled'
295
+ track.oncuechange = () => { }
296
+ track.mode = 'disabled'
284
297
  }
285
298
  }
286
299
  }
@@ -289,7 +302,7 @@ export class ClosedCaptions extends UICorePlugin {
289
302
  try {
290
303
  // TODO ensure to apply only once
291
304
  this.currentTracks = this.core.activePlayback.closedCaptionsTracks
292
- this.applyPreselectedSubtitles()
305
+ this.applySelectedSubtitles()
293
306
  this.render()
294
307
  } catch (error) {
295
308
  reportError(error)
@@ -327,8 +340,10 @@ export class ClosedCaptions extends UICorePlugin {
327
340
  this.$el.find('#gplayer-cc-menu').hide()
328
341
  this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false')
329
342
  this.$line.hide()
330
- for (const track of this.currentTracks) {
331
- track.track.mode = 'hidden'
343
+ for (const track of this.core.activePlayback.el.textTracks) {
344
+ if (track.mode === 'showing') {
345
+ track.mode = 'hidden'
346
+ }
332
347
  }
333
348
  }
334
349
 
@@ -342,7 +357,6 @@ export class ClosedCaptions extends UICorePlugin {
342
357
  this.core.activeContainer &&
343
358
  isFullscreen(this.core.activeContainer.el) &&
344
359
  this.currentTrack &&
345
- // this.currentTrack.track.mode &&
346
360
  (Browser.isiOS || this.useNativeSubtitles)
347
361
  ) {
348
362
  this.$line.hide()
@@ -393,6 +407,10 @@ export class ClosedCaptions extends UICorePlugin {
393
407
  this.clampPopup()
394
408
 
395
409
  this.core.activeContainer.$el.append(this.$line)
410
+ const mc = this.core.getPlugin('media_control')
411
+ if (!mc?.isVisible()) {
412
+ this.$line?.addClass('media-control-cc-pulled')
413
+ }
396
414
 
397
415
  this.updateSelection()
398
416
 
@@ -418,29 +436,41 @@ export class ClosedCaptions extends UICorePlugin {
418
436
 
419
437
  private onItemSelect(event: MouseEvent) {
420
438
  // event.target does not exist for some reason in tests
421
- const id =
439
+ const id = Number(
422
440
  ((event.target ?? event.currentTarget) as HTMLElement).dataset?.item ??
423
- '-1'
441
+ '-1',
442
+ )
424
443
 
425
- localStorage.setItem(LOCAL_STORAGE_CC_ID, id) // TODO store language instead?
426
- this.selectItem(this.findById(Number(id)))
444
+ // TODO review, make configurable, and emit event in addition
445
+ // localStorage.setItem(LOCAL_STORAGE_CC_ID, id) // TODO store language instead?
446
+ this.userSelectedItemId = id
447
+ this.selectItem(this.findById(id))
427
448
  this.hideMenu()
428
449
  return false
429
450
  }
430
451
 
431
- private applyPreselectedSubtitles() {
432
- if (!this.isPreselectedApplied) {
433
- this.isPreselectedApplied = true
434
- // if the language is undefined, then let the engine decide
435
- // to hide the subtitles forcefully, set the language to 'none'
436
- setTimeout(() => {
437
- this.selectItem(
438
- this.tracks.find((t) =>
439
- this.isPreselectedLanguage(t.track.language),
440
- ) ?? null,
441
- )
442
- }, 0)
452
+ private applySelectedSubtitles() {
453
+ if (this.isSelectedApplied) {
454
+ return;
443
455
  }
456
+ this.isSelectedApplied = true
457
+ // If user selected a language, activate that
458
+ // Otherwise, if there is no configured language, then let the engine decide
459
+ // To hide the subtitles initially forcefully, set the language to 'none'
460
+ let matcher: (track: TextTrackItem) => boolean;
461
+ if (this.userSelectedItemId !== -1) {
462
+ matcher = (track: TextTrackItem) => track.id === this.userSelectedItemId;
463
+ } else if (this.preselectedLanguage) {
464
+ matcher = (track: TextTrackItem) => this.isPreselectedLanguage(track.track.language);
465
+ } else {
466
+ return;
467
+ }
468
+
469
+ setTimeout(() => {
470
+ this.selectItem(
471
+ this.tracks.find(matcher) ?? null,
472
+ )
473
+ }, 0)
444
474
  }
445
475
 
446
476
  private hideMenu() {
@@ -479,11 +509,9 @@ export class ClosedCaptions extends UICorePlugin {
479
509
  }
480
510
 
481
511
  private selectSubtitles() {
482
- const trackId = this.currentTrack?.id ?? -1
483
-
484
- // TODO find out if this is needed
485
- this.core.activePlayback.closedCaptionsTrackId = trackId
486
- // this.core.activePlayback.closedCaptionsTrackId = -1
512
+ if (this.currentTrack) {
513
+ this.core.activePlayback.closedCaptionsTrackId = this.currentTrack.id
514
+ }
487
515
  }
488
516
 
489
517
  private getSubtitleText(track: TextTrack) {
@@ -512,7 +540,7 @@ export class ClosedCaptions extends UICorePlugin {
512
540
  }
513
541
 
514
542
  private updateSelection() {
515
- if (!this.currentTrack) {
543
+ if (this.core.activePlayback.closedCaptionsTrackId === -1) {
516
544
  this.hide()
517
545
  } else {
518
546
  this.show()