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