/*
* VideoKit
* Copyright © 2026 Yusuf Olokoba. All Rights Reserved.
*/
#nullable enable
namespace VideoKit.Internal {
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using Muna;
using Newtonsoft.Json;
using Status = VideoKit.Status;
///
/// VideoKit API client.
///
[DefaultExecutionOrder(-10_000)]
public sealed class VideoKitClient : ScriptableObject {
#region --Client API--
///
/// VideoKit API URL.
///
[SerializeField, HideInInspector]
public string url = URL;
///
/// VideoKit Muna client.
///
public Muna muna => _muna ??= MunaUnity.Create(authToken, url: $"{url}/muna");
///
/// VideoKit client for this project.
///
public static VideoKitClient? Instance { get; internal set; }
///
/// VideoKit client version.
///
public const string Version = @"1.0.13";
///
/// Check the application VideoKit session status.
///
public async Task CheckSession() {
try {
// Check if initialized
if (VideoKit.SetSessionToken(sessionToken) == Status.Ok)
return Status.Ok;
// Create token
var token = await CreateSessionToken();
var status = VideoKit.SetSessionToken(token);
if (status == Status.Ok) {
sessionToken = token;
PlayerPrefs.SetString(SessionTokenKey, token);
}
// Return
return status;
} catch (Exception ex) {
Debug.LogWarning($"VideoKit: Failed to check session with error: {ex.Message}");
return Status.InvalidOperation;
}
}
///
/// Create a VideoKit client.
///
/// VideoKit auth token.
/// VideoKit API URL.
public static VideoKitClient Create(string? token, string? url = null) {
var client = CreateInstance();
client.authToken = token;
client.url = !string.IsNullOrEmpty(url) ? url : client.url;
return client;
}
#endregion
#region --Operations--
[SerializeField, HideInInspector]
private string? authToken = string.Empty;
private Muna? _muna;
private string? sessionToken;
public const string URL = @"https://www.videokit.ai/api";
private const string SessionTokenKey = @"ai.videokit.session";
private void Awake() {
// Check editor
if (Application.isEditor)
return;
// Set singleton in player
Instance = Instance ? Instance : this;
// Set session token
sessionToken = (
PlayerPrefs.HasKey(SessionTokenKey) ?
PlayerPrefs.GetString(SessionTokenKey) :
null
);
}
internal async static Task CreateAuthToken(
string platform,
string apiKey,
string? url = URL
) {
// Create client
using var request = new HttpClient();
request.DefaultRequestHeaders.Authorization = !string.IsNullOrEmpty(apiKey) ?
new AuthenticationHeaderValue(@"Bearer", apiKey) :
null;
// Request
var version = Marshal.PtrToStringUTF8(VideoKit.GetVersion());
var payload = new Dictionary {
[@"platform"] = platform,
[@"version"] = version
};
var payloadStr = JsonConvert.SerializeObject(payload);
using var content = new StringContent(payloadStr, Encoding.UTF8, @"application/json");
using var response = await request.PostAsync($"{url}/build", content);
// Parse response
var responseStr = await response.Content.ReadAsStringAsync();
Dictionary responseBody;
try {
responseBody = JsonConvert.DeserializeObject>(responseStr)!;
} catch {
throw new InvalidOperationException($"Failed to create build token with status {response.StatusCode} and error: {responseStr}");
}
// Check error
if (responseBody.TryGetValue(@"error", out var error))
throw new InvalidOperationException(error);
// Return
return responseBody[@"token"];
}
private async Task CreateSessionToken() {
// Get session id
var sessionId = new StringBuilder(2048);
VideoKit.GetSessionIdentifier(sessionId, sessionId.Capacity);
// Create payload
var payload = new Dictionary {
[@"buildToken"] = authToken,
[@"sessionId"] = sessionId.ToString(),
};
var payloadStr = JsonConvert.SerializeObject(payload);
// Generate session token
using var request = new UnityWebRequest($"{url}/session/v3", UnityWebRequest.kHttpVerbPOST) {
uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(payloadStr)),
downloadHandler = new DownloadHandlerBuffer(),
disposeDownloadHandlerOnDispose = true,
disposeUploadHandlerOnDispose = true,
timeout = 20,
};
request.SetRequestHeader(@"Content-Type", @"application/json");
request.SendWebRequest();
while (!request.isDone)
await Task.Yield();
// Parse response
Dictionary response;
try {
response = JsonConvert.DeserializeObject>(request.downloadHandler.text)!;
} catch {
throw new InvalidOperationException($"Failed to create session token with status {request.responseCode} and error: {request.downloadHandler.text}");
}
// Check error
if (response.TryGetValue(@"error", out var error))
throw new InvalidOperationException(error);
// Return
return response["token"];
}
#endregion
}
}