Some fixes
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { setupHlsInstance } from './hlsSetup'
|
||||
|
||||
class MockHlsInstance {
|
||||
public static Events = {
|
||||
MANIFEST_PARSED: 'manifestParsed',
|
||||
LEVEL_LOADED: 'levelLoaded',
|
||||
AUDIO_TRACKS_UPDATED: 'audioTracksUpdated',
|
||||
SUBTITLE_TRACKS_UPDATED: 'subtitleTracksUpdated',
|
||||
ERROR: 'error',
|
||||
}
|
||||
|
||||
public static ErrorTypes = {
|
||||
NETWORK_ERROR: 'networkError',
|
||||
MEDIA_ERROR: 'mediaError',
|
||||
OTHER_ERROR: 'otherError',
|
||||
}
|
||||
|
||||
public loadSource = vi.fn()
|
||||
public attachMedia = vi.fn()
|
||||
public destroy = vi.fn()
|
||||
public startLoad = vi.fn()
|
||||
public recoverMediaError = vi.fn()
|
||||
|
||||
private handlers = new Map<string, Array<(...args: any[]) => void>>()
|
||||
|
||||
on(event: string, handler: (...args: any[]) => void) {
|
||||
const existing = this.handlers.get(event) || []
|
||||
existing.push(handler)
|
||||
this.handlers.set(event, existing)
|
||||
}
|
||||
|
||||
emit(event: string, ...args: any[]) {
|
||||
for (const handler of this.handlers.get(event) || []) {
|
||||
handler(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadHls = vi.fn(async () => MockHlsInstance)
|
||||
const isHlsSupported = vi.fn(() => true)
|
||||
const getHlsAudioTracks = vi.fn(() => [{ name: 'English', language: 'en', groupId: 'audio', url: '' }])
|
||||
const getHlsQualities = vi.fn(() => [{ label: '720p', height: 720, levelIndex: 1 }])
|
||||
const getHlsSubtitleTracks = vi.fn(() => [{ label: 'English', lang: 'en', src: '/sub.vtt' }])
|
||||
|
||||
vi.mock('./hlsLoader', () => ({
|
||||
loadHls,
|
||||
isHlsSupported,
|
||||
getHlsAudioTracks,
|
||||
getHlsQualities,
|
||||
getHlsSubtitleTracks,
|
||||
}))
|
||||
|
||||
describe('setupHlsInstance', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('sets up hls, emits levels/tracks and cleans up', async () => {
|
||||
const video = document.createElement('video')
|
||||
const onAudioTracksLoaded = vi.fn()
|
||||
const onQualityLevelsLoaded = vi.fn()
|
||||
const onSubtitleTracksLoaded = vi.fn()
|
||||
|
||||
const cleanup = await setupHlsInstance({
|
||||
video,
|
||||
src: 'https://example.com/stream.m3u8',
|
||||
autoplay: false,
|
||||
onAudioTracksLoaded,
|
||||
onQualityLevelsLoaded,
|
||||
onSubtitleTracksLoaded,
|
||||
})
|
||||
|
||||
const hls = (video as any).__hlsInstance as MockHlsInstance
|
||||
expect(hls).toBeDefined()
|
||||
expect(hls.loadSource).toHaveBeenCalledWith('https://example.com/stream.m3u8')
|
||||
expect(hls.attachMedia).toHaveBeenCalledWith(video)
|
||||
|
||||
hls.emit(MockHlsInstance.Events.MANIFEST_PARSED)
|
||||
expect(onAudioTracksLoaded).toHaveBeenCalled()
|
||||
expect(onQualityLevelsLoaded).toHaveBeenCalled()
|
||||
expect(onSubtitleTracksLoaded).toHaveBeenCalled()
|
||||
|
||||
vi.advanceTimersByTime(250)
|
||||
expect(onQualityLevelsLoaded).toHaveBeenCalledTimes(2)
|
||||
|
||||
cleanup()
|
||||
expect(hls.destroy).toHaveBeenCalledTimes(1)
|
||||
expect((video as any).__hlsInstance).toBeUndefined()
|
||||
})
|
||||
|
||||
it('attempts recovery on fatal network/media errors', async () => {
|
||||
const video = document.createElement('video')
|
||||
const onError = vi.fn()
|
||||
|
||||
await setupHlsInstance({
|
||||
video,
|
||||
src: 'https://example.com/stream.m3u8',
|
||||
autoplay: false,
|
||||
onError,
|
||||
})
|
||||
|
||||
const hls = (video as any).__hlsInstance as MockHlsInstance
|
||||
hls.emit(MockHlsInstance.Events.ERROR, null, {
|
||||
fatal: true,
|
||||
type: MockHlsInstance.ErrorTypes.NETWORK_ERROR,
|
||||
})
|
||||
hls.emit(MockHlsInstance.Events.ERROR, null, {
|
||||
fatal: true,
|
||||
type: MockHlsInstance.ErrorTypes.MEDIA_ERROR,
|
||||
})
|
||||
hls.emit(MockHlsInstance.Events.ERROR, null, {
|
||||
fatal: true,
|
||||
type: MockHlsInstance.ErrorTypes.OTHER_ERROR,
|
||||
})
|
||||
|
||||
expect(hls.startLoad).toHaveBeenCalledTimes(1)
|
||||
expect(hls.recoverMediaError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(expect.any(Error))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { setupMpegtsInstance } from './mpegtsSetup'
|
||||
|
||||
const createMockPlayer = () => {
|
||||
const handlers = new Map<string, (...args: any[]) => void>()
|
||||
|
||||
return {
|
||||
attachMediaElement: vi.fn(),
|
||||
load: vi.fn(),
|
||||
unload: vi.fn(),
|
||||
detachMediaElement: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
||||
handlers.set(event, handler)
|
||||
}),
|
||||
off: vi.fn(),
|
||||
emit: (event: string, ...args: any[]) => {
|
||||
handlers.get(event)?.(...args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const mockPlayer = createMockPlayer()
|
||||
|
||||
const mockMpegts = {
|
||||
Events: {
|
||||
ERROR: 'error',
|
||||
LOADING_COMPLETE: 'loadingComplete',
|
||||
RECOVERED_EARLY_EOF: 'recoveredEOF',
|
||||
METADATA_ARRIVED: 'metadataArrived',
|
||||
STATISTICS_INFO: 'stats',
|
||||
},
|
||||
ErrorTypes: {
|
||||
NETWORK_ERROR: 'networkError',
|
||||
MEDIA_ERROR: 'mediaError',
|
||||
},
|
||||
ErrorDetails: {
|
||||
NETWORK_EXCEPTION: 'networkException',
|
||||
NETWORK_STATUS_CODE_INVALID: 'networkStatusCodeInvalid',
|
||||
MEDIA_MSE_ERROR: 'mediaMSEError',
|
||||
},
|
||||
createPlayer: vi.fn(() => mockPlayer),
|
||||
}
|
||||
|
||||
vi.mock('./mpegtsLoader', () => ({
|
||||
loadMpegts: vi.fn(async () => mockMpegts),
|
||||
isMpegtsSupported: vi.fn(() => true),
|
||||
createDefaultMpegtsConfig: vi.fn(() => ({ enableWorker: false })),
|
||||
}))
|
||||
|
||||
describe('setupMpegtsInstance', () => {
|
||||
it('sets up and cleans mpegts player instance', async () => {
|
||||
const video = document.createElement('video')
|
||||
const cleanup = await setupMpegtsInstance({
|
||||
video,
|
||||
src: 'http://example.com/live/stream.ts',
|
||||
autoplay: false,
|
||||
})
|
||||
|
||||
expect(mockMpegts.createPlayer).toHaveBeenCalled()
|
||||
expect(mockPlayer.attachMediaElement).toHaveBeenCalledWith(video)
|
||||
expect(mockPlayer.load).toHaveBeenCalled()
|
||||
expect((video as any).__mpegtsInstance).toBeDefined()
|
||||
|
||||
cleanup()
|
||||
|
||||
expect(mockPlayer.unload).toHaveBeenCalled()
|
||||
expect(mockPlayer.detachMediaElement).toHaveBeenCalled()
|
||||
expect(mockPlayer.destroy).toHaveBeenCalled()
|
||||
expect((video as any).__mpegtsInstance).toBeUndefined()
|
||||
})
|
||||
|
||||
it('triggers metadata callback and non-recoverable error callback', async () => {
|
||||
const video = document.createElement('video')
|
||||
const onLoadedMetadata = vi.fn()
|
||||
const onError = vi.fn()
|
||||
|
||||
await setupMpegtsInstance({
|
||||
video,
|
||||
src: 'http://example.com/live/stream.ts',
|
||||
autoplay: false,
|
||||
onLoadedMetadata,
|
||||
onError,
|
||||
})
|
||||
|
||||
mockPlayer.emit(mockMpegts.Events.METADATA_ARRIVED, { duration: 123 })
|
||||
mockPlayer.emit(mockMpegts.Events.ERROR, 'networkError', 'fatalError', {})
|
||||
|
||||
expect(onLoadedMetadata).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(expect.any(Error))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { setupRtmpInstance } from './rtmpSetup'
|
||||
|
||||
const createMockPlayer = () => {
|
||||
const handlers = new Map<string, (...args: any[]) => void>()
|
||||
|
||||
return {
|
||||
attachMediaElement: vi.fn(),
|
||||
load: vi.fn(),
|
||||
unload: vi.fn(),
|
||||
detachMediaElement: vi.fn(),
|
||||
destroy: vi.fn(),
|
||||
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
||||
handlers.set(event, handler)
|
||||
}),
|
||||
off: vi.fn(),
|
||||
emit: (event: string, ...args: any[]) => {
|
||||
handlers.get(event)?.(...args)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const mockPlayer = createMockPlayer()
|
||||
|
||||
const mockFlvjs = {
|
||||
Events: {
|
||||
ERROR: 'error',
|
||||
LOADING_COMPLETE: 'loadingComplete',
|
||||
RECOVERED_EARLY_EOF: 'recoveredEOF',
|
||||
METADATA_ARRIVED: 'metadataArrived',
|
||||
STATISTICS_INFO: 'stats',
|
||||
},
|
||||
ErrorTypes: {
|
||||
NETWORK_ERROR: 'networkError',
|
||||
MEDIA_ERROR: 'mediaError',
|
||||
},
|
||||
ErrorDetails: {
|
||||
NETWORK_EXCEPTION: 'networkException',
|
||||
NETWORK_STATUS_CODE_INVALID: 'networkStatusCodeInvalid',
|
||||
MEDIA_MSE_ERROR: 'mediaMSEError',
|
||||
},
|
||||
createPlayer: vi.fn(() => mockPlayer),
|
||||
}
|
||||
|
||||
vi.mock('./rtmpLoader', () => ({
|
||||
loadFlvjs: vi.fn(async () => mockFlvjs),
|
||||
isFlvjsSupported: vi.fn(() => true),
|
||||
createDefaultFlvConfig: vi.fn(() => ({ enableWorker: true })),
|
||||
}))
|
||||
|
||||
describe('setupRtmpInstance', () => {
|
||||
it('sets up and cleans flv player instance', async () => {
|
||||
const video = document.createElement('video')
|
||||
const cleanup = await setupRtmpInstance({
|
||||
video,
|
||||
src: 'http://example.com/live.flv',
|
||||
autoplay: false,
|
||||
})
|
||||
|
||||
expect(mockFlvjs.createPlayer).toHaveBeenCalled()
|
||||
expect(mockPlayer.attachMediaElement).toHaveBeenCalledWith(video)
|
||||
expect(mockPlayer.load).toHaveBeenCalled()
|
||||
expect((video as any).__rtmpInstance).toBeDefined()
|
||||
|
||||
cleanup()
|
||||
|
||||
expect(mockPlayer.unload).toHaveBeenCalled()
|
||||
expect(mockPlayer.detachMediaElement).toHaveBeenCalled()
|
||||
expect(mockPlayer.destroy).toHaveBeenCalled()
|
||||
expect((video as any).__rtmpInstance).toBeUndefined()
|
||||
})
|
||||
|
||||
it('triggers metadata callback and non-recoverable error callback', async () => {
|
||||
const video = document.createElement('video')
|
||||
const onLoadedMetadata = vi.fn()
|
||||
const onError = vi.fn()
|
||||
|
||||
await setupRtmpInstance({
|
||||
video,
|
||||
src: 'http://example.com/live.flv',
|
||||
autoplay: false,
|
||||
onLoadedMetadata,
|
||||
onError,
|
||||
})
|
||||
|
||||
mockPlayer.emit(mockFlvjs.Events.METADATA_ARRIVED, { duration: 123 })
|
||||
mockPlayer.emit(mockFlvjs.Events.ERROR, 'networkError', 'fatalError', {})
|
||||
|
||||
expect(onLoadedMetadata).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(expect.any(Error))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user