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);
}
}
}
}