Add min user requirement to autoplay, make settings persistent (and settable via INI), and tooltips

This commit is contained in:
Et0h 2015-02-02 00:24:33 +00:00
parent 31c83fbc8a
commit a8275032a2
5 changed files with 141 additions and 37 deletions

View File

@ -11,7 +11,6 @@ from syncplay.messages import getMissingStrings, getMessage
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
PRIVACY_HIDDENFILENAME
import collections
class SyncClientFactory(ClientFactory):
def __init__(self, client, retry=constants.RECONNECT_RETRIES):
self._client = client
@ -106,6 +105,7 @@ class SyncplayClient(object):
self._speedChanged = False
self.behindFirstDetected = None
self.autoPlay = False
self.autoPlayThreshold = None
self._warnings = self._WarningManager(self._player, self.userlist, self.ui, self)
if constants.LIST_RELATIVE_CONFIGS and self._config.has_key('loadedRelativePaths') and self._config['loadedRelativePaths']:
@ -319,7 +319,6 @@ class SyncplayClient(object):
self.ui.showMessage(getMessage("current-offset-notification").format(self._userOffset))
def onDisconnect(self):
self.resetAutoPlayState()
if self._config['pauseOnLeave']:
self.setPaused(True)
self.lastPausedOnLeaveTime = time.time()
@ -404,9 +403,10 @@ class SyncplayClient(object):
def getUsername(self):
return self.userlist.currentUser.username
def setRoom(self, roomName):
def setRoom(self, roomName, resetAutoplay=False):
self.userlist.currentUser.room = roomName
self.resetAutoPlayState()
if resetAutoplay:
self.resetAutoPlayState()
def sendRoom(self):
room = self.userlist.currentUser.room
@ -493,12 +493,16 @@ class SyncplayClient(object):
return wrapper
return requireMinVersionDecorator
def changeAutoPlayState(self, newState):
def changeAutoplayState(self, newState):
self.autoPlay = newState
self.autoPlayCheck()
self.autoplayCheck()
def changeAutoPlayThrehsold(self, newThreshold):
self.autoPlayThreshold = newThreshold
self.autoplayCheck()
def autoPlayCheck(self):
if self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady():
def autoplayCheck(self):
if self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady() and self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold:
self.setPaused(False)
def resetAutoPlayState(self):
@ -532,7 +536,7 @@ class SyncplayClient(object):
def controlledRoomCreated(self, roomName, controlPassword):
self.ui.showMessage(getMessage("created-controlled-room-notification").format(roomName, controlPassword))
self.setRoom(roomName)
self.setRoom(roomName, resetAutoplay=True)
self.sendRoom()
self._protocol.requestControlledRoom(roomName, controlPassword)
self.ui.updateRoomName(roomName)
@ -911,6 +915,13 @@ class SyncplayUserlist(object):
if user.room == self.currentUser.room and user.isReady():
readyCount += 1
return readyCount
def usersInRoomCount(self):
userCount = 1
for user in self._users.itervalues():
if user.room == self.currentUser.room and user.isReady():
userCount += 1
return userCount
def usersInRoomNotReady(self):
notReady = []
@ -970,7 +981,7 @@ class SyncplayUserlist(object):
self.currentUser.setReady(isReady)
elif self._users.has_key(username):
self._users[username].setReady(isReady)
self._client.autoPlayCheck()
self._client.autoplayCheck()
def userListChange(self, room = None):
if room is not None and self.isRoomSame(room):
@ -994,7 +1005,7 @@ class SyncplayUserlist(object):
rooms[self.currentUser.room].append(self.currentUser)
rooms = self.sortList(rooms)
self.ui.showUserList(self.currentUser, rooms)
self._client.autoPlayCheck()
self._client.autoplayCheck()
def clearList(self):
self._users = {}

View File

