@gcorevideo/player 2.10.0 → 2.12.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.
- 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 -1185
- package/lib/Player.d.ts +3 -2
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +33 -20
- package/lib/index.d.ts +6 -5
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -5
- package/lib/internal.types.d.ts +3 -1
- 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 +9 -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 +0 -1
- package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
- package/lib/plugins/dash-playback/DashPlayback.js +32 -96
- 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 +3 -3
- package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/plugins/hls-playback/HlsPlayback.js +136 -63
- 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 +46 -26
- package/src/__tests__/Player.test.ts +357 -0
- package/src/index.ts +6 -5
- package/src/internal.types.ts +3 -1
- package/src/playback/index.ts +17 -0
- package/src/playback.types.ts +11 -1
- package/src/plugins/dash-playback/DashPlayback.ts +38 -114
- package/src/plugins/hls-playback/HlsPlayback.ts +561 -386
- 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 +67 -63
- 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.1",
|
|
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,16 @@ import type {
|
|
|
15
15
|
CoreOptions,
|
|
16
16
|
CorePluginOptions,
|
|
17
17
|
} from './internal.types.js'
|
|
18
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
PlayerPlugin,
|
|
20
|
+
} from './types.js'
|
|
19
21
|
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
22
|
import {
|
|
23
|
-
|
|
24
|
-
buildSourcesSet,
|
|
23
|
+
buildMediaSourcesList,
|
|
25
24
|
unwrapSource,
|
|
25
|
+
wrapSource,
|
|
26
26
|
} from './utils/mediaSources.js'
|
|
27
|
-
|
|
28
|
-
// TODO implement transport retry/failover and fallback logic
|
|
27
|
+
import { registerPlaybacks } from './playback/index.js'
|
|
29
28
|
|
|
30
29
|
/**
|
|
31
30
|
* @beta
|
|
@@ -48,7 +47,7 @@ const DEFAULT_OPTIONS: PlayerConfig = {
|
|
|
48
47
|
/**
|
|
49
48
|
* @beta
|
|
50
49
|
*/
|
|
51
|
-
export type PlaybackModule = 'dash' | 'hls' | '
|
|
50
|
+
export type PlaybackModule = 'dash' | 'hls' | 'html5_video'
|
|
52
51
|
|
|
53
52
|
type PluginOptions = Record<string, unknown>
|
|
54
53
|
|
|
@@ -75,6 +74,12 @@ export class Player {
|
|
|
75
74
|
|
|
76
75
|
private tunedIn = false
|
|
77
76
|
|
|
77
|
+
// private sourcesList: PlayerMediaSourceDesc[] = []
|
|
78
|
+
|
|
79
|
+
// private currentSourceIndex = 0
|
|
80
|
+
|
|
81
|
+
// private sourcesDelay: Record<string, number> = {}
|
|
82
|
+
|
|
78
83
|
constructor(config: PlayerConfig) {
|
|
79
84
|
this.setConfig(config)
|
|
80
85
|
}
|
|
@@ -282,15 +287,12 @@ export class Player {
|
|
|
282
287
|
}
|
|
283
288
|
this.tunedIn = true
|
|
284
289
|
const player = this.player
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
})
|
|
293
|
-
}
|
|
290
|
+
this.bindContainerEventListeners(player)
|
|
291
|
+
player.core.on(
|
|
292
|
+
ClapprEvents.CORE_ACTIVE_CONTAINER_CHANGED,
|
|
293
|
+
() => this.bindContainerEventListeners(player),
|
|
294
|
+
null,
|
|
295
|
+
)
|
|
294
296
|
player.core.on(
|
|
295
297
|
ClapprEvents.CORE_SCREEN_ORIENTATION_CHANGED,
|
|
296
298
|
({ orientation }: { orientation: 'landscape' | 'portrait' }) => {
|
|
@@ -359,6 +361,7 @@ export class Player {
|
|
|
359
361
|
clearTimeout(this.tuneInTimerId)
|
|
360
362
|
this.tuneInTimerId = null
|
|
361
363
|
}
|
|
364
|
+
// TODO ensure that CORE_ACTIVE_CONTAINER_CHANGED does not get caught before onReady
|
|
362
365
|
setTimeout(() => this.tuneIn(), 0)
|
|
363
366
|
},
|
|
364
367
|
onResize: (newSize: { width: number; height: number }) => {
|
|
@@ -397,7 +400,11 @@ export class Player {
|
|
|
397
400
|
}
|
|
398
401
|
|
|
399
402
|
private buildCoreOptions(rootNode: HTMLElement): CoreOptions {
|
|
400
|
-
|
|
403
|
+
this.buildMediaSourcesList()
|
|
404
|
+
const source = this.config.sources[0]
|
|
405
|
+
if (!source) {
|
|
406
|
+
trace(`${T} buildCoreOptions no source selected`)
|
|
407
|
+
}
|
|
401
408
|
|
|
402
409
|
this.rootNode = rootNode
|
|
403
410
|
|
|
@@ -425,22 +432,35 @@ export class Player {
|
|
|
425
432
|
playbackType: this.config.playbackType,
|
|
426
433
|
width: rootNode.clientWidth,
|
|
427
434
|
source: source ? unwrapSource(source) : undefined,
|
|
435
|
+
sources: undefined,
|
|
428
436
|
strings: this.config.strings,
|
|
429
437
|
}
|
|
430
438
|
return coreOptions
|
|
431
439
|
}
|
|
432
440
|
|
|
433
441
|
private configurePlaybacks() {
|
|
434
|
-
|
|
435
|
-
Loader.registerPlayback(DashPlayback)
|
|
436
|
-
Loader.registerPlayback(HlsPlayback)
|
|
442
|
+
registerPlaybacks()
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
buildSourcesSet(this.config.sources),
|
|
445
|
+
private buildMediaSourcesList() {
|
|
446
|
+
return buildMediaSourcesList( // TODO ensure unsupported sources are filtered out
|
|
447
|
+
this.config.sources.map(s => wrapSource(s)),
|
|
443
448
|
this.config.priorityTransport,
|
|
444
|
-
)
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private bindContainerEventListeners(player: PlayerClappr) {
|
|
453
|
+
trace(`${T} bindContainerEventListeners`, {
|
|
454
|
+
activePlayback: player.core.activePlayback?.name,
|
|
455
|
+
})
|
|
456
|
+
if (Browser.isiOS && player.core.activePlayback) {
|
|
457
|
+
player.core.activePlayback.$el.on('webkitendfullscreen', () => {
|
|
458
|
+
try {
|
|
459
|
+
player.core.handleFullscreenChange()
|
|
460
|
+
} catch (e) {
|
|
461
|
+
reportError(e)
|
|
462
|
+
}
|
|
463
|
+
})
|
|
464
|
+
}
|
|
445
465
|
}
|
|
446
466
|
}
|
|
@@ -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,9 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
export { setTracer } from
|
|
12
|
-
export * from
|
|
13
|
-
export
|
|
14
|
-
export * from
|
|
15
|
-
export * from
|
|
11
|
+
export { setTracer } from '@gcorevideo/utils'
|
|
12
|
+
export * from './Player.js'
|
|
13
|
+
export { PlaybackError } from './internal.types.js'
|
|
14
|
+
export * from './playback.types.js'
|
|
15
|
+
export * from './types.js'
|
|
16
|
+
export * from './version.js'
|