Unity Sample Integration

Warning

This feature is currently in beta. All the basic functionalities are present but some conveniences are missing and some API are likely to change in the future. Particularly, the capacity to run the sample from the Unity Editor is missing. Your feedback is welcome.

Integrating the Genvid SDK into your Unity project is a simple process using 3 DLL files. In this page, we will use our own sample to explain how to integrate the API directly into your project and how to use it. The Unity sample is similar to our D3D11 tutorial sample and exposes the same functionalities.

Current limitation

The sample currently only allow the Genvid SDK to be activated while built as an executable project, so it is not working in the editor. We added an option to disable the Genvid SDK inside the editor in the event you would need to test in the editor. Simply select the GameManager object and click on the Genvid SDK Active checkbox to disable it.

  1. How to run the sample
  2. Files needed
  3. Additional script files
  4. Include and declaration
  5. Initialization
  6. Callbacks
  7. Update
  8. Video capture
  9. Termination

How to run the sample

This guide assumes that you are already properly configured to run the Genvid tutorial sample (see Tutorial), i.e., that your config file is properly set up with a working YouTube account (see Genvid Configuration) and that you can successfully run the Genvid services (see Local Environment Usage).

Before explaining about the files and methods used, let us explain how to run the Unity sample.

  1. Activate the project:
py local.py load-project samples/unity
  1. Build the project and the website:
py local.py run-script build

Note that there are now several build options, so you can use: build which is building in 64-bits in Release, build_debug which is building in 64-bits in Debug, build_32 which is building in 32-bits in Release and build_debug_32 which is building in 64-bits in Debug. Note that any debug version will build the sample in Development mode. Using this script will copy all the necessary dll files into the current Unity sample.

  1. Launch the game with all of the required Genvid services:
py local.py start

You can see the website by doing py local.py open.

In the event you would want to stop the stack: py local.py stop.

Files needed

Three files are currently required to be integrated into your project to fully use our SDK without issue. If you want to run the current sample, you don’t need to copy the files in the sample. By using local.py, you can build the project and run it without having to manually copy any file.

Genvid.dll

This is the file created by our Native SDK and it is used to make all the calls related to Genvid. The Genvid SDK includes both a 32-bit and 64-bit version of the library for the Windows platform. Make sure to use the proper one related to the compilation of your application.

This file needs to be placed inside the Plugins folder of your project. For the current sample, we placed the files inside a x64 and x86 folder depending of the library version. The command script of the project handles this for you when you run local.py run-script build.

As a developer, you will not need to call any function from this DLL directly since we created a C# wrapper integrating all the functions needed from it to simplify the process.

GenvidPlugin.dll

This is a file created by the GenvidPlugin project integrated in the Unity sample project folder. This file is taking care of making calls to do the video capture.

This file needs to be placed inside the Plugins folder of your project. For the current sample, we placed the files inside a x64 and x86 folder depending of the library version. The command script of the project handles this for you when you run local.py run-script build.

GenvidSDKCSharp.dll

This is a file that is used to make all the calls to the Genvid.dll. This file is a C# wrapper of the Genvid C SDK, so all the proper conversions are handled by this dll.

This file needs to be placed inside the Plugins folder of your project.

Additional script files

Some additional script files are used for the Unity sample, so we made up a list for each and what are their purpose.

GenvidActivation.cs

This is a file that is used to create a dynamic object that is carried from one scene to another. This object is used to determine if the Genvid SDK initialization process has been done when proceeding to another scene.

This file is placed inside the Assets folder of the sample project.

GameManagement.cs

This is a file that is used to handle the Genvid SDK in our sample project. This script also coordinates various actions happening on the scene.

This file is placed inside the Assets folder of the sample project.

MovementCube.cs

This is a file that is used to move the objects on the scene. It is also used to update popularity and colors of the associated game object.

This file is placed inside the Assets folder of the sample project.

Build.cs

This is a file that is used to build the unity sample project via the python script in various configurations.

This file is placed inside the Assets folder of the sample project.

Include and declaration

In the include section, we need to have using GenvidSDKCSharp to be able to use all the calls from the Genvid SDK. Afterwards, you can call any content from the Genvid SDK by writting GenvidSDK.theVariableOrFunctionNeeded.

