feat: add HLS manifest cache with TTL-based expiry

This commit is contained in:
2026-07-05 23:08:34 +02:00
parent a0d11e3a7b
commit 8c2562496a
2 changed files with 231 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
package handler
import (
"container/list"
"net/http"
"strings"
"sync"
"time"
)
const (
manifestCacheVODTTL = 45 * time.Second
manifestCacheLiveTTL = 3 * time.Second
manifestCacheMaxEntries = 256
manifestCacheMaxBodySize = 2 << 20
)
type manifestCacheEntry struct {
key string
body []byte
headers http.Header
status int
expiresAt time.Time
}
type manifestCache struct {
mu sync.Mutex
maxEntries int
entries map[string]*list.Element
lru *list.List
}
func newManifestCache(maxEntries int) *manifestCache {
if maxEntries <= 0 {
maxEntries = manifestCacheMaxEntries
}
return &manifestCache{
maxEntries: maxEntries,
entries: make(map[string]*list.Element, maxEntries),
lru: list.New(),
}
}
func manifestCacheKey(targetURL string, referer string) string {
return targetURL + "\x00" + referer
}
func (c *manifestCache) get(key string, now time.Time) (manifestCacheEntry, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.entries[key]
if el == nil {
return manifestCacheEntry{}, false
}
entry, ok := el.Value.(manifestCacheEntry)
if !ok || !now.Before(entry.expiresAt) {
c.remove(el)
return manifestCacheEntry{}, false
}
c.lru.MoveToFront(el)
entry.body = append([]byte(nil), entry.body...)
entry.headers = entry.headers.Clone()
return entry, true
}
func (c *manifestCache) set(key string, status int, headers http.Header, body []byte, now time.Time) bool {
if status < http.StatusOK || status >= http.StatusMultipleChoices || len(body) > manifestCacheMaxBodySize {
return false
}
ttl := manifestCacheVODTTL
bodyText := string(body)
if strings.Contains(bodyText, "#EXTINF") && !strings.Contains(bodyText, "#EXT-X-ENDLIST") {
ttl = manifestCacheLiveTTL
}
entry := manifestCacheEntry{
key: key,
body: append([]byte(nil), body...),
headers: headers.Clone(),
status: status,
expiresAt: now.Add(ttl),
}
c.mu.Lock()
defer c.mu.Unlock()
if el := c.entries[key]; el != nil {
el.Value = entry
c.lru.MoveToFront(el)
return true
}
c.entries[key] = c.lru.PushFront(entry)
for len(c.entries) > c.maxEntries {
back := c.lru.Back()
if back == nil {
break
}
c.remove(back)
}
return true
}
func (c *manifestCache) remove(el *list.Element) {
entry, ok := el.Value.(manifestCacheEntry)
if ok {
delete(c.entries, entry.key)
}
c.lru.Remove(el)
}