@gcorevideo/player 2.21.6 → 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 (71) 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/media-control/media-control.ejs +1 -1
  10. package/assets/playback-rate/list.ejs +5 -5
  11. package/dist/core.js +1 -2
  12. package/dist/index.css +652 -651
  13. package/dist/index.js +3850 -3766
  14. package/dist/player.d.ts +10 -17
  15. package/dist/plugins/index.css +742 -741
  16. package/dist/plugins/index.js +3951 -3868
  17. package/docs/api/player.mediacontrol.md +8 -36
  18. package/docs/api/player.mediacontrol.toggleelement.md +72 -0
  19. package/docs/api/player.mediacontrolelement.md +1 -1
  20. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  21. package/lib/playback/dash-playback/DashPlayback.js +0 -1
  22. package/lib/plugins/bottom-gear/BottomGear.d.ts +65 -14
  23. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  24. package/lib/plugins/bottom-gear/BottomGear.js +113 -37
  25. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts +2 -3
  26. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.d.ts.map +1 -1
  27. package/lib/plugins/clappr-nerd-stats/ClapprNerdStats.js +18 -15
  28. package/lib/plugins/dvr-controls/DvrControls.js +1 -1
  29. package/lib/plugins/level-selector/LevelSelector.d.ts +8 -11
  30. package/lib/plugins/level-selector/LevelSelector.d.ts.map +1 -1
  31. package/lib/plugins/level-selector/LevelSelector.js +66 -102
  32. package/lib/plugins/media-control/MediaControl.d.ts +7 -5
  33. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  34. package/lib/plugins/media-control/MediaControl.js +40 -20
  35. package/lib/plugins/picture-in-picture/PictureInPicture.d.ts.map +1 -1
  36. package/lib/plugins/picture-in-picture/PictureInPicture.js +7 -2
  37. package/lib/plugins/playback-rate/PlaybackRate.d.ts +42 -14
  38. package/lib/plugins/playback-rate/PlaybackRate.d.ts.map +1 -1
  39. package/lib/plugins/playback-rate/PlaybackRate.js +101 -83
  40. package/lib/plugins/subtitles/ClosedCaptions.js +1 -1
  41. package/lib/testUtils.d.ts +1 -0
  42. package/lib/testUtils.d.ts.map +1 -1
  43. package/lib/testUtils.js +13 -0
  44. package/package.json +1 -1
  45. package/src/playback/dash-playback/DashPlayback.ts +0 -1
  46. package/src/plugins/bottom-gear/BottomGear.ts +162 -72
  47. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +21 -5
  48. package/src/plugins/bottom-gear/__tests__/__snapshots__/BottomGear.test.ts.snap +5 -12
  49. package/src/plugins/clappr-nerd-stats/ClapprNerdStats.ts +27 -25
  50. package/src/plugins/dvr-controls/DvrControls.ts +1 -1
  51. package/src/plugins/dvr-controls/__tests__/DvrControls.test.ts +6 -4
  52. package/src/plugins/dvr-controls/__tests__/__snapshots__/DvrControls.test.ts.snap +2 -2
  53. package/src/plugins/level-selector/LevelSelector.ts +80 -120
  54. package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +69 -79
  55. package/src/plugins/level-selector/__tests__/__snapshots__/LevelSelector.test.ts.snap +38 -71
  56. package/src/plugins/media-control/MediaControl.ts +54 -26
  57. package/src/plugins/media-control/__tests__/MediaControl.test.ts +4 -4
  58. package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +4 -0
  59. package/src/plugins/picture-in-picture/PictureInPicture.ts +7 -2
  60. package/src/plugins/playback-rate/PlaybackRate.ts +136 -108
  61. package/src/plugins/playback-rate/__tests__/PlaybackRate.test.ts +84 -37
  62. package/src/plugins/playback-rate/__tests__/__snapshots__/PlaybackRate.test.ts.snap +55 -6
  63. package/src/plugins/subtitles/ClosedCaptions.ts +1 -1
  64. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +1 -1
  65. package/src/testUtils.ts +14 -0
  66. package/src/typings/vitest.d.ts +1 -0
  67. package/temp/player.api.json +66 -94
  68. package/tsconfig.tsbuildinfo +1 -1
  69. package/docs/api/player.mediacontrol.getcenterpanel.md +0 -18
  70. package/docs/api/player.mediacontrol.getleftpanel.md +0 -22
  71. package/docs/api/player.mediacontrol.getrightpanel.md +0 -22
