fix playback issues

This commit is contained in:
Mukhtar Akere
2025-07-01 16:10:23 +01:00
parent dec7d93272
commit 2c90e518aa

View File

@@ -12,19 +12,24 @@ import (
"github.com/sirrobot01/decypharr/pkg/debrid/store" "github.com/sirrobot01/decypharr/pkg/debrid/store"
) )
var streamingTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
MaxIdleConns: 200,
MaxIdleConnsPerHost: 100,
MaxConnsPerHost: 200,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 60 * time.Second, // give the upstream a minute to send headers
ExpectContinueTimeout: 1 * time.Second,
DisableKeepAlives: true, // close after each request
ForceAttemptHTTP2: false, // dont speak HTTP/2
// this line is what truly blocks HTTP/2:
TLSNextProto: make(map[string]func(string, *tls.Conn) http.RoundTripper),
}
var sharedClient = &http.Client{ var sharedClient = &http.Client{
Transport: &http.Transport{ Transport: streamingTransport,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, Timeout: 0,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 20,
MaxConnsPerHost: 50,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 30 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DisableKeepAlives: false,
},
Timeout: 0,
} }
type streamError struct { type streamError struct {
@@ -192,8 +197,47 @@ func (f *File) streamWithRetry(w http.ResponseWriter, r *http.Request, retryCoun
setVideoResponseHeaders(w, resp, isRangeRequest == 1) setVideoResponseHeaders(w, resp, isRangeRequest == 1)
// Stream with optimized buffering for video return f.streamBuffer(w, resp.Body)
return f.streamVideoOptimized(w, resp.Body) }
func (f *File) streamBuffer(w http.ResponseWriter, src io.Reader) error {
flusher, ok := w.(http.Flusher)
if !ok {
return fmt.Errorf("response does not support flushing")
}
smallBuf := make([]byte, 64*1024) // 64 KB
if n, err := src.Read(smallBuf); n > 0 {
if _, werr := w.Write(smallBuf[:n]); werr != nil {
return werr
}
flusher.Flush()
} else if err != nil && err != io.EOF {
return err
}
buf := make([]byte, 256*1024) // 256 KB
for {
n, readErr := src.Read(buf)
if n > 0 {
if _, writeErr := w.Write(buf[:n]); writeErr != nil {
if isClientDisconnection(writeErr) {
return &streamError{Err: writeErr, StatusCode: 0, IsClientDisconnection: true}
}
return writeErr
}
flusher.Flush()
}
if readErr != nil {
if readErr == io.EOF {
return nil
}
if isClientDisconnection(readErr) {
return &streamError{Err: readErr, StatusCode: 0, IsClientDisconnection: true}
}
return readErr
}
}
} }
func (f *File) handleUpstream(resp *http.Response, retryCount, maxRetries int) (shouldRetry bool, err error) { func (f *File) handleUpstream(resp *http.Response, retryCount, maxRetries int) (shouldRetry bool, err error) {
@@ -319,51 +363,6 @@ func (f *File) handleRangeRequest(upstreamReq *http.Request, r *http.Request, w
return 1 // Valid range request return 1 // Valid range request
} }
func (f *File) streamVideoOptimized(w http.ResponseWriter, src io.Reader) error {
// Use larger buffer for video streaming (better throughput)
buf := make([]byte, 64*1024) // 64KB buffer
// First chunk optimization - send immediately for faster start
n, err := src.Read(buf)
if err != nil && err != io.EOF {
if isClientDisconnection(err) {
return &streamError{Err: err, StatusCode: 0, IsClientDisconnection: true}
}
return &streamError{Err: err, StatusCode: 0}
}
if n > 0 {
// Write first chunk immediately
_, writeErr := w.Write(buf[:n])
if writeErr != nil {
if isClientDisconnection(writeErr) {
return &streamError{Err: writeErr, StatusCode: 0, IsClientDisconnection: true}
}
return &streamError{Err: writeErr, StatusCode: 0}
}
// Flush immediately for faster video start
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
}
if err == io.EOF {
return nil
}
// Continue with optimized copy for remaining data
_, err = io.CopyBuffer(w, src, buf)
if err != nil {
if isClientDisconnection(err) {
return &streamError{Err: err, StatusCode: 0, IsClientDisconnection: true}
}
return &streamError{Err: err, StatusCode: 0}
}
return nil
}
/* /*
These are the methods that implement the os.File interface for the File type. These are the methods that implement the os.File interface for the File type.
Only Stat and ReadDir are used Only Stat and ReadDir are used