In the declaration section of the script, we need to have various dll import to be able to use the GenvidPlugin properly for the video capture.

    [DllImport("GenvidPlugin", EntryPoint = "getVideoInitStatus")]
    [return: MarshalAs(UnmanagedType.I4)]
    private static extern GenvidSDK.Status GetVideoInitStatus();

    [DllImport("GenvidPlugin", EntryPoint = "getVideoSubmitDataStatus")]
    [return: MarshalAs(UnmanagedType.I4)]
    private static extern GenvidSDK.Status GetVideoSubmitDataStatus();

    [DllImport("GenvidPlugin", EntryPoint = "setupVideoChannel")]
    public static extern void SetupVideoChannel(string streamID);

    [DllImport("GenvidPlugin", EntryPoint = "cleanUp")]
    public static extern void CleanUp();

    [DllImport("GenvidPlugin")]
    public static extern IntPtr GetRenderEventFunc();
  1. GenvidSDK.Status GetVideoInitStatus() is used to get the GenvidSDK.GenvidStatus of the video initialization process.
  2. GenvidSDK.Status GetVideoSubmitDataStatus() is used to get the GenvidSDK.GenvidStatus of the submit video data process.
  3. SetupVideoChannel(string streamID) is used to indicate which stream is the one used for the video.
  4. CleanUp() is used to clean up pointers used in GenvidPlugin.dll when done with the video capture.
  5. IntPtr GetRenderEventFunc() is a function that we call to proceed with the video streaming (either starting the video capture or submitting video data).

Initialization

The initialization process is performed during the OnEnable() function of our sample inside the GameManagement.cs script.

    // Use this for initialization
    void OnEnable ()
    {
        // Find all the camera available on the scene
        m_sceneCamera = FindObjectsOfType (typeof(Camera)) as Camera[];

        if (m_sceneCamera != null) 
        {
            for (int x = 0; x < m_sceneCamera.Length; x++)
            {
                if(x == 0)
                {
                    m_sceneCamera[x].enabled = true;
                }
                else
                {
                    m_sceneCamera[x].enabled = false;
                }
            }
        }

        m_cameraText = GameObject.Find("Camera_active_text").GetComponent<Text>();
        m_sceneText = GameObject.Find("Scene_change_text").GetComponent<Text>();
        m_timecodeText = GameObject.Find("timecode_text").GetComponent<Text>();
        m_getParamIntText = GameObject.Find("getParamInt_text").GetComponent<Text>();
        m_getUTF8Text = GameObject.Find("getUTF8_text").GetComponent<Text>();

        m_activeObjectsList = GameObject.FindGameObjectsWithTag("ActiveObjects");
        m_GenvidActivation = GameObject.Find("GenvidActivation");
        bool initializationGenvid;

        if (m_GenvidActivation == null)
        {
            m_GenvidActivation = new GameObject();
            m_GenvidActivation.name = "GenvidActivation";
            m_GenvidActivation.AddComponent<GenvidActivation>();
            m_GenvidActivation.GetComponent<GenvidActivation>().activateObject(GenvidSDKActive);
            GC.KeepAlive(m_GenvidActivation);
            initializationGenvid = true;
        }
        else
        {
            GenvidSDKActive = m_GenvidActivation.GetComponent<GenvidActivation>().getGenvidActive();
            initializationGenvid = false;
        }

        if (GenvidSDKActive)
        {
            GenvidSDK.Status gvStatus;
            
            if (initializationGenvid)
            {
                // Genvid Initialize
                gvStatus = GenvidSDK.Initialize();
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while Genvid Initialize : " + GenvidSDK.StatusToString(gvStatus));
                }

                // Initialize all Genvid streams
                foreach (string stream in Enum.GetNames(typeof(Stream)))
                {
                    var status = GenvidSDK.CreateStream(stream);
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while creating the " + stream + " stream: " + GenvidSDK.StatusToString(status));
                    }
                }

                gvStatus = GenvidSDK.SetParameter(Stream.Audio, "Audio.Source.WASAPI", 1);
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while setting the parameter for the audio : " + GenvidSDK.StatusToString(gvStatus));
                }

                // Setting up the video channel
                SetupVideoChannel(Stream.Video.ToString());
            }

            // Setting up the callback for the events with their subscribe
            m_eventCallback = new GenvidSDK.EventSummaryCallback(this.eventCallbackFunction);
            m_operationCompleteCallback = new GenvidSDK.EventSummaryCallback(this.operationCompleteCallbackFunction);

            // Initialize all Genvid Events
            foreach (string eventSummary in Enum.GetNames(typeof(EventSummary)))
            {
                var userData = new IntPtr((int)Enum.Parse(typeof(EventSummary), eventSummary));

                var status = GenvidSDK.Subscribe(eventSummary, m_eventCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe for " + eventSummary + " for m_eventCallback : " + GenvidSDK.StatusToString(status));
                }

                status = GenvidSDK.Subscribe(eventSummary, m_operationCompleteCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe for " + eventSummary + " for m_operationCompleteCallback : " + GenvidSDK.StatusToString(status));
                }
            }

            // Setting up the callback for the commands with their subscribe
            m_commandCallback = new GenvidSDK.CommandCallback(this.commandCallbackFunction);

            // Initialize all Genvid Commands
            foreach (string command in Enum.GetNames(typeof(Command)))
            {
                var userData = new IntPtr((int)Enum.Parse(typeof(Command), command));

                var status = GenvidSDK.SubscribeCommand(command, m_commandCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the subscribe command for " + command + ": " + GenvidSDK.StatusToString(status));
                }
            }

            int paramReceived = 0;
            gvStatus = GenvidSDK.GetParameterInt(Stream.Audio.ToString(), "Audio.Source.WASAPI", ref paramReceived);

            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the int parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            int major = 0;
            int minor = 0;
            int version = 0;
            int build = 0;
            m_getParamIntText.text = "getParamInt: " + paramReceived + " version: " + GenvidSDK.GetVersion(ref major, ref minor, ref version, ref build);

            string paramSearch = "";
            gvStatus = GenvidSDK.GetParameterUTF8(null, "genvid/encode/input/height", ref paramSearch, 256);

            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the UTF8 parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            gvStatus = GenvidSDK.SetParameter(Stream.Video, "framerate", 30.0f);
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while setting the float parameter for the video : " + GenvidSDK.StatusToString(gvStatus));
            }

            float floatParamReceived = 0.0f;
            gvStatus = GenvidSDK.GetParameter(Stream.Video, "framerate", ref floatParamReceived);

            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while trying to get the float parameter : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_getUTF8Text.text = "get float param : " + floatParamReceived + " get UTF8 : " + paramSearch;
        }
    }

