diff --git a/resources/syncplayintf.lua b/resources/syncplayintf.lua index 2c3a880..8313820 100644 --- a/resources/syncplayintf.lua +++ b/resources/syncplayintf.lua @@ -36,14 +36,24 @@ function clear_chat() chat_log = {} end -local secondary_osd = "" -local last_secondary_osd_time = nil -local secondary_osd_mood = MOOD_NEUTRAL +local alert_osd = "" +local last_alert_osd_time = nil +local alert_osd_mood = MOOD_NEUTRAL -function set_secondary_osd(osd_message, mood) - secondary_osd = osd_message - last_secondary_osd_time = mp.get_time() - secondary_osd_mood = mood +local notification_osd = "" +local last_notification_osd_time = nil +local notification_osd_mood = MOOD_NEUTRAL + +function set_alert_osd(osd_message, mood) + alert_osd = osd_message + last_alert_osd_time = mp.get_time() + alert_osd_mood = mood +end + +function set_notification_osd(osd_message, mood) + notification_osd = osd_message + last_notification_osd_time = mp.get_time() + notification_osd_mood = mood end function add_chat(chat_message, mood) @@ -68,32 +78,28 @@ end function chat_update() ass = assdraw.ass_new() local chat_ass = '' - if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then + local rowsAdded = 0 + local to_add = '' + local incrementRow = 0 + if opts['chatOutputMode'] == CHAT_MODE_CHATROOM and chat_log ~= {} then local timedelta = mp.get_time() - last_chat_time - if timedelta >= 7 then + if timedelta >= opts['chatTimeout'] then clear_chat() end end - if secondary_osd ~= "" then - if mp.get_time() - last_secondary_osd_time < 5 and last_secondary_osd_time ~= nil then - local xpos = opts['chatLeftMargin'] - local ypos = opts['chatTopMargin']+(0*opts['chatOutputFontSize']) - local messageString = '('..secondary_osd..')' - local messageColour - if secondary_osd_mood == MOOD_NEUTRAL then - messageColour = "{\\1c&HFFFFFF}" - elseif secondary_osd_mood == MOOD_BAD then - messageColour = "{\\1c&H0000FF}" - elseif secondary_osd_mood == MOOD_GOOD then - messageColour = "{\\1c&H00FF00}" - end - messageString = messageColour..messageString - chat_ass = format_chatroom(xpos,ypos,messageString) - end + rowsAdded,to_add = process_alert_osd() + if to_add ~= nil and to_add ~= "" then + chat_ass = to_add end + incrementRow,to_add = process_notification_osd(rowsAdded) + rowsAdded = rowsAdded + incrementRow + if to_add ~= nil and to_add ~= "" then + chat_ass = chat_ass .. to_add + end + if #chat_log > 0 then for i = 1, #chat_log do - local to_add = process_chat_item(i) + local to_add = process_chat_item(i,rowsAdded) if to_add ~= nil and to_add ~= "" then chat_ass = chat_ass .. to_add end @@ -111,11 +117,69 @@ function chat_update() mp.set_osd_ass(CANVAS_WIDTH,CANVAS_HEIGHT, ass.text) end -function process_chat_item(i) +function process_alert_osd() + local rowsCreated = 0 + local stringToAdd = "" + if alert_osd ~= "" and mp.get_time() - last_alert_osd_time < opts['alertTimeout'] and last_alert_osd_time ~= nil then + local xpos = opts['chatLeftMargin'] + local ypos + local messageColour + if alert_osd_mood == MOOD_NEUTRAL then + messageColour = "{\\1c&HFFFFFF}" + elseif alert_osd_mood == MOOD_BAD then + messageColour = "{\\1c&H0000FF}" + elseif alert_osd_mood == MOOD_GOOD then + messageColour = "{\\1c&H00FF00}" + end + local messageString + local startRow = 0 + local stringLeftToProccess = alert_osd + while stringLeftToProccess ~= '' and stringLeftToProccess ~= nil do + local toDisplay + ypos = opts['chatTopMargin'] + ((startRow+rowsCreated)*opts['chatOutputFontSize']) + toDisplay, stringLeftToProccess = trim_string(stringLeftToProccess,opts['chatSplitMessageAt']) + rowsCreated = rowsCreated + 1 + messageString = messageColour..toDisplay + if stringToAdd ~= "" then + stringToAdd = stringToAdd .. format_chatroom(xpos,ypos,messageString) + else + stringToAdd = format_chatroom(xpos,ypos,messageString) + end + end + end + return rowsCreated, stringToAdd +end + +function process_notification_osd(startRow) + local rowsCreated = 0 + local stringToAdd = "" + if notification_osd ~= "" and mp.get_time() - last_notification_osd_time < opts['alertTimeout'] and last_notification_osd_time ~= nil then + local xpos = opts['chatLeftMargin'] + local messageColour + messageColour = "{\\1c&HFFFF00}" + local messageString + local startRow = startRow + local stringLeftToProccess = notification_osd + while stringLeftToProccess ~= '' and stringLeftToProccess ~= nil do + local toDisplay + local ypos = opts['chatTopMargin'] + ((startRow+rowsCreated)*opts['chatOutputFontSize']) + toDisplay, stringLeftToProccess = trim_string(stringLeftToProccess,opts['chatSplitMessageAt']) + rowsCreated = rowsCreated + 1 + messageString = messageColour..toDisplay + if stringToAdd ~= "" then + stringToAdd = stringToAdd .. format_chatroom(xpos,ypos,messageString) + else + stringToAdd =format_chatroom(xpos,ypos,messageString) + end + end + end + return rowsCreated, stringToAdd +end + + +function process_chat_item(i, rowsAdded) if opts['chatOutputMode'] == CHAT_MODE_CHATROOM then - return process_chat_item_chatroom(i) - elseif opts['chatOutputMode'] == CHAT_MODE_CHATROOM then - return process_chat_item_subtitle(i) + return process_chat_item_chatroom(i, rowsAdded) elseif opts['chatOutputMode'] == CHAT_MODE_SCROLLING then return process_chat_item_scrolling(i) end @@ -129,7 +193,7 @@ function process_chat_item_scrolling(i) if text ~= '' then local roughlen = string.len(text) * opts['chatOutputFontSize'] * 1.5 if xpos > (-1*roughlen) then - local row = chat_log[i].row + local row = chat_log[i].row-1+opts['scrollingFirstRowOffset'] local ypos = opts['chatTopMargin']+(row * opts['chatOutputFontSize']) return format_scrolling(xpos,ypos,text) else @@ -138,11 +202,12 @@ function process_chat_item_scrolling(i) end end -function process_chat_item_chatroom(i) +function process_chat_item_chatroom(i, startRow) local text = chat_log[i].text if text ~= '' then - xpos = opts['chatLeftMargin'] - ypos = opts['chatTopMargin']+(i*opts['chatOutputFontSize']) + local xpos = opts['chatLeftMargin'] + local rowNumber = i+startRow-1 + local ypos = opts['chatTopMargin']+(rowNumber*opts['chatOutputFontSize']) local timecreated = chat_log[i].timecreated local timedelta = 200 * (mp.get_time() - timecreated) @@ -178,30 +243,50 @@ mp.register_script_message('chat', function(e) add_chat(e) end) -mp.register_script_message('primary-osd-neutral', function(e) +-- Chat OSD + +mp.register_script_message('chat-osd-neutral', function(e) add_chat(e,MOOD_NEUTRAL) end) -mp.register_script_message('primary-osd-bad', function(e) +mp.register_script_message('chat-osd-bad', function(e) add_chat(e,MOOD_BAD) end) -mp.register_script_message('primary-osd-good', function(e) +mp.register_script_message('chat-osd-good', function(e) add_chat(e,MOOD_GOOD) end) -mp.register_script_message('secondary-osd-neutral', function(e) - set_secondary_osd(e,MOOD_NEUTRAL) +-- Alert OSD + +mp.register_script_message('alert-osd-neutral', function(e) + set_alert_osd(e,MOOD_NEUTRAL) end) -mp.register_script_message('secondary-osd-bad', function(e) - set_secondary_osd(e,MOOD_BAD) +mp.register_script_message('alert-osd-bad', function(e) + set_alert_osd(e,MOOD_BAD) end) -mp.register_script_message('secondary-osd-good', function(e) - set_secondary_osd(e,MOOD_GOOD) +mp.register_script_message('alert-osd-good', function(e) + set_alert_osd(e,MOOD_GOOD) end) +-- Notification OSD + +mp.register_script_message('notification-osd-neutral', function(e) + set_notification_osd(e,MOOD_NEUTRAL) +end) + +mp.register_script_message('notification-osd-bad', function(e) + set_notification_osd(e,MOOD_BAD) +end) + +mp.register_script_message('notification-osd-good', function(e) + set_notification_osd(e,MOOD_GOOD) +end) + +-- + mp.register_script_message('set_syncplayintf_options', function(e) set_syncplayintf_options(e) end) @@ -240,18 +325,24 @@ opts = { ['chatInputFontColor'] = "#000000", ['chatInputPosition'] = "Top", ['MaxChatMessageLength'] = 50, + ['chatSplitMessageAt'] = 70, ['chatOutputFontFamily'] = 'sans serif', ['chatOutputFontSize'] = 50, ['chatOutputFontWeight'] = 1, ['chatOutputFontUnderline'] = false, ['chatOutputFontColor'] = "#FFFFFF", ['chatOutputMode'] = "Chatroom", + ['scrollingFirstRowOffset'] = 3, -- Can be "Chatroom", "Subtitle" or "Scrolling" style ['chatMaxLines'] = 7, ['chatTopMargin'] = 25, ['chatLeftMargin'] = 20, ['chatBottomMargin'] = 30, - ['chatDirectInput'] = true + ['chatDirectInput'] = true, + -- + ['notificationTimeout'] = 3, + ['alertTimeout'] = 5, + ['chatTimeout'] = 7, } function detect_platform() @@ -439,9 +530,31 @@ function prev_utf8(str, pos) return pos end -function trim_input() - -- Naive helper function to find the next UTF-8 character in 'str' after 'pos' +function trim_string(line,maxCharacters) +-- Naive helper function to find the next UTF-8 character in 'str' after 'pos' -- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. + + local str = line + if str == nil or str == "" or str:len() <= maxCharacters then + return str, "" + end + local pos = 0 + local oldPos = -1 + local chars = 0 + + repeat + oldPos = pos + pos = next_utf8(str, pos) + chars = chars + 1 + until pos == oldPos or chars > maxCharacters + return str:sub(1,pos-1), str:sub(pos) +end + + +function trim_input() +-- Naive helper function to find the next UTF-8 character in 'str' after 'pos' +-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. + local str = line if str == nil or str == "" or str:len() <= opts['MaxChatMessageLength'] then return diff --git a/syncplay/client.py b/syncplay/client.py index e21c766..8a0bc86 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -143,8 +143,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 + if not self._player.alertOSDSupported: + constants.OSD_WARNING_MESSAGE_DURATION = constants.NO_ALERT_OSD_WARNING_DURATION self.scheduleAskPlayer() self.__playerReady.callback(player) @@ -790,7 +790,7 @@ class SyncplayClient(object): allReadyMessage = getMessage("all-users-ready").format(self.userlist.readyUserCount()) autoplayingMessage = getMessage("autoplaying-notification").format(int(self.autoplayTimeLeft)) countdownMessage = u"{}{}{}".format(allReadyMessage,self._player.osdMessageSeparator, autoplayingMessage) - self.ui.showOSDMessage(countdownMessage, 1, secondaryOSD=True, mood=constants.MESSAGE_GOODNEWS) + self.ui.showOSDMessage(countdownMessage, 1, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_GOODNEWS) if self.autoplayTimeLeft <= 0: self.setPaused(False) self.stopAutoplayCountdown() @@ -939,7 +939,7 @@ class SyncplayClient(object): def _checkIfYouReAloneInTheRoom(self, OSDOnly): if self._userlist.areYouAloneInRoom(): - self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True, mood=constants.MESSAGE_BADNEWS) + self._ui.showOSDMessage(getMessage("alone-in-the-room"), constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=constants.MESSAGE_BADNEWS) 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: @@ -990,7 +990,7 @@ class SyncplayClient(object): messageMood = constants.MESSAGE_BADNEWS osdMessage = getMessage("not-all-ready").format(self._userlist.usersInRoomNotReady()) if osdMessage: - self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, secondaryOSD=True, mood=messageMood) + self._ui.showOSDMessage(osdMessage, constants.WARNING_OSD_MESSAGES_LOOP_INTERVAL, OSDType=constants.OSD_ALERT, mood=messageMood) def __displayMessageOnOSD(self, warningName, warningFunction): if constants.OSD_WARNING_MESSAGE_DURATION > self._warnings[warningName]["displayedFor"]: @@ -1361,10 +1361,10 @@ 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.lastNotificatinOSDMessage = None + self.lastNotificationOSDEndTime = None + self.lastAlertOSDMessage = None + self.lastAlertOSDEndTime = None self.lastError = "" def setPlaylist(self, newPlaylist, newIndexFilename=None): @@ -1385,14 +1385,14 @@ class UiManager(object): def showChatMessage(self, username, userMessage): messageString = u"<{}> {}".format(username, userMessage) - if self._client._player.chatOSDSupported: + if self._client._player.alertOSDSupported: self._client._player.displayChatMessage(username,userMessage) else: self.showOSDMessage(messageString, duration=constants.OSD_DURATION) self.__ui.showMessage(messageString) - def showMessage(self, message, noPlayer=False, noTimestamp=False, secondaryOSD=False,mood=constants.MESSAGE_NEUTRAL): - if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD, mood=mood) + def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION,mood=constants.MESSAGE_NEUTRAL): + if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood) self.__ui.showMessage(message, noTimestamp) def updateAutoPlayState(self, newState): @@ -1401,28 +1401,28 @@ class UiManager(object): def showUserList(self, currentUser, rooms): self.__ui.showUserList(currentUser, rooms) - def showOSDMessage(self, message, duration=constants.OSD_DURATION, secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL): + def showOSDMessage(self, message, duration=constants.OSD_DURATION, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL): autoplayConditionsMet = self._client.autoplayConditionsMet() - if secondaryOSD and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning(): + if OSDType == constants.OSD_ALERT and not constants.SHOW_OSD_WARNINGS and not self._client.autoplayTimerIsRunning(): 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 + if not self._client._player.alertOSDSupported: + if OSDType == constants.OSD_ALERT: + self.lastAlertOSDMessage = message if autoplayConditionsMet: - self.lastSecondaryOSDEndTime = time.time() + 1.0 + self.lastAlertOSDEndTime = time.time() + 1.0 else: - 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) + self.lastAlertOSDEndTime = time.time() + constants.NO_ALERT_OSD_WARNING_DURATION + if self.lastNotificationOSDEndTime and time.time() < self.lastNotificationOSDEndTime: + message = u"{}{}{}".format(message, self._client._player.osdMessageSeparator, self.lastNotificatinOSDMessage) 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, int(duration * 1000), secondaryOSD, mood) + self.lastNotificatinOSDMessage = message + self.lastNotificationOSDEndTime = time.time() + constants.OSD_DURATION + if self.lastAlertOSDEndTime and time.time() < self.lastAlertOSDEndTime: + message = u"{}{}{}".format(self.lastAlertOSDMessage, self._client._player.osdMessageSeparator, message) + self._client._player.displayMessage(message, int(duration * 1000), OSDType, mood) def setControllerStatus(self, username, isController): self.__ui.setControllerStatus(username, isController) diff --git a/syncplay/constants.py b/syncplay/constants.py index 8805e3d..facc4bd 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -2,7 +2,7 @@ DEFAULT_PORT = 8999 OSD_DURATION = 3.0 OSD_WARNING_MESSAGE_DURATION = 5.0 -NO_SECONDARY_OSD_WARNING_DURATION = 13.0 +NO_ALERT_OSD_WARNING_DURATION = 13.0 MPC_OSD_POSITION = 1 #Right corner, 1 for left MPLAYER_OSD_LEVEL = 1 UI_TIME_FORMAT = "[%X] " @@ -216,10 +216,13 @@ FILEITEM_SWITCH_FILE_SWITCH = 1 FILEITEM_SWITCH_STREAM_SWITCH = 2 PLAYLISTITEM_CURRENTLYPLAYING_ROLE = 3 -MESSAGE_NEUTRAL = 0 -MESSAGE_BADNEWS = 1 -MESSAGE_GOODNEWS = 2 -MOOD_LIST = ["neutral","bad","good"] +MESSAGE_NEUTRAL = "neutral" +MESSAGE_BADNEWS = "bad" +MESSAGE_GOODNEWS = "good" + +OSD_NOTIFICATION = "notification" # Also known as PrimaryOSD +OSD_ALERT = "alert" # Also known as SecondaryOSD +OSD_CHAT = "chat" SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index a794b70..4282ad0 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -306,7 +306,7 @@ class MpcHcApi: class MPCHCAPIPlayer(BasePlayer): speedSupported = False - secondaryOSDSupported = False + alertOSDSupported = False customOpenDialog = False chatOSDSupported = False osdMessageSeparator = "; " @@ -396,7 +396,7 @@ class MPCHCAPIPlayer(BasePlayer): def openFile(self, filePath, resetPosition=False): self._mpcApi.openFile(filePath) - def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL): + def displayMessage(self, message, duration = (constants.OSD_DURATION*1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL): self._mpcApi.sendOsd(message, constants.MPC_OSD_POSITION, duration) @retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1) diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 117ea09..e8aac44 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -10,8 +10,8 @@ import os, sys class MplayerPlayer(BasePlayer): speedSupported = True customOpenDialog = False - #secondaryOSDSupported = False # TODO: Make conditional - secondaryOSDSupported = True + #chatOSDSupported = False # TODO: Make conditional + aletOSDSupported = True chatOSDSupported = False osdMessageSeparator = "; " @@ -89,18 +89,14 @@ class MplayerPlayer(BasePlayer): def _getProperty(self, property_): self._listener.sendLine("get_property {}".format(property_)) - def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False, mood=constants.MESSAGE_NEUTRAL): - moodString = constants.MOOD_LIST[mood] + def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL): + messageString = self._sanitizeText(message.replace("\\n", "")).replace("", "\\n") + self._listener.sendLine( + u'script-message-to syncplayintf {}-osd-{} "{}"'.format(OSDType,mood, messageString)) - if not secondaryOSD and self._client.chatIsEnabled(): # TODO: Add check to ensure it is in 'chatroom' mode - max_message_length = 3+constants.MAX_USERNAME_LENGTH+constants.MAX_CHAT_MESSAGE_LENGTH - messageStrings = utils.splitText(message,max_message_length) - for messageString in messageStrings: - self._listener.sendLine(u'script-message-to syncplayintf primary-osd-{} "{}"'.format(moodString,messageString)) - return - if secondaryOSD: - messageString = self._sanitizeText(message.replace("\\n", "")).replace("", "\\n") - self._listener.sendLine(u'script-message-to syncplayintf secondary-osd-{} "{}"'.format(moodString,messageString)) + return + + # TODO: Support legacy displayMessage for versiosn that don't support syncplayintf.lua message = self._sanitizeText(message.replace("\\n","")).replace("","\\n") #self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, message, duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8')) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 4d4a6c1..de48dfe 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -110,6 +110,7 @@ class OldMpvPlayer(MpvPlayer): class NewMpvPlayer(OldMpvPlayer): lastResetTime = None lastMPVPositionUpdate = None + alertOSDSupported = True chatOSDSupported = True def setPaused(self, value): diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index 2b0505c..8153e0e 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -15,8 +15,8 @@ from syncplay.messages import getMessage class VlcPlayer(BasePlayer): speedSupported = True customOpenDialog = False - secondaryOSDSupported = True - chatOSDSupported = False + chatOSDSupported = True + alertOSDSupported = False osdMessageSeparator = "; " RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX)