Ädd support for IINA
This commit is contained in:
parent
69607c740c
commit
03ef29390f
@ -139,6 +139,8 @@ USER_READY_MIN_VERSION = "1.3.0"
|
|||||||
SHARED_PLAYLIST_MIN_VERSION = "1.4.0"
|
SHARED_PLAYLIST_MIN_VERSION = "1.4.0"
|
||||||
CHAT_MIN_VERSION = "1.5.0"
|
CHAT_MIN_VERSION = "1.5.0"
|
||||||
FEATURE_LIST_MIN_VERSION = "1.5.0"
|
FEATURE_LIST_MIN_VERSION = "1.5.0"
|
||||||
|
|
||||||
|
IINA_PATHS = ['/Applications/IINA.app/Contents/MacOS/iina-cli']
|
||||||
MPC_PATHS = [
|
MPC_PATHS = [
|
||||||
r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
|
r"c:\program files (x86)\mpc-hc\mpc-hc.exe",
|
||||||
r"c:\program files\mpc-hc\mpc-hc.exe",
|
r"c:\program files\mpc-hc\mpc-hc.exe",
|
||||||
@ -176,6 +178,7 @@ VLC_PATHS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
VLC_ICONPATH = "vlc.png"
|
VLC_ICONPATH = "vlc.png"
|
||||||
|
IINA_ICONPATH = "iina.png"
|
||||||
MPLAYER_ICONPATH = "mplayer.png"
|
MPLAYER_ICONPATH = "mplayer.png"
|
||||||
MPV_ICONPATH = "mpv.png"
|
MPV_ICONPATH = "mpv.png"
|
||||||
MPVNET_ICONPATH = "mpvnet.png"
|
MPVNET_ICONPATH = "mpvnet.png"
|
||||||
@ -250,6 +253,13 @@ MPV_ARGS = {'force-window': 'yes',
|
|||||||
'keep-open-pause': 'yes'
|
'keep-open-pause': 'yes'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IINA_PROPERTIES = {'geometry': '25%+100+100',
|
||||||
|
'idle': 'yes',
|
||||||
|
'hr-seek': 'always',
|
||||||
|
'input-terminal': 'no',
|
||||||
|
'term-playing-msg': '<SyncplayUpdateFile>\nANS_filename=${filename}\nANS_length=${=duration:${=length:0}}\nANS_path=${path}\n</SyncplayUpdateFile>',
|
||||||
|
'keep-open-pause': 'yes',
|
||||||
|
}
|
||||||
|
|
||||||
MPV_NEW_VERSION = False
|
MPV_NEW_VERSION = False
|
||||||
MPV_OSC_VISIBILITY_CHANGE_VERSION = False
|
MPV_OSC_VISIBILITY_CHANGE_VERSION = False
|
||||||
|
|||||||
@ -12,7 +12,12 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from syncplay.players.basePlayer import DummyPlayer
|
from syncplay.players.basePlayer import DummyPlayer
|
||||||
MpcBePlayer = DummyPlayer
|
MpcBePlayer = DummyPlayer
|
||||||
|
try:
|
||||||
|
from syncplay.players.iina import IinaPlayer
|
||||||
|
except ImportError:
|
||||||
|
from syncplay.players.basePlayer import DummyPlayer
|
||||||
|
IinaPlayer = DummyPlayer
|
||||||
|
|
||||||
|
|
||||||
def getAvailablePlayers():
|
def getAvailablePlayers():
|
||||||
return [MPCHCAPIPlayer, MpvPlayer, MpvnetPlayer, VlcPlayer, MpcBePlayer, MplayerPlayer]
|
return [MPCHCAPIPlayer, MpvPlayer, MpvnetPlayer, VlcPlayer, MpcBePlayer, MplayerPlayer, IinaPlayer]
|
||||||
|
|||||||
77
syncplay/players/iina.py
Normal file
77
syncplay/players/iina.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import os
|
||||||
|
from syncplay import constants
|
||||||
|
from syncplay.utils import findResourcePath
|
||||||
|
from syncplay.players.mpv import MpvPlayer
|
||||||
|
from syncplay.players.python_mpv_jsonipc.python_mpv_jsonipc import IINA
|
||||||
|
|
||||||
|
class IinaPlayer(MpvPlayer):
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(client, playerPath, filePath, args):
|
||||||
|
constants.MPV_NEW_VERSION = True
|
||||||
|
constants.MPV_OSC_VISIBILITY_CHANGE_VERSION = True
|
||||||
|
return IinaPlayer(client, IinaPlayer.getExpandedPath(playerPath), filePath, args)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getStartupArgs(userArgs):
|
||||||
|
args = {}
|
||||||
|
if userArgs:
|
||||||
|
for argToAdd in userArgs:
|
||||||
|
if argToAdd.startswith('--'):
|
||||||
|
argToAdd = argToAdd[2:]
|
||||||
|
elif argToAdd.startswith('-'):
|
||||||
|
argToAdd = argToAdd[1:]
|
||||||
|
if argToAdd.strip() == "":
|
||||||
|
continue
|
||||||
|
if "=" in argToAdd:
|
||||||
|
(argName, argValue) = argToAdd.split("=", 1)
|
||||||
|
else:
|
||||||
|
argName = argToAdd
|
||||||
|
argValue = "yes"
|
||||||
|
args[argName] = argValue
|
||||||
|
return args
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getDefaultPlayerPathsList():
|
||||||
|
l = []
|
||||||
|
for path in constants.IINA_PATHS:
|
||||||
|
p = IinaPlayer.getExpandedPath(path)
|
||||||
|
print(p)
|
||||||
|
if p:
|
||||||
|
l.append(p)
|
||||||
|
return l
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def isValidPlayerPath(path):
|
||||||
|
if "iina-cli" in path and IinaPlayer.getExpandedPath(path):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getExpandedPath(playerPath):
|
||||||
|
if os.access(playerPath, os.X_OK):
|
||||||
|
return playerPath
|
||||||
|
for path in os.environ['PATH'].split(':'):
|
||||||
|
path = os.path.join(os.path.realpath(path), playerPath)
|
||||||
|
if os.access(path, os.X_OK):
|
||||||
|
return path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getIconPath(path):
|
||||||
|
return constants.IINA_ICONPATH
|
||||||
|
|
||||||
|
def __init__(self, client, playerPath, filePath, args):
|
||||||
|
from twisted.internet import reactor
|
||||||
|
self.reactor = reactor
|
||||||
|
self._client = client
|
||||||
|
self._set_defaults()
|
||||||
|
|
||||||
|
self._playerIPCHandler = IINA
|
||||||
|
self._create_listener(playerPath, filePath, args)
|
||||||
|
|
||||||
|
def _preparePlayer(self):
|
||||||
|
for key, value in constants.IINA_PROPERTIES.items():
|
||||||
|
self._setProperty(key, value)
|
||||||
|
self._listener.sendLine(["load-script", findResourcePath("syncplayintf.lua")])
|
||||||
|
super()._preparePlayer()
|
||||||
@ -504,6 +504,12 @@ class MpvPlayer(BasePlayer):
|
|||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
self.reactor = reactor
|
self.reactor = reactor
|
||||||
self._client = client
|
self._client = client
|
||||||
|
self._set_defaults()
|
||||||
|
|
||||||
|
self._playerIPCHandler = MPV
|
||||||
|
self._create_listener(playerPath, filePath, args)
|
||||||
|
|
||||||
|
def _set_defaults(self):
|
||||||
self._paused = None
|
self._paused = None
|
||||||
self._position = 0.0
|
self._position = 0.0
|
||||||
self._duration = None
|
self._duration = None
|
||||||
@ -513,8 +519,10 @@ class MpvPlayer(BasePlayer):
|
|||||||
self.lastLoadedTime = None
|
self.lastLoadedTime = None
|
||||||
self.fileLoaded = False
|
self.fileLoaded = False
|
||||||
self.delayedFilePath = None
|
self.delayedFilePath = None
|
||||||
|
|
||||||
|
def _create_listener(self, playerPath, filePath, args):
|
||||||
try:
|
try:
|
||||||
self._listener = self.__Listener(self, playerPath, filePath, args)
|
self._listener = self.__Listener(self, self._playerIPCHandler, playerPath, filePath, args)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._client.ui.showMessage(getMessage("mplayer-file-required-notification"))
|
self._client.ui.showMessage(getMessage("mplayer-file-required-notification"))
|
||||||
self._client.ui.showMessage(getMessage("mplayer-file-required-notification/example"))
|
self._client.ui.showMessage(getMessage("mplayer-file-required-notification/example"))
|
||||||
@ -549,7 +557,8 @@ class MpvPlayer(BasePlayer):
|
|||||||
self.lineReceived(text)
|
self.lineReceived(text)
|
||||||
|
|
||||||
class __Listener(threading.Thread):
|
class __Listener(threading.Thread):
|
||||||
def __init__(self, playerController, playerPath, filePath, args):
|
def __init__(self, playerController, playerIPCHandler, playerPath, filePath, args):
|
||||||
|
self.playerIPCHandler = playerIPCHandler
|
||||||
self.playerPath = playerPath
|
self.playerPath = playerPath
|
||||||
self.mpv_arguments = playerController.getStartupArgs(args)
|
self.mpv_arguments = playerController.getStartupArgs(args)
|
||||||
self.mpv_running = True
|
self.mpv_running = True
|
||||||
@ -594,7 +603,7 @@ class MpvPlayer(BasePlayer):
|
|||||||
env['PYTHONPATH'] = pythonPath
|
env['PYTHONPATH'] = pythonPath
|
||||||
try:
|
try:
|
||||||
socket = self.mpv_arguments.get('input-ipc-server')
|
socket = self.mpv_arguments.get('input-ipc-server')
|
||||||
self.mpvpipe = MPV(mpv_location=self.playerPath, ipc_socket=socket, loglevel="info", log_handler=self.__playerController.mpv_log_handler, quit_callback=self.stop_client, **self.mpv_arguments)
|
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, **self.mpv_arguments)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.quitReason = getMessage("media-player-error").format(str(e)) + " " + getMessage("mpv-failed-advice")
|
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)
|
self.__playerController.reactor.callFromThread(self.__playerController._client.ui.showErrorMessage, self.quitReason, True)
|
||||||
@ -721,5 +730,6 @@ class MpvPlayer(BasePlayer):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.__playerController._client.ui.showDebugMessage("CANNOT SEND {} DUE TO {}".format(line, e))
|
self.__playerController._client.ui.showDebugMessage("CANNOT SEND {} DUE TO {}".format(line, e))
|
||||||
self.stop_client()
|
self.stop_client()
|
||||||
|
raise
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import random
|
|||||||
import queue
|
import queue
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from syncplay.utils import resourcespath
|
||||||
|
|
||||||
log = logging.getLogger('mpv-jsonipc')
|
log = logging.getLogger('mpv-jsonipc')
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
@ -207,13 +209,11 @@ class MPVProcess:
|
|||||||
|
|
||||||
log.debug("Using IPC socket {0} for MPV.".format(ipc_socket))
|
log.debug("Using IPC socket {0} for MPV.".format(ipc_socket))
|
||||||
self.ipc_socket = ipc_socket
|
self.ipc_socket = ipc_socket
|
||||||
args = [mpv_location]
|
args = self._get_arglist(mpv_location, **kwargs)
|
||||||
self._set_default(kwargs, "idle", True)
|
|
||||||
self._set_default(kwargs, "input_ipc_server", ipc_socket_name)
|
self._start_process(ipc_socket, args)
|
||||||
self._set_default(kwargs, "input_terminal", False)
|
|
||||||
self._set_default(kwargs, "terminal", False)
|
def _start_process(self, ipc_socket, args):
|
||||||
args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1]))
|
|
||||||
for v in kwargs.items())
|
|
||||||
self.process = subprocess.Popen(args)
|
self.process = subprocess.Popen(args)
|
||||||
ipc_exists = False
|
ipc_exists = False
|
||||||
for _ in range(100): # Give MPV 10 seconds to start.
|
for _ in range(100): # Give MPV 10 seconds to start.
|
||||||
@ -238,6 +238,16 @@ class MPVProcess:
|
|||||||
if key not in prop_dict:
|
if key not in prop_dict:
|
||||||
prop_dict[key] = value
|
prop_dict[key] = value
|
||||||
|
|
||||||
|
def _get_arglist(self, exec_location, **kwargs):
|
||||||
|
args = [exec_location]
|
||||||
|
self._set_default(kwargs, "idle", True)
|
||||||
|
self._set_default(kwargs, "input_ipc_server", ipc_socket_name)
|
||||||
|
self._set_default(kwargs, "input_terminal", False)
|
||||||
|
self._set_default(kwargs, "terminal", False)
|
||||||
|
args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1]))
|
||||||
|
for v in kwargs.items())
|
||||||
|
return args
|
||||||
|
|
||||||
def _mpv_fmt(self, data):
|
def _mpv_fmt(self, data):
|
||||||
if data == True:
|
if data == True:
|
||||||
return "yes"
|
return "yes"
|
||||||
@ -252,6 +262,40 @@ class MPVProcess:
|
|||||||
if os.name != 'nt' and os.path.exists(self.ipc_socket):
|
if os.name != 'nt' and os.path.exists(self.ipc_socket):
|
||||||
os.remove(self.ipc_socket)
|
os.remove(self.ipc_socket)
|
||||||
|
|
||||||
|
class IINAProcess(MPVProcess):
|
||||||
|
"""
|
||||||
|
Manages an IINA process, ensuring the socket or pipe is available. (Internal)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _start_process(self, ipc_socket, args):
|
||||||
|
self.process = subprocess.Popen(args)
|
||||||
|
ipc_exists = False
|
||||||
|
for _ in range(100): # Give IINA 10 seconds to start.
|
||||||
|
time.sleep(0.1)
|
||||||
|
self.process.poll()
|
||||||
|
if os.path.exists(ipc_socket):
|
||||||
|
ipc_exists = True
|
||||||
|
log.debug("Found IINA socket.")
|
||||||
|
break
|
||||||
|
if self.process.returncode != 0: # iina-cli returns immediately after its start
|
||||||
|
log.error("IINA failed with returncode {0}.".format(self.process.returncode))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.process.terminate()
|
||||||
|
raise MPVError("IINA start timed out.")
|
||||||
|
|
||||||
|
if not ipc_exists or self.process.returncode != 0:
|
||||||
|
self.process.terminate()
|
||||||
|
raise MPVError("IINA not started.")
|
||||||
|
|
||||||
|
def _get_arglist(self, exec_location, **kwargs):
|
||||||
|
args = [exec_location]
|
||||||
|
args.append(resourcespath + 'syncplay.png')
|
||||||
|
self._set_default(kwargs, "mpv-input-ipc-server", self.ipc_socket)
|
||||||
|
args.extend("--{0}={1}".format(v[0].replace("_", "-"), self._mpv_fmt(v[1]))
|
||||||
|
for v in kwargs.items())
|
||||||
|
return args
|
||||||
|
|
||||||
class MPVInter:
|
class MPVInter:
|
||||||
"""
|
"""
|
||||||
Low-level interface to MPV. Does NOT manage an mpv process. (Internal)
|
Low-level interface to MPV. Does NOT manage an mpv process. (Internal)
|
||||||
@ -405,16 +449,7 @@ class MPV:
|
|||||||
ipc_socket = "/tmp/{0}".format(rand_file)
|
ipc_socket = "/tmp/{0}".format(rand_file)
|
||||||
|
|
||||||
if start_mpv:
|
if start_mpv:
|
||||||
# Attempt to start MPV 3 times.
|
self._start_mpv(ipc_socket, mpv_location, **kwargs)
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
self.mpv_process = MPVProcess(ipc_socket, mpv_location, **kwargs)
|
|
||||||
break
|
|
||||||
except MPVError:
|
|
||||||
log.warning("MPV start failed.", exc_info=1)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise MPVError("MPV process retry limit reached.")
|
|
||||||
|
|
||||||
self.mpv_inter = MPVInter(ipc_socket, self._callback, self._quit_callback)
|
self.mpv_inter = MPVInter(ipc_socket, self._callback, self._quit_callback)
|
||||||
self.properties = set(x.replace("-", "_") for x in self.command("get_property", "property-list"))
|
self.properties = set(x.replace("-", "_") for x in self.command("get_property", "property-list"))
|
||||||
@ -451,6 +486,17 @@ class MPV:
|
|||||||
if len(args) == 2 and args[0] == "custom-bind":
|
if len(args) == 2 and args[0] == "custom-bind":
|
||||||
self.event_handler.put_task(self.key_bindings[args[1]])
|
self.event_handler.put_task(self.key_bindings[args[1]])
|
||||||
|
|
||||||
|
def _start_mpv(self, ipc_socket, mpv_location, **kwargs):
|
||||||
|
for i in range(3):
|
||||||
|
try:
|
||||||
|
self.mpv_process = MPVProcess(ipc_socket, mpv_location, **kwargs)
|
||||||
|
break
|
||||||
|
except MPVError:
|
||||||
|
log.warning("MPV start failed.", exc_info=1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise MPVError("MPV process retry limit reached.")
|
||||||
|
|
||||||
def _quit_callback(self):
|
def _quit_callback(self):
|
||||||
"""
|
"""
|
||||||
Internal handler for quit events.
|
Internal handler for quit events.
|
||||||
@ -638,3 +684,17 @@ class MPV:
|
|||||||
|
|
||||||
def __dir__(self):
|
def __dir__(self):
|
||||||
return self._dir
|
return self._dir
|
||||||
|
|
||||||
|
class IINA(MPV):
|
||||||
|
"""The main IINA interface class. Use this to control the MPV player instantiated by IINA."""
|
||||||
|
|
||||||
|
def _start_mpv(self, ipc_socket, mpv_location, **kwargs):
|
||||||
|
for i in range(3):
|
||||||
|
try:
|
||||||
|
self.mpv_process = IINAProcess(ipc_socket, mpv_location, **kwargs)
|
||||||
|
break
|
||||||
|
except MPVError:
|
||||||
|
log.warning("IINA start failed.", exc_info=1)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise MPVError("IINA process retry limit reached.")
|
||||||
BIN
syncplay/resources/IINA.png
Normal file
BIN
syncplay/resources/IINA.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Loading…
x
Reference in New Issue
Block a user