Initial commit: LiveStream Platform

Backend: FastAPI + SQLAlchemy + SQLite
Frontend: React + Vite + Ant Design (serif Anthropic theme)
Media server: MediaMTX with edge node support
Load balancing: round-robin / weighted / least-conn
Multicast detection with automatic fallback
Transcoding support via FFmpeg (configurable quality)
hls.js player with line & quality switching
Admin panel with password auth
Windows compatible scripts
This commit is contained in:
admin committed 2026-08-07 15:02:07 +08:00
commit 2e4594fa77
43 files changed
+2196

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
import api, { setToken, getToken } from './client'
export async function login(password: string): Promise<string> {
const { data } = await api.post(`/auth/login?password=${encodeURIComponent(password)}`)
setToken(data.token)
return data.token
}
export async function verifyToken(): Promise<boolean> {
const token = getToken()
if (!token) return false
try {
await api.post(`/auth/verify?token=${encodeURIComponent(token)}`)
return true
} catch {
return false
}
}
+30
View File
@@ -0,0 +1,30 @@
import axios from 'axios'
const api = axios.create({
baseURL: '/api',
timeout: 15000,
})
const TOKEN_KEY = 'livestream_admin_token'
api.interceptors.request.use((config) => {
const token = localStorage.getItem(TOKEN_KEY)
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export function setToken(token: string) {
localStorage.setItem(TOKEN_KEY, token)
}
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY)
}
export function clearToken() {
localStorage.removeItem(TOKEN_KEY)
}
export default api
+92
View File
@@ -0,0 +1,92 @@
import api from './client'
export interface StreamInfo {
id: number
title: string
description?: string
cover_url?: string
stream_key: string
status: string
scheduled_at?: string
started_at?: string
ended_at?: string
multicast_enabled: boolean
created_at: string
}
export interface StreamCreateReq {
title: string
description?: string
cover_url?: string
scheduled_at?: string
}
export interface WatchInfo {
stream_id: number
title: string
status: string
multicast_available: boolean
lines: LineInfo[]
qualities: QualityInfo[]
}
export interface LineInfo {
node_id: string
node_name: string
weight: number
type: string
url: string
unicast_fallback?: string
qualities: QualityUrl[]
}
export interface QualityUrl {
label: string
width: number
height: number
url: string
}
export interface QualityInfo {
label: string
width: number
height: number
}
export async function fetchStreams(): Promise<StreamInfo[]> {
const { data } = await api.get('/streams')
return data
}
export async function createStream(req: StreamCreateReq): Promise<{
id: number; stream_key: string; rtmp_url: string; status: string
}> {
const { data } = await api.post('/streams', req)
return data
}
export async function getStream(streamId: number): Promise<StreamInfo> {
const { data } = await api.get(`/streams/${streamId}`)
return data
}
export async function updateStream(streamId: number, req: Partial<StreamCreateReq>): Promise<void> {
await api.put(`/streams/${streamId}`, req)
}
export async function deleteStream(streamId: number): Promise<void> {
await api.delete(`/streams/${streamId}`)
}
export async function goLive(streamId: number): Promise<void> {
await api.post(`/streams/${streamId}/go-live`)
}
export async function endStream(streamId: number): Promise<void> {
await api.post(`/streams/${streamId}/end`)
}
export async function getWatchUrls(streamId: number): Promise<WatchInfo> {
const { data } = await api.get(`/streams/${streamId}/watch`)
return data
}