iOS 播放修复:媒体资产接口支持无 cookie 拉流(会话 ID 即凭证)+ 拒绝诊断日志;新增 caddy-djk HTTPS 反代部署文件

This commit is contained in:
yvan
2026-09-18 11:14:56 +08:00
parent b96363d787
commit 67bb1d01c8
7 changed files with 61 additions and 2 deletions
+4
View File
@@ -19,3 +19,7 @@ __pycache__/
*.log
*.zip
*.tar
*.tar.gz
_*.py
deploy/caddy-djk/deploy_nas.py
deploy/caddy-djk/Caddyfile.nas
+14
View File
@@ -0,0 +1,14 @@
{
email yvan@xyvan.cn
}
djk2.xyvan.cn:31513 {
tls {
dns alidns {
access_key_id {$ALI_ID}
access_key_secret {$ALI_SECRET}
}
resolvers 223.5.5.5
}
reverse_proxy 192.168.31.32:8998
}
+6
View File
@@ -0,0 +1,6 @@
FROM caddy:2-builder AS builder
ENV GOPROXY=https://goproxy.cn,direct
RUN xcaddy build --with github.com/caddy-dns/alidns
FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
+7 -1
View File
@@ -259,10 +259,16 @@ func (app *UIApp) handlePlaybackMediaAsset(writer http.ResponseWriter, request *
return
}
run, err := strconv.ParseUint(parts[1], 10, 64)
viewer := contextViewer(request.Context())
app.playbackMu.Lock()
session := app.playbacks[parts[0]]
if err != nil || !viewerOwnsPlayback(request.Context(), session) || session.run != run || session.media == nil {
denied := err != nil || session == nil || session.run != run || session.media == nil
if !denied && viewer != nil {
denied = !viewerOwnsPlayback(request.Context(), session)
}
if denied {
app.playbackMu.Unlock()
app.recordPlaybackAssetDenial(request, "session-unavailable")
writer.WriteHeader(http.StatusGone)
return
}
+11
View File
@@ -94,6 +94,17 @@ func (app *UIApp) registerPlaybackRoutes(mux *http.ServeMux) {
mux.HandleFunc("/api/ui/playback/progress", app.handlePlaybackProgress)
}
// recordPlaybackAssetDenial 记录媒体资产请求被拒的情况,便于诊断
// iOS/WebKit 原生媒体管线的拉流失败(区分会话失效与观看者不匹配)。
func (app *UIApp) recordPlaybackAssetDenial(request *http.Request, reason string) {
agent := request.UserAgent()
if len(agent) > 120 {
agent = agent[:120]
}
app.downloader.recordDiagnostic(diagnosticEvent{Level: "warn", Event: "playback.asset.denied",
HTTPStatus: http.StatusGone, Message: reason + " ua=" + agent})
}
func playbackRequestAllowed(writer http.ResponseWriter, request *http.Request, method string) bool {
writer.Header().Set("Cache-Control", "private, no-store, no-transform")
writer.Header().Set("X-Accel-Buffering", "no")
+7 -1
View File
@@ -95,10 +95,16 @@ func (app *UIApp) handlePlaybackNativeAsset(writer http.ResponseWriter, request
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "播放请求编号无效"})
return
}
viewer := contextViewer(request.Context())
app.playbackMu.Lock()
session := app.playbacks[query.Get("session")]
if !viewerOwnsPlayback(request.Context(), session) || session.run != run || session.native == nil {
denied := session == nil || session.run != run || session.native == nil
if !denied && viewer != nil {
denied = !viewerOwnsPlayback(request.Context(), session)
}
if denied {
app.playbackMu.Unlock()
app.recordPlaybackAssetDenial(request, "stream-unavailable")
writeJSON(writer, http.StatusGone, map[string]string{"error": "此播放流已结束,请重新选择分集"})
return
}
+12
View File
@@ -207,6 +207,12 @@ func viewerOwnsPlayback(ctx context.Context, session *playbackSession) bool {
return viewer != nil && session != nil && session.viewer == viewer && playbackSourceAllowed(ctx, session)
}
// playbackCapabilityPath 判断是否为无需 viewer cookie 的媒体资产路径:
// 这些 URL 中已包含不可猜测的播放会话 ID,可作为独立能力凭证。
func playbackCapabilityPath(path string) bool {
return strings.HasPrefix(path, "/api/ui/playback/media/") || path == "/api/ui/playback/hls/index.m3u8" || path == "/api/ui/playback/hls/segment.ts"
}
func writeViewerError(writer http.ResponseWriter, status int, code, message string) {
writer.Header().Set("Cache-Control", "private, no-store")
writer.Header().Add("Vary", "Cookie")
@@ -234,6 +240,12 @@ func (app *UIApp) withBrowserViewer(next http.Handler) http.Handler {
}
identity := manager.identity(request)
if account.ID == "" && (identity == "" || accountCookie) {
if playbackCapabilityPath(path) {
// AVPlayer 等原生媒体管线可能不携带 cookie;播放会话 ID 本身即能力凭证,
// 由媒体资产接口自行校验会话与运行编号。
next.ServeHTTP(writer, request)
return
}
writeViewerError(writer, http.StatusUnauthorized, "viewer_required", "请刷新页面,恢复此浏览器的独立观看记录")
return
}