feat(api): streaming, transcoding, events, history and likes (M3)
ci / api / api (push) Successful in 29s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 35s
ci / ts / ts (packages + web) (push) Failing after 49s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s

- 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:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 07:25:15 +03:00
1 parent 8b76aa3964
commit c05e523cbe
16 files changed
+3913 -155

No files matched your search

+25 -2
View File
@@ -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)