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