using Byn.Media; using UnityEngine; namespace Byn.Unity.Examples { /// <summary> /// This scenario uses two roles: a receiver and a sender. /// The receiver call object will switch into listening mode and /// wait for an incoming call. Once the receiver is fully set /// up a sender call object is created which then establishes a /// direct connection. Once the connection is established you should /// be able to hear your own voice being replayed by the speaker. /// /// Both receiver & sender use the ICall interface. This interface /// is the main interface of the library and was designed to allow /// creating one-on-one audio & video calls that work on all /// platforms with only minimal use platform specific code.It is /// also designed to be improved and maintained for a long time /// into the future while new features are being added thus as /// Conference calls. /// /// The connection will be established between STEP6 and STEP7. /// Here is what happens during that time: /// 1. The receiver is registered using a unique address on the /// signaling server /// 2. The sender is connecting to the signaling server and requests /// to be connected to the address used by the receiver /// 3. The signaling server will now connect them indirectly and /// forward messages between both /// 4. sender & receiver will exchange WebRTC specific messages /// containing details about if audio, video is used, what /// data channels are being establishes, the codecs being used /// and multiple IP addresses, port numbers and network protocols /// that might possibly allow the two to connect directly /// (or indirectly if TURN is available) /// 5. This step starts already in parallel with 4. /// Sender & receiver will now try to connect using /// the info exchanged. First it will try connections via /// LAN / WIFI. If this fails it will try to use STUN server to /// open the routers port and connect via internet. If this also /// fails it will try to use a TURN server to relay the data /// instead of using a direct connection. Note that this /// example doesn't set a stun / turn server. /// 6. Once a direct connection is established and all data, audio /// and video channels are ready the call object will report a /// CallAccepted event. /// 7. The Update method of the call objects have to keep getting called /// until the call ends. /// The call will keep checking the state of the connection and forward /// possible events that might be triggerd (e.g. CallEnded or /// updated video frames if video is active) /// 8. The signaling connections will be cut after a few seconds and the /// address can be reused before the call finishes. /// /// Please follow the comments in the example to learn more. /// </summary> public class MinimalCall : MonoBehaviour { //Sender call object ICall sender; //Receiver call object ICall receiver; //Network configuration shared by both NetworkConfig netConf; //Address used to connect the right sender & receiver private string address; void Start() { //STEP1: set up our member variables //first access to this will boot up the library and create a UnityCallFactory //game object which will manage everything in the background. if (UnityCallFactory.Instance == null) { //if it is null something went terribly wrong Debug.LogError("UnityCallFactory missing. Platform not supported / dll's missing?"); return; } //Use this address to connect. Watch out: you use a generic name //and someone else starts the app using the same name you might end up connecting to //someone else! address = Application.productName + "_MinimalCall"; //Set signaling server url. This server is used to reserve the address, to find the //other call object, to exchange connection information (ip, port + webrtc specific info) //which are then used later to create a direct connection. //"callapp" corresponds to a specific configuration on the server side //and also acts as a pool of possible users with addresses we can connect to. netConf = new NetworkConfig(); //e.g.: "ws://signaling.because-why-not.com/test" netConf.SignalingUrl = ExampleGlobals.Signaling; //possible stun / turn servers. not needed for local test. only for online connections //netConf.IceServers.Add(new IceServer("stun.l.google.com:19302")); SetupReceiver(); } private void SetupReceiver() { //STEP2: Setup and create the receiver call Debug.Log("receiver setup"); //receiver doesn't use video and audio MediaConfig mediaConf1 = new MediaConfig(); mediaConf1.Video = false; mediaConf1.Audio = false; //this creates the receiver receiver = UnityCallFactory.Instance.Create(netConf); //register our event handler. This is used to control all //further interaction with the call object later receiver.CallEvent += Receiver_CallEvent; //Ask for permission to access the media //(will always work as both is set to false) //See Receiver_CallEvent for next step receiver.Configure(mediaConf1); } /// <summary> /// Called by Unitys update loop. Will refresh the state of the call, sync the events with /// unity and trigger the event callbacks below. /// /// </summary> private void Update() { if (receiver != null) receiver.Update(); if (sender != null) sender.Update(); } /// <summary> /// Event handler for the receiver side. /// </summary> /// <param name="src">receiver mCall object</param> /// <param name="args">event specific arguments</param> private void Receiver_CallEvent(object src, CallEventArgs args) { if (args.Type == CallEventType.ConfigurationComplete) { //STEP3: Connect to the previously set signaling server //and try to listen for incoming calls on the set address. Debug.Log("receiver configuration done. Listening on address " + address); receiver.Listen(address); } else if (args.Type == CallEventType.WaitForIncomingCall) { //STEP4A: Our address is registered with the server now //we wait for the sender to connect Debug.Log("receiver is ready to accept incoming calls"); //setup the sender to connect SenderSetup(); } else if (args.Type == CallEventType.ListeningFailed) { //STEP4B: Alternatively, we failed to listen. //e.g. due to no internet / server down / address in use //currently no specific error information are available. Debug.LogError("receiver failed to listen to the address"); } else if (args.Type == CallEventType.CallAccepted) { //STEP7: //The sender connected successfully and a direct connection was //created. Debug.Log("receiver CallAccepted"); } } /// <summary> /// Setting up the sender. This is called once the receiver is registered /// at the signaling server and is ready to receive an incoming connection. /// </summary> private void SenderSetup() { //STEP5: sending up the sender Debug.Log("sender setup"); sender = UnityCallFactory.Instance.Create(netConf); MediaConfig mediaConf2 = new MediaConfig(); //keep video = false for now to keep the example simple & without UI mediaConf2.Video = false; //send audio. an echo will be heard to confirm it works mediaConf2.Audio = true; sender.CallEvent += Sender_CallEvent; //Set the configuration. It will trigger an ConfigurationComplete // event once completed or ConnectionFailed if something went wrong. // //Note: platforms handle this very differently e.g. // * Windows & Mac will access the media devices and immediately trigger // ConfigurationComplete event // * iOS and Android might ask the user first for permission // (or crash the app if it isn't allowed to access! Check your // Unity project setup!) // * WebGL behavior is browser specific. Currently, Chrome has a fixed // audio & video device configured and just asks for access while // firefox lets the user decide which device to use once Configure is // called. // See Receiver_CallEvent for next step sender.Configure(mediaConf2); } private void Sender_CallEvent(object src, CallEventArgs args) { if (args.Type == CallEventType.ConfigurationComplete) { //STEP6: we got access to media devices Debug.Log("sender configuration done. Listening on address " + address); sender.Call(address); } else if (args.Type == CallEventType.ConfigurationFailed) { //STEP6: user might have blocked access? Debug.LogError("sender failed to access the audio device"); } else if (args.Type == CallEventType.ConnectionFailed) { //This can happen if the signaling connection failed or //if the direct connection failed e.g. due to firewall //See FAQ for more info how to find problems that cause this Debug.LogError("sender failed to connect"); } else if (args.Type == CallEventType.CallAccepted) { //STEP7: Call Accepted Debug.Log("sender CallAccepted"); } else if (args.Type == CallEventType.CallEnded) { //STEP8: CallEnded. Either network died or //one of the calls was destroyed/disposed Debug.Log("sender received CallEnded event"); } } private void OnDestroy() { //STEP9: GameObject is being destroyed either due to user action, another script or //because Unity shuts down -> end the calls + cleanup memory //This step is extremely important and not cleaning up might //cause stalls or crashes. It will also keep the users webcam active thus //preventing other apps from accessing it if (receiver != null) { receiver.Dispose(); receiver = null; } if (sender != null) { sender.Dispose(); sender = null; } } } }