Node 下 Http Streaming 的跨浏览器实现

Node 下 Http Streaming 的跨浏览器实现 - CNode

Node 下 Http Streaming 的跨浏览器实现

最近考虑把整个前端架构使用http streaming方式实现


对这方面做了一下调研,并在在node上实现了一个简单的原型

 

顺带提一下,


楼下pengchun同学所提到的node chat使用的是longpoll的模型


和httpstreaming同属与http comet的解决方案.


不过在具体http连接的处理上有所不同


long poll在数据通道每次接收到一个数据包后即关闭连接,并立即重新打开数据通道


http streaming的数据通道始终处于打开状态.


具体的介绍可以看这里 http://en.wikipedia.org/wiki/Comet_(programming)#Streaming

 

一些细节:


由于ie下在xhr readystate=3时无法取得responseText,


因此在ie下改为通过使用htmlfile控件调用iframe实现


另在输出正式数据前需现输出1k的噪音数据


以解决浏览器的阻塞问题

 

原型功能设计如下

 

pipe.png

 

具体代码如下

 

pipe.js: 主服务程序



var http = require('http'),

	fs = require('fs'),

	url = require('url'),

	page = null;

// static files read & watch

var readFile = function(files) {

	var buffers = {};

	// sync read

	var fread = function(file, cb){

		fs.readFile(file, 'binary', function(err, data){

			if (err) {

				throw err;

			}

			buffers[file] = new Buffer(data, 'binary');

			console.log('load', file)

		});

	}

	// watch changes

	var watch = function watch(file) {

		fs.watchFile(file, {persistent: true, interval: 100}, function (curr, prev) {

			if (curr.mtime.getTime() != prev.mtime.getTime()) {

				fread(file);

			}

		});

	}

	// run all files

	for (var i = 0; i < files.length; i++) {

		watch(files[i]);

		fread(files[i]);

	}

	return buffers;

}

// http query

var httpQuery = function(u, cb){

	console.log('http begin');

	// parse url

	var uinfo = url.parse(u);

	// create client

	var client = http.createClient(uinfo.port ? uinfo.port : 80, uinfo.hostname, false);

	var uri = uinfo.pathname + (uinfo.search ? uinfo.search : '');

	var req = client.request('GET', uri, {'host': uinfo.hostname});

	// send request

	req.end();

	console.log('http request sent');





	var len = 4096;

	var pointer = 0;

	var extendFactor = 2;

	// response start

	req.on('response', function (res) {

		if (res.headers['content-length']) {

			len = parseInt(res.headers['content-length']);

		}

		// body init

		var body = new Buffer(len);

		// chunk recived

		res.on('data', function(chunk){

			// extends

			if (pointer + chunk.length > len) {

				len *= extendFactor;

				body = body.copy(new Buffer(len), 0, 0);

				console.log('proxy extend to', len);

			}

			// copy chunk to buf

			chunk.copy(body, pointer, 0);

			// move pointer

			pointer += chunk.length;

		})

		// response end

		res.on('end', function() {

			cb(body.length > pointer ? body.slice(0, pointer) : body);

			console.log('proxy end', pointer);

		});

	})

}



// main server

