@clappr/hlsjs-playback 3.0.2 → 3.1.0

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/package.json CHANGED
@@ -1,20 +1,19 @@
1
1
  {
2
2
  "name": "@clappr/hlsjs-playback",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "HLS Playback based on hls.js",
5
5
  "main": "./dist/hlsjs-playback.js",
6
6
  "module": "./dist/hlsjs-playback.esm.js",
7
7
  "scripts": {
8
- "start": "SERVE=true rollup --config --bundleConfigAsCjs --watch",
9
- "start:with-reload": "SERVE=true RELOAD=true rollup --config --bundleConfigAsCjs --watch",
10
- "build": "rollup --config --bundleConfigAsCjs",
11
- "watch": "rollup --config --bundleConfigAsCjs --watch",
12
- "bundle-check": "ANALYZE_BUNDLE=true rollup --config --bundleConfigAsCjs",
13
- "release": "rm -rf dist && MINIMIZE=true rollup --config --bundleConfigAsCjs",
14
- "test": "jest ./src --coverage --silent",
15
- "test:smoke": "jest ./src/dist.smoke.test.js --testPathIgnorePatterns=/node_modules/ --silent",
16
- "test:watch": "jest ./src --watch",
17
- "lint": "eslint *.js ./src",
8
+ "start": "vite",
9
+ "build": "vite build",
10
+ "watch": "vite build --watch",
11
+ "bundle-check": "ANALYZE_BUNDLE=true vite build",
12
+ "release": "rm -rf dist && vite build && vite build --mode minify",
13
+ "test": "vitest run --coverage",
14
+ "test:smoke": "vitest run src/dist.smoke.test.js",
15
+ "test:watch": "vitest",
16
+ "lint": "eslint --no-error-on-unmatched-pattern *.js ./src",
18
17
  "lint:fix": "yarn lint -- --fix",
19
18
  "prepublishOnly": "lerna run release --scope=@clappr/hlsjs-playback"
20
19
  },
@@ -41,7 +40,7 @@
41
40
  "hls.js": "^1"
42
41
  },
43
42
  "devDependencies": {
44
- "@clappr/core": "^0.16.2",
45
- "hls.js": "~1.6.17"
43
+ "@clappr/core": "^0.17.0",
44
+ "hls.js": "^1"
46
45
  }
47
46
  }
@@ -12,7 +12,7 @@ const { TextEncoder, TextDecoder } = require('util')
12
12
  global.TextEncoder = global.TextEncoder || TextEncoder
13
13
  global.TextDecoder = global.TextDecoder || TextDecoder
14
14
  const { JSDOM } = require('jsdom')
15
- const { expectNoNativeClasses } = require('../../../test/dist-contract')
15
+ const { expectEs5Syntax, expectSourcemapFromSrc } = require('../../../test/dist-contract')
16
16
 
17
17
  const DIST = path.join(__dirname, '..', 'dist')
18
18
 
@@ -29,7 +29,7 @@ function readArtifact(name) {
29
29
  }
30
30
 
31
31
  function loadArtifact(name) {
32
- jest.resetModules()
32
+ vi.resetModules()
33
33
  return require(path.join(DIST, name))
34
34
  }
35
35
 
