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:
commit
2e4594fa77
43 files changed
+2196
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user