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