Merge pull request #1 from Uriziel/master

.
This commit is contained in:
Etoh 2012-12-29 03:40:00 -08:00
commit 663fc835ad
11 changed files with 120 additions and 70 deletions

View File

@ -18,7 +18,7 @@ Syncplay does not use video streaming or file sharing so each user must have the
## Requirements
Frozen Windows executables are available on the download page - https://github.com/Uriziel/syncplay/downloads
* On Windows: `Media Player Classic - Home Cinema (MPC-HC)` >= `1.6.4`.
* On Windows: `Media Player Classic - Home Cinema (MPC-HC)` >= `1.6.4` or `mplayer2`.
* On Linux: `mplayer2`. `MPlayer` >= `1.1` should be compatible, but is not supported.
### Python scripts (for those not using the frozen executable package)
@ -33,9 +33,9 @@ If you are not using the frozen executable package then you will need the follow
If you are using the frozen executable package available from the download page then you will not need to be able to run Python scripts.
## Supported players
### mplayer2 on Linux
### mplayer2 on Linux and Windows
On Linux `syncplay` acts as a front-end for mplayer2.
On Linux and Windows `syncplay` acts as a front-end for mplayer2.
To use it select "Open with..." in context menu and choose `Syncplay` or from command line: "syncplay video_filename". If you wish to pass more arguments to mplayer2 prepend them with -- argument, it's treated as the last argument for wrapper.
Default mplayer2 output is suppressed, but if mplayer2 quits with errors, those errors will be printed (at most 50 last lines).
@ -48,19 +48,17 @@ On Windows simply running `syncplayClient.exe` opens a Syncplay command-line win
### Getting started with Syncplay on Windows
1. Ensure that you have the latest version of `Media Player Classic - Home Cinema (MPC-HC)` installed. The latest stable build is `1.6.4`.
1. Ensure that you have the latest version of `Media Player Classic - Home Cinema (MPC-HC)` and/or `mplayer2` installed. The latest stable build of MPC-HC is `1.6.4`.
2. Download Syncplay frozen executable package from https://github.com/Uriziel/syncplay/downloads and extract to a folder of your choosing.
2. Download Syncplay frozen executable package from the link provided in the topic of the #syncplay channel on irc.rizon.net and extract to a folder of your choosing.
3. If you are running your own server then open `syncplayServer.exe` (see "How to use the server", below).
4. Open `syncplayClient.exe` (or open the media file you wish to play with `syncplayClient.exe`, e.g. using "Open with").
4. Open the media file you wish to play with `syncplayClient.exe` (e.g. using "Open with"). If you are using `MPC-HC`, you can alternatively open `syncplayClient.exe` directly and then load the file later.
5. Enter configuration settings (see "Configuration window", below). Ensure that you are on the same server and room as your fellow viewers.
6. If you don't have the file you want to play open then open it from within the MPC-HC instance initiated by Syncplay.
7. Playing, pausing and seeking from within the MPC-HC instance should now be synchronised with everyone else in the same 'room'.
6. Playing, pausing and seeking from within the media player instance should now be synchronised with everyone else in the same 'room'.
### Getting started with Syncplay on Linux
@ -125,7 +123,9 @@ You can run `syncplayClient` with the following command-line switches to alter S
### Notification messages
* `Rewinded due to time difference with [user]` - This means that your media player ended up too far in front of the specified user and has jumped back to keep you in sync. This is usually because someone's computer isn't powerful enough to play the file smoothly. If someone is only a small amount in front then their playback rate will be reduced to 95% for a short amount of time to bring them back into sync.
* `Rewinded due to time difference with [user]` - This means that your media player ended up too far in front of the specified user and has jumped back to help keep you in sync. This is usually because someone's computer isn't powerful enough to play the file smoothly - it might be helpful for them to close unnecessary applications.
* `Slowing down due to time difference with [user]` - This means that your media player ended up too far in front of the specified user and has temporarily slowed down playback on your player to help keep you in sync. This is usually because someone's computer isn't powerful enough to play the file smoothly - it might be helpful for them to close unnecessary applications.
* `Reverting speed back to normal` - Slowing down due to time difference with user has ended (see above).
* `File you are playing appears to be different from [user]'s` - This means that the filename, size and/or duration of the file that the user is playing is different from the file that you are playing. This is for information only and is not an error.
* `[User] has left` - This means that the user is no longer connected to the server. If room isolation is enabled on the server then this could also mean that the user moved to a different room.
@ -171,6 +171,7 @@ You might also be able to discuss your problem through Internet Relay Chat (IRC)
1. Changing your system time while Syncplay is running confuses the sync. PROTIP: Don't do it.
2. In MPC-HC the 'Remember File position' feature will not work as expected if you are using Syncplay. If you want to save/load the file position when using Syncplay then use MPC-HC's built in Favorites feature.
3. Connecting to port 8999 is disallowed in some firewall configurations. Check your firewall settings if you are experiencing problems connecting to a server.
4. Manually setting the playback rate may cause problems. Syncplay generally assumes a playback rate of 1.0 and in some instances sets the playback rate to help get everyone more in sync.
## Authors
* *Concept and principal Syncplay developer* - Uriziel.

