初始提交:短剧库源码(含转码优化 ultrafast)

This commit is contained in:
yvan
2026-09-17 19:09:08 +08:00
commit 07dc0f1059
247 changed files with 43242 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
package app
import (
"bytes"
"context"
"fmt"
"math"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
func (app *UIApp) handlePlaybackNativeOpen(writer http.ResponseWriter, request *http.Request) {
var input struct {
Session string `json:"session"`
Episode int `json:"episode"`
Start float64 `json:"start"`
Quality int `json:"quality"`
Version uint64 `json:"version"`
}
if !readPlaybackRequest(writer, request, &input) {
return
}
if input.Episode < 1 || math.IsNaN(input.Start) || math.IsInf(input.Start, 0) || input.Start < 0 || input.Start > 24*60*60 || input.Quality < 0 || input.Quality > 4320 {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "集数、播放位置或清晰度无效"})
return
}
if request.Context().Err() != nil {
return
}
run, status, err := app.beginPlayback(context.WithoutCancel(request.Context()), input.Session, input.Episode, input.Start, input.Quality, input.Version, true)
if err != nil {
writeJSON(writer, status, map[string]string{"error": err.Error()})
return
}
var cache *playbackNative
if run.cache != nil && run.cache.view().State != "failed" {
cache = run.cache.native
}
prefetched := cache != nil
if cache == nil {
cache = newPlaybackNative(app, run.ctx, run.task, run.downloadID, input.Quality, input.Start, false)
} else {
cache.mu.Lock()
cache.background = false
cache.mu.Unlock()
}
stop := func() { run.stop(); cache.Close() }
app.playbackMu.Lock()
if app.playbacks[run.id] != run.session || run.session.run != run.run || run.ctx.Err() != nil {
app.playbackMu.Unlock()
stop()
writeJSON(writer, http.StatusConflict, map[string]string{"error": "播放请求已更新"})
return
}
run.session.native, run.session.cancel = cache, stop
app.playbackMu.Unlock()
cache.start()
startup, cancel := context.WithTimeout(request.Context(), 90*time.Second)
defer cancel()
_, err = cache.segment(startup, int(input.Start/playbackNativeSegmentSeconds))
if err != nil {
stop()
app.finishPlaybackRun(run, err, request.Context().Err() != nil)
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": app.redactError(err)})
return
}
media, duration, source := cache.metadata()
if duration <= 0 || duration > 24*60*60 || math.IsNaN(duration) || math.IsInf(duration, 0) || input.Start >= duration {
stop()
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": "未取得有效播放时长或播放位置已超出本集"})
return
}
app.playbackRunReady(run, duration)
query := url.Values{"session": {run.id}, "run": {strconv.FormatUint(run.run, 10)}}
writeJSON(writer, http.StatusOK, map[string]any{
"url": "/api/ui/playback/hls/index.m3u8?" + query.Encode(), "run": run.run, "duration": duration, "source": source,
"quality": media.Quality, "qualities": playbackQualityOptions(media), "prefetched": prefetched,
})
}
func (app *UIApp) handlePlaybackNativeAsset(writer http.ResponseWriter, request *http.Request) {
method := http.MethodGet
if request.Method == http.MethodHead {
method = http.MethodHead
}
if !playbackRequestAllowed(writer, request, method) {
return
}
query := request.URL.Query()
run, runErr := strconv.ParseUint(query.Get("run"), 10, 64)
if runErr != nil || run == 0 {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "播放请求编号无效"})
return
}
app.playbackMu.Lock()
session := app.playbacks[query.Get("session")]
if !viewerOwnsPlayback(request.Context(), session) || session.run != run || session.native == nil {
app.playbackMu.Unlock()
writeJSON(writer, http.StatusGone, map[string]string{"error": "此播放流已结束,请重新选择分集"})
return
}
cache := session.native
app.touchPlaybackLocked(session)
app.playbackMu.Unlock()
if strings.HasSuffix(request.URL.Path, "/index.m3u8") {
_, duration, _ := cache.metadata()
if duration <= 0 || duration > 24*60*60 || math.IsNaN(duration) || math.IsInf(duration, 0) {
writeJSON(writer, http.StatusServiceUnavailable, map[string]string{"error": "播放列表尚未就绪"})
return
}
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)
if cache.offset > 0 {
fmt.Fprintf(&playlist, "#EXT-X-START:TIME-OFFSET=%.3f,PRECISE=YES\n", cache.offset)
}
for index, count := 0, int(math.Ceil(duration/playbackNativeSegmentSeconds)); index < count; index++ {
length := math.Min(playbackNativeSegmentSeconds, duration-float64(index*playbackNativeSegmentSeconds))
asset := url.Values{"session": {session.id}, "run": {strconv.FormatUint(run, 10)}, "segment": {strconv.Itoa(index)}}
fmt.Fprintf(&playlist, "#EXTINF:%.6f,\nsegment.ts?%s\n", length, asset.Encode())
}
playlist.WriteString("#EXT-X-ENDLIST\n")
writer.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
http.ServeContent(writer, request, "index.m3u8", time.Time{}, strings.NewReader(playlist.String()))
return
}
index, err := strconv.Atoi(query.Get("segment"))
_, duration, _ := cache.metadata()
if err != nil || index < 0 || duration <= 0 || index >= int(math.Ceil(duration/playbackNativeSegmentSeconds)) {
writeJSON(writer, http.StatusBadRequest, map[string]string{"error": "播放分片编号无效"})
return
}
ctx, cancel := context.WithTimeout(request.Context(), 90*time.Second)
defer cancel()
body, err := cache.segment(ctx, index)
if err != nil {
writeJSON(writer, http.StatusBadGateway, map[string]string{"error": app.redactError(err)})
return
}
writer.Header().Set("Content-Type", "video/mp2t")
http.ServeContent(writer, request, "segment.ts", time.Time{}, bytes.NewReader(body))
}