Fixed race conditions between clients on high latency connections

This works, but code craves for a huge cleanup and refactoring.
This commit is contained in:
Tomasz Fluxid Kowalczyk 2012-02-18 22:08:38 +01:00
parent d0e23f9b0e
commit 1705647ddd
4 changed files with 39 additions and 25 deletions

View File

@ -9,7 +9,10 @@ from .network_utils import (
arg_count, arg_count,
CommandProtocol, CommandProtocol,
) )
from .utils import parse_state from .utils import (
format_time,
parse_state,
)
class SyncClientProtocol(CommandProtocol): class SyncClientProtocol(CommandProtocol):
@ -156,6 +159,7 @@ class Manager(object):
self.global_noted_pause_change = None self.global_noted_pause_change = None
self.last_global_update = None self.last_global_update = None
self.counter = 0 self.counter = 0
self.counter_recv = 0
self.player = None self.player = None
self.ask_delayed = None self.ask_delayed = None
@ -249,8 +253,11 @@ class Manager(object):
else: else:
self.send_delayed = reactor.callLater(when, self.send_status) self.send_delayed = reactor.callLater(when, self.send_status)
def send_status(self): def send_status(self, force = False):
if not self.running: if not (self.running and self.protocol):
return
self.schedule_send_status()
if self.counter > self.counter_recv and not force:
return return
self.counter += 1 self.counter += 1
curtime = time.time() curtime = time.time()
@ -259,12 +266,12 @@ class Manager(object):
position += curtime - self.last_player_update position += curtime - self.last_player_update
if self.protocol: if self.protocol:
self.protocol.send_state(self.counter, curtime, self.player_paused, self.player_position) self.protocol.send_state(self.counter, curtime, self.player_paused, self.player_position)
self.schedule_send_status()
def send_seek(self): def send_seek(self):
if not (self.running and self.protocol): if not (self.running and self.protocol):
return return
self.counter += 1 self.counter += 1
self.protocol.send_seek(self.counter, time.time(), self.player_position) self.protocol.send_seek(self.counter, time.time(), self.player_position)
def send_filename(self): def send_filename(self):
@ -285,7 +292,7 @@ class Manager(object):
if old_paused and not paused: if old_paused and not paused:
self.player_paused_at = None self.player_paused_at = None
if old_paused != paused and self.global_paused != paused: if old_paused != paused and self.global_paused != paused:
self.send_status() self.send_status(True)
if not (self.global_paused or self.seek_sent_wait): if not (self.global_paused or self.seek_sent_wait):
diff = position - self.get_global_position() diff = position - self.get_global_position()
@ -301,6 +308,7 @@ class Manager(object):
self.player_speed_fix = False self.player_speed_fix = False
if abs(diff) > 8: if abs(diff) > 8:
self.send_seek() self.send_seek()
self.seek_sent_wait = True
if not paused and self.player_paused_at is not None and position >= self.player_paused_at: if not paused and self.player_paused_at is not None and position >= self.player_paused_at:
#print 'Pausing %0.2fs after pause point' % (position - self.player_paused_at) #print 'Pausing %0.2fs after pause point' % (position - self.player_paused_at)
@ -312,27 +320,35 @@ class Manager(object):
self.send_filename() self.send_filename()
def update_global_state(self, counter, ctime, paused, position, name): def update_global_state(self, counter, ctime, paused, position, name):
self.counter_recv = max(self.counter_recv, counter)
counter_valid = self.counter and counter >= self.counter
curtime = time.time() curtime = time.time()
updated_before = bool(self.last_global_update)
if updated_before and not counter_valid:
return
if not paused: if not paused:
position += curtime - ctime position += curtime - ctime
self.global_paused = paused self.global_paused = paused
self.global_position = position self.global_position = position
self.global_who_changed = name self.global_who_changed = name
updated_before = bool(self.last_global_update)
self.last_global_update = curtime self.last_global_update = curtime
if not self.player: if not self.player:
return return
changed = False changed = False
self.seek_sent_wait = False
if not updated_before: if not updated_before:
self.player.set_position(position) self.player.set_position(position)
self.player.set_paused(paused) self.player.set_paused(paused)
changed = True changed = True
elif not (self.counter and counter < self.counter):
if counter_valid:
diff = self.get_player_position() - position diff = self.get_player_position() - position
if self.last_player_update is not None:
diff += curtime - self.last_player_update
if abs(diff) > 4: if abs(diff) > 4:
self.player.set_position(position) self.player.set_position(position)
changed = True changed = True
@ -349,27 +365,20 @@ class Manager(object):
if diff < 0: if diff < 0:
self.player.set_paused(True) self.player.set_paused(True)
changed = True changed = True
#else: self.global_noted_pause_change = paused
# print 'Not pausing now'
self.seek_sent_wait = False
self.global_noted_pause_change = paused
if changed: if changed:
self.ask_player() self.ask_player()
def seek(self, ctime, position, who): def seek(self, ctime, position, who):
position += time.time() - ctime curtime = time.time()
position += curtime - ctime
self.global_position = position self.global_position = position
self.last_global_update = curtime
if self.player: if self.player:
self.player.set_position(position) self.player.set_position(position)
self.ask_player() self.ask_player()
position = int(position*1000) print who, 'seeked to', format_time(position)
seconds, mseconds = divmod(position, 1000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
print '%s seeked to %02d:%02d:%02d.%03d' % (
who, hours, minutes, seconds, mseconds
)

View File

@ -1,12 +1,11 @@
#coding:utf8 #coding:utf8
from functools import wraps
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO from StringIO import StringIO
from functools import wraps
from twisted.internet.defer import succeed from twisted.internet.defer import succeed
from twisted.internet.protocol import ( from twisted.internet.protocol import (
ProcessProtocol, ProcessProtocol,
@ -22,6 +21,7 @@ from .utils import (
split_args, split_args,
) )
def arg_count(minimum, maximum=None): def arg_count(minimum, maximum=None):
def decorator(f): def decorator(f):
@wraps(f) @wraps(f)

View File

@ -212,7 +212,6 @@ class SyncFactory(Factory):
self.pause_change_by = None self.pause_change_by = None
if not self.watchers: if not self.watchers:
self.paused = True self.paused = True
# send info someone quit
def update_state(self, watcher_proto, counter, ctime, paused, position): def update_state(self, watcher_proto, counter, ctime, paused, position):
watcher = self.watchers.get(watcher_proto) watcher = self.watchers.get(watcher_proto)

View File

@ -44,7 +44,6 @@ def split_args(args):
def join_args(args): def join_args(args):
return ' '.join(quote_arg(arg) for arg in args) return ' '.join(quote_arg(arg) for arg in args)
def parse_state(args): def parse_state(args):
if len(args) == 4: if len(args) == 4:
counter, ctime, state, position = args counter, ctime, state, position = args
@ -79,3 +78,10 @@ def find_exec_path(name):
if os.access(path, os.X_OK): if os.access(path, os.X_OK):
return path return path
def format_time(value):
value = int(value*100)
seconds, mseconds = divmod(value, 100)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return '%02d:%02d:%02d.%02d' % (hours, minutes, seconds, mseconds)