Add MPEG-TS support and related utilities
Introduces MPEG-TS streaming support by adding mpegts.js as an optional dependency, updating keywords, and implementing new utility modules for MPEG-TS loading and setup. Updates VideoElement and videoProtocol logic to handle MPEG-TS streams, adds corresponding tests and mocks, and improves local settings for npm install.
This commit is contained in:
@@ -11,7 +11,8 @@
|
|||||||
"Bash(npm run build:lib:*)",
|
"Bash(npm run build:lib:*)",
|
||||||
"Bash(npm publish)",
|
"Bash(npm publish)",
|
||||||
"Bash(git push:*)",
|
"Bash(git push:*)",
|
||||||
"Bash(npm test:*)"
|
"Bash(npm test:*)",
|
||||||
|
"Bash(npm install:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
+4
-1
@@ -57,7 +57,8 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"hls.js": "^1.6.13",
|
"hls.js": "^1.6.13",
|
||||||
"flv.js": "^1.6.2"
|
"flv.js": "^1.6.2",
|
||||||
|
"mpegts.js": "^1.7.3"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
@@ -67,6 +68,8 @@
|
|||||||
"hls",
|
"hls",
|
||||||
"rtmp",
|
"rtmp",
|
||||||
"flv",
|
"flv",
|
||||||
|
"mpegts",
|
||||||
|
"iptv",
|
||||||
"streaming",
|
"streaming",
|
||||||
"media"
|
"media"
|
||||||
],
|
],
|
||||||
|
|||||||
Generated
-3583
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import { validateVideoURL, getCORSErrorMessage, isCORSError } from '../utils/cor
|
|||||||
import { setHlsAudioTrack, setHlsQualityLevel } from '../utils/hlsControl'
|
import { setHlsAudioTrack, setHlsQualityLevel } from '../utils/hlsControl'
|
||||||
import { setupHlsInstance } from '../utils/hlsSetup'
|
import { setupHlsInstance } from '../utils/hlsSetup'
|
||||||
import { setupRtmpInstance } from '../utils/rtmpSetup'
|
import { setupRtmpInstance } from '../utils/rtmpSetup'
|
||||||
|
import { setupMpegtsInstance } from '../utils/mpegtsSetup'
|
||||||
import { detectVideoProtocol } from '../utils/videoProtocol'
|
import { detectVideoProtocol } from '../utils/videoProtocol'
|
||||||
import { createSubtitleBlobURL } from '../utils/subtitles'
|
import { createSubtitleBlobURL } from '../utils/subtitles'
|
||||||
import './VideoElement.css'
|
import './VideoElement.css'
|
||||||
@@ -391,6 +392,19 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'mpegts': {
|
||||||
|
// MPEG-TS/IPTV streaming setup
|
||||||
|
console.log('[VideoElement] Setting up MPEG-TS player...')
|
||||||
|
cleanupFn = await setupMpegtsInstance({
|
||||||
|
video,
|
||||||
|
src,
|
||||||
|
autoplay,
|
||||||
|
onError: handleError,
|
||||||
|
onLoadedMetadata,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case 'dash': {
|
case 'dash': {
|
||||||
// DASH streaming - not yet implemented
|
// DASH streaming - not yet implemented
|
||||||
const error = new Error('DASH streaming is not yet supported')
|
const error = new Error('DASH streaming is not yet supported')
|
||||||
@@ -451,6 +465,13 @@ export const VideoElement: React.FC<VideoElementProps> = ({
|
|||||||
}
|
}
|
||||||
delete (video as any).__rtmpInstance
|
delete (video as any).__rtmpInstance
|
||||||
}
|
}
|
||||||
|
if ((video as any).__mpegtsInstance) {
|
||||||
|
const mpegts = (video as any).__mpegtsInstance
|
||||||
|
if (mpegts && typeof mpegts.destroy === 'function') {
|
||||||
|
mpegts.destroy()
|
||||||
|
}
|
||||||
|
delete (video as any).__mpegtsInstance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
src,
|
src,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Mock for mpegts.js library used in tests
|
||||||
|
export default {
|
||||||
|
isSupported: () => true,
|
||||||
|
getFeatureList: () => ({
|
||||||
|
mseSupported: true,
|
||||||
|
networkStreamIOSupported: true,
|
||||||
|
httpsSupported: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* MPEG-TS loader utility
|
||||||
|
* Dynamically loads mpegts.js library
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface MpegtsConfig {
|
||||||
|
enableWorker?: boolean
|
||||||
|
enableStashBuffer?: boolean
|
||||||
|
stashInitialSize?: number
|
||||||
|
isLive?: boolean
|
||||||
|
lazyLoad?: boolean
|
||||||
|
lazyLoadMaxDuration?: number
|
||||||
|
lazyLoadRecoverDuration?: number
|
||||||
|
deferLoadAfterSourceOpen?: boolean
|
||||||
|
autoCleanupSourceBuffer?: boolean
|
||||||
|
autoCleanupMaxBackwardDuration?: number
|
||||||
|
autoCleanupMinBackwardDuration?: number
|
||||||
|
fixAudioTimestampGap?: boolean
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
let mpegtsInstance: any = null
|
||||||
|
let loadingPromise: Promise<any> | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load mpegts.js library dynamically
|
||||||
|
* @returns Promise that resolves to mpegts.js module
|
||||||
|
*/
|
||||||
|
export const loadMpegts = async (): Promise<any> => {
|
||||||
|
// Return cached instance if available
|
||||||
|
if (mpegtsInstance) {
|
||||||
|
console.log('[MPEG-TS Loader] Using cached mpegts.js instance')
|
||||||
|
return mpegtsInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return existing loading promise if already loading
|
||||||
|
if (loadingPromise) {
|
||||||
|
console.log('[MPEG-TS Loader] Already loading, waiting for existing promise...')
|
||||||
|
return loadingPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading
|
||||||
|
loadingPromise = (async () => {
|
||||||
|
try {
|
||||||
|
console.log('[MPEG-TS Loader] Attempting to load from npm package...')
|
||||||
|
const module = await import('mpegts.js')
|
||||||
|
mpegtsInstance = module.default || module
|
||||||
|
console.log('[MPEG-TS Loader] Successfully loaded from npm package')
|
||||||
|
return mpegtsInstance
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[MPEG-TS Loader] Failed to load mpegts.js:', error)
|
||||||
|
throw new Error('Failed to load mpegts.js. Make sure it is installed: npm install mpegts.js')
|
||||||
|
} finally {
|
||||||
|
loadingPromise = null
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return loadingPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if mpegts.js is supported in the current browser
|
||||||
|
* @param mpegts - The mpegts.js module
|
||||||
|
* @returns True if supported
|
||||||
|
*/
|
||||||
|
export const isMpegtsSupported = (mpegts: any): boolean => {
|
||||||
|
return mpegts && mpegts.isSupported()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create default configuration for mpegts.js player
|
||||||
|
* @param isLive - Whether the stream is live
|
||||||
|
* @returns Configuration object
|
||||||
|
*/
|
||||||
|
export const createDefaultMpegtsConfig = (isLive: boolean = false): MpegtsConfig => {
|
||||||
|
return {
|
||||||
|
enableWorker: false, // Disabled by default due to cross-origin issues
|
||||||
|
enableStashBuffer: true,
|
||||||
|
stashInitialSize: isLive ? 128 : 384,
|
||||||
|
isLive: isLive,
|
||||||
|
lazyLoad: !isLive,
|
||||||
|
lazyLoadMaxDuration: 3 * 60,
|
||||||
|
lazyLoadRecoverDuration: 30,
|
||||||
|
deferLoadAfterSourceOpen: false,
|
||||||
|
autoCleanupSourceBuffer: true,
|
||||||
|
autoCleanupMaxBackwardDuration: 180,
|
||||||
|
autoCleanupMinBackwardDuration: 120,
|
||||||
|
fixAudioTimestampGap: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cached mpegts.js instance
|
||||||
|
* @returns The mpegts.js module or null
|
||||||
|
*/
|
||||||
|
export const getMpegtsInstance = (): any | null => {
|
||||||
|
return mpegtsInstance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cached mpegts.js instance
|
||||||
|
*/
|
||||||
|
export const clearMpegtsCache = (): void => {
|
||||||
|
mpegtsInstance = null
|
||||||
|
loadingPromise = null
|
||||||
|
console.log('[MPEG-TS Loader] Cache cleared')
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
/**
|
||||||
|
* MPEG-TS player setup utility
|
||||||
|
* Initializes and configures mpegts.js player for MPEG-TS/IPTV streams
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { loadMpegts, isMpegtsSupported, createDefaultMpegtsConfig } from './mpegtsLoader'
|
||||||
|
import { isLiveStream } from './videoProtocol'
|
||||||
|
|
||||||
|
export interface MpegtsSetupOptions {
|
||||||
|
video: HTMLVideoElement
|
||||||
|
src: string
|
||||||
|
autoplay?: boolean
|
||||||
|
onError?: (error: Error) => void
|
||||||
|
onLoadedMetadata?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up mpegts.js player instance for MPEG-TS streaming
|
||||||
|
* @param options - Setup options
|
||||||
|
* @returns Cleanup function to destroy the player
|
||||||
|
*/
|
||||||
|
export const setupMpegtsInstance = async ({
|
||||||
|
video,
|
||||||
|
src,
|
||||||
|
autoplay = false,
|
||||||
|
onError,
|
||||||
|
onLoadedMetadata,
|
||||||
|
}: MpegtsSetupOptions): Promise<() => void> => {
|
||||||
|
try {
|
||||||
|
// Load mpegts.js library
|
||||||
|
const mpegts = await loadMpegts()
|
||||||
|
|
||||||
|
// Check if mpegts.js is supported
|
||||||
|
if (!isMpegtsSupported(mpegts)) {
|
||||||
|
const error = new Error(
|
||||||
|
'mpegts.js is not supported in this browser. Media Source Extensions (MSE) is required.'
|
||||||
|
)
|
||||||
|
if (onError) {
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[MPEG-TS Setup] Creating player instance for:', src)
|
||||||
|
|
||||||
|
// Detect if stream is live
|
||||||
|
const isLive = isLiveStream(src)
|
||||||
|
|
||||||
|
// Create mpegts.js player configuration
|
||||||
|
const config = createDefaultMpegtsConfig(isLive)
|
||||||
|
|
||||||
|
// Create player instance
|
||||||
|
const player = mpegts.createPlayer(
|
||||||
|
{
|
||||||
|
type: 'mpegts',
|
||||||
|
url: src,
|
||||||
|
isLive: isLive,
|
||||||
|
hasAudio: true,
|
||||||
|
hasVideo: true,
|
||||||
|
},
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attach to video element
|
||||||
|
player.attachMediaElement(video)
|
||||||
|
|
||||||
|
// Load the stream
|
||||||
|
player.load()
|
||||||
|
|
||||||
|
// Store player instance on video element for later access
|
||||||
|
;(video as any).__mpegtsInstance = player
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
player.on(mpegts.Events.ERROR, (errorType: string, errorDetail: string, errorInfo: any) => {
|
||||||
|
console.error('mpegts.js error:', { errorType, errorDetail, errorInfo })
|
||||||
|
|
||||||
|
const error = new Error(`MPEG-TS Player Error: ${errorType} - ${errorDetail}`)
|
||||||
|
|
||||||
|
// Handle specific error types
|
||||||
|
if (errorType === mpegts.ErrorTypes.NETWORK_ERROR) {
|
||||||
|
console.error('Network error occurred:', errorDetail)
|
||||||
|
|
||||||
|
// Attempt recovery for recoverable network errors
|
||||||
|
if (
|
||||||
|
errorDetail === mpegts.ErrorDetails.NETWORK_EXCEPTION ||
|
||||||
|
errorDetail === mpegts.ErrorDetails.NETWORK_STATUS_CODE_INVALID
|
||||||
|
) {
|
||||||
|
console.log('Attempting to recover from network error...')
|
||||||
|
try {
|
||||||
|
player.unload()
|
||||||
|
player.load()
|
||||||
|
return
|
||||||
|
} catch (recoveryError) {
|
||||||
|
console.error('Failed to recover from network error:', recoveryError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (errorType === mpegts.ErrorTypes.MEDIA_ERROR) {
|
||||||
|
console.error('Media error occurred:', errorDetail)
|
||||||
|
|
||||||
|
// Some media errors are recoverable
|
||||||
|
if (errorDetail === mpegts.ErrorDetails.MEDIA_MSE_ERROR) {
|
||||||
|
console.log('Attempting to recover from media error...')
|
||||||
|
try {
|
||||||
|
player.unload()
|
||||||
|
player.load()
|
||||||
|
return
|
||||||
|
} catch (recoveryError) {
|
||||||
|
console.error('Failed to recover from media error:', recoveryError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call error callback
|
||||||
|
if (onError) {
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on(mpegts.Events.LOADING_COMPLETE, () => {
|
||||||
|
console.log('mpegts.js: Loading complete')
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on(mpegts.Events.RECOVERED_EARLY_EOF, () => {
|
||||||
|
console.log('mpegts.js: Recovered from early EOF')
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on(mpegts.Events.METADATA_ARRIVED, (metadata: any) => {
|
||||||
|
console.log('mpegts.js: Metadata arrived', metadata)
|
||||||
|
|
||||||
|
// Trigger onLoadedMetadata callback
|
||||||
|
if (onLoadedMetadata) {
|
||||||
|
onLoadedMetadata()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on(mpegts.Events.STATISTICS_INFO, (stats: any) => {
|
||||||
|
// Statistics info for debugging/monitoring
|
||||||
|
if (stats) {
|
||||||
|
;(video as any).__mpegtsStats = stats
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Auto-play if requested
|
||||||
|
if (autoplay) {
|
||||||
|
try {
|
||||||
|
await video.play()
|
||||||
|
} catch (playError) {
|
||||||
|
console.warn('Autoplay failed:', playError)
|
||||||
|
// Autoplay might be blocked by browser, ignore error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => {
|
||||||
|
try {
|
||||||
|
console.log('Cleaning up mpegts.js player...')
|
||||||
|
|
||||||
|
// Remove event listeners
|
||||||
|
player.off(mpegts.Events.ERROR)
|
||||||
|
player.off(mpegts.Events.LOADING_COMPLETE)
|
||||||
|
player.off(mpegts.Events.RECOVERED_EARLY_EOF)
|
||||||
|
player.off(mpegts.Events.METADATA_ARRIVED)
|
||||||
|
player.off(mpegts.Events.STATISTICS_INFO)
|
||||||
|
|
||||||
|
// Pause and unload
|
||||||
|
video.pause()
|
||||||
|
player.unload()
|
||||||
|
|
||||||
|
// Detach from video element
|
||||||
|
player.detachMediaElement()
|
||||||
|
|
||||||
|
// Destroy player instance
|
||||||
|
player.destroy()
|
||||||
|
|
||||||
|
// Clean up stored references
|
||||||
|
delete (video as any).__mpegtsInstance
|
||||||
|
delete (video as any).__mpegtsStats
|
||||||
|
} catch (cleanupError) {
|
||||||
|
console.error('Error during mpegts.js cleanup:', cleanupError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to setup mpegts.js player:', error)
|
||||||
|
|
||||||
|
const setupError =
|
||||||
|
error instanceof Error ? error : new Error('Failed to setup MPEG-TS player')
|
||||||
|
|
||||||
|
if (onError) {
|
||||||
|
onError(setupError)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw setupError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current mpegts.js player instance from a video element
|
||||||
|
* @param video - The video element
|
||||||
|
* @returns The mpegts.js player instance or null
|
||||||
|
*/
|
||||||
|
export const getMpegtsInstance = (video: HTMLVideoElement | null): any | null => {
|
||||||
|
if (!video) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (video as any).__mpegtsInstance || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current mpegts.js statistics from a video element
|
||||||
|
* @param video - The video element
|
||||||
|
* @returns The statistics object or null
|
||||||
|
*/
|
||||||
|
export const getMpegtsStats = (video: HTMLVideoElement | null): any | null => {
|
||||||
|
if (!video) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (video as any).__mpegtsStats || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a video element has an active MPEG-TS player instance
|
||||||
|
* @param video - The video element
|
||||||
|
* @returns True if has active instance
|
||||||
|
*/
|
||||||
|
export const hasMpegtsInstance = (video: HTMLVideoElement | null): boolean => {
|
||||||
|
return getMpegtsInstance(video) !== null
|
||||||
|
}
|
||||||
@@ -5,16 +5,16 @@ describe('videoProtocol', () => {
|
|||||||
describe('detectVideoProtocol', () => {
|
describe('detectVideoProtocol', () => {
|
||||||
it('should detect MPEG-TS IPTV streams', () => {
|
it('should detect MPEG-TS IPTV streams', () => {
|
||||||
const result = detectVideoProtocol('http://favoritv65.xyz:8080/live/Apollon45/HpjWrDa6gWWd/98925.ts')
|
const result = detectVideoProtocol('http://favoritv65.xyz:8080/live/Apollon45/HpjWrDa6gWWd/98925.ts')
|
||||||
expect(result.protocol).toBe('native')
|
expect(result.protocol).toBe('mpegts')
|
||||||
expect(result.isLive).toBe(true)
|
expect(result.isLive).toBe(true)
|
||||||
expect(result.needsSpecialPlayer).toBe(false)
|
expect(result.needsSpecialPlayer).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should detect .ts files with query parameters', () => {
|
it('should detect .ts files with query parameters', () => {
|
||||||
const result = detectVideoProtocol('http://example.com/stream/video.ts?token=abc123')
|
const result = detectVideoProtocol('http://example.com/stream/video.ts?token=abc123')
|
||||||
expect(result.protocol).toBe('native')
|
expect(result.protocol).toBe('mpegts')
|
||||||
expect(result.isLive).toBe(true)
|
expect(result.isLive).toBe(true)
|
||||||
expect(result.needsSpecialPlayer).toBe(false)
|
expect(result.needsSpecialPlayer).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should detect HLS streams', () => {
|
it('should detect HLS streams', () => {
|
||||||
@@ -75,7 +75,7 @@ describe('videoProtocol', () => {
|
|||||||
expect(isHlsStream('http://example.com/stream.m3u8')).toBe(true)
|
expect(isHlsStream('http://example.com/stream.m3u8')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return false for IPTV .ts streams (they use native playback)', () => {
|
it('should return false for IPTV .ts streams (they use mpegts.js)', () => {
|
||||||
expect(isHlsStream('http://favoritv65.xyz:8080/live/user/pass/98925.ts')).toBe(false)
|
expect(isHlsStream('http://favoritv65.xyz:8080/live/user/pass/98925.ts')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Detects the streaming protocol from a video URL
|
* Detects the streaming protocol from a video URL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type VideoProtocol = 'native' | 'hls' | 'rtmp' | 'dash'
|
export type VideoProtocol = 'native' | 'hls' | 'rtmp' | 'dash' | 'mpegts'
|
||||||
|
|
||||||
export interface ProtocolDetectionResult {
|
export interface ProtocolDetectionResult {
|
||||||
protocol: VideoProtocol
|
protocol: VideoProtocol
|
||||||
@@ -74,12 +74,12 @@ export const detectVideoProtocol = (src: string): ProtocolDetectionResult => {
|
|||||||
// MPEG-TS (IPTV) detection
|
// MPEG-TS (IPTV) detection
|
||||||
// Check for .ts extension (Transport Stream used in IPTV)
|
// Check for .ts extension (Transport Stream used in IPTV)
|
||||||
// Note: These are direct TS streams, not HLS playlists
|
// Note: These are direct TS streams, not HLS playlists
|
||||||
// Try native playback first as modern browsers support MPEG-TS
|
// Browsers don't support MPEG-TS natively, so we use mpegts.js for playback
|
||||||
if (lowerSrc.includes('.ts') || lowerSrc.match(/\.ts(\?|$)/)) {
|
if (lowerSrc.includes('.ts') || lowerSrc.match(/\.ts(\?|$)/)) {
|
||||||
return {
|
return {
|
||||||
protocol: 'native', // Try native playback first
|
protocol: 'mpegts', // Use mpegts.js for MPEG-TS streaming
|
||||||
isLive: true, // IPTV streams are typically live
|
isLive: true, // IPTV streams are typically live
|
||||||
needsSpecialPlayer: false, // Modern browsers support MPEG-TS
|
needsSpecialPlayer: true, // Requires mpegts.js for MPEG-TS playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'flv.js': new URL('./src/test/mocks/flv.mock.ts', import.meta.url).pathname,
|
'flv.js': new URL('./src/test/mocks/flv.mock.ts', import.meta.url).pathname,
|
||||||
'hls.js': new URL('./src/test/mocks/hls.mock.ts', import.meta.url).pathname,
|
'hls.js': new URL('./src/test/mocks/hls.mock.ts', import.meta.url).pathname,
|
||||||
|
'mpegts.js': new URL('./src/test/mocks/mpegts.mock.ts', import.meta.url).pathname,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user