Update tracker

This commit is contained in:
Zoe Roux 2024-08-02 21:05:29 +02:00
parent e3fdf0af45
commit 59010797a8
4 changed files with 99 additions and 69 deletions

View File

@ -55,8 +55,12 @@ func (h *Handler) GetMaster(c echo.Context) error {
// This route can take a few seconds to respond since it will way for at least one segment to be // This route can take a few seconds to respond since it will way for at least one segment to be
// available. // available.
// //
// Path: /:path/:quality/index.m3u8 // Path: /:path/:video/:quality/index.m3u8
func (h *Handler) GetVideoIndex(c echo.Context) error { func (h *Handler) GetVideoIndex(c echo.Context) error {
video, err := strconv.ParseInt(c.Param("video"), 10, 32)
if err != nil {
return err
}
quality, err := src.QualityFromString(c.Param("quality")) quality, err := src.QualityFromString(c.Param("quality"))
if err != nil { if err != nil {
return err return err
@ -70,7 +74,7 @@ func (h *Handler) GetVideoIndex(c echo.Context) error {
return err return err
} }
ret, err := h.transcoder.GetVideoIndex(path, quality, client, sha) ret, err := h.transcoder.GetVideoIndex(path, int32(video), quality, client, sha)
if err != nil { if err != nil {
return err return err
} }
@ -109,8 +113,12 @@ func (h *Handler) GetAudioIndex(c echo.Context) error {
// //
// Retrieve a chunk of a transmuxed video. // Retrieve a chunk of a transmuxed video.
// //
// Path: /:path/:quality/segments-:chunk.ts // Path: /:path/:video/:quality/segments-:chunk.ts
func (h *Handler) GetVideoSegment(c echo.Context) error { func (h *Handler) GetVideoSegment(c echo.Context) error {
video, err := strconv.ParseInt(c.Param("video"), 10, 32)
if err != nil {
return err
}
quality, err := src.QualityFromString(c.Param("quality")) quality, err := src.QualityFromString(c.Param("quality"))
if err != nil { if err != nil {
return err return err
@ -128,7 +136,14 @@ func (h *Handler) GetVideoSegment(c echo.Context) error {
return err return err
} }
ret, err := h.transcoder.GetVideoSegment(path, quality, segment, client, sha) ret, err := h.transcoder.GetVideoSegment(
path,
int32(video),
quality,
segment,
client,
sha,
)
if err != nil { if err != nil {
return err return err
} }
@ -285,7 +300,7 @@ func main() {
e.GET("/:path/direct", DirectStream) e.GET("/:path/direct", DirectStream)
e.GET("/:path/master.m3u8", h.GetMaster) e.GET("/:path/master.m3u8", h.GetMaster)
e.GET("/:path/:quality/index.m3u8", h.GetVideoIndex) e.GET("/:path/:video/:quality/index.m3u8", h.GetVideoIndex)
e.GET("/:path/audio/:audio/index.m3u8", h.GetAudioIndex) e.GET("/:path/audio/:audio/index.m3u8", h.GetAudioIndex)
e.GET("/:path/:quality/:chunk", h.GetVideoSegment) e.GET("/:path/:quality/:chunk", h.GetVideoSegment)
e.GET("/:path/audio/:audio/:chunk", h.GetAudioSegment) e.GET("/:path/audio/:audio/:chunk", h.GetAudioSegment)

View File

@ -121,7 +121,7 @@ func toSegmentStr(segments []float64) string {
func (ts *Stream) run(start int32) error { func (ts *Stream) run(start int32) error {
// Start the transcode up to the 100th segment (or less) // Start the transcode up to the 100th segment (or less)
length, is_done := ts.file.Keyframes.Length() length, is_done := ts.keyframes.Length()
end := min(start+100, length) end := min(start+100, length)
// if keyframes analysys is not finished, always have a 1-segment padding // if keyframes analysys is not finished, always have a 1-segment padding
// for the extra segment needed for precise split (look comment before -to flag) // for the extra segment needed for precise split (look comment before -to flag)
@ -151,7 +151,7 @@ func (ts *Stream) run(start int32) error {
log.Printf( log.Printf(
"Starting transcode %d for %s (from %d to %d out of %d segments)", "Starting transcode %d for %s (from %d to %d out of %d segments)",
encoder_id, encoder_id,
ts.file.Path, ts.file.Info.Path,
start, start,
end, end,
length, length,
@ -169,7 +169,7 @@ func (ts *Stream) run(start int32) error {
// the previous segment is played another time. the -segment_times is way more precise so it does not do the same with this one // the previous segment is played another time. the -segment_times is way more precise so it does not do the same with this one
start_segment = start - 1 start_segment = start - 1
if ts.handle.getFlags()&AudioF != 0 { if ts.handle.getFlags()&AudioF != 0 {
start_ref = ts.file.Keyframes.Get(start_segment) start_ref = ts.keyframes.Get(start_segment)
} else { } else {
// the param for the -ss takes the keyframe before the specificed time // the param for the -ss takes the keyframe before the specificed time
// (if the specified time is a keyframe, it either takes that keyframe or the one before) // (if the specified time is a keyframe, it either takes that keyframe or the one before)
@ -178,9 +178,9 @@ func (ts *Stream) run(start int32) error {
// this can't be used with audio since we need to have context before the start-time // this can't be used with audio since we need to have context before the start-time
// without this context, the cut loses a bit of audio (audio gap of ~100ms) // without this context, the cut loses a bit of audio (audio gap of ~100ms)
if start_segment+1 == length { if start_segment+1 == length {
start_ref = (ts.file.Keyframes.Get(start_segment) + float64(ts.file.Info.Duration)) / 2 start_ref = (ts.keyframes.Get(start_segment) + float64(ts.file.Info.Duration)) / 2
} else { } else {
start_ref = (ts.file.Keyframes.Get(start_segment) + ts.file.Keyframes.Get(start_segment+1)) / 2 start_ref = (ts.keyframes.Get(start_segment) + ts.keyframes.Get(start_segment+1)) / 2
} }
} }
} }
@ -188,7 +188,7 @@ func (ts *Stream) run(start int32) error {
if end == length { if end == length {
end_padding = 0 end_padding = 0
} }
segments := ts.file.Keyframes.Slice(start_segment+1, end+end_padding) segments := ts.keyframes.Slice(start_segment+1, end+end_padding)
if len(segments) == 0 { if len(segments) == 0 {
// we can't leave that empty else ffmpeg errors out. // we can't leave that empty else ffmpeg errors out.
segments = []float64{9999999} segments = []float64{9999999}
@ -222,17 +222,17 @@ func (ts *Stream) run(start int32) error {
if end+1 < length { if end+1 < length {
// sometimes, the duration is shorter than expected (only during transcode it seems) // sometimes, the duration is shorter than expected (only during transcode it seems)
// always include more and use the -f segment to split the file where we want // always include more and use the -f segment to split the file where we want
end_ref := ts.file.Keyframes.Get(end + 1) end_ref := ts.keyframes.Get(end + 1)
// it seems that the -to is confused when -ss seek before the given time (because it searches for a keyframe) // it seems that the -to is confused when -ss seek before the given time (because it searches for a keyframe)
// add back the time that would be lost otherwise // add back the time that would be lost otherwise
// this only appens when -to is before -i but having -to after -i gave a bug (not sure, don't remember) // this only appens when -to is before -i but having -to after -i gave a bug (not sure, don't remember)
end_ref += start_ref - ts.file.Keyframes.Get(start_segment) end_ref += start_ref - ts.keyframes.Get(start_segment)
args = append(args, args = append(args,
"-to", fmt.Sprintf("%.6f", end_ref), "-to", fmt.Sprintf("%.6f", end_ref),
) )
} }
args = append(args, args = append(args,
"-i", ts.file.Path, "-i", ts.file.Info.Path,
// this makes behaviors consistent between soft and hardware decodes. // this makes behaviors consistent between soft and hardware decodes.
// this also means that after a -ss 50, the output video will start at 50s // this also means that after a -ss 50, the output video will start at 50s
"-start_at_zero", "-start_at_zero",
@ -258,7 +258,7 @@ func (ts *Stream) run(start int32) error {
// segment_times want durations, not timestamps so we must substract the -ss param // segment_times want durations, not timestamps so we must substract the -ss param
// since we give a greater value to -ss to prevent wrong seeks but -segment_times // since we give a greater value to -ss to prevent wrong seeks but -segment_times
// needs precise segments, we use the keyframe we want to seek to as a reference. // needs precise segments, we use the keyframe we want to seek to as a reference.
return seg - ts.file.Keyframes.Get(start_segment) return seg - ts.keyframes.Get(start_segment)
})), })),
"-segment_list_type", "flat", "-segment_list_type", "flat",
"-segment_list", "pipe:1", "-segment_list", "pipe:1",
@ -361,16 +361,16 @@ func (ts *Stream) GetIndex() (string, error) {
#EXT-X-MEDIA-SEQUENCE:0 #EXT-X-MEDIA-SEQUENCE:0
#EXT-X-INDEPENDENT-SEGMENTS #EXT-X-INDEPENDENT-SEGMENTS
` `
length, is_done := ts.file.Keyframes.Length() length, is_done := ts.keyframes.Length()
for segment := int32(0); segment < length-1; segment++ { for segment := int32(0); segment < length-1; segment++ {
index += fmt.Sprintf("#EXTINF:%.6f\n", ts.file.Keyframes.Get(segment+1)-ts.file.Keyframes.Get(segment)) index += fmt.Sprintf("#EXTINF:%.6f\n", ts.keyframes.Get(segment+1)-ts.keyframes.Get(segment))
index += fmt.Sprintf("segment-%d.ts\n", segment) index += fmt.Sprintf("segment-%d.ts\n", segment)
} }
// do not forget to add the last segment between the last keyframe and the end of the file // do not forget to add the last segment between the last keyframe and the end of the file
// if the keyframes extraction is not done, do not bother to add it, it will be retrived on the next index retrival // if the keyframes extraction is not done, do not bother to add it, it will be retrived on the next index retrival
if is_done { if is_done {
index += fmt.Sprintf("#EXTINF:%.6f\n", float64(ts.file.Info.Duration)-ts.file.Keyframes.Get(length-1)) index += fmt.Sprintf("#EXTINF:%.6f\n", float64(ts.file.Info.Duration)-ts.keyframes.Get(length-1))
index += fmt.Sprintf("segment-%d.ts\n", length-1) index += fmt.Sprintf("segment-%d.ts\n", length-1)
index += `#EXT-X-ENDLIST` index += `#EXT-X-ENDLIST`
} }
@ -442,13 +442,13 @@ func (ts *Stream) prerareNextSegements(segment int32) {
} }
func (ts *Stream) getMinEncoderDistance(segment int32) float64 { func (ts *Stream) getMinEncoderDistance(segment int32) float64 {
time := ts.file.Keyframes.Get(segment) time := ts.keyframes.Get(segment)
distances := Map(ts.heads, func(head Head, _ int) float64 { distances := Map(ts.heads, func(head Head, _ int) float64 {
// ignore killed heads or heads after the current time // ignore killed heads or heads after the current time
if head.segment < 0 || ts.file.Keyframes.Get(head.segment) > time || segment >= head.end { if head.segment < 0 || ts.keyframes.Get(head.segment) > time || segment >= head.end {
return math.Inf(1) return math.Inf(1)
} }
return time - ts.file.Keyframes.Get(head.segment) return time - ts.keyframes.Get(head.segment)
}) })
if len(distances) == 0 { if len(distances) == 0 {
return math.Inf(1) return math.Inf(1)

View File

@ -6,11 +6,12 @@ import (
) )
type ClientInfo struct { type ClientInfo struct {
client string client string
path string path string
quality *Quality video *VideoKey
audio int32 audio int32
head int32 vhead int32
ahead int32
} }
type Tracker struct { type Tracker struct {
@ -56,14 +57,17 @@ func (t *Tracker) start() {
old, ok := t.clients[info.client] old, ok := t.clients[info.client]
// First fixup the info. Most routes ruturn partial infos // First fixup the info. Most routes ruturn partial infos
if ok && old.path == info.path { if ok && old.path == info.path {
if info.quality == nil { if info.video == nil {
info.quality = old.quality info.video = old.video
} }
if info.audio == -1 { if info.audio == -1 {
info.audio = old.audio info.audio = old.audio
} }
if info.head == -1 { if info.vhead == -1 {
info.head = old.head info.vhead = old.vhead
}
if info.ahead == -1 {
info.ahead = old.ahead
} }
} }
@ -76,11 +80,14 @@ func (t *Tracker) start() {
if old.audio != info.audio && old.audio != -1 { if old.audio != info.audio && old.audio != -1 {
t.KillAudioIfDead(old.path, old.audio) t.KillAudioIfDead(old.path, old.audio)
} }
if old.quality != info.quality && old.quality != nil { if old.video != info.video && old.video != nil {
t.KillQualityIfDead(old.path, *old.quality) t.KillVideoIfDead(old.path, *old.video)
} }
if old.head != -1 && Abs(info.head-old.head) > 100 { if old.vhead != -1 && Abs(info.vhead-old.vhead) > 100 {
t.KillOrphanedHeads(old.path, old.quality, old.audio) t.KillOrphanedHeads(old.path, old.video, -1)
}
if old.ahead != -1 && Abs(info.ahead-old.ahead) > 100 {
t.KillOrphanedHeads(old.path, nil, old.audio)
} }
} else if ok { } else if ok {
t.KillStreamIfDead(old.path) t.KillStreamIfDead(old.path)
@ -100,9 +107,9 @@ func (t *Tracker) start() {
if !t.KillStreamIfDead(info.path) { if !t.KillStreamIfDead(info.path) {
audio_cleanup := info.audio != -1 && t.KillAudioIfDead(info.path, info.audio) audio_cleanup := info.audio != -1 && t.KillAudioIfDead(info.path, info.audio)
video_cleanup := info.quality != nil && t.KillQualityIfDead(info.path, *info.quality) video_cleanup := info.video != nil && t.KillVideoIfDead(info.path, *info.video)
if !audio_cleanup || !video_cleanup { if !audio_cleanup || !video_cleanup {
t.KillOrphanedHeads(info.path, info.quality, info.audio) t.KillOrphanedHeads(info.path, info.video, info.audio)
} }
} }
} }
@ -163,19 +170,19 @@ func (t *Tracker) KillAudioIfDead(path string, audio int32) bool {
return true return true
} }
func (t *Tracker) KillQualityIfDead(path string, quality Quality) bool { func (t *Tracker) KillVideoIfDead(path string, video VideoKey) bool {
for _, stream := range t.clients { for _, stream := range t.clients {
if stream.path == path && stream.quality != nil && *stream.quality == quality { if stream.path == path && stream.video != nil && *stream.video == video {
return false return false
} }
} }
log.Printf("Nobody is watching quality %s of %s. Killing it", quality, path) log.Printf("Nobody is watching %s video %d quality %s. Killing it", path, video.idx, video.quality)
stream, ok := t.transcoder.streams.Get(path) stream, ok := t.transcoder.streams.Get(path)
if !ok { if !ok {
return false return false
} }
vstream, vok := stream.videos.Get(quality) vstream, vok := stream.videos.Get(video)
if !vok { if !vok {
return false return false
} }
@ -183,27 +190,27 @@ func (t *Tracker) KillQualityIfDead(path string, quality Quality) bool {
return true return true
} }
func (t *Tracker) KillOrphanedHeads(path string, quality *Quality, audio int32) { func (t *Tracker) KillOrphanedHeads(path string, video *VideoKey, audio int32) {
stream, ok := t.transcoder.streams.Get(path) stream, ok := t.transcoder.streams.Get(path)
if !ok { if !ok {
return return
} }
if quality != nil { if video != nil {
vstream, vok := stream.videos.Get(*quality) vstream, vok := stream.videos.Get(*video)
if vok { if vok {
t.killOrphanedeheads(&vstream.Stream) t.killOrphanedeheads(&vstream.Stream, true)
} }
} }
if audio != -1 { if audio != -1 {
astream, aok := stream.audios.Get(audio) astream, aok := stream.audios.Get(audio)
if aok { if aok {
t.killOrphanedeheads(&astream.Stream) t.killOrphanedeheads(&astream.Stream, false)
} }
} }
} }
func (t *Tracker) killOrphanedeheads(stream *Stream) { func (t *Tracker) killOrphanedeheads(stream *Stream, is_video bool) {
stream.lock.Lock() stream.lock.Lock()
defer stream.lock.Unlock() defer stream.lock.Unlock()
@ -214,13 +221,14 @@ func (t *Tracker) killOrphanedeheads(stream *Stream) {
distance := int32(99999) distance := int32(99999)
for _, info := range t.clients { for _, info := range t.clients {
if info.head == -1 { ihead := info.vhead
continue if is_video {
ihead = info.ahead
} }
distance = min(Abs(info.head-head.segment), distance) distance = min(Abs(ihead-head.segment), distance)
} }
if distance > 20 { if distance > 20 {
log.Printf("Killing orphaned head %s %d", stream.file.Path, encoder_id) log.Printf("Killing orphaned head %s %d", stream.file.Info.Path, encoder_id)
stream.KillHead(encoder_id) stream.KillHead(encoder_id)
} }
} }

View File

@ -53,17 +53,19 @@ func (t *Transcoder) GetMaster(path string, client string, sha string) (string,
return "", err return "", err
} }
t.clientChan <- ClientInfo{ t.clientChan <- ClientInfo{
client: client, client: client,
path: path, path: path,
quality: nil, video: nil,
audio: -1, audio: -1,
head: -1, vhead: -1,
ahead: -1,
} }
return stream.GetMaster(), nil return stream.GetMaster(), nil
} }
func (t *Transcoder) GetVideoIndex( func (t *Transcoder) GetVideoIndex(
path string, path string,
video int32,
quality Quality, quality Quality,
client string, client string,
sha string, sha string,
@ -73,13 +75,14 @@ func (t *Transcoder) GetVideoIndex(
return "", err return "", err
} }
t.clientChan <- ClientInfo{ t.clientChan <- ClientInfo{
client: client, client: client,
path: path, path: path,
quality: &quality, video: &VideoKey{video, quality},
audio: -1, audio: -1,
head: -1, vhead: -1,
ahead: -1,
} }
return stream.GetVideoIndex(quality) return stream.GetVideoIndex(video, quality)
} }
func (t *Transcoder) GetAudioIndex( func (t *Transcoder) GetAudioIndex(
@ -96,13 +99,15 @@ func (t *Transcoder) GetAudioIndex(
client: client, client: client,
path: path, path: path,
audio: audio, audio: audio,
head: -1, vhead: -1,
ahead: -1,
} }
return stream.GetAudioIndex(audio) return stream.GetAudioIndex(audio)
} }
func (t *Transcoder) GetVideoSegment( func (t *Transcoder) GetVideoSegment(
path string, path string,
video int32,
quality Quality, quality Quality,
segment int32, segment int32,
client string, client string,
@ -113,13 +118,14 @@ func (t *Transcoder) GetVideoSegment(
return "", err return "", err
} }
t.clientChan <- ClientInfo{ t.clientChan <- ClientInfo{
client: client, client: client,
path: path, path: path,
quality: &quality, video: &VideoKey{video, quality},
audio: -1, vhead: segment,
head: segment, audio: -1,
ahead: -1,
} }
return stream.GetVideoSegment(quality, segment) return stream.GetVideoSegment(video, quality, segment)
} }
func (t *Transcoder) GetAudioSegment( func (t *Transcoder) GetAudioSegment(
@ -137,7 +143,8 @@ func (t *Transcoder) GetAudioSegment(
client: client, client: client,
path: path, path: path,
audio: audio, audio: audio,
head: segment, ahead: segment,
vhead: -1,
} }
return stream.GetAudioSegment(audio, segment) return stream.GetAudioSegment(audio, segment)
} }