543 lines
24 KiB
Python
Executable File
543 lines
24 KiB
Python
Executable File
|
|
import os
|
|
import random
|
|
import re
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
from twisted.internet.protocol import ReconnectingClientFactory
|
|
from twisted.protocols.basic import LineReceiver
|
|
|
|
from syncplay import constants, utils
|
|
from syncplay.messages import getMessage
|
|
from syncplay.players.basePlayer import BasePlayer
|
|
from syncplay.utils import isBSD, isLinux, isWindows, isMacOS
|
|
|
|
class VLCProtocol(LineReceiver):
|
|
def __init__(self):
|
|
self.delimiter = b'\n'
|
|
|
|
def lineReceived(self, data):
|
|
self.factory.vlcHasResponded = True
|
|
self.factory._playerController.lineReceived(data)
|
|
|
|
def sendLine(self, line):
|
|
if self.factory.connected:
|
|
if not self.factory.requestedVLCVersion:
|
|
self.factory.requestedVLCVersion = True
|
|
self.sendLine("get-vlc-version")
|
|
try:
|
|
lineToSend = line.encode('utf-8') + self.delimiter
|
|
self.transport.write(lineToSend)
|
|
if self.factory._playerController._client and self.factory._playerController._client.ui:
|
|
self.factory._playerController._client.ui.showDebugMessage("player >> {}".format(line))
|
|
except:
|
|
pass
|
|
|
|
def connectionMade(self):
|
|
self.factory.connected = True
|
|
self.factory._playerController._vlcready.set()
|
|
self.factory.timeVLCLaunched = None
|
|
self.factory._playerController.initWhenConnected()
|
|
|
|
def connectionLost(self, reason):
|
|
self.factory.connected = False
|
|
|
|
class VLCClientFactory(ReconnectingClientFactory):
|
|
|
|
# http://twistedmatrix.com/documents/current/api/twisted.internet.protocol.ReconnectingClientFactory.html
|
|
#
|
|
initialDelay = 0.3
|
|
maxDelay = 0.45
|
|
maxRetries = 50
|
|
|
|
def startedConnecting(self, connector):
|
|
self._playerController._client.ui.showDebugMessage("Starting to connect to VLC...")
|
|
|
|
def clientConnectionLost(self, connector, reason):
|
|
self._playerController._client.ui.showDebugMessage("Connection to VLC lost: {}".format(reason))
|
|
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
|
|
|
def clientConnectionFailed(self, connector, reason):
|
|
self._playerController._client.ui.showDebugMessage("Connection to VLC failed: {}".format(reason))
|
|
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
|
|
|
def __init__(self, playerController, vlcHasResponded, vlcLaunchedTime, vlcProcess):
|
|
self._playerController = playerController
|
|
self._process = vlcProcess
|
|
self.requestedVLCVersion = False
|
|
self.vlcHasResponded = vlcHasResponded
|
|
self.timeVLCLaunched = vlcLaunchedTime
|
|
self.connected = False
|
|
|
|
def buildProtocol(self, addr):
|
|
self.protocol = VLCProtocol()
|
|
self.protocol.factory = self
|
|
return self.protocol
|
|
|
|
def clientConnectionLost(self, connector, reason):
|
|
if self.timeVLCLaunched and time.time() - self.timeVLCLaunched < constants.VLC_OPEN_MAX_WAIT_TIME:
|
|
try:
|
|
self._playerController._client.ui.showDebugMessage("Failed to connect to VLC, but reconnecting as within max wait time")
|
|
except:
|
|
pass
|
|
self._playerController._vlcready.clear()
|
|
connector.connect()
|
|
elif self.vlcHasResponded:
|
|
self._playerController.drop()
|
|
else:
|
|
self.vlcHasResponded = True
|
|
self._playerController.drop(getMessage("vlc-failed-connection").format(constants.VLC_MIN_VERSION))
|
|
|
|
def closeVLC(self):
|
|
self._playerController._vlcclosed.set()
|
|
if not self.connected and not self.timeVLCLaunched:
|
|
# For circumstances where Syncplay is not connected to VLC and is not reconnecting
|
|
try:
|
|
self._process.terminate()
|
|
except: # When VLC is already closed
|
|
pass
|
|
|
|
|
|
class VlcPlayer(BasePlayer):
|
|
speedSupported = True
|
|
customOpenDialog = False
|
|
chatOSDSupported = False
|
|
alertOSDSupported = True
|
|
osdMessageSeparator = "; "
|
|
|
|
RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)
|
|
SLAVE_ARGS = constants.VLC_SLAVE_ARGS
|
|
SLAVE_ARGS.extend(constants.VLC_SLAVE_EXTRA_ARGS)
|
|
vlcport = random.randrange(constants.VLC_MIN_PORT, constants.VLC_MAX_PORT) if (constants.VLC_MIN_PORT < constants.VLC_MAX_PORT) else constants.VLC_MIN_PORT
|
|
|
|
def __init__(self, client, playerPath, filePath, args):
|
|
from twisted.internet import reactor
|
|
self.reactor = reactor
|
|
self._client = client
|
|
self._paused = None
|
|
self._duration = None
|
|
self._filename = None
|
|
self._filepath = None
|
|
self._filechanged = False
|
|
self._lastVLCPositionUpdate = None
|
|
self.shownVLCLatencyError = False
|
|
self._previousPreviousPosition = -2
|
|
self._previousPosition = -1
|
|
self._position = 0
|
|
try: # Hack to fix locale issue without importing locale library
|
|
self.radixChar = "{:n}".format(1.5)[1:2]
|
|
if self.radixChar == "" or self.radixChar == "1" or self.radixChar == "5":
|
|
raise ValueError
|
|
except:
|
|
self._client.ui.showErrorMessage(
|
|
"Failed to determine locale. As a fallback Syncplay is using the following radix character: \".\".")
|
|
self.radixChar = "."
|
|
|
|
self._durationAsk = threading.Event()
|
|
self._filenameAsk = threading.Event()
|
|
self._pathAsk = threading.Event()
|
|
self._positionAsk = threading.Event()
|
|
self._pausedAsk = threading.Event()
|
|
self._vlcready = threading.Event()
|
|
self._vlcclosed = threading.Event()
|
|
self._listener = None
|
|
try:
|
|
self._listener = self.__Listener(self, playerPath, filePath, args, self.reactor)
|
|
except ValueError:
|
|
self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
|
|
self.reactor.callFromThread(self._client.stop, True,)
|
|
return
|
|
|
|
def initWhenConnected(self):
|
|
try:
|
|
self._client.ui.showErrorMessage(getMessage("vlc-initial-warning"))
|
|
if not self._vlcready.wait(constants.VLC_OPEN_MAX_WAIT_TIME):
|
|
self._vlcready.set()
|
|
self._client.ui.showErrorMessage(getMessage("vlc-failed-connection"), True)
|
|
self.reactor.callFromThread(self._client.stop, True,)
|
|
self.reactor.callFromThread(self._client.initPlayer, self,)
|
|
except:
|
|
pass
|
|
|
|
def _fileUpdateClearEvents(self):
|
|
self._durationAsk.clear()
|
|
self._filenameAsk.clear()
|
|
self._pathAsk.clear()
|
|
|
|
def _fileUpdateWaitEvents(self):
|
|
self._durationAsk.wait()
|
|
self._filenameAsk.wait()
|
|
self._pathAsk.wait()
|
|
|
|
def _onFileUpdate(self):
|
|
self._fileUpdateClearEvents()
|
|
self._getFileInfo()
|
|
self._fileUpdateWaitEvents()
|
|
args = (self._filename, self._duration, self._filepath)
|
|
self.reactor.callFromThread(self._client.updateFile, *args)
|
|
self.setPaused(self._client.getGlobalPaused())
|
|
self.setPosition(self._client.getGlobalPosition())
|
|
|
|
def askForStatus(self):
|
|
self._filechanged = False
|
|
self._positionAsk.clear()
|
|
self._pausedAsk.clear()
|
|
self._listener.sendLine(".")
|
|
if self._filename and not self._filechanged:
|
|
self._positionAsk.wait(constants.PLAYER_ASK_DELAY)
|
|
self._client.updatePlayerStatus(self._paused, self.getCalculatedPosition())
|
|
else:
|
|
self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition())
|
|
|
|
def getCalculatedPosition(self):
|
|
if self._lastVLCPositionUpdate is None:
|
|
return self._client.getGlobalPosition()
|
|
diff = time.time() - self._lastVLCPositionUpdate
|
|
if diff > constants.PLAYER_ASK_DELAY and not self._paused:
|
|
self._client.ui.showDebugMessage("VLC did not response in time, so assuming position is {} ({}+{})".format(
|
|
self._position + diff, self._position, diff))
|
|
if diff > constants.VLC_LATENCY_ERROR_THRESHOLD:
|
|
if not self.shownVLCLatencyError or constants.DEBUG_MODE:
|
|
self._client.ui.showErrorMessage(getMessage("media-player-latency-warning").format(int(diff)))
|
|
self.shownVLCLatencyError = True
|
|
return self._position + diff
|
|
else:
|
|
return self._position
|
|
|
|
def displayMessage(
|
|
self, message,
|
|
duration=constants.OSD_DURATION * 1000, OSDType=constants.OSD_DURATION, mood=constants.MESSAGE_NEUTRAL
|
|
):
|
|
duration /= 1000
|
|
if OSDType != constants.OSD_ALERT:
|
|
self._listener.sendLine('display-osd: {}, {}, {}'.format('top-right', duration, message))
|
|
else:
|
|
self._listener.sendLine('display-secondary-osd: {}, {}, {}'.format('center', duration, message))
|
|
|
|
def setSpeed(self, value):
|
|
self._listener.sendLine("set-rate: {:.2n}".format(value))
|
|
|
|
def setFeatures(self, featureList):
|
|
pass
|
|
|
|
def setPosition(self, value):
|
|
self._lastVLCPositionUpdate = time.time()
|
|
self._listener.sendLine("set-position: {}".format(value).replace(".", self.radixChar))
|
|
|
|
def setPaused(self, value):
|
|
self._paused = value
|
|
if not value:
|
|
self._lastVLCPositionUpdate = time.time()
|
|
self._listener.sendLine('set-playstate: {}'.format("paused" if value else "playing"))
|
|
|
|
def getMRL(self, fileURL):
|
|
if utils.isURL(fileURL):
|
|
fileURL = urllib.parse.quote(fileURL, safe="%/:=&?~#+!$,;'@()*")
|
|
return fileURL
|
|
|
|
fileURL = fileURL.replace('\\', '/')
|
|
fileURL = fileURL.encode('utf8')
|
|
fileURL = urllib.parse.quote_plus(fileURL)
|
|
if isWindows():
|
|
fileURL = "file:///" + fileURL
|
|
else:
|
|
fileURL = "file://" + fileURL
|
|
fileURL = fileURL.replace("+", "%20")
|
|
return fileURL
|
|
|
|
def openFile(self, filePath, resetPosition=False):
|
|
if not utils.isURL(filePath):
|
|
normedPath = os.path.normpath(filePath)
|
|
if os.path.isfile(normedPath):
|
|
filePath = normedPath
|
|
if utils.isASCII(filePath) and not utils.isURL(filePath):
|
|
self._listener.sendLine('load-file: {}'.format(filePath))
|
|
else:
|
|
fileURL = self.getMRL(filePath)
|
|
self._listener.sendLine('load-file: {}'.format(fileURL))
|
|
|
|
def _getFileInfo(self):
|
|
self._listener.sendLine("get-duration")
|
|
self._listener.sendLine("get-filepath")
|
|
self._listener.sendLine("get-filename")
|
|
|
|
def lineReceived(self, line):
|
|
# try:
|
|
line = line.decode('utf-8')
|
|
self._client.ui.showDebugMessage("player << {}".format(line))
|
|
# except:
|
|
# pass
|
|
match, name, value = self.RE_ANSWER.match(line), "", ""
|
|
if match:
|
|
name, value = match.group('command'), match.group('argument')
|
|
|
|
if line == "filepath-change-notification":
|
|
self._filechanged = True
|
|
t = threading.Thread(target=self._onFileUpdate)
|
|
t.setDaemon(True)
|
|
t.start()
|
|
elif name == "filepath":
|
|
self._filechanged = True
|
|
if value == "no-input":
|
|
self._filepath = None
|
|
else:
|
|
if "file://" in value:
|
|
value = value.replace("file://", "")
|
|
if not os.path.isfile(value):
|
|
value = value.lstrip("/")
|
|
elif utils.isURL(value):
|
|
value = urllib.parse.unquote(value)
|
|
# value = value.decode('utf-8')
|
|
self._filepath = value
|
|
self._pathAsk.set()
|
|
elif name == "duration" or name == "duration-change":
|
|
if value == "no-input":
|
|
self._duration = 0
|
|
elif value == "invalid-32-bit-value":
|
|
self._duration = 0
|
|
self.drop(getMessage("vlc-failed-versioncheck"))
|
|
else:
|
|
self._duration = float(value.replace(",", "."))
|
|
self._durationAsk.set()
|
|
if name == "duration-change":
|
|
self._filechanged = True
|
|
t = threading.Thread(target=self._onFileUpdate)
|
|
t.setDaemon(True)
|
|
t.start()
|
|
elif name == "playstate":
|
|
self._paused = bool(value != 'playing') if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused()
|
|
diff = time.time() - self._lastVLCPositionUpdate if self._lastVLCPositionUpdate else 0
|
|
if (
|
|
self._paused == False and
|
|
self._position == self._previousPreviousPosition and
|
|
self._previousPosition == self._position and
|
|
self._duration and
|
|
self._duration > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
|
|
(self._duration - self._position) < constants.VLC_EOF_DURATION_THRESHOLD and
|
|
diff > constants.VLC_LATENCY_ERROR_THRESHOLD
|
|
):
|
|
self._client.ui.showDebugMessage("Treating 'playing' response as 'paused' due to VLC EOF bug")
|
|
self.setPaused(True)
|
|
self._pausedAsk.set()
|
|
elif name == "position":
|
|
newPosition = float(value.replace(",", ".")) if (value != "no-input" and not self._filechanged) else self._client.getGlobalPosition()
|
|
if newPosition == self._previousPosition and newPosition != self._duration and self._paused is False:
|
|
self._client.ui.showDebugMessage(
|
|
"Not considering position {} duplicate as new time because of VLC time precision bug".format(
|
|
newPosition))
|
|
self._previousPreviousPosition = self._previousPosition
|
|
self._previousPosition = self._position
|
|
self._positionAsk.set()
|
|
return
|
|
self._previousPreviousPosition = self._previousPosition
|
|
self._previousPosition = self._position
|
|
self._position = newPosition
|
|
if self._position < 0 and self._duration > 2147 and self._vlcVersion == "3.0.0":
|
|
self.drop(getMessage("vlc-failed-versioncheck"))
|
|
self._lastVLCPositionUpdate = time.time()
|
|
self._positionAsk.set()
|
|
elif name == "filename":
|
|
self._filechanged = True
|
|
self._filename = value
|
|
self._filenameAsk.set()
|
|
elif line.startswith("vlc-version: "):
|
|
self._vlcVersion = line.split(': ')[1].replace(' ', '-').split('-')[0]
|
|
if not utils.meetsMinVersion(self._vlcVersion, constants.VLC_MIN_VERSION):
|
|
self._client.ui.showErrorMessage(getMessage("vlc-version-mismatch").format(constants.VLC_MIN_VERSION))
|
|
self._vlcready.set()
|
|
|
|
@staticmethod
|
|
def run(client, playerPath, filePath, args):
|
|
vlc = VlcPlayer(client, VlcPlayer.getExpandedPath(playerPath), filePath, args)
|
|
return vlc
|
|
|
|
@staticmethod
|
|
def getDefaultPlayerPathsList():
|
|
l = []
|
|
for path in constants.VLC_PATHS:
|
|
p = VlcPlayer.getExpandedPath(path)
|
|
if p:
|
|
l.append(p)
|
|
return l
|
|
|
|
@staticmethod
|
|
def isValidPlayerPath(path):
|
|
if "vlc" in path.lower() and VlcPlayer.getExpandedPath(path):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def getPlayerPathErrors(playerPath, filePath):
|
|
return None
|
|
|
|
@staticmethod
|
|
def getIconPath(path):
|
|
return constants.VLC_ICONPATH
|
|
|
|
@staticmethod
|
|
def getExpandedPath(playerPath):
|
|
if not os.path.isfile(playerPath):
|
|
if os.path.isfile(playerPath + "vlc.exe"):
|
|
playerPath += "vlc.exe"
|
|
return playerPath
|
|
elif os.path.isfile(playerPath + "\\vlc.exe"):
|
|
playerPath += "\\vlc.exe"
|
|
return playerPath
|
|
elif os.path.isfile(playerPath + "VLCPortable.exe"):
|
|
playerPath += "VLCPortable.exe"
|
|
return playerPath
|
|
elif os.path.isfile(playerPath + "\\VLCPortable.exe"):
|
|
playerPath += "\\VLCPortable.exe"
|
|
return 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
|
|
|
|
def drop(self, dropErrorMessage=None):
|
|
if self._listener:
|
|
self._vlcclosed.clear()
|
|
self._listener._factory.closeVLC()
|
|
self._vlcclosed.wait()
|
|
self._durationAsk.set()
|
|
self._filenameAsk.set()
|
|
self._pathAsk.set()
|
|
self._positionAsk.set()
|
|
self._vlcready.set()
|
|
self._pausedAsk.set()
|
|
if dropErrorMessage:
|
|
self.reactor.callFromThread(self._client.ui.showErrorMessage, dropErrorMessage, True)
|
|
self.reactor.callFromThread(self._client.stop, False,)
|
|
|
|
|
|
class __Listener():
|
|
def __init__(self, playerController, playerPath, filePath, args, reactor):
|
|
self.__playerController = playerController
|
|
self.reactor = reactor
|
|
self.vlcHasResponded = False
|
|
self.oldIntfVersion = None
|
|
self.timeVLCLaunched = None
|
|
|
|
call = [playerPath]
|
|
if filePath:
|
|
if utils.isASCII(filePath):
|
|
call.append(filePath)
|
|
else:
|
|
call.append(self.__playerController.getMRL(filePath))
|
|
if isLinux():
|
|
if 'snap' in playerPath:
|
|
self.__playerController.vlcIntfPath = '/snap/vlc/current/usr/lib/vlc/lua/intf/'
|
|
self.__playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), "snap/vlc/current/.local/share/vlc/lua/intf/")
|
|
else:
|
|
self.__playerController.vlcIntfPath = "/usr/lib/vlc/lua/intf/"
|
|
self.__playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
|
|
elif isMacOS():
|
|
self.__playerController.vlcIntfPath = "/Applications/VLC.app/Contents/MacOS/share/lua/intf/"
|
|
self.__playerController.vlcIntfUserPath = os.path.join(
|
|
os.getenv('HOME', '.'), "Library/Application Support/org.videolan.vlc/lua/intf/")
|
|
elif isBSD():
|
|
# *BSD ports/pkgs install to /usr/local by default.
|
|
# This should also work for all the other BSDs, such as OpenBSD or DragonFly.
|
|
self.__playerController.vlcIntfPath = "/usr/local/lib/vlc/lua/intf/"
|
|
self.__playerController.vlcIntfUserPath = os.path.join(os.getenv('HOME', '.'), ".local/share/vlc/lua/intf/")
|
|
elif "vlcportable.exe" in playerPath.lower():
|
|
self.__playerController.vlcIntfPath = os.path.dirname(playerPath).replace("\\", "/") + "/App/vlc/lua/intf/"
|
|
self.__playerController.vlcIntfUserPath = playerController.vlcIntfPath
|
|
else:
|
|
self.__playerController.vlcIntfPath = os.path.dirname(playerPath).replace("\\", "/") + "/lua/intf/"
|
|
self.__playerController.vlcIntfUserPath = os.path.join(os.getenv('APPDATA', '.'), "VLC\\lua\\intf\\")
|
|
self.__playerController.vlcModulePath = self.__playerController.vlcIntfPath + "modules/?.luac"
|
|
def _createIntfFolder(vlcSyncplayInterfaceDir):
|
|
self.__playerController._client.ui.showDebugMessage("Checking if syncplay.lua intf directory exists")
|
|
from pathlib import Path
|
|
if os.path.exists(vlcSyncplayInterfaceDir):
|
|
self.__playerController._client.ui.showDebugMessage("Found syncplay.lua intf directory:'{}'".format(vlcSyncplayInterfaceDir))
|
|
else:
|
|
self.__playerController._client.ui.showDebugMessage("syncplay.lua intf directory not found, so creating directory '{}'".format(vlcSyncplayInterfaceDir))
|
|
Path(vlcSyncplayInterfaceDir).mkdir(mode=0o755, parents=True, exist_ok=True)
|
|
try:
|
|
_createIntfFolder(self.__playerController.vlcIntfUserPath)
|
|
copyForm = utils.findResourcePath("syncplay.lua")
|
|
copyTo = os.path.join(self.__playerController.vlcIntfUserPath, "syncplay.lua")
|
|
self.__playerController._client.ui.showDebugMessage("Copying VLC Lua Interface from '{}' to '{}'".format(copyForm, copyTo))
|
|
import shutil
|
|
if os.path.exists(copyTo):
|
|
os.chmod(copyTo, 0o755)
|
|
shutil.copyfile(copyForm, copyTo)
|
|
os.chmod(copyTo, 0o755)
|
|
except Exception as e:
|
|
self.__playerController._client.ui.showErrorMessage(e)
|
|
return
|
|
if isLinux():
|
|
self.__playerController.vlcDataPath = "/usr/lib/syncplay/resources"
|
|
else:
|
|
self.__playerController.vlcDataPath = utils.findWorkingDir() + "\\resources"
|
|
self.__playerController.SLAVE_ARGS.append(
|
|
'--lua-config=syncplay={{modulepath=\"{}\",port=\"{}\"}}'.format(
|
|
self.__playerController.vlcModulePath, str(self.__playerController.vlcport)))
|
|
|
|
call.extend(self.__playerController.SLAVE_ARGS)
|
|
if args:
|
|
call.extend(args)
|
|
|
|
self._vlcVersion = None
|
|
|
|
if isWindows() and getattr(sys, 'frozen', '') and getattr(sys, '_MEIPASS', '') is not None: # Needed for pyinstaller --onefile bundle
|
|
self.__process = subprocess.Popen(
|
|
call, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
shell=False, creationflags=0x08000000)
|
|
else:
|
|
self.__process = subprocess.Popen(call, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
self.timeVLCLaunched = time.time()
|
|
if self._shouldListenForSTDOUT():
|
|
for line in iter(self.__process.stderr.readline, ''):
|
|
line = line.decode('utf-8')
|
|
self.vlcHasResponded = True
|
|
self.timeVLCLaunched = None
|
|
if "[syncplay]" in line:
|
|
if "Listening on host" in line:
|
|
break
|
|
if "Hosting Syncplay" in line:
|
|
break
|
|
elif "Couldn't find lua interface" in line:
|
|
self.__playerController._client.ui.showErrorMessage(
|
|
getMessage("vlc-failed-noscript").format(line), True)
|
|
break
|
|
elif "lua interface error" in line:
|
|
self.__playerController._client.ui.showErrorMessage(
|
|
getMessage("media-player-error").format(line), True)
|
|
break
|
|
if not isMacOS():
|
|
self.__process.stderr = None
|
|
else:
|
|
vlcoutputthread = threading.Thread(target=self.handle_vlcoutput, args=())
|
|
vlcoutputthread.setDaemon(True)
|
|
vlcoutputthread.start()
|
|
self.__playerController._vlcready.clear()
|
|
self._factory = VLCClientFactory(self.__playerController, self.vlcHasResponded, self.timeVLCLaunched, self.__process)
|
|
self.reactor.connectTCP('localhost', self.__playerController.vlcport, self._factory)
|
|
|
|
def _shouldListenForSTDOUT(self):
|
|
return not isWindows()
|
|
|
|
def handle_vlcoutput(self):
|
|
out = self.__process.stderr
|
|
for line in iter(out.readline, ''):
|
|
line = line.decode('utf-8')
|
|
if '[syncplay] core interface debug: removing module' in line:
|
|
self.__playerController.drop()
|
|
break
|
|
out.close()
|
|
|
|
def sendLine(self, line):
|
|
self.reactor.callFromThread(self._factory.protocol.sendLine, line)
|