FluorineFx:视频录制及回放(Flash/AS3环境)

如果不考虑安全因素(指任何人都可连接FluorineFx进行视频录制,而不需要登录认证),其实服务端不用写一行代码,仅需要在apps目录下建一个子目录当作应用,以及在services-config.xml中配置一下rtmp的Channel即可

下面这段flash客户端的as3代码,是从FluorineFx官方的Flash AS2示例修改而来的(当然:只一个示例,细节还有很多可优化的地方)

package 
{
	import fl.controls.Button;
	import fl.controls.Label;
	import fl.controls.TextInput;
	import fl.controls.CheckBox;
	import flash.display.Sprite;
	import flash.utils.Timer;
	import flash.events.ActivityEvent;
	import flash.events.TimerEvent;
	import flash.events.MouseEvent;
	import flash.events.NetStatusEvent;
	import flash.events.StatusEvent;
	import flash.media.Camera;
	import flash.media.Microphone;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.media.Video;


	public class VideoRecord extends Sprite
	{
		private var _btnRecord:Button;
		private var _btnPlay:Button;
		private var _btnConnect:Button;
		private var _txtVideoFileName:TextInput;
		private var _chkAppend:CheckBox;
		private var _txtServerUrl:TextInput;
		private var _lblResult:Label;
		private var _nc:NetConnection = null;
		private var _nsPublish:NetStream = null;
		private var _nsPlay:NetStream = null;
		private var _ncPlay:NetConnection = null;
		private var _camera:Camera;
		private var _microphone:Microphone;
		private var _videoRecord:Video;
		private var _videoPlay:Video;
		private var _videoIsWorked = false;
		private var _timer:Timer;


		public function VideoRecord()
		{
			init();
		}

		private function init():void
		{

			this._btnRecord = this.btnRecord;
			this._txtVideoFileName = this.videoFileName;
			this._chkAppend = this.chk1;
			this._chkAppend.label = "追加";
			this._btnPlay = btnPlay;
			this._btnConnect = btnConnect;
			this._txtServerUrl = this.txtServerUrl;
			this._lblResult = lblResult;
			this._btnRecord.enabled = false;
			this._btnRecord.label = "录制";
			this._txtVideoFileName.enabled = false;
			this._chkAppend.enabled = false;
			this._btnPlay.enabled = false;
			this._btnPlay.label = "播放";
			this._btnConnect.label = "连接";
			this._txtServerUrl.text = "rtmp://localhost/VideoRecording";
			this._videoRecord = videoRecord;
			this._videoPlay = videoPlay;
			this._btnConnect.addEventListener(MouseEvent.CLICK, doConnect);
		}

		//连接服务器;
		private function doConnect(e:MouseEvent):void
		{

			if (this._nc == null)
			{
				this._nc = new NetConnection  ;
				this._nc.addEventListener(NetStatusEvent.NET_STATUS, ncNetStatus);
			}
			this._nc.connect(this._txtServerUrl.text);
			this._lblResult.text = "服务器连接中...";
		}

		//关闭与服务器的连接
		private function doCloseConn(e:MouseEvent):void
		{
			if (this._nc != null)
			{
				if (this._nsPublish != null)
				{
					this._nsPublish.attachCamera(null);
				}
				this._videoRecord.attachCamera(null);
				this._videoRecord.attachNetStream(null);
				this._videoRecord.clear();
				this._nc.close();				
				this._btnConnect.label = "连接";
				this._btnRecord.enabled = false;
				this._txtVideoFileName.enabled = false;
				this._chkAppend.enabled = false;
				this._videoRecord.clear();

				this._btnConnect.removeEventListener(MouseEvent.CLICK,doCloseConn);
				this._btnConnect.addEventListener(MouseEvent.CLICK,doConnect );

				stopPublish();
			}
		}

		//检测conn对象的状态变化
		private function ncNetStatus(e:NetStatusEvent):void
		{
			//trace(e.info.code);
			if (e.info.code == "NetConnection.Connect.Success")
			{
				//连接成功
				this._lblResult.text = "服务器已经连接!";
				this._btnConnect.label = "断开";
				this._btnConnect.removeEventListener(MouseEvent.CLICK,doConnect);
				this._btnConnect.addEventListener(MouseEvent.CLICK, doCloseConn);
				this._camera = Camera.getCamera();
				if (_camera == null)
				{
					this._lblResult.text = "未安装摄像头!";
					return;
				}

				_camera.addEventListener(StatusEvent.STATUS, cameraStatusHandler);
				_camera.addEventListener(ActivityEvent.ACTIVITY, cameraActivityHandler);
				this._videoRecord.attachCamera(this._camera);

				//点击"断开"后后,又重新点击"连接";
				if (_videoIsWorked)
				{
					//恢复控件的可用性;
					this._txtVideoFileName.enabled = true;
					this._chkAppend.enabled = true;
					this._btnRecord.enabled = true;

					if (this._txtVideoFileName.text == "")
					{
						//this._txtVideoFileName.text = Math.round(Math.random() * 10000).toString();
						this._txtVideoFileName.text = "demo";
					}
					this._btnRecord.label = "录制";
					this._btnRecord.removeEventListener(MouseEvent.CLICK,prepareStopRecord);
					this._btnRecord.addEventListener(MouseEvent.CLICK, startRecord);
				}
			}
			else if (e.info.code == "NetConnection.Connect.Closed")
			{
				this._lblResult.text = "服务器连接已关闭!";
			}
			else
			{
				this._lblResult.text = "错误-服务器连接失败!";
			}
		}

		//用户选择是否摄像头时触发
		function cameraStatusHandler(e:StatusEvent):void
		{
			//trace(e);
			if (e.code == "Camera.Muted")
			{
				this._lblResult.text = "您不允许使用摄像头!";
			}
			else if (e.code == "Camera.Unmuted")
			{
				this._lblResult.text = "摄像头视频获取中...";
				_timer = new Timer(100,20);//每隔100ms检测摄像头状态,一共检测20次  
				cameraActivityHandler(null);
			}
		}

		//摄像头有活动时被触发  
		private function cameraActivityHandler(e:ActivityEvent):void
		{
			//trace("cameraActivityHandler被调用!");
			if (! _videoIsWorked)
			{
				if (_timer != null)
				{
					_timer.addEventListener(TimerEvent.TIMER, checkCamera);
					_timer.addEventListener(TimerEvent.TIMER_COMPLETE, checkCameraComplete);
					_timer.start();
					//trace("_timer已经启动!");
				}
			}
		}


		//timer回调函数,用于检测摄像头设备是否正确
		function checkCamera(e:TimerEvent):void
		{
			this._lblResult.text = "摄像头视频获取中...";
			if (this._camera.currentFPS > 0)
			{
				_timer.stop();
				_videoIsWorked = true;
				this._lblResult.text = "摄像头工作正常";

				//恢复控件的可用性;
				this._txtVideoFileName.enabled = true;
				this._chkAppend.enabled = true;
				this._btnRecord.enabled = true;

				if (this._txtVideoFileName.text == "")
				{
					//this._txtVideoFileName.text = Math.round(Math.random() * 10000).toString();
					this._txtVideoFileName.text = "demo";
				}
				this._btnRecord.addEventListener(MouseEvent.CLICK, startRecord);
			}
		}

		//开始录制
		private function startRecord(e:MouseEvent):void
		{
			//trace("开始录制,_nsPublish=",_nsPublish);
			if (this._nsPublish == null)
			{
				//trace("重新创建ns");
				_nsPublish = new NetStream(this._nc);
			}
			this._nsPublish.attachCamera(this._camera);
			this._nsPublish.publish(this._txtVideoFileName.text, this._chkAppend.selected ? "append" : "record");
			this._nsPublish.addEventListener(NetStatusEvent.NET_STATUS, nsPublishNetStatus);
			//缓冲20秒;
			this._nsPublish.bufferTime = 20;



		}

		private function nsPublishNetStatus(e:NetStatusEvent):void
		{
			//trace(e.info.code);
			if (e.info.code == "NetStream.Play.StreamNotFound" || e.info.code == "NetStream.Play.Failed" || e.info.code == "NetStream.Publish.BadName")
			{
				this._lblResult.text = "推送失败,原因:" + e.info.code;

			}
			else if (e.info.code == "NetStream.Record.Start" || e.info.code == "NetStream.Buffer.Empty")
			{
				//录制开始
				this._btnRecord.removeEventListener(MouseEvent.CLICK, startRecord);
				this._btnRecord.addEventListener(MouseEvent.CLICK, prepareStopRecord);
				this._lblResult.text = "正在录制...";
				this._btnRecord.label = "停止";

				//录制时,禁止回放
				this._btnPlay.enabled = false;
				//this._btnPlay.label = "播放";
				this._btnPlay.removeEventListener(MouseEvent.CLICK,this.doStopPlay);
				this._btnPlay.addEventListener(MouseEvent.CLICK,this.doPlay);
			}
		}

		private function nsPlayNetStatus(e:NetStatusEvent):void
		{
			//trace(e.info.code);
			//失败
			if (e.info.code == "NetStream.Play.StreamNotFound" || e.info.code == "NetStream.Play.Failed")
			{

			}
			else if (e.info.code=="NetStream.Play.Start")
			{

			}
		}

		private function doStopPlay(e:MouseEvent):void
		{
			if (this._nsPlay != null)
			{
				this._videoPlay.attachNetStream(null);				
				this._videoPlay.clear();
				this._ncPlay.close();
				this._ncPlay = null;
				this._nsPlay.close();
				this._nsPlay = null;				
				
				this._btnPlay.label = "播放";
				this._btnPlay.removeEventListener(MouseEvent.CLICK,doStopPlay);
				this._btnPlay.addEventListener(MouseEvent.CLICK,doPlay);
				//trace("已经停止!");
			}

		}


		function doPlay(e:MouseEvent):void
		{
			if (this._ncPlay == null)
			{
				this._ncPlay = new NetConnection();
				this._ncPlay.addEventListener(NetStatusEvent.NET_STATUS,ncPlayNetStatus);
				this._ncPlay.connect(this._txtServerUrl.text);
			}
		}


		private function ncPlayNetStatus(e:NetStatusEvent):void
		{
			if (e.info.code == "NetConnection.Connect.Success")
			{
				if (this._nsPlay == null)
				{
					//trace("_nsPlay已经创建!");
					this._nsPlay = new NetStream(this._ncPlay);
					this._nsPlay.addEventListener(NetStatusEvent.NET_STATUS, nsPlayNetStatus);
					
					var _client:Object = new Object();
					_client.onMetaData = nsPlayOnMetaData;
					_client.onPlayStatus = nsPlayOnPlayStatus;
					this._nsPlay.client = _client;
					this._nsPlay.bufferTime = 5;

					this._nsPlay.play(this._txtVideoFileName.text);
					this._videoPlay.attachNetStream(this._nsPlay);
					this._btnPlay.label = "停止";
					this._btnPlay.removeEventListener(MouseEvent.CLICK,doPlay);
					this._btnPlay.addEventListener(MouseEvent.CLICK,doStopPlay);
				}

			}
		}

		private function nsPlayOnMetaData(e:Object):void
		{
			trace("onmetaData:" + e.duration);
		}

		private function nsPlayOnPlayStatus(e:Object):void
		{
			trace("onPlayStatus:" + e.code);

			if (e.code == "NetStream.Play.Complete")
			{
				trace("播放已经停止");
				this._videoPlay.attachNetStream(null);
				this._videoPlay.clear();
				this._btnPlay.label = "播放";
				this._btnPlay.removeEventListener(MouseEvent.CLICK,doStopPlay);
				this._btnPlay.addEventListener(MouseEvent.CLICK,doPlay);
				
				this._ncPlay.close();
				this._ncPlay = null;
				this._nsPlay.close();
				this._nsPlay = null;
			}
		}

		//准备停止录制
		private function prepareStopRecord(e:MouseEvent)
		{

			this._nsPublish.attachCamera(null);
			var _bufferLength = this._nsPublish.bufferLength;
			//必须等当前缓冲区中的数据全部发送完以后再正式停止
			if (_bufferLength > 0)
			{
				this._btnRecord.label = "稍等...";
				this._lblResult.text = "正在保存,请稍候...";
				//每0.1秒检查一次
				_timer = new Timer(100);
				_timer.addEventListener(TimerEvent.TIMER, doWait);
				_timer.start();
			}
			else
			{
				//trace("停止!");
				stopPublish();
			}
		}

		//停止发布(录制)
		private function stopPublish():void
		{
			if (this._nsPublish != null)
			{
				this._nsPublish.removeEventListener(NetStatusEvent.NET_STATUS, nsPublishNetStatus);
				this._nsPublish.close();
				this._nsPublish = null;
			}
			this._btnRecord.label = "录制";
			this._lblResult.text = "";
			this._btnRecord.removeEventListener(MouseEvent.CLICK, prepareStopRecord);
			this._btnRecord.addEventListener(MouseEvent.CLICK, startRecord);


			//允许回放;
			this._btnPlay.enabled = true;
			this._btnPlay.label = "播放";
			this._btnPlay.removeEventListener(MouseEvent.CLICK,doStopPlay);
			this._btnPlay.addEventListener(MouseEvent.CLICK,doPlay);
		}



		//等待录制视频缓冲区的数据全部保存到服务器上;
		private function doWait(e:TimerEvent)
		{
			var _bufferLength = this._nsPublish.bufferLength;
			if (_bufferLength <= 0)
			{
				_timer.removeEventListener(TimerEvent.TIMER, doWait);
				_timer.stop();
				_timer = null;
				stopPublish();
			}
		}

		function checkCameraComplete(e:TimerEvent):void
		{
			this._lblResult.text = "设备无法使用(有可能被占用)";
			_timer.removeEventListener(TimerEvent.TIMER, checkCamera);
			_timer.removeEventListener(TimerEvent.TIMER_COMPLETE, checkCameraComplete);
			_timer = null;
		}

	}
}

