diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 10c0aca..cf9e745 100755 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -1,5 +1,6 @@ # coding:utf8 import os +import random import re import sys import time @@ -10,7 +11,7 @@ import ast from syncplay import constants from syncplay.messages import getMessage from syncplay.players.basePlayer import BasePlayer -from syncplay.utils import isURL, findResourcePath +from syncplay.utils import getRuntimeDir, isURL, findResourcePath from syncplay.utils import isMacOS, isWindows, isASCII from syncplay.vendor.python_mpv_jsonipc.python_mpv_jsonipc import MPV @@ -375,7 +376,7 @@ class MpvPlayer(BasePlayer): self._listener.sendLine(['loadfile', filePath], notReadyAfterThis=True) def setFeatures(self, featureList): - self.sendMpvOptions() + self._sendMpvOptions() def setPosition(self, value): if value < constants.DO_NOT_RESET_POSITION_THRESHOLD and self._recentlyReset(): @@ -408,7 +409,7 @@ class MpvPlayer(BasePlayer): self._storePosition(0) # TO TRY: self._listener.setReadyToSend(False) - def sendMpvOptions(self): + def _sendMpvOptions(self): options = [] for option in constants.MPV_SYNCPLAYINTF_OPTIONS_TO_SEND: options.append("{}={}".format(option, self._client._config[option])) @@ -420,8 +421,9 @@ class MpvPlayer(BasePlayer): options_string = ", ".join(options) self._listener.sendLine(["script-message-to", "syncplayintf", "set_syncplayintf_options", options_string]) self._setOSDPosition() - publicIPCSocket = self._listener.mpv_arguments.get("input-ipc-server") if self._listener.mpv_arguments.get("input-ipc-server") else "mpvSyncplaySocket" - self._setProperty("input-ipc-server", publicIPCSocket) + socketPath = self._listener.mpv_arguments.get("input-ipc-server") + if socketPath is not None: + self._setProperty("input-ipc-server", socketPath) def _handleUnknownLine(self, line): self.mpvErrorCheck(line) @@ -449,7 +451,7 @@ class MpvPlayer(BasePlayer): #self._client.ui.showDebugMessage("{} = {} / {}".format(update_string, paused_update, position_update)) if "" in line: - self.sendMpvOptions() + self._sendMpvOptions() if line == "" or "Playing:" in line: self._client.ui.showDebugMessage("Not ready to send due to ") @@ -620,8 +622,15 @@ class MpvPlayer(BasePlayer): env['PATH'] = python_executable + ':' + env['PATH'] env['PYTHONPATH'] = pythonPath try: - socket = self.mpv_arguments.get('input-ipc-server') - self.mpvpipe = self.playerIPCHandler(mpv_location=self.playerPath, ipc_socket=socket, loglevel="info", log_handler=self.__playerController.mpv_log_handler, quit_callback=self.stop_client, env=env, **self.mpv_arguments) + self.mpvpipe = self.playerIPCHandler( + loglevel="info", + ipc_socket=self._get_ipc_socket(), + mpv_location=self.playerPath, + log_handler=self.__playerController.mpv_log_handler, + quit_callback=self.stop_client, + env=env, + **self.mpv_arguments + ) except Exception as e: self.quitReason = getMessage("media-player-error").format(str(e)) + " " + getMessage("mpv-failed-advice") self.__playerController.reactor.callFromThread(self.__playerController._client.ui.showErrorMessage, self.quitReason, True) @@ -630,6 +639,12 @@ class MpvPlayer(BasePlayer): #self.mpvpipe.show_text("HELLO WORLD!", 1000) threading.Thread.__init__(self, name="MPV Listener") + def _get_ipc_socket(self): + if isWindows(): + # On Windows, mpv expects a named pipe identifier (not a path) + return "syncplay-mpv-{0}".format(random.randint(0, 2**48)) + return getRuntimeDir().joinpath("mpv-socket").as_posix() + def __getCwd(self, filePath, env): if not filePath: return None diff --git a/syncplay/utils.py b/syncplay/utils.py index 6adf663..556d461 100755 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -1,5 +1,5 @@ - import ast +import atexit import datetime import hashlib import itertools @@ -10,11 +10,13 @@ import re import string import subprocess import sys +import tempfile import time import traceback import urllib.error import urllib.parse import urllib.request +from pathlib import Path from syncplay import constants from syncplay.messages import getMessage @@ -37,9 +39,28 @@ def isMacOS(): def isBSD(): return constants.OS_BSD in sys.platform or sys.platform.startswith(constants.OS_DRAGONFLY) + def isWindowsConsole(): return os.path.basename(sys.executable) == "SyncplayConsole.exe" + +def getRuntimeDir(): + cachedPath = getattr(getRuntimeDir, "cachedPath", None) + if cachedPath is not None: + return cachedPath + + baseDir = None + if not isWindows() and not isMacOS(): + baseDir = os.getenv("XDG_RUNTIME_DIR", None) + + tmp = tempfile.TemporaryDirectory(prefix="syncplay-", dir=baseDir) + atexit.register(tmp.cleanup) + + o = Path(tmp.name) + setattr(getRuntimeDir, "cachedPath", o) + return o + + def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): """Retry calling the decorated function using an exponential backoff.