Skip to content

Commit f9026cc

Browse files
应元东应元东
应元东
authored and
应元东
committed
refactor
1 parent 01c5cc6 commit f9026cc

File tree

4 files changed

+210
-182
lines changed

4 files changed

+210
-182
lines changed

.idea/misc.xml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

WebRtmpPlayer.js

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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;

test.js

+3-179
Original file line numberDiff line numberDiff line change
@@ -1,181 +1,5 @@
1-
var RTMP = require('./node-rtmpapi');
2-
var SimpleWebsocket = require('simple-websocket');
3-
var Buffer = require('buffer').Buffer;
1+
const WebRtmpPlayer = require('./WebRtmpPlayer');
42

5-
const H264_SEP = new Buffer([0,0,0,1]);
6-
const FRAME_Q_SIZE = 15;
7-
8-
const url = {host: "ws://127.0.0.1:1999", app:"live", stream: "B012"};
9-
10-
var frameQ = [];
11-
var fps = 20;
12-
var lastRenderTime = 0;
13-
14-
var decoder = new Decoder();
15-
var player = new Player({ useWorker: false, webgl: true });
3+
var player = new WebRtmpPlayer('ws://127.0.0.1:1999', 'live', 'B011', 'rtmp://video.7uan7uan.com/live');
164
player.canvas.style['height'] = '100%';
17-
document.getElementById("vidCont").appendChild(player.canvas);
18-
19-
var sock = new SimpleWebsocket(url.host);
20-
sock.setMaxListeners(100);
21-
22-
decoder.onPictureDecoded = function(buffer, width, height, infos)
23-
{
24-
if(frameQ.length === FRAME_Q_SIZE)
25-
{
26-
console.log("** drop oldest frame!");
27-
frameQ.shift(); //如果播放速度跟不上,扔掉最老那一帧
28-
}
29-
frameQ.push({data: Buffer.from(buffer), width: width, height: height, canvasObj: player.canvasObj});
30-
};
31-
32-
function drawFrame()
33-
{
34-
var now = new Date();//如果播放速度跟不上网络速度,跳帧
35-
var skipFrame = Math.floor(Math.abs(now - lastRenderTime) / (1000 / fps) ) - 1;
36-
if(lastRenderTime && skipFrame > 0)
37-
{
38-
console.log("SkipFrmae = " + skipFrame);
39-
while(skipFrame-- > 0 && frameQ.length > 0) frameQ.shift();
40-
}
41-
42-
var frame = frameQ.shift();
43-
if(frame)
44-
{
45-
player.renderFrameWebGL(frame);
46-
}
47-
lastRenderTime = now;
48-
}
49-
50-
sock.on('connect', function()
51-
{
52-
var transId = 0;
53-
var session = new RTMP.rtmpSession(sock, true, function(me)
54-
{
55-
console.log("rtmpSession...cb...");
56-
var invokeChannel = new RTMP.rtmpChunk.RtmpChunkMsgClass({streamId:5}, {sock: sock, Q: me.Q, debug: false});
57-
invokeChannel.invokedMethods = {}; //用来保存invoke的次数,以便收到消息的时候确认对应结果
58-
59-
var videoChannel = new RTMP.rtmpChunk.RtmpChunkMsgClass({streamId:8}, {sock: sock, Q: me.Q, debug: false});
60-
61-
var msger = me.msg;
62-
me.Q.Q(0,function()
63-
{
64-
console.log("sending connect");
65-
66-
//todo: 先确定可行,再重构
67-
invokeChannel.sendAmf0EncCmdMsg({
68-
cmd: 'connect',
69-
transId:++transId,
70-
cmdObj:
71-
{
72-
app: url.app,
73-
tcUrl: "rtmp://video.7uan7uan.com/live",
74-
fpad: false,
75-
capabilities: 15.0,
76-
audioCodecs: 3191,
77-
videoCodecs: 252,
78-
videoFunction: 1.0
79-
}
80-
});
81-
invokeChannel.invokedMethods[transId] = 'connect';
82-
});
83-
84-
me.Q.Q(0, function()
85-
{
86-
console.log("Begin LOOP");
87-
msger.loop(handleMessage);
88-
});
89-
90-
function handleMessage(chunkMsg)
91-
{
92-
var chunk = chunkMsg.chunk;
93-
var msg = chunk.msg;
94-
95-
console.log("GOT MESSAGE: " + chunk.msgTypeText);
96-
console.log("===========>\n" + JSON.stringify(msg));
97-
98-
if(chunk.msgTypeText == "amf0cmd")
99-
{
100-
if(msg.cmd == "_result")
101-
{
102-
var lastInvoke = invokeChannel.invokedMethods[msg.transId];
103-
if(lastInvoke)
104-
{
105-
console.log("<--Got Invoke Result for: " + lastInvoke);
106-
delete invokeChannel.invokedMethods[msg.transId];
107-
}
108-
109-
if(lastInvoke == "connect") //确认是connect的结果
110-
{
111-
console.log("sending createStream");
112-
invokeChannel.sendAmf0EncCmdMsg({
113-
cmd: 'createStream',
114-
transId: ++transId,
115-
cmdObj: null
116-
});
117-
invokeChannel.invokedMethods[transId] = 'createStream';
118-
}
119-
else if(lastInvoke == "createStream") //确认是createStream的结果
120-
{
121-
videoChannel.chunk.msgStreamId = msg.info;
122-
//send play ??
123-
videoChannel.sendAmf0EncCmdMsg({
124-
cmd: 'play',
125-
transId: ++transId,
126-
cmdObj:null,
127-
streamName: url.stream,
128-
start:-2
129-
130-
},0);
131-
invokeChannel.invokedMethods[transId] = "play";
132-
}
133-
}
134-
else if(msg.cmd == 'onBWDone')
135-
{
136-
console.log("onBWDone");
137-
//send checkBW
138-
invokeChannel.sendAmf0EncCmdMsg({
139-
cmd: '_checkbw',
140-
transId: ++transId,
141-
cmdObj:null
142-
},0);
143-
invokeChannel.invokedMethods[transId] = "_checkbw";
144-
}
145-
}
146-
147-
if(chunk.msgTypeText == "video")
148-
{
149-
var chunkData = chunk.data;
150-
if (chunkData.length > 4)
151-
{
152-
if (chunkData[1] === 1)
153-
{
154-
chunkData = Buffer.concat([H264_SEP, chunkData.slice(9)]);
155-
}
156-
else if (chunkData[1] === 0)
157-
{
158-
var spsSize = (chunkData[11] << 8) | chunkData[12];
159-
var spsEnd = 13 + spsSize;
160-
chunkData = Buffer.concat([H264_SEP, chunkData.slice(13, spsEnd), H264_SEP, chunkData.slice(spsEnd + 3)]);
161-
}
162-
decoder.decode(chunkData);
163-
//player.decode(chunkData);
164-
}
165-
}
166-
167-
if(chunk.msgTypeText == "amf0meta" && msg.cmd == 'onMetaData')
168-
{
169-
console.log("onmetadata");
170-
fps = chunk.msg['event']['framerate'];
171-
console.log("fps = "+fps);
172-
setInterval(drawFrame, 1000.0/fps);
173-
}
174-
175-
me.Q.Q(0,function()
176-
{
177-
msger.loop(handleMessage);
178-
});
179-
}
180-
});
181-
});
5+
document.getElementById("vidCont").appendChild(player.canvas);

0 commit comments

Comments
 (0)