class TwitCastingReader(StreamIO): def __init__(self, stream: "TwitCastingStream", timeout=None): super().__init__() self.session = stream.session self.stream = stream self.timeout = timeout or self.session.options.get("stream-timeout") buffer_size = self.session.get_option("ringbuffer-size") self.buffer = RingBuffer(buffer_size) self.wsclient = TwitCastingWsClient(self.buffer, stream.session, stream.url, origin="https://twitcasting.tv/") def open(self): self.wsclient.start() def close(self): self.wsclient.close() self.buffer.close() def read(self, size): return self.buffer.read(size, block=self.wsclient.is_alive(), timeout=self.timeout)
class SegmentedStreamReader(StreamIO): __worker__ = SegmentedStreamWorker __writer__ = SegmentedStreamWriter def __init__(self, stream, timeout=None): super().__init__() self.session = stream.session self.stream = stream self.timeout = timeout or self.session.options.get("stream-timeout") buffer_size = self.session.get_option("ringbuffer-size") self.buffer = RingBuffer(buffer_size) self.writer = self.__writer__(self) self.worker = self.__worker__(self) def open(self): self.writer.start() self.worker.start() def close(self): self.worker.close() self.writer.close() self.buffer.close() def read(self, size): return self.buffer.read( size, block=self.writer.is_alive(), timeout=self.timeout )
class TwitCastingReader(StreamIO): def __init__(self, stream, timeout=None, **kwargs): StreamIO.__init__(self) self.stream = stream self.session = stream.session self.timeout = timeout if timeout else self.session.options.get("stream-timeout") self.buffer = None if logger.root.level <= logger.DEBUG: websocket.enableTrace(True, log) def open(self): # Prepare buffer buffer_size = self.session.get_option("ringbuffer-size") self.buffer = RingBuffer(buffer_size) log.debug("Starting WebSocket client") self.client = TwitCastingWsClient( self.stream.url, buffer=self.buffer, proxy=self.session.get_option("http-proxy") ) self.client.setDaemon(True) self.client.start() def close(self): self.client.stop() self.buffer.close() def read(self, size): if not self.buffer: return b"" return self.buffer.read(size, block=(not self.client.stopped.wait(0)), timeout=self.timeout)
class TestRingBuffer(unittest.TestCase): BUFFER_SIZE = 8192*4 def setUp(self): self.buffer = RingBuffer(size=self.BUFFER_SIZE) def test_write(self): self.buffer.write(b"1" * 8192) self.buffer.write(b"2" * 4096) self.assertEqual(self.buffer.length, 8192 + 4096) def test_read(self): self.buffer.write(b"1" * 8192) self.buffer.write(b"2" * 4096) self.assertEqual(self.buffer.length, 8192 + 4096) self.assertEqual(self.buffer.read(4096), b"1" * 4096) self.assertEqual(self.buffer.read(4096), b"1" * 4096) self.assertEqual(self.buffer.read(), b"2" * 4096) self.assertEqual(self.buffer.length, 0) def test_read_timeout(self): self.assertRaises( IOError, self.buffer.read, timeout=0.1) def test_write_after_close(self): self.buffer.close() self.buffer.write(b"1" * 8192) self.assertEqual(self.buffer.length, 0) self.assertTrue(self.buffer.closed) def test_resize(self): self.assertEqual(self.buffer.buffer_size, self.BUFFER_SIZE) self.buffer.resize(self.BUFFER_SIZE*2) self.assertEqual(self.buffer.buffer_size, self.BUFFER_SIZE*2) def test_free(self): self.assertEqual(self.buffer.free, self.BUFFER_SIZE) self.buffer.write(b'1' * 100) self.assertEqual(self.buffer.free, self.BUFFER_SIZE-100)
class TestRingBuffer(unittest.TestCase): BUFFER_SIZE = 8192 * 4 def setUp(self): self.buffer = RingBuffer(size=self.BUFFER_SIZE) def test_write(self): self.buffer.write(b"1" * 8192) self.buffer.write(b"2" * 4096) self.assertEqual(self.buffer.length, 8192 + 4096) def test_read(self): self.buffer.write(b"1" * 8192) self.buffer.write(b"2" * 4096) self.assertEqual(self.buffer.length, 8192 + 4096) self.assertEqual(self.buffer.read(4096), b"1" * 4096) self.assertEqual(self.buffer.read(4096), b"1" * 4096) self.assertEqual(self.buffer.read(), b"2" * 4096) self.assertEqual(self.buffer.length, 0) def test_read_timeout(self): self.assertRaises( IOError, self.buffer.read, timeout=0.1) def test_write_after_close(self): self.buffer.close() self.buffer.write(b"1" * 8192) self.assertEqual(self.buffer.length, 0) self.assertTrue(self.buffer.closed) def test_resize(self): self.assertEqual(self.buffer.buffer_size, self.BUFFER_SIZE) self.buffer.resize(self.BUFFER_SIZE * 2) self.assertEqual(self.buffer.buffer_size, self.BUFFER_SIZE * 2) def test_free(self): self.assertEqual(self.buffer.free, self.BUFFER_SIZE) self.buffer.write(b'1' * 100) self.assertEqual(self.buffer.free, self.BUFFER_SIZE - 100)
class SegmentedStreamReader(StreamIO): __worker__ = SegmentedStreamWorker __writer__ = SegmentedStreamWriter def __init__(self, stream, timeout=None): StreamIO.__init__(self) self.session = stream.session self.stream = stream if not timeout: timeout = self.session.options.get("stream-timeout") self.timeout = timeout def open(self): buffer_size = self.session.get_option("ringbuffer-size") self.buffer = RingBuffer(buffer_size) self.writer = self.__writer__(self) self.worker = self.__worker__(self) self.writer.start() self.worker.start() def close(self): self.worker.close() self.writer.close() self.buffer.close() current = current_thread() if current is not self.worker: # pragma: no branch self.worker.join(timeout=self.timeout) if current is not self.writer: # pragma: no branch self.writer.join(timeout=self.timeout) def read(self, size): if not self.buffer: return b"" return self.buffer.read(size, block=self.writer.is_alive(), timeout=self.timeout)
class FLVTagConcatIO(IOBase): __worker__ = FLVTagConcatWorker def __init__(self, session, duration=None, tags=[], skip_header=None, timeout=30, **concater_params): self.session = session self.timeout = timeout self.concater_params = concater_params self.duration = duration self.skip_header = skip_header self.tags = tags def open(self, iterator): self.buffer = RingBuffer(self.session.get_option("ringbuffer-size")) self.worker = self.__worker__(iterator, self) self.worker.start() def close(self): self.worker.stop() if self.worker.is_alive(): self.worker.join() def read(self, size=-1): if not self.buffer: return b"" if self.worker.error: raise self.worker.error return self.buffer.read(size, block=self.worker.is_alive(), timeout=self.timeout)
class StreamIOThreadWrapper(io.IOBase): """Wraps a file-like object in a thread. Useful for getting control over read timeout where timeout handling is missing or out of our control. """ class Filler(Thread): def __init__(self, fd, buffer, chunk_size): Thread.__init__(self) self.error = None self.fd = fd self.buffer = buffer self.chunk_size = chunk_size self.daemon = True self.running = False def run(self): self.running = True while self.running: try: data = self.fd.read(self.chunk_size) except OSError as error: self.error = error break if len(data) == 0: break self.buffer.write(data) self.stop() def stop(self): self.running = False self.buffer.close() if hasattr(self.fd, "close"): try: self.fd.close() except Exception: pass def __init__(self, session, fd, timeout=30, chunk_size=8192): self.buffer = RingBuffer(session.get_option("ringbuffer-size")) self.fd = fd self.timeout = timeout self.chunk_size = chunk_size self.filler = StreamIOThreadWrapper.Filler(self.fd, self.buffer, self.chunk_size) self.filler.start() def read(self, size=-1): if self.filler.error and self.buffer.length == 0: raise self.filler.error return self.buffer.read(size, block=self.filler.is_alive(), timeout=self.timeout) def close(self): self.filler.stop() if self.filler.is_alive(): self.filler.join()