Hey guys,
Referring back to the post on a previous page about sending initializeReady() and requestVideoStream() to Cam4, the little patch I provided to RTMPDump to allow it to record MyFreeCams can easily be altered to do this.
The thread is here:
.../rtmpdump-patch-myfreecams-t16266.html
Basically the call-order is reversed, instead of waiting for an AMF invoke, you have to send it yourself, but you will get the "real" RTMP server in the same way you get the MFC challenge.
I have made a separate patch for RTMPDump (LibRTMP, really) but the issue is, you then have to re-run the application with the new information or add more stuff that I really don't feel like doing (I don't cap). If someone's application is using LibRTMP this would be trivial.
Here is a bit of AS3 that does exactly what I described above, it might help someone, just create a 480x400 Flash project, give it network permissions and paste this script (change the broadcaster name, obviously):
Code:
import flash.net.*;
import flash.text.*;
import flash.events.*;
import flash.display.*;
import flash.external.*;
var cam4ip: String="";
var cam4origin: String="";
var cam4guid: String="";
NetConnection.defaultObjectEncoding=flash.net.ObjectEncoding.AMF0;
SharedObject.defaultObjectEncoding=flash.net.ObjectEncoding.AMF0;
Security.allowDomain("*");
Security.allowInsecureDomain("*");
var menu: ContextMenu=new ContextMenu();
menu.hideBuiltInItems();
var stretch: Boolean=false;
var toggleStretch=new ContextMenuItem("Unlock Aspect Ratio (Stretch)");
toggleStretch.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,function(e: ContextMenuEvent) {
stretch=(!stretch);
if (stretch) e.target.caption="Lock Aspect Ratio (Scale)"; else e.target.caption="Unlock Aspect Ratio (Stretch)";
stage.dispatchEvent(new Event(Event.RESIZE));
});
menu.customItems.push(toggleStretch);
var toggleSound=new ContextMenuItem("Mute Sound");
toggleSound.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,function(e: ContextMenuEvent) {
if (!snd||!ns) return;
if (snd.volume) snd.volume=0; else snd.volume=1;
if (snd.volume) e.target.caption="Mute Sound"; else e.target.caption="Unmute Sound";
ns.soundTransform=snd;
});
menu.customItems.push(toggleSound);
var toggleFullscreen=new ContextMenuItem("View Fullscreen");
toggleFullscreen.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,function(e: ContextMenuEvent) {
if (stage.displayState==StageDisplayState.FULL_SCREEN) {
stage.displayState=StageDisplayState.NORMAL;
e.target.caption="View Fullscreen";
} else {
try {
stage.displayState=StageDisplayState.FULL_SCREEN;
e.target.caption="Exit Fullscreen";
} catch (err: SecurityError) {
e.target.caption="Fullscreen Mode Unavailable";
e.target.enabled=false;
}
}
});
menu.customItems.push(toggleFullscreen);
this.contextMenu=menu;
stage.scaleMode=StageScaleMode.NO_SCALE;
stage.align=StageAlign.TOP_LEFT;
stage.stageFocusRect=false;
stage.addEventListener(Event.RESIZE,function(e: Event) {
var targetAr: Number=1.2;
var sx=0,sy=0,sw=stage.stageWidth,sh=stage.stageHeight;
if (!stretch) {
var ar: Number=int((stage.stageWidth/stage.stageHeight)*100)/100;
if (ar>targetAr) {
sw=(sh*targetAr);
sx=(stage.stageWidth/2)-(sw/2);
} else if (ar<targetAr) {
sh=(sw/targetAr);
sy=(stage.stageHeight/2)-(sh/2);
}
}
video.x=sx;
video.y=sy;
video.width=sw;
video.height=sh;
menuhack.width=stage.stageWidth;
menuhack.height=stage.stageHeight;
log.width=stage.stageWidth-6;
log.height=stage.stageHeight;
});
var nc: NetConnection;
var ncclient: Object;
var ns: NetStream;
var nsclient: Object;
var snd: SoundTransform;
var video: Video=new Video();
video.deblocking=3;
video.smoothing=true;
video.width=480; video.height=400;
addChild(video);
var log: TextField=new TextField();
log.border=false;
log.type=TextFieldType.DYNAMIC;
var fmt: TextFormat=new TextFormat("Arial",10,0xffffff);
log.defaultTextFormat=fmt;
log.wordWrap=false;
log.x=3;
log.y=0;
log.width=476;
log.height=400;
addChild(log);
var menuhack: MovieClip=new MovieClip();
menuhack.x=0;
menuhack.y=0;
menuhack.graphics.beginFill(0,0);
menuhack.graphics.drawRect(0,0,1,1);
menuhack.graphics.endFill();
addChild(menuhack);
menuhack.doubleClickEnabled=true;
menuhack.addEventListener(MouseEvent.DOUBLE_CLICK,function(e: MouseEvent) {
if (stage.displayState==StageDisplayState.FULL_SCREEN) {
stage.displayState=StageDisplayState.NORMAL;
toggleFullscreen.caption="View Fullscreen";
} else {
try {
stage.displayState=StageDisplayState.FULL_SCREEN;
toggleFullscreen.caption="Exit Fullscreen";
} catch (err: SecurityError) {
toggleFullscreen.caption="Fullscreen Mode Unavailable";
toggleFullscreen.enabled=false;
}
}
});
stage.dispatchEvent(new Event(Event.RESIZE));
function viewCam4(): void {
nc=new NetConnection();
ncclient=new Object();
nc.client=ncclient;
nc.addEventListener(NetStatusEvent.NET_STATUS,function(e: NetStatusEvent) {
trace("NC:"+e.info.code);
if (e.info.code=="NetConnection.Connect.Rejected") {
trace("NetConnection rejected, aborting!");
}
if (e.info.code=="NetConnection.Connect.Success") {
ns=new NetStream(nc);
nsclient=new Object();
nsclient.onMetaData=function(o: Object) {};
ns.client=nsclient;
ns.addEventListener(NetStatusEvent.NET_STATUS,function(e: NetStatusEvent) {
trace("NS:"+e.info.code);
if (e.info.code=="NetStream.Unpublish.Success") {
log.visible=true;
trace("Stream unpublished.");
}
});
snd=new SoundTransform();
video.attachNetStream(ns);
ns.soundTransform=snd;
snd.volume=1;
ns.play("streams/"+cam4guid);
}
});
nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR,function(e: SecurityErrorEvent) {
trace("SecurityErrorEvent in NetConnection!");
});
nc.connect("rtmp://"+cam4ip+"/cam4-"+cam4origin+"/_definst_");
}
function startCam4(ip,origin,guid: String): void {
cam4ip=ip;
cam4origin=origin;
cam4guid=guid;
viewCam4();
}
function initializeCam4(who: String): void {
trace("Initializing:"+who);
var loader: URLLoader=new URLLoader();
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,function(e: SecurityErrorEvent) {trace(e)});
loader.addEventListener(IOErrorEvent.IO_ERROR,function(e: IOErrorEvent) {trace(e)});
loader.addEventListener(Event.COMPLETE,function(e: Event) {
var rtmp_origin: String=e.target.data;
trace(rtmp_origin);
nc=new NetConnection();
ncclient=new Object();
nc.client=ncclient;
nc.addEventListener(NetStatusEvent.NET_STATUS,function(e: NetStatusEvent) {
trace("NC:"+e.info.code);
if (e.info.code=="NetConnection.Connect.Success") {
var r: Responder=new Responder(function(str: String) {
var re: RegExp=new RegExp("^rtmp...(.+?)\\/cam4-(.+?)\\/.+?streams\\/(.+?)\\?","i");
var rs: Array=re.exec(str);
if (!rs||rs.length<4) {
return;
}
nc.close();
startCam4(rs[1],rs[2],rs[3]);
});
nc.call("initializeReady",null);
nc.call("requestVideoStream",r);
}
});
nc.addEventListener(SecurityErrorEvent.SECURITY_ERROR,function(e: SecurityErrorEvent) {
trace("SecurityErrorEvent in NetConnection!");
});
nc.connect(rtmp_origin,who,"guest","");
});
loader.load(new URLRequest("http://cam4.com/direct?room="+who+"&username=guest&devenv=false"));
}
ExternalInterface.addCallback("initializeCam4",initializeCam4);
initializeCam4("vnmses"); //<-- cam4 broadcaster here
loaderInfo.addEventListener(Event.COMPLETE,function() {
ExternalInterface.call("AppLoaded");
});
Note that most of the random crap in the AS3 above is due to the bug of not allowing context menus on Video components, aspect ratio, stretching etc. and the actual Cam4-specific bit is really small. Feel free to cut, re-use, implement etc. in whichever way you want.
E.