界面:

示例源代码下载:http://cid-2959920b8267aaca.office.live.com/self.aspx/Flash/VideoRecording.rar

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

Flash/Flex学习笔记(29):MovieClip帧/时间轴的控制

在开发过程中,经常会遇到要从一个界面跳到另一个界面的情况,比如:软件中的窗口跳转,web开发中的页面跳转...但在Flash的世界里:只有帧,没有窗口与页面,所...

21170
来自专栏听雨堂

从MapX到MapXtreme2004[2]-图层操作

Mapx中基本的图层操作还是比较简单的,集中在对Layers和Layer的处理上,对别的没有太多要求。   在MapXtreme中,要完成类似功能,发生了一点...

22980
来自专栏Golang语言社区

使用Go开发一个简单的服务器程序

最近有个小项目,需要一个简单的后台程序来支撑,本来想用Nodejs来做,但是由于本人js一直很菜,并且很讨厌callback,虽然我也很喜欢异步模型,但我一直都...

30280
来自专栏林德熙的博客

Sublime Text 安装中文、英文字体 YaHei Consolas HybridSourceCodeProYahei Monaco Hybird 混合字体做字体

在 Sublimte Text 如何使用默认的字体,英文好看,但是中文不好,所以我就找了一个支持中文英文的字体。

