Refactor live stream page and add Shaka decoder

- Move files for `/tv/live/[id]` route into a dedicated directory structure.
- Introduce Shaka player decoder for HLS playback.
- Remove deprecated FLV decoder (mpegts.js).
- Add basic chat page placeholder.
- Improve cleanup logic for decoder instances.
This commit is contained in:
SrGooglo 2025-05-21 19:06:51 +00:00
parent 68af8c6f93
commit 87f746d5b9
8 changed files with 122 additions and 37 deletions

View File

@ -0,0 +1,7 @@
import React from "react"
const ChatPage = () => {
return <div>Chat</div>
}
export default ChatPage

View File

@ -40,7 +40,6 @@ export default (player, sources = {}, options = {}) => {
source += `?token=${options.authToken}`
}
console.log("[HLS] Instance options >", options)
console.log(`[HLS] Loading source [${source}]`)
hlsInstance.attachMedia(player)

View File

@ -0,0 +1,2 @@
export { default as hls } from "./hls"
export { default as shaka } from "./shaka"

View File

@ -0,0 +1,100 @@
import shaka from "shaka-player"
export default async (player, sources = {}, options = {}) => {
if (!player) {
console.error("[Shaka] player is not defined")
return false
}
if (!sources.hls) {
console.error("[Shaka] an hls source is not provided")
return false
}
let source = sources.hls
// Initialize shaka player
const shakaInstance = new shaka.Player(player)
// Helper function to sync to live edge
const syncToLive = () => {
if (shakaInstance.isLive()) {
const end = shakaInstance.seekRange().end
player.currentTime = end
}
}
// Configure for low-latency HLS
shakaInstance.configure({
streaming: {
lowLatencyMode: true,
inaccurateManifestTolerance: 0,
rebufferingGoal: 0.01,
bufferingGoal: 0.1,
bufferBehind: 30,
startAtSegmentBoundary: false,
durationBackoff: 0.2,
},
})
// Add request filter for authentication if token is provided
if (options.authToken) {
shakaInstance
.getNetworkingEngine()
.registerRequestFilter((type, request) => {
request.headers = {
...request.headers,
Authorization: `Bearer ${options.authToken}`,
}
})
source += `?token=${options.authToken}`
}
console.log("[Shaka] Instance options >", options)
console.log(`[Shaka] Loading source [${source}]`)
// Error handling
shakaInstance.addEventListener("error", (error) => {
console.error("[Shaka] Error", error)
})
// Buffer state monitoring
player.addEventListener("waiting", () => {
console.log("[Shaka] Buffer underrun")
})
// Handle stream end
player.addEventListener("ended", () => {
console.log("[Shaka] Stream ended")
if (typeof options.onSourceEnd === "function") {
options.onSourceEnd()
}
})
try {
await shakaInstance.load(source)
console.log("[Shaka] Stream loaded successfully")
const tracks = shakaInstance.getVariantTracks()
console.log("[Shaka] Available qualities >", tracks)
} catch (error) {
console.error("[Shaka] Error loading stream:", error)
}
player.addEventListener("play", () => {
console.log("[SHAKA] Syncing to last position")
syncToLive()
})
// Add destroy method for cleanup
shakaInstance._destroy = () => {
try {
shakaInstance.unload()
shakaInstance.destroy()
} catch (error) {
console.error("[Shaka] Error during cleanup:", error)
}
}
return shakaInstance
}

View File

@ -29,7 +29,7 @@ async function fetchStream(stream_id) {
stream = stream[0]
}
if (!stream.sources) {
if (!stream.sources || !stream.sources.hls) {
return false
}
@ -60,6 +60,8 @@ export default class StreamViewer extends React.Component {
return false
}
console.log(`[TV] Switching decoder to: ${decoder}`)
await this.toggleLoading(true)
// check if decoder is already loaded
@ -71,8 +73,6 @@ export default class StreamViewer extends React.Component {
this.setState({ decoderInstance: null })
}
console.log(`[TV] Switching decoder to: ${decoder}`)
const decoderInstance = await Decoders[decoder](...args)
await this.setState({
@ -236,8 +236,12 @@ export default class StreamViewer extends React.Component {
spectators: stream.viewers,
})
try {
// joinStreamWebsocket
await this.joinStreamWebsocket(stream)
this.joinStreamWebsocket(stream)
} catch (error) {
console.error(error)
}
// load decoder with provided data
await this.loadDecoder(
@ -260,6 +264,10 @@ export default class StreamViewer extends React.Component {
this.state.decoderInstance.destroy()
}
if (typeof this.state.decoderInstance?._destroy === "function") {
this.state.decoderInstance._destroy()
}
if (this.state.websocket) {
if (typeof this.state.websocket.destroy === "function") {
this.state.websocket.destroy()

View File

@ -1,29 +0,0 @@
import mpegts from "mpegts.js"
export default async (player, source, { onSourceEnd } = {}) => {
if (!source) {
console.error("Stream source is not defined")
return false
}
const decoderInstance = mpegts.createPlayer({
type: "flv",
isLive: true,
enableWorker: true,
url: source,
})
if (typeof onSourceEnd === "function") {
decoderInstance.on(mpegts.Events.ERROR, onSourceEnd)
}
decoderInstance.attachMediaElement(player)
decoderInstance.load()
await decoderInstance.play().catch((error) => {
console.error(error)
})
return decoderInstance
}

View File

@ -1,2 +0,0 @@
export { default as hls } from "./hls"
export { default as flv } from "./flv"