v2: mpegts.js + single-upstream fan-out hub (no FFmpeg/HLS)
Replaces the FFmpeg -> MediaMTX -> HLS pipeline with a raw MPEG-TS fan-out architecture: - Go stream.Hub holds ONE upstream connection per active channel and broadcasts the raw TS bytes to all connected browsers via /ts. The IPTV provider only ever sees one IP (the server), no matter how many viewers are watching (shared single-channel model). - Frontend uses mpegts.js to play the raw TS in-browser via MSE, fixing HEVC/H.265 4K streams that HLS could not package. - Dropped FFmpeg, MediaMTX, RTMP/RTSP, HLS proxy, playback/ingest modes, the OBS source toggle, and FFmpeg health stats. - UI look preserved (fullscreen video, collapsible sidebar, status bar, upstream-down banner, hourly playlist auto-refresh). Tradeoff: loses iOS Safari (no MSE/mpegts.js); fixes desktop/Android playback of both H.264 and H.265.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import type { StreamStatus, Channel, PlaybackMode } from "./types";
|
||||
import type { StreamStatus, Channel } from "./types";
|
||||
import * as api from "./api/client";
|
||||
import Player from "./components/Player";
|
||||
import StatusBar from "./components/StatusBar";
|
||||
@@ -17,23 +17,9 @@ export default function App() {
|
||||
const [status, setStatus] = useState<StreamStatus | null>(null);
|
||||
const [channels, setChannels] = useState<Channel[]>([]);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [playbackMode, setPlaybackMode] = useState<PlaybackMode>("standard");
|
||||
const [toggleVisible, setToggleVisible] = useState(false);
|
||||
const hideTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
api
|
||||
.getConfig()
|
||||
.then((c) => {
|
||||
if (c.playback_mode === "llhls" || c.playback_mode === "fmp4") {
|
||||
setPlaybackMode(c.playback_mode);
|
||||
} else {
|
||||
setPlaybackMode("standard");
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const fetchStatus = useCallback(async () => {
|
||||
try {
|
||||
const s = await api.getStatus();
|
||||
@@ -56,7 +42,7 @@ export default function App() {
|
||||
fetchStatus();
|
||||
fetchChannels();
|
||||
// Poll faster while live so passive viewers detect channel switches promptly.
|
||||
// Transitioning gets the fastest rate so the UI clears quickly after FFmpeg starts.
|
||||
// Transitioning gets the fastest rate so the UI clears quickly after a switch.
|
||||
const rate = status?.transitioning ? 1000 : status?.live ? 2000 : 5000;
|
||||
const interval = setInterval(fetchStatus, rate);
|
||||
return () => clearInterval(interval);
|
||||
@@ -103,10 +89,6 @@ export default function App() {
|
||||
}
|
||||
}, [showToggle]);
|
||||
|
||||
const handleSourceChanged = () => {
|
||||
fetchStatus();
|
||||
};
|
||||
|
||||
const handlePlaylistReloaded = () => {
|
||||
fetchChannels();
|
||||
};
|
||||
@@ -121,8 +103,7 @@ export default function App() {
|
||||
<div className="stage">
|
||||
<Player
|
||||
live={status?.live ?? false}
|
||||
mode={playbackMode}
|
||||
streamKey={`${status?.source ?? "offline"}:${status?.channel_name ?? ""}`}
|
||||
streamKey={status?.channel_name ?? "offline"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -138,11 +119,7 @@ export default function App() {
|
||||
|
||||
<aside className={`sidebar ${sidebarOpen ? "open" : "collapsed"}`}>
|
||||
<div className="sidebar-inner">
|
||||
<AdminPanel
|
||||
status={status}
|
||||
onSourceChanged={handleSourceChanged}
|
||||
onPlaylistReloaded={handlePlaylistReloaded}
|
||||
/>
|
||||
<AdminPanel onPlaylistReloaded={handlePlaylistReloaded} />
|
||||
<ChannelList
|
||||
channels={channels}
|
||||
activeChannel={status?.channel_name ?? ""}
|
||||
|
||||
Reference in New Issue
Block a user