Feat: cache mpd file

pull/31/head
zijiren233 2 years ago
parent 1bcfcaebe0
commit eaca40b5a0

@ -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")
}

@ -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(),

@ -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,
}
}

@ -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,
},
}
}

@ -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)
}
}

@ -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 {

@ -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 {

@ -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)
)

@ -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
}

@ -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{}))
}

Loading…
Cancel
Save