FlashPlayer在执行NetStream.play的时候崩溃的解决办法

FlashPlayer在执行NetStream.play的时候崩溃的解决办法

这是个隐藏非常深的BUG,我都怀疑如果再做一次,我能不能把它找出来。它耗费了我宝贵的三天时间,三天啊……

BUG表现

在使用NetStream连接FMS发布的流时,在执行NetStream.play('streamName')方法时,FlashPlayer会崩溃。独立版、调试版以及基于浏览器的插件版均如此。

但是,这还不是全部。必须满足以下几点,该BUG才会出现。

  • 使用Windows 7操作系统。也就是说,Windows XP不会出现这个问题;
  • 播放的必须是RTMP流,RTMP流可以由Flash Media Server或者Red5来发布。也就是说,使用NetStream播放本地的flv/f4v/mp4视频不会出现这个问题;
  • 播放的流包含音频。也就是说,如果该流只包含视频,不会出现这个问题;
  • 播放的流中包含的音频声音较大。也就是说,即使该流包含音频,但如果发布方没有发出声音,或者发出的声音很小,该问题不会出现;当然,不需要很大的声音就能让播放端立即崩溃;
  • 使用了Frame标签来做预加载。不了解Frame标签预加载的,看这篇文章:Preloaders in AS3;
  • 在预加载完毕之后,使用removeChild移除了预加载类的实例(BUG就在这里)。

开发和测试平台(出现BUG的平台)

  • Flex SDK 4.5.1
  • Flash Media Server 4.0
  • Flash Player 10.3独立版/调试版/插件版
  • Windows 7 旗舰版
  • Chrome12/Opera11.5/Firefox5/IE9

BUG再现

我写了两个简单的Demo(一个发布端,一个接收端)来重现这个BUG。Demo需要FMS的支持。

错误的重点在于预加载类(PreloaderNSPlay.as)。由于预加载类在完成加载后就不再需要,一般的处理方法是将其从Stage中移除。只要将移除,就会出现这个BUG(并非移除后立即出现,而是在接收音频流的视频出现)。而如果使用visible将预加载类隐藏,就不会出现这个问题。

Demo的使用方法(服务端以FMS为例):

  1. 安装FMS,在安装目录下建立 /applications/testspeed/ 文件夹;
  2. 编译NSPulish和NSPlay,或者 在这里直接下载
  3. 确认本机安装了摄像头和麦克风,运行NSPublish.swf,单击“连接”按钮,查看log信息确定连接正常,见下图: 发布流
  4. 运行NSPlay.swf,单击“连接”按钮,查看log信息确认连接正常。此时会看到发布端的摄像头视频。如果FlashPlaye没有崩溃的话,就向着麦克风吹口气……呼……整个世界清静了…… 播放流

下面只贴出了 PreloaderNSPlay.as 的源码,需要整个项目源码可以在 这里 下载。

PreloaderNSPlay.as

 1package
 2{
 3import flash.display.MovieClip;
 4import flash.display.DisplayObject;
 5import flash.display.StageAlign;
 6import flash.display.StageScaleMode;
 7import flash.events.Event;
 8import flash.events.ProgressEvent;
 9import flash.text.TextField;
10import flash.text.TextFormat;
11import flash.utils.getDefinitionByName;
12
13public class PreloaderNSPlay extends MovieClip 
14{
15	public function PreloaderNSPlay()
16	{
17		_mainClassName = 'NSPlay';
18		stage.scaleMode = StageScaleMode.NO_SCALE;
19		stage.align = StageAlign.TOP_LEFT;
20		stage.showDefaultContextMenu = false;
21		_tf = new TextField();
22		_tf.defaultTextFormat = new TextFormat(null,12,0,null,null,null,null,null,"center");
23		_tf.mouseEnabled = false;
24		_tf.height = 20;
25		_tf.x = (stage.stageWidth-_tf.width)*.5;
26		_tf.y = stage.stageHeight*.5;
27		this.addChild(_tf);
28
29		this.loaderInfo.addEventListener(ProgressEvent.PROGRESS,progress);
30		this.loaderInfo.addEventListener(Event.COMPLETE,complete);
31	}
32
33	protected var _tf:TextField;
34	protected var _mainClassName:String;
35	
36	private function progress(e:ProgressEvent):void
37	{
38		_tf.text = int(e.bytesLoaded/e.bytesTotal*100)+"% 载入中……";
39	}
40
41	private function complete(e:Event):void
42	{
43		gotoAndStop(2);
44		var mainClass:Class = Class(getDefinitionByName(_mainClassName));
45		stage.addChild(new mainClass() as DisplayObject);
46		destroy();
47	}
48
49	private function destroy():void
50	{
51		this.loaderInfo.removeEventListener(ProgressEvent.PROGRESS,progress);
52		this.loaderInfo.removeEventListener(Event.COMPLETE,complete);
53		//将预加载类从舞台移除(parent.removeChild也一样,因为parent就是舞台),就会导致Flash Player崩溃
54		stage.removeChild(this);
55		//parent.removeChild(this);
56
57		//如果只移除显示进度的文本,或者只将自身隐藏而不移除,就不会出现这个Bug
58		//this.removeChild(_tf);
59		//this.visible = false;
60	}
61}
62}