diff --git a/buildPy2exe.py b/buildPy2exe.py index 49f1d46..9f3113a 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -68,6 +68,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_ENGLISH} "Start Menu" LangString ^Desktop $${LANG_ENGLISH} "Desktop" LangString ^QuickLaunchBar $${LANG_ENGLISH} "Quick Launch Bar" + LangString ^AutomaticUpdates $${LANG_ENGLISH} "Check for updates automatically" LangString ^UninstConfig $${LANG_ENGLISH} "Delete configuration file." LangString ^SyncplayLanguage $${LANG_POLISH} "pl" @@ -88,6 +89,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_RUSSIAN} "в меню Пуск" LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе" LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска" + LangString ^AutomaticUpdates $${LANG_RUSSIAN} "Проверять обновления автоматически"; TODO: Confirm Russian translation ("Check for updates automatically") LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек." LangString ^SyncplayLanguage $${LANG_GERMAN} "de" @@ -98,6 +100,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_GERMAN} "Startmenü" LangString ^Desktop $${LANG_GERMAN} "Desktop" LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste" + LangString ^AutomaticUpdates $${LANG_GERMAN} "Automatisch nach Updates suchen"; TODO: Confirm German translation ("Check for updates automatically") LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen." ; Remove text to save space @@ -117,11 +120,13 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Icon_Syncplay_Handle Var CheckBox_Associate Var CheckBox_VLC + Var CheckBox_AutomaticUpdates Var CheckBox_StartMenuShortcut Var CheckBox_DesktopShortcut Var CheckBox_QuickLaunchShortcut Var CheckBox_Associate_State Var CheckBox_VLC_State + Var CheckBox_AutomaticUpdates_State Var CheckBox_StartMenuShortcut_State Var CheckBox_DesktopShortcut_State Var CheckBox_QuickLaunchShortcut_State @@ -237,10 +242,10 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateGroupBox} 1u 27u 264u 30u "$$(^DirSubText)" Pop $$GroupBox_DirSub - $${NSD_CreateLabel} 0u 111u 265u 8u "$$(^SpaceRequired)$$SizeMB" + $${NSD_CreateLabel} 0u 122u 132 8u "$$(^SpaceRequired)$$SizeMB" Pop $$Label_Size - $${NSD_CreateLabel} 0u 122u 265u 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB" + $${NSD_CreateLabel} 321u 122u 132 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB" Pop $$Label_Space $${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)" @@ -253,16 +258,20 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateCheckBox} 8u 72u 250u 10u "$$(^VLC)" Pop $$CheckBox_VLC - $${NSD_CreateLabel} 8u 85u 187u 10u "$$(^Shortcut)" + $${NSD_CreateCheckBox} 8u 85u 250u 10u "$$(^AutomaticUpdates)" + Pop $$CheckBox_AutomaticUpdates + $${NSD_Check} $$CheckBox_AutomaticUpdates + + $${NSD_CreateLabel} 8u 98u 187u 10u "$$(^Shortcut)" Pop $$Label_Shortcut - $${NSD_CreateCheckbox} 8u 98u 60u 10u "$$(^StartMenu)" + $${NSD_CreateCheckbox} 8u 111u 60u 10u "$$(^StartMenu)" Pop $$CheckBox_StartMenuShortcut - $${NSD_CreateCheckbox} 78u 98u 70u 10u "$$(^Desktop)" + $${NSD_CreateCheckbox} 78u 111u 70u 10u "$$(^Desktop)" Pop $$CheckBox_DesktopShortcut - $${NSD_CreateCheckbox} 158u 98u 130u 10u "$$(^QuickLaunchBar)" + $${NSD_CreateCheckbox} 158u 111u 130u 10u "$$(^QuickLaunchBar)" Pop $$CheckBox_QuickLaunchShortcut $${If} $$CheckBox_Associate_State == $${BST_CHECKED} @@ -287,6 +296,10 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_Check} $$CheckBox_QuickLaunchShortcut $${EndIf} + $${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED} + $${NSD_Check} $$CheckBox_AutomaticUpdates + $${EndIf} + nsDialogs::Show $${NSD_FreeIcon} $$Icon_Syncplay_Handle @@ -297,6 +310,7 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_GetText} $$Text_Directory $$INSTDIR $${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State $${NSD_GetState} $$CheckBox_VLC $$CheckBox_VLC_State + $${NSD_GetState} $$CheckBox_AutomaticUpdates $$CheckBox_AutomaticUpdates_State $${NSD_GetState} $$CheckBox_StartMenuShortcut $$CheckBox_StartMenuShortcut_State $${NSD_GetState} $$CheckBox_DesktopShortcut $$CheckBox_DesktopShortcut_State $${NSD_GetState} $$CheckBox_QuickLaunchShortcut $$CheckBox_QuickLaunchShortcut_State @@ -444,6 +458,11 @@ NSIS_SCRIPT_TEMPLATE = r""" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "EstimatedSize" "$$SizeHex" WriteINIStr $$APPDATA\syncplay.ini general language $$(^SyncplayLanguage) + $${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED} + WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "True" + $${Else} + WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "False" + $${EndIf} FunctionEnd Function un.installConfirm @@ -624,7 +643,8 @@ 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/empty_checkbox.png', 'resources/tick_checkbox.png', + 'resources/world_explore.png', 'resources/application_get.png', 'resources/cog.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/resources/application_get.png b/resources/application_get.png new file mode 100644 index 0000000..28e41ea Binary files /dev/null and b/resources/application_get.png differ diff --git a/resources/cog.png b/resources/cog.png new file mode 100644 index 0000000..67de2c6 Binary files /dev/null and b/resources/cog.png differ diff --git a/resources/cross_checkbox.png b/resources/cross_checkbox.png new file mode 100644 index 0000000..785a529 Binary files /dev/null and b/resources/cross_checkbox.png differ diff --git a/resources/empty_checkbox.png b/resources/empty_checkbox.png new file mode 100644 index 0000000..e8ad9d7 Binary files /dev/null and b/resources/empty_checkbox.png differ 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/resources/tick_checkbox.png b/resources/tick_checkbox.png new file mode 100644 index 0000000..d434c7f Binary files /dev/null and b/resources/tick_checkbox.png differ diff --git a/resources/world_explore.png b/resources/world_explore.png new file mode 100644 index 0000000..c9a2bb0 Binary files /dev/null and b/resources/world_explore.png differ diff --git a/syncplay/__init__.py b/syncplay/__init__.py index a1fa3eb..dbe21b3 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,4 @@ version = '1.3.0' -milestone = 'Akki' +milestone = 'Chami' +release_number = '2' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 5fafbc6..8f32077 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) @@ -104,8 +105,9 @@ class SyncplayClient(object): self._userOffset = 0.0 self._speedChanged = False self.behindFirstDetected = None + self.autoPlay = False - 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) @@ -125,6 +127,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): @@ -158,6 +162,20 @@ 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 not paused and not self.userlist.currentUser.isReady() and not self.userlist.areAllOtherUsersInRoomReady(): + paused = True + self._player.setPaused(paused) + self._playerPaused = paused + self.changeReadyState(True, manuallyInitiated=True) + pauseChange = False + self.ui.showMessage(getMessage("ready-to-unpause-notification")) + else: + 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(), manuallyInitiated=False) if self._lastGlobalUpdate: self._lastPlayerUpdate = time.time() if (pauseChange or seeked) and self._protocol: @@ -301,8 +319,10 @@ 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() def removeUser(self, username): if self.userlist.isUserInYourRoom(username): @@ -379,13 +399,14 @@ class SyncplayClient(object): import random random.seed() random_number = random.randrange(1000, 9999) - self.userlist.currentUser.username = "Anonymous" + str(random_number) + self.userlist.currentUser.username = "Anonymous" + str(random_number) # Not localised as this would give away locale def getUsername(self): return self.userlist.currentUser.username def setRoom(self, roomName): self.userlist.currentUser.room = roomName + self.resetAutoPlayState() def sendRoom(self): room = self.userlist.currentUser.room @@ -402,6 +423,8 @@ class SyncplayClient(object): self.identifyAsController(storedRoomPassword) def connected(self): + 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): @@ -470,15 +493,45 @@ class SyncplayClient(object): return wrapper return requireMinVersionDecorator + def changeAutoPlayState(self, newState): + self.autoPlay = newState + self.autoPlayCheck() + + def autoPlayCheck(self): + if self.autoPlay and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported() and self.userlist.areAllUsersInRoomReady(): + self.setPaused(False) + + def resetAutoPlayState(self): + self.autoPlay = False + self.ui.updateAutoPlayState(False) + + @requireMinServerVersion(constants.USER_READY_MIN_VERSION) + def toggleReady(self, manuallyInitiated=True): + self._protocol.setReady(not self.userlist.currentUser.isReady(), manuallyInitiated) + + @requireMinServerVersion(constants.USER_READY_MIN_VERSION) + def changeReadyState(self, newState, manuallyInitiated=True): + oldState = self.userlist.currentUser.isReady() + if newState != oldState: + self.toggleReady(manuallyInitiated) + + 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: + self._warnings.checkReadyStates() + @requireMinServerVersion(constants.CONTROLLED_ROOMS_MIN_VERSION) def createControlledRoom(self, roomName): controlPassword = utils.RandomStringGenerator.generate_room_password() - self.ui.showMessage(u"Attempting to create controlled room '{}' with password '{}'...".format(roomName, controlPassword)) self.lastControlPasswordAttempt = controlPassword self._protocol.requestControlledRoom(roomName, controlPassword) def controlledRoomCreated(self, roomName, controlPassword): - self.ui.showMessage(u"Created controlled room '{}' with password '{}'. Please save this information for future reference!".format(roomName, controlPassword)) + self.ui.showMessage(getMessage("created-controlled-room-notification").format(roomName, controlPassword)) self.setRoom(roomName) self.sendRoom() self._protocol.requestControlledRoom(roomName, controlPassword) @@ -498,7 +551,8 @@ class SyncplayClient(object): self._protocol.requestControlledRoom(self.getRoom(), controlPassword) def controllerIdentificationError(self, username, room): - self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username)) + if username == self.getUsername(): + self.ui.showErrorMessage(getMessage("failed-to-identify-as-controller-notification").format(username)) def controllerIdentificationSuccess(self, username, roomname): self.userlist.setUserAsController(username) @@ -517,55 +571,132 @@ class SyncplayClient(object): if self.controlpasswords.has_key(room): return self.controlpasswords[room] + def checkForUpdate(self, userInitiated): + try: + import urllib, syncplay, sys, messages, json + params = urllib.urlencode({'version': syncplay.version, 'milestone': syncplay.milestone, 'release_number': syncplay.release_number, + 'language': messages.messages["CURRENT"], 'platform': sys.platform, 'userInitiated': userInitiated}) + + f = urllib.urlopen(constants.SYNCPLAY_UPDATE_URL.format(params)) + response = f.read() + response = response.replace("

","").replace("

","").replace("
","").replace("“","\"").replace("”","\"") # Fix Wordpress + response = json.loads(response) + return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None + except: + return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL + 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",), - "displayedFor": 0, - }, - "alone-in-the-room": { - "timer": task.LoopingCall(self.__displayMessageOnOSD, - "alone-in-the-room",), - "displayedFor": 0, - }, - } + "room-file-differences": { + "timer": task.LoopingCall(self.__displayMessageOnOSD, "room-file-differences", + lambda: self._checkRoomForSameFiles(OSDOnly=True),), + "displayedFor": 0, + }, + "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, + }, + } + self.pausedTimer = task.LoopingCall(self.__displayPausedMessagesOnOSD) + self.pausedTimer.start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) + def checkWarnings(self): - self._checkIfYouReAloneInTheRoom() - self._checkRoomForSameFiles() + self._checkIfYouReAloneInTheRoom(OSDOnly=False) + self._checkRoomForSameFiles(OSDOnly=False) + self.checkReadyStates() - def _checkRoomForSameFiles(self): + 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) - elif self._warnings["room-files-not-same"]['timer'].running: - self._warnings["room-files-not-same"]['timer'].stop() + self._displayReadySameWarning() + if not OSDOnly and constants.SHOW_OSD_WARNINGS and not self._warnings["room-file-differences"]['timer'].running: + self._warnings["room-file-differences"]['timer'].start(constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, True) + elif self._warnings["room-file-differences"]['timer'].running: + self._warnings["room-file-differences"]['timer'].stop() - def _checkIfYouReAloneInTheRoom(self): + def _checkIfYouAreOnlyUserInRoomWhoSupportsReadiness(self): + self._userlist._onlyUserInRoomWhoSupportsReadiness() + + 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._client._player: + return + osdMessage = None + if not self._userlist.areAllFilesInRoomSame(): + fileDifferencesMessage = getMessage("room-file-differences").format(self._userlist.getFileDifferencesForRoom()) + if self._userlist.currentUser.canControl() and self._userlist.isReadinessSupported(): + if self._userlist.areAllUsersInRoomReady(): + osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("all-users-ready").format(self._userlist.readyUserCount())) + else: + osdMessage = u"{}{}{}".format(fileDifferencesMessage, self._client._player.osdMessageSeparator, getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady())) + else: + osdMessage = fileDifferencesMessage + elif self._userlist.isReadinessSupported(): + if self._userlist.areAllUsersInRoomReady(): + osdMessage = getMessage("all-users-ready").format(self._userlist.readyUserCount()) + else: + osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()) + if osdMessage: + 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.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): + self.ready = None self.username = username self.room = room self.file = file_ @@ -573,10 +704,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 +742,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() @@ -619,6 +756,14 @@ class SyncplayUserlist(object): self._client = client self._roomUsersChanged = True + def isReadinessSupported(self): + if not utils.meetsMinVersion(self._client.serverVersion,constants.USER_READY_MIN_VERSION): + return False + elif self.onlyUserInRoomWhoSupportsReadiness(): + return False + else: + return True + def isRoomSame(self, room): if room and self.currentUser.room and self.currentUser.room == room: return True @@ -644,30 +789,49 @@ class SyncplayUserlist(object): message += getMessage("playing-notification/room-addendum").format(room) self.ui.showMessage(message, hideFromOSD) if self.currentUser.file and not self.currentUser.isFileSame(file_) and self.currentUser.room == room: - message = getMessage("file-different-notification").format(username) - self.ui.showMessage(message, hideFromOSD) - differences = [] - differentName = not utils.sameFilename(self.currentUser.file['name'], file_['name']) - differentSize = not utils.sameFilesize(self.currentUser.file['size'], file_['size']) - differentDuration = not utils.sameFileduration(self.currentUser.file['duration'], file_['duration']) - if differentName: - differences.append("filename") - if differentSize: - differences.append("size") - if differentDuration: - differences.append("duration") - message = getMessage("file-differences-notification") + ", ".join(differences) - self.ui.showMessage(message, hideFromOSD) + message = getMessage("file-differences-notification").format(self.getFileDifferencesForUser(self.currentUser.file, file_)) + self.ui.showMessage(message, True) + + def getFileDifferencesForUser(self, currentUserFile, otherUserFile): + differences = [] + differentName = not utils.sameFilename(currentUserFile['name'], otherUserFile['name']) + differentSize = not utils.sameFilesize(currentUserFile['size'], otherUserFile['size']) + differentDuration = not utils.sameFileduration(currentUserFile['duration'], otherUserFile['duration']) + if differentName: differences.append(getMessage("file-difference-filename")) + if differentSize: differences.append(getMessage("file-difference-filesize")) + if differentDuration: differences.append(getMessage("file-difference-duration")) + return ", ".join(differences) - def addUser(self, username, room, file_, noMessage=False, isController=None): + def getFileDifferencesForRoom(self): + differences = [] + differentName = False + differentSize = False + differentDuration = False + for otherUser in self._users.itervalues(): + if otherUser.room == self.currentUser.room: + if not utils.sameFilename(self.currentUser.file['name'], otherUser.file['name']): + differentName = True + if not utils.sameFilesize(self.currentUser.file['size'], otherUser.file['size']): + differentSize = True + if not utils.sameFileduration(self.currentUser.file['duration'], otherUser.file['duration']): + differentDuration = True + if differentName: differences.append(getMessage("file-difference-filename")) + if differentSize: differences.append(getMessage("file-difference-filesize")) + if differentDuration: differences.append(getMessage("file-difference-duration")) + return ", ".join(differences) + + def addUser(self, username, room, file_, noMessage=False, isController=None, isReady=None): 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) @@ -716,11 +880,46 @@ 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 areAllOtherUsersInRoomReady(self): + for user in self._users.itervalues(): + if user.room == self.currentUser.room and user.isReady() == False: + return False + return True + + def readyUserCount(self): + readyCount = 0 + if self.currentUser.isReady(): + readyCount += 1 + for user in self._users.itervalues(): + if user.room == self.currentUser.room and user.isReady(): + readyCount += 1 + return readyCount + + 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): @@ -728,6 +927,12 @@ class SyncplayUserlist(object): if user.room == self.currentUser.room: return False return True + + def onlyUserInRoomWhoSupportsReadiness(self): + for user in self._users.itervalues(): + if user.room == self.currentUser.room and user.isReady() is not None: + return False + return True def isUserInYourRoom(self, username): for user in self._users.itervalues(): @@ -744,6 +949,22 @@ 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 None + + 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) + self._client.autoPlayCheck() + def userListChange(self, room = None): if room is not None and self.isRoomSame(room): self._roomUsersChanged = True @@ -766,6 +987,7 @@ class SyncplayUserlist(object): rooms[self.currentUser.room].append(self.currentUser) rooms = self.sortList(rooms) self.ui.showUserList(self.currentUser, rooms) + self._client.autoPlayCheck() def clearList(self): self._users = {} @@ -780,22 +1002,44 @@ 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 updateAutoPlayState(self, newState): + self.__ui.updateAutoPlayState(newState) + 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 secondaryOSD and not constants.SHOW_OSD_WARNINGS: + return + if not self._client._player: + return + if constants.SHOW_OSD and self._client and self._client._player: + if not self._client._player.secondaryOSDSupported: + if secondaryOSD: + 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._client._player.osdMessageSeparator, 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, self._client._player.osdMessageSeparator, 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 7bbb7ec..682ca5e 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -1,8 +1,9 @@ # You might want to change these DEFAULT_PORT = 8999 OSD_DURATION = 3 -OSD_WARNING_MESSAGE_DURATION = 15 -MPC_OSD_POSITION = 2 #Right corner, 1 for left +OSD_WARNING_MESSAGE_DURATION = 5 +NO_SECONDARY_OSD_WARNING_DURATION = 13 +MPC_OSD_POSITION = 1 #Right corner, 1 for left MPLAYER_OSD_LEVEL = 1 UI_TIME_FORMAT = "[%X] " CONFIG_NAMES = [".syncplay", "syncplay.ini"] #Syncplay searches first to last @@ -12,7 +13,6 @@ RECENT_CLIENT_THRESHOLD = "1.2.9" #This and higher considered 'recent' clients WARN_OLD_CLIENTS = True #Use MOTD to inform old clients to upgrade LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI -SHOW_BUTTON_LABELS = True # If disabled, only shows icons for main GUI buttons SHOW_TOOLTIPS = True WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled) FALLBACK_INITIAL_LANGUAGE = "en" @@ -28,6 +28,7 @@ SHOW_DURATION_NOTIFICATION = True DEBUG_MODE = False #Changing these might be ok +AUTOMATIC_UPDATE_CHECK_FREQUENCY = 7 * 86400 # Days converted into seconds DEFAULT_REWIND_THRESHOLD = 4 MINIMUM_REWIND_THRESHOLD = 3 DEFAULT_FASTFORWARD_THRESHOLD = 5 @@ -45,9 +46,9 @@ PROTOCOL_TIMEOUT = 12.5 RECONNECT_RETRIES = 10 SERVER_STATE_INTERVAL = 1 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})$" @@ -58,10 +59,12 @@ COMMANDS_ROOM = ["r", "room"] COMMANDS_HELP = ['help', 'h', '?', '/?', r'\?'] COMMANDS_CREATE = ['c','create'] COMMANDS_AUTH = ['a','auth'] +COMMANDS_TOGGLE = ['t','toggle'] 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.0" MPC_PATHS = [ r"c:\program files (x86)\mpc-hc\mpc-hc.exe", r"c:\program files\mpc-hc\mpc-hc.exe", @@ -115,7 +118,10 @@ STYLE_SUBCHECKBOX = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 21px; STYLE_SUBLABEL = "QCheckBox, QLabel {{ margin-left: 6px; padding-left: 16px; background:url('{}') left no-repeat }}" #Graphic path STYLE_ERRORLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: red; padding: 2px; background: #FFAAAA; }" STYLE_SUCCESSLABEL = "QLabel { color : black; border-style: outset; border-width: 2px; border-radius: 7px; border-color: green; padding: 2px; background: #AAFFAA; }" +STYLE_READY_PUSHBUTTON = "QPushButton { text-align: left; padding: 10px 5px 10px 5px;}" +STYLE_AUTO_PLAY_PUSHBUTTON = "QPushButton { text-align: left; padding: 5px 5px 5px 5px; }" STYLE_NOTIFICATIONBOX = "Username { color: #367AA9; font-weight:bold; }" +STYLE_CONTACT_INFO = u"{}

" # Contact info message STYLE_USERNAME = "color: #367AA9; font-weight:bold;" STYLE_ERRORNOTIFICATION = "color: red;" STYLE_DIFFERENTITEM_COLOR = 'red' @@ -137,7 +143,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