At the start, we are finding all the cameras available on the scene and activate the first one. We are also finding our text objects that will be modified later on and we are also finding all the active objects via the tag we created via ActivateObjects on the scene (cube or sphere depending on the scene).

Afterwards, we proceed to verify if the GenvidActivation object exists. If the object doesn’t exist, it means that we need to do the proper initialization process.

We start by creating an empty game object, give it a name, add the script GenvidActivation to it and we proceed to activate. The activateObject(bool GenvidSDKActive) method gets the number of scenes available in this project along with the current scene number plus it saves the GenvidSDKActive value to know if the Genvid SDK will be active (useful when testing in the editor). We make sure that the GenvidActivation object will not be deleted by the garbage collector and we set a bool variable to true to perform the Genvid SDK initialization later.

In the case the object exist, we simply get the GenvidSDKActive value from the object and we set the bool variable to false.

Afterwards if the Genvid SDK is active and if it is the first time that we are booting the project, we proceed to initialize the Genvid SDK with GenvidSDK.Initialize(). Each GenvidSDK function return a GenvidSDK.GenvidStatus which we validate to know if there was an error with the SDK interaction.

We then proceed to create the various streams needed with GenvidSDK.CreateStream(string streamID).

As for the audio stream, we also proceed to do a GenvidSDK.SetParameter(string streamID, string paramKey, int paramValue) to activate the sound capture with the proper parameters.

For the video stream, the stream creation process is a bit different. We are still creating the stream as usual, but we also need to call setupVideoChannel(string streamID) from the GenvidPlugin.dll.

Outside the initialization condition, we proceed to subscribe for events and commands. We use GenvidSDK.Subscribe(string streamID, EventSummaryCallback callback, IntPtr userData) and GenvidSDK.SubscribeCommand(string streamID, CommandCallback callback, IntPtr userData) respectively. The EventSummaryCallback is a callback that has no return value but takes two IntPtr parameters (one for the EventResults and one for the userData. The EventResults parameter is used to get the event results needed and the userData is the data that was sent as argument during the GenvidSDK.Subscribe.

Callbacks

When an event is called from the website, the appropriate callback that was subscribed to the event StreamID is triggered.

    void eventCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        var summary = GenvidSDK.GetSummary(summaryData);

        if (summary.id == EventSummary.changeColor.ToString()) 
        {
            string cubeName = summary.results[0].key.fields[0];
            string cubeColor = summary.results[0].key.fields[1];

            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    for (int x = 0; x < m_NameToColor.Length; x++) 
                    {
                        if (m_NameToColor[x] == cubeColor) 
                        {
                            changeColorCube(i, m_gColors[x]);
                        }
                    }
                }
            }
        }
        else if (summary.id == EventSummary.cheer.ToString())
        {
            // Handle cube cheering.
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().cheer((float)(summary.results[0].values[0].value));
                }
            }
        }
        else if (summary.id == EventSummary.reset.ToString())
        {
            // Handle cube reset
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                }
            }
        }
    }

