Compare commits

...

3 Commits

Author SHA1 Message Date
Benedict Xavier
ab93e088da Merge pull request #176 from xodexa/self-fix 2025-12-30 09:28:59 +03:00
Zen
bedd345eb8 Merge branch 'master' into self-fix 2025-12-30 12:43:28 +08:00
Zen
4dc15eec50 fix: IPC socket for windows 2025-12-30 12:39:45 +08:00

View File

@@ -5,6 +5,7 @@ This provides advanced features like episode navigation, quality switching, and
import json import json
import logging import logging
import os
import socket import socket
import subprocess import subprocess
import tempfile import tempfile
@@ -43,7 +44,7 @@ class MPVIPCClient:
def __init__(self, socket_path: str): def __init__(self, socket_path: str):
self.socket_path = socket_path self.socket_path = socket_path
self.socket: Optional[socket.socket] = None self.socket: Optional[Any] = None
self._request_id_counter = 0 self._request_id_counter = 0
self._lock = threading.Lock() self._lock = threading.Lock()
@@ -55,13 +56,54 @@ class MPVIPCClient:
self._response_dict: Dict[int, Any] = {} self._response_dict: Dict[int, Any] = {}
self._response_events: Dict[int, threading.Event] = {} self._response_events: Dict[int, threading.Event] = {}
@staticmethod
def _is_windows_named_pipe(path: str) -> bool:
return path.startswith("\\\\.\\pipe\\")
@staticmethod
def _supports_unix_sockets() -> bool:
return hasattr(socket, "AF_UNIX")
@staticmethod
def _open_windows_named_pipe(path: str):
# MPV's JSON IPC on Windows uses named pipes like: \\.\pipe\mpvpipe
# Opening the pipe as a binary file supports read/write.
f = open(path, "r+b", buffering=0)
class _PipeConn:
def __init__(self, fileobj):
self._f = fileobj
def recv(self, n: int) -> bytes:
return self._f.read(n)
def sendall(self, data: bytes) -> None:
self._f.write(data)
self._f.flush()
def close(self) -> None:
self._f.close()
return _PipeConn(f)
def connect(self, timeout: float = 5.0) -> None: def connect(self, timeout: float = 5.0) -> None:
"""Connect to MPV IPC socket and start the reader thread.""" """Connect to MPV IPC socket and start the reader thread."""
start_time = time.time() start_time = time.time()
while time.time() - start_time < timeout: while time.time() - start_time < timeout:
try: try:
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) if self._supports_unix_sockets() and not self._is_windows_named_pipe(
self.socket.connect(self.socket_path) self.socket_path
):
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.connect(self.socket_path)
else:
if os.name != "nt" or not self._is_windows_named_pipe(self.socket_path):
raise MPVIPCError(
"MPV IPC requires Unix domain sockets (AF_UNIX) or a Windows named pipe path "
"like \\\\.\\pipe\\mpvpipe. Got: "
f"{self.socket_path}"
)
self.socket = self._open_windows_named_pipe(self.socket_path)
logger.info(f"Connected to MPV IPC socket at {self.socket_path}") logger.info(f"Connected to MPV IPC socket at {self.socket_path}")
self._start_reader_thread() self._start_reader_thread()
return return
@@ -329,8 +371,12 @@ class MpvIPCPlayer(BaseIPCPlayer):
def _start_mpv_process(self, player: BasePlayer, params: PlayerParams) -> None: def _start_mpv_process(self, player: BasePlayer, params: PlayerParams) -> None:
"""Start MPV process with IPC enabled.""" """Start MPV process with IPC enabled."""
temp_dir = Path(tempfile.gettempdir()) if hasattr(socket, "AF_UNIX"):
self.socket_path = str(temp_dir / f"mpv_ipc_{time.time()}.sock") temp_dir = Path(tempfile.gettempdir())
self.socket_path = str(temp_dir / f"mpv_ipc_{time.time()}.sock")
else:
# Windows MPV IPC uses named pipes.
self.socket_path = f"\\\\.\\pipe\\mpv_ipc_{int(time.time() * 1000)}"
self.mpv_process = player.play_with_ipc(params, self.socket_path) self.mpv_process = player.play_with_ipc(params, self.socket_path)
time.sleep(1.0) time.sleep(1.0)
@@ -480,7 +526,11 @@ class MpvIPCPlayer(BaseIPCPlayer):
self.mpv_process.wait(timeout=3) self.mpv_process.wait(timeout=3)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
self.mpv_process.kill() self.mpv_process.kill()
if self.socket_path and Path(self.socket_path).exists(): if (
self.socket_path
and not self.socket_path.startswith("\\\\.\\pipe\\")
and Path(self.socket_path).exists()
):
Path(self.socket_path).unlink(missing_ok=True) Path(self.socket_path).unlink(missing_ok=True)
def _get_episode( def _get_episode(