@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.
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 -1209
  14. package/lib/Player.d.ts +3 -2
  15. package/lib/Player.d.ts.map +1 -1
  16. package/lib/Player.js +32 -24
  17. package/lib/index.d.ts +5 -5
  18. package/lib/index.d.ts.map +1 -1
  19. package/lib/index.js +5 -5
  20. package/lib/internal.types.d.ts +1 -10
  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 +19 -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 +1 -1
  29. package/lib/plugins/dash-playback/DashPlayback.d.ts.map +1 -1
  30. package/lib/plugins/dash-playback/DashPlayback.js +39 -100
  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 +6 -7
  35. package/lib/plugins/hls-playback/HlsPlayback.d.ts.map +1 -1
  36. package/lib/plugins/hls-playback/HlsPlayback.js +131 -80
  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 +40 -31
  47. package/src/__tests__/Player.test.ts +357 -0
  48. package/src/index.ts +5 -5
  49. package/src/internal.types.ts +1 -12
  50. package/src/playback/index.ts +17 -0
  51. package/src/playback.types.ts +29 -8
  52. package/src/plugins/dash-playback/DashPlayback.ts +44 -120
  53. package/src/plugins/hls-playback/HlsPlayback.ts +544 -390
  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 +78 -64
  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.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": "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,14 @@ 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 { 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
- buildSourcesPriorityList,
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' | 'native'
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
- 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
- }
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 source = this.selectMediaSource()
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
- // 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)
430
+ registerPlaybacks()
437
431
  }
438
432
 
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),
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
- )[0]
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 "@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 * from './playback.types.js'
14
+ export * from './types.js'
15
+ export * from './version.js'