/* * VideoKit * Copyright © 2025 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 Function; 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 Function client. /// public Function fxn => _fxn ??= FunctionUnity.Create(token, url: url); /// /// VideoKit client for this project. /// public static VideoKitClient? Instance { get; internal set; } /// /// VideoKit client version. /// public const string Version = @"1.0.0"; /// /// Check the application VideoKit session status. /// public async Task CheckSession () { // INCOMPLETE // Try local, then fallback, then save try { // Set var session = sessionToken ?? await CreateSessionToken(); var result = VideoKit.SetSessionToken(session); // Cache if (result == Status.Ok) { PlayerPrefs.SetString(BuildTokenKey, token); PlayerPrefs.SetString(SessionTokenKey, session); } // Return return result; } 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.token = token; client.url = !string.IsNullOrEmpty(url) ? url : client.url; return client; } #endregion #region --Operations-- [SerializeField, HideInInspector] private string? token = string.Empty; private Function? _fxn; public const string URL = @"https://www.videokit.ai/api"; private const string BuildTokenKey = @"ai.videokit.build"; private const string SessionTokenKey = @"ai.videokit.session"; private string? sessionToken => ( !Application.isEditor && PlayerPrefs.HasKey(BuildTokenKey) && PlayerPrefs.GetString(BuildTokenKey) == token && !string.IsNullOrEmpty(PlayerPrefs.GetString(SessionTokenKey)) ) ? PlayerPrefs.GetString(SessionTokenKey) : null; private void Awake () { // Set singleton in player if (!Application.isEditor && Instance == null) Instance = this; } internal async static Task CreateToken ( 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"] = token, [@"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 } }