1+ var RTMP = require ( './node-rtmpapi' ) ;
2+ var SimpleWebsocket = require ( 'simple-websocket' ) ;
3+ var Buffer = require ( 'buffer' ) . Buffer ;
4+
5+ const H264_SEP = new Buffer ( [ 0 , 0 , 0 , 1 ] ) ;
6+ const FRAME_Q_SIZE = 15 ;
7+
8+ class WebRtmpPlayer
9+ {
10+ constructor ( wsHost , app , streamName , tcUrl )
11+ {
12+ this . _frameQ = [ ] ;
13+ this . _fps = NaN ;
14+ this . _lastRenderTime = 0 ;
15+
16+ this . _decoder = new Decoder ( ) ;
17+ this . _player = new Player ( { useWorker : false , webgl : true } ) ;
18+ this . _url = { host : wsHost , app : app , tcUrl : tcUrl , stream : streamName } ;
19+
20+ this . _decoder . onPictureDecoded = this . _onPictureDecoded . bind ( this ) ;
21+
22+ this . _rtmpTransId = 0 ;
23+ this . _invokeChannel = null ;
24+ this . _videoChannel = null ;
25+ this . _rtmpSession = null ;
26+
27+ this . _sock = new SimpleWebsocket ( this . _url . host ) ;
28+ this . _sock . setMaxListeners ( 100 ) ;
29+ this . _sock . on ( 'connect' , ( ) =>
30+ {
31+ new RTMP . rtmpSession ( this . _sock , true , this . _onRtmpSessionCreated . bind ( this ) ) ;
32+ } )
33+ }
34+
35+ get canvas ( ) { return this . _player . canvas ; }
36+
37+ _onPictureDecoded ( buffer , width , height , infos )
38+ {
39+ if ( this . _frameQ . length === FRAME_Q_SIZE )
40+ {
41+ console . log ( "** drop oldest frame!" ) ;
42+ this . _frameQ . shift ( ) ; //如果播放速度跟不上,扔掉最老那一帧
43+ }
44+ this . _frameQ . push ( { data : Buffer . from ( buffer ) , width : width , height : height , canvasObj : this . _player . canvasObj } ) ;
45+ }
46+
47+ _drawFrame ( )
48+ {
49+ var now = new Date ( ) ; //如果播放速度跟不上网络速度,跳帧
50+ var skipFrame = Math . floor ( Math . abs ( now - this . _lastRenderTime ) / ( 1000 / this . _fps ) ) - 1 ;
51+ if ( this . _lastRenderTime && skipFrame > 0 )
52+ {
53+ console . log ( "SkipFrmae = " + skipFrame ) ;
54+ while ( skipFrame -- > 0 && this . _frameQ . length > 0 ) this . _frameQ . shift ( ) ;
55+ }
56+
57+ var frame = this . _frameQ . shift ( ) ;
58+ if ( frame )
59+ {
60+ this . _player . renderFrameWebGL ( frame ) ;
61+ }
62+ this . _lastRenderTime = now ;
63+ }
64+
65+ _rtmpConnect ( )
66+ {
67+ this . _rtmpSession . Q . Q ( 0 , ( ) =>
68+ {
69+ console . log ( "sending connect" ) ;
70+
71+ this . _invokeChannel . sendAmf0EncCmdMsg ( {
72+ cmd : 'connect' ,
73+ transId :++ this . _rtmpTransId ,
74+ cmdObj :
75+ {
76+ app : this . _url . app ,
77+ tcUrl : this . _url . tcUrl ,
78+ fpad : false ,
79+ capabilities : 15.0 , //note: 我不知道这些参数什么鬼,依据rtmpdump分析出来的
80+ audioCodecs : 3191 ,
81+ videoCodecs : 252 ,
82+ videoFunction : 1.0
83+ }
84+ } ) ;
85+ this . _invokeChannel . invokedMethods [ this . _rtmpTransId ] = 'connect' ;
86+ } ) ;
87+ }
88+
89+ _rtmpCreateStream ( )
90+ {
91+ this . _rtmpSession . Q . Q ( 0 , ( ) =>
92+ {
93+ console . log ( "sending createStream" ) ;
94+ this . _invokeChannel . sendAmf0EncCmdMsg ( {
95+ cmd : 'createStream' ,
96+ transId : ++ this . _rtmpTransId ,
97+ cmdObj : null
98+ } ) ;
99+ this . _invokeChannel . invokedMethods [ this . _rtmpTransId ] = 'createStream' ;
100+ } ) ;
101+ }
102+
103+ _rtmpSendPlay ( msgStreamId )
104+ {
105+ this . _rtmpSession . Q . Q ( 0 , ( ) =>
106+ {
107+ this . _videoChannel . chunk . msgStreamId = msgStreamId ;
108+ //send play ??
109+ this . _videoChannel . sendAmf0EncCmdMsg ( {
110+ cmd : 'play' ,
111+ transId : ++ this . _rtmpTransId ,
112+ cmdObj :null ,
113+ streamName : this . _url . stream ,
114+ start :- 2
115+
116+ } , 0 ) ;
117+ this . _invokeChannel . invokedMethods [ this . _rtmpTransId ] = "play" ;
118+ } ) ;
119+ }
120+
121+ _onRtmpSessionCreated ( session )
122+ {
123+ this . _rtmpSession = session ;
124+ console . log ( "rtmpSession...cb..." ) ;
125+ this . _invokeChannel = new RTMP . rtmpChunk . RtmpChunkMsgClass ( { streamId :5 } , { sock : this . _sock , Q : session . Q , debug : false } ) ;
126+ this . _invokeChannel . invokedMethods = { } ; //用来保存invoke的次数,以便收到消息的时候确认对应结果
127+ this . _videoChannel = new RTMP . rtmpChunk . RtmpChunkMsgClass ( { streamId :8 } , { sock : this . _sock , Q : session . Q , debug : false } ) ;
128+
129+ session . Q . Q ( 0 , this . _rtmpConnect . bind ( this ) ) ;
130+ session . Q . Q ( 0 , ( ) =>
131+ {
132+ console . log ( "Begin LOOP" ) ;
133+ session . msg . loop ( this . _handleRtmpMessage . bind ( this ) ) ;
134+ } ) ;
135+ }
136+
137+ _handleRtmpMessage ( chunkMsg )
138+ {
139+ var chunk = chunkMsg . chunk ;
140+ var msg = chunk . msg ;
141+
142+ console . log ( "GOT MESSAGE: " + chunk . msgTypeText ) ;
143+ //console.log("===========>\n" + JSON.stringify(msg));
144+
145+ if ( chunk . msgTypeText == "amf0cmd" )
146+ {
147+ if ( msg . cmd == "_result" )
148+ {
149+ var lastInvoke = this . _invokeChannel . invokedMethods [ msg . transId ] ;
150+ if ( lastInvoke )
151+ {
152+ console . log ( "<--Got Invoke Result for: " + lastInvoke ) ;
153+ delete this . _invokeChannel . invokedMethods [ msg . transId ] ;
154+ }
155+
156+ switch ( lastInvoke )
157+ {
158+ case 'connect' :
159+ return this . _rtmpCreateStream ( ) ;
160+ case 'createStream' :
161+ return this . _rtmpSendPlay ( msg . info ) ;
162+ }
163+ }
164+ }
165+
166+ if ( chunk . msgTypeText == "video" )
167+ {
168+ //提取h264流
169+ var chunkData = chunk . data ;
170+ if ( chunkData . length > 4 )
171+ {
172+ if ( chunkData [ 1 ] === 1 )
173+ {
174+ chunkData = Buffer . concat ( [ H264_SEP , chunkData . slice ( 9 ) ] ) ;
175+ }
176+ else if ( chunkData [ 1 ] === 0 )
177+ {
178+ var spsSize = ( chunkData [ 11 ] << 8 ) | chunkData [ 12 ] ;
179+ var spsEnd = 13 + spsSize ;
180+ chunkData = Buffer . concat ( [ H264_SEP , chunkData . slice ( 13 , spsEnd ) , H264_SEP , chunkData . slice ( spsEnd + 3 ) ] ) ;
181+ }
182+ this . _decoder . decode ( chunkData ) ;
183+ }
184+ }
185+
186+ if ( chunk . msgTypeText == "amf0meta" && msg . cmd == 'onMetaData' )
187+ {
188+ console . log ( "onmetadata" ) ;
189+ this . _fps = chunk . msg [ 'event' ] [ 'framerate' ] ;
190+ console . log ( "fps = " + this . _fps ) ;
191+ setInterval ( this . _drawFrame . bind ( this ) , 1000.0 / this . _fps ) ; //todo: clear
192+ }
193+
194+ this . _rtmpSession . Q . Q ( 0 , ( ) =>
195+ {
196+ this . _rtmpSession . msg . loop ( this . _handleRtmpMessage . bind ( this ) ) ;
197+ } ) ;
198+ }
199+ }
200+
201+ module . exports = WebRtmpPlayer ;
0 commit comments