@clappr/hlsjs-playback 3.0.3 → 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.3",
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.3",
43
+ "@clappr/core": "^0.17.0",
45
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: {
@@ -459,9 +458,9 @@ describe('HlsjsPlayback', () => {
459
458
  playback._hls = {
460
459
  levels: [{ height: 1080, width: 1920, bitrate: 4000000 }]
461
460
  }
462
- const onSwitch = jest.fn()
463
- const onHd = jest.fn()
464
- const onBitrate = jest.fn()
461
+ const onSwitch = vi.fn()
462
+ const onHd = vi.fn()
463
+ const onBitrate = vi.fn()
465
464
  playback.on(Events.PLAYBACK_LEVEL_SWITCH, onSwitch)
466
465
  playback.on(Events.PLAYBACK_HIGHDEFINITIONUPDATE, onHd)
467
466
  playback.on(Events.PLAYBACK_BITRATE, onBitrate)
@@ -479,8 +478,8 @@ describe('HlsjsPlayback', () => {
479
478
 
480
479
  test('_onFragmentLoaded and _onFragmentBuffered forward playback events', () => {
481
480
  const playback = createPlayback()
482
- const onLoaded = jest.fn()
483
- const onBuffered = jest.fn()
481
+ const onLoaded = vi.fn()
482
+ const onBuffered = vi.fn()
484
483
  playback.on(Events.PLAYBACK_FRAGMENT_LOADED, onLoaded)
485
484
  playback.on(Events.PLAYBACK_FRAGMENT_BUFFERED, onBuffered)
486
485
  const data = { frag: { sn: 1 } }
@@ -778,8 +777,8 @@ describe('HlsjsPlayback', () => {
778
777
  const playback = createPlayback()
779
778
  playback._playbackType = Playback.LIVE
780
779
  freezeNow(playback)
781
- jest.spyOn(playback, '_onDurationChange').mockImplementation(() => {})
782
- jest.spyOn(playback, '_onProgress').mockImplementation(() => {})
780
+ vi.spyOn(playback, '_onDurationChange').mockImplementation(() => {})
781
+ vi.spyOn(playback, '_onProgress').mockImplementation(() => {})
783
782
 
784
783
  playback._onLevelUpdated(
785
784
  'levelUpdated',
@@ -836,7 +835,7 @@ describe('HlsjsPlayback', () => {
836
835
  playback._playbackType = Playback.VOD
837
836
  playback._playableRegionStartTime = 0
838
837
  playback._playableRegionDuration = 80
839
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
838
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
840
839
 
841
840
  playback.seek(-1)
842
841
 
@@ -884,7 +883,7 @@ describe('HlsjsPlayback', () => {
884
883
  playback._playableRegionDuration = 100
885
884
  playback.el.currentTime = 1
886
885
  freezeNow(playback, 1000000)
887
- const onTimeUpdate = jest.fn()
886
+ const onTimeUpdate = vi.fn()
888
887
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
889
888
 
890
889
  playback._onTimeUpdate()
@@ -899,7 +898,7 @@ describe('HlsjsPlayback', () => {
899
898
  playback._playableRegionDuration = 100
900
899
  playback.el.currentTime = 1
901
900
  const clock = freezeNow(playback, 1000000)
902
- const onTimeUpdate = jest.fn()
901
+ const onTimeUpdate = vi.fn()
903
902
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
904
903
 
905
904
  playback._onTimeUpdate()
@@ -915,7 +914,7 @@ describe('HlsjsPlayback', () => {
915
914
  playback._playableRegionDuration = 100
916
915
  playback.el.currentTime = 1
917
916
  const clock = freezeNow(playback, 1000000)
918
- const onTimeUpdate = jest.fn()
917
+ const onTimeUpdate = vi.fn()
919
918
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
920
919
 
921
920
  playback._onTimeUpdate()
@@ -931,7 +930,7 @@ describe('HlsjsPlayback', () => {
931
930
  playback._playableRegionDuration = 100
932
931
  playback.el.currentTime = 1
933
932
  const clock = freezeNow(playback, 1000000)
934
- const onTimeUpdate = jest.fn()
933
+ const onTimeUpdate = vi.fn()
935
934
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
936
935
 
937
936
  playback._onTimeUpdate()
@@ -949,7 +948,7 @@ describe('HlsjsPlayback', () => {
949
948
  playback._programDateTime = 1000
950
949
  playback.el.currentTime = 1
951
950
  const clock = freezeNow(playback, 1000000)
952
- const onTimeUpdate = jest.fn()
951
+ const onTimeUpdate = vi.fn()
953
952
  playback.on(Events.PLAYBACK_TIMEUPDATE, onTimeUpdate)
954
953
 
955
954
  playback._onTimeUpdate()
@@ -973,14 +972,14 @@ describe('HlsjsPlayback', () => {
973
972
  cores.push(core)
974
973
  const playback = createPlayback(options, core.playerError)
975
974
  stubHls(playback)
976
- jest.spyOn(playback, 'play').mockImplementation(() => {})
977
- jest.spyOn(playback, 'stop').mockImplementation(() => {})
975
+ vi.spyOn(playback, 'play').mockImplementation(() => {})
976
+ vi.spyOn(playback, 'stop').mockImplementation(() => {})
978
977
  return playback
979
978
  }
980
979
 
981
980
  test('recoverable network fatal error calls startLoad', () => {
982
981
  const playback = createErrorPlayback()
983
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
982
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
984
983
 
985
984
  playback._onHLSJSError('hlsError', {
986
985
  fatal: true,
@@ -1001,8 +1000,8 @@ describe('HlsjsPlayback', () => {
1001
1000
  HLSJS.ErrorDetails.LEVEL_LOAD_TIMEOUT
1002
1001
  ])('unrecoverable network fatal %s triggers error and stop', details => {
1003
1002
  const playback = createErrorPlayback()
1004
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1005
- const onError = jest.fn()
1003
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1004
+ const onError = vi.fn()
1006
1005
  playback.on(Events.PLAYBACK_ERROR, onError)
1007
1006
 
1008
1007
  playback._onHLSJSError('hlsError', {
@@ -1018,9 +1017,9 @@ describe('HlsjsPlayback', () => {
1018
1017
 
1019
1018
  test('media fatal error recovers via recoverMediaError then swapAudioCodec then fails', () => {
1020
1019
  const playback = createErrorPlayback()
1021
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
1022
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1023
- const onError = jest.fn()
1020
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1021
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1022
+ const onError = vi.fn()
1024
1023
  playback.on(Events.PLAYBACK_ERROR, onError)
1025
1024
  const mediaError = {
1026
1025
  fatal: true,
@@ -1046,8 +1045,8 @@ describe('HlsjsPlayback', () => {
1046
1045
 
1047
1046
  test('other fatal error types trigger error and stop', () => {
1048
1047
  const playback = createErrorPlayback()
1049
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1050
- const onError = jest.fn()
1048
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1049
+ const onError = vi.fn()
1051
1050
  playback.on(Events.PLAYBACK_ERROR, onError)
1052
1051
 
1053
1052
  playback._onHLSJSError('hlsError', {
@@ -1062,8 +1061,8 @@ describe('HlsjsPlayback', () => {
1062
1061
 
1063
1062
  test('exhausted recover attempts trigger error and stop', () => {
1064
1063
  const playback = createErrorPlayback({ hlsRecoverAttempts: 0 })
1065
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1066
- const onError = jest.fn()
1064
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1065
+ const onError = vi.fn()
1067
1066
  playback.on(Events.PLAYBACK_ERROR, onError)
1068
1067
 
1069
1068
  playback._onHLSJSError('hlsError', {
@@ -1081,8 +1080,8 @@ describe('HlsjsPlayback', () => {
1081
1080
  const playback = createErrorPlayback({
1082
1081
  playback: { triggerFatalErrorOnResourceDenied: true }
1083
1082
  })
1084
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1085
- const onError = jest.fn()
1083
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1084
+ const onError = vi.fn()
1086
1085
  playback.on(Events.PLAYBACK_ERROR, onError)
1087
1086
 
1088
1087
  playback._onHLSJSError('hlsError', {
@@ -1098,8 +1097,8 @@ describe('HlsjsPlayback', () => {
1098
1097
 
1099
1098
  test('non-fatal key denial without option only warns', () => {
1100
1099
  const playback = createErrorPlayback()
1101
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
1102
- const onError = jest.fn()
1100
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1101
+ const onError = vi.fn()
1103
1102
  playback.on(Events.PLAYBACK_ERROR, onError)
1104
1103
 
1105
1104
  playback._onHLSJSError('hlsError', {
@@ -1118,8 +1117,8 @@ describe('HlsjsPlayback', () => {
1118
1117
  const playback = createErrorPlayback({
1119
1118
  playback: { triggerFatalErrorOnResourceDenied: true }
1120
1119
  })
1121
- jest.spyOn(Log, 'warn').mockImplementation(() => {})
1122
- const onError = jest.fn()
1120
+ vi.spyOn(Log, 'warn').mockImplementation(() => {})
1121
+ const onError = vi.fn()
1123
1122
  playback.on(Events.PLAYBACK_ERROR, onError)
1124
1123
 
1125
1124
  playback._onHLSJSError('hlsError', {
@@ -1135,8 +1134,8 @@ describe('HlsjsPlayback', () => {
1135
1134
 
1136
1135
  test('includes response in error description when present', () => {
1137
1136
  const playback = createErrorPlayback()
1138
- jest.spyOn(Log, 'error').mockImplementation(() => {})
1139
- const onError = jest.fn()
1137
+ vi.spyOn(Log, 'error').mockImplementation(() => {})
1138
+ const onError = vi.fn()
1140
1139
  playback.on(Events.PLAYBACK_ERROR, onError)
1141
1140
 
1142
1141
  playback._onHLSJSError('hlsError', {
@@ -1148,6 +1147,85 @@ describe('HlsjsPlayback', () => {
1148
1147
 
1149
1148
  expect(onError.mock.calls[0][0].description).toContain('response:')
1150
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
+ })
1151
1229
  })
1152
1230
 
1153
1231
  describe('dvrEnabled and settings', () => {
@@ -1190,7 +1268,7 @@ describe('HlsjsPlayback', () => {
1190
1268
 
1191
1269
  test('_updateSettings chooses left controls from playback type and dvr', () => {
1192
1270
  const playback = createPlayback({ hlsMinimumDvrSize: 60 })
1193
- const onSettings = jest.fn()
1271
+ const onSettings = vi.fn()
1194
1272
  playback.on(Events.PLAYBACK_SETTINGSUPDATE, onSettings)
1195
1273
 
1196
1274
  playback._playbackType = Playback.VOD