Skip to content

Commit 35958ab

Browse files
committed
fix: refactor live playback position calculation and update handling
1 parent a0765a7 commit 35958ab

1 file changed

Lines changed: 65 additions & 20 deletions

File tree

main.go

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -785,12 +785,10 @@ func (s *Server) handleReconnect(c *Client, payload []byte) {
785785
}
786786

787787
// Calculate live position for reconnect state
788+
nowMs := time.Now().UnixMilli()
788789
liveState := *room.State
789-
if liveState.IsPlaying {
790-
elapsed := time.Now().UnixMilli() - liveState.LastUpdate
791-
liveState.Position += elapsed
792-
}
793-
liveState.LastUpdate = time.Now().UnixMilli()
790+
liveState.Position = livePlaybackPosition(room.State, nowMs)
791+
liveState.LastUpdate = nowMs
794792

795793
isHost := room.Host == c
796794

@@ -886,6 +884,30 @@ func sanitizeString(s string, maxLen int) string {
886884
return s
887885
}
888886

887+
func livePlaybackPosition(state *RoomState, nowMs int64) int64 {
888+
if state == nil {
889+
return 0
890+
}
891+
892+
position := state.Position
893+
if position < 0 {
894+
position = 0
895+
}
896+
897+
if state.IsPlaying && state.LastUpdate > 0 {
898+
elapsed := nowMs - state.LastUpdate
899+
if elapsed > 0 {
900+
position += elapsed
901+
}
902+
}
903+
904+
if state.CurrentTrack != nil && state.CurrentTrack.Duration > 0 && position > state.CurrentTrack.Duration {
905+
return state.CurrentTrack.Duration
906+
}
907+
908+
return position
909+
}
910+
889911
func (s *Server) handleMessage(c *Client, data []byte) {
890912
// Decode message using protobuf codec
891913
msgType, payloadBytes, err := c.codec.Decode(data)
@@ -1362,15 +1384,16 @@ func (s *Server) handleApproveJoin(c *Client, payload []byte) {
13621384

13631385
// If there is a current track, immediately send buffer-complete + seek (+ play if host is playing)
13641386
if room.State.CurrentTrack != nil {
1387+
syncPosition := livePlaybackPosition(room.State, time.Now().UnixMilli())
13651388
joiningClient.sendMessage(s.logger, MsgTypeBufferComplete, BufferCompletePayload{TrackID: room.State.CurrentTrack.ID})
13661389
joiningClient.sendMessage(s.logger, MsgTypeSyncPlayback, PlaybackActionPayload{
13671390
Action: ActionSeek,
1368-
Position: room.State.Position,
1391+
Position: syncPosition,
13691392
})
13701393
if room.State.IsPlaying {
13711394
joiningClient.sendMessage(s.logger, MsgTypeSyncPlayback, PlaybackActionPayload{
13721395
Action: ActionPlay,
1373-
Position: room.State.Position,
1396+
Position: syncPosition,
13741397
})
13751398
}
13761399
}
@@ -1470,10 +1493,6 @@ func (s *Server) handlePlaybackAction(c *Client, payload []byte) {
14701493
return
14711494
}
14721495

1473-
// Update room state based on action
1474-
prevLastUpdate := room.State.LastUpdate
1475-
room.State.LastUpdate = time.Now().UnixMilli()
1476-
14771496
switch p.Action {
14781497
case ActionPlay:
14791498
// Block play if no track is set
@@ -1482,21 +1501,44 @@ func (s *Server) handlePlaybackAction(c *Client, payload []byte) {
14821501
c.sendError(s.logger, "no_track", "Cannot play without a track")
14831502
return
14841503
}
1504+
if p.Position < 0 {
1505+
c.sendError(s.logger, "invalid_position", "Position cannot be negative")
1506+
return
1507+
}
1508+
nowMs := time.Now().UnixMilli()
1509+
if room.State.CurrentTrack != nil && room.State.CurrentTrack.Duration > 0 && p.Position > room.State.CurrentTrack.Duration {
1510+
p.Position = room.State.CurrentTrack.Duration
1511+
}
14851512
room.State.IsPlaying = true
14861513
room.State.Position = p.Position
1487-
p.ServerTime = time.Now().UnixMilli()
1514+
room.State.LastUpdate = nowMs
1515+
p.ServerTime = nowMs
14881516

14891517
case ActionPause:
14901518
// Pause is always allowed
1519+
if p.Position < 0 {
1520+
c.sendError(s.logger, "invalid_position", "Position cannot be negative")
1521+
return
1522+
}
1523+
nowMs := time.Now().UnixMilli()
1524+
if room.State.CurrentTrack != nil && room.State.CurrentTrack.Duration > 0 && p.Position > room.State.CurrentTrack.Duration {
1525+
p.Position = room.State.CurrentTrack.Duration
1526+
}
14911527
room.State.IsPlaying = false
14921528
room.State.Position = p.Position
1529+
room.State.LastUpdate = nowMs
14931530

14941531
case ActionSeek:
14951532
if p.Position < 0 {
14961533
c.sendError(s.logger, "invalid_position", "Position cannot be negative")
14971534
return
14981535
}
1536+
nowMs := time.Now().UnixMilli()
1537+
if room.State.CurrentTrack != nil && room.State.CurrentTrack.Duration > 0 && p.Position > room.State.CurrentTrack.Duration {
1538+
p.Position = room.State.CurrentTrack.Duration
1539+
}
14991540
room.State.Position = p.Position
1541+
room.State.LastUpdate = nowMs
15001542

15011543
case ActionChangeTrack:
15021544
if p.TrackInfo == nil {
@@ -1525,6 +1567,7 @@ func (s *Server) handlePlaybackAction(c *Client, payload []byte) {
15251567
room.State.CurrentTrack = p.TrackInfo
15261568
room.State.Position = 0
15271569
room.State.IsPlaying = false
1570+
room.State.LastUpdate = time.Now().UnixMilli()
15281571

15291572
// For new tracks, always start at position 0
15301573
room.HostStartPosition = 0
@@ -1570,6 +1613,7 @@ func (s *Server) handlePlaybackAction(c *Client, payload []byte) {
15701613

15711614
case ActionSkipNext, ActionSkipPrev:
15721615
room.State.Position = 0
1616+
room.State.LastUpdate = time.Now().UnixMilli()
15731617

15741618
case ActionQueueAdd:
15751619
if p.TrackInfo == nil {
@@ -1660,7 +1704,6 @@ func (s *Server) handlePlaybackAction(c *Client, payload []byte) {
16601704
return
16611705
}
16621706
room.State.Volume = p.Volume
1663-
room.State.LastUpdate = prevLastUpdate
16641707

16651708
default:
16661709
c.sendError(s.logger, "unknown_action", fmt.Sprintf("Unknown action: %s", p.Action))
@@ -1712,16 +1755,17 @@ func (s *Server) handleBufferReady(c *Client, payload []byte) {
17121755
// If buffering is disabled for this room, respond per-client so late buffer_ready still receives SEEK/PLAY
17131756
if room.BufferingUsers == nil {
17141757
s.logger.Debug("Buffering disabled for room - per-client ACK", zap.String("room_code", room.Code), zap.String("user_id", c.ID))
1758+
syncPosition := livePlaybackPosition(room.State, time.Now().UnixMilli())
17151759
// Send buffer-complete and sync to this specific client so they will apply seek/play
17161760
c.sendMessage(s.logger, MsgTypeBufferComplete, BufferCompletePayload{TrackID: p.TrackID})
17171761
c.sendMessage(s.logger, MsgTypeSyncPlayback, PlaybackActionPayload{
17181762
Action: ActionSeek,
1719-
Position: room.State.Position,
1763+
Position: syncPosition,
17201764
})
17211765
if room.State.IsPlaying {
17221766
c.sendMessage(s.logger, MsgTypeSyncPlayback, PlaybackActionPayload{
17231767
Action: ActionPlay,
1724-
Position: room.State.Position,
1768+
Position: syncPosition,
17251769
})
17261770
}
17271771
return
@@ -1964,10 +2008,11 @@ func (s *Server) handleRequestSync(c *Client) {
19642008
defer room.mu.RUnlock()
19652009

19662010
// Calculate live position
1967-
currentPosition := room.State.Position
1968-
elapsed := time.Now().UnixMilli() - room.State.LastUpdate
1969-
if room.State.IsPlaying {
1970-
currentPosition += elapsed
2011+
nowMs := time.Now().UnixMilli()
2012+
currentPosition := livePlaybackPosition(room.State, nowMs)
2013+
elapsed := int64(0)
2014+
if room.State.IsPlaying && currentPosition > room.State.Position {
2015+
elapsed = currentPosition - room.State.Position
19712016
}
19722017

19732018
responsePlaying := room.State.IsPlaying
@@ -1985,7 +2030,7 @@ func (s *Server) handleRequestSync(c *Client) {
19852030
CurrentTrack: room.State.CurrentTrack,
19862031
IsPlaying: responsePlaying,
19872032
Position: currentPosition,
1988-
LastUpdate: time.Now().UnixMilli(),
2033+
LastUpdate: nowMs,
19892034
Volume: room.State.Volume,
19902035
})
19912036
}

0 commit comments

Comments
 (0)