class Stream(Emitter): chunk_size = 10240 # 10kB def __init__(self, track, num, r_range): """ :type track: plugin.track.Track :type num: int :type r_range: plugin.range.Range """ self.track = track self.server = track.server self.stream_num = num self.range = r_range # HTTP request/response self.request = None self.response = None self.headers = None # Content info self.content_range = None self.content_length = None self.total_length = None # Data buffering self.read_thread = None self.read_sleep = None self.buffer = bytearray() self.on_open = REvent() self.on_reading = Event() self.state = '' self.request_lock = Lock() self.request_seq = 0 def log(self, message, *args, **kwargs): header = '[%s] [%s] ' % (self.track.uri, self.stream_num) log.info(header + str(message), *args, **kwargs) def prepare(self): headers = {} if self.range: headers['Range'] = str(self.range) return headers def open(self): if self.state != '': return self.state = 'opening' self.emit('opening') future = self.server.session.get(self.track.info['uri'], headers=self.prepare(), stream=True) future.add_done_callback(self.callback) def callback(self, future): ex = future.exception() if ex: log.warn('Request failed: %s', ex) self.on_open.set(False) return self.response = future.result() self.headers = self.response.headers self.state = 'opened' self.emit('opened') self.content_length = int(self.headers.get('Content-Length')) self.log('Content-Length: %s', self.content_length) self.content_range = ContentRange.parse( self.headers.get('Content-Range')) self.log('Content-Range: %s', self.content_range) if self.content_range: self.total_length = self.content_range.length else: # Build dummy ContentRange self.content_range = ContentRange(start=0, end=self.content_length - 1, length=self.content_length) self.total_length = self.content_length self.log('Total-Length: %s', self.total_length) if self.headers.get('Content-Type') == 'text/xml': # Error, log response self.log(self.response.content) self.on_open.set(False) return # Read back entire stream self.on_reading.set() self.read_thread = Thread(target=func_catch, args=(self.run, )) self.read_thread.start() def run(self): self.state = 'reading' self.emit('reading') self.on_open.set(True) last_progress = None for chunk in self.response.iter_content(self.chunk_size): self.buffer.extend(chunk) self.emit('received', len(chunk), __suppress=True) log.log(logging.TRACE, '[%s] [%s] Received chunk - len(chunk): %s', self.track.uri, self.stream_num, len(chunk)) last_progress = log_progress(self, '[%s] Reading' % self.stream_num, len(self.buffer), last_progress) self.on_reading.wait() self.state = 'buffered' self.emit('buffered') def iter(self, c_range): """ :type c_range: plugin.range.ContentRange """ with self.request_lock: num = self.request_seq self.request_seq += 1 position = 0 end = self.content_range.end + 1 last_progress = None ev_received = Event() @self.on(['received', 'buffered']) def on_received(*args): ev_received.set() if c_range: position = c_range.start - self.content_range.start end = (c_range.end - self.content_range.start) + 1 log.info( '[%s] [%s:%s] Streaming - c_range: %s, position: %s, end: %s', self.track.uri, self.stream_num, num, c_range, position, end) while position < self.content_length: chunk_size = end - position # Clamp to maximum `chunk_size` if chunk_size > self.chunk_size: chunk_size = self.chunk_size # Check if range has reached the end if not chunk_size: log.debug('[%s] [%s:%s] Range complete', self.track.uri, self.stream_num, num) break chunk = self.buffer[position:position + chunk_size] if chunk: log.log(logging.TRACE, '[%s] [%s:%s] Sending chunk - len(buffer[%s:%s]): %s', self.track.uri, self.stream_num, num, position, position + chunk_size, len(chunk)) last_progress = log_progress(self, '[%s:%s] Streaming' % (self.stream_num, num), position, last_progress, length=end) position += len(chunk) yield str(chunk) elif self.state != 'buffered': log.log(logging.TRACE, '[%s] [%s:%s] Waiting for buffer', self.track.uri, self.stream_num, num) ev_received.clear() ev_received.wait() else: log.debug('[%s] [%s:%s] Buffer doesn\'t contain range [%s:%s]', self.track.uri, self.stream_num, num, position, position + chunk_size) break self.off('received', on_received)\ .off('buffered', on_received) log.info('[%s] [%s:%s] Complete', self.track.uri, self.stream_num, num)
def plugin_callback(method, kwargs=None, async=False): """ Invokes callbacks on the plugin instance :param method: The method on the SpotifyPlugin class to call. :param kwargs: A dictionary of keyward args to pass to the method. """ Log.Debug('plugin_callback - method: %s, kwargs: %s, async: %s' % (method, kwargs, async)) kwargs = kwargs or {} result = None if async: on_complete = REvent() kwargs['callback'] = lambda result: on_complete.set(result) method(host, **kwargs) result = on_complete.wait(30) else: result = method(host, **kwargs) if result is None: if async: return MessageContainer( header=L("MSG_CALLBACK_TIMEOUT_TITLE"), message=L("MSG_CALLBACK_TIMEOUT_BODY") ) return MessageContainer(
class Stream(Emitter): chunk_size = 10240 # 10kB def __init__(self, track, num, r_range): """ :type track: plugin.track.Track :type num: int :type r_range: plugin.range.Range """ self.track = track self.server = track.server self.stream_num = num self.range = r_range # HTTP request/response self.request = None self.response = None self.headers = None # Content info self.content_range = None self.content_length = None self.total_length = None # Data buffering self.read_thread = None self.read_sleep = None self.buffer = bytearray() self.on_open = REvent() self.on_reading = Event() self.state = '' self.request_lock = Lock() self.request_seq = 0 def log(self, message, *args, **kwargs): header = '[%s] [%s] ' % (self.track.uri, self.stream_num) log.info(header + str(message), *args, **kwargs) def prepare(self): headers = {} if self.range: headers['Range'] = str(self.range) return headers def open(self): if self.state != '': return self.state = 'opening' self.emit('opening') future = self.server.session.get( self.track.info['uri'], headers=self.prepare(), stream=True ) future.add_done_callback(self.callback) def callback(self, future): ex = future.exception() if ex: log.warn('Request failed: %s', ex) self.on_open.set(False) return self.response = future.result() self.headers = self.response.headers self.state = 'opened' self.emit('opened') self.content_length = int(self.headers.get('Content-Length')) self.log('Content-Length: %s', self.content_length) self.content_range = ContentRange.parse(self.headers.get('Content-Range')) self.log('Content-Range: %s', self.content_range) if self.content_range: self.total_length = self.content_range.length else: # Build dummy ContentRange self.content_range = ContentRange( start=0, end=self.content_length - 1, length=self.content_length ) self.total_length = self.content_length self.log('Total-Length: %s', self.total_length) if self.headers.get('Content-Type') == 'text/xml': # Error, log response self.log(self.response.content) self.on_open.set(False) return # Read back entire stream self.on_reading.set() self.read_thread = Thread(target=func_catch, args=(self.run,)) self.read_thread.start() def run(self): self.state = 'reading' self.emit('reading') self.on_open.set(True) last_progress = None for chunk in self.response.iter_content(self.chunk_size): self.buffer.extend(chunk) self.emit('received', len(chunk), __suppress=True) log.log( logging.TRACE, '[%s] [%s] Received chunk - len(chunk): %s', self.track.uri, self.stream_num, len(chunk) ) last_progress = log_progress(self, '[%s] Reading' % self.stream_num, len(self.buffer), last_progress) self.on_reading.wait() self.state = 'buffered' self.emit('buffered') def iter(self, c_range): """ :type c_range: plugin.range.ContentRange """ with self.request_lock: num = self.request_seq self.request_seq += 1 position = 0 end = self.content_range.end + 1 last_progress = None ev_received = Event() @self.on(['received', 'buffered']) def on_received(*args): ev_received.set() if c_range: position = c_range.start - self.content_range.start end = (c_range.end - self.content_range.start) + 1 log.info( '[%s] [%s:%s] Streaming - c_range: %s, position: %s, end: %s', self.track.uri, self.stream_num, num, c_range, position, end ) while position < self.content_length: chunk_size = end - position # Clamp to maximum `chunk_size` if chunk_size > self.chunk_size: chunk_size = self.chunk_size # Check if range has reached the end if not chunk_size: log.debug( '[%s] [%s:%s] Range complete', self.track.uri, self.stream_num, num ) break chunk = self.buffer[position:position + chunk_size] if chunk: log.log( logging.TRACE, '[%s] [%s:%s] Sending chunk - len(buffer[%s:%s]): %s', self.track.uri, self.stream_num, num, position, position + chunk_size, len(chunk) ) last_progress = log_progress( self, '[%s:%s] Streaming' % (self.stream_num, num), position, last_progress, length=end ) position += len(chunk) yield str(chunk) elif self.state != 'buffered': log.log( logging.TRACE, '[%s] [%s:%s] Waiting for buffer', self.track.uri, self.stream_num, num ) ev_received.clear() ev_received.wait() else: log.debug( '[%s] [%s:%s] Buffer doesn\'t contain range [%s:%s]', self.track.uri, self.stream_num, num, position, position + chunk_size ) break self.off('received', on_received)\ .off('buffered', on_received) log.info( '[%s] [%s:%s] Complete', self.track.uri, self.stream_num, num )