In the sample, we are sending the IntPtr into GenvidSDK.GetSummary(System.IntPtr summaryData) which provide the callback with a GenvidSDK.EventSummary converted properly in C#.

Once we have the GenvidEventSummary, we can access the data received and handle the event appropriately. In the sample, we verify the id of the structure to know which event was triggered and we use the result to interact with the objects of the sample.

When a command is called from the website, the appropriate callback that was subscribed to the command StreamID is triggered.

    void commandCallbackFunction(GenvidSDK.CommandResult commandResult, IntPtr uniqueID)
    {
        if (commandResult.id == Command.direction.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().changeDirection(int.Parse(substrings[1]), int.Parse(substrings[3]));
                    return;
                }
            }
        }
        else if (commandResult.id == Command.speed.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    if (float.Parse(substrings[1]) > 1.0f) 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]));
                    } 
                    else 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]) * -1.0f);
                    }
                    return;
                }
            }
        }
        else if (commandResult.id == Command.reset.ToString())
        {
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == commandResult.value)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                    return;
                }
            }
        }
    }

In the sample, we use the GenvidSDK.CommandResult to access the data received and perform the action related to the command that was sent.

It is also possible to assign a second callback to the same stream ID. In the sample, we assign a second callback for each event subscribed to display the event unique int associated with it that was sent as userData.

    void operationCompleteCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        int uniqueId = userData.ToInt32();
        m_getUTF8Text.text = "Event completed with uniqueID: " + uniqueId;
    }

