@gcorevideo/player 2.28.29 → 2.28.35

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.
@@ -26,6 +26,8 @@ import { CLAPPR_VERSION } from '../../build.js'
26
26
  import { ZeptoResult } from '../../types.js'
27
27
  import { getPageX } from '../utils.js'
28
28
  import { fullscreenEnabled, isFullscreen } from '../utils/fullscreen.js'
29
+ import { isMobile } from '../utils/mobile.js'
30
+ import { mediaControlClickaway } from '../../utils/clickaway.js'
29
31
 
30
32
  import '../../../assets/media-control/media-control.scss'
31
33
 
@@ -37,7 +39,6 @@ import volumeMaxIcon from '../../../assets/icons/new/volume-max.svg'
37
39
  import volumeOffIcon from '../../../assets/icons/new/volume-off.svg'
38
40
  import fullscreenOffIcon from '../../../assets/icons/new/fullscreen-off.svg'
39
41
  import fullscreenOnIcon from '../../../assets/icons/new/fullscreen-on.svg'
40
- import { mediaControlClickaway } from '../../utils/clickaway.js'
41
42
 
42
43
  const STANDARD_MEDIA_CONTROL_ELEMENTS: string[] = [
43
44
  'duration',
@@ -352,8 +353,6 @@ export class MediaControl extends UICorePlugin {
352
353
  'touchmove .bar-container[data-seekbar]': 'mousemoveOnSeekBar',
353
354
  'mouseleave .bar-container[data-seekbar]': 'mouseleaveOnSeekBar',
354
355
  'touchend .bar-container[data-seekbar]': 'mouseleaveOnSeekBar',
355
- 'mouseenter .media-control-layer[data-controls]': 'setUserKeepVisible',
356
- 'mouseleave .media-control-layer[data-controls]': 'resetUserKeepVisible',
357
356
  }
358
357
  }
359
358
 
@@ -470,9 +469,6 @@ export class MediaControl extends UICorePlugin {
470
469
  Events.CONTAINER_DBLCLICK,
471
470
  this.toggleFullscreen,
472
471
  )
473
- this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () =>
474
- this.clickaway(this.core.activeContainer.$el[0]),
475
- )
476
472
  this.listenTo(
477
473
  this.core.activeContainer,
478
474
  Events.CONTAINER_TIMEUPDATE,
@@ -526,6 +522,7 @@ export class MediaControl extends UICorePlugin {
526
522
  )
527
523
  this.listenTo(this.core, Events.CONTAINER_DESTROYED, () => {
528
524
  this.cancelRenderTimer()
525
+ this.setKeepVisible(false)
529
526
  })
530
527
  this.listenTo(
531
528
  this.core.activeContainer,
@@ -537,10 +534,6 @@ export class MediaControl extends UICorePlugin {
537
534
  Events.CONTAINER_MOUSE_LEAVE,
538
535
  this.delayHide,
539
536
  )
540
-
541
- this.listenTo(this.core.activeContainer, Events.CONTAINER_DESTROYED, () => {
542
- this.clickaway(null)
543
- })
544
537
  }
545
538
 
546
539
  /**
@@ -692,7 +685,7 @@ export class MediaControl extends UICorePlugin {
692
685
  this.$playPauseToggle?.append(playIcon)
693
686
  this.$playStopToggle?.append(playIcon)
694
687
  this.trigger(Events.MEDIACONTROL_NOTPLAYING)
695
- if (Browser.isMobile) {
688
+ if (isMobile()) {
696
689
  this.show()
697
690
  }
698
691
  }
@@ -745,7 +738,7 @@ export class MediaControl extends UICorePlugin {
745
738
  width: this.container.$el.width(),
746
739
  height: this.container.$el.height(),
747
740
  hideVolumeBar: this.options.hideVolumeBar,
748
- isMobile: Browser.isMobile,
741
+ isMobile: isMobile(),
749
742
  })
750
743
 
751
744
  try {
@@ -754,7 +747,7 @@ export class MediaControl extends UICorePlugin {
754
747
  this.$el.addClass('w370')
755
748
  }
756
749
 
757
- if (skinWidth <= 270 && !Browser.isMobile) {
750
+ if (skinWidth <= 270 && !isMobile()) {
758
751
  this.verticalVolume = true
759
752
  this.$el.addClass('w270')
760
753
  }
@@ -769,6 +762,18 @@ export class MediaControl extends UICorePlugin {
769
762
  return false
770
763
  }
771
764
 
765
+ private play() {
766
+ this.container && this.container.play()
767
+ }
768
+
769
+ private pause() {
770
+ this.container && this.container.pause()
771
+ }
772
+
773
+ private stop() {
774
+ this.container && this.container.stop()
775
+ }
776
+
772
777
  private togglePlayStop() {
773
778
  this.container.isPlaying()
774
779
  ? this.container.stop({ ui: true })
@@ -893,11 +898,11 @@ export class MediaControl extends UICorePlugin {
893
898
  }
894
899
 
895
900
  private toggleFullscreen() {
896
- if (!Browser.isMobile) {
901
+ if (!isMobile()) {
897
902
  this.trigger(Events.MEDIACONTROL_FULLSCREEN, this.name)
898
903
  this.core.activeContainer.fullscreen()
899
904
  this.core.toggleFullscreen()
900
- this.resetUserKeepVisible()
905
+ // this.resetUserKeepVisible()
901
906
  }
902
907
  }
903
908
 
@@ -1048,15 +1053,6 @@ export class MediaControl extends UICorePlugin {
1048
1053
  this.setSeekPercentage(pos)
1049
1054
  }
1050
1055
 
1051
- private setUserKeepVisible(e?: MouseEvent) {
1052
- this.userKeepVisible = true
1053
- this.clickaway(this.core.activeContainer.$el[0])
1054
- }
1055
-
1056
- private resetUserKeepVisible = (e?: MouseEvent) => {
1057
- this.userKeepVisible = false
1058
- }
1059
-
1060
1056
  private isVisible() {
1061
1057
  return !this.$el.hasClass('media-control-hide')
1062
1058
  }
@@ -1066,7 +1062,6 @@ export class MediaControl extends UICorePlugin {
1066
1062
  return
1067
1063
  }
1068
1064
 
1069
- const timeout = DEFAULT_HIDE_DELAY
1070
1065
  const mousePointerMoved =
1071
1066
  event &&
1072
1067
  event.clientX !== this.lastMouseX &&
@@ -1077,6 +1072,8 @@ export class MediaControl extends UICorePlugin {
1077
1072
  clearTimeout(this.hideId)
1078
1073
  this.hideId = null
1079
1074
  }
1075
+ this.hideId = setTimeout(() => this.hide(), DEFAULT_HIDE_DELAY)
1076
+
1080
1077
  this.$el.show()
1081
1078
  this.trigger(Events.MEDIACONTROL_SHOW, this.name)
1082
1079
  this.core.activeContainer?.trigger(
@@ -1084,15 +1081,13 @@ export class MediaControl extends UICorePlugin {
1084
1081
  this.name,
1085
1082
  )
1086
1083
  this.$el.removeClass('media-control-hide')
1087
- this.hideId = setTimeout(() => this.hide(), timeout)
1088
1084
  if (event) {
1089
1085
  this.lastMouseX = event.clientX
1090
1086
  this.lastMouseY = event.clientY
1091
1087
  }
1092
1088
  }
1093
- const showing = true
1094
1089
 
1095
- this.updateCursorStyle(showing)
1090
+ this.updateCursorStyle(true)
1096
1091
  }
1097
1092
 
1098
1093
  private hide(delay = 0) {
@@ -1100,10 +1095,9 @@ export class MediaControl extends UICorePlugin {
1100
1095
  return
1101
1096
  }
1102
1097
 
1103
- const timeout = delay || 2000
1104
-
1105
1098
  if (this.hideId !== null) {
1106
1099
  clearTimeout(this.hideId)
1100
+ this.hideId = null
1107
1101
  }
1108
1102
 
1109
1103
  if (!this.disabled && this.options.hideMediaControl === false) {
@@ -1117,7 +1111,7 @@ export class MediaControl extends UICorePlugin {
1117
1111
  !this.disabled &&
1118
1112
  (delay || hasKeepVisibleRequested || hasDraggingAction)
1119
1113
  ) {
1120
- this.hideId = setTimeout(() => this.hide(), timeout)
1114
+ this.hideId = setTimeout(() => this.hide(), delay || 2000)
1121
1115
  } else {
1122
1116
  if (!this.options.controlsDontHide || isFullscreen(this.container.el)) {
1123
1117
  this.trigger(Events.MEDIACONTROL_HIDE, this.name)
@@ -1278,18 +1272,13 @@ export class MediaControl extends UICorePlugin {
1278
1272
  /**
1279
1273
  * Set or reset the keep visibility state
1280
1274
  *
1281
- * Keep visibility state controls whether the media control is hidden automatically after a delay.
1282
- * Keep visibility prevents the the auto-hide behaviour
1275
+ * Keep visibility state controls whether the media control is hidden automatically after a delay, which is a default behaviour.
1283
1276
  *
1284
1277
  * @param keepVisible - The state
1285
1278
  */
1286
1279
  setKeepVisible(keepVisible: boolean) {
1287
1280
  this.keepVisible = keepVisible
1288
- if (keepVisible) {
1289
- this.clickaway(this.core.activeContainer.$el[0])
1290
- } else {
1291
- this.clickaway(null)
1292
- }
1281
+ this.clickaway(keepVisible ? this.core.activeContainer.$el[0] : null)
1293
1282
  }
1294
1283
 
1295
1284
  private getMountParent(name: MediaControlSlotMountPoint): ZeptoResult {
@@ -1390,7 +1379,7 @@ export class MediaControl extends UICorePlugin {
1390
1379
  }
1391
1380
 
1392
1381
  private bindKeyEvents() {
1393
- if (Browser.isMobile || this.options.disableKeyboardShortcuts) {
1382
+ if (isMobile() || this.options.disableKeyboardShortcuts) {
1394
1383
  return
1395
1384
  }
1396
1385
 
@@ -1491,6 +1480,7 @@ export class MediaControl extends UICorePlugin {
1491
1480
  * @internal
1492
1481
  */
1493
1482
  override destroy() {
1483
+ this.cancelTimers()
1494
1484
  this.cancelRenderTimer()
1495
1485
  $(document).unbind('mouseup', this.stopDrag)
1496
1486
  $(document).unbind('mousemove', this.updateDrag)
@@ -1500,6 +1490,18 @@ export class MediaControl extends UICorePlugin {
1500
1490
  return super.destroy()
1501
1491
  }
1502
1492
 
1493
+ private cancelTimers() {
1494
+ if (this.hideId !== null) {
1495
+ clearTimeout(this.hideId)
1496
+ this.hideId = null
1497
+ }
1498
+ if (this.hideVolumeId !== null) {
1499
+ clearTimeout(this.hideVolumeId)
1500
+ this.hideVolumeId = null
1501
+ }
1502
+ this.cancelRenderTimer()
1503
+ }
1504
+
1503
1505
  private cancelRenderTimer() {
1504
1506
  if (this.renderTimerId) {
1505
1507
  clearTimeout(this.renderTimerId)
@@ -1537,7 +1539,7 @@ export class MediaControl extends UICorePlugin {
1537
1539
 
1538
1540
  // Video volume cannot be changed with Safari on mobile devices
1539
1541
  // Display mute/unmute icon only if Safari version >= 10
1540
- if (Browser.isSafari && Browser.isMobile) {
1542
+ if (Browser.isSafari && isMobile()) {
1541
1543
  if (Browser.version < 10) {
1542
1544
  this.$volumeContainer?.css({ display: 'none' })
1543
1545
  } else {
@@ -1557,7 +1559,7 @@ export class MediaControl extends UICorePlugin {
1557
1559
  setTimeout(() => {
1558
1560
  !this.settings.seekEnabled &&
1559
1561
  this.$seekBarContainer?.addClass('seek-disabled')
1560
- !Browser.isMobile &&
1562
+ !isMobile() &&
1561
1563
  !this.options.disableKeyboardShortcuts &&
1562
1564
  this.bindKeyEvents()
1563
1565
  this.playerResize({
@@ -1587,6 +1589,7 @@ export class MediaControl extends UICorePlugin {
1587
1589
  this.container.fullscreen()
1588
1590
  // TODO: fix after it full screen will be fixed on iOS
1589
1591
  if (Browser.isiOS) {
1592
+ // TODO use isFullscreen utility function
1590
1593
  if (this.core.isFullscreen()) {
1591
1594
  Fullscreen.cancelFullscreen(this.core.el)
1592
1595
  } else {
@@ -1595,7 +1598,6 @@ export class MediaControl extends UICorePlugin {
1595
1598
  } else {
1596
1599
  this.core.toggleFullscreen()
1597
1600
  }
1598
- this.resetUserKeepVisible()
1599
1601
  }
1600
1602
 
1601
1603
  private static getPageX(event: MouseEvent | TouchEvent): number {
@@ -1653,7 +1655,7 @@ export class MediaControl extends UICorePlugin {
1653
1655
  this.hide(this.options.hideMediaControlDelay || DEFAULT_HIDE_DELAY)
1654
1656
  }
1655
1657
 
1656
- private clickaway = mediaControlClickaway(() => this.resetUserKeepVisible())
1658
+ private clickaway = mediaControlClickaway(() => this.setKeepVisible(false))
1657
1659
  }
1658
1660
 
1659
1661
  MediaControl.extend = function (properties) {
@@ -237,41 +237,38 @@ describe('MediaControl', () => {
237
237
  )
238
238
  expect(el.length).toEqual(0)
239
239
  })
240
- it(`should ${
241
- settings.seekEnabled ? '' : 'not '
242
- }render the seek bar`, () => {
243
- const seekbar = mediaControl.$el.find(
244
- '.media-control-center-panel [data-seekbar]',
245
- )
246
- expect(seekbar.length).toBeGreaterThan(1)
247
- if (settings.seekEnabled) {
248
- expect(seekbar.hasClass('seek-disabled')).toBe(false)
249
- } else {
250
- expect(seekbar.hasClass('seek-disabled')).toBe(true)
251
- }
252
- })
253
- it(`should ${
254
- settings.left.includes('volume') ? '' : 'not '
255
- }render the volume control`, () => {
256
- const volume = mediaControl.$el.find('.drawer-container[data-volume]')
257
- if (settings.left.includes('volume')) {
258
- expect(volume.length).toEqual(1)
259
- } else {
260
- expect(volume.length).toEqual(0)
261
- }
262
- })
263
- it(`should ${
264
- settings.right.includes('fullscreen') ? '' : 'not '
265
- }render the fullscreen control`, () => {
266
- const fullscreen = mediaControl.$el.find(
267
- '.media-control-right-panel [data-fullscreen]',
268
- )
269
- if (settings.right.includes('fullscreen')) {
270
- expect(fullscreen.length).toEqual(1)
271
- } else {
272
- expect(fullscreen.length).toEqual(0)
273
- }
274
- })
240
+ it(`should ${settings.seekEnabled ? '' : 'not '
241
+ }render the seek bar`, () => {
242
+ const seekbar = mediaControl.$el.find(
243
+ '.media-control-center-panel [data-seekbar]',
244
+ )
245
+ expect(seekbar.length).toBeGreaterThan(1)
246
+ if (settings.seekEnabled) {
247
+ expect(seekbar.hasClass('seek-disabled')).toBe(false)
248
+ } else {
249
+ expect(seekbar.hasClass('seek-disabled')).toBe(true)
250
+ }
251
+ })
252
+ it(`should ${settings.left.includes('volume') ? '' : 'not '
253
+ }render the volume control`, () => {
254
+ const volume = mediaControl.$el.find('.drawer-container[data-volume]')
255
+ if (settings.left.includes('volume')) {
256
+ expect(volume.length).toEqual(1)
257
+ } else {
258
+ expect(volume.length).toEqual(0)
259
+ }
260
+ })
261
+ it(`should ${settings.right.includes('fullscreen') ? '' : 'not '
262
+ }render the fullscreen control`, () => {
263
+ const fullscreen = mediaControl.$el.find(
264
+ '.media-control-right-panel [data-fullscreen]',
265
+ )
266
+ if (settings.right.includes('fullscreen')) {
267
+ expect(fullscreen.length).toEqual(1)
268
+ } else {
269
+ expect(fullscreen.length).toEqual(0)
270
+ }
271
+ })
275
272
  })
276
273
  describe('basically', () => {
277
274
  let handler: MockedFunction<() => void>
@@ -338,6 +335,37 @@ describe('MediaControl', () => {
338
335
  expect(mediaControl.el.innerHTML).toMatchSnapshot()
339
336
  })
340
337
  })
338
+ describe('auto-hide', () => {
339
+ beforeEach(async () => {
340
+ mediaControl = new MediaControl(core)
341
+ core.emit(Events.CORE_READY)
342
+ core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
343
+ await runMetadataLoaded(core)
344
+ })
345
+ describe('when rendered', () => {
346
+ it('should auto-hide', async () => {
347
+ expect(mediaControl.$el.hasClass('media-control-hide')).toBe(false)
348
+ await new Promise((resolve) => setTimeout(resolve, 2000))
349
+ expect(mediaControl.$el.hasClass('media-control-hide')).toBe(true)
350
+ })
351
+ })
352
+ describe('after rendered', () => {
353
+ beforeEach(async () => {
354
+ await new Promise((resolve) => setTimeout(resolve, 2000))
355
+ })
356
+ describe('after user interaction', () => {
357
+ beforeEach(async () => {
358
+ core.activeContainer.trigger(Events.CONTAINER_MOUSE_ENTER)
359
+ await new Promise((resolve) => setTimeout(resolve, 0))
360
+ })
361
+ it('should auto-hide', async () => {
362
+ expect(mediaControl.$el.hasClass('media-control-hide')).toBe(false)
363
+ await new Promise((resolve) => setTimeout(resolve, 2000))
364
+ expect(mediaControl.$el.hasClass('media-control-hide')).toBe(true)
365
+ })
366
+ })
367
+ })
368
+ })
341
369
  })
342
370
 
343
371
  function arraySubtract<T extends string>(arr1: T[], arr2: T[]) {
@@ -1,5 +1,5 @@
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'
@@ -19,7 +19,8 @@ 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
25
  const T = 'plugins.cc'
25
26
 
@@ -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[] = []
@@ -223,11 +229,10 @@ export class ClosedCaptions extends UICorePlugin {
223
229
  }
224
230
  })
225
231
 
226
- this.isPreselectedApplied = false
232
+ this.isSelectedApplied = false
227
233
  }
228
234
 
229
235
  private onPlaybackReady() {
230
- trace(`${T} onPlaybackReady`)
231
236
  this.core.activePlayback.oncueenter = (e: VTTCueInfo) => {
232
237
  this.setSubtitleText(e.text)
233
238
  }
@@ -251,7 +256,7 @@ export class ClosedCaptions extends UICorePlugin {
251
256
  }
252
257
 
253
258
  private activateTrack(id: number) {
254
- if (['dash', 'hls'].includes(this.core.activePlayback?.name)) {
259
+ if (this.core.activePlayback && ['dash', 'hls'].includes(this.core.activePlayback.name)) {
255
260
  this.core.activePlayback.setTextTrack(id)
256
261
  return
257
262
  }
@@ -289,7 +294,7 @@ export class ClosedCaptions extends UICorePlugin {
289
294
  try {
290
295
  // TODO ensure to apply only once
291
296
  this.currentTracks = this.core.activePlayback.closedCaptionsTracks
292
- this.applyPreselectedSubtitles()
297
+ this.applySelectedSubtitles()
293
298
  this.render()
294
299
  } catch (error) {
295
300
  reportError(error)
@@ -418,29 +423,40 @@ export class ClosedCaptions extends UICorePlugin {
418
423
 
419
424
  private onItemSelect(event: MouseEvent) {
420
425
  // event.target does not exist for some reason in tests
421
- const id =
426
+ const id = Number(
422
427
  ((event.target ?? event.currentTarget) as HTMLElement).dataset?.item ??
423
- '-1'
428
+ '-1',
429
+ )
424
430
 
425
- localStorage.setItem(LOCAL_STORAGE_CC_ID, id) // TODO store language instead?
426
- this.selectItem(this.findById(Number(id)))
431
+ // TODO review, make configurable, and emit event in addition
432
+ // localStorage.setItem(LOCAL_STORAGE_CC_ID, id) // TODO store language instead?
433
+ this.userSelectedItemId = id
434
+ this.selectItem(this.findById(id))
427
435
  this.hideMenu()
428
436
  return false
429
437
  }
430
438
 
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)
439
+ private applySelectedSubtitles() {
440
+ if (this.isSelectedApplied) {
441
+ return;
442
+ }
443
+ this.isSelectedApplied = true
444
+ // If user selected a language, activate that
445
+ // Otherwise, if there is no configured language, then let the engine decide
446
+ // To hide the subtitles initially forcefully, set the language to 'none'
447
+ let matcher: (track: TextTrackItem) => boolean;
448
+ if (this.userSelectedItemId !== -1) {
449
+ matcher = (track: TextTrackItem) => track.id === this.userSelectedItemId;
450
+ } else if (this.preselectedLanguage) {
451
+ matcher = (track: TextTrackItem) => this.isPreselectedLanguage(track.track.language);
452
+ } else {
453
+ return;
443
454
  }
455
+ setTimeout(() => {
456
+ this.selectItem(
457
+ this.tracks.find(matcher) ?? null,
458
+ )
459
+ }, 0)
444
460
  }
445
461
 
446
462
  private hideMenu() {
@@ -479,11 +495,9 @@ export class ClosedCaptions extends UICorePlugin {
479
495
  }
480
496
 
481
497
  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
498
+ if (this.currentTrack) {
499
+ this.core.activePlayback.closedCaptionsTrackId = this.currentTrack.id
500
+ }
487
501
  }
488
502
 
489
503
  private getSubtitleText(track: TextTrack) {