def preroll_done(self): if self.rewrite: gps = [b[-1][0] for b in self.bufs.values() if b] if gps: self.base_gp = max(gps) log.debug('preroll_done: buf lens: %r, rewr: %r (%r), mark: %r', [(t, len(b)) for (t, b) in self.bufs.items()], self.rewrite, self.base_gp, self.mark) for type, mark in ((chunks.MSG_VIDEO, self.mark), (chunks.MSG_AUDIO, False)): frames = self.bufs.get(type) if mark: # TODO: use correct codec id for the info markers, # not always "7" self.nstream.send(0, t, vb('\x57\x00')) if frames: if self.rewrite: self._send_many_zero_gp(type, frames) else: self._send_many(type, frames) frames.clear() self.nstream.send(0, t, vb('\x57\x01')) elif frames: if self.rewrite: self._send_many_zero_gp(type, frames) else: self._send_many(type, frames) frames.clear() self.prerolling = False log.debug('preroll_done: done.')
def _connect_succeeded(self, result): self._connected = True sm = self.muxer.sendMessage sm(0, chunks.PROTO_WINDOW_SIZE, 0, vb('002625a0'.decode('hex'))) sm(0, chunks.PROTO_SET_BANDWIDTH, 0, vb('002625a002'.decode('hex'))) sm(0, chunks.PROTO_USER_CONTROL, 0, vb('000000000000'.decode('hex'))) return result
def _init_connect(self): # why change chunk size? :/ sm = self.muxer.sendMessage sm(0, chunks.PROTO_SET_CHUNK_SIZE, 0, vb('00000400'.decode('hex'))) self.muxer.set_chunk_size(0x0400) app_path = self.factory.get_connect_app_path() app_url = self.factory.get_connect_url() self._app = self.factory.make_app(self) def _connected(info): log.debug('_connected: %r', info) self._app.makeConnection(info) def _translate_failure(failure): failure.trap(CommandResultError) # for the moment we know we tried to connect, hence ConnectError # TODO: add a more fine-grained translation(?) return Failure(ClientConnectError(*failure.value.args)) params = self.make_connect_params(app=app_path, # flashVer='LNX 10,0,22,87', flashVer=CLIENT_VERSION, tcUrl=app_url, objectEncoding=0) extra_params = self._app.get_connect_extra() log.debug('invoking connect(%r, extra=%r)' % (params, extra_params)) d = self.callRemote(0, 'connect', params, *extra_params) d.addErrback(_translate_failure) d.addCallbacks(_connected, self._connect_call_failed) d.addErrback(self._failed_disconnect)
def muxer_messages(mux): return [ (m[0], m[1], m[2], (decode_amf(vb(m[3])) if m[1] in (chunks.MSG_COMMAND, chunks.MSG_DATA) else m[3]), m[4]) for m in mux.messages ]
def muxer_messages(mux): return [(m[0], m[1], m[2], (decode_amf(vb(m[3])) if m[1] in (chunks.MSG_COMMAND, chunks.MSG_DATA) else m[3]), m[4]) for m in mux.messages]
def _init_connect(self): # why change chunk size? :/ sm = self.muxer.sendMessage sm(0, chunks.PROTO_SET_CHUNK_SIZE, 0, vb('00000400'.decode('hex'))) self.muxer.set_chunk_size(0x0400) app_path = self.factory.get_connect_app_path() app_url = self.factory.get_connect_url() self._app = self.factory.make_app(self) def _connected(info): log.debug('_connected: %r', info) self._app.makeConnection(info) def _translate_failure(failure): failure.trap(CommandResultError) # for the moment we know we tried to connect, hence ConnectError # TODO: add a more fine-grained translation(?) return Failure(ClientConnectError(*failure.value.args)) params = self.make_connect_params(app=app_path, # flashVer='LNX 10,0,22,87', flashVer=CLIENT_VERSION, tcUrl=app_url, objectEncoding=0) log.debug('invoking connect(%r)', params) d = self.callRemote(0, 'connect', params, {}) d.addErrback(_translate_failure) d.addCallbacks(_connected, self._connect_call_failed) d.addErrback(self._failed_disconnect)
def test_callRemote_disconnect(self): p, t, dmx, mux = self.build_proto() d = p.callRemote(1, 'echo', 'sing it back') self.assertEquals(len(mux.messages), 1) msg = mux.messages[0] self.assertEquals(msg[0:3] + msg[4:], (0, chunks.MSG_COMMAND, 1, False)) # can't rely on transaction number, so we skip it decoded = decode_amf(vb(msg[3])) self.assertEquals((decoded[0], decoded[2]), ('echo', 'sing it back')) p.connectionLost() self.assertFailure(d, error.ConnectionDone) return d
def test_callRemote_result(self): p, t, dmx, mux = self.build_proto() d = p.callRemote(1, 'echo', 'sing it back') self.assertEquals(len(mux.messages), 1) msg = mux.messages[0] self.assertEquals(msg[0:3] + msg[4:], (0, chunks.MSG_COMMAND, 1, False)) # can't rely on transaction number, so we skip it decoded = decode_amf(vb(msg[3])) self.assertEquals((decoded[0], decoded[2]), ('echo', 'sing it back')) dmx.inject(3, 0, const.RTMP_COMMAND, 1, encode_amf('_result', decoded[1], None, decoded[2])) d.addCallback(self.assertEquals, (None, 'sing it back')) return d
def test_callRemote_error(self): p, t, dmx, mux = self.build_proto() d = p.callRemote(1, 'echo', 'sing it back') self.assertEquals(len(mux.messages), 1) msg = mux.messages[0] self.assertEquals(msg[0:3] + msg[4:], (0, chunks.MSG_COMMAND, 1, False)) # can't rely on transaction number, so we skip it decoded = decode_amf(vb(msg[3])) self.assertEquals((decoded[0], decoded[2]), ('echo', 'sing it back')) err_info = Object(code='Failed', level='error', desc='no echo method') dmx.inject(3, 0, const.RTMP_COMMAND, 1, encode_amf('_error', decoded[1], None, err_info)) self.assertFailure(d, CommandResultError) d.addCallback(lambda r: self.assertEquals(r.args, (None, err_info))) return d
def doUserControlPing(self, header, peer_time): sm = self.protocol.muxer.sendMessage sm(0, chunks.PROTO_USER_CONTROL, 0, vb(_s_uctrl_single.pack(const.UCTRL_PONG, peer_time)))
def on_data(self, ts, type_, data): stream = self._tracks.get(type_, None) if not stream: d = self._make_stream(ts, type_) if not d: return # FIXME: there should really be a queue between nstream and sg # instead, for the moment, we'll just re-try the on_data() d.addCallback(lambda _: self.on_data(ts, type_, data)) return flags = 0 if type_ == chunks.MSG_VIDEO: bytes = len(data) frame_type, codec_id, h264_type = None, None, None if bytes > 1: ft_codec, h264_type = _s_double_uchar.unpack(data.peek(2)) frame_type, codec_id = ft_codec >> 4, ft_codec & 0x0f elif bytes > 0: ft_codec, = _s_uchar.unpack(data.peek(1)) frame_type, codec_id = ft_codec >> 4, ft_codec & 0x0f if frame_type == 1 and codec_id == 7 and h264_type == 0: d = stream.write_headers(data) return elif frame_type is not None: if frame_type == 1: flags = FF_KEYFRAME else: # FIXME: this is not necessarily correct flags = FF_INTERFRAME elif type_ == chunks.MSG_AUDIO: bytes = len(data) codec_id, aac_type = None, None if bytes > 1: ft_codec, aac_type = _s_double_uchar.unpack(data.peek(2)) codec_id = ft_codec >> 4 elif bytes > 0: ft_codec, = _s_uchar.unpack(data.peek(1)) codec_id = ft_codec >> 4 if codec_id == 10 and aac_type == 0: self._audio_headers += 1 d = stream.write_headers(data) return elif codec_id != 10 and self._audio_headers == 0: # Flash, doesn't use real headers for those formats, # but let's mark that there is an audio track with an # empty packet (Flash doesn't seem to mind those) # early on self._audio_headers += 1 d = stream.write_headers(vb(data.peek(1))) if codec_id is not None: flags = FF_KEYFRAME # audio usually is all keyframes else: log.error('Unsupported data type: %r', type_) return d = stream.write(ts, flags, data)
def sinject(self, cs_id, time, type_, ms_id, data): body = vb(data) self.inject(cs_id, time, type_, ms_id, body)
def check_send_ack(self): if self._next_ack < self.bytes_read: self.set_next_ack() self.muxer.sendMessage(0, chunks.PROTO_ACK, 0, vb(_s_ulong.pack(self.bytes_read)))
def set_chunk_size(self, new_size): sm = self.protocol.muxer.sendMessage sm(0, chunks.PROTO_SET_CHUNK_SIZE, 0, vb(_s_ulong.pack(new_size))) self.protocol.muxer.set_chunk_size(new_size)
def ctrlStreamBegin(self): sm = self.protocol.muxer.sendMessage return sm(0, chunks.PROTO_USER_CONTROL, 0, vb(_s_uc_single.pack(const.UCTRL_STREAM_BEGIN, self.id)))
def ctrlStreamRecorded(self): sm = self.protocol.muxer.sendMessage return sm(0, chunks.PROTO_USER_CONTROL, 0, vb(_s_uc_single.pack(const.UCTRL_STREAM_RECORDED, self.id)))