feat: store movie current status

main v0.9.7-beta.5
zijiren233 2 weeks ago
parent e60a15d3fb
commit 14c59e9fb2

@ -184,3 +184,14 @@ func SetRoomStatusByCreator(userID string, status model.RoomStatus) error {
result := db.Model(&model.Room{}).Where("creator_id = ?", userID).Update("status", status)
return HandleUpdateResult(result, ErrRoomNotFound)
}
func SetRoomCurrent(roomID string, current *model.Current) error {
r := &model.Room{
Current: current,
}
result := db.Model(r).
Where("id = ?", roomID).
Select("Current").
Updates(r)
return HandleUpdateResult(result, ErrRoomNotFound)
}

@ -15,7 +15,7 @@ type dbVersion struct {
NextVersion string
}
const CurrentVersion = "0.0.12"
const CurrentVersion = "0.0.13"
var models = []any{
new(model.Setting),
@ -82,6 +82,9 @@ var dbVersions = map[string]dbVersion{
NextVersion: "0.0.12",
},
"0.0.12": {
NextVersion: "0.0.13",
},
"0.0.13": {
NextVersion: "",
},
}

@ -0,0 +1,91 @@
package model
import "time"
type Current struct {
Movie CurrentMovie `json:"movie"`
Status Status `json:"status"`
}
type CurrentMovie struct {
ID string `json:"id,omitempty"`
IsLive bool `json:"isLive,omitempty"`
SubPath string `json:"subPath,omitempty"`
}
type Status struct {
LastUpdate time.Time `json:"lastUpdate,omitempty"`
CurrentTime float64 `json:"currentTime,omitempty"`
PlaybackRate float64 `json:"playbackRate,omitempty"`
IsPlaying bool `json:"isPlaying,omitempty"`
}
func NewStatus() Status {
return Status{
CurrentTime: 0,
PlaybackRate: 1.0,
LastUpdate: time.Now(),
}
}
func (c *Current) UpdateStatus() Status {
if c.Movie.IsLive {
c.Status.LastUpdate = time.Now()
return c.Status
}
if c.Status.IsPlaying {
c.Status.CurrentTime += time.Since(c.Status.LastUpdate).Seconds() * c.Status.PlaybackRate
}
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) setLiveStatus() Status {
c.Status.IsPlaying = true
c.Status.PlaybackRate = 1.0
c.Status.CurrentTime = 0
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
c.Status.IsPlaying = playing
c.Status.PlaybackRate = rate
if playing {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.PlaybackRate = rate
c.Status.LastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeek(seek, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * c.Status.PlaybackRate)
} else {
c.Status.CurrentTime = seek
}
c.Status.LastUpdate = time.Now()
return c.Status
}

