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"):

View File

@ -1,45 +1,154 @@
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)
vbox.show()
self._addLabeledEntries(self.config, vbox)
self._addCheckboxEntries(self.config, vbox)
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
button = gtk.Button(stock=gtk.STOCK_SAVE)
button.connect("clicked", lambda w: self._saveDataAndLeave())
guideLink = gtk.LinkButton("http://syncplay.pl/guide/", "Configuration Guide")
guideLink.show()
vbox.add(guideLink)
vbox.pack_start(button, True, True, 0)
button.set_flags(gtk.CAN_DEFAULT)
button.grab_default()
button.show()
self.window.show()
gtk.main()
def _windowClosed(self): def setAvailablePaths(self, paths):
self.window.destroy() self._availablePlayerPaths = paths
gtk.main_quit()
self.closedAndNotSaved = True def getProcessedConfiguration(self):
return self.config
class WindowClosed(Exception):
pass
class ConfigDialog(QtGui.QDialog):
pressedclosebutton = False
malToggling = False
def malToggled(self):
if self.malToggling == False:
self.malToggling = True
if self.malenabledCheckbox.isChecked() and self.malenabledCheckbox.isVisible():
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 = ""
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"))
def _addLabeledEntries(self, config, vbox):
if(config['host'] == None): if(config['host'] == None):
host = "" host = ""
elif(":" in config['host']): elif(":" in config['host']):
@ -47,86 +156,101 @@ class GuiConfiguration:
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)
def _tryToFillPlayerPath(self): self.mediaplayerSettingsGroup = QtGui.QGroupBox("Media Player Settings")
for path in self._availablePlayerPaths: self.executablepathCombobox = QtGui.QComboBox(self)
if(os.path.isfile(path)): self.executablepathCombobox.setEditable(True)
return path self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'],playerpaths))
return self.config["playerPath"] 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 getProcessedConfiguration(self): self.malSettingsGroup = QtGui.QGroupBox("Enable MyAnimeList Updater (EXPERIMENTAL)")
if(self.closedAndNotSaved): self.malSettingsGroup.setCheckable(True)
raise self.WindowClosed self.malSettingsGroup.toggled.connect(self.malToggled)
return self.config self.malSettingsSplit = QtGui.QSplitter(self)
self.malusernameTextbox = QLineEdit(config['malUsername'],self)
self.malusernameLabel = QLabel("MAL Username:", self)
self.malpasswordTextbox = QLineEdit(config['malPassword'],self)
self.malpasswordTextbox.setEchoMode(QtGui.QLineEdit.Password)
self.malpasswordLabel = QLabel("MAL Password:", self)
self.malSettingsLayout = QtGui.QGridLayout()
self.malSettingsLayout.addWidget(self.malusernameLabel , 0, 0)
self.malSettingsLayout.addWidget(self.malusernameTextbox, 0, 1)
self.malSettingsLayout.addWidget(self.malpasswordLabel , 1, 0)
self.malSettingsLayout.addWidget(self.malpasswordTextbox, 1, 1)
self.malSettingsGroup.setLayout(self.malSettingsLayout)
def _saveDataAndLeave(self): self.malenabledCheckbox = QCheckBox("Enable MyAnimeList Updater (EXPERIMENTAL)")
self.config['host'] = self.hostEntry.get_text() self.malenabledCheckbox.toggled.connect(self.malToggled)
self.config['name'] = self.userEntry.get_text() if config['malPassword'] == None or config['malPassword'] == "":
self.config['room'] = self.roomEntry.get_text() self.malenabledCheckbox.setChecked(False)
self.config['password'] = self.passEntry.get_text() self.malSettingsGroup.hide()
self.config['playerPath'] = self.mpcEntry.get_text()
if self.alwaysShowCheck.get_active() == True:
self.config['forceGuiPrompt'] = True
else: else:
self.config['forceGuiPrompt'] = False self.malenabledCheckbox.hide()
if self.storeConfigCheck.get_active() == True:
self.config['noStore'] = True
else:
self.config['noStore'] = False
if self.slowOnDesyncCheck.get_active() == True:
self.config['slowOnDesync'] = True
else:
self.config['slowOnDesync'] = False
self.window.destroy()
gtk.main_quit()
def _addLabeledEntryToVbox(self, label, initialEntryValue, vbox, callback): self.alwaysshowCheckbox = QCheckBox("Always Show This Dialog")
hbox = gtk.HBox(False, 0) if config['forceGuiPrompt'] == True:
hbox.set_border_width(3) self.alwaysshowCheckbox.setChecked(True)
vbox.pack_start(hbox, False, False, 0)
hbox.show()
label_ = gtk.Label()
label_.set_text(label)
label_.set_alignment(xalign=0, yalign=0.5)
hbox.pack_start(label_, False, False, 0)
label_.show()
entry = gtk.Entry()
entry.connect("activate", callback, entry)
if(initialEntryValue == None):
initialEntryValue = ""
entry.set_text(initialEntryValue)
hbox.pack_end(entry, False, False, 0)
entry.set_usize(200, -1)
entry.show()
return entry
def _addCheckboxEntries(self, config, vbox): self.donotstoreCheckbox = QCheckBox("Do Not Store This Configuration")
CheckVbox = gtk.VBox(False, 0) if config['noStore'] == True:
vbox.pack_start(CheckVbox, False, False, 0) self.donotstoreCheckbox.setChecked(True)
self.alwaysShowCheck = gtk.CheckButton("Always Show This Dialog")
if self.config['forceGuiPrompt'] == True:
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.donotstoreCheckbox.toggled.connect(self.runButtonTextUpdate)
self._availablePlayerPaths = paths
class WindowClosed(Exception): self.mainLayout = QtGui.QVBoxLayout()
pass self.mainLayout.addWidget(self.connectionSettingsGroup)
self.mainLayout.addSpacing(12)
self.mainLayout.addWidget(self.mediaplayerSettingsGroup)
self.mainLayout.addSpacing(12)
self.mainLayout.addWidget(self.malenabledCheckbox)
self.mainLayout.addWidget(self.malSettingsGroup)
self.topLayout = QtGui.QHBoxLayout()
self.helpButton = QtGui.QPushButton("Help")
self.helpButton.setMaximumSize(self.helpButton.sizeHint())
self.helpButton.pressed.connect(self.openHelp)
self.runButton = QtGui.QPushButton("Store configuration and run Syncplay")
self.runButton.pressed.connect(self._saveDataAndLeave)
self.runButtonTextUpdate
self.topLayout.addWidget(self.helpButton, Qt.AlignLeft)
self.topLayout.addWidget(self.runButton, Qt.AlignRight)
self.mainLayout.addWidget(self.alwaysshowCheckbox)
self.mainLayout.addWidget(self.donotstoreCheckbox)
self.mainLayout.addLayout(self.topLayout)
self.mainLayout.addStretch(1)
self.setLayout(self.mainLayout)
self.setFixedSize(self.sizeHint())