diff --git a/resources/syncplayintf.lua b/resources/syncplayintf.lua index 2b7b96a..0cf049b 100644 --- a/resources/syncplayintf.lua +++ b/resources/syncplayintf.lua @@ -1,652 +1,69 @@ --- repl.lua -- A graphical REPL for mpv input commands --- --- © 2016, James Ross-Gowan --- --- Permission to use, copy, modify, and/or distribute this software for any --- purpose with or without fee is hereby granted, provided that the above --- copyright notice and this permission notice appear in all copies. --- --- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES --- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF --- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY --- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES --- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION --- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN --- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +-- syncplayintf.lua -- An interface for communication between mpv and Syncplay +-- Author: Etoh +-- Thanks: RiCON, James Ross-Gowan, Argon-, wm4 -local utils = require 'mp.utils' -local options = require 'mp.options' -local assdraw = require 'mp.assdraw' +local CANVAS_WIDTH = 1000 +local CANVAS_HEIGHT = 1000 +local ROW_HEIGHT = 100 +local PIXELS_PER_CHAR = 16 +local CHAT_FORMAT = "{\\fs60}{\an1}{\\q2}" +local MAX_ROWS = 7 +local MOVEMENT_PER_TICK = 6 +local TICK_FREQUENCY = 0.03 --- Default options -local opts = { - -- All drawing is scaled by this value, including the text borders and the - -- cursor. Change it if you have a high-DPI display. - scale = 1, - -- Set the font used for the REPL and the console. This probably doesn't - -- have to be a monospaced font. - font = 'monospace', - -- Set the font size used for the REPL and the console. This will be - -- multiplied by "scale." - ['font-size'] = 16, -} +local chat_log = {} -function detect_platform() - local o = {} - -- Kind of a dumb way of detecting the platform but whatever - if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then - return 'windows' - elseif mp.get_property_native('options/input-app-events', o) ~= o then - return 'macos' +local assdraw = require "mp.assdraw" + +function format_chat(xpos, ypos, text) + chat_message = CHAT_FORMAT .. "{\\pos("..xpos..","..ypos..")}"..text.."\n" + return string.format(chat_message) +end + +function clear_chat() + chat_log = {} +end + +function add_chat(chat_message) + local entry = #chat_log+1 + for i = 1, #chat_log do + if chat_log[i].text == '' then + entry = i + break + end end - return 'linux' + local row = ((entry-1) % MAX_ROWS)+1 + chat_log[entry] = { xpos=CANVAS_WIDTH, text=tostring(chat_message), row=row } end --- Pick a better default font for Windows and macOS -local platform = detect_platform() -if platform == 'windows' then - opts.font = 'Consolas' -elseif platform == 'macos' then - opts.font = 'Menlo' -end - --- Apply user-set options -options.read_options(opts) - --- Build a list of commands, properties and options for tab-completion -local option_info = { - 'name', 'type', 'set-from-commandline', 'set-locally', 'default-value', - 'min', 'max', 'choices', -} -local cmd_list = { - 'ignore', 'seek', 'revert-seek', 'quit', 'quit-watch-later', 'stop', - 'frame-step', 'frame-back-step', 'playlist-next', 'playlist-prev', - 'playlist-shuffle', 'sub-step', 'sub-seek', 'osd', 'print-text', - 'show-text', 'show-progress', 'sub-add', 'sub-remove', 'sub-reload', - 'tv-last-channel', 'screenshot', 'screenshot-to-file', 'screenshot-raw', - 'loadfile', 'loadlist', 'playlist-clear', 'playlist-remove', - 'playlist-move', 'run', 'set', 'add', 'cycle', 'multiply', 'cycle-values', - 'enable-section', 'disable-section', 'define-section', 'ab-loop', - 'drop-buffers', 'af', 'af-command', 'ao-reload', 'vf', 'vf-command', - 'script-binding', 'script-message', 'script-message-to', 'overlay-add', - 'overlay-remove', 'write-watch-later-config', 'hook-add', 'hook-ack', - 'mouse', 'keypress', 'keydown', 'keyup', 'audio-add', 'audio-remove', - 'audio-reload', 'rescan-external-file', 'apply-profile', 'load-script', -} -local prop_list = mp.get_property_native('property-list') -for _, opt in ipairs(mp.get_property_native('options')) do - prop_list[#prop_list + 1] = 'options/' .. opt - prop_list[#prop_list + 1] = 'file-local-options/' .. opt - prop_list[#prop_list + 1] = 'option-info/' .. opt - for _, p in ipairs(option_info) do - prop_list[#prop_list + 1] = 'option-info/' .. opt .. '/' .. p - end -end - -local repl_active = false -local insert_mode = false -local line = '' -local cursor = 1 -local history = {} -local history_pos = 1 -local log_ring = {} - --- Add a line to the log buffer (which is limited to 100 lines) -function log_add(style, text) - log_ring[#log_ring + 1] = { style = style, text = text } - if #log_ring > 100 then - table.remove(log_ring, 1) - end -end - --- Escape a string for verbatim display on the OSD -function ass_escape(str) - -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if - -- it isn't followed by a recognised character, so add a zero-width - -- non-breaking space - str = str:gsub('\\', '\\\239\187\191') - str = str:gsub('{', '\\{') - str = str:gsub('}', '\\}') - -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of - -- consecutive newlines - str = str:gsub('\n', '\239\187\191\\N') - return str -end - --- Render the REPL and console as an ASS OSD -function update() +function chat_update() + ass = assdraw.ass_new() local screenx, screeny, aspect = mp.get_osd_size() - screenx = screenx / opts.scale - screeny = screeny / opts.scale - - -- Clear the OSD if the REPL is not active - if not repl_active then - mp.set_osd_ass(screenx, screeny, '') - return - end - - local ass = assdraw.ass_new() - local style = '{\\r' .. - '\\1a&H00&\\3a&H00&\\4a&H99&' .. - '\\1c&Heeeeee&\\3c&H111111&\\4c&H000000&' .. - '\\fn' .. opts.font .. '\\fs' .. opts['font-size'] .. - '\\bord2\\xshad0\\yshad1\\fsp0\\q1}' - -- Create the cursor glyph as an ASS drawing. ASS will draw the cursor - -- inline with the surrounding text, but it sets the advance to the width - -- of the drawing. So the cursor doesn't affect layout too much, make it as - -- thin as possible and make it appear to be 1px wide by giving it 0.5px - -- horizontal borders. - local cheight = opts['font-size'] * 8 - local cglyph = '{\\r' .. - '\\1a&H44&\\3a&H44&\\4a&H99&' .. - '\\1c&Heeeeee&\\3c&Heeeeee&\\4c&H000000&' .. - '\\xbord0.5\\ybord0\\xshad0\\yshad1\\p4\\pbo24}' .. - 'm 0 0 l 1 0 l 1 ' .. cheight .. ' l 0 ' .. cheight .. - '{\\p0}' - local before_cur = ass_escape(line:sub(1, cursor - 1)) - local after_cur = ass_escape(line:sub(cursor)) - - -- Render log messages as ASS. This will render at most screeny / font-size - -- messages. - local log_ass = '' - local log_messages = #log_ring - local log_max_lines = math.ceil(screeny / opts['font-size']) - if log_max_lines < log_messages then - log_messages = log_max_lines - end - for i = #log_ring - log_messages + 1, #log_ring do - log_ass = log_ass .. style .. log_ring[i].style .. ass_escape(log_ring[i].text) - end - - ass:new_event() - ass:an(1) - ass:pos(2, screeny - 2) - ass:append(log_ass .. '\\N') - ass:append(style .. '> ' .. before_cur) - ass:append(cglyph) - ass:append(style .. after_cur) - - -- Redraw the cursor with the REPL text invisible. This will make the - -- cursor appear in front of the text. - ass:new_event() - ass:an(1) - ass:pos(2, screeny - 2) - ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur) - ass:append(cglyph) - ass:append(style .. '{\\alpha&HFF&}' .. after_cur) - - mp.set_osd_ass(screenx, screeny, ass.text) -end - --- Set the REPL visibility (`, Esc) -function set_active(active) - if active == repl_active then return end - if active then - repl_active = true - insert_mode = false - mp.enable_key_bindings('repl-input', 'allow-hide-cursor+allow-vo-dragging') - else - repl_active = false - mp.disable_key_bindings('repl-input') - end - update() -end - --- Show the repl if hidden and replace its contents with 'text' --- (script-message-to repl type) -function show_and_type(text) - text = text or '' - - -- Save the line currently being edited, just in case - if line ~= text and line ~= '' and history[#history] ~= line then - history[#history + 1] = line - end - - line = text - cursor = line:len() + 1 - history_pos = #history + 1 - insert_mode = false - if repl_active then - update() - else - set_active(true) - end -end - --- Naive helper function to find the next UTF-8 character in 'str' after 'pos' --- by skipping continuation bytes. Assumes 'str' contains valid UTF-8. -function next_utf8(str, pos) - if pos > str:len() then return pos end - repeat - pos = pos + 1 - until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf - return pos -end - --- As above, but finds the previous UTF-8 charcter in 'str' before 'pos' -function prev_utf8(str, pos) - if pos <= 1 then return pos end - repeat - pos = pos - 1 - until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf - return pos -end - --- Insert a character at the current cursor position (' '-'~', Shift+Enter) -function handle_char_input(c) - if insert_mode then - line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor)) - else - line = line:sub(1, cursor - 1) .. c .. line:sub(cursor) - end - cursor = cursor + 1 - update() -end - --- Remove the character behind the cursor (Backspace) -function handle_backspace() - if cursor <= 1 then return end - local prev = prev_utf8(line, cursor) - line = line:sub(1, prev - 1) .. line:sub(cursor) - cursor = prev - update() -end - --- Remove the character in front of the cursor (Del) -function handle_del() - if cursor > line:len() then return end - line = line:sub(1, cursor - 1) .. line:sub(next_utf8(line, cursor)) - update() -end - --- Toggle insert mode (Ins) -function handle_ins() - insert_mode = not insert_mode -end - --- Move the cursor to the next character (Right) -function next_char(amount) - cursor = next_utf8(line, cursor) - update() -end - --- Move the cursor to the previous character (Left) -function prev_char(amount) - cursor = prev_utf8(line, cursor) - update() -end - --- Clear the current line (Ctrl+C) -function clear() - line = '' - cursor = 1 - insert_mode = false - history_pos = #history + 1 - update() -end - --- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D) -function maybe_exit() - if line == '' then - set_active(false) - end -end - --- Run the current command and clear the line (Enter) -function handle_enter() - if line == '' then - return - end - mp.command('print-text "'..line..'"') - clear() -end - --- Go to the specified position in the command history -function go_history(new_pos) - local old_pos = history_pos - history_pos = new_pos - - -- Restrict the position to a legal value - if history_pos > #history + 1 then - history_pos = #history + 1 - elseif history_pos < 1 then - history_pos = 1 - end - - -- Do nothing if the history position didn't actually change - if history_pos == old_pos then - return - end - - -- If the user was editing a non-history line, save it as the last history - -- entry. This makes it much less frustrating to accidentally hit Up/Down - -- while editing a line. - if old_pos == #history + 1 and line ~= '' and history[#history] ~= line then - history[#history + 1] = line - end - - -- Now show the history line (or a blank line for #history + 1) - if history_pos <= #history then - line = history[history_pos] - else - line = '' - end - cursor = line:len() + 1 - insert_mode = false - update() -end - --- Go to the specified relative position in the command history (Up, Down) -function move_history(amount) - go_history(history_pos + amount) -end - --- Go to the first command in the command history (PgUp) -function handle_pgup() - go_history(1) -end - --- Stop browsing history and start editing a blank line (PgDown) -function handle_pgdown() - go_history(#history + 1) -end - --- Move to the start of the current word, or if already at the start, the start --- of the previous word. (Ctrl+Left) -function prev_word() - -- This is basically the same as next_word() but backwards, so reverse the - -- string in order to do a "backwards" find. This wouldn't be as annoying - -- to do if Lua didn't insist on 1-based indexing. - cursor = line:len() - select(2, line:reverse():find('%s*[^%s]*', line:len() - cursor + 2)) + 1 - update() -end - --- Move to the end of the current word, or if already at the end, the end of --- the next word. (Ctrl+Right) -function next_word() - cursor = select(2, line:find('%s*[^%s]*', cursor)) + 1 - update() -end - --- List of tab-completions: --- pattern: A Lua pattern used in string:find. Should return the start and --- end positions of the word to be completed in the first and second --- capture groups (using the empty parenthesis notation "()") --- list: A list of candidate completion values. --- append: An extra string to be appended to the end of a successful --- completion. It is only appended if 'list' contains exactly one --- match. -local completers = { - { pattern = '^%s*()[%w_-]+()$', list = cmd_list, append = ' ' }, - { pattern = '^%s*set%s+()[%w_/-]+()$', list = prop_list, append = ' ' }, - { pattern = '^%s*set%s+"()[%w_/-]+()$', list = prop_list, append = '" ' }, - { pattern = '^%s*add%s+()[%w_/-]+()$', list = prop_list, append = ' ' }, - { pattern = '^%s*add%s+"()[%w_/-]+()$', list = prop_list, append = '" ' }, - { pattern = '^%s*cycle%s+()[%w_/-]+()$', list = prop_list, append = ' ' }, - { pattern = '^%s*cycle%s+"()[%w_/-]+()$', list = prop_list, append = '" ' }, - { pattern = '^%s*multiply%s+()[%w_/-]+()$', list = prop_list, append = ' ' }, - { pattern = '^%s*multiply%s+"()[%w_/-]+()$', list = prop_list, append = '" ' }, - { pattern = '${()[%w_/-]+()$', list = prop_list, append = '}' }, -} - --- Use 'list' to find possible tab-completions for 'part.' Returns the longest --- common prefix of all the matching list items and a flag that indicates --- whether the match was unique or not. -function complete_match(part, list) - local completion = nil - local full_match = false - - for _, candidate in ipairs(list) do - if candidate:sub(1, part:len()) == part then - if completion and completion ~= candidate then - local prefix_len = part:len() - while completion:sub(1, prefix_len + 1) - == candidate:sub(1, prefix_len + 1) do - prefix_len = prefix_len + 1 + local chat_messages = #chat_log + local chat_ass = '' + if #chat_log > 0 then + for i = 1, #chat_log do + local xpos = chat_log[i].xpos + local text = chat_log[i].text + if text ~= '' then + local roughlen = string.len(text) * PIXELS_PER_CHAR + if xpos > (-1*roughlen) then + local row = chat_log[i].row + local ypos = row * ROW_HEIGHT + chat_ass = chat_ass .. format_chat(xpos,ypos,text) + chat_log[i].xpos = xpos-MOVEMENT_PER_TICK + else + chat_log[i].text = '' end - completion = candidate:sub(1, prefix_len) - full_match = false - else - completion = candidate - full_match = true end end end - - return completion, full_match + ass:append(chat_ass) + mp.set_osd_ass(CANVAS_WIDTH,CANVAS_HEIGHT, ass.text) end - --- Complete the option or property at the cursor (TAB) -function complete() - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - - -- Try the first completer that works - for _, completer in ipairs(completers) do - -- Completer patterns should return the start and end of the word to be - -- completed as the first and second capture groups - local _, _, s, e = before_cur:find(completer.pattern) - if not s then - -- Multiple input commands can be separated by semicolons, so all - -- completions that are anchored at the start of the string with - -- '^' can start from a semicolon as well. Replace ^ with ; and try - -- to match again. - _, _, s, e = before_cur:find(completer.pattern:gsub('^^', ';')) - end - if s then - -- If the completer's pattern found a word, check the completer's - -- list for possible completions - local part = before_cur:sub(s, e) - local c, full = complete_match(part, completer.list) - if c then - -- If there was only one full match from the list, add - -- completer.append to the final string. This is normally a - -- space or a quotation mark followed by a space. - if full and completer.append then - c = c .. completer.append - end - - -- Insert the completion and update - before_cur = before_cur:sub(1, s - 1) .. c - cursor = before_cur:len() + 1 - line = before_cur .. after_cur - update() - return - end - end - end -end - --- Move the cursor to the beginning of the line (HOME) -function go_home() - cursor = 1 - update() -end - --- Move the cursor to the end of the line (END) -function go_end() - cursor = line:len() + 1 - update() -end - --- Delete from the cursor to the end of the word (Ctrl+W) -function del_word() - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - - before_cur = before_cur:gsub('[^%s]+%s*$', '', 1) - line = before_cur .. after_cur - cursor = before_cur:len() + 1 - update() -end - --- Delete from the cursor to the end of the line (Ctrl+K) -function del_to_eol() - line = line:sub(1, cursor - 1) - update() -end - --- Delete from the cursor back to the start of the line (Ctrl+U) -function del_to_start() - line = line:sub(cursor) - cursor = 1 - update() -end - --- Empty the log buffer of all messages (Ctrl+L) -function clear_log_buffer() - log_ring = {} - update() -end - --- Returns a string of UTF-8 text from the clipboard (or the primary selection) -function get_clipboard(clip) - if platform == 'linux' then - local res = utils.subprocess({ args = { - 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' - } }) - if not res.error then - return res.stdout - end - elseif platform == 'windows' then - local res = utils.subprocess({ args = { - 'powershell', '-NoProfile', '-Command', [[& { - Trap { - Write-Error -ErrorRecord $_ - Exit 1 - } - - $clip = "" - if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) { - $clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText - } else { - Add-Type -AssemblyName PresentationCore - $clip = [Windows.Clipboard]::GetText() - } - - $clip = $clip -Replace "`r","" - $u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip) - [Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length) - }]] - } }) - if not res.error then - return res.stdout - end - elseif platform == 'macos' then - local res = utils.subprocess({ args = { 'pbpaste' } }) - if not res.error then - return res.stdout - end - end - return '' -end - --- Paste text from the window-system's clipboard. 'clip' determines whether the --- clipboard or the primary selection buffer is used (on X11 only.) -function paste(clip) - local text = get_clipboard(clip) - local before_cur = line:sub(1, cursor - 1) - local after_cur = line:sub(cursor) - line = before_cur .. text .. after_cur - cursor = cursor + text:len() - update() -end - --- The REPL has pretty specific requirements for key bindings that aren't --- really satisified by any of mpv's helper methods, since they must be in --- their own input section, but they must also raise events on key-repeat. --- Hence, this function manually creates an input section and puts a list of --- bindings in it. -function add_repl_bindings(bindings) - local cfg = '' - for i, binding in ipairs(bindings) do - local key = binding[1] - local fn = binding[2] - local name = '__repl_binding_' .. i - mp.add_key_binding(nil, name, fn, 'repeatable') - cfg = cfg .. key .. ' script-binding ' .. mp.script_name .. '/' .. - name .. '\n' - end - mp.commandv('define-section', 'repl-input', cfg, 'force') -end - --- Mapping from characters to mpv key names -local binding_name_map = { - [' '] = 'SPACE', - ['#'] = 'SHARP', -} - --- List of input bindings. This is a weird mashup between common GUI text-input --- bindings and readline bindings. -local bindings = { - { 'esc', function() set_active(false) end }, - { 'enter', handle_enter }, - { 'shift+enter', function() handle_char_input('\n') end }, - { 'bs', handle_backspace }, - { 'shift+bs', handle_backspace }, - { 'del', handle_del }, - { 'shift+del', handle_del }, - { 'ins', handle_ins }, - { 'shift+ins', function() paste(false) end }, - { 'mouse_btn1', function() paste(false) end }, - { 'left', function() prev_char() end }, - { 'right', function() next_char() end }, - { 'up', function() move_history(-1) end }, - { 'axis_up', function() move_history(-1) end }, - { 'mouse_btn3', function() move_history(-1) end }, - { 'down', function() move_history(1) end }, - { 'axis_down', function() move_history(1) end }, - { 'mouse_btn4', function() move_history(1) end }, - { 'axis_left', function() end }, - { 'axis_right', function() end }, - { 'ctrl+left', prev_word }, - { 'ctrl+right', next_word }, - { 'tab', complete }, - { 'home', go_home }, - { 'end', go_end }, - { 'pgup', handle_pgup }, - { 'pgdwn', handle_pgdown }, - { 'ctrl+c', clear }, - { 'ctrl+d', maybe_exit }, - { 'ctrl+k', del_to_eol }, - { 'ctrl+l', clear_log_buffer }, - { 'ctrl+u', del_to_start }, - { 'ctrl+v', function() paste(true) end }, - { 'meta+v', function() paste(true) end }, - { 'ctrl+w', del_word }, -} --- Add bindings for all the printable US-ASCII characters from ' ' to '~' --- inclusive. Note, this is a pretty hacky way to do text input. mpv's input --- system was designed for single-key key bindings rather than text input, so --- things like dead-keys and non-ASCII input won't work. This is probably okay --- though, since all mpv's commands and properties can be represented in ASCII. -for b = (' '):byte(), ('~'):byte() do - local c = string.char(b) - local binding = binding_name_map[c] or c - bindings[#bindings + 1] = {binding, function() handle_char_input(c) end} -end -add_repl_bindings(bindings) - --- Add a global binding for enabling the REPL. While it's enabled, its bindings --- will take over and it can be closed with ESC. -mp.add_key_binding('`', 'repl-enable', function() - set_active(true) -end) - --- Add a script-message to show the REPL and fill it with the provided text -mp.register_script_message('type', function(text) - show_and_type(text) -end) - --- Redraw the REPL when the OSD size changes. This is needed because the --- PlayRes of the OSD will need to be adjusted. -mp.observe_property('osd-width', 'native', update) -mp.observe_property('osd-height', 'native', update) - --- Watch for log-messages and print them in the REPL console ---mp.enable_messages('info') +chat_timer=mp.add_periodic_timer(TICK_FREQUENCY, chat_update) +--mp.register_event("tick", chat_update) mp.register_script_message('chat', function(e) - log_add('', e .. '\r\n') - update() + add_chat(e) end) \ No newline at end of file diff --git a/syncplay/client.py b/syncplay/client.py index 6250b45..9db333a 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -203,7 +203,7 @@ class SyncplayClient(object): if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: self.playlist.advancePlaylistCheck() - elif pauseChange and self.serverFeatures["readiness"]: + elif pauseChange and self.serverFeatures.has_key("readiness") and self.serverFeatures["readiness"]: if currentLength == 0 or currentLength == -1 or\ not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): pauseChange = self._toggleReady(pauseChange, paused) @@ -1343,6 +1343,14 @@ class UiManager(object): if constants.DEBUG_MODE and message.rstrip(): sys.stderr.write("{}{}\n".format(time.strftime(constants.UI_TIME_FORMAT, time.localtime()),message.rstrip())) + def showChatMessage(self, username, userMessage): + messageString = u"<{}> {}".format(username, userMessage) + if self._client._player.chatOSDSupported: + 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): if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, secondaryOSD=secondaryOSD) self.__ui.showMessage(message, noTimestamp) diff --git a/syncplay/players/mpc.py b/syncplay/players/mpc.py index c38b074..3670394 100644 --- a/syncplay/players/mpc.py +++ b/syncplay/players/mpc.py @@ -308,6 +308,7 @@ class MPCHCAPIPlayer(BasePlayer): speedSupported = False secondaryOSDSupported = False customOpenDialog = False + chatOSDSupported = False osdMessageSeparator = "; " def __init__(self, client): diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py index 58871c6..a145ce6 100644 --- a/syncplay/players/mplayer.py +++ b/syncplay/players/mplayer.py @@ -11,6 +11,7 @@ class MplayerPlayer(BasePlayer): speedSupported = True customOpenDialog = False secondaryOSDSupported = False + chatOSDSupported = False osdMessageSeparator = "; " RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX) @@ -88,9 +89,12 @@ class MplayerPlayer(BasePlayer): self._listener.sendLine("get_property {}".format(property_)) def displayMessage(self, message, duration=(constants.OSD_DURATION * 1000), secondaryOSD=False): - self._listener.sendLine(u'script-message-to syncplayintf chat "{}"'.format(self._stripNewlines(message))) self._listener.sendLine(u'{} "{!s}" {} {}'.format(self.OSD_QUERY, self._stripNewlines(message), duration, constants.MPLAYER_OSD_LEVEL).encode('utf-8')) + def displayChatMessage(self, username, message): + messageString = u"<{}> {}".format(username, message) + self._listener.sendLine(u'script-message-to syncplayintf chat "{}"'.format(self._stripNewlines(messageString))) + def setSpeed(self, value): self._setProperty('speed', "{:.2f}".format(value)) diff --git a/syncplay/players/mpv.py b/syncplay/players/mpv.py index 84c7c4b..bae78d0 100644 --- a/syncplay/players/mpv.py +++ b/syncplay/players/mpv.py @@ -109,6 +109,7 @@ class OldMpvPlayer(MpvPlayer): class NewMpvPlayer(OldMpvPlayer): lastResetTime = None lastMPVPositionUpdate = None + chatOSDSupported = True def setPaused(self, value): if self._paused == value: diff --git a/syncplay/players/vlc.py b/syncplay/players/vlc.py index f3571ab..9113851 100644 --- a/syncplay/players/vlc.py +++ b/syncplay/players/vlc.py @@ -16,6 +16,7 @@ class VlcPlayer(BasePlayer): speedSupported = True customOpenDialog = False secondaryOSDSupported = True + chatOSDSupported = False osdMessageSeparator = "; " RE_ANSWER = re.compile(constants.VLC_ANSWER_REGEX) diff --git a/syncplay/protocols.py b/syncplay/protocols.py index c0055be..d1b35e7 100644 --- a/syncplay/protocols.py +++ b/syncplay/protocols.py @@ -249,9 +249,9 @@ class SyncClientProtocol(JSONCommandProtocol): } }) def handleChat(self,message): + username = message['username'] userMessage = message['message'] - messageString = u"<{}> {}".format(message['username'], userMessage) - self._client.ui.showMessage(messageString) + self._client.ui.showChatMessage(username, userMessage) def setReady(self, isReady, manuallyInitiated=True): self.sendSet({