Add SRT/FLV/RTMP support and update documentation
Introduced Python scripts for SRT subtitle checking and fixing, and added comprehensive documentation covering advanced features such as protocol detection, subtitle/audio/quality management, keyboard shortcuts, and touch gestures. Updated local settings to allow new build and Python commands, added TypeScript definitions for FLV, and implemented RTMP/FLV protocol support in the player. Removed CHANGELOG.md and made various improvements to styles and example app.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* RTMP/FLV player setup utility
|
||||
* Initializes and configures flv.js player for RTMP/FLV streams
|
||||
* Mirrors the HLS setup pattern for consistency
|
||||
*/
|
||||
|
||||
import { loadFlvjs, isFlvjsSupported, createDefaultFlvConfig } from './rtmpLoader'
|
||||
import { isLiveStream } from './videoProtocol'
|
||||
|
||||
export interface RtmpSetupOptions {
|
||||
video: HTMLVideoElement
|
||||
src: string
|
||||
autoplay?: boolean
|
||||
onError?: (error: Error) => void
|
||||
onLoadedMetadata?: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up flv.js player instance for RTMP/FLV streaming
|
||||
* @param options - Setup options
|
||||
* @returns Cleanup function to destroy the player
|
||||
*/
|
||||
export const setupRtmpInstance = async ({
|
||||
video,
|
||||
src,
|
||||
autoplay = false,
|
||||
onError,
|
||||
onLoadedMetadata,
|
||||
}: RtmpSetupOptions): Promise<() => void> => {
|
||||
try {
|
||||
// Load flv.js library
|
||||
const flvjs = await loadFlvjs()
|
||||
|
||||
// Check if flv.js is supported
|
||||
if (!isFlvjsSupported(flvjs)) {
|
||||
const error = new Error(
|
||||
'flv.js is not supported in this browser. Media Source Extensions (MSE) is required.'
|
||||
)
|
||||
if (onError) {
|
||||
onError(error)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
// Detect if stream is live
|
||||
const isLive = isLiveStream(src)
|
||||
|
||||
// Determine media type
|
||||
let type = 'flv'
|
||||
if (src.startsWith('rtmp://') || src.startsWith('rtmps://')) {
|
||||
// For RTMP URLs, flv.js expects HTTP-FLV endpoint
|
||||
// This is a limitation - direct RTMP playback requires server-side conversion
|
||||
console.warn(
|
||||
'Direct RTMP playback requires an HTTP-FLV proxy. Please ensure your RTMP stream is available via HTTP-FLV.'
|
||||
)
|
||||
type = 'flv'
|
||||
} else if (src.includes('.flv')) {
|
||||
type = 'flv'
|
||||
}
|
||||
|
||||
// Create flv.js player configuration
|
||||
const config = createDefaultFlvConfig(isLive)
|
||||
|
||||
// Create player instance
|
||||
const player = flvjs.createPlayer(
|
||||
{
|
||||
type: type,
|
||||
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).__rtmpInstance = player
|
||||
|
||||
// Event handlers
|
||||
player.on(flvjs.Events.ERROR, (errorType: string, errorDetail: string, errorInfo: any) => {
|
||||
console.error('flv.js error:', { errorType, errorDetail, errorInfo })
|
||||
|
||||
const error = new Error(`FLV Player Error: ${errorType} - ${errorDetail}`)
|
||||
|
||||
// Handle specific error types
|
||||
if (errorType === flvjs.ErrorTypes.NETWORK_ERROR) {
|
||||
console.error('Network error occurred:', errorDetail)
|
||||
|
||||
// Attempt recovery for recoverable network errors
|
||||
if (
|
||||
errorDetail === flvjs.ErrorDetails.NETWORK_EXCEPTION ||
|
||||
errorDetail === flvjs.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 === flvjs.ErrorTypes.MEDIA_ERROR) {
|
||||
console.error('Media error occurred:', errorDetail)
|
||||
|
||||
// Some media errors are recoverable
|
||||
if (errorDetail === flvjs.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(flvjs.Events.LOADING_COMPLETE, () => {
|
||||
console.log('flv.js: Loading complete')
|
||||
})
|
||||
|
||||
player.on(flvjs.Events.RECOVERED_EARLY_EOF, () => {
|
||||
console.log('flv.js: Recovered from early EOF')
|
||||
})
|
||||
|
||||
player.on(flvjs.Events.METADATA_ARRIVED, (metadata: any) => {
|
||||
console.log('flv.js: Metadata arrived', metadata)
|
||||
|
||||
// Trigger onLoadedMetadata callback
|
||||
if (onLoadedMetadata) {
|
||||
onLoadedMetadata()
|
||||
}
|
||||
})
|
||||
|
||||
player.on(flvjs.Events.STATISTICS_INFO, (stats: any) => {
|
||||
// Statistics info for debugging/monitoring
|
||||
// Can be used to display stream quality, bitrate, etc.
|
||||
if (stats) {
|
||||
;(video as any).__rtmpStats = 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 flv.js player...')
|
||||
|
||||
// Remove event listeners
|
||||
player.off(flvjs.Events.ERROR)
|
||||
player.off(flvjs.Events.LOADING_COMPLETE)
|
||||
player.off(flvjs.Events.RECOVERED_EARLY_EOF)
|
||||
player.off(flvjs.Events.METADATA_ARRIVED)
|
||||
player.off(flvjs.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).__rtmpInstance
|
||||
delete (video as any).__rtmpStats
|
||||
} catch (cleanupError) {
|
||||
console.error('Error during flv.js cleanup:', cleanupError)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to setup flv.js player:', error)
|
||||
|
||||
const setupError =
|
||||
error instanceof Error ? error : new Error('Failed to setup RTMP/FLV player')
|
||||
|
||||
if (onError) {
|
||||
onError(setupError)
|
||||
}
|
||||
|
||||
throw setupError
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current flv.js player instance from a video element
|
||||
* @param video - The video element
|
||||
* @returns The flv.js player instance or null
|
||||
*/
|
||||
export const getRtmpInstance = (video: HTMLVideoElement | null): any | null => {
|
||||
if (!video) {
|
||||
return null
|
||||
}
|
||||
return (video as any).__rtmpInstance || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current flv.js statistics from a video element
|
||||
* @param video - The video element
|
||||
* @returns The statistics object or null
|
||||
*/
|
||||
export const getRtmpStats = (video: HTMLVideoElement | null): any | null => {
|
||||
if (!video) {
|
||||
return null
|
||||
}
|
||||
return (video as any).__rtmpStats || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a video element has an active RTMP player instance
|
||||
* @param video - The video element
|
||||
* @returns True if has active instance
|
||||
*/
|
||||
export const hasRtmpInstance = (video: HTMLVideoElement | null): boolean => {
|
||||
return getRtmpInstance(video) !== null
|
||||
}
|
||||
Reference in New Issue
Block a user