Full Code of yingDev/Web-Rtmp for AI

master bc85b5c7e03d cached
16 files
13.6 KB
4.6k tokens
12 symbols
1 requests
Download .txt
Repository: yingDev/Web-Rtmp
Branch: master
Commit: bc85b5c7e03d
Files: 16
Total size: 13.6 KB

Directory structure:
gitextract_ok_mgr1a/

├── .editorconfig
├── .gitignore
├── .gitmodules
├── .idea/
│   ├── encodings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── vcs.xml
│   └── web-rtmp.iml
├── README.md
├── WebRtmpPlayer.js
├── index.template.html
├── misc/
│   ├── do-rtmpsuck.sh
│   └── reloadChrome.scpt
├── package.json
├── test.js
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true


# Matches multiple files with brace expansion notation
# Set default charset
[*.*]
charset = utf-8
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true


================================================
FILE: .gitignore
================================================
node_modules


================================================
FILE: .gitmodules
================================================
[submodule "node-rtmpapi"]
	path = node-rtmpapi
	url = https://github.com/yingDev/node-rtmpapi.git
[submodule "websockify"]
	path = websockify
	url = https://github.com/yingDev/websockify.git


================================================
FILE: .idea/encodings.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Encoding">
    <file url="PROJECT" charset="UTF-8" />
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="JavaScriptSettings">
    <option name="languageLevel" value="ES6" />
  </component>
  <component name="ProjectLevelVcsManager" settingsEditedManually="false">
    <OptionsSetting value="true" id="Add" />
    <OptionsSetting value="true" id="Remove" />
    <OptionsSetting value="true" id="Checkout" />
    <OptionsSetting value="true" id="Update" />
    <OptionsSetting value="true" id="Status" />
    <OptionsSetting value="true" id="Edit" />
    <ConfirmationsSetting value="0" id="Add" />
    <ConfirmationsSetting value="0" id="Remove" />
  </component>
</project>

================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/web-rtmp.iml" filepath="$PROJECT_DIR$/.idea/web-rtmp.iml" />
    </modules>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
    <mapping directory="$PROJECT_DIR$/node-rtmpapi" vcs="Git" />
  </component>
</project>

================================================
FILE: .idea/web-rtmp.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <excludeFolder url="file://$MODULE_DIR$/build" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>

================================================
FILE: README.md
================================================
# Web-Rtmp
在网页上播放RTMP视频流,通过Websocket。

# WARNING ============
这个项目很大程度上只能算可行性验证,不适合作为 library 使用。试验结论是:用 js 解码视频效率已经够低了,rtmp 协议增加了许多额外开销,如果单纯为了播放视频,并不明智。不如用纯粹的 ws 传送 h264 流。

