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