fix: cannot parse input config

mod: set gin release mode
fix: input item start and end args not work
fix: cannot auto play next video
This commit is contained in:
Nite07 2024-10-24 22:29:03 +08:00
parent 104195b977
commit 6093f5d36b
7 changed files with 91 additions and 60 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
video video
config.json config.json
logs logs
dist

View File

@ -8,7 +8,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
) )
@ -63,18 +62,7 @@ var GlobalConfig Config
func init() { func init() {
GlobalConfig = Config{} GlobalConfig = Config{}
err := readConfig("config.json") err := readConfig("config.json")
for i, item := range GlobalConfig.InputItems { if len(GlobalConfig.Input) == 0 {
if item.ItemType == "file" {
GlobalConfig.VideoList = append(GlobalConfig.VideoList, item)
} else if item.ItemType == "dir" {
videos, err := getAllVideos(item.Path)
if err != nil {
log.Fatalf("input[%v] walk error: %v", i, err)
}
GlobalConfig.VideoList = append(GlobalConfig.VideoList, videos...)
}
}
if len(GlobalConfig.VideoList) == 0 {
log.Fatal("No input video found") log.Fatal("No input video found")
} }
if err != nil { if err != nil {
@ -127,31 +115,66 @@ func validateConfig() error {
func validateInputConfig() error { func validateInputConfig() error {
if GlobalConfig.Input == nil { if GlobalConfig.Input == nil {
return errors.New("video_path is nil") return errors.New("video_path is nil")
} else {
for i, item := range GlobalConfig.Input {
typeOf := reflect.TypeOf(item)
var inputItem InputItem
if typeOf.Kind() == reflect.String {
inputItem = InputItem{Path: item.(string)}
}
if inputItem.Path == "" {
return fmt.Errorf("video_path[%v] is empty", i)
}
stat, err := os.Stat(inputItem.Path)
if err != nil {
return fmt.Errorf("video_path[%v] stat failed: %v", i, err)
}
if stat.IsDir() {
inputItem.ItemType = "dir"
} else {
inputItem.ItemType = "file"
if !utils.IsSupportedVideo(inputItem.Path) {
return fmt.Errorf("video_path[%v] is not supported", i)
}
}
GlobalConfig.InputItems = append(GlobalConfig.InputItems, inputItem)
}
} }
GlobalConfig.InputItems = make([]InputItem, 0, len(GlobalConfig.Input))
GlobalConfig.VideoList = []InputItem{}
for i, item := range GlobalConfig.Input {
var inputItem InputItem
switch v := item.(type) {
case string:
inputItem = InputItem{Path: v}
case map[string]any:
data, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("failed to marshal input item[%d]: %v", i, err)
}
if err := json.Unmarshal(data, &inputItem); err != nil {
return fmt.Errorf("failed to unmarshal input item[%d]: %v", i, err)
}
// more efficient, but coupled
// if path, ok := v["path"].(string); ok {
// inputItem.Path = path
// }
// if start, ok := v["start"].(string); ok {
// inputItem.Start = start
// }
// if end, ok := v["end"].(string); ok {
// inputItem.End = end
// }
default:
return fmt.Errorf("invalid input type for item[%d]: %T", i, item)
}
if inputItem.Path == "" {
return fmt.Errorf("video_path[%d] is empty", i)
}
stat, err := os.Stat(inputItem.Path)
if err != nil {
return fmt.Errorf("video_path[%d] stat failed: %v", i, err)
}
if stat.IsDir() {
inputItem.ItemType = "dir"
videos, err := getAllVideos(inputItem.Path)
if err != nil {
return fmt.Errorf("video_path[%d] get videos error: %v", i, err)
}
GlobalConfig.VideoList = append(GlobalConfig.VideoList, videos...)
} else {
inputItem.ItemType = "file"
if !utils.IsSupportedVideo(inputItem.Path) {
return fmt.Errorf("video_path[%d] is not supported", i)
}
GlobalConfig.VideoList = append(GlobalConfig.VideoList, inputItem)
}
GlobalConfig.InputItems = append(GlobalConfig.InputItems, inputItem)
}
return nil return nil
} }

View File

@ -47,7 +47,6 @@ func input() {
GlobalStreamer.Prev() GlobalStreamer.Prev()
case "quit": case "quit":
GlobalStreamer.Close() GlobalStreamer.Close()
os.Exit(0)
case "current": case "current":
fmt.Println(GlobalStreamer.GetCurrentVideoPath()) fmt.Println(GlobalStreamer.GetCurrentVideoPath())
} }

View File

@ -52,6 +52,7 @@ func NewServer(addr string, dealInputFunc InputFunc) {
} }
func (s *Server) Run() { func (s *Server) Run() {
gin.SetMode(gin.ReleaseMode)
router := gin.New() router := gin.New()
tpl, err := template.ParseFS(staticFiles, "static/*") tpl, err := template.ParseFS(staticFiles, "static/*")
if err != nil { if err != nil {

View File

@ -377,6 +377,7 @@
ws.onopen = function () { ws.onopen = function () {
console.log("Connected to WebSocket"); console.log("Connected to WebSocket");
setStoredToken(document.getElementById("token-input").value);
document.getElementById("token-screen").style.display = "none"; document.getElementById("token-screen").style.display = "none";
document.querySelector(".container-fluid").style.display = "flex"; document.querySelector(".container-fluid").style.display = "flex";
document.getElementById("status").textContent = document.getElementById("status").textContent =
@ -387,7 +388,7 @@
ws.onmessage = function (evt) { ws.onmessage = function (evt) {
let obj = JSON.parse(evt.data); let obj = JSON.parse(evt.data);
messagesArea.value = obj.output; messagesArea.value = obj.output;
messagesArea.scrollTop = messagesArea.scrollHeight; // messagesArea.scrollTop = messagesArea.scrollHeight;
document.querySelector("#current-video>span").innerHTML = document.querySelector("#current-video>span").innerHTML =
obj.currentVideoPath; obj.currentVideoPath;
const listContainer = document.querySelector( const listContainer = document.querySelector(
@ -400,6 +401,7 @@
}; };
ws.onerror = function () { ws.onerror = function () {
localStorage.removeItem("streaming_token");
document.getElementById("token-error").style.display = "block"; document.getElementById("token-error").style.display = "block";
}; };
@ -412,8 +414,22 @@
}; };
} }
function getStoredToken() {
return localStorage.getItem("streaming_token");
}
function setStoredToken(token) {
localStorage.setItem("streaming_token", token);
}
function validateToken() { function validateToken() {
connectWebSocket(); const tokenInput = document.getElementById("token-input");
const token = tokenInput.value || getStoredToken();
if (token) {
tokenInput.value = token;
connectWebSocket();
}
} }
const messagesArea = document.getElementById("messages"); const messagesArea = document.getElementById("messages");

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"live-streamer/config" "live-streamer/config"
"log" "log"
"os"
"os/exec" "os/exec"
"strings" "strings"
"sync" "sync"
@ -47,11 +48,11 @@ func NewStreamer(videoList []config.InputItem) *Streamer {
func (s *Streamer) start() { func (s *Streamer) start() {
s.playStateMu.Lock() s.playStateMu.Lock()
s.playState.ctx, s.playState.cancel = context.WithCancel(context.Background()) s.playState.ctx, s.playState.cancel = context.WithCancel(context.Background())
cancel := s.playState.cancel
currentVideo := s.videoList[s.playState.currentVideoIndex] currentVideo := s.videoList[s.playState.currentVideoIndex]
videoPath := currentVideo.Path videoPath := currentVideo.Path
s.playState.cmd = exec.CommandContext(s.playState.ctx, "ffmpeg", s.buildFFmpegArgs(currentVideo)...) s.playState.cmd = exec.CommandContext(s.playState.ctx, "ffmpeg", s.buildFFmpegArgs(currentVideo)...)
cmd := s.playState.cmd cmd := s.playState.cmd
ctx := s.playState.ctx
s.playStateMu.Unlock() s.playStateMu.Unlock()
s.writeOutput(fmt.Sprintln("start stream: ", videoPath)) s.writeOutput(fmt.Sprintln("start stream: ", videoPath))
@ -71,7 +72,9 @@ func (s *Streamer) start() {
go s.log(reader) go s.log(reader)
<-ctx.Done() _ = cmd.Wait()
cancel()
s.writeOutput(fmt.Sprintf("stop stream: %s\n", videoPath)) s.writeOutput(fmt.Sprintf("stop stream: %s\n", videoPath))
s.playStateMu.Lock() s.playStateMu.Lock()
@ -102,33 +105,24 @@ func (s *Streamer) Stream() {
func (s *Streamer) Stop() { func (s *Streamer) Stop() {
s.playStateMu.Lock() s.playStateMu.Lock()
cancel := s.playState.cancel cancel := s.playState.cancel
cmd := s.playState.cmd
s.playState.cancel = nil s.playState.cancel = nil
cmd := s.playState.cmd
s.playState.cmd = nil s.playState.cmd = nil
ctx := s.playState.ctx
s.playStateMu.Unlock() s.playStateMu.Unlock()
if cancel == nil || cmd == nil { if cancel == nil || cmd == nil {
return return
} }
stopped := make(chan error, 1)
go func() {
if cmd.Process != nil {
stopped <- cmd.Wait()
} else {
stopped <- nil
}
}()
cancel() cancel()
if cmd.Process != nil { if cmd.Process != nil {
select { select {
case <-stopped: case <-ctx.Done():
case <-time.After(3 * time.Second): case <-time.After(3 * time.Second):
_ = cmd.Process.Kill() _ = cmd.Process.Kill()
} }
close(stopped)
} }
} }
@ -285,6 +279,7 @@ func (s *Streamer) GetOutput() string {
func (s *Streamer) Close() { func (s *Streamer) Close() {
s.Stop() s.Stop()
os.Exit(0)
} }
func (s *Streamer) buildFFmpegArgs(videoItem config.InputItem) []string { func (s *Streamer) buildFFmpegArgs(videoItem config.InputItem) []string {
@ -295,13 +290,12 @@ func (s *Streamer) buildFFmpegArgs(videoItem config.InputItem) []string {
args = append(args, "-ss", videoItem.Start) args = append(args, "-ss", videoItem.Start)
} }
args = append(args, "-i", videoPath)
if videoItem.End != "" { if videoItem.End != "" {
args = append(args, "-to", videoItem.End) args = append(args, "-to", videoItem.End)
} }
args = append(args, args = append(args,
"-i", videoPath,
"-c:v", config.GlobalConfig.Play.VideoCodec, "-c:v", config.GlobalConfig.Play.VideoCodec,
"-preset", config.GlobalConfig.Play.Preset, "-preset", config.GlobalConfig.Play.Preset,
"-crf", fmt.Sprintf("%d", config.GlobalConfig.Play.CRF), "-crf", fmt.Sprintf("%d", config.GlobalConfig.Play.CRF),
@ -313,7 +307,6 @@ func (s *Streamer) buildFFmpegArgs(videoItem config.InputItem) []string {
"-b:a", config.GlobalConfig.Play.AudioBitrate, "-b:a", config.GlobalConfig.Play.AudioBitrate,
"-ar", fmt.Sprintf("%d", config.GlobalConfig.Play.AudioSampleRate), "-ar", fmt.Sprintf("%d", config.GlobalConfig.Play.AudioSampleRate),
"-f", config.GlobalConfig.Play.OutputFormat, "-f", config.GlobalConfig.Play.OutputFormat,
"-stats", "-loglevel", "info",
) )
if config.GlobalConfig.Play.CustomArgs != "" { if config.GlobalConfig.Play.CustomArgs != "" {
@ -323,7 +316,7 @@ func (s *Streamer) buildFFmpegArgs(videoItem config.InputItem) []string {
args = append(args, fmt.Sprintf("%s/%s", config.GlobalConfig.Output.RTMPServer, config.GlobalConfig.Output.StreamKey)) args = append(args, fmt.Sprintf("%s/%s", config.GlobalConfig.Output.RTMPServer, config.GlobalConfig.Output.StreamKey))
// logger.GlobalLogger.Println("ffmpeg args: ", args) log.Println("ffmpeg args: ", args)
return args return args
} }

View File

@ -2,7 +2,6 @@ package websocket
import ( import (
"live-streamer/streamer" "live-streamer/streamer"
"os"
) )
type RequestType string type RequestType string
@ -32,6 +31,5 @@ func RequestHandler(reqType RequestType) {
streamer.GlobalStreamer.Prev() streamer.GlobalStreamer.Prev()
case TypeQuit: case TypeQuit:
streamer.GlobalStreamer.Close() streamer.GlobalStreamer.Close()
os.Exit(0)
} }
} }