mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-08-30 20:49:22 +00:00
feat(sync): add canonical room media state
This commit is contained in:
@@ -32,6 +32,27 @@ Ensures all peers are buffered and synchronized before resuming:
|
||||
> **Network Transit Buffer Rule**: The orchestrator (`background.js`) must always use a timeout at least 500ms longer than the worker (`content.js`) to account for IPC and network transit time. Never align them exactly 1:1, as this will introduce a race condition on slow connections.
|
||||
4. **Resume**: All peers call `play()` simultaneously.
|
||||
|
||||
## 3.1 Canonical Media State v1
|
||||
|
||||
The relay keeps one optional, in-memory canonical playback state per active room.
|
||||
Accepted `PLAY`, `PAUSE`, and `SEEK` commands advance a server-owned revision.
|
||||
Playing positions advance lazily from the server update time; paused positions do
|
||||
not. Heartbeats remain observational and do not mutate canonical state.
|
||||
|
||||
`ROOM_DATA` materializes the playing position at snapshot creation and advertises
|
||||
the optional `media-state-v1` capability. A joining/reconnecting client validates
|
||||
the room/revision, respects Host Control solo mode and Episode Lobby, then sends an
|
||||
internal `APPLY_CANONICAL_MEDIA_STATE` message to the existing content/video path.
|
||||
That path reuses frame election, Netflix/Disney page-API seeks, native play/pause,
|
||||
the 2-second drift tolerance, and programmatic-event suppression. The apply is
|
||||
one-shot recovery: it creates no action history, notification, command ACK, or
|
||||
relay media event.
|
||||
|
||||
Force Sync remains a two-phase ACK protocol. `PREPARE` is temporary choreography;
|
||||
the matching `EXECUTE` commits its validated target to canonical state. Per-sender
|
||||
`seq`, peer heartbeats, and the existing reconnect event queue remain separate.
|
||||
Offline media-command compaction is intentionally deferred.
|
||||
|
||||
## 4. Episode Auto-Sync
|
||||
Maintains continuous synchronized viewing when watching series:
|
||||
1. **Detection**: `content.js` monitors the Media Session API for title changes.
|
||||
|
||||
+58
-2
@@ -83,13 +83,67 @@ Payload:
|
||||
"hostPeerId": "string or null",
|
||||
"controlMode": "everyone | host-only",
|
||||
"controllers": ["peerId"],
|
||||
"capabilities": ["host-control", "co-host", "chat", "chat-v1"]
|
||||
"mediaState": "canonical media state object or null",
|
||||
"capabilities": ["host-control", "co-host", "chat", "chat-v1", "media-state-v1"]
|
||||
}
|
||||
```
|
||||
|
||||
`room_data` is sent to the joining socket. It is not the general broadcast used
|
||||
for every later room update.
|
||||
|
||||
## Canonical Media State v1
|
||||
|
||||
Relays advertise this optional recovery primitive with `"media-state-v1"` in
|
||||
`room_data.capabilities`. Each active room stores at most one state:
|
||||
|
||||
```json
|
||||
{
|
||||
"revision": 42,
|
||||
"playbackState": "playing",
|
||||
"currentTime": 1234.5,
|
||||
"updatedAt": 1787234425123,
|
||||
"updatedBy": "peer-id"
|
||||
}
|
||||
```
|
||||
|
||||
The internal `currentTime` is the media position at server-owned `updatedAt`.
|
||||
Playing state advances lazily when a snapshot is requested; paused state stays
|
||||
fixed. `revision`, `updatedAt`, and `updatedBy` are server-owned. Clients cannot
|
||||
spoof them. The wire snapshot contains the already-projected `currentTime`,
|
||||
`revision`, `playbackState`, and `updatedBy`, so clients never compare client and
|
||||
server wall clocks.
|
||||
|
||||
Only accepted, sanitized room controls update canonical state:
|
||||
|
||||
- `play` uses its valid `currentTime`, or an existing effective canonical position.
|
||||
- `pause` uses its valid `currentTime`, or freezes an existing effective position.
|
||||
- `seek` prefers `targetTime` (with `currentTime` compatibility) and preserves the
|
||||
established playback state.
|
||||
- `force_sync_prepare` records only temporary coordination state. Its matching
|
||||
`force_sync_execute` commits the prepared target as playing.
|
||||
|
||||
`peer_status` heartbeats are observations and never rewrite canonical intent.
|
||||
Per-sender `seq` still orders commands from one sender; canonical `revision`
|
||||
orders server-accepted room transitions. Neither replaces the other.
|
||||
|
||||
On join/reconnect, a capable extension applies a valid snapshot once through an
|
||||
extension-internal recovery message. Existing seek/page-API and native-event
|
||||
suppression prevent `play`, `pause`, or `seek` echoes. Pending recovery is scoped
|
||||
to the room/revision in `chrome.storage.session`, waits for the selected media
|
||||
target lifecycle, and is cleared on leave/switch. Intentional host-only guest
|
||||
desync and an active Episode Lobby take precedence over snapshot recovery.
|
||||
|
||||
Compatibility is additive: new clients use old behavior with a relay that omits
|
||||
the capability; old clients ignore the extra `room_data` field from a new relay.
|
||||
A new relay canonicalizes every accepted legacy `play`, `pause`, `seek`, and
|
||||
matching Force Sync command regardless of `join_room.clientCapabilities`, while
|
||||
relaying the established event names, payloads, and order unchanged. This makes
|
||||
server-first rollout safe: old clients populate recovery state without needing to
|
||||
understand or acknowledge it, and new clients consume it only when the relay
|
||||
advertises the capability. No protocol-version or minimum-version bump is
|
||||
required. Offline `play`/`pause`/`seek` compaction is planned separately and is
|
||||
not part of Media State v1.
|
||||
|
||||
## Ephemeral encrypted chat
|
||||
|
||||
Relays advertise chat support with `"chat-v1"` in `room_data.capabilities` and keep
|
||||
@@ -253,7 +307,9 @@ them with the same sanitized relay envelope as other room events, including
|
||||
|
||||
### `force_sync_execute`
|
||||
|
||||
Payload includes `targetTime`. In `host-only` mode, only controllers may send it.
|
||||
The current extension sends sequence/action metadata but no target; the relay uses
|
||||
the validated target retained from the matching `force_sync_prepare`. In
|
||||
`host-only` mode, only controllers may send it.
|
||||
The relay also allows a matching initiator's execute event after that initiator
|
||||
started the prepare step, even if their controller state changed before execute.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user