优化播放逻辑

This commit is contained in:
truewhile
2026-10-21 12:02:11 +08:00
parent 121a0a3cc5
commit 55854a0adb
25 changed files with 943 additions and 129 deletions
+24 -2
View File
@@ -124,10 +124,32 @@ func cloud115HLSSessionHandler(svc *service.Container) gin.HandlerFunc {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing media_id"})
return
}
if !enforceScopedPlaybackToken(c, mediaID) {
if c.Request.Context().Err() != nil {
c.AbortWithStatus(499)
return
}
err := svc.Cloud115.HLSProxy().ServeChild(
actualMediaID, err := svc.Cloud115.HLSProxy().SessionMediaID(c.Param("session"), c.Param("key"))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"code": "hls_session_not_found", "error": "hls session not found"})
return
}
if mediaID != actualMediaID {
c.JSON(http.StatusForbidden, gin.H{"error": "playback session media mismatch"})
return
}
if !enforceScopedPlaybackToken(c, actualMediaID) {
return
}
if svc.Repo == nil || svc.Repo.Media == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "media not found"})
return
}
m, findErr := svc.Repo.Media.FindByID(c.Request.Context(), actualMediaID)
if findErr != nil || m == nil || !mediaVisibleForRequest(c, svc, m) {
c.JSON(http.StatusNotFound, gin.H{"error": "media not found"})
return
}
err = svc.Cloud115.HLSProxy().ServeChild(
c.Request.Context(),
c.Writer,
c.Request,
+10 -2
View File
@@ -418,7 +418,11 @@ func embyVideoHLSPlaylistHandler(svc *service.Container) gin.HandlerFunc {
c.Status(http.StatusNotFound)
return
}
err = svc.Stream.ServeHLSPlaylist(c.Writer, c.Request, c.Param("id"))
scope, ok := localHLSPlaybackScope(c)
if !ok {
return
}
err = svc.Stream.ServeHLSPlaylist(c.Writer, c.Request, c.Param("id"), scope)
if errors.Is(err, service.ErrTranscodeDisabled) {
c.JSON(http.StatusConflict, gin.H{"error": "transcode disabled"})
return
@@ -444,7 +448,11 @@ func embyVideoHLSSegmentHandler(svc *service.Container) gin.HandlerFunc {
c.Status(http.StatusNotFound)
return
}
if err := svc.Stream.ServeHLSSegment(c.Writer, c.Request, c.Param("id"), c.Param("seg")); err != nil {
scope, ok := localHLSPlaybackScope(c)
if !ok {
return
}
if err := svc.Stream.ServeHLSSegment(c.Writer, c.Request, c.Param("id"), c.Param("seg"), scope); err != nil {
c.Status(http.StatusNotFound)
}
}
+37
View File
@@ -0,0 +1,37 @@
package handler
import (
"net/http"
"net/http/httptest"
"testing"
"go.uber.org/zap"
"github.com/truewhile/MeBox/internal/middleware"
"github.com/truewhile/MeBox/internal/model"
"github.com/truewhile/MeBox/internal/service"
)
func TestStopHLSChecksMediaVisibility(t *testing.T) {
router, svc, secret := newPlaybackScopeTestRouter(t)
svc.Transcoder = service.NewTranscoderService(svc.Cfg, zap.NewNop(), svc.Repo, nil)
router.DELETE("/api/hls/:id", middleware.AuthRequired(secret), stopTranscodeHandler(svc))
req := httptest.NewRequest(http.MethodDelete, "/api/hls/nonexistent?session_id=player-a", nil)
req.Header.Set("Authorization", "Bearer "+signedTestToken(t, secret))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("stop of a nonexistent/invisible media: status=%d body=%s, want 404", rec.Code, rec.Body.String())
}
profile := &model.PlayProfile{UserID: "user-1", Name: "restricted", IsDefault: true, AllowedLibraryIDs: `["other-library"]`}
if err := svc.Repo.DB.Create(profile).Error; err != nil {
t.Fatal(err)
}
req = httptest.NewRequest(http.MethodDelete, "/api/hls/media-1?session_id=player-a", nil)
req.Header.Set("Authorization", "Bearer "+signedTestToken(t, secret))
rec = httptest.NewRecorder()
router.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("invisible existing media can be stopped: %d %s", rec.Code, rec.Body.String())
}
}
+43 -3
View File
@@ -27,7 +27,15 @@ func hlsPlaylistHandler(svc *service.Container) gin.HandlerFunc {
if !enforceScopedPlaybackToken(c, m.ID) {
return
}
err = svc.Stream.ServeHLSPlaylist(c.Writer, c.Request, c.Param("id"))
scope, ok := localHLSPlaybackScope(c)
if !ok {
return
}
err = svc.Stream.ServeHLSPlaylist(c.Writer, c.Request, c.Param("id"), scope)
if errors.Is(err, service.ErrHLSGenerationSuperseded) {
c.JSON(http.StatusConflict, gin.H{"code": "hls_generation_superseded", "error": err.Error()})
return
}
if errors.Is(err, service.ErrMediaNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
@@ -62,7 +70,11 @@ func hlsSegmentHandler(svc *service.Container) gin.HandlerFunc {
if !enforceScopedPlaybackToken(c, m.ID) {
return
}
err = svc.Stream.ServeHLSSegment(c.Writer, c.Request, c.Param("id"), c.Param("seg"))
scope, ok := localHLSPlaybackScope(c)
if !ok {
return
}
err = svc.Stream.ServeHLSSegment(c.Writer, c.Request, c.Param("id"), c.Param("seg"), scope)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
@@ -72,11 +84,39 @@ func hlsSegmentHandler(svc *service.Container) gin.HandlerFunc {
func stopTranscodeHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
svc.Transcoder.StopJob(c.Param("id"))
m, err := findMediaForPlaybackEndpoint(c, svc, c.Param("id"))
if err != nil || m == nil || !mediaVisibleForRequest(c, svc, m) {
c.JSON(http.StatusNotFound, gin.H{"error": "media not found"})
return
}
if !enforceScopedPlaybackToken(c, m.ID) {
return
}
scope, ok := localHLSPlaybackScope(c)
if !ok {
return
}
if svc.Transcoder != nil {
svc.Transcoder.StopPlayback(m.ID, scope)
}
c.Status(http.StatusNoContent)
}
}
func localHLSPlaybackScope(c *gin.Context) (service.HLSPlaybackScope, bool) {
userID := currentUserID(c)
if userID == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing playback owner"})
return service.HLSPlaybackScope{}, false
}
scope, err := service.HLSPlaybackScopeForRequest(c.Request, userID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return service.HLSPlaybackScope{}, false
}
return scope, true
}
func imageProxyHandler(svc *service.Container) gin.HandlerFunc {
return func(c *gin.Context) {
raw := c.Query("url")