@@ -1,31 +1,36 @@
1
- import { UICorePlugin, template, Events as ClapprEvents } from '@clappr/core';
2
- import { trace } from '@gcorevideo/utils';
3
- import assert from 'assert';
1
+ import { UICorePlugin, template, Events as ClapprEvents, $ } from '@clappr/core'
2
+ import { trace } from '@gcorevideo/utils'
3
+ import assert from 'assert'
4
4
 
5
- import { CLAPPR_VERSION } from '../../build.js';
5
+ import { CLAPPR_VERSION } from '../../build.js'
6
6
 
7
- import pluginHtml from '../../../assets/bottom-gear/bottomgear.ejs';
8
- import '../../../assets/bottom-gear/gear.scss';
9
- import '../../../assets/bottom-gear/gear-sub-menu.scss';
10
- import gearIcon from '../../../assets/icons/new/gear.svg';
11
- import gearHdIcon from '../../../assets/icons/new/gear-hd.svg';
12
- import { ZeptoResult } from '../../types.js';
13
- import { MediaControlEvents } from '../media-control/MediaControl';
7
+ import pluginHtml from '../../../assets/bottom-gear/bottomgear.ejs'
8
+ import '../../../assets/bottom-gear/gear.scss'
9
+ import '../../../assets/bottom-gear/gear-sub-menu.scss'
10
+ import gearIcon from '../../../assets/icons/new/gear.svg'
11
+ import gearHdIcon from '../../../assets/icons/new/gear-hd.svg'
12
+ import { ZeptoResult } from '../../types.js'
13
+ import { MediaControlEvents } from '../media-control/MediaControl'
14
14
 
15
- const VERSION = '2.19.12';
15
+ const VERSION = '2.19.12'
16
16
 
17
- const T = 'plugins.bottom_gear';
17
+ const T = 'plugins.bottom_gear'
18
+
19
+ export enum GearEvents {
20
+ RENDERED = 'rendered',
21
+ }
18
22
 
19
23
  /**
20
24
  * An element inside the gear menu
21
25
  * @beta
26
+ * @deprecated
22
27
  */
23
- export type GearOptionsItem = 'quality' | 'rate' | 'nerd';
28
+ export type GearOptionsItem = 'quality' | 'rate' | 'nerd'
24
29
 
25
30
  /**
26
31
  * @deprecated Use {@link GearOptionsItem} instead
27
32
  */
28
- export type GearItemElement = GearOptionsItem;
33
+ export type GearItemElement = GearOptionsItem
29
34
 
30
35
  // TODO disabled if no items added
31
36
 
@@ -34,33 +39,90 @@ export type GearItemElement = GearOptionsItem;
34
39
  * @beta
35
40
  * @remarks
36
41
  * The plugins provides a base for attaching custom settings UI in the gear menu
37
- *
42
+ *
38
43
  * Depends on:
39
44
  *
40
45
  * - {@link MediaControl}