Also, in the OnDisable function, we are removing the callback. When we are proceeding to another scene, we need to register the callback to new callback instead. This prevents the SDK from calling a callback that does not exist in the new scene.

    void OnDisable()
    {
        if (GenvidSDKActive)
        {
            // Unsubscribe all the events
            foreach (string eventSummary in Enum.GetNames(typeof(EventSummary)))
            {
                var userData = new IntPtr((int)Enum.Parse(typeof(EventSummary), eventSummary));

                var status = GenvidSDK.Unsubscribe(eventSummary, m_eventCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while unsubscribe process for " + eventSummary + " on the m_eventCallback : " + GenvidSDK.StatusToString(status));
                }

                status = GenvidSDK.Unsubscribe(eventSummary, m_operationCompleteCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while unsubscribe process for " + eventSummary + " on the  m_operationCompleteCallback : " + GenvidSDK.StatusToString(status));
                }
            }

            // Unsubscribe all the commands
            foreach (string command in Enum.GetNames(typeof(Command)))
            {
                var userData = new IntPtr((int)Enum.Parse(typeof(Command), command));

                var status = GenvidSDK.UnsubscribeCommand(command, m_commandCallback, userData);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing the unsubscribe command for " + command + ": " + GenvidSDK.StatusToString(status));
                }
            }
        }
    }

    // GENVID - Start Quit process
    void OnApplicationQuit()
    {
        if (GenvidSDKActive)
        {
            StopCoroutine("CallPluginAtEndOfFrames");

            OnDisable();

            // Destory all Genvid streams
            foreach (string stream in Enum.GetNames(typeof(Stream)))
            {
                var status = GenvidSDK.DestroyStream(stream);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while creating the " + stream + " stream: " + GenvidSDK.StatusToString(status));
                }
            }

            var gvStatus = GenvidSDK.Terminate();
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the terminate process : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_quitProcess = true;
            CleanUp();
        }
    }
    // GENVID - Stop Quit process

    // GENVID - Second Event callback start
    void operationCompleteCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        int uniqueId = userData.ToInt32();
        m_getUTF8Text.text = "Event completed with uniqueID: " + uniqueId;
    }
    // GENVID - Second Event callback stop

    // GENVID - Event callback start
    void eventCallbackFunction(IntPtr summaryData, IntPtr userData)
    {
        var summary = GenvidSDK.GetSummary(summaryData);

        if (summary.id == EventSummary.changeColor.ToString()) 
        {
            string cubeName = summary.results[0].key.fields[0];
            string cubeColor = summary.results[0].key.fields[1];

            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    for (int x = 0; x < m_NameToColor.Length; x++) 
                    {
                        if (m_NameToColor[x] == cubeColor) 
                        {
                            changeColorCube(i, m_gColors[x]);
                        }
                    }
                }
            }
        }
        else if (summary.id == EventSummary.cheer.ToString())
        {
            // Handle cube cheering.
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().cheer((float)(summary.results[0].values[0].value));
                }
            }
        }
        else if (summary.id == EventSummary.reset.ToString())
        {
            // Handle cube reset
            string cubeName = summary.results[0].key.fields[0];
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == cubeName)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                }
            }
        }
    }
    // GENVID - Event callback stop

    // GENVID - Command callback start
    void commandCallbackFunction(GenvidSDK.CommandResult commandResult, IntPtr uniqueID)
    {
        if (commandResult.id == Command.direction.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().changeDirection(int.Parse(substrings[1]), int.Parse(substrings[3]));
                    return;
                }
            }
        }
        else if (commandResult.id == Command.speed.ToString())
        {
            Char delimiter = ':';
            String[] substrings = commandResult.value.Split(delimiter);
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == substrings[0])
                {
                    if (float.Parse(substrings[1]) > 1.0f) 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]));
                    } 
                    else 
                    {
                        m_activeObjectsList [i].GetComponent<MovementCube>().changeSpeed (float.Parse (substrings [1]) * -1.0f);
                    }
                    return;
                }
            }
        }
        else if (commandResult.id == Command.reset.ToString())
        {
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                if (m_activeObjectsList[i].name == commandResult.value)
                {
                    m_activeObjectsList[i].GetComponent<MovementCube>().transform.position = m_activeObjectsList[i].GetComponent<MovementCube>().oldPosition;
                    return;
                }
            }
        }
    }
    // GENVID - Command callback stop

    // GENVID - On start begin
    IEnumerator Start()
    {
        if (GenvidSDKActive)
        {
            yield return StartCoroutine(CallPluginAtEndOfFrames());
        }
    }
    // GENVID - On start end

    // GENVID - Video capture start
    private IEnumerator CallPluginAtEndOfFrames()
    {
        // GetRenderEventFunc param
        System.IntPtr renderingFunction = GetRenderEventFunc();
        var waitForEndOfFrame = new WaitForEndOfFrame();
        GenvidSDK.Status status = GenvidSDK.Status.Success;

        while (true) 
        {
            // Wait until all frame rendering is done
            yield return waitForEndOfFrame;

            if (m_quitProcess == false) 
            {
                if (m_processComplete) 
                {
                    GL.IssuePluginEvent(renderingFunction, 0);
                    m_processComplete = false;
                    status = GetVideoInitStatus();
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while starting the video stream : " + GenvidSDK.StatusToString(status));
                    }
                    status = GenvidSDK.Status.ConnectionInProgress;
                } 
                else 
                {
                    GL.IssuePluginEvent(renderingFunction, 1);
                    status = GetVideoSubmitDataStatus();
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while sending video data : " + GenvidSDK.StatusToString(status));
                    }
                }
            }
        }
    }
    // GENVID - Video capture end

    // GENVID - Update start
    // Update is called once per frame - Used to submit the game data mostly
    void FixedUpdate ()
    {
        if (GenvidSDKActive)
        {
            //Getting the cube information and send it into the game data stream
            long tc = GenvidSDK.GetCurrentTimecode();

            Matrix4x4 ProjView = m_sceneCamera[m_currentActiveCamera].projectionMatrix * m_sceneCamera[m_currentActiveCamera].worldToCameraMatrix;

            string jsonCubesArray = "";
            jsonCubesArray += '[';
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                jsonCubesArray += toJSONObject(m_activeObjectsList[i]);
                if ((i + 1) < m_activeObjectsList.Length)
                {
                    jsonCubesArray += ','; // Exclude last one.
                }
            }
            jsonCubesArray += ']';

            string jsonData = "";
            jsonData += '{';
            jsonData += toJSON("MatProjView", toJSON(ProjView));
            jsonData += ',';
            jsonData += toJSON("cubes", jsonCubesArray);
            jsonData += '}';

            var gvStatus = GenvidSDK.SubmitGameData(tc, Stream.GameData.ToString(), jsonData);
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the submit game data : " + GenvidSDK.StatusToString(gvStatus));
            }

            //Get the popularity JSON send a notification if it was changed
            string popularityJSON = getPopularityJSON();
            if (!popularityJSON.Equals(m_oldPopularityJSON))
            {
                gvStatus = GenvidSDK.SubmitNotification(Stream.Popularity.ToString(), popularityJSON);
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing submit notification for popularity : " + GenvidSDK.StatusToString(gvStatus));
                }
            }
            m_oldPopularityJSON = popularityJSON;

            //To calculate the popularity decay
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                m_activeObjectsList[i].GetComponent<MovementCube>().cheer(-Time.deltaTime);
            }

            gvStatus = GenvidSDK.CheckForEvents();
            if (gvStatus != GenvidSDK.Status.Success && gvStatus != GenvidSDK.Status.ConnectionTimeout)
            {
                Debug.LogError("Error while doing the CheckForEvents : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_timecodeText.text = "Previous timecode: " + GenvidSDK.GetPreviousTimecode();
        }

         //camera change
         m_cameraCounter = m_cameraCounter + Time.deltaTime;

         if (m_cameraCounter >= m_maxCameraCounter)
         {
             changeCamera();
         }

         m_cameraText.text = m_sceneCamera[m_currentActiveCamera].name + " : time until change : " + m_cameraCounter.ToString("F2") + "/" + m_maxCameraCounter;
         m_changeSceneCounter = m_changeSceneCounter + Time.deltaTime;
         m_sceneText.text = "Time until scene change : " + m_changeSceneCounter.ToString("F2") + "/" + m_maxChangeSceneCounter;
 
        if (m_changeSceneCounter >= m_maxChangeSceneCounter)
         {
             changeScene();
         }
    }
    // GENVID - Update stop

    public void changeScene()
    {
        m_GenvidActivation.GetComponent<GenvidActivation>().activeScene++;

        if (m_GenvidActivation.GetComponent<GenvidActivation>().activeScene == m_GenvidActivation.GetComponent<GenvidActivation>().maxScene) 
        {
            m_GenvidActivation.GetComponent<GenvidActivation>().activeScene = 0;
        }

        SceneManager.LoadScene(m_GenvidActivation.GetComponent<GenvidActivation>().activeScene, LoadSceneMode.Single);
    }

    public void changeCamera()
    {
        m_cameraCounter = 0;

        int tempActivateCamera = m_currentActiveCamera + 1;
        if (tempActivateCamera == m_sceneCamera.Length) 
        {
            tempActivateCamera = 0;
        }

        activateCamera(tempActivateCamera);
    }

    void activateCamera(int target)
    {
        m_sceneCamera[target].enabled = true;
        m_sceneCamera[m_currentActiveCamera].enabled = false;
        m_currentActiveCamera = target;
    }

    //Change the color of a cube
    void changeColorCube(int location, Vector4 colorChange)
    {
        m_activeObjectsList[location].transform.Find(m_activeObjectsList[location].name + "_child").GetComponent<Renderer>().material.SetColor("_EmissionColor", colorChange);
        m_activeObjectsList[location].GetComponent<MovementCube>().cubeColor = colorChange;
        m_activeObjectsList[location].GetComponent<MovementCube>().cubeChangedColor = true;

        long tc = GenvidSDK.GetCurrentTimecode();
        var status = GenvidSDK.SubmitAnnotation(tc, Stream.ColorChanged.ToString(), getColorJSON());
        if (status != GenvidSDK.Status.Success) 
        {
            Debug.LogError("Annotation fail with error " + GenvidSDK.StatusToString(status));
        }
    }

    public static string enquote(string str)
    {
        return "\"" + str + "\"";
    }

    public static string toJSON(string key, string jsonValue)
    {
        return enquote(key) + ":" + jsonValue;
    }

    public static string toJSON(Vector4 key)
    {
        string os = "";
        os = os + '[' + key[0] + ',' + key[1] + ',' + key[2] + ',' + key[3] + ']';
        return os;
    }

    public static string toJSON(Matrix4x4 m)
    {
        string os = "";
        os = os + '[' + m[0] + ',' + m[1] + ',' + m[2] + ',' + m[3]
            + ',' + m[4] + ',' + m[5] + ',' + m[6] + ',' + m[7]
            + ',' + m[8] + ',' + m[9] + ',' + m[10] + ',' + m[11]
            + ',' + m[12] + ',' + m[13] + ',' + m[14] + ',' + m[15] + ']';
        return os;
    }

    //Convert a cube object into a JSON string
    string toJSONObject(GameObject cube)
    {
        string str = "";
        str += '{';
        str += toJSON("mat", toJSON(cube.transform.localToWorldMatrix));
        str += ',';
        str += toJSON("name", enquote(cube.name));
        str += ',';
        str += toJSON("color", toJSON(cube.GetComponent<MovementCube>().cubeColor));
        str += ',';
        str += toJSON("popularity", cube.GetComponent<MovementCube>().popularity.ToString());

        if (cube.GetComponent<MovementCube>().cubeSelected)
        {
            str += ',';
            str += toJSON("leader", "true");
        }
        str += '}';
        return str;
    }
        
    //Get the color JSON string for all cubes
    string getColorJSON()
    {
        string jsonData = "[";
        bool isPrevious = false;
        for (int i = 0; i < m_activeObjectsList.Length; ++i) 
        {
            if (m_activeObjectsList[i].GetComponent<MovementCube>().HasChangedColor()) 
            {
                if (isPrevious)
                {
                    jsonData += ",";
                }
                isPrevious = true;

                jsonData += "{\"name\":\"" + m_activeObjectsList[i].name + "\", \"color\":" + m_activeObjectsList[i].GetComponent<MovementCube>().cubeColor +"}";
            }
        }
        jsonData += "]";

        return jsonData;
    }

    //Get the popularity string for all cubes
    string getPopularityJSON()
    {
        string jsonData = "[";
        for (int i = 0; i < m_activeObjectsList.Length; ++i) 
        {
            jsonData += "{\"name\":\"" + m_activeObjectsList[i].name + "\", \"popularity\":" + m_activeObjectsList[i].GetComponent<MovementCube>().popularity +"}";
            if ((i + 1) < m_activeObjectsList.Length)
            {
                jsonData += ",";
            }
        }
        jsonData += "]";

        return jsonData;
    }
}

