feat(api): streaming, transcoding, events, history and likes (M3)
- tracks carry a stream_url signed for the user for 12 h and the user's reaction; /stream checks the signature, expiry and that the user is not blocked, then hands the file to nginx by X-Accel-Redirect (or serves it with Range support when X_ACCEL=false) - original by default; formats browsers can't play and explicit high/medium/low go through Opus transcoding, never upwards: a lossy source under the target bitrate stays original; transcodes are cached per file and size, deduplicated in flight, limited by TRANSCODE_CONCURRENCY and trimmed hourly by LRU to the size limit - /events takes up to 500 events with client UUIDs, drops repeats and events older than 30 days, counts plays (≥ half or 4 min, or finished), early skips and history entries (≥ 30 s) - history with clearing, likes and dislikes with cursors and totals, track reactions, playback state with last-write-wins, tracks batch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b76aa3964
commit
c05e523cbe
16 files changed
+3913
-155
No files matched your search
@@ -9,9 +9,11 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
@@ -22,6 +24,7 @@ import (
|
||||
"pipu/api/internal/platform/db"
|
||||
"pipu/api/internal/platform/httpx"
|
||||
"pipu/api/internal/platform/sign"
|
||||
"pipu/api/internal/playback"
|
||||
"pipu/api/internal/search"
|
||||
"pipu/api/internal/server"
|
||||
"pipu/api/internal/system"
|
||||
@@ -105,6 +108,25 @@ func run() error {
|
||||
indexer.Trigger()
|
||||
}
|
||||
go covers.Run(ctx) // альбомы, оставшиеся без обложки с прошлого запуска
|
||||
|
||||
concurrency := cfg.TranscodeConcurrency
|
||||
if concurrency <= 0 {
|
||||
concurrency = max(1, runtime.NumCPU()/2)
|
||||
}
|
||||
transcoder := playback.NewTranscoder(cfg.FFmpegPath, filepath.Join(cfg.CacheDir, "transcode"), concurrency,
|
||||
cfg.TranscodeCacheLimitGB, log)
|
||||
go transcoder.RunCleanup(ctx)
|
||||
rootFor := func(ctx context.Context, sourceID uuid.UUID) (string, error) {
|
||||
src, err := libStore.GetSource(ctx, sourceID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return libSvc.Root(src)
|
||||
}
|
||||
var dto library.DTO
|
||||
pb := playback.NewHandler(playback.NewStore(pool), libStore, func() library.DTO { return dto }, signer, transcoder,
|
||||
rootFor, cfg.XAccel, log)
|
||||
dto = library.DTO{Signer: signer, Personal: pb}
|
||||
libSvc.OnCatalogChanged = indexer.Trigger
|
||||
indexer.Trigger() // индекс мог отстать (новая версия, сбой Meilisearch) — пересобираем при старте
|
||||
go libSvc.Restore(ctx)
|
||||
@@ -115,8 +137,9 @@ func run() error {
|
||||
System: sys,
|
||||
Auth: auth.NewHandler(authSvc, cfg.CookieSecure),
|
||||
AuthService: authSvc,
|
||||
Library: library.NewHandler(libSvc, libStore, signer, covers, cfg.XAccel),
|
||||
Search: search.NewHandler(meili, libStore, indexer, library.DTO{Signer: signer}, log),
|
||||
Library: library.NewHandler(libSvc, libStore, dto, covers, cfg.XAccel),
|
||||
Search: search.NewHandler(meili, libStore, indexer, dto, log),
|
||||
Playback: pb,
|
||||
}))
|
||||
log.Info("starting", "version", buildinfo.Version)
|
||||
return httpx.Serve(ctx, log, srv, cfg.ShutdownTimeout)
|
||||
|
||||
Reference in new issue
Block a user