@gcorevideo/player 2.20.4 → 2.20.6

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 (63) hide show
  1. package/assets/error-screen/error_screen.ejs +3 -1
  2. package/dist/core.js +407 -205
  3. package/dist/index.css +1285 -1285
  4. package/dist/index.js +550 -386
  5. package/dist/plugins/index.css +966 -966
  6. package/dist/plugins/index.js +121 -162
  7. package/lib/Player.d.ts.map +1 -1
  8. package/lib/Player.js +2 -2
  9. package/lib/playback/BasePlayback.d.ts +11 -0
  10. package/lib/playback/BasePlayback.d.ts.map +1 -0
  11. package/lib/playback/BasePlayback.js +33 -0
  12. package/lib/playback/dash-playback/DashPlayback.d.ts +3 -2
  13. package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
  14. package/lib/playback/dash-playback/DashPlayback.js +7 -7
  15. package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -2
  16. package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
  17. package/lib/playback/hls-playback/HlsPlayback.js +8 -5
  18. package/lib/playback/utils.d.ts +2 -0
  19. package/lib/playback/utils.d.ts.map +1 -0
  20. package/lib/playback/utils.js +1 -0
  21. package/lib/playback.types.d.ts +10 -3
  22. package/lib/playback.types.d.ts.map +1 -1
  23. package/lib/playback.types.js +3 -3
  24. package/lib/plugins/context-menu/ContextMenu.d.ts.map +1 -1
  25. package/lib/plugins/context-menu/ContextMenu.js +1 -2
  26. package/lib/plugins/error-screen/ErrorScreen.d.ts +39 -24
  27. package/lib/plugins/error-screen/ErrorScreen.d.ts.map +1 -1
  28. package/lib/plugins/error-screen/ErrorScreen.js +69 -136
  29. package/lib/plugins/media-control/MediaControl.d.ts +1 -1
  30. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  31. package/lib/plugins/media-control/MediaControl.js +16 -8
  32. package/lib/plugins/multi-camera/MultiCamera.d.ts.map +1 -1
  33. package/lib/plugins/multi-camera/MultiCamera.js +2 -3
  34. package/lib/plugins/poster/Poster.js +1 -1
  35. package/lib/plugins/source-controller/SourceController.d.ts +2 -1
  36. package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
  37. package/lib/plugins/source-controller/SourceController.js +12 -6
  38. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +2 -1
  39. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
  40. package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +19 -3
  41. package/lib/testUtils.d.ts +66 -2
  42. package/lib/testUtils.d.ts.map +1 -1
  43. package/lib/testUtils.js +95 -2
  44. package/package.json +2 -2
  45. package/src/Player.ts +2 -2
  46. package/src/__tests__/Player.test.ts +2 -3
  47. package/src/playback/BasePlayback.ts +41 -0
  48. package/src/playback/dash-playback/DashPlayback.ts +12 -17
  49. package/src/playback/hls-playback/HlsPlayback.ts +9 -7
  50. package/src/playback.types.ts +11 -3
  51. package/src/plugins/context-menu/ContextMenu.ts +1 -2
  52. package/src/plugins/error-screen/ErrorScreen.ts +120 -195
  53. package/src/plugins/error-screen/__tests__/ErrorScreen.test.ts +113 -0
  54. package/src/plugins/error-screen/__tests__/__snapshots__/ErrorScreen.test.ts.snap +20 -0
  55. package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +32 -57
  56. package/src/plugins/media-control/MediaControl.ts +16 -8
  57. package/src/plugins/multi-camera/MultiCamera.ts +2 -3
  58. package/src/plugins/poster/Poster.ts +1 -1
  59. package/src/plugins/source-controller/SourceController.ts +20 -14
  60. package/src/plugins/source-controller/__tests__/SourceController.test.ts +29 -46
  61. package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +20 -3
  62. package/src/testUtils.ts +100 -3
  63. package/tsconfig.tsbuildinfo +1 -1