@ -41,6 +41,7 @@ type Room struct {
RoomMembers []*RoomMember `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Status RoomStatus `gorm:"not null;default:2"`
Current *Current `gorm:"serializer:fastjson"`
}
func (r *Room) BeforeCreate(tx *gorm.DB) error {

@ -2,144 +2,76 @@ package op
import (
"sync"
"time"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/model"
)
type current struct {
current Current
roomID string
current model.Current
lock sync.RWMutex
}
type Current struct {
Movie CurrentMovie
Status Status
}
type CurrentMovie struct {
ID string
IsLive bool
}
func newCurrent() *current {
return &current{
current: Current{
Status: newStatus(),
},
func newCurrent(roomID string, c *model.Current) *current {
if c == nil {
return &current{
roomID: roomID,
current: model.Current{
Status: model.NewStatus(),
},
}
}
}
type Status struct {
lastUpdate time.Time `json:"-"`
CurrentTime float64 `json:"currentTime"`
PlaybackRate float64 `json:"playbackRate"`
IsPlaying bool `json:"isPlaying"`
}
func newStatus() Status {
return Status{
CurrentTime: 0,
PlaybackRate: 1.0,
lastUpdate: time.Now(),
return &current{
roomID: roomID,
current: *c,
}
}
func (c *current) Current() Current {
func (c *current) Current() model.Current {
c.lock.RLock()
defer c.lock.RUnlock()
c.current.UpdateStatus()
return c.current
}
func (c *current) SetMovie(movie CurrentMovie, play bool) {
func (c *current) SubPath() string {
c.lock.RLock()
defer c.lock.RUnlock()
return c.current.Movie.SubPath
}
func (c *current) SetMovie(movie model.CurrentMovie, play bool) {
c.lock.Lock()
defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
c.current.Movie = movie
c.current.SetSeek(0, 0)
c.current.Status.IsPlaying = play
}
func (c *current) Status() Status {
func (c *current) Status() model.Status {
c.lock.RLock()
defer c.lock.RUnlock()
c.current.UpdateStatus()
return c.current.Status
}
func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) *Status {
func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) *model.Status {
c.lock.Lock()
defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
s := c.current.SetStatus(playing, seek, rate, timeDiff)
return &s
}
func (c *current) SetSeekRate(seek, rate, timeDiff float64) *Status {
func (c *current) SetSeekRate(seek, rate, timeDiff float64) *model.Status {
c.lock.Lock()
defer c.lock.Unlock()
defer db.SetRoomCurrent(c.roomID, &c.current)
s := c.current.SetSeekRate(seek, rate, timeDiff)
return &s
}
func (c *Current) UpdateStatus() Status {
if c.Movie.IsLive {
c.Status.lastUpdate = time.Now()
return c.Status
}
if c.Status.IsPlaying {
c.Status.CurrentTime += time.Since(c.Status.lastUpdate).Seconds() * c.Status.PlaybackRate
}
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) setLiveStatus() Status {
c.Status.IsPlaying = true
c.Status.PlaybackRate = 1.0
c.Status.CurrentTime = 0
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetStatus(playing bool, seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
c.Status.IsPlaying = playing
c.Status.PlaybackRate = rate
if playing {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeekRate(seek, rate, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * rate)
} else {
c.Status.CurrentTime = seek
}
c.Status.PlaybackRate = rate
c.Status.lastUpdate = time.Now()
return c.Status
}
func (c *Current) SetSeek(seek, timeDiff float64) Status {
if c.Movie.IsLive {
return c.setLiveStatus()
}
if c.Status.IsPlaying {
c.Status.CurrentTime = seek + (timeDiff * c.Status.PlaybackRate)
} else {
c.Status.CurrentTime = seek
}
c.Status.lastUpdate = time.Now()
return c.Status
}

@ -26,16 +26,16 @@ import (
)
type Movie struct {
room *Room
*model.Movie
channel atomic.Pointer[rtmps.Channel]
alistCache atomic.Pointer[cache.AlistMovieCache]
bilibiliCache atomic.Pointer[cache.BilibiliMovieCache]
embyCache atomic.Pointer[cache.EmbyMovieCache]
subPath string
}
func (m *Movie) SubPath() string {
return m.subPath
return m.room.CurrentSubPath()
}
func (m *Movie) ExpireID(ctx context.Context) (uint64, error) {
@ -99,7 +99,7 @@ func (m *Movie) ClearCache() error {
func (m *Movie) AlistCache() *cache.AlistMovieCache {
c := m.alistCache.Load()
if c == nil {
c = cache.NewAlistMovieCache(m.Movie, m.subPath)
c = cache.NewAlistMovieCache(m.Movie, m.SubPath())
if !m.alistCache.CompareAndSwap(nil, c) {
return m.AlistCache()
}
@ -121,7 +121,7 @@ func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache {
func (m *Movie) EmbyCache() *cache.EmbyMovieCache {
c := m.embyCache.Load()
if c == nil {
c = cache.NewEmbyMovieCache(m.Movie, m.subPath)
c = cache.NewEmbyMovieCache(m.Movie, m.SubPath())
if !m.embyCache.CompareAndSwap(nil, c) {
return m.EmbyCache()
}

@ -14,12 +14,14 @@ import (
type movies struct {
roomID string
room *Room
cache rwmap.RWMap[string, *Movie]
}
func (m *movies) AddMovie(mo *model.Movie) error {
mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{
room: m.room,
Movie: mo,
}
@ -45,6 +47,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
for _, mo := range mos {
mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{
room: m.room,
Movie: mo,
}
@ -184,7 +187,10 @@ func (m *movies) GetMovieByID(id string) (*Movie, error) {
if err != nil {
return nil, err
}
mm, _ = m.cache.LoadOrStore(mv.ID, &Movie{Movie: mv})
mm, _ = m.cache.LoadOrStore(mv.ID, &Movie{
room: m.room,
Movie: mv,
})
return mm, nil
}

@ -416,12 +416,12 @@ func (r *Room) GetMovieByID(id string) (*Movie, error) {
return r.movies.GetMovieByID(id)
}
func (r *Room) Current() *Current {
func (r *Room) Current() *model.Current {
c := r.current.Current()
return &c
}
func (r *Room) CurrentMovie() CurrentMovie {
func (r *Room) CurrentMovie() model.CurrentMovie {
return r.current.current.Movie
}
@ -460,7 +460,7 @@ func (r *Room) SetCurrentMovie(movieID string, subPath string, play bool) error
}
}
if movieID == "" {
r.current.SetMovie(CurrentMovie{}, false)
r.current.SetMovie(model.CurrentMovie{}, false)
return nil
}
m, err := r.GetMovieByID(movieID)
@ -470,14 +470,22 @@ func (r *Room) SetCurrentMovie(movieID string, subPath string, play bool) error
if m.IsFolder && !m.IsDynamicFolder() {
return errors.New("cannot set static folder as current movie")
}
m.subPath = subPath
r.current.SetMovie(CurrentMovie{
ID: m.ID,
IsLive: m.Live,
r.current.SetMovie(model.CurrentMovie{
ID: m.ID,
IsLive: m.Live,
SubPath: subPath,
}, play)
return m.ClearCache()
}
func (r *Room) CurrentSubPath() string {
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
fmt.Println("CurrentSubPath", r.current)
return r.current.SubPath()
}
func (r *Room) SwapMoviePositions(id1, id2 string) error {
return r.movies.SwapMoviePositions(id1, id2)
}
@ -512,11 +520,11 @@ func (r *Room) UserOnlineCount(userID string) int {
return r.lazyInitHub().OnlineCount(userID)
}
func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *Status {
func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *model.Status {
return r.current.SetStatus(playing, seek, rate, timeDiff)
}
func (r *Room) SetCurrentSeekRate(seek float64, rate float64, timeDiff float64) *Status {
func (r *Room) SetCurrentSeekRate(seek float64, rate float64, timeDiff float64) *model.Status {
return r.current.SetSeekRate(seek, rate, timeDiff)
}

@ -54,11 +54,14 @@ func LoadOrInitRoom(room *model.Room) (*RoomEntry, error) {
return nil, err
}
i, _ := roomCache.LoadOrStore(room.ID, &Room{
r := &Room{
Room: *room,
current: newCurrent(),
current: newCurrent(room.ID, room.Current),
movies: &movies{roomID: room.ID},
}, time.Duration(settings.RoomTTL.Get())*time.Hour)
}
r.movies.room = r
i, _ := roomCache.LoadOrStore(room.ID, r, time.Duration(settings.RoomTTL.Get())*time.Hour)
return i, nil
}

@ -509,7 +509,7 @@ func (u *User) GetRoomMoviesWithPage(room *Room, keyword string, page, pageSize
return room.GetMoviesWithPage(keyword, page, pageSize, parentID)
}
func (u *User) SetRoomCurrentStatus(room *Room, playing bool, seek, rate, timeDiff float64) (*Status, error) {
func (u *User) SetRoomCurrentStatus(room *Room, playing bool, seek, rate, timeDiff float64) (*model.Status, error) {
if !u.HasRoomPermission(room, model.PermissionSetCurrentStatus) {
return nil, model.ErrNoPermission
}

@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/model"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
pb "github.com/synctv-org/synctv/proto/message"
@ -449,7 +450,7 @@ func handleCheckStatusMessage(cli *op.Client, msg *pb.Message, timeDiff float64)
return nil
}
func needsSync(clientStatus *pb.Status, serverStatus op.Status, timeDiff float64) bool {
func needsSync(clientStatus *pb.Status, serverStatus model.Status, timeDiff float64) bool {
if clientStatus.IsPlaying != serverStatus.IsPlaying ||
clientStatus.PlaybackRate != serverStatus.PlaybackRate ||
serverStatus.CurrentTime+maxInterval < clientStatus.CurrentTime+timeDiff ||
@ -468,7 +469,7 @@ func sendErrorMessage(c *op.Client, errorMsg string) error {
})
}
func sendSyncStatus(cli *op.Client, status *op.Status) error {
func sendSyncStatus(cli *op.Client, status *model.Status) error {
return cli.Send(&pb.Message{
Type: pb.MessageType_CHECK_STATUS,
Payload: &pb.Message_PlaybackStatus{

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin"
json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/utils"
)
@ -206,9 +205,9 @@ type Movie struct {
}
type CurrentMovieResp struct {
Movie *Movie `json:"movie"`
Status op.Status `json:"status"`
ExpireID uint64 `json:"expireId"`
Movie *Movie `json:"movie"`
Status model.Status `json:"status"`
ExpireID uint64 `json:"expireId"`
}
type ClearMoviesReq struct {

@ -1 +1 @@
Subproject commit 96adf55ec2b4eb84ad890c47db25bccb548ea725
Subproject commit 6f2411e004a7957c4be8e2f7d5129b42362fc896
Loading…
Cancel
Save