View File

@ -5,10 +5,10 @@ import time
from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor, task
from syncplay.protocols import SyncClientProtocol
from syncplay import utils
from syncplay import utils, constants
class SyncClientFactory(ClientFactory):
def __init__(self, client, retry = 10):
def __init__(self, client, retry = constants.RECONNECT_RETRIES):
self._client = client
self.retry = retry
self._timesTried = 0
@ -53,7 +53,7 @@ class SyncplayClient(object):
self.userlist = SyncplayUserlist(self.ui, self)
self._protocol = None
if(args.room == None or args.room == ''):
args.room = 'default'
args.room = constants.DEFAULT_ROOM
self.defaultRoom = args.room
self.playerPositionBeforeLastSeek = 0.0
self.setUsername(args.name)
@ -94,7 +94,7 @@ class SyncplayClient(object):
self._player = player
self.scheduleAskPlayer()
def scheduleAskPlayer(self, when=0.1):
def scheduleAskPlayer(self, when=constants.PLAYER_ASK_DELAY):
self._askPlayerTimer = task.LoopingCall(self.askPlayer)
self._askPlayerTimer.start(when)
@ -106,7 +106,7 @@ class SyncplayClient(object):
self.checkIfConnected()
def checkIfConnected(self):
if(self._lastGlobalUpdate and self._protocol and time.time() - self._lastGlobalUpdate > 4.1):
if(self._lastGlobalUpdate and self._protocol and time.time() - self._lastGlobalUpdate > constants.PROTOCOL_TIMEOUT):
self._lastGlobalUpdate = None
self.ui.showErrorMessage("Connection with server timed out")
self._protocol.drop()
@ -117,7 +117,7 @@ class SyncplayClient(object):
pauseChange = self.getPlayerPaused() != paused and self.getGlobalPaused() != paused
_playerDiff = abs(self.getPlayerPosition() - position)
_globalDiff = abs(self.getGlobalPosition() - position)
seeked = _playerDiff > 1 and _globalDiff > 1
seeked = _playerDiff > constants.SEEK_BOUNDARY and _globalDiff > constants.SEEK_BOUNDARY
return pauseChange, seeked
def updatePlayerStatus(self, paused, position):
@ -182,12 +182,12 @@ class SyncplayClient(object):
return madeChangeOnPlayer
def _slowDownToCoverTimeDifference(self, diff, setBy):
if(1.5 < diff and not self._speedChanged):
self._player.setSpeed(0.95)
if(constants.SLOWDOWN_KICKIN_BOUNDARY < diff and not self._speedChanged):
self._player.setSpeed(constants.SLOWDOWN_RATE)
self._speedChanged = True
message = "Slowing down due to time difference with <{}>".format(setBy)
self.ui.showMessage(message)
elif(self._speedChanged and diff < 0.1 ):
elif(self._speedChanged and diff < constants.SLOWDOWN_RESET_BOUNDARY):
self._player.setSpeed(1.00)
self._speedChanged = False
message = "Reverting speed back to normal"
@ -363,7 +363,7 @@ class SyncplayUser(object):
return False
sameName = self.file['name'] == file_['name']
sameSize = self.file['size'] == file_['size']
sameDuration = int(self.file['duration']) - int(file_['duration']) < 1
sameDuration = int(self.file['duration']) - int(file_['duration']) < constants.DIFFFERENT_DURATION_BOUNDARY
return sameName and sameSize and sameDuration
def __lt__(self, other):

