2011年11月24日星期四

js中的preventDefault

官方的解释: Cancels the event if it is cancelable, without stopping further propagation of the event.

比如要禁止已经点击的link再点击时刷新页面(通常我们在编辑器中不希望这样,页面会被清空),可以用类似下面代码:

<script type="text/javascript">
    $('#navigation').find("li.active a").click(function(evt) {
         evt.preventDefault();
     });
</script>

下面是两个例子

Please click on the checkbox control.

Please enter your name using lowercase letters only.

代码:

<script type="text/javascript">
  
      
    function stopDefAction(evt) {  
      evt.preventDefault();  
    }  
 
   function checkName(evt) {  
     var charCode = evt.charCode;  
    
     if (charCode != 0) {  
       if (charCode < 97 || charCode > 122) {  
         evt.preventDefault();  
         alert("Please use lowercase letters only." + "\n"  
           + "charCode: " + charCode + "\n"  
        );  
       }  
    }
 
 }  

special thanks htmlencode

2011年11月18日星期五

关于flex build中的network monitor

之前猜测过其工作原理,发现了一篇文章介绍了,和之前的猜测一样,HOOK了API。

Network monitor works by conditionally enabling code in the Flex SDK (depending upon whether Network monitor is enabled or not) that re-routes the Flex RPC calls to an internal proxy. This proxy makes the outward HTTP request. Therefore, it can report the actual HTTP request and response and is not bound by the several security and transformation rules that the Flash Player enforces on network requests.

http://anirudhs.chaosnet.org/blog/2009.06.01.html

另外还提到有http://www.charlesproxy.com/和http://www.fiddler2.com/fiddler2/,这两个没比较过,而wireshark对amf的支持还没有测试过。

2011年11月17日星期四

bigbluebutton中的audio

官网的FAQ中提到,0,71a里面的默认的VOIP语音有2-4秒的时延,原因有两点,一个是服务器audio转码(nellymoser -> mulaw),另一个原因是TCP链路。但在网络状况比较好的情况下,还有问题,那应该是转码带来的开销太大。大致的流程是:
flash(nellymose) -> Red5phone(mulaw)->asterisk/freeswitch -> Red5phone(mulaw)->flash(nellymose)。

里面提到了几个方案:
1. 用speex代替nellymoser,因为asterisk和freeswitch都支持speex,所以不需要转码。另外可以看到bbb的开发人员也在尝试为freeswitch添加nellymoser的支持(http://lists.freeswitch.org/pipermail/freeswitch-dev/2011-March/004742.html),另外freeswitch也在添加对rtmp协议的支持,http://freeswitch-users.2379917.n2.nabble.com/Conference-and-Speex-Wideband-audio-quality-problem-td5356620.html,如果flash能够直接连上freeswitch,那么开销会进一步降低。asterisk似乎也添加了对rtmp的支持,但好像还有些问题https://issues.asterisk.org/view.php?id=15484。另外最新版本的asterisk和freeswitch都已经支持speex.

2. 用native code来替代java做转码。

3. 使用java的客户端直接连到freeswitch或者asterisk。

4. 使用push-to-talk的模式(http://zhidao.baidu.com/question/100105828),感觉和微信差不多。

5. 通过电话call-in。

最终在0.8版本中采用的方案是1,具体见http://groups.google.com/group/bigbluebutton-dev/browse_thread/thread/a36d61df9b9fc45b?pli=1,根据大家的反馈来看效果还不错。

2011年11月15日星期二

[转]向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析

最近在了解red5,由于其使用了spring framework,也大致了解了一下,其中提到到了一些设计模式,这些名词不熟悉,不过对其使用已经比较熟悉,看到一篇不错的文章。节选其中的一部分内容如下:

向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析

虽然“依赖倒置”和“控制反转”在设计层面上都是消解模块耦合的 有效方法,也都是试图令具体的、易变的模块依赖于抽象的、稳定的模块的基本原则,但二者在使用语境和关注点上存在差异:“依赖倒置”强调的是对于传统的、 源于面向过程设计思想的层次概念的“倒置”,而“控制反转”强调的是对程序流程控制权的反转;“依赖倒置”的使用范围更为宽泛,既可用于对程序流程的描述 (如流程的主从和层次关系),也可用于描述其他拥有概念层次的设计模型(如服务组件与客户组件、核心模块与外围应用等),而“控制反转”则仅适用于描述流 程控制权的场合(如算法流程或业务流程的控制权)。

依赖注入的核心思想是:
  1. 抽象接口隔离了使用者和实现之间的依赖关系,但创建具体实现类的实例对象仍会造成对于具体实现的依赖。
  2. 采用依赖注入可以消除这种创建依赖性。使用依赖注入后,某些类完全是基于抽象接口编写而成的,这可以最大限度地适应需求的变化。

2011年11月9日星期三

bigbluebutton客户端主程序及初始化流程

前面一篇文章已经提及到了bigbluebutton的main目录结构,这里再详细分析一下。bbb-client采用了mate框架。events目录定义了主程序逻辑相关的事件,这些事件都是继承自flash.events.Event,并且事件的bubbles属性要设置为true才行(mate框架的需要),具体的event可以在用到的时候大致看一下,就是一个数据结构,没有什么逻辑。

ApplicationEventMap.mxml

mate框架的中定义了一些EventHandlers,其中的type属性就是事件类型(字符串),可以生成generator属性指定的对象,cache属性指定对象的作用域,global就是全局作用域。FlexEvent.PREINITIALIZE是预初始化的事件,如果有全局的对象,可以在这里指定。

下面的处理的事件有下面几类,在内定义了,表示事件发生时调用其generator属性指定的method,并可以在arguments中指定参数:

PortTestEvent - 触发测试RTMP端口,端口测试成功,端口测试失败。对应的类为ModuleProxy,可以参考对应的as文件。

LogoutEvent - 用户logout调用UserService.logoutUser()和ModulesProxy.handleLogout(),测试连接断开调用UserService.disconnectTest()
UserServicesEvent - 启动服务,也是调用UserService.startService()方法

UsersConnectionEvent - 连接成功,也是调用UserService.userLoggedIn()方法

SuccessfulLoginEvent - 成功登录,调用ModulesProxy.loadAllModules()

RaiseHandEvent - 举手发言,调用UserService.raiseHand()

LowerHandEvent - 放弃发言,调用UserService.lowerHand()

BroadcastStartedEvent - 流广播开始事件(具体是什么流还不太清楚),调用UserService.addStream()

BroadcastStoppedEvent - 流广播结束事件,调用UserService.removeStream()

KickUserEvent - 踢人事件, 调用UserService.kickUser()

ConfigEvent - 配置读取后触发,依次调用ConfigManager.setConfig(),SkinningService.loadSkins()。

下面紧接的两个HTTPService好像没什么用,并没有调用。

需要重点分析的是model目录下的文件。

BigBlueButtonPreloader - 下载进度条,这里主要是flash的下载和初始化进度,之前也看到过LoadingBar.mxml,那个主要是显示模块的加载进度,模块加载后面再分析。

ConferenceParameters - 存放会议的相关信息,没什么逻辑,就是一个结构体。包括会议名称,当前的用户名,用户的角色(协调者或者普通参与者),room id,语音会议bridge的地址(asterisk或者freeswitch),语音会议bridge的外部地址(这里不太确定),欢迎提示,会议id,外部用户id(不清楚),logout地址,connection(不清楚是什么连接,和red5的?),内部用户id(不清楚),是否录制会议。

ConfigParameters - 加载conf/conf.xml配置文件,这里会构造ModuleDescriptors,后面可以再仔细分析。

LayoutOptions - 控制一些UI上的显示。

PortTest - 用来测试多种flash支持的网络协议的联通性

PortTestProxy - 对PortTest做了个包装,PortTest看代码应该是第三方的。

User - 也是一个结构体

model下面还有一个User子目录,按理来说是和用户关联比较紧密的:
Status - key,value状态信息。
StatusCollection - 存储和管理Status信息。
BBBUser - 这里维护用户的状态,包括:当前的presenter是谁,是否有媒体流,是否举手了。
Conference - 维护会议的用户,包括自己和其他人的信息,从[bindable]可以看到这里是为了显示用的。
JoinService - 发起加会请求,成功后保存会议信息。
NetConnectionDelegate - 网络连接和重连
UserSOService - 举手,踢人,连接流媒体,连接服务器
UserService - 包装 JoinService和UserSOService。

接下来分析一下modules目录,整个客户端的框架基本上就分析完了。
 bigbluebutton采用模块化的方式来组织,在common目录下面,可以看到有三个接口,一个是IBbbModuleWindow,这个是模块的窗口,通过这个接口来获取窗口位置来布局,IBigBlueButtonModule,这个用来设置模块的属性,这些属性都是从config.xml中读取的,并通过事件传递过来的。

在modules目录下面,是模块相关的代码,在src目录下面,有很多*Module.mxml,这些都是继承了IBigBlueButtonModule,而且基本上每个模块都定义了自己的EventMap,mate可以定义多个EventMap,这些都会挂接到一个总的event bus上去,每个模块会定义自己的event和对应的event映射和处理函数,也可以处理全局的一些事件。

初始化流程

1. BigBlueButton.mxml的preloader第一步被执行:org.bigbluebutton.main.model.BigBlueButtonPreloader,会下载BigBlueButton.swf文件。

2. 建立ApplicationEventMap,设置事件处理表,在FlexEvent.PREINITIALIZE中,还会创建一些全局对象,包括ModulesProxy,UserService,ConfigManager,在ModulesProxy中会依次创建ModulesDispatcher, PortTestProxy和ModuleManager,ModulesDispatcher用来发送模块相关的一些事件,PortTestProxy用来测试rtmp和rtmpt端口是否是通的,ModuleManager用来管理模块的(加载,删除,传递配置信息);UserService用来加会的。ConfigManager作为一部全局的对象来存储配置信息。ModuleManager的构造函数中创建了ConfigParameters,在ConfigParameters的构造函数中,回去获取conf/config.xml,这是个异步的过程。

3. BigBlueButton.mxml的最下面的MainApplicationShell会创建一个初始化的布局,也就是打开页面后看到的进度条还有下面的一些调试窗口按钮等。ResourceUtil 初始化会加载conf/locale.xml,并且会根据默认的语言来加载对应的swf资源。

4. 配置加载成功回调到ConfigParameters::handleComplete,会将模块配置信息放到ModuleDescriptor中,并回调ModuleManager::handleComplete,在这中间会先触发PortTestEvent.TEST_RTMP,然后检查模块依赖,看所需要的模块是否都在配置列表中,最后触发ConfigEvent.CONFIG_EVENT事件。


5.ConfigEvent.CONFIG_EVENT事件有几个响应的地方,一个是在ApplicationEventMap中,会调用ConfigManager::setConfig存储配置,会调用SkinningService::loadSkins更换皮肤。第二个是在MainApplicationShell.mxml中,设置LOG窗口可见性,不过这里好像屏蔽掉了,是通过另外一个事件configLoadedEvent来触发,设置多个窗口的可见性。第三个是在MainToolbar.mxml中,设置语言选择栏可见性和帮助的URL链接。


6.PortTestEvent.TEST_RTMP事件有几个响应的地方,一个是ApplicationEventMap中,会调用ModuleProxy::testRTMP测试端口,另外一个是在LoadingBar.mxml中,这里只是用来显示。这里连的uri是在config.xml中指定的 。如果测试成功,会触发PortTestEvent.PORT_TEST_SUCCESS,否则会触发PortTestEvent.PORT_TEST_FAILED。如果失败,则继续进行RTMPT的测试。如果成功,则调用ModulesProxy::portTestSuccess,在其中调用modulesManager.startUserServices(),这里会传递连接成功的协议类型,并替换掉原先配置中的协议类型(rtmpt没开源,按理应该不支持,可能为了以后扩展用吧),接下来会触发UserServicesEvent.START_USER_SERVICES。会调用到UserService.startService()。

7. startService中会调用joinService.load来加载配置中的中host指定的地址,如果不是调试,host="http://192.168.1.101/bigbluebutton/api/enter",具体参数是怎么传到这个API的还不太清楚,抓包分析猜测是通过seesionid。上面这个API会返回会议的信息。然后会触发ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT,并通过NetConnectionDelegate创建一条长连接到指定的地址。

8. ConferenceCreatedEvent.CONFERENCE_CREATED_EVENT的处理也有两个地方,一个是在viewsWindow.mxml中,这里是视图上的显示,另外一个是APIEventMap,这里只是将meeting信息存储在UserManager中。

9. 第6步中的连接创建成功后,会触发UsersConnectionEvent.CONNECTION_SUCCESS,这个事件也有两个处理的地方,一个是ConnectionLostWindow.mxml中,这里会关掉连接断开的窗口,并重新刷新页面;另一个地方是ApplicationEventMap.mxml,会调用UserService.userLoggedIn(),在这里会调用UsersSOService.join(),会获取当前与会的人员信息,并触发ParticipantJoinEvent.PARTICIPANT_JOINED_EVENT。随后会触发SuccessfulLoginEvent.USER_LOGGED_IN。

10. SuccessfulLoginEvent.USER_LOGGED_IN事件的处理,一个是UI上的显示toolbar,另一个是ModulesProxy.loadAllModules(),从这一步开始起,就依次从配置中给定的模块的uri加载模块了。在模块加载过程中,会依次收到MODULE_LOAD_PROGRESS事件通知,这个会更新UI的进度条显示,当所有的模块都加载完成后,然后会检查locale的版本信息,(这里好像遗漏了locale资源的加载步骤,后面再看吧),然后调用module的start()方法,最后触发ALL_MODULES_LOADED,这个事件的响应处理在ui上就是移除掉进度条。而模块的窗口显示和其他UI按钮的显示,则由模块自己来触发,比如OpenWindowEvent,这个事件触发后会由MainCanvas.mxml处理,调用模块窗口的getPrefferedPosition()获取窗口位置来布局。

至此,bigbluebutton客户端的框架加载流程已经分析完,其中具体模块的加载和具体的应用有关系,下周打算分析其中的语音模块,接下来会分析presenter模块和video模块,并且打算尝试修复presenter的BUG(好像0.8版本并没有改进)。

2011年11月7日星期一

bigbluebutton flex客户端的UI视图

今天周末,时间有限,打算泛泛的看一下代码,做点记录。这篇就大致写一下代码结构好了,bbb-client的主要代码在org.bigbluebutton下,里面有五个目录

common - 公共目录,包括...
core - 读取配置和配置管理
main - 住程序和模块框架的代码
modules - 各个功能模块的代码
util - 工具类代码,包括日志模块,多语言加载模块等

初看有点地方都不太合理,比如core中的文件很少,也不算什么核心代码。后面再看吧。

先看main里面的代码吧,看起来这是个不错的选择,因为程序框架的主要逻辑都在这里,而功能模块的分析也要先了解主程序的运行才方便理解,其他地方的一些基础代码可以在看主程序时顺带看看就好。

main里面有五个目录:

api -
event - 自定义事件,用来解耦模块
maps - 消息映射
model - 控制逻辑
views - 视图

先挑简单的看,先看视图。
BrandingLogo.mxml - 这个可以放自定义的Logo,当然,这里得自己加代码,最简单的就是放个

ConnectionLostWindow.mxml - 这个是重连时显示的窗口,其中的ProgressBar有一个属性indeterminate,这个为true时,在现实进度的时候,就没有百分百的显示,因为断开连接后是没法去估计什么时候能重连上的。其中为重连事件注册了一个回调函数,在重连上后,重新打开页面,关闭这个popup窗口。这里有行没用的代码,var pageURL:String = mx.core.Application.application.url.split("/")[2];

LanguageSelector.mxml - 这里是个combobox,其中dataProvider绑定了一个数组变量,flex这种数据绑定的总是要显示的去在变量前加[bindable],难道编译器就不能把这块给屏蔽掉吗,说实话不太喜欢flex,感觉语法不简练,要多敲不少和逻辑没太大关系的代码,就感觉在手写html文件一样写代码,你要写的代码逻辑都淹没在一堆和主逻辑不相关的标记中了,有了flex build的智能提示是可以少敲点代码,但看起来还是很不爽。这里在语言选择变化后会调用到ResourceUtil来修改语言显示。

LoadingBar.mxml - 这里就是最开始加载时显示的进度条了,可以看到这有几个事件绑定:

ModuleLoadEvent.MODULE_LOADING_STARTED - 模块开始加载
ModuleLoadEvent.MODULE_LOAD_PROGRESS     - 模块加载进度
ModuleLoadEvent.ALL_MODULES_LOADED          - 所有模块成功加载
PortTestEvent.TEST_RTMP                                         - 开始RTMP端口测试
PortTestEvent.TUNNELING_FAILED                        - Tunneling测试失败

再看几个事件处理函数
moduleLoadingStarted
这里面会统计模块的总数,并在进度条显示总共会加载多少个模块,其中可以看到引用多语言文件中的bbb.mainshell.statusProgress.loading = Loading {0} modules,这里面有个{0}可以被替换到,看来properties中还可以使用模板语法。另外还初始化了每个模块的加载进度为0。

moduleLoadProgress
这里会更新总的加载进度。这里有个疑问,各个模块能同时加载吗?记得调试的时候看flex也就只有一个线程,不知道是否支持多线程。

allModulesLoaded
模块加载成功,移除进度条

testRTMP
显示在测试RTMP连接

tunnelingFailed
显示连接失败

LoggedOutWindow.mxml
这个就是点击了logout按钮后弹出来的,里面就只有一个OK按钮,点击之后就调用logout的API,"/bigbluebutton/api/signOut",这里感觉有点不太合理,点击退出就就没cancel,只能ok。另外这里显示的字符串也没有加入到多语言支持。

LogWindow.mxml
这个用来显示日志,里面有个TextArea,还有控制面板,里面可以过滤日志,关闭日志显示,清除日志,手工刷新日志。这里有点要提到的是,里面的成员变量并没有采取和普通的变量区别的写法,有些地方也没有加this,看起来总觉得不那么舒服。
另外测试的时候发现filter后的标记在clear后也不会清除掉。Clear掉后再refresh,还是会出现之前的日志。这里还有些bug的。
还有,这里log窗口是有滚动条的,我记得presenter的窗口好像没有,也许可以参考着加上。
需要说明的是,这里的日志并不会自己滚动,而是需要手动刷新,而从TextArea中获取更新的值也有点意思,从flex文档中可以看到“The TextArea control is a multiline text field with a border and optional scroll bars.”,另外这里用的是MDIWindow,Highlighter也是来是flexlib中,displayLogMessages()肯定是在窗口显示出来前被调用的。

MainToolbar.mxml
这里是toolbar的UI,没什么特殊逻辑,就是一些事件处理,但是这里loggedIn的参数都没用到,感觉有点乱。有点要提到的是,这里toolbar的按钮是可以动态的扩展的,也就是说,自己写了一个扩展模块,可以通过事件添加按钮到toolbar。

MainCanvas.mxml
可以向canvas上添加窗口,窗口要实现IBbbModuleWindow接口,可以在实现时说明窗口的布局方式。这里还实现了对video窗口的布局。默认是空的,窗口都是动态加载的。

MicSettings.mxml
这个是麦克风设置的窗口。

OldLocaleWarnWindow.mxml
用来给出locale的版本不对的提示,在从远端获取了conf.xml配置后,会比较其中的多语言版本号,和本地的比较。

MainApplicationShell.mxml
这就是程序的主窗口了,其中包括MainToolbar, MainCanvas(初始化时有进度条和logo,加载完后被移除)和控制面板(copyright, video layout,日志显示按钮,和重置布局按钮)。

上面其他的一些窗口都是在程序运行时,动态的创建和销毁的。

initializeShell()中只创建了一个全局的Dispatcher对象,该对象用来分发事件。具体的流程可能还要看看mate framework。

2011年11月4日星期五

关于clock skew问题的简短对话


clock skew解决的那个我没看懂 16:51
怎么解决的啊?解释一下? 16:51
Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
比较绕 16:51
两种情况


Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
一种,发送端快,接收端慢,那么rel_owd会减少,因为每次计算base owd都是取最小值,所以对结果没影响,right? 16:53
Wilson Chen (weichen2 - ENGINEER.SOFTWARE ENGINEERING):
减少就对结果没影响吗?也有吧? 16:53
把本来的拥塞当成非拥塞了 16:54
Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
因为计算queuing_owd = current_owd - base_owd
16:54
Wilson Chen (weichen2 - ENGINEER.SOFTWARE ENGINEERING):
base owd和current owd同步减少? 16:54
Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
16:54
Wilson Chen (weichen2 - ENGINEER.SOFTWARE ENGINEERING):
y 16:55
Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
另外一种情况,发送端慢,接收端快,那么rel_owd会变大 16:58
如果接收端收到数据包后,再把收到的时间戳带回去到发送端(ack包里),那发送端就能计算逆向的owd, reverse_owd
16:59
对发送端来说,收到ack时,他的角色和第一种情况的接收端是一样的,也就是说,可以准备计算reverse_owd
17:01
Wilson Chen (weichen2 - ENGINEER.SOFTWARE ENGINEERING):
了解 17:01
这个比较复杂 17:01
Jeromy Fu (jianfu - ENGINEER.SOFTWARE ENGINEERING):
那好了,发送端和接收端的时钟偏移是一样的,只是方向不一样而已 17:02
叠加上去就可以了 17:02
Wilson Chen (weichen2 - ENGINEER.SOFTWARE ENGINEERING):
有道理,就是靠reverse_owd

bigbluebutton客户端flex分析- 初始化流程(1)

  作为flex的初学者,大致浏览的一下flex官方的视频教程,接下来就打算了解bigbluebutton的客户端了。进程远没有我当初想象中的那么顺 利,经常的都会碰到一些问题。相信在解决一个个问题的过程中,会慢慢的熟悉flex和这个产品。通读文档肯定是来不及,时间紧迫,很多不清楚的地方就查阅 一下资料,然后自己推测或猜测的,可能会有些不对的地方,但有需要的时候一定会查阅资料弄清楚问题。


  首先从启动开始,客户端已经通过flex build编译通过,但是启动的时候并没有显示加载的进度条,而是卡在了初始画面,为了找到原因,只有探索源码。第一步,要知道flex程序的入口在哪里,一个flex程序可以有很多mxml和as文件,但是包含Application只有一个,这个Application在mxml中用来标识,把鼠标移到到其上,可以看到其对应的as类mx.core.Application。

  flex程序感觉写起来要敲很多的字符,并不简洁,也不明了。可以看到BigBlueButton实际上就只做的下面几个事情:

  1. 声明pageTitle为"BigBlueButton"。
  2. 声明应用程序的布局采用绝对布局的方式。
  3. 构造了一个ResourceUtil对象,处理i18n。
  4. 声明了IDragManager,可能是支持窗口拖动?不太清楚,先跳过。
  5. 声明了HistoryManager,绑定浏览器的前进后退操作?不太清楚,先跳过
  6. 声明了一个私有的handleChatEvent,没有看到有引用的地方,函数体里面也是打了一条trace,不太清楚,先跳过
  7. 然后声明了一个ApplicationEventMap,这个是mate框架相关的,定义了一些事件处理的映射关系。不太清楚,先跳过
  8. 声明了一个MainApplicationShell,估计也是和mate框架相关,定义了app的视图。不太清楚,先跳过
  9. 在application中,可以看到有一个preloader的属性,其中指向了"org.bigbluebutton.main.model.BigBlueButtonPreloader",这里有点类似js, BigBlueButtonPreloader是一个as文件。

  由此可以看到,入口的地方应该是顺着preloader往下走,接下来看看BigBlueButtonPreloader都干了些什么。

  可以看到有一个“import flash.display.Sprite;”查阅了一下“在不使用 Flash 动画时用这个 Sprite ,Sprite 是轻量级的容器,不包括时间轴的容器,比 MC 小一些,更少占用内存(系统资源)。”

  下面这两个应该就是显示进度条相关的,RSL(Runtime Shared Libraries)。
  import mx.events.RSLEvent;
  import mx.preloaders.DownloadProgressBar;

  然后定义了一个继承自DownloadProgressBar的类并自定义了一些显示的字符串。这里好像没看到有什么特殊的逻辑。

  接下来再看MainApplicationShell,这里主要是定义了UI视图,最外层的是一个VBox,可以看到VBox有一个creationComplete属性,指向一个initialzeShell()函数。

  再下面的一系列的声明了一系列的事件监听器,先跳过。

  再下面是,这个也先跳过,再后面分析了mate框架就明白了。

  里面是as脚本,定义了上面的事件监听器函数,基本上都是根据事件来更新UI显示的。

  再往下就是一些UI的布局了:MainToolbar,然后是MainCanvas,在MainCanvas中定义了LoadingBar和BrandingLogo,其中BrandingLogo(.mxml)中可以添加自己Logo图片。再往下就是ControlBar,下面是copyright和一个log按钮(可以打开调试窗口看日志)和reset layout窗口。

  看到这里,都还没什么头绪,为什么会freeze住,但是从上面发现可以打开调试窗口,显示日志,接下来再来一次,进度条显示为"connecting to the server",难道server没连上?打开调试窗口,可以看到,语言和模块都已经加载成功,并且已经能够联通rtmp服务器,最下面显示:

11/3/2011 15:44:02.980 [DEBUG] JoinService:load(...) http://64.104.177.100/bigbluebutton/api/enter
11/3/2011 15:44:03.396 [DEBUG] Join FAILED =
  FAILED
  Could not find conference null.


加会失败?为什么刚开始登录的时候就会加会?从日志里面看到对应的url地址为:http://64.104.177.100/bigbluebutton/api/enter,想到之前修改过config.xml,其中有一项是,这里我是修改过的,最初的值不太记得了,但是好像官网上说如果要调试运行,要将host修改一下,好像和join-mock有关系,但是当时感觉官网没说太清楚,再查阅一下吧,又看了一下,只是提到了要用这个替换掉,另外有些帖子说join-mock文件没更新,连不上freeswitch。猜测这个join-mock应该是模拟http://64.104.177.100/bigbluebutton/api/enter的返回值,但是为什么用flex编译调试就有问题,直接从浏览器打开就没问题呢?看来还得继续再分析。

首先第一步就要看哪里会读取config.xml配置了。搜索到了,这个在ApplicationEventMap.mxml文件中引用了,下面还有一行,不清楚是做什么的,joinResultMock.xml这个文件不存在。


另外再搜索join-mock.xml,可以看到这个在JoinServiceTest.as文件中引用,这个看起来像是Testcase的程序,怎么会跑到这里来?再搜索一下JoinServiceTest这个类,看看哪里有引用到,查了一下,只有在UserTestSuite.as中引用到了。

后来发现config.xml还有一处地方引用到了,ConfigParameters.as,应该就是这里了。可以看到这里是远程的获取config.xml文件,从这里可以知道,我们可以动态的去配置config.xml,不需要重启服务,只需要刷新页面就可以让配置生效了。另外这里host配置项目读取后放置到public的变量host中去了,接下来就要去看哪里引用了这个类。ConfigParameters引用的地方倒是比较好找,但后面就不容易了,由于flex本质上也是一个事件驱动的,再加上这个项目使用了mate这个事件框架(我的理解是mate本质上是封装了flex底层的事件模型,使得各个模块之间通过信号事件进行交互,来达到模块间解耦的目的,还没仔细看,现在的理解是这样的)。

这样单看代码不是特别容易,只好结合调试一起来看,但事件机制导致在不是特别熟悉代码的情况下调试也不是那么容易,因此要利用一切可以利用的信息,比如,在UI上面显示的一些状态提示字符。在最后一会,屏幕上显示了“Connecting to the server”,搜索了工程,发现这个在"en_US/bbbResources.properties"中 。这个文件是多语言中的en_US版本,看来多语言的支持是做在了客户端,并不需要从服务器去更新。接下来搜索“bbb.mainshell.statusProgress.connecting”,发现在LoadingBar.mxml中:

private function testRTMP(e:PortTestEvent):void{

//- Cannot get locale string this early in loading process
       this.label = 'Connecting to the server';  
      //ResourceUtil.getInstance().getString('bbb.mainshell.statusProgress.connecting');
}

这里应该就是对应的位置,可以看到多语言的资源还没加载完,所以这里用英文显示了,如果要做汉化,这里也是需要修改的地方。

可以看到最终浏览器中的flex应用给出的提示是"Sorry, we cannot connect to the server.",再打开log窗口,看到连的服务器是"64.104.177.100",刚换了一下网络,从公司网络切换到家里的网络了,地址已经变了,这个错误和之前出现的并不一样。


有点奇怪的是,本已经通过bbb-conf --setip修改了配置中的ip地址了,但是为什么还是错误的ip地址,难道不是从远程获取的(远程是修改了/var//www/bigbluebutton/conf下面的,并没有修改设置的dev目录下的,我已经将配置改为使用dev环境)?但是明明是URLRequest获取的啊,难道有cache?但是可以看到,代码中获取时加上了时间参数,以避免有cache,_urlLoader.load(new URLRequest(file + "?a=" + date.time)); 只有再调试一把了,看看Network Monitor窗口里的显示(多多少少觉得之前的flex视频教程的基础部分没白看,多少都能用到一些)。居然没有,还不清楚这个Network Monitor是怎么工作的,不过我猜测也就是hack了flex的SDK里面的网络操作。另外构造URLRequest的时候,并没有加上域名或ip的前缀,难道真的是访问本地文件(好像不应该访问远程的,因为现在调试运行时,远程ip都还没有)?先看看SDK说明。看URLRequest里最后一个例子提到:如果没有域名或ip,那这个文件应该就是当前flash的文件所在目录的相对位置,但是bigbluebutton.swf在bin目录下,下面的conf/config.xml配置的是192.168.1.102,应该是没有使用这个配置,我怀疑调试运行的时候,默认的工作目录就是BigBlueButton.mxml所在的目录。当然这是我的猜测,网上还没找到相应的说明。先修改了试试先。如果改了还不行就可能是cache的原因了。

经过修改后,重新运行发现配置生效了。真是在摸索中前进,都是苦力活。好了,现在回到了之前的bug,


11/3/2011 22:02:17.954 [DEBUG] JoinService:load(...) http://192.168.1.101/bigbluebutton/api/enter
11/3/2011 22:02:18.063 [DEBUG] Join FAILED =
  FAILED
  Could not find conference null.


接下来看日志,看起来JoinService是一个加会的服务,怎么会跑到这里来,一开始就加会了?哦,好像是的,demo打开的那个web页面是html的,不是flash的。


那这个问题看来是清楚了,加会但是参数不对,会议根本就不存在。接下来要看看的就是加会的api和对于的官网文档里面提到的那个join-mock文件怎么使用的了。

至此为止,为什么用'conf/join-mock'来替换掉之前的那个url(http://192.168.1.101/bigbluebutton/api/enter)就很清楚了,join-mock直接返回了一个mock的会议信息。

好了,经过验证已经可以成功加会了。是的,如果我按照官网的说明,根本不会遇到这些乱七八糟的问题,但是,通过解决这个问题,让我对bbb-client端的代码有了一个初步的熟悉,特别是我这种一行flex代码都没写过的人来说,我感觉已经找到那么一点门路了。

接下来还得继续写,因为还没有开始写正题 - 初始化的流程,但上面是一个不错的开端。再继续努力吧。





 




2011年11月2日星期三

重新写博客了

几年后重新看之前的博客,自己都不想看,记录的这些琐碎的技术细节。好了,作为一名技术人员,一定要能够积累自己的产品,工作五年了,却没有一个拿的出手的产品,很失败。最近的目标是学习bigbluebutton,并能做一定定制开发。现在先从客户端开始入手,flex的,没经验,有学习成本,但是我决定要做好。期待我自己的定制化产品。