@gcorevideo/player 2.10.0 → 2.12.2
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/LICENSE +13 -0
- package/coverage/clover.xml +6 -0
- package/coverage/coverage-final.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +101 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +0 -0
- package/dist/index.js +3115 -1209
- package/lib/Player.d.ts +3 -2
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +32 -24
- package/lib/index.d.ts +5 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -5
- package/lib/internal.types.d.ts +1 -10
- package/lib/internal.types.d.ts.map +1 -1
- package/lib/playback/index.d.ts +4 -0
- package/lib/playback/index.d.ts.map +1 -0
- package/lib/playback/index.js +13 -0
- package/lib/playback.types.d.ts +19 -0
- package/lib/playback.types.d.ts.map +1 -1
- package/lib/playback.types.js +9 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts +1 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/plugins/dash-playback/DashPlayback.js +39 -100
- package/lib/plugins/dash-playback/types.d.ts +6 -0
- package/lib/plugins/dash-playback/types.d.ts.map +1 -0
- package/lib/plugins/dash-playback/types.js +1 -0
- package/lib/plugins/hls-playback/HlsPlayback.d.ts +6 -7
- package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/plugins/hls-playback/HlsPlayback.js +131 -80
- package/lib/types.d.ts +3 -3
- package/lib/types.d.ts.map +1 -1
- package/lib/utils/mediaSources.d.ts +14 -6
- package/lib/utils/mediaSources.d.ts.map +1 -1
- package/lib/utils/mediaSources.js +56 -53
- package/lib/utils/testUtils.d.ts +3 -0
- package/lib/utils/testUtils.d.ts.map +1 -0
- package/lib/utils/testUtils.js +12 -0
- package/package.json +6 -4
- package/src/Player.ts +40 -31
- package/src/__tests__/Player.test.ts +357 -0
- package/src/index.ts +5 -5
- package/src/internal.types.ts +1 -12
- package/src/playback/index.ts +17 -0
- package/src/playback.types.ts +29 -8
- package/src/plugins/dash-playback/DashPlayback.ts +44 -120
- package/src/plugins/hls-playback/HlsPlayback.ts +544 -390
- package/src/types.ts +5 -3
- package/src/typings/@clappr/core/error_mixin.d.ts +0 -2
- package/src/typings/@clappr/core/index.d.ts +5 -0
- package/src/typings/@clappr/index.d.ts +1 -0
- package/src/utils/__tests__/mediaSources.test.ts +230 -0
- package/src/utils/mediaSources.ts +78 -64
- package/src/utils/testUtils.ts +15 -0
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +8 -0
- package/licenses.json +0 -782
- package/src/utils/queryParams.ts +0 -5
|
@@ -1,73 +1,76 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { canPlayDash, canPlayHls } from '../playback/index.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param sources
|
|
5
|
+
* @deprecated
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
3
8
|
export function buildSourcesSet(sources) {
|
|
4
9
|
const sv = {
|
|
5
10
|
dash: null,
|
|
6
|
-
master: null,
|
|
7
11
|
hls: null,
|
|
8
12
|
mpegts: null,
|
|
9
13
|
};
|
|
10
14
|
sources.forEach((ps) => {
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
13
|
-
sv.dash =
|
|
15
|
+
const ws = wrapSource(ps);
|
|
16
|
+
if (canPlayDash(ws.source, ws.mimeType)) {
|
|
17
|
+
sv.dash = ws;
|
|
14
18
|
}
|
|
15
|
-
else if (
|
|
16
|
-
sv.hls =
|
|
19
|
+
else if (canPlayHls(ws.source, ws.mimeType)) {
|
|
20
|
+
sv.hls = ws;
|
|
17
21
|
}
|
|
18
22
|
else {
|
|
19
|
-
sv.
|
|
23
|
+
sv.mpegts = ws;
|
|
20
24
|
}
|
|
21
25
|
});
|
|
22
26
|
return sv;
|
|
23
27
|
}
|
|
24
|
-
export function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
addMpegts();
|
|
35
|
-
break;
|
|
36
|
-
case 'auto':
|
|
37
|
-
addDash();
|
|
38
|
-
addHls();
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
Object.values(sources).forEach((s) => {
|
|
42
|
-
if (s) {
|
|
43
|
-
msl.push(s);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return msl;
|
|
47
|
-
function addMpegts() {
|
|
48
|
-
if (sources.mpegts) {
|
|
49
|
-
msl.push(sources.mpegts);
|
|
50
|
-
sources.mpegts = null;
|
|
28
|
+
export function buildMediaSourcesList(sources, priorityTransport = 'auto') {
|
|
29
|
+
const [preferred, rest] = sources.reduce(priorityTransport === 'dash' || priorityTransport === 'auto'
|
|
30
|
+
? (acc, item) => {
|
|
31
|
+
if (canPlayDash(item.source, item.mimeType)) {
|
|
32
|
+
acc[0].push(item);
|
|
33
|
+
}
|
|
34
|
+
else if (canPlayHls(item.source, item.mimeType)) {
|
|
35
|
+
acc[1].push(item);
|
|
36
|
+
}
|
|
37
|
+
return acc;
|
|
51
38
|
}
|
|
39
|
+
: (acc, item) => {
|
|
40
|
+
if (canPlayHls(item.source, item.mimeType)) {
|
|
41
|
+
acc[0].push(item);
|
|
42
|
+
}
|
|
43
|
+
else if (canPlayDash(item.source, item.mimeType)) {
|
|
44
|
+
acc[1].push(item);
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}, [[], []]);
|
|
48
|
+
return preferred.concat(rest);
|
|
49
|
+
}
|
|
50
|
+
export function unwrapSource(s) {
|
|
51
|
+
return typeof s === 'string' ? s : s.source;
|
|
52
|
+
}
|
|
53
|
+
export function wrapSource(s) {
|
|
54
|
+
return typeof s === 'string' ? { source: s, mimeType: guessMimeType(s) } : s;
|
|
55
|
+
}
|
|
56
|
+
function guessMimeType(s) {
|
|
57
|
+
if (s.endsWith('.mpd')) {
|
|
58
|
+
return 'application/dash+xml';
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
msl.push(sources.hls);
|
|
56
|
-
sources.hls = null;
|
|
57
|
-
}
|
|
58
|
-
if (sources.master?.endsWith('.m3u8') &&
|
|
59
|
-
HlsPlayback.canPlay(sources.master, undefined)) {
|
|
60
|
-
msl.push(sources.master);
|
|
61
|
-
sources.master = null;
|
|
62
|
-
}
|
|
60
|
+
if (s.endsWith('.m3u8')) {
|
|
61
|
+
return 'application/vnd.apple.mpegurl';
|
|
63
62
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
throw new Error('Unrecognized media source type');
|
|
64
|
+
}
|
|
65
|
+
export function isDashSource(source, mimeType) {
|
|
66
|
+
if (mimeType) {
|
|
67
|
+
return mimeType === 'application/dash+xml';
|
|
69
68
|
}
|
|
69
|
+
return source.endsWith('.mpd');
|
|
70
70
|
}
|
|
71
|
-
export function
|
|
72
|
-
|
|
71
|
+
export function isHlsSource(source, mimeType) {
|
|
72
|
+
if (mimeType) {
|
|
73
|
+
return ['application/vnd.apple.mpegurl', 'application/x-mpegURL'].includes(mimeType);
|
|
74
|
+
}
|
|
75
|
+
return source.endsWith('.m3u8');
|
|
73
76
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../../src/utils/testUtils.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAK7D;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,WAO5D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isDashSource(source, mimeType) {
|
|
2
|
+
if (mimeType) {
|
|
3
|
+
return mimeType === 'application/dash+xml';
|
|
4
|
+
}
|
|
5
|
+
return source.endsWith('.mpd');
|
|
6
|
+
}
|
|
7
|
+
export function isHlsSource(source, mimeType) {
|
|
8
|
+
if (mimeType) {
|
|
9
|
+
return ['application/vnd.apple.mpegurl', 'application/x-mpegURL'].includes(mimeType);
|
|
10
|
+
}
|
|
11
|
+
return source.endsWith('.m3u8');
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcorevideo/player",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.2",
|
|
4
4
|
"description": "Gcore JavaScript video player",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"format": "prettier --write src/",
|
|
18
18
|
"lint": "oxlint -c ../../.oxlintrc.json --tsconfig=./tsconfig.json --fix --ignore-path=../../.gitignore src",
|
|
19
19
|
"ship": "npm run build && npm run build:bundle && npm publish --access public",
|
|
20
|
-
"test": "
|
|
21
|
-
"test:watch": "vitest --environment=jsdom watch"
|
|
20
|
+
"test": "NODE_OPTIONS='--trace-deprecation' vitest"
|
|
22
21
|
},
|
|
23
22
|
"repository": {
|
|
24
23
|
"type": "git",
|
|
@@ -36,12 +35,15 @@
|
|
|
36
35
|
"@rollup/plugin-commonjs": "^28.0.1",
|
|
37
36
|
"@rollup/plugin-json": "^6.1.0",
|
|
38
37
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
38
|
+
"@sinonjs/fake-timers": "^14.0.0",
|
|
39
39
|
"@types/node": "^22.10.1",
|
|
40
|
+
"@types/sinonjs__fake-timers": "^8.1.5",
|
|
40
41
|
"assert": "^2.1.0",
|
|
41
42
|
"nodemon": "^3.1.9",
|
|
42
43
|
"rollup": "^4.27.4",
|
|
43
44
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
44
|
-
"typescript": "^5.7.2"
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"vitest": "^3.0.4"
|
|
45
47
|
},
|
|
46
48
|
"dependencies": {
|
|
47
49
|
"@clappr/core": "^0.11.3",
|
package/src/Player.ts
CHANGED
|
@@ -15,17 +15,14 @@ import type {
|
|
|
15
15
|
CoreOptions,
|
|
16
16
|
CorePluginOptions,
|
|
17
17
|
} from './internal.types.js'
|
|
18
|
-
import type {
|
|
18
|
+
import type { PlayerMediaSourceDesc, PlayerPlugin } from './types.js'
|
|
19
19
|
import { PlayerConfig, PlayerEvent } from './types.js'
|
|
20
|
-
import DashPlayback from './plugins/dash-playback/DashPlayback.js'
|
|
21
|
-
import HlsPlayback from './plugins/hls-playback/HlsPlayback.js'
|
|
22
20
|
import {
|
|
23
|
-
|
|
24
|
-
buildSourcesSet,
|
|
21
|
+
buildMediaSourcesList,
|
|
25
22
|
unwrapSource,
|
|
23
|
+
wrapSource,
|
|
26
24
|
} from './utils/mediaSources.js'
|
|
27
|
-
|
|
28
|
-
// TODO implement transport retry/failover and fallback logic
|
|
25
|
+
import { registerPlaybacks } from './playback/index.js'
|
|
29
26
|
|
|
30
27
|
/**
|
|
31
28
|
* @beta
|
|
@@ -48,7 +45,7 @@ const DEFAULT_OPTIONS: PlayerConfig = {
|
|
|
48
45
|
/**
|
|
49
46
|
* @beta
|
|
50
47
|
*/
|
|
51
|
-
export type PlaybackModule = 'dash' | 'hls' | '
|
|
48
|
+
export type PlaybackModule = 'dash' | 'hls' | 'html5_video'
|
|
52
49
|
|
|
53
50
|
type PluginOptions = Record<string, unknown>
|
|
54
51
|
|
|
@@ -120,10 +117,6 @@ export class Player {
|
|
|
120
117
|
Log.setLevel(0)
|
|
121
118
|
}
|
|
122
119
|
|
|
123
|
-
trace(`${T} init`, {
|
|
124
|
-
// TODO selected options
|
|
125
|
-
})
|
|
126
|
-
|
|
127
120
|
this.configurePlaybacks()
|
|
128
121
|
const coreOpts = this.buildCoreOptions(playerElement)
|
|
129
122
|
const { core, container } = Loader.registeredPlugins
|
|
@@ -282,15 +275,12 @@ export class Player {
|
|
|
282
275
|
}
|
|
283
276
|
this.tunedIn = true
|
|
284
277
|
const player = this.player
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
}
|
|
278
|
+
this.bindContainerEventListeners(player)
|
|
279
|
+
player.core.on(
|
|
280
|
+
ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
|
|
281
|
+
() => this.bindContainerEventListeners(player),
|
|
282
|
+
null,
|
|
283
|
+
)
|
|
294
284
|
player.core.on(
|
|
295
285
|
ClapprEvents.CORE_SCREEN_ORIENTATION_CHANGED,
|
|
296
286
|
({ orientation }: { orientation: 'landscape' | 'portrait' }) => {
|
|
@@ -359,6 +349,7 @@ export class Player {
|
|
|
359
349
|
clearTimeout(this.tuneInTimerId)
|
|
360
350
|
this.tuneInTimerId = null
|
|
361
351
|
}
|
|
352
|
+
// TODO ensure that CORE_ACTIVE_CONTAINER_CHANGED does not get caught before onReady
|
|
362
353
|
setTimeout(() => this.tuneIn(), 0)
|
|
363
354
|
},
|
|
364
355
|
onResize: (newSize: { width: number; height: number }) => {
|
|
@@ -397,7 +388,11 @@ export class Player {
|
|
|
397
388
|
}
|
|
398
389
|
|
|
399
390
|
private buildCoreOptions(rootNode: HTMLElement): CoreOptions {
|
|
400
|
-
const
|
|
391
|
+
const sources = this.buildMediaSourcesList()
|
|
392
|
+
const source = sources[0]
|
|
393
|
+
trace(`${T} buildCoreOptions`, {
|
|
394
|
+
source
|
|
395
|
+
})
|
|
401
396
|
|
|
402
397
|
this.rootNode = rootNode
|
|
403
398
|
|
|
@@ -405,7 +400,7 @@ export class Player {
|
|
|
405
400
|
...this.config, // plugin settings
|
|
406
401
|
allowUserInteraction: true,
|
|
407
402
|
autoPlay: false,
|
|
408
|
-
dash: this.config.dash,
|
|
403
|
+
dash: this.config.dash, // TODO move this to the playback section
|
|
409
404
|
debug: this.config.debug || 'none',
|
|
410
405
|
events: this.events,
|
|
411
406
|
height: rootNode.clientHeight,
|
|
@@ -425,22 +420,36 @@ export class Player {
|
|
|
425
420
|
playbackType: this.config.playbackType,
|
|
426
421
|
width: rootNode.clientWidth,
|
|
427
422
|
source: source ? unwrapSource(source) : undefined,
|
|
423
|
+
sources, // prevent Clappr from loading all sources simultaneously
|
|
428
424
|
strings: this.config.strings,
|
|
429
425
|
}
|
|
430
426
|
return coreOptions
|
|
431
427
|
}
|
|
432
428
|
|
|
433
429
|
private configurePlaybacks() {
|
|
434
|
-
|
|
435
|
-
Loader.registerPlayback(DashPlayback)
|
|
436
|
-
Loader.registerPlayback(HlsPlayback)
|
|
430
|
+
registerPlaybacks()
|
|
437
431
|
}
|
|
438
432
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
433
|
+
private buildMediaSourcesList(): PlayerMediaSourceDesc[] {
|
|
434
|
+
return buildMediaSourcesList(
|
|
435
|
+
// TODO ensure unsupported sources are filtered out
|
|
436
|
+
this.config.sources.map((s) => wrapSource(s)),
|
|
443
437
|
this.config.priorityTransport,
|
|
444
|
-
)
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private bindContainerEventListeners(player: PlayerClappr) {
|
|
442
|
+
trace(`${T} bindContainerEventListeners`, {
|
|
443
|
+
activePlayback: player.core.activePlayback?.name,
|
|
444
|
+
})
|
|
445
|
+
if (Browser.isiOS && player.core.activePlayback) {
|
|
446
|
+
player.core.activePlayback.$el.on('webkitendfullscreen', () => {
|
|
447
|
+
try {
|
|
448
|
+
player.core.handleFullscreenChange()
|
|
449
|
+
} catch (e) {
|
|
450
|
+
reportError(e)
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
}
|
|
445
454
|
}
|
|
446
455
|
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
MockedObject,
|
|
8
|
+
vi,
|
|
9
|
+
} from 'vitest'
|
|
10
|
+
import FakeTimers from '@sinonjs/fake-timers'
|
|
11
|
+
import { LogTracer, Logger, setTracer } from '@gcorevideo/utils'
|
|
12
|
+
import { Loader, Player as PlayerClappr } from '@clappr/core'
|
|
13
|
+
import EventLite from 'event-lite'
|
|
14
|
+
|
|
15
|
+
import { Player } from '../Player'
|
|
16
|
+
import { TransportPreference } from '../types'
|
|
17
|
+
import { canPlayDash, canPlayHls } from '../playback'
|
|
18
|
+
import { PlaybackErrorCode } from '../playback.types'
|
|
19
|
+
import { isDashSource, isHlsSource } from '../utils/testUtils'
|
|
20
|
+
|
|
21
|
+
function createMockClapprPlayer(): MockedObject<typeof PlayerClappr> {
|
|
22
|
+
return {
|
|
23
|
+
core: Object.assign(new EventLite(), {
|
|
24
|
+
activeContainer: null,
|
|
25
|
+
activePlayback: null,
|
|
26
|
+
load: vi.fn(),
|
|
27
|
+
resize: vi.fn(),
|
|
28
|
+
trigger(event: string, ...args: any[]) {
|
|
29
|
+
this.emit(event, ...args)
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
attachTo: vi.fn(),
|
|
33
|
+
} as any
|
|
34
|
+
}
|
|
35
|
+
vi.mock('@clappr/core', async () => {
|
|
36
|
+
const imported = await import('@clappr/core')
|
|
37
|
+
return {
|
|
38
|
+
$: {
|
|
39
|
+
extend: vi.fn().mockImplementation((v, ...objs) => {
|
|
40
|
+
if (v !== true) {
|
|
41
|
+
objs.unshift(v)
|
|
42
|
+
}
|
|
43
|
+
return objs.reduce((acc, obj) => ({ ...acc, ...obj }), {})
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
Browser: {
|
|
47
|
+
isiOS: false,
|
|
48
|
+
},
|
|
49
|
+
Events: imported.Events,
|
|
50
|
+
Loader: {
|
|
51
|
+
registerPlugin: vi.fn(),
|
|
52
|
+
registeredPlaybacks: [],
|
|
53
|
+
registeredPlugins: { core: [], container: [] },
|
|
54
|
+
unregisterPlugin: vi.fn(),
|
|
55
|
+
},
|
|
56
|
+
Player: vi.fn().mockImplementation(createMockClapprPlayer),
|
|
57
|
+
Utils: {
|
|
58
|
+
now: vi.fn().mockReturnValue(150),
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
vi.mock('../playback/index.ts', () => ({
|
|
64
|
+
registerPlaybacks: vi.fn(),
|
|
65
|
+
canPlayDash: vi.fn(),
|
|
66
|
+
canPlayHls: vi.fn(),
|
|
67
|
+
}))
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
vi.mocked(PlayerClappr).mockClear()
|
|
71
|
+
setTracer(new LogTracer('Player.test.js'))
|
|
72
|
+
vi.mocked(canPlayDash)
|
|
73
|
+
.mockReset()
|
|
74
|
+
.mockImplementation((source, mimeType) => isDashSource(source, mimeType))
|
|
75
|
+
vi.mocked(canPlayHls)
|
|
76
|
+
.mockReset()
|
|
77
|
+
.mockImplementation((source, mimeType) => isHlsSource(source, mimeType))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('Player', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
vi.mocked(Loader).registeredPlaybacks = [
|
|
83
|
+
new MockDashPlayback({} as any, {} as any),
|
|
84
|
+
new MockHlsPlayback({} as any, {} as any),
|
|
85
|
+
new MockHTML5VideoPlayback({} as any, {} as any),
|
|
86
|
+
]
|
|
87
|
+
})
|
|
88
|
+
describe('selecting media source', () => {
|
|
89
|
+
describe.each([
|
|
90
|
+
[undefined, true, true, 'http://0eab.cdn.globo.com/1932-1447.mpd'],
|
|
91
|
+
['dash', true, true, 'http://0eab.cdn.globo.com/1932-1447.mpd'],
|
|
92
|
+
['dash', false, true, 'http://0eab.cdn.globo.com/1932-1447.m3u8'],
|
|
93
|
+
['dash', false, false, undefined],
|
|
94
|
+
['hls', true, true, 'http://0eab.cdn.globo.com/1932-1447.m3u8'],
|
|
95
|
+
['hls', true, false, 'http://0eab.cdn.globo.com/1932-1447.mpd'],
|
|
96
|
+
['hls', false, false, undefined],
|
|
97
|
+
['auto', true, true, 'http://0eab.cdn.globo.com/1932-1447.mpd'],
|
|
98
|
+
['auto', true, false, 'http://0eab.cdn.globo.com/1932-1447.mpd'],
|
|
99
|
+
['auto', false, true, 'http://0eab.cdn.globo.com/1932-1447.m3u8'],
|
|
100
|
+
['auto', false, false, undefined],
|
|
101
|
+
])(
|
|
102
|
+
' according to the preference (%s) and capabilities (dash=%s, hls=%s)',
|
|
103
|
+
(priority, dash, hls, source: string | undefined) => {
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
if (dash === false) {
|
|
106
|
+
vi.mocked(canPlayDash).mockReturnValue(false)
|
|
107
|
+
}
|
|
108
|
+
if (hls === false) {
|
|
109
|
+
vi.mocked(canPlayHls).mockReturnValue(false)
|
|
110
|
+
}
|
|
111
|
+
const player = new Player({
|
|
112
|
+
priorityTransport: priority as TransportPreference | undefined,
|
|
113
|
+
sources: [
|
|
114
|
+
{ source: 'http://0eab.cdn.globo.com/1932-1447.mpd' },
|
|
115
|
+
{
|
|
116
|
+
source: 'http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
117
|
+
mimeType: 'application/vnd.apple.mpegurl',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
source: 'http://0eab.cdn.globo.com/1932-1447_mpegts.m3u8',
|
|
121
|
+
mimeType: 'application/mpegts',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})
|
|
125
|
+
const node = document.createElement('div')
|
|
126
|
+
player.attachTo(node)
|
|
127
|
+
})
|
|
128
|
+
afterEach(() => {
|
|
129
|
+
vi.mocked(canPlayDash).mockImplementation((source, mimeType) =>
|
|
130
|
+
isDashSource(source, mimeType),
|
|
131
|
+
)
|
|
132
|
+
vi.mocked(canPlayHls).mockImplementation((source, mimeType) =>
|
|
133
|
+
isHlsSource(source, mimeType),
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
it('should select the first supported available source', () => {
|
|
137
|
+
expect(PlayerClappr).toHaveBeenCalledWith(
|
|
138
|
+
expect.objectContaining({
|
|
139
|
+
source,
|
|
140
|
+
}),
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
describe('on media source failure', () => {
|
|
147
|
+
let player: Player
|
|
148
|
+
let clappr: any
|
|
149
|
+
let clock: ReturnType<typeof FakeTimers.install>
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
Logger.enable('*')
|
|
152
|
+
clock = FakeTimers.install()
|
|
153
|
+
vi.mocked(Loader).registeredPlaybacks = [
|
|
154
|
+
MockDashPlayback,
|
|
155
|
+
MockHlsPlayback,
|
|
156
|
+
MockHTML5VideoPlayback,
|
|
157
|
+
]
|
|
158
|
+
clappr = createMockClapprPlayer() as any
|
|
159
|
+
const playback = new MockDashPlayback({} as any, {} as any)
|
|
160
|
+
clappr.core.activePlayback = playback
|
|
161
|
+
vi.mocked(PlayerClappr).mockReturnValue(clappr as any)
|
|
162
|
+
player = new Player({
|
|
163
|
+
sources: [
|
|
164
|
+
{
|
|
165
|
+
source: 'http://0eab.cdn.globo.com/1932-1447.mpd',
|
|
166
|
+
mimeType: 'application/dash+xml',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
source: 'http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
170
|
+
mimeType: 'application/vnd.apple.mpegurl',
|
|
171
|
+
},
|
|
172
|
+
// TODO configure as a separate fallback source
|
|
173
|
+
// {
|
|
174
|
+
// source: 'http://0eab.cdn.globo.com/1932-1447_mpegts.m3u8',
|
|
175
|
+
// mimeType: 'application/mpegts',
|
|
176
|
+
// },
|
|
177
|
+
],
|
|
178
|
+
})
|
|
179
|
+
const node = document.createElement('div')
|
|
180
|
+
player.attachTo(node)
|
|
181
|
+
await clock.tickAsync(4000)
|
|
182
|
+
playback.trigger('playback:error', {
|
|
183
|
+
code: PlaybackErrorCode.MediaSourceUnavailable, // TODO rename to MediaSourceUnavailable
|
|
184
|
+
message: 'Failed to download http://0eab.cdn.globo.com/1932-1447.mpd',
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
afterEach(() => {
|
|
188
|
+
clock.uninstall()
|
|
189
|
+
Logger.enable('')
|
|
190
|
+
})
|
|
191
|
+
it('should try the next sources with a short delay', async () => {
|
|
192
|
+
await clock.tickAsync(400) // 250 initial + random jitter up to 150
|
|
193
|
+
expect(clappr.core.load).toHaveBeenCalledWith(
|
|
194
|
+
'http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
195
|
+
'application/vnd.apple.mpegurl',
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
// const playback = new MockHlsPlayback({} as any, {} as any)
|
|
199
|
+
// clappr.core.activePlayback = playback
|
|
200
|
+
// clappr.core.trigger('core:active:container:changed', {})
|
|
201
|
+
// await clock.tickAsync(300)
|
|
202
|
+
// clappr.core.load.mockClear()
|
|
203
|
+
// playback.trigger('playback:error', {
|
|
204
|
+
// code: PlaybackErrorCode.MediaSourceUnavailable,
|
|
205
|
+
// message: 'Failed to download http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
206
|
+
// })
|
|
207
|
+
// await clock.tickAsync(400)
|
|
208
|
+
// expect(clappr.core.load).toHaveBeenCalledWith(
|
|
209
|
+
// 'http://0eab.cdn.globo.com/1932-1447_mpegts.m3u8',
|
|
210
|
+
// 'application/mpegts',
|
|
211
|
+
// )
|
|
212
|
+
})
|
|
213
|
+
describe('when rolled over to the first source', () => {
|
|
214
|
+
beforeEach(async () => {
|
|
215
|
+
await clock.tickAsync(400) // initial retry has passed
|
|
216
|
+
clappr.core.load.mockClear()
|
|
217
|
+
const playback = new MockHlsPlayback({} as any, {} as any)
|
|
218
|
+
clappr.core.activePlayback = playback
|
|
219
|
+
clappr.core.trigger('core:active:container:changed', {})
|
|
220
|
+
await clock.tickAsync(300)
|
|
221
|
+
playback.trigger('playback:error', {
|
|
222
|
+
code: PlaybackErrorCode.MediaSourceUnavailable,
|
|
223
|
+
message:
|
|
224
|
+
'Failed to download http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
225
|
+
})
|
|
226
|
+
await clock.tickAsync(400)
|
|
227
|
+
clappr.core.load.mockClear()
|
|
228
|
+
})
|
|
229
|
+
afterEach(() => {
|
|
230
|
+
// Logger.enable('')
|
|
231
|
+
})
|
|
232
|
+
it('should start increasing the delay exponentially per source', async () => {
|
|
233
|
+
expect(clappr.core.load).not.toHaveBeenCalled()
|
|
234
|
+
await clock.tickAsync(250)
|
|
235
|
+
expect(clappr.core.load).toHaveBeenCalledWith(
|
|
236
|
+
'http://0eab.cdn.globo.com/1932-1447.mpd',
|
|
237
|
+
'application/dash+xml',
|
|
238
|
+
)
|
|
239
|
+
// )
|
|
240
|
+
// // TODO
|
|
241
|
+
// clappr.core.load.mockClear()
|
|
242
|
+
// const p2 = new MockDashPlayback({} as any, {} as any)
|
|
243
|
+
// clappr.core.activePlayback = p2
|
|
244
|
+
// clappr.core.trigger('core:active:container:changed', {})
|
|
245
|
+
// await clock.tickAsync(300)
|
|
246
|
+
// p2.trigger('playback:error', {
|
|
247
|
+
// code: PlaybackErrorCode.MediaSourceUnavailable,
|
|
248
|
+
// message: 'Failed to download http://0eab.cdn.globo.com/1932-1447.mpd',
|
|
249
|
+
// })
|
|
250
|
+
// await clock.tickAsync(400)
|
|
251
|
+
// expect(clappr.core.load).not.toHaveBeenCalledWith(
|
|
252
|
+
// 'http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
253
|
+
// 'application/vnd.apple.mpegurl',
|
|
254
|
+
// )
|
|
255
|
+
// await clock.tickAsync(250)
|
|
256
|
+
// expect(clappr.core.load).toHaveBeenCalledWith(
|
|
257
|
+
// 'http://0eab.cdn.globo.com/1932-1447.m3u8',
|
|
258
|
+
// 'application/vnd.apple.mpegurl',
|
|
259
|
+
// )
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
class MockPlayback extends EventLite {
|
|
266
|
+
constructor(
|
|
267
|
+
protected options: any,
|
|
268
|
+
readonly i18n: any,
|
|
269
|
+
protected playerError?: any,
|
|
270
|
+
) {
|
|
271
|
+
super()
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get name() {
|
|
275
|
+
return 'mock'
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
consent() {}
|
|
279
|
+
|
|
280
|
+
play() {}
|
|
281
|
+
|
|
282
|
+
pause() {}
|
|
283
|
+
|
|
284
|
+
stop() {}
|
|
285
|
+
|
|
286
|
+
destroy() {}
|
|
287
|
+
|
|
288
|
+
seek() {}
|
|
289
|
+
|
|
290
|
+
seekPercentage() {}
|
|
291
|
+
|
|
292
|
+
getDuration() {
|
|
293
|
+
return 100
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
enterPiP() {}
|
|
297
|
+
|
|
298
|
+
exitPiP() {}
|
|
299
|
+
|
|
300
|
+
getPlaybackType() {
|
|
301
|
+
return 'live'
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
getStartTimeOffset() {
|
|
305
|
+
return 0
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
getCurrentTime() {
|
|
309
|
+
return 0
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
isHighDefinitionInUse() {
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
mute() {}
|
|
317
|
+
|
|
318
|
+
unmute() {}
|
|
319
|
+
|
|
320
|
+
volume() {}
|
|
321
|
+
|
|
322
|
+
configure() {}
|
|
323
|
+
|
|
324
|
+
attemptAutoPlay() {
|
|
325
|
+
return true
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
canAutoPlay() {
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
onResize() {
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
trigger(event: string, ...args: any[]) {
|
|
337
|
+
this.emit(event, ...args)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
class MockDashPlayback extends MockPlayback {
|
|
342
|
+
get name() {
|
|
343
|
+
return 'dash'
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
class MockHlsPlayback extends MockPlayback {
|
|
348
|
+
get name() {
|
|
349
|
+
return 'hls'
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
class MockHTML5VideoPlayback extends MockPlayback {
|
|
354
|
+
get name() {
|
|
355
|
+
return 'html5_video'
|
|
356
|
+
}
|
|
357
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export { setTracer } from
|
|
12
|
-
export * from
|
|
13
|
-
export * from
|
|
14
|
-
export * from
|
|
15
|
-
export * from
|
|
11
|
+
export { setTracer } from '@gcorevideo/utils'
|
|
12
|
+
export * from './Player.js'
|
|
13
|
+
export * from './playback.types.js'
|
|
14
|
+
export * from './types.js'
|
|
15
|
+
export * from './version.js'
|