43
syncplay/constants.py Normal file
View File

@ -0,0 +1,43 @@
DEFAULT_PORT = 8999
MPC_OPEN_MAX_WAIT_TIME = 10
MPC_LOCK_WAIT_TIME = 0.2
OSD_DURATION = 3000
MPC_OSD_POSITION = 2 #Right corner, 1 for left
MPC_RETRY_WAIT_TIME = 0.01
MPC_MAX_RETRIES = 30
MPC_PAUSE_TOGGLE_DELAY = 0.05
MPLAYER_OSD_LEVEL = 1
MPLAYER_ANSWER_REGEX = "^ANS_([a-zA-Z_]+)=(.+)$"
MPLAYER_SLAVE_ARGS = [ '-slave', '-msglevel', 'all=1:global=4']
UI_COMMAND_REGEX = r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?"
UI_OFFSET_REGEX = r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d+(?:[^\d\.](?:\d+)){0,2}(?:\.(?:\d+))?)$"
UI_SEEK_REGEX = r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d+(?:[^\d\.](?:\d+)){0,2}(?:\.(?:\d+))?)$"
UI_TIME_FORMAT = "[%X] "
COMMANDS_UNDO = ["u", "undo", "revert"]
COMMANDS_LIST = ["l", "list", "users"]
COMMANDS_PAUSE = ["p", "play", "pause"]
COMMANDS_ROOM = ["r", "room"]
COMMANDS_HELP = ['help', 'h', '?', '/?', '\?']
MPC_PATHS = [
"C:\Program Files (x86)\MPC-HC\mpc-hc.exe",
"C:\Program Files\MPC-HC\mpc-hc.exe",
"C:\Program Files\MPC-HC\mpc-hc64.exe",
"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc.exe",
"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc64.exe",
"C:\Program Files (x86)\Media Player Classic - Home Cinema\mpc-hc.exe",
"C:\Program Files (x86)\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
"C:\Program Files\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
"C:\Program Files (x86)\Combined Community Codec Pack\MPC\mpc-hc.exe",
"C:\Program Files\MPC HomeCinema (x64)\mpc-hc64.exe",
]
RECONNECT_RETRIES = 10
DEFAULT_ROOM = 'default'
PLAYER_ASK_DELAY = 0.1
PROTOCOL_TIMEOUT = 5
SEEK_BOUNDARY = 1
SLOWDOWN_RATE = 0.95
SLOWDOWN_KICKIN_BOUNDARY = 1.5
SLOWDOWN_RESET_BOUNDARY = 0.1
DIFFFERENT_DURATION_BOUNDARY = 1
PING_MOVING_AVERAGE_WEIGHT = 0.85
PARSE_TIME_REGEX = r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$'

16
syncplay/messages.py Normal file
View File

@ -0,0 +1,16 @@
en = {
"connecting" : "Attempting to connect to {}:{}"
}
messages = {
"en": en
}
def getMessage(locale, type_):
if(messages.has_key(locale)):
if(messages[locale].has_key(type_)):
return messages[locale][type_]
if(messages["en"].has_key(type_)):
return messages["en"][type_]
else:
raise KeyError()

View File

