同步上游 0921 版 zip(新增 emby 播放等 20 处变更)

This commit is contained in:
yvan
2026-09-17 19:10:04 +08:00
parent 07dc0f1059
commit 10baea5a87
22 changed files with 731 additions and 274 deletions
+22 -69
View File
@@ -242,58 +242,16 @@ func (app *UIApp) handleEmbyLegacyStream(writer http.ResponseWriter, request *ht
writer.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
return
}
sessionID := randomHex(24)
if sessionID == "" {
writeJSON(writer, http.StatusInternalServerError, map[string]string{"error": "无法创建播放会话"})
return
}
ctx, cancel := context.WithTimeout(request.Context(), 90*time.Second)
defer cancel()
app.playbackMu.Lock()
if len(app.playbacks) >= app.mediaResources().snapshot().MaxSessions {
app.playbackMu.Unlock()
writeJSON(writer, http.StatusTooManyRequests, map[string]string{"error": "同时播放数量已达上限,请稍后再试"})
return
}
if app.playbacks == nil {
app.playbacks = make(map[string]*playbackSession)
}
session := &playbackSession{accountID: request.URL.Query().Get("account"), dramaID: id, id: sessionID, run: 1, state: "opening", expires: time.Now().Add(playbackIdleTimeout), cancel: cancel}
session.timer = time.AfterFunc(playbackIdleTimeout, func() { app.expirePlayback(sessionID) })
app.playbacks[sessionID] = session
app.playbackMu.Unlock()
ready := false
defer func() {
if !ready {
app.closePlayback(sessionID)
}
}()
task, downloadID, err := app.embyTask(ctx, id, chapter)
if err != nil {
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": "解析 Emby 分集失败:" + app.redactError(err)})
return
}
cache := newPlaybackNative(app, context.Background(), task, downloadID, 0, 0, false)
app.playbackMu.Lock()
if app.playbacks[sessionID] != session || ctx.Err() != nil {
app.playbackMu.Unlock()
cache.Close()
return
}
session.tasks = []Task{task}
session.native = cache
session.cancel = cache.Close
app.playbackMu.Unlock()
cache.start()
if _, err = cache.segment(ctx, 0); err != nil {
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": "准备 Emby 播放失败:" + app.redactError(err)})
return
}
_, duration, _ := cache.metadata()
if duration <= 0 || duration > 24*60*60 || math.IsNaN(duration) || math.IsInf(duration, 0) {
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": "未取得有效播放时长"})
session := app.acquireEmbyPlayback(writer, request, id, chapter, "", true)
if session == nil {
return
}
defer app.releaseEmbyPlayback(session)
app.serveEmbyPlaylist(writer, request, session)
}
func (app *UIApp) serveEmbyPlaylist(writer http.ResponseWriter, request *http.Request, session *playbackSession) {
id, chapter, sessionID, duration := session.dramaID, session.tasks[0].Chapter.ID, session.id, session.duration
var playlist strings.Builder
fmt.Fprintf(&playlist, "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:%d\n#EXT-X-MEDIA-SEQUENCE:0\n#EXT-X-PLAYLIST-TYPE:VOD\n#EXT-X-INDEPENDENT-SEGMENTS\n", playbackNativeSegmentSeconds)
for part, count := 0, int(math.Ceil(duration/playbackNativeSegmentSeconds)); part < count; part++ {
@@ -304,17 +262,6 @@ func (app *UIApp) handleEmbyLegacyStream(writer http.ResponseWriter, request *ht
fmt.Fprintf(&playlist, "#EXTINF:%.6f,\nsegment.ts?%s\n", math.Min(playbackNativeSegmentSeconds, duration-float64(part*playbackNativeSegmentSeconds)), query.Encode())
}
playlist.WriteString("#EXT-X-ENDLIST\n")
app.playbackMu.Lock()
if app.playbacks[sessionID] == session {
session.state = "streaming"
session.duration = duration
app.touchPlaybackLocked(session)
ready = true
}
app.playbackMu.Unlock()
if !ready {
return
}
writer.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
http.ServeContent(writer, request, "index.m3u8", time.Time{}, strings.NewReader(playlist.String()))
}
@@ -330,23 +277,29 @@ func (app *UIApp) handleEmbySegment(writer http.ResponseWriter, request *http.Re
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "分片编号无效"})
return
}
app.playbackMu.Lock()
session := app.playbacks[query.Get("session")]
if session == nil || session.native == nil || len(session.tasks) != 1 || session.tasks[0].DramaID != id || session.tasks[0].Chapter.ID != chapter {
app.playbackMu.Unlock()
writeJSON(writer, http.StatusGone, map[string]string{"error": "播放已过期,请重新播放"})
if query.Get("session") == "" {
app.writeEmbyPlaybackError(writer, request, http.StatusGone, "播放已过期,请重新播放")
return
}
session := app.acquireEmbyPlayback(writer, request, id, chapter, query.Get("session"), true)
if session == nil {
return
}
defer app.releaseEmbyPlayback(session)
cache := session.native
app.touchPlaybackLocked(session)
app.playbackMu.Unlock()
if cache == nil {
app.writeEmbyPlaybackError(writer, request, http.StatusGone, "播放格式已变化,请重新播放")
return
}
ctx, cancel := context.WithTimeout(request.Context(), 90*time.Second)
defer cancel()
body, err := cache.segment(ctx, part)
if err != nil {
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": "读取 Emby 分片失败:" + app.redactError(err)})
app.writeEmbyPlaybackError(writer, request, http.StatusBadGateway, "读取 Emby 分片失败:"+app.redactError(err))
return
}
active := &playbackRunContext{id: session.id, run: session.run, session: session, ctx: cache.ctx}
writer = &playbackActivityWriter{ResponseWriter: writer, app: app, run: active}
writer.Header().Set("Content-Type", "video/mp2t")
http.ServeContent(writer, request, "segment.ts", time.Time{}, bytes.NewReader(body))
}