Simple Call

using Byn.Media;
using Byn.Net;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

namespace Byn.Unity.Examples
{
    /// <summary>
    /// This app is a more complex and realistic version of the MinimalCall example.
    /// Only code not yet mentioned in an earlier example is commented.
    /// It requires two game objects that have both the SimpleCall script attached. 
    /// One is set as sender (Sender ticked) and one set as receiver. 
    /// 
    /// Unlike the MinimalCall it uses a stun server thus it is able to connect via internet.
    /// (assuming router supports it and no filewalls blocks it)
    /// It can also be used with a Unity RawFrame UI element to show the local or remote video.
    /// Note that the video needs to be flipped (Y Scale -1) as Unity stores video upside down
    /// internally and the library avoid flipping it via the CPU to increase performance.
    /// 
    /// You can use this example in two different ways:
    /// * For easy testing make sure to place both sender & receiver gameobject in a single scene.
    ///   During start the receiver will automatically start and wait for an incoming call and
    ///   after a few seconds the sender will start and then connect
    /// * You can also place them in two different scenes and use two different systems. First one 
    ///   starts the scene with the receiver. Then the other can start the scene with the sender.
    ///   They will then connect via network. If you start this on two different systems that
    ///   aren't in the same LAN note that in some cases the firewall or router configuration
    ///   can block a connection. 
    ///   Make sure if you use two projects they have the same name as this is used as an address
    ///   for the two applications to find each other. 
    ///   
    /// </summary>
    public class SimpleCall : MonoBehaviour
    {
        /// <summary>
        /// As part of this scenario and to simplify local testing one SimpleCall needs to be
        /// sender and one needs to be receiver (set via UnityEditor)
        /// </summary>
        public bool _Sender = false;
        public RawImage _LocalImage;
        public RawImage _RemoteImage;


        /// <summary>
        /// A specific state during this example
        /// </summary>
        public enum SimpleCallState
        {
            Invalid,
            Config,
            Calling,
            InCall,
            Ended,
            Error
        }

        /// <summary>
        /// Used to keep track of the current state for error messages / user info
        /// </summary>
        SimpleCallState mState = SimpleCallState.Invalid;

        /// <summary>
        /// Interface representing a single call.
        /// </summary>
        private ICall mCall;

        /// <summary>
        /// Texture for local image
        /// camera
        /// </summary>
        private Texture2D mLocalVideo;

        /// <summary>
        /// Texture for remote image
        /// </summary>
        private Texture2D mRemoteVideo;


        void Start()
        {
            //STEP1: setup
            if (UnityCallFactory.Instance == null)
            {
                Error("Factory init failed");
                return;
            }
            Log("Starting SimpleCall example");

            NetworkConfig netConf = new NetworkConfig();
            netConf.SignalingUrl = "ws://signaling.because-why-not.com/callapp";

            //Set a stun server as ice server. We use a free google stun
            //server here. (blocked in China)
            //This is used by WebRTC to open a port in your router to allow 
            //incoming connections. (not all routers support this though and
            //some firewalls block it)
            netConf.IceServers.Add(new IceServer("stun:stun.l.google.com:19302"));

            mCall = UnityCallFactory.Instance.Create(netConf);
            if (mCall == null)
            {
                //this might happen if our configuration is invalid e.g. broken stun server url
                //(it won't notice if the stun server is offline though)
                Error("Call init failed");
                return;
            }
            Log("Call object created");

            mCall.CallEvent += Call_CallEvent;

            //Setup is completed. Now set media configuration
            if (_Sender)
            {
                //the sender side waits 5 seconds to give the receiver
                //time to register the address online and wait for the connection
                StartCoroutine(ConfigureDelayed(5));
            }
            else
            {
                //Receiver starts immediately
                Configure();
            }
        }

        /// <summary>
        /// This is setting the media used for this call.
        /// </summary>
        public void Configure()
        {
            //STEP2: configure media devices
            MediaConfig mediaConfig = new MediaConfig();
            if (_Sender)
            {
                //sender is sending audio and video
                mediaConfig.Audio = true;
                mediaConfig.Video = true;

                //We ask for 320x240 (should work fine
                //even on the weakest systems)
                //note that not all devices can actually
                //deliver the resolution we ask for
                mediaConfig.IdealWidth = 320;
                mediaConfig.IdealHeight = 240;

            }
            else
            {
                //set to false to avoid 
                //echo & multiple calls trying to access the same camera
                mediaConfig.Audio = false;
                mediaConfig.Video = false;
            }

            mCall.Configure(mediaConfig);
            mState = SimpleCallState.Config;
            Log("Configure media devices");
        }