@ -7,7 +7,7 @@ from functools import wraps
from syncplay.players.basePlayer import BasePlayer
import re
from syncplay.utils import retry
from syncplay import constants
class MpcHcApi:
def __init__(self):
@ -31,7 +31,7 @@ class MpcHcApi:
def waitForFileStateReady(f): #@NoSelf
@wraps(f)
def wrapper(self, *args, **kwds):
if(not self.__locks.fileReady.wait(0.2)):
if(not self.__locks.fileReady.wait(constants.MPC_LOCK_WAIT_TIME)):
raise self.PlayerNotReadyException()
return f(self, *args, **kwds)
return wrapper
@ -39,7 +39,7 @@ class MpcHcApi:
def startMpc(self, path, args=()):
args = "%s /slave %s" % (" ".join(args), str(self.__listener.hwnd))
win32api.ShellExecute(0, "open", path, args, None, 1)
if(not self.__locks.mpcStart.wait(10)):
if(not self.__locks.mpcStart.wait(constants.MPC_OPEN_MAX_WAIT_TIME)):
raise self.NoSlaveDetectedException("Unable to start MPC in slave mode!")
self.__mpcExistenceChecking.start()
@ -76,7 +76,7 @@ class MpcHcApi:
def setSpeed(self, rate):
self.__listener.SendCommand(self.CMD_SETSPEED, unicode(rate))
def sendOsd(self, message, MsgPos=2, DurationMs=3000):
def sendOsd(self, message, MsgPos=constants.MPC_OSD_POSITION, DurationMs=constants.OSD_DURATION):
class __OSDDATASTRUCT(ctypes.Structure):
_fields_ = [
('nMsgPos', ctypes.c_int32),
@ -300,11 +300,6 @@ class MpcHcApi:
('lpData', ctypes.c_void_p)
]
class MPCHCAPIPlayer(BasePlayer):
speedSupported = False
@ -383,9 +378,9 @@ class MPCHCAPIPlayer(BasePlayer):
self._mpcApi.openFile(filePath)
def displayMessage(self, message):
self._mpcApi.sendOsd(message, 2, 3000)
self._mpcApi.sendOsd(message)
@retry(MpcHcApi.PlayerNotReadyException, 30, 0.01, 1)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
def setPaused(self, value):
if self.__switchPauseCalls:
value = not value
@ -394,17 +389,17 @@ class MPCHCAPIPlayer(BasePlayer):
else:
self._mpcApi.unpause()
@retry(MpcHcApi.PlayerNotReadyException, 30, 0.01, 1)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
def setPosition(self, value):
self._mpcApi.seek(value)
def __getPosition(self):
self.__positionUpdate.clear()
self._mpcApi.askForCurrentPosition()
self.__positionUpdate.wait(0.2)
self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME)
return self._mpcApi.lastFilePosition
@retry(MpcHcApi.PlayerNotReadyException, 30, 0.01, 1)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
def askForStatus(self):
if(self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0)):
self.__fileUpdate.release()
@ -421,14 +416,14 @@ class MPCHCAPIPlayer(BasePlayer):
self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition())
def __forcePause(self):
for _ in xrange(30):
for _ in xrange(constants.MPC_MAX_RETRIES):
self.setPaused(True)
time.sleep(0.01)
time.sleep(constants.MPC_RETRY_WAIT_TIME)
def __refreshMpcPlayState(self):
for _ in xrange(2):
self._mpcApi.playPause()
time.sleep(0.05)
time.sleep(constants.MPC_PAUSE_TOGGLE_DELAY)
def _setPausedAccordinglyToServer(self):
self.__forcePause()
@ -438,7 +433,7 @@ class MPCHCAPIPlayer(BasePlayer):
if(self._mpcApi.isPaused() <> self.__client.getGlobalPaused()):
self.__setUpStateForNewlyOpenedFile()
@retry(MpcHcApi.PlayerNotReadyException, 30, 0.1, 1)
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
def __setUpStateForNewlyOpenedFile(self):
self._setPausedAccordinglyToServer()
self._mpcApi.seek(self.__client.getGlobalPosition())

View File

