@gcorevideo/player 2.20.6 → 2.20.8

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 (65) hide show
  1. package/dist/core.js +37 -13
  2. package/dist/index.css +1163 -1163
  3. package/dist/index.js +2557 -2513
  4. package/dist/plugins/index.css +470 -470
  5. package/dist/plugins/index.js +5230 -5217
  6. package/lib/playback/BasePlayback.d.ts +5 -0
  7. package/lib/playback/BasePlayback.d.ts.map +1 -1
  8. package/lib/playback/BasePlayback.js +8 -0
  9. package/lib/playback/HTML5Video.d.ts +4 -0
  10. package/lib/playback/HTML5Video.d.ts.map +1 -0
  11. package/lib/playback/HTML5Video.js +3 -0
  12. package/lib/playback/dash-playback/DashPlayback.d.ts +1 -0
  13. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  14. package/lib/playback/dash-playback/DashPlayback.js +6 -2
  15. package/lib/playback/index.d.ts.map +1 -1
  16. package/lib/playback/index.js +2 -0
  17. package/lib/playback/types.d.ts +9 -0
  18. package/lib/playback/types.d.ts.map +1 -0
  19. package/lib/playback/types.js +9 -0
  20. package/lib/plugins/bottom-gear/BottomGear.d.ts +6 -11
  21. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  22. package/lib/plugins/bottom-gear/BottomGear.js +9 -21
  23. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.js +2 -2
  24. package/lib/plugins/dvr-controls/DvrControls.d.ts +1 -1
  25. package/lib/plugins/dvr-controls/DvrControls.d.ts.map +1 -1
  26. package/lib/plugins/dvr-controls/DvrControls.js +27 -16
  27. package/lib/plugins/level-selector/LevelSelector.d.ts +17 -5
  28. package/lib/plugins/level-selector/LevelSelector.d.ts.map +1 -1
  29. package/lib/plugins/level-selector/LevelSelector.js +35 -24
  30. package/lib/plugins/media-control/MediaControl.d.ts +11 -0
  31. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  32. package/lib/plugins/media-control/MediaControl.js +16 -3
  33. package/lib/plugins/playback-rate/PlaybackRate.d.ts +11 -10
  34. package/lib/plugins/playback-rate/PlaybackRate.d.ts.map +1 -1
  35. package/lib/plugins/playback-rate/PlaybackRate.js +83 -91
  36. package/lib/plugins/source-controller/SourceController.d.ts +1 -0
  37. package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
  38. package/lib/plugins/source-controller/SourceController.js +8 -4
  39. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +7 -3
  40. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
  41. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +35 -27
  42. package/lib/testUtils.d.ts +5 -8
  43. package/lib/testUtils.d.ts.map +1 -1
  44. package/lib/testUtils.js +15 -9
  45. package/package.json +1 -1
  46. package/src/playback/BasePlayback.ts +12 -4
  47. package/src/playback/HTML5Video.ts +3 -0
  48. package/src/playback/dash-playback/DashPlayback.ts +15 -11
  49. package/src/playback/index.ts +2 -1
  50. package/src/playback/types.ts +9 -0
  51. package/src/plugins/bottom-gear/BottomGear.ts +10 -21
  52. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +36 -0
  53. package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +41 -0
  54. package/src/plugins/clappr-nerd-stats/ClapprNerdStats.ts +3 -3
  55. package/src/plugins/dvr-controls/DvrControls.ts +87 -54
  56. package/src/plugins/level-selector/LevelSelector.ts +64 -31
  57. package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +15 -16
  58. package/src/plugins/media-control/MediaControl.ts +20 -6
  59. package/src/plugins/playback-rate/PlaybackRate.ts +89 -105
  60. package/src/plugins/source-controller/SourceController.ts +9 -4
  61. package/src/plugins/source-controller/__tests__/SourceController.test.ts +35 -1
  62. package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +80 -57
  63. package/src/testUtils.ts +16 -9
  64. package/tsconfig.tsbuildinfo +1 -1
  65. package/assets/playback-rate/playback-rate-selector.ejs +0 -9