We are using GenvidSDK.Unsubscribe(string streamID, EventSummaryCallback callback, IntPtr userData) to unsubscribe the events and GenvidSDK.UnsubscribeCommand(string streamID, CommandCallback callback, IntPtr userData) to unsubscribe the commands.

Note that, for simplicity reasons, the sample shares the same callback for all events and another one for all commands.

Update

During the FixedUpdate() process, we perform some actions related to the Genvid SDK.

    // Update is called once per frame - Used to submit the game data mostly
    void FixedUpdate ()
    {
        if (GenvidSDKActive)
        {
            //Getting the cube information and send it into the game data stream
            long tc = GenvidSDK.GetCurrentTimecode();

            Matrix4x4 ProjView = m_sceneCamera[m_currentActiveCamera].projectionMatrix * m_sceneCamera[m_currentActiveCamera].worldToCameraMatrix;

            string jsonCubesArray = "";
            jsonCubesArray += '[';
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                jsonCubesArray += toJSONObject(m_activeObjectsList[i]);
                if ((i + 1) < m_activeObjectsList.Length)
                {
                    jsonCubesArray += ','; // Exclude last one.
                }
            }
            jsonCubesArray += ']';

            string jsonData = "";
            jsonData += '{';
            jsonData += toJSON("MatProjView", toJSON(ProjView));
            jsonData += ',';
            jsonData += toJSON("cubes", jsonCubesArray);
            jsonData += '}';

            var gvStatus = GenvidSDK.SubmitGameData(tc, Stream.GameData.ToString(), jsonData);
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the submit game data : " + GenvidSDK.StatusToString(gvStatus));
            }

            //Get the popularity JSON send a notification if it was changed
            string popularityJSON = getPopularityJSON();
            if (!popularityJSON.Equals(m_oldPopularityJSON))
            {
                gvStatus = GenvidSDK.SubmitNotification(Stream.Popularity.ToString(), popularityJSON);
                if (gvStatus != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while doing submit notification for popularity : " + GenvidSDK.StatusToString(gvStatus));
                }
            }
            m_oldPopularityJSON = popularityJSON;

            //To calculate the popularity decay
            for (int i = 0; i < m_activeObjectsList.Length; ++i)
            {
                m_activeObjectsList[i].GetComponent<MovementCube>().cheer(-Time.deltaTime);
            }

            gvStatus = GenvidSDK.CheckForEvents();
            if (gvStatus != GenvidSDK.Status.Success && gvStatus != GenvidSDK.Status.ConnectionTimeout)
            {
                Debug.LogError("Error while doing the CheckForEvents : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_timecodeText.text = "Previous timecode: " + GenvidSDK.GetPreviousTimecode();
        }

         //camera change
         m_cameraCounter = m_cameraCounter + Time.deltaTime;

         if (m_cameraCounter >= m_maxCameraCounter)
         {
             changeCamera();
         }

         m_cameraText.text = m_sceneCamera[m_currentActiveCamera].name + " : time until change : " + m_cameraCounter.ToString("F2") + "/" + m_maxCameraCounter;
         m_changeSceneCounter = m_changeSceneCounter + Time.deltaTime;
         m_sceneText.text = "Time until scene change : " + m_changeSceneCounter.ToString("F2") + "/" + m_maxChangeSceneCounter;
 
        if (m_changeSceneCounter >= m_maxChangeSceneCounter)
         {
             changeScene();
         }
    }

