From db91c348a47d2c2b11efaf9b0ceed7423ff46ddc Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sat, 6 Dec 2014 16:14:24 +0100 Subject: [PATCH 01/63] Added 'readiness' toggle to protocols and console UI --- syncplay/client.py | 59 +++++++++++++++++++++++++++++----------- syncplay/constants.py | 1 + syncplay/protocols.py | 27 ++++++++++++++++-- syncplay/server.py | 13 +++++++++ syncplay/ui/consoleUI.py | 7 ++++- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 5fafbc6..23e9ac7 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -459,6 +459,17 @@ class SyncplayClient(object): if promptForAction: self.ui.promptFor(getMessage("enter-to-exit-prompt")) + def toggleReady(self): + self._protocol.setReady(not self.userlist.currentUser.isReady()) + + def setReady(self, username, isReady): + self.userlist.setReady(username, isReady) + if isReady: + message = "<{}> I'm ready".format(username) + else: + message = "<{}> I'm not ready".format(username) + self.ui.showMessage(message) + def requireMinServerVersion(minVersion): def requireMinVersionDecorator(f): @wraps(f) @@ -523,17 +534,17 @@ class SyncplayClient(object): self._userlist = userlist self._ui = ui self._warnings = { - "room-files-not-same": { - "timer": task.LoopingCall(self.__displayMessageOnOSD, - "room-files-not-same",), - "displayedFor": 0, - }, - "alone-in-the-room": { - "timer": task.LoopingCall(self.__displayMessageOnOSD, - "alone-in-the-room",), - "displayedFor": 0, - }, - } + "room-files-not-same": { + "timer": task.LoopingCall(self.__displayMessageOnOSD, + "room-files-not-same", ), + "displayedFor": 0, + }, + "alone-in-the-room": { + "timer": task.LoopingCall(self.__displayMessageOnOSD, + "alone-in-the-room", ), + "displayedFor": 0, + }, + } def checkWarnings(self): self._checkIfYouReAloneInTheRoom() self._checkRoomForSameFiles() @@ -566,6 +577,7 @@ class SyncplayClient(object): class SyncplayUser(object): def __init__(self, username=None, room=None, file_=None): + self.ready = False self.username = username self.room = room self.file = file_ @@ -573,10 +585,10 @@ class SyncplayUser(object): def setFile(self, filename, duration, size): file_ = { - "name": filename, - "duration": duration, - "size":size - } + "name": filename, + "duration": duration, + "size": size + } self.file = file_ def isFileSame(self, file_): @@ -611,6 +623,12 @@ class SyncplayUser(object): else: return False + def isReady(self): + return self.ready + + def setReady(self, ready): + self.ready = ready + class SyncplayUserlist(object): def __init__(self, ui, client): self.currentUser = SyncplayUser() @@ -659,15 +677,18 @@ class SyncplayUserlist(object): message = getMessage("file-differences-notification") + ", ".join(differences) self.ui.showMessage(message, hideFromOSD) - def addUser(self, username, room, file_, noMessage=False, isController=None): + def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=False): if username == self.currentUser.username: if isController is not None: self.currentUser.setControllerStatus(isController) + self.currentUser.setReady(isReady) return user = SyncplayUser(username, room, file_) if isController is not None: user.setControllerStatus(isController) self._users[username] = user + user.setReady(isReady) + if not noMessage: self.__showUserChangeMessage(username, room, file_) self.userListChange(room) @@ -744,6 +765,12 @@ class SyncplayUserlist(object): return True return False + def setReady(self, username, isReady): + if self.currentUser.username == username: + self.currentUser.setReady(isReady) + elif self._users.has_key(username): + self._users[username].setReady(isReady) + def userListChange(self, room = None): if room is not None and self.isRoomSame(room): self._roomUsersChanged = True diff --git a/syncplay/constants.py b/syncplay/constants.py index 7bbb7ec..6e85c61 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -58,6 +58,7 @@ COMMANDS_ROOM = ["r", "room"] COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?'] COMMANDS_CREATE = ['c','create'] COMMANDS_AUTH = ['a','auth'] +COMMANDS_READY = ['re'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" diff --git a/syncplay/protocols.py b/syncplay/protocols.py index 8271a2b..f262623 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -136,6 +136,9 @@ class SyncClientProtocol(JSONCommandProtocol): controlPassword = values['password'] roomName = values['roomName'] self._client.controlledRoomCreated(roomName, controlPassword) + elif command == "ready": + user, isReady = values["username"], values["isReady"] + self._client.setReady(user, isReady) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -158,7 +161,8 @@ class SyncClientProtocol(JSONCommandProtocol): userName = user[0] file_ = user[1]['file'] if user[1]['file'] <> {} else None isController = user[1]['controller'] if 'controller' in user[1] else False - self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController) + isReady = user[1]['isReady'] if 'isReady' in user[1] else False + self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady) self._client.userlist.showUserList() def sendList(self): @@ -234,6 +238,13 @@ class SyncClientProtocol(JSONCommandProtocol): } }) + def setReady(self, isReady): + self.sendSet({ + "ready": { + "isReady": isReady + } + }) + def handleError(self, error): self.dropWithError(error["message"]) @@ -342,6 +353,8 @@ class SyncServerProtocol(JSONCommandProtocol): password = set_[1]["password"] if set_[1].has_key("password") else None room = set_[1]["room"] if set_[1].has_key("room") else None self._factory.authRoomController(self._watcher, password, room) + elif command == "ready": + self._factory.setReady(self._watcher, set_[1]['isReady']) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -363,6 +376,15 @@ class SyncServerProtocol(JSONCommandProtocol): } }) + + def sendSetReady(self, username, isReady): + self.sendSet({ + "ready": { + "username": username, + "isReady": isReady + } + }) + def sendUserSetting(self, username, room, file_, event): room = {"name": room.getName()} user = {username: {}} @@ -381,7 +403,8 @@ class SyncServerProtocol(JSONCommandProtocol): userFile = { "position": 0, "file": watcher.getFile() if watcher.getFile() else {}, - "controller": watcher.isController() + "controller": watcher.isController(), + "isReady": watcher.isReady() } userlist[room.getName()][watcher.getName()] = userFile diff --git a/syncplay/server.py b/syncplay/server.py index 221b262..b743ea9 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -125,6 +125,9 @@ class SyncFactory(Factory): except ValueError: self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) + def setReady(self, watcher, isReady): + watcher.setReady(isReady) + self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), isReady)) class RoomManager(object): def __init__(self): @@ -299,6 +302,7 @@ class ControlledRoom(Room): class Watcher(object): def __init__(self, server, connector, name): + self._ready = False self._server = server self._connector = connector self._name = name @@ -322,6 +326,12 @@ class Watcher(object): self._resetStateTimer() self._askForStateUpdate(True, True) + def setReady(self, ready): + self._ready = ready + + def isReady(self): + return self._ready + def getRoom(self): return self._room @@ -352,6 +362,9 @@ class Watcher(object): def sendControlledRoomAuthStatus(self, success, username, room): self._connector.sendControlledRoomAuthStatus(success, username, room) + def sendSetReady(self, username, isReady): + self._connector.sendSetReady(username, isReady) + def __lt__(self, b): if self.getPosition() is None or self._file is None: return False diff --git a/syncplay/ui/consoleUI.py b/syncplay/ui/consoleUI.py index 1b060ef..f113d80 100644 --- a/syncplay/ui/consoleUI.py +++ b/syncplay/ui/consoleUI.py @@ -54,7 +54,10 @@ class ConsoleUI(threading.Thread): for user in rooms[room]: userflags = u"" if user.isController(): - userflags = userflags + u"(Controller) " + userflags += u"(Controller) " + if user.isReady(): + userflags += u"(Ready) " + username = userflags + u"*<{}>*".format(user.username) if user == currentUser else userflags + u"<{}>".format(user.username) if user.file: message = u"{} is playing:".format(username) @@ -149,6 +152,8 @@ class ConsoleUI(threading.Thread): elif command.group('command') in constants.COMMANDS_AUTH: controlpassword = command.group('parameter') self._syncplayClient.identifyAsController(controlpassword) + elif command.group('command') in constants.COMMANDS_READY: + self._syncplayClient.toggleReady() else: if self._tryAdvancedCommands(data): return From 383d0b65c2c83d29de5466e48e9cbc6c9f0bffe5 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 16:40:10 +0000 Subject: [PATCH 02/63] Initial readiness GUI code + none state (back compat_ + 1.3.1 ver-up/ver-check --- syncplay/__init__.py | 4 ++-- syncplay/client.py | 43 +++++++++++++++++++++++++++++++------------ syncplay/constants.py | 1 + syncplay/protocols.py | 2 +- syncplay/server.py | 2 +- syncplay/ui/gui.py | 20 +++++++++++++++++++- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/syncplay/__init__.py b/syncplay/__init__.py index a1fa3eb..ff59e5a 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,3 @@ -version = '1.3.0' -milestone = 'Akki' +version = '1.3.1' +milestone = 'Chami' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 23e9ac7..855b73f 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -402,6 +402,10 @@ class SyncplayClient(object): self.identifyAsController(storedRoomPassword) def connected(self): + if self.userlist.currentUser.isReady() is not None: + self._protocol.setReady(self.userlist.currentUser.isReady()) + else: + self._protocol.setReady(False) self.reIdentifyAsController() def getRoom(self): @@ -459,17 +463,6 @@ class SyncplayClient(object): if promptForAction: self.ui.promptFor(getMessage("enter-to-exit-prompt")) - def toggleReady(self): - self._protocol.setReady(not self.userlist.currentUser.isReady()) - - def setReady(self, username, isReady): - self.userlist.setReady(username, isReady) - if isReady: - message = "<{}> I'm ready".format(username) - else: - message = "<{}> I'm not ready".format(username) - self.ui.showMessage(message) - def requireMinServerVersion(minVersion): def requireMinVersionDecorator(f): @wraps(f) @@ -481,6 +474,23 @@ class SyncplayClient(object): return wrapper return requireMinVersionDecorator + @requireMinServerVersion(constants.USER_READY_MIN_VERSION) + def toggleReady(self): + self._protocol.setReady(not self.userlist.currentUser.isReady()) + + def setReady(self, username, isReady): + oldReadyState = self.userlist.isReady(username) + if oldReadyState is None: + oldReadyState = False + self.userlist.setReady(username, isReady) + self.ui.userListChange() + if oldReadyState != isReady: + if isReady: + message = "<{}> I'm ready".format(username) + else: + message = "<{}> I'm not ready".format(username) + self.ui.showMessage(message) + @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def createControlledRoom(self, roomName): controlPassword = utils.RandomStringGenerator.generate_room_password() @@ -577,7 +587,7 @@ class SyncplayClient(object): class SyncplayUser(object): def __init__(self, username=None, room=None, file_=None): - self.ready = False + self.ready = None self.username = username self.room = room self.file = file_ @@ -765,6 +775,15 @@ class SyncplayUserlist(object): return True return False + def isReady(self, username): + if self.currentUser.username == username: + return self.currentUser.isReady() + + for user in self._users.itervalues(): + if user.username == username: + return user.isReady() + return False + def setReady(self, username, isReady): if self.currentUser.username == username: self.currentUser.setReady(isReady) diff --git a/syncplay/constants.py b/syncplay/constants.py index 6e85c61..19d9dfe 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -63,6 +63,7 @@ MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" VLC_INTERFACE_MIN_VERSION = "0.2.1" CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" +USER_READY_MIN_VERSION = "1.3.1" MPC_PATHS = [ r"c:\program files (x86)\mpc-hc\mpc-hc.exe", r"c:\program files\mpc-hc\mpc-hc.exe", diff --git a/syncplay/protocols.py b/syncplay/protocols.py index f262623..cd1ead6 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -161,7 +161,7 @@ class SyncClientProtocol(JSONCommandProtocol): userName = user[0] file_ = user[1]['file'] if user[1]['file'] <> {} else None isController = user[1]['controller'] if 'controller' in user[1] else False - isReady = user[1]['isReady'] if 'isReady' in user[1] else False + isReady = user[1]['isReady'] if 'isReady' in user[1] else None self._client.userlist.addUser(userName, roomName, file_, noMessage=True, isController=isController, isReady=isReady) self._client.userlist.showUserList() diff --git a/syncplay/server.py b/syncplay/server.py index b743ea9..6e61ef9 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -302,7 +302,7 @@ class ControlledRoom(Room): class Watcher(object): def __init__(self, server, connector, name): - self._ready = False + self._ready = None self._server = server self._connector = connector self._name = name diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 5b02d8b..dd51ecd 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -125,7 +125,13 @@ class MainWindow(QtGui.QMainWindow): for user in rooms[room]: useritem = QtGui.QStandardItem(user.username) isController = user.isController() + sameRoom = room == currentUser.room + if sameRoom: + isReady = user.isReady() + else: + isReady = None useritem.setData(isController, Qt.UserRole + constants.USERITEM_CONTROLLER_ROLE) + useritem.setData(isReady, Qt.UserRole + constants.USERITEM_READY_ROLE) if user.file: filesizeitem = QtGui.QStandardItem(formatSize(user.file['size'])) filedurationitem = QtGui.QStandardItem("({})".format(formatTime(user.file['duration']))) @@ -134,7 +140,6 @@ class MainWindow(QtGui.QMainWindow): sameName = sameFilename(user.file['name'], currentUser.file['name']) sameSize = sameFilesize(user.file['size'], currentUser.file['size']) sameDuration = sameFileduration(user.file['duration'], currentUser.file['duration']) - sameRoom = room == currentUser.room underlinefont = QtGui.QFont() underlinefont.setUnderline(True) if sameRoom: @@ -446,6 +451,15 @@ class MainWindow(QtGui.QMainWindow): window.listLayout.setContentsMargins(0, 0, 0, 0) window.listLayout.addWidget(window.listlabel) window.listLayout.addWidget(window.listTreeView) + + window.readyCheckbox = QtGui.QCheckBox() + readyFont = QtGui.QFont() + readyFont.setWeight(QtGui.QFont.Bold) + window.readyCheckbox.setText("I'm ready to watch!") + window.readyCheckbox.setFont(readyFont) + window.readyCheckbox.toggled.connect(self.changeReadyState) + window.listLayout.addWidget(window.readyCheckbox, Qt.AlignRight) + window.contactLabel = QtGui.QLabel() window.contactLabel.setWordWrap(True) window.contactLabel.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Sunken) @@ -606,6 +620,10 @@ class MainWindow(QtGui.QMainWindow): self.listbox.insertHtml(item) self.listbox.moveCursor(QtGui.QTextCursor.End) + def changeReadyState(self): + if self.readyCheckbox.isChecked() != self._syncplayClient.userlist.currentUser.isReady(): + self._syncplayClient.toggleReady() + def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() From 90c3ca2ab0f98b7cb76b4858e403e6f9765ab8fc Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 18:39:32 +0000 Subject: [PATCH 03/63] Change ready state automatically on manual pause/unpause --- syncplay/client.py | 14 ++++++++++++++ syncplay/constants.py | 1 + syncplay/ui/gui.py | 9 +++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 855b73f..fba023a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -68,6 +68,7 @@ class SyncplayClient(object): self.serverVersion = "0.0.0" self.lastLeftTime = 0 + self.lastPausedOnLeaveTime = None self.lastLeftUser = u"" self.protocolFactory = SyncClientFactory(self) self.ui = UiManager(self, ui) @@ -158,6 +159,12 @@ class SyncplayClient(object): pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused + if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): + lastPausedDiff = time.time() - self.lastPausedOnLeaveTime if self.lastPausedOnLeaveTime else None + if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: + self.lastPausedOnLeaveTime = None + else: + self.changeReadyState(not self.getPlayerPaused()) if self._lastGlobalUpdate: self._lastPlayerUpdate = time.time() if (pauseChange or seeked) and self._protocol: @@ -303,6 +310,7 @@ class SyncplayClient(object): def onDisconnect(self): if self._config['pauseOnLeave']: self.setPaused(True) + self.lastPausedOnLeaveTime = time.time() def removeUser(self, username): if self.userlist.isUserInYourRoom(username): @@ -478,6 +486,12 @@ class SyncplayClient(object): def toggleReady(self): self._protocol.setReady(not self.userlist.currentUser.isReady()) + @requireMinServerVersion(constants.USER_READY_MIN_VERSION) + def changeReadyState(self, newState): + oldState = self.userlist.currentUser.isReady() + if newState != oldState: + self.toggleReady() + def setReady(self, username, isReady): oldReadyState = self.userlist.isReady(username) if oldReadyState is None: diff --git a/syncplay/constants.py b/syncplay/constants.py index 19d9dfe..7f389f3 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -48,6 +48,7 @@ WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 MERGE_PLAYPAUSE_BUTTONS = False SYNC_ON_PAUSE = True # Client seek to global position - subtitles may disappear on some media players #Usually there's no need to adjust these +LAST_PAUSED_DIFF_THRESHOLD = 2 FILENAME_STRIP_REGEX = u"[-~_\.\[\](): ]" CONTROL_PASSWORD_STRIP_REGEX = u"[^a-zA-Z0-9\-]" ROOM_NAME_STRIP_REGEX = u"^(\+)(?P.*)(:)(\w{12})$" diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index dd51ecd..7075319 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -163,6 +163,7 @@ class MainWindow(QtGui.QMainWindow): font = QtGui.QFont() if currentUser.username == user.username: font.setWeight(QtGui.QFont.Bold) + self.updateReadyState(currentUser.isReady()) if isControlledRoom and not isController: useritem.setForeground(QtGui.QBrush(QtGui.QColor(constants.STYLE_NOTCONTROLLER_COLOR))) useritem.setFont(font) @@ -199,6 +200,11 @@ class MainWindow(QtGui.QMainWindow): except: pass + def updateReadyState(self, newState): + oldState = self.readyCheckbox.isChecked() + if newState != oldState: + self.readyCheckbox.setChecked(newState) + def roomClicked(self, item): while item.parent().row() != -1: item = item.parent() @@ -621,8 +627,7 @@ class MainWindow(QtGui.QMainWindow): self.listbox.moveCursor(QtGui.QTextCursor.End) def changeReadyState(self): - if self.readyCheckbox.isChecked() != self._syncplayClient.userlist.currentUser.isReady(): - self._syncplayClient.toggleReady() + self._syncplayClient.changeReadyState(self.readyCheckbox.isChecked()) def dragEnterEvent(self, event): data = event.mimeData() From 0a924ac299a11a55cfa0f374be18805d5651b704 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 19:01:46 +0000 Subject: [PATCH 04/63] Ignore manual pause if can't control + (mostly) honour OSD options for isReady --- syncplay/client.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index fba023a..aa3ee1b 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -159,7 +159,7 @@ class SyncplayClient(object): pauseChange, seeked = self._determinePlayerStateChange(paused, position) self._playerPosition = position self._playerPaused = paused - if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION): + if pauseChange and utils.meetsMinVersion(self.serverVersion, constants.USER_READY_MIN_VERSION) and self.userlist.currentUser.canControl(): lastPausedDiff = time.time() - self.lastPausedOnLeaveTime if self.lastPausedOnLeaveTime else None if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: self.lastPausedOnLeaveTime = None @@ -499,11 +499,16 @@ class SyncplayClient(object): self.userlist.setReady(username, isReady) self.ui.userListChange() if oldReadyState != isReady: + showOnOSD = constants.SHOW_OSD_WARNINGS + if constants.SHOW_NONCONTROLLER_OSD == False and self.userlist.canControl(username) == False and self.userlist.currentUser.isController() == False: + # Ignore SHOW_NONCONTROLLER_OSD setting if user is controller, because they need to know who is/isn't ready + showOnOSD = False + hideFromOSD = not showOnOSD if isReady: message = "<{}> I'm ready".format(username) else: message = "<{}> I'm not ready".format(username) - self.ui.showMessage(message) + self.ui.showMessage(message, hideFromOSD) @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def createControlledRoom(self, roomName): From ebeaa92724cd5d9075a94cf855cd1893c8cbd166 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 19:14:52 +0000 Subject: [PATCH 05/63] Add readyAtStart config option --- syncplay/client.py | 2 +- syncplay/messages.py | 2 ++ syncplay/ui/ConfigurationGetter.py | 4 +++- syncplay/ui/GuiConfiguration.py | 12 ++++++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index aa3ee1b..dfac60c 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -413,7 +413,7 @@ class SyncplayClient(object): if self.userlist.currentUser.isReady() is not None: self._protocol.setReady(self.userlist.currentUser.isReady()) else: - self._protocol.setReady(False) + self._protocol.setReady(self._config['readyAtStart']) self.reIdentifyAsController() def getRoom(self): diff --git a/syncplay/messages.py b/syncplay/messages.py index 0d6d06b..1bf5f37 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -156,6 +156,7 @@ en = { "fastforwardondesync-label" : "Fast-forward if lagging behind (recommended)", "dontslowdownwithme-label" : "Never slow down or rewind others (experimental)", "pauseonleave-label" : "Pause when user leaves (e.g. if they are disconnected)", + "readyatstart-label" : "Set me as 'ready to watch' by default", "forceguiprompt-label" : "Don't always show this dialog", # (Inverted) "nostore-label" : "Don't store this configuration", # (Inverted) "showosd-label" : "Enable OSD Messages", @@ -250,6 +251,7 @@ en = { "slowondesync-tooltip" : "Reduce playback rate temporarily when needed to bring you back in sync with other viewers. Not supported on MPC-HC.", "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.", "pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.", + "readyatstart-tooltip" : "Set yourself as 'ready' at start (otherwise you are set as 'not ready' until you change your readiness state)", "forceguiprompt-tooltip" : "Configuration dialogue is not shown when opening a file with Syncplay.", # (Inverted) "nostore-tooltip" : "Run Syncplay with the given configuration, but do not permanently store the changes.", # (Inverted) "rewindondesync-tooltip" : "Jump back when needed to get back in sync. Disabling this option can result in major desyncs!", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 9feec05..89ce26b 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -44,6 +44,7 @@ class ConfigurationGetter(object): "filenamePrivacyMode": constants.PRIVACY_SENDRAW_MODE, "filesizePrivacyMode": constants.PRIVACY_SENDRAW_MODE, "pauseOnLeave": False, + "readyAtStart": False, "clearGUIData": False, "language" : "", "resetConfig" : False, @@ -77,6 +78,7 @@ class ConfigurationGetter(object): "noStore", "dontSlowDownWithMe", "pauseOnLeave", + "readyAtStart", "clearGUIData", "rewindOnDesync", "slowOnDesync", @@ -102,7 +104,7 @@ class ConfigurationGetter(object): self._iniStructure = { "server_data": ["host", "port", "password"], - "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave"], + "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showContactInfo" , "showDurationNotification"], "general": ["language"] } diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 21c69c8..9c1b0f1 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -545,17 +545,21 @@ class ConfigDialog(QtGui.QDialog): self.othersyncSettingsLayout.addWidget(self.pauseonleaveCheckbox, 1, 0, 1, 2, Qt.AlignLeft) self.pauseonleaveCheckbox.setObjectName("pauseOnLeave") + self.readyatstartCheckbox = QCheckBox(getMessage("readyatstart-label")) + self.othersyncSettingsLayout.addWidget(self.readyatstartCheckbox, 2, 0, 1, 2, Qt.AlignLeft) + self.readyatstartCheckbox.setObjectName("readyAtStart") + self.dontslowwithmeCheckbox = QCheckBox(getMessage("dontslowdownwithme-label")) self.dontslowwithmeCheckbox.setObjectName("dontSlowDownWithMe") - self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 2, 0, 1, 2, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.dontslowwithmeCheckbox, 3, 0, 1, 2, Qt.AlignLeft) self.fastforwardThresholdLabel.setObjectName("fastforward-threshold") self.fastforwardThresholdSpinbox.setObjectName("fastforward-threshold") - self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 3, 0,1,2, Qt.AlignLeft) - self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdLabel, 4, 0, 1, 1, Qt.AlignLeft) - self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdSpinbox, 4, 1, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.fastforwardCheckbox, 4, 0,1,2, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdLabel, 5, 0, 1, 1, Qt.AlignLeft) + self.othersyncSettingsLayout.addWidget(self.fastforwardThresholdSpinbox, 5, 1, Qt.AlignLeft) self.subitems['fastforwardOnDesync'] = ["fastforward-threshold"] self.othersyncSettingsGroup.setLayout(self.othersyncSettingsLayout) From b9374adc6cbb13e51c5c3a404e2f3024ec1497f5 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 19:29:43 +0000 Subject: [PATCH 06/63] Handle ready none status in checkbox update --- syncplay/ui/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index 7075319..c60ea11 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -202,7 +202,7 @@ class MainWindow(QtGui.QMainWindow): def updateReadyState(self, newState): oldState = self.readyCheckbox.isChecked() - if newState != oldState: + if newState != oldState and newState != None: self.readyCheckbox.setChecked(newState) def roomClicked(self, item): From fe040438f55fa0f55f71c55f73f6cd4c4eca3758 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 19:38:34 +0000 Subject: [PATCH 07/63] Move ready/not-ready messages into messages.py --- syncplay/client.py | 4 ++-- syncplay/messages.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index dfac60c..907d243 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -505,9 +505,9 @@ class SyncplayClient(object): showOnOSD = False hideFromOSD = not showOnOSD if isReady: - message = "<{}> I'm ready".format(username) + message = getMessage("user-ready-notification").format(username) else: - message = "<{}> I'm not ready".format(username) + message = getMessage("user-not-ready-notification").format(username) self.ui.showMessage(message, hideFromOSD) @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) diff --git a/syncplay/messages.py b/syncplay/messages.py index 1bf5f37..aee0f99 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -33,6 +33,9 @@ en = { "playing-notification" : "<{}> is playing '{}' ({})", # User, file, duration "playing-notification/room-addendum" : " in room: '{}'", # Room + "user-ready-notification" : "<{}> I'm ready", # Username + "user-not-ready-notification" : "<{}> I'm ready", # Username + "identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", "failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", "authenticated-as-controller-notification" : u"<{}> authenticated as a room controller", From d3be1f0917134f9e68faedda3a7380fb38a00ac7 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 6 Dec 2014 19:39:12 +0000 Subject: [PATCH 08/63] Fix not-ready message --- syncplay/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncplay/messages.py b/syncplay/messages.py index aee0f99..450a074 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -34,7 +34,7 @@ en = { "playing-notification/room-addendum" : " in room: '{}'", # Room "user-ready-notification" : "<{}> I'm ready", # Username - "user-not-ready-notification" : "<{}> I'm ready", # Username + "user-not-ready-notification" : "<{}> I'm not ready", # Username "identifying-as-controller-notification" : u"Identifying as room controller with password '{}'...", "failed-to-identify-as-controller-notification" : u"<{}> failed to identify as a room controller.", From cabadec4e7e97a0cfb0b6282ab3ec54a58458128 Mon Sep 17 00:00:00 2001 From: Et0h Date: Sun, 7 Dec 2014 01:38:05 +0000 Subject: [PATCH 09/63] Move from readyCheckbox to readyPushButton --- resources/cross_checkbox.png | Bin 0 -> 671 bytes resources/tick_checkbox.png | Bin 0 -> 556 bytes syncplay/constants.py | 1 + syncplay/ui/gui.py | 29 +++++++++++++++++++++-------- 4 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 resources/cross_checkbox.png create mode 100644 resources/tick_checkbox.png diff --git a/resources/cross_checkbox.png b/resources/cross_checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..785a5293ff074071b220d7d9c7a44e9df67a0d8f GIT binary patch literal 671 zcmV;Q0$}}#P)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^4L^m3s900J9HL_t(IPoedeFoe{}kg*k6buvh(;3>J+!rmNgujlY_PoYwUI2Vq0!L{xB74 zu6X9GPofAsZ2RrIK*=G(w0sgppy+?lB84hWGeV(I=r6^0HpC2D{>A_R002ovPDHLk FV1iWEFpvNM literal 0 HcmV?d00001 diff --git a/resources/tick_checkbox.png b/resources/tick_checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..d434c7f14eed680af0da2081d2328a92cb5e0cf4 GIT binary patch literal 556 zcmV+{0@MA8P)pF8gxZibW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE-^4L^m3s900F2;L_t(IPsPy5OM*ch#_{)WY9TEuZ_z0%X|=Kp+VobY=JKk& zg@x78R-@HJi&-9m3aLZBmPXP-b?X{+%=gvc$$1k)%*Z_Sg@Ktle3;)*B$}p??-G!F z^ZWh(47q}{aEDiur$0iA> z?=g7P51n6)g9o2*N19I{J=<-thm05t8PL8~j|!_Og^*Iv&2t2rLls6@9UNgAEgN<4 z-R@$EokRDQ9HSQoFy}h7uF6nuDM>>}DTsgPq4YFClX%f|WCRSWN1s;#^}Z4|RtxL7 z7V?!kl+nc*2q|qeF3zEOT?(~NDg49O#V{;E4J@n%&C9hYH5Gk}kTO1-i$@`ytwz^o z3#`E*m;wW6T9QI+EX<0KG9Xek7Dd&hBr%E%4Xzr93}7>WQU2{$2 Date: Sun, 7 Dec 2014 01:42:59 +0000 Subject: [PATCH 10/63] Include tick and cross checkbox images within Windows installer --- buildPy2exe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildPy2exe.py b/buildPy2exe.py index 49f1d46..b9b586b 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -624,7 +624,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/mpv.png','resources/vlc.png', 'resources/house.png', 'resources/film_link.png', 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', - 'resources/tick.png', 'resources/lock_open.png' + 'resources/tick.png', 'resources/lock_open.png', 'resources/cross_checkbox.png', 'resources/tick_checkbox.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) From 29f109f1fb21ee9b46b500742244c19108862719 Mon Sep 17 00:00:00 2001 From: Uriziel Date: Sun, 7 Dec 2014 16:16:29 +0100 Subject: [PATCH 11/63] Changed default program to run syncplay (python to python2) in *nix --- syncplayClient.py | 2 +- syncplayServer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/syncplayClient.py b/syncplayClient.py index 236df21..107d942 100755 --- a/syncplayClient.py +++ b/syncplayClient.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 import site, sys diff --git a/syncplayServer.py b/syncplayServer.py index c127fbf..23b5381 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 #coding:utf8 import site, sys From 440c79f1a3690d70050bcf73453b89baf3f99a7c Mon Sep 17 00:00:00 2001 From: Et0h Date: Sat, 13 Dec 2014 11:30:11 +0000 Subject: [PATCH 12/63] Specify whether change in ready state is manually initiated or automatic --- syncplay/client.py | 20 +++++++++----------- syncplay/protocols.py | 16 ++++++++++------ syncplay/server.py | 8 ++++---- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/syncplay/client.py b/syncplay/client.py index 907d243..11f05b2 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -164,7 +164,7 @@ class SyncplayClient(object): if lastPausedDiff is not None and lastPausedDiff < constants.LAST_PAUSED_DIFF_THRESHOLD: self.lastPausedOnLeaveTime = None else: - self.changeReadyState(not self.getPlayerPaused()) + self.changeReadyState(not self.getPlayerPaused(), manuallyInitiated=False) if self._lastGlobalUpdate: self._lastPlayerUpdate = time.time() if (pauseChange or seeked) and self._protocol: @@ -410,10 +410,8 @@ class SyncplayClient(object): self.identifyAsController(storedRoomPassword) def connected(self): - if self.userlist.currentUser.isReady() is not None: - self._protocol.setReady(self.userlist.currentUser.isReady()) - else: - self._protocol.setReady(self._config['readyAtStart']) + readyState = self._config['readyAtStart'] if self.userlist.currentUser.isReady() is None else self.userlist.currentUser.isReady() + self._protocol.setReady(readyState, manuallyInitiated=False) self.reIdentifyAsController() def getRoom(self): @@ -483,22 +481,22 @@ class SyncplayClient(object): return requireMinVersionDecorator @requireMinServerVersion(constants.USER_READY_MIN_VERSION) - def toggleReady(self): - self._protocol.setReady(not self.userlist.currentUser.isReady()) + def toggleReady(self, manuallyInitiated=True): + self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated) @requireMinServerVersion(constants.USER_READY_MIN_VERSION) - def changeReadyState(self, newState): + def changeReadyState(self, newState, manuallyInitiated=True): oldState = self.userlist.currentUser.isReady() if newState != oldState: - self.toggleReady() + self.toggleReady(manuallyInitiated) - def setReady(self, username, isReady): + def setReady(self, username, isReady, manuallyInitiated=True): oldReadyState = self.userlist.isReady(username) if oldReadyState is None: oldReadyState = False self.userlist.setReady(username, isReady) self.ui.userListChange() - if oldReadyState != isReady: + if manuallyInitiated and oldReadyState != isReady: showOnOSD = constants.SHOW_OSD_WARNINGS if constants.SHOW_NONCONTROLLER_OSD == False and self.userlist.canControl(username) == False and self.userlist.currentUser.isController() == False: # Ignore SHOW_NONCONTROLLER_OSD setting if user is controller, because they need to know who is/isn't ready diff --git a/syncplay/protocols.py b/syncplay/protocols.py index cd1ead6..a91d429 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -138,7 +138,8 @@ class SyncClientProtocol(JSONCommandProtocol): self._client.controlledRoomCreated(roomName, controlPassword) elif command == "ready": user, isReady = values["username"], values["isReady"] - self._client.setReady(user, isReady) + manuallyInitiated = values["manuallyInitiated"] if values.has_key("manuallyInitiated") else True + self._client.setReady(user, isReady, manuallyInitiated) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -238,10 +239,11 @@ class SyncClientProtocol(JSONCommandProtocol): } }) - def setReady(self, isReady): + def setReady(self, isReady, manuallyInitiated=True): self.sendSet({ "ready": { - "isReady": isReady + "isReady": isReady, + "manuallyInitiated": manuallyInitiated } }) @@ -354,7 +356,8 @@ class SyncServerProtocol(JSONCommandProtocol): room = set_[1]["room"] if set_[1].has_key("room") else None self._factory.authRoomController(self._watcher, password, room) elif command == "ready": - self._factory.setReady(self._watcher, set_[1]['isReady']) + manuallyInitiated = set_[1]['manuallyInitiated'] if set_[1].has_key("manuallyInitiated") else False + self._factory.setReady(self._watcher, set_[1]['isReady'], manuallyInitiated=manuallyInitiated) def sendSet(self, setting): self.sendMessage({"Set": setting}) @@ -377,11 +380,12 @@ class SyncServerProtocol(JSONCommandProtocol): }) - def sendSetReady(self, username, isReady): + def sendSetReady(self, username, isReady, manuallyInitiated=True): self.sendSet({ "ready": { "username": username, - "isReady": isReady + "isReady": isReady, + "manuallyInitiated": manuallyInitiated } }) diff --git a/syncplay/server.py b/syncplay/server.py index 6e61ef9..f2bded0 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -125,9 +125,9 @@ class SyncFactory(Factory): except ValueError: self._roomManager.broadcastRoom(watcher, lambda w: w.sendControlledRoomAuthStatus(False, watcher.getName(), room._name)) - def setReady(self, watcher, isReady): + def setReady(self, watcher, isReady, manuallyInitiated=True): watcher.setReady(isReady) - self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), isReady)) + self._roomManager.broadcastRoom(watcher, lambda w: w.sendSetReady(watcher.getName(), isReady, manuallyInitiated)) class RoomManager(object): def __init__(self): @@ -362,8 +362,8 @@ class Watcher(object): def sendControlledRoomAuthStatus(self, success, username, room): self._connector.sendControlledRoomAuthStatus(success, username, room) - def sendSetReady(self, username, isReady): - self._connector.sendSetReady(username, isReady) + def sendSetReady(self, username, isReady, manuallyInitiated=True): + self._connector.sendSetReady(username, isReady, manuallyInitiated) def __lt__(self, b): if self.getPosition() is None or self._file is None: From 3ff43473eb9ac332e5f9b4a14cb2618f6311396b Mon Sep 17 00:00:00 2001 From: Et0h Date: Mon, 29 Dec 2014 12:47:31 +0000 Subject: [PATCH 13/63] Add secondaryOSD (w/ VLC support & fallback code), re-work warnings, fix misc. VLC bugs, allow longer offsets --- resources/lua/intf/syncplay.lua | 76 ++++++++++---- syncplay/client.py | 179 ++++++++++++++++++++++++-------- syncplay/constants.py | 7 +- syncplay/messages.py | 18 +++- syncplay/players/mpc.py | 3 +- syncplay/players/mplayer.py | 4 +- syncplay/players/vlc.py | 18 +++- 7 files changed, 232 insertions(+), 73 deletions(-) diff --git a/resources/lua/intf/syncplay.lua b/resources/lua/intf/syncplay.lua index 0dacaf7..3c8df92 100644 --- a/resources/lua/intf/syncplay.lua +++ b/resources/lua/intf/syncplay.lua @@ -5,7 +5,7 @@ Principal author: Etoh Other contributors: DerGenaue, jb Project: http://syncplay.pl/ - Version: 0.2.3 + Version: 0.2.4 Note: * This interface module is intended to be used in conjunction with Syncplay. @@ -68,6 +68,9 @@ You may also need to re-copy the syncplay.lua file when you update VLC. display-osd: [placement on screen
], [duration in seconds], [message] ? >> display-osd-error: no-input + display-secondary-osd: [placement on screen
], [duration in seconds], [message] + ? >> display-secondary-osd-error: no-input + load-file: [filepath] * >> load-file-attempted @@ -78,7 +81,7 @@ You may also need to re-copy the syncplay.lua file when you update VLC. --]==========================================================================] -local connectorversion = "0.2.3" +local connectorversion = "0.2.4" local durationdelay = 500000 -- Pause for get_duration command etc for increased reliability (uses microseconds) local loopsleepduration = 5000 -- Pause for every event loop (uses microseconds) local quitcheckfrequency = 20 -- Check whether VLC has closed every X loops @@ -108,12 +111,18 @@ local newinputstate local oldtitle = 0 local newtitle = 0 +local channel1 +local channel2 +local l + local running = true + function radixsafe_tonumber(str) -- Version of tonumber that works with any radix character (but not thousand seperators) -- Based on the public domain VLC common.lua us_tonumber() function - str = string.gsub(tostring(str), "[^0-9]", ".") + + str = string.gsub(tostring(str), "[^0-9]", ".") local s, i, d = string.match(str, "^([+-]?)(%d*)%.?(%d*)$") if not s or not i or not d then return nil @@ -277,8 +286,8 @@ function set_time ( timetoset) end errormsg = set_var("time", radixsafe_tonumber(realtime)) return errormsg - else - return noinput + else + return noinput end end @@ -379,17 +388,19 @@ function get_duration () if input then local item = vlc.input.item() - if (item and item:duration()) then -- Try to get duration, which might not be available straight away - local i = 0 - repeat - vlc.misc.mwait(vlc.misc.mdate() + durationdelay) + local i = 0 + response = 0 + repeat + vlc.misc.mwait(vlc.misc.mdate() + durationdelay) + if item and item:duration() then response = item:duration() - i = i + 1 - until response > 0 or i > 5 - else - errormsg = noinput - end + if response < 1 then + response = 0 + end + end + i = i + 1 + until response > 1 or i > 5 else errormsg = noinput end @@ -400,11 +411,16 @@ end function display_osd ( argument ) -- [Used by display-osd command] - local errormsg local osdarray local input = vlc.object.input() - if input then + if input and vlc.osd and vlc.object.vout() then + if not channel1 then + channel1 = vlc.osd.channel_register() + end + if not channel2 then + channel2 = vlc.osd.channel_register() + end osdarray = get_args(argument,3) --position, duration, message -> message, , position, duration (converted from seconds to microseconds) local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000 @@ -415,6 +431,28 @@ function display_osd ( argument ) return errormsg end +function display_secondary_osd ( argument ) + -- [Used by display-secondary-osd command] + local errormsg + local osdarray + local input = vlc.object.input() + if input and vlc.osd and vlc.object.vout() then + if not channel1 then + channel1 = vlc.osd.channel_register() + end + if not channel2 then + channel2 = vlc.osd.channel_register() + end + osdarray = get_args(argument,3) + --position, duration, message -> message, , position, duration (converted from seconds to microseconds) + local osdduration = radixsafe_tonumber(osdarray[2]) * 1000 * 1000 + vlc.osd.message(osdarray[3],channel2,osdarray[1],osdduration) + else + errormsg = noinput + end + return errormsg +end + function load_file (filepath) -- [Used by load-file command] @@ -445,6 +483,7 @@ function do_command ( command, argument) elseif command == "set-rate" then errormsg = set_var("rate", radixsafe_tonumber(argument)) elseif command == "set-title" then errormsg = set_var("title", radixsafe_tonumber(argument)) elseif command == "display-osd" then errormsg = display_osd(argument) + elseif command == "display-secondary-osd" then errormsg = display_secondary_osd(argument) elseif command == "load-file" then response = load_file(argument) elseif command == "close-vlc" then quit_vlc() else errormsg = unknowncommand @@ -498,7 +537,7 @@ while running == true do --accept new connections and select active clients local quitcheckcounter = 0 local fd = l:accept() - local buffer, inputbuffer, responsebuffer = "" + local buffer, inputbuffer, responsebuffer = "", "", "" while fd >= 0 and running == true do -- handle read mode @@ -560,7 +599,6 @@ while running == true do quitcheckcounter = 0 end - end -end +end \ No newline at end of file diff --git a/syncplay/client.py b/syncplay/client.py index 11f05b2..f2a5a6e 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -106,7 +106,7 @@ class SyncplayClient(object): self._speedChanged = False self.behindFirstDetected = None - self._warnings = self._WarningManager(self._player, self.userlist, self.ui) + 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']: paths = "; ".join(self._config['loadedRelativePaths']) self.ui.showMessage(getMessage("relative-config-notification").format(paths), noPlayer=True, noTimestamp=True) @@ -126,6 +126,8 @@ class SyncplayClient(object): def initPlayer(self, player): self._player = player + if not self._player.secondaryOSDSupported: + constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_SECONDARY_OSD_WARNING_DURATION self.scheduleAskPlayer() def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY): @@ -496,17 +498,20 @@ class SyncplayClient(object): oldReadyState = False self.userlist.setReady(username, isReady) self.ui.userListChange() - if manuallyInitiated and oldReadyState != isReady: - showOnOSD = constants.SHOW_OSD_WARNINGS - if constants.SHOW_NONCONTROLLER_OSD == False and self.userlist.canControl(username) == False and self.userlist.currentUser.isController() == False: - # Ignore SHOW_NONCONTROLLER_OSD setting if user is controller, because they need to know who is/isn't ready - showOnOSD = False - hideFromOSD = not showOnOSD - if isReady: - message = getMessage("user-ready-notification").format(username) - else: - message = getMessage("user-not-ready-notification").format(username) - self.ui.showMessage(message, hideFromOSD) + if oldReadyState != isReady: + self._warnings.checkReadyStates() + if manuallyInitiated: + showOnOSD = constants.SHOW_OSD_WARNINGS + if constants.SHOW_NONCONTROLLER_OSD == False and self.userlist.canControl(username) == False and self.userlist.currentUser.isController() == False: + # Ignore SHOW_NONCONTROLLER_OSD setting if user is controller, because they need to know who is/isn't ready + showOnOSD = False + hideFromOSD = not showOnOSD + if isReady: + message = getMessage("user-ready-notification").format(username) + else: + message = getMessage("user-not-ready-notification").format(username) + self.ui.showMessage(message, hideFromOSD, False) + @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def createControlledRoom(self, roomName): @@ -556,51 +561,107 @@ class SyncplayClient(object): return self.controlpasswords[room] class _WarningManager(object): - def __init__(self, player, userlist, ui): + def __init__(self, player, userlist, ui, client): + self._client = client self._player = player self._userlist = userlist self._ui = ui self._warnings = { "room-files-not-same": { - "timer": task.LoopingCall(self.__displayMessageOnOSD, - "room-files-not-same", ), + "timer": task.LoopingCall(self.__displayMessageOnOSD, "room-files-not-same", + lambda: self._checkRoomForSameFiles(OSDOnly=True),), "displayedFor": 0, }, "alone-in-the-room": { - "timer": task.LoopingCall(self.__displayMessageOnOSD, - "alone-in-the-room", ), + "timer": task.LoopingCall(self.__displayMessageOnOSD, "alone-in-the-room", + lambda: self._checkIfYouReAloneInTheRoom(OSDOnly=True)), + "displayedFor": 0, + }, + "not-all-ready": { + "timer": task.LoopingCall(self.__displayMessageOnOSD, "not-all-ready", + lambda: self.checkReadyStates(),), "displayedFor": 0, }, } - def checkWarnings(self): - self._checkIfYouReAloneInTheRoom() - self._checkRoomForSameFiles() + self.pausedTimer = task.LoopingCall(self.__displayPausedMessagesOnOSD) + self.pausedTimer.start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) - def _checkRoomForSameFiles(self): + def checkWarnings(self): + self._checkIfYouReAloneInTheRoom(OSDOnly=False) + self._checkRoomForSameFiles(OSDOnly=False) + self.checkReadyStates() + + def _checkRoomForSameFiles(self, OSDOnly): if not self._userlist.areAllFilesInRoomSame(): - self._ui.showMessage(getMessage("room-files-not-same"), True) - if constants.SHOW_OSD_WARNINGS and not self._warnings["room-files-not-same"]['timer'].running: - self._warnings["room-files-not-same"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) + self._displayReadySameWarning() + if not OSDOnly: + self._ui.showMessage(getMessage("room-files-not-same"), True) + if constants.SHOW_OSD_WARNINGS and not self._warnings["room-files-not-same"]['timer'].running: + self._warnings["room-files-not-same"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) elif self._warnings["room-files-not-same"]['timer'].running: self._warnings["room-files-not-same"]['timer'].stop() - def _checkIfYouReAloneInTheRoom(self): + def _checkIfYouReAloneInTheRoom(self, OSDOnly): if self._userlist.areYouAloneInRoom(): - self._ui.showMessage(getMessage("alone-in-the-room"), True) - if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running: - self._warnings["alone-in-the-room"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) + self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True) + if not OSDOnly: + self._ui.showMessage(getMessage("alone-in-the-room"), True) + if constants.SHOW_OSD_WARNINGS and not self._warnings["alone-in-the-room"]['timer'].running: + self._warnings["alone-in-the-room"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) elif self._warnings["alone-in-the-room"]['timer'].running: self._warnings["alone-in-the-room"]['timer'].stop() - def __displayMessageOnOSD(self, warningName): + def checkReadyStates(self): + if not self._client: + return + + if self._client.getPlayerPaused() or not self._userlist.currentUser.isReady(): + self._warnings["not-all-ready"]["displayedFor"] = 0 + if self._userlist.areYouAloneInRoom() or not self._userlist.currentUser.canControl(): + if self._warnings["not-all-ready"]['timer'].running: + self._warnings["not-all-ready"]['timer'].stop() + elif not self._userlist.areAllUsersInRoomReady(): + self._displayReadySameWarning() + if constants.SHOW_OSD_WARNINGS and not self._warnings["not-all-ready"]['timer'].running: + self._warnings["not-all-ready"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) + elif self._warnings["not-all-ready"]['timer'].running: + self._warnings["not-all-ready"]['timer'].stop() + self._displayReadySameWarning() + elif self._client.getPlayerPaused() or not self._userlist.currentUser.isReady(): + self._displayReadySameWarning() + + def _displayReadySameWarning(self): + if not self._userlist.areAllFilesInRoomSame(): + if self._userlist.currentUser.canControl(): + if self._userlist.areAllUsersInRoomReady(): + osdMessage = u"{}; {}".format(getMessage("room-files-not-same"), getMessage("all-users-ready")) + else: + osdMessage = u"{}; {}".format(getMessage("room-files-not-same"), getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())) + else: + osdMessage = getMessage("room-files-not-same") + elif self._userlist.areAllUsersInRoomReady(): + osdMessage = getMessage("all-users-ready") + else: + osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()) + self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True) + + def __displayMessageOnOSD(self, warningName, warningFunction): if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]: - self._ui.showOSDMessage(getMessage(warningName), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL) + warningFunction() self._warnings[warningName]["displayedFor"] += constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL else: self._warnings[warningName]["displayedFor"] = 0 - self._warnings[warningName]["timer"].stop() - + try: + self._warnings[warningName]["timer"].stop() + except: + pass + def __displayPausedMessagesOnOSD(self): + if self._client and self._client._player and self._client._player.secondaryOSDSupported and self._userlist.currentUser.file and self._client.getPlayerPaused(): + self._checkRoomForSameFiles(OSDOnly=True) + self.checkReadyStates() + elif not self._userlist.currentUser.isReady(): # CurrentUser should always be reminded they are set to not ready + self.checkReadyStates() class SyncplayUser(object): def __init__(self, username=None, room=None, file_=None): @@ -764,11 +825,31 @@ class SyncplayUserlist(object): user = self._users[username] user.setControllerStatus(True) - def areAllFilesInRoomSame(self): + def areAllUsersInRoomReady(self): + if not self.currentUser.canControl(): + return True + if not self.currentUser.isReady(): + return False for user in self._users.itervalues(): - if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file): - if user.canControl(): - return False + if user.room == self.currentUser.room and user.isReady() == False: + return False + return True + + def usersInRoomNotReady(self): + notReady = [] + if not self.currentUser.isReady(): + notReady.append(self.currentUser.username) + for user in self._users.itervalues(): + if user.room == self.currentUser.room and user.isReady() == False: + notReady.append(user.username) + return ", ".join(notReady) + + def areAllFilesInRoomSame(self): + if self.currentUser.file: + for user in self._users.itervalues(): + if user.room == self.currentUser.room and user.file and not self.currentUser.isFileSame(user.file): + if user.canControl(): + return False return True def areYouAloneInRoom(self): @@ -843,22 +924,38 @@ class UiManager(object): def __init__(self, client, ui): self._client = client self.__ui = ui + self.lastPrimaryOSDMessage = None + self.lastPrimaryOSDEndTime = None + self.lastSecondaryOSDMessage = None + self.lastSecondaryOSDEndTime = None self.lastError = "" def showDebugMessage(self, message): if constants.DEBUG_MODE and message.rstrip(): print "{}{}".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip()) - def showMessage(self, message, noPlayer=False, noTimestamp=False): - if not noPlayer: self.showOSDMessage(message) + def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False): + if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD) self.__ui.showMessage(message, noTimestamp) def showUserList(self, currentUser, rooms): self.__ui.showUserList(currentUser, rooms) - def showOSDMessage(self, message, duration=constants.OSD_DURATION): - if constants.SHOW_OSD and self._client._player: - self._client._player.displayMessage(message, duration * 1000) + def showOSDMessage(self, message, duration=constants.OSD_DURATION, secondaryOSD=False): + if constants.SHOW_OSD and self._client and self._client._player: + if not self._client._player.secondaryOSDSupported: + if secondaryOSD: + message = u"({})".format(message) + self.lastSecondaryOSDMessage = message + self.lastSecondaryOSDEndTime = time.time() + constants.NO_SECONDARY_OSD_WARNING_DURATION + if self.lastPrimaryOSDEndTime and time.time() < self.lastPrimaryOSDEndTime: + message = u"{}; {}".format(message, self.lastPrimaryOSDMessage) + else: + self.lastPrimaryOSDMessage = message + self.lastPrimaryOSDEndTime = time.time() + constants.OSD_DURATION + if self.lastSecondaryOSDEndTime and time.time() < self.lastSecondaryOSDEndTime: + message = u"{}; {}".format(self.lastSecondaryOSDMessage, message) + self._client._player.displayMessage(message, duration * 1000, secondaryOSD) def setControllerStatus(self, username, isController): self.__ui.setControllerStatus(username, isController) diff --git a/syncplay/constants.py b/syncplay/constants.py index 5866fe0..191116b 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -1,7 +1,8 @@ # You might want to change these DEFAULT_PORT = 8999 OSD_DURATION = 3 -OSD_WARNING_MESSAGE_DURATION = 15 +OSD_WARNING_MESSAGE_DURATION = 5 +NO_SECONDARY_OSD_WARNING_DURATION = 15 MPC_OSD_POSITION = 2 #Right corner, 1 for left MPLAYER_OSD_LEVEL = 1 UI_TIME_FORMAT = "[%X] " @@ -62,7 +63,7 @@ COMMANDS_AUTH = ['a','auth'] COMMANDS_READY = ['re'] MPC_MIN_VER = "1.6.4" VLC_MIN_VERSION = "2.0.0" -VLC_INTERFACE_MIN_VERSION = "0.2.1" +VLC_INTERFACE_MIN_VERSION = "0.2.4" CONTROLLED_ROOMS_MIN_VERSION = "1.3.0" USER_READY_MIN_VERSION = "1.3.1" MPC_PATHS = [ @@ -141,7 +142,7 @@ VLC_SLAVE_NONOSX_ARGS = ['--no-one-instance', '--no-one-instance-when-started-fr MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_-]+)=(.+)$|^(Exiting)\.\.\. \((.+)\)$" VLC_ANSWER_REGEX = r"(?:^(?P[a-zA-Z_]+)(?:\: )?(?P.*))" UI_COMMAND_REGEX = r"^(?P[^\ ]+)(?:\ (?P.+))?" -UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P[/+-])?(?P