diff --git a/go.mod b/go.mod index 69b356a..5be800a 100644 --- a/go.mod +++ b/go.mod @@ -33,10 +33,10 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/soheilhy/cmux v0.1.5 github.com/spf13/cobra v1.8.0 - github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2 + github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3 github.com/ulule/limiter/v3 v3.11.2 github.com/zencoder/go-dash/v3 v3.0.3 - github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7 + github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb github.com/zijiren233/go-uhc v0.2.3 github.com/zijiren233/livelib v0.3.1 diff --git a/go.sum b/go.sum index 0b71326..1b7c283 100644 --- a/go.sum +++ b/go.sum @@ -365,8 +365,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2 h1:EiooxruEJSuMlDe4PAD+mlMz8ChvSWeql4Z9kdc7/Rg= -github.com/synctv-org/vendors v0.3.3-0.20240418153252-2198604d72f2/go.mod h1:8ONGycZGgULyvajYbmjwGo8fEN6g9c+Q1lhoeDn6rqY= +github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3 h1:+KV0W7224USi8KqXVG3aAmJQEAbumzLi9/UhwALvkAk= +github.com/synctv-org/vendors v0.3.3-0.20240507143426-41159dcb43e3/go.mod h1:8ONGycZGgULyvajYbmjwGo8fEN6g9c+Q1lhoeDn6rqY= github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8= github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -381,8 +381,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zencoder/go-dash/v3 v3.0.3 h1:xqwGJ2fJCSArwONGx6sY26Z1lxQ7zTURoxdRjCpuodM= github.com/zencoder/go-dash/v3 v3.0.3/go.mod h1:30R5bKy1aUYY45yesjtZ9l8trNc2TwNqbS17WVQmCzk= -github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7 h1:ymsEhM4NrTiZx/nyJb5CQRVOmdgZm6L6hHcRUErXYVQ= -github.com/zijiren233/gencontainer v0.0.0-20240331174346-b5e420773df7/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY= +github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c h1:xpQLguenF4bPTrbtCz+kMHpDtUDFIn/iZKd4VRfxe3Y= +github.com/zijiren233/gencontainer v0.0.0-20240507135401-7a9c8355bb2c/go.mod h1:V5oL7PrZxgisuLCblFWd89Jg99O8vM1n58llcxZ2hDY= github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb h1:0DyOxf/TbbGodHhOVHNoPk+7v/YBJACs22gKpKlatWw= github.com/zijiren233/go-colorable v0.0.0-20230930131441-997304c961cb/go.mod h1:6TCzjDiQ8+5gWZiwsC3pnA5M0vUy2jV2Y7ciHJh729g= github.com/zijiren233/go-uhc v0.2.3 h1:mBtTqLpdFzGSmeSYYUn+y0w1J+qolVd8UoS6h6ROyUs= diff --git a/internal/cache/emby.go b/internal/cache/emby.go index 1a13dc0..05397f0 100644 --- a/internal/cache/emby.go +++ b/internal/cache/emby.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" + log "github.com/sirupsen/logrus" "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/vendor" @@ -23,6 +24,7 @@ type EmbyUserCacheData struct { Host string ServerID string ApiKey string + UserID string Backend string } @@ -47,14 +49,16 @@ func EmbyAuthorizationCacheWithUserIDInitFunc(userID, serverID string) (*EmbyUse Host: v.Host, ServerID: v.ServerID, ApiKey: v.ApiKey, + UserID: v.UserID, Backend: v.Backend, }, nil } type EmbySource struct { URLs []struct { - URL string - Name string + URL string + IsTranscode bool + Name string } Subtitles []struct { URL string @@ -65,13 +69,52 @@ type EmbySource struct { } type EmbyMovieCacheData struct { - Sources []EmbySource + Sources []EmbySource + TranscodeSessionID string } type EmbyMovieCache = refreshcache.RefreshCache[*EmbyMovieCacheData, *EmbyUserCache] func NewEmbyMovieCache(movie *model.Movie) *EmbyMovieCache { - return refreshcache.NewRefreshCache(NewEmbyMovieCacheInitFunc(movie), 0) + cache := refreshcache.NewRefreshCache(NewEmbyMovieCacheInitFunc(movie), 0) + cache.SetClearFunc(NewEmbyMovieClearCacheFunc(movie)) + return cache +} + +func NewEmbyMovieClearCacheFunc(movie *model.Movie) func(ctx context.Context, args ...*EmbyUserCache) error { + return func(ctx context.Context, args ...*MapCache[*EmbyUserCacheData, struct{}]) error { + if !movie.Base.VendorInfo.Emby.Transcode { + return nil + } + + serverID, _, err := model.GetEmbyServerIdFromPath(movie.Base.VendorInfo.Emby.Path) + if err != nil { + return err + } + + oldVal, ok := ctx.Value(refreshcache.OldValKey).(*EmbyMovieCacheData) + if !ok { + return nil + } + + aucd, err := args[0].LoadOrStore(ctx, serverID) + if err != nil { + return err + } + if aucd.Host == "" || aucd.ApiKey == "" { + return errors.New("not bind emby vendor") + } + cli := vendor.LoadEmbyClient(aucd.Backend) + _, err = cli.DeleteActiveEncodeings(ctx, &emby.DeleteActiveEncodeingsReq{ + Host: aucd.Host, + Token: aucd.ApiKey, + PalySessionId: oldVal.TranscodeSessionID, + }) + if err != nil { + log.Errorf("delete active encodeings: %v", err) + } + return nil + } } func NewEmbyMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, args ...*EmbyUserCache) (*EmbyMovieCacheData, error) { @@ -97,51 +140,72 @@ func NewEmbyMovieCacheInitFunc(movie *model.Movie) func(ctx context.Context, arg if aucd.Host == "" || aucd.ApiKey == "" { return nil, errors.New("not bind emby vendor") } - u, err := url.Parse(aucd.Host) - if err != nil { - return nil, err - } cli := vendor.LoadEmbyClient(aucd.Backend) - data, err := cli.GetItem(ctx, &emby.GetItemReq{ + data, err := cli.PlaybackInfo(ctx, &emby.PlaybackInfoReq{ Host: aucd.Host, Token: aucd.ApiKey, + UserId: aucd.UserID, ItemId: truePath, }) if err != nil { - return nil, err - } - if data.IsFolder { - return nil, errors.New("path is dir") + return nil, fmt.Errorf("playback info: %w", err) } var resp EmbyMovieCacheData = EmbyMovieCacheData{ - Sources: make([]EmbySource, len(data.MediaSourceInfo)), + Sources: make([]EmbySource, len(data.MediaSourceInfo)), + TranscodeSessionID: data.PlaySessionID, + } + u, err := url.Parse(aucd.Host) + if err != nil { + return nil, err } for i, v := range data.MediaSourceInfo { - if v.Container == "" { - continue - } - result, err := url.JoinPath("emby", "Videos", data.Id, fmt.Sprintf("stream.%s", v.Container)) - if err != nil { - return nil, err + if movie.Base.VendorInfo.Emby.Transcode && v.TranscodingUrl != "" { + resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct { + URL string + IsTranscode bool + Name string + }{ + URL: fmt.Sprintf("%s/emby%s", aucd.Host, v.TranscodingUrl), + Name: v.Name, + IsTranscode: true, + }) + } else if v.DirectPlayUrl != "" { + resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct { + URL string + IsTranscode bool + Name string + }{ + URL: fmt.Sprintf("%s/emby%s", aucd.Host, v.DirectPlayUrl), + Name: v.Name, + }) + } else { + if v.Container == "" { + continue + } + result, err := url.JoinPath("emby", "Videos", truePath, fmt.Sprintf("stream.%s", v.Container)) + if err != nil { + return nil, err + } + u.Path = result + query := url.Values{} + query.Set("api_key", aucd.ApiKey) + query.Set("Static", "true") + query.Set("MediaSourceId", v.Id) + u.RawQuery = query.Encode() + resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct { + URL string + IsTranscode bool + Name string + }{ + URL: u.String(), + Name: v.Name, + }) } - u.Path = result - query := url.Values{} - query.Set("api_key", aucd.ApiKey) - query.Set("Static", "true") - query.Set("MediaSourceId", v.Id) - u.RawQuery = query.Encode() - resp.Sources[i].URLs = append(resp.Sources[i].URLs, struct { - URL string - Name string - }{ - URL: u.String(), - Name: v.Name, - }) for _, msi := range v.MediaStreamInfo { switch msi.Type { case "Subtitle": subtutleType := "srt" - result, err = url.JoinPath("emby", "Videos", data.Id, v.Id, "Subtitles", fmt.Sprintf("%d", msi.Index), fmt.Sprintf("Stream.%s", subtutleType)) + result, err := url.JoinPath("emby", "Videos", truePath, v.Id, "Subtitles", fmt.Sprintf("%d", msi.Index), fmt.Sprintf("Stream.%s", subtutleType)) if err != nil { return nil, err } diff --git a/internal/model/movie.go b/internal/model/movie.go index 413401c..ce35a2b 100644 --- a/internal/model/movie.go +++ b/internal/model/movie.go @@ -136,7 +136,8 @@ func (a *AlistStreamingInfo) AfterFind(tx *gorm.DB) error { type EmbyStreamingInfo struct { // {/}serverId/ItemId - Path string `gorm:"type:varchar(52)" json:"path,omitempty"` + Path string `gorm:"type:varchar(52)" json:"path,omitempty"` + Transcode bool `json:"transcode,omitempty"` } func GetEmbyServerIdFromPath(path string) (serverID string, filePath string, err error) { diff --git a/internal/op/movie.go b/internal/op/movie.go index a8a433d..ae64cd4 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -1,6 +1,7 @@ package op import ( + "context" "errors" "fmt" "hash/crc32" @@ -56,7 +57,7 @@ func (m *Movie) CheckExpired(expireId uint64) bool { } } -func (m *Movie) ClearCache() { +func (m *Movie) ClearCache() error { m.alistCache.Store(nil) bmc := m.bilibiliCache.Swap(nil) @@ -64,7 +65,19 @@ func (m *Movie) ClearCache() { bmc.NoSharedMovie.Clear() } - m.embyCache.Store(nil) + emc := m.embyCache.Swap(nil) + if emc != nil { + u, err := LoadOrInitUserByID(m.CreatorID) + if err != nil { + return err + } + err = emc.Clear(context.Background(), u.Value().EmbyCache()) + if err != nil { + return err + } + } + + return nil } func (m *Movie) AlistCache() *cache.AlistMovieCache { diff --git a/internal/op/room.go b/internal/op/room.go index 8578bc5..2b291a1 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -387,6 +387,17 @@ func (r *Room) CheckCurrentExpired(expireId uint64) (bool, error) { } func (r *Room) SetCurrentMovie(movieID string, play bool) error { + currentMovie, err := r.CurrentMovie() + if err != nil { + if err != ErrNoCurrentMovie { + return err + } + } else { + err = currentMovie.ClearCache() + if err != nil { + return fmt.Errorf("clear cache failed: %w", err) + } + } if movieID == "" { r.current.SetMovie("", false, play) return nil diff --git a/internal/vendor/emby.go b/internal/vendor/emby.go index 5fe7275..9e4c21f 100644 --- a/internal/vendor/emby.go +++ b/internal/vendor/emby.go @@ -78,3 +78,11 @@ func (e *grpcEmby) Logout(ctx context.Context, req *emby.LogoutReq) (*emby.Empty func (e *grpcEmby) Me(ctx context.Context, req *emby.MeReq) (*emby.MeResp, error) { return e.client.Me(ctx, req) } + +func (e *grpcEmby) PlaybackInfo(ctx context.Context, req *emby.PlaybackInfoReq) (*emby.PlaybackInfoResp, error) { + return e.client.PlaybackInfo(ctx, req) +} + +func (e *grpcEmby) DeleteActiveEncodeings(ctx context.Context, req *emby.DeleteActiveEncodeingsReq) (*emby.Empty, error) { + return e.client.DeleteActiveEncodeings(ctx, req) +} diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 7ca4422..68c8c33 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -1040,6 +1040,10 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("id out of range")) return } + if embyC.Sources[source].URLs[id].IsTranscode { + ctx.Redirect(http.StatusFound, embyC.Sources[source].URLs[id].URL) + return + } err = proxyURL(ctx, embyC.Sources[source].URLs[id].URL, nil) if err != nil { log.Errorf("proxy vendor movie error: %v", err) diff --git a/server/handlers/vendors/vendorAlist/login.go b/server/handlers/vendors/vendorAlist/login.go index 5bb9b9f..4a9b8ef 100644 --- a/server/handlers/vendors/vendorAlist/login.go +++ b/server/handlers/vendors/vendorAlist/login.go @@ -115,7 +115,7 @@ func Logout(ctx *gin.Context) { } if rc, ok := user.AlistCache().LoadCache(req.ServerID); ok { - rc.Clear() + rc.Clear(ctx) } ctx.Status(http.StatusNoContent) diff --git a/server/handlers/vendors/vendorBilibili/login.go b/server/handlers/vendors/vendorBilibili/login.go index 77b1a09..59d0507 100644 --- a/server/handlers/vendors/vendorBilibili/login.go +++ b/server/handlers/vendors/vendorBilibili/login.go @@ -233,6 +233,6 @@ func Logout(ctx *gin.Context) { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - user.BilibiliCache().Clear() + user.BilibiliCache().Clear(ctx) ctx.Status(http.StatusNoContent) } diff --git a/server/handlers/vendors/vendorEmby/list.go b/server/handlers/vendors/vendorEmby/list.go index 8ceeafd..5369844 100644 --- a/server/handlers/vendors/vendorEmby/list.go +++ b/server/handlers/vendors/vendorEmby/list.go @@ -136,6 +136,7 @@ EmbyFSListResp: Host: aucd.Host, Path: req.Path, Token: aucd.ApiKey, + UserId: aucd.UserID, Limit: uint64(size), StartIndex: uint64((page - 1) * size), SearchTerm: req.Keywords,