diff --git a/syncplay/client.py b/syncplay/client.py new file mode 100644 index 0000000..6a1a5e3 --- /dev/null +++ b/syncplay/client.py @@ -0,0 +1,39 @@ +#coding:utf8 + +import time + +from twisted.internet.protocol import Factory + +from .network_utils import CommandProtocol +from .utils import parse_state + + +class SyncClientProtocol(CommandProtocol): + def __init__(self, factory): + CommandProtocol.__init__(self) + + self.factory = factory + + def connectionMade(self): + self.send_message('iam', self.factory.name) + + def handle_connected_state(self, arg): + arg = parse_state(arg) + if not arg: + self.drop_with_error('Malformed state attributes') + return + + paused, position, name = arg + + self.factory.update_state(self, paused, position, name) + + + states = dict( + connected = dict( + state = 'handle_connected_state', + seek = 'handle_connected_seek', + #ping = 'handle_connected_ping', + ), + ) + initial_state = 'connected' + diff --git a/syncplay/network_utils.py b/syncplay/network_utils.py index 98eae3d..259d6b3 100644 --- a/syncplay/network_utils.py +++ b/syncplay/network_utils.py @@ -1,5 +1,10 @@ #coding:utf8 +import re + +RE_NL_SPLIT = re.compile(r'(?:\r\n|\n|\r)') + +from twisted.internet.protocol import ProcessProtocol from twisted.protocols.basic import LineReceiver class CommandProtocol(LineReceiver): @@ -47,3 +52,28 @@ class CommandProtocol(LineReceiver): self.send_message('error', error) self.drop() + +def parse_lines(leftovers, data): + data = leftovers+data + lines = RE_NL.split(data) + leftovers = lines.pop(-1) + return leftovers, lines + +class LineProcessProtocol(ProcessProtocol) + __leftover_out = '' + __leftover_err = '' + + def outReceived(self, data): + self.__leftover_out, lines = parse_lines(__leftover_out, data) + for line in lines: + self.outLineReceived(line) + + def errReceived(self, data): + self.__leftover_err, lines = parse_lines(__leftover_err, data) + for line in lines: + self.errLineReceived(line) + + def writeLines(self, *lines): + for line in lines: + self.transport.write(line+'\n') + diff --git a/syncplay/players/__init__.py b/syncplay/players/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/syncplay/players/mplayer.py b/syncplay/players/mplayer.py new file mode 100644 index 0000000..72348cd --- /dev/null +++ b/syncplay/players/mplayer.py @@ -0,0 +1,58 @@ +#coding:utf8 + +import re + +from twisted.internet import reactor + +from ..network_utils import network_utils + +RE_ANSWER = re.compile('^ANS_([a-zA-Z_])=(.+)$') + + +class MplayerProtocol(ProcessProtocol): + def outLineReceived(self, line): + if not line.starts_with('ANS_'): + return + m = RE_ANSWER.match(line) + if not m: + return + + name, value = m.group(1).lower, m.group(2) + handler = getattr(self, 'answer_' + name, None) + if handler: + handler(value) + + + def send_set_paused(self, value): + # docs say i can't set "pause" property, but it works... + self.set_property('paused', 'yes' if value else 'no') + + def send_get_paused(self): + self.get_property('paused') + + def answer_pause(self, value): + value = value == 'yes' + + + def send_set_position(self, value): + self.set_property('time_pos', '%0.2f'%value) + + def send_get_position(self): + self.get_property('time_pos') + + def answer_time_pos(self, value): + value = float(value) + + + def send_set_speed(self, value): + self.set_property('speed', '%0.2f'%value) + + def send_get_speed(self): + self.get_property('speed') + + def answer_speed(self, value): + value = float(value) + + +def run_mplayer(manager, mplayer_path, args): + pass diff --git a/syncplay/server.py b/syncplay/server.py index 6ca50fd..b45bacc 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -5,6 +5,8 @@ import time from twisted.internet.protocol import Factory from .network_utils import CommandProtocol +from .utils import parse_state + class SyncServerProtocol(CommandProtocol): def __init__(self, factory): @@ -20,24 +22,12 @@ class SyncServerProtocol(CommandProtocol): self.change_state('connected') def handle_connected_state(self, arg): - arg = arg.split(None, 1) - if len(arg) != 2: + arg = parse_state(arg) + if not arg: self.drop_with_error('Malformed state attributes') return - state, position = arg - if not state in ('paused', 'playing'): - self.drop_with_error('Unknown state') - return - - paused = state == 'paused' - - try: - position = int(position) - except ValueError: - self.drop_with_error('Invalid position numeral') - - position /= 100.0 + paused, position, _ = arg self.factory.update_state(self, paused, position) diff --git a/syncplay/utils.py b/syncplay/utils.py new file mode 100644 index 0000000..3563aa9 --- /dev/null +++ b/syncplay/utils.py @@ -0,0 +1,31 @@ +#coding:utf8 + +def split_args(args, number): + # FIXME Make argument format smarter + return args.split(None, number-1) + +def parse_state(args): + args = split_args(args, 3) + l = len(args) + if l == 2: + state, position = args + who_changed_state = None + elif l == 3: + state, position, who_changed_state = args + else: + return + + if not state in ('paused', 'playing'): + return + + paused = state == 'paused' + + try: + position = int(position) + except ValueError: + return + + position /= 100.0 + + return paused, position, who_changed_state +