@gcorevideo/player 2.22.0 → 2.22.1

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 (68) hide show
  1. package/assets/bottom-gear/bottomgear copy.ejs +10 -0
  2. package/assets/bottom-gear/bottomgear.ejs +4 -8
  3. package/assets/bottom-gear/gear-sub-menu.scss +0 -1
  4. package/assets/bottom-gear/gear.scss +0 -1
  5. package/assets/clappr-nerd-stats/button.ejs +3 -3
  6. package/assets/level-selector/button.ejs +2 -4
  7. package/assets/level-selector/list.ejs +14 -10
  8. package/assets/level-selector/style.scss +9 -4
  9. package/assets/playback-rate/list.ejs +5 -5
  10. package/dist/core.js +1 -2
  11. package/dist/index.css +1104 -1103
  12. package/dist/index.js +3849 -3767
  13. package/dist/player.d.ts +10 -17
  14. package/dist/plugins/index.css +1541 -1540
  15. package/dist/plugins/index.js +3949 -3868
  16. package/docs/api/player.mediacontrol.md +8 -36
  17. package/docs/api/player.mediacontrol.toggleelement.md +72 -0
  18. package/docs/api/player.mediacontrolelement.md +1 -1
  19. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  20. package/lib/playback/dash-playback/DashPlayback.js +0 -1
  21. package/lib/plugins/bottom-gear/BottomGear.d.ts +65 -14
  22. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  23. package/lib/plugins/bottom-gear/BottomGear.js +113 -37
  24. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts +2 -3
  25. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts.map +1 -1
  26. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.js +18 -15
  27. package/lib/plugins/dvr-controls/DvrControls.js +1 -1
  28. package/lib/plugins/level-selector/LevelSelector.d.ts +8 -11
  29. package/lib/plugins/level-selector/LevelSelector.d.ts.map +1 -1
  30. package/lib/plugins/level-selector/LevelSelector.js +66 -102
  31. package/lib/plugins/media-control/MediaControl.d.ts +7 -5
  32. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  33. package/lib/plugins/media-control/MediaControl.js +37 -19
  34. package/lib/plugins/picture-in-picture/PictureInPicture.d.ts.map +1 -1
  35. package/lib/plugins/picture-in-picture/PictureInPicture.js +7 -2
  36. package/lib/plugins/playback-rate/PlaybackRate.d.ts +42 -14
  37. package/lib/plugins/playback-rate/PlaybackRate.d.ts.map +1 -1
  38. package/lib/plugins/playback-rate/PlaybackRate.js +101 -83
  39. package/lib/plugins/subtitles/ClosedCaptions.js +1 -1
  40. package/lib/testUtils.d.ts +1 -0
  41. package/lib/testUtils.d.ts.map +1 -1
  42. package/lib/testUtils.js +13 -0
  43. package/package.json +1 -1
  44. package/src/playback/dash-playback/DashPlayback.ts +0 -1
  45. package/src/plugins/bottom-gear/BottomGear.ts +162 -72
  46. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +21 -5
  47. package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +5 -12
  48. package/src/plugins/clappr-nerd-stats/ClapprNerdStats.ts +27 -25
  49. package/src/plugins/dvr-controls/DvrControls.ts +1 -1
  50. package/src/plugins/dvr-controls/__tests__/DvrControls.test.ts +1 -1
  51. package/src/plugins/level-selector/LevelSelector.ts +80 -120
  52. package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +69 -79
  53. package/src/plugins/level-selector/__tests__/__snapshots__/LevelSelector.test.ts.snap +38 -71
  54. package/src/plugins/media-control/MediaControl.ts +51 -25
  55. package/src/plugins/media-control/__tests__/MediaControl.test.ts +4 -4
  56. package/src/plugins/picture-in-picture/PictureInPicture.ts +7 -2
  57. package/src/plugins/playback-rate/PlaybackRate.ts +136 -108
  58. package/src/plugins/playback-rate/__tests__/PlaybackRate.test.ts +84 -37
  59. package/src/plugins/playback-rate/__tests__/__snapshots__/PlaybackRate.test.ts.snap +55 -6
  60. package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
  61. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -1
  62. package/src/testUtils.ts +14 -0
  63. package/src/typings/vitest.d.ts +1 -0
  64. package/temp/player.api.json +66 -94
  65. package/tsconfig.tsbuildinfo +1 -1
  66. package/docs/api/player.mediacontrol.getcenterpanel.md +0 -18
  67. package/docs/api/player.mediacontrol.getleftpanel.md +0 -22
  68. package/docs/api/player.mediacontrol.getrightpanel.md +0 -22
