fix playback issues
This commit is contained in:
@@ -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, // don’t 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
|
||||||
|
|||||||
Reference in New Issue
Block a user