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:
hibna
2025-11-03 02:35:56 +03:00
parent 42a12dfa8b
commit 36f83ff72c
26 changed files with 3833 additions and 1212 deletions
+240
View File
@@ -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
}