@@ -68,8 +68,8 @@ function assertPlaybackContract(HlsjsPlaybackExport) {
68
68
  expect(typeof HLSJS.version).toBe('string')
69
69
  expect(HLSJS.version.length).toBeGreaterThan(0)
70
70
 
71
- jest.spyOn(HLSJS, 'isSupported').mockReturnValue(true)
72
- jest.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation(() => {})
71
+ vi.spyOn(HLSJS, 'isSupported').mockReturnValue(true)
72
+ vi.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation(() => {})
73
73
 
74
74
  expect(Playback.canPlay('http://example.com/video.m3u8')).toBe(true)
75
75
  expect(Playback.canPlay('http://example.com/video.m3u8?token=1')).toBe(true)
@@ -81,6 +81,7 @@ function assertPlaybackContract(HlsjsPlaybackExport) {
81
81
 
82
82
  function assertSourceMappingURL(filename) {
83
83
  expect(readArtifact(filename)).toContain(`sourceMappingURL=${filename}.map`)
84
+ expectSourcemapFromSrc(readArtifact(`${filename}.map`), filename)
84
85
  }
85
86
 
86
87
  const EXPECTED_SOURCEMAPS = Object.values(ARTIFACTS)
@@ -88,7 +89,7 @@ const EXPECTED_SOURCEMAPS = Object.values(ARTIFACTS)
88
89
  .sort()
89
90
 
90
91
  afterEach(() => {
91
- jest.restoreAllMocks()
92
+ vi.restoreAllMocks()
92
93
  })
93
94
 
94
95
  describe.each([
@@ -105,7 +106,7 @@ describe.each([
105
106
  })
106
107
 
107
108
  test('does not emit native class syntax', () => {
108
- expectNoNativeClasses(readArtifact(filename))
109
+ expectEs5Syntax(readArtifact(filename), filename)
109
110
  })
110
111
  })
111
112
 
@@ -120,10 +121,16 @@ describe('dist sourcemap inventory', () => {
120
121
  })
121
122
 
122
123
  describe('hls.js peer identity', () => {
123
- test.each(Object.values(ARTIFACTS))('%s defers to the consumer hls.js', filename => {
124
+ test.each(UMD_ARTIFACTS)('%s defers to the consumer hls.js', filename => {
124
125
  const Playback = resolvePlayback(loadArtifact(filename))
125
126
  expect(Playback.HLSJS).toBe(require('hls.js'))
126
127
  })
128
+
129
+ test(`${ARTIFACTS.esm} defers to the consumer hls.js`, async () => {
130
+ const Playback = resolvePlayback(loadArtifact(ARTIFACTS.esm))
131
+ const { default: hls } = await import('hls.js')
132
+ expect(Playback.HLSJS).toBe(hls)
133
+ })
127
134
  })
128
135
 
129
136
  describe('UMD global.Hls branch', () => {
package/src/hls.js CHANGED
@@ -415,7 +415,7 @@ export default class HlsjsPlayback extends HTML5Video {
415
415
  }
416
416
  let formattedError
417
417
  if (data.response) error.description += `, response: ${JSON.stringify(data.response)}`
418
- // only report/handle errors if they are fatal
418
+ // only handle errors if they are fatal
419
419
  // hlsjs should automatically handle non fatal errors
420
420
  if (data.fatal) {
421
421
  if (this._recoverAttemptsRemaining > 0) {
@@ -479,6 +479,7 @@ export default class HlsjsPlayback extends HTML5Video {
479
479
 
480
480
  error.level = PlayerError.Levels.WARN
481
481
  Log.warn('hlsjs: non-fatal error occurred', { evt, data })
482
+ this.trigger(Events.PLAYBACK_WARNING, error, this.name)
482
483
  }
483
484
  }
484
485
 
package/src/hls.test.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Core, Events, Log, Playback } from '@clappr/core'
1
+ import { Core, Events, Log, Playback, PlayerError } from '@clappr/core'
2
2
  import HlsjsPlayback from './hls.js'
3
3
  import HLSJS from 'hls.js'
4
4
 
@@ -25,17 +25,17 @@ const makeLevelData = ({
25
25
 
26
26
  const stubHls = playback => {
27
27
  playback._hls = {
28
- startLoad: jest.fn(),
29
- recoverMediaError: jest.fn(),
30
- swapAudioCodec: jest.fn(),
31
- destroy: jest.fn()
28
+ startLoad: vi.fn(),
29
+ recoverMediaError: vi.fn(),
30
+ swapAudioCodec: vi.fn(),
31
+ destroy: vi.fn()
32
32
  }
33
33
  return playback._hls
34
34
  }
35
35
 
36
36
  const freezeNow = (playback, initialMs = 1000000) => {
37
37
  let current = initialMs
38
- jest.spyOn(playback, '_now', 'get').mockImplementation(() => current)
38
+ vi.spyOn(playback, '_now', 'get').mockImplementation(() => current)
39
39
  return {
40
40
  advance(ms) {
41
41
  current += ms
@@ -45,12 +45,13 @@ const freezeNow = (playback, initialMs = 1000000) => {
45
45
 
46
46
  describe('HlsjsPlayback', () => {
47
47
  beforeEach(() => {
48
- jest.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(() => {})
49
- jest.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation(() => {})
48
+ vi.spyOn(window.HTMLMediaElement.prototype, 'play').mockImplementation(() => {})
49
+ vi.spyOn(window.HTMLMediaElement.prototype, 'load').mockImplementation(() => {})
50
50
  })
51
51
 
52
52
  afterEach(() => {
53
- jest.restoreAllMocks()
53
+ vi.restoreAllMocks()
54
+ vi.clearAllTimers()
54
55
  })
55
56
 
56
57
  test('have a getter called defaultOptions', () => {
@@ -83,7 +84,7 @@ describe('HlsjsPlayback', () => {
83
84
  })
84
85
 
85
86
  test('should be able to identify it can play resources independently of the file extension case', () => {
86
- jest.spyOn(HLSJS, 'isSupported').mockImplementation(() => true)
87
+ vi.spyOn(HLSJS, 'isSupported').mockImplementation(() => true)
87
88
  expect(HlsjsPlayback.canPlay('/relative/video.m3u8')).toBeTruthy()
88
89
  expect(HlsjsPlayback.canPlay('/relative/VIDEO.M3U8')).toBeTruthy()
89
90
  expect(HlsjsPlayback.canPlay('/relative/video.m3u8?foobarQuery=1234#somefragment')).toBeTruthy()
@@ -102,7 +103,7 @@ describe('HlsjsPlayback', () => {
102
103
  })
103
104
 
104
105
  test('can play regardless of any mime type letter case', () => {
105
- jest.spyOn(HLSJS, 'isSupported').mockImplementation(() => true)
106
+ vi.spyOn(HLSJS, 'isSupported').mockImplementation(() => true)
106
107
  expect(HlsjsPlayback.canPlay('/path/list.m3u8', 'APPLICATION/VND.APPLE.MPEGURL')).toBeTruthy()
107
108
  expect(
108
109
  HlsjsPlayback.canPlay(
@@ -154,9 +155,7 @@ describe('HlsjsPlayback', () => {
154
155
  })
155
156
 
156
157
  test('registers PLAYBACK_FRAGMENT_PARSING_METADATA event', () => {
157
- expect(Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA).toEqual(
158
- 'playbackFragmentParsingMetadata'
159
- )
158
+ expect(Events.Custom.PLAYBACK_FRAGMENT_PARSING_METADATA).toEqual('playbackFragmentParsingMetadata')
160
159
  })
161
160
 
162
161
  test('levels supports specifying the level', () => {
@@ -237,14 +236,14 @@ describe('HlsjsPlayback', () => {
237
236
  hlsPlayback: { preload: false }
238
237
  })
239
238
  playback._setup()
240
- jest.spyOn(playback._hls, 'loadSource')
239
+ vi.spyOn(playback._hls, 'loadSource')
241
240
  playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
242
241
 
243
242
  expect(playback._hls.loadSource).not.toHaveBeenCalled()
244
243
 
245
244
  playback.options.hlsPlayback.preload = true
246
245
  playback._setup()
247
- jest.spyOn(playback._hls, 'loadSource')
246
+ vi.spyOn(playback._hls, 'loadSource')
248
247
  playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
249
248
 
250
249
  expect(playback._hls.loadSource).toHaveBeenCalledTimes(1)
@@ -264,7 +263,7 @@ describe('HlsjsPlayback', () => {
264
263
 
265
264
  test('calls bindCustomListeners method', () => {
266
265
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8' })
267
- jest.spyOn(playback, 'bindCustomListeners')
266
+ vi.spyOn(playback, 'bindCustomListeners')
268
267
  playback._setup()
269
268
 
270
269
  expect(playback.bindCustomListeners).toHaveBeenCalledTimes(1)
@@ -275,7 +274,7 @@ describe('HlsjsPlayback', () => {
275
274
  test('avoid to run internal logic if _isReadyState flag is true', () => {
276
275
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/video.m3u8' })
277
276
  playback._isReadyState = true
278
- jest.spyOn(playback, '_setup')
277
+ vi.spyOn(playback, '_setup')
279
278
  playback._ready()
280
279
 
281
280
  expect(playback._setup).not.toHaveBeenCalled()
@@ -283,7 +282,7 @@ describe('HlsjsPlayback', () => {
283
282
 
284
283
  test("call _setup method if HLS.JS internal don't exists", () => {
285
284
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/video.m3u8' })
286
- jest.spyOn(playback, '_setup')
285
+ vi.spyOn(playback, '_setup')
287
286
  playback._ready()
288
287
 
289
288
  expect(playback._setup).toHaveBeenCalledTimes(1)
@@ -302,8 +301,8 @@ describe('HlsjsPlayback', () => {
302
301
  expect(playback._isReadyState).toBeTruthy()
303
302
  })
304
303
 
305
- test('triggers PLAYBACK_READY event', done => {
306
- const cb = jest.fn()
304
+ test('triggers PLAYBACK_READY event', () => new Promise(done => {
305
+ const cb = vi.fn()
307
306
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/video.m3u8' })
308
307
 
309
308
  playback.listenTo(playback, Events.PLAYBACK_READY, cb)
@@ -312,12 +311,12 @@ describe('HlsjsPlayback', () => {
312
311
  done()
313
312
  })
314
313
  playback._ready()
315
- })
314
+ }))
316
315
  })
317
316
 
318
317
  describe('play method', () => {
319
318
  test('calls this._hls.loadSource once if _manifestLoading flag and options.hlsPlayback.preload are falsy', () => {
320
- jest.spyOn(HLSJS.prototype, 'loadSource').mockImplementation()
319
+ vi.spyOn(HLSJS.prototype, 'loadSource').mockImplementation()
321
320
  const src = 'http://clappr.io/foo.m3u8'
322
321
  const playback = new HlsjsPlayback({ src, hlsPlayback: { preload: false } })
323
322
  playback._setup()
@@ -350,7 +349,7 @@ describe('HlsjsPlayback', () => {
350
349
  })
351
350
  const url = 'http://clappr.io/foo2.m3u8'
352
351
  playback.load(url)
353
- jest.spyOn(playback._hls, 'loadSource')
352
+ vi.spyOn(playback._hls, 'loadSource')
354
353
  playback._hls.trigger(HLSJS.Events.MEDIA_ATTACHED, { media: playback.el })
355
354
  expect(playback.options.src).toBe(url)
356
355
  expect(playback._hls.loadSource).toHaveBeenCalledWith(url)
@@ -359,7 +358,7 @@ describe('HlsjsPlayback', () => {
359
358
 
360
359
  describe('bindCustomListeners method', () => {
361
360
  test('creates listeners for each item configured on customListeners array', () => {
362
- const cb = jest.fn()
361
+ const cb = vi.fn()
363
362
  const playback = new HlsjsPlayback({
364
363
  src: 'http://clappr.io/foo.m3u8',
365
364
  hlsPlayback: {
@@ -376,7 +375,7 @@ describe('HlsjsPlayback', () => {
376
375
  })
377
376
 
378
377
  test("don't add one listener without a valid configuration", () => {
379
- const cb = jest.fn()
378
+ const cb = vi.fn()
380
379
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8' })
381
380
  playback._setup()
382
381
 
@@ -398,7 +397,7 @@ describe('HlsjsPlayback', () => {
398
397
  })
399
398
 
400
399
  test('adds a listener for one time when the customListeners array item is configured with the "once" param', () => {
401
- const cb = jest.fn()
400
+ const cb = vi.fn()
402
401
  const playback = new HlsjsPlayback({
403
402
  src: 'http://clappr.io/foo.m3u8',
404
403
  hlsPlayback: {
@@ -417,7 +416,7 @@ describe('HlsjsPlayback', () => {
417
416
 
418
417
  describe('unbindCustomListeners method', () => {
419
418
  test('remove listeners for each item configured on customListeners array', () => {
420
- const cb = jest.fn()
419
+ const cb = vi.fn()
421
420
  const playback = new HlsjsPlayback({
422
421
  src: 'http://clappr.io/foo.m3u8',
423
422
  hlsPlayback: {
@@ -442,9 +441,7 @@ describe('HlsjsPlayback', () => {
442
441
  }
443
442
  const playback = new HlsjsPlayback({ src: 'http://clappr.io/foo.m3u8' })
444
443
  playback.el.currentTime = 5
445
- playback._setup()
446
- playback.unbindCustomListeners()
447
- playback._hls.trigger(HLSJS.Events.FRAG_CHANGED, fragmentMock)
444
+ playback._onFragmentChanged(HLSJS.Events.FRAG_CHANGED, fragmentMock)
448
445
  expect(playback.currentTimestamp).toBe(1556663045) // 'Tue Apr 30 2019 19:24:05'
449
446
  })
450
447
 
@@ -455,6 +452,46 @@ describe('HlsjsPlayback', () => {
455
452
  })
456
453
  })
457
454
 
455
+ describe('level and fragment events', () => {
456
+ test('_onLevelSwitch fills levels and reports bitrate', () => {
457
+ const playback = createPlayback()
458
+ playback._hls = {
459
+ levels: [{ height: 1080, width: 1920, bitrate: 4000000 }]
460
+ }
461
+ const onSwitch = vi.fn()
462
+ const onHd = vi.fn()
463
+ const onBitrate = vi.fn()
464
+ playback.on(Events.PLAYBACK_LEVEL_SWITCH, onSwitch)
465
+ playback.on(Events.PLAYBACK_HIGHDEFINITIONUPDATE, onHd)
466
+ playback.on(Events.PLAYBACK_BITRATE, onBitrate)
467
+
468
+ playback._onLevelSwitch(HLSJS.Events.LEVEL_SWITCHED, { level: 0 })
469
+
470
+ expect(playback.levels).toHaveLength(1)
471
+ expect(playback.highDefinition).toBe(true)
472
+ expect(onSwitch).toHaveBeenCalled()
473
+ expect(onHd).toHaveBeenCalledWith(true)
474
+ expect(onBitrate).toHaveBeenCalledWith(
475
+ expect.objectContaining({ height: 1080, bitrate: 4000000, level: 0 })
476
+ )
477
+ })
478
+
479
+ test('_onFragmentLoaded and _onFragmentBuffered forward playback events', () => {
480
+ const playback = createPlayback()
481
+ const onLoaded = vi.fn()
482
+ const onBuffered = vi.fn()
483
+ playback.on(Events.PLAYBACK_FRAGMENT_LOADED, onLoaded)
484
+ playback.on(Events.PLAYBACK_FRAGMENT_BUFFERED, onBuffered)
485
+ const data = { frag: { sn: 1 } }
486
+
487
+ playback._onFragmentLoaded(HLSJS.Events.FRAG_LOADED, data)
488
+ playback._onFragmentBuffered(HLSJS.Events.FRAG_BUFFERED, data)
489
+
490
+ expect(onLoaded).toHaveBeenCalledWith(data)
491
+ expect(onBuffered).toHaveBeenCalledWith(data)
492
+ })
493
+ })
494
+
458
495
  describe('DVR time model', () => {
459
496
  test('without correlation duration equals playable region duration', () => {
460
497
  const playback = createPlayback()
@@ -740,8 +777,8 @@ describe('HlsjsPlayback', () => {
740
777
  const playback = createPlayback()
741
778
  playback._playbackType = Playback.LIVE
742
779
  freezeNow(playback)
743
- jest.spyOn(playback, '_onDurationChange').mockImplementation(() => {})
744
- jest.spyOn(playback, '_onProgress').mockImplementation(() => {})
780
+ vi.spyOn(playback, '_onDurationChange').mockImplementation(() => {})
781
+ vi.spyOn(playback, '_onProgress').mockImplementation(() => {})
745
782
 
746
783
  playback._onLevelUpdated(
747
784
  'levelUpdated',
@@ -798,7 +835,7 @@ describe('HlsjsPlayback', () => {
798
835
  playback._playbackType = Playback.VOD
799
836
  playback._playableRegionStartTime = 0
800
837
  playback._playableRegionDuration = 80
801
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
838
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
802
839
 
803
840
  playback.seek(-1)
804
841
 
@@ -846,7 +883,7 @@ describe('HlsjsPlayback', () => {
846
883
  playback._playableRegionDuration = 100
847
884
  playback.el.currentTime = 1
848
885
  freezeNow(playback, 1000000)
849
- const onTimeUpdate = jest.fn()
886
+ const onTimeUpdate = vi.fn()
850
887
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
851
888
 
852
889
  playback._onTimeUpdate()
@@ -861,7 +898,7 @@ describe('HlsjsPlayback', () => {
861
898
  playback._playableRegionDuration = 100
862
899
  playback.el.currentTime = 1
863
900
  const clock = freezeNow(playback, 1000000)
864
- const onTimeUpdate = jest.fn()
901
+ const onTimeUpdate = vi.fn()
865
902
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
866
903
 
867
904
  playback._onTimeUpdate()
@@ -877,7 +914,7 @@ describe('HlsjsPlayback', () => {
877
914
  playback._playableRegionDuration = 100
878
915
  playback.el.currentTime = 1
879
916
  const clock = freezeNow(playback, 1000000)
880
- const onTimeUpdate = jest.fn()
917
+ const onTimeUpdate = vi.fn()
881
918
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
882
919
 
883
920
  playback._onTimeUpdate()
@@ -893,7 +930,7 @@ describe('HlsjsPlayback', () => {
893
930
  playback._playableRegionDuration = 100
894
931
  playback.el.currentTime = 1
895
932
  const clock = freezeNow(playback, 1000000)
896
- const onTimeUpdate = jest.fn()
933
+ const onTimeUpdate = vi.fn()
897
934
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
898
935
 
899
936
  playback._onTimeUpdate()
@@ -911,7 +948,7 @@ describe('HlsjsPlayback', () => {
911
948
  playback._programDateTime = 1000
912
949
  playback.el.currentTime = 1
913
950
  const clock = freezeNow(playback, 1000000)
914
- const onTimeUpdate = jest.fn()
951
+ const onTimeUpdate = vi.fn()
915
952
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
916
953
 
917
954
  playback._onTimeUpdate()
@@ -935,14 +972,14 @@ describe('HlsjsPlayback', () => {
935
972
  cores.push(core)
936
973
  const playback = createPlayback(options, core.playerError)
937
974
  stubHls(playback)
938
- jest.spyOn(playback, 'play').mockImplementation(() => {})
939
- jest.spyOn(playback, 'stop').mockImplementation(() => {})
975
+ vi.spyOn(playback, 'play').mockImplementation(() => {})
976
+ vi.spyOn(playback, 'stop').mockImplementation(() => {})
940
977
  return playback
941
978
  }
942
979
 
943
980
  test('recoverable network fatal error calls startLoad', () => {
944
981
  const playback = createErrorPlayback()
945
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
982
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
946
983
 
947
984
  playback._onHLSJSError('hlsError', {
948
985
  fatal: true,
@@ -963,8 +1000,8 @@ describe('HlsjsPlayback', () => {
963
1000
  HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT
964
1001
  ])('unrecoverable network fatal %s triggers error and stop', details => {
965
1002
  const playback = createErrorPlayback()
966
- jest.spyOn(Log, 'error').mockImplementation(() => {})
967
- const onError = jest.fn()
1003
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1004
+ const onError = vi.fn()
968
1005
  playback.on(Events.PLAYBACK_ERROR, onError)
969
1006
 
970
1007
  playback._onHLSJSError('hlsError', {
@@ -980,9 +1017,9 @@ describe('HlsjsPlayback', () => {
980
1017
 
981
1018
  test('media fatal error recovers via recoverMediaError then swapAudioCodec then fails', () => {
982
1019
  const playback = createErrorPlayback()
983
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
984
- jest.spyOn(Log, 'error').mockImplementation(() => {})
985
- const onError = jest.fn()
1020
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1021
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1022
+ const onError = vi.fn()
986
1023
  playback.on(Events.PLAYBACK_ERROR, onError)
987
1024
  const mediaError = {
988
1025
  fatal: true,
@@ -1008,8 +1045,8 @@ describe('HlsjsPlayback', () => {
1008
1045
 
1009
1046
  test('other fatal error types trigger error and stop', () => {
1010
1047
  const playback = createErrorPlayback()
1011
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1012
- const onError = jest.fn()
1048
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1049
+ const onError = vi.fn()
1013
1050
  playback.on(Events.PLAYBACK_ERROR, onError)
1014
1051
 
1015
1052
  playback._onHLSJSError('hlsError', {
@@ -1024,8 +1061,8 @@ describe('HlsjsPlayback', () => {
1024
1061
 
1025
1062
  test('exhausted recover attempts trigger error and stop', () => {
1026
1063
  const playback = createErrorPlayback({ hlsRecoverAttempts: 0 })
1027
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1028
- const onError = jest.fn()
1064
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1065
+ const onError = vi.fn()
1029
1066
  playback.on(Events.PLAYBACK_ERROR, onError)
1030
1067
 
1031
1068
  playback._onHLSJSError('hlsError', {
@@ -1043,8 +1080,8 @@ describe('HlsjsPlayback', () => {
1043
1080
  const playback = createErrorPlayback({
1044
1081
  playback: { triggerFatalErrorOnResourceDenied: true }
1045
1082
  })
1046
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1047
- const onError = jest.fn()
1083
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1084
+ const onError = vi.fn()
1048
1085
  playback.on(Events.PLAYBACK_ERROR, onError)
1049
1086
 
1050
1087
  playback._onHLSJSError('hlsError', {
@@ -1060,8 +1097,8 @@ describe('HlsjsPlayback', () => {
1060
1097
 
1061
1098
  test('non-fatal key denial without option only warns', () => {
1062
1099
  const playback = createErrorPlayback()
1063
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
1064
- const onError = jest.fn()
1100
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1101
+ const onError = vi.fn()
1065
1102
  playback.on(Events.PLAYBACK_ERROR, onError)
1066
1103
 
1067
1104
  playback._onHLSJSError('hlsError', {
@@ -1080,8 +1117,8 @@ describe('HlsjsPlayback', () => {
1080
1117
  const playback = createErrorPlayback({
1081
1118
  playback: { triggerFatalErrorOnResourceDenied: true }
1082
1119
  })
1083
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
1084
- const onError = jest.fn()
1120
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1121
+ const onError = vi.fn()
1085
1122
  playback.on(Events.PLAYBACK_ERROR, onError)
1086
1123
 
1087
1124
  playback._onHLSJSError('hlsError', {
@@ -1097,8 +1134,8 @@ describe('HlsjsPlayback', () => {
1097
1134
 
1098
1135
  test('includes response in error description when present', () => {
1099
1136
  const playback = createErrorPlayback()
1100
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1101
- const onError = jest.fn()
1137
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1138
+ const onError = vi.fn()
1102
1139
  playback.on(Events.PLAYBACK_ERROR, onError)
1103
1140
 
1104
1141
  playback._onHLSJSError('hlsError', {
@@ -1110,6 +1147,85 @@ describe('HlsjsPlayback', () => {
1110
1147
 
1111
1148
  expect(onError.mock.calls[0][0].description).toContain('response:')
1112
1149
  })
1150
+
1151
+ test('triggers PLAYBACK_WARNING for a non-fatal error', () => {
1152
+ const playback = createErrorPlayback()
1153
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1154
+ const onWarning = vi.fn()
1155
+ playback.on(Events.PLAYBACK_WARNING, onWarning)
1156
+
1157
+ playback._onHLSJSError('hlsError', {
1158
+ fatal: false,
1159
+ type: HLSJS.ErrorTypes.NETWORK_ERROR,
1160
+ details: HLSJS.ErrorDetails.FRAG_LOAD_ERROR,
1161
+ response: { code: 500 }
1162
+ })
1163
+
1164
+ expect(onWarning).toHaveBeenCalledTimes(1)
1165
+ expect(onWarning.mock.calls[0][0]).toMatchObject({
1166
+ code: `${HLSJS.ErrorTypes.NETWORK_ERROR}_${HLSJS.ErrorDetails.FRAG_LOAD_ERROR}`,
1167
+ level: PlayerError.Levels.WARN,
1168
+ raw: { response: { code: 500 } }
1169
+ })
1170
+ })
1171
+
1172
+ test('does not trigger PLAYBACK_WARNING for a fatal error', () => {
1173
+ const playback = createErrorPlayback()
1174
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1175
+ const onWarning = vi.fn()
1176
+ const onError = vi.fn()
1177
+ playback.on(Events.PLAYBACK_WARNING, onWarning)
1178
+ playback.on(Events.PLAYBACK_ERROR, onError)
1179
+
1180
+ playback._onHLSJSError('hlsError', {
1181
+ fatal: true,
1182
+ type: HLSJS.ErrorTypes.NETWORK_ERROR,
1183
+ details: HLSJS.ErrorDetails.MANIFEST_LOAD_TIMEOUT,
1184
+ response: { code: 504 }
1185
+ })
1186
+
1187
+ expect(onWarning).not.toHaveBeenCalled()
1188
+ expect(onError).toHaveBeenCalledTimes(1)
1189
+ expect(playback.stop).toHaveBeenCalled()
1190
+ })
1191
+
1192
+ test('does not trigger PLAYBACK_WARNING when non-fatal key denial escalates to fatal', () => {
1193
+ const playback = createErrorPlayback({
1194
+ playback: { triggerFatalErrorOnResourceDenied: true }
1195
+ })
1196
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1197
+ const onWarning = vi.fn()
1198
+ const onError = vi.fn()
1199
+ playback.on(Events.PLAYBACK_WARNING, onWarning)
1200
+ playback.on(Events.PLAYBACK_ERROR, onError)
1201
+
1202
+ playback._onHLSJSError('hlsError', {
1203
+ fatal: false,
1204
+ type: HLSJS.ErrorTypes.NETWORK_ERROR,
1205
+ details: HLSJS.ErrorDetails.KEY_LOAD_ERROR,
1206
+ response: { code: 403 }
1207
+ })
1208
+
1209
+ expect(onWarning).not.toHaveBeenCalled()
1210
+ expect(onError).toHaveBeenCalledTimes(1)
1211
+ })
1212
+
1213
+ test('triggers PLAYBACK_WARNING for a non-fatal key denial that does not escalate', () => {
1214
+ const playback = createErrorPlayback()
1215
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1216
+ const onWarning = vi.fn()
1217
+ playback.on(Events.PLAYBACK_WARNING, onWarning)
1218
+
1219
+ playback._onHLSJSError('hlsError', {
1220
+ fatal: false,
1221
+ type: HLSJS.ErrorTypes.NETWORK_ERROR,
1222
+ details: HLSJS.ErrorDetails.KEY_LOAD_ERROR,
1223
+ response: { code: 403 }
1224
+ })
1225
+
1226
+ expect(onWarning).toHaveBeenCalledTimes(1)
1227
+ expect(onWarning.mock.calls[0][0].level).toBe(PlayerError.Levels.WARN)
1228
+ })
1113
1229
  })
1114
1230
 
1115
1231
  describe('dvrEnabled and settings', () => {
@@ -1152,7 +1268,7 @@ describe('HlsjsPlayback', () => {
1152
1268
 
1153
1269
  test('_updateSettings chooses left controls from playback type and dvr', () => {
1154
1270
  const playback = createPlayback({ hlsMinimumDvrSize: 60 })
1155
- const onSettings = jest.fn()
1271
+ const onSettings = vi.fn()
1156
1272
  playback.on(Events.PLAYBACK_SETTINGSUPDATE, onSettings)
1157
1273
 
1158
1274
  playback._playbackType = Playback.VOD