        /// <summary>
        /// Used for running the example automatically. 
        /// The receiver needs some time to register its address on the server
        /// first before the sender can connect.
        /// </summary>
        /// <param name="startInSec">time in seconds</param>
        /// <returns>Unity coroutine</returns>
        private IEnumerator ConfigureDelayed(float startInSec)
        {
            yield return new WaitForSeconds(startInSec);
            Configure();
        }

        void Update()
        {
            mCall.Update();
        }

        private void OnDestroy()
        {
            Cleanup();
        }

        public void Cleanup()
        {
            if (mCall != null)
            {
                mCall.Dispose();
                mCall = null;
            }
        }

        private void Call_CallEvent(object sender, CallEventArgs args)
        {
            if (args.Type == CallEventType.ConfigurationComplete)
            {
                //STEP3: configuration completed -> try calling
                Call();
            }
            else if (args.Type == CallEventType.ConfigurationFailed)
            {
                Error("Accessing audio / video failed");
            }
            else if (args.Type == CallEventType.ConnectionFailed)
            {
                Error("ConnectionFailed");
            }
            else if (args.Type == CallEventType.ListeningFailed)
            {
                Error("ListeningFailed");
            }
            else if (args.Type == CallEventType.CallAccepted)
            {
                //STEP5: We are connected
                mState = SimpleCallState.InCall;
                Log("Connection established");
            }
            else if (args.Type == CallEventType.CallEnded)
            {
                mState = SimpleCallState.Ended;
                Log("Call ended.");
            }
            else if (args.Type == CallEventType.FrameUpdate)
            {
                //STEP6: until the end of the call we receive frames here
                //Note that this is being called after Configure already for local frames even before
                //a connection is established!
                //This is triggered each video frame for local and remote video images
                FrameUpdateEventArgs frameArgs = args as FrameUpdateEventArgs;


                if (frameArgs.ConnectionId == ConnectionId.INVALID)
                {
                    //invalid connection id means this is a local frame
                    //copy the raw pixels into a unity texture
                    bool textureCreated = UnityMediaHelper.UpdateTexture(ref mLocalVideo, frameArgs.Frame, frameArgs.Format);
                    if (textureCreated)
                    {
                        if (_LocalImage != null)
                            _LocalImage.texture = mLocalVideo;
                        Log("Local Texture created " + frameArgs.Frame.Width + "x" + frameArgs.Frame.Height + " format: " + frameArgs.Format);
                    }

                }
                else
                {
                    //remote frame. For conference calls we would get multiple remote frames with different id's
                    bool textureCreated = UnityMediaHelper.UpdateTexture(ref mRemoteVideo, frameArgs.Frame, frameArgs.Format);
                    if (textureCreated)
                    {
                        if (_RemoteImage != null)
                            _RemoteImage.texture = mRemoteVideo;
                        Log("Remote Texture created " + frameArgs.Frame.Width + "x" + frameArgs.Frame.Height + " format: " + frameArgs.Format);
                    }
                }
            }
        }

        private void Call()
        {
            string address = Application.productName + "_SimpleCall";

            if (_Sender)
            {
                //STEP4: Sender calls (outgoing connection) 
                mCall.Call(address);
            }
            else
            {
                //STEP4: Receiver listens (waiting for incoming connection)
                mCall.Listen(address);
            }
            mState = SimpleCallState.Calling;
        }

        private void Error(string errormsg)
        {
            if (_Sender)
            {
                Debug.LogError("Sender: Error during state " + mState + ": " + errormsg);
            }
            else
            {
                Debug.LogError("Receiver: Error during state " + mState + ": " + errormsg);
            }
            mState = SimpleCallState.Error;
        }

        private void Log(string msg)
        {
            if (_Sender)
            {
                Debug.Log("Sender: " + msg);
            }
            else
            {
                Debug.Log("Receiver: " + msg);
            }
        }
    }
}

Leave a Reply