mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
* fix(playback): seek Embedded MPV steps relative to mpv's own position Arrow keys and the ±10 s buttons in the Embedded MPV player advanced only about a second per press when pressed repeatedly or held. The shortcuts already asked for 5 s steps, but `EmbeddedMpvCommandRunner.seekBy` turned each step into an absolute `seek` computed from `session.positionSeconds`, which is floored to whole seconds, polled every 500 ms (helper snapshots at most every 250 ms) and not refreshed by the seek reply. Every press inside that window therefore landed on the same target. Steps now go through a new `EMBEDDED_MPV_SEEK_BY` IPC / `seekEmbeddedMpvBy` bridge method that every backend forwards as mpv `seek <delta> relative+exact`: `seekBy` exports in the macOS addon and the Windows/Linux `wid` addon (Linux over its JSON IPC socket), and a `seek-by` stdin command in the frame-copy helper. mpv resolves the delta against its own position and merges queued relative seeks, so presses accumulate as in mpv itself. The absolute form survives only as a fallback for a preload without the method or an addon binary without `seekBy`; the timeline scrub still commits an absolute target. Validated with a real mpv 0.39 IPC probe: three relative seeks in a burst advance +15 s, three absolute seeks from one stale base advance +5 s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(playback): drop speculative position update from relative Embedded MPV seeks Review follow-up for the relative seek path. The macOS and Windows/Linux `seekBy` exports advanced `snapshot.positionSeconds` by the delta after dispatching the mpv command. That is not idempotent the way the absolute seek's optimistic write is: the observer (mpv event thread, or the Linux IPC poll) can already have stored the post-seek `time-pos` under the same mutex, so adding the delta on top counted the step twice, and while paused nothing corrected it. On Linux it also advertised a position that a failed socket delivery never reached. Relative steps now leave the snapshot alone; only the observed `time-pos` updates the position. The packaged Linux frame-copy smoke now drives `seekEmbeddedMpvBy` through the built app: a burst of three +2 s steps issued without waiting for snapshots has to land on 6 s, and a -60 s step has to clamp at 0. The generated Y4M fixture grows from 2 s to 12 s (about 415 KB) so the burst and the playing section that follows stay inside the clip. Replayed against a local mpv 0.39 with the same fixture and media server: burst -> 6.0, -60 -> 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(agents): mirror the Embedded MPV relative-seek contract into AGENTS.md Review follow-up: the Shared Player Controls section documents the frame-copy commands and shortcuts, so the relative seekEmbeddedMpvBy invariant lives there too, next to the CLAUDE.md note. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(playback): reject a Linux relative seek the mpv IPC socket did not accept Review follow-up: the Linux branch of SeekBy discarded the socket transaction result and returned normally, so a step that never reached mpv looked like a seek still awaiting observation. It now throws like a failed mpv_command_async on the in-process engines; the renderer swallows the rejection and resyncs from the next snapshot, and the main process logs it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
147 lines
6.8 KiB
TypeScript
147 lines
6.8 KiB
TypeScript
// EPG related commands
|
|
export const EPG_FETCH = 'EPG:FETCH';
|
|
export const EPG_FETCH_DONE = 'EPG:FETCH_DONE';
|
|
export const EPG_ERROR = 'EPG:ERROR';
|
|
export const EPG_GET_CHANNELS = 'EPG:GET_CHANNELS';
|
|
export const EPG_GET_CHANNELS_DONE = 'EPG:GET_CHANNELS_DONE';
|
|
export const EPG_GET_CHANNELS_BY_RANGE = 'EPG:EPG_GET_CHANNELS_BY_RANGE';
|
|
export const EPG_GET_CHANNELS_BY_RANGE_RESPONSE =
|
|
'EPG:EPG_GET_CHANNELS_BY_RANGE_RESPONSE';
|
|
export const EPG_FORCE_FETCH = 'EPG:EPG_FORCE_FETCH';
|
|
|
|
// Playlist related commands
|
|
export const PLAYLIST_PARSE_BY_URL = 'PLAYLIST:PARSE_PLAYLIST_BY_URL';
|
|
export const PLAYLIST_UPDATE = 'PLAYLIST:UPDATE';
|
|
export const PLAYLIST_REFRESH = 'PLAYLIST:REFRESH';
|
|
export const PLAYLIST_REFRESH_EVENT = 'PLAYLIST:REFRESH_EVENT';
|
|
export const PLAYLIST_CANCEL_REFRESH = 'PLAYLIST:CANCEL_REFRESH';
|
|
|
|
// General
|
|
export const ERROR = 'ERROR';
|
|
/**
|
|
* Main -> renderer push carrying an `ElectronBridgePlaylistOpenRequest`: a
|
|
* playlist file the OS asked the app to open, either before the window existed
|
|
* (first launch with a path argument) or while it was already running (second
|
|
* launch, macOS `open-file`). This is the only channel requests leave the
|
|
* main-process queue through.
|
|
*/
|
|
export const OPEN_FILE = 'OPEN_FILE';
|
|
/**
|
|
* Renderer -> main invoke announcing that an `OPEN_FILE` listener is attached.
|
|
* Carries no payload: it only tells the queue where to push.
|
|
*/
|
|
export const ANNOUNCE_PLAYLIST_OPEN_LISTENER =
|
|
'announce-playlist-open-listener';
|
|
/**
|
|
* Renderer -> main invoke confirming a pushed request was received. Until it
|
|
* arrives the request stays in the queue, so a renderer that reloads or dies
|
|
* mid-delivery gets it replayed instead of losing it.
|
|
*/
|
|
export const ACKNOWLEDGE_PLAYLIST_OPEN_REQUEST =
|
|
'acknowledge-playlist-open-request';
|
|
|
|
// Views
|
|
export const VIEW_SETTINGS = 'VIEW:SETTINGS';
|
|
export const VIEW_ADD_PLAYLIST = 'VIEW:PLAYLISTS';
|
|
|
|
// Auto-update
|
|
export const AUTO_UPDATE_PLAYLISTS = 'AUTO_UPDATE';
|
|
export const AUTO_UPDATE_PLAYLISTS_RESPONSE = 'AUTO_UPDATE_RESPONSE';
|
|
|
|
// Application updates
|
|
export const APP_UPDATE_GET_STATUS = 'APP_UPDATE:GET_STATUS';
|
|
export const APP_UPDATE_CHECK = 'APP_UPDATE:CHECK';
|
|
export const APP_UPDATE_DOWNLOAD = 'APP_UPDATE:DOWNLOAD';
|
|
export const APP_UPDATE_INSTALL = 'APP_UPDATE:INSTALL';
|
|
export const APP_UPDATE_GET_RELEASE_NOTES = 'APP_UPDATE:GET_RELEASE_NOTES';
|
|
export const APP_UPDATE_STATUS_CHANGED = 'APP_UPDATE:STATUS_CHANGED';
|
|
|
|
// Experimental
|
|
export const OPEN_MPV_PLAYER = 'OPEN_MPV_PLAYER';
|
|
export const SET_MPV_PLAYER_PATH = 'SET_MPV_PLAYER_PATH';
|
|
export const SET_MPV_REUSE_INSTANCE = 'SET_MPV_REUSE_INSTANCE';
|
|
export const OPEN_VLC_PLAYER = 'OPEN_VLC_PLAYER';
|
|
export const SET_VLC_PLAYER_PATH = 'SET_VLC_PLAYER_PATH';
|
|
export const SET_VLC_REUSE_INSTANCE = 'SET_VLC_REUSE_INSTANCE';
|
|
export const CLOSE_EXTERNAL_PLAYER_SESSION = 'CLOSE_EXTERNAL_PLAYER_SESSION';
|
|
export const EXTERNAL_PLAYER_SESSION_UPDATE = 'EXTERNAL_PLAYER_SESSION_UPDATE';
|
|
export const EMBEDDED_MPV_SUPPORT = 'EMBEDDED_MPV_SUPPORT';
|
|
export const EMBEDDED_MPV_CREATE_SESSION = 'EMBEDDED_MPV_CREATE_SESSION';
|
|
export const EMBEDDED_MPV_LOAD_PLAYBACK = 'EMBEDDED_MPV_LOAD_PLAYBACK';
|
|
export const EMBEDDED_MPV_SET_BOUNDS = 'EMBEDDED_MPV_SET_BOUNDS';
|
|
export const EMBEDDED_MPV_SET_PAUSED = 'EMBEDDED_MPV_SET_PAUSED';
|
|
export const EMBEDDED_MPV_SEEK = 'EMBEDDED_MPV_SEEK';
|
|
export const EMBEDDED_MPV_SEEK_BY = 'EMBEDDED_MPV_SEEK_BY';
|
|
export const EMBEDDED_MPV_SET_VOLUME = 'EMBEDDED_MPV_SET_VOLUME';
|
|
export const EMBEDDED_MPV_SET_AUDIO_TRACK = 'EMBEDDED_MPV_SET_AUDIO_TRACK';
|
|
export const EMBEDDED_MPV_SET_SUBTITLE_TRACK =
|
|
'EMBEDDED_MPV_SET_SUBTITLE_TRACK';
|
|
export const EMBEDDED_MPV_ADD_SUBTITLE = 'EMBEDDED_MPV_ADD_SUBTITLE';
|
|
export const EMBEDDED_MPV_SET_SUBTITLE_DELAY =
|
|
'EMBEDDED_MPV_SET_SUBTITLE_DELAY';
|
|
export const EMBEDDED_MPV_SET_SUBTITLE_STYLE =
|
|
'EMBEDDED_MPV_SET_SUBTITLE_STYLE';
|
|
export const EMBEDDED_MPV_SELECT_SUBTITLE_FILE =
|
|
'EMBEDDED_MPV_SELECT_SUBTITLE_FILE';
|
|
export const EMBEDDED_MPV_SET_SPEED = 'EMBEDDED_MPV_SET_SPEED';
|
|
export const EMBEDDED_MPV_SET_ASPECT = 'EMBEDDED_MPV_SET_ASPECT';
|
|
export const EMBEDDED_MPV_START_RECORDING = 'EMBEDDED_MPV_START_RECORDING';
|
|
export const EMBEDDED_MPV_STOP_RECORDING = 'EMBEDDED_MPV_STOP_RECORDING';
|
|
export const EMBEDDED_MPV_GET_DEFAULT_RECORDING_FOLDER =
|
|
'EMBEDDED_MPV_GET_DEFAULT_RECORDING_FOLDER';
|
|
export const EMBEDDED_MPV_SELECT_RECORDING_FOLDER =
|
|
'EMBEDDED_MPV_SELECT_RECORDING_FOLDER';
|
|
export const EMBEDDED_MPV_DISPOSE_SESSION = 'EMBEDDED_MPV_DISPOSE_SESSION';
|
|
export const EMBEDDED_MPV_SESSION_UPDATE = 'EMBEDDED_MPV_SESSION_UPDATE';
|
|
export const EMBEDDED_MPV_PREPARE = 'EMBEDDED_MPV_PREPARE';
|
|
export const EMBEDDED_MPV_GET_FRAME_SOURCE = 'EMBEDDED_MPV_GET_FRAME_SOURCE';
|
|
export const EMBEDDED_MPV_FRAME_SOURCE_CHANGED =
|
|
'EMBEDDED_MPV_FRAME_SOURCE_CHANGED';
|
|
|
|
// Xtream
|
|
export const XTREAM_REQUEST = 'XTREAM_REQUEST';
|
|
export const XTREAM_RESPONSE = 'XTREAM_RESPONSE';
|
|
export const XTREAM_CANCEL_SESSION = 'XTREAM_CANCEL_SESSION';
|
|
|
|
// Stalker
|
|
export const STALKER_REQUEST = 'STALKER_REQUEST';
|
|
export const STALKER_RESPONSE = 'STALKER_RESPONSE';
|
|
export const PORTAL_DEBUG_EVENT = 'PORTAL_DEBUG_EVENT';
|
|
|
|
/**
|
|
* Forgets the main process' recorded connection failures for a portal host, so
|
|
* the next request contacts it for real. Sent whenever the user asks for a
|
|
* fresh attempt (portal retry, "test connection") or hands over a possibly
|
|
* different portal (endpoint discovery on import, edit, or lazy repair).
|
|
*/
|
|
export const CONNECTIVITY_GUARD_RESET = 'CONNECTIVITY_GUARD_RESET';
|
|
|
|
// Settings
|
|
export const SETTINGS_UPDATE = 'SETTINGS_UPDATE';
|
|
export const DELETE_ALL_PLAYLISTS = 'DELETE_ALL_PLAYLISTS';
|
|
|
|
// Remote Control
|
|
export const REMOTE_CONTROL_CHANGE_CHANNEL = 'REMOTE_CONTROL_CHANGE_CHANNEL';
|
|
|
|
// Display sleep: while a built-in video player is playing, the renderer asks
|
|
// the main process to hold a powerSaveBlocker so the screen stays awake
|
|
export const PLAYBACK_SET_KEEP_AWAKE = 'PLAYBACK:SET_KEEP_AWAKE';
|
|
|
|
// Window controls (custom title bar on Windows/Linux)
|
|
export const WINDOW_MINIMIZE = 'WINDOW:MINIMIZE';
|
|
export const WINDOW_TOGGLE_MAXIMIZE = 'WINDOW:TOGGLE_MAXIMIZE';
|
|
// OS-level window fullscreen toggle (F11). The exit path from a fullscreen
|
|
// launch on Windows/Linux, where the title bar is hidden and the custom
|
|
// window controls hide themselves while fullscreen.
|
|
export const WINDOW_TOGGLE_FULLSCREEN = 'WINDOW:TOGGLE_FULLSCREEN';
|
|
export const WINDOW_CLOSE = 'WINDOW:CLOSE';
|
|
export const WINDOW_GET_STATE = 'WINDOW:GET_STATE';
|
|
export const WINDOW_STATE_CHANGED = 'WINDOW:STATE_CHANGED';
|
|
|
|
// Close guard: while active, closing/quitting the app is intercepted in the
|
|
// main process and handed to the renderer for a save/discard/stay decision
|
|
export const WINDOW_SET_CLOSE_GUARD = 'WINDOW:SET_CLOSE_GUARD';
|
|
export const WINDOW_CONFIRM_CLOSE = 'WINDOW:CONFIRM_CLOSE';
|
|
export const WINDOW_CANCEL_CLOSE = 'WINDOW:CANCEL_CLOSE';
|
|
export const WINDOW_CLOSE_REQUESTED = 'WINDOW:CLOSE_REQUESTED';
|