196 lines
6.5 KiB
Go
196 lines
6.5 KiB
Go
package app
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type playbackHistoryProgress struct {
|
|
Run uint64 `json:"run"`
|
|
Sequence uint64 `json:"sequence"`
|
|
Episode int `json:"episode"`
|
|
Position float64 `json:"position"`
|
|
Duration float64 `json:"duration"`
|
|
Completed bool `json:"completed"`
|
|
}
|
|
|
|
type playbackHistoryRun struct {
|
|
episode int
|
|
duration float64
|
|
}
|
|
|
|
func (app *UIApp) handlePlaybackHistory(writer http.ResponseWriter, request *http.Request) {
|
|
if !playbackRequestAllowed(writer, request, http.MethodGet) {
|
|
return
|
|
}
|
|
viewer := requestViewer(writer, request)
|
|
if viewer == nil {
|
|
return
|
|
}
|
|
entries, err := viewer.playbackHistory().list()
|
|
if err != nil {
|
|
writeJSON(writer, http.StatusInternalServerError, map[string]string{"error": "无法读取观看记录:" + publicError(err).Error()})
|
|
return
|
|
}
|
|
visible := make([]playbackHistoryEntry, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if dramaAllowed(request.Context(), entry.DramaID, entry.Source) {
|
|
visible = append(visible, entry)
|
|
}
|
|
}
|
|
writeJSON(writer, http.StatusOK, map[string]any{"data": visible, "limit": playbackHistoryLimit})
|
|
}
|
|
|
|
func (app *UIApp) handlePlaybackHistoryRemove(writer http.ResponseWriter, request *http.Request) {
|
|
var input struct {
|
|
DramaID string `json:"dramaId"`
|
|
All bool `json:"all"`
|
|
}
|
|
if !readPlaybackRequest(writer, request, &input) {
|
|
return
|
|
}
|
|
id, _, valid := playbackHistoryIdentity(input.DramaID)
|
|
if input.All && input.DramaID != "" || !input.All && !valid {
|
|
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "请选择要删除的观看记录"})
|
|
return
|
|
}
|
|
viewer := requestViewer(writer, request)
|
|
if viewer == nil {
|
|
return
|
|
}
|
|
if !input.All && !app.requireDramaSources(writer, request, []string{id}) {
|
|
return
|
|
}
|
|
if err := viewer.playbackHistory().removeForSources(request.Context(), id, input.All); err != nil {
|
|
writeJSON(writer, http.StatusInternalServerError, map[string]string{"error": "删除观看记录未能保存:" + publicError(err).Error()})
|
|
return
|
|
}
|
|
writeJSON(writer, http.StatusOK, map[string]bool{"ok": true})
|
|
}
|
|
|
|
func (app *UIApp) handlePlaybackProgress(writer http.ResponseWriter, request *http.Request) {
|
|
var input struct {
|
|
Session string `json:"session"`
|
|
Progress playbackHistoryProgress `json:"progress"`
|
|
}
|
|
if !readPlaybackRequest(writer, request, &input) {
|
|
return
|
|
}
|
|
if !app.requirePlaybackOwner(writer, request, input.Session) {
|
|
return
|
|
}
|
|
entry, saved, err := app.recordPlaybackProgress(contextViewer(request.Context()), input.Session, input.Progress)
|
|
if err != nil {
|
|
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": publicError(err).Error()})
|
|
return
|
|
}
|
|
writeJSON(writer, http.StatusOK, map[string]any{"saved": saved, "entry": entry})
|
|
}
|
|
|
|
func (app *UIApp) recordPlaybackProgress(viewer *viewerRecords, id string, progress playbackHistoryProgress) (playbackHistoryEntry, bool, error) {
|
|
if progress.Run == 0 || progress.Sequence == 0 || !validPlaybackHistoryTime(progress.Position) || !validPlaybackHistoryTime(progress.Duration) {
|
|
return playbackHistoryEntry{}, false, errors.New("观看进度无效")
|
|
}
|
|
if viewer == nil {
|
|
return playbackHistoryEntry{}, false, errors.New("浏览器身份无效")
|
|
}
|
|
store := viewer.playbackHistory()
|
|
app.playbackMu.Lock()
|
|
session := app.playbacks[id]
|
|
if session == nil || progress.Episode < 1 || progress.Episode > len(session.tasks) {
|
|
app.playbackMu.Unlock()
|
|
return playbackHistoryEntry{}, false, nil
|
|
}
|
|
if session.viewer != viewer {
|
|
app.playbackMu.Unlock()
|
|
return playbackHistoryEntry{}, false, errors.New("播放会话已过期,请重新打开本剧")
|
|
}
|
|
run, known := session.historyRuns[progress.Run]
|
|
if !known || run.episode != progress.Episode || progress.Sequence <= session.historySequence {
|
|
app.playbackMu.Unlock()
|
|
return playbackHistoryEntry{}, false, nil
|
|
}
|
|
task := session.tasks[progress.Episode-1]
|
|
dramaID, source, valid := playbackHistoryIdentity(task.DramaID)
|
|
if !valid {
|
|
app.playbackMu.Unlock()
|
|
return playbackHistoryEntry{}, false, errors.New("观看记录的站源或剧集 ID 无效")
|
|
}
|
|
duration := progress.Duration
|
|
if run.duration > 0 && validPlaybackHistoryTime(run.duration) {
|
|
duration = run.duration
|
|
}
|
|
position := progress.Position
|
|
if duration > 0 {
|
|
if position > duration+2 {
|
|
app.playbackMu.Unlock()
|
|
return playbackHistoryEntry{}, false, errors.New("观看位置超过分集时长")
|
|
}
|
|
position = math.Min(position, duration)
|
|
}
|
|
completed := progress.Completed && duration > 0 && position >= math.Max(0, duration-2)
|
|
index := task.Index
|
|
if index < 1 {
|
|
index = progress.Episode
|
|
}
|
|
total := task.Total
|
|
if total < index {
|
|
total = index
|
|
}
|
|
if total < len(session.tasks) {
|
|
total = len(session.tasks)
|
|
}
|
|
entry := playbackHistoryEntry{DramaID: dramaID, Source: source, Title: task.DramaTitle, ChapterID: task.Chapter.ID, Episode: task.Chapter.EpisodeString(index), Index: index, Total: total, Position: math.Round(position*1000) / 1000, Duration: duration, Completed: completed, Mode: "online", WatchedAt: time.Now(), sessionID: id, sessionOpened: session.openedAt}
|
|
if len(session.downloadIDs) >= progress.Episode {
|
|
entry.Mode, entry.TaskID = "collection", session.downloadIDs[progress.Episode-1]
|
|
}
|
|
saved, err := store.record(entry, session.openedAt)
|
|
if err == nil {
|
|
session.historySequence = progress.Sequence
|
|
app.touchPlaybackLocked(session)
|
|
}
|
|
app.playbackMu.Unlock()
|
|
if err == nil && saved {
|
|
err = store.flush()
|
|
}
|
|
return entry, saved && err == nil, err
|
|
}
|
|
|
|
func playbackHistoryResume(entry playbackHistoryEntry, tasks []Task, fallback int) (int, float64, bool, string) {
|
|
matched := -1
|
|
for index, task := range tasks {
|
|
if entry.ChapterID != "" && entry.ChapterID == task.Chapter.ID {
|
|
matched = index
|
|
break
|
|
}
|
|
}
|
|
if matched < 0 {
|
|
for index, task := range tasks {
|
|
if task.Chapter.EpisodeString(task.Index) == entry.Episode {
|
|
matched = index
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if matched < 0 {
|
|
return fallback, 0, false, "当前选集中没有上次观看的分集,请手动选集"
|
|
}
|
|
if entry.Completed {
|
|
if matched+1 < len(tasks) {
|
|
return matched + 2, 0, false, "已接续到下一集"
|
|
}
|
|
message := "已看至最新,可选集重看"
|
|
if entry.Index < entry.Total {
|
|
message = "已看完此合集当前可播放的分集,可更新合集或从剧库继续观看"
|
|
}
|
|
return matched + 1, 0, true, message
|
|
}
|
|
position := entry.Position
|
|
if entry.Duration > 0 && position >= entry.Duration {
|
|
position = math.Max(0, entry.Duration-1)
|
|
}
|
|
return matched + 1, position, false, "已恢复上次观看进度"
|
|
}
|