fix seek issue

This commit is contained in:
2026-08-11 12:25:49 +02:00
parent d1a98c5da2
commit ab07373b68
5 changed files with 164 additions and 9 deletions
+7 -4
View File
@@ -59,10 +59,13 @@ playback continues locally and the event is logged; clients do not reconnect.
6. Any connected client receives the event and sends the corresponding JSON IPC 6. Any connected client receives the event and sends the corresponding JSON IPC
command to its local MPV instance. command to its local MPV instance.
Remote seeks use a serialized, acknowledged transaction: disable delivery of Remote seeks use a serialized transaction: disable delivery of the `seek`
the `seek` event for the IPC connection, apply an `absolute+exact` seek, then event for the IPC connection, apply an `absolute+exact` seek, wait for mpv's
re-enable the event even when the seek fails. This prevents remote seeks from `playback-restart` completion event, then re-enable the `seek` event even when
being reported as new local seeks and looping through the server. the operation fails. A successful command reply alone is not a completion
barrier because mpv can deliver seek effects afterward. Keeping delivery
disabled through completion prevents remote seeks from being reported as new
local seeks and looping through the server.
**Example cli command to run MPV** **Example cli command to run MPV**
+7 -4
View File
@@ -71,10 +71,13 @@ triggering ID to relayed events. Server and client terminals log play, pause,
and seek actions with that ID; the triggering client is marked with `(you)` in and seek actions with that ID; the triggering client is marked with `(you)` in
its own terminal. its own terminal.
Remote seeks are applied using an acknowledged mpv IPC transaction: seek event Remote seeks are applied using an mpv IPC transaction: seek event delivery is
delivery is disabled for this IPC connection, an exact absolute seek is sent, disabled for this IPC connection, an exact absolute seek is sent, and the
and event delivery is restored even if the seek fails. This prevents a remote client waits for mpv's `playback-restart` completion event before restoring
seek from being mistaken for a new local seek and sent back indefinitely. seek event delivery. A command reply can arrive before the seek effects, so it
is not sufficient as the end of the suppression window. Event delivery is
still restored if the operation fails. This prevents a remote seek from being
mistaken for a new local seek and sent back indefinitely.
## Prototype limitations ## Prototype limitations
+104
View File
@@ -0,0 +1,104 @@
# seek issue
When seeking (when video is paused, or being played) the video stutters and has clearly some feedback loop between clients.
Analyze the code and find a fix.
# client output
```sh
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.848 seconds
client: client 2 (you) played
client: client 1 sought to 1766.848 seconds
client: client 2 (you) sought to 1766.890 seconds
client: client 1 sought to 1766.932 seconds
client: client 2 (you) sought to 1766.974 seconds
client: client 1 sought to 1767.015 seconds
client: client 2 (you) sought to 1767.057 seconds
client: client 1 sought to 1767.099 seconds
client: client 2 (you) sought to 1767.140 seconds
client: client 1 sought to 1767.182 seconds
client: client 2 (you) sought to 1767.224 seconds
client: client 1 sought to 1767.266 seconds
client: client 2 (you) sought to 1767.307 seconds
client: client 1 sought to 1767.349 seconds
client: client 2 (you) paused
client: client 2 (you) played
client: client 2 (you) paused
client: client 2 (you) played
client: client 2 (you) paused
client: mpv IPC connection closed
client: server connection lost (mpv exited); playback will continue
client: mpv exited with status 15
```
# server output
```sh
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.848 seconds
server: client 2 played
server: client 1 sought to 1766.848 seconds
server: client 2 sought to 1766.890 seconds
server: client 1 sought to 1766.932 seconds
server: client 2 sought to 1766.974 seconds
server: client 1 sought to 1767.015 seconds
server: client 2 sought to 1767.057 seconds
server: client 1 sought to 1767.099 seconds
server: client 2 sought to 1767.140 seconds
server: client 1 sought to 1767.182 seconds
server: client 2 sought to 1767.224 seconds
server: client 1 sought to 1767.266 seconds
server: client 2 sought to 1767.307 seconds
server: client 1 sought to 1767.349 seconds
server: client 2 paused
server: client 2 played
server: client 2 paused
server: client 2 played
server: client 2 paused
server: client 2 disconnected (connection closed)
server: client 1 disconnected (connection closed)
```
+36 -1
View File
@@ -26,6 +26,8 @@ Mpv_Connection :: struct {
pending_id: i64, pending_id: i64,
pending_done: bool, pending_done: bool,
pending_result: Mpv_Command_Result, pending_result: Mpv_Command_Result,
remote_seek_waiting: bool,
remote_seek_completed: bool,
closed: bool, closed: bool,
client: ^Client_State, client: ^Client_State,
@@ -136,10 +138,35 @@ mpv_apply_remote_seek :: proc(mpv: ^Mpv_Connection, position: f64) -> bool {
seek_command := fmt.aprintf(`["seek",%.6f,"absolute+exact"]`, position) seek_command := fmt.aprintf(`["seek",%.6f,"absolute+exact"]`, position)
defer delete(seek_command) defer delete(seek_command)
sync.mutex_lock(&mpv.response_mutex)
mpv.remote_seek_waiting = true
mpv.remote_seek_completed = false
sync.mutex_unlock(&mpv.response_mutex)
seek_result := mpv_command_locked(mpv, seek_command) seek_result := mpv_command_locked(mpv, seek_command)
seek_completed := false
sync.mutex_lock(&mpv.response_mutex)
if seek_result.success {
deadline_remaining := MPV_COMMAND_TIMEOUT
for !mpv.remote_seek_completed && !mpv.closed {
start := time.now()
if !sync.cond_wait_with_timeout(&mpv.response_cond, &mpv.response_mutex, deadline_remaining) {
break
}
elapsed := time.since(start)
if elapsed >= deadline_remaining {
break
}
deadline_remaining -= elapsed
}
seek_completed = mpv.remote_seek_completed
}
mpv.remote_seek_waiting = false
sync.mutex_unlock(&mpv.response_mutex)
enable_result := mpv_command_locked(mpv, `["enable_event","seek"]`) enable_result := mpv_command_locked(mpv, `["enable_event","seek"]`)
reenabled = enable_result.success reenabled = enable_result.success
return seek_result.success && enable_result.success return seek_result.success && seek_completed && enable_result.success
} }
mpv_set_pause :: proc(mpv: ^Mpv_Connection, paused: bool) -> bool { mpv_set_pause :: proc(mpv: ^Mpv_Connection, paused: bool) -> bool {
@@ -223,6 +250,14 @@ mpv_handle_line :: proc(mpv: ^Mpv_Connection, line: string) {
if !event_ok { if !event_ok {
return return
} }
if string(event_name) == "playback-restart" {
sync.mutex_lock(&mpv.response_mutex)
if mpv.remote_seek_waiting {
mpv.remote_seek_completed = true
sync.cond_broadcast(&mpv.response_cond)
}
sync.mutex_unlock(&mpv.response_mutex)
}
if mpv.client != nil { if mpv.client != nil {
client_on_mpv_event(mpv.client, string(event_name), object) client_on_mpv_event(mpv.client, string(event_name), object)
} }
+10
View File
@@ -11,6 +11,7 @@ Fake_Mpv :: struct {
fd: posix.FD, fd: posix.FD,
received: [3]string, received: [3]string,
fail_seek: bool, fail_seek: bool,
enable_before_restart: bool,
} }
fake_mpv_read_line :: proc(fd: posix.FD, buffer: []byte) -> (line: string, ok: bool) { fake_mpv_read_line :: proc(fd: posix.FD, buffer: []byte) -> (line: string, ok: bool) {
@@ -46,6 +47,14 @@ fake_mpv_loop :: proc(data: rawptr) {
) )
_ = mpv_send_all(fake.fd, response) _ = mpv_send_all(fake.fd, response)
delete(response) delete(response)
if index == 1 && error_name == "success" {
poll_fd := posix.pollfd {
fd = fake.fd,
events = {.IN},
}
fake.enable_before_restart = posix.poll(&poll_fd, 1, 50) > 0
_ = mpv_send_all(fake.fd, "{\"event\":\"playback-restart\"}\n")
}
} }
} }
@@ -83,6 +92,7 @@ remote_seek_masks_and_restores_seek_events :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(fake.received[0], `"disable_event","seek"`)) testing.expect(t, strings.contains(fake.received[0], `"disable_event","seek"`))
testing.expect(t, strings.contains(fake.received[1], `"seek",42.500000,"absolute+exact"`)) testing.expect(t, strings.contains(fake.received[1], `"seek",42.500000,"absolute+exact"`))
testing.expect(t, strings.contains(fake.received[2], `"enable_event","seek"`)) testing.expect(t, strings.contains(fake.received[2], `"enable_event","seek"`))
testing.expect(t, !fake.enable_before_restart)
for line in fake.received { for line in fake.received {
delete(line) delete(line)
} }