@@ -1,5 +1,6 @@
1
1
  import { Events, template, UICorePlugin } from '@clappr/core'
2
2
  import { reportError, trace } from '@gcorevideo/utils'
3
+ import assert from 'assert'
3
4
 
4
5
  import { type QualityLevel } from '../../playback.types.js'
5
6
  import { CLAPPR_VERSION } from '../../build.js'
@@ -14,12 +15,26 @@ import arrowRightIcon from '../../../assets/icons/new/arrow-right.svg'
14
15
  import arrowLeftIcon from '../../../assets/icons/new/arrow-left.svg'
15
16
  import checkIcon from '../../../assets/icons/new/check.svg'
16
17
  import '../../../assets/level-selector/style.scss'
17
- import assert from 'assert'
18
-
18
+ import { MediaControl, MediaControlEvents } from '../media-control/MediaControl.js'
19
19
 
20
20
  const T = 'plugins.level_selector'
21
21
  const VERSION = '2.19.4'
22
22
 
23
+ export interface LevelSelectorPluginSettings {
24
+ /**
25
+ * The maximum resolution to allow in the level selector.
26
+ */
27
+ restrictResolution?: number
28
+ /**
29
+ * The labels to show in the level selector.
30
+ * @example
31
+ * ```ts
32
+ * { 360: 'SD', 720: 'HD' }
33
+ * ```
34
+ */
35
+ labels?: Record<number, string>
36
+ }
37
+
23
38
  /**
24
39
  * A {@link MediaControl | media control} plugin that provides a UI to control the quality level of the playback.
25
40
  * @beta
@@ -35,11 +50,7 @@ const VERSION = '2.19.4'
35
50
  *
36
51
  * When clicked, it shows a list of quality levels to choose from.
37
52
  *
38
- * Configuration options:
39
- *
40
- * - `labels`: The labels to show in the level selector. [video resolution]: string
41
- *
42
- * - `restrictResolution`: The maximum resolution to allow in the level selector.
53
+ * Configuration options - {@link LevelSelectorPluginSettings}
43
54
  *
44
55
  * @example
45
56
  * ```ts
@@ -62,7 +73,8 @@ export class LevelSelector extends UICorePlugin {
62
73
 
63
74
  private isOpen = false
64
75
 
65
- private static readonly buttonTemplate: TemplateFunction = template(buttonHtml)
76
+ private static readonly buttonTemplate: TemplateFunction =
77
+ template(buttonHtml)
66
78
 
67
79
  private static readonly listTemplate: TemplateFunction = template(listHtml)
68
80
 
@@ -113,8 +125,24 @@ export class LevelSelector extends UICorePlugin {
113
125
  * @internal
114
126
  */
115
127
  override bindEvents() {
116
- this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, () => this.bindPlaybackEvents())
117
- this.listenTo(this.core, 'gear:rendered', this.render)
128
+ this.listenTo(this.core, Events.CORE_READY, this.onCoreReady);
129
+ this.listenTo(
130
+ this.core,
131
+ Events.CORE_ACTIVE_CONTAINER_CHANGED,
132
+ this.bindPlaybackEvents,
133
+ )
134
+ }
135
+
136
+ private onCoreReady() {
137
+ trace(`${T} onCoreReady`);
138
+ const mediaControl = this.core.getPlugin('media_control') as MediaControl
139
+ assert(mediaControl, 'media_control plugin is required')
140
+ this.listenTo(mediaControl, MediaControlEvents.MEDIACONTROL_GEAR_RENDERED, this.onGearRendered);
141
+ }
142
+
143
+ private onGearRendered() {
144
+ trace(`${T} onGearRendered`);
145
+ this.deferRender();
118
146
  }
119
147
 
