@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.
Files changed (65) hide show
  1. package/LICENSE +13 -0
  2. package/coverage/clover.xml +6 -0
  3. package/coverage/coverage-final.json +1 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +101 -0
  8. package/coverage/lcov-report/prettify.css +1 -0
  9. package/coverage/lcov-report/prettify.js +2 -0
  10. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  11. package/coverage/lcov-report/sorter.js +196 -0
  12. package/coverage/lcov.info +0 -0
  13. package/dist/index.js +3115 -1185
  14. package/lib/Player.d.ts +3 -2
  15. package/lib/Player.d.ts.map +1 -1
  16. package/lib/Player.js +33 -20
  17. package/lib/index.d.ts +6 -5
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +5 -5
  20. package/lib/internal.types.d.ts +3 -1
  21. package/lib/internal.types.d.ts.map +1 -1
  22. package/lib/playback/index.d.ts +4 -0
  23. package/lib/playback/index.d.ts.map +1 -0
  24. package/lib/playback/index.js +13 -0
  25. package/lib/playback.types.d.ts +9 -0
  26. package/lib/playback.types.d.ts.map +1 -1
  27. package/lib/playback.types.js +9 -1
  28. package/lib/plugins/dash-playback/DashPlayback.d.ts +0 -1
  29. package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
  30. package/lib/plugins/dash-playback/DashPlayback.js +32 -96
  31. package/lib/plugins/dash-playback/types.d.ts +6 -0
  32. package/lib/plugins/dash-playback/types.d.ts.map +1 -0
  33. package/lib/plugins/dash-playback/types.js +1 -0
  34. package/lib/plugins/hls-playback/HlsPlayback.d.ts +3 -3
  35. package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
  36. package/lib/plugins/hls-playback/HlsPlayback.js +136 -63
  37. package/lib/types.d.ts +3 -3
  38. package/lib/types.d.ts.map +1 -1
  39. package/lib/utils/mediaSources.d.ts +14 -6
  40. package/lib/utils/mediaSources.d.ts.map +1 -1
  41. package/lib/utils/mediaSources.js +56 -53
  42. package/lib/utils/testUtils.d.ts +3 -0
  43. package/lib/utils/testUtils.d.ts.map +1 -0
  44. package/lib/utils/testUtils.js +12 -0
  45. package/package.json +6 -4
  46. package/src/Player.ts +46 -26
  47. package/src/__tests__/Player.test.ts +357 -0
  48. package/src/index.ts +6 -5
  49. package/src/internal.types.ts +3 -1
  50. package/src/playback/index.ts +17 -0
  51. package/src/playback.types.ts +11 -1
  52. package/src/plugins/dash-playback/DashPlayback.ts +38 -114
  53. package/src/plugins/hls-playback/HlsPlayback.ts +561 -386
  54. package/src/types.ts +5 -3
  55. package/src/typings/@clappr/core/error_mixin.d.ts +0 -2
  56. package/src/typings/@clappr/core/index.d.ts +5 -0
  57. package/src/typings/@clappr/index.d.ts +1 -0
  58. package/src/utils/__tests__/mediaSources.test.ts +230 -0
  59. package/src/utils/mediaSources.ts +67 -63
  60. package/src/utils/testUtils.ts +15 -0
  61. package/tsconfig.json +0 -9
  62. package/tsconfig.tsbuildinfo +1 -1
  63. package/vitest.config.ts +8 -0
  64. package/licenses.json +0 -782
  65. package/src/utils/queryParams.ts +0 -5
@@ -1,73 +1,76 @@
1
- import DashPlayback from '../plugins/dash-playback/DashPlayback';
2
- import HlsPlayback from '../plugins/hls-playback/HlsPlayback';
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 [s, t] = typeof ps === 'string' ? [ps, ''] : [ps.source, ps.mimeType];
12
- if (DashPlayback.canPlay(s, t)) {
13
- sv.dash = s;
15
+ const ws = wrapSource(ps);
16
+ if (canPlayDash(ws.source, ws.mimeType)) {
17
+ sv.dash = ws;
14
18
  }
15
- else if (HlsPlayback.canPlay(s, t)) {
16
- sv.hls = s;
19
+ else if (canPlayHls(ws.source, ws.mimeType)) {
20
+ sv.hls = ws;
17
21
  }
18
22
  else {
19
- sv.master = s;
23
+ sv.mpegts = ws;
20
24
  }
21
25
  });
22
26
  return sv;
23
27
  }
24
- export function buildSourcesPriorityList(sources, priorityTransport = 'auto') {
25
- const msl = [];
26
- switch (priorityTransport) {
27
- case 'dash':
28
- addDash();
29
- break;
30
- case 'hls':
31
- addHls();
32
- break;
33
- case 'mpegts':
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
- function addHls() {
54
- if (sources.hls && HlsPlayback.canPlay(sources.hls, undefined)) {
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
- function addDash() {
65
- if (sources.dash && DashPlayback.canPlay(sources.dash, undefined)) {
66
- msl.push(sources.dash);
67
- sources.dash = null;
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 unwrapSource(s) {
72
- return typeof s === 'string' ? s : s.source;
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,3 @@
1
+ export declare function isDashSource(source: string, mimeType?: string): boolean;
2
+ export declare function isHlsSource(source: string, mimeType?: string): boolean;
3
+ //# sourceMappingURL=testUtils.d.ts.map
@@ -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.10.0",
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": "vitest --environment=jsdom run",
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 { PlayerMediaSource, PlayerPlugin } from './types.js'
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
- buildSourcesPriorityList,
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' | 'native'
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
- if (Browser.isiOS && player.core.activePlayback) {
286
- player.core.activePlayback.$el.on('webkitendfullscreen', () => {
287
- try {
288
- player.core.handleFullscreenChange()
289
- } catch (e) {
290
- reportError(e)
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
- const source = this.selectMediaSource()
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
- // TODO check if there are DASH and HLS sources and don't register the respective playbacks if not
435
- Loader.registerPlayback(DashPlayback)
436
- Loader.registerPlayback(HlsPlayback)
442
+ registerPlaybacks()
437
443
  }
438
444
 
439
- // Select a single source to play according to the priority transport and the modules support
440
- private selectMediaSource(): PlayerMediaSource | undefined {
441
- return buildSourcesPriorityList(
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
- )[0]
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 "@gcorevideo/utils";
12
- export * from "./Player.js"
13
- export * from "./playback.types.js";
14
- export * from "./types.js";
15
- export * from "./version.js";
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'