46
+ *
47
+ * @example
48
+ * You can use bottom gear to add custom settings UI to the gear menu.
49
+ *
50
+ * ```ts
51
+ * import { BottomGear } from '@gcorevideo/player/plugins/bottom-gear';
52
+ *
53
+ * class CustomOptionsPlugin extends UICorePlugin {
54
+ * // ...
55
+ *
56
+ * override get events() {
57
+ * return {
58
+ * 'click #my-button': 'doMyAction',
59
+ * }
60
+ * }
61
+ *
62
+ * private doMyAction() {
63
+ * // ...
64
+ * }
65
+ *
66
+ * override render() {
67
+ * const bottomGear = this.core.getPlugin('bottom_gear');
68
+ * if (!bottomGear) {
69
+ * return this;
70
+ * }
71
+ * this.$el.html('<button class="custom-option">Custom option</button>');
72
+ * // Put rendered element into the gear menu
73
+ * bottomGear.addItem('custom').html(this.$el)
74
+ * return this;
75
+ * }
76
+ *
77
+ * // alternatively, add an option with a submenu
78
+ * override render() {
79
+ * this.$el.html(template(templateHtml)({
80
+ * // ...
81
+ * })));
82
+ * return this;
83
+ * }
84
+ *
85
+ * private addGearOption() {
86
+ * this.core.getPlugin('bottom_gear')
87
+ * .addItem('custom', this.$el)
88
+ * .html($('<button class="custom-option">Custom option</button>'))
89
+ * }
90
+ *
91
+ * override bindEvents() {
92
+ * this.listenToOnce(this.core, ClapprEvents.CORE_READY, () => {
93
+ * const bottomGear = this.core.getPlugin('bottom_gear');
94
+ * assert(bottomGear, 'bottom_gear plugin is required');
95
+ * // simple case
96
+ * this.listenTo(bottomGear, GearEvents.RENDERED, this.render);
97
+ * // or with a submenu
98
+ * this.listenTo(bottomGear, GearEvents.RENDERED, this.addGearOption);
99
+ * });
100
+ * }
101
+ * }
102
+ * ```
41
103
  */
42
104
  export class BottomGear extends UICorePlugin {
43
- private isHd = false;
105
+ private isHd = false
44
106
 
45
107
  /**
46
108
  * @internal
47
109
  */
48
110
  get name() {
49
- return 'bottom_gear';
111
+ return 'bottom_gear'
50
112
  }
51
113
 
52
114
  /**
53
115
  * @internal
54
116
  */
55
117
  get supportedVersion() {
56
- return { min: CLAPPR_VERSION };
118
+ return { min: CLAPPR_VERSION }
57
119
  }
58
120
 
59
121
  /**
60
122
  * @internal
61
123
  */
62
124
  static get version() {
63
- return VERSION;
125
+ return VERSION
64
126
  }
65
127
 
66
128
  private static readonly template = template(pluginHtml)
@@ -70,8 +132,8 @@ export class BottomGear extends UICorePlugin {
70
132
  */
71
133
  override get attributes() {
72
134
  return {
73
- 'class': 'media-control-gear',
74
- };
135
+ class: 'media-control-gear',
136
+ }
75
137
  }
76
138
 
77
139
  /**
@@ -79,94 +141,122 @@ export class BottomGear extends UICorePlugin {
79
141
  */
80
142
  override get events() {
81
143
  return {
82
- 'click .button-gear': 'toggleGearMenu',
83
- };
144
+ 'click #gear-button': 'toggleGearMenu',
145
+ }
84
146
  }
85
147
 
86
148
  /**
87
149
  * @internal
88
150
  */
89
151
  override bindEvents() {
90
- this.listenTo(this.core, ClapprEvents.CORE_READY, this.onCoreReady)
91
- this.listenTo(this.core, ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED, this.onActiveContainerChanged);
152
+ this.listenToOnce(this.core, ClapprEvents.CORE_READY, this.onCoreReady)
153
+ this.listenTo(
154
+ this.core,
155
+ ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
156
+ this.onActiveContainerChanged,
157
+ )
92
158
  }
93
159
 