var server = http.createServer(function (req, res){

	// main page

	if (req.url == '/') {

		res.writeHeader(200);

		res.end(page["pipe.html"]);

	// time serve

	} else if (req.url == '/time') {

		res.writeHeader(200);

		res.end(new Date().toString());

	// iframe recv

	} else if (req.url.match(/^\/iframe\//)) {

		var clientid = parseInt(req.url.substr(8));

		pipeClient.add(clientid, res, pipeClient.iframe);

		console.log('iframe connect', clientid);

	// ajax recv

	} else if (req.url.match(/^\/ajax\//)) {

		var clientid = parseInt(req.url.substr(6));

		pipeClient.add(clientid, res, pipeClient.ajax);

		console.log('ajax connect', clientid);

	// request listen

	} else if (req.url.match(/^\/req\//)) {

		res.writeHeader(200,{

			'Cache-Control': 'no-cache, must-revalidate'

		});

		res.end();

		// url parse

		var clientid = parseInt(req.url.substr(5, 13));

		// get page

		httpQuery("http://localhost:8000/time", function (data){

			console.log(data.toString());

			pipeClient.write(clientid, data);

			console.log("write", clientid, data.length);

		});

	// error pages

	} else {

		res.writeHeader(404, {"Content-Type" : "text/html"});

		res.end();

	}

});



var pipeClient = {

	timeout : 30000,

	client : {},

	prefix : "",

	iframe : 'iframe',

	ajax : 'ajax',

	noise : null,

	noiseSize : 1024,

	page : null,

	init : function(){

		this.noise = new Buffer(1024);

		for (var i = 0; i < this.noiseSize; i++) {

			this.noise[i] = 32;

		}

		this.page = readFile(['iframe.html']);

	},

	add : function(id, res, type) {



		if (type == this.ajax) {

			res.writeHeader(200, {

				'Cache-Control': 'no-cache, must-revalidate'

			});

			res.write(this.noise);

		} else {

			res.writeHeader(200, {

				"Content-Type" : "multipart/x-mixed-replace",

				'Cache-Control': 'no-cache, must-revalidate'

			});

			res.write(this.page['iframe.html']);

			res.write(this.noise);

		}

		this.client[id] = {

			res : res,

			type : type,

			tm : setTimeout(function(){

				pipeClient.close(id);

			}, this.timeout)

		};

	},

	close : function (id) {

		console.log("client close", id)

		this.client[id].res.end();

		this.client[id].res = null;

		delete this.client[id];

	},

	write : function (id, data) {

		clearTimeout(this.client[id].tm);

		this.client[id].tm = setTimeout(function(){

			pipeClient.close(id);

		}, this.timeout);

		this.client[id].res.write(this.format(data, this.client[id].type));



	},

	format : function(data, type) {

		// with iframe

		if (type == this.iframe) {

			var buf = new Buffer(this.prefix.length + data.length + this.suffix.length);

			buf.write(this.prefix, 0, 'binary');

			data.copy(buf, this.prefix.length, 0);

			buf.write(this.suffix, this.prefix.length + data.length);

		// with ajax

		} else {

			var buf = new Buffer(data.length + 8);

			// set length

			buf.write(data.length.toString(16), 0, 'binary');

			// space padding

			for (var i = data.length.toString(16).length; i < 8; i++) {

				buf[i] = 32;

			}

			// set data

			data.copy(buf, 8, 0);

		}

		console.log(buf.toString());

		return buf;

	}

}

pipeClient.init();



page = readFile(['pipe.html']);

setTimeout(function(){

	server.listen(8000);

}, 500);




pipe.html: 客户端程序










Comet Pipe Demo




















 

iframe.html: ie下iframe模式运行的输出头











标签:


原创文章


qingdu 在 2011-1-21 15:28发布


qingdu 在 2012-1-19 12:10重新编辑

 
分享到 weibo

4 回复

#1
pengchun

developerworks上的一篇经典的文章:

http://www.ibm.com/developerworks/cn/web/wa-lo-comet/


pengchun 在 2011-1-21 21:24回复


 

 

#2
anonymous

30秒后由服务器端向客户端传输 数据的通道就关闭了?


anonymous 在 2011-1-24 16:27回复


 

{1}

 

qingdu

对, 这里是开发时为了方便,避免浏览器的http并发上限阻塞用的

实际系统中可以去掉这块

或者在client中增加重连的功能


qingdu 在 2011-1-25 14:17回复


 

 

#3
suqian

原来就在这里。。。先收藏


suqian 在 2011-2-16 22:09回复


 

 

#4
suqian

pipe通道在一段时间没有数据返回将会中断,服务器端再向它发送数据就会无效了。监听response的error事件又无法捕获到错误事件,这样会导致坏死的client链接越来越多。需要一种机制来处理这个问题。


suqian 在 2011-2-18 17:48回复

 

posted on 2012-05-01 22:53  lexus 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/lexus/archive/2012/05/01/2478295.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Vue.js 是一个流行的前端框架,它通过使用组件化的开发方式,使得构建用户界面更加简单和可扩展。但是,Vue.js 本身并不直接支持播放 RTSP 流这样的功能。要实现浏览器无插件播放 RTSP 流,我们需要借助一些其他的技术和插件。 一种常用的方法是使用 hls.js 或者 flv.js 这样的 JavaScript 库来处理 RTSP 流。这两个库可以通过将 RTSP 流转换为 HLS 或者 FLV 格式来在浏览器中播放。我们可以通过 npm 或者 yarn 安装这些库,并在 Vue.js 项目中使用。 首先,我们需要在 Vue.js 项目中安装 hls.js 或者 flv.js。可以通过运行像 `npm install hls.js` 或者 `yarn add flv.js` 这样的命令来安装它们。 然后,在 Vue 组件中使用这些库来播放 RTSP 流。我们可以在组件的 `mounted` 生命周期钩子中进行初始化设置和播放操作。这通常涉及到创建一个播放器实例,并指定 RTSP 流的 URL。 import Hls from 'hls.js'; // 或者 import flvjs from 'flv.js'; export default { mounted() { // 使用 hls.js 的示例代码 const video = this.$refs.video; // 获取 video 元素的引用 const hls = new Hls(); hls.loadSource('rtsp://example.com/stream'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function() { video.play(); }); // 使用 flv.js 的示例代码 const flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'rtsp://example.com/stream', }); flvPlayer.attachMediaElement(video); flvPlayer.load(); flvPlayer.play(); }, }; 根据不同的使用情况和具体需求,需要选择适合的库和对应的初始化设置。需要注意的是,这些库可能有不同的兼容性和功能限制,因此在选择和使用时需要参考它们的文档和示例。 总结起来,如果要在 Vue.js 项目中实现浏览器无插件播放 RTSP 流,我们可以使用 hls.js 或者 flv.js 这样的 JavaScript 库。通过安装这些库,并在 Vue 组件中使用它们的示例代码,我们可以实现 RTSP 流的播放功能。 ### 回答2: 在Vue中实现浏览器无插件播放RTSP流,可以通过以下步骤实现: 1. 首先,需要在Vue项目中引入一个能够处理RTSP流的JavaScript库,如hls.js或者node-rtsp-stream。 2. 在Vue组件中,使用该库提供的API,创建一个RTSP流播放器实例。 3. 在Vue组件的模板中,使用 `<video>` 标签来显示播放器的画面,并给它一个唯一的ID。 4. 在Vue组件的生命周期方法中,通过对播放器实例的配置,将RTSP流链接传入播放器,并将视频画面绑定到 `<video>` 标签上。 5. 可以通过一些操作控件按钮,如播放、暂停、停止等来控制播放器的行为。 6. 如果需要实现一些特殊效果,如实时更新进度条、显示播放时间等,可以对播放器实例进行监听,并在触发相应事件时更新相关的组件数据。 需要注意的是,由于RTSP流是一种实时传输的协议,要在浏览器中实时播放RTSP流,并非常困难。因为浏览器对RTSP协议的支持有限,通常需要借助第三方库来实现。同时,RTSP协议也会受到网络环境的影响,如果网络状况不好,可能会导致播放不流畅或者中断。 以上是实现浏览器无插件播放RTSP流的一般步骤,具体实现方法还需要根据具体的开发环境和需求来定制。在实际开发中,可以参考相关的文档和示例代码,来更好地理解和应用。 ### 回答3: Vue是一种用于构建用户界面的JavaScript框架,可以用于在浏览器中创建交互式的Web应用程序。Vue本身并不直接支持播放rtsp流,因为rtsp是一种实时流传输协议,而不是普通的HTML5视频格式。 要在浏览器中无需插件播放rtsp流,可以借助第三方插件或库来实现。一种常见的解决方案是使用ffmpeg.js和hls.js。 首先,采用ffmeg.js将rtsp流转换为hls流。ffmpeg.js是一个开源的JavaScript版本的ffmpeg,可以在浏览器中进行视频转码和处理。 使用ffmpeg.js将rtsp流转换为hls流的代码片段如下: ``` ffmpeg({ arguments: ['-i', 'rtsp://stream_url', '-f', 'hls', 'output.m3u8'] }) .then((output) => { // 转换完成 console.log('转换成功'); }) .catch((error) => { // 转换失败 console.error('转换失败'); }); ``` 然后,使用hls.js库将生成的hls流播放在浏览器中。hls.js是一个开源的JavaScript库,用于在浏览器中播放HLS(HTTP Live Streaming)流。 在Vue中使用hls.js播放hls流的代码片段如下: ``` <template> <div> <video ref="hlsPlayer" controls></video> </div> </template> <script> import Hls from 'hls.js'; export default { mounted() { if (Hls.isSupported()) { const video = this.$refs.hlsPlayer; const hls = new Hls(); hls.loadSource('http://path_to_hls_stream.m3u8'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); }); } }, }; </script> ``` 这样,借助ffmpeg.js和hls.js,我们可以在Vue中实现浏览器无需插件播放rtsp流。请注意,转换rtsp流为hls流和使用hls.js播放hls流可能需要一定的服务器端配置和前端代码调整,具体实现会根据实际情况有所变化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值