2.2K20
来自专栏Golang语言社区

使用Go开发一个简单的服务器程序

最近有个小项目,需要一个简单的后台程序来支撑,本来想用Nodejs来做,但是由于本人js一直很菜,并且很讨厌callback,虽然我也很喜欢异步模型,但我一直都...

38390
来自专栏QQ音乐技术团队的专栏

一个循环动画引起的内存泄露问题总结

本文主要记录项目中遇到的一个内存泄露问题:由于一个循环动画引起的内存泄露,并且这个问题也是偶现的,在后面的隐藏问题里会说明。

46620
来自专栏一个爱瞎折腾的程序猿

dotnet使用Selenium执行自动化任务

源码地址:https://coding.net/u/yimocoding/p/WeDemo/git/tree/SeleniumDemo/SeleniumDemo

13510
来自专栏向治洪

iOS开发技巧篇

在iOS开发中,有一些技巧可以提高程序猿的开发效率。 1,Xcode真机调试 Xcode 7推出之前,想要真机调试,iOS开发者必须花$99购买苹果开发者账号,...

27490
来自专栏Clive的技术分享

修改CentOS服务器时间为北京时间

91940
来自专栏GIS讲堂

OL3中结合Jquery UI实现图层拖动并改变图层顺序

12220

扫码关注云+社区

领取腾讯云代金券