@ -219,7 +219,8 @@ en = {
"pause-menu-label" : "Pause",
"playbackbuttons-menu-label" : u"Show playback buttons",
"autoplay-menu-label" : u"Show auto-play button",
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready",
"autoplay-guipushbuttonlabel" : u"Play when all ready",
"autoplay-minimum-label" : u"Min users:",
"ready-guipushbuttonlabel" : u"I'm ready to watch!",
@ -304,6 +305,8 @@ en = {
"joinroom-tooltip" : "Leave current room and joins specified room.",
"seektime-msgbox-label" : "Jump to specified time (in seconds / min:sec). Use +/- for relative seek.",
"ready-tooltip" : "Indicates whether you are ready to watch.",
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.",
# In-userlist notes (GUI)
"differentsize-note" : "Different size!",
@ -562,6 +565,7 @@ ru = {
"playbackbuttons-menu-label" : u"Show playback buttons", # TODO: Translate into Russian
"autoplay-menu-label" : u"Show auto-play button", # TODO: Translate into Russian
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready", # TODO: Translate into Russian
"autoplay-minimum-label" : u"Min users:", # TODO: Translate into Russian
"ready-guipushbuttonlabel" : u"I'm ready to watch!", # TODO: Translate into Russian
@ -647,6 +651,8 @@ ru = {
"joinroom-tooltip" : u"Покинуть комнату и зайти в другую, указанную комнату.",
"seektime-msgbox-label" : u"Перемотать к определенному моменту времени (указывать в секундах или мин:сек). Используйте +/-, чтобы перемотать вперед/назад относительно настоящего момента.",
"ready-tooltip" : "Indicates whether you are ready to watch.", # TODO: Translate into Russian
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", # TODO: Translate into Russian
# In-userlist notes (GUI)
"differentsize-note" : u"Размер файла не совпадает!",
@ -903,6 +909,7 @@ de = {
"playbackbuttons-menu-label" : u"Show playback buttons", # TODO: Translate into German
"autoplay-menu-label" : u"Show auto-play button", # TODO: Translate into German
"autoplay-guipushbuttonlabel" : u"Auto-play when everyone is ready", # TODO: Translate into German
"autoplay-minimum-label" : u"Min users:", # TODO: Translate into German
"ready-guipushbuttonlabel" : u"I'm ready to watch!", # TODO: Translate into German
@ -989,6 +996,8 @@ de = {
"joinroom-tooltip" : u"Den aktuellen Raum verlassen und stattdessen den angegebenen betreten.",
"seektime-msgbox-label" : u"Springe zur angegebenen Zeit (in Sekunden oder min:sek). Verwende +/- zum relativen Springen.",
"ready-tooltip" : "Indicates whether you are ready to watch.", # TODO: Translate into German
"autoplay-tooltip" : "Auto-play when all users who have readiness indicator are ready and minimum user threshold met.", # TODO: Translate into German
# In-userlist notes (GUI)
"differentsize-note" : u"Verschiedene Größe!",

View File

@ -45,6 +45,8 @@ class ConfigurationGetter(object):
"filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE,
"pauseOnLeave": False,
"readyAtStart": False,
"autoplayInitialState" : None,
"autoplayInitialThreshold" : -1,
"clearGUIData": False,
"language" : "",
"checkForUpdatesAutomatically" : None,
@ -97,18 +99,20 @@ class ConfigurationGetter(object):
"showDurationNotification"
]
self._tristate = [
"checkForUpdatesAutomatically"
"checkForUpdatesAutomatically",
"autoplayInitialState",
]
self._numeric = [
"slowdownThreshold",
"rewindThreshold",
"fastforwardThreshold",
"autoplayInitialThreshold",
]
self._iniStructure = {
"server_data": ["host", "port", "password"],
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart"],
"client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart", "autoplayInitialThreshold", "autoplayInitialState"],
"gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showDurationNotification"],
"general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"]
}

View File

@ -144,7 +144,7 @@ class ConsoleUI(threading.Thread):
else:
room = self._syncplayClient.defaultRoom
self._syncplayClient.setRoom(room)
self._syncplayClient.setRoom(room, resetAutoplay=True)
self._syncplayClient.sendRoom()
elif command.group('command') in constants.COMMANDS_CREATE:
roombasename = command.group('parameter')

View File

@ -8,6 +8,7 @@ from datetime import datetime
import re
import os
from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize
from functools import wraps
lastCheckedForUpdates = None
class UserlistItemDelegate(QtGui.QStyledItemDelegate):
@ -71,14 +72,35 @@ class MainWindow(QtGui.QMainWindow):
QtGui.QSplitterHandle.mouseMoveEvent(self, event)
self.parent().parent().parent().updateListGeometry()
def needsClient(f): # @NoSelf
@wraps(f)
def wrapper(self, *args, **kwds):
if not self._syncplayClient:
self.showDebugMessage("Tried to use client before it was ready!")
return
return f(self, *args, **kwds)
return wrapper
def addClient(self, client):
self._syncplayClient = client
self.roomInput.setText(self._syncplayClient.getRoom())
self.config = self._syncplayClient.getConfig()
try:
self.updateReadyState(self.config['readyAtStart'])
self.updateReadyState(self.config['readyAtStart'])
autoplayInitialState = self.config['autoplayInitialState']
if autoplayInitialState is not None:
self.autoplayPushButton.blockSignals(True)
self.autoplayPushButton.setChecked(autoplayInitialState)
self.autoplayPushButton.blockSignals(False)
if self.config['autoplayInitialThreshold'] > 1:
self.autoplayThresholdSpinbox.blockSignals(True)
self.autoplayThresholdSpinbox.setValue(self.config['autoplayInitialThreshold'])
self.autoplayThresholdSpinbox.blockSignals(False)
self.changeAutoplayState()
self.changeAutoplayThreshold()
self.updateAutoPlayIcon()
except:
pass
self.showErrorMessage("Failed to load some settings.")
self.automaticUpdateCheck()
def promptFor(self, prompt=">", message=""):
@ -212,6 +234,7 @@ class MainWindow(QtGui.QMainWindow):
item = item.parent()
self.joinRoom(item.sibling(item.row(), 0).data())
@needsClient
def userListChange(self):
self._syncplayClient.showUserList()
@ -230,6 +253,7 @@ class MainWindow(QtGui.QMainWindow):
message = "<span style=\"{}\">".format(constants.STYLE_ERRORNOTIFICATION) + message + "</span>"
self.newMessage(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message + "<br />")
@needsClient
def joinRoom(self, room=None):
if room == None:
room = self.roomInput.text()
@ -240,7 +264,7 @@ class MainWindow(QtGui.QMainWindow):
room = self._syncplayClient.defaultRoom
self.roomInput.setText(room)
if room != self._syncplayClient.getRoom():
self._syncplayClient.setRoom(room)
self._syncplayClient.setRoom(room, resetAutoplay=True)
self._syncplayClient.sendRoom()
def seekPositionDialog(self):
@ -253,6 +277,7 @@ class MainWindow(QtGui.QMainWindow):
def seekFromButton(self):
self.seekPosition(self.seekInput.text())
@needsClient
def seekPosition(self, seekTime):
s = re.match(constants.UI_SEEK_REGEX, seekTime)
if s:
@ -266,20 +291,25 @@ class MainWindow(QtGui.QMainWindow):
else:
self.showErrorMessage(getMessage("invalid-seek-value"))
@needsClient
def undoSeek(self):
tmp_pos = self._syncplayClient.getPlayerPosition()
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
@needsClient
def togglePause(self):
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
@needsClient
def play(self):
self._syncplayClient.setPaused(False)
@needsClient
def pause(self):
self._syncplayClient.setPaused(True)
@needsClient
def exitSyncplay(self):
self._syncplayClient.stop()
@ -299,6 +329,7 @@ class MainWindow(QtGui.QMainWindow):
settings.setValue("mediadir", self.mediadirectory)
settings.endGroup()
@needsClient
def browseMediapath(self):
if self._syncplayClient._player.customOpenDialog == True:
self._syncplayClient._player.openCustomOpenDialog()
@ -324,6 +355,7 @@ class MainWindow(QtGui.QMainWindow):
self.saveMediaBrowseSettings()
self._syncplayClient._player.openFile(fileName)
@needsClient
def promptForStreamURL(self):
streamURL, ok = QtGui.QInputDialog.getText(self, getMessage("promptforstreamurl-msgbox-label"),
getMessage("promptforstreamurlinfo-msgbox-label"), QtGui.QLineEdit.Normal,
@ -331,6 +363,7 @@ class MainWindow(QtGui.QMainWindow):
if ok and streamURL != '':
self._syncplayClient._player.openFile(streamURL)
@needsClient
def createControlledRoom(self):
controlroom, ok = QtGui.QInputDialog.getText(self, getMessage("createcontrolledroom-msgbox-label"),
getMessage("controlledroominfo-msgbox-label"), QtGui.QLineEdit.Normal,
@ -338,6 +371,7 @@ class MainWindow(QtGui.QMainWindow):
if ok and controlroom != '':
self._syncplayClient.createControlledRoom(controlroom)
@needsClient
def identifyAsController(self):
msgboxtitle = getMessage("identifyascontroller-msgbox-label")
msgboxtext = getMessage("identifyinfo-msgbox-label")
@ -354,6 +388,7 @@ class MainWindow(QtGui.QMainWindow):
else:
return None
@needsClient
def setOffset(self):
newoffset, ok = QtGui.QInputDialog.getText(self, getMessage("setoffset-msgbox-label"),
getMessage("offsetinfo-msgbox-label"), QtGui.QLineEdit.Normal,
@ -465,19 +500,41 @@ class MainWindow(QtGui.QMainWindow):
window.readyPushButton.toggled.connect(self.changeReadyState)
window.readyPushButton.setFont(readyFont)
window.readyPushButton.setStyleSheet(constants.STYLE_READY_PUSHBUTTON)
window.readyPushButton.setToolTip(getMessage("ready-tooltip"))
window.listLayout.addWidget(window.readyPushButton, Qt.AlignRight)
window.autoPlayPushButton = QtGui.QPushButton()
window.autoPlayPushButton.setVisible(False)
window.autoplayLayout = QtGui.QHBoxLayout()
window.autoplayFrame = QtGui.QFrame()
window.autoplayFrame.setVisible(False)
window.autoplayLayout.setContentsMargins(0,0,0,0)
window.autoplayFrame.setLayout(window.autoplayLayout)
window.autoplayPushButton = QtGui.QPushButton()
autoPlayFont = QtGui.QFont()
autoPlayFont.setWeight(QtGui.QFont.Bold)
window.autoPlayPushButton.setText(getMessage("autoplay-guipushbuttonlabel"))
window.autoPlayPushButton.setCheckable(True)
window.autoPlayPushButton.setAutoExclusive(False)
window.autoPlayPushButton.toggled.connect(self.changeAutoPlayState)
window.autoPlayPushButton.setFont(autoPlayFont)
window.autoPlayPushButton.setStyleSheet(constants.STYLE_AUTO_PLAY_PUSHBUTTON)
window.listLayout.addWidget(window.autoPlayPushButton, Qt.AlignRight)
self.updateAutoPlayIcon()
window.autoplayPushButton.setText(getMessage("autoplay-guipushbuttonlabel"))
window.autoplayPushButton.setCheckable(True)
window.autoplayPushButton.setAutoExclusive(False)
window.autoplayPushButton.toggled.connect(self.changeAutoplayState)
window.autoplayPushButton.setFont(autoPlayFont)
window.autoplayPushButton.setStyleSheet(constants.STYLE_AUTO_PLAY_PUSHBUTTON)
window.autoplayPushButton.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
window.autoplayPushButton.setToolTip(getMessage("autoplay-tooltip"))
window.autoplayLabel = QtGui.QLabel(getMessage("autoplay-minimum-label"))
window.autoplayLabel.setSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum)
window.autoplayLabel.setMaximumWidth(window.autoplayLabel.minimumSizeHint().width())
window.autoplayLabel.setToolTip(getMessage("autoplay-tooltip"))
window.autoplayThresholdSpinbox = QtGui.QSpinBox()
window.autoplayThresholdSpinbox.setMaximumWidth(window.autoplayThresholdSpinbox.minimumSizeHint().width())
window.autoplayThresholdSpinbox.setMinimum(2)
window.autoplayThresholdSpinbox.setMaximum(99)
window.autoplayThresholdSpinbox.setToolTip(getMessage("autoplay-tooltip"))
window.autoplayThresholdSpinbox.valueChanged.connect(self.changeAutoplayThreshold)
window.autoplayLayout.addWidget(window.autoplayPushButton, Qt.AlignRight)
window.autoplayLayout.addWidget(window.autoplayLabel, Qt.AlignRight)
window.autoplayLayout.addWidget(window.autoplayThresholdSpinbox, Qt.AlignRight)
window.listLayout.addWidget(window.autoplayFrame, Qt.AlignLeft)
window.autoplayFrame.setMaximumHeight(window.autoplayFrame.sizeHint().height())
window.mainLayout.addWidget(window.bottomFrame, Qt.AlignLeft)
window.bottomFrame.setMaximumHeight(window.bottomFrame.minimumSizeHint().height())
@ -617,21 +674,34 @@ class MainWindow(QtGui.QMainWindow):
self.playbackFrame.setVisible(self.playbackAction.isChecked())
def updateAutoplayVisibility(self):
self.autoPlayPushButton.setVisible(self.autoplayAction.isChecked())
self.autoplayFrame.setVisible(self.autoplayAction.isChecked())
def changeReadyState(self):
self.updateReadyIcon()
self._syncplayClient.changeReadyState(self.readyPushButton.isChecked())
if self._syncplayClient:
self._syncplayClient.changeReadyState(self.readyPushButton.isChecked())
else:
self.showDebugMessage("Tried to change ready state too soon.")
@needsClient
def changeAutoplayThreshold(self, source=None):
self._syncplayClient.changeAutoPlayThrehsold(self.autoplayThresholdSpinbox.value())
def updateAutoPlayState(self, newState):
oldState = self.autoPlayPushButton.isChecked()
oldState = self.autoplayPushButton.isChecked()
if newState != oldState and newState != None:
self.autoPlayPushButton.setChecked(newState)
self.autoplayPushButton.blockSignals(True)
self.autoplayPushButton.setChecked(newState)
self.autoplayPushButton.blockSignals(False)
self.updateAutoPlayIcon()
def changeAutoPlayState(self):
@needsClient
def changeAutoplayState(self, source=None):
self.updateAutoPlayIcon()
self._syncplayClient.changeAutoPlayState(self.autoPlayPushButton.isChecked())
if self._syncplayClient:
self._syncplayClient.changeAutoplayState(self.autoplayPushButton.isChecked())
else:
self.showDebugMessage("Tried to set AutoplayState too soon")
def updateReadyIcon(self):
ready = self.readyPushButton.isChecked()
@ -641,11 +711,11 @@ class MainWindow(QtGui.QMainWindow):
self.readyPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'empty_checkbox.png'))
def updateAutoPlayIcon(self):
ready = self.autoPlayPushButton.isChecked()
ready = self.autoplayPushButton.isChecked()
if ready:
self.autoPlayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'tick_checkbox.png'))
self.autoplayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'tick_checkbox.png'))
else:
self.autoPlayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'empty_checkbox.png'))
self.autoplayPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'empty_checkbox.png'))
def automaticUpdateCheck(self):
currentDateTime = datetime.utcnow()
@ -665,6 +735,7 @@ class MainWindow(QtGui.QMainWindow):
def userCheckForUpdates(self):
self.checkForUpdates(userInitiated=True)
@needsClient
def checkForUpdates(self, userInitiated=False):
self.lastCheckedForUpdates = datetime.utcnow()
updateStatus, updateMessage, updateURL = self._syncplayClient.checkForUpdate(userInitiated)
@ -717,6 +788,8 @@ class MainWindow(QtGui.QMainWindow):
settings.setValue("pos", self.pos())
settings.setValue("showPlaybackButtons", self.playbackAction.isChecked())
settings.setValue("showAutoPlayButton", self.autoplayAction.isChecked())
settings.setValue("autoplayChecked", self.autoplayPushButton.isChecked())
settings.setValue("autoplayMinUsers", self.autoplayThresholdSpinbox.value())
settings.endGroup()
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
@ -734,6 +807,12 @@ class MainWindow(QtGui.QMainWindow):
if settings.value("showAutoPlayButton", "false") == "true":
self.autoplayAction.setChecked(True)
self.updateAutoplayVisibility()
if settings.value("autoplayChecked", "false") == "true":
self.updateAutoPlayState(True)
self.autoplayPushButton.setChecked(True)
self.autoplayThresholdSpinbox.blockSignals(True)
self.autoplayThresholdSpinbox.setValue(settings.value("autoplayMinUsers", 2))
self.autoplayThresholdSpinbox.blockSignals(False)
settings.endGroup()
settings = QSettings("Syncplay", "Interface")
settings.beginGroup("Update")
@ -741,6 +820,7 @@ class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self._syncplayClient = None
self.QtGui = QtGui
if sys.platform.startswith('win'):
self.resourcespath = utils.findWorkingDir() + "\\resources\\"