Merge pull request #22 from Et0h/master

Improvements to VLC, seek, desync, GUI
This commit is contained in:
Uriziel 2013-06-01 08:48:04 -07:00
commit 3ad3646f20
5 changed files with 259 additions and 127 deletions

View File

@ -209,7 +209,7 @@ NSIS_SCRIPT_TEMPLATE = r"""
Pop $$CheckBox_QuickLaunchShortcut Pop $$CheckBox_QuickLaunchShortcut
$${If} $$CheckBox_Associate_State == $${BST_CHECKED} $${If} $$CheckBox_Associate_State == $${BST_CHECKED}
$${NSD_Check} $$CheckBox_Associate $${NSD_Check} $$CheckBox_Associate
$${EndIf} $${EndIf}
$${If} $$CheckBox_VLC_State == $${BST_CHECKED} $${If} $$CheckBox_VLC_State == $${BST_CHECKED}
@ -522,9 +522,10 @@ info = dict(
console=[{"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"}, 'syncplayServer.py'], console=[{"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"}, 'syncplayServer.py'],
options={'py2exe': { options={'py2exe': {
'dist_dir': OUT_DIR, 'dist_dir': OUT_DIR,
'includes': 'cairo, pango, pangocairo, atk, gobject, twisted', 'packages': 'PySide.QtUiTools',
'includes': 'cairo, pango, pangocairo, atk, gobject, twisted, sys, encodings, datetime, os, time, math, PySide',
'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32event, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process', 'excludes': 'venv, _ssl, doctest, pdb, unittest, win32clipboard, win32event, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process',
'dll_excludes': 'msvcr71.dll', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll',
'optimize': 2, 'optimize': 2,
'compressed': 1 'compressed': 1
} }
@ -536,4 +537,3 @@ info = dict(
sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource']) sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource'])
setup(**info) setup(**info)

View File

@ -212,7 +212,7 @@ class SyncplayClient(object):
madeChangeOnPlayer = self._serverSeeked(position, setBy) madeChangeOnPlayer = self._serverSeeked(position, setBy)
if (diff > constants.REWIND_THRESHOLD and not doSeek): if (diff > constants.REWIND_THRESHOLD and not doSeek):
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
if (self._player.speedSupported and not doSeek and not paused): if (self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False):
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
if (paused == False and pauseChanged): if (paused == False and pauseChanged):
madeChangeOnPlayer = self._serverUnpaused(setBy) madeChangeOnPlayer = self._serverUnpaused(setBy)

View File

@ -9,7 +9,7 @@ DEFAULT_CONFIG_NAME = ".syncplay"
#Changing these might be ok #Changing these might be ok
REWIND_THRESHOLD = 4 REWIND_THRESHOLD = 4
SEEK_THRESHOLD = 0.9 SEEK_THRESHOLD = 1
SLOWDOWN_RATE = 0.95 SLOWDOWN_RATE = 0.95
SLOWDOWN_KICKIN_THRESHOLD = 1.5 SLOWDOWN_KICKIN_THRESHOLD = 1.5
SLOWDOWN_RESET_THRESHOLD = 0.1 SLOWDOWN_RESET_THRESHOLD = 0.1

View File

@ -28,6 +28,7 @@ class VlcPlayer(BasePlayer):
self._duration = None self._duration = None
self._filename = None self._filename = None
self._filepath = None self._filepath = None
self._filechanged = False
self._durationAsk = threading.Event() self._durationAsk = threading.Event()
self._filenameAsk = threading.Event() self._filenameAsk = threading.Event()
@ -68,12 +69,16 @@ class VlcPlayer(BasePlayer):
self.setPosition(self._client.getGlobalPosition()) self.setPosition(self._client.getGlobalPosition())
def askForStatus(self): def askForStatus(self):
self._filechanged = False
self._positionAsk.clear() self._positionAsk.clear()
self._pausedAsk.clear() self._pausedAsk.clear()
self._listener.sendLine(".") self._listener.sendLine(".")
self._positionAsk.wait() if self._filechanged == False:
self._pausedAsk.wait() self._positionAsk.wait()
self._client.updatePlayerStatus(self._paused, self._position) self._pausedAsk.wait()
self._client.updatePlayerStatus(self._paused, self._position)
else:
self._client.updatePlayerStatus(self._client.getGlobalPaused(), self._client.getGlobalPosition())
def displayMessage(self, message, duration = constants.OSD_DURATION * 1000): def displayMessage(self, message, duration = constants.OSD_DURATION * 1000):
duration /= 1000 duration /= 1000
@ -101,10 +106,12 @@ class VlcPlayer(BasePlayer):
name, value = match.group('command'), match.group('argument') name, value = match.group('command'), match.group('argument')
if(line == "filepath-change-notification"): if(line == "filepath-change-notification"):
self._filechanged = True
t = threading.Thread(target=self._onFileUpdate) t = threading.Thread(target=self._onFileUpdate)
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
elif (name == "filepath" and value != "no-input"): elif (name == "filepath" and value != "no-input"):
self._filechanged = True
if("file://" in value): if("file://" in value):
value = value.replace("file://", "") value = value.replace("file://", "")
if(not os.path.isfile(value)): if(not os.path.isfile(value)):
@ -115,12 +122,13 @@ class VlcPlayer(BasePlayer):
self._duration = float(value.replace(",", ".")) self._duration = float(value.replace(",", "."))
self._durationAsk.set() self._durationAsk.set()
elif(name == "playstate"): elif(name == "playstate"):
self._paused = bool(value != 'playing') if(value != "no-input") else self._client.getGlobalPaused() self._paused = bool(value != 'playing') if(value != "no-input" and self._filechanged == False) else self._client.getGlobalPaused()
self._pausedAsk.set() self._pausedAsk.set()
elif(name == "position"): elif(name == "position"):
self._position = float(value.replace(",", ".")) if (value != "no-input") else self._client.getGlobalPosition() self._position = float(value.replace(",", ".")) if (value != "no-input" and self._filechanged == False) else self._client.getGlobalPosition()
self._positionAsk.set() self._positionAsk.set()
elif(name == "filename"): elif(name == "filename"):
self._filechanged = True
self._filename = value self._filename = value
self._filenameAsk.set() self._filenameAsk.set()
elif (line[:16] == "VLC media player"): elif (line[:16] == "VLC media player"):
@ -217,4 +225,4 @@ class VlcPlayer(BasePlayer):
def sendLine(self, line): def sendLine(self, line):
if(self.connected): if(self.connected):
# print "send: {}".format(line) # print "send: {}".format(line)
self.push(line + "\n") self.push(line + "\n")

View File

@ -1,132 +1,256 @@
import pygtk from PySide import QtCore, QtGui
from PySide.QtCore import QSettings, Qt
from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices
import os import os
pygtk.require('2.0') import sys
import gtk
gtk.set_interactive(False)
import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport
from syncplay.messages import getMessage from syncplay.messages import getMessage
class GuiConfiguration: class GuiConfiguration:
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self._availablePlayerPaths = [] self._availablePlayerPaths = []
self.closedAndNotSaved = False
def run(self): def run(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.app = QtGui.QApplication(sys.argv)
self.window.set_title(getMessage("en", "config-window-title")) dialog = ConfigDialog(self.config, self._availablePlayerPaths)
self.window.connect("delete_event", lambda w, e: self._windowClosed()) dialog.exec_()
vbox = gtk.VBox(False, 0)
self.window.add(vbox) def setAvailablePaths(self, paths):
vbox.show() self._availablePlayerPaths = paths
self._addLabeledEntries(self.config, vbox)
self._addCheckboxEntries(self.config, vbox) def getProcessedConfiguration(self):
self.hostEntry.select_region(0, len(self.hostEntry.get_text())) return self.config
button = gtk.Button(stock=gtk.STOCK_SAVE)
button.connect("clicked", lambda w: self._saveDataAndLeave()) class WindowClosed(Exception):
guideLink = gtk.LinkButton("http://syncplay.pl/guide/", "Configuration Guide") pass
guideLink.show()
vbox.add(guideLink) class ConfigDialog(QtGui.QDialog):
vbox.pack_start(button, True, True, 0)
button.set_flags(gtk.CAN_DEFAULT) pressedclosebutton = False
button.grab_default()
button.show() malToggling = False
self.window.show()
gtk.main() def malToggled(self):
if self.malToggling == False:
def _windowClosed(self): self.malToggling = True
self.window.destroy()
gtk.main_quit() if self.malenabledCheckbox.isChecked() and self.malenabledCheckbox.isVisible():
self.closedAndNotSaved = True self.malenabledCheckbox.setChecked(False)
self.malSettingsGroup.setChecked(True)
self.malSettingsGroup.show()
self.malpasswordLabel.show()
self.malpasswordTextbox.show()
self.malusernameLabel.show()
self.malusernameTextbox.show()
self.malenabledCheckbox.hide()
else:
self.malSettingsGroup.setChecked(False)
self.malSettingsGroup.hide()
self.malpasswordLabel.hide()
self.malpasswordTextbox.hide()
self.malusernameLabel.hide()
self.malusernameTextbox.hide()
self.malenabledCheckbox.show()
self.malToggling = False
self.adjustSize()
self.setFixedSize(self.sizeHint())
def runButtonTextUpdate(self):
if (self.donotstoreCheckbox.isChecked()):
self.runButton.setText("Run Syncplay")
else:
self.runButton.setText("Store configuration and run Syncplay")
def openHelp(self):
self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/")
def _tryToFillPlayerPath(self, playerpath, playerpathlist):
foundpath = ""
def _addLabeledEntries(self, config, vbox): if playerpath != None and playerpath != "" and os.path.isfile(playerpath):
foundpath = playerpath
self.executablepathCombobox.addItem(foundpath)
for path in playerpathlist:
if(os.path.isfile(path) and path.lower() != foundpath.lower()):
self.executablepathCombobox.addItem(path)
if foundpath == None:
foundpath = path
if foundpath:
return(foundpath)
else:
return("")
def browsePlayerpath(self):
options = QtGui.QFileDialog.Options()
defaultdirectory = ""
browserfilter = "All Files (*)"
if os.name == 'nt':
browserfilter = "Executable files (*.exe);;All Files (*)"
if "PROGRAMFILES(X86)" in os.environ:
defaultdirectory = os.environ["ProgramFiles(x86)"]
elif "PROGRAMFILES" in os.environ:
defaultdirectory = os.environ["ProgramFiles"]
elif "PROGRAMW6432" in os.environ:
defaultdirectory = os.environ["ProgramW6432"]
elif sys.platform.startswith('linux'):
defaultdirectory = "/usr/bin"
fileName, filtr = QtGui.QFileDialog.getOpenFileName(self,
"Browse for media player executable",
defaultdirectory,
browserfilter, "", options)
if fileName:
self.executablepathCombobox.setEditText(fileName)
def _saveDataAndLeave(self):
self.config['host'] = self.hostTextbox.text()
self.config['name'] = self.usernameTextbox.text()
self.config['room'] = self.defaultroomTextbox.text()
self.config['password'] = self.serverpassTextbox.text()
self.config['playerPath'] = self.executablepathCombobox.currentText()
if self.alwaysshowCheckbox.isChecked() == True:
self.config['forceGuiPrompt'] = True
else:
self.config['forceGuiPrompt'] = False
if self.donotstoreCheckbox.isChecked() == True:
self.config['noStore'] = True
else:
self.config['noStore'] = False
if self.slowdownCheckbox.isChecked() == True:
self.config['slowOnDesync'] = True
else:
self.config['slowOnDesync'] = False
self.config['malUsername'] = self.malusernameTextbox.text()
if self.malSettingsGroup.isChecked():
self.config['malPassword'] = self.malpasswordTextbox.text()
else:
self.config['malPassword'] = ""
self.pressedclosebutton = True
self.close()
return
def closeEvent(self, event):
if self.pressedclosebutton == False:
sys.exit()
raise GuiConfiguration.WindowClosed
event.accept()
def __init__(self, config, playerpaths):
self.config = config
self.QtGui = QtGui
super(ConfigDialog, self).__init__()
self.setWindowTitle(getMessage("en", "config-window-title"))
if(config['host'] == None): if(config['host'] == None):
host = "" host = ""
elif(":" in config['host']): elif(":" in config['host']):
host = config['host'] host = config['host']
else: else:
host = config['host']+":"+str(config['port']) host = config['host']+":"+str(config['port'])
self.hostEntry = self._addLabeledEntryToVbox(getMessage("en", "host-label"), host, vbox, lambda __, _: self._saveDataAndLeave()) self.connectionSettingsGroup = QtGui.QGroupBox("Connection Settings")
self.userEntry = self._addLabeledEntryToVbox(getMessage("en", "username-label"), config['name'], vbox, lambda __, _: self._saveDataAndLeave()) self.hostTextbox = QLineEdit(host, self)
self.roomEntry = self._addLabeledEntryToVbox(getMessage("en", "room-label"), config['room'], vbox, lambda __, _: self._saveDataAndLeave()) self.hostLabel = QLabel(getMessage("en", "host-label"), self)
self.passEntry = self._addLabeledEntryToVbox(getMessage("en", "password-label"), config['password'], vbox, lambda __, _: self._saveDataAndLeave()) self.usernameTextbox = QLineEdit(config['name'],self)
self.mpcEntry = self._addLabeledEntryToVbox(getMessage("en", "path-label"), self._tryToFillPlayerPath(), vbox, lambda __, _: self._saveDataAndLeave()) self.serverpassLabel = QLabel(getMessage("en", "password-label"), self)
self.defaultroomTextbox = QLineEdit(config['room'],self)
self.usernameLabel = QLabel(getMessage("en", "username-label"), self)
self.serverpassTextbox = QLineEdit(config['password'],self)
self.defaultroomLabel = QLabel(getMessage("en", "room-label"), self)
self.connectionSettingsLayout = QtGui.QGridLayout()
self.connectionSettingsLayout.addWidget(self.hostLabel, 0, 0)
self.connectionSettingsLayout.addWidget(self.hostTextbox, 0, 1)
self.connectionSettingsLayout.addWidget(self.serverpassLabel, 1, 0)
self.connectionSettingsLayout.addWidget(self.serverpassTextbox, 1, 1)
self.connectionSettingsLayout.addWidget(self.usernameLabel, 2, 0)
self.connectionSettingsLayout.addWidget(self.usernameTextbox, 2, 1)
self.connectionSettingsLayout.addWidget(self.defaultroomLabel, 3, 0)
self.connectionSettingsLayout.addWidget(self.defaultroomTextbox, 3, 1)
self.connectionSettingsGroup.setLayout(self.connectionSettingsLayout)
self.mediaplayerSettingsGroup = QtGui.QGroupBox("Media Player Settings")
self.executablepathCombobox = QtGui.QComboBox(self)
self.executablepathCombobox.setEditable(True)
self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'],playerpaths))
self.executablepathCombobox.setMinimumWidth(200)
self.executablepathCombobox.setMaximumWidth(200)
self.executablepathLabel = QLabel("Path to player executable:", self)
self.executablebrowseButton = QtGui.QPushButton("Browse")
self.executablebrowseButton.clicked.connect(self.browsePlayerpath)
self.slowdownCheckbox = QCheckBox("Slow down on desync")
self.mediaplayerSettingsLayout = QtGui.QGridLayout()
self.mediaplayerSettingsLayout.addWidget(self.executablepathLabel, 0, 0)
self.mediaplayerSettingsLayout.addWidget(self.executablepathCombobox , 0, 1)
self.mediaplayerSettingsLayout.addWidget(self.executablebrowseButton , 0, 2)
self.mediaplayerSettingsLayout.addWidget(self.slowdownCheckbox, 1, 0)
self.mediaplayerSettingsGroup.setLayout(self.mediaplayerSettingsLayout)
if config['slowOnDesync'] == True:
self.slowdownCheckbox.setChecked(True)
def _tryToFillPlayerPath(self): self.malSettingsGroup = QtGui.QGroupBox("Enable MyAnimeList Updater (EXPERIMENTAL)")
for path in self._availablePlayerPaths: self.malSettingsGroup.setCheckable(True)
if(os.path.isfile(path)): self.malSettingsGroup.toggled.connect(self.malToggled)
return path self.malSettingsSplit = QtGui.QSplitter(self)
return self.config["playerPath"] self.malusernameTextbox = QLineEdit(config['malUsername'],self)
self.malusernameLabel = QLabel("MAL Username:", self)
def getProcessedConfiguration(self): self.malpasswordTextbox = QLineEdit(config['malPassword'],self)
if(self.closedAndNotSaved): self.malpasswordTextbox.setEchoMode(QtGui.QLineEdit.Password)
raise self.WindowClosed self.malpasswordLabel = QLabel("MAL Password:", self)
return self.config self.malSettingsLayout = QtGui.QGridLayout()
self.malSettingsLayout.addWidget(self.malusernameLabel , 0, 0)
def _saveDataAndLeave(self): self.malSettingsLayout.addWidget(self.malusernameTextbox, 0, 1)
self.config['host'] = self.hostEntry.get_text() self.malSettingsLayout.addWidget(self.malpasswordLabel , 1, 0)
self.config['name'] = self.userEntry.get_text() self.malSettingsLayout.addWidget(self.malpasswordTextbox, 1, 1)
self.config['room'] = self.roomEntry.get_text() self.malSettingsGroup.setLayout(self.malSettingsLayout)
self.config['password'] = self.passEntry.get_text()
self.config['playerPath'] = self.mpcEntry.get_text() self.malenabledCheckbox = QCheckBox("Enable MyAnimeList Updater (EXPERIMENTAL)")
if self.alwaysShowCheck.get_active() == True: self.malenabledCheckbox.toggled.connect(self.malToggled)
self.config['forceGuiPrompt'] = True if config['malPassword'] == None or config['malPassword'] == "":
self.malenabledCheckbox.setChecked(False)
self.malSettingsGroup.hide()
else: else:
self.config['forceGuiPrompt'] = False self.malenabledCheckbox.hide()
if self.storeConfigCheck.get_active() == True:
self.config['noStore'] = True self.alwaysshowCheckbox = QCheckBox("Always Show This Dialog")
else: if config['forceGuiPrompt'] == True:
self.config['noStore'] = False self.alwaysshowCheckbox.setChecked(True)
if self.slowOnDesyncCheck.get_active() == True:
self.config['slowOnDesync'] = True self.donotstoreCheckbox = QCheckBox("Do Not Store This Configuration")
else: if config['noStore'] == True:
self.config['slowOnDesync'] = False self.donotstoreCheckbox.setChecked(True)
self.window.destroy()
gtk.main_quit() self.donotstoreCheckbox.toggled.connect(self.runButtonTextUpdate)
def _addLabeledEntryToVbox(self, label, initialEntryValue, vbox, callback): self.mainLayout = QtGui.QVBoxLayout()
hbox = gtk.HBox(False, 0) self.mainLayout.addWidget(self.connectionSettingsGroup)
hbox.set_border_width(3) self.mainLayout.addSpacing(12)
vbox.pack_start(hbox, False, False, 0) self.mainLayout.addWidget(self.mediaplayerSettingsGroup)
hbox.show() self.mainLayout.addSpacing(12)
label_ = gtk.Label() self.mainLayout.addWidget(self.malenabledCheckbox)
label_.set_text(label) self.mainLayout.addWidget(self.malSettingsGroup)
label_.set_alignment(xalign=0, yalign=0.5)
hbox.pack_start(label_, False, False, 0) self.topLayout = QtGui.QHBoxLayout()
label_.show() self.helpButton = QtGui.QPushButton("Help")
entry = gtk.Entry() self.helpButton.setMaximumSize(self.helpButton.sizeHint())
entry.connect("activate", callback, entry) self.helpButton.pressed.connect(self.openHelp)
if(initialEntryValue == None): self.runButton = QtGui.QPushButton("Store configuration and run Syncplay")
initialEntryValue = "" self.runButton.pressed.connect(self._saveDataAndLeave)
entry.set_text(initialEntryValue) self.runButtonTextUpdate
hbox.pack_end(entry, False, False, 0) self.topLayout.addWidget(self.helpButton, Qt.AlignLeft)
entry.set_usize(200, -1) self.topLayout.addWidget(self.runButton, Qt.AlignRight)
entry.show() self.mainLayout.addWidget(self.alwaysshowCheckbox)
return entry self.mainLayout.addWidget(self.donotstoreCheckbox)
self.mainLayout.addLayout(self.topLayout)
def _addCheckboxEntries(self, config, vbox):
CheckVbox = gtk.VBox(False, 0) self.mainLayout.addStretch(1)
vbox.pack_start(CheckVbox, False, False, 0)
self.alwaysShowCheck = gtk.CheckButton("Always Show This Dialog") self.setLayout(self.mainLayout)
if self.config['forceGuiPrompt'] == True: self.setFixedSize(self.sizeHint())
self.alwaysShowCheck.set_active(True)
self.alwaysShowCheck.show()
self.storeConfigCheck = gtk.CheckButton("Do Not Store This Configuration")
if self.config['noStore'] == True:
self.storeConfigCheck.set_active(True)
self.storeConfigCheck.show()
self.slowOnDesyncCheck = gtk.CheckButton("Slow Down On Desync")
if self.config['slowOnDesync'] == True:
self.slowOnDesyncCheck.set_active(True)
self.slowOnDesyncCheck.show()
CheckVbox.pack_start(self.alwaysShowCheck, False, False, 0)
CheckVbox.add(self.storeConfigCheck)
CheckVbox.add(self.slowOnDesyncCheck)
CheckVbox.show()
def setAvailablePaths(self, paths):
self._availablePlayerPaths = paths
class WindowClosed(Exception):
pass