First, we get the GenvidSDK.GetCurrentTimecode() that will be sent via any data during the FixedUpdate(). In the sample, we collect the cube matrices that are formatted in JSON and sent using GenvidSDK.SubmitGameData(long timecode, string streamID, string dataToSend). We provide a toJSON() function to help format your content, but you are free to do it yourself via other means.

In the sample, we are also doing an example on how to do a notification via the GenvidSDK.SubmitNotification(string streamID, string data). In this case, we are simply comparing the popularity to notice if there is a difference with the most recent one, and proceed to submit the notification if that is the case.

Before finishing FixedUpdate(), we do a GenvidSDK.CheckForEvents() that is very important to be able to trigger the callbacks properly.

We are also proceeding with a simple timer to change the camera every 10 seconds and scene every 60 seconds along filling some text fields with data (camera change time, scene change time, previous timecode).

Video capture

To be able to capture the video, we need to create a specific loop at the appropriate time which is the end of the end of the frame.

    IEnumerator Start()
    {
        if (GenvidSDKActive)
        {
            yield return StartCoroutine(CallPluginAtEndOfFrames());
        }
    }

First, in the Start() method, if the Genvid SDK is active, we are starting a coroutine with the function CallPluginAtEndOfFrames().

    private IEnumerator CallPluginAtEndOfFrames()
    {
        // GetRenderEventFunc param
        System.IntPtr renderingFunction = GetRenderEventFunc();
        var waitForEndOfFrame = new WaitForEndOfFrame();
        GenvidSDK.Status status = GenvidSDK.Status.Success;

        while (true) 
        {
            // Wait until all frame rendering is done
            yield return waitForEndOfFrame;

            if (m_quitProcess == false) 
            {
                if (m_processComplete) 
                {
                    GL.IssuePluginEvent(renderingFunction, 0);
                    m_processComplete = false;
                    status = GetVideoInitStatus();
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while starting the video stream : " + GenvidSDK.StatusToString(status));
                    }
                    status = GenvidSDK.Status.ConnectionInProgress;
                } 
                else 
                {
                    GL.IssuePluginEvent(renderingFunction, 1);
                    status = GetVideoSubmitDataStatus();
                    if (status != GenvidSDK.Status.Success)
                    {
                        Debug.LogError("Error while sending video data : " + GenvidSDK.StatusToString(status));
                    }
                }
            }
        }
    }

