2023-06-13 00:07:14 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2024-02-23 23:35:12 +02:00
|
|
|
"fmt"
|
2023-06-13 00:07:14 +03:00
|
|
|
"log"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
|
|
|
"strings"
|
2024-02-23 23:35:12 +02:00
|
|
|
|
|
|
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
2023-06-13 00:07:14 +03:00
|
|
|
)
|
|
|
|
|
2024-02-25 00:08:06 +02:00
|
|
|
const OUTFILE = "stream.m3u8"
|
2023-06-13 00:07:14 +03:00
|
|
|
|
|
|
|
type ffmpegProcess struct {
|
2024-02-23 23:35:12 +02:00
|
|
|
cancel func() error
|
2023-06-13 00:07:14 +03:00
|
|
|
cmd *exec.Cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *ffmpegProcess) Stop() {
|
|
|
|
c := f.cancel
|
|
|
|
if c != nil {
|
|
|
|
c()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func playffmpeg(loop bool, input string, output string, WhenVideoEnds func(forcequit bool)) (f ffmpegProcess) {
|
2024-02-25 17:26:48 +02:00
|
|
|
subtitle_stream_index, err := SelectPreferredSubtitle(input)
|
2024-02-23 23:35:12 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
inputArgs := ffmpeg.KwArgs{
|
|
|
|
"re": "",
|
|
|
|
}
|
|
|
|
|
|
|
|
outputArgs := ffmpeg.KwArgs{
|
2024-02-25 00:08:06 +02:00
|
|
|
"c:v": "libx264",
|
|
|
|
"c:a": "aac",
|
|
|
|
"b:a": "128k",
|
|
|
|
"ac": "2",
|
2024-02-25 02:54:23 +02:00
|
|
|
"pix_fmt": "yuv420p",
|
2024-02-25 00:08:06 +02:00
|
|
|
"preset": "ultrafast",
|
|
|
|
"hls_flags": "delete_segments+append_list+omit_endlist",
|
|
|
|
"hls_time": "1",
|
|
|
|
"f": "hls",
|
2024-02-23 23:35:12 +02:00
|
|
|
}
|
|
|
|
|
2024-02-25 17:26:48 +02:00
|
|
|
if subtitle_stream_index != nil {
|
2024-02-23 23:35:12 +02:00
|
|
|
// Makes file with subtitles not spit them into .vtt file
|
|
|
|
inputArgs["sn"] = ""
|
|
|
|
}
|
2023-06-13 00:07:14 +03:00
|
|
|
|
|
|
|
if loop {
|
2024-02-23 23:35:12 +02:00
|
|
|
inputArgs["stream_loop"] = "-1"
|
|
|
|
}
|
|
|
|
|
2024-02-25 17:26:48 +02:00
|
|
|
if subtitle_stream_index != nil {
|
2024-02-23 23:35:12 +02:00
|
|
|
// Burns subtitles into video
|
2024-02-25 17:26:48 +02:00
|
|
|
outputArgs["vf"] = fmt.Sprintf("subtitles=%s:si=%d", input, *subtitle_stream_index)
|
2023-06-13 00:07:14 +03:00
|
|
|
}
|
2024-02-23 23:35:12 +02:00
|
|
|
|
|
|
|
ffmpegCmd := ffmpeg.Input(input, inputArgs).Output(output, outputArgs).Compile()
|
|
|
|
f.cancel = ffmpegCmd.Cancel
|
|
|
|
|
2023-06-13 00:07:14 +03:00
|
|
|
go func() {
|
2024-02-23 23:35:12 +02:00
|
|
|
out, err := ffmpegCmd.CombinedOutput()
|
2023-06-13 00:07:14 +03:00
|
|
|
|
2024-02-25 21:08:04 +02:00
|
|
|
var was_force_quit bool
|
2023-06-13 00:07:14 +03:00
|
|
|
if err != nil && strings.Contains(err.Error(), "signal: killed") {
|
2024-02-25 21:08:04 +02:00
|
|
|
was_force_quit = true
|
|
|
|
} else if err != nil {
|
|
|
|
log.Println(err.Error()+":", string(out))
|
2023-06-13 00:07:14 +03:00
|
|
|
}
|
|
|
|
|
2024-02-23 23:35:12 +02:00
|
|
|
log.Println("Playback ended")
|
2023-06-13 00:07:14 +03:00
|
|
|
if WhenVideoEnds != nil {
|
2024-02-25 21:08:04 +02:00
|
|
|
WhenVideoEnds(was_force_quit)
|
2023-06-13 00:07:14 +03:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *State) playFile(fileName string) (err error) {
|
|
|
|
s.stopPlaying()
|
|
|
|
log.Println("Playing", fileName)
|
2024-02-25 21:08:04 +02:00
|
|
|
s.VideoProcess = playffmpeg(false, fileName, path.Join(s.StreamCacheDir, OUTFILE), func(forcequit bool) {
|
2023-06-13 00:07:14 +03:00
|
|
|
if err := s.playFallback(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
s.PlayState = PlayingVideo
|
|
|
|
s.SetNowPlaying(path.Base(fileName))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *State) playFallback() (err error) {
|
|
|
|
s.stopPlaying()
|
|
|
|
log.Println("Playing fallback")
|
2024-02-25 21:08:04 +02:00
|
|
|
s.VideoProcess = playffmpeg(true, s.FallbackFile, path.Join(s.StreamCacheDir, OUTFILE), nil)
|
2023-06-13 00:07:14 +03:00
|
|
|
s.PlayState = PlayingFallback
|
|
|
|
s.SetNowPlaying("")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *State) PlayRandom(autoplay bool) {
|
|
|
|
s.stopPlaying()
|
|
|
|
|
|
|
|
fileName, err := s.getRandomMediaFile()
|
|
|
|
if err != nil {
|
2024-02-23 23:35:12 +02:00
|
|
|
log.Println(err)
|
2023-06-13 00:07:14 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-23 23:35:12 +02:00
|
|
|
log.Println("Playing random file", fileName)
|
2024-02-25 21:08:04 +02:00
|
|
|
s.VideoProcess = playffmpeg(false, fileName, path.Join(s.StreamCacheDir, OUTFILE), func(forcequit bool) {
|
2023-06-13 00:07:14 +03:00
|
|
|
if forcequit {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if autoplay {
|
|
|
|
s.PlayRandom(autoplay)
|
|
|
|
} else {
|
2024-02-24 16:54:27 +02:00
|
|
|
s.playFallback()
|
2023-06-13 00:07:14 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
s.PlayState = PlayingVideo
|
|
|
|
|
|
|
|
s.SetNowPlaying(path.Base(fileName))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *State) stopPlaying() {
|
|
|
|
s.VideoProcess.Stop()
|
|
|
|
s.PlayState = NotPlaying
|
|
|
|
|
|
|
|
switch s.PlayState {
|
|
|
|
case PlayingVideo:
|
|
|
|
log.Println("Stopping currently playing video")
|
|
|
|
case PlayingFallback:
|
|
|
|
log.Println("Stopping currently playing fallback video")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *State) SetNowPlaying(p string) {
|
|
|
|
s.CurrentFile = p
|
|
|
|
|
|
|
|
s.Websocket.Broadcast(WebsocketMessage{
|
|
|
|
Type: WebsocketMessageTypeCurrentlyPlaying,
|
|
|
|
Message: s.NowPlaying(),
|
|
|
|
}.Encode())
|
|
|
|
}
|