/**
* Copyright hacker_3u0w7y0e ( http://wonderfl.net/user/hacker_3u0w7y0e )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/4Pti
*/
<?xml version="1.0" encoding="utf-8"?>
<!--
ADOBE SYSTEMS INCORPORATED
Copyright 2008 Adobe Systems Incorporated
All Rights Reserved.
NOTICE: Adobe permits you to use, modify, and distribute this file
in accordance with the terms of the license agreement accompanying it.
Author: Jozsef Vass
-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="init()" backgroundColor="0xffffff" width="530" height="630">
<mx:Script>
<![CDATA[
import mx.charts.chartClasses.StackedSeries;
import mx.formatters.DateFormatter;
import mx.events.SliderEvent;
import mx.events.FlexEvent;
import mx.collections.ArrayCollection;
import mx.events.ItemClickEvent;
import flash.events.SampleDataEvent;
// rtmfp server address (Adobe Stratus or FMS)
[Bindable] private var connectUrl:String = "rtmfp://stratus.rtmfp.net";
// developer key, please insert your developer key here
private const DeveloperKey:String = "your developer key";
// please insert your web service URL here for exchanging peer ID
private const WebServiceUrl:String = "your webservice URL";
// this is the connection to rtmfp server
private var netConnection:NetConnection;
// after connection to rtmfp server, publish listener stream to wait for incoming call
private var listenerStream:NetStream;
// caller's incoming stream that is connected to callee's listener stream
private var controlStream:NetStream;
// outgoing media stream (audio, video, text and some control messages)
private var outgoingStream:NetStream;
// incoming media stream (audio, video, text and some control messages)
private var incomingStream:NetStream;
// ID management serice
private var idManager:AbstractIdManager;
private var remoteVideo:Video;
// login/registration state machine
[Bindable] private var loginState:int;
private const LoginNotConnected:int = 0;
private const LoginConnecting:int = 1;
private const LoginConnected:int = 2;
private const LoginDisconnecting:int = 3;
// call state machine
[Bindable] private var callState:int;
private const CallNotReady:int = 0;
private const CallReady:int = 1;
private const CallCalling:int = 2;
private const CallRinging:int = 3;
private const CallEstablished:int = 4;
private const CallFailed:int = 5;
// available microphone devices
[Bindable] private var micNames:Array;
private var micIndex:int = 0;
// available camera deviced
[Bindable] private var cameraNames:Array;
private var cameraIndex:int = 0;
private var activityTimer:Timer;
// user name is saved in local shared object
private var localSO:SharedObject;
[Bindable] private var remoteName:String = "";
private var callTimer:int;
// charts
private var audioRate:Array = new Array(30);
[Bindable] private var audioRateDisplay:ArrayCollection = new ArrayCollection();
private var videoRate:Array = new Array(30);
[Bindable] private var videoRateDisplay:ArrayCollection = new ArrayCollection();
private var srtt:Array = new Array(30);
[Bindable] private var srttDisplay:ArrayCollection = new ArrayCollection();
private const defaultMacCamera:String = "USB Video Class Video";
private var ringer:Sound;
private var ringerChannel:SoundChannel;
// called when application is loaded
private function init():void
{
status("Player: " + Capabilities.version + "\n");
loginState = LoginNotConnected;
callState = CallNotReady;
localSO = SharedObject.getLocal("videoPhoneSettings");
if (localSO.data.hasOwnProperty("user"))
{
userNameInput.text = localSO.data.user;
}
var mics:Array = Microphone.names;
if (mics)
{
micNames = mics;
}
else
{
status("No microphone available.\n");
}
var cameras:Array = Camera.names;
if (cameras)
{
cameraNames = cameras;
}
else
{
status("No camera available.\n");
}
// statistics timer
activityTimer = new Timer(1000);
activityTimer.addEventListener(TimerEvent.TIMER, onActivityTimer);
activityTimer.start();
// selected mic device
micIndex = 0;
if (localSO.data.hasOwnProperty("micIndex"))
{
micIndex = localSO.data.micIndex;
}
micSelection.selectedIndex = micIndex;
// set Mac default camera
if (Capabilities.os.search("Mac") != -1)
{
for (cameraIndex = 0; cameraIndex < cameras.length; cameraIndex++)
{
if (cameras[cameraIndex] == defaultMacCamera)
{
break;
}
}
}
// selected camera device
if (localSO.data.hasOwnProperty("cameraIndex"))
{
cameraIndex = localSO.data.cameraIndex;
}
cameraSelection.selectedIndex = cameraIndex;
// mic volume
var micVolume:int = 50;
if (localSO.data.hasOwnProperty("micVolume"))
{
micVolume = localSO.data.micVolume;
}
micVolumeSlider.value = micVolume;
// speaker volume
var speakerVolume:Number = 0.8;
if (localSO.data.hasOwnProperty("speakerVolume"))
{
speakerVolume = localSO.data.speakerVolume;
}
speakerVolumeSlider.value = speakerVolume;
// configure audio and video
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
mic.codec = SoundCodec.SPEEX;
mic.setSilenceLevel(0);
mic.framesPerPacket = 1;
mic.gain = micVolume;
mic.addEventListener(StatusEvent.STATUS, onDeviceStatus);
mic.addEventListener(ActivityEvent.ACTIVITY, onDeviceActivity);
}
var camera:Camera = Camera.getCamera(cameraIndex.toString());
if (camera)
{
camera.addEventListener(StatusEvent.STATUS, onDeviceStatus);
camera.addEventListener(ActivityEvent.ACTIVITY, onDeviceActivity);
camera.setMode(320, 240, 15);
camera.setQuality(0, 80);
}
}
private function status(msg:String):void
{
statusArea.text += msg;
statusArea.validateNow();
statusArea.verticalScrollPosition = statusArea.textHeight;
trace("ScriptDebug: " + msg);
}
// user clicked connect
private function onConnect():void
{
statusArea.text = "";
localSO.data.user = userNameInput.text;
localSO.flush();
netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnectionHandler);
try
{
netConnection.connect(connectUrl + "/" + DeveloperKey);
}
catch (e:ArgumentError)
{
status("Incorrect connet URL\n");
return;
}
loginState = LoginConnecting;
status("Connecting to " + connectUrl + "\n");
}
private function netConnectionHandler(event:NetStatusEvent):void
{
status("NetConnection event: " + event.info.code + "\n");
switch (event.info.code)
{
case "NetConnection.Connect.Success":
connectSuccess();
break;
case "NetConnection.Connect.Closed":
loginState = LoginNotConnected;
callState = CallNotReady;
break;
case "NetStream.Connect.Success":
// we get this when other party connects to our control stream our outgoing stream
status("Connection from: " + event.info.stream.farID + "\n");
break;
case "NetConnection.Connect.Failed":
status("Unable to connect to " + connectUrl + "\n");
loginState = LoginNotConnected;
break;
case "NetStream.Connect.Closed":
onHangup();
break;
}
}
private function listenerHandler(event:NetStatusEvent):void
{
status("Listener event: " + event.info.code + "\n");
}
private function controlHandler(event:NetStatusEvent):void
{
status("Control event: " + event.info.code + "\n");
}
private function outgoingStreamHandler(event:NetStatusEvent):void
{
status("Outgoing stream event: " + event.info.code + "\n");
switch (event.info.code)
{
case "NetStream.Play.Start":
if (callState == CallCalling)
{
outgoingStream.send("onIncomingCall", userNameInput.text);
}
break;
}
}
private function incomingStreamHandler(event:NetStatusEvent):void
{
status("Incoming stream event: " + event.info.code + "\n");
switch (event.info.code)
{
case "NetStream.Play.UnpublishNotify":
onHangup();
break;
}
}
// connection to rtmfp server succeeded and we register our peer ID with an id exchange service
// other clients can use id exchnage service to lookup our peer ID
private function connectSuccess():void
{
status("Connected, my ID: " + netConnection.nearID + "\n");
// exchange peer id using web service
idManager = new HttpIdManager();
idManager.service = WebServiceUrl;
idManager.addEventListener("registerSuccess", idManagerEvent);
idManager.addEventListener("registerFailure", idManagerEvent);
idManager.addEventListener("lookupFailure", idManagerEvent);
idManager.addEventListener("lookupSuccess", idManagerEvent);
idManager.addEventListener("idManagerError", idManagerEvent);
idManager.register(userNameInput.text, netConnection.nearID);
}
private function completeRegistration():void
{
// start the control stream that will listen to incoming calls
listenerStream = new NetStream(netConnection, NetStream.DIRECT_CONNECTIONS);
listenerStream.addEventListener(NetStatusEvent.NET_STATUS, listenerHandler);
listenerStream.publish("control" + userNameInput.text);
var c:Object = new Object
c.onPeerConnect = function(caller:NetStream):Boolean
{
status("Caller connecting to listener stream: " + caller.farID + "\n");
if (callState == CallReady)
{
ring();
callState = CallRinging;
// callee subscribes to media, to be able to get the remote user name
incomingStream = new NetStream(netConnection, caller.farID);
incomingStream.addEventListener(NetStatusEvent.NET_STATUS, incomingStreamHandler);
incomingStream.play("media-caller");
// set volume for incoming stream
var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value);
incomingStream.soundTransform = st;
incomingStream.receiveAudio(false);
incomingStream.receiveVideo(false);
var i:Object = new Object;
i.onIncomingCall = function(caller:String):void
{
if (callState != CallRinging)
{
status("onIncomingCall: Wrong call state: " + callState + "\n");
return;
}
remoteName = caller;
status("Incoming call from: " + caller + "\n");
}
i.onIm = function(name:String, text:String):void
{
textOutput.text += name + ": " + text + "\n";
textOutput.validateNow();
textOutput.verticalScrollPosition = textOutput.textHeight;
}
incomingStream.client = i;
return true;
}
status("onPeerConnect: all rejected due to state: " + callState + "\n");
return false;
}
listenerStream.client = c;
callState = CallReady;
}
private function placeCall(user:String, identity:String):void
{
status("Calling " + user + ", id: " + identity + "\n");
if (identity.length != 64)
{
status("Invalid remote ID, call failed\n");
callState = CallFailed;
return;
}
// caller subsrcibes to callee's listener stream
controlStream = new NetStream(netConnection, identity);
controlStream.addEventListener(NetStatusEvent.NET_STATUS, controlHandler);
controlStream.play("control" + user);
// caller publishes media stream
outgoingStream = new NetStream(netConnection, NetStream.DIRECT_CONNECTIONS);
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS, outgoingStreamHandler);
outgoingStream.publish("media-caller");
var o:Object = new Object
o.onPeerConnect = function(caller:NetStream):Boolean
{
status("Callee connecting to media stream: " + caller.farID + "\n");
return true;
}
outgoingStream.client = o;
startAudio();
startVideo();
// caller subscribes to callee's media stream
incomingStream = new NetStream(netConnection, identity);
incomingStream.addEventListener(NetStatusEvent.NET_STATUS, incomingStreamHandler);
incomingStream.play("media-callee");
// set volume for incoming stream
var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value);
incomingStream.soundTransform = st;
var i:Object = new Object;
i.onCallAccepted = function(callee:String):void
{
if (callState != CallCalling)
{
status("onCallAccepted: Wrong call state: " + callState + "\n");
return;
}
callState = CallEstablished;
status("Call accepted by " + callee + "\n");
}
i.onIm = function(name:String, text:String):void
{
textOutput.text += name + ": " + text + "\n";
}
incomingStream.client = i;
remoteVideo = new Video();
remoteVideo.width = 320;
remoteVideo.height = 240;
remoteVideo.attachNetStream(incomingStream);
remoteVideoDisplay.addChild(remoteVideo);
remoteName = user;
callState = CallCalling;
}
// process successful response from id manager
private function idManagerEvent(e:Event):void
{
status("ID event: " + e.type + "\n");
if (e.type == "registerSuccess")
{
switch (loginState)
{
case LoginConnecting:
loginState = LoginConnected;
break;
case LoginDisconnecting:
case LoginNotConnected:
loginState = LoginNotConnected;
return;
case LoginConnected:
return;
}
completeRegistration();
}
else if (e.type == "lookupSuccess")
{
// party query response
var i:IdManagerEvent = e as IdManagerEvent;
placeCall(i.user, i.id);
}
else
{
// all error messages ar IdManagerError type
var error:IdManagerError = e as IdManagerError;
status("Error description: " + error.description + "\n")
onDisconnect();
}
}
// user clicked accept button
private function acceptCall():void
{
stopRing();
incomingStream.receiveAudio(true);
incomingStream.receiveVideo(true);
remoteVideo = new Video();
remoteVideo.width = 320;
remoteVideo.height = 240;
remoteVideo.attachNetStream(incomingStream);
remoteVideoDisplay.addChild(remoteVideo);
// callee publishes media
outgoingStream = new NetStream(netConnection, NetStream.DIRECT_CONNECTIONS);
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS, outgoingStreamHandler);
outgoingStream.publish("media-callee");
var o:Object = new Object
o.onPeerConnect = function(caller:NetStream):Boolean
{
status("Caller connecting to media stream: " + caller.farID + "\n");
return true;
}
outgoingStream.client = o;
outgoingStream.send("onCallAccepted", userNameInput.text);
startVideo();
startAudio();
callState = CallEstablished;
}
private function cancelCall():void
{
onHangup();
}
private function rejectCall():void
{
onHangup();
}
private function onDisconnect():void
{
status("Disconnecting.\n");
onHangup();
callState = CallNotReady;
if (idManager)
{
idManager.unregister();
idManager = null;
}
loginState = LoginNotConnected;
netConnection.close();
netConnection = null;
}
// placing a call
private function onCall():void
{
if (netConnection && netConnection.connected)
{
if (calleeInput.text.length == 0)
{
status("Please enter name to call\n");
return;
}
// first, we need to lookup callee's peer ID
if (idManager)
{
idManager.lookup(calleeInput.text);
}
else
{
status("Not registered.\n");
return;
}
}
else
{
status("Not connected.\n");
}
}
private function startAudio():void
{
if (sendAudioCheckbox.selected)
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic && outgoingStream)
{
outgoingStream.attachAudio(mic);
}
}
else
{
if (outgoingStream)
{
outgoingStream.attachAudio(null);
}
}
}
private function startVideo():void
{
if (sendVideoCheckbox.selected)
{
var camera:Camera = Camera.getCamera(cameraIndex.toString());
if (camera)
{
localVideoDisplay.attachCamera(camera);
if (outgoingStream)
{
outgoingStream.attachCamera(camera);
}
}
}
else
{
localVideoDisplay.attachCamera(null);
if (outgoingStream)
{
outgoingStream.attachCamera(null);
}
}
}
// this function is called in every second to update charts, microhone level, and call timer
private function onActivityTimer(e:TimerEvent):void
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
micActivityLabel.text = mic.activityLevel.toString();
}
if (callState == CallEstablished && incomingStream && outgoingStream && outgoingStream.peerStreams.length == 1)
{
var recvInfo:NetStreamInfo = incomingStream.info;
var sentInfo:NetStreamInfo = outgoingStream.peerStreams[0].info;
audioRate.shift();
var a:Object = new Object;
a.Recv = recvInfo.audioBytesPerSecond * 8 / 1024;
a.Sent = sentInfo.audioBytesPerSecond * 8 / 1024;
audioRate.push(a);
audioRateDisplay.source = audioRate;
videoRate.shift();
var v:Object = new Object;
v.Recv = recvInfo.videoBytesPerSecond * 8 / 1024;
v.Sent = sentInfo.videoBytesPerSecond * 8 / 1024;
videoRate.push(v);
videoRateDisplay.source = videoRate;
srtt.shift();
var s:Object = new Object;
s.Data = recvInfo.SRTT;
srtt.push(s);
srttDisplay.source = srtt;
}
if (callState == CallEstablished)
{
callTimer++;
var elapsed:Date = new Date(2008, 4, 12);
elapsed.setTime(elapsed.getTime() + callTimer * 1000);
var formatter:DateFormatter = new DateFormatter();
var format:String = "JJ:NN:SS";
if (callTimer < 60)
{
format = "SS";
}
else if (callTimer < 60 * 60)
{
format = "NN:SS";
}
formatter.formatString = format
callTimerText.text = formatter.format(elapsed);
}
}
private function onDeviceStatus(e:StatusEvent):void
{
status("Device status: " + e.code + "\n");
}
private function onDeviceActivity(e:ActivityEvent):void
{
// status("Device activity: " + e.activating + "\n");
}
private function onHangup():void
{
status("Hanging up call\n");
stopRing();
calleeInput.text = "";
callState = CallReady;
if (incomingStream)
{
incomingStream.close();
incomingStream.removeEventListener(NetStatusEvent.NET_STATUS, incomingStreamHandler);
}
if (outgoingStream)
{
outgoingStream.close();
outgoingStream.removeEventListener(NetStatusEvent.NET_STATUS, outgoingStreamHandler);
}
if (controlStream)
{
controlStream.close();
controlStream.removeEventListener(NetStatusEvent.NET_STATUS, controlHandler);
}
incomingStream = null;
outgoingStream = null;
controlStream = null;
remoteName = "";
receiveAudioCheckbox.selected = true;
receiveVideoCheckbox.selected = true;
callTimer = 0;
}
private function speakerVolumeChanged(e:SliderEvent):void
{
if (incomingStream)
{
var st:SoundTransform = new SoundTransform(e.value);
incomingStream.soundTransform = st;
status("Setting speaker volume to: " + e.value + "\n");
}
localSO.data.speakerVolume = e.value;
localSO.flush();
}
private function micVolumeChanged(e:SliderEvent):void
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
mic.gain = e.value;
localSO.data.micVolume = e.value;
localSO.flush();
status("Setting mic volume to: " + e.value + "\n");
}
}
// sending text message
private function onSend():void
{
var msg:String = textInput.text;
if (msg.length != 0 && outgoingStream)
{
textOutput.text += userNameInput.text + ": " + msg + "\n";
outgoingStream.send("onIm", userNameInput.text, msg);
textInput.text = "";
}
}
private function micChanged(event:Event):void
{
var oldMicIndex:int = micIndex;
micIndex = micSelection.selectedIndex;
var mic:Microphone = Microphone.getMicrophone(micIndex);
var oldMic:Microphone = Microphone.getMicrophone(oldMicIndex);
mic.codec = oldMic.codec;
mic.rate = oldMic.rate;
mic.encodeQuality = oldMic.encodeQuality;
mic.framesPerPacket = oldMic.framesPerPacket;
mic.gain = oldMic.gain;
mic.setSilenceLevel(oldMic.silenceLevel);
if (callState == CallEstablished)
{
outgoingStream.attachAudio(mic);
}
localSO.data.micIndex = micIndex;
localSO.flush();
}
private function cameraChanged(event:Event):void
{
var oldCameraIndex:int = cameraIndex;
cameraIndex = cameraSelection.selectedIndex;
var camera:Camera = Camera.getCamera(cameraIndex.toString());
var oldCamera:Camera = Camera.getCamera(oldCameraIndex.toString());
camera.setMode(320, 240, 15);
camera.setQuality(0, oldCamera.quality);
// when user changes video device, we want to show preview
localVideoDisplay.attachCamera(camera);
if (callState == CallEstablished)
{
outgoingStream.attachCamera(camera);
}
localSO.data.cameraIndex = cameraIndex;
localSO.flush();
}
private function videoQualityChanged(e:SliderEvent):void
{
var camera:Camera = Camera.getCamera(cameraIndex.toString());
if (camera)
{
camera.setQuality(0, e.value);
status("Setting camera quality to: " + e.value + "\n");
}
}
private function onAudioMuted():void
{
if (incomingStream)
{
incomingStream.receiveAudio(receiveAudioCheckbox.selected);
}
}
private function onVideoPaused():void
{
if (incomingStream)
{
incomingStream.receiveVideo(receiveVideoCheckbox.selected);
}
}
private function handleCodecChange(event:ItemClickEvent):void
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
if (event.currentTarget.selectedValue == "speex")
{
codecPropertyStack.selectedChild = speexCanvas;
mic.codec = SoundCodec.SPEEX;
mic.framesPerPacket = 1;
mic.encodeQuality = int(speexQualitySelector.selectedItem);
mic.setSilenceLevel(0);
}
else
{
codecPropertyStack.selectedChild = nellymoserCanvas;
mic.codec = SoundCodec.NELLYMOSER;
mic.rate = int(nellymoserRateSelector.selectedItem);
mic.setSilenceLevel(10);
}
}
}
private function speexQuality(e:Event):void
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
var quality:int = int(ComboBox(e.target).selectedItem);
mic.encodeQuality = quality;
status("Setting speex quality to: " + quality);
}
}
private function nellymoserRate(e:Event):void
{
var mic:Microphone = Microphone.getMicrophone(micIndex);
if (mic)
{
var rate:int = int(ComboBox(e.target).selectedItem);
mic.rate = rate;
status("Setting Nellymoser rate to: " + rate);
}
}
private function ring():void
{
ringer = new Sound();
ringer.addEventListener("sampleData", ringTone);
ringerChannel = ringer.play();
}
private function stopRing():void
{
if (ringerChannel)
{
ringerChannel.stop();
ringer.removeEventListener("sampleData", ringTone);
ringer = null;
ringerChannel = null;
}
}
private function ringTone(event:SampleDataEvent):void
{
for (var c:int=0; c<8192; c++)
{
var pos:Number = Number(c + event.position) / Number(6 * 44100);
var frac:Number = pos - int(pos);
var sample:Number;
if (frac < 0.066)
{
sample = 0.4 * Math.sin(2* Math.PI / (44100/784) * (Number(c + event.position)));
}
else if (frac < 0.333)
{
sample = 0.2 * (Math.sin(2* Math.PI / (44100/646) * (Number(c + event.position)))
+ Math.sin(2* Math.PI / (44100/672) * (Number(c + event.position)))
+ Math.sin(2* Math.PI / (44100/1034) * (Number(c + event.position)))
+ Math.sin(2* Math.PI / (44100/1060) * (Number(c + event.position))));
}
else
{
sample = 0;
}
event.data.writeFloat(sample);
event.data.writeFloat(sample);
}
}
]]>
</mx:Script>
<mx:Style>
.buttonStyle {
color: "0x000000";
textRollOverColor: "0x000000";
textSelectedColor: "0x000000";
}
</mx:Style>
<mx:VBox paddingTop="5" paddingLeft="5" paddingBottom="5" borderStyle="solid" cornerRadius="10" backgroundColor="0x303030">
<mx:ViewStack selectedIndex="{loginState}" resizeToContent="true">
<mx:HBox>
<mx:Label text="User Name: " color="0xffffff"/>
<mx:TextInput id="userNameInput" width="80" />
<mx:Button label="CONNECT" click="onConnect()" enabled="{userNameInput.text.length > 0}" styleName="buttonStyle"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Connecting to {connectUrl}" color="0xffffff"/>
<mx:Button label="CANCEL" click="onDisconnect()" styleName="buttonStyle"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Connected as {userNameInput.text}" color="0xffffff"/>
<mx:Button label="DISCONNECT" click="onDisconnect()" styleName="buttonStyle"/>
</mx:HBox>
<mx:Canvas>
<mx:Label text="Disconnecting from {connectUrl}" color="0xffffff"/>
</mx:Canvas>
</mx:ViewStack>
<mx:ViewStack selectedIndex="{callState}" resizeToContent="true" creationPolicy="all" >
<mx:Canvas>
<mx:Label text="Please enter user name and connect" color="0xffffff" height="22"/>
</mx:Canvas>
<mx:HBox>
<mx:TextInput id="calleeInput" enter="onCall()"/>
<mx:Button label="CALL" click="onCall()" styleName="buttonStyle" enabled="{calleeInput.text.length > 0}"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Calling {remoteName}" color="0xffffff"/>
<mx:Button label="CANCEL" click="cancelCall()" styleName="buttonStyle"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Incoming call from: {remoteName}" color="0xffffff"/>
<mx:Button label="ACCEPT" click="acceptCall()" styleName="buttonStyle"/>
<mx:Button label="REJECT" click="rejectCall()" styleName="buttonStyle"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Call in progress with {remoteName}" color="0xffffff"/>
<mx:Button label="HANGUP" click="onHangup()" styleName="buttonStyle"/>
<mx:Label id="callTimerText" color="0xffffff"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Call failed to {calleeInput.text}" color="0xffffff"/>
<mx:Button label="HANGUP" click="onHangup()" styleName="buttonStyle"/>
</mx:HBox>
</mx:ViewStack>
<mx:HBox>
<mx:VideoDisplay id="remoteVideoDisplay" width="320" height="240" />
<mx:VBox>
<mx:VideoDisplay id="localVideoDisplay" width="180" height="135" />
<mx:Spacer height="10" />
<mx:HBox visible="false" includeInLayout="false">
<mx:Label text="Microphone: " color="0xffffff"/>
<mx:Label id="micActivityLabel" color="0xffffff"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Mic:" color="0xffffff" width="56"/>
<mx:HSlider id="micVolumeSlider" showDataTip="false" width="120" minimum="0" maximum="100" change="micVolumeChanged(event)"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Speaker:" color="0xffffff" width="56"/>
<mx:HSlider id="speakerVolumeSlider" showDataTip="false" width="120" minimum="0" maximum="1" change="speakerVolumeChanged(event)"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Receive:" color="0xffffff" width="60"/>
<mx:CheckBox id="receiveAudioCheckbox" label="Audio" fontSize="9" click="onAudioMuted()" enabled="{callState == CallEstablished}" selected="true" styleName="buttonStyle"/>
<mx:CheckBox id="receiveVideoCheckbox" label="Video" fontSize="9" click="onVideoPaused()" enabled="{callState == CallEstablished}" selected="true" styleName="buttonStyle"/>
</mx:HBox>
</mx:VBox>
</mx:HBox>
<mx:TextArea id="textOutput" width="509" height="100" editable="false" verticalScrollPolicy="auto" />
<mx:HBox>
<mx:TextInput id="textInput" width="432" enter="onSend()"/>
<mx:Button label="SEND" click="onSend()" styleName="buttonStyle" enabled="{textInput.text.length > 0 && callState == CallEstablished}"/>
</mx:HBox>
<mx:TabBar dataProvider="{optionsStack}" width="509" styleName="buttonStyle"/>
<mx:ViewStack id="optionsStack" borderStyle="solid" creationPolicy="all" >
<mx:VBox label="STATUS" >
<mx:TextArea id="statusArea" width="507" height="120" editable="false" verticalScrollPolicy="auto" />
<mx:Button label="Clear" click="statusArea.text=''" />
</mx:VBox>
<mx:VBox label="DEVICES" enabled="{loginState == LoginConnected}" paddingTop="5" paddingLeft="5">
<mx:HBox>
<mx:Label text="Audio capture: " color="0xffffff"/>
<mx:ComboBox id="micSelection" dataProvider="{micNames}" change="micChanged(event)" />
</mx:HBox>
<mx:HBox>
<mx:Label text="Video capture: " color="0xffffff"/>
<mx:ComboBox id="cameraSelection" dataProvider="{cameraNames}" change="cameraChanged(event)" />
</mx:HBox>
</mx:VBox>
<mx:HBox label="AUDIO/VIDEO" enabled="{loginState == LoginConnected}">
<mx:Panel borderStyle="solid" title="Audio Codec" width="247" height="120" backgroundColor="0xA0A0A0">
<mx:HBox>
<mx:RadioButtonGroup id="codecGroup" itemClick="handleCodecChange(event)"/>
<mx:RadioButton groupName="codecGroup" value="speex" label="Speex" selected="true" />
<mx:RadioButton groupName="codecGroup" value="nellymoser" label="Nellymoser" />
</mx:HBox>
<mx:ViewStack id="codecPropertyStack" resizeToContent="true" >
<mx:Canvas id="speexCanvas">
<mx:HBox>
<mx:Label text="Encode quality: " />
<mx:ComboBox id="speexQualitySelector" dataProvider="{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}" selectedIndex="6" change="speexQuality(event)" />
</mx:HBox>
</mx:Canvas>
<mx:Canvas id="nellymoserCanvas">
<mx:HBox>
<mx:Label text="Encode rate [kHz]: " />
<mx:ComboBox id="nellymoserRateSelector" dataProvider="{[5, 8, 11, 16, 22, 44]}" selectedIndex="1" change="nellymoserRate(event)" />
</mx:HBox>
</mx:Canvas>
</mx:ViewStack>
<mx:CheckBox id="sendAudioCheckbox" label="Send Audio" click="startAudio()" selected="true" />
</mx:Panel >
<mx:Panel borderStyle="solid" title="Video Quality" width="247" height="120" backgroundColor="0xA0A0A0">
<mx:HSlider width="220" showDataTip="false" tickInterval="25" labels="[0, 25, 50, 75, 100]" minimum="1" maximum="100" value="80" change="videoQualityChanged(event)"/>
<mx:CheckBox id="sendVideoCheckbox" label="Send Video" click="startVideo()" selected="true" />
</mx:Panel>
</mx:HBox>
<mx:VBox label="STATISTICS" enabled="{loginState == LoginConnected}">
<mx:LineChart id="audioRateChart" dataProvider="{audioRateDisplay}" height="105" width="485" color="0xffffff">
<mx:series>
<mx:LineSeries yField="Recv" displayName="Received Audio Rate [kbps]"/>
<mx:LineSeries yField="Sent" displayName="Sent Audio Rate [kbps]" />
</mx:series>
</mx:LineChart>
<mx:Legend dataProvider="{audioRateChart}" direction="horizontal" color="0xffffff"/>
<mx:LineChart id="videoRateChart" dataProvider="{videoRateDisplay}" height="105" width="485" color="0xffffff">
<mx:series>
<mx:LineSeries yField="Recv" displayName="Received Video Rate [kbps]"/>
<mx:LineSeries yField="Sent" displayName="Sent Video Rate [kbps]" />
</mx:series>
</mx:LineChart>
<mx:Legend dataProvider="{videoRateChart}" direction="horizontal" color="0xffffff"/>
<mx:LineChart id="rttChart" dataProvider="{srttDisplay}" height="105" width="485" color="0xffffff">
<mx:series>
<mx:LineSeries yField="Data" displayName="RTT [ms]" />
</mx:series>
</mx:LineChart>
<mx:Legend dataProvider="{rttChart}" color="0xffffff"/>
</mx:VBox>
</mx:ViewStack>
</mx:VBox>
</mx:Application>