ということで、以前作ったニコニコ動画のダウンロードツールを改良し、RTMPで配信されている動画をダウンロードするときにも進捗度合いがわかるようにしてみた。
まず、ニコニコ動画のダウンロードツールをRubyで書いた話は、以下から。
そして、これを改善するために、Ruby-FFIについて調べた話は、以下から。
最後に、rtmpdumpの実装について調べた話は、以下から。
- rtmpdumpのコードを読んでみた。(その1) - いものやま。
- rtmpdumpのコードを読んでみた。(その2) - いものやま。
- rtmpdumpのコードを読んでみた。(その3) - いものやま。
今回の目指すところ
まず、今回の目指すところ(スコープ)は、以下。
- ツールの使い方の変更はしない(=オプションを増やしたりはしない)
- HTTPで配信されている動画は、これまで通りダウンロードでき、進捗度合いも表示される
- rtmpdumpを使うのでなく、librtmpをRuby-FFI経由で使うようにする
- RTMPで配信されている動画は、これまで通りダウンロードでき、さらに進捗度合いも表示されるようにする
さっそく仕事に取り掛かる。
librtmpの結びつけ
librtmpを使えるようにするために、LibRTMPというモジュールを用意し、ここでライブラリとの結びつけを行う。
なお、結びつける構造体や関数は、ヘッダファイルに書かれているもの全部ではなく、実際に使うものだけにした。
コードは以下。(無駄に長い・・・)
require 'ffi' require 'uri' # attach 'librtmp' interface module NicovideoDL module LibRTMP extend FFI::Library ffi_lib 'librtmp' ### log.h ### # typedef enum # { RTMP_LOGCRIT=0, RTMP_LOGERROR, RTMP_LOGWARNING, RTMP_LOGINFO, # RTMP_LOGDEBUG, RTMP_LOGDEBUG2, RTMP_LOGALL # } RTMP_LogLevel; # extern RTMP_LogLevel RTMP_debuglevel; RTMP_LogLevel = enum [ :RTMP_LOGCRIT, 0, :RTMP_LOGERROR, :RTMP_LOGWARNING, :RTMP_LOGINFO, :RTMP_LOGDEBUG, :RTMP_LOGDEBUG2, :RTMP_LOGALL ] attach_variable :RTMP_debuglevel, RTMP_LogLevel # set debug level to 'quiet' LibRTMP.RTMP_debuglevel = RTMP_LogLevel[:RTMP_LOGCRIT] ### amf.h ### # typedef enum # { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT, # AMF_MOVIECLIP, /* reserved, not used */ # AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END, # AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED, # AMF_RECORDSET, /* reserved, not used */ # AMF_XML_DOC, AMF_TYPED_OBJECT, # AMF_AVMPLUS, /* switch to AMF3 */ # AMF_INVALID = 0xff # } AMFDataType; AMFDataType = enum [ :AMF_NUMBER, 0, :AMF_BOOLEAN, :AMF_STRING, :AMF_OBJECT, :AMF_MOVIECLIP, :AMF_NULL, :AMF_UNDEFINED, :AMF_REFERENCE, :AMF_ECMA_ARRAY, :AMF_OBJECT_END, :AMF_STRICT_ARRAY, :AMF_DATE, :AMF_LONG_STRING, :AMF_UNSUPPORTED, :AMF_RECORDSET, :AMF_XML_DOC, :AMF_TYPED_OBJECT, :AMF_AVMPLUS, :AMF_INVALID, 0xff ] # typedef struct AVal # { # char *av_val; # int av_len; # } AVal; class AVal < FFI::Struct layout( :av_val, :pointer, :av_len, :int) end # struct AMFObjectProperty; class AMFObjectProperty < FFI::Struct; end # typedef struct AMFObject # { # int o_num; # struct AMFObjectProperty *o_props; # } AMFObject; class AMFObject < FFI::Struct layout( :o_num, :int, :o_props, AMFObjectProperty.ptr) end # typedef struct AMFObjectProperty # { # AVal p_name; # AMFDataType p_type; # union # { # double p_number; # AVal p_aval; # AMFObject p_object; # } p_vu; # int16_t p_UTCoffset; # } AMFObjectProperty; class AMFObjectProperty < FFI::Struct class PropData < FFI::Union layout( :p_number, :double, :p_aval, AVal, :p_object, AMFObject) end layout( :p_name, AVal, :p_type, AMFDataType, :p_vu, PropData, :p_UTCoffset, :int16) end # unsigned int AMF_DecodeInt24(const char *data); # unsigned int AMF_DecodeInt32(const char *data); attach_function :AMF_DecodeInt24, [:pointer], :uint attach_function :AMF_DecodeInt32, [:pointer], :uint # int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName); # void AMF_Dump(AMFObject * obj); attach_function :AMF_Decode, [AMFObject.ptr, :pointer, :int, :int], :int attach_function :AMF_Dump, [AMFObject.ptr], :void # AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex); attach_function :AMF_GetProp, [AMFObject.ptr, AVal.ptr, :int], AMFObjectProperty.ptr # double AMFProp_GetNumber(AMFObjectProperty * prop); # void AMFProp_GetString(AMFObjectProperty * prop, AVal * str); attach_function :AMFProp_GetNumber, [AMFObjectProperty.ptr], :double attach_function :AMFProp_GetString, [AMFObjectProperty.ptr, AVal.ptr], :void ### rtmp.h ### # #define RTMP_LIB_VERSION 0x020300 /* 2.3 */ RTMP_LIB_VERSION = 0x020300 # 2.3 # #define RTMP_FEATURE_HTTP 0x01 # #define RTMP_FEATURE_ENC 0x02 # #define RTMP_FEATURE_SSL 0x04 # #define RTMP_FEATURE_MFP 0x08 /* not yet supported */ # #define RTMP_FEATURE_WRITE 0x10 /* publish, not play */ # #define RTMP_FEATURE_HTTP2 0x20 /* server-side rtmpt */ RTMP_FEATURE_HTTP = 0x01 RTMP_FEATURE_ENC = 0x02 RTMP_FEATURE_SSL = 0x04 RTMP_FEATURE_MFP = 0x08 RTMP_FEATURE_WRITE = 0x10 RTMP_FEATURE_HTTP2 = 0x20 # #define RTMP_PROTOCOL_UNDEFINED -1 # #define RTMP_PROTOCOL_RTMP 0 # #define RTMP_PROTOCOL_RTMPE RTMP_FEATURE_ENC # #define RTMP_PROTOCOL_RTMPT RTMP_FEATURE_HTTP # #define RTMP_PROTOCOL_RTMPS RTMP_FEATURE_SSL # #define RTMP_PROTOCOL_RTMPTE (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC) # #define RTMP_PROTOCOL_RTMPTS (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL) # #define RTMP_PROTOCOL_RTMFP RTMP_FEATURE_MFP RTMP_PROTOCOL_UNDEFINED = -1 RTMP_PROTOCOL_RTMP = 0 RTMP_PROTOCOL_RTMPE = RTMP_FEATURE_ENC RTMP_PROTOCOL_RTMPT = RTMP_FEATURE_HTTP RTMP_PROTOCOL_RTMPS = RTMP_FEATURE_SSL RTMP_PROTOCOL_RTMPTE = (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC) RTMP_PROTOCOL_RTMPTS = (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL) RTMP_PROTOCOL_RTMFP = RTMP_FEATURE_MFP # #define RTMP_READ_HEADER 0x01 # #define RTMP_READ_RESUME 0x02 # #define RTMP_READ_NO_IGNORE 0x04 # #define RTMP_READ_GOTKF 0x08 # #define RTMP_READ_GOTFLVK 0x10 # #define RTMP_READ_SEEKING 0x20 RTMP_READ_HEADER = 0x01 RTMP_READ_RESUME = 0x02 RTMP_READ_NO_IGNORE = 0x04 RTMP_READ_GOTKF = 0x08 RTMP_READ_GOTFLVK = 0x10 RTMP_READ_SEEKING = 0x20 # #define RTMP_READ_COMPLETE -3 # #define RTMP_READ_ERROR -2 # #define RTMP_READ_EOF -1 # #define RTMP_READ_IGNORE 0 RTMP_READ_COMPLETE = -3 RTMP_READ_ERROR = -2 RTMP_READ_EOF = -1 RTMP_READ_IGNORE = 0 # typedef struct RTMP_READ # { # char *buf; # char *bufpos; # unsigned int buflen; # uint32_t timestamp; # uint8_t dataType; # uint8_t flags; # int8_t status; # uint8_t initialFrameType; # uint32_t nResumeTS; # char *metaHeader; # char *initialFrame; # uint32_t nMetaHeaderSize; # uint32_t nInitialFrameSize; # uint32_t nIgnoredFrameCounter; # uint32_t nIgnoredFlvFrameCounter; # } RTMP_READ; class RTMP_READ < FFI::Struct layout( :buf, :pointer, :bufpos, :pointer, :buflen, :uint, :timestamp, :uint32, :dataType, :uint8, :flags, :uint8, :status, :int8, :initialFrameType, :uint8, :nResumeTS, :uint32, :metaHeader, :pointer, :initialFrame, :pointer, :nMetaHeaderSize, :uint32, :nInitialFrameSize, :uint32, :nIgnoredFrameCounter, :uint32, :nIgnoredFlvFrameCounter, :uint32) end # typedef struct RTMPPacket # { # uint8_t m_headerType; # uint8_t m_packetType; # uint8_t m_hasAbsTimestamp; /* timestamp absolute or relative? */ # int m_nChannel; # uint32_t m_nTimeStamp; /* timestamp */ # int32_t m_nInfoField2; /* last 4 bytes in a long header */ # uint32_t m_nBodySize; # uint32_t m_nBytesRead; # RTMPChunk *m_chunk; # char *m_body; # } RTMPPacket; class RTMPPacket < FFI::Struct layout( :m_headerType, :uint8, :m_packetType, :uint8, :m_hasAbsTimestamp, :uint8, :m_nChannel, :int, :m_nTimeStamp, :uint32, :m_nInfoField2, :int32, :m_nBodySize, :uint32, :m_nBytesRead, :uint32, :m_chunk, :pointer, :m_body, :pointer) end # #define RTMP_BUFFER_CACHE_SIZE (16*1024) RTMP_BUFFER_CACHE_SIZE = (16 * 1024) # typedef struct RTMPSockBuf # { # int sb_socket; # int sb_size; /* number of unprocessed bytes in buffer */ # char *sb_start; /* pointer into sb_pBuffer of next byte to process */ # char sb_buf[RTMP_BUFFER_CACHE_SIZE]; /* data read from socket */ # int sb_timedout; # void *sb_ssl; # } RTMPSockBuf; class RTMPSockBuf < FFI::Struct layout( :sb_socket, :int, :sb_size, :int, :sb_start, :pointer, :sb_buf, [:char, RTMP_BUFFER_CACHE_SIZE], :sb_timedout, :int, :sb_ssl, :pointer) end # #define RTMP_LF_AUTH 0x0001 /* using auth param */ # #define RTMP_LF_LIVE 0x0002 /* stream is live */ # #define RTMP_LF_SWFV 0x0004 /* do SWF verification */ # #define RTMP_LF_PLST 0x0008 /* send playlist before play */ # #define RTMP_LF_BUFX 0x0010 /* toggle stream on BufferEmpty msg */ # #define RTMP_LF_FTCU 0x0020 /* free tcUrl on close */ # #define RTMP_LF_FAPU 0x0040 /* free app on close */ RTMP_LF_AUTH = 0x0001 RTMP_LF_LIVE = 0x0002 RTMP_LF_SWFV = 0x0004 RTMP_LF_PLST = 0x0008 RTMP_LF_BUFX = 0x0010 RTMP_LF_FTCU = 0x0020 RTMP_LF_FAPU = 0x0040 # #define RTMP_SWF_HASHLEN 32 RTMP_SWF_HASHLEN = 32 # typedef struct RTMP_LNK # { # AVal hostname; # AVal sockshost; # AVal playpath0; /* parsed from URL */ # AVal playpath; /* passed in explicitly */ # AVal tcUrl; # AVal swfUrl; # AVal pageUrl; # AVal app; # AVal auth; # AVal flashVer; # AVal subscribepath; # AVal usherToken; # AVal token; # AVal pubUser; # AVal pubPasswd; # AMFObject extras; # int edepth; # int seekTime; # int stopTime; # int lFlags; # int swfAge; # int protocol; # int timeout; /* connection timeout in seconds */ # int pFlags; /* unused, but kept to avoid breaking ABI */ # unsigned short socksport; # unsigned short port; # void *dh; /* for encryption */ # void *rc4keyIn; # void *rc4keyOut; # uint32_t SWFSize; # uint8_t SWFHash[RTMP_SWF_HASHLEN]; # char SWFVerificationResponse[RTMP_SWF_HASHLEN+10]; # } RTMP_LNK; class RTMP_LNK < FFI::Struct layout( :hostname, AVal, :sockshost, AVal, :playpath0, AVal, :playpath, AVal, :tcUrl, AVal, :swfUrl, AVal, :pageUrl, AVal, :app, AVal, :auth, AVal, :flashVer, AVal, :subscribepath, AVal, :usherToken, AVal, :token, AVal, :pubUser, AVal, :pubPasswd, AVal, :extras, AMFObject, :edepth, :int, :seekTime, :int, :stopTime, :int, :lFlags, :int, :swfAge, :int, :protocol, :int, :timeout, :int, :pFlags, :int, :socksport, :ushort, :port, :ushort, :dh, :pointer, :rc4keyIn, :pointer, :rc4keyOut, :pointer, :SWFSize, :uint32, :SWFHash, [:uint8, RTMP_SWF_HASHLEN], :SWFVerificationResponse, [:char, RTMP_SWF_HASHLEN+10]) end # typedef struct RTMP # { # int m_inChunkSize; # int m_outChunkSize; # int m_nBWCheckCounter; # int m_nBytesIn; # int m_nBytesInSent; # int m_nBufferMS; # int m_stream_id; /* returned in _result from createStream */ # int m_mediaChannel; # uint32_t m_mediaStamp; # uint32_t m_pauseStamp; # int m_pausing; # int m_nServerBW; # int m_nClientBW; # uint8_t m_nClientBW2; # uint8_t m_bPlaying; # uint8_t m_bSendEncoding; # uint8_t m_bSendCounter; # int m_numInvokes; # int m_numCalls; # RTMP_METHOD *m_methodCalls; /* remote method calls queue */ # int m_channelsAllocatedIn; # int m_channelsAllocatedOut; # RTMPPacket **m_vecChannelsIn; # RTMPPacket **m_vecChannelsOut; # int *m_channelTimestamp; /* abs timestamp of last packet */ # double m_fAudioCodecs; /* audioCodecs for the connect packet */ # double m_fVideoCodecs; /* videoCodecs for the connect packet */ # double m_fEncoding; /* AMF0 or AMF3 */ # double m_fDuration; /* duration of stream in seconds */ # int m_msgCounter; /* RTMPT stuff */ # int m_polling; # int m_resplen; # int m_unackd; # AVal m_clientID; # RTMP_READ m_read; # RTMPPacket m_write; # RTMPSockBuf m_sb; # RTMP_LNK Link; # } RTMP; class RTMP < FFI::Struct layout( :m_inChunkSize, :int, :m_outChunkSize, :int, :m_nBWCheckCounter, :int, :m_nBytesIn, :int, :m_nBytesInSent, :int, :m_nBufferMS, :int, :m_stream_id, :int, :m_mediaChannel, :int, :m_mediaStamp, :uint32, :m_pauseStamp, :uint32, :m_pausing, :int, :m_nServerBW, :int, :m_nClientBW, :int, :m_nClientBW2, :uint8, :m_bPlaying, :uint8, :m_bSendEncoding, :uint8, :m_bSendCounter, :uint8, :m_numInvokes, :int, :m_numCalls, :int, :m_methodCalls, :pointer, :m_channelsAllocatedIn, :int, :m_channelsAllocatedOut, :int, :m_vecChannelsIn, :pointer, :m_vecChannelsOut, :pointer, :m_channelTimestamp, :pointer, :m_fAudioCodecs, :double, :m_fVideoCodecs, :double, :m_fEncoding, :double, :m_fDuration, :double, :m_msgCounter, :int, :m_polling, :int, :m_resplen, :int, :m_unackd, :int, :m_clientID, AVal, :m_read, RTMP_READ, :m_write, RTMPPacket, :m_sb, RTMPSockBuf, :Link, RTMP_LNK) end # int RTMP_ParseURL(const char *url, int *protocol, # AVal *host, unsigned int *port, AVal *playpath, AVal *app); attach_function :RTMP_ParseURL, [ :string, :pointer, AVal.ptr, :pointer, AVal.ptr, AVal.ptr], :int # void RTMP_SetBufferMS(RTMP *r, int size); # void RTMP_UpdateBufferMS(RTMP *r); attach_function :RTMP_SetBufferMS, [RTMP.ptr, :int], :void attach_function :RTMP_UpdateBufferMS, [RTMP.ptr], :void # int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg); # void RTMP_SetupStream(RTMP *r, int protocol, # AVal *hostname, unsigned int port, AVal *sockshost, # AVal *playpath, AVal *tcUrl, AVal *swfUrl, # AVal *pageUrl, AVal *app, AVal *auth, # AVal *swfSHA256Hash, uint32_t swfSize, # AVal *flashVer, AVal *subscribepath, AVal *usherToken, # int dStart, int dStop, int bLiveStream, long int timeout); attach_function :RTMP_SetOpt, [RTMP.ptr, AVal.ptr, AVal.ptr], :int attach_function :RTMP_SetupStream, [ RTMP.ptr, :int, AVal.ptr, :uint, AVal.ptr, AVal.ptr, AVal.ptr, AVal.ptr, AVal.ptr, AVal.ptr, AVal.ptr, AVal.ptr, :uint32, AVal.ptr, AVal.ptr, AVal.ptr, :int, :int, :int, :long], :void # int RTMP_Connect(RTMP *r, RTMPPacket *cp); attach_function :RTMP_Connect, [RTMP.ptr, RTMPPacket.ptr], :int # int RTMP_IsConnected(RTMP *r); # int RTMP_IsTimedout(RTMP *r); # double RTMP_GetDuration(RTMP *r); # int RTMP_ToggleStream(RTMP *r); attach_function :RTMP_IsConnected, [RTMP.ptr], :int attach_function :RTMP_IsTimedout, [RTMP.ptr], :int attach_function :RTMP_GetDuration, [RTMP.ptr], :double attach_function :RTMP_ToggleStream, [RTMP.ptr], :int # int RTMP_ConnectStream(RTMP *r, int seekTime); # int RTMP_ReconnectStream(RTMP *r, int seekTime); attach_function :RTMP_ConnectStream, [RTMP.ptr, :int], :int attach_function :RTMP_ReconnectStream, [RTMP.ptr, :int], :int # void RTMP_Init(RTMP *r); # void RTMP_Close(RTMP *r); attach_function :RTMP_Init, [RTMP.ptr], :void attach_function :RTMP_Close, [RTMP.ptr], :void # int RTMP_LibVersion(void); attach_function :RTMP_LibVersion, [], :int # int RTMP_FindFirstMatchingProperty( # AMFObject *obj, const AVal *name, AMFObjectProperty * p); attach_function :RTMP_FindFirstMatchingProperty, [ AMFObject.ptr, AVal.ptr, AMFObjectProperty.ptr], :int # int RTMP_Read(RTMP *r, char *buf, int size); attach_function :RTMP_Read, [RTMP.ptr, :pointer, :int], :int ### rtmpdump.c ### # #define RD_SUCCESS 0 # #define RD_FAILED 1 # #define RD_INCOMPLETE 2 # #define RD_NO_CONNECT 3 RD_SUCCESS = 0 RD_FAILED = 1 RD_INCOMPLETE = 2 RD_NO_CONNECT = 3 # #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */ DEF_BUFTIME = (10 * 60 * 60 * 1000) end end
ラッパーメソッド
結びつけた関数を見てみると、第1引数に自分自身を表すオブジェクトを渡す関数が多いことが分かると思う。
これは、C言語でオブジェクト指向的にコードを書く場合、第1引数に自分自身のオブジェクトを渡すというのがお約束だから。
(Perlのオブジェクト指向機能を使ったことがある人だと、この感覚は分かりやすいかも)
ただ、Rubyだとちょっと不自然な感じなので、各クラスにラッパーとなるインスタンスメソッドを用意しておく。
ついでに、Rubyの文字列とAValの変換を一々やるのは面倒なので、ラッパーメソッドではその辺りも解消してある。
# define wrapper methods module NicovideoDL module LibRTMP class AVal def self.from_string(str) aval = self.new aval[:av_val] = FFI::MemoryPointer.from_string(str) aval[:av_len] = str.bytesize aval end def to_s self[:av_val].read_string(self[:av_len]) rescue "" end def ==(other) self.to_s == other.to_s end end def decode_int24(str) buffer = FFI::MemoryPointer.new :char, str.bytesize buffer.put_bytes(0, str, 0, str.bytesize) return LibRTMP.AMF_DecodeInt24(buffer) end def decode_int32(str) buffer = FFI::MemoryPointer.new :char, str.bytesize buffer.put_bytes(0, str, 0, str.bytesize) return LibRTMP.AMF_DecodeInt32(buffer) end def parse_url(url) protocol_p = FFI::MemoryPointer.new :int host_aval = AVal.new port_p = FFI::MemoryPointer.new :uint playpath_aval = AVal.new app_aval = AVal.new if LibRTMP.RTMP_ParseURL(url, protocol_p, host_aval, port_p, playpath_aval, app_aval) <= 0 raise "failed to parse url. [url: #{url}]" end return { protocol: protocol_p.read_int, host: host_aval.to_s, port: port_p.read_uint, playpath: playpath_aval.to_s, app: app_aval.to_s} end module_function :decode_int24, :decode_int32, :parse_url class AMFObject def decode(buffer, buffer_size, decode_name) LibRTMP.AMF_Decode(self, buffer, buffer_size, decode_name ? 1 : 0) end def dump LibRTMP.AMF_Dump(self) end def get_property(name, index) name_aval = name.nil? ? nil : AVal.from_string(name) LibRTMP.AMF_GetProp(self, name_aval, index) end def find_first_matching_property(name) name_aval = AVal.from_string(name) prop = AMFObjectProperty.new if LibRTMP.RTMP_FindFirstMatchingProperty(self, name_aval, prop) > 0 prop else nil end end end class AMFObjectProperty def get_number LibRTMP.AMFProp_GetNumber(self) end def get_string str_aval = AVal.new LibRTMP.AMFProp_GetString(self, str_aval) str_aval.to_s end end class RTMP def set_buffer_ms(buffer_ms) LibRTMP.RTMP_SetBufferMS(self, buffer_ms) end def update_buffer_ms LibRTMP.RTMP_UpdateBufferMS(self) end def set_option(option, value) option_aval = AVal.from_string(option) value_aval = AVal.from_string(value) LibRTMP.RTMP_SetOpt(self, option_aval, value_aval) end def setup_stream(protocol, host_name, port, socks_host, playpath, tc_url, swf_url, page_url, app, auth, swf_sha256_hash, swf_size, flash_ver, subscribe_path, usher_token, start, stop, is_live_stream, timeout) host_name_aval = AVal.from_string(host_name) socks_host_aval = AVal.from_string(socks_host) playpath_aval = AVal.from_string(playpath) tc_url_aval = AVal.from_string(tc_url) swf_url_aval = AVal.from_string(swf_url) page_url_aval = AVal.from_string(page_url) app_aval = AVal.from_string(app) auth_aval = AVal.from_string(auth) swf_sha256_hash_aval = AVal.from_string(swf_sha256_hash) flash_ver_aval = AVal.from_string(flash_ver) subscribe_path_aval = AVal.from_string(subscribe_path) usher_token_aval = AVal.from_string(usher_token) LibRTMP.RTMP_SetupStream(self, protocol, host_name_aval, port, socks_host_aval, playpath_aval, tc_url_aval, swf_url_aval, page_url_aval, app_aval, auth_aval, swf_sha256_hash_aval, swf_size, flash_ver_aval, subscribe_path_aval, usher_token_aval, start, stop, is_live_stream ? 1 : 0, timeout) end def connect(packet) LibRTMP.RTMP_Connect(self, packet) end def connected? LibRTMP.RTMP_IsConnected(self) > 0 end def timedout? LibRTMP.RTMP_IsTimedout(self) > 0 end def get_duration LibRTMP.RTMP_GetDuration(self) end def toggle_stream LibRTMP.RTMP_ToggleStream(self) end def connect_stream(seek_time) LibRTMP.RTMP_ConnectStream(self, seek_time) end def reconnect_stream(seek_time) LibRTMP.RTMP_ReconnectStream(self, seek_time) end def init LibRTMP.RTMP_Init(self) end def close LibRTMP.RTMP_Close(self) end def read(buffer, buffer_size) LibRTMP.RTMP_Read(self, buffer, buffer_size) end def add_connect_option(option) if self.set_option("conn", option) <= 0 raise "faild to set connect option. [option: #{option}]" end end end end end
中身がないのに無駄に長い・・・
今日はここまで!