{"action":"opened","issue":{"url":"https://api.github.com/repos/Syncplay/syncplay/issues/73","labels_url":"https://api.github.com/repos/Syncplay/syncplay/issues/73/labels{/name}","comments_url":"https://api.github.com/repos/Syncplay/syncplay/issues/73/comments","events_url":"https://api.github.com/repos/Syncplay/syncplay/issues/73/events","html_url":"https://github.com/Syncplay/syncplay/issues/73","id":105253201,"number":73,"title":"High-latency player connections can cause \"fighting\"","user":{"login":"tari","id":506181,"avatar_url":"https://avatars.githubusercontent.com/u/506181?v=3","gravatar_id":"","url":"https://api.github.com/users/tari","html_url":"https://github.com/tari","followers_url":"https://api.github.com/users/tari/followers","following_url":"https://api.github.com/users/tari/following{/other_user}","gists_url":"https://api.github.com/users/tari/gists{/gist_id}","starred_url":"https://api.github.com/users/tari/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tari/subscriptions","organizations_url":"https://api.github.com/users/tari/orgs","repos_url":"https://api.github.com/users/tari/repos","events_url":"https://api.github.com/users/tari/events{/privacy}","received_events_url":"https://api.github.com/users/tari/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-09-07T18:07:52Z","updated_at":"2015-09-07T18:07:52Z","closed_at":null,"body":"I've observed this behavior when working on Kodi/XBMC support, and I suspect #52 is related since the symptom (\"it actually does manage to pause the video, but the madVR user will start it immediately again\") is the same.\r\n\r\nMy implementation of control of Kodi involves connecting over HTTP, making a request and waiting for a response. Due to how the API is designed, most actions require two round-trips (a state query to get a necessary parameter value, then the actual command/query). As I've implemented it now, every query opens a new HTTP connection. The upshot of all this is that operations on a Kodi player tend to have high latency.\r\n\r\nWhere things start going wrong is when another user in the room performs an action on playback state (play, pause, seek). A state change arrives from the server and a command is dispatched to the player, but a preceding status check will usually complete first. This causes a conflicting state to be reported, and latency effects tend to make the player cycle between playing and paused states.\r\n\r\n(Performing blocking RPCs might be an easy fix but would tend to bog down the reactor a lot, so it's not a good solution.)\r\n\r\nRepresentative debug output:\r\n```\r\n[11:17:07] client/server << {\"State\": {\"ignoringOnTheFly\": {\"server\": 1}, \"playstate\": {\"paused\": true, \"position\": 2\r\n6.0, \"setBy\": \"None\", \"doSeek\": null}, \"ping\": {\"serverRtt\": 0.1529710292816162, \"clientLatencyCalculation\": 1441646227.429288, \"latencyCalculation\": 1441646227.505084}}}\r\n[11:17:07] <None> paused\r\n[11:17:07] client/server >> {\"State\": {\"ignoringOnTheFly\": {\"server\": 1}, \"ping\": {\"clientRtt\": 0.1514420509338379, \"clientLatencyCalculation\": 1441646227.581642, \"latencyCalculation\": 1441646227.505084}, \"playstate\": {\"paused\": false, \"position\": 27.008032083511353}}}\r\n[11:17:07] client/server << {\"State\": {\"ignoringOnTheFly\": {\"server\": 1}, \"playstate\": {\"paused\": false, \"position\":\r\n27.082831205780106, \"setBy\": \"None\", \"doSeek\": null}, \"ping\": {\"serverRtt\": 0.1451740264892578, \"clientLatencyCalculation\": 1441646227.581754, \"latencyCalculation\": 1441646227.65037}}}\r\n[11:17:07] <None> unpaused\r\n[11:17:07] client/server >> {\"State\": {\"ignoringOnTheFly\": {\"server\": 1}, \"ping\": {\"clientRtt\": 0.14456892013549805,\r\n\"clientLatencyCalculation\": 1441646227.727131, \"latencyCalculation\": 1441646227.65037}, \"playstate\": {\"paused\": false, \"position\": 27.05450201034546}}}\r\n[11:17:07] client/server >> {\"Set\": {\"ready\": {\"isReady\": false, \"manuallyInitiated\": false}}}\r\n[11:17:07] client/server >> {\"State\": {\"ignoringOnTheFly\": {\"client\": 1}, \"ping\": {\"clientRtt\": 0.14456892013549805, \"clientLatencyCalculation\": 1441646227.773196}, \"playstate\": {\"paused\": true, \"position\": 27.0}}}\r\n```\r\n\r\n---\r\n\r\nI believe enforced ordering of commands would solve this problem, which could be done by passing a cookie (perhaps just a timestamp) to most player queries. If on callback (for example to `client.updatePlayerStatus`) a cookie is older than the newest state-change command, the result is ignored.\r\n\r\nFor example, under normal operation:\r\n\r\n1. `player.askForStatus` with cookie 0\r\n2. `askForStatus` completes, passing cookie 0 back to `client.updatePlayerStatus`\r\n3. `askForStatus` with cookie 1\r\n4. ...\r\n\r\nHigh-latency with commands. Player is playing initially:\r\n\r\n1.` askForStatus(cookie=1)` begins a status request.\r\n2. `setPaused(True)` begins player state change request. Client notes that all cookies <= 1 are stale.\r\n3. `askForStatus(1)` completes, calls `updatePlayerStatus(cookie=1)`. Client recognizes stale cookie and ignores status.\r\n4. `askForStatus(cookie=2)`. This cookie is fresh.\r\n5. Player changes state to paused.\r\n6. `askForStatus(2)` completes, returning paused state. `updatePlayerStatus` notes state change.\r\n\r\n---\r\n\r\nI implemented something like this in my player interface and it seemed to help, but it's much cleaner (and benefits all other players) to implement this ordering cooperatively between the client and player modules."}}