@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
@@ -90,6 +90,8 @@ export class SourceController extends CorePlugin {
90
90
 
91
91
  private active = false
92
92
 
93
+ private switching = false
94
+
93
95
  private sync: SyncFn = noSync
94
96
 
95
97
  /**
@@ -166,15 +168,18 @@ export class SourceController extends CorePlugin {
166
168
  description: error?.description,
167
169
  level: error?.level,
168
170
  },
171
+ switching: this.switching,
169
172
  retrying: this.active,
170
173
  currentSource: this.sourcesList[this.currentSourceIndex],
171
174
  })
175
+ if (this.switching) {
176
+ return
177
+ }
172
178
  switch (error.code) {
173
179
  case PlaybackErrorCode.MediaSourceUnavailable:
174
180
  this.core.activeContainer?.getPlugin('poster_custom')?.disable()
175
- setTimeout(() => this.retryPlayback(), 0)
181
+ this.retryPlayback()
176
182
  break
177
- // TODO handle other errors
178
183
  default:
179
184
  break
180
185
  }
@@ -187,7 +192,6 @@ export class SourceController extends CorePlugin {
187
192
  })
188
193
  if (this.active) {
189
194
  this.reset()
190
- // TODO make poster reset its state on enable
191
195
  this.core.activeContainer?.getPlugin('poster_custom')?.enable()
192
196
  this.core.activeContainer?.getPlugin('spinner')?.hide()
193
197
  }
@@ -205,6 +209,7 @@ export class SourceController extends CorePlugin {
205
209
  currentSource: this.sourcesList[this.currentSourceIndex],
206
210
  })
207
211
  this.active = true
212
+ this.switching = true
208
213
  this.core.activeContainer?.getPlugin('spinner')?.show(0)
209
214
  this.getNextMediaSource().then((nextSource: PlayerMediaSourceDesc) => {
210
215
  trace(`${T} retryPlayback syncing...`, {
@@ -213,12 +218,12 @@ export class SourceController extends CorePlugin {
213
218
  const rnd = RETRY_DELAY_BLUR * Math.random()
214
219
  this.sync(() => {
215
220
  trace(`${T} retryPlayback loading...`)
221
+ this.switching = false
216
222
  this.core.load(nextSource.source, nextSource.mimeType)
217
223
  trace(`${T} retryPlayback loaded`, {
218
224
  nextSource,
219
225
  })
220
226
  setTimeout(() => {
221
- // this.core.activePlayback.consent()
222
227
  this.core.activePlayback.play()
223
228
  trace(`${T} retryPlayback playing`)
224
229
  }, rnd)
@@ -74,7 +74,6 @@ describe('SourceController', () => {
74
74
  describe('on fatal playback failure', () => {
75
75
  let core: any
76
76
  let nextPlayback: any
77
-
78
77
  describe('basically', () => {
79
78
  beforeEach(() => {
80
79
  core = createMockCore({
@@ -215,5 +214,40 @@ describe('SourceController', () => {
215
214
  })
216
215
  })
217
216
  })
217
+ describe('given that playback triggers many errors in a row', () => {
218
+ beforeEach(async () => {
219
+ core = createMockCore({
220
+ sources: MOCK_SOURCES,
221
+ })
222
+ const _ = new SourceController(core)
223
+ core.emit('core:ready')
224
+ core.emit('core:active:container:changed')
225
+ core.activePlayback.emit('playback:error', {
226
+ code: PlaybackErrorCode.MediaSourceUnavailable,
227
+ })
228
+ await clock.tickAsync(1)
229
+ core.activePlayback.emit('playback:error', {
230
+ code: PlaybackErrorCode.MediaSourceUnavailable,
231
+ })
232
+ await clock.tickAsync(1)
233
+ core.activePlayback.emit('playback:error', {
234
+ code: PlaybackErrorCode.MediaSourceUnavailable,
235
+ })
236
+ await clock.tickAsync(1)
237
+ nextPlayback = createMockPlayback()
238
+ vi.spyOn(nextPlayback, 'consent')
239
+ vi.spyOn(nextPlayback, 'play')
240
+ core.activePlayback = nextPlayback
241
+ })
242
+ it('should run handler only once', async () => {
243
+ expect(core.load).not.toHaveBeenCalled()
244
+ await clock.tickAsync(1000)
245
+ expect(core.load).toHaveBeenCalledTimes(1)
246
+ expect(core.load).toHaveBeenCalledWith(
247
+ MOCK_SOURCES[1].source,
248
+ MOCK_SOURCES[1].mimeType,
249
+ )
250
+ })
251
+ })
218
252
  })
219
253
  })
@@ -2,14 +2,19 @@
2
2
  // Use of this source code is governed by a BSD-style
3
3
  // license that can be found in the LICENSE file.
4
4
 
5
- import { Container, Events as ClapprEvents, UIContainerPlugin, template } from '@clappr/core';
6
- import { PlaybackError, PlaybackErrorCode } from '../../playback.types.js';
7
- import { trace } from '@gcorevideo/utils';
8
-
9
- import spinnerHTML from '../../../assets/spinner-three-bounce/spinner.ejs';
10
- import '../../../assets/spinner-three-bounce/spinner.scss';
11
- import { TimerId } from '../../utils/types.js';
12
- import { CLAPPR_VERSION } from '../../build.js';
5
+ import {
6
+ Container,
7
+ Events as ClapprEvents,
8
+ UIContainerPlugin,
9
+ template,
10
+ } from '@clappr/core'
11
+ import { trace } from '@gcorevideo/utils'
12
+
13
+ import { PlaybackError, PlaybackErrorCode } from '../../playback.types.js'
14
+ import spinnerHTML from '../../../assets/spinner-three-bounce/spinner.ejs'
15
+ import '../../../assets/spinner-three-bounce/spinner.scss'
16
+ import { TimerId } from '../../utils/types.js'
17
+ import { CLAPPR_VERSION } from '../../build.js'
13
18
 
14
19
  const T = 'plugins.spinner'
15
20
 
@@ -28,21 +33,23 @@ export enum SpinnerEvents {
28
33
  * Shows a pending operation indicator when playback is buffering or in other appropriate cases
29
34
  * @beta
30
35
  * @remarks
31
- * The plugin emits
36
+ * The plugin emits
32
37
  */
33
38
  export class SpinnerThreeBounce extends UIContainerPlugin {
39
+ private userShown = false
40
+
34
41
  /**
35
42
  * @internal
36
43
  */
37
44
  get name() {
38
- return 'spinner';
45
+ return 'spinner'
39
46
  }
40
47
 
41
48
  /**
42
49
  * @internal
43
50
  */
44
51
  get supportedVersion() {
45
- return { min: CLAPPR_VERSION };
52
+ return { min: CLAPPR_VERSION }
46
53
  }
47
54
 
48
55
  /**
@@ -50,47 +57,53 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
50
57
  */
51
58
  override get attributes() {
52
59
  return {
53
- 'data-spinner':'',
54
- 'class': 'spinner-three-bounce'
55
- };
60
+ 'data-spinner': '',
61
+ class: 'spinner-three-bounce',
62
+ }
56
63
  }
57
64
 
58
- private hideTimeout: TimerId | null = null;
59
-
60
- private showTimeout: TimerId | null = null;
65
+ private showTimeout: TimerId | null = null
61
66
 
62
- private template = template(spinnerHTML);
67
+ private template = template(spinnerHTML)
63
68
 
64
69
  private hasFatalError = false
65
70
 
66
71
  private hasBuffering = false
67
72
 
68
73
  constructor(container: Container) {
69
- super(container);
70
- this.listenTo(this.container, ClapprEvents.CONTAINER_STATE_BUFFERING, this.onBuffering);
71
- this.listenTo(this.container, ClapprEvents.CONTAINER_STATE_BUFFERFULL, this.onBufferFull);
72
- this.listenTo(this.container, ClapprEvents.CONTAINER_PLAY, this.onPlay);
73
- this.listenTo(this.container, ClapprEvents.CONTAINER_STOP, this.onStop);
74
- this.listenTo(this.container, ClapprEvents.CONTAINER_ENDED, this.onStop);
75
- this.listenTo(this.container, ClapprEvents.CONTAINER_ERROR, this.onError);
76
- this.listenTo(this.container, ClapprEvents.CONTAINER_READY, this.render);
74
+ super(container)
75
+ this.listenTo(
76
+ this.container,
77
+ ClapprEvents.CONTAINER_STATE_BUFFERING,
78
+ this.onBuffering,
79
+ )
80
+ this.listenTo(
81
+ this.container,
82
+ ClapprEvents.CONTAINER_STATE_BUFFERFULL,
83
+ this.onBufferFull,
84
+ )
85
+ this.listenTo(this.container, ClapprEvents.CONTAINER_PLAY, this.onPlay)
86
+ this.listenTo(this.container, ClapprEvents.CONTAINER_STOP, this.onStop)
87
+ this.listenTo(this.container, ClapprEvents.CONTAINER_ENDED, this.onStop)
88
+ this.listenTo(this.container, ClapprEvents.CONTAINER_ERROR, this.onError)
89
+ this.listenTo(this.container, ClapprEvents.CONTAINER_READY, this.render)
77
90
  }
78
91
 
79
92
  private onBuffering() {
80
93
  this.hasBuffering = true
81
- this.show();
94
+ this._show()
82
95
  }
83
96
 
84
97
  private onBufferFull() {
85
98
  if (!this.hasFatalError && this.hasBuffering) {
86
- this.hide();
99
+ this._hide()
87
100
  }
88
101
  this.hasBuffering = false
89
102
  }
90
103
 
91
104
  private onPlay() {
92
- trace(`${T} onPlay`);
93
- this.hide();
105
+ trace(`${T} onPlay`)
106
+ this._hide()
94
107
  }
95
108
 
96
109
  private onStop() {
@@ -99,7 +112,7 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
99
112
  hasFatalError: this.hasFatalError,
100
113
  })
