// Package engine — stream_source_debrid.go implements a FileProvider that // serves a /stream session straight from a debrid HTTPS direct URL (hueco #2 / // 2a). No local file is involved: the browser's Range requests are translated // into ranged GETs against the debrid link, so a cache-confirmed torrent plays // instantly without ever hitting the swarm or touching disk. // // The web resolves the DirectURL server-side (resolveDebridDirectUrl) and only // sends it when the hash is debrid-cached and the container is browser-native // (mp4/m4v), so this provider stays a pure pass-through — same role as // diskFileProvider/torrentFileProvider, just backed by HTTP Range instead of a // file handle. http.ServeContent drives it exactly like a local file: it Seeks // to discover size + the range start (no network), then Reads (lazy GET). package engine import ( "context" "errors" "fmt" "io" "log" "net/http" "path" "strings" "sync" "time" ) // debridHTTPClient is used for ranged debrid reads. Separate from the download // httpClient so a slow streaming read can't starve a concurrent download's // header-timeout budget, and vice versa. No overall timeout: a paused player // can legitimately hold a body open for minutes; ResponseHeaderTimeout bounds // the part that actually matters (a hung server before first byte). var debridHTTPClient = &http.Client{ Transport: &http.Transport{ ResponseHeaderTimeout: 30 * time.Second, // debrid CDNs are remote; a generous idle-conn pool avoids a fresh TLS // handshake on every seek-driven reopen. MaxIdleConns: 4, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 15 * time.Second, }, } // NewDebridFileProvider builds a FileProvider backed by a debrid HTTPS URL. // It performs a single HEAD up front to learn the exact file size (the torrent // size the web knows can differ from the resolved file's size). If the HEAD // fails or omits Content-Length, fallbackSize (from the StreamSession) is used. // Returns an error only when neither a HEAD size nor a fallback is available — // http.ServeContent needs a real size to range-serve, and serving size 0 would // hand the browser an empty file. // refresh, when non-nil, re-resolves a fresh debrid URL for the same content // (hueco #2 / 2c) — called when the current link expires mid-stream. nil keeps // 2a behaviour (an expired link is a hard error, no recovery). func NewDebridFileProvider(ctx context.Context, directURL, fileName string, fallbackSize int64, refresh func(context.Context) (string, error)) (FileProvider, error) { if directURL == "" { return nil, errors.New("debrid provider: empty direct URL") } size := fallbackSize if headSize, ok := debridHeadSize(ctx, directURL); ok { size = headSize } if size <= 0 { return nil, fmt.Errorf("debrid provider: unknown file size (HEAD gave nothing, no fallback)") } // The name drives the served Content-Type (mimeTypeFromExt on FileName). // The web may pass a torrent title with no extension (its file-name // fallback), which would yield application/octet-stream and break