@gcorevideo/player 2.20.4 → 2.20.5
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.
- package/assets/error-screen/error_screen.ejs +3 -1
- package/dist/core.js +407 -205
- package/dist/index.css +1238 -1238
- package/dist/index.js +542 -383
- package/dist/plugins/index.css +993 -993
- package/dist/plugins/index.js +113 -159
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +2 -2
- package/lib/playback/BasePlayback.d.ts +11 -0
- package/lib/playback/BasePlayback.d.ts.map +1 -0
- package/lib/playback/BasePlayback.js +33 -0
- package/lib/playback/dash-playback/DashPlayback.d.ts +3 -2
- package/lib/playback/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/playback/dash-playback/DashPlayback.js +7 -7
- package/lib/playback/hls-playback/HlsPlayback.d.ts +2 -2
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +8 -5
- package/lib/playback/utils.d.ts +2 -0
- package/lib/playback/utils.d.ts.map +1 -0
- package/lib/playback/utils.js +1 -0
- package/lib/playback.types.d.ts +10 -3
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/playback.types.js +3 -3
- package/lib/plugins/context-menu/ContextMenu.d.ts.map +1 -1
- package/lib/plugins/context-menu/ContextMenu.js +1 -2
- package/lib/plugins/error-screen/ErrorScreen.d.ts +39 -24
- package/lib/plugins/error-screen/ErrorScreen.d.ts.map +1 -1
- package/lib/plugins/error-screen/ErrorScreen.js +70 -136
- package/lib/plugins/media-control/MediaControl.d.ts +1 -1
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +7 -5
- package/lib/plugins/multi-camera/MultiCamera.d.ts.map +1 -1
- package/lib/plugins/multi-camera/MultiCamera.js +2 -3
- package/lib/plugins/poster/Poster.js +1 -1
- package/lib/plugins/source-controller/SourceController.d.ts +2 -1
- package/lib/plugins/source-controller/SourceController.d.ts.map +1 -1
- package/lib/plugins/source-controller/SourceController.js +12 -6
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts +2 -1
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.d.ts.map +1 -1
- package/lib/plugins/spinner-three-bounce/SpinnerThreeBounce.js +19 -3
- package/lib/testUtils.d.ts +66 -2
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +95 -2
- package/package.json +2 -2
- package/src/Player.ts +2 -2
- package/src/__tests__/Player.test.ts +2 -3
- package/src/playback/BasePlayback.ts +41 -0
- package/src/playback/dash-playback/DashPlayback.ts +11 -15
- package/src/playback/hls-playback/HlsPlayback.ts +7 -5
- package/src/playback/utils.ts +2 -0
- package/src/playback.types.ts +11 -3
- package/src/plugins/context-menu/ContextMenu.ts +1 -2
- package/src/plugins/error-screen/ErrorScreen.ts +121 -195
- package/src/plugins/error-screen/__tests__/ErrorScreen.test.ts +113 -0
- package/src/plugins/error-screen/__tests__/__snapshots__/ErrorScreen.test.ts.snap +20 -0
- package/src/plugins/level-selector/__tests__/LevelSelector.test.ts +32 -57
- package/src/plugins/media-control/MediaControl.ts +8 -5
- package/src/plugins/multi-camera/MultiCamera.ts +2 -3
- package/src/plugins/poster/Poster.ts +1 -1
- package/src/plugins/source-controller/SourceController.ts +20 -14
- package/src/plugins/source-controller/__tests__/SourceController.test.ts +29 -46
- package/src/plugins/spinner-three-bounce/SpinnerThreeBounce.ts +20 -3
- package/src/testUtils.ts +100 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
135
|
-
trace(`${T}
|
|
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 {
|
|
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(
|
|
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', {
|
|
80
|
-
|
|
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(
|
|
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', {
|
|
141
|
-
|
|
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', {
|
|
178
|
+
core.activePlayback.emit('playback:error', {
|
|
179
|
+
code: PlaybackErrorCode.MediaSourceUnavailable,
|
|
180
|
+
})
|
|
165
181
|
await clock.tickAsync(1000)
|
|
166
|
-
nextPlayback =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|