94
- /**
95
- * @param name - Name of a gear menu placeholder item to attach custom UI
96
- * @returns Zepto result of the element
97
- */
98
- getElement(name: GearOptionsItem): ZeptoResult | null {
99
- return this.$el.find(`.gear-options-list [data-${name}]`);
100
- }
101
-
102
- // TODO implement putElement/addElement method
103
-
104
- /**
105
- * Replaces the content of the gear menu
106
- * @param content - Zepto result of the element
107
- */
108
- setContent(content: ZeptoResult) {
109
- this.$el.find('.gear-wrapper').html(content);
160
+ addItem(name: string, $subMenu?: ZeptoResult): ZeptoResult {
161
+ const $existingItem = this.$el.find(`#gear-options li[data-${name}`)
162
+ if ($existingItem.length) {
163
+ trace(`${T} addItem already exists`, { name })
164
+ return $existingItem
165
+ }
166
+ const $item = $('<li></li>')
167
+ .attr(`data-${name}`, '')
168
+ .appendTo(this.$el.find('#gear-options'))
169
+ if ($subMenu) {
170
+ trace(`${T} addItem adding submenu`, { name })
171
+ $subMenu
172
+ .addClass('gear-sub-menu-wrapper')
173
+ .hide()
174
+ .appendTo(this.$el.find('#gear-options-wrapper'))
175
+ $item.on('click', (e: MouseEvent) => {
176
+ trace(`${T} addItem submenu clicked`, { name })
177
+ e.stopPropagation()
178
+ $subMenu.show()
179
+ this.$el.find('#gear-options').hide()
180
+ })
181
+ }
182
+ return $item
110
183
  }
111
184
 
112
185
  private onActiveContainerChanged() {
113
- trace(`${T} onActiveContainerChanged`);
114
- this.bindContainerEvents();
186
+ trace(`${T} onActiveContainerChanged`)
187
+ this.bindContainerEvents()
115
188
  }
116
189
 
117
190
  private bindContainerEvents() {
118
- trace(`${T} bindContainerEvents`);
119
- this.listenTo(this.core.activeContainer, ClapprEvents.CONTAINER_HIGHDEFINITIONUPDATE, this.highDefinitionUpdate);
191
+ trace(`${T} bindContainerEvents`)
192
+ this.listenTo(
193
+ this.core.activeContainer,
194
+ ClapprEvents.CONTAINER_HIGHDEFINITIONUPDATE,
195
+ this.highDefinitionUpdate,
196
+ )
120
197
  }
121
198
 
122
199
  private highDefinitionUpdate(isHd: boolean) {
123
- trace(`${T} highDefinitionUpdate`, { isHd });
124
- this.isHd = isHd;
125
- this.$el.find('.gear-icon').html(isHd ? gearHdIcon : gearIcon);
200
+ trace(`${T} highDefinitionUpdate`, { isHd })
201
+ this.isHd = isHd
202
+ this.$el.find('.gear-icon').html(isHd ? gearHdIcon : gearIcon)
126
203
  }
127
204
 
128
205
  /**
129
206
  * @internal
130
207
  */
131
208
  override render() {
132
- const mediaControl = this.core.getPlugin('media_control');
209
+ trace(`${T} render`)
210
+ const mediaControl = this.core.getPlugin('media_control')
211
+ if (!mediaControl) {
212
+ return this // TODO test
213
+ }
214
+ const icon = this.isHd ? gearHdIcon : gearIcon
215
+ this.$el
216
+ .html(BottomGear.template({ icon }))
217
+ .find('#gear-sub-menu-wrapper')
218
+ .hide()
219
+
220
+ // TODO make non-clickable when there are no items
221
+ mediaControl.putElement('gear', this.$el)
133
222
 
134
- // TODO use options.mediaControl.gear.items
135
- const items: GearOptionsItem[] = [
136
- 'quality',
137
- 'rate',
138
- 'nerd',
139
- ];
140
- const icon = this.isHd ? gearHdIcon : gearIcon;
141
- this.$el.html(BottomGear.template({ icon, items }));
223
+ setTimeout(() => {
224
+ this.trigger(GearEvents.RENDERED)
225
+ }, 0)
142
226
 
143
- mediaControl.putElement('gear', this.el);
144
- mediaControl.trigger(MediaControlEvents.MEDIACONTROL_GEAR_RENDERED);
145
- return this;
227
+ return this
146
228
  }
147
229
 
148
230
  /**
149
- * Re-renders the gear menu.
150
- * It fires the {@link MediaControlEvents.MEDIACONTROL_GEAR_RENDERED | MEDIACONTROL_GEAR_RENDERED} event,
151
- * which the plugins that attach to the gear menu can listen to to re-render themselves.
231
+ * Collapses any submenu open back to the gear menu
152
232
  */
153
233
  refresh() {
154
- this.render();
155
- this.$el.find('.gear-wrapper').show();
234
+ this.$el.find('.gear-sub-menu-wrapper').hide()
235
+ this.$el.find('#gear-options').show()
156
236
  }
157
237
 
158
238
  private toggleGearMenu() {
159
- this.$el.find('.gear-wrapper').toggle();
239
+ this.$el.find('#gear-options-wrapper').toggle()
160
240
  }
161
241
 
162
242
  private hide() {
163
- this.$el.find('.gear-wrapper').hide();
243
+ this.$el.find('#gear-options-wrapper').hide()
164
244
  }
165
245
 
166
246
  private onCoreReady() {
167
- const mediaControl = this.core.getPlugin('media_control');
168
- assert(mediaControl, 'media_control plugin is required');
169
- this.listenTo(mediaControl, ClapprEvents.MEDIACONTROL_RENDERED, this.render);
170
- this.listenTo(mediaControl, ClapprEvents.MEDIACONTROL_HIDE, this.hide); // TODO mediacontrol show as well
247
+ trace(`${T} onCoreReady`)
248
+ const mediaControl = this.core.getPlugin('media_control')
249
+ assert(mediaControl, 'media_control plugin is required')
250
+ this.listenTo(
251
+ mediaControl,
252
+ ClapprEvents.MEDIACONTROL_RENDERED,
253
+ this.onMediaControlRendered,
254
+ )
255
+ this.listenTo(mediaControl, ClapprEvents.MEDIACONTROL_HIDE, this.hide)
256
+ }
257
+
258
+ private onMediaControlRendered() {
259
+ trace(`${T} onMediaControlRendered`)
260
+ this.render()
171
261
  }
172
262
  }
