2020-07-23 01:56:08 +00:00
|
|
|
package ffmpeg
|
|
|
|
|
|
|
|
import (
|
2022-04-18 00:50:10 +00:00
|
|
|
"context"
|
2020-07-23 01:56:08 +00:00
|
|
|
"net/http"
|
2023-02-24 03:55:46 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
"github.com/stashapp/stash/pkg/fsutil"
|
2020-07-23 01:56:08 +00:00
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
2023-02-24 03:55:46 +00:00
|
|
|
"github.com/stashapp/stash/pkg/models"
|
2020-07-23 01:56:08 +00:00
|
|
|
)
|
|
|
|
|
2022-04-18 00:50:10 +00:00
|
|
|
const (
|
2023-02-24 03:55:46 +00:00
|
|
|
MimeWebmVideo string = "video/webm"
|
|
|
|
MimeWebmAudio string = "audio/webm"
|
|
|
|
MimeMkvVideo string = "video/x-matroska"
|
|
|
|
MimeMkvAudio string = "audio/x-matroska"
|
|
|
|
MimeMp4Video string = "video/mp4"
|
|
|
|
MimeMp4Audio string = "audio/mp4"
|
2022-04-18 00:50:10 +00:00
|
|
|
)
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
type StreamManager struct {
|
|
|
|
cacheDir string
|
2023-03-10 00:25:55 +00:00
|
|
|
encoder *FFMpeg
|
2024-09-03 06:33:15 +00:00
|
|
|
ffprobe *FFProbe
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
config StreamManagerConfig
|
|
|
|
lockManager *fsutil.ReadLockManager
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
context context.Context
|
|
|
|
cancelFunc context.CancelFunc
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
runningStreams map[string]*runningStream
|
|
|
|
streamsMutex sync.Mutex
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
type StreamManagerConfig interface {
|
|
|
|
GetMaxStreamingTranscodeSize() models.StreamingResolutionEnum
|
2023-02-27 21:15:52 +00:00
|
|
|
GetLiveTranscodeInputArgs() []string
|
|
|
|
GetLiveTranscodeOutputArgs() []string
|
2023-03-10 00:25:55 +00:00
|
|
|
GetTranscodeHardwareAcceleration() bool
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
|
2024-09-03 06:33:15 +00:00
|
|
|
func NewStreamManager(cacheDir string, encoder *FFMpeg, ffprobe *FFProbe, config StreamManagerConfig, lockManager *fsutil.ReadLockManager) *StreamManager {
|
2023-02-24 03:55:46 +00:00
|
|
|
if cacheDir == "" {
|
2023-03-10 01:19:36 +00:00
|
|
|
logger.Warn("cache directory is not set. Live HLS/DASH transcoding will be disabled")
|
2022-04-18 00:50:10 +00:00
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
ret := &StreamManager{
|
|
|
|
cacheDir: cacheDir,
|
|
|
|
encoder: encoder,
|
|
|
|
ffprobe: ffprobe,
|
|
|
|
config: config,
|
|
|
|
lockManager: lockManager,
|
|
|
|
context: ctx,
|
|
|
|
cancelFunc: cancel,
|
|
|
|
runningStreams: make(map[string]*runningStream),
|
2022-04-18 00:50:10 +00:00
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-time.After(monitorInterval):
|
|
|
|
ret.monitorStreams()
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
return ret
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
// Shutdown shuts down the stream manager, killing any running transcoding processes and removing all cached files.
|
|
|
|
func (sm *StreamManager) Shutdown() {
|
|
|
|
sm.cancelFunc()
|
|
|
|
sm.stopAndRemoveAll()
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
type StreamRequestContext struct {
|
|
|
|
context.Context
|
|
|
|
ResponseWriter http.ResponseWriter
|
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
func NewStreamRequestContext(w http.ResponseWriter, r *http.Request) *StreamRequestContext {
|
|
|
|
return &StreamRequestContext{
|
|
|
|
Context: r.Context(),
|
|
|
|
ResponseWriter: w,
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
2023-02-24 03:55:46 +00:00
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
func (c *StreamRequestContext) Cancel() {
|
|
|
|
hj, ok := (c.ResponseWriter).(http.Hijacker)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// hijack and close the connection
|
|
|
|
conn, bw, _ := hj.Hijack()
|
|
|
|
if conn != nil {
|
|
|
|
if bw != nil {
|
|
|
|
// notify end of stream
|
|
|
|
_, err := bw.WriteString("0\r\n")
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("unable to write end of stream: %v", err)
|
|
|
|
}
|
|
|
|
_, err = bw.WriteString("\r\n")
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("unable to write end of stream: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// flush the buffer, but don't wait indefinitely
|
|
|
|
timeout := make(chan struct{}, 1)
|
|
|
|
go func() {
|
|
|
|
_ = bw.Flush()
|
|
|
|
close(timeout)
|
|
|
|
}()
|
|
|
|
|
|
|
|
const waitTime = time.Second
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-timeout:
|
|
|
|
case <-time.After(waitTime):
|
|
|
|
logger.Warnf("unable to flush buffer - closing connection")
|
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
|
2023-02-24 03:55:46 +00:00
|
|
|
conn.Close()
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
|
|
|
}
|