At the start of the method, we are assigning the GenvidPlugin GetRenderEventFunc() to a System.IntPtr and we are creating a new variable for a new WaitForEndOFrame().

We are then using a while loop with a yield return waitForEndOfFrame to wait until all the rendering is completed to proceed. In the event that the video capture was not started yet, we are doing a GL.IssuePluginEvent(renderingFunction, 0) with 0 as argument to indicate to initialize the video capture. Afterwards, we can verify if the process was completed properly by verifying the GenvidSDK.Status with GetVideoInitStatus().

Otherwise, if the video capture initialization process was completed, it is performing able GL.IssuePluginEvent(renderingFunction, 1) with 1 as argument to indicate to do a normal video capture (submitting the video data). Afterwards, we can verify if the process was completed properly by verifying the GenvidSDK.Status with GetVideoSubmitDataStatus().

Termination

When the application is terminated, we need to perform various actions to terminate the Genvid SDK. In the current sample, we perform these operations inside the OnApplicationQuit() process.

    void OnApplicationQuit()
    {
        if (GenvidSDKActive)
        {
            StopCoroutine("CallPluginAtEndOfFrames");

            OnDisable();

            // Destory all Genvid streams
            foreach (string stream in Enum.GetNames(typeof(Stream)))
            {
                var status = GenvidSDK.DestroyStream(stream);
                if (status != GenvidSDK.Status.Success)
                {
                    Debug.LogError("Error while creating the " + stream + " stream: " + GenvidSDK.StatusToString(status));
                }
            }

            var gvStatus = GenvidSDK.Terminate();
            if (gvStatus != GenvidSDK.Status.Success)
            {
                Debug.LogError("Error while doing the terminate process : " + GenvidSDK.StatusToString(gvStatus));
            }

            m_quitProcess = true;
            CleanUp();
        }
    }

We first proceed with the unsubscribe process for all the events and commands that is available inside the OnDisable().

Afterwards, we destroy all the streams that were created during the initialization process via the GenvidSDK.DestroyStream(string streamID). We then call GenvidSDK.Terminate() to clear all the data that was allocated by the Genvid SDK. Finally, we need to call cleanUp() to clear all the data that was allocated by the GenvidPlugin.