@@ -1,8 +1,7 @@
1
1
  import { MockedFunction, beforeEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
- import { BottomGear } from '../BottomGear'
3
+ import { BottomGear, GearEvents } from '../BottomGear'
4
4
  import { createMockCore, createMockMediaControl } from '../../../testUtils'
5
- import { MediaControlEvents } from '../../media-control/MediaControl'
6
5
 
7
6
  describe('BottomGear', () => {
8
7
  let mediaControl: any
@@ -19,16 +18,33 @@ describe('BottomGear', () => {
19
18
  )
20
19
  bottomGear = new BottomGear(core)
21
20
  onGearRendered = vi.fn()
22
- mediaControl.on(MediaControlEvents.MEDIACONTROL_GEAR_RENDERED, onGearRendered, null)
21
+ bottomGear.on(GearEvents.RENDERED, onGearRendered, null)
23
22
  bottomGear.render()
24
23
  })
25
24
  it('should render', () => {
26
25
  expect(bottomGear.el.innerHTML).toMatchSnapshot()
27
26
  })
28
27
  it('should attach to media control', () => {
29
- expect(mediaControl.putElement).toHaveBeenCalledWith('gear', bottomGear.el)
28
+ expect(mediaControl.putElement).toHaveBeenCalledWith('gear', bottomGear.$el)
30
29
  })
31
- it('should emit event', () => {
30
+ it('should emit event in the next cycle', async () => {
31
+ expect(onGearRendered).not.toHaveBeenCalled()
32
+ await new Promise((resolve) => setTimeout(resolve, 0))
32
33
  expect(onGearRendered).toHaveBeenCalled()
33
34
  })
35
+ it('should render the gear menu hidden', () => {
36
+ expect(bottomGear.$el.find('#gear-options-wrapper').css('display')).toBe(
37
+ 'none',
38
+ )
39
+ })
40
+ describe('when clicked', () => {
41
+ beforeEach(() => {
42
+ bottomGear.$el.find('#gear-button').click()
43
+ })
44
+ it('should toggle the gear menu', () => {
45
+ expect(bottomGear.$el.find('#gear-options-wrapper').css('display')).toBe(
46
+ 'block',
47
+ )
48
+ })
49
+ })
34
50
  })
@@ -1,18 +1,11 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`BottomGear > should render 1`] = `
4
- "<button type="button" class="button-gear media-control-button gplayer-lite-btn gcore-skin-button-color gear-icon" data-gear-button="-1" id="gear-button">
4
+ "<button type="button" class="media-control-button gplayer-lite-btn gcore-skin-button-color gear-icon" id="gear-button">
5
5
  /assets/icons/new/gear.svg
6
6
  </button>
7
- <div class="gear-wrapper gcore-skin-bg-color">
8
- <ul class="gear-options-list" id="gear-options">
9
-
10
- <li data-quality=""></li>
11
-
12
- <li data-rate=""></li>
13
-
14
- <li data-nerd=""></li>
15
-
16
- </ul>
17
- </div>"
7
+ <div class="gear-wrapper gcore-skin-bg-color" id="gear-options-wrapper" style="display: none;">
8
+ <ul class="gear-options-list" id="gear-options"></ul>
9
+ </div>
10
+ "
18
11
  `;
@@ -1,5 +1,5 @@
1
1
  import { UICorePlugin, Events, template, Core, Container } from '@clappr/core'
2
- import { reportError } from '@gcorevideo/utils'
2
+ import { reportError, trace } from '@gcorevideo/utils'
3
3
  import Mousetrap from 'mousetrap'
4
4
 
5
5
  import { CLAPPR_VERSION } from '../../build.js'
@@ -24,8 +24,8 @@ import '../../../assets/clappr-nerd-stats/clappr-nerd-stats.scss'
24
24
  import pluginHtml from '../../../assets/clappr-nerd-stats/clappr-nerd-stats.ejs'
25
25
  import buttonHtml from '../../../assets/clappr-nerd-stats/button.ejs'
26
26
  import statsIcon from '../../../assets/icons/new/stats.svg'
27
- import { BottomGear } from '../bottom-gear/BottomGear.js'
28
- import { MediaControl, MediaControlEvents } from '../media-control/MediaControl.js'
27
+ import { BottomGear, GearEvents } from '../bottom-gear/BottomGear.js'
28
+ import { MediaControl } from '../media-control/MediaControl.js'
29
29
  import assert from 'assert'
30
30
 
31
31
  const qualityClasses = [
@@ -114,7 +114,7 @@ type Metrics = BaseMetrics & {
114
114
  }
115
115
  }
116
116
 
117
- // const T = 'plugins.clappr_nerd_stats';
117
+ const T = 'plugins.clappr_nerd_stats'
118
118
 
119
119
  /**
120
120
  * `PLUGIN` that displays useful network-related statistics.
@@ -123,8 +123,6 @@ type Metrics = BaseMetrics & {
123
123
  * @remarks
124
124
  * Depends on:
125
125
  *
126
- * - {@link MediaControl}
127
- *
128
126
  * - {@link BottomGear}
129
127
  *
130
128
  * - {@link ClapprStats}
@@ -151,6 +149,8 @@ export class ClapprNerdStats extends UICorePlugin {
151
149
 
152
150
  private iconPosition: IconPosition
153
151
 
152
+ private static readonly buttonTemplate = template(buttonHtml)
153
+
154
154
  /**
155
155
  * @internal
156
156
  */
@@ -224,17 +224,15 @@ export class ClapprNerdStats extends UICorePlugin {
224
224
  * @internal
225
225
  */
226
226
  override bindEvents() {
227
- const mediaControl = this.core.getPlugin('media_control') as MediaControl
228
- assert(mediaControl, 'media_control plugin is required')
229
- this.listenToOnce(this.core, Events.CORE_READY, this.init)
230
- this.listenTo(
231
- mediaControl,
232
- MediaControlEvents.MEDIACONTROL_GEAR_RENDERED,
233
- this.addToBottomGear,
234
- )
227
+ this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady)
235
228
  }
236
229
 
237
- private init() {
230
+ private onCoreReady() {
231
+ const bottomGear = this.core.getPlugin('bottom_gear') as BottomGear
232
+ assert(bottomGear, 'bottom_gear plugin is required')
233
+
234
+ this.listenTo(bottomGear, GearEvents.RENDERED, this.addToBottomGear)
235
+
238
236
  this.container = this.core.activeContainer
239
237
  const clapprStats = this.container?.getPlugin('clappr_stats')
240
238
 
@@ -375,6 +373,7 @@ export class ClapprNerdStats extends UICorePlugin {
375
373
  * @internal
376
374
  */
377
375
  override render() {
376
+ trace(`${T} render`)
378
377
  // TODO append to the container
379
378
  this.core.$el.append(this.$el[0])
380
379
  this.hide()
@@ -383,17 +382,20 @@ export class ClapprNerdStats extends UICorePlugin {
383
382
  }
384
383
 
385
384
  private addToBottomGear() {
385
+ trace(`${T} addToBottomGear`)
386
386
  const gear = this.core.getPlugin('bottom_gear') as BottomGear
387
- const $el = gear.getElement('nerd')
388
- $el.html(buttonHtml)
389
- const $button = $el.find('.nerd-button')
390
-
391
- $button.find('.stats-icon').html(statsIcon)
392
-
393
- $button.on('click', (e: MouseEvent) => {
394
- e.stopPropagation()
395
- this.toggle()
396
- })
387
+ const $button = gear
388
+ .addItem('nerd')
389
+ .html(
390
+ ClapprNerdStats.buttonTemplate({
391
+ icon: statsIcon,
392
+ i18n: this.core.i18n,
393
+ }),
394
+ )
395
+ .on('click', (e: MouseEvent) => {
396
+ e.stopPropagation()
397
+ this.toggle()
398
+ })
397
399
  }
398
400
 
399
401
  private clearCustomMetrics() {
@@ -129,7 +129,7 @@ export class DvrControls extends UICorePlugin {
129
129
  i18n: this.core.i18n,
130
130
  }),
131
131
  )
132
- mediaControl.putElement('dvr', this.el)
132
+ mediaControl.putElement('dvr', this.$el)
133
133
 
134
134
  return this
135
135
  }
@@ -50,7 +50,7 @@ describe('DvrControls', () => {
50
50
  expect(mediaControl.toggleElement).toHaveBeenCalledWith('position', false)
51
51
  })
52
52
  it('should render to the media control', () => {
53
- expect(mediaControl.putElement).toHaveBeenCalledWith('dvr', dvrControls.el)
53
+ expect(mediaControl.putElement).toHaveBeenCalledWith('dvr', dvrControls.$el)
54
54
  })
55
55
  })
56
56
  describe('when back_to_live button is clicked', () => {
@@ -70,7 +70,7 @@ describe('DvrControls', () => {
70
70
  })
71
71
  })
72
72
  })