@@ -1,4 +1,4 @@
1
- import { Events, UICorePlugin, Playback, template, Core } from '@clappr/core'
1
+ import { Events, UICorePlugin, Playback, template, Core, $ } from '@clappr/core'
2
2
  import { trace } from '@gcorevideo/utils'
3
3
  import assert from 'assert'
4
4
 
@@ -11,29 +11,37 @@ import speedIcon from '../../../assets/icons/new/speed.svg'
11
11
  import arrowRightIcon from '../../../assets/icons/new/arrow-right.svg'
12
12
  import arrowLeftIcon from '../../../assets/icons/new/arrow-left.svg'
13
13
  import checkIcon from '../../../assets/icons/new/check.svg'
14
- import { BottomGear } from '../bottom-gear/BottomGear.js'
14
+ import { BottomGear, GearEvents } from '../bottom-gear/BottomGear.js'
15
15
  import { PlaybackEvents } from '../../playback/types.js'
16
- import {
17
- MediaControl,
18
- MediaControlEvents,
19
- } from '../media-control/MediaControl.js'
16
+ import { MediaControl } from '../media-control/MediaControl.js'
20
17
 
21
- type PlaybackRateOption = {
22
- value: string
18
+ /**
19
+ * @beta
20
+ */
21
+ export type PlaybackRateOption = {
22
+ value: number
23
23
  label: string
24
24
  }
25
25
 
26
+ /**
27
+ * @beta
28
+ */
29
+ export type PlaybackRateSettings = {
30
+ options?: PlaybackRateOption[]
31
+ defaultValue?: number
32
+ }
33
+
26
34
  const DEFAULT_PLAYBACK_RATES = [
27
- { value: '0.5', label: '0.5x' },
28
- { value: '0.75', label: '0.75x' },
29
- { value: '1.0', label: '1x' },
30
- { value: '1.25', label: '1.25x' },
31
- { value: '1.5', label: '1.5x' },
32
- { value: '1.75', label: '1.75x' },
33
- { value: '2.0', label: '2x' },
35
+ { value: 0.5, label: '0.5x' },
36
+ { value: 0.75, label: '0.75x' },
37
+ { value: 1.0, label: '1x' },
38
+ { value: 1.25, label: '1.25x' },
39
+ { value: 1.5, label: '1.5x' },
40
+ { value: 1.75, label: '1.75x' },
41
+ { value: 2.0, label: '2x' },
34
42
  ]
35
43
 
36
- const DEFAULT_PLAYBACK_RATE = '1.0'
44
+ const DEFAULT_PLAYBACK_RATE = 1
37
45
 
38
46
  const T = 'plugins.playback_rate'
39
47
 
@@ -44,22 +52,37 @@ const T = 'plugins.playback_rate'
44
52
  * @remarks
45
53
  * Depends on:
46
54
  *
47
- * - {@link MediaControl | media_control}
55
+ * - {@link MediaControl}
56
+ *
57
+ * - {@link BottomGear}
48
58
  *
49
- * - {@link BottomGear | bottom_gear}
59
+ * It renders an option in the gear menu, which opens a dropdown with the options to change the playback rate.
60
+ * Note that the playback rate change is supported only for VOD or DVR-enabled live streams.
50
61
  *
51
- * It renders a button in the gear menu, which opens a dropdown with the options to change the playback rate.
52
- * Note that the playback rate change is supported only for VOD or DVR enabled live streams.
62
+ * Plugin settings - {@link PlaybackRateSettings}
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { Player, PlaybackRateSettings } from '@gcorevideo/player'
67
+ * Player.registerPlugin(PlaybackRate)
68
+ * const player = new Player({
69
+ * playbackRate: {
70
+ * options: [
71
+ * { value: 0.5, label: '0.5x' },
72
+ * { value: 1, label: '1x' },
73
+ * ],
74
+ * defaultValue: 1,
75
+ * } as PlaybackRateSettings,
76
+ * })
77
+ * ```
53
78
  */
54
79
  export class PlaybackRate extends UICorePlugin {
55
80
  private playbackRates: PlaybackRateOption[] = DEFAULT_PLAYBACK_RATES
56
81
 
57
82
  // Saved when an ad starts to restore after it finishes
58
- private prevSelectedRate: string | undefined
59
-
60
- private rendered = false
83
+ // private prevSelectedRate: string | undefined
61
84
 
62
- private selectedRate: string = DEFAULT_PLAYBACK_RATE
85
+ private selectedRate = DEFAULT_PLAYBACK_RATE
63
86
 
64
87
  /**
65
88
  * @internal
@@ -101,9 +124,8 @@ export class PlaybackRate extends UICorePlugin {
101
124
  */
102
125
  override get events() {
103
126
  return {
104
- 'click .gear-sub-menu_btn': 'onRateSelect',
105
- 'click .gear-option': 'onShowMenu',
106
- 'click .go-back': 'goBack',
127
+ 'click [data-rate]': 'onSelect',
128
+ 'click #playback-rate-back-button': 'goBack',
107
129
  }
108
130
  }
109
131
 
@@ -111,7 +133,7 @@ export class PlaybackRate extends UICorePlugin {
111
133
  * @internal
112
134
  */
113
135
  override bindEvents() {
114
- this.listenTo(this.core, Events.CORE_READY, this.onCoreReady)
136
+ this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady)
115
137
  this.listenTo(
116
138
  this.core,
117
139
  Events.CORE_ACTIVE_CONTAINER_CHANGED,
@@ -125,11 +147,13 @@ export class PlaybackRate extends UICorePlugin {
125
147
  assert(mediaControl, 'media_control plugin is required')
126
148
  const gear = this.core.getPlugin('bottom_gear') as BottomGear
127
149
  assert(gear, 'bottom_gear plugin is required')
150
+
128
151
  this.listenTo(
129
152
  mediaControl,
130
- MediaControlEvents.MEDIACONTROL_GEAR_RENDERED,
131
- this.onGearRendered,
153
+ Events.MEDIACONTROL_RENDERED,
154
+ this.onMediaControlRendered,
132
155
  )
156
+ this.listenTo(gear, GearEvents.RENDERED, this.onGearRendered)
133
157
  }
134
158
 
135
159
  private onActiveContainerChange() {
@@ -143,47 +167,65 @@ export class PlaybackRate extends UICorePlugin {
143
167
  )
144
168
  this.listenTo(
145
169
  this.core.activeContainer,
146
- Events.CONTAINER_PLAYBACKDVRSTATECHANGED,
147
- this.onDvrStateChanged,
170
+ Events.CONTAINER_LOADEDMETADATA,
171
+ this.onMetaDataLoaded,
148
172
  )
149
173
  }
150
174
 
151
- private onGearRendered() {
152
- trace(`${T} onGearRendered`, {
153
- rendered: this.rendered,
154
- })
155
- this.rendered = false
175
+ private onMediaControlRendered() {
176
+ trace(`${T} onMediaControlRendered`)
156
177
  this.render()
157
178
  }
158
179
 
159
- private onDvrStateChanged(dvrEnabled: boolean) {
160
- trace(`${T} onDvrStateChanged`, {
161
- dvrEnabled,
162
- })
163
- if (dvrEnabled) {
164
- this.render()
165
- }
180
+ private onGearRendered() {
181
+ trace(`${T} onGearRendered`)
182
+ this.addGearItem()
183
+ }
184
+
185
+ private addGearItem() {
186
+ trace(`${T} addGearItem`)
187
+ this.core
188
+ .getPlugin('bottom_gear')
189
+ ?.addItem('rate', this.$el)
190
+ .html(
191
+ $(
192
+ PlaybackRate.buttonTemplate({
193
+ title: this.getTitle(),
194
+ speedIcon,
195
+ arrowRightIcon,
196
+ i18n: this.core.i18n,
197
+ }),
198
+ ),
199
+ )
200
+ }
201
+
202
+ private onMetaDataLoaded() {
203
+ trace(`${T} onMetaDataLoaded`)
204
+ this.render()
166
205
  }
167
206
 
168
207
  private allRateElements(): ZeptoResult {
169
- return this.$('ul.gear-sub-menu li')
208
+ return this.$el.find('#playback-rate-menu li')
170
209
  }
171
210
 
172
- private rateElement(rate = '1'): ZeptoResult {
211
+ private rateElement(rate: number): ZeptoResult {
173
212
  return (
174
- this.$(`ul.gear-sub-menu a[data-rate="${rate}"]`) as ZeptoResult
213
+ this.$el.find(`#playback-rate-menu a[data-rate="${rate}"]`) as ZeptoResult
175
214
  ).parent()
176
215
  }
177
216
 
178
217
  private onPlaybackRateChange(playbackRate: number) {
179
- const selectedRate = parseInt(this.selectedRate, 10)
180
218
  // TODO check it doesn't interfere with the DASH.js or HLS.js playback live catchup
181
- if (Math.abs(playbackRate - selectedRate) > 0.1) {
182
- trace(`${T} onPlaybackRateChange setting target rate`, {
183
- playbackRate,
184
- selectedRate,
185
- })
186
- this.core.activePlayback?.setPlaybackRate(selectedRate)
219
+ if (Math.abs(playbackRate - this.selectedRate) > 0.1) {
220
+ this.core.activePlayback?.setPlaybackRate(this.selectedRate)
221
+ } else {
222
+ trace(
223
+ `${T} onPlaybackRateChange not steering to the selected rate, it is seemingly a catchup algorithm working`,
224
+ {
225
+ playbackRate,
226
+ selectedRate: this.selectedRate,
227
+ },
228
+ )
187
229
  }
188
230
  }
189
231
 
@@ -207,7 +249,6 @@ export class PlaybackRate extends UICorePlugin {
207
249
  */
208
250
  override render() {
209
251
  trace(`${T} render`, {
210
- rendered: this.rendered,
211
252
  shouldRender: this.shouldRender(),
212
253
  })
213
254
 
@@ -215,43 +256,36 @@ export class PlaybackRate extends UICorePlugin {
215
256
  return this
216
257
  }
217
258
 
218
- if (this.rendered) {
219
- return this
220
- }
221
-
222
- const button = PlaybackRate.buttonTemplate({
223
- title: this.getTitle(),
224
- speedIcon,
225
- arrowRightIcon,
226
- i18n: this.core.i18n,
227
- })
228
-
229
- this.$el.html(button)
230
-
231
- ;(this.core.getPlugin('bottom_gear') as BottomGear)
232
- ?.getElement('rate')
233
- ?.html(this.el)
259
+ this.$el.html(
260
+ PlaybackRate.listTemplate({
261
+ arrowLeftIcon,
262
+ checkIcon,
263
+ current: this.selectedRate,
264
+ i18n: this.core.i18n,
265
+ playbackRates: this.playbackRates,
266
+ }),
267
+ )
234
268
 
235
- this.rendered = true
269
+ this.addGearItem()
236
270
 
237
271
  return this
238
272
  }
239
273
 
240
- private onStartAd() {
241
- this.prevSelectedRate = this.selectedRate
242
- this.resetPlaybackRate()
243
- this.listenToOnce(
244
- this.core.activePlayback,
245
- Events.PLAYBACK_PLAY,
246
- this.onFinishAd,
247
- )
248
- }
249
-
250
- private onFinishAd() {
251
- if (this.prevSelectedRate) {
252
- this.setSelectedRate(this.prevSelectedRate)
253
- }
254
- }
274
+ // private onStartAd() {
275
+ // this.prevSelectedRate = this.selectedRate
276
+ // this.resetPlaybackRate()
277
+ // this.listenToOnce(
278
+ // this.core.activePlayback,
279
+ // Events.PLAYBACK_PLAY,
280
+ // this.onFinishAd,
281
+ // )
282
+ // }
283
+
284
+ // private onFinishAd() {
285
+ // if (this.prevSelectedRate) {
286
+ // this.setSelectedRate(this.prevSelectedRate)
287
+ // }
288
+ // }
255
289
 
256
290
  private onPlay() {
257
291
  if (
@@ -270,38 +304,27 @@ export class PlaybackRate extends UICorePlugin {
270
304
 
271
305
  private onStop() {}
272
306
 
273
- private onRateSelect(event: MouseEvent) {
307
+ private onSelect(event: MouseEvent) {
274
308
  event.stopPropagation()
275
- const rate = (event.currentTarget as HTMLElement).dataset.rate
309
+ const rate = parseFloat(
310
+ (event.currentTarget as HTMLElement).dataset.rate || '',
311
+ )
276
312
  if (rate) {
277
313
  this.setSelectedRate(rate)
278
314
  this.highlightCurrentRate()
315
+ this.updateGearOptionLabel()
279
316
  }
280
317
 
281
318
  return false
282
319
  }
283
320
 
284
- private onShowMenu() {
285
- this.$el.html(
286
- PlaybackRate.listTemplate({
287
- playbackRates: this.playbackRates,
288
- arrowLeftIcon,
289
- checkIcon,
290
- i18n: this.core.i18n,
291
- }),
292
- )
293
- ;(this.core.getPlugin('bottom_gear') as BottomGear)?.setContent(this.el)
294
- this.highlightCurrentRate()
295
- }
296
-
297
321
  private goBack() {
298
322
  setTimeout(() => {
299
323
  this.core.getPlugin('bottom_gear').refresh()
300
324
  }, 0)
301
325
  }
302
326
 
303
- private setSelectedRate(rate: string) {
304
- // Set <video playbackRate="..."
327
+ private setSelectedRate(rate: number) {
305
328
  this.core.activePlayback?.setPlaybackRate(rate)
306
329
  this.selectedRate = rate
307
330
  }
@@ -309,7 +332,7 @@ export class PlaybackRate extends UICorePlugin {
309
332
  private getTitle() {
310
333
  return (
311
334
  this.playbackRates.find((r) => r.value === this.selectedRate)?.label ||
312
- this.selectedRate
335
+ `x${this.selectedRate}`
313
336
  )
314
337
  }
315
338
 
@@ -317,9 +340,14 @@ export class PlaybackRate extends UICorePlugin {
317
340
  this.allRateElements().removeClass('current')
318
341
  this.allRateElements().find('a').removeClass('gcore-skin-active')
319
342
 
320
- const currentLevelElement = this.rateElement(this.selectedRate)
343
+ this.rateElement(this.selectedRate)
344
+ .addClass('current')
345
+ .find('a')
346
+ .addClass('gcore-skin-active')
347
+ }
321
348
 
322
- currentLevelElement.addClass('current')
323
- currentLevelElement.find('a').addClass('gcore-skin-active')
349
+ private updateGearOptionLabel() {
350
+ trace(`${T} updateGearOptionLabel`)
351
+ this.addGearItem()
324
352
  }
325
353
  }
@@ -1,23 +1,26 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
2
2
  import { PlaybackRate } from '../PlaybackRate'
3
3
  import {
4
+ createMockBottomGear,
4
5
  createMockCore,
5
6
  createMockMediaControl,
6
- createMockPlugin,
7
7
  } from '../../../testUtils'
8
- import { $ } from '@clappr/core'
9
- import { Logger, LogTracer, setTracer } from '@gcorevideo/utils'
8
+ import { Events } from '@clappr/core'
9
+ import { GearEvents } from '../../bottom-gear/BottomGear'
10
+ // import { Logger, LogTracer, setTracer } from '@gcorevideo/utils'
10
11
 
11
- Logger.enable('*')
12
- setTracer(new LogTracer('PlaybackRate.test'))
12
+ // Logger.enable('*')
13
+ // setTracer(new LogTracer('PlaybackRate.test'))
13
14
 
14
15
  describe('PlaybackRate', () => {
15
16
  let core: any
16
17
  let bottomGear: any
18
+ let mediaControl: any
19
+ let playbackRate: PlaybackRate
17
20
  beforeEach(() => {
18
21
  core = createMockCore()
19
- const mediaControl = createMockMediaControl(core)
20
- bottomGear = createMockGearPlugin()
22
+ mediaControl = createMockMediaControl(core)
23
+ bottomGear = createMockBottomGear(core)
21
24
  core.getPlugin.mockImplementation((name: string) => {
22
25
  if (name === 'bottom_gear') {
23
26
  return bottomGear
@@ -27,39 +30,83 @@ describe('PlaybackRate', () => {
27
30
  }
28
31
  return null
29
32
  })
30
- })
31
- it('should render', () => {
32
- const playbackRate = new PlaybackRate(core)
33
- core.emit('core:ready')
33
+ playbackRate = new PlaybackRate(core)
34
+ core.emit(Events.CORE_READY)
34
35
  core.activePlayback.getPlaybackType.mockReturnValue('live')
35
- core.emit('core:active:container:changed')
36
+ core.activeContainer.getPlaybackType.mockReturnValue('live')
37
+ core.getPlaybackType.mockReturnValue('live')
38
+ core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED)
36
39
  core.activePlayback.dvrEnabled = true
37
- core.activeContainer.emit('container:dvr', true)
40
+ core.activeContainer.isDvrEnabled.mockReturnValue(true)
41
+ core.activeContainer.emit(Events.CONTAINER_LOADEDMETADATA)
42
+ bottomGear.trigger(GearEvents.RENDERED)
43
+ })
44
+ it('should render', () => {
38
45
  expect(playbackRate.el.innerHTML).toMatchSnapshot()
39
- expect(bottomGear.getElement).toHaveBeenCalledWith('rate')
46
+ expect(bottomGear.addItem).toHaveBeenCalledWith('rate', playbackRate.$el)
47
+ expect(
48
+ bottomGear.$el.find('li[data-rate]').text(),
49
+ // @ts-ignore
50
+ ).toMatchPlaybackRateLabel('playback_rate 1x')
51
+ })
52
+ it('should have normal rate initially', () => {
53
+ expect(
54
+ playbackRate.$el.find('[data-rate="1"]').parent().hasClass('current'),
55
+ ).toBe(true)
40
56
  expect(
41
- bottomGear.$el
42
- .find('[data-rate]')
43
- .text()
44
- .replace(/\/assets.*\.svg/g, '')
45
- .replace(/\s+/g, ' ')
46
- .trim(),
47
- ).toEqual('playback_rate 1x')
57
+ playbackRate.$el.find('[data-rate="1"]').hasClass('gcore-skin-active'),
58
+ ).toBe(true)
59
+ })
60
+ describe('on playback rate select', () => {
61
+ describe.each([[2], [1.5], [1.25], [1], [0.75], [0.5]])('%s', (rate) => {
62
+ beforeEach(() => {
63
+ playbackRate.$el.find(`[data-rate="${rate}"]`).click()
64
+ })
65
+ it('should set the selected rate', () => {
66
+ expect(core.activePlayback.setPlaybackRate).toHaveBeenCalledWith(rate)
67
+ })
68
+ it('should highlight the selected rate', () => {
69
+ expect(
70
+ playbackRate.$el
71
+ .find(`[data-rate="${rate}"]`)
72
+ .parent()
73
+ .hasClass('current'),
74
+ ).toBe(true)
75
+ expect(
76
+ playbackRate.$el
77
+ .find(`[data-rate="${rate}"]`)
78
+ .hasClass('gcore-skin-active'),
79
+ ).toBe(true)
80
+ })
81
+ it('should update the gear box option label', () => {
82
+ expect(
83
+ bottomGear.$el.find('#playback-rate-button').text(),
84
+ // @ts-ignore
85
+ ).toMatchPlaybackRateLabel(`playback_rate ${rate}x`)
86
+ })
87
+ })
88
+ })
89
+ describe('on go back', () => {
90
+ beforeEach(async () => {
91
+ playbackRate.$el.find('#playback-rate-back-button').click()
92
+ return new Promise((resolve) => setTimeout(resolve, 0))
93
+ })
94
+ it('should refresh the bottom gear', () => {
95
+ expect(bottomGear.refresh).toHaveBeenCalled()
96
+ })
48
97
  })
49
98
  })
50
99
 
51
- function createMockGearPlugin() {
52
- const elements = {
53
- nerd: $(document.createElement('li')).attr('data-nerd', 'nerd'),
54
- quality: $(document.createElement('li')).attr('data-quality', 'quality'),
55
- rate: $(document.createElement('li')).attr('data-rate', 'rate'),
56
- }
57
- const $el = $(document.createElement('ul'))
58
- $el.append(elements.nerd, elements.quality, elements.rate)
59
- const plugin = Object.assign(createMockPlugin(), {
60
- setContent: vi.fn(),
61
- getElement: vi.fn((name: string) => elements[name]),
62
- $el,
63
- })
64
- return plugin
65
- }
100
+ expect.extend({
101
+ toMatchPlaybackRateLabel(received, expected) {
102
+ const { isNot } = this
103
+ return {
104
+ pass:
105
+ received
106
+ .replace(/\/assets.*\.svg/g, '')
107
+ .replace(/\s+/g, ' ')
108
+ .trim().includes(expected),
109
+ message: () => `${received} does${isNot ? '' : ' not'} match ${expected}`,
110
+ }
111
+ },
112
+ })
@@ -1,11 +1,60 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`PlaybackRate > should render 1`] = `
4
- "<button class="gplayer-lite-btn gcore-skin-text-color gear-option" id="playback-rate-button">
5
- <span class="gear-option_speed-icon">/assets/icons/new/speed.svg</span>
6
- <span class="gear-option_label">playback_rate</span>
7
- <span class="gear-option_arrow-right-icon">/assets/icons/new/arrow-right.svg</span>
8
- <span class="gear-option_value">1x</span>
4
+ "<button class="gplayer-lite-btn go-back gcore-skin-text-color" id="playback-rate-back-button">
5
+ <span class="arrow-left-icon">/assets/icons/new/arrow-left.svg</span>
6
+ playback_rate
9
7
  </button>
10
- "
8
+ <ul class="gear-sub-menu" id="playback-rate-menu">
9
+
10
+ <li>
11
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="0.5">
12
+ <span class="check-icon">/assets/icons/new/check.svg</span>
13
+ 0.5x
14
+ </a>
15
+ </li>
16
+
17
+ <li>
18
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="0.75">
19
+ <span class="check-icon">/assets/icons/new/check.svg</span>
20
+ 0.75x
21
+ </a>
22
+ </li>
23
+
24
+ <li class="current">
25
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-rate="1">
26
+ <span class="check-icon">/assets/icons/new/check.svg</span>
27
+ 1x
28
+ </a>
29
+ </li>
30
+
31
+ <li>
32
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="1.25">
33
+ <span class="check-icon">/assets/icons/new/check.svg</span>
34
+ 1.25x
35
+ </a>
36
+ </li>
37
+
38
+ <li>
39
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="1.5">
40
+ <span class="check-icon">/assets/icons/new/check.svg</span>
41
+ 1.5x
42
+ </a>
43
+ </li>
44
+
45
+ <li>
46
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="1.75">
47
+ <span class="check-icon">/assets/icons/new/check.svg</span>
48
+ 1.75x
49
+ </a>
50
+ </li>
51
+
52
+ <li>
53
+ <a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-rate="2">
54
+ <span class="check-icon">/assets/icons/new/check.svg</span>
55
+ 2x
56
+ </a>
57
+ </li>
58
+
59
+ </ul>"
11
60
  `;
@@ -338,7 +338,7 @@ export class ClosedCaptions extends UICorePlugin {
338
338
  this.resizeFont()
339
339
 
340
340
  this.core.activeContainer.$el.append(this.$line)
341
- mediaControl.putElement('cc', this.el)
341
+ mediaControl.putElement('cc', this.$el)
342
342
 
343
343
  this.updateSelection()
344
344
 
@@ -52,7 +52,7 @@ describe('ClosedCaptions', () => {
52
52
  it('should render', () => {
53
53
  expect(cc.el.innerHTML).toMatchSnapshot()
54
54
  expect(cc.$el.find('[data-cc-button]').length).toEqual(1)
55
- expect(mediaControl.putElement).toHaveBeenCalledWith('cc', cc.el)
55
+ expect(mediaControl.putElement).toHaveBeenCalledWith('cc', cc.$el)
56
56
  })
57
57
  })
58
58
  })
package/src/testUtils.ts CHANGED
@@ -193,3 +193,17 @@ export function createMockMediaControl(core: any) {
193
193
  mediaControl.toggleElement = vi.fn()
194
194
  return mediaControl
195
195
  }
196
+
197
+ export function createMockBottomGear(core: any) {
198
+ const plugin: any = new UICorePlugin(core)
199
+ plugin.getItem = vi.fn()
200
+ plugin.addItem = vi.fn().mockImplementation((name: string, $el: any) => {
201
+ const existing = plugin.$el.find(`[data-${name}]`)
202
+ if (existing.length) {
203
+ return existing
204
+ }
205
+ return $('<li></li>').attr(`data-${name}`, '').append($el).appendTo(plugin.$el)
206
+ })
207
+ plugin.refresh = vi.fn()
208
+ return plugin
209
+ }
@@ -1,6 +1,7 @@
1
1
  import 'vitest'
2
2
 
3
3
  interface CustomMatchers<R = unknown> {
4
+ toMatchPlaybackRateOption: (expected: string) => R
4
5
  toMatchQualityLevelLabel: (expected: string) => R
5
6
  toMatchQualityLevelOption: (expected: string) => R
6
7
  }