Files
seanime-docker/seanime-2.9.10/internal/torrentstream/handler.go
2025-09-20 14:08:38 +01:00

83 lines
2.6 KiB
Go

package torrentstream
import (
"net/http"
"seanime/internal/util/torrentutil"
"strconv"
"time"
"github.com/anacrolix/torrent"
)
var _ = http.Handler(&handler{})
type (
// handler serves the torrent stream
handler struct {
repository *Repository
}
)
func newHandler(repository *Repository) *handler {
return &handler{
repository: repository,
}
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.repository.logger.Trace().Str("range", r.Header.Get("Range")).Msg("torrentstream: Stream endpoint hit")
if h.repository.client.currentFile.IsAbsent() || h.repository.client.currentTorrent.IsAbsent() {
h.repository.logger.Error().Msg("torrentstream: No torrent to stream")
http.Error(w, "No torrent to stream", http.StatusNotFound)
return
}
if r.Method == http.MethodHead {
r.Response.Header.Set("Content-Type", "video/mp4")
r.Response.Header.Set("Content-Length", strconv.Itoa(int(h.repository.client.currentFile.MustGet().Length())))
r.Response.Header.Set("Content-Disposition", "inline; filename="+h.repository.client.currentFile.MustGet().DisplayPath())
r.Response.Header.Set("Accept-Ranges", "bytes")
r.Response.Header.Set("Cache-Control", "no-cache")
r.Response.Header.Set("Pragma", "no-cache")
r.Response.Header.Set("Expires", "0")
r.Response.Header.Set("X-Content-Type-Options", "nosniff")
// No content, just headers
w.WriteHeader(http.StatusOK)
return
}
file := h.repository.client.currentFile.MustGet()
h.repository.logger.Trace().Str("file", file.DisplayPath()).Msg("torrentstream: New reader")
tr := file.NewReader()
defer func(tr torrent.Reader) {
h.repository.logger.Trace().Msg("torrentstream: Closing reader")
_ = tr.Close()
}(tr)
tr.SetResponsive()
// Read ahead 5MB for better streaming performance
// DEVNOTE: Not sure if dynamic prioritization overwrites this but whatever
tr.SetReadahead(5 * 1024 * 1024)
// If this is a range request for a later part of the file, prioritize those pieces
rangeHeader := r.Header.Get("Range")
if rangeHeader != "" && h.repository.client.currentTorrent.IsPresent() {
t := h.repository.client.currentTorrent.MustGet()
// Attempt to prioritize the pieces requested in the range
torrentutil.PrioritizeRangeRequestPieces(rangeHeader, t, file, h.repository.logger)
}
h.repository.logger.Trace().Str("file", file.DisplayPath()).Msg("torrentstream: Serving file content")
w.Header().Set("Content-Type", "video/mp4")
http.ServeContent(
w,
r,
file.DisplayPath(),
time.Now(),
tr,
)
h.repository.logger.Trace().Msg("torrentstream: File content served")
}