73
- describe('VOD stream', () => {
73
+ describe('basically', () => {
74
74
  beforeEach(() => {
75
75
  core.getPlaybackType.mockReturnValue(Playback.VOD)
76
76
  core.activeContainer.getPlaybackType.mockReturnValue(Playback.VOD)
@@ -80,8 +80,10 @@ describe('DvrControls', () => {
80
80
  core.trigger(Events.CORE_READY)
81
81
  core.trigger(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
82
82
  })
83
- it('should not render', () => {
84
- expect(dvrControls.el.textContent).toBeFalsy()
83
+ it('should render', () => {
84
+ expect(dvrControls.el.innerHTML).toMatchSnapshot()
85
+ expect(dvrControls.el.textContent).toContain('live')
86
+ expect(dvrControls.el.textContent).toContain('back_to_live')
85
87
  })
86
88
  })
87
89
  })
@@ -1,12 +1,12 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`DvrControls > live stream > DVR at live edge > should render 1`] = `
3
+ exports[`DvrControls > basically > should render 1`] = `
4
4
  "<div class="live-info" id="media-control-live">live</div>
5
5
  <button type="button" class="live-button" aria-label="back_to_live" id="media-control-back-to-live">back_to_live</button>
6
6
  "
7
7
  `;
8
8
 
9
- exports[`DvrControls > live stream > DVR behind live edge > should render 1`] = `
9
+ exports[`DvrControls > live stream > DVR at live edge > should render 1`] = `
10
10
  "<div class="live-info" id="media-control-live">live</div>
11
11
  <button type="button" class="live-button" aria-label="back_to_live" id="media-control-back-to-live">back_to_live</button>
12
12
  "