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,128 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Card, Form, Input, Button, DatePicker, Typography, message, Space, Alert, Divider,
|
||||
} from 'antd'
|
||||
import { CopyOutlined, ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import { createStream } from '../../api/streams'
|
||||
|
||||
const { Title, Text } = Typography
|
||||
const { TextArea } = Input
|
||||
|
||||
export default function StreamForm() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [result, setResult] = useState<{ rtmp_url: string; stream_key: string } | null>(null)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await createStream({
|
||||
title: values.title,
|
||||
description: values.description,
|
||||
cover_url: values.cover_url,
|
||||
scheduled_at: values.scheduled_at?.toISOString(),
|
||||
})
|
||||
setResult({ rtmp_url: res.rtmp_url, stream_key: res.stream_key })
|
||||
message.success('Stream created')
|
||||
} catch {
|
||||
message.error('Failed to create stream')
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<Title level={3} style={{ marginBottom: 24 }}>Stream Created</Title>
|
||||
<Alert
|
||||
type="success"
|
||||
message="Your stream is ready"
|
||||
description={
|
||||
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Text strong>Stream Key (UUID):</Text>
|
||||
<div style={{
|
||||
marginTop: 8, padding: 12,
|
||||
background: '#F5F2EE', borderRadius: 6,
|
||||
fontFamily: 'monospace', fontSize: 14,
|
||||
wordBreak: 'break-all',
|
||||
}}>
|
||||
{result.stream_key}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Text strong>RTMP Push URL:</Text>
|
||||
<div style={{
|
||||
marginTop: 8, padding: 12,
|
||||
background: '#F5F2EE', borderRadius: 6,
|
||||
fontFamily: 'monospace', fontSize: 14,
|
||||
wordBreak: 'break-all',
|
||||
}}>
|
||||
{result.rtmp_url}
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
}
|
||||
showIcon
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`Stream Key: ${result.stream_key}\nRTMP URL: ${result.rtmp_url}`
|
||||
)
|
||||
message.success('Copied to clipboard')
|
||||
}}
|
||||
>
|
||||
Copy All
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => navigate('/admin/streams')}>
|
||||
Back to Streams
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: 640 }}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => navigate('/admin/streams')}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Card>
|
||||
<Title level={3} style={{ marginBottom: 24 }}>New Stream</Title>
|
||||
<Form layout="vertical" onFinish={handleSubmit}>
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="Title"
|
||||
rules={[{ required: true, message: 'Enter a title' }]}
|
||||
>
|
||||
<Input placeholder="Stream title" size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item name="description" label="Description">
|
||||
<TextArea rows={3} placeholder="Stream description (optional)" />
|
||||
</Form.Item>
|
||||
<Form.Item name="cover_url" label="Cover Image URL">
|
||||
<Input placeholder="https://..." />
|
||||
</Form.Item>
|
||||
<Form.Item name="scheduled_at" label="Scheduled Time">
|
||||
<DatePicker showTime style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={loading} size="large" block>
|
||||
Create Stream
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user