Files
djk/internal/app/ui_playback_collection.go
T

183 lines
5.9 KiB
Go

package app
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
func isPlayableDownloadTask(task *UITask) bool {
return task != nil && !task.RemoveRequested && !isChapterPlaceholderTask(task) &&
(hasCollectionPlaybackMedia(task.Source) || task.Status == uiStatusSuccess && firstNonEmpty(task.Path, task.Source.OutPath) != "")
}
func hasCollectionPlaybackMedia(task Task) bool {
if task.Chapter.VideoURL != "" || task.Chapter.PageURL != "" {
return true
}
source, _, valid := splitProviderDramaID(task.DramaID)
return valid && (source == sourceHuangdou || source == sourceHuangguoAI || source == sourceHuangguoVideo)
}
func (app *UIApp) collectionPlaybackTasksLocked(selectedID string) (string, []Task, []string, int, error) {
selected := app.tasks[selectedID]
if !isPlayableDownloadTask(selected) {
return "", nil, nil, 0, errors.New("此分集不存在或尚未完成章节解析,请更新合集后再播放")
}
var candidates []*UITask
for _, taskID := range app.taskOrder {
task := app.tasks[taskID]
if isPlayableDownloadTask(task) && task.DramaID == selected.DramaID {
candidates = append(candidates, task)
}
}
sort.SliceStable(candidates, func(left, right int) bool {
return playbackEpisodeNumber(candidates[left]) < playbackEpisodeNumber(candidates[right])
})
var tasks []Task
var ids []string
initialIndex := 0
for index, candidate := range candidates {
task := candidate.Source
if task.Index <= 0 {
task.Index = playbackEpisodeNumber(candidate)
}
tasks = append(tasks, task)
ids = append(ids, candidate.ID)
if candidate.ID == selectedID {
initialIndex = index + 1
}
}
if initialIndex == 0 {
return "", nil, nil, 0, errors.New("合集任务记录已变化,请刷新页面后再播放")
}
return firstNonEmpty(selected.DramaTitle, selected.Source.DramaTitle, "短剧"), tasks, ids, initialIndex, nil
}
func playbackEpisodeNumber(task *UITask) int {
if task.Source.Index > 0 {
return task.Source.Index
}
return episodeNumber(firstNonEmpty(task.Episode, task.Source.Chapter.EpisodeString(1)))
}
func completedPlaybackPath(task *UITask) string {
if task.Status != uiStatusSuccess {
return ""
}
path := firstNonEmpty(task.Path, task.Source.OutPath)
if !strings.EqualFold(filepath.Ext(path), ".mp4") || strings.HasSuffix(strings.ToLower(path), ".part.mp4") {
return ""
}
absolute, err := filepath.Abs(path)
if err != nil {
return ""
}
info, err := os.Stat(absolute)
if err != nil || !info.Mode().IsRegular() || info.Size() <= 0 {
return ""
}
return absolute
}
func (app *UIApp) playbackCollectionTask(id string) (Task, string, error) {
app.mu.Lock()
defer app.mu.Unlock()
task := app.tasks[id]
if !isPlayableDownloadTask(task) {
return Task{}, "", errors.New("分集任务已被清理或正在移除,请重新打开合集")
}
return task.Source, completedPlaybackPath(task), nil
}
type collectionPlaybackPreparation struct {
Source string `json:"source"`
DownloadStatus string `json:"downloadStatus"`
Queued bool `json:"queued"`
}
func (app *UIApp) prepareCollectionDownload(ctx context.Context, id string) (collectionPlaybackPreparation, error) {
app.mu.Lock()
defer app.mu.Unlock()
task := app.tasks[id]
if !isPlayableDownloadTask(task) {
return collectionPlaybackPreparation{}, errors.New("分集任务已被清理或正在移除,请重新打开合集")
}
if err := ctx.Err(); err != nil {
return collectionPlaybackPreparation{}, err
}
if completedPlaybackPath(task) != "" {
return collectionPlaybackPreparation{Source: "local", DownloadStatus: task.Status}, nil
}
if task.CancelRequested || task.PauseRequested {
return collectionPlaybackPreparation{}, errors.New("此分集的下载正在停止,请稍后重新播放")
}
if task.Status == uiStatusRunning || task.Status == uiStatusQueued {
return collectionPlaybackPreparation{Source: "online", DownloadStatus: task.Status}, nil
}
if task.Source.OutPath == "" || !hasCollectionPlaybackMedia(task.Source) {
return collectionPlaybackPreparation{}, errors.New("本地分集不可用,且任务缺少下载地址,请更新该合集")
}
previous := *task
task.Status = uiStatusQueued
task.Phase = "queued"
task.Progress = 0
task.Error = ""
task.CancelRequested = false
task.PauseRequested = false
task.DownloadedBytes = 0
task.ElapsedSeconds = 0
task.MediaElapsedSeconds = 0
task.SpeedBytesPerSecond = 0
task.RemainingSeconds = 0
task.UpdatedAt = time.Now()
if err := app.saveStateLocked(); err != nil {
*task = previous
return collectionPlaybackPreparation{}, fmt.Errorf("无法保存自动下载任务:%w", err)
}
app.cond.Broadcast()
return collectionPlaybackPreparation{Source: "online", DownloadStatus: uiStatusQueued, Queued: true}, nil
}
func (app *UIApp) handlePlaybackPrepare(writer http.ResponseWriter, request *http.Request) {
var input struct {
Session string `json:"session"`
Episode int `json:"episode"`
}
if !readPlaybackRequest(writer, request, &input) {
return
}
app.playbackMu.Lock()
session := app.playbacks[input.Session]
if !viewerOwnsPlayback(request.Context(), session) {
app.playbackMu.Unlock()
writeJSON(writer, http.StatusGone, map[string]string{"error": "播放会话已过期,请重新打开合集"})
return
}
if input.Episode < 1 || input.Episode > len(session.downloadIDs) {
app.playbackMu.Unlock()
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "请选择下载合集中的有效分集"})
return
}
id := session.downloadIDs[input.Episode-1]
app.touchPlaybackLocked(session)
app.playbackMu.Unlock()
result, err := app.prepareCollectionDownload(request.Context(), id)
if err != nil {
writeJSON(writer, http.StatusConflict, map[string]string{"error": app.redactError(err)})
return
}
app.playbackMu.Lock()
if app.playbacks[input.Session] == session {
session.prepared[input.Episode] = true
}
app.playbackMu.Unlock()
writeJSON(writer, http.StatusOK, result)
}