Skip to content

Comments

Sync playback speed across room#754

Open
hex110 wants to merge 5 commits intoSyncplay:masterfrom
hex110:feature/speed-sync
Open

Sync playback speed across room#754
hex110 wants to merge 5 commits intoSyncplay:masterfrom
hex110:feature/speed-sync

Conversation

@hex110
Copy link

@hex110 hex110 commented Feb 14, 2026

Fixes #115 , #339 , #406

Summary

  • Adds playback speed as a synced room property, similar to pause and seek
  • When a user changes speed in their player, it propagates to all clients in the room
  • In controlled rooms, only operators can change speed
  • Slowdown-on-desync works relative to the room speed (e.g. room at 1.2x, slowdown = 1.2 * 0.95)
  • Dead-reckoning on both client and server uses speed for position extrapolation
  • Backward compatible: missing speed field defaults to 1.0 — old clients/servers work fine

Player Support

  • Supported: mpv, mpv.net, IINA, Memento, VLC, MPlayer
  • Unsupported: MPC-HC, MPC-BE (limitation: speedSupported = False in current API implementation)

Files Changed

  • server.py: Room/ControlledRoom store speed; Watcher propagates speed changes.
  • protocols.py: Added Speed field in State message (client + server).
  • client.py: Global speed state, dead-reckoning, slowdown relative to room speed.
  • players/*.py: Added thread-safe speed detection via properties/events for mpv, VLC, and MPlayer.
  • resources/lua/*: Updated Lua scripts to report speed/rate in status responses.
  • constants.py: DEFAULT_PLAYBACK_SPEED = 1.0
  • messages_en.py: Added speed-change-notification message.

Test plan

I have verified the following scenarios:

  • Basic Sync: Two mpv clients on local server — speed change on one syncs to the other.
  • Permissions: Controlled room — non-operator speed change is rejected.
  • Drift/Lag: Slowdown-on-desync calculates correctly relative to room speed.
  • Compatibility: Old client (no speed field) connects and defaults to 1.0 without errors.
  • Unit Tests: Added tests/test_speed_sync.py and all tests pass.

Didn't verify VLC or MPlayer as none of my friends use it and I'm not familiar with how they work at all, so I'd appreciate it if someone can verify these players as well.

@Et0h
Copy link
Contributor

Et0h commented Feb 14, 2026

Hi, I'm busy with other things at the moment it will take me some time to go through all this, but thanks for helping to progress this long-requested feature (and for you following Syncplay naming conventions, etc).

Based in part on my experience from shared playlists and a quick look at the code my initial thoughts in terms of checking to ensure things work as expected are (in no particular order):

  • Reliability on disconnect/reconnect (including room being temporarily empty and therefore deleted)
  • Reliability on joining and changing rooms to those with same/different speeds
  • Reliability when changing from one media file to another
  • Backwards/forwards compatibility with older/newer clients and servers (inc warning messages using chat as appropriate), inc. what happens for people using incompatible clients, potentially making use of the 'features' system
  • Compatibility with (mostly deprecated) offset feature
  • Whether the speed variable needs to be explicitly typed to a float in setSpeed
  • Ensuring speed-up/slowdown code does not improperly trigger 'speedChanged = speed is not None and speed != self._room.getSpeed()', and that code like "if self._detectedSpeed is not None and self._detectedSpeed != self._client.getGlobalSpeed():" doesn't get in the way of the speed-up/slowdown code, and that "if speed is not None and speed != self._globalSpeed: self._globalSpeed = speed" isn't triggered inappropriately when there is currently slowdown mode.
  • Investigate why _pendingSpeed is set but never used

In my recollection MPC-HC and MPC-BE do support variable speeds, it is just they sometimes had a short pause when the speed changed so using it to handle slowdown would be counterproductive. It might be possible to support speed changes on MPC-HC/BE just not using it for slowdown support - someone would need to test it.

At present your code uses the in-player speed change to trigger changes in speed. It might be better to get people to control speed via Syncplay GUI and CLI as I'm not sure how easy it is to reliably detect a manual change in speed compared to a change which might happen when you change file for example. However, if it is reliable then allowing it to change via media player would be in line with our ability for people to seek via the player.

@hex110
Copy link
Author

hex110 commented Feb 15, 2026

Thanks for the detailed review @Et0h! I've gone through your checklist systematically, added comprehensive unit tests (now totaling 51), and spent significant time smoothing out interactions between different player engines (specifically mpv and VLC).

Here is the status on the specific points you raised:

  1. Disconnect/Reconnect: Verified. Room speed resets correctly when non-persistent rooms are recreated. On reconnect, the client correctly picks up the room speed from the initial state update.
  2. Room Switching: Verified. Clients receive the new room's speed immediately via standard state updates. Transitions between rooms with identical speeds produce no spurious updates.
  3. File Changes: Speed is treated as a room-level property independent of the file. The Lua scripts report speed on every status poll, so any stale player-side state (e.g., if a player resets speed on file load) is overwritten within one poll cycle.
  4. Compatibility:
    • Backward: Missing speed fields default to None. Old clients ignore the field and don't change speed; old servers ignore the unknown field from new clients.
    • Forward: I haven't used the specific 'features' flag system yet as the graceful fallback seemed sufficient, but I can add it if you prefer explicit warning messages for mismatched versions.
  5. Offsets: Confirmed that Offset and Speed work as independent axes. Offset applies at the position set/get boundary, while speed affects the dead-reckoning calculation.
  6. Type Safety: JSON parsing preserves the float type, so it arrives correctly from the protocol. I added a test confirming that string input would break dead-reckoning to ensure we catch regressions.
  7. Slowdown/Speed Conflict: Fixed. You were right - this was a bug. When slowdown mode set the player to globalSpeed * SLOWDOWN_RATE, the player reported that rate back, which askForStatus misidentified as a manual user change. I fixed this by adding a not client._speedChanged guard to the speed detection in mpv, VLC, and MPlayer.
  8. Dead Code: Confirmed _pendingSpeed was never used. It has been removed.

Feedback Loops & Player Fixes

While validating the above, I tried the changes on VLC as well, and found and fixed several specific synchronization issues, particularly when syncing between mpv and VLC:

  • Float Precision Oscillation: VLC reports rates like 1.10011005 instead of 1.1. This caused infinite update loops where clients kept "correcting" each other. I implemented a SPEED_TOLERANCE = 0.01 constant and rounded detected speeds to 2 decimal places to stabilize this.
  • Speed Echo: When a client received a speed change from the server, the immediate next poll would sometimes detect the "old" speed before the player updated, triggering a revert. I added a 0.5s grace period that suppresses speed detection immediately after a setSpeed command.
  • VLC Hotkey Desync: VLC's hotkeys use a cached rate different from the playback rate. I updated syncplay.lua to set the rate on VLC's playlist object as well, ensuring hotkeys remain in sync with the room speed.

I have verified these scenarios with mpv <-> VLC cross-sync, fast consecutive changes, and lag/drift simulation.

@Et0h
Copy link
Contributor

Et0h commented Feb 15, 2026

Nice work. You now probably know some parts of the Syncplay code better than me.

Testing is very valuable so I appreciate you introducing a methodical approach to it.

Trying to deal with VLC quirks can be its own unique type of a headache and can require a bit of fudging - that's why we advise people to use mpv for the most reliable Syncplay experience.

I'll give some thought on how best to handle inter-version compatibility in terms of giving warnings if the server or other clients don't support speed changes. My inclination is to use the features feature as that is what I designed it for.

@hex110
Copy link
Author

hex110 commented Feb 15, 2026

Thanks for the quick replies!

Summary for the new commit:

  • Use the features system for speed sync inter-version compatibility
  • Enable MPC-HC/MPC-BE speed support (receive-only, no API for reading speed back)
  • Add GUI and CLI speed controls
{533E29CD-C5E9-4C5B-AE18-D0126ACF157B}

Test plan

  • All 59 unit tests pass
  • Verify warning message appears when connecting to an old server without speedSync feature
  • Verify GUI: Playback menu "Set Speed" dialog and playback frame speed input
  • Verify CLI: sp 1.5 sets speed, sp shows current speed
  • Verify MPC-HC receives room speed changes
  • Verify speed controls are disabled when server lacks speedSync

… sync

- Remove syncplay_speed_sync.7z binary from repository
- Extract hardcoded "Current speed" string to messages_en.py
- Downgrade speed-sync-not-supported message from error to debug
- Bump SPEED_SYNC_MIN_VERSION from 1.7.3 to 1.7.5
- Add updateSpeed() to GUI and console UI so speedInput reflects
  remote speed changes and local setSpeed() calls
- Update test to match new minimum version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] custom playback speed

2 participants