@ -2,10 +2,11 @@ import subprocess
import re
import threading
from syncplay.players.basePlayer import BasePlayer
from syncplay import constants
class MplayerPlayer(BasePlayer):
speedSupported = True
RE_ANSWER = re.compile('^ANS_([a-zA-Z_]+)=(.+)$')
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
def __init__(self, client, playerPath, filePath, args):
self._client = client
self._paused = None
@ -71,7 +72,7 @@ class MplayerPlayer(BasePlayer):
self._listener.sendLine("get_property {}".format(property_))
def displayMessage(self, message):
self._listener.sendLine('osd_show_text "{!s}" {} {}'.format(message, 3000, 1))
self._listener.sendLine('osd_show_text "{!s}" {} {}'.format(message, constants.OSD_DURATION, constants.MPLAYER_OSD_LEVEL))
def setSpeed(self, value):
self._setProperty('speed', "{:.2f}".format(value))
@ -142,7 +143,8 @@ class MplayerPlayer(BasePlayer):
self.__playerController = playerController
if(not filePath):
raise ValueError
call = [playerPath, filePath, '-slave', '-msglevel', 'all=1:global=4']
call = [playerPath, filePath]
call.extend(constants.MPLAYER_SLAVE_ARGS)
if(args):
call.extend(args)
self.__process = subprocess.Popen(call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)

View File

@ -6,6 +6,7 @@ from twisted.internet.protocol import Factory
import syncplay
from syncplay.protocols import SyncServerProtocol
import time
from syncplay import constants
class SyncFactory(Factory):
def __init__(self, password = ''):
@ -87,7 +88,7 @@ class SyncFactory(Factory):
watcher.paused = paused
watcher.position = position
watcherProtocol.sendState(position, paused, doSeek, setBy, senderLatency, watcher.latency, forcedUpdate)
if(time.time() - watcher.lastUpdate > 4.1):
if(time.time() - watcher.lastUpdate > constants.PROTOCOL_TIMEOUT):
watcherProtocol.drop()
self.removeWatcher(watcherProtocol)
@ -95,7 +96,7 @@ class SyncFactory(Factory):
if (latencyCalculation):
ping = (time.time() - latencyCalculation) / 2
if (watcher.latency):
watcher.latency = watcher.latency * (0.85) + ping * (0.15) #Exponential moving average
watcher.latency = watcher.latency * (constants.PING_MOVING_AVERAGE_WEIGHT) + ping * (1-constants.PING_MOVING_AVERAGE_WEIGHT) #Exponential moving average
else:
watcher.latency = ping
@ -190,7 +191,7 @@ class SyncFactory(Factory):
def _checkUsers(self):
for room in self._rooms.itervalues():
for watcher in room.itervalues():
if(time.time() - watcher.lastUpdate > 4.1):
if(time.time() - watcher.lastUpdate > constants.PROTOCOL_TIMEOUT):
watcher.watcherProtocol.drop()
self.removeWatcher(watcher.watcherProtocol)
self._checkUsers() #restart

View File

@ -2,6 +2,7 @@ import ConfigParser
import argparse
import os
import sys
from syncplay import constants
class InvalidConfigValue(Exception):
def __init__(self, message):
@ -153,7 +154,7 @@ class ConfigurationGetter(object):
self._args.host, port = self._args.host.split(':', 1)
self._args.port = int(port)
elif("port" not in self._args):
self._args.port = 8999
self._args.port = constants.DEFAULT_PORT
def setConfiguration(self, args):
self._args = args
@ -171,7 +172,7 @@ class ServerConfigurationGetter(ConfigurationGetter):
self._prepareArgParser()
self._args = self._parser.parse_args()
if(self._args.port == None):
self._args.port = 8999
self._args.port = constants.DEFAULT_PORT
return self._args
def _prepareArgParser(self):

View File

@ -1,5 +1,6 @@
import pygtk
import os
from syncplay import constants
pygtk.require('2.0')
import gtk
gtk.set_interactive(False)
@ -51,18 +52,7 @@ class GuiConfiguration:
def _tryToFillUpMpcPath(self):
if(self.args.player_path == None):
paths = ["C:\Program Files (x86)\MPC-HC\mpc-hc.exe",
"C:\Program Files\MPC-HC\mpc-hc.exe",
"C:\Program Files\MPC-HC\mpc-hc64.exe",
"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc.exe",
"C:\Program Files\Media Player Classic - Home Cinema\mpc-hc64.exe",
"C:\Program Files (x86)\Media Player Classic - Home Cinema\mpc-hc.exe",
"C:\Program Files (x86)\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
"C:\Program Files\K-Lite Codec Pack\Media Player Classic\mpc-hc.exe",
"C:\Program Files (x86)\Combined Community Codec Pack\MPC\mpc-hc.exe",
"C:\Program Files\MPC HomeCinema (x64)\mpc-hc64.exe",
]
for path in paths:
for path in constants.MPC_PATHS:
if(os.path.isfile(path)):
self.args.player_path = path
return