@@ -320,13 +320,12 @@ export class MultiCamera extends UICorePlugin {
320
320
  } catch (error) {
321
321
  reportError(error);
322
322
  }
323
- // TODO figure out
324
- this.core.getPlugin('error_gplayer')?.show({
323
+ // TODO trigger error instead
324
+ this.core.getPlugin('error_screen')?.show({
325
325
  title: this.core.i18n.t('source_offline'),
326
326
  message: '',
327
327
  code: '',
328
328
  icon: '',
329
- reloadIcon: '',
330
329
  });
331
330
  }
332
331
 
@@ -78,7 +78,7 @@ export class Poster extends UIContainerPlugin {
78
78
  private static readonly template = template(posterHTML)
79
79
 
80
80
  private get shouldRender() {
81
- if (!this.enabled) {
81
+ if (!this.enabled || this.options.reloading) {
82
82
  return false
83
83
  }
84
84
  const showForNoOp = !!this.options.poster?.showForNoOp
@@ -3,13 +3,8 @@ import {
3
3
  CorePlugin,
4
4
  type Core as ClapprCore,
5
5
  } from '@clappr/core'
6
- import {
7
- type PlaybackError,
8
- PlaybackErrorCode,
9
- } from '../../playback.types.js'
10
- import {
11
- type PlayerMediaSourceDesc,
12
- } from '../../types.js'
6
+ import { type PlaybackError, PlaybackErrorCode } from '../../playback.types.js'
7
+ import { type PlayerMediaSourceDesc } from '../../types.js'
13
8
  import { trace } from '@gcorevideo/utils'
14
9
  import { SpinnerEvents } from '../spinner-three-bounce/SpinnerThreeBounce.js'
15
10
 
@@ -44,7 +39,7 @@ function noSync(cb: () => void) {
44
39
  * @example
45
40
  * ```ts
46
41
  * import { SourceController } from '@gcorevideo/player'
47
- *
42
+ *
48
43
  * Player.registerPlugin(SourceController)
49
44
  * ```
50
45
  */
@@ -53,7 +48,7 @@ export class SourceController extends CorePlugin {
53
48
  * The Logic itself is quite simple:
54
49
  * * Here is the short diagram:
55
50
  *
56
- * sources_list:
51
+ * sources_list:
57
52
  * - a.mpd | +--------------------+
58
53
  * - b.m3u8 |--->| init |
59
54
  * - ... | |--------------------|
@@ -128,11 +123,21 @@ export class SourceController extends CorePlugin {
128
123
  override bindEvents() {
129
124
  super.bindEvents()
130
125
 
131
- this.listenTo(this.core, ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED, () => this.onReady())
126
+ this.listenTo(this.core, ClapprEvents.CORE_READY, this.onCoreReady)
127
+ this.listenTo(
128
+ this.core,
129
+ ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
130
+ this.onActiveContainerChanged,
131
+ )
132
+ }
133
+
134
+ private onCoreReady() {
135
+ trace(`${T} onCoreReady`)
136
+ this.core.getPlugin('error_screen')?.disable() // TODO test
132
137
  }
133
138
 
134
- private onReady() {
135
- trace(`${T} onReady`, {
139
+ private onActiveContainerChanged() {
140
+ trace(`${T} onActiveContainerChanged`, {
136
141
  retrying: this.active,
137
142
  currentSource: this.sourcesList[this.currentSourceIndex],
138
143
  })
@@ -147,7 +152,7 @@ export class SourceController extends CorePlugin {
147
152
  this.bindContainerEventListeners()
148
153
  if (this.active) {
149
154
  this.core.activeContainer?.getPlugin('poster_custom')?.disable()
150
- spinner?.show()
155
+ spinner?.show(0)
151
156
  }
152
157
  }
153
158
 
@@ -167,7 +172,7 @@ export class SourceController extends CorePlugin {
167
172
  switch (error.code) {
168
173
  case PlaybackErrorCode.MediaSourceUnavailable:
169
174
  this.core.activeContainer?.getPlugin('poster_custom')?.disable()
170
- this.retryPlayback()
175
+ setTimeout(() => this.retryPlayback(), 0)
171
176
  break
172
177
  // TODO handle other errors
173
178
  default:
@@ -200,6 +205,7 @@ export class SourceController extends CorePlugin {
200
205
  currentSource: this.sourcesList[this.currentSourceIndex],
201
206
  })
202
207
  this.active = true
208
+ this.core.activeContainer?.getPlugin('spinner')?.show(0)
203
209
  this.getNextMediaSource().then((nextSource: PlayerMediaSourceDesc) => {
204
210
  trace(`${T} retryPlayback syncing...`, {
205
211
  nextSource,
@@ -1,11 +1,16 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
2
 
3
- import EventLite from 'event-lite'
4
3
  import FakeTimers from '@sinonjs/fake-timers'
5
4
 
6
5
  import { SourceController } from '../SourceController'
7
6
  import { PlaybackErrorCode } from '../../../playback.types.js'
8
- import { _MockPlayback } from '../../../testUtils.js'
7
+ import {
8
+ _MockPlayback,
9
+ createMockCore,
10
+ createMockPlayback,
11
+ createMockPlugin,
12
+ createSpinnerPlugin,
13
+ } from '../../../testUtils.js'
9
14
 
10
15
  const MOCK_SOURCES = [
11
16
  {
@@ -60,7 +65,9 @@ describe('SourceController', () => {
60
65
  it('should keep a single source for the core', () => {
61
66
  core = createMockCore(inputSourcesConfig)
62
67
  const _ = new SourceController(core)
63
- expect(core.options).toEqual(expect.objectContaining(correctedSourcesConfig))
68
+ expect(core.options).toEqual(
69
+ expect.objectContaining(correctedSourcesConfig),
70
+ )
64
71
  })
65
72
  })
66
73
  })
@@ -76,8 +83,10 @@ describe('SourceController', () => {
76
83
  const _ = new SourceController(core)
77
84
  core.emit('core:ready')
78
85
  core.emit('core:active:container:changed')
79
- core.activePlayback.emit('playback:error', { code: PlaybackErrorCode.MediaSourceUnavailable })
80
- nextPlayback = new _MockPlayback({} as any, {} as any)
86
+ core.activePlayback.emit('playback:error', {
87
+ code: PlaybackErrorCode.MediaSourceUnavailable,
88
+ })
89
+ nextPlayback = createMockPlayback()
81
90
  vi.spyOn(nextPlayback, 'consent')
82
91
  vi.spyOn(nextPlayback, 'play')
83
92
  core.activePlayback = nextPlayback
@@ -85,7 +94,10 @@ describe('SourceController', () => {
85
94
  it('should load the next source after a delay', async () => {
86
95
  expect(core.load).not.toHaveBeenCalled()
87
96
  await clock.tickAsync(1000)
88
- expect(core.load).toHaveBeenCalledWith(MOCK_SOURCES[1].source, MOCK_SOURCES[1].mimeType)
97
+ expect(core.load).toHaveBeenCalledWith(
98
+ MOCK_SOURCES[1].source,
99
+ MOCK_SOURCES[1].mimeType,
100
+ )
89
101
  })
90
102
  it('should try to play after loading the source in a random delay', async () => {
91
103
  await clock.tickAsync(1000)
@@ -101,6 +113,7 @@ describe('SourceController', () => {
101
113
  let poster: any
102
114
  let spinner: any
103
115
  beforeEach(async () => {
116
+ await clock.tickAsync(0)
104
117
  poster = createMockPlugin()
105
118
  spinner = createSpinnerPlugin()
106
119
  core.activeContainer.getPlugin.mockImplementation((name: string) => {
@@ -111,7 +124,6 @@ describe('SourceController', () => {
111
124
  return spinner
112
125
  }
113
126
  })
114
- core.emit('core:ready')
115
127
  core.emit('core:active:container:changed')
116
128
  })
117
129
  it('should disable the poster', async () => {
@@ -137,8 +149,10 @@ describe('SourceController', () => {
137
149
  const _ = new SourceController(core)
138
150
  core.emit('core:ready')
139
151
  core.emit('core:active:container:changed')
140
- core.activePlayback.emit('playback:error', { code: PlaybackErrorCode.MediaSourceUnavailable })
141
- nextPlayback = new _MockPlayback({} as any, {} as any)
152
+ core.activePlayback.emit('playback:error', {
153
+ code: PlaybackErrorCode.MediaSourceUnavailable,
154
+ })
155
+ nextPlayback = new _MockPlayback({} as any, {} as any)
142
156
  vi.spyOn(nextPlayback, 'consent')
143
157
  vi.spyOn(nextPlayback, 'play')
144
158
  core.activePlayback = nextPlayback
@@ -161,9 +175,11 @@ describe('SourceController', () => {
161
175
  const _ = new SourceController(core)
162
176
  core.emit('core:ready')
163
177
  core.emit('core:active:container:changed')
164
- core.activePlayback.emit('playback:error', { code: PlaybackErrorCode.MediaSourceUnavailable })
178
+ core.activePlayback.emit('playback:error', {
179
+ code: PlaybackErrorCode.MediaSourceUnavailable,
180
+ })
165
181
  await clock.tickAsync(1000)
166
- nextPlayback = new _MockPlayback({} as any, {} as any)
182
+ nextPlayback = createMockPlayback()
167
183
  core.activePlayback = nextPlayback
168
184
  poster = createMockPlugin()
169
185
  spinner = createSpinnerPlugin()
@@ -186,14 +202,8 @@ describe('SourceController', () => {
186
202
  expect(spinner.hide).toHaveBeenCalled()
187
203
  })
188
204
  describe.each([
189
- [
190
- 'buffering',
191
- 'playback:buffering',
192
- ],
193
- [
194
- 'pause',
195
- 'playback:pause',
196
- ],
205
+ ['buffering', 'playback:buffering'],
206
+ ['pause', 'playback:pause'],
197
207
  ])('on a following playback:play event due to %s', (_, event) => {
198
208
  it('should do nothing', async () => {
199
209
  nextPlayback.emit(event)
@@ -207,30 +217,3 @@ describe('SourceController', () => {
207
217
  })
208
218
  })
209
219
  })
210
-
211
- function createMockCore(options: Record<string, unknown> = {}) {
212
- return Object.assign(new EventLite(), {
213
- activePlayback: new _MockPlayback({} as any, {} as any),
214
- activeContainer: Object.assign(new EventLite(), {
215
- getPlugin: vi.fn(),
216
- }),
217
- options: {
218
- ...options,
219
- },
220
- load: vi.fn(),
221
- })
222
- }
223
-
224
- function createMockPlugin() {
225
- return Object.assign(new EventLite(), {
226
- enable: vi.fn(),
227
- disable: vi.fn(),
228
- })
229
- }
230
-
231
- function createSpinnerPlugin() {
232
- return Object.assign(createMockPlugin(), {
233
- show: vi.fn(),
234
- hide: vi.fn(),
235
- })
236
- }
@@ -55,6 +55,8 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
55
55
  };
56
56
  }
57
57
 
58
+ private hideTimeout: TimerId | null = null;
59
+
58
60
  private showTimeout: TimerId | null = null;
59
61
 
60
62
  private template = template(spinnerHTML);
@@ -119,8 +121,18 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
119
121
  /**
120
122
  * Shows the spinner
121
123
  */
122
- show() {
123
- this.showTimeout = setTimeout(() => this.$el.show(), 300);
124
+ show(delay = 300) {
125
+ trace(`${T} show`)
126
+ if (this.showTimeout === null) {
127
+ if (this.hideTimeout !== null) {
128
+ clearTimeout(this.hideTimeout);
129
+ this.hideTimeout = null;
130
+ }
131
+ this.showTimeout = setTimeout(() => {
132
+ this.showTimeout = null
133
+ this.$el.show()
134
+ }, delay);
135
+ }
124
136
  }
125
137
 
126
138
  /**
@@ -131,7 +143,12 @@ export class SpinnerThreeBounce extends UIContainerPlugin {
131
143
  clearTimeout(this.showTimeout);
132
144
  this.showTimeout = null;
133
145
  }
134
- this.$el.hide();
146
+ this.hideTimeout = setTimeout(() => {
147
+ this.hideTimeout = null
148
+ if (this.showTimeout === null) {
149
+ this.$el.hide();
150
+ }
151
+ }, 0);
135
152
  }
136
153
 
137
154
  /**
package/src/testUtils.ts CHANGED
@@ -1,9 +1,11 @@
1
- import EventLite from 'event-lite'
2
-
1
+ import Events from 'eventemitter3'
2
+ import { vi } from 'vitest'
3
3
  /**
4
4
  * @internal
5
+ * @deprecated
6
+ * TODO use createMockPlayback() instead
5
7
  */
6
- export class _MockPlayback extends EventLite {
8
+ export class _MockPlayback extends Events {
7
9
  constructor(
8
10
  protected options: any,
9
11
  readonly i18n: any,
@@ -78,3 +80,98 @@ export class _MockPlayback extends EventLite {
78
80
  this.emit(event, ...args)
79
81
  }
80
82
  }
83
+
84
+ export function createMockCore(options: Record<string, unknown> = {}, container: any = createMockContainer()) {
85
+ const el = document.createElement('div')
86
+ return Object.assign(new Events(), {
87
+ el,
88
+ $el: {
89
+ [0]: el,
90
+ append: vi.fn(),
91
+ },
92
+ activePlayback: container.playback,
93
+ activeContainer: container,
94
+ options: {
95
+ ...options,
96
+ },
97
+ configure: vi.fn(),
98
+ getPlugin: vi.fn(),
99
+ load: vi.fn(),
100
+ })
101
+ }
102
+
103
+ export function createMockPlugin() {
104
+ return Object.assign(new Events(), {
105
+ enable: vi.fn(),
106
+ disable: vi.fn(),
107
+ })
108
+ }
109
+
110
+ export function createSpinnerPlugin() {
111
+ return Object.assign(createMockPlugin(), {
112
+ show: vi.fn(),
113
+ hide: vi.fn(),
114
+ })
115
+ }
116
+
117
+ export function createMockPlayback(name = 'mock') {
118
+ const emitter = new Events()
119
+ return Object.assign(emitter, {
120
+ name,
121
+ currentLevel: -1,
122
+ levels: [],
123
+ consent() {},
124
+ play() {},
125
+ pause() {},
126
+ stop() {},
127
+ destroy() {},
128
+ seek() {},
129
+ seekPercentage() {},
130
+ getDuration() {
131
+ return 100
132
+ },
133
+ enterPiP() {},
134
+ exitPiP() {},
135
+ getPlaybackType() {
136
+ return 'live'
137
+ },
138
+ getStartTimeOffset() {
139
+ return 0
140
+ },
141
+ getCurrentTime() {
142
+ return 0
143
+ },
144
+ isHighDefinitionInUse() {
145
+ return false
146
+ },
147
+ mute() {},
148
+ unmute() {},
149
+ volume() {},
150
+ configure() {},
151
+ attemptAutoPlay() {
152
+ return true
153
+ },
154
+ canAutoPlay() {
155
+ return true
156
+ },
157
+ onResize() {
158
+ return true
159
+ },
160
+ trigger(event: string, ...args: any[]) {
161
+ emitter.emit(event, ...args)
162
+ },
163
+ })
164
+ }
165
+
166
+ export function createMockContainer(playback: any = createMockPlayback()) {
167
+ const el = document.createElement('div')
168
+ return Object.assign(new Events(), {
169
+ $el: {
170
+ html: vi.fn(),
171
+ [0]: el,
172
+ },
173
+ el,
174
+ getPlugin: vi.fn(),
175
+ playback,
176
+ })
177
+ }