101
114
  if (!(this.hasFatalError && this.options.spinner?.showOnError)) {
102
- this.hide();
115
+ this._hide()
103
116
  }
104
117
  }
105
118
 
@@ -112,65 +125,75 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
112
125
  error: e.code,
113
126
  })
114
127
  if (this.options.spinner?.showOnError) {
115
- this.show();
128
+ this._show()
116
129
  } else {
117
- this.hide();
130
+ this._hide()
118
131
  }
119
132
  }
120
133
 
121
134
  /**
122
- * Shows the spinner
135
+ * Shows the spinner.
136
+ *
137
+ * When called, the spinner will not hide (due to its built-in logic) until {@link SpinnerThreeBounce#hide} is called
123
138
  */
124
139
  show(delay = 300) {
125
140
  trace(`${T} show`)
141
+ this.userShown = true
142
+ this._show(delay)
143
+ }
144
+
145
+ /**
146
+ * Hides the spinner.
147
+ */
148
+ hide() {
149
+ this.userShown = false
150
+ this._hide()
151
+ }
152
+
153
+ private _show(delay = 300) {
126
154
  if (this.showTimeout === null) {
127
- if (this.hideTimeout !== null) {
128
- clearTimeout(this.hideTimeout);
129
- this.hideTimeout = null;
130
- }
131
155
  this.showTimeout = setTimeout(() => {
132
156
  this.showTimeout = null
133
157
  this.$el.show()
134
- }, delay);
158
+ }, delay)
135
159
  }
136
160
  }
137
161
 
138
- /**
139
- * Hides the spinner
140
- */
141
- hide() {
162
+ private _hide() {
163
+ trace(`${T} _hide`, {
164
+ userShown: this.userShown,
165
+ })
166
+ if (this.userShown) {
167
+ return
168
+ }
142
169
  if (this.showTimeout !== null) {
143
- clearTimeout(this.showTimeout);
144
- this.showTimeout = null;
170
+ clearTimeout(this.showTimeout)
171
+ this.showTimeout = null
145
172
  }
146
- this.hideTimeout = setTimeout(() => {
147
- this.hideTimeout = null
148
- if (this.showTimeout === null) {
149
- this.$el.hide();
150
- }
151
- }, 0);
173
+ this.$el.hide()
174
+ this.trigger(SpinnerEvents.SYNC) // TODO test
152
175
  }
153
176
 
154
177
  /**
155
178
  * @internal
156
179
  */
157
180
  override render() {
158
- const showOnStart = this.options.spinner?.showOnStart;
181
+ const showOnStart = this.options.spinner?.showOnStart
159
182
  trace(`${T} render`, {
160
183
  buffering: this.container.buffering,
161
184
  showOnStart,
162
185
  })
163
- this.$el.html(this.template());
186
+ this.$el.html(this.template())
164
187
  this.el.firstElementChild?.addEventListener('animationiteration', () => {
165
188
  this.trigger(SpinnerEvents.SYNC)
166
189
  })
167
- this.container.$el.append(this.$el[0]);
190
+ this.container.$el.append(this.$el[0])
168
191
  if (showOnStart || this.container.buffering) {
169
- this.show();
192
+ this._show()
170
193
  } else {
171
- this.hide();
194
+ this._hide()
172
195
  }
173
196
 
174
- return this;
197
+ return this
175
198
  }
176
199
  }
package/src/testUtils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { $, UICorePlugin } from '@clappr/core'
1
2
  import Events from 'eventemitter3'
2
3
  import { vi } from 'vitest'
3
4
  /**
@@ -83,12 +84,10 @@ export class _MockPlayback extends Events {
83
84
 
84
85
  export function createMockCore(options: Record<string, unknown> = {}, container: any = createMockContainer()) {
85
86
  const el = document.createElement('div')
86
- return Object.assign(new Events(), {
87
+ const emitter = new Events()
88
+ return Object.assign(emitter, {
87
89
  el,
88
- $el: {
89
- [0]: el,
90
- append: vi.fn(),
91
- },
90
+ $el: $(el),
92
91
  activePlayback: container.playback,
93
92
  activeContainer: container,
94
93
  options: {
@@ -97,6 +96,7 @@ export function createMockCore(options: Record<string, unknown> = {}, container:
97
96
  configure: vi.fn(),
98
97
  getPlugin: vi.fn(),
99
98
  load: vi.fn(),
99
+ trigger: emitter.emit,
100
100
  })
101
101
  }
102
102
 
@@ -166,12 +166,19 @@ export function createMockPlayback(name = 'mock') {
166
166
  export function createMockContainer(playback: any = createMockPlayback()) {
167
167
  const el = document.createElement('div')
168
168
  return Object.assign(new Events(), {
169
- $el: {
170
- html: vi.fn(),
171
- [0]: el,
172
- },
169
+ $el: $(el),
173
170
  el,
174
171
  getPlugin: vi.fn(),
175
172
  playback,
176
173
  })
177
174
  }
175
+
176
+ export function createMockMediaControl(core: any) {
177
+ const mediaControl = new UICorePlugin(core)
178
+ const elements = {
179
+ gear: $(document.createElement('div')),
180
+ }
181
+ // @ts-ignore
182
+ mediaControl.getElement = vi.fn().mockImplementation((name) => elements[name])
183
+ return mediaControl
184
+ }