## 基本原理
- 服务端
	- 使用 [websockify](https://github.com/kanaka/websockify)  wrap 一个 rtmp 服务器地址。 ([yingDev的fork](https://github.com/yingDev/websockify) 去掉了base64子协议检查)
	
     ```bash
     ./websockify.py 1999 <rtmp_server>:1935
     ```
- 浏览器
	- 使用 [node-rtmpapi](https://github.com/delian/node-rtmpapi) 解析 RTMP 协议,完成握手和通信。 ([yingDev的fork](https://github.com/yingDev/node-rtmpapi) 增加了浏览器支持、修正了几个错误)

	- 提取其中的 H264 视频流

	- 喂给 [Broadway](https://github.com/mbebenita/Broadway) 解码
	
   	 ```js
    decoder.decode(frame);
   	 ```
    
## 使用
```js
//比如 rtmp://helloworld.com/live/abc ---> app='live', streamName='abc', rtmp_server='helloworld.com'
// ./websockify.py 1999 helloworld.com:1935
var player = new WebRtmpPlayer('ws://127.0.0.1:1999', '<app>', '<streamName>', 'rtmp://<rtmp_server>/<app>');
player.canvas.style['height'] = '100%';
document.getElementById("vidCont").appendChild(player.canvas);
```
    
## 运行
```bash
git clone https://github.com/yingDev/Web-Rtmp.git
cd Web-Rtmp
git submodule update --init --recursive
cnpm install
```

```bash
# set your rtmp params in test.js first, then 
webpack -w
```
```bash
# setup test server
./websockify/websockify.py 1999 <rtmp_server>:1935
```
```bash
open index.html
```

## 局限
- Broadway: 
   <blockquote> The decoder ...does not support weighted prediction for P-frames and CABAC entropy encoding...</blockquote>

 
## 参考资料
- Real-Time Messaging Protocol (RTMP) specification <br>
http://www.adobe.com/devnet/rtmp.html

- FLV and F4V File Format Specification <br>
http://www.adobe.com/devnet/f4v.html

- h264-live-player <br> https://github.com/131/h264-live-player


================================================
FILE: WebRtmpPlayer.js
================================================
var RTMP = require('./node-rtmpapi');
var SimpleWebsocket = require('simple-websocket');
var Buffer = require('buffer').Buffer;

const H264_SEP = new Buffer([0,0,0,1]);
const FRAME_Q_SIZE = 15;

class WebRtmpPlayer
{
	constructor(wsHost, app, streamName, tcUrl)
	{
		this._frameQ = [];
		this._fps = NaN;
		this._lastRenderTime = 0;

		this._decoder = new Decoder();
		this._player = new Player({ useWorker: false });
		this._url = {host: wsHost, app: app, tcUrl: tcUrl, stream: streamName};

		this._decoder.onPictureDecoded = this._onPictureDecoded.bind(this);

		this._rtmpTransId = 0;
		this._invokeChannel = null;
		this._videoChannel = null;
		this._rtmpSession = null;

		this._sock = new SimpleWebsocket(this._url.host);
		this._sock.setMaxListeners(100);
		this._sock.on('connect', ()=>
		{
			new RTMP.rtmpSession(this._sock, true, this._onRtmpSessionCreated.bind(this));
		})
	}

	get canvas() { return this._player.canvas; }

	_onPictureDecoded(buffer, width, height, infos)
	{
		if(this._frameQ.length === FRAME_Q_SIZE)
		{
			console.log("** drop oldest frame!");
			this._frameQ.shift(); //如果播放速度跟不上,扔掉最老那一帧
		}
		this._frameQ.push({data: Buffer.from(buffer), width: width, height: height, canvasObj: this._player.canvasObj});
	}

	_drawFrame()
	{
		var now = new Date();//如果播放速度跟不上网络速度,跳帧
		var skipFrame = Math.floor(Math.abs(now - this._lastRenderTime) / (1000 / this._fps) ) - 1;
		if(this._lastRenderTime && skipFrame > 0)
		{
			console.log("SkipFrmae = " + skipFrame);
			//while(skipFrame-- > 0 && this._frameQ.length > 0) this._frameQ.shift();
		}

		var frame = this._frameQ.shift();
		if(frame)
		{
			this._player.renderFrame(frame);
		}
		this._lastRenderTime = now;
	}

	_rtmpConnect()
	{
		this._rtmpSession.Q.Q(0,() =>
		{
			console.log("sending connect");

			this._invokeChannel.sendAmf0EncCmdMsg({
				cmd: 'connect',
				transId:++this._rtmpTransId,
				cmdObj:
				{
					app: this._url.app,
					tcUrl: this._url.tcUrl,
					fpad: false,
					capabilities: 15.0, //note: 我不知道这些参数什么鬼,依据rtmpdump分析出来的
					audioCodecs: 3191,
					videoCodecs: 252,
					videoFunction: 1.0
				}
			});
			this._invokeChannel.invokedMethods[this._rtmpTransId] = 'connect';
		});
	}

	_rtmpCreateStream()
	{
		this._rtmpSession.Q.Q(0, ()=>
		{
			console.log("sending createStream");
			this._invokeChannel.sendAmf0EncCmdMsg({
				cmd: 'createStream',
				transId: ++this._rtmpTransId,
				cmdObj: null
			});
			this._invokeChannel.invokedMethods[this._rtmpTransId] = 'createStream';
		});
	}

	_rtmpSendPlay(msgStreamId)
	{
		this._rtmpSession.Q.Q(0, ()=>
		{
			this._videoChannel.chunk.msgStreamId = msgStreamId;
			//send play ??
			this._videoChannel.sendAmf0EncCmdMsg({
				cmd: 'play',
				transId: ++this._rtmpTransId,
				cmdObj:null,
				streamName: this._url.stream,
				start:-2

			},0);
			this._invokeChannel.invokedMethods[this._rtmpTransId] = "play";
		});
	}

	_onRtmpSessionCreated(session)
	{
		this._rtmpSession = session;
		console.log("rtmpSession...cb...");
		this._invokeChannel = new RTMP.rtmpChunk.RtmpChunkMsgClass({streamId:5}, {sock: this._sock, Q: session.Q, debug: false});
		this._invokeChannel.invokedMethods = {}; //用来保存invoke的次数,以便收到消息的时候确认对应结果
		this._videoChannel = new RTMP.rtmpChunk.RtmpChunkMsgClass({streamId:8}, {sock: this._sock, Q: session.Q, debug: false});

		session.Q.Q(0,this._rtmpConnect.bind(this));
		session.Q.Q(0, () =>
		{
			console.log("Begin LOOP");
			session.msg.loop(this._handleRtmpMessage.bind(this));
		});
	}

	 _handleRtmpMessage(chunkMsg)
	{
		var chunk = chunkMsg.chunk;
		var msg = chunk.msg;

		console.log("GOT MESSAGE: " + chunk.msgTypeText);
		//console.log("===========>\n" + JSON.stringify(msg));

		if(chunk.msgTypeText == "amf0cmd")
		{
			if(msg.cmd == "_result")
			{
				var lastInvoke = this._invokeChannel.invokedMethods[msg.transId];
				if(lastInvoke)
				{
					console.log("<--Got Invoke Result for: " + lastInvoke);
					delete this._invokeChannel.invokedMethods[msg.transId];
				}

				switch (lastInvoke)
				{
					case 'connect':
						return this._rtmpCreateStream();
					case 'createStream':
						return this._rtmpSendPlay(msg.info);
				}
			}
		}

		if(chunk.msgTypeText == "video")
		{
			//提取h264流
			var chunkData = chunk.data;
			if (chunkData.length > 4)
			{
				if (chunkData[1] === 1)
				{
					chunkData = Buffer.concat([H264_SEP, chunkData.slice(9)]);
				}
				else if (chunkData[1] === 0)
				{
					var spsSize = (chunkData[11] << 8) | chunkData[12];
					var spsEnd = 13 + spsSize;
					chunkData = Buffer.concat([H264_SEP, chunkData.slice(13, spsEnd), H264_SEP, chunkData.slice(spsEnd + 3)]);
				}
				this._decoder.decode(chunkData);
			}
		}

		if(chunk.msgTypeText == "amf0meta" && msg.cmd == 'onMetaData')
		{
			console.log("onmetadata");
			this._fps = chunk.msg['event']['framerate'];
			console.log("fps = "+this._fps);
			setInterval(this._drawFrame.bind(this), 1000.0/this._fps); //todo: clear
		}

		this._rtmpSession.Q.Q(0,()=>
		{
			this._rtmpSession.msg.loop(this._handleRtmpMessage.bind(this));
		});
	}
}

module.exports = WebRtmpPlayer;

================================================
FILE: index.template.html
================================================
<html>
<head>
<title>rtmp test</title>
    <script src="./node_modules/broadway-player/Player/Decoder.js"></script>
    <script src="./node_modules/broadway-player/Player/YUVCanvas.js"></script>
    <script src="./node_modules/broadway-player/Player/Player.js"></script>
    <script src="./node_modules/broadway-player/Player/stream.js"></script>
    <script src="./node_modules/broadway-player/Player/Player.js"></script>
</head>

<body>
	build time: {{buildTime}}

	<div id="vidCont" style="width:640px; height:360px;">
	</div>

	<script type="text/javascript" src="build/bundle.js"></script>
	<script type="text/javascript">
		
	</script>
</body>

</html>


================================================
FILE: misc/do-rtmpsuck.sh
================================================
#!/bin/bash

# enable internal port forwarding:
sudo sysctl -w net.inet.ip.forwarding=1

# apply the pf rules:
echo '
rdr pass log on lo0 proto tcp from en0 to any port 1935 -> 127.0.0.1
pass out on en0 route-to lo0 inet proto tcp from en0 to any port 1935 keep state user != root
' | sudo pfctl -ef -

# check the pf rules:
# sudo pfctl -s all
say "starting r-t-m-p-suck";

sudo rtmpsuck $@;

say "r-t-m-p-suck Stopped.";

# clear the pf rules:
echo ''
echo ''
echo "====== restting pfctl rules ======="
echo ''

sudo pfctl -F all -f /etc/pf.conf

================================================
FILE: misc/reloadChrome.scpt
================================================
on run {targetUrl}
    tell application "Google Chrome"
        activate

        set theUrl to my remove_http(targetUrl)

        if (count every window) = 0 then
            make new window
        end if

        set found to false
        set theTabIndex to -1
        repeat with theWindow in every window
            set theTabIndex to 0

            repeat with theTab in every tab of theWindow
                set theTabIndex to theTabIndex + 1
                set theTabUrl to my remove_http(theTab's URL as string)

                if (theTabUrl contains theUrl) then
                    set found to true
                    exit repeat
                end if

            end repeat

            if found then
                exit repeat
            end if
        end repeat

        if found then
            tell theTab to reload
            set theWindow's active tab index to theTabIndex
            set index of theWindow to 1
        else
            tell window 1 to make new tab with properties {URL:targetUrl}
        end if
    end tell
end run

on remove_http(input_url)
    if (input_url contains "https://") then
         return trim_line(input_url, "https://")
    else
         return trim_line(input_url, "http://")
    end if
    return input_url
end remove_http

-- Taken from: http://www.macosxautomation.com/applescript/sbrt/sbrt-06.html --
on trim_line(this_text, trim_chars)
    set x to the length of the trim_chars
    -- TRIM BEGINNING
    repeat while this_text begins with the trim_chars
        try
            set this_text to characters (x + 1) thru -1 of this_text as string
        on error
            -- the text contains nothing but the trim characters
            return ""
        end try
    end repeat
    return this_text
end trim_line

================================================
FILE: package.json
================================================
{
  "name": "web-rtmp",
  "version": "1.0.0",
  "description": "Play rtmp video stream on the web page",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "yingDev",
  "license": "ISC",
  "dependencies": {
    "babel-preset-es2015": "^6.16.0",
    "broadway-player": "^0.1.1",
    "buffer": "^5.0.0",
    "simple-websocket": "^4.1.0"
  },
  "devDependencies": {
    "babel-core": "^6.17.0",
    "babel-loader": "^6.2.5",
    "babel-preset-es2015": "^6.16.0",
    "webpack": "^1.13.2",
    "webpack-shell-plugin": "^0.4.3",
    "websockify": "^0.7.1"
  }
}


================================================
FILE: test.js
================================================
const WebRtmpPlayer = require('./WebRtmpPlayer');

//note: tcUrl是原始rtmp地址,不含流名称。参见rtmp spec
alert('set your rtmp params in test.js first!');
var player = new WebRtmpPlayer('ws://127.0.0.1:1999', '<app>', '<streamName>', 'rtmp://<rtmp_server>/<app>');
player.canvas.style['height'] = '100%';
document.getElementById("vidCont").appendChild(player.canvas);


================================================
FILE: webpack.config.js
================================================
var webpack = require('webpack');
const WebpackShellPlugin = require('webpack-shell-plugin');

module.exports = {
    entry: "./test.js",
    output: {
        path: __dirname + "/build/",
        filename: "bundle.js"
    },
    devtool: "source-map",
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /(node_modules, build)/,
                loader: 'babel',
	            query: {
		            presets: ['es2015']
	            }
            }
        ]
    },
	externals: {
		"broadway": "broadway-player"
	},
	plugins:[
		new webpack.optimize.DedupePlugin(),
		new WebpackShellPlugin({
			onBuildStart:[/*'say begin'*/],
			onBuildEnd:[
				'sed "s/{{buildTime}}/$(date)/g" index.template.html > index.html',
				//'say end!'
				//'say world; open "/Applications/Google Chrome.app"',
				//'sleep 1; say chrome & osascript ./misc/reloadChrome.scpt "http://localhost:63342/web-rtmp/index.html"'
			],
			dev:false}
		)
	]
};
Download .txt
gitextract_ok_mgr1a/

├── .editorconfig
├── .gitignore
├── .gitmodules
├── .idea/
│   ├── encodings.xml
│   ├── misc.xml
│   ├── modules.xml
│   ├── vcs.xml
│   └── web-rtmp.iml
├── README.md
├── WebRtmpPlayer.js
├── index.template.html
├── misc/
│   ├── do-rtmpsuck.sh
│   └── reloadChrome.scpt
├── package.json
├── test.js
└── webpack.config.js
Download .txt
SYMBOL INDEX (12 symbols across 1 files)

FILE: WebRtmpPlayer.js
  constant H264_SEP (line 5) | const H264_SEP = new Buffer([0,0,0,1]);
  constant FRAME_Q_SIZE (line 6) | const FRAME_Q_SIZE = 15;
  class WebRtmpPlayer (line 8) | class WebRtmpPlayer
    method constructor (line 10) | constructor(wsHost, app, streamName, tcUrl)
    method canvas (line 35) | get canvas() { return this._player.canvas; }
    method _onPictureDecoded (line 37) | _onPictureDecoded(buffer, width, height, infos)
    method _drawFrame (line 47) | _drawFrame()
    method _rtmpConnect (line 65) | _rtmpConnect()
    method _rtmpCreateStream (line 89) | _rtmpCreateStream()
    method _rtmpSendPlay (line 103) | _rtmpSendPlay(msgStreamId)
    method _onRtmpSessionCreated (line 121) | _onRtmpSessionCreated(session)
    method _handleRtmpMessage (line 137) | _handleRtmpMessage(chunkMsg)
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (17K chars).
[
  {
    "path": ".editorconfig",
    "chars": 285,
    "preview": "root = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\n\n\n#"
  },
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": ".gitmodules",
    "chars": 192,
    "preview": "[submodule \"node-rtmpapi\"]\n\tpath = node-rtmpapi\n\turl = https://github.com/yingDev/node-rtmpapi.git\n[submodule \"websockif"
  },
  {
    "path": ".idea/encodings.xml",
    "chars": 159,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"Encoding\">\n    <file url=\"PROJECT\" chars"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 648,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"JavaScriptSettings\">\n    <option name=\"l"
  },
  {
    "path": ".idea/modules.xml",
    "chars": 268,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 245,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": ".idea/web-rtmp.iml",
    "chars": 350,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n"
  },
  {
    "path": "README.md",
    "chars": 1793,
    "preview": "# Web-Rtmp\n在网页上播放RTMP视频流,通过Websocket。\n\n# WARNING ============\n这个项目很大程度上只能算可行性验证,不适合作为 library 使用。试验结论是:用 js 解码视频效率已经够低了,"
  },
  {
    "path": "WebRtmpPlayer.js",
    "chars": 5088,
    "preview": "var RTMP = require('./node-rtmpapi');\nvar SimpleWebsocket = require('simple-websocket');\nvar Buffer = require('buffer')."
  },
  {
    "path": "index.template.html",
    "chars": 659,
    "preview": "<html>\n<head>\n<title>rtmp test</title>\n    <script src=\"./node_modules/broadway-player/Player/Decoder.js\"></script>\n    "
  },
  {
    "path": "misc/do-rtmpsuck.sh",
    "chars": 547,
    "preview": "#!/bin/bash\n\n# enable internal port forwarding:\nsudo sysctl -w net.inet.ip.forwarding=1\n\n# apply the pf rules:\necho '\nrd"
  },
  {
    "path": "misc/reloadChrome.scpt",
    "chars": 1788,
    "preview": "on run {targetUrl}\n    tell application \"Google Chrome\"\n        activate\n\n        set theUrl to my remove_http(targetUrl"
  },
  {
    "path": "package.json",
    "chars": 596,
    "preview": "{\n  \"name\": \"web-rtmp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Play rtmp video stream on the web page\",\n  \"scripts\": {\n"
  },
  {
    "path": "test.js",
    "chars": 354,
    "preview": "const WebRtmpPlayer = require('./WebRtmpPlayer');\n\n//note: tcUrl是原始rtmp地址,不含流名称。参见rtmp spec\nalert('set your rtmp params "
  },
  {
    "path": "webpack.config.js",
    "chars": 982,
    "preview": "var webpack = require('webpack');\nconst WebpackShellPlugin = require('webpack-shell-plugin');\n\nmodule.exports = {\n    en"
  }
]

About this extraction

This page contains the full source code of the yingDev/Web-Rtmp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (13.6 KB), approximately 4.6k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!