From eaca40b5a0611c7e12b3aaba6ba59324c59cd2ba Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Mon, 13 Nov 2023 17:42:36 +0800 Subject: [PATCH] Feat: cache mpd file --- internal/bootstrap/rtmp.go | 4 +- internal/conf/config.go | 6 -- internal/conf/proxy.go | 15 --- internal/conf/server.go | 11 +-- internal/model/movie.go | 186 +++++++++---------------------------- internal/model/setting.go | 6 +- internal/op/movie.go | 97 ++++++++++++++++++- internal/settings/var.go | 15 +++ server/handlers/movie.go | 175 ++++++++++++++++++++++++---------- server/handlers/public.go | 12 +-- 10 files changed, 293 insertions(+), 234 deletions(-) delete mode 100644 internal/conf/proxy.go diff --git a/internal/bootstrap/rtmp.go b/internal/bootstrap/rtmp.go index 1f7503d..7886b27 100644 --- a/internal/bootstrap/rtmp.go +++ b/internal/bootstrap/rtmp.go @@ -5,9 +5,9 @@ import ( "fmt" log "github.com/sirupsen/logrus" - "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/rtmp" + "github.com/synctv-org/synctv/internal/settings" rtmps "github.com/zijiren233/livelib/server" ) @@ -34,7 +34,7 @@ func auth(ReqAppName, ReqChannelName string, IsPublisher bool) (*rtmps.Channel, return r.GetChannel(channelName) } - if !conf.Conf.Server.Rtmp.RtmpPlayer { + if !settings.RtmpPlayer.Get() { log.Warnf("rtmp: dial to %s/%s error: %s", ReqAppName, ReqChannelName, "rtmp player is not enabled") return nil, fmt.Errorf("rtmp: dial to %s/%s error: %s", ReqAppName, ReqChannelName, "rtmp player is not enabled") } diff --git a/internal/conf/config.go b/internal/conf/config.go index 4c21036..0c56db0 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -14,9 +14,6 @@ type Config struct { // Jwt Jwt JwtConfig `yaml:"jwt"` - // Proxy - Proxy ProxyConfig `yaml:"proxy" hc:"you can use proxy to proxy movie and live when custom headers or network is slow to connect to origin server"` - // Database Database DatabaseConfig `yaml:"database"` @@ -45,9 +42,6 @@ func DefaultConfig() *Config { // Jwt Jwt: DefaultJwtConfig(), - // Proxy - Proxy: DefaultProxyConfig(), - // Database Database: DefaultDatabaseConfig(), diff --git a/internal/conf/proxy.go b/internal/conf/proxy.go deleted file mode 100644 index 1a2b029..0000000 --- a/internal/conf/proxy.go +++ /dev/null @@ -1,15 +0,0 @@ -package conf - -type ProxyConfig struct { - MovieProxy bool `yaml:"movie_proxy" env:"PROXY_MOVIE"` - LiveProxy bool `yaml:"live_proxy" env:"PROXY_LIVE"` - AllowProxyToLocal bool `yaml:"allow_proxy_to_local" env:"PROXY_ALLOW_TO_LOCAL"` -} - -func DefaultProxyConfig() ProxyConfig { - return ProxyConfig{ - MovieProxy: true, - LiveProxy: true, - AllowProxyToLocal: false, - } -} diff --git a/internal/conf/server.go b/internal/conf/server.go index 94d7ce1..a34fcbe 100644 --- a/internal/conf/server.go +++ b/internal/conf/server.go @@ -18,10 +18,6 @@ type RtmpServerConfig struct { Enable bool `yaml:"enable" env:"RTMP_ENABLE"` Listen string `yaml:"listen" lc:"default use http listen" env:"RTMP_LISTEN"` Port uint16 `yaml:"port" lc:"default use server port" env:"RTMP_PORT"` - - CustomPublishHost string `yaml:"custom_publish_host" lc:"default use http header host" env:"RTMP_CUSTOM_PUBLISH_HOST"` - RtmpPlayer bool `yaml:"rtmp_player" hc:"can watch live streams through the RTMP protocol (without authentication, insecure)." env:"RTMP_PLAYER"` - TsDisguisedAsPng bool `yaml:"ts_disguised_as_png" hc:"disguise the .ts file as a .png file" env:"RTMP_TS_DISGUISED_AS_PNG"` } func DefaultServerConfig() ServerConfig { @@ -34,11 +30,8 @@ func DefaultServerConfig() ServerConfig { KeyPath: "", }, Rtmp: RtmpServerConfig{ - Enable: true, - Port: 0, - CustomPublishHost: "", - RtmpPlayer: false, - TsDisguisedAsPng: true, + Enable: true, + Port: 0, }, } } diff --git a/internal/model/movie.go b/internal/model/movie.go index 10038d9..2c9f8d8 100644 --- a/internal/model/movie.go +++ b/internal/model/movie.go @@ -1,13 +1,10 @@ package model import ( - "errors" "fmt" - "net/url" "sync/atomic" "time" - "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/gencontainer/refreshcache" "github.com/zijiren233/gencontainer/rwmap" @@ -22,6 +19,46 @@ type Movie struct { RoomID string `gorm:"not null;index" json:"-"` CreatorID string `gorm:"not null;index" json:"creatorId"` Base BaseMovie `gorm:"embedded;embeddedPrefix:base_" json:"base"` + Cache BaseCache `gorm:"-:all" json:"-"` +} + +type BaseCache struct { + URL rwmap.RWMap[string, *refreshcache.RefreshCache[string]] + MPD atomic.Pointer[refreshcache.RefreshCache[*MPDCache]] +} + +type MPDCache struct { + MPDFile string + URLs []string +} + +func (b *BaseCache) Clear() { + b.MPD.Store(nil) + b.URL.Clear() +} + +func (b *BaseCache) InitOrLoadURLCache(id string, refreshFunc func() (string, error), maxAge time.Duration) (*refreshcache.RefreshCache[string], error) { + c, loaded := b.URL.Load(id) + if loaded { + return c, nil + } + + c, _ = b.URL.LoadOrStore(id, refreshcache.NewRefreshCache[string](refreshFunc, maxAge)) + return c, nil +} + +func (b *BaseCache) InitOrLoadMPDCache(refreshFunc func() (*MPDCache, error), maxAge time.Duration) (*refreshcache.RefreshCache[*MPDCache], error) { + c := b.MPD.Load() + if c != nil { + return c, nil + } + + c = refreshcache.NewRefreshCache[*MPDCache](refreshFunc, maxAge) + if b.MPD.CompareAndSwap(nil, c) { + return c, nil + } else { + return b.InitOrLoadMPDCache(refreshFunc, maxAge) + } } func (m *Movie) BeforeCreate(tx *gorm.DB) error { @@ -31,10 +68,6 @@ func (m *Movie) BeforeCreate(tx *gorm.DB) error { return nil } -func (m *Movie) Validate() error { - return m.Base.Validate() -} - type BaseMovie struct { Url string `json:"url"` Name string `gorm:"not null" json:"name"` @@ -46,95 +79,6 @@ type BaseMovie struct { VendorInfo VendorInfo `gorm:"embedded;embeddedPrefix:vendor_info_" json:"vendorInfo,omitempty"` } -func (m *BaseMovie) Validate() error { - if m.VendorInfo.Vendor != "" { - err := m.validateVendorMovie() - if err != nil { - return err - } - } - switch { - case m.RtmpSource && m.Proxy: - return errors.New("rtmp source and proxy can't be true at the same time") - case m.Live && m.RtmpSource: - if !conf.Conf.Server.Rtmp.Enable { - return errors.New("rtmp is not enabled") - } - case m.Live && m.Proxy: - if !conf.Conf.Proxy.LiveProxy { - return errors.New("live proxy is not enabled") - } - u, err := url.Parse(m.Url) - if err != nil { - return err - } - if !conf.Conf.Proxy.AllowProxyToLocal && utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } - switch u.Scheme { - case "rtmp": - case "http", "https": - default: - return errors.New("unsupported scheme") - } - case !m.Live && m.RtmpSource: - return errors.New("rtmp source can't be true when movie is not live") - case !m.Live && m.Proxy: - if !conf.Conf.Proxy.MovieProxy { - return errors.New("movie proxy is not enabled") - } - if m.VendorInfo.Vendor != "" { - return nil - } - u, err := url.Parse(m.Url) - if err != nil { - return err - } - if !conf.Conf.Proxy.AllowProxyToLocal && utils.IsLocalIP(u.Host) { - return errors.New("local ip is not allowed") - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("unsupported scheme") - } - case !m.Live && !m.Proxy, m.Live && !m.Proxy && !m.RtmpSource: - if m.VendorInfo.Vendor == "" { - u, err := url.Parse(m.Url) - if err != nil { - return err - } - if u.Scheme != "http" && u.Scheme != "https" { - return errors.New("unsupported scheme") - } - } - default: - return errors.New("unknown error") - } - return nil -} - -func (m *BaseMovie) validateVendorMovie() error { - switch m.VendorInfo.Vendor { - case StreamingVendorBilibili: - err := m.VendorInfo.Bilibili.Validate() - if err != nil { - return err - } - if m.Headers == nil { - m.Headers = map[string]string{ - "Referer": "https://www.bilibili.com", - "User-Agent": utils.UA, - } - } else { - m.Headers["Referer"] = "https://www.bilibili.com" - m.Headers["User-Agent"] = utils.UA - } - default: - return fmt.Errorf("vendor not support") - } - - return nil -} - type VendorInfo struct { Vendor StreamingVendor `json:"vendor"` Shared bool `gorm:"not null;default:false" json:"shared"` @@ -142,12 +86,11 @@ type VendorInfo struct { } type BilibiliVendorInfo struct { - Bvid string `json:"bvid,omitempty"` - Cid uint64 `json:"cid,omitempty"` - Epid uint64 `json:"epid,omitempty"` - Quality uint64 `json:"quality,omitempty"` - VendorName string `json:"vendorName,omitempty"` - Cache BilibiliVendorCache `gorm:"-:all" json:"-"` + Bvid string `json:"bvid,omitempty"` + Cid uint64 `json:"cid,omitempty"` + Epid uint64 `json:"epid,omitempty"` + Quality uint64 `json:"quality,omitempty"` + VendorName string `json:"vendorName,omitempty"` } func (b *BilibiliVendorInfo) Validate() error { @@ -165,42 +108,3 @@ func (b *BilibiliVendorInfo) Validate() error { return nil } - -type BilibiliVendorCache struct { - URL rwmap.RWMap[string, *refreshcache.RefreshCache[string]] - MPD atomic.Pointer[refreshcache.RefreshCache[*MPDCache]] -} - -type MPDCache struct { - MPDFile string - URLs []string -} - -func (b *BilibiliVendorInfo) InitOrLoadURLCache(id string, initCache func(*BilibiliVendorInfo) (*refreshcache.RefreshCache[string], error)) (*refreshcache.RefreshCache[string], error) { - if c, loaded := b.Cache.URL.Load(id); loaded { - return c, nil - } - c, err := initCache(b) - if err != nil { - return nil, err - } - - c, _ = b.Cache.URL.LoadOrStore(id, c) - - return c, nil -} - -func (b *BilibiliVendorInfo) InitOrLoadMPDCache(initCache func(*BilibiliVendorInfo) (*refreshcache.RefreshCache[*MPDCache], error)) (*refreshcache.RefreshCache[*MPDCache], error) { - if c := b.Cache.MPD.Load(); c != nil { - return c, nil - } - c, err := initCache(b) - if err != nil { - return nil, err - } - if b.Cache.MPD.CompareAndSwap(nil, c) { - return c, nil - } else { - return b.InitOrLoadMPDCache(initCache) - } -} diff --git a/internal/model/setting.go b/internal/model/setting.go index cdd47d3..9a96428 100644 --- a/internal/model/setting.go +++ b/internal/model/setting.go @@ -16,8 +16,10 @@ func (s SettingGroup) String() string { } const ( - SettingGroupRoom SettingGroup = "room" - SettingGroupUser SettingGroup = "user" + SettingGroupRoom SettingGroup = "room" + SettingGroupUser SettingGroup = "user" + SettingGroupProxy SettingGroup = "proxy" + SettingGroupRtmp SettingGroup = "rtmp" ) type Setting struct { diff --git a/internal/op/movie.go b/internal/op/movie.go index 3e39eb6..8eb6ba9 100644 --- a/internal/op/movie.go +++ b/internal/op/movie.go @@ -2,12 +2,15 @@ package op import ( "errors" + "fmt" "net/url" "sync" "time" "github.com/go-resty/resty/v2" + "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/model" + "github.com/synctv-org/synctv/internal/settings" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/livelib/av" "github.com/zijiren233/livelib/container/flv" @@ -34,7 +37,7 @@ func genTsName() string { } func (m *Movie) init() (err error) { - if err = m.Movie.Validate(); err != nil { + if err = m.Validate(); err != nil { return } switch { @@ -104,6 +107,97 @@ func (m *Movie) init() (err error) { return nil } +func (movie *Movie) Validate() error { + m := movie.Base + if m.VendorInfo.Vendor != "" { + err := movie.validateVendorMovie() + if err != nil { + return err + } + } + switch { + case m.RtmpSource && m.Proxy: + return errors.New("rtmp source and proxy can't be true at the same time") + case m.Live && m.RtmpSource: + if !conf.Conf.Server.Rtmp.Enable { + return errors.New("rtmp is not enabled") + } + case m.Live && m.Proxy: + if !settings.LiveProxy.Get() { + return errors.New("live proxy is not enabled") + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if !settings.AllowProxyToLocal.Get() && utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + switch u.Scheme { + case "rtmp": + case "http", "https": + default: + return errors.New("unsupported scheme") + } + case !m.Live && m.RtmpSource: + return errors.New("rtmp source can't be true when movie is not live") + case !m.Live && m.Proxy: + if !settings.MovieProxy.Get() { + return errors.New("movie proxy is not enabled") + } + if m.VendorInfo.Vendor != "" { + return nil + } + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if !settings.AllowProxyToLocal.Get() && utils.IsLocalIP(u.Host) { + return errors.New("local ip is not allowed") + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") + } + case !m.Live && !m.Proxy, m.Live && !m.Proxy && !m.RtmpSource: + if m.VendorInfo.Vendor == "" { + u, err := url.Parse(m.Url) + if err != nil { + return err + } + if u.Scheme != "http" && u.Scheme != "https" { + return errors.New("unsupported scheme") + } + } + default: + return errors.New("unknown error") + } + return nil +} + +func (movie *Movie) validateVendorMovie() error { + m := movie.Base + switch m.VendorInfo.Vendor { + case model.StreamingVendorBilibili: + err := m.VendorInfo.Bilibili.Validate() + if err != nil { + return err + } + if m.Headers == nil { + m.Headers = map[string]string{ + "Referer": "https://www.bilibili.com", + "User-Agent": utils.UA, + } + } else { + m.Headers["Referer"] = "https://www.bilibili.com" + m.Headers["User-Agent"] = utils.UA + } + default: + return fmt.Errorf("vendor not support") + } + + return nil +} + func (m *Movie) Terminate() { m.lock.Lock() defer m.lock.Unlock() @@ -115,6 +209,7 @@ func (m *Movie) terminate() { m.channel.Close() m.channel = nil } + m.Cache.Clear() } func (m *Movie) Update(movie model.BaseMovie) error { diff --git a/internal/settings/var.go b/internal/settings/var.go index 6f17fae..be1682b 100644 --- a/internal/settings/var.go +++ b/internal/settings/var.go @@ -18,3 +18,18 @@ var ( DisableUserSignup = newBoolSetting("disable_user_signup", false, model.SettingGroupUser) SignupNeedReview = newBoolSetting("signup_need_review", false, model.SettingGroupUser) ) + +var ( + MovieProxy = newBoolSetting("movie_proxy", true, model.SettingGroupProxy) + LiveProxy = newBoolSetting("live_proxy", true, model.SettingGroupProxy) + AllowProxyToLocal = newBoolSetting("allow_proxy_to_local", false, model.SettingGroupProxy) +) + +var ( + // can watch live streams through the RTMP protocol (without authentication, insecure). + RtmpPlayer = newBoolSetting("rtmp_player", false, model.SettingGroupRtmp) + // default use http header host + CustomPublishHost = newStringSetting("custom_publish_host", "", model.SettingGroupRtmp) + // disguise the .ts file as a .png file + TsDisguisedAsPng = newBoolSetting("ts_disguised_as_png", true, model.SettingGroupRtmp) +) diff --git a/server/handlers/movie.go b/server/handlers/movie.go index e5cda2d..9964739 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -11,6 +11,7 @@ import ( "io" "math/rand" "net/http" + "net/url" "path" "strconv" "strings" @@ -22,13 +23,13 @@ import ( dbModel "github.com/synctv-org/synctv/internal/model" "github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/rtmp" + "github.com/synctv-org/synctv/internal/settings" "github.com/synctv-org/synctv/internal/vendor" pb "github.com/synctv-org/synctv/proto/message" "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/synctv-org/vendors/api/bilibili" "github.com/zencoder/go-dash/v3/mpd" - "github.com/zijiren233/gencontainer/refreshcache" "github.com/zijiren233/livelib/protocol/hls" "github.com/zijiren233/livelib/protocol/httpflv" ) @@ -239,6 +240,11 @@ func PushMovies(ctx *gin.Context) { } func NewPublishKey(ctx *gin.Context) { + if !conf.Conf.Server.Rtmp.Enable { + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled")) + return + } + room := ctx.MustGet("room").(*op.Room) user := ctx.MustGet("user").(*op.User) @@ -269,7 +275,7 @@ func NewPublishKey(ctx *gin.Context) { return } - host := conf.Conf.Server.Rtmp.CustomPublishHost + host := settings.CustomPublishHost.Get() if host == "" { host = ctx.Request.Host } @@ -464,6 +470,10 @@ func ChangeCurrentMovie(ctx *gin.Context) { } func ProxyMovie(ctx *gin.Context) { + if !settings.MovieProxy.Get() { + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("movie proxy is not enabled")) + return + } roomId := ctx.Param("roomId") if roomId == "" { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("roomId is empty")) @@ -492,15 +502,72 @@ func ProxyMovie(ctx *gin.Context) { return } - err = proxyURL(ctx, m.Base.Url, m.Base.Headers) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + switch m.Base.Type { + case "mpd": + mpdCache, err := m.Cache.InitOrLoadMPDCache(initDashCache(ctx, m.Movie), time.Minute*5) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) + return + } + mpd, err := mpdCache.Get() + if err != nil { + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) + return + } + ctx.Data(http.StatusOK, "application/dash+xml", []byte(mpd.MPDFile)) return + default: + err = proxyURL(ctx, m.Base.Url, m.Base.Headers) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + return + } + } +} + +// only cache mpd file +func initDashCache(ctx context.Context, movie *dbModel.Movie) func() (*dbModel.MPDCache, error) { + return func() (*dbModel.MPDCache, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, movie.Base.Url, nil) + if err != nil { + return nil, err + } + for k, v := range movie.Base.Headers { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", utils.UA) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + m, err := mpd.ReadFromString(string(b)) + if err != nil { + return nil, err + } + if len(m.BaseURL) != 0 && !path.IsAbs(m.BaseURL[0]) { + result, err := url.JoinPath(path.Dir(movie.Base.Url), m.BaseURL[0]) + if err != nil { + return nil, err + } + m.BaseURL = []string{result} + } + s, err := m.WriteToString() + if err != nil { + return nil, err + } + return &dbModel.MPDCache{ + MPDFile: s, + }, nil } } func proxyURL(ctx *gin.Context, u string, headers map[string]string) error { - if !conf.Conf.Proxy.AllowProxyToLocal { + if !settings.AllowProxyToLocal.Get() { if l, err := utils.ParseURLIsLocalIP(u); err != nil { return err } else if l { @@ -526,8 +593,8 @@ func proxyURL(ctx *gin.Context, u string, headers map[string]string) error { ctx.Header("Cache-Control", resp.Header.Get("Cache-Control")) ctx.Header("Content-Range", resp.Header.Get("Content-Range")) ctx.Status(resp.StatusCode) - _, err = io.Copy(ctx.Writer, resp.Body) - return err + io.Copy(ctx.Writer, resp.Body) + return nil } type FormatErrNotSupportFileType string @@ -544,11 +611,28 @@ func JoinLive(ctx *gin.Context) { fileExt := path.Ext(movieId) splitedMovieId := strings.Split(movieId, "/") channelName := strings.TrimSuffix(splitedMovieId[0], fileExt) - channel, err := room.GetChannel(channelName) + m, err := room.GetMovieByID(channelName) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) + return + } + if m.Base.RtmpSource && !conf.Conf.Server.Rtmp.Enable { + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("rtmp is not enabled")) + return + } else if m.Base.Live && !settings.LiveProxy.Get() { + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy is not enabled")) + return + } + channel, err := m.Channel() if err != nil { ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } + if channel == nil { + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorStringResp("channel is nil")) + return + } + switch fileExt { case ".flv": ctx.Header("Cache-Control", "no-store") @@ -560,7 +644,7 @@ func JoinLive(ctx *gin.Context) { ctx.Header("Cache-Control", "no-store") b, err := channel.GenM3U8File(func(tsName string) (tsPath string) { ext := "ts" - if conf.Conf.Server.Rtmp.TsDisguisedAsPng { + if settings.TsDisguisedAsPng.Get() { ext = "png" } return fmt.Sprintf("/api/movie/live/%s/%s.%s", channelName, tsName, ext) @@ -571,7 +655,7 @@ func JoinLive(ctx *gin.Context) { } ctx.Data(http.StatusOK, hls.M3U8ContentType, b) case ".ts": - if conf.Conf.Server.Rtmp.TsDisguisedAsPng { + if settings.TsDisguisedAsPng.Get() { ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt))) return } @@ -583,7 +667,7 @@ func JoinLive(ctx *gin.Context) { ctx.Header("Cache-Control", "public, max-age=90") ctx.Data(http.StatusOK, hls.TSContentType, b) case ".png": - if !conf.Conf.Server.Rtmp.TsDisguisedAsPng { + if !settings.TsDisguisedAsPng.Get() { ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt))) return } @@ -608,14 +692,19 @@ func JoinLive(ctx *gin.Context) { } } -func initBilibiliMPDCache(ctx context.Context, cli vendor.Bilibili, cookies []*http.Cookie, bvid string, cid, epid uint64, roomID, movieID string) *refreshcache.RefreshCache[*dbModel.MPDCache] { - return refreshcache.NewRefreshCache[*dbModel.MPDCache](func() (*dbModel.MPDCache, error) { +func initBilibiliMPDCache(ctx context.Context, roomID, movieID, CreatorID string, info *dbModel.BilibiliVendorInfo) func() (*dbModel.MPDCache, error) { + return func() (*dbModel.MPDCache, error) { + v, err := db.FirstOrInitVendorByUserIDAndVendor(CreatorID, dbModel.StreamingVendorBilibili) + if err != nil { + return nil, err + } + cli := vendor.BilibiliClient(info.VendorName) var m *mpd.MPD - if bvid != "" && cid != 0 { + if info.Bvid != "" && info.Cid != 0 { resp, err := cli.GetDashVideoURL(ctx, &bilibili.GetDashVideoURLReq{ - Cookies: utils.HttpCookieToMap(cookies), - Bvid: bvid, - Cid: cid, + Cookies: utils.HttpCookieToMap(v.Cookies), + Bvid: info.Bvid, + Cid: info.Cid, }) if err != nil { return nil, err @@ -624,10 +713,10 @@ func initBilibiliMPDCache(ctx context.Context, cli vendor.Bilibili, cookies []*h if err != nil { return nil, err } - } else if epid != 0 { + } else if info.Epid != 0 { resp, err := cli.GetDashPGCURL(ctx, &bilibili.GetDashPGCURLReq{ - Cookies: utils.HttpCookieToMap(cookies), - Epid: epid, + Cookies: utils.HttpCookieToMap(v.Cookies), + Epid: info.Epid, }) if err != nil { return nil, err @@ -661,26 +750,31 @@ func initBilibiliMPDCache(ctx context.Context, cli vendor.Bilibili, cookies []*h URLs: movies, MPDFile: s, }, nil - }, time.Minute*119) + } } -func initBilibiliShareCache(ctx context.Context, cli vendor.Bilibili, cookies []*http.Cookie, bvid string, cid, epid uint64) *refreshcache.RefreshCache[string] { - return refreshcache.NewRefreshCache[string](func() (string, error) { +func initBilibiliShareCache(ctx context.Context, CreatorID string, info *dbModel.BilibiliVendorInfo) func() (string, error) { + return func() (string, error) { + v, err := db.FirstOrInitVendorByUserIDAndVendor(CreatorID, dbModel.StreamingVendorBilibili) + if err != nil { + return "", err + } + cli := vendor.BilibiliClient(info.VendorName) var u string - if bvid != "" { + if info.Bvid != "" { resp, err := cli.GetVideoURL(ctx, &bilibili.GetVideoURLReq{ - Cookies: utils.HttpCookieToMap(cookies), - Bvid: bvid, - Cid: cid, + Cookies: utils.HttpCookieToMap(v.Cookies), + Bvid: info.Bvid, + Cid: info.Cid, }) if err != nil { return "", err } u = resp.Url - } else if epid != 0 { + } else if info.Epid != 0 { resp, err := cli.GetPGCURL(ctx, &bilibili.GetPGCURLReq{ - Cookies: utils.HttpCookieToMap(cookies), - Epid: epid, + Cookies: utils.HttpCookieToMap(v.Cookies), + Epid: info.Epid, }) if err != nil { return "", err @@ -690,19 +784,13 @@ func initBilibiliShareCache(ctx context.Context, cli vendor.Bilibili, cookies [] return "", errors.New("bvid and epid are empty") } return u, nil - }, time.Minute*119) + } } func proxyVendorMovie(ctx *gin.Context, movie *dbModel.Movie) { switch movie.Base.VendorInfo.Vendor { case dbModel.StreamingVendorBilibili: - bvc, err := movie.Base.VendorInfo.Bilibili.InitOrLoadMPDCache(func(info *dbModel.BilibiliVendorInfo) (*refreshcache.RefreshCache[*dbModel.MPDCache], error) { - v, err := db.FirstOrInitVendorByUserIDAndVendor(movie.CreatorID, dbModel.StreamingVendorBilibili) - if err != nil { - return nil, err - } - return initBilibiliMPDCache(ctx, vendor.BilibiliClient(info.VendorName), v.Cookies, info.Bvid, info.Cid, info.Epid, movie.RoomID, movie.ID), nil - }) + bvc, err := movie.Cache.InitOrLoadMPDCache(initBilibiliMPDCache(ctx, movie.RoomID, movie.ID, movie.CreatorID, movie.Base.VendorInfo.Bilibili), time.Minute*119) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return @@ -712,7 +800,6 @@ func proxyVendorMovie(ctx *gin.Context, movie *dbModel.Movie) { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - if id := ctx.Query("id"); id == "" { ctx.Data(http.StatusOK, "application/dash+xml", []byte(mpd.MPDFile)) return @@ -744,13 +831,7 @@ func parse2VendorMovie(ctx context.Context, userID string, movie *dbModel.Movie) switch movie.Base.VendorInfo.Vendor { case dbModel.StreamingVendorBilibili: if !movie.Base.Proxy { - c, err := movie.Base.VendorInfo.Bilibili.InitOrLoadURLCache(userID, func(info *dbModel.BilibiliVendorInfo) (*refreshcache.RefreshCache[string], error) { - v, err := db.FirstOrInitVendorByUserIDAndVendor(userID, dbModel.StreamingVendorBilibili) - if err != nil { - return nil, err - } - return initBilibiliShareCache(ctx, vendor.BilibiliClient(info.VendorName), v.Cookies, info.Bvid, info.Cid, info.Epid), nil - }) + c, err := movie.Cache.InitOrLoadURLCache(userID, initBilibiliShareCache(ctx, movie.CreatorID, movie.Base.VendorInfo.Bilibili), time.Minute*119) if err != nil { return err } diff --git a/server/handlers/public.go b/server/handlers/public.go index 61d4318..e678099 100644 --- a/server/handlers/public.go +++ b/server/handlers/public.go @@ -2,19 +2,9 @@ package handlers import ( "github.com/gin-gonic/gin" - "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/server/model" ) func Settings(ctx *gin.Context) { - ctx.JSON(200, model.NewApiDataResp(gin.H{ - "rtmp": gin.H{ - "enable": conf.Conf.Server.Rtmp.Enable, - "rtmpPlayer": conf.Conf.Server.Rtmp.RtmpPlayer, - }, - "proxy": gin.H{ - "movieProxy": conf.Conf.Proxy.MovieProxy, - "liveProxy": conf.Conf.Proxy.LiveProxy, - }, - })) + ctx.JSON(200, model.NewApiDataResp(gin.H{})) }