View File

@ -5,7 +5,7 @@ import syncplay
import os
import re
from syncplay import utils
from syncplay import constants
class ConsoleUI(threading.Thread):
def __init__(self):
self.promptMode = threading.Event()
@ -41,7 +41,7 @@ class ConsoleUI(threading.Thread):
if(noTimestamp):
print(message)
else:
print(time.strftime("[%X] ", time.localtime()) + message)
print(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message)
def showDebugMessage(self, message):
print(message)
@ -59,8 +59,8 @@ class ConsoleUI(threading.Thread):
return None
def _tryAdvancedCommands(self, data):
o = re.match(r"^(?:o|offset)\ ?(?P<sign>[/+-])?(?P<time>\d+(?:[^\d\.](?:\d+)){0,2}(?:\.(?:\d+))?)$", data)
s = re.match(r"^(?:s|seek)?\ ?(?P<sign>[+-])?(?P<time>\d+(?:[^\d\.](?:\d+)){0,2}(?:\.(?:\d+))?)$", data)
o = re.match(constants.UI_OFFSET_REGEX, data)
s = re.match(constants.UI_SEEK_REGEX, data)
if(o):
sign = self._extractSign(o.group('sign'))
t = utils.parseTime(o.group('time'))
@ -84,18 +84,18 @@ class ConsoleUI(threading.Thread):
return False
def _executeCommand(self, data):
command = re.match(r"^(?P<command>[^\ ]+)(?:\ (?P<parameter>.+))?", data)
command = re.match(constants.UI_COMMAND_REGEX, data)
if(not command):
return
if(command.group('command') in ["u", "undo", "revert"]):
if(command.group('command') in constants.COMMANDS_UNDO):
tmp_pos = self._syncplayClient.getPlayerPosition()
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
elif (command.group('command') in ["l", "list", "users"]):
elif (command.group('command') in constants.COMMANDS_LIST):
self._syncplayClient.getUserList()
elif (command.group('command') in ["p", "play", "pause"]):
elif (command.group('command') in constants.COMMANDS_PAUSE):
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
elif (command.group('command') in ["r", "room"]):
elif (command.group('command') in constants.COMMANDS_ROOM):
room = command.group('parameter')
if room == None:
if self._syncplayClient.userlist.currentUser.file:
@ -108,14 +108,14 @@ class ConsoleUI(threading.Thread):
else:
if(self._tryAdvancedCommands(data)):
return
if (command.group('command') not in ['help', 'h', '?', '/?', '\?']):
if (command.group('command') not in constants.COMMANDS_HELP):
self.showMessage("Unrecognized command")
self.showMessage("Available commands:", True)
self.showMessage("\tr [name] - change room", True)
self.showMessage("\tl - show user list", True)
self.showMessage("\tu - undo last seek", True)
self.showMessage("\tp - toggle pause", True)
self.showMessage("\t[s][+-][time] - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", True)
self.showMessage("\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", True)
self.showMessage("\th - this help", True)
self.showMessage("Syncplay version: {}".format(syncplay.version), True)
self.showMessage("More info available at: {}".format(syncplay.projectURL), True)

View File

@ -1,6 +1,7 @@
import time
import re
import datetime
from syncplay import constants
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
"""Retry calling the decorated function using an exponential backoff.
@ -44,7 +45,7 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
return deco_retry
def parseTime(timeStr):
regex = re.compile(r'(:?(?:(?P<hours>\d+?)[^\d\.])?(?:(?P<minutes>\d+?))?[^\d\.])?(?P<seconds>\d+?)(?:\.(?P<miliseconds>\d+?))?$')
regex = re.compile(constants.PARSE_TIME_REGEX)
parts = regex.match(timeStr)
if not parts:
return