120
148
  private bindPlaybackEvents() {
@@ -123,8 +151,10 @@ export class LevelSelector extends UICorePlugin {
123
151
 
124
152
  const activePlayback = this.core.activePlayback
125
153
 
126
- this.listenTo(activePlayback, Events.PLAYBACK_LEVELS_AVAILABLE, (levels: QualityLevel[]) =>
127
- this.fillLevels(levels),
154
+ this.listenTo(
155
+ activePlayback,
156
+ Events.PLAYBACK_LEVELS_AVAILABLE,
157
+ this.fillLevels,
128
158
  )
129
159
  this.listenTo(
130
160
  activePlayback,
@@ -150,32 +180,27 @@ export class LevelSelector extends UICorePlugin {
150
180
  this.deferRender()
151
181
  },
152
182
  )
153
- if (activePlayback?.levels?.length > 0) {
183
+ if (activePlayback.levels?.length > 0) {
154
184
  this.fillLevels(activePlayback.levels)
155
185
  }
156
186
  }
157
187
 
158
188
  private onStop() {
159
189
  trace(`${T} onStop`)
160
- const currentPlayback = this.core.activePlayback
161
-
162
- this.listenToOnce(currentPlayback, Events.PLAYBACK_PLAY, () => {
163
- trace(`${T} on PLAYBACK_PLAY after stop`, { selectedLevelId: this.selectedLevelId })
164
- if (currentPlayback.getPlaybackType() === 'live') {
190
+ this.listenToOnce(this.core.activePlayback, Events.PLAYBACK_PLAY, () => {
191
+ trace(`${T} on PLAYBACK_PLAY after stop`, {
192
+ selectedLevelId: this.selectedLevelId,
193
+ })
194
+ if (this.core.activePlayback.getPlaybackType() === 'live') {
165
195
  if (this.selectedLevelId !== -1) {
166
- currentPlayback.currentLevel = this.selectedLevelId
196
+ this.core.activePlayback.currentLevel = this.selectedLevelId
167
197
  }
168
198
  }
169
199
  })
170
200
  }
171
201
 
172
202
  private shouldRender() {
173
- if (!this.core.activeContainer) {
174
- return false
175
- }
176
-
177
203
  const activePlayback = this.core.activePlayback
178
-
179
204
  if (!activePlayback) {
180
205
  return false
181
206
  }
@@ -192,8 +217,6 @@ export class LevelSelector extends UICorePlugin {
192
217
  * @internal
193
218
  */
194
219
  override render() {
195
- assert(this.core.getPlugin('bottom_gear'), 'bottom_gear plugin is required')
196
-
197
220
  if (!this.shouldRender()) {
198
221
  return this
199
222
  }
@@ -213,7 +236,10 @@ export class LevelSelector extends UICorePlugin {
213
236
  })
214
237
  this.$el.html(html)
215
238
  const gear = this.core.getPlugin('bottom_gear') as BottomGear
216
- gear.getElement('quality')?.html(this.el)
239
+ if (!gear) {
240
+ trace(`${T} renderButton: bottom_gear plugin not found`)
241
+ }
242
+ gear?.getElement('quality')?.html(this.el)
217
243
  }
218
244
  }
219
245
 
@@ -228,6 +254,7 @@ export class LevelSelector extends UICorePlugin {
228
254
  })
229
255
  this.$el.html(html)
230
256
  const gear = this.core.getPlugin('bottom_gear') as BottomGear
257
+ trace(`${T} renderDropdown: bottom_gear plugin not found`)
231
258
  gear?.setContent(this.el)
232
259
  }
233
260
 
@@ -236,7 +263,8 @@ export class LevelSelector extends UICorePlugin {
236
263
  return maxRes
237
264
  ? this.levels.findIndex(
238
265
  (level) =>
239
- (level.height > level.width ? level.width : level.height) === maxRes,
266
+ (level.height > level.width ? level.width : level.height) ===
267
+ maxRes,
240
268
  )
241
269
  : -1
242
270
  }
@@ -248,7 +276,11 @@ export class LevelSelector extends UICorePlugin {
248
276
  if (maxResolution) {
249
277
  this.removeAuto = true
250
278
  const initialLevel = levels
251
- .filter((level) => (level.width > level.height ? level.height : level.width) <= maxResolution)
279
+ .filter(
280
+ (level) =>
281
+ (level.width > level.height ? level.height : level.width) <=
282
+ maxResolution,
283
+ )
252
284
  .pop()
253
285
  this.setLevel(initialLevel?.level ?? 0)
254
286
  }
@@ -280,8 +312,9 @@ export class LevelSelector extends UICorePlugin {
280
312
  private goBack() {
281
313
  trace(`${T} goBack`)
282
314
  this.isOpen = false
283
- this.core.trigger('gear:refresh')
284
- this.deferRender()
315
+ setTimeout(() => {
316
+ this.core.getPlugin('bottom_gear').refresh()
317
+ }, 0);
285
318
  }
286
319
 
287
320
  private setLevel(index: number) {
@@ -1,9 +1,14 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { UICorePlugin } from '@clappr/core'
2
+ import { $, UICorePlugin } from '@clappr/core'
3
3
  import FakeTimers from '@sinonjs/fake-timers'
4
4
  import { Logger, LogTracer, setTracer } from '@gcorevideo/utils'
5
5
  import { LevelSelector } from '../LevelSelector.js'
6
- import { createMockCore, createMockPlayback } from '../../../testUtils.js'
6
+ import {
7
+ createMockCore,
8
+ createMockMediaControl,
9
+ createMockPlayback,
10
+ } from '../../../testUtils.js'
11
+ import { MediaControlEvents } from '../../media-control/MediaControl.js'
7
12
 
8
13
  setTracer(new LogTracer('LevelSelector.test'))
9
14
  Logger.enable('*')
@@ -33,6 +38,8 @@ describe('LevelSelector', () => {
33
38
  let core: any
34
39
  let levelSelector: LevelSelector
35
40
  let activePlayback: any
41
+ let mediaControl: UICorePlugin
42
+ let bottomGear: UICorePlugin | null
36
43
  beforeEach(() => {
37
44
  clock = FakeTimers.install()
38
45
  })
@@ -41,10 +48,6 @@ describe('LevelSelector', () => {
41
48
  })
42
49
  describe('basically', () => {
43
50
  beforeEach(() => {
44
- // const activeContainer = createMockContainer()
45
- let mediaControl: UICorePlugin | null = null
46
- let bottomGear: UICorePlugin | null = null
47
- // TODO create mock core
48
51
  core = createMockCore({
49
52
  levelSelector: {
50
53
  // restrictResolution: 360,
@@ -61,7 +64,7 @@ describe('LevelSelector', () => {
61
64
  }
62
65
  return null
63
66
  })
64
- mediaControl = createMediaControl(core)
67
+ mediaControl = createMockMediaControl(core)
65
68
  bottomGear = createBottomGear(core)
66
69
  levelSelector = new LevelSelector(core)
67
70
  })
@@ -126,7 +129,7 @@ describe('LevelSelector', () => {
126
129
  }
127
130
  return null
128
131
  })
129
- mediaControl = createMediaControl(core)
132
+ mediaControl = createMockMediaControl(core)
130
133
  bottomGear = createBottomGear(core)
131
134
  levelSelector = new LevelSelector(core)
132
135
  })
@@ -219,17 +222,13 @@ expect.extend({
219
222
  },
220
223
  })
221
224
 
222
- function createMediaControl(core: any) {
223
- const mediaControl = new UICorePlugin(core)
224
- // @ts-ignore
225
- mediaControl.getElement = vi.fn().mockReturnValue(null)
226
- return mediaControl
227
- }
228
-
229
225
  function createBottomGear(core: any) {
230
226
  const bottomGear = new UICorePlugin(core)
227
+ const elemets = {
228
+ quality: $(document.createElement('div')),
229
+ }
231
230
  // @ts-ignore
232
- bottomGear.getElement = vi.fn().mockReturnValue(null)
231
+ bottomGear.getElement = vi.fn().mockImplementation((name) => elemets[name])
233
232
  // @ts-ignore
234
233
  bottomGear.setContent = vi.fn()
235
234
  return bottomGear
@@ -49,6 +49,17 @@ export type MediaControlElement =
49
49
  | 'seekBarContainer'
50
50
  | 'subtitlesSelector'
51
51
 
52
+ /**
53
+ * Custom events emitted by the plugins to communicate with one another
54
+ * @beta
55
+ */
56
+ export enum MediaControlEvents {
57
+ /**
58
+ * Emitted when the gear menu is rendered
59
+ */
60
+ MEDIACONTROL_GEAR_RENDERED = 'mediacontrol:gear:rendered',
61
+ }
62
+
52
63
  const T = 'plugins.media_control'
53
64
 
54
65
  const LEFT_ORDER = [
@@ -96,7 +107,7 @@ export class MediaControl extends UICorePlugin {
96
107
 
97
108
  private currentDurationValue: number = 0
98
109
  private currentPositionValue: number = 0
99
- private currentSeekBarPercentage: number | null = null
110
+ private currentSeekBarPercentage = 0
100
111
 
101
112
  private disabledClickableList: DisabledClickable[] = []
102
113
  private displayedDuration: string | null = null
@@ -264,6 +275,10 @@ export class MediaControl extends UICorePlugin {
264
275
  }
265
276
  }
266
277
 
278
+ get currentSeekPos() {
279
+ return this.currentSeekBarPercentage
280
+ }
281
+
267
282
  /**
268
283
  * Current volume [0..100]
269
284
  */
@@ -735,11 +750,10 @@ export class MediaControl extends UICorePlugin {
735
750
  this.changeTogglePlay()
736
751
  this.bindContainerEvents()
737
752
  this.settingsUpdate()
738
- this.core.activeContainer &&
739
- this.core.activeContainer.trigger(
740
- Events.CONTAINER_PLAYBACKDVRSTATECHANGED,
741
- this.core.activeContainer.isDvrInUse(),
742
- )
753
+ this.core.activeContainer.trigger(
754
+ Events.CONTAINER_PLAYBACKDVRSTATECHANGED,
755
+ this.core.activeContainer.isDvrInUse(),
756
+ )
743
757
  // TODO test
744
758
  if (this.core.activeContainer.mediaControlDisabled) {
745
759
  this.disable()
@@ -1,15 +1,19 @@
1
- import { Events, UICorePlugin, Playback, template } from '@clappr/core';
1
+ import { Events, UICorePlugin, Playback, template, Core } from '@clappr/core';
2
+ import { trace } from '@gcorevideo/utils';
3
+ import assert from 'assert';
2
4
 
3
5
  import { CLAPPR_VERSION } from '../../build.js';
4
6
  import type { ZeptoResult } from '../../utils/types.js';
5
7
 
6
- import pluginHtml from '../../../assets/playback-rate/playback-rate-selector.ejs';
7
8
  import buttonHtml from '../../../assets/playback-rate/button.ejs';
8
9
  import listHtml from '../../../assets/playback-rate/list.ejs';
9
10
  import speedIcon from '../../../assets/icons/new/speed.svg';
10
11
  import arrowRightIcon from '../../../assets/icons/new/arrow-right.svg';
11
12
  import arrowLeftIcon from '../../../assets/icons/new/arrow-left.svg';
12
13
  import checkIcon from '../../../assets/icons/new/check.svg';
14
+ import { BottomGear } from '../bottom-gear/BottomGear.js';
15
+ import { PlaybackEvents } from '../../playback/types.js';
16
+ import { MediaControl, MediaControlEvents } from '../media-control/MediaControl.js';
13
17
 
14
18
  type PlaybackRateOption = {
15
19
  value: string;
@@ -28,11 +32,10 @@ const DEFAULT_PLAYBACK_RATES = [
28
32
 
29
33
  const DEFAULT_PLAYBACK_RATE = '1.0';
30
34
 
31
- // TODO
32
- const MEDIACONTROL_PLAYBACKRATE = 'playbackRate';
35
+ const T = 'plugins.playback_rate';
33
36
 
34
37
  /**
35
- * Allows changing the playback speed of the video.
38
+ * PLUGIN that allows changing the playback speed of the video.
36
39
  * @beta
37
40
  *
38
41
  * @remarks
@@ -42,15 +45,16 @@ const MEDIACONTROL_PLAYBACKRATE = 'playbackRate';
42
45
  *
43
46
  * - {@link BottomGear | bottom_gear}
44
47
  *
45
- * It renders a button in the gear menu, which opens a dropdown with the available playback rates.
48
+ * It renders a button in the gear menu, which opens a dropdown with the options to change the playback rate.
46
49
  */
47
50
  export class PlaybackRate extends UICorePlugin {
48
- private currentPlayback: Playback | null = null;
49
-
50
51
  private playbackRates: PlaybackRateOption[] = DEFAULT_PLAYBACK_RATES;
51
52
 
53
+ // Saved when an ad starts to restore after it finishes
52
54
  private prevSelectedRate: string | undefined;
53
55
 
56
+ private rendered = false;
57
+
54
58
  private selectedRate: string = DEFAULT_PLAYBACK_RATE;
55
59
 
56
60
  /**
@@ -67,12 +71,16 @@ export class PlaybackRate extends UICorePlugin {
67
71
  return { min: CLAPPR_VERSION };
68
72
  }
69
73
 
70
- private static readonly template = template(pluginHtml);
71
-
72
74
  private static readonly buttonTemplate = template(buttonHtml);
73
75
 
74
76
  private static readonly listTemplate = template(listHtml);
75
77
 
78
+ constructor(core: Core) {
79
+ super(core);
80
+ this.playbackRates = core.options.playbackRate?.options || DEFAULT_PLAYBACK_RATES;
81
+ this.selectedRate = core.options.playbackRate?.defaultValue || DEFAULT_PLAYBACK_RATE;
82
+ }
83
+
76
84
  /**
77
85
  * @internal
78
86
  */
@@ -98,31 +106,40 @@ export class PlaybackRate extends UICorePlugin {
98
106
  * @internal
99
107
  */
100
108
  override bindEvents() {
101
- this.listenTo(this.core, 'gear:rendered', this.render);
102
- // TODO this.core.getPlugin('media_control'), bottom_gear
103
- this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.reload);
104
- this.listenTo(this.core.mediaControl, MEDIACONTROL_PLAYBACKRATE, this.updatePlaybackRate);
105
-
106
- this.listenTo(this.core, 'core:advertisement:start', this.onStartAd);
107
- this.listenTo(this.core, 'core:advertisement:finish', this.onFinishAd);
108
- if (this.core.activeContainer) {
109
- this.listenTo(this.core.activePlayback, Events.PLAYBACK_BUFFERFULL, this.updateLiveStatus);
110
- }
109
+ this.listenTo(this.core, Events.CORE_READY, this.onCoreReady);
110
+ this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChange);
111
+ }
111
112
 
112
- if (this.currentPlayback) {
113
- this.listenTo(this.currentPlayback, Events.PLAYBACK_STOP, this.onStop);
114
- this.listenTo(this.currentPlayback, Events.PLAYBACK_PLAY, this.onPlay);
113
+ private onCoreReady() {
114
+ const mediaControl = this.core.getPlugin('media_control');
115
+ assert(mediaControl, 'media_control plugin is required');
116
+ const gear = this.core.getPlugin('bottom_gear') as BottomGear;
117
+ assert(gear, 'bottom_gear plugin is required');
118
+ this.listenTo(mediaControl, MediaControlEvents.MEDIACONTROL_GEAR_RENDERED, this.onGearRendered);
119
+ }
115
120
 
116
- // TODO import dash playback events
117
- this.listenTo(this.currentPlayback, 'dash:playback-rate-changed', this.onDashRateChange);
118
- }
121
+ private onActiveContainerChange() {
122
+ this.listenTo(this.core.activePlayback, Events.PLAYBACK_STOP, this.onStop);
123
+ this.listenTo(this.core.activePlayback, Events.PLAYBACK_PLAY, this.onPlay);
124
+ this.listenTo(this.core.activePlayback, PlaybackEvents.PLAYBACK_RATE_CHANGED, this.onPlaybackRateChange);
125
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_PLAYBACKDVRSTATECHANGED, this.onDvrStateChanged);
119
126
  }
120
127
 
121
- private unBindEvents() {
122
- this.stopListening(this.core, 'gear:rendered', this.render);
123
- this.stopListening(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this.reload);
124
- this.stopListening(this.core, 'core:advertisement:start', this.onStartAd);
125
- this.stopListening(this.core, 'core:advertisement:finish', this.onFinishAd);
128
+ private onGearRendered() {
129
+ trace(`${T} onGearRendered`, {
130
+ rendered: this.rendered,
131
+ });
132
+ this.rendered = false;
133
+ this.render();
134
+ }
135
+
136
+ private onDvrStateChanged(dvrEnabled: boolean) {
137
+ trace(`${T} onDvrStateChanged`, {
138
+ dvrEnabled,
139
+ })
140
+ if (dvrEnabled) {
141
+ this.render();
142
+ }
126
143
  }
127
144
 
128
145
  private allRateElements(): ZeptoResult {
@@ -133,86 +150,65 @@ export class PlaybackRate extends UICorePlugin {
133
150
  return (this.$(`ul.gear-sub-menu a[data-rate="${rate}"]`) as ZeptoResult).parent();
134
151
  }
135
152
 
136
- private onDashRateChange() {
137
- // TODO consider removing
138
- ((this.currentPlayback as any)._dash as any)?.setPlaybackRate(this.selectedRate);
139
- }
140
-
141
- private updateLiveStatus() {
142
- if (this.core.getPlaybackType() === Playback.LIVE) {
143
- if (this.core.mediaControl.currentSeekBarPercentage <= 98.9) {
144
- this.core.mediaControl.$playbackRate.removeClass('playbackrate-enable');
145
- this.core.mediaControl.$el.addClass('dvr');
146
-
147
- return;
148
- }
149
- this.updatePlaybackRate(DEFAULT_PLAYBACK_RATE);
150
- this.core.mediaControl.$playbackRate.addClass('playbackrate-enable');
151
- this.core.mediaControl.$el.removeClass('dvr');
153
+ private onPlaybackRateChange(playbackRate: number) {
154
+ const selectedRate = parseInt(this.selectedRate, 10);
155
+ if (playbackRate !== selectedRate) {
156
+ trace(`${T} onPlaybackRateChange setting target rate`, {
157
+ playbackRate,
158
+ selectedRate,
159
+ })
160
+ this.core.activePlayback?.setPlaybackRate(selectedRate);
152
161
  }
153
162
  }
154
163
 
155
- private reload() {
156
- this.unBindEvents();
157
- this.bindEvents();
158
- }
159
-
160
164
  private shouldRender() {
161
165
  if (!this.core.activeContainer) {
162
166
  return false;
163
167
  }
164
168
 
165
- this.currentPlayback = this.core.activePlayback;
169
+ if (this.core.getPlaybackType() === Playback.LIVE && !this.core.activePlayback.dvrEnabled) {
170
+ return false;
171
+ }
166
172
 
167
- return !(this.currentPlayback?.tagName !== 'video' && this.currentPlayback?.tagName !== 'audio');
173
+ return 'setPlaybackRate' in this.core.activePlayback;
168
174
  }
169
175
 
170
176
  /**
171
177
  * @internal
172
178
  */
173
179
  override render() {
174
- const container = this.core.activeContainer;
180
+ trace(`${T} render`, {
181
+ rendered: this.rendered,
182
+ shouldRender: this.shouldRender(),
183
+ })
175
184
 
176
- if (this.core.getPlaybackType() === Playback.LIVE && !container.isDvrEnabled()) {
185
+ if (!this.shouldRender()) {
177
186
  return this;
178
187
  }
179
- const cfg = this.core.options.playbackRateConfig || {};
180
188
 
181
- if (!this.playbackRates) {
182
- this.playbackRates = cfg.options || DEFAULT_PLAYBACK_RATES;
183
- }
184
-
185
- if (!this.selectedRate) {
186
- this.selectedRate = cfg.defaultValue || DEFAULT_PLAYBACK_RATE;
189
+ if (this.rendered) {
190
+ return this;
187
191
  }
188
192
 
189
- if (this.shouldRender()) {
190
- const button = PlaybackRate.buttonTemplate({
191
- title: this.getTitle(),
192
- speedIcon,
193
- arrowRightIcon,
194
- });
195
-
196
- this.$el.html(button);
197
-
198
- // if (this.core.getPlaybackType() === Playback.LIVE) {
199
- // this.core.mediaControl.$playbackRate.addClass('playbackrate-enable');
200
- // }
193
+ const button = PlaybackRate.buttonTemplate({
194
+ title: this.getTitle(),
195
+ speedIcon,
196
+ arrowRightIcon,
197
+ });
201
198
 
202
- // this.core.mediaControl.$playbackRate.append(this.el);
199
+ this.$el.html(button);
203
200
 
204
- this.core.mediaControl.$el?.find('.gear-options-list [data-rate]').html(this.el);
201
+ (this.core.getPlugin('bottom_gear') as BottomGear)?.getElement('rate')?.html(this.el);
205
202
 
206
- // this.updateText();
207
- }
203
+ this.rendered = true;
208
204
 
209
205
  return this;
210
206
  }
211
207
 
212
208
  private onStartAd() {
213
209
  this.prevSelectedRate = this.selectedRate;
214
- this.setSelectedRate('1.0');
215
- this.listenToOnce(this.currentPlayback, Events.PLAYBACK_PLAY, this.onFinishAd);
210
+ this.resetPlaybackRate();
211
+ this.listenToOnce(this.core.activePlayback, Events.PLAYBACK_PLAY, this.onFinishAd);
216
212
  }
217
213
 
218
214
  private onFinishAd() {
@@ -222,16 +218,17 @@ export class PlaybackRate extends UICorePlugin {
222
218
  }
223
219
 
224
220
  private onPlay() {
225
- if (!this.core.mediaControl.$el.hasClass('dvr')) {
226
- if (this.core.getPlaybackType() === Playback.LIVE) {
227
- this.updatePlaybackRate(DEFAULT_PLAYBACK_RATE);
228
- this.core.mediaControl.$playbackRate.addClass('playbackrate-enable');
229
- }
221
+ if (this.core.getPlaybackType() === Playback.LIVE && !this.core.activePlayback.dvrEnabled) {
222
+ this.resetPlaybackRate();
230
223
  } else {
231
224
  this.setSelectedRate(this.selectedRate);
232
225
  }
233
226
  }
234
227
 
228
+ private resetPlaybackRate() {
229
+ this.setSelectedRate(DEFAULT_PLAYBACK_RATE);
230
+ }
231
+
235
232
  private onStop() {
236
233
  }
237
234
 
@@ -252,37 +249,24 @@ export class PlaybackRate extends UICorePlugin {
252
249
  arrowLeftIcon,
253
250
  checkIcon,
254
251
  }));
255
-
256
- this.core.mediaControl.$el?.find('.gear-wrapper').html(this.el);
252
+ (this.core.getPlugin('bottom_gear') as BottomGear)?.setContent(this.el);
257
253
  this.highlightCurrentRate();
258
254
  }
259
255
 
260
256
  private goBack() {
261
- this.core.trigger('gear:refresh');
262
- }
263
-
264
- private updatePlaybackRate(rate: string) {
265
- this.setSelectedRate(rate);
257
+ setTimeout(() => {
258
+ this.core.getPlugin('bottom_gear').refresh()
259
+ }, 0);
266
260
  }
267
261
 
268
262
  private setSelectedRate(rate: string) {
269
263
  // Set <video playbackRate="..."
270
- this.core.$el.find('video,audio').get(0).playbackRate = rate;
264
+ this.core.activePlayback?.setPlaybackRate(rate);
271
265
  this.selectedRate = rate;
272
- // TODO
273
- // Player.player.trigger('playbackRateChanged', rate);
274
266
  }
275
267
 
276
268
  private getTitle() {
277
- let title = this.selectedRate;
278
-
279
- this.playbackRates.forEach((r) => {
280
- if (r.value === this.selectedRate) {
281
- title = r.label;
282
- }
283
- });
284
-
285
- return title;
269
+ return this.playbackRates.find((r) => r.value === this.selectedRate)?.label || this.selectedRate;
286
270
  }
287
271
 
288
272
  private highlightCurrentRate() {