於是,我的初探程式,就來試做一下聊天室吧!
透過之前介紹過的線上影音教學:http://www.flashextensions.com/tutorials.php,不懂 Eclipse 的人可以稍微認識一下基本開發環境,以及如何在 Eclipse 中設定開發 Red5 程式的環境。然後要順便認識一下 Ant 開發工具,了解如何 Compile 開發好的 Java 程式並啟動 Red5 Server。
我的資料夾放在這:{Red5}\webapps\FirstRed5App
依照 HOWTO-NewApplications.txt 的說明,修改以下檔案
{Red5}\webapps\FirstRed5App\WEB-INF\web.xml
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>/FirstRed5App</param-value>
</context-param>
{Red5}\webapps\FirstRed5App\WEB-INF\red5-web.xml
<bean id="web.handler"
class="idv.ben.red5.Application"
singleton="true" />
{Red5}\webapps\FirstRed5App\WEB-INF\red5-web.properties
webapp.contextPath=/FirstRed5App
webapp.virtualHosts=localhost, 127.0.0.1
所以,我這個 Context 的位置在 /FirstRed5App,並且將會由 idv.ben.red5.Application 這個類別作處理!
以下,是這支 Java 程式,會處理來自 Flash 送來的資訊,並廣播到所有連線端!
package idv.ben.red5;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;
public class Application extends ApplicationAdapter implements IPendingServiceCallback {
protected static Log log = LogFactory.getLog(Application.class.getName());
//存一份 連線物件 與 登入名稱 的對照表
private List<MyConn> connNameList = new ArrayList<MyConn>();
private String jobId = null;
//實作IPendingServiceCallback介面所要實作的method
public void resultReceived(IPendingServiceCall call) {
// TODO Auto-generated method stub
}
@Override
public boolean appStart(IScope app) {
// TODO Auto-generated method stub
if(!super.appStart(app)){
return false;
}
IScheduledJob job = new MyJob(this);
jobId = this.addScheduledJob(5000, job);
return true;
}
@Override
public void appStop(IScope app) {
// TODO Auto-generated method stub
this.removeScheduledJob(jobId);
super.appStop(app);
}
//新加入
public void join(String myName){
log.info("join(" + myName + ")");
IConnection current = Red5.getConnectionLocal();
//加到名單
connNameList.add(new MyConn(current, myName));
Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();
if (conn instanceof IServiceCapableConnection) {
//通知所有人
((IServiceCapableConnection) conn).invoke("onNewMemberJoined", new Object[]{myName}, this);
}
}
}
//說話
public void talk(String myName, String msg){
log.info("talk(" + myName + ", " + msg + ")");
Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();
if (conn instanceof IServiceCapableConnection) {
//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneTalking", new Object[]{myName, msg}, this);
}
}
}
//移動
public void walk(String myName, double x, double y){
log.info("walk(" + myName + ", " + x + ", " + y + ")");
Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();
if (conn instanceof IServiceCapableConnection) {
//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneWalking", new Object[]{myName, x, y}, this);
}
}
}
//檢查斷線
public void checkDisconnection(){
log.info("checkDisconnection()");
//IConnection current = Red5.getConnectionLocal();
Iterator<IConnection> it = scope.getConnections();
for(int i=connNameList.size()-1; i>=0; i--){
MyConn myConn = (MyConn)connNameList.get(i);
boolean isDisconnect = true;
while (it.hasNext()) {
IConnection conn = it.next();
if (conn.equals(myConn.conn)) {
isDisconnect = false;
break;
}
}
if(isDisconnect){
//通知所有人 有人斷線
notifyDisconnect(myConn.name);
//從名單移除
connNameList.remove(i);
}
}
}
//通知所有人 有人斷線
private void notifyDisconnect(String myName){
log.info("notifyDisconnect(" + myName + ")");
Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();
if (conn instanceof IServiceCapableConnection) {
//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneDisconnect", new Object[]{myName}, this);
}
}
}
}
class MyJob implements IScheduledJob {
private Application app;
public MyJob(Application app){
this.app = app;
}
public void execute(ISchedulingService service) throws CloneNotSupportedException {
// TODO Auto-generated method stub
//檢查斷線
this.app.checkDisconnection();
}
}
在這個 Java 程式中,我存了一個 connNameList 陣列,用來存放每個連線,我自訂了 MyConn 類別來存放 連線物件 與 登入名稱,這個 MyConn 類別如下:
package idv.ben.red5;
import org.red5.server.api.IConnection;
public class MyConn {
public IConnection conn;
public String name;
public MyConn(IConnection conn, String name){
this.conn = conn;
this.name = name;
}
}
在上面的 Java 程式中,除了要注意每個 method 的名稱與參數外,因為那些是讓 Flash 可以呼叫的名稱,另外要注意的就是這句由 Server --> Flash 的寫法:
((IServiceCapableConnection) conn).invoke("函式名稱", new Object[]{參數1, 參數2, ...參數n}, this);
另外一個大重點是,要如何在這個 Java 程式中,製作週期性的動作,可以觀察我在 appStart() 中的寫法:
IScheduledJob job = new MyJob(this);
jobId = this.addScheduledJob(5000, job);
自訂一個實作 IScheduledJob 介面的 MyJob 類別,裡面要實作 execute() 方法,撰寫實際要執行的工作內容即可。這部份也可以參考另外這個網站的範例:http://www.joachim-bauch.de/tutorials/red5/MigrationGuide.txt
接下來,以下是 Flash 的程式部分:
var nc:NetConnection = new NetConnection();
nc.connect("rtmp://127.0.0.1/FirstRed5App");
nc.onStatus = function(info:Object){
for(var i in info){
trace(i + "=" + info[i]);
}
}
nc.onResult = function(obj) {
//trace("The result is "+obj);
};
//----------------------------------Server to Flash
nc.onNewMemberJoined = function(name:String) {
showMsg("歡迎" + name + "加入");
if(_root[name + "_mc"]==undefined){
var mc:MovieClip = _root.createEmptyMovieClip(name + "_mc", _root.getNextHighestDepth());
mc.lineStyle(1, 0x000000, 100);
if(name==myName){
mc.beginFill(0xFF0000);
}else{
mc.beginFill(0xFFCC00);
}
mc.lineTo(5, 0);
mc.lineTo(5, 5);
mc.lineTo(0, 5);
mc.lineTo(0, 0);
mc.endFill();
}
};
nc.onSomeoneTalking = function(myName:String, msg:String) {
showMsg(myName + "說:" + msg);
};
nc.onSomeoneWalking = function(name:String, x:Number, y:Number){
if(_root[name + "_mc"]!=undefined){
_root[name + "_mc"].targetX = x;
_root[name + "_mc"].targetY = y;
_root[name + "_mc"].onEnterFrame = function(){
this._x += (this.targetX - this._x) * 0.2;
this._y += (this.targetY - this._y) * 0.2;
if(Math.abs(this.targetX - this._x)<0){
this._x = this.targetX;
}
if(Math.abs(this.targetY - this._y)<0){
this._y = this.targetY;
}
if(this._x == this.targetX && this._y == this.targetY){
delete this.onEnterFrame;
}
}
}
}
nc.onSomeoneDisconnect = function(name:String){
showMsg(name + "離開了");
if(_root[name + "_mc"]!=undefined){
_root[name + "_mc"].removeMovieClip();
delete _root[name + "_mc"];
}
}
//----------------------------------Flash to Server
function join(myName:String){
nc.call("join", nc, myName);
}
function talk(myName:String, msg:String){
nc.call("talk", nc, myName, msg);
}
function walk(myName:String, x:Number, y:Number){
nc.call("walk", nc, myName, x, y);
}
//----------------------------------Tools
_global.showMsg = function(msg:String){
//trace(msg);
msg_txt.text = msg + "\n" + msg_txt.text;
}
//----------------------------------
//加入聊天室
var myName:String = "user" + (new Date()).getTime();
join(myName);
//亂說話
function talkSomething(){
talk(myName, "msg" + new Date());
}
setInterval(talkSomething, 5000);
//到處亂走
function workSomewhere(){
walk(myName, Math.random()*Stage.width, Math.random()*Stage.height);
}
workSomewhere();
setInterval(workSomewhere, 10000);
這個聊天室,只要一進入,我就會自動給一個登錄名稱 myName,之後所有的動作都要傳遞此一識別字串,然後會週期性的亂走與亂說話。
可以看到,Flash --> Server 的部份,搭配 Java 的 Server 程式,不難理解吧?
nc.call("join", nc, myName);
nc.call("talk", nc, myName, msg);
nc.call("walk", nc, myName, x, y);
然後是 Server --> Flash 的部份,應該也不難理解?!
nc.onNewMemberJoined = function(name:String) {...}
nc.onSomeoneTalking = function(myName:String, msg:String) {...}
nc.onSomeoneWalking = function(name:String, x:Number, y:Number){...}
nc.onSomeoneDisconnect = function(name:String){...}
大概就這樣,該提的重點應該都提到了!不過以上的程式,判斷連線中斷並通知所有 Flash 的部份,我單機開多個瀏覽器時會測不出來,不知道是否是因為每個瀏覽器都在相同的電腦上,所以被視同為相同的 IConnection?所以會使得其中一個瀏覽器若是重新整理或離開時,會造成所有瀏覽器上的 MovieClip 都被移除,被視為離開!當然也有可能是我的 Java 程式寫得有誤,這就需要進一步的測試了,不過至少週期執行的寫法是可以學的。
後記:我覺得斷線踢人的那段,我一定寫得不正確,有空再來改!
0 意見:
張貼意見