feat: add admin login page and authentication API
- Created a new login page for admin access at frontend/admin/login/index.html. - Implemented form submission with error handling for login credentials. - Updated frontend/index.html to mute the video player and removed unnecessary Alpine.js data binding. - Enhanced manifest URL resolution in frontend/src/main.ts to include cache-busting parameters. - Modified vite.config.ts to include the new admin login page in the build process. - Updated nginx.conf to handle admin login routing, authentication, and API proxying for admin actions.
This commit is contained in:
1 parent
1ae0ad121b
commit
6b494a34fe
15 files changed
+1896
-52
No files matched your search
@@ -143,6 +143,46 @@ func HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, `{"status":"healthy","service":"playout","timestamp":"`+time.Now().UTC().Format(time.RFC3339)+`"}`)
|
||||
}
|
||||
|
||||
func resetHLSOutput(hlsDir string) error {
|
||||
if err := os.MkdirAll(hlsDir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
matches, err := filepath.Glob(filepath.Join(hlsDir, "segment_*.ts"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
if err := os.Remove(match); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmp4Matches, err := filepath.Glob(filepath.Join(hlsDir, "segment_*.m4s"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, match := range fmp4Matches {
|
||||
if err := os.Remove(match); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
manifestPath := filepath.Join(hlsDir, "playlist.m3u8")
|
||||
if err := os.Remove(manifestPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
initPath := filepath.Join(hlsDir, "init.mp4")
|
||||
if err := os.Remove(initPath); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
playlistPath := "/app/playlist/playlist.txt"
|
||||
if len(os.Args) > 1 {
|
||||
@@ -176,16 +216,29 @@ func main() {
|
||||
|
||||
// Run FFmpeg in a loop for continuous playback with R2 sync hook
|
||||
for {
|
||||
fmt.Printf("[%s] Starting FFmpeg playout with -c copy (immutable segments for Cloudflare caching)...\n", time.Now().Format(time.RFC3339))
|
||||
if err := resetHLSOutput("/app/hls"); err != nil {
|
||||
fmt.Println("Failed to reset HLS output:", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] Starting FFmpeg playout with browser-safe H.264/AAC HLS encoding...\n", time.Now().Format(time.RFC3339))
|
||||
|
||||
cmd := exec.Command("ffmpeg", "-re", "-f", "concat", "-safe", "0", "-i", playlistPath,
|
||||
"-c:v", "copy", "-c:a", "copy",
|
||||
"-map", "0:v:0", "-map", "0:a:0?",
|
||||
"-vf", "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,fps=30,format=yuv420p",
|
||||
"-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p",
|
||||
"-profile:v", "high", "-level", "4.0",
|
||||
"-r", "30", "-g", "60", "-keyint_min", "60", "-sc_threshold", "0",
|
||||
"-c:a", "aac", "-ar", "48000", "-ac", "2", "-b:a", "128k",
|
||||
"-f", "hls",
|
||||
"-hls_time", "6",
|
||||
"-hls_list_size", "24",
|
||||
"-hls_segment_type", "fmp4",
|
||||
"-hls_fmp4_init_filename", "init.mp4",
|
||||
"-strftime", "1",
|
||||
"-hls_flags", "delete_segments+append_list+independent_segments",
|
||||
"-hls_segment_filename", "/app/hls/segment_%Y%m%d_%H%M%S.ts",
|
||||
"-hls_flags", "delete_segments+independent_segments",
|
||||
"-hls_segment_filename", "/app/hls/segment_%Y%m%d_%H%M%S.m4s",
|
||||
"/app/hls/playlist.m3u8")
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
@@ -203,7 +256,7 @@ func main() {
|
||||
|
||||
// syncLatestToR2 uploads new segments and the playlist manifest to R2
|
||||
func syncLatestToR2(hlsDir string) error {
|
||||
files, err := filepath.Glob(filepath.Join(hlsDir, "segment_*.ts"))
|
||||
files, err := filepath.Glob(filepath.Join(hlsDir, "segment_*.m4s"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -222,6 +275,10 @@ func syncLatestToR2(hlsDir string) error {
|
||||
if err := SyncToR2(manifestPath, "live/playlist.m3u8"); err != nil {
|
||||
return err
|
||||
}
|
||||
initPath := filepath.Join(hlsDir, "init.mp4")
|
||||
if err := SyncToR2(initPath, "live/init.mp4"); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Synced live playlist.m3u8 to R2")
|
||||
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user