Files
djk/internal/app/playback_run.go
T

121 lines
3.9 KiB
Go

package app
import (
"context"
"errors"
"net/http"
)
type playbackRunContext struct {
id string
index int
run uint64
session *playbackSession
task Task
downloadID string
ctx context.Context
cancel context.CancelFunc
stop context.CancelFunc
cache *playbackPrefetch
offset float64
}
func (app *UIApp) beginPlayback(parent context.Context, id string, index int, offset float64, quality int, version uint64, native bool) (*playbackRunContext, int, error) {
app.playbackMu.Lock()
session := app.playbacks[id]
if !viewerOwnsPlayback(parent, session) {
app.playbackMu.Unlock()
return nil, http.StatusGone, errors.New("播放会话已过期,请重新打开本剧")
}
if index < 1 || index > len(session.tasks) {
app.playbackMu.Unlock()
return nil, http.StatusBadRequest, errors.New("分集不存在")
}
if version > 0 && version <= session.streamVersion {
app.playbackMu.Unlock()
return nil, http.StatusConflict, errors.New("播放请求已更新")
}
downloadID := ""
if len(session.downloadIDs) > 0 {
if !session.prepared[index] {
app.playbackMu.Unlock()
return nil, http.StatusConflict, errors.New("请先选择要播放的分集")
}
downloadID = session.downloadIDs[index-1]
}
previousCancel := session.cancel
cache := session.prefetch
session.prefetch, session.native, session.media = nil, nil, nil
remux, _ := parent.Value(playbackRemuxKey{}).(bool)
if cache != nil && (offset != 0 && !cache.planOnly || cache.episode != index || cache.fromRun != session.run || cache.quality != quality || (cache.native != nil) != native || !native && cache.remux != remux) {
cache.cancel()
cache = nil
}
ctx := context.WithValue(parent, playbackQualityKey{}, quality)
ctx, cancel := context.WithCancel(ctx)
stop := func() {
cancel()
if cache != nil {
cache.cancel()
}
}
session.cancel = stop
session.run++
session.currentIndex, session.quality = index, quality
if version > 0 {
session.streamVersion = version
}
if session.historyRuns == nil {
session.historyRuns = make(map[uint64]playbackHistoryRun)
}
session.historyRuns[session.run] = playbackHistoryRun{episode: index}
for oldRun := range session.historyRuns {
if session.run > oldRun && session.run-oldRun > 8 {
delete(session.historyRuns, oldRun)
}
}
session.prefetchVersion = 0
session.state, session.error, session.duration = "buffering", "", 0
app.touchPlaybackLocked(session)
run := &playbackRunContext{id: id, index: index, run: session.run, session: session, task: session.tasks[index-1], downloadID: downloadID, ctx: ctx, cancel: cancel, stop: stop, cache: cache, offset: offset}
app.playbackMu.Unlock()
if previousCancel != nil {
previousCancel()
}
app.downloader.recordDiagnostic(diagnosticEvent{Level: "info", Event: "playback.started", Source: sourceFromDramaID(run.task.DramaID),
DramaID: run.task.DramaID, DramaTitle: run.task.DramaTitle, Episode: index, Run: run.run, StartSeconds: offset, Message: "开始播放"})
return run, http.StatusOK, nil
}
func (app *UIApp) playbackRunReady(run *playbackRunContext, duration float64) {
app.playbackMu.Lock()
if current := app.playbacks[run.id]; current == run.session && current.run == run.run {
current.state, current.duration = "streaming", duration
current.historyRuns[run.run] = playbackHistoryRun{episode: run.index, duration: duration}
}
app.playbackMu.Unlock()
}
func (app *UIApp) finishPlaybackRun(run *playbackRunContext, err error, stopped bool) {
failed := false
app.playbackMu.Lock()
if current := app.playbacks[run.id]; current == run.session && current.run == run.run {
if current.native == nil {
current.cancel = nil
}
current.state = "ended"
if err != nil {
current.state, current.error = "failed", app.redactError(err)
if stopped {
current.state = "stopped"
} else {
failed = true
}
}
}
app.playbackMu.Unlock()
if failed {
app.downloader.recordTaskFailure("playback.failed", run.task, run.offset, err)
}
}