@gcorevideo/player 2.24.10 → 2.24.13

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 (48) hide show
  1. package/README.md +1 -0
  2. package/assets/dvr-controls/dvr_controls.scss +43 -80
  3. package/assets/dvr-controls/index.ejs +8 -2
  4. package/assets/media-control/width370.scss +1 -1
  5. package/dist/core.js +34 -23
  6. package/dist/index.css +394 -420
  7. package/dist/index.embed.js +107 -71
  8. package/dist/index.js +172 -134
  9. package/dist/player.d.ts +3264 -0
  10. package/lib/playback/dash-playback/DashPlayback.d.ts +2 -0
  11. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  12. package/lib/playback/dash-playback/DashPlayback.js +17 -11
  13. package/lib/playback/hls-playback/HlsPlayback.d.ts +6 -2
  14. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  15. package/lib/playback/hls-playback/HlsPlayback.js +16 -11
  16. package/lib/plugins/audio-selector/AudioTracks.js +1 -1
  17. package/lib/plugins/bottom-gear/BottomGear.js +1 -1
  18. package/lib/plugins/clips/Clips.js +1 -1
  19. package/lib/plugins/dvr-controls/DvrControls.d.ts +3 -3
  20. package/lib/plugins/dvr-controls/DvrControls.d.ts.map +1 -1
  21. package/lib/plugins/dvr-controls/DvrControls.js +14 -12
  22. package/lib/plugins/media-control/MediaControl.d.ts +14 -5
  23. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  24. package/lib/plugins/media-control/MediaControl.js +55 -29
  25. package/lib/plugins/picture-in-picture/PictureInPicture.js +1 -1
  26. package/lib/plugins/subtitles/ClosedCaptions.js +1 -1
  27. package/lib/testUtils.d.ts.map +1 -1
  28. package/lib/testUtils.js +2 -0
  29. package/package.json +1 -1
  30. package/src/playback/dash-playback/DashPlayback.ts +20 -13
  31. package/src/playback/hls-playback/HlsPlayback.ts +40 -26
  32. package/src/plugins/audio-selector/AudioTracks.ts +1 -1
  33. package/src/plugins/audio-selector/__tests__/AudioTracks.test.ts +2 -2
  34. package/src/plugins/bottom-gear/BottomGear.ts +1 -1
  35. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +2 -2
  36. package/src/plugins/clips/Clips.ts +1 -1
  37. package/src/plugins/clips/__tests__/Clips.test.ts +1 -1
  38. package/src/plugins/dvr-controls/DvrControls.ts +14 -14
  39. package/src/plugins/dvr-controls/__tests__/DvrControls.test.ts +20 -17
  40. package/src/plugins/dvr-controls/__tests__/__snapshots__/DvrControls.test.ts.snap +4 -2
  41. package/src/plugins/media-control/MediaControl.ts +69 -35
  42. package/src/plugins/media-control/__tests__/MediaControl.test.ts +128 -112
  43. package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +227 -24
  44. package/src/plugins/picture-in-picture/PictureInPicture.ts +1 -1
  45. package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
  46. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -1
  47. package/src/testUtils.ts +2 -0
  48. package/tsconfig.tsbuildinfo +1 -1
@@ -325,11 +325,10 @@ export default class DashPlayback extends BasePlayback {
325
325
  }
326
326
 
327
327
  _ready() {
328
- this._isReadyState = true
329
- this.trigger(Events.PLAYBACK_READY, this.name)
328
+ !this._dash && this._setup()
329
+ super._ready()
330
330
  }
331
331
 
332
- // override
333
332
  private override _setupSrc() {
334
333
  // this playback manages the src on the video element itself
335
334
  }
@@ -558,10 +557,7 @@ export default class DashPlayback extends BasePlayback {
558
557
 
559
558
  override play() {
560
559
  trace(`${T} play`, { dash: !!this._dash })
561
- if (!this._dash) {
562
- this._setup()
563
- }
564
-
560
+ !this._dash && this._setup()
565
561
  super.play()
566
562
  this._startTimeUpdateTimer()
567
563
  }
@@ -579,14 +575,12 @@ export default class DashPlayback extends BasePlayback {
579
575
  override stop() {
580
576
  if (this._dash) {
581
577
  this._stopTimeUpdateTimer()
582
- this._dash.reset()
578
+ this.destroyInstance()
583
579
  super.stop()
584
- this._dash = null
585
580
  }
586
581
  }
587
582
 
588
- override destroy() {
589
- this._stopTimeUpdateTimer()
583
+ private destroyInstance() {
590
584
  if (this._dash) {
591
585
  this._dash.off(DASHJS.MediaPlayer.events.ERROR, this._onDASHJSSError)
592
586
  this._dash.off(
@@ -598,8 +592,13 @@ export default class DashPlayback extends BasePlayback {
598
592
  this.getDuration,
599
593
  )
600
594
  this._dash.reset()
595
+ this._dash = null
601
596
  }
602
- this._dash = null
597
+ }
598
+
599
+ override destroy() {
600
+ this._stopTimeUpdateTimer()
601
+ this.destroyInstance()
603
602
  return super.destroy()
604
603
  }
605
604
 
@@ -673,7 +672,7 @@ export default class DashPlayback extends BasePlayback {
673
672
  return toClapprTrack(t)
674
673
  }
675
674
 
676
- switchAudioTrack(id: string): void {
675
+ override switchAudioTrack(id: string): void {
677
676
  assert.ok(this._dash, 'DASH.js MediaPlayer is not initialized')
678
677
  const tracks = this._dash.getTracksFor('audio')
679
678
  const track = tracks.find((t) => t.id === id)
@@ -681,6 +680,14 @@ export default class DashPlayback extends BasePlayback {
681
680
  this._dash.setCurrentTrack(track)
682
681
  }
683
682
 
683
+ override load(srcUrl: string) {
684
+ this._stopTimeUpdateTimer()
685
+ this.options.src = srcUrl
686
+ // TODO destroy the instance first?
687
+ this.destroyInstance()
688
+ this._setup()
689
+ }
690
+
684
691
  private checkAudioTracks() {
685
692
  // @ts-ignore
686
693
  const tracks = this._dash.getTracksFor('audio')
@@ -87,8 +87,6 @@ export default class HlsPlayback extends BasePlayback {
87
87
 
88
88
  private _hls: HLSJS | null = null
89
89
 
90
- private _isReadyState = false
91
-
92
90
  private _lastDuration: number | null = null
93
91
 
94
92
  private _lastTimeUpdate: TimePosition | null = null
@@ -121,10 +119,16 @@ export default class HlsPlayback extends BasePlayback {
121
119
 
122
120
  private _timeUpdateTimer: TimerId | null = null
123
121
 
122
+ /**
123
+ * @internal
124
+ */
124
125
  get name() {
125
126
  return 'hls'
126
127
  }
127
128
 
129
+ /**
130
+ * @internal
131
+ */
128
132
  get supportedVersion() {
129
133
  return { min: CLAPPR_VERSION }
130
134
  }
@@ -137,10 +141,6 @@ export default class HlsPlayback extends BasePlayback {
137
141
  return this._currentLevel ?? AUTO
138
142
  }
139
143
 
140
- get isReady() {
141
- return this._isReadyState
142
- }
143
-
144
144
  set currentLevel(id: number) {
145
145
  this._currentLevel = id
146
146
  this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
@@ -369,6 +369,7 @@ export default class HlsPlayback extends BasePlayback {
369
369
  {
370
370
  maxBufferLength: 2,
371
371
  maxMaxBufferLength: 4,
372
+ autoStartLoad: false,
372
373
  },
373
374
  this.options.playback.hlsjsConfig,
374
375
  )
@@ -393,6 +394,7 @@ export default class HlsPlayback extends BasePlayback {
393
394
  this.options.hlsPlayback.preload && this._hls.loadSource(this.options.src)
394
395
  })
395
396
 
397
+ // TODO drop?
396
398
  const onPlaying = () => {
397
399
  if (this._hls) {
398
400
  this._hls.config.maxBufferLength =
@@ -405,14 +407,15 @@ export default class HlsPlayback extends BasePlayback {
405
407
 
406
408
  this.el.addEventListener('playing', onPlaying)
407
409
 
408
- this._hls.on(
409
- HLSJS.Events.MANIFEST_PARSED,
410
- () => (this._manifestParsed = true),
411
- )
410
+ this._hls.on(HLSJS.Events.MANIFEST_PARSED, () => {
411
+ this._manifestParsed = true
412
+ this._hls!.startLoad(-1)
413
+ })
412
414
  this._hls.on(
413
415
  HLSJS.Events.LEVEL_LOADED,
414
- (evt: HlsEvents.LEVEL_LOADED, data: LevelLoadedData) =>
415
- this._updatePlaybackType(evt, data),
416
+ (evt: HlsEvents.LEVEL_LOADED, data: LevelLoadedData) => {
417
+ this._updatePlaybackType(evt, data)
418
+ },
416
419
  )
417
420
  this._hls.on(
418
421
  HLSJS.Events.LEVEL_UPDATED,
@@ -458,7 +461,9 @@ export default class HlsPlayback extends BasePlayback {
458
461
  this._hls.on(HlsEvents.AUDIO_TRACKS_UPDATED, (evt, data) =>
459
462
  this._onAudioTracksUpdated(evt, data),
460
463
  )
461
- this._hls.on(HlsEvents.AUDIO_TRACK_SWITCHED, (evt, data) => this._onAudioTrackSwitched(evt, data))
464
+ this._hls.on(HlsEvents.AUDIO_TRACK_SWITCHED, (evt, data) =>
465
+ this._onAudioTrackSwitched(evt, data),
466
+ )
462
467
  this.bindCustomListeners()
463
468
  }
464
469
 
@@ -499,15 +504,15 @@ export default class HlsPlayback extends BasePlayback {
499
504
  }
500
505
 
501
506
  protected override _ready() {
502
- if (this._isReadyState) {
503
- return
504
- }
505
507
  !this._hls && this._setup()
506
- this._isReadyState = true
507
- this.trigger(Events.PLAYBACK_READY, this.name)
508
+ super._ready()
508
509
  }
509
510
 
510
- private _recover(evt: HlsEvents.ERROR, data: HlsErrorData, error: PlaybackError) {
511
+ private _recover(
512
+ evt: HlsEvents.ERROR,
513
+ data: HlsErrorData,
514
+ error: PlaybackError,
515
+ ) {
511
516
  assert(this._hls, 'HLS.js is not initialized')
512
517
  if (!this._recoveredDecodingError) {
513
518
  this._recoveredDecodingError = true
@@ -808,13 +813,13 @@ export default class HlsPlayback extends BasePlayback {
808
813
  this.trigger(Events.PLAYBACK_PROGRESS, progress, buffered)
809
814
  }
810
815
 
811
- load(url: string) {
816
+ override load(url: string) {
812
817
  this._stopTimeUpdateTimer()
813
818
  this.options.src = url
814
819
  this._setup()
815
820
  }
816
821
 
817
- play() {
822
+ override play() {
818
823
  !this._hls && this._setup()
819
824
  !this._manifestParsed &&
820
825
  !this.options.hlsPlayback.preload &&
@@ -824,7 +829,7 @@ export default class HlsPlayback extends BasePlayback {
824
829
  this._startTimeUpdateTimer()
825
830
  }
826
831
 
827
- pause() {
832
+ override pause() {
828
833
  if (!this._hls) {
829
834
  return
830
835
  }
@@ -1129,12 +1134,21 @@ export default class HlsPlayback extends BasePlayback {
1129
1134
  this._hls.audioTrack = Number(id) // TODO or find index by .id == id?
1130
1135
  }
1131
1136
 
1132
- private _onAudioTracksUpdated(_: HlsEvents.AUDIO_TRACKS_UPDATED, data: AudioTracksUpdatedData) {
1137
+ private _onAudioTracksUpdated(
1138
+ _: HlsEvents.AUDIO_TRACKS_UPDATED,
1139
+ data: AudioTracksUpdatedData,
1140
+ ) {
1133
1141
  trace(`${T} onAudioTracksUpdated`)
1134
- this.trigger(Events.PLAYBACK_AUDIO_AVAILABLE, data.audioTracks.map(toClapprTrack))
1142
+ this.trigger(
1143
+ Events.PLAYBACK_AUDIO_AVAILABLE,
1144
+ data.audioTracks.map(toClapprTrack),
1145
+ )
1135
1146
  }
1136
1147
 
1137
- private _onAudioTrackSwitched(_: HlsEvents.AUDIO_TRACK_SWITCHED, data: AudioTrackSwitchedData) {
1148
+ private _onAudioTrackSwitched(
1149
+ _: HlsEvents.AUDIO_TRACK_SWITCHED,
1150
+ data: AudioTrackSwitchedData,
1151
+ ) {
1138
1152
  trace(`${T} onAudioTrackSwitched`)
1139
1153
  // @ts-ignore
1140
1154
  const track = this._hls.audioTracks[data.id]
@@ -1156,4 +1170,4 @@ function toClapprTrack(t: MediaPlaylist): AudioTrack {
1156
1170
  kind: t.type === 'main' ? 'main' : 'description', // TODO check
1157
1171
  label: t.name,
1158
1172
  }
1159
- }
1173
+ }
@@ -88,7 +88,7 @@ export class AudioTracks extends UICorePlugin {
88
88
  const mediaControl = this.core.getPlugin('media_control')
89
89
  assert(mediaControl, 'media_control plugin is required')
90
90
  this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, () => {
91
- mediaControl.mount('audiotracks', this.$el)
91
+ mediaControl.slot('audiotracks', this.$el)
92
92
  })
93
93
  this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, this.hideMenu)
94
94
  this.listenTo(mediaControl, ExtendedEvents.MEDIACONTROL_MENU_COLLAPSE, (from: string) => {
@@ -34,7 +34,7 @@ describe('AudioTracks', () => {
34
34
  emitTracksAvailable(core, TRACKS)
35
35
  })
36
36
  it('should not attach to the media control', () => {
37
- expect(mediaControl.mount).not.toHaveBeenCalledWith(
37
+ expect(mediaControl.slot).not.toHaveBeenCalledWith(
38
38
  'audiotracks',
39
39
  expect.anything(),
40
40
  )
@@ -45,7 +45,7 @@ describe('AudioTracks', () => {
45
45
  mediaControl.trigger(Events.MEDIACONTROL_RENDERED)
46
46
  })
47
47
  it('should attach to the media control', () => {
48
- expect(mediaControl.mount).toHaveBeenCalledWith(
48
+ expect(mediaControl.slot).toHaveBeenCalledWith(
49
49
  'audiotracks',
50
50
  audioTracks.$el,
51
51
  )
@@ -320,7 +320,7 @@ export class BottomGear extends UICorePlugin {
320
320
 
321
321
  private mount() {
322
322
  const mediaControl = this.core.getPlugin('media_control')
323
- mediaControl.mount('gear', this.$el)
323
+ mediaControl.slot('gear', this.$el)
324
324
  }
325
325
 
326
326
  private alignSubmenu($subMenu: ZeptoResult) {
@@ -61,7 +61,7 @@ describe('BottomGear', () => {
61
61
  })
62
62
  describe('until media control is rendered', () => {
63
63
  it('should not attach to media control', () => {
64
- expect(mediaControl.mount).not.toHaveBeenCalledWith(
64
+ expect(mediaControl.slot).not.toHaveBeenCalledWith(
65
65
  'gear',
66
66
  expect.anything(),
67
67
  )
@@ -72,7 +72,7 @@ describe('BottomGear', () => {
72
72
  mediaControl.trigger(Events.MEDIACONTROL_RENDERED)
73
73
  })
74
74
  it('should attach to media control', () => {
75
- expect(mediaControl.mount).toHaveBeenCalledWith('gear', bottomGear.$el)
75
+ expect(mediaControl.slot).toHaveBeenCalledWith('gear', bottomGear.$el)
76
76
  })
77
77
  })
78
78
  describe('when clicked', () => {
@@ -139,7 +139,7 @@ export class Clips extends UICorePlugin {
139
139
  private onMcRender() {
140
140
  trace(`${T} onMcRender`)
141
141
  const mediaControl = this.core.getPlugin('media_control')
142
- mediaControl.mount('clips', this.$el)
142
+ mediaControl.slot('clips', this.$el)
143
143
  }
144
144
 
145
145
  private onContainerChanged() {
@@ -66,7 +66,7 @@ describe('Clips', () => {
66
66
  mediaControl.trigger(Events.MEDIACONTROL_RENDERED)
67
67
  })
68
68
  it('should mount the indicator', () => {
69
- expect(mediaControl.mount).toHaveBeenCalledWith('clips', clips.$el)
69
+ expect(mediaControl.slot).toHaveBeenCalledWith('clips', clips.$el)
70
70
  })
71
71
  })
72
72
  })
@@ -1,14 +1,14 @@
1
1
  import { Events, Playback, UICorePlugin, template } from '@clappr/core'
2
2
  import assert from 'assert'
3
+ import { trace } from '@gcorevideo/utils'
3
4
 
4
5
  import { CLAPPR_VERSION } from '../../build.js'
5
6
 
6
7
  import dvrHTML from '../../../assets/dvr-controls/index.ejs'
7
8
  import '../../../assets/dvr-controls/dvr_controls.scss'
8
- // import { trace } from '@gcorevideo/utils'
9
9
  import { MediaControl } from '../media-control/MediaControl.js'
10
10
 
11
- // const T = 'plugins.dvr_controls'
11
+ const T = 'plugins.dvr_controls'
12
12
 
13
13
  /**
14
14
  * `PLUGIN` that adds the DVR controls to the media control UI
@@ -46,7 +46,7 @@ export class DvrControls extends UICorePlugin {
46
46
  */
47
47
  override get events() {
48
48
  return {
49
- 'click .live-button': 'click',
49
+ 'click #gplayer-mc-back-to-live': 'clicked',
50
50
  }
51
51
  }
52
52
 
@@ -56,6 +56,7 @@ export class DvrControls extends UICorePlugin {
56
56
  override get attributes() {
57
57
  return {
58
58
  class: 'dvr-controls',
59
+ 'data-dvr': '',
59
60
  }
60
61
  }
61
62
 
@@ -91,7 +92,7 @@ export class DvrControls extends UICorePlugin {
91
92
  )
92
93
  }
93
94
 
94
- private click() {
95
+ private clicked() {
95
96
  const container = this.core.activeContainer
96
97
  if (!container.isPlaying()) {
97
98
  container.play()
@@ -103,19 +104,18 @@ export class DvrControls extends UICorePlugin {
103
104
  * @internal
104
105
  */
105
106
  override render() {
107
+ trace(`${T} render`)
106
108
  this.$el.html(
107
109
  DvrControls.template({
108
110
  i18n: this.core.i18n,
109
111
  }),
110
112
  )
113
+ this.$el.find('#gplayer-mc-back-to-live').hide()
114
+ this.$el.find('#gplayer-mc-live').hide()
111
115
 
112
116
  return this
113
117
  }
114
118
 
115
- private onMediacontrolRendered() {
116
- this.render()
117
- }
118
-
119
119
  private onMetadataLoaded() {
120
120
  this.mount()
121
121
  this.toggleState(this.core.activeContainer.isDvrInUse())
@@ -128,10 +128,10 @@ export class DvrControls extends UICorePlugin {
128
128
  }
129
129
  const mediaControl = this.core.getPlugin('media_control') as MediaControl
130
130
  assert(mediaControl, 'media_control plugin is required')
131
- // TODO -> to MediaControl
131
+ // TODO -> to MediaControl (auto hide)
132
132
  mediaControl.toggleElement('duration', false)
133
133
  mediaControl.toggleElement('position', false)
134
- mediaControl.mount('dvr', this.$el)
134
+ mediaControl.mount('left', this.$el) // TODO use independent mount point
135
135
  }
136
136
 
137
137
  private onDvrStateChanged(dvrInUse: boolean) {
@@ -140,11 +140,11 @@ export class DvrControls extends UICorePlugin {
140
140
 
141
141
  private toggleState(dvrInUse: boolean) {
142
142
  if (dvrInUse) {
143
- this.$el.find('#media-control-back-to-live').show()
144
- this.$el.find('#media-control-live').hide()
143
+ this.$el.find('#gplayer-mc-back-to-live').show()
144
+ this.$el.find('#gplayer-mc-live').hide()
145
145
  } else {
146
- this.$el.find('#media-control-back-to-live').hide()
147
- this.$el.find('#media-control-live').show()
146
+ this.$el.find('#gplayer-mc-back-to-live').hide()
147
+ this.$el.find('#gplayer-mc-live').show()
148
148
  }
149
149
  }
150
150
  }
@@ -33,10 +33,7 @@ describe('DvrControls', () => {
33
33
  })
34
34
  describe('while playback type is unknown', () => {
35
35
  it('should not mount', () => {
36
- expect(mediaControl.mount).not.toHaveBeenCalledWith(
37
- 'dvr',
38
- dvrControls.$el,
39
- )
36
+ expect(mediaControl.slot).not.toHaveBeenCalledWith('dvr', dvrControls.$el)
40
37
  })
41
38
  })
42
39
  describe('live stream', () => {
@@ -75,33 +72,29 @@ describe('DvrControls', () => {
75
72
  )
76
73
  })
77
74
  it('should mount to the media control', () => {
78
- expect(mediaControl.mount).toHaveBeenCalledWith('dvr', dvrControls.$el)
75
+ expect(mediaControl.mount).toHaveBeenCalledWith('left', dvrControls.$el)
79
76
  })
80
77
  if (dvrEnabled) {
81
78
  if (dvrInUse) {
82
79
  it('should show back_to_live button', () => {
83
80
  expect(
84
- dvrControls.$el
85
- .find('#media-control-back-to-live')
86
- .css('display'),
81
+ dvrControls.$el.find('#gplayer-mc-back-to-live').css('display'),
87
82
  ).not.toBe('none')
88
83
  })
89
- it('should hide live inficator', () => {
84
+ it('should hide live indicator', () => {
90
85
  expect(
91
- dvrControls.$el.find('#media-control-live').css('display'),
86
+ dvrControls.$el.find('#gplayer-mc-live').css('display'),
92
87
  ).toBe('none')
93
88
  })
94
89
  } else {
95
- it('should show live inficator', () => {
90
+ it('should show live indicator', () => {
96
91
  expect(
97
- dvrControls.$el.find('#media-control-live').css('display'),
92
+ dvrControls.$el.find('#gplayer-mc-live').css('display'),
98
93
  ).not.toBe('none')
99
94
  })
100
95
  it('should hide back_to_live button', () => {
101
96
  expect(
102
- dvrControls.$el
103
- .find('#media-control-back-to-live')
104
- .css('display'),
97
+ dvrControls.$el.find('#gplayer-mc-back-to-live').css('display'),
105
98
  ).toBe('none')
106
99
  })
107
100
  }
@@ -118,7 +111,7 @@ describe('DvrControls', () => {
118
111
  Events.CONTAINER_PLAYBACKDVRSTATECHANGED,
119
112
  true,
120
113
  )
121
- dvrControls.$el.find('#media-control-back-to-live').click()
114
+ dvrControls.$el.find('#gplayer-mc-back-to-live').click()
122
115
  })
123
116
  it('should play stream', () => {
124
117
  expect(core.activeContainer.play).toHaveBeenCalled()
@@ -139,9 +132,19 @@ describe('DvrControls', () => {
139
132
  // TODO handle mount points in MediaControl
140
133
  it('should not mount', () => {
141
134
  expect(mediaControl.mount).not.toHaveBeenCalledWith(
142
- 'dvr',
135
+ 'left',
143
136
  expect.anything(),
144
137
  )
145
138
  })
139
+ it('should not alter position and duration indicators', () => {
140
+ expect(mediaControl.toggleElement).not.toHaveBeenCalledWith(
141
+ 'duration',
142
+ false,
143
+ )
144
+ expect(mediaControl.toggleElement).not.toHaveBeenCalledWith(
145
+ 'position',
146
+ false,
147
+ )
148
+ })
146
149
  })
147
150
  })
@@ -1,7 +1,9 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`DvrControls > basically > should render 1`] = `
4
- "<div class="live-info" id="media-control-live">live</div>
5
- <button type="button" class="live-button" aria-label="back_to_live" id="media-control-back-to-live">back_to_live</button>
4
+ "<div class="live-info" id="gplayer-mc-live" style="display: none;">live</div>
5
+ <button type="button" class="live-button" aria-label="back_to_live" id="gplayer-mc-back-to-live" style="display: none;">
6
+ back_to_live
7
+ </button>
6
8
  "
7
9
  `;
@@ -56,7 +56,7 @@ export type MediaControlLeftElement =
56
56
  * Media control elements that appear in main layer, spanning the entire width of the player.
57
57
  * @beta
58
58
  */
59
- export type MediaControlLayerElement = 'seekbar' | 'seekBarContainer' // TODO rename seekbar
59
+ export type MediaControlLayerElement = 'seekbar' | 'seekBarContainer' | '_' // TODO remove seekbar
60
60
 
61
61
  /**
62
62
  * Media control elements that appear in the right area.
@@ -91,6 +91,14 @@ const MANAGED_ELEMENTS: MediaControlElement[] = [
91
91
  'volume',
92
92
  ]
93
93
 
94
+ export type MediaControlSlotSelector =
95
+ | 'root'
96
+ | 'base'
97
+ | 'left'
98
+ | 'right'
99
+ | 'center'
100
+ | 'seekbar'
101
+
94
102
  /**
95
103
  * Specifies the allowed media control elements in each area.
96
104
  * Can be used to restrict rendered media control elements.
@@ -723,7 +731,9 @@ export class MediaControl extends UICorePlugin {
723
731
  }
724
732
 
725
733
  private togglePlayStop() {
726
- this.container.isPlaying() ? this.container.stop({ ui: true }) : this.container.play()
734
+ this.container.isPlaying()
735
+ ? this.container.stop({ ui: true })
736
+ : this.container.play()
727
737
  }
728
738
 
729
739
  private startSeekDrag(event: MouseEvent) {
@@ -897,19 +907,19 @@ export class MediaControl extends UICorePlugin {
897
907
  }
898
908
 
899
909
  private hideVolumeBar(timeout = 400) {
900
- if (!this.$volumeBarContainer) {
901
- return
902
- }
903
910
  if (this.hideVolumeId) {
904
911
  clearTimeout(this.hideVolumeId)
905
912
  }
906
913
  if (this.draggingVolumeBar) {
907
- this.hideVolumeId = setTimeout(() => this.hideVolumeBar(), timeout)
914
+ this.hideVolumeId = setTimeout(() => {
915
+ this.hideVolumeId = null
916
+ this.hideVolumeBar()
917
+ }, timeout)
908
918
  } else {
909
- this.hideVolumeId = setTimeout(
910
- () => this.$volumeBarContainer?.addClass('volume-bar-hide'),
911
- timeout,
912
- )
919
+ this.hideVolumeId = setTimeout(() => {
920
+ this.hideVolumeId = null
921
+ this.$volumeBarContainer.addClass('volume-bar-hide')
922
+ }, timeout)
913
923
  }
914
924
  }
915
925
 
@@ -1133,8 +1143,7 @@ export class MediaControl extends UICorePlugin {
1133
1143
  newSettings.seekEnabled = this.isSeekEnabledForHtml5Playback()
1134
1144
  }
1135
1145
 
1136
- const settingsChanged =
1137
- serializeSettings(this.settings) !== serializeSettings(newSettings)
1146
+ const settingsChanged = !isEqualSettings(this.settings, newSettings)
1138
1147
 
1139
1148
  if (settingsChanged) {
1140
1149
  this.settings = newSettings
@@ -1182,34 +1191,52 @@ export class MediaControl extends UICorePlugin {
1182
1191
  /**
1183
1192
  * Get a media control element DOM node
1184
1193
  * @param name - The name of the media control element
1185
- * @returns The DOM node to render to or extend
1194
+ * @param element - The DOM node/fragment to mount
1195
+ * @deprecated Use {@link MediaControl.mount} instead
1196
+ */
1197
+ slot(name: MediaControlElement, element: ZeptoResult): void {
1198
+ const panel = this.getElementLocation(name)
1199
+ if (panel) {
1200
+ element.attr(`data-${name}`, '')
1201
+ mountTo(panel, element)
1202
+ }
1203
+ }
1204
+
1205
+ /**
1206
+ * Mounts a media control element at a specific location
1207
+ * @param name - The location to mount the element
1208
+ * @param element - The element to mount
1186
1209
  * @remarks
1187
1210
  * Use this method to render custom media control UI in a plugin
1188
1211
  * @example
1189
1212
  * ```ts
1190
1213
  * class MyPlugin extends UICorePlugin {
1191
1214
  * override render() {
1192
- * this.$el.html('<div data-clips>Here we go</div>')
1193
- * this.core.getPlugin('media_control').mount('clips', this.$el)
1215
+ * this.$el.html('<div id="my-element" class="my-class">Here we go</div>')
1216
+ * this.core.getPlugin('media_control').mount('left', this.$el)
1194
1217
  * return this
1195
1218
  * }
1196
1219
  * }
1197
1220
  * ```
1198
1221
  */
1199
- mount(name: MediaControlElement, element: ZeptoResult) {
1200
- const panel = this.getElementLocation(name)
1201
- if (panel) {
1202
- const current = panel.find(`[data-${name}]`)
1203
- element.attr(`data-${name}`, '')
1204
- // TODO test
1205
- if (current.length) {
1206
- if (current[0] === element[0]) {
1207
- return
1208
- }
1209
- current.replaceWith(element)
1210
- } else {
1211
- panel.append(element)
1212
- }
1222
+ mount(name: MediaControlSlotSelector, element: ZeptoResult) {
1223
+ mountTo(this.getMountParent(name), element)
1224
+ }
1225
+
1226
+ private getMountParent(name: MediaControlSlotSelector): ZeptoResult {
1227
+ switch (name) {
1228
+ case 'root':
1229
+ return this.$el
1230
+ case 'base':
1231
+ return this.$el.find('.media-control-layer')
1232
+ case 'center':
1233
+ return this.getCenterPanel()
1234
+ case 'left':
1235
+ return this.getLeftPanel()
1236
+ case 'right':
1237
+ return this.getRightPanel()
1238
+ default:
1239
+ assert.fail(`Invalid mount parent name: ${name}`)
1213
1240
  }
1214
1241
  }
1215
1242
 
@@ -1452,17 +1479,14 @@ export class MediaControl extends UICorePlugin {
1452
1479
  }
1453
1480
  }
1454
1481
 
1482
+ // TODO check if needed
1455
1483
  this.$seekBarPosition?.addClass('media-control-notransition')
1456
1484
  this.$seekBarScrubber?.addClass('media-control-notransition')
1457
1485
 
1458
- let previousSeekPercentage = 0
1459
-
1460
- if (this.displayedSeekBarPercentage) {
1461
- previousSeekPercentage = this.displayedSeekBarPercentage
1462
- }
1486
+ const prevSeekPercentage = this.displayedSeekBarPercentage || 0
1463
1487
 
1464
1488
  this.displayedSeekBarPercentage = null
1465
- this.setSeekPercentage(previousSeekPercentage)
1489
+ this.setSeekPercentage(prevSeekPercentage)
1466
1490
 
1467
1491
  setTimeout(() => {
1468
1492
  !this.settings.seekEnabled &&
@@ -1609,3 +1633,13 @@ function serializeSettings(s: MediaControlSettings) {
1609
1633
  .concat([s.seekEnabled as any])
1610
1634
  .join(',')
1611
1635
  }
1636
+
1637
+ function isEqualSettings(a: MediaControlSettings, b: MediaControlSettings) {
1638
+ return serializeSettings(a) === serializeSettings(b)
1639
+ }
1640
+
1641
+ function mountTo(parent: ZeptoResult, element: ZeptoResult) {
1642
+ if (!parent.find(element).length) {
1643
+ parent.append(element)
1644
+ }
1645
+ }