# LckCamera Source: https://docs.liv.tv/api-reference/unity/classes/LckCamera How to register and switch between in-game cameras for recording and streaming in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckCamera` lets you turn any Unity Camera into a capture source for in-game recording and streaming. It registers automatically with the LCK system at runtime via `LckMediator`, and can be activated or deactivated programmatically to switch camera angles — even during an active recording. *** ## Usage Use `LckCamera` when you want players to record or stream from multiple camera angles (e.g., selfie cam, third-person, drone cam) within your VR game. * Add an `LckCamera` component to a GameObject with a Unity Camera. * The camera will automatically register itself with the LCK system at runtime. * Use `ILckService.SetActiveCamera(cameraId)` to switch between registered cameras. * Cameras are automatically deactivated when not in use. *** ### Example ```c# theme={null} [InjectLck] private ILckService _lckService; public void SwitchToCamera(ILckCamera camera) { var result = _lckService.SetActiveCamera(camera.CameraId); if (!result.Success) Debug.LogError($"Failed to switch camera: {result.ErrorMessage}"); } ``` *** ## References ### Fields | Field | Type | Description | | :--------- | :----- | :------------------------------------------ | | \_camera | Camera | Reference to the Unity `Camera` component. | | \_cameraId | string | Unique identifier for this camera instance. | *** ### Properties | Property | Type | Description | | :------- | :----- | :------------------------------------- | | CameraId | string | The unique identifier for this camera. | *** ### Methods | Method | Returns | Description | | :---------------------------- | :------ | :--------------------------------------------------------------------------------- | | ActivateCamera(RenderTexture) | void | Enables the camera and sets its `targetTexture` for capture. | | DeactivateCamera() | void | Disables the camera and clears its `targetTexture`. Should not be called directly. | | GetCameraComponent() | Camera | Returns the underlying Unity `Camera` component. | *** ## See Also * [LckMonitor](/api-reference/unity/classes/LckMonitor) — Display the camera output on an in-game surface * [LckService](/api-reference/unity/classes/LckService) — Start/stop recording and manage active cameras * [ILckCamera](/api-reference/unity/interfaces/ILckCamera) — Interface implemented by LckCamera # LCKCameraController Source: https://docs.liv.tv/api-reference/unity/classes/LckCameraController How to control camera modes (Selfie, First Person, Third Person), orientation, FOV, and recording in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LCKCameraController` is a Unity `MonoBehaviour` that manages the full in-game camera system for LCK. It switches between three virtual cameras (Selfie, First Person, Third Person), applies stabilisation and smoothing, handles FOV and distance adjustments from UI controls, toggles between portrait and landscape orientation, and communicates the active camera and track settings to `ILckService`. It can also auto-configure culling masks to hide the tablet layer from the Selfie view. *** ## Usage Use `LCKCameraController` when you need a ready-made camera rig that handles mode switching, orientation, and recording controls through Unity UI. Add it to a GameObject and assign your UI controls, camera/stabilizer components for each mode, and optionally an `ILckQualityConfig` ScriptableObject for quality presets. ### Toggle recording from a UI button ```c# theme={null} [SerializeField] private LCKCameraController _cameraController; public void OnRecordPressed() { _cameraController.ToggleRecording(); } ``` This disables orientation/quality/top buttons while capture starts, and re-enables on stop or failure. ### Switch orientation safely ```c# theme={null} public void OnOrientationPressed() { _cameraController.ToggleOrientation(); // no-op while capturing } ``` Updates `ILckService` camera orientation, resizes the preview, and recalculates FOV to preserve perceived width in Portrait. ### Flip selfie camera / set third-person position ```c# theme={null} public void OnFlipSelfie() => _cameraController.ProcessSelfieFlip(); public void OnThirdPersonFrontBack() => _cameraController.ProcessThirdPersonPosition(); ``` Flips preview scale and stabilizer orientation for Selfie; toggles in-front/behind for Third Person. *** ## References ### Serialized Fields & Options | Field | Type | Description | | :---------------------------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | `_modifyRenderLayerAndCullingMasks` | `bool` | If true, automatically moves specified objects to the tablet layer and adjusts camera culling masks so Selfie hides that layer while others show it. | | `_tabletRenderingLayer` | `string` | Name of the Unity layer used to hide objects from the Selfie camera (e.g., "LCK Tablet"). | | `_objectsHiddenFromSelfieCamera` | `List` | Objects (e.g., tablet model) to assign to the tablet layer. | | `_qualityConfig` | `ScriptableObject` (`ILckQualityConfig`) | Provides quality options; updates `CameraTrackDescriptor` for Recording/Streaming on selection. | | `_hmdTransform` | `Transform` | Player/HMD anchor; defaults to `Camera.main.transform` if null. | | UI references | Various | Settings/top buttons, preview monitor `RectTransform`, `LckQualitySelector`, per-mode FOV/smoothing/distance controls, and per-mode cameras/stabilizers. | ### Properties | Property | Type | Description | | :--------------------- | :------------------- | :------------------------------------------------------------------------- | | `HmdTransform` | `Transform` | Gets/sets HMD anchor; falls back to main camera if unset. | | `OnCameraModeChanged` | `Action` | Event raised after switching Selfie/First/Third Person. | | `ColliderButtonsInUse` | `static bool` | Global flag for collider-based UI interactions (not used internally here). | ### Camera Modes * **Selfie** — parented to the tablet; supports flip (front/back), smoothing/FOV, hides tablet layer from the view. * **First Person** — follows HMD (position + slight forward offset), smoothing/FOV, instant snap after mode switch. * **Third Person** — orbital camera with height angle and adjustable distance; toggle front/behind; smoothing/FOV. ### Key Methods | Method | Returns | Description | | :---------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------- | | `ToggleRecording()` | `void` | Starts/stops recording via `ILckService`; disables orientation/quality/top buttons during start; re-enables on stop/failure. | | `ToggleGameAudio()` | `void` | Toggles game audio capture on/off via `ILckService`. | | `ToggleMicrophoneRecording(bool)` | `void` | Enables/disables microphone capture via `ILckService`. | | `ToggleOrientation()` | `void` | Switches Landscape/Portrait (no-op while capturing); updates `ILckService`, preview size, and recomputes FOV to preserve horizontal FOV feel. | | `ProcessSelfieFlip()` | `void` | Flips Selfie view 180° and adjusts preview mirroring. | | `ProcessThirdPersonPosition()` | `void` | Toggles third-person camera in-front/behind the player. | | `SetOrientationQualityAndTopButtonsIsDisabledState(bool)` | `void` | Enables/disables orientation, quality, and top button UI during capture transitions. | | `OnCaptureStart(LckResult)` / `OnCaptureStopped(LckResult)` | `void` | Re-enable UI on failure/stop; used with service events for recording/streaming. | ### Lifecycle & Internals * Injects or initialises `ILckService` (fallback DI setup in `Awake` if missing). * Subscribes to service events to reflect capture state; unsubscribes and stops recording on destroy. * Optional auto-layer/culling setup hides tablet layer from Selfie, shows it for others. * Recomputes Portrait FOV to maintain perceived width using the current descriptor's resolution. *** ## See Also * [ILckService](/api-reference/unity/interfaces/ILckService) — Service interface used for recording, streaming, and camera registration * [LckCamera](/api-reference/unity/classes/LckCameraController) — Lower-level camera registration if you need custom camera control * [LckQualityConfig](/api-reference/unity/classes/LckQualityConfig) — Quality preset configuration used by the quality selector * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — Resolution and format settings updated on quality/orientation changes # LckDescriptor Source: https://docs.liv.tv/api-reference/unity/classes/LckDescriptor How to read the active capture resolution, framerate, and bitrate settings from the LIV Camera Kit (LCK) SDK in Unity. ## Description `LckDescriptor` is a wrapper around the active `CameraTrackDescriptor` that describes the current capture configuration — resolution, framerate, and bitrate. You get it from `ILckService.GetDescriptor()` to inspect what settings are being used for recording or streaming. *** ## Usage Use `LckDescriptor` when you need to read or display the current capture settings at runtime. ```c# theme={null} var descriptorResult = _lckService.GetDescriptor(); if (descriptorResult.Success) { var descriptor = descriptorResult.Result; Debug.Log($"Active resolution: {descriptor.cameraTrackDescriptor.Resolution.Width}x{descriptor.cameraTrackDescriptor.Resolution.Height}"); Debug.Log($"Active framerate: {descriptor.cameraTrackDescriptor.Framerate}"); Debug.Log($"Active bitrate: {descriptor.cameraTrackDescriptor.Bitrate}"); } else { Debug.LogError($"Failed to retrieve descriptor: {descriptorResult.ErrorMessage}"); } ``` *** ## References ### Fields | Field | Type | Description | | :-------------------- | :--------------------------------------------------------------------- | :------------------------------------------- | | cameraTrackDescriptor | [`CameraTrackDescriptor`](/api-reference/struct/CameraTrackDescriptor) | The active track descriptor used in capture. | *** ## See Also * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — The struct containing resolution, framerate, and bitrate fields * [ILckService](/api-reference/unity/interfaces/ILckService) — Service interface whose GetDescriptor() returns this class * [LckQualityConfig](/api-reference/unity/classes/LckQualityConfig) — Quality presets that determine descriptor values # LckMediator Source: https://docs.liv.tv/api-reference/unity/classes/LckMediator How to register, look up, and listen for cameras and monitors in the LIV Camera Kit (LCK) SDK for Unity. ## Description `LckMediator` is a static utility that coordinates registration and lookup of cameras (`ILckCamera`) and monitors (`ILckMonitor`) within LCK. It maintains collections of active cameras and monitors, raises events when they are registered or unregistered, and forwards monitor-to-camera assignments to downstream systems like mixers. In most cases you won't call `LckMediator` directly — `LckCamera` and `LckMonitor` register and unregister themselves automatically during their lifecycle. Use it when you need manual control over custom camera/monitor implementations or want to react to registration events. *** ## Usage ### Manually registering a custom ILckCamera implementation ```c# theme={null} var customCamera = new CustomLckCamera(); LckMediator.RegisterCamera(customCamera); var foundCamera = LckMediator.GetCameraById(customCamera.CameraId); if (foundCamera != null) Debug.Log($"Camera {foundCamera.CameraId} is registered."); // When cleaning up LckMediator.UnregisterCamera(customCamera); ``` ### Reacting to camera/monitor registration events ```c# theme={null} LckMediator.CameraRegistered += cam => Debug.Log($"Camera registered: {cam.CameraId}"); LckMediator.MonitorRegistered += mon => Debug.Log($"Monitor registered: {mon.MonitorId}"); ``` *** ## References ### Events | Event | Type | Description | | :------------------------ | :------------------------------------------------------------- | :---------------------------------------------- | | CameraRegistered | Action\<[`ILckCamera`](/api-reference/interface/ILckCamera)> | Invoked when a new camera is registered. | | CameraUnregistered | Action\<[`ILckCamera`](/api-reference/interface/ILckCamera)> | Invoked when a camera is unregistered. | | MonitorRegistered | Action\<[`ILckMonitor`](/api-reference/interface/ILckMonitor)> | Invoked when a new monitor is registered. | | MonitorUnregistered | Action\<[`ILckMonitor`](/api-reference/interface/ILckMonitor)> | Invoked when a monitor is unregistered. | | MonitorToCameraAssignment | Action\ | Invoked when a monitor is assigned to a camera. | ### Methods | Method | Returns | Description | | :----------------------------------------------------------------------- | :------------------------------------------------------------------ | :----------------------------------------------------------------- | | RegisterCamera([`ILckCamera`](/api-reference/interface/ILckCamera)) | void | Registers a camera if not already present. | | UnregisterCamera([`ILckCamera`](/api-reference/interface/ILckCamera)) | void | Unregisters a camera by ID. | | RegisterMonitor([`ILckMonitor`](/api-reference/interface/ILckMonitor)) | void | Registers a monitor if not already present. | | UnregisterMonitor([`ILckMonitor`](/api-reference/interface/ILckMonitor)) | void | Unregisters a monitor by ID. | | GetCameraById(string id) | ILckCamera | Retrieves a registered camera by its ID. | | GetMonitorById(string id) | ILckMonitor | Retrieves a registered monitor by its ID. | | GetCameras() | IEnumerable\<[`ILckCamera`](/api-reference/interface/ILckCamera)> | Returns all currently registered cameras. | | GetMonitors() | IEnumerable\<[`ILckMonitor`](/api-reference/interface/ILckMonitor)> | Returns all currently registered monitors. | | NotifyMixerAboutMonitorForCamera(string, string) | void | Notifies listeners about an assignment between monitor and camera. | *** ## See Also * [LckCamera](/api-reference/unity/classes/LckCameraController) — MonoBehaviour that auto-registers with LckMediator on enable * [LckMonitor](/api-reference/unity/classes/LckMonitor) — MonoBehaviour that auto-registers with LckMediator on enable * [LckCamera](/api-reference/unity/classes/LckCameraController) — Interface for custom camera implementations * [LckMonitor](/api-reference/unity/classes/LckMonitor) — Interface for custom monitor implementations # LckMonitor Source: https://docs.liv.tv/api-reference/unity/classes/LckMonitor How to display the LIV Camera Kit (LCK) camera preview on an in-game surface using a RenderTexture in Unity VR apps. ## Description `LckMonitor` is a Unity `MonoBehaviour` implementing `ILckMonitor` that acts as a display surface for LCK camera output. It auto-registers with the LCK system on enable, provides a `RenderTexture` output from connected `LckCamera` instances, and raises an event whenever a new texture is assigned — so you can pipe the capture preview onto any in-game screen, UI element, or 3D mesh. *** ## Usage Use `LckMonitor` when you need to show the camera preview in your scene. Add it to a GameObject, then subscribe to `OnRenderTextureSet` to apply the texture wherever you need it. ```c# theme={null} using Liv.Lck; using UnityEngine; public class MonitorExample : MonoBehaviour { [SerializeField] private Renderer _screenRenderer; [SerializeField] private LckMonitor _lckMonitor; private void OnEnable() { _lckMonitor.OnRenderTextureSet += HandleRenderTextureSet; } private void OnDisable() { _lckMonitor.OnRenderTextureSet -= HandleRenderTextureSet; } private void HandleRenderTextureSet(RenderTexture rt) { // Apply the LCK render texture to a material on a screen mesh _screenRenderer.material.mainTexture = rt; } } ``` *** ## References ### Fields | Field | Type | Description | | :---------- | :----- | :------------------------------------------- | | \_monitorId | string | Unique identifier for this monitor instance. | ### Properties | Property | Type | Description | | :-------- | :----- | :-------------------------------------- | | MonitorId | string | The unique identifier for this monitor. | ### Events | Event | Type | Description | | :----------------- | :-------------------------------------------------------------- | :------------------------------------------------------------------ | | OnRenderTextureSet | LckMonitorRenderTextureSetDelegate(RenderTexture renderTexture) | Invoked whenever a new `RenderTexture` is assigned to this monitor. | ### Methods | Method | Returns | Description | | :------------------------------ | :------ | :------------------------------------------------------------------- | | SetRenderTexture(RenderTexture) | void | Assigns a new `RenderTexture` to the monitor and triggers the event. | *** ## See Also * [LckCamera](/api-reference/unity/classes/LckCameraController) — Camera component that outputs to monitors * [LckMediator](/api-reference/unity/classes/LckMediator) — Static registry where monitors are auto-registered # LckNotificationController Source: https://docs.liv.tv/api-reference/unity/classes/LckNotificationController How to show, hide, and manage in-game notifications for recording, streaming, and login events in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckNotificationController` is a Unity `MonoBehaviour` that handles user-facing notifications in LCK — things like "video saved", "enter stream code", or "configure streaming". It instantiates notification prefabs mapped to `NotificationType` values, shows/hides them in response to service events, and provides a simple API for other controllers (like `LckStreamingController`) to trigger notifications from code. Attach this component to a GameObject and configure notification prefabs in the Inspector. Each prefab must contain a component inheriting from `LckBaseNotification`. *** ## Usage Use `LckNotificationController` to give users visual feedback during recording, streaming, and authentication flows. ### Showing a saved video notification ```c# theme={null} _notificationController.ShowNotification(NotificationType.VideoSaved); ``` ### Displaying a login code ```c# theme={null} _notificationController.SetNotificationStreamCode("123-456"); ``` ### Clearing all notifications ```c# theme={null} _notificationController.HideNotifications(); ``` *** ## References ### NotificationType | Value | Description | | :------------------ | :---------------------------------------------------------------------- | | **VideoSaved** | Shown when a recording has been saved successfully. | | **PhotoSaved** | Shown when a photo capture is saved successfully. | | **EnterStreamCode** | Shown to display the LIV Hub login/stream code. | | **CheckSubscribed** | Shown to prompt the user to verify or upgrade their subscription. | | **ConfigureStream** | Shown to prompt the user to configure their streaming setup in LIV Hub. | | **InternalError** | Shown when an error occurs during the streaming setup process. | ### Properties | Property | Type | Description | | :----------------------------- | :-------------------------------------------------- | :-------------------------------------------------------------------------------- | | **\_notificationsInitializer** | `List` | Inspector-configured list mapping `NotificationType` to prefabs. | | **\_notifications** | `Dictionary` | Runtime dictionary of instantiated notifications. | | **\_currentNotification** | `LckBaseNotification` | Reference to the currently active notification (if any). | | **\_notificationShowDuration** | `float` | Default duration in seconds before auto-hiding a notification. | | **\_notificationsTransform** | `Transform` | Parent transform where notification prefabs are instantiated. | | **\_onScreenUIController** | `LckOnScreenUIController` | Optional higher-level UI controller that reacts to notification lifecycle events. | ### Methods | Method | Returns | Description | | :------------------------------------------ | :------ | :----------------------------------------------------------------------------------------- | | **ShowNotification(NotificationType type)** | `void` | Displays a notification of the given type. Hides any currently visible notification first. | | **HideNotifications()** | `void` | Immediately hides any active notification and stops pending auto-hide timers. | | **SetNotificationStreamCode(string code)** | `void` | Updates the `EnterStreamCode` notification with a login code. | | **InitializeNotifications()** | `void` | Instantiates all configured notification prefabs at startup. Called in `Awake()`. | | **DestroyNotifications()** | `void` | Destroys all instantiated notification prefabs for cleanup. | *** ## See Also * [LckStreamingController](/api-reference/unity/classes/LckStreamingController) — Streaming controller that triggers notifications during auth/streaming flows * [LckOnScreenUIController](/api-reference/unity/classes/LckOnScreenUIController) — UI controller that reacts to notification show/hide events # LckOnScreenUIController Source: https://docs.liv.tv/api-reference/unity/classes/LckOnScreenUIController How to manage on-screen button visibility and state during notifications and recording in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckOnScreenUIController` is a Unity `MonoBehaviour` that controls the visibility and visual state of on-screen UI elements (Photo Mode, Selfie Mode buttons, etc.) during notifications and recording. It disables buttons while notifications are active and resets them to their default state when notifications end, preventing user interaction during transient UI states. It works alongside `LckNotificationController`, which calls `OnNotificationStarted()` and `OnNotificationEnded()` to lock/unlock UI automatically. *** ## Usage Use `LckOnScreenUIController` to keep your on-screen buttons in sync with notification and recording state. Add it to a GameObject, populate the on-screen UI list in the Inspector, and connect it to your `LckNotificationController`. ### Notification flow ```c# theme={null} // Called when a notification is shown — disables all UI _onScreenUIController.OnNotificationStarted(); // Called when a notification ends — re-enables UI and resets button visuals _onScreenUIController.OnNotificationEnded(); ``` *** ## References ### Properties | Property | Type | Description | | :------------------ | :---------------------------------------------------- | :---------------------------------------------------------- | | **\_lckService** | [`ILckService`](/api-reference/interface/ILckService) | Injected LCK service used to subscribe to recording events. | | **\_allOnscreenUI** | `List` | List of UI elements controlled by this manager. | ### Methods | Method | Returns | Description | | :------------------------------------------------------------------------- | :------ | :---------------------------------------------------------------------------------------- | | **OnNotificationStarted()** | `void` | Disables all UI elements when a notification appears. | | **OnNotificationEnded()** | `void` | Re-enables all UI elements and resets their visuals to default. | | [**OnRecordingStarted(LckResult result)**](/api-reference/class/LckResult) | `void` | Event handler: ensures UI is enabled when recording begins successfully. | | **SetAllOnscreenButtonsState(bool state)** | `void` | Enables or disables all tracked UI GameObjects. | | **SetAllOnscreenButtonsToDefaultVisual(List\ objectList)** | `void` | Resets UI button visuals by calling `SetDefaultButtonColors()` on each `LckScreenButton`. | *** ## See Also * [LckNotificationController](/api-reference/unity/classes/LckNotificationController) — Triggers OnNotificationStarted/OnNotificationEnded on this controller * [ILckService](/api-reference/unity/interfaces/ILckService) — Service interface providing recording events * [LckResult](/api-reference/unity/classes/LckResult) — Result type passed to OnRecordingStarted # LckQualityConfig Source: https://docs.liv.tv/api-reference/unity/classes/LckQualityConfig How to configure recording and streaming quality presets per platform and device in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckQualityConfig` is a `ScriptableObject` that defines capture quality presets (resolution, bitrate, framerate) for different platforms and devices. It holds base options for Android and Desktop, plus optional per-device overrides for specific Android hardware. At runtime, `GetQualityOptionsForSystem()` automatically detects the platform and device model to return the correct set of quality options. *** ## Usage Use `LckQualityConfig` to give users quality presets that match their hardware. Assign it to `LCKCameraController` in the Inspector — the camera controller's quality selector will populate from it automatically. *** ## References ### Fields | Field | Type | Description | | :---------------------------- | :---------------------------------------------------------------------------- | :------------------------------------------------------------- | | BaseAndroidQualityOptions | List\<[`QualityOption`](/api-reference/struct/QualityOption)> | Base quality options for Android devices. | | AndroidOptionsDeviceOverrides | List\<[`QualityOptionOverride`](/api-reference/struct/QualityOptionOverride)> | Device-specific override options for Android devices. | | DesktopQualityOptions | List\<[`QualityOption`](/api-reference/struct/QualityOption)> | Quality options for desktop platforms (Windows, Linux, macOS). | ### Methods | Method | Returns | Description | | :--------------------------- | :------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | GetQualityOptionsForSystem() | List\<[`QualityOption`](/api-reference/struct/QualityOption)> | Returns the appropriate quality options for the current runtime platform. On Android, may return device-specific overrides if available; otherwise, returns base Android options. On desktop platforms, returns desktop options. Throws NotImplementedException if the platform is unsupported. | *** ## See Also * [QualityOption](/api-reference/unity/structs/QualityOption) — Struct defining a single quality preset (resolution, bitrate, framerate) * [QualityOptionOverride](/api-reference/unity/structs/QualityOptionOverride) — Device-specific override for Android quality options * [LCKCameraController](/api-reference/unity/classes/LckCameraController) — Camera controller that consumes this config for its quality selector # LckResult Source: https://docs.liv.tv/api-reference/unity/classes/LckResult How to handle success and error responses from LIV Camera Kit (LCK) service operations in Unity without exceptions. ## Description `LckResult` is the standard return type for `LckService` operations. Instead of throwing exceptions, methods return this structured result containing a success flag, optional error code, descriptive message, and the operation's value. It serves the same purpose as `Result` in `LckCore` but is used by the higher-level service layer. *** ## Usage Use `LckResult` whenever you call an `LckService` method. Always check `Success` before accessing the value. ```c# theme={null} var result = _lckService.GetDescriptor(); if (result.Success) { Debug.Log($"Descriptor: {result.Result}"); } else { Debug.LogError($"Failed ({result.Error}): {result.Message}"); } ``` *** ## References ### Properties | Name | Type | Description | | :------ | :------------------------------------------ | :------------------------------------------------------------------------------- | | Success | bool | Indicates whether the operation was successful. | | Message | string | Provides an optional message, typically used for error information. | | Error | [`LckError`](/api-reference/enum/LckError)? | Contains the error code or type if the operation failed. | | Result | T | The result value of the operation if successful; default value of `T` otherwise. | *** ## See Also * [Result T](/api-reference/unity/classes/core/Result) — Similar result type used by LckCore methods * [LckError](/api-reference/unity/enums/LckError) — Error codes returned in the Error property * [LckService](/api-reference/unity/classes/LckService) — Service class whose methods return LckResult # LckService Source: https://docs.liv.tv/api-reference/unity/classes/LckService How to record video, live stream, capture photos, and control audio in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckService` is the main runtime API for the LIV Camera Kit and the default implementation of `ILckService`. It provides methods for starting/stopping recording and streaming, configuring capture settings (resolution, framerate, bitrate, orientation), managing game and microphone audio, capturing photos, and switching active cameras. It also exposes events for every capture lifecycle moment — recording started, stopped, paused, resumed, streaming events, photo saved, and more. *** ## Usage Use `LckService` as the central hub for all capture operations. Inject it via DI, configure your capture settings, then call recording/streaming methods and subscribe to events. ```c# theme={null} [InjectLck] private ILckService _lckService; // Configure capture before starting _lckService.SetTrackResolution(new CameraResolutionDescriptor(1920, 1080)); _lckService.SetTrackFramerate(30); _lckService.SetTrackBitrate(8000000); // Subscribe to events _lckService.OnRecordingStarted += result => Debug.Log("Recording started"); _lckService.OnRecordingStopped += result => Debug.Log("Recording stopped"); // Start recording _lckService.StartRecording(); ``` *** ## References ### Events | Event | Type | Description | | :----------------- | :--------------------------------------------------------------------- | :---------------------------------------------------- | | OnRecordingStarted | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a recording session starts. | | OnRecordingStopped | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a recording session stops. | | OnRecordingPaused | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a recording session is paused. | | OnRecordingResumed | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a recording session resumes. | | OnStreamingStarted | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a live stream starts. | | OnStreamingStopped | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a live stream stops. | | OnPhotoSaved | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when a photo capture is successfully saved. | | OnLowStorageSpace | Action\<[`LckResult`](/api-reference/class/LckResult)> | Invoked when low storage space is detected. | | OnRecordingSaved | Action\<[`LckResult`](/api-reference/class/LckResult)\> | Invoked when a recording has been successfully saved. | | OnActiveCameraSet | Action\<[`LckResult`](/api-reference/class/LckResult)\> | Invoked when the active camera is changed. | ### Methods — Recording | Method | Returns | Description | | :--------------------- | :------------------------------------------------------- | :-------------------------------------------------- | | StartRecording() | [`LckResult`](/api-reference/class/LckResult) | Starts recording to disk. | | StopRecording() | [`LckResult`](/api-reference/class/LckResult) | Stops recording (reason defaults to `UserStopped`). | | PauseRecording() | [`LckResult`](/api-reference/class/LckResult) | Pauses an active recording. | | ResumeRecording() | [`LckResult`](/api-reference/class/LckResult) | Resumes a paused recording. | | GetRecordingDuration() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the duration of the current recording. | | IsRecording() | [`LckResult`](/api-reference/class/LckResult)\ | Returns whether recording is currently active. | | IsPaused() | [`LckResult`](/api-reference/class/LckResult)\ | Returns whether the recording is paused. | ### Methods — Streaming | Method | Returns | Description | | :--------------------------- | :------------------------------------------------------- | :--------------------------------------------- | | StartStreaming() | [`LckResult`](/api-reference/class/LckResult) | Starts live streaming. | | StopStreaming(\[StopReason]) | [`LckResult`](/api-reference/class/LckResult) | Stops live streaming (optional stop reason). | | GetStreamDuration() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the duration of the current stream. | | IsStreaming() | [`LckResult`](/api-reference/class/LckResult)\ | Returns whether streaming is currently active. | ### Methods — Capture Settings | Method | Returns | Description | | :-------------------------------------------------------- | :------------------------------------------------------------- | :----------------------------------------------------------------- | | SetTrackResolution(CameraResolutionDescriptor) | [`LckResult`](/api-reference/class/LckResult) | Sets the capture resolution (only when not capturing). | | SetCameraOrientation(LckCameraOrientation) | [`LckResult`](/api-reference/class/LckResult) | Sets the camera orientation (only when not capturing). | | SetTrackFramerate(uint) | [`LckResult`](/api-reference/class/LckResult) | Sets the capture framerate (only when not capturing). | | SetTrackBitrate(uint) | [`LckResult`](/api-reference/class/LckResult) | Sets the video bitrate (only when not capturing). | | SetTrackAudioBitrate(uint) | [`LckResult`](/api-reference/class/LckResult) | Sets the audio bitrate (only when not capturing). | | SetTrackDescriptor(CameraTrackDescriptor) | [`LckResult`](/api-reference/class/LckResult) | Sets the active camera track descriptor (only when not capturing). | | SetTrackDescriptor(LckCaptureType, CameraTrackDescriptor) | [`LckResult`](/api-reference/class/LckResult) | Sets a track descriptor for a specific capture type. | | GetDescriptor() | [`LckResult`](/api-reference/class/LckResult)\ | Retrieves the current active track descriptor. | | SetPreviewActive(bool) | [`LckResult`](/api-reference/class/LckResult) | Enables or disables preview rendering. | | IsCapturing() | [`LckResult`](/api-reference/class/LckResult)\ | Returns whether capture is active. | | GetActiveCaptureType() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the current capture type (e.g. recording, streaming). | | SetActiveCaptureType(LckCaptureType) | [`LckResult`](/api-reference/class/LckResult) | Sets the current capture type. | | CapturePhoto() | [`LckResult`](/api-reference/class/LckResult) | Captures a still photo. | ### Methods — Camera | Method | Returns | Description | | :-------------------------------------------------------- | :--------------------------------------------------------- | :-------------------------------- | | SetActiveCamera(string cameraId, string monitorId = null) | [`LckResult`](/api-reference/class/LckResult) | Sets the active camera by ID. | | GetActiveCamera() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the currently active camera. | ### Methods — Audio | Method | Returns | Description | | :------------------------------------------- | :---------------------------------------------------- | :-------------------------------------------- | | SetGameAudioCaptureActive(bool) | [`LckResult`](/api-reference/class/LckResult) | Enables/disables game audio capture. | | SetMicrophoneCaptureActive(bool) | [`LckResult`](/api-reference/class/LckResult) | Enables/disables microphone capture. | | SetMicrophoneGain(float) | [`LckResult`](/api-reference/class/LckResult) | Sets microphone gain. | | SetGameAudioGain(float) | [`LckResult`](/api-reference/class/LckResult) | Sets game audio gain. | | GetMicrophoneOutputLevel() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the current microphone output level. | | GetGameOutputLevel() | [`LckResult`](/api-reference/class/LckResult)\ | Gets the current game audio output level. | | IsGameAudioMute() | [`LckResult`](/api-reference/class/LckResult)\ | Returns whether game audio is muted. | | PreloadDiscreetAudio(AudioClip, float, bool) | [`LckResult`](/api-reference/class/LckResult) | Preloads an audio clip for discreet playback. | | PlayDiscreetAudioClip(AudioClip) | [`LckResult`](/api-reference/class/LckResult) | Plays a discreet audio clip once. | | StopAllDiscreetAudio() | [`LckResult`](/api-reference/class/LckResult) | Stops all discreet audio clips. | *** ## See Also * [ILckService](/api-reference/unity/interfaces/ILckService) — Interface that LckService implements; use for DI injection * [LckResult](/api-reference/unity/classes/LckResult) — Result type returned by all service methods * [LckDescriptor](/api-reference/unity/classes/LckDescriptor) — Wrapper for active capture settings returned by GetDescriptor * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — Resolution/framerate/bitrate configuration struct * [LckStreamingController](/api-reference/unity/classes/LckStreamingController) — Higher-level streaming workflow built on LckService * [LCKCameraController](/api-reference/unity/classes/LckCameraController) — Higher-level camera controller that uses LckService internally # LckStreamingController Source: https://docs.liv.tv/api-reference/unity/classes/LckStreamingController How to implement the LIV Hub login, subscription check, and live streaming workflow in Unity VR apps using the LIV Camera Kit (LCK) SDK. ## Description `LckStreamingController` is a Unity `MonoBehaviour` that guides users through the steps required before they can live stream with LIV Hub: logging in, verifying their subscription, and confirming stream configuration. It runs as a state machine that advances through each check automatically, then enables the stream toggle once everything is validated. This class is a reference implementation — you can use it directly or extend it to build custom UI/UX flows. At runtime it integrates with `ILckService` for starting/stopping streams and `ILckCore` for authentication and user status checks. *** ## Usage Use `LckStreamingController` to manage the full streaming setup flow. Attach it to a GameObject, connect a `LckNotificationController` for user messages, and wire your stream button to `StreamingButtonToggled()`. ```c# theme={null} [SerializeField] private LckStreamingController _streamingController; public void OnStreamButtonPressed() { _streamingController.StreamingButtonToggled(); } ``` Call `CheckCurrentState()` when the user opens the streaming UI to kick off the validation state machine. *** ## References ### Properties | Property | Type | Description | | :--------------------------- | :------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | | **IsConfiguredCorrectly** | `bool` | Indicates whether user setup and validation have been completed. When `true`, streaming can be toggled directly. | | **CurrentState** | `LckStreamingBaseState` | The currently active state in the streaming state machine. | | **CheckConfiguredState** | `LckStreamingCheckConfiguredState` | State for verifying if the user has already configured streaming. | | **ShowCodeState** | `LckStreamingShowCodeState` | State for showing the user their login code. | | **CheckSubscribedState** | `LckStreamingCheckSubscribedState` | State for verifying if the user has an active subscription. | | **WaitingForConfigureState** | `LckStreamingWaitingForConfigureState` | State for waiting while the user configures streaming in LIV Hub. | | **ConfiguredCorrectlyState** | `LckStreamingConfiguredCorrectlyState` | State indicating setup is complete and the user can start streaming. | | **CancellationTokenSource** | `CancellationTokenSource` | Token used to cancel any ongoing async tasks when switching states or destroying the object. | | **LckCore** | `ILckCore` | Provides access to the LIV Core SDK for authentication and user status checks. | ### Methods | Method | Returns | Description | | :------------------------------------------- | :------ | :----------------------------------------------------------------------------------------------------------- | | **CheckCurrentState()** | `void` | Starts the setup/validation state machine from the beginning (`CheckConfiguredState`). | | **StopCheckingStates()** | `void` | Cancels any ongoing async checks or polling in the current state. Useful when leaving the streaming UI. | | **SwitchState(LckStreamingBaseState state)** | `void` | Switches to a new streaming state, cancelling pending operations and updating `IsConfiguredCorrectly`. | | **StreamingButtonToggled()** | `void` | Toggles streaming: if configured, starts/stops streaming; if not configured, invokes `_onStreamButtonError`. | | **StopStreaming()** | `void` | Force stops streaming if a stream is currently active. | | **ShowNotification(NotificationType type)** | `void` | Displays a notification via the linked `LckNotificationController`. | | **HideNotifications()** | `void` | Hides currently visible notifications. | | **SetNotificationStreamCode(string code)** | `void` | Updates the notification UI with a login/streaming code. | | **Log(string message)** | `void` | Logs debug messages if `_showDebugLogs` is enabled. | | **LogError(string error)** | `void` | Logs error messages if `_showDebugLogs` is enabled. | *** ## See Also * [ILckService](/api-reference/unity/interfaces/ILckService) — Service interface used to start/stop streams * [ILckCore](/api-reference/unity/interfaces/core/ILckCore) — Core interface used for authentication and subscription checks * [LckNotificationController](/api-reference/unity/classes/LckNotificationController) — Displays login codes, errors, and configuration prompts during the flow * [LckService](/api-reference/unity/classes/LckService) — Default ILckService implementation with streaming methods # LckCore Source: https://docs.liv.tv/api-reference/unity/classes/core/LckCore How to initialize the LIV Camera Kit (LCK) SDK, authenticate users, and check streaming/subscription status in Unity VR apps. ## Description `LckCore` is the entry point for setting up the LIV Camera Kit before any recording or streaming can happen. It handles SDK initialization with your tracking ID, manages user authentication via LIV Hub, and lets you check whether users have configured streaming or hold an active subscription. This class should always be used as provided; re-implementing it is not recommended. For custom streaming flows, use `LckStreamingController`. *** ## Usage Use `LckCore` at game startup to initialize the SDK, and during the streaming setup flow to authenticate users and verify their configuration. ### Initialization ```c# theme={null} var gameInfo = new GameInfo { GameName = "My Game", GameVersion = "1.0.0", ProjectName = "MyProject", CompanyName = "MyCompany", EngineVersion = Application.unityVersion, RenderPipeline = "URP", GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() }; var initResult = LckCore.Initialize("tracking-id-123", gameInfo); if (!initResult.IsOk) { Debug.LogError($"Initialization failed: {initResult.Message}"); } ``` ### Authentication Flow ```c# theme={null} // Start login attempt var loginResult = await LckCore.StartLoginAttemptAsync(); if (loginResult.IsOk) { Debug.Log($"Login code: {loginResult.Ok}"); } // Poll to check whether login is complete var loginCheck = await LckCore.CheckLoginCompletedAsync(); if (loginCheck.IsOk && loginCheck.Ok) { Debug.Log("User successfully logged in!"); } ``` ### Checking User Status ```c# theme={null} // Has the user configured streaming? var hasStreaming = await LckCore.HasUserConfiguredStreaming(); Debug.Log($"Streaming configured: {hasStreaming.Ok}"); // Is the user subscribed? var subscription = await LckCore.IsUserSubscribed(); Debug.Log($"User subscribed: {subscription.Ok}"); ``` ### Cleanup ```c# theme={null} LckCore.Dispose(); ``` *** ## References ### Methods | Method | Returns | Description | | :--------------------------------------------------------------- | :--------------------- | :---------------------------------------------------------- | | SetMaxLogLevel([`LevelFilter`](/api-reference/enum/LevelFilter)) | void | Sets the maximum log verbosity. | | Initialize(string, [`GameInfo`](/api-reference/struct/GameInfo)) | Result\ | Initializes the SDK with tracking ID and game info. | | HasUserConfiguredStreaming() | Task\> | Checks if the user has configured streaming. | | IsUserSubscribed() | Task\> | Checks if the user has an active subscription. | | StartLoginAttemptAsync() | Task\> | Starts a login attempt, returns a login code if successful. | | CheckLoginCompletedAsync() | Task\> | Polls whether the login attempt has completed. | | Dispose() | void | Releases resources and cleans up the SDK. | *** ## See Also * [Result T](/api-reference/unity/classes/core/Result) — Generic result type returned by all LckCore methods * [GameInfo](/api-reference/unity/structs/core/GameInfo) — Struct passed to Initialize with game metadata * [LevelFilter](/api-reference/unity/enums/core/LevelFilter) — Log verbosity levels for SetMaxLogLevel * [LckStreamingController](/api-reference/unity/classes/LckStreamingController) — Higher-level streaming workflow built on LckCore # Result < T > Source: https://docs.liv.tv/api-reference/unity/classes/core/Result How to handle success and error responses from LIV Camera Kit (LCK) SDK methods in Unity without exceptions. ## Description `Result` is the standard return type for all `LckCore` operations. Instead of throwing exceptions, methods like `Initialize()`, `HasUserConfiguredStreaming()`, `IsUserSubscribed()`, and `StartLoginAttemptAsync()` return a `Result` that wraps either a success value or an error with a message. This gives you consistent, predictable error handling across the entire SDK without try/catch blocks. *** ## Usage Use `Result` whenever you call an LckCore method. Always check `IsOk` before accessing the value. ```c# theme={null} Result initResult = LckCore.Initialize("trackingId", gameInfo); if (initResult.IsOk) { Debug.Log("Initialization successful!"); } else { Debug.LogError($"Init failed: {initResult.Err} - {initResult.Message}"); } ``` *** ## References ### Properties | Property | Type | Description | | :---------- | :------------ | :---------------------------------------------------------------------------------------------------- | | **IsOk** | `bool` | Indicates whether the operation was successful. Always check this first. | | **Message** | `string` | Error message when the operation fails. `null` if successful. | | **Err** | `CoreError` ? | High-level error category (e.g., `InternalError`, `InvalidArgument`). `null` if successful. | | **Ok** | `T` | The result value on success. Returns default(`T`) (e.g., `null`, `0`, or `false`) if `IsOk == false`. | *** ### Methods | Method | Returns | Description | | :----------------------------------------------------------- | :---------- | :-------------------------------------------------------------------------------------------- | | `static Result NewSuccess(T result)` | `Result` | Creates a result representing a successful operation and wraps the returned value. | | `static Result NewError(CoreError error, string message)` | `Result` | Creates a result representing a failed operation, with an error type and descriptive message. | *** ## See Also * [LckCore](/api-reference/unity/classes/core/LckCore) — Primary class whose methods return Result\ * [CoreError](/api-reference/unity/enums/CoreError) — Error category enum used in the Err property # CoreError Source: https://docs.liv.tv/api-reference/unity/enums/CoreError High-level error categories from LckCore operations like initialization and authentication failures. ## What Problem Does This Solve? Before you can use the LCK SDK, you need to initialize it with `LckCore.Initialize()`. This process can fail for several reasons: missing tracking ID, invalid arguments, network issues, or user not logged in. `CoreError` provides specific error codes for these initialization and core-level failures so you can diagnose and fix setup problems. ## When to Use This You'll encounter `CoreError` when: * Initializing the SDK with `LckCore.Initialize()` * Operations require authentication (user login) * Passing invalid arguments to core methods * SDK encounters internal errors during setup These are **setup-time errors**, not runtime recording errors (those use `LckError`). *** ## Quick Example ```csharp theme={null} var gameInfo = new GameInfo { /* ... */ }; var result = LckCore.Initialize("tracking-id-123", gameInfo); if (!result.IsOk) { Debug.LogError($"Initialization failed: {result.Message}"); switch (result.Error) { case CoreError.MissingTrackingId: Debug.LogError("No tracking ID provided"); break; case CoreError.InvalidArgument: Debug.LogError("Invalid GameInfo configuration"); break; case CoreError.UserNotLoggedIn: Debug.LogError("User must be logged in"); break; case CoreError.InternalError: Debug.LogError("SDK internal error"); break; } return; } Debug.Log("LCK initialized successfully"); ``` *** ## Error Breakdown ### MissingTrackingId **When it happens:** You passed `null`, empty string, or invalid tracking ID to `Initialize()` **How to fix:** ```csharp theme={null} // Bad LckCore.Initialize(null, gameInfo); // Error LckCore.Initialize("", gameInfo); // Error // Good LckCore.Initialize("your-tracking-id", gameInfo); // OK ``` Your tracking ID comes from the LIV Developer Dashboard. Get it from `https://dashboard.liv.tv/dev/` *** ### InvalidArgument **When it happens:** You passed invalid data to a core method (bad GameInfo, null parameters, etc.) **How to fix:** ```csharp theme={null} // Bad LckCore.Initialize("tracking-id", null); // Error - null GameInfo // Good var gameInfo = new GameInfo { GameName = "My Game", GameVersion = "1.0.0", ProjectName = "MyProject", CompanyName = "Studio", EngineVersion = Application.unityVersion, RenderPipeline = "URP", GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() }; LckCore.Initialize("tracking-id", gameInfo); // OK ``` Validate your inputs before calling core methods. *** ### UserNotLoggedIn **When it happens:** Operation requires authentication but user hasn't logged in **How to fix:** ```csharp theme={null} // Check login status bool isLoggedIn = LckCore.IsUserLoggedIn(); if (!isLoggedIn) { // Prompt user to log in ShowLoginDialog(); return; } // Proceed with authenticated operation ``` Some SDK features require the user to be logged into their LIV account. *** ### InternalError **When it happens:** Unexpected SDK internal failure **What to do:** 1. Check the full error message: `result.Message` 2. Look for more details in Unity console or device logs 3. Report to LIV support with: * Error message * SDK version * Platform (Android, Windows, etc.) * Steps to reproduce ```csharp theme={null} if (result.Error == CoreError.InternalError) { Debug.LogError($"Internal error: {result.Message}"); Debug.LogError($"Platform: {Application.platform}"); Debug.LogError($"SDK Version: [your SDK version]"); // Report to analytics/crash reporting ReportError("LCK Internal Error", result.Message); } ``` *** ## Complete Error Reference | Error | Value | Description | Fix | | ------------------- | ----- | --------------------------------- | ---------------------------------------- | | `InternalError` | 0 | Unexpected SDK internal error | Check logs, report to support | | `MissingTrackingId` | 1 | Tracking ID missing or invalid | Provide valid tracking ID from dashboard | | `InvalidArgument` | 2 | Invalid argument passed to method | Validate inputs (GameInfo, etc.) | | `UserNotLoggedIn` | 3 | User authentication required | Prompt user to log in | *** ## Common Initialization Pattern ```csharp theme={null} public class LCKManager : MonoBehaviour { [SerializeField] private string trackingId = "your-tracking-id-here"; void Awake() { InitializeLCK(); } void InitializeLCK() { // Validate tracking ID if (string.IsNullOrEmpty(trackingId)) { Debug.LogError("Tracking ID not set in inspector!"); return; } // Create GameInfo var gameInfo = new GameInfo { GameName = Application.productName, GameVersion = Application.version, ProjectName = Application.productName, CompanyName = Application.companyName, EngineVersion = Application.unityVersion, RenderPipeline = GetRenderPipeline(), GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() }; // Initialize var result = LckCore.Initialize(trackingId, gameInfo); if (!result.IsOk) { HandleInitializationError(result); return; } Debug.Log("LCK initialized successfully"); } void HandleInitializationError(LckResult result) { Debug.LogError($"LCK initialization failed: {result.Message}"); switch (result.Error) { case CoreError.MissingTrackingId: ShowError("Configuration error: Missing tracking ID"); break; case CoreError.InvalidArgument: ShowError("Configuration error: Invalid setup"); break; case CoreError.UserNotLoggedIn: ShowLoginPrompt(); break; case CoreError.InternalError: ShowError("SDK initialization failed. Please restart."); ReportToAnalytics("LCK_Init_Internal_Error", result.Message); break; } } string GetRenderPipeline() { #if UNITY_PIPELINE_URP return "URP"; #elif UNITY_PIPELINE_HDRP return "HDRP"; #else return "Built-in"; #endif } } ``` *** ## Best Practices **Validate inputs** — Check tracking ID and GameInfo before initializing **Initialize early** — Call in `Awake()` or `Start()` before using SDK **Handle all errors** — Don't ignore initialization failures **Log details** — Include platform, SDK version in error reports ### Don't ignore initialization errors ```csharp theme={null} // Bad - ignoring failure LckCore.Initialize(trackingId, gameInfo); // Good - checking result var result = LckCore.Initialize(trackingId, gameInfo); if (!result.IsOk) { Debug.LogError($"Init failed: {result.Message}"); return; // Don't proceed if init failed } ``` *** ## Debugging Tips ### Check if already initialized ```csharp theme={null} if (LckCore.IsInitialized()) { Debug.Log("LCK already initialized"); return; } ``` ### Validate tracking ID format ```csharp theme={null} bool IsValidTrackingId(string id) { return !string.IsNullOrWhiteSpace(id) && id.Length > 10; } if (!IsValidTrackingId(trackingId)) { Debug.LogError("Invalid tracking ID format"); } ``` ### Test with mock data ```csharp theme={null} #if UNITY_EDITOR // Use test tracking ID in editor trackingId = "test-tracking-id-editor"; #endif ``` *** ## Related * [GameInfo](/api-reference/unity/structs/core/GameInfo) — Required initialization parameter * [LckError](/api-reference/unity/enums/LckError) — Runtime operation errors * [Initialization Guide](/unity/installation) — Complete setup walkthrough # LckCameraOrientation Source: https://docs.liv.tv/api-reference/unity/enums/LckCameraOrientation Set camera output orientation to portrait (vertical) or landscape (horizontal) for mobile and social media content. ## What Problem Does This Solve? Mobile users hold their devices in portrait (vertical) or landscape (horizontal) orientation. Social media platforms like Instagram Stories, TikTok, and YouTube Shorts expect vertical video (9:16), while traditional YouTube prefers horizontal (16:9). `LckCameraOrientation` lets you set the output orientation to match how users hold their device or the platform they're targeting. ## When to Use This Use `LckCameraOrientation` when: * Building a mobile app with recording/streaming * Supporting social media uploads (TikTok, Instagram, Snapchat) * Users can rotate their device during gameplay * Creating platform-specific content (vertical for Stories, horizontal for YouTube) Skip this if you're desktop-only or always output the same orientation. *** ## Quick Example ```csharp theme={null} // Switch to portrait for TikTok/Instagram Stories var result = lckService.SetCameraOrientation(LckCameraOrientation.Portrait); if (!result.IsOk) { Debug.LogError($"Failed to set orientation: {result.Message}"); } // Switch to landscape for YouTube lckService.SetCameraOrientation(LckCameraOrientation.Landscape); ``` *** ## How It Works Camera orientation determines the aspect ratio of the output video: **Portrait (Vertical)** * Aspect ratio: 9:16 (e.g., 1080×1920) * Matches vertical phone orientation * Best for: TikTok, Instagram Stories, Snapchat, Reels **Landscape (Horizontal)** * Aspect ratio: 16:9 (e.g., 1920×1080) * Matches horizontal phone orientation * Best for: YouTube, traditional video platforms The orientation affects how the resolution is interpreted. A `CameraResolutionDescriptor(1080, 1920)` in portrait mode outputs vertical video, while landscape swaps it to 1920×1080. *** ## Common Patterns ### Auto-detect device orientation ```csharp theme={null} void Update() { var currentOrientation = Input.deviceOrientation; if (currentOrientation == DeviceOrientation.Portrait || currentOrientation == DeviceOrientation.PortraitUpsideDown) { lckService.SetCameraOrientation(LckCameraOrientation.Portrait); } else if (currentOrientation == DeviceOrientation.LandscapeLeft || currentOrientation == DeviceOrientation.LandscapeRight) { lckService.SetCameraOrientation(LckCameraOrientation.Landscape); } } ``` ### Platform-specific orientation ```csharp theme={null} public enum SharePlatform { TikTok, Instagram, YouTube, Twitter } void SetOrientationForPlatform(SharePlatform platform) { var orientation = platform switch { SharePlatform.TikTok => LckCameraOrientation.Portrait, SharePlatform.Instagram => LckCameraOrientation.Portrait, SharePlatform.YouTube => LckCameraOrientation.Landscape, SharePlatform.Twitter => LckCameraOrientation.Landscape, _ => LckCameraOrientation.Landscape }; lckService.SetCameraOrientation(orientation); Debug.Log($"Set {orientation} orientation for {platform}"); } ``` ### UI toggle ```csharp theme={null} public class OrientationToggle : MonoBehaviour { public Toggle portraitToggle; void Start() { portraitToggle.onValueChanged.AddListener(OnOrientationChanged); } void OnOrientationChanged(bool isPortrait) { var orientation = isPortrait ? LckCameraOrientation.Portrait : LckCameraOrientation.Landscape; var result = lckService.SetCameraOrientation(orientation); if (!result.IsOk) { Debug.LogError($"Failed to change orientation: {result.Message}"); portraitToggle.isOn = !isPortrait; // Revert toggle } } } ``` *** ## Resolution by Orientation **Portrait (9:16)** ```csharp theme={null} // Common portrait resolutions new CameraResolutionDescriptor(720, 1280) // HD vertical new CameraResolutionDescriptor(1080, 1920) // Full HD vertical ``` **Landscape (16:9)** ```csharp theme={null} // Common landscape resolutions new CameraResolutionDescriptor(1280, 720) // HD horizontal new CameraResolutionDescriptor(1920, 1080) // Full HD horizontal ``` Make sure your resolution descriptor matches your intended orientation. A 1920×1080 resolution in portrait mode will output vertical video at 1080×1920. *** ## Platform Orientation Guidelines | Platform | Preferred Orientation | Aspect Ratio | Resolution | | --------------------- | --------------------- | ------------ | ---------- | | **TikTok** | Portrait | 9:16 | 1080×1920 | | **Instagram Stories** | Portrait | 9:16 | 1080×1920 | | **Instagram Reels** | Portrait | 9:16 | 1080×1920 | | **YouTube Shorts** | Portrait | 9:16 | 1080×1920 | | **Snapchat** | Portrait | 9:16 | 1080×1920 | | **YouTube** | Landscape | 16:9 | 1920×1080 | | **Twitter/X** | Landscape | 16:9 | 1280×720 | | **Twitch** | Landscape | 16:9 | 1920×1080 | *** ## Enum Values | Value | Description | Aspect Ratio | Use Case | | ----------- | ----------------------------- | ------------ | ----------------------------------- | | `Portrait` | Vertical orientation (tall) | 9:16 | Mobile-first, social media stories | | `Landscape` | Horizontal orientation (wide) | 16:9 | Traditional video, YouTube, desktop | *** ## API Reference ### Set Orientation ```csharp theme={null} LckResult SetCameraOrientation(LckCameraOrientation orientation) ``` **Parameters:** * `orientation` — `Portrait` or `Landscape` **Returns:** `LckResult` indicating success/failure *** ## Best Practices **Match target platform** — Use portrait for TikTok, landscape for YouTube **Set before recording** — Orientation should be configured before starting capture **Test both modes** — Ensure your UI works in both orientations **Don't change during recording** — Stop recording before switching orientation ### Good Pattern ```csharp theme={null} // Check if recording before changing bool isRecording = lckService.IsRecording(); if (isRecording) { Debug.LogWarning("Cannot change orientation during recording"); return; } var result = lckService.SetCameraOrientation(newOrientation); if (result.IsOk) { Debug.Log($"Orientation set to {newOrientation}"); } ``` *** ## Mobile Development Tips ### Lock orientation in Unity ```csharp theme={null} // Lock to portrait Screen.orientation = ScreenOrientation.Portrait; Screen.autorotateToPortrait = true; Screen.autorotateToPortraitUpsideDown = false; Screen.autorotateToLandscapeLeft = false; Screen.autorotateToLandscapeRight = false; // Lock to landscape Screen.orientation = ScreenOrientation.LandscapeLeft; Screen.autorotateToLandscapeLeft = true; Screen.autorotateToLandscapeRight = true; Screen.autorotateToPortrait = false; Screen.autorotateToPortraitUpsideDown = false; ``` ### Handle safe areas ```csharp theme={null} // Adjust UI for notches and rounded corners var safeArea = Screen.safeArea; rectTransform.anchoredPosition = new Vector2( safeArea.x, safeArea.y ); ``` *** ## Related * [CameraResolutionDescriptor](/api-reference/unity/structs/CameraResolutionDescriptor) — Set output resolution * [QualityOption](/api-reference/unity/structs/QualityOption) — Quality presets with resolution # LckCaptureType Source: https://docs.liv.tv/api-reference/unity/enums/LckCaptureType Specify whether the camera is configured for local recording or live streaming, which determines quality settings and bitrate. ## What Problem Does This Solve? Recording and streaming have different requirements. Recording prioritizes quality and file size—you want high bitrate, 60fps, large files you'll upload later. Streaming prioritizes bandwidth and latency—lower bitrate, 30fps, smaller data sent live. `LckCaptureType` lets you switch between these modes, which automatically applies the appropriate track descriptor from your quality options (recording vs. streaming settings). ## When to Use This Use `LckCaptureType` when: * Your app supports both recording and streaming * You want different quality settings for each mode * Users can toggle between "Record for upload" vs. "Stream live" * You need to optimize for bandwidth (streaming) vs. quality (recording) Skip this if you only support one mode. *** ## Quick Example ```csharp theme={null} // Switch to streaming mode (applies streaming track descriptor) var result = lckService.SetActiveCaptureType(LckCaptureType.Streaming); if (!result.IsOk) { Debug.LogError($"Failed to set capture type: {result.Message}"); } // Later, switch back to recording lckService.SetActiveCaptureType(LckCaptureType.Recording); // Check current mode var currentType = lckService.GetActiveCaptureType(); Debug.Log($"Current mode: {currentType.Result}"); ``` *** ## How It Works When you define a `QualityOption`, you specify **two** track descriptors: ```csharp theme={null} var qualityOption = new QualityOption( name: "High", isDefault: true, cameraTrackDescriptor: new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 10 << 20, // 10 Mbps for recording framerate: 60 ), streamingCameraTrackDescriptor: new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 5 << 20, // 5 Mbps for streaming framerate: 30 ) ); ``` **When you set the capture type:** * `LckCaptureType.Recording` → uses `cameraTrackDescriptor` * `LckCaptureType.Streaming` → uses `streamingCameraTrackDescriptor` The SDK automatically applies the right settings. *** ## Common Patterns ### Toggle between modes with UI ```csharp theme={null} public class CaptureTypeToggle : MonoBehaviour { public void OnRecordingToggleChanged(bool isRecording) { var captureType = isRecording ? LckCaptureType.Recording : LckCaptureType.Streaming; var result = lckService.SetActiveCaptureType(captureType); if (!result.IsOk) { Debug.LogError($"Failed to switch mode: {result.Message}"); return; } UpdateUI(captureType); } void UpdateUI(LckCaptureType type) { string mode = type == LckCaptureType.Recording ? "Recording" : "Streaming"; modeLabel.text = $"Mode: {mode}"; } } ``` ### Auto-select based on network ```csharp theme={null} void Start() { // Use streaming if network available, recording otherwise bool hasNetwork = Application.internetReachability != NetworkReachability.NotReachable; var captureType = hasNetwork ? LckCaptureType.Streaming : LckCaptureType.Recording; lckService.SetActiveCaptureType(captureType); Debug.Log($"Selected {captureType} mode (network: {hasNetwork})"); } ``` ### Initialize to default mode ```csharp theme={null} void InitializeLCK() { // ... LckCore.Initialize() ... // Default to recording mode var result = lckService.SetActiveCaptureType(LckCaptureType.Recording); if (!result.IsOk) { Debug.LogError($"Failed to set default capture type: {result.Message}"); } } ``` *** ## Typical Settings by Mode ### Recording Mode Optimized for quality and upload: * **Higher bitrate** (8-12 Mbps) for better quality * **Higher framerate** (60 fps) for smooth motion * **Larger file sizes** acceptable ```csharp theme={null} // Recording track example new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 10 << 20, // 10 Mbps framerate: 60, audioBitrate: 256000 // 256 kbps ) ``` ### Streaming Mode Optimized for bandwidth and latency: * **Lower bitrate** (3-5 Mbps) to reduce bandwidth * **Standard framerate** (30 fps) for stability * **Smaller data packets** for real-time delivery ```csharp theme={null} // Streaming track example new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 5 << 20, // 5 Mbps framerate: 30, audioBitrate: 192000 // 192 kbps ) ``` *** ## Enum Values | Value | Description | Use Case | | ----------- | -------------------- | --------------------------------------------------- | | `Recording` | Local recording mode | Save to device, upload later, prioritize quality | | `Streaming` | Live streaming mode | Real-time delivery, prioritize bandwidth efficiency | *** ## API Reference ### Set Capture Type ```csharp theme={null} LckResult SetActiveCaptureType(LckCaptureType captureType) ``` **Parameters:** * `captureType` — `Recording` or `Streaming` **Returns:** `LckResult` indicating success/failure ### Get Capture Type ```csharp theme={null} LckResult GetActiveCaptureType() ``` **Returns:** `LckResult` containing current capture type *** ## When to Switch Modes **Before starting capture** — Don't change during active recording **Based on user preference** — UI toggle for "Record" vs. "Stream" **Network availability** — Auto-select streaming if online **Cannot change during recording** — Returns `CantEditSettingsWhileRecording` error *** ## Best Practices ```csharp theme={null} // Good: Check before switching var currentType = lckService.GetActiveCaptureType(); if (currentType.Result != desiredType) { var result = lckService.SetActiveCaptureType(desiredType); if (!result.IsOk) { Debug.LogError($"Mode switch failed: {result.Message}"); } } // Bad: Switching during recording (will fail) lckService.StartRecording(); lckService.SetActiveCaptureType(LckCaptureType.Streaming); // ERROR ``` **Define both tracks** — Always configure recording and streaming in QualityOption **Test both modes** — Ensure settings work for both use cases **Stop before switching** — Don't change modes during active recording *** ## Related * [QualityOption](/api-reference/unity/structs/QualityOption) — Define recording and streaming tracks * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — Configure bitrate/framerate # LckError Source: https://docs.liv.tv/api-reference/unity/enums/LckError Error codes that explain why LCK operations fail, returned in LckResult for debugging and error handling. ## What Problem Does This Solve? When an LCK operation fails (starting recording, capturing a photo, changing settings), you need to know *why* it failed. Was it a permission issue? Storage full? Already recording? Wrong platform? `LckError` provides specific error codes so you can handle failures appropriately—show the right error message, retry the operation, or disable features. ## When to Use This You'll check `LckError` whenever an `LckResult` indicates failure: ```csharp theme={null} var result = lckService.StartRecording(); if (!result.IsOk) { // result.Error contains the LckError enum value switch (result.Error) { case LckError.NotEnoughStorageSpace: ShowError("Not enough storage space to record"); break; case LckError.MicrophonePermissionDenied: ShowError("Microphone permission required"); break; default: ShowError($"Recording failed: {result.Message}"); break; } } ``` *** ## Common Error Scenarios ### Storage Issues ```csharp theme={null} if (result.Error == LckError.NotEnoughStorageSpace) { // Prompt user to free up space long bytesNeeded = GetEstimatedRecordingSize(); ShowStorageWarning($"Need {bytesNeeded / 1_000_000} MB free"); } ``` ### Permission Errors ```csharp theme={null} if (result.Error == LckError.MicrophonePermissionDenied) { // Request permission or guide user to settings RequestMicrophonePermission(); } ``` ### State Errors ```csharp theme={null} if (result.Error == LckError.RecordingAlreadyStarted) { // User already recording, show status instead Debug.Log("Recording already in progress"); return; } if (result.Error == LckError.NotCurrentlyRecording) { // Tried to stop when not recording Debug.LogWarning("No active recording to stop"); } ``` ### Platform/Compatibility ```csharp theme={null} if (result.Error == LckError.UnsupportedGraphicsApi) { ShowError("Graphics API not supported. Try switching to Vulkan or DirectX11."); } if (result.Error == LckError.UnsupportedPlatform) { ShowError("LCK is not supported on this platform"); } ``` *** ## Error Categories ### **Initialization & Setup Errors** | Error | When It Happens | How to Fix | | ------------------------ | ------------------------------------ | ------------------------------------- | | `ServiceNotCreated` | Calling LCK before initialization | Call `LckCore.Initialize()` first | | `ServiceDisposed` | Using LCK after disposal | Don't use after calling `Dispose()` | | `InvalidDescriptor` | Bad configuration in `LckDescriptor` | Check camera/quality settings | | `UnsupportedGraphicsApi` | Graphics API not supported | Switch to DirectX11, Vulkan, or Metal | | `UnsupportedPlatform` | Platform not supported | Check platform compatibility | ### **Permission Errors** | Error | When It Happens | How to Fix | | ---------------------------- | ---------------------- | ------------------------------------- | | `MicrophonePermissionDenied` | User denied mic access | Request permission, guide to settings | ### **Recording State Errors** | Error | When It Happens | How to Fix | | -------------------------------- | ------------------------------------ | --------------------------------------- | | `RecordingAlreadyStarted` | Start called while recording | Check if recording before starting | | `NotCurrentlyRecording` | Stop/query called when not recording | Check recording state first | | `NotPaused` | Resume called when not paused | Only resume if paused | | `CantEditSettingsWhileRecording` | Changing settings during recording | Stop recording before changing settings | ### **Runtime Errors** | Error | When It Happens | How to Fix | | ------------------- | ------------------------ | ------------------------------ | | `RecordingError` | Failure during recording | Check logs, report issue | | `PhotoCaptureError` | Photo capture failed | Retry or check camera state | | `StreamingError` | Streaming failure | Check network, streamer config | | `EncodingError` | Encoder failure | Reduce quality settings | | `MicrophoneError` | Mic input failure | Check device, permissions | ### **Storage Errors** | Error | When It Happens | How to Fix | | -------------------------------- | --------------------------- | ----------------------------- | | `NotEnoughStorageSpace` | Insufficient disk space | Free up space, reduce quality | | `FailedToCopyRecordingToGallery` | Can't save video to gallery | Check permissions, storage | | `FailedToCopyPhotoToGallery` | Can't save photo to gallery | Check permissions, storage | ### **Missing Features** | Error | When It Happens | How to Fix | | ------------------------ | ------------------------------- | ----------------------------- | | `StreamerNotImplemented` | Streaming package not installed | Install LCK Streaming package | ### **Camera/Monitor Errors** | Error | When It Happens | How to Fix | | ------------------- | ------------------ | -------------------------------------- | | `CameraIdNotFound` | Invalid camera ID | Use valid camera from `GetCameras()` | | `MonitorIdNotFound` | Invalid monitor ID | Use valid monitor from `GetMonitors()` | ### **Unknown** | Error | When It Happens | How to Fix | | -------------- | ------------------ | --------------------------------- | | `UnknownError` | Unexpected failure | Check logs, report to LIV support | *** ## Complete Error Reference | Error Code | Value | Description | | -------------------------------- | ----- | -------------------------------------- | | `ServiceNotCreated` | 1 | LCK service not initialized before use | | `ServiceDisposed` | 2 | LCK service already disposed | | `InvalidDescriptor` | 3 | Invalid `LckDescriptor` configuration | | `CameraIdNotFound` | 4 | Camera ID doesn't exist | | `MonitorIdNotFound` | 5 | Monitor ID doesn't exist | | `MicrophonePermissionDenied` | 6 | Microphone permission denied | | `RecordingAlreadyStarted` | 7 | Recording already in progress | | `NotCurrentlyRecording` | 8 | No active recording | | `NotPaused` | 9 | Recording not paused (can't resume) | | `RecordingError` | 10 | General recording failure | | `PhotoCaptureError` | 11 | Photo capture failed | | `CantEditSettingsWhileRecording` | 12 | Settings locked during recording | | `NotEnoughStorageSpace` | 13 | Insufficient disk space | | `FailedToCopyRecordingToGallery` | 14 | Can't save video to gallery | | `FailedToCopyPhotoToGallery` | 15 | Can't save photo to gallery | | `UnsupportedGraphicsApi` | 16 | Graphics API not supported | | `UnsupportedPlatform` | 17 | Platform not supported | | `MicrophoneError` | 18 | Microphone runtime error | | `StreamerNotImplemented` | 19 | Streaming package missing | | `StreamingError` | 20 | Streaming failure | | `EncodingError` | 21 | Encoding failure | | `UnknownError` | 22 | Unknown/unspecified error | *** ## Best Practices **Always check IsOk** — Never assume operations succeed **Log errors** — Include `result.Message` for context **Handle common cases** — Storage, permissions, state conflicts **User-friendly messages** — Don't show raw error codes to users ### Good Error Handling Pattern ```csharp theme={null} var result = lckService.StartRecording(); if (!result.IsOk) { Debug.LogError($"Recording failed: {result.Error} - {result.Message}"); string userMessage = result.Error switch { LckError.NotEnoughStorageSpace => "Not enough space. Free up storage and try again.", LckError.MicrophonePermissionDenied => "Microphone access required. Enable in Settings.", LckError.RecordingAlreadyStarted => "Recording already in progress.", LckError.UnsupportedGraphicsApi => "Graphics API not supported. Try switching to Vulkan.", _ => "Recording failed. Please try again." }; ShowErrorDialog(userMessage); return; } Debug.Log("Recording started successfully"); ``` *** ## Related * [LckResult](/api-reference/unity/classes/LckResult) — Contains LckError when operations fail * [Error Handling Guide](https://docs.liv.tv/api-reference/unity/interfaces/core/ILckCore#error-handling) # LevelFilter Source: https://docs.liv.tv/api-reference/unity/enums/core/LevelFilter Control SDK logging verbosity from silent to full trace for debugging and production builds. ## What Problem Does This Solve? During development, you want detailed logs to debug issues—what methods are called, what data is passed, when errors occur. In production, you want minimal logging to avoid performance overhead and log spam. `LevelFilter` controls how much the LCK SDK logs to the console, letting you dial logging verbosity up for debugging or down for release builds. ## When to Use This Set `LevelFilter` when: * **Development:** Use `Debug` or `Trace` to see detailed SDK behavior * **Testing/QA:** Use `Info` to see important events without noise * **Production:** Use `Warn` or `Error` to only log problems * **Release builds:** Use `Off` to disable all SDK logging *** ## Quick Example ```csharp theme={null} // Development build - verbose logging #if DEVELOPMENT_BUILD LckCore.SetLogLevel(LevelFilter.Debug); #else // Production - errors only LckCore.SetLogLevel(LevelFilter.Error); #endif ``` *** ## Log Levels Explained ### Off **No logging at all.** The SDK produces zero console output. **When to use:** * Shipping builds where you don't want any SDK logs * Performance-critical sections * Final release to customers ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Off); // SDK will produce no logs ``` *** ### Error **Critical errors only.** Only logs when something breaks. **When to use:** * Production/release builds * You only want to know when things fail * Minimize log noise **What you'll see:** * Initialization failures * Recording errors * Permission denials * Platform incompatibility ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Error); // Example output: // [LCK ERROR] Failed to start recording: NotEnoughStorageSpace // [LCK ERROR] Microphone permission denied ``` *** ### Warn **Errors + warnings.** Problems that might cause issues but aren't critical. **When to use:** * Staging/beta builds * You want to catch potential issues before they become errors * Production with monitoring **What you'll see:** * Low storage warnings * Deprecated API usage * Suboptimal configurations * Performance concerns ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Warn); // Example output: // [LCK WARN] Storage space low (< 500 MB remaining) // [LCK WARN] Using deprecated method, migrate to XYZ // [LCK ERROR] Recording failed ``` *** ### Info **Important events.** SDK lifecycle events and state changes. **When to use:** * QA/testing builds * You want visibility into what the SDK is doing * Troubleshooting user reports **What you'll see:** * SDK initialization * Recording start/stop * Quality changes * Camera configuration ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Info); // Example output: // [LCK INFO] LckCore initialized successfully // [LCK INFO] Recording started at 1080p60 // [LCK INFO] Quality changed to Medium // [LCK WARN] Storage low // [LCK ERROR] Mic permission denied ``` *** ### Debug **Everything + debug details.** Internal SDK operations and parameter values. **When to use:** * Active development * Debugging SDK integration issues * Understanding SDK behavior * Reproducing bugs **What you'll see:** * Method calls with parameters * Internal state changes * Configuration details * Data flow ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Debug); // Example output: // [LCK DEBUG] SetActiveCaptureType(Recording) // [LCK DEBUG] Applying quality option: High (1920x1080, 10Mbps, 60fps) // [LCK DEBUG] Encoder initialized: H264, bitrate=10485760 // [LCK INFO] Recording started // [LCK WARN] Storage: 450 MB remaining ``` *** ### Trace **Maximum verbosity.** Frame-by-frame details, extremely detailed logging. **When to use:** * Deep debugging of specific issues * Understanding exact execution flow * Performance profiling * Reporting bugs to LIV support **Warning:** Very high log volume, may impact performance. **What you'll see:** * Every frame's processing * Memory allocations * Texture updates * Encoder buffer states * Network packet details (streaming) ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Trace); // Example output: // [LCK TRACE] Frame 1245: Capture started // [LCK TRACE] Frame 1245: Texture copy (1920x1080, 8.2ms) // [LCK TRACE] Frame 1245: Encoder input queued // [LCK TRACE] Frame 1245: Audio buffer: 1024 samples // [LCK DEBUG] Quality check: framerate stable at 60fps // [LCK INFO] Recording at 00:01:23 ``` *** ## Recommended Settings by Build Type | Build Type | Recommended Level | Rationale | | ------------------------ | ----------------- | --------------------------------------------- | | **Editor (Development)** | `Debug` | See what SDK is doing during integration | | **Development Build** | `Info` | Track SDK events without overwhelming detail | | **QA/Testing Build** | `Warn` | Catch warnings before release | | **Staging/Beta** | `Error` | Production-like with error visibility | | **Release/Production** | `Off` or `Error` | Minimize logs for performance and cleanliness | *** ## Common Patterns ### Build-specific logging ```csharp theme={null} void SetAppropriateLogLevel() { #if UNITY_EDITOR // Verbose in editor LckCore.SetLogLevel(LevelFilter.Debug); #elif DEVELOPMENT_BUILD // Important events only LckCore.SetLogLevel(LevelFilter.Info); #else // Errors only in production LckCore.SetLogLevel(LevelFilter.Error); #endif Debug.Log($"LCK log level: {GetCurrentLogLevel()}"); } ``` ### Runtime toggle (debug menu) ```csharp theme={null} public class DebugMenu : MonoBehaviour { private LevelFilter currentLevel = LevelFilter.Info; void OnGUI() { if (GUILayout.Button("Cycle Log Level")) { currentLevel = currentLevel switch { LevelFilter.Off => LevelFilter.Error, LevelFilter.Error => LevelFilter.Warn, LevelFilter.Warn => LevelFilter.Info, LevelFilter.Info => LevelFilter.Debug, LevelFilter.Debug => LevelFilter.Trace, _ => LevelFilter.Off }; LckCore.SetLogLevel(currentLevel); Debug.Log($"LCK log level: {currentLevel}"); } GUILayout.Label($"Current Level: {currentLevel}"); } } ``` ### Platform-specific ```csharp theme={null} void InitializeLCK() { // More verbose logging on desktop for debugging var logLevel = Application.platform switch { RuntimePlatform.WindowsEditor => LevelFilter.Debug, RuntimePlatform.OSXEditor => LevelFilter.Debug, RuntimePlatform.WindowsPlayer => LevelFilter.Warn, RuntimePlatform.OSXPlayer => LevelFilter.Warn, RuntimePlatform.Android => LevelFilter.Error, RuntimePlatform.IPhonePlayer => LevelFilter.Error, _ => LevelFilter.Info }; LckCore.SetLogLevel(logLevel); Debug.Log($"LCK logging: {logLevel} on {Application.platform}"); } ``` ### Temporary verbose logging for debugging ```csharp theme={null} void DebugRecordingIssue() { // Save current level var originalLevel = GetCurrentLogLevel(); // Temporarily enable trace logging LckCore.SetLogLevel(LevelFilter.Trace); // Perform problematic operation var result = lckService.StartRecording(); // Restore original level LckCore.SetLogLevel(originalLevel); } ``` *** ## Enum Values | Level | Value | Output | Performance Impact | | ------- | ----------------- | ------------------- | ------------------ | | `Off` | Silent | Nothing | None | | `Error` | Errors only | Critical failures | Minimal | | `Warn` | Errors + warnings | Potential problems | Minimal | | `Info` | Important events | SDK lifecycle | Low | | `Debug` | Detailed info | Internal operations | Moderate | | `Trace` | Everything | Frame-by-frame | High | *** ## API Reference ### Set Log Level ```csharp theme={null} void LckCore.SetLogLevel(LevelFilter level) ``` **Parameters:** * `level` — Desired logging verbosity **Example:** ```csharp theme={null} LckCore.SetLogLevel(LevelFilter.Debug); ``` *** ## Performance Considerations **Trace logging can impact performance** — especially at high framerates (60+ fps). Use only for debugging specific issues. **Impact by level:** * `Off`, `Error`, `Warn` — Negligible impact * `Info` — Very low impact (under 1% typical) * `Debug` — Low impact (1-3% typical) * `Trace` — Moderate impact (5-10% possible) **Best practice:** Ship production builds with `Error` or `Off`. *** ## Debugging Tips ### Capture logs to file ```csharp theme={null} void Start() { // Enable detailed logging for bug reports LckCore.SetLogLevel(LevelFilter.Debug); // Unity captures to log file automatically Debug.Log("Log location: " + Application.persistentDataPath); } ``` ### Conditional compilation ```csharp theme={null} #if DEBUG_LCK LckCore.SetLogLevel(LevelFilter.Trace); #else LckCore.SetLogLevel(LevelFilter.Error); #endif ``` Define `DEBUG_LCK` in Player Settings → Scripting Define Symbols when debugging. *** ## Best Practices **Set early** — Configure log level in `Awake()` before SDK initialization **Build-specific** — Use preprocessor directives for different builds **Ship clean** — Use `Error` or `Off` in production **Debug temporarily** — Increase verbosity when debugging, restore after **Don't leave Trace enabled** — High performance cost, huge log files *** ## Related * [LckCore](/api-reference/unity/classes/core/LckCore) — Where to set log level * [Debugging Guide](https://docs.liv.tv/api-reference/unity/structs/core/GameInfo#why-this-information-matters) — Troubleshooting SDK issues * [Performance Optimization](https://docs.liv.tv/api-reference/unity/structs/CameraTrackDescriptor#performance-tips) — Performance best practices # ILckAudioSource Source: https://docs.liv.tv/api-reference/unity/interfaces/ILckAudioSource API reference for the ILckAudioSource interface in the LIV Camera Kit (LCK) Unity SDK, defining the contract for custom audio sources that supply game audio streams for recording and streaming, with support for FMOD and Wwise middleware. ## Description `ILckAudioSource` defines the contract for custom audio sources in LCK. As of **v1.2.0**, developers can implement this interface to manually provide game audio to LCK. This is useful when integrating with third-party audio middleware (such as FMOD or Wwise) or when Unity's built-in audio pipeline does not represent the full mix you want to capture. Microphone audio is still collected separately by LCK and is **not** replaced by custom sources. *** ## Supplying Custom Audio When implementing `ILckAudioSource`, keep the following requirements in mind: * **Audio format**: stereo, interleaved floats, 48 kHz sample rate * **Placement**: attach your component next to either a Unity `AudioListener` or an `LckAudioMarker` * **Examples**: see built-in implementations such as `LckAudioListener`, FMOD variant, and Wwise variant This flexibility allows you to integrate external audio systems or customize exactly what LCK captures for streaming and recording. *** ## Usage ### Example: Supplying Audio from a Custom Source ```csharp theme={null} public class MyCustomAudioSource : MonoBehaviour, ILckAudioSource { public void GetAudioData(ILckAudioSource.AudioDataCallbackDelegate callback) { // Fill an AudioBuffer with your audio data here AudioBuffer buffer = MyAudioEngine.FetchAudioBuffer(); callback(buffer); } public void EnableCapture() { Debug.Log("Custom audio capture enabled."); } public void DisableCapture() { Debug.Log("Custom audio capture disabled."); } public bool IsCapturing() => true; // return whether actively supplying data } ``` Attach this component next to an `AudioListener` or `LckAudioMarker` to begin supplying custom game audio. *** ## Methods | Method | Returns | Description | | :-------------------------------------- | :------ | :----------------------------------------------------------------------------------------------------------- | | GetAudioData(AudioDataCallbackDelegate) | void | Called by LCK to request audio data. Supply an `AudioBuffer` with interleaved stereo float samples at 48kHz. | | EnableCapture() | void | Activates audio capture for this source. | | DisableCapture() | void | Deactivates audio capture for this source. | | IsCapturing() | bool | Returns whether this audio source is currently supplying audio data. | # ILckCamera Source: https://docs.liv.tv/api-reference/unity/interfaces/ILckCamera API reference for the ILckCamera interface in the LIV Camera Kit (LCK) Unity SDK, defining the contract for cameras that can be activated and deactivated with RenderTexture output targets in the capture system. ## Description `ILckCamera` defines the contract for cameras managed within LCK. It ensures every camera has a unique identifier (`CameraId`), can be activated or deactivated with a `RenderTexture` as its output target, and exposes its Unity `Camera` component for direct access. Within LCK, this interface allows the service ([`ILckService`](/api-reference/unity/ILckService)) and mediator components to register, switch, and control cameras in a consistent way, regardless of whether the implementation is the built-in [`LckCamera`](/api-reference/unity/LckCamera) or a custom one. *** ## Usage To use a camera with the system, implement `ILckCamera` in your own component. See [`LckCamera`](/api-reference/unity/LckCamera) for a reference implementation. *** ## Properties | Property | Type | Description | | :------- | :----- | :----------------------------------------- | | CameraId | string | Unique identifier for this camera instance | *** ## Methods | Method | Returns | Description | | :---------------------------- | :------ | :----------------------------------------------------------------- | | ActivateCamera(RenderTexture) | void | Activates the camera and sets its render target. | | DeactivateCamera() | void | Deactivates the camera and clears its render target. | | GetCameraComponent() | Camera | Returns the underlying Unity `Camera` component for direct access. | # ILckMonitor Source: https://docs.liv.tv/api-reference/unity/interfaces/ILckMonitor API reference for the ILckMonitor interface in the LIV Camera Kit (LCK) Unity SDK, defining the contract for monitor components that act as render targets for camera output via MonitorId and SetRenderTexture. ## Description `ILckMonitor` is an interface representing a monitor endpoint within LCK. Monitors act as render targets for camera outputs, identified by a unique `MonitorId`. Any class implementing this interface can be registered with the LCK system to receive video frames via a `RenderTexture`. *** ## Usage To implement a custom monitor, create a class that implements `ILckMonitor` and register it with the system. See [`LckMonitor`](/api-reference/unity/LckMonitor) for a reference implementation. *** ## Properties | Property | Type | Description | | :-------- | :----- | :------------------------------------------ | | MonitorId | string | Unique identifier for the monitor instance. | *** ## Methods | Method | Returns | Description | | :------------------------------ | :------ | :--------------------------------------------------------------- | | SetRenderTexture(RenderTexture) | void | Assigns a `RenderTexture` as the output target for this monitor. | # ILckService Source: https://docs.liv.tv/api-reference/unity/interfaces/ILckService API reference for the ILckService interface in the LIV Camera Kit (LCK) Unity SDK, defining the public API for controlling capture, recording, streaming, audio, camera management, and lifecycle events. ## Description `ILckService` defines the contract for interacting with the capture system. It exposes events for lifecycle notifications (recording, streaming, saving, etc.), and methods for managing capture sessions, configuring video/audio parameters, and controlling cameras and audio. *** ## Usage Retrieve an `ILckService` instance through dependency injection or the DI container: ```csharp theme={null} [InjectLck] private ILckService _lckService; ``` ### Example: Start a Recording ```csharp theme={null} _lckService.OnRecordingStarted += result => { if (result.Success) Debug.Log("Recording started successfully!"); else Debug.LogError($"Failed to start recording: {result.ErrorMessage}"); }; var startResult = _lckService.StartRecording(); if (!startResult.Success) Debug.LogError($"Error: {startResult.ErrorMessage}"); ``` ### Example: Switch Capture Settings ```csharp theme={null} // Change resolution before capturing var resolution = new CameraResolutionDescriptor(1920, 1080); var result = _lckService.SetTrackResolution(resolution); if (!result.Success) Debug.LogError($"Failed to set resolution: {result.ErrorMessage}"); ``` ### Example: Control Audio ```csharp theme={null} // Enable microphone capture _lckService.SetMicrophoneCaptureActive(true); // Adjust microphone gain _lckService.SetMicrophoneGain(1.5f); // Check microphone level var micLevel = _lckService.GetMicrophoneOutputLevel(); Debug.Log($"Mic Output Level: {micLevel.Result}"); ``` *** ## Events | Event | Type | Description | | :----------------- | :--------------------------------------------------------------------- | :--------------------------------------- | | OnRecordingStarted | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when recording starts. | | OnRecordingPaused | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when recording is paused. | | OnRecordingResumed | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when recording resumes. | | OnRecordingStopped | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when recording stops. | | OnStreamingStarted | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when streaming starts. | | OnStreamingStopped | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when streaming stops. | | OnLowStorageSpace | Action\<[`LckResult`](/api-reference/unity/LckResult)> | Invoked when low storage is detected. | | OnRecordingSaved | Action\<[`LckResult`](/api-reference/unity/LckResult)\> | Invoked when a recording has been saved. | *** ## Methods | Method | Returns | Description | | :-------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | :-------------------------------------------------- | | GetRecordingDuration() | [`LckResult`](/api-reference/unity/LckResult)\ | Gets the duration of the current recording. | | GetStreamDuration() | [`LckResult`](/api-reference/unity/LckResult)\ | Gets the duration of the current stream. | | StartRecording() | [`LckResult`](/api-reference/unity/LckResult) | Starts recording. | | PauseRecording() | [`LckResult`](/api-reference/unity/LckResult) | Pauses recording. | | ResumeRecording() | [`LckResult`](/api-reference/unity/LckResult) | Resumes recording. | | StopRecording() | [`LckResult`](/api-reference/unity/LckResult) | Stops recording. | | StartStreaming() | [`LckResult`](/api-reference/unity/LckResult) | Starts streaming. | | StopStreaming() | [`LckResult`](/api-reference/unity/LckResult) | Stops streaming. | | SetTrackFramerate(uint) | [`LckResult`](/api-reference/unity/LckResult) | Sets the capture framerate. | | SetTrackDescriptor(CameraTrackDescriptor) | [`LckResult`](/api-reference/unity/LckResult) | Sets the track descriptor for capture. | | SetTrackDescriptor(LckCaptureType, CameraTrackDescriptor) | [`LckResult`](/api-reference/unity/LckResult) | Sets the track descriptor for a given capture type. | | SetTrackResolution(CameraResolutionDescriptor) | [`LckResult`](/api-reference/unity/LckResult) | Sets the capture resolution. | | SetTrackBitrate(uint) | [`LckResult`](/api-reference/unity/LckResult) | Sets the video bitrate. | | SetTrackAudioBitrate(uint) | [`LckResult`](/api-reference/unity/LckResult) | Sets the audio bitrate. | | SetCameraOrientation(LckCameraOrientation) | [`LckResult`](/api-reference/unity/LckResult) | Sets the camera orientation. | | GetActiveCaptureType() | [`LckResult`](/api-reference/unity/LckResult)\ | Gets the currently active capture type. | | SetActiveCaptureType(LckCaptureType) | [`LckResult`](/api-reference/unity/LckResult) | Sets the active capture type. | | SetPreviewActive(bool) | [`LckResult`](/api-reference/unity/LckResult) | Enables or disables preview rendering. | | IsRecording() | [`LckResult`](/api-reference/unity/LckResult)\ | Returns whether recording is active. | | IsStreaming() | [`LckResult`](/api-reference/unity/LckResult)\ | Returns whether streaming is active. | | IsCapturing() | [`LckResult`](/api-reference/unity/LckResult)\ | Returns whether capture is active. | | SetGameAudioCaptureActive(bool) | [`LckResult`](/api-reference/unity/LckResult) | Enables/disables game audio capture. | | SetMicrophoneCaptureActive(bool) | [`LckResult`](/api-reference/unity/LckResult) | Enables/disables microphone capture. | | GetMicrophoneOutputLevel() | [`LckResult`](/api-reference/unity/LckResult)\ | Gets the current microphone output level. | | SetMicrophoneGain(float) | [`LckResult`](/api-reference/unity/LckResult) | Sets microphone gain. | | SetGameAudioGain(float) | [`LckResult`](/api-reference/unity/LckResult) | Sets game audio gain. | | GetGameOutputLevel() | [`LckResult`](/api-reference/unity/LckResult)\ | Gets the current game audio output level. | | IsGameAudioMute() | [`LckResult`](/api-reference/unity/LckResult)\ | Returns whether game audio is muted. | | SetActiveCamera(string, string monitorId = null) | [`LckResult`](/api-reference/unity/LckResult) | Sets the active camera (and optional monitor). | | PreloadDiscreetAudio(AudioClip, float, bool) | [`LckResult`](/api-reference/unity/LckResult) | Preloads an audio clip for discreet playback. | | PlayDiscreetAudioClip(AudioClip) | [`LckResult`](/api-reference/unity/LckResult) | Plays a discreet audio clip. | | StopAllDiscreetAudio() | [`LckResult`](/api-reference/unity/LckResult) | Stops all discreet audio playback. | | GetDescriptor() | [`LckResult`](/api-reference/unity/LckResult)\<[`LckDescriptor`](/api-reference/unity/LckDescriptor)> | Gets the current descriptor. | | CapturePhoto() | [`LckResult`](/api-reference/unity/LckResult) | Captures a photo. | | Dispose() | void | Cleans up and disposes the service. | # ILckCore Source: https://docs.liv.tv/api-reference/unity/interfaces/core/ILckCore Interface for user authentication, subscription checks, and streaming configuration validation in custom UX flows. ## What Problem Does This Solve? LCK includes default UI (tablets) for user login and streaming setup, but you might want to build your own custom interface that matches your game's style. You need a way to check if users are logged in, subscribed, and have configured streaming—without using the default tablet UI. `ILckCore` provides the core authentication and configuration APIs so you can build custom login flows and subscription gates. ## When to Use This Use `ILckCore` when: * Building a custom login UI instead of using LCK tablets * Implementing subscription-gated features * Checking if streaming is configured before showing recording options * Creating a branded authentication experience **Don't use this if:** You're happy with the default tablet UI—just use that instead. *** ## Quick Example ```csharp theme={null} using LCK.Core; public class CustomLoginUI : MonoBehaviour { [InjectLck] private ILckCore _lckCore; public async void OnLoginButtonPressed() { // Start login and get short code var loginResult = await _lckCore.StartLoginAttemptAsync(); if (!loginResult.IsOk) { ShowError($"Login failed: {loginResult.Message}"); return; } string loginCode = loginResult.Ok; ShowLoginCode(loginCode); // Display "Enter code: ABC123" // Poll for completion bool loggedIn = await WaitForLoginCompletion(); if (loggedIn) { ShowSuccess("Login successful!"); CheckSubscription(); } } async Task WaitForLoginCompletion() { for (int i = 0; i < 60; i++) // 60 seconds timeout { await Task.Delay(1000); var result = await _lckCore.CheckLoginCompletedAsync(); if (result.IsOk && result.Ok) return true; } return false; } async void CheckSubscription() { var subResult = await _lckCore.IsUserSubscribed(); if (subResult.IsOk && subResult.Ok) { EnablePremiumFeatures(); } else { ShowSubscriptionPrompt(); } } } ``` *** ## How It Works ### Login Flow 1. **Start login** → Get a short code (e.g., "ABC123") 2. **Display code** → User enters it on a website 3. **Poll for completion** → Check if login succeeded 4. **Handle result** → Proceed with authenticated features ```csharp theme={null} // Step 1: Get login code var codeResult = await _lckCore.StartLoginAttemptAsync(); string code = codeResult.Ok; // "ABC123" // Step 2: User visits login URL and enters code ShowMessage($"Go to liv.tv/login and enter: {code}"); // Step 3: Poll until complete while (true) { await Task.Delay(2000); // Check every 2 seconds var loginCheck = await _lckCore.CheckLoginCompletedAsync(); if (loginCheck.IsOk && loginCheck.Ok) { Debug.Log("Login successful!"); break; } } ``` *** ## Common Patterns ### Complete custom login flow ```csharp theme={null} public class CustomAuthManager : MonoBehaviour { [InjectLck] private ILckCore _lckCore; [SerializeField] private TMP_Text loginCodeText; [SerializeField] private GameObject loginPanel; [SerializeField] private GameObject loadingIndicator; public async void StartCustomLogin() { loginPanel.SetActive(true); loadingIndicator.SetActive(true); // Request login code var result = await _lckCore.StartLoginAttemptAsync(); loadingIndicator.SetActive(false); if (!result.IsOk) { HandleLoginError(result.Err, result.Message); return; } // Display code to user string loginCode = result.Ok; loginCodeText.text = $"Enter this code at liv.tv/login:\n\n{loginCode}"; // Wait for completion await PollForLoginCompletion(); } async Task PollForLoginCompletion() { const int maxAttempts = 60; // 2 minutes (2s intervals) for (int attempt = 0; attempt < maxAttempts; attempt++) { await Task.Delay(2000); var checkResult = await _lckCore.CheckLoginCompletedAsync(); if (checkResult.IsOk && checkResult.Ok) { OnLoginSuccess(); return; } } // Timeout ShowError("Login timed out. Please try again."); loginPanel.SetActive(false); } void OnLoginSuccess() { loginPanel.SetActive(false); ShowSuccess("Login successful!"); // Proceed to next step CheckUserStatus(); } async void CheckUserStatus() { // Check subscription var subResult = await _lckCore.IsUserSubscribed(); bool isSubscribed = subResult.IsOk && subResult.Ok; // Check streaming config var streamResult = await _lckCore.HasUserConfiguredStreaming(); bool hasStreaming = streamResult.IsOk && streamResult.Ok; UpdateUI(isSubscribed, hasStreaming); } void HandleLoginError(CoreError error, string message) { string errorText = error switch { CoreError.UserNotLoggedIn => "Login required. Please try again.", CoreError.InvalidArgument => "Invalid login request.", CoreError.InternalError => "Server error. Please try again later.", _ => $"Login failed: {message}" }; ShowError(errorText); } } ``` ### Subscription gate for premium features ```csharp theme={null} public class PremiumFeatureGate : MonoBehaviour { [InjectLck] private ILckCore _lckCore; public async void OnPremiumButtonClicked() { var subResult = await _lckCore.IsUserSubscribed(); if (!subResult.IsOk) { ShowError("Could not verify subscription"); return; } if (subResult.Ok) { // User is subscribed UnlockPremiumFeature(); } else { // User not subscribed ShowSubscriptionUpsell(); } } void ShowSubscriptionUpsell() { // Show subscription benefits and purchase flow upsellPanel.SetActive(true); } } ``` ### Check streaming configuration before enabling streaming ```csharp theme={null} public class StreamingSetupChecker : MonoBehaviour { [InjectLck] private ILckCore _lckCore; [SerializeField] private Button streamButton; [SerializeField] private GameObject setupPrompt; async void Start() { await CheckStreamingStatus(); } async Task CheckStreamingStatus() { var result = await _lckCore.HasUserConfiguredStreaming(); if (result.IsOk && result.Ok) { // Streaming configured, enable button streamButton.interactable = true; setupPrompt.SetActive(false); } else { // Not configured, show setup prompt streamButton.interactable = false; setupPrompt.SetActive(true); } } public void OnSetupClicked() { // Open streaming configuration UI // (Default tablet or custom implementation) } } ``` ### Conditional feature availability ```csharp theme={null} public class FeatureManager : MonoBehaviour { [InjectLck] private ILckCore _lckCore; [SerializeField] private GameObject recordingFeature; [SerializeField] private GameObject streamingFeature; [SerializeField] private GameObject premiumFeature; async void Start() { await InitializeFeatures(); } async Task InitializeFeatures() { // Everyone can record recordingFeature.SetActive(true); // Streaming requires configuration var streamConfig = await _lckCore.HasUserConfiguredStreaming(); streamingFeature.SetActive(streamConfig.IsOk && streamConfig.Ok); // Premium features require subscription var subscription = await _lckCore.IsUserSubscribed(); premiumFeature.SetActive(subscription.IsOk && subscription.Ok); } } ``` *** ## Dependency Injection `ILckCore` is injected using the `[InjectLck]` attribute: ```csharp theme={null} using LCK.Core; public class MyCustomUI : MonoBehaviour { [InjectLck] private ILckCore _lckCore; void Start() { if (_lckCore == null) { Debug.LogError("ILckCore not injected!"); return; } // Use _lckCore... } } ``` Make sure LCK is properly initialized before using injected services. Call `LckCore.Initialize()` first. *** ## API Reference ### StartLoginAttemptAsync() Initiates the login process and returns a short code for the user to enter on the login website. ```csharp theme={null} Task> StartLoginAttemptAsync() ``` **Returns:** `Result` containing the login code (e.g., "ABC123") **Example:** ```csharp theme={null} var result = await _lckCore.StartLoginAttemptAsync(); if (result.IsOk) { Debug.Log($"Login code: {result.Ok}"); ShowCodeToUser(result.Ok); } ``` *** ### CheckLoginCompletedAsync() Checks whether the user has completed the login process on the website. ```csharp theme={null} Task> CheckLoginCompletedAsync() ``` **Returns:** `Result` — `true` if login completed, `false` if still pending **Example:** ```csharp theme={null} var result = await _lckCore.CheckLoginCompletedAsync(); if (result.IsOk && result.Ok) { Debug.Log("User logged in!"); } ``` **Usage pattern:** Poll this method every 1-2 seconds after starting login. *** ### IsUserSubscribed() Checks if the logged-in user has an active subscription. ```csharp theme={null} Task> IsUserSubscribed() ``` **Returns:** `Result` — `true` if user has active subscription **Example:** ```csharp theme={null} var result = await _lckCore.IsUserSubscribed(); if (result.IsOk && result.Ok) { EnablePremiumFeatures(); } else { ShowUpgradePrompt(); } ``` *** ### HasUserConfiguredStreaming() Checks if the user has configured their streaming setup (platforms, keys, etc.). ```csharp theme={null} Task> HasUserConfiguredStreaming() ``` **Returns:** `Result` — `true` if streaming is configured **Example:** ```csharp theme={null} var result = await _lckCore.HasUserConfiguredStreaming(); if (result.IsOk && result.Ok) { ShowStreamingOptions(); } else { ShowStreamingSetupPrompt(); } ``` *** ## Login Flow Best Practices **Display code clearly** — Use large, readable font for login code **Show instructions** — Tell users where to enter the code (liv.tv/login) **Poll every 2 seconds** — Balance responsiveness vs. server load **Set timeout** — Don't poll forever (60-120 seconds reasonable) **Handle errors** — Network issues, expired codes, etc. ### Good polling pattern ```csharp theme={null} async Task WaitForLogin(int timeoutSeconds = 120) { int attempts = timeoutSeconds / 2; // Poll every 2 seconds for (int i = 0; i < attempts; i++) { await Task.Delay(2000); var result = await _lckCore.CheckLoginCompletedAsync(); if (!result.IsOk) { Debug.LogWarning($"Login check failed: {result.Message}"); continue; } if (result.Ok) return true; // Success! } return false; // Timeout } ``` *** ## Error Handling All methods return `Result` which can contain `CoreError`: ```csharp theme={null} var result = await _lckCore.StartLoginAttemptAsync(); if (!result.IsOk) { switch (result.Err) { case CoreError.InternalError: ShowError("Server error. Try again."); break; case CoreError.UserNotLoggedIn: ShowError("You must be logged in."); break; case CoreError.InvalidArgument: ShowError("Invalid request."); break; default: ShowError($"Error: {result.Message}"); break; } } ``` *** ## Custom Implementation Warning **`Do not implement your own ILckCore.`** This interface is for **consuming** core services, not creating custom implementations. Use the provided implementation via dependency injection. The LCK SDK provides the implementation—you just inject and use it. *** ## Testing Without Real Login For testing purposes, you can mock the interface: ```csharp theme={null} #if UNITY_EDITOR public class MockLckCore : ILckCore { public async Task> StartLoginAttemptAsync() { await Task.Delay(100); return Result.Ok("TEST123"); } public async Task> CheckLoginCompletedAsync() { await Task.Delay(100); return Result.Ok(true); // Always succeeds in test } public async Task> IsUserSubscribed() { await Task.Delay(100); return Result.Ok(true); // Test with subscription } public async Task> HasUserConfiguredStreaming() { await Task.Delay(100); return Result.Ok(false); // Test without streaming } } #endif ``` *** ## Related * [CoreError](/api-reference/unity/enums/CoreError) — Error codes returned by core methods * [LckCore](/api-reference/unity/classes/core/LckCore) — Core initialization and setup # API Reference Source: https://docs.liv.tv/api-reference/unity/introduction LIV Camera Kit (LCK) Unity scripting API reference. Classes, interfaces, structs, and enums for controlling in-game recording, streaming, cameras, and audio. LCK provides a scripting API for building custom camera implementations, controlling recording and streaming programmatically, and integrating capture into your game's UI and logic. Access the API through the `LckService` singleton via dependency injection: ```csharp theme={null} [InjectLck] private ILckService _lckService; ``` ## Core components | Component | Purpose | | ------------------------------------------------------ | ------------------------------------------------------------------ | | [LckService](/api-reference/unity/classes) | Central entry point -- recording, streaming, audio, camera control | | [LckCamera](/api-reference/unity/classes) | Connects a Unity Camera to the LCK capture system | | [LckMonitor](/api-reference/unity/classes) | Provides RenderTexture output from the active capture | | [LCKCameraController](/api-reference/unity/classes) | Controls camera modes (selfie, FPV, TPV) and quality config | | [LckStreamingController](/api-reference/unity/classes) | Manages RTMP streaming lifecycle | ## API sections LckService, LckCamera, LckMonitor, LckQualityConfig, LckDescriptor, LckStreamingController, LckNotificationController, LckOnScreenUIController, LCKCameraController, LckCore, Result ILckService, ILckMonitor, ILckCamera, ILckAudioSource, ILckCore QualityOption, QualityOptionOverride, CameraTrackDescriptor, CameraResolutionDescriptor, GameInfo LckError, LckCaptureType, LckCameraOrientation, CoreError, LevelFilter # CameraResolutionDescriptor Source: https://docs.liv.tv/api-reference/unity/structs/CameraResolutionDescriptor Specify output video resolution in pixels (width × height) for video recording and streaming. ## What Problem Does This Solve? Video capture needs to know the output dimensions: 1920×1080, 1280×720, etc. This simple struct defines the width and height in pixels. `CameraResolutionDescriptor` is used inside `CameraTrackDescriptor` to specify resolution for recording and streaming tracks. ## When to Use This You'll use this whenever defining: * Video output resolution in quality presets * Custom camera configurations * Resolution-specific settings It's a basic building block—you'll create these frequently but they're simple. *** ## Quick Example ```csharp theme={null} // Full HD 1080p var fullHD = new CameraResolutionDescriptor(1920, 1080); // HD 720p var hd = new CameraResolutionDescriptor(1280, 720); // 4K UHD var uhd = new CameraResolutionDescriptor(3840, 2160); // Use in a track descriptor var track = new CameraTrackDescriptor(fullHD, bitrate: 8 << 20, framerate: 60); ``` *** ## Common Resolutions | Name | Width | Height | Aspect Ratio | Use Case | | ------------------- | ----- | ------ | ------------ | ------------------------------------------- | | **720p (HD)** | 1280 | 720 | 16:9 | Mobile, streaming, low-end hardware | | **1080p (Full HD)** | 1920 | 1080 | 16:9 | Standard quality, most common | | **1440p (2K/QHD)** | 2560 | 1440 | 16:9 | High quality, gaming monitors | | **4K (UHD)** | 3840 | 2160 | 16:9 | Premium quality, requires powerful hardware | | **Square** | 1080 | 1080 | 1:1 | Social media (Instagram, TikTok) | | **Vertical** | 1080 | 1920 | 9:16 | Mobile-first, stories, reels | *** ## Default Value If you create a `CameraResolutionDescriptor` with no parameters, you get a 512×512 square: ```csharp theme={null} var defaultRes = new CameraResolutionDescriptor(); // width: 512, height: 512 ``` This is rarely useful—always specify your target resolution explicitly. *** ## Common Patterns ### Standard 16:9 resolutions ```csharp theme={null} var resolutions = new[] { new CameraResolutionDescriptor(1280, 720), // 720p new CameraResolutionDescriptor(1920, 1080), // 1080p new CameraResolutionDescriptor(2560, 1440), // 1440p new CameraResolutionDescriptor(3840, 2160) // 4K }; ``` ### Social media-optimized ```csharp theme={null} // Instagram/TikTok square var square = new CameraResolutionDescriptor(1080, 1080); // Stories/Reels vertical var vertical = new CameraResolutionDescriptor(1080, 1920); // YouTube landscape var youtube = new CameraResolutionDescriptor(1920, 1080); ``` ### Performance tiers ```csharp theme={null} // Low-end devices var low = new CameraResolutionDescriptor(854, 480); // 480p // Mid-range devices var medium = new CameraResolutionDescriptor(1280, 720); // 720p // High-end devices var high = new CameraResolutionDescriptor(1920, 1080); // 1080p ``` *** ## Aspect Ratio Considerations Always match your source content aspect ratio. Recording 4:3 gameplay to 16:9 video will add black bars. Common aspect ratios: * **16:9** — Standard widescreen (1920×1080, 1280×720) * **21:9** — Ultrawide (2560×1080) * **4:3** — Legacy (1024×768) * **1:1** — Square (1080×1080) * **9:16** — Vertical mobile (1080×1920) Calculate aspect ratio: `width / height` * 1920 / 1080 = 1.777... ≈ 16:9 * 1080 / 1080 = 1.0 = 1:1 *** ## Performance Impact Higher resolutions = more pixels = more processing power required: | Resolution | Pixels | Relative Cost | | ---------- | --------- | ------------- | | 720p | 921,600 | 1× (baseline) | | 1080p | 2,073,600 | 2.25× | | 1440p | 3,686,400 | 4× | | 4K | 8,294,400 | 9× | Doubling width and height quadruples the pixel count. 4K has 4× the pixels of 1080p, not 2×. *** ## API Reference ### Constructor ```csharp theme={null} public CameraResolutionDescriptor( uint width = 512, uint height = 512 ) ``` #### Parameters * `width` — Horizontal resolution in pixels (default: 512) * `height` — Vertical resolution in pixels (default: 512) ### Fields | Field | Type | Description | | -------- | ------ | ------------------------------- | | `Width` | `uint` | Horizontal resolution in pixels | | `Height` | `uint` | Vertical resolution in pixels | *** ## Best Practices **Match display resolution** — Don't record 4K from a 1080p display **Consider target platform** — Mobile users rarely need >1080p **Test performance** — Higher resolution = higher CPU/GPU load **Even numbers preferred** — Some encoders require width/height divisible by 2 or 8 *** ## Related * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — Uses resolution with bitrate/framerate * [QualityOption](/api-reference/unity/structs/QualityOption) — Quality presets using track descriptors # CameraTrackDescriptor Source: https://docs.liv.tv/api-reference/unity/structs/CameraTrackDescriptor Configure video resolution, bitrate, framerate, and audio settings for recording and streaming tracks. ## What Problem Does This Solve? When capturing video, you need to specify technical parameters: resolution (1920×1080), bitrate (5 Mbps), framerate (30 fps), and audio bitrate (192 kbps). These settings determine video quality, file size, and performance impact. `CameraTrackDescriptor` bundles these parameters into a single struct that you pass to quality options and camera configurations. ## When to Use CameraTrackDescriptor You'll use this whenever configuring: * Quality presets in `QualityOption` * Recording vs. streaming settings (different bitrates) * Custom camera configurations * Device-specific overrides This is the fundamental building block for all quality configurations in LCK. *** ## Quick Example ```csharp theme={null} // 1080p60 recording at 10 Mbps var recordingTrack = new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 10 << 20, // 10 Mbps framerate: 60, audioBitrate: 256000 // 256 kbps ); // 1080p30 streaming at 5 Mbps var streamingTrack = new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 5 << 20, // 5 Mbps framerate: 30, audioBitrate: 192000 // 192 kbps ); ``` *** ## Understanding the Parameters ### Resolution The output video dimensions in pixels. Common values: * `1920×1080` — Full HD (1080p) * `1280×720` — HD (720p) * `2560×1440` — 2K/QHD * `3840×2160` — 4K/UHD ### Bitrate Controls video quality and file size. Higher = better quality, larger files. | Bitrate | Use Case | | -------------------- | ------------------------------- | | `3 << 20` (3 Mbps) | Low-quality streaming, mobile | | `5 << 20` (5 Mbps) | Standard streaming, 1080p30 | | `8 << 20` (8 Mbps) | High-quality recording, 1080p60 | | `15 << 20` (15 Mbps) | Very high quality, 1440p/4K | The `<< 20` syntax shifts bits to get megabits. `5 << 20` = 5,242,880 bits/sec ≈ 5 Mbps. ### Framerate Frames per second. Common values: * `30` — Standard for most content * `60` — Smooth motion, gaming, action * `120` — High-speed capture (requires powerful hardware) ### Audio Bitrate Audio quality in bits per second: * `128000` (128 kbps) — Acceptable for voice * `192000` (192 kbps) — Good quality, default * `256000` (256 kbps) — High quality music/ambience *** ## Default Values If you don't specify parameters, you get these defaults: ```csharp theme={null} var defaultTrack = new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080) // bitrate: 5 << 20 (5 Mbps) // framerate: 30 (30 fps) // audioBitrate: 192000 (192 kbps) ); ``` *** ## Common Patterns ### High-quality recording ```csharp theme={null} var recording = new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 12 << 20, // 12 Mbps framerate: 60, audioBitrate: 256000 ); ``` ### Bandwidth-constrained streaming ```csharp theme={null} var streaming = new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 3 << 20, // 3 Mbps framerate: 30, audioBitrate: 128000 ); ``` ### 4K recording ```csharp theme={null} var uhd = new CameraTrackDescriptor( new CameraResolutionDescriptor(3840, 2160), bitrate: 25 << 20, // 25 Mbps minimum for 4K framerate: 30, audioBitrate: 256000 ); ``` ### Mobile-optimized ```csharp theme={null} var mobile = new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 2 << 20, // 2 Mbps framerate: 30, audioBitrate: 128000 ); ``` *** ## Bitrate Guidelines by Resolution | Resolution | 30fps Min | 30fps Recommended | 60fps Recommended | | ---------- | --------- | ----------------- | ----------------- | | 720p | 2 Mbps | 3-4 Mbps | 5-6 Mbps | | 1080p | 3 Mbps | 5-8 Mbps | 10-12 Mbps | | 1440p | 6 Mbps | 10-15 Mbps | 18-25 Mbps | | 4K | 15 Mbps | 25-35 Mbps | 40-50 Mbps | Higher settings require more CPU/GPU power. Test on target devices to ensure smooth performance. *** ## API Reference ### Constructor ```csharp theme={null} public CameraTrackDescriptor( CameraResolutionDescriptor resolution, uint bitrate = 5242880, // 5 << 20 uint framerate = 30, uint audioBitrate = 192000 ) ``` #### Parameters * `resolution` — Output resolution (width × height) * `bitrate` — Video bitrate in bits per second (default: 5 Mbps) * `framerate` — Frames per second (default: 30) * `audioBitrate` — Audio bitrate in bits per second (default: 192 kbps) ### Fields | Field | Type | Description | | -------------- | ---------------------------- | -------------------------------- | | `Resolution` | `CameraResolutionDescriptor` | Video output resolution | | `Bitrate` | `uint` | Video bitrate in bits per second | | `Framerate` | `uint` | Video framerate (fps) | | `AudioBitrate` | `uint` | Audio bitrate in bits per second | *** ## Performance Tips **Start conservative** — Use 1080p30 at 5 Mbps as a baseline **Profile on device** — Desktop performance ≠ mobile performance **Match display framerate** — Recording at 60fps from a 30fps game wastes bandwidth **4K requires powerful hardware** — Test extensively before shipping *** ## Related * [CameraResolutionDescriptor](/api-reference/unity/structs/CameraResolutionDescriptor) — Define output resolution * [QualityOption](/api-reference/unity/structs/QualityOption) — Use tracks in quality presets * [QualityOptionOverride](/api-reference/unity/structs/QualityOptionOverride) — Device-specific settings # QualityOption Source: https://docs.liv.tv/api-reference/unity/structs/QualityOption Define named quality presets for video recording and streaming with different resolution, bitrate, and framerate settings. ## What Problem Does This Solve? When building apps with video capture, you need to offer users quality options that balance visual fidelity with performance and file size. Different use cases need different settings—recording gameplay for upload needs higher quality than live streaming, and mobile devices have different constraints than desktop. `QualityOption` lets you define named presets (like "High", "Medium", "Low") where each preset has separate configurations for recording vs. streaming. ## When to Use QualityOption Use `QualityOption` when: * You want to give users selectable quality presets (Low/Medium/High) * Recording and streaming need different settings (recording at higher quality, streaming at lower bitrate) * You're building a quality settings menu in your app * Different platforms need different quality tiers Skip this if you're hardcoding a single quality level for all users. *** ## Quick Example ```csharp theme={null} // Define a "High" quality preset var resolution = new CameraResolutionDescriptor(1920, 1080); var highQuality = new QualityOption( name: "High", isDefault: true, cameraTrackDescriptor: new CameraTrackDescriptor( resolution, bitrate: 8 << 20, // 8 Mbps for recording framerate: 60 ), streamingCameraTrackDescriptor: new CameraTrackDescriptor( resolution, bitrate: 5 << 20, // 5 Mbps for streaming framerate: 30 ) ); ``` This creates a "High" preset that records at 8 Mbps/60fps but streams at 5 Mbps/30fps. *** ## How It Works Each `QualityOption` contains: 1. **Name** — Display label shown to users ("High", "Medium", "Low") 2. **Recording settings** — Resolution, bitrate, framerate for saved videos 3. **Streaming settings** — Usually lower bitrate/framerate for live streaming 4. **Default flag** — Marks which option is selected by default You typically create multiple `QualityOption` instances and add them to a `LckQualityConfig` asset. *** ## Common Patterns ### Three-tier quality system ```csharp theme={null} var options = new List { new QualityOption("Low", false, new CameraTrackDescriptor(new CameraResolutionDescriptor(1280, 720), 3 << 20, 30), new CameraTrackDescriptor(new CameraResolutionDescriptor(1280, 720), 2 << 20, 30) ), new QualityOption("Medium", true, // default new CameraTrackDescriptor(new CameraResolutionDescriptor(1920, 1080), 5 << 20, 30), new CameraTrackDescriptor(new CameraResolutionDescriptor(1920, 1080), 3 << 20, 30) ), new QualityOption("High", false, new CameraTrackDescriptor(new CameraResolutionDescriptor(1920, 1080), 8 << 20, 60), new CameraTrackDescriptor(new CameraResolutionDescriptor(1920, 1080), 5 << 20, 30) ) }; ``` ### Record-only mode (no streaming) ```csharp theme={null} var recordTrack = new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), 10 << 20, 60 ); // Use same track for both recording and streaming var option = new QualityOption("Ultra", true, recordTrack, recordTrack); ``` *** ## API Reference ### Constructor ```csharp theme={null} public QualityOption( string name, bool isDefault, CameraTrackDescriptor cameraTrackDescriptor, CameraTrackDescriptor streamingCameraTrackDescriptor ) ``` #### Parameters * `name` — Display name shown in UI ("High", "Medium", "Low") * `isDefault` — Set `true` for the default selection * `cameraTrackDescriptor` — Settings for recording * `streamingCameraTrackDescriptor` — Settings for live streaming ### Fields | Field | Type | Description | | -------------------------------- | ----------------------- | ------------------------------------- | | `Name` | `string` | Display name of the quality option | | `IsDefault` | `bool` | Whether this is the default selection | | `RecordingCameraTrackDescriptor` | `CameraTrackDescriptor` | Track settings for recording | | `StreamingCameraTrackDescriptor` | `CameraTrackDescriptor` | Track settings for streaming | ### Obsolete Members **Deprecated:** `CameraTrackDescriptor` field is obsolete. Use `RecordingCameraTrackDescriptor` or `StreamingCameraTrackDescriptor` instead. *** ## Related * [CameraTrackDescriptor](/api-reference/unity/structs/CameraTrackDescriptor) — Define resolution, bitrate, framerate * [CameraResolutionDescriptor](/api-reference/unity/structs/CameraResolutionDescriptor) — Specify output resolution * [LckQualityConfig](/api-reference/unity/classes/LckQualityConfig) — Asset that contains quality options * [QualityOptionOverride](/api-reference/unity/structs/QualityOptionOverride) — Device-specific overrides # QualityOptionOverride Source: https://docs.liv.tv/api-reference/unity/structs/QualityOptionOverride Override quality settings for specific device models to optimize performance and quality on different hardware. ## What Problem Does This Solve? Different devices have vastly different hardware capabilities. A high-end gaming PC can handle 4K60 recording, but a mid-range Android phone might struggle with 1080p30. Your default quality options might be too aggressive for some devices or too conservative for others. `QualityOptionOverride` lets you define device-specific quality presets that automatically apply when the SDK detects specific hardware. ## When to Use QualityOptionOverride Use this when: * You have performance data showing certain devices need different settings * Supporting a wide range of hardware (flagship phones to budget tablets) * Users report performance issues on specific device models * You want to maximize quality on high-end devices without breaking low-end ones Skip this if you're targeting a single platform or device type. *** ## Quick Example ```csharp theme={null} // Lower quality for a specific device that struggles with defaults var phoneOverride = new QualityOptionOverride { DeviceModel = new DeviceModel("Samsung Galaxy A52"), QualityOptions = new List { new QualityOption( name: "Medium", isDefault: true, cameraTrackDescriptor: new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 4 << 20, // 4 Mbps framerate: 30, audioBitrate: 128000 ), streamingCameraTrackDescriptor: new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 3 << 20, // 3 Mbps framerate: 30, audioBitrate: 128000 ) ) } }; ``` When the SDK detects a Galaxy A52, it uses these settings instead of your base configuration. *** ## How It Works 1. You define a `DeviceModel` identifier (device name/model string) 2. Create a list of `QualityOption` instances tailored for that device 3. At runtime, the SDK checks the device model 4. If a match is found, it uses the override options instead of base config The override completely replaces the base quality options for that device—it doesn't merge them. *** ## Common Patterns ### Override for low-end mobile devices ```csharp theme={null} var budgetPhoneOverride = new QualityOptionOverride { DeviceModel = new DeviceModel("Generic_Android_Device"), QualityOptions = new List { new QualityOption("Low", true, new CameraTrackDescriptor( new CameraResolutionDescriptor(854, 480), bitrate: 2 << 20, framerate: 30 ), new CameraTrackDescriptor( new CameraResolutionDescriptor(854, 480), bitrate: 1 << 20, framerate: 30 ) ), new QualityOption("Medium", false, new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 3 << 20, framerate: 30 ), new CameraTrackDescriptor( new CameraResolutionDescriptor(1280, 720), bitrate: 2 << 20, framerate: 30 ) ) } }; ``` ### Boost quality on flagship devices ```csharp theme={null} var flagshipOverride = new QualityOptionOverride { DeviceModel = new DeviceModel("iPhone 15 Pro"), QualityOptions = new List { new QualityOption("High", true, new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 12 << 20, // Higher bitrate framerate: 60 ), new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 8 << 20, framerate: 60 // 60fps streaming ) ), new QualityOption("Ultra", false, new CameraTrackDescriptor( new CameraResolutionDescriptor(2560, 1440), bitrate: 20 << 20, framerate: 60 ), new CameraTrackDescriptor( new CameraResolutionDescriptor(1920, 1080), bitrate: 10 << 20, framerate: 60 ) ) } }; ``` *** ## Finding Device Model Strings Device model strings must match exactly. On Android, this is typically `Build.MODEL`. On iOS, it's the device identifier like "iPhone15,2". Log the device model at runtime to ensure you're using the correct string: ```csharp theme={null} #if UNITY_ANDROID string deviceModel = SystemInfo.deviceModel; #elif UNITY_IOS string deviceModel = UnityEngine.iOS.Device.generation.ToString(); #endif Debug.Log($"Device Model: {deviceModel}"); ``` *** ## API Reference ### Fields | Field | Type | Description | | ---------------- | --------------------- | ---------------------------------------------- | | `DeviceModel` | `DeviceModel` | The device identifier this override applies to | | `QualityOptions` | `List` | Quality options specific to this device | ### Usage in Config Overrides are typically added to a `LckQualityConfig` asset: ```csharp theme={null} var config = ScriptableObject.CreateInstance(); config.BaseQualityOptions = baseOptions; config.DeviceOverrides = new List { phoneOverride, flagshipOverride, budgetPhoneOverride }; ``` *** ## Best Practices **Test on actual devices** — Emulators don't reflect real performance **Monitor crash analytics** — Device-specific overrides often surface hidden issues **Start conservative** — Easier to increase quality later than debug performance problems **Exact model matching** — Typos in device model strings mean the override won't apply *** ## Related * [QualityOption](/api-reference/unity/structs/QualityOption) — Define quality presets * [LckQualityConfig](/api-reference/unity/classes/LckQualityConfig) — Quality configuration asset # GameInfo Source: https://docs.liv.tv/api-reference/unity/structs/core/GameInfo Provide game and engine metadata to LckCore during SDK initialization for compatibility tracking and analytics. ## What Problem Does This Solve? When your app initializes the LCK SDK, the system needs to know basic information about your game: name, version, what engine you're using, which render pipeline, etc. This helps with: * Compatibility reporting (which Unity versions work with which SDK versions) * Debugging (crash reports show engine version and graphics API) * Analytics (understanding which games/platforms use the SDK) `GameInfo` is a struct you populate and pass to `LckCore.Initialize()` during startup. ## When to Use This You create and pass `GameInfo` once during app initialization: ```csharp theme={null} void Start() { var gameInfo = new GameInfo { /* ... */ }; LckCore.Initialize("your-tracking-id", gameInfo); } ``` This is a required parameter for SDK initialization—you'll always use it. *** ## Quick Example ```csharp theme={null} using UnityEngine; void InitializeLCK() { var gameInfo = new GameInfo { GameName = "Space Blasters VR", GameVersion = "1.2.3", ProjectName = "SpaceBlasters", CompanyName = "Awesome Games Studio", EngineVersion = Application.unityVersion, RenderPipeline = "URP", GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() }; var result = LckCore.Initialize("tracking-id-12345", gameInfo); if (!result.IsOk) { Debug.LogError($"LCK initialization failed: {result.Message}"); return; } Debug.Log("LCK initialized successfully"); } ``` *** ## Field Details ### GameName The display name of your game as users see it. ```csharp theme={null} GameName = "Beat Saber Clone" ``` ### GameVersion Your app's version string. Use semantic versioning (major.minor.patch). ```csharp theme={null} GameVersion = "2.1.0" GameVersion = Application.version // Unity's version from Player Settings ``` ### ProjectName Internal project name (can match GameName if you prefer). ```csharp theme={null} ProjectName = "BeatSaberClone" ``` ### CompanyName Your studio or publisher name. ```csharp theme={null} CompanyName = "Indie VR Games" CompanyName = Application.companyName // From Unity Player Settings ``` ### EngineVersion The Unity (or Unreal) version you're building with. ```csharp theme={null} // Unity EngineVersion = Application.unityVersion // e.g., "2022.3.10f1" // Unreal (example) EngineVersion = "5.3.2" ``` ### RenderPipeline Which render pipeline your project uses. ```csharp theme={null} // Unity built-in RenderPipeline = "Built-in" // Universal Render Pipeline RenderPipeline = "URP" // High Definition Render Pipeline RenderPipeline = "HDRP" // Unreal RenderPipeline = "Deferred" ``` Detect Unity's render pipeline at runtime: ```csharp theme={null} #if UNITY_PIPELINE_URP RenderPipeline = "URP" #elif UNITY_PIPELINE_HDRP RenderPipeline = "HDRP" #else RenderPipeline = "Built-in" #endif ``` ### GraphicsAPI The graphics API currently in use. ```csharp theme={null} // Auto-detect in Unity GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() // Outputs: "Direct3D11", "Vulkan", "Metal", "OpenGLES3", etc. ``` *** ## Complete Example (Unity) ```csharp theme={null} using UnityEngine; public class LCKInitializer : MonoBehaviour { void Awake() { InitializeLCK(); } void InitializeLCK() { var gameInfo = new GameInfo { GameName = Application.productName, GameVersion = Application.version, ProjectName = Application.productName, CompanyName = Application.companyName, EngineVersion = Application.unityVersion, RenderPipeline = GetRenderPipeline(), GraphicsAPI = SystemInfo.graphicsDeviceType.ToString() }; var initResult = LckCore.Initialize( "your-tracking-id-here", gameInfo ); if (!initResult.IsOk) { Debug.LogError($"LCK failed to initialize: {initResult.Message}"); return; } Debug.Log($"LCK initialized for {gameInfo.GameName} v{gameInfo.GameVersion}"); } string GetRenderPipeline() { #if UNITY_PIPELINE_URP return "URP"; #elif UNITY_PIPELINE_HDRP return "HDRP"; #else return "Built-in"; #endif } } ``` *** ## Why This Information Matters **For debugging:** * Crash reports show engine version and graphics API * Easier to reproduce issues when you know exact configuration **For compatibility:** * SDK team can track which engine versions work/break * Helps prioritize which platforms to test **For analytics:** * Understand which games/studios use the SDK * Track adoption across Unity vs. Unreal, different pipelines *** ## API Reference ### Fields | Field | Type | Description | | ---------------- | -------- | -------------------------------------------- | | `GameName` | `string` | Display name of the game | | `GameVersion` | `string` | Version string (e.g., "1.0.0") | | `ProjectName` | `string` | Internal project name | | `CompanyName` | `string` | Studio or publisher name | | `EngineVersion` | `string` | Engine version (Unity/Unreal) | | `RenderPipeline` | `string` | Active render pipeline (URP, HDRP, Built-in) | | `GraphicsAPI` | `string` | Graphics API (DirectX, Vulkan, Metal, etc.) | ### Used By * `LckCore.Initialize(string trackingId, GameInfo gameInfo)` — Required parameter for SDK initialization *** ## Common Mistakes **Don't hardcode version strings** — Use `Application.version` or auto-detect to avoid mismatches. ```csharp theme={null} // Bad - will be wrong after version bump GameVersion = "1.0.0" // Good - always matches build GameVersion = Application.version ``` **Don't guess the render pipeline** — Use preprocessor directives or runtime detection. ```csharp theme={null} // Bad - might be wrong RenderPipeline = "URP" // Good - guaranteed correct #if UNITY_PIPELINE_URP RenderPipeline = "URP" #else RenderPipeline = "Built-in" #endif ``` *** ## Best Practices **Auto-detect everything** — Use Unity's `Application` and `SystemInfo` APIs **Initialize early** — Call in `Awake()` or `Start()` before using other LCK features **Log failures** — Always check `IsOk` and log `Message` if initialization fails **Use semantic versioning** — Format: `major.minor.patch` (e.g., "2.1.0") *** # Architecture (Unreal) Source: https://docs.liv.tv/api-reference/unreal/architecture Internal architecture of the LCK SDK for Unreal Engine - modules, subsystems, and data flow. ## What Problem Does This Solve? Understanding LCK's architecture helps you: * Debug issues by knowing which subsystem handles what * Extend the SDK with custom encoders or audio sources * Optimize performance by understanding the data flow * Build custom UI without breaking core functionality This page maps out how LCK's modules work together in Unreal Engine. ## When to Read This Read this when: * Integrating LCK for the first time * Building custom recording UI * Creating custom audio sources (FMOD, Wwise) * Debugging recording or encoding issues * Contributing to LCK development Skip this if you're just using the default tablet UI. *** ## High-Level Overview LCK is organized into modular runtime modules that load in specific phases: ``` LCKVulkan (EarliestPossible) ← Android Vulkan interop ↓ LCKCore (PostDefault) ← Recording subsystem, encoders, streaming interfaces ├── LCKAudio (PostDefault) ← Audio capture framework ├── LCKWindowsEncoder (PostDefault) ← Windows Media Foundation ├── LCKAndroidEncoder (PostDefault) ← Android MediaCodec └── LCKAndroidGallery (PostDefault) ← Save to Android gallery ↓ LCKTablet (Default) ← High-level service, tablet UI └── LCKUI (Default) ← 3D UI components, streaming state, tablet modes ↓ Optional Plugins (Default): ├── LCKStreaming ← RTMP live streaming (implements ILCKStreamingFeature) ├── LCKUnrealAudio ← Unreal Engine audio capture ├── LCKFMOD ← FMOD integration ├── LCKWwise ← Wwise integration ├── LCKVivox ← Vivox voice chat └── LCKOboe ← Android low-latency mic ``` **Key principle:** Lower modules (Core, Audio) know nothing about higher modules (Tablet, UI). This lets you build custom UI without modifying core functionality. *** ## Module Dependency Map This diagram shows which modules depend on which, and whether they are required or optional. ``` ┌──────────────────────────────────────────────────────────────────────┐ │ REQUIRED MODULES │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ │ │ │ LCKVulkan │ (EarliestPossible) Android only │ │ └──────┬──────┘ │ │ ↓ │ │ ┌─────────────┐ ┌────────────────────┐ ┌───────────────────┐ │ │ │ LCKCore │←────│ LCKWindowsEncoder │ │ LCKAndroidEncoder │ │ │ │ (PostDefault)│←────│ (PostDefault) │ │ (PostDefault) │ │ │ └──────┬──────┘ │ Win64 only │ │ Android only │ │ │ │ └────────────────────┘ └───────────────────┘ │ │ ↓ │ │ ┌─────────────┐ ┌────────────────────┐ │ │ │ LCKAudio │ │ LCKAndroidGallery │ │ │ │ (PostDefault)│ │ (PostDefault) │ │ │ │ ← LCKCore │ │ Android only │ │ │ └──────┬──────┘ │ ← LCKCore │ │ │ │ └────────────────────┘ │ │ ↓ │ │ ┌─────────────┐ ┌────────────────────┐ │ │ │ LCKTablet │────>│ LCKUI │ │ │ │ (Default) │ │ (Default) │ │ │ │ ← LCKCore │ │ ← LCKCore │ │ │ │ ← LCKUI │ └────────────────────┘ │ │ └─────────────┘ │ │ │ ├──────────────────────────────────────────────────────────────────────┤ │ OPTIONAL MODULES │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ LCKStreaming │ │ LCKUnrealAudio │ │ LCKFMOD │ │ │ │ (Default) │ │ (Default) │ │ (Default) │ │ │ │ ← LCKCore │ │ ← LCKCore │ │ ← LCKCore │ │ │ │ ← LCKAudio │ │ ← LCKAudio │ │ ← LCKAudio │ │ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │ │ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ LCKWwise │ │ LCKVivox │ │ LCKOboe │ │ │ │ (Default) │ │ (Default) │ │ (Default) │ │ │ │ ← LCKCore │ │ ← LCKCore │ │ ← LCKCore │ │ │ │ ← LCKAudio │ │ ← LCKAudio │ │ ← LCKAudio │ │ │ └────────────────┘ └────────────────┘ │ Android only │ │ │ └────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────┘ ``` ### Module Classification | Module | Type | Loading Phase | Depends On | Purpose | | --------------------- | --------------- | ------------------ | ----------------- | ---------------------------------------------------------- | | **LCKVulkan** | Platform (auto) | `EarliestPossible` | None | Android Vulkan interop | | **LCKCore** | Required | `PostDefault` | None | Recording subsystem, encoder factory, streaming interfaces | | **LCKAudio** | Required | `PostDefault` | LCKCore | Audio source interface, mixing | | **LCKWindowsEncoder** | Platform (auto) | `PostDefault` | LCKCore | Windows Media Foundation encoder | | **LCKAndroidEncoder** | Platform (auto) | `PostDefault` | LCKCore | Android MediaCodec encoder | | **LCKAndroidGallery** | Platform (auto) | `PostDefault` | LCKCore | Android gallery integration | | **LCKUI** | Required | `Default` | LCKCore | 3D UI components, streaming state enum, tablet modes | | **LCKTablet** | Required | `Default` | LCKCore, LCKUI | High-level service, tablet actor | | **LCKStreaming** | Optional | `Default` | LCKCore, LCKAudio | RTMP streaming via `ILCKStreamingFeature` | | **LCKUnrealAudio** | Optional | `Default` | LCKCore, LCKAudio | Unreal Engine audio capture | | **LCKFMOD** | Optional | `Default` | LCKCore, LCKAudio | FMOD audio capture | | **LCKWwise** | Optional | `Default` | LCKCore, LCKAudio | Wwise audio capture | | **LCKVivox** | Optional | `Default` | LCKCore, LCKAudio | Vivox voice chat capture | | **LCKOboe** | Optional | `Default` | LCKCore, LCKAudio | Android low-latency mic | ### Load Order Modules load in three phases, in this order: 1. **`EarliestPossible`** -- Before engine initialization. Only `LCKVulkan` loads here (Android only). This phase exists because Vulkan interop must be established before the RHI initializes. 2. **`PostDefault`** -- After engine init, before game modules. Core infrastructure loads here: `LCKCore`, `LCKAudio`, and all platform-specific encoder modules. These must be available before any game code tries to use the recording API. 3. **`Default`** -- Standard game module loading. All high-level and optional modules load here: `LCKTablet`, `LCKUI`, `LCKStreaming`, and all audio plugins. By this point, core infrastructure is guaranteed to be available. Platform-specific modules (`LCKVulkan`, `LCKWindowsEncoder`, `LCKAndroidEncoder`, `LCKAndroidGallery`) are auto-loaded by the engine based on the target platform. You never need to add them to your `.Build.cs`. ### Runtime Discovery Optional modules register themselves via Unreal's `IModularFeatures` system at startup. Core modules discover them at runtime without compile-time coupling: * **Audio sources** register as `ILCKAudioSource` modular features * **Encoder factories** register as `ILCKEncoderFactory` modular features * **Streaming backends** register as `ILCKStreamingFeature` modular features * **Packet sinks** are passed directly to encoders via `ILCKEncoder::AddPacketSink()` This means you can add or remove optional modules without recompiling core code. *** ## Platform Coverage Matrix | Module | Win64 | Android (Quest 2) | Android (Quest 3) | Linux | | --------------------- | :---: | :---------------: | :---------------: | :---: | | **LCKCore** | Yes | Yes | Yes | Yes | | **LCKAudio** | Yes | Yes | Yes | Yes | | **LCKTablet** | Yes | Yes | Yes | Yes | | **LCKUI** | Yes | Yes | Yes | Yes | | **LCKWindowsEncoder** | Yes | -- | -- | -- | | **LCKAndroidEncoder** | -- | Yes | Yes | -- | | **LCKAndroidGallery** | -- | Yes | Yes | -- | | **LCKVulkan** | -- | Yes | Yes | -- | | **LCKUnrealAudio** | Yes | Yes | Yes | Yes | | **LCKFMOD** | Yes | Yes | Yes | -- | | **LCKWwise** | Yes | Yes | Yes | -- | | **LCKVivox** | Yes | Yes | Yes | Yes | | **LCKOboe** | -- | Yes | Yes | -- | | **LCKStreaming** | Yes | Yes | Yes | -- | Linux support is limited to core, audio, UI, and Vivox modules. Encoding and streaming require Windows or Android. *** ## Subsystem Hierarchy LCK uses Unreal's subsystem architecture for lifetime management: ``` UWorld ├── ULCKRecorderSubsystem (TickableWorldSubsystem) │ └── ILCKEncoder (platform-specific) ├── ULCKSubsystem (WorldSubsystem) │ └── ULCKService (high-level API) └── ALCKTablet (Actor) └── ULCKTabletDataModel (state management) UGameInstance └── ULCKTelemetrySubsystem (GameInstanceSubsystem) ``` ### ULCKRecorderSubsystem **Type:** `UTickableWorldSubsystem` (ticks every frame)\ **Module:** LCKCore\ **Purpose:** Low-level recording control, frame capture, encoder lifecycle ```cpp theme={null} UCLASS() class LCKCORE_API ULCKRecorderSubsystem : public UTickableWorldSubsystem { // Recording control void SetupRecorder(const FLCKRecorderParams& Params, USceneCaptureComponent2D* Capture); bool StartRecording(); bool StopRecording(); void StartRecordingAsync(FOnLCKRecorderBoolResult Callback); void StopRecordingAsync(FOnLCKRecorderBoolResult Callback, FOnLCKRecorderProgress Progress); // Preview mode (camera without recording) void StartPreview(); void StopPreview(); // Photo capture bool TakePhoto(); // State queries bool IsRecording() const; float GetTime() const; float GetMicrophoneVolume() const; }; ``` **When to use:** Advanced scenarios where you need direct control over the encoder. Most developers should use `ULCKService` instead. *** ### ULCKSubsystem **Type:** `UWorldSubsystem`\ **Module:** LCKTablet\ **Purpose:** Provides access to `ULCKService` ```cpp theme={null} UCLASS() class LCKTABLET_API ULCKSubsystem : public UWorldSubsystem { public: UFUNCTION(BlueprintCallable, Category = "LCK") ULCKService* GetService(); }; ``` **When to use:** This is your entry point. Get the service, use its methods. ```cpp theme={null} // Access from C++ ULCKSubsystem* Subsystem = GetWorld()->GetSubsystem(); ULCKService* Service = Subsystem->GetService(); // Access from Blueprint ULCKService* Service = GetLCKService(); // Blueprint helper ``` *** ### ULCKTelemetrySubsystem **Type:** `UGameInstanceSubsystem`\ **Module:** LCKCore\ **Purpose:** Analytics and usage tracking ```cpp theme={null} UCLASS() class LCKCORE_API ULCKTelemetrySubsystem : public UGameInstanceSubsystem { public: void SendTelemetry(const FLCKTelemetryEvent& EventData); FString GetCurrentTrackingId() const; }; ``` Automatically tracks SDK events like recording start/stop, errors, quality changes. *** ## Encoder Architecture Encoders implement the `ILCKEncoder` interface and are created via modular features: ```cpp theme={null} class ILCKEncoder : public TSharedFromThis, public FRunnable { public: virtual bool Open() noexcept = 0; virtual bool IsEncoding() const noexcept = 0; virtual void EncodeTexture(FTextureRHIRef& Texture, float TimeSeconds) = 0; virtual void EncodeAudio(TArrayView PCMData) = 0; virtual void Save(TFunction ProgressCallback) = 0; [[nodiscard]] virtual float GetAudioTime() const noexcept = 0; // v1.0: Packet sink support for streaming virtual void SetRecordToDisk(bool bRecord) { bRecordToDisk = bRecord; } virtual void AddPacketSink(ILCKPacketSink* Sink) {} virtual void RemovePacketSink(ILCKPacketSink* Sink) {} }; ``` Encoders now support dual output: they can write to disk (MP4 file) and simultaneously route encoded packets to one or more `ILCKPacketSink` implementations (for RTMP streaming or other transports). Use `SetRecordToDisk(false)` for stream-only mode. ### Platform Implementations | Platform | Encoder | Technologies | Features | | ----------- | -------------------- | ------------------------------------ | ------------------------------------- | | **Windows** | `FLCKWindowsEncoder` | Windows Media Foundation, DirectX 11 | H.264, AAC, MP4, Hardware-accelerated | | **Android** | `FLCKAndroidEncoder` | NDK MediaCodec, Vulkan/EGL | H.264, AAC, MP4, Hardware-accelerated | #### Windows Encoder * Uses `IMFSinkWriter` for muxing * Uses `IMFTransform` for H.264 encoding * Triple-buffered texture pool to avoid GPU stalls * Direct3D 11 texture interop #### Android Encoder * Uses `AMediaCodec` for H.264/AAC encoding * Uses `AMediaMuxer` for MP4 container * Vulkan texture export via EGL * Hardware-accelerated on Quest devices ### Encoder Factory Encoders are discovered and created via Unreal's modular features system: ```cpp theme={null} class ILCKEncoderFactory : public IModularFeature, public TSharedFromThis { public: static FName GetModularFeatureName() noexcept; virtual const FString& GetEncoderName() const noexcept = 0; virtual TSharedPtr CreateEncoder( uint32 Width, uint32 Height, uint32 VideoBitrate, uint32 Framerate, uint32 Samplerate, uint32 AudioBitrate) const noexcept = 0; }; ``` **How to find an encoder:** ```cpp theme={null} auto& ModularFeatures = IModularFeatures::Get(); if (ModularFeatures.IsModularFeatureAvailable(ILCKEncoderFactory::GetModularFeatureName())) { ILCKEncoderFactory* Factory = &ModularFeatures.GetModularFeature( ILCKEncoderFactory::GetModularFeatureName() ); TSharedPtr Encoder = Factory->CreateEncoder( 1920, 1080, 12000000, 60, 48000, 256000 ); } ``` *** ## Audio Architecture Audio sources also use modular features for extensibility: ```cpp theme={null} class ILCKAudioSource : public IModularFeature, public TSharedFromThis { public: static FName GetModularFeatureName() noexcept; // v1.0: Multicast delegate with 4 params (added SampleRate) DECLARE_MULTICAST_DELEGATE_FourParams(FDelegateRenderAudio, TArrayView/*PCM*/, int32/*Channels*/, int32/*SampleRate*/, ELCKAudioChannel/*SourceChannel*/); typedef FDelegateRenderAudio::FDelegate FOnRenderAudioDelegate; FOnRenderAudioDelegate OnAudioDataDelegate; // Control virtual bool StartCapture() noexcept = 0; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept = 0; virtual void StopCapture() noexcept = 0; // Query virtual float GetVolume() const noexcept = 0; virtual const FString& GetSourceName() const noexcept = 0; TLCKAudioChannelsMask GetSupportedChannels() const noexcept; }; ``` In v1.0, `FDelegateRenderAudio` is the multicast parent delegate. `FOnRenderAudioDelegate` is a typedef for its inner `FDelegate` type. The delegate now includes `SampleRate` as a third parameter. ### Audio Mixing Multiple audio sources are combined via `FLCKAudioMix`: ```cpp theme={null} class FLCKAudioMix { public: void AddSource(TWeakPtr AudioSource) noexcept; // Get mixed stereo audio for specified channels TArray StereoMix(TLCKAudioChannelsMask Channels); }; ``` **Example: Game audio + microphone:** ```cpp theme={null} FLCKAudioMix Mixer; // Add game audio source TSharedPtr GameAudio = FindGameAudioSource(); Mixer.AddSource(GameAudio); // Add microphone TSharedPtr Mic = FindMicrophoneSource(); Mixer.AddSource(Mic); // Get mixed stereo output TArray MixedAudio = Mixer.StereoMix( ELCKAudioChannel::Game | ELCKAudioChannel::Microphone ); ``` *** ## Data Flow ``` ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ Scene Capture │────>│ Render Target │────>│ Texture Pool │ │ Component │ │ (RenderTarget2D) │ │ (3 buffers) │ └─────────────────────┘ └─────────────────────┘ └──────────┬──────────┘ │ v ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ MP4 File │<────│ Platform Encoder │<────│ GPU Readback │ │ (Movies folder) │ │ (Windows/Android) │ │ (RHI Command) │ └─────────────────────┘ └──────────┬──────────┘ └─────────────────────┘ │ ↑ Packet │ │ Sinks │ │ v │ ┌─────────────────────┐ ┌─────────────────────┐ │ RTMP Stream │<────│ ILCKPacketSink │ │ (LCKStreaming) │ │ (encoded H.264/AAC)│ └─────────────────────┘ └─────────────────────┘ ↑ │ ┌─────────────────────┐ ┌─────────────────────┐ │ Audio Sources │────>│ Audio Mixer │ │ (Game, Mic, Vivox) │ │ (Combine channels) │ └─────────────────────┘ └─────────────────────┘ ``` The encoder can output to both disk (MP4 file) and packet sinks (RTMP streaming) simultaneously. When streaming without recording, set `SetRecordToDisk(false)` on the encoder. ### Triple-Buffered Texture Pool Encoder uses triple-buffering to prevent GPU stalls: ```cpp theme={null} class FTexturePool { static constexpr int32 PoolSize = 3; TArray Textures; int32 CurrentIndex = 0; public: FTextureRHIRef GetNextTexture() { FTextureRHIRef Texture = Textures[CurrentIndex]; CurrentIndex = (CurrentIndex + 1) % PoolSize; return Texture; } }; ``` **Why triple buffering?** 1. GPU is rendering to texture 0 2. Encoder is reading from texture 1 3. Texture 2 is free for next frame This prevents GPU-CPU synchronization stalls. *** ## Thread Safety LCK uses standard Unreal thread-safety patterns: | Mechanism | Usage | | --------------------- | -------------------------------------------------- | | `FCriticalSection` | Protect shared data (audio buffers, encoder state) | | `FRunnableThread` | Background encoding thread | | Queue-based messaging | Thread-safe command passing | | Atomic operations | State flags (IsRecording, IsPaused) | **Audio callbacks come from different threads.** If you need to access game thread objects (like UObject properties), use: ```cpp theme={null} AudioSource->OnAudioDataDelegate.BindLambda([this](auto PCM, auto Channels, auto SampleRate, auto Source) { AsyncTask(ENamedThreads::GameThread, [this, Data = TArray(PCM)]() { ProcessAudioOnGameThread(Data); }); }); ``` *** ## Modular Feature Discovery Encoders and audio sources are discovered at runtime: ```cpp theme={null} // Find default encoder auto& ModularFeatures = IModularFeatures::Get(); if (ModularFeatures.IsModularFeatureAvailable(ILCKEncoderFactory::GetModularFeatureName())) { ILCKEncoderFactory* Factory = &ModularFeatures.GetModularFeature( ILCKEncoderFactory::GetModularFeatureName() ); } // Find all audio sources TArray AudioSources = ModularFeatures.GetModularFeatureImplementations( ILCKAudioSource::GetModularFeatureName() ); for (ILCKAudioSource* Source : AudioSources) { UE_LOG(LogLCK, Log, TEXT("Audio source: %s"), *Source->GetSourceName()); } ``` *** ## Log Categories Enable verbose logging for debugging: ```ini theme={null} ; DefaultEngine.ini [Core.Log] LogLCK=VeryVerbose LogLCKEncoding=VeryVerbose LogLCKAudio=VeryVerbose LogLCKUI=Verbose LogLCKTablet=Verbose ``` | Category | What It Logs | | ----------------- | --------------------------------------------------------- | | `LogLCK` | Core SDK operations (init, shutdown, state changes) | | `LogLCKEncoding` | Video/audio encoding, frame capture, muxing | | `LogLCKAudio` | Audio capture, mixing, source registration | | `LogLCKUI` | UI component interactions (button presses, state updates) | | `LogLCKTablet` | Tablet actor lifecycle, camera mode changes | | `LogLCKStreaming` | Streaming lifecycle, RTMP connection, auth flow | *** ## Key Takeaways **Modular design** — Core functionality (encoding, audio) is separate from UI (tablet) **Subsystem-based** — Uses Unreal's subsystem architecture for clean lifetime management **Platform abstraction** — Encoder interface allows platform-specific implementations **Extensible audio** — Audio sources register via modular features **Thread-safe** — Encoding happens on background thread, careful with audio callbacks *** ## Related * [Module Loading](/api-reference/unreal/module-loading) — Module hierarchy and dependencies * [Types & Enums](/api-reference/unreal/types) — Data structures used throughout * [Encoder Interface](/api-reference/unreal/encoder-interface) — Platform-specific encoding details * [Audio Source Interface](/api-reference/unreal/audio-source-interface) — Custom audio source implementation # ILCKAudioSource Interface Source: https://docs.liv.tv/api-reference/unreal/audio-source-interface Modular audio source interface for extensible audio capture in Unreal Engine. ## What Problem Does This Solve? LCK needs to capture audio from multiple sources: * Game audio (Unreal, FMOD, Wwise) * Microphone input * Voice chat (Vivox) `ILCKAudioSource` provides a unified interface so all audio plugins work the same way. This lets you: * Mix multiple audio sources together * Switch audio middleware without changing code * Create custom audio sources * Route audio to the encoder ## When to Use This **Read this if:** * Building a custom audio source plugin * Integrating new audio middleware * Understanding how audio flows through LCK * Debugging audio capture issues **Skip this if:** You're just using the default audio plugins (UnrealAudio, FMOD, Wwise). They already implement this interface. *** ## Critical: Single Delegate Warning **IMPORTANT:** `OnAudioDataDelegate` is a **SINGLE delegate**, not multicast. Use `BindLambda()`, NOT `AddLambda()`. Each bind **replaces** the previous binding. This is by design—audio data goes to one destination (the encoder). ### Correct Usage ✅ ```cpp theme={null} // CORRECT: Use BindLambda (replaces any existing binding) Source->OnAudioDataDelegate.BindLambda([]( TArrayView PCM, int32 Channels, int32 SampleRate, ELCKAudioChannel SourceChannel) { // Process audio data }); ``` ### Incorrect Usage ❌ ```cpp theme={null} // WRONG: AddLambda doesn't exist on single delegates Source->OnAudioDataDelegate.AddLambda(...); // Compilation error // WRONG: AddDynamic doesn't exist on single delegates Source->OnAudioDataDelegate.AddDynamic(...); // Compilation error ``` *** ## Interface Definition ```cpp theme={null} class ILCKAudioSource : public IModularFeature, public TSharedFromThis { public: static FName GetModularFeatureName() { return TEXT("LCKAudioSource"); } // Audio data callback (single delegate - use BindLambda) FOnRenderAudioDelegate OnAudioDataDelegate; // Control methods virtual bool StartCapture() noexcept = 0; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept = 0; virtual void StopCapture() noexcept = 0; // Query methods virtual float GetVolume() const noexcept = 0; virtual const FString& GetSourceName() const noexcept = 0; TLCKAudioChannelsMask GetSupportedChannels() const noexcept; protected: TLCKAudioChannelsMask SupportedChannels; }; ``` | Method | Purpose | When to Call | | ------------------------ | -------------------------------------------- | --------------------- | | `StartCapture()` | Begin audio capture (all supported channels) | Before recording | | `StartCapture(Channels)` | Begin capture for specific channels | Before recording | | `StopCapture()` | Stop audio capture | After recording | | `GetVolume()` | Get current audio level (0.0-1.0) | For volume indicators | | `GetSourceName()` | Get source identifier | Debugging, UI labels | | `GetSupportedChannels()` | Get channel bitmask | Capability detection | *** ## Audio Delegate Signature ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_FourParams( FDelegateRenderAudio, TArrayView, // PCM samples (interleaved) int32, // Number of channels int32, // Sample rate (Hz) ELCKAudioChannel // Source channel type ); typedef FDelegateRenderAudio::FDelegate FOnRenderAudioDelegate; ``` ### Parameters Explained | Parameter | Type | Description | | ----------------- | ------------------------- | ------------------------------------------------------------ | | **PCM** | `TArrayView` | Interleaved audio samples, 32-bit float format (-1.0 to 1.0) | | **Channels** | `int32` | Number of audio channels (1 = mono, 2 = stereo) | | **SampleRate** | `int32` | Audio sample rate in Hz (typically 48000) | | **SourceChannel** | `ELCKAudioChannel` | Audio type: Game, Microphone, or VoiceChat | ### Audio Data Format | Property | Value | | ---------------- | -------------------------------------------------- | | Sample format | 32-bit float | | Range | -1.0 to 1.0 | | Layout | Interleaved (e.g., `[L, R, L, R, ...]` for stereo) | | Typical channels | 2 (stereo) | | Sample rate | Source-dependent (usually 48000 Hz) | **Example PCM data (stereo):** ```cpp theme={null} float PCM[] = { 0.5f, -0.3f, // Frame 0: Left = 0.5, Right = -0.3 0.2f, 0.1f, // Frame 1: Left = 0.2, Right = 0.1 -0.4f, 0.6f // Frame 2: Left = -0.4, Right = 0.6 }; ``` *** ## Supported Channels Each audio source declares which channels it can capture: | Source | Game | Microphone | VoiceChat | Notes | | ------------------ | ---- | ---------- | --------- | ---------------------------------------------- | | **LCKUnrealAudio** | ✅ | ✅ | ❌ | Built-in Unreal audio | | **LCKFMOD** | ✅ | ❌ | ❌ | FMOD Studio game audio | | **LCKWwise** | ✅ | ❌ | ❌ | Wwise game audio | | **LCKOboe** | ❌ | ✅ | ❌ | Android low-latency mic | | **LCKVivox** | ✅ | ✅ | ❌ | Vivox voice chat (incoming→Game, outgoing→Mic) | **Check supported channels:** ```cpp theme={null} TLCKAudioChannelsMask SupportedChannels = Source->GetSupportedChannels(); // Check if source supports microphone if ((SupportedChannels & ELCKAudioChannel::Microphone) != 0) { UE_LOG(LogLCK, Log, TEXT("Source can capture microphone")); } // Check multiple channels if ((SupportedChannels & (ELCKAudioChannel::Game | ELCKAudioChannel::Microphone)) == (ELCKAudioChannel::Game | ELCKAudioChannel::Microphone)) { UE_LOG(LogLCK, Log, TEXT("Source can capture both game audio and mic")); } ``` *** ## Finding Audio Sources Audio sources are discovered via Unreal's modular features system: ```cpp theme={null} // Get all registered audio sources TArray GetAllAudioSources() { return IModularFeatures::Get() .GetModularFeatureImplementations( ILCKAudioSource::GetModularFeatureName() ); } // Find sources that support a specific channel TArray GetSourcesForChannel(ELCKAudioChannel Channel) { TArray Result; for (ILCKAudioSource* Source : GetAllAudioSources()) { if ((Source->GetSupportedChannels() & Channel) != 0) { Result.Add(Source); } } return Result; } // Example: Find all game audio sources TArray GameAudioSources = GetSourcesForChannel(ELCKAudioChannel::Game); for (ILCKAudioSource* Source : GameAudioSources) { UE_LOG(LogLCK, Log, TEXT("Game audio source: %s"), *Source->GetSourceName()); } ``` *** ## Audio Mixing with FLCKAudioMix `FLCKAudioMix` combines multiple audio sources into a single stereo output: ```cpp theme={null} class FLCKAudioMix { public: void AddSource(TWeakPtr AudioSource) noexcept; // Get mixed stereo audio for specified channels TArray StereoMix(TLCKAudioChannelsMask Channels); private: TArray> Sources; FCriticalSection Mutex; }; ``` ### Usage Example ```cpp theme={null} // Create mixer FLCKAudioMix AudioMix; // Add game audio source TSharedPtr GameAudio = GetUnrealAudioSource(); AudioMix.AddSource(GameAudio); // Add microphone source TSharedPtr Microphone = GetMicrophoneSource(); AudioMix.AddSource(Microphone); // Get mixed audio (Game + Microphone) TLCKAudioChannelsMask Channels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; TArray MixedAudio = AudioMix.StereoMix(Channels); // Pass to encoder Encoder->EncodeAudio(MixedAudio); ``` *** ## Implementing a Custom Audio Source ### Step 1: Create Source Class ```cpp theme={null} class FMyAudioSource : public ILCKAudioSource { public: FMyAudioSource() { // Declare which channels this source supports SupportedChannels = ELCKAudioChannel::Game; SourceName = TEXT("MyAudioSource"); } // ILCKAudioSource interface virtual bool StartCapture() noexcept override { return StartCapture(SupportedChannels); } virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override { // Only start if requested channels are supported if ((SupportedChannels & Channels) != Channels) { UE_LOG(LogLCK, Warning, TEXT("Requested channels not supported")); return false; } // Initialize audio capture bIsCapturing = true; // Start your audio callback thread/system here StartAudioThread(); return true; } virtual void StopCapture() noexcept override { bIsCapturing = false; StopAudioThread(); } virtual float GetVolume() const noexcept override { return CurrentVolume; } virtual const FString& GetSourceName() const noexcept override { return SourceName; } private: FString SourceName; float CurrentVolume = 0.0f; bool bIsCapturing = false; void StartAudioThread() { /* Your implementation */ } void StopAudioThread() { /* Your implementation */ } }; ``` *** ### Step 2: Fire Audio Delegate When you have audio data, fire the delegate: ```cpp theme={null} void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples, int32 NumChannels) { if (!bIsCapturing || !OnAudioDataDelegate.IsBound()) { return; } // Calculate volume (RMS) float Sum = 0.0f; for (int32 i = 0; i < NumSamples; ++i) { Sum += Buffer[i] * Buffer[i]; } CurrentVolume = FMath::Sqrt(Sum / NumSamples); // Fire delegate with audio data TArrayView PCMData(Buffer, NumSamples); OnAudioDataDelegate.ExecuteIfBound( PCMData, NumChannels, 48000, ELCKAudioChannel::Game ); } ``` *** ### Step 3: Register as Modular Feature ```cpp theme={null} class FMyAudioModule : public IModuleInterface { public: virtual void StartupModule() override { // Create audio source AudioSource = MakeShared(); // Register with modular features IModularFeatures::Get().RegisterModularFeature( ILCKAudioSource::GetModularFeatureName(), AudioSource.Get() ); UE_LOG(LogLCK, Log, TEXT("MyAudioSource registered")); } virtual void ShutdownModule() override { // Unregister if (AudioSource.IsValid()) { IModularFeatures::Get().UnregisterModularFeature( ILCKAudioSource::GetModularFeatureName(), AudioSource.Get() ); } } private: TSharedPtr AudioSource; }; IMPLEMENT_MODULE(FMyAudioModule, MyAudioPlugin) ``` *** ## Thread Safety Audio callbacks often come from different threads (audio thread, render thread). If you need to access game thread objects (UObject, UI), use `AsyncTask`. ### Thread-Safe Audio Callback ```cpp theme={null} void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples) { // This may be called from audio thread, render thread, or any thread // Option 1: Fire delegate immediately (receiver must be thread-safe) if (OnAudioDataDelegate.IsBound()) { TArrayView PCMData(Buffer, NumSamples); OnAudioDataDelegate.Execute(PCMData, 2, 48000, ELCKAudioChannel::Game); } // Option 2: Marshal to game thread (safer for UI updates) TArray AudioCopy(Buffer, NumSamples); AsyncTask(ENamedThreads::GameThread, [this, AudioCopy = MoveTemp(AudioCopy)]() { if (OnAudioDataDelegate.IsBound()) { OnAudioDataDelegate.Execute(AudioCopy, 2, 48000, ELCKAudioChannel::Game); } }); } ``` *** ### When to Use Each Approach | Approach | Use When | Notes | | -------------------------- | ------------------------------ | -------------------------- | | **Fire immediately** | Encoder is the only listener | LCK encoder is thread-safe | | **Marshal to game thread** | Multiple listeners, UI updates | Safer but adds latency | *** ## Complete Example: Custom Microphone Source ```cpp theme={null} class FCustomMicSource : public ILCKAudioSource { public: FCustomMicSource() { SupportedChannels = ELCKAudioChannel::Microphone; SourceName = TEXT("CustomMicrophone"); } virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override { if ((Channels & ELCKAudioChannel::Microphone) == 0) { return false; } // Initialize microphone capture (platform-specific) #if PLATFORM_WINDOWS InitializeWASAPI(); #elif PLATFORM_ANDROID InitializeOboe(); #endif bIsCapturing = true; return true; } virtual void StopCapture() noexcept override { bIsCapturing = false; ShutdownCapture(); } virtual float GetVolume() const noexcept override { return CurrentVolume; } virtual const FString& GetSourceName() const noexcept override { return SourceName; } private: FString SourceName; float CurrentVolume = 0.0f; bool bIsCapturing = false; void OnMicrophoneData(float* Buffer, int32 NumSamples) { if (!bIsCapturing) { return; } // Calculate RMS volume float Sum = 0.0f; for (int32 i = 0; i < NumSamples; ++i) { Sum += Buffer[i] * Buffer[i]; } CurrentVolume = FMath::Sqrt(Sum / NumSamples); // Fire delegate if (OnAudioDataDelegate.IsBound()) { TArrayView PCM(Buffer, NumSamples); OnAudioDataDelegate.Execute(PCM, 1, 48000, ELCKAudioChannel::Microphone); } } #if PLATFORM_WINDOWS void InitializeWASAPI() { /* WASAPI setup */ } #elif PLATFORM_ANDROID void InitializeOboe() { /* Oboe setup */ } #endif void ShutdownCapture() { /* Cleanup */ } }; ``` *** ## Debugging Audio Sources ### List All Sources ```cpp theme={null} void ListAudioSources() { TArray Sources = IModularFeatures::Get() .GetModularFeatureImplementations( ILCKAudioSource::GetModularFeatureName() ); UE_LOG(LogLCK, Log, TEXT("Found %d audio sources:"), Sources.Num()); for (ILCKAudioSource* Source : Sources) { TLCKAudioChannelsMask Channels = Source->GetSupportedChannels(); UE_LOG(LogLCK, Log, TEXT(" - %s"), *Source->GetSourceName()); UE_LOG(LogLCK, Log, TEXT(" Supports Game: %s"), (Channels & ELCKAudioChannel::Game) ? TEXT("Yes") : TEXT("No")); UE_LOG(LogLCK, Log, TEXT(" Supports Mic: %s"), (Channels & ELCKAudioChannel::Microphone) ? TEXT("Yes") : TEXT("No")); UE_LOG(LogLCK, Log, TEXT(" Supports VoiceChat: %s"), (Channels & ELCKAudioChannel::VoiceChat) ? TEXT("Yes") : TEXT("No")); } } ``` *** ### Monitor Audio Levels ```cpp theme={null} void MonitorAudioLevels() { TArray Sources = GetAllAudioSources(); for (ILCKAudioSource* Source : Sources) { float Volume = Source->GetVolume(); UE_LOG(LogLCK, Log, TEXT("%s volume: %.2f"), *Source->GetSourceName(), Volume); } } ``` *** ## Key Takeaways **Single delegate** — Use BindLambda, not AddLambda **Modular features** — Audio sources register at module startup **Channel bitmask** — Sources declare what they support (Game, Mic, VoiceChat) **Thread safety** — Audio callbacks may come from any thread **Audio format** — 32-bit float, interleaved, -1.0 to 1.0 range **FLCKAudioMix** — Combines multiple sources into one output *** ## Related * [Architecture](/api-reference/unreal/architecture) — How audio flows through the system * [Delegates Reference](/api-reference/unreal/delegates) — FOnRenderAudioDelegate details * [Module Loading](/api-reference/unreal/module-loading) — Audio plugin priorities * [Best Practices](/api-reference/unreal/best-practices) — Threading and performance tips # FMOD Source: https://docs.liv.tv/api-reference/unreal/audio/FMOD API reference for FLCKFMODSource, the audio source that captures game audio from FMOD Studio via DSP callback. **Module:** LCKFMOD | **Version:** 1.0 | **Platforms:** All ## Overview LCKFMOD captures game audio from the FMOD Studio master bus using a non-destructive DSP callback. It provides game audio only; pair it with LCKUnrealAudio or LCKOboe for microphone capture. ## Supported Channels | Channel | Supported | Description | | ------------ | --------- | ----------------------------- | | `Game` | Yes | FMOD Studio master bus output | | `Microphone` | No | Use LCKUnrealAudio or LCKOboe | | `VoiceChat` | No | Use LCKVivox | *** ## FLCKFMODSource Audio source class that captures FMOD master bus output via a DSP attached at `FMOD_CHANNELCONTROL_DSP_TAIL`. Implements `ILCKFeatureInstance`. ```cpp theme={null} class FLCKFMODSource : public ILCKFeatureInstance { public: // ILCKFeatureInstance interface virtual bool StartCapture() noexcept override; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override; virtual void StopCapture() noexcept override; virtual float GetVolume() const noexcept override; virtual const FString& GetSourceName() const noexcept override; // FMOD-specific int32 Samplerate = 48000; protected: TLCKAudioChannelsMask SupportedChannels = ELCKAudioChannel::Game; }; ``` ### ILCKFeatureInstance Methods | Method | Behavior | | ------------------------ | ----------------------------------------------------------- | | `StartCapture()` | Attaches DSP to the FMOD master bus and begins capture | | `StartCapture(Channels)` | Begins capture if `Game` is in the requested channel mask | | `StopCapture()` | Detaches DSP and stops capture | | `GetVolume()` | Returns current RMS volume of captured game audio (0.0-1.0) | | `GetSourceName()` | Returns `"LCKFMOD"` | ### Fields | Field | Type | Default | Description | | ------------ | ------- | ------- | ------------------------------ | | `Samplerate` | `int32` | 48000 | FMOD output sample rate in Hz. | Ensure the FMOD sample rate matches your encoder configuration (typically 48000 Hz). Mismatched sample rates between game audio and voice chat cause distortion. *** ## FLCKFMODModule Module that manages the lifecycle of `FLCKFMODSource`. ```cpp theme={null} class FLCKFMODModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtr FeatureInstance; }; IMPLEMENT_MODULE(FLCKFMODModule, LCKFMOD) ``` On startup, the module detects the FMODStudio plugin, creates an `FLCKFMODSource`, and registers it as a modular feature. On shutdown, it detaches the DSP callback, unregisters, and destroys the source. **LCKFMOD requires the FMODStudio plugin** which must be downloaded separately from [fmod.com](https://www.fmod.com/download). The LCKFMOD plugin is disabled by default and will not compile without FMODStudio installed. *** ## Log Category ```cpp theme={null} DECLARE_LOG_CATEGORY_EXTERN(LogLCKFMOD, Log, All); ``` *** ## Related Integration guide with configuration steps and troubleshooting Audio system API overview # Oboe (Android) Source: https://docs.liv.tv/api-reference/unreal/audio/Oboe-Android API reference for FLCKOboeSource, the low-latency microphone capture source for Android using the Oboe audio library. **Module:** LCKOboe | **Version:** 1.0 | **Platforms:** Android ## Overview LCKOboe provides low-latency microphone capture on Android using Google's [Oboe](https://github.com/google/oboe) audio library. It is microphone-only; pair it with LCKFMOD or LCKWwise for game audio capture. ## Supported Channels | Channel | Supported | Description | | ------------ | --------- | ---------------------------------------- | | `Game` | No | Use LCKFMOD, LCKWwise, or LCKUnrealAudio | | `Microphone` | Yes | Low-latency Android mic via Oboe | | `VoiceChat` | No | Use LCKVivox | *** ## FLCKOboeSource Audio source class that captures microphone input on Android through the Oboe library. Implements `ILCKFeatureInstance`. ```cpp theme={null} class FLCKOboeSource : public ILCKFeatureInstance { public: // ILCKFeatureInstance interface virtual bool StartCapture() noexcept override; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override; virtual void StopCapture() noexcept override; virtual float GetVolume() const noexcept override; virtual const FString& GetSourceName() const noexcept override; protected: TLCKAudioChannelsMask SupportedChannels = ELCKAudioChannel::Microphone; }; ``` ### ILCKFeatureInstance Methods | Method | Behavior | | ------------------------ | --------------------------------------------------------------- | | `StartCapture()` | Opens Oboe audio stream and begins microphone recording | | `StartCapture(Channels)` | Begins capture if `Microphone` is in the requested channel mask | | `StopCapture()` | Closes the Oboe stream and stops recording | | `GetVolume()` | Returns current RMS volume of microphone input (0.0-1.0) | | `GetSourceName()` | Returns `"LCKOboe"` | Android microphone capture requires the `RECORD_AUDIO` permission. Ensure your app requests this permission at runtime before calling `StartCapture()`. *** ## FLCKOboeModule Module that manages the lifecycle of `FLCKOboeSource`. ```cpp theme={null} class FLCKOboeModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtr FeatureInstance; }; IMPLEMENT_MODULE(FLCKOboeModule, LCKOboe) ``` On startup, the module creates an `FLCKOboeSource` and registers it as a modular feature. The module only loads on Android; it is excluded from other platforms at build time. *** ## Log Category ```cpp theme={null} DECLARE_LOG_CATEGORY_EXTERN(LogLCKOboe, Log, All); ``` *** ## Related Audio system API overview Full interface specification # Vivox Source: https://docs.liv.tv/api-reference/unreal/audio/Vivox API reference for FLCKVivoxSource, the dual-channel audio source that captures microphone and voice chat audio through Vivox callbacks. **Module:** LCKVivox | **Version:** 1.0 | **Platforms:** All ## Overview LCKVivox integrates with the Vivox voice chat SDK to capture both outgoing microphone audio and incoming voice chat audio. It uses Vivox's audio callbacks and thread-safe atomics for lock-free data handoff between the Vivox audio thread and the game thread. ## Supported Channels | Channel | Supported | Description | | ------------ | --------- | ------------------------------------------------------------- | | `Game` | Yes | Incoming voice chat audio (mapped from Vivox render callback) | | `Microphone` | Yes | Outgoing mic audio (mapped from Vivox capture callback) | | `VoiceChat` | No | Not used — Vivox audio mapped to Game/Microphone channels | *** ## FLCKVivoxSource Audio source class that captures microphone and voice chat audio through Vivox SDK callbacks. Implements `ILCKFeatureInstance` with dual-channel support. ```cpp theme={null} class FLCKVivoxSource : public ILCKFeatureInstance { public: // ILCKFeatureInstance interface virtual bool StartCapture() noexcept override; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override; virtual void StopCapture() noexcept override; virtual float GetVolume() const noexcept override; virtual const FString& GetSourceName() const noexcept override; protected: TLCKAudioChannelsMask CaptureChannels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; }; ``` ### ILCKFeatureInstance Methods | Method | Behavior | | ------------------------ | ------------------------------------------------------------------- | | `StartCapture()` | Registers Vivox capture and render callbacks for mic and voice chat | | `StartCapture(Channels)` | Registers callbacks for the specified channel mask only | | `StopCapture()` | Unregisters all Vivox callbacks and stops capture | | `GetVolume()` | Returns current RMS volume across active channels (0.0-1.0) | | `GetSourceName()` | Returns `"LCKVivox"` | ### Thread Safety `FLCKVivoxSource` uses atomic operations for lock-free data exchange between threads: * Vivox audio callbacks run on a dedicated Vivox audio thread * Audio data is written to an atomic buffer by the callback * The game thread reads from the atomic buffer when `StereoMix` is called * No mutex contention on the audio hot path ```cpp theme={null} // Internal threading model (simplified) // Vivox thread: writes PCM data via atomic swap // Game thread: reads PCM data via atomic swap // No locks on the audio path ``` *** ## FLCKVivoxModule Module that manages the lifecycle of `FLCKVivoxSource`. ```cpp theme={null} class FLCKVivoxModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtr FeatureInstance; }; IMPLEMENT_MODULE(FLCKVivoxModule, LCKVivox) ``` On startup, the module creates an `FLCKVivoxSource` and registers it as a modular feature. On shutdown, it unregisters Vivox callbacks, unregisters the modular feature, and destroys the source. LCKVivox requires the Vivox plugin to be installed and configured in your project. The module will not load if the Vivox SDK is unavailable. *** ## Log Category ```cpp theme={null} DECLARE_LOG_CATEGORY_EXTERN(LogLCKVivox, Log, All); ``` *** ## Related Audio system API overview Full interface specification # Wwise Source: https://docs.liv.tv/api-reference/unreal/audio/Wwise API reference for FLCKWwiseSource, the audio source that captures game audio from Wwise via capture callback with ambisonic-to-stereo conversion. **Module:** LCKWwise | **Version:** 1.0 | **Platforms:** Win64, Android ## Overview LCKWwise captures game audio from the Wwise sound engine using a capture callback on the output device. It handles ambisonic-to-stereo downmix automatically, producing stereo PCM data regardless of the Wwise output configuration. ## Supported Channels | Channel | Supported | Description | | ------------ | --------- | ----------------------------- | | `Game` | Yes | Wwise master output capture | | `Microphone` | No | Use LCKUnrealAudio or LCKOboe | | `VoiceChat` | No | Use LCKVivox | *** ## FLCKWwiseSource Audio source class that captures Wwise master output via a capture callback. Implements `ILCKFeatureInstance` with automatic ambisonic-to-stereo conversion. ```cpp theme={null} class FLCKWwiseSource : public ILCKFeatureInstance { public: // ILCKFeatureInstance interface virtual bool StartCapture() noexcept override; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override; virtual void StopCapture() noexcept override; virtual float GetVolume() const noexcept override; virtual const FString& GetSourceName() const noexcept override; // Wwise-specific AkOutputDeviceID OutputDeviceId = AK_INVALID_OUTPUT_DEVICE_ID; protected: TLCKAudioChannelsMask SupportedChannels = ELCKAudioChannel::Game; }; ``` ### ILCKFeatureInstance Methods | Method | Behavior | | ------------------------ | ----------------------------------------------------------- | | `StartCapture()` | Registers capture callback on the Wwise output device | | `StartCapture(Channels)` | Begins capture if `Game` is in the requested channel mask | | `StopCapture()` | Unregisters the capture callback and stops capture | | `GetVolume()` | Returns current RMS volume of captured game audio (0.0-1.0) | | `GetSourceName()` | Returns `"LCKWwise"` | ### Fields | Field | Type | Default | Description | | ---------------- | ------------------ | ----------------------------- | ------------------------------------------------------------------------ | | `OutputDeviceId` | `AkOutputDeviceID` | `AK_INVALID_OUTPUT_DEVICE_ID` | Wwise output device to capture from. Invalid ID uses the default device. | ### Ambisonic-to-Stereo Conversion If Wwise is configured with ambisonic output, `FLCKWwiseSource` automatically converts the multi-channel ambisonic data to stereo before firing the audio delegate. No additional configuration is required. ``` ┌──────────────────────────────────────┐ │ Wwise Sound Engine │ ├──────────────────────────────────────┤ │ Events → Buses → Master Output │ │ ↓ │ │ Capture Callback │ └───────────────┬──────────────────────┘ │ ┌──────▼──────┐ │ LCKWwise │ │ Capture │ │ ↓ │ │ Ambisonic │ │ → Stereo │ └─────────────┘ ``` *** ## FLCKWwiseModule Module that manages the lifecycle of `FLCKWwiseSource`. ```cpp theme={null} class FLCKWwiseModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtr FeatureInstance; }; IMPLEMENT_MODULE(FLCKWwiseModule, LCKWwise) ``` On startup, the module creates an `FLCKWwiseSource` and registers it as a modular feature. On shutdown, it unregisters the capture callback, unregisters the modular feature, and destroys the source. LCKWwise requires the Wwise Unreal integration plugin. The module will not compile without the Wwise SDK headers available. *** ## Log Category ```cpp theme={null} DECLARE_LOG_CATEGORY_EXTERN(LogLCKWwise, Log, All); ``` *** ## Related Audio system API overview Alternative middleware integration # Audio API Overview Source: https://docs.liv.tv/api-reference/unreal/audio/overview API reference overview for the LCK audio system: ILCKAudioSource interface, FLCKAudioMix mixer, and ELCKAudioChannel enum. **Module:** LCKAudio | **Version:** 1.0 | **Platforms:** All ## Overview The LCK audio system provides modular audio capture through a plugin architecture. Audio sources register as modular features and feed PCM data into a mixer that produces a final stereo output for the encoder. This page covers the three core API types. For the full interface specification, see [ILCKAudioSource Interface](/api-reference/unreal/audio-source-interface). *** ## ELCKAudioChannel Enum defining audio channel types. Used as a bitmask to declare source capabilities and request capture channels. ```cpp theme={null} enum ELCKAudioChannel : uint64 { None = 0, Game = 1 << 0, // Game audio (Unreal, FMOD, Wwise) Microphone = 1 << 1, // Microphone input VoiceChat = 1 << 2, // Voice chat (Vivox) Max = 1 << 3 // Maximum value marker }; ``` | Value | Bit | Description | | ------------ | --- | ---------------------------------- | | `None` | 0 | No channels | | `Game` | 0x1 | Game audio output | | `Microphone` | 0x2 | Microphone input | | `VoiceChat` | 0x4 | Voice chat audio (send or receive) | *** ## FLCKAudioMix Combines multiple `ILCKAudioSource` instances into a single stereo output for the encoder. ```cpp theme={null} class FLCKAudioMix { public: void SetTargetSampleRate(int32 InSampleRate) noexcept; void AddSource(TWeakPtr AudioSource) noexcept; TArray StereoMix(TLCKAudioChannelsMask Channels); bool StartCapture() noexcept; void StopCapture() noexcept; void EnsureMicrophoneCapture() noexcept; float GetVolume() const noexcept; }; ``` | Method | Purpose | | --------------------------- | ------------------------------------------------------------ | | `SetTargetSampleRate(Hz)` | Set the target sample rate for resampling (typically 48000) | | `AddSource(Source)` | Register an audio source with the mixer (takes `TWeakPtr`) | | `StereoMix(Channels)` | Return interleaved stereo PCM for the requested channel mask | | `StartCapture()` | Begin capture on all registered sources | | `StopCapture()` | Stop capture on all registered sources | | `EnsureMicrophoneCapture()` | Start microphone capture if a mic source exists but is idle | | `GetVolume()` | Get the current mixed volume level (0.0-1.0) | ### Usage Example ```cpp theme={null} FLCKAudioMix AudioMix; // Add sources AudioMix.AddSource(UnrealAudioSource); AudioMix.AddSource(FMODSource); // Set target sample rate AudioMix.SetTargetSampleRate(48000); // Start capturing all registered sources AudioMix.StartCapture(); // Each frame: get mixed audio for the encoder TArray MixedPCM = AudioMix.StereoMix(Channels); Encoder->EncodeAudio(MixedPCM); ``` *** ## ILCKAudioSource The base interface all audio plugins implement. See the full specification at [ILCKAudioSource Interface](/api-reference/unreal/audio-source-interface). | Method | Purpose | | ------------------------ | ----------------------------- | | `StartCapture()` | Begin audio capture | | `StopCapture()` | Stop audio capture | | `GetVolume()` | Current audio level (0.0-1.0) | | `GetSourceName()` | Source identifier string | | `GetSupportedChannels()` | Channel capability bitmask | *** ## Audio Source Plugins | Plugin | Module | Game | Mic | VoiceChat | Platform | | ---------------------------------------------------------- | -------------- | ---- | --- | --------- | -------------- | | [Unreal Audio](/api-reference/unreal/audio/unreal-audio) | LCKUnrealAudio | Yes | Yes | No | All | | [FMOD](/api-reference/unreal/audio/FMOD) | LCKFMOD | Yes | No | No | All | | [Wwise](/api-reference/unreal/audio/Wwise) | LCKWwise | Yes | No | No | Win64, Android | | [Oboe (Android)](/api-reference/unreal/audio/Oboe-Android) | LCKOboe | No | Yes | No | Android | | [Vivox](/api-reference/unreal/audio/Vivox) | LCKVivox | Yes | Yes | No | All | *** ## Related Full interface specification, delegate signature, and custom source guide Audio channel structs and configuration types # Unreal Audio Source: https://docs.liv.tv/api-reference/unreal/audio/unreal-audio API reference for FLCKUnrealFeatureInstance, the built-in audio source that captures game audio and microphone input through Unreal Engine's native audio system. **Module:** LCKUnrealAudio | **Version:** 1.0 | **Platforms:** All ## Overview LCKUnrealAudio captures audio through Unreal Engine's built-in audio system. It is the default audio source and supports both game audio and microphone input without any third-party dependencies. ## Supported Channels | Channel | Supported | Description | | ------------ | --------- | ------------------------------------ | | `Game` | Yes | Unreal Engine audio submix output | | `Microphone` | Yes | Platform microphone via Unreal Audio | | `VoiceChat` | No | Use LCKVivox | *** ## FLCKUnrealFeatureInstance Primary audio source class. Implements `ILCKFeatureInstance` with dual-channel support for game audio and microphone capture. ```cpp theme={null} class FLCKUnrealFeatureInstance : public ILCKFeatureInstance { public: // ILCKFeatureInstance interface virtual bool StartCapture() noexcept override; virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override; virtual void StopCapture() noexcept override; virtual float GetVolume() const noexcept override; virtual const FString& GetSourceName() const noexcept override; protected: TLCKAudioChannelsMask SupportedChannels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; }; ``` ### ILCKFeatureInstance Methods | Method | Behavior | | ------------------------ | ----------------------------------------------------------- | | `StartCapture()` | Begins capture on both game and microphone channels | | `StartCapture(Channels)` | Begins capture for the specified channel mask only | | `StopCapture()` | Stops all active capture | | `GetVolume()` | Returns current RMS volume across active channels (0.0-1.0) | | `GetSourceName()` | Returns `"LCKUnrealAudio"` | ### Usage ```cpp theme={null} // Get the Unreal audio source TSharedPtr Source = /* from module or modular features */; // Bind audio data delegate Source->OnAudioDataDelegate.BindLambda([]( TArrayView PCM, int32 Channels, int32 SampleRate, ELCKAudioChannel SourceChannel) { // SourceChannel will be Game or Microphone // depending on which channel produced the data }); // Start capturing both channels Source->StartCapture(); ``` *** ## ULCKUnrealAudioBPL Blueprint function library exposing Unreal audio utilities. ```cpp theme={null} UCLASS() class ULCKUnrealAudioBPL : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, BlueprintPure, Category = "LCK") static int32 GetUnrealAudioSamplerate() noexcept; }; ``` | Method | Returns | Description | | ---------------------------- | ------- | --------------------------------------------- | | `GetUnrealAudioSamplerate()` | `int32` | Current Unreal Engine audio sample rate in Hz | ### Blueprint Usage Call **Get Unreal Audio Samplerate** from any Blueprint to query the engine's current audio sample rate. This is useful for verifying that game audio and voice chat sample rates match. *** ## FLCKUnrealAudioModule Module that manages the lifecycle of `FLCKUnrealFeatureInstance`. ```cpp theme={null} class FLCKUnrealAudioModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: TSharedPtr FeatureInstance; }; IMPLEMENT_MODULE(FLCKUnrealAudioModule, LCKUnrealAudio) ``` On startup, the module creates an `FLCKUnrealFeatureInstance` instance and registers it as a modular feature. On shutdown, it unregisters and destroys the source. *** ## Log Category ```cpp theme={null} DECLARE_LOG_CATEGORY_EXTERN(LogLCKUnrealAudio, Log, All); ``` *** ## Related Audio system API overview Full interface specification # Best Practices (Unreal) Source: https://docs.liv.tv/api-reference/unreal/best-practices Optimization tips and recommended patterns for the LCK SDK in Unreal Engine. ## What Problem Does This Solve? Following best practices helps you: * Avoid common integration mistakes * Optimize recording performance * Handle errors gracefully * Write maintainable code * Deliver smooth user experience This page collects lessons learned from hundreds of LCK integrations. ## When to Use This Read this when: * First-time LCK integration * Debugging performance issues * Building custom recording UI * Optimizing for Quest devices * Code review / refactoring *** ## Recording ### Quality Guidelines | Quality | Resolution | Bitrate | FPS | Use Case | | ------- | ---------- | ------- | --- | ---------------------------------- | | **SD** | 1280×720 | 4 Mbps | 30 | Quest 2, performance mode | | **HD** | 1920×1080 | 12 Mbps | 60 | **Standard quality (recommended)** | | **2K** | 2560×1440 | 20 Mbps | 60 | Quest 3/Pro, high quality | | **4K** | 3840×2160 | 35 Mbps | 60 | PCVR only, max quality | **Quest recommendations:** * Quest 2: Use HD (1080p) @ 30fps for best balance * Quest 3/Pro: Can handle 2K if game performance allows * Avoid 4K on Quest devices (thermal/performance) **File size estimates:** * SD @ 30fps (default 4 Mbps): \~2 GB per hour * HD @ 60fps (default 12 Mbps): \~5 GB per hour * 2K @ 60fps (default 20 Mbps): \~9 GB per hour * 4K @ 60fps (default 35 Mbps): \~16 GB per hour *** ### Use Async Methods **Do this:** ```cpp theme={null} Recorder->StartRecordingAsync( FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess) { if (bSuccess) { ShowRecordingIndicator(); PlayRecordingSound(); } else { ShowError(TEXT("Failed to start recording")); } }) ); ``` **Don't do this:** ```cpp theme={null} // No error feedback, blocks game thread bool bSuccess = Service->StartRecording(); ``` **Why async is better:** * Non-blocking—doesn't freeze game * Detailed error callbacks * Progress tracking for save operations * Better user experience *** ### Subscribe to Recording Events **Do this:** ```cpp theme={null} void ARecordingUI::BeginPlay() { Super::BeginPlay(); // Subscribe to events Service->OnRecordingSaveFinished.AddDynamic(this, &ARecordingUI::OnSaveFinished); Service->OnRecordingError.AddDynamic(this, &ARecordingUI::OnError); Service->OnRecordingSaveProgress.AddDynamic(this, &ARecordingUI::OnProgress); // Get state changes via DataModel DataModel->OnRecordStateChanged.AddUObject(this, &ARecordingUI::OnStateChanged); } UFUNCTION() void ARecordingUI::OnSaveFinished(bool bSuccess) { if (bSuccess) ShowNotification(TEXT("Recording saved!")); } ``` **Don't do this:** ```cpp theme={null} // Polling state every frame = BAD void Tick(float DeltaTime) { if (Service->IsRecording() != bWasRecording) { bWasRecording = Service->IsRecording(); UpdateUI(); } } ``` **Why events are better:** * React immediately to state changes * No performance cost of polling * Cleaner code * More responsive UI *** ### Validate Before Recording ```cpp theme={null} bool ARecorder::CanStartRecording() { // 1. Check tracking ID ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); if (!Settings->IsTrackingIdValid()) { ShowError(TEXT("Recording not configured")); return false; } // 2. Check if already recording if (Service->IsRecording()) { UE_LOG(LogLCK, Warning, TEXT("Already recording")); return false; } // 3. Check storage space int64 FreeSpace = FPlatformMisc::GetDiskFreeSpace(FPaths::ProjectSavedDir()); int64 RequiredSpace = 500 * 1024 * 1024; // 500 MB if (FreeSpace < RequiredSpace) { ShowError(FString::Printf( TEXT("Low storage: %d MB free. Need 500 MB minimum."), FreeSpace / (1024 * 1024) )); return false; } // 4. Validate audio config (warnings, not blocking) FLCKAudioConfigValidation AudioValidation = Settings->ValidateAudioConfig(); for (const FString& Warning : AudioValidation.Warnings) { UE_LOG(LogLCK, Warning, TEXT("Audio: %s"), *Warning); } return true; } void ARecorder::StartRecording() { if (!CanStartRecording()) return; Recorder->StartRecordingAsync(/* ... */); } ``` *** ## Audio ### Audio Source Priority When multiple audio plugins are enabled, LCK uses priority order: 1. **LCKFMOD** (highest priority) 2. **LCKWwise** 3. **LCKUnrealAudio** (lowest priority, always available) Only ONE game audio source is active at a time. Microphone and voice chat can run alongside game audio. **Check which is active:** ```cpp theme={null} ELCKGameAudioType ActiveAudio = Settings->GetActiveGameAudioType(); switch (ActiveAudio) { case ELCKGameAudioType::FMOD: UE_LOG(LogLCK, Log, TEXT("Using FMOD for game audio")); break; case ELCKGameAudioType::Wwise: UE_LOG(LogLCK, Log, TEXT("Using Wwise for game audio")); break; case ELCKGameAudioType::UnrealAudio: UE_LOG(LogLCK, Log, TEXT("Using Unreal Audio")); break; } ``` *** ### Match Sample Rates **Do this:** ```cpp theme={null} // Get Unreal Audio sample rate int32 SampleRate = ULCKUnrealAudioBPL::GetUnrealAudioSamplerate(); // Configure encoder with matching rate FLCKRecorderParams Params; Params.Width = 1920; Params.Height = 1080; Params.Framerate = 30; Params.Samplerate = SampleRate; // Match! Recorder->SetupRecorder(Params, CaptureComponent); ``` **Don't do this:** ```cpp theme={null} // Hardcoded sample rate = audio distortion/sync issues Params.Samplerate = 48000; // May not match actual audio ``` *** ### Thread-Safe Audio Callbacks Audio callbacks may come from different threads. If you need to access game thread objects (UObject properties, UI), use `AsyncTask`. **Do this:** ```cpp theme={null} AudioSource->OnAudioDataDelegate.BindLambda([this]( TArrayView PCM, int32 Channels, int32 SampleRate, ELCKAudioChannel SourceChannel) { // Calculate volume on audio thread (OK) float Volume = CalculateRMS(PCM); // Update UI on game thread AsyncTask(ENamedThreads::GameThread, [this, Volume]() { VolumeIndicator->SetPercent(Volume); }); }); ``` **Don't do this:** ```cpp theme={null} AudioSource->OnAudioDataDelegate.BindLambda([this](auto PCM, auto Channels, auto SampleRate, auto Source) { // CRASH: Accessing UObject from non-game thread VolumeIndicator->SetPercent(CalculateRMS(PCM)); }); ``` *** ### Audio Delegate is Single, Not Multicast `OnAudioDataDelegate` is a **SINGLE delegate**. Use `BindLambda()`, NOT `AddLambda()`. **Do this:** ```cpp theme={null} // BindLambda replaces any existing binding AudioSource->OnAudioDataDelegate.BindLambda([](auto PCM, auto Channels, auto SampleRate, auto Source) { // Process audio }); ``` **Don't do this:** ```cpp theme={null} // AddLambda doesn't exist on single delegates AudioSource->OnAudioDataDelegate.AddLambda([](auto PCM, auto Channels, auto SampleRate, auto Source) { // Compilation error }); ``` *** ## UI ### Don't Add Your Own Button Cooldown LCK buttons include automatic 0.25s cooldown. **Do this:** ```cpp theme={null} // Just bind the event, cooldown is automatic RecordButton->OnTapStarted.AddDynamic(this, &AMyActor::OnRecordPressed); ``` **Don't do this:** ```cpp theme={null} // Redundant cooldown logic void OnRecordPressed() { if (FPlatformTime::Seconds() - LastPressTime < 0.25f) return; // Unnecessary! LastPressTime = FPlatformTime::Seconds(); ToggleRecording(); } ``` *** ### Use Showable Groups for Batch Operations **Do this:** ```cpp theme={null} // Group related UI elements ULCKShowablesGroup* SettingsGroup = NewObject(this); SettingsGroup->Add(FOVButton); SettingsGroup->Add(DistanceButton); SettingsGroup->Add(SmoothnessButton); // Batch show/hide void ShowSettings() { SettingsGroup->Show(); } void HideSettings() { SettingsGroup->Hide(); } ``` **Don't do this:** ```cpp theme={null} // Manually show/hide each element void ShowSettings() { FOVButton->SetVisibility(true); DistanceButton->SetVisibility(true); SmoothnessButton->SetVisibility(true); } ``` *** ## Performance ### Optimize Scene Capture Component ```cpp theme={null} void ARecorder::SetupCaptureComponent(USceneCaptureComponent2D* Capture) { // Disable expensive capture options Capture->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; Capture->bCaptureEveryFrame = false; // Manual capture Capture->bCaptureOnMovement = false; // Manual capture Capture->bAlwaysPersistRenderingState = true; // Disable post-processing for performance Capture->PostProcessSettings.bOverride_AmbientOcclusionIntensity = true; Capture->PostProcessSettings.AmbientOcclusionIntensity = 0.0f; // Disable expensive features Capture->ShowFlags.SetTemporalAA(false); Capture->ShowFlags.SetMotionBlur(false); } ``` *** ### Unregister Capture Components **Do this:** ```cpp theme={null} void ARecorder::BeginDestroy() { Super::BeginDestroy(); if (Service) { Service->StopRecording(); Service->UnregisterCaptureComponent(TEXT("MainCapture")); } } ``` **Don't do this:** ```cpp theme={null} // Memory leak - capture component never cleaned up void ARecorder::BeginDestroy() { Super::BeginDestroy(); Service->StopRecording(); } ``` *** ### Monitor Frame Times During Recording ```cpp theme={null} void APerformanceMonitor::Tick(float DeltaTime) { if (!Service || !Service->IsRecording()) return; float FrameTimeMs = DeltaTime * 1000.0f; // Track frame time history FrameTimeHistory.Add(FrameTimeMs); if (FrameTimeHistory.Num() > 60) // 2 seconds at 30fps { FrameTimeHistory.RemoveAt(0); } // Calculate average float AvgFrameTime = 0.0f; for (float Time : FrameTimeHistory) { AvgFrameTime += Time; } AvgFrameTime /= FrameTimeHistory.Num(); // Warn if frame time is too high float TargetFrameTime = 1000.0f / 30.0f; // 33.3ms for 30fps if (AvgFrameTime > TargetFrameTime * 1.2f) // 20% over target { UE_LOG(LogLCK, Warning, TEXT("Recording impacting performance: %.1fms avg"), AvgFrameTime); SuggestLowerQuality(); } } ``` *** ## Common Pitfalls ### 1. Not Handling Recording State Feedback **Problem:** No feedback when recording starts/stops/fails **Solution:** Subscribe to delegates ```cpp theme={null} Service->OnRecordingSaveFinished.AddDynamic(this, &AMyUI::OnSaveFinished); Service->OnRecordingError.AddDynamic(this, &AMyUI::OnError); DataModel->OnRecordStateChanged.AddUObject(this, &AMyUI::OnStateChanged); ``` *** ### 2. Mismatched Resolutions **Problem:** Render target doesn't match recording resolution → distortion **Solution:** Match exactly ```cpp theme={null} // Render target RenderTarget->ResX = 1920; RenderTarget->ResY = 1080; // Recording params FLCKRecorderParams Params; Params.Width = 1920; // Must match RenderTarget->ResX Params.Height = 1080; // Must match RenderTarget->ResY ``` *** ### 3. Forgetting to Unregister Capture Component **Problem:** Memory leak from orphaned capture components **Solution:** Always unregister on cleanup ```cpp theme={null} void ARecorder::BeginDestroy() { Super::BeginDestroy(); if (Service) { Service->UnregisterCaptureComponent(CaptureComponentName); } } ``` *** ### 4. Sample Rate Mismatch **Problem:** Audio distortion or A/V sync issues **Solution:** Query and match sample rates ```cpp theme={null} int32 SampleRate = ULCKUnrealAudioBPL::GetUnrealAudioSamplerate(); Params.Samplerate = SampleRate; ``` *** ### 5. Using AddLambda for Audio Delegate **Problem:** `OnAudioDataDelegate` is single, not multicast **Solution:** Use `BindLambda()` instead ```cpp theme={null} // Correct AudioSource->OnAudioDataDelegate.BindLambda([](auto PCM, auto Channels, auto SampleRate, auto Source) { // ... }); // Wrong - compilation error AudioSource->OnAudioDataDelegate.AddLambda([](auto PCM, auto Channels, auto SampleRate, auto Source) { // ... }); ``` *** ## Platform Checklist ### Android (Quest) **Vulkan enabled** in Project Settings → Android **RECORD\_AUDIO permission** in AndroidManifest.xml **WRITE\_EXTERNAL\_STORAGE permission** (API \< 29) **LCKOboe plugin enabled** for low-latency mic **LCKVulkan loads at EarliestPossible** (don't change!) **AndroidManifest.xml:** ```xml theme={null} ``` *** ### Windows (PCVR) **Media Foundation available** (Windows 10+) **DirectX 11 compatible GPU** **H.264 hardware encoding support** *** ## Debugging ### Enable Verbose Logging ```ini theme={null} ; DefaultEngine.ini [Core.Log] LogLCK=VeryVerbose LogLCKEncoding=VeryVerbose LogLCKAudio=VeryVerbose LogLCKUI=Verbose LogLCKTablet=Verbose ``` **What you'll see:** ``` LogLCK: Recording started LogLCKEncoding: Encoder initialized: 1920x1080 @ 60fps, 12 Mbps LogLCKAudio: Audio source registered: UnrealAudio LogLCKEncoding: Frame 0 encoded (8.2ms) LogLCKEncoding: Frame 30 encoded (7.9ms) LogLCK: Recording stopped LogLCKEncoding: Finalizing video file... LogLCK: Recording saved: /Game/Movies/recording_001.mp4 ``` *** ### Common Log Messages | Log Message | Meaning | Action | | ------------------------- | ----------------------------- | --------------------------- | | `Recording started` | Recording began successfully | - | | `Encoder initialized` | Platform encoder ready | - | | `Audio source registered` | Audio capture active | - | | `Invalid Tracking ID` | Tracking ID missing/invalid | Add ID from dashboard | | `Encoder not available` | Platform encoder failed | Check platform requirements | | `Permission denied` | Missing permissions (Android) | Request permissions | *** ## Quick Reference **Start recording:** ```cpp theme={null} Recorder->StartRecordingAsync(FOnLCKRecorderBoolResult::CreateLambda([](bool bSuccess) { // Handle result })); ``` **Stop recording:** ```cpp theme={null} Recorder->StopRecordingAsync( FOnLCKRecorderBoolResult::CreateLambda([](bool bSuccess) { /* Done */ }), FOnLCKRecorderProgress::CreateLambda([](float Progress) { /* 0.0-1.0 */ }) ); ``` **Check state:** ```cpp theme={null} bool bRecording = Service->IsRecording(); float Duration = Service->GetCurrentRecordingDuration(); ``` **Subscribe to events:** ```cpp theme={null} Service->OnRecordingSaveFinished.AddDynamic(this, &AMyActor::OnSaveFinished); Service->OnRecordingError.AddDynamic(this, &AMyActor::OnError); DataModel->OnRecordStateChanged.AddUObject(this, &AMyActor::OnStateChanged); ``` *** ## Key Takeaways **Use async methods** for better error handling **Subscribe to events** instead of polling state **Validate before recording** (tracking ID, storage, state) **Match sample rates** to avoid audio issues **Audio callbacks on any thread** — use AsyncTask for UI updates **Single delegate for audio** — use BindLambda, not AddLambda **Unregister captures** to prevent memory leaks **HD @ 30fps for Quest 2** — higher quality impacts performance *** ## Related * [Error Codes](/api-reference/unreal/errors) — Error handling patterns * [Delegates Reference](/api-reference/unreal/delegates) — All event delegates * [Architecture](/api-reference/unreal/architecture) — System design * [Device Overrides](/api-reference/unreal/device-overrides) — Platform-specific settings # Delegates Reference Source: https://docs.liv.tv/api-reference/unreal/delegates Event delegates for reactive programming with the LCK SDK in Unreal Engine. ## What Problem Does This Solve? Delegates let you react to LCK events in real-time: * Recording started/stopped * Save progress updates * Button interactions * Camera mode changes * Audio callbacks Instead of polling state every frame, you bind handlers that fire when events occur. This keeps your code clean and responsive. ## When to Use This Reference this when: * Building custom recording UI * Handling recording lifecycle events * Responding to user interactions * Processing audio data * Tracking save progress *** ## Delegate Types LCK uses two kinds of delegates: | Type | Usage | Binding Method | | ------------------------- | --------------------------------------- | ------------------------------- | | **Dynamic Multicast** | Blueprint-compatible, multiple bindings | `AddDynamic()` | | **Non-Dynamic Multicast** | C++ only, multiple bindings | `AddUObject()` or `AddLambda()` | | **Raw/Lambda** | C++ only, single or multiple bindings | `BindLambda()` or `AddLambda()` | **Audio delegates are SINGLE delegates** (not multicast). Use `BindLambda()`, not `AddLambda()`. Each bind replaces the previous one. **DataModel delegates are non-dynamic multicast.** Most delegates on `ULCKTabletDataModel` (camera mode, mic state, video quality, recording state) use `DECLARE_MULTICAST_DELEGATE`, not `DECLARE_DYNAMIC_MULTICAST_DELEGATE`. Bind with `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. *** ## Recording Lifecycle Delegates ### FOnRecordingSaveFinished **What it's for:** Know when recording save completes ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRecordingSaveFinished, bool, Success); ``` **Declared on:** `ULCKService` **Parameters:** * `Success` (bool) -- True if save succeeded, false on error **Usage:** ```cpp theme={null} // Subscribe (dynamic multicast -- use AddDynamic) Service->OnRecordingSaveFinished.AddDynamic(this, &AMyActor::HandleSaveFinished); // Handler UFUNCTION() void AMyActor::HandleSaveFinished(bool bSuccess) { if (bSuccess) { ShowNotification(TEXT("Recording saved!")); PlaySuccessSound(); } else { ShowError(TEXT("Failed to save recording")); } } ``` *** ### FOnRecordingSaveProgress **What it's for:** Update progress bar during save ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRecordingSaveProgress, float, Progress); ``` **Declared on:** `ULCKService` **Parameters:** * `Progress` (float) -- Value from 0.0 (start) to 1.0 (complete) **Usage:** ```cpp theme={null} Service->OnRecordingSaveProgress.AddDynamic(this, &AMyUI::UpdateProgressBar); UFUNCTION() void AMyUI::UpdateProgressBar(float Progress) { ProgressBar->SetPercent(Progress); ProgressText->SetText(FText::AsPercent(Progress)); } ``` *** ### FOnRecordingError **What it's for:** Handle recording errors with context ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnRecordingError, FString, ErrorMessage, int32, ErrorCode ); ``` **Declared on:** `ULCKService` **Parameters:** * `ErrorMessage` (FString) -- Human-readable error description * `ErrorCode` (int32) -- Numeric error code (see `ELCKError`) **Usage:** ```cpp theme={null} Service->OnRecordingError.AddDynamic(this, &AMyActor::HandleRecordingError); UFUNCTION() void AMyActor::HandleRecordingError(FString ErrorMessage, int32 ErrorCode) { UE_LOG(LogLCK, Error, TEXT("Recording error %d: %s"), ErrorCode, *ErrorMessage); // Show user-friendly message ShowErrorDialog(ErrorMessage); // Log to analytics Analytics->TrackError(TEXT("Recording"), ErrorCode); } ``` *** ## Camera & Settings Delegates ### FOnTabletCameraModeChanged **What it's for:** React to camera mode switches ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_OneParam(FOnTabletCameraModeChanged, UClass*); ``` **Declared on:** `ULCKTabletDataModel` **Parameters:** * `UClass*` -- The new camera mode class (e.g., `ULCKSelfieCameraMode`, `ULCKFirstPersonCameraMode`, `ULCKThirdPersonCameraMode`) This is a **non-dynamic** multicast delegate. Use `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. **Usage:** ```cpp theme={null} // Subscribe (non-dynamic -- use AddUObject, not AddDynamic) DataModel->OnTabletCameraModeChanged.AddUObject(this, &AMyUI::HandleCameraMode); // Handler (no UFUNCTION() needed for non-dynamic delegates) void AMyUI::HandleCameraMode(UClass* ModeClass) { if (ModeClass->IsChildOf(ULCKSelfieCameraMode::StaticClass())) { CameraModeText->SetText(FText::FromString("Selfie")); } else if (ModeClass->IsChildOf(ULCKFirstPersonCameraMode::StaticClass())) { CameraModeText->SetText(FText::FromString("First Person")); } else if (ModeClass->IsChildOf(ULCKThirdPersonCameraMode::StaticClass())) { CameraModeText->SetText(FText::FromString("Third Person")); } } ``` *** ### FOnMicStateChanged **What it's for:** Update UI when mic state changes ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_OneParam(FOnMicStateChanged, ELCKMicState); ``` **Declared on:** `ULCKTabletDataModel` **Parameters:** * `ELCKMicState` -- New microphone state (On, Off, No\_Access) This is a **non-dynamic** multicast delegate. Use `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. **Usage:** ```cpp theme={null} // Subscribe (non-dynamic -- use AddUObject, not AddDynamic) DataModel->OnMicStateChanged.AddUObject(this, &AMicButton::UpdateIcon); void AMicButton::UpdateIcon(ELCKMicState NewState) { switch (NewState) { case ELCKMicState::On: Icon->SetBrush(MicOnTexture); Icon->SetColorAndOpacity(FLinearColor::White); break; case ELCKMicState::Off: Icon->SetBrush(MicOffTexture); Icon->SetColorAndOpacity(FLinearColor::Gray); break; case ELCKMicState::No_Access: Icon->SetBrush(MicBlockedTexture); ShowPermissionPrompt(); break; } } ``` *** ### FOnVideoQualityChanged **What it's for:** React to quality profile changes ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_OneParam(FOnVideoQualityChanged, ELCKVideoQuality); ``` **Declared on:** `ULCKTabletDataModel` This is a **non-dynamic** multicast delegate. Use `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. **Usage:** ```cpp theme={null} DataModel->OnVideoQualityChanged.AddUObject(this, &AMyUI::HandleQualityChanged); void AMyUI::HandleQualityChanged(ELCKVideoQuality NewQuality) { // Update quality display } ``` *** ### FOnRecordStateChange **What it's for:** React to recording state changes on the DataModel ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_OneParam(FOnRecordStateChange, ELCKRecordingState); ``` **Declared on:** `ULCKTabletDataModel` This is a **non-dynamic** multicast delegate. Use `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. **Usage:** ```cpp theme={null} DataModel->OnRecordStateChanged.AddUObject(this, &AMyUI::HandleRecordStateChanged); void AMyUI::HandleRecordStateChanged(ELCKRecordingState NewState) { switch (NewState) { case ELCKRecordingState::Idle: // Ready to record break; case ELCKRecordingState::Recording: // Recording in progress break; case ELCKRecordingState::Saving: // Saving to disk break; } } ``` *** ### FOnScreenOrientationChanged **What it's for:** React to screen orientation changes ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_OneParam(FOnScreenOrientationChanged, ELCKScreenOrientation); ``` **Declared on:** `ULCKTabletDataModel` This is a **non-dynamic** multicast delegate. Use `AddUObject()` or `AddLambda()`, **not** `AddDynamic()`. **Usage:** ```cpp theme={null} DataModel->OnScreenOrientationChanged.AddUObject(this, &AMyUI::HandleOrientationChanged); void AMyUI::HandleOrientationChanged(ELCKScreenOrientation NewOrientation) { // Update UI layout for new orientation } ``` *** ## UI Interaction Delegates ### FOnTapStarted **What it's for:** Handle button presses ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTapStarted); ``` **Usage:** ```cpp theme={null} RecordButton->OnTapStarted.AddDynamic(this, &AMyActor::HandleRecordButton); UFUNCTION() void AMyActor::HandleRecordButton() { if (Service->IsRecording()) { Service->StopRecording(); } else { Service->StartRecording(); } } ``` LCK buttons include automatic 0.25s cooldown. Don't add your own debouncing. *** ### FOnStepperValueChanged **What it's for:** Handle stepper control changes (increment/decrement buttons) ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStepperValueChanged, int8, NewValue); ``` **Parameters:** * `NewValue` (int8) -- Direction: -1 (decrease) or +1 (increase) **Usage:** ```cpp theme={null} FOVStepper->OnStepperValueChanged.AddDynamic(this, &ACamera::AdjustFOV); UFUNCTION() void ACamera::AdjustFOV(int8 Direction) { CurrentFOV += Direction * 5.0f; // Adjust by 5 degrees CurrentFOV = FMath::Clamp(CurrentFOV, 30.0f, 120.0f); UpdateCamera(); } ``` *** ### FOnPad2DChanged **What it's for:** Handle 2D directional pad input ```cpp theme={null} DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPad2DChanged, FIntPoint, NewValue); ``` **Parameters:** * `NewValue` (FIntPoint) -- Direction as (X, Y) where values are -1, 0, or +1 **Usage:** ```cpp theme={null} Pad2D->OnPad2DChanged.AddDynamic(this, &ANavigator::HandleDirection); UFUNCTION() void ANavigator::HandleDirection(FIntPoint Direction) { // Direction.X: -1 (left), 0 (none), +1 (right) // Direction.Y: -1 (down), 0 (none), +1 (up) if (Direction.X != 0) { // Handle horizontal ScrollHorizontal(Direction.X); } if (Direction.Y != 0) { // Handle vertical ScrollVertical(Direction.Y); } } ``` *** ## Audio Delegates ### FOnRenderAudioDelegate **What it's for:** Process raw audio data from audio sources ```cpp theme={null} DECLARE_MULTICAST_DELEGATE_FourParams( FDelegateRenderAudio, TArrayView, // PCM samples (interleaved) int32, // Channels int32, // Sample rate (Hz) ELCKAudioChannel // Source channel type ); typedef FDelegateRenderAudio::FDelegate FOnRenderAudioDelegate; ``` **Parameters:** * `PCM` (TArrayView\) -- Interleaved audio samples (32-bit float) * `Channels` (int32) -- Number of channels (typically 2 for stereo) * `SampleRate` (int32) -- Audio sample rate in Hz (typically 48000) * `Source` (ELCKAudioChannel) -- Audio source type (Game, Microphone, VoiceChat) **This is a SINGLE delegate** (not multicast). Use `BindLambda()`, not `AddLambda()`. Each bind replaces the previous one. Audio callbacks may come from different threads. Use `AsyncTask(ENamedThreads::GameThread, ...)` if accessing game thread objects. **Usage:** ```cpp theme={null} // Bind audio callback (replaces any existing binding) AudioSource->OnAudioDataDelegate.BindLambda([this]( TArrayView PCM, int32 Channels, int32 SampleRate, ELCKAudioChannel SourceChannel) { // PCM format: interleaved 32-bit float samples // Example: [L, R, L, R, L, R, ...] for stereo // Calculate RMS volume float Sum = 0.0f; for (float Sample : PCM) { Sum += Sample * Sample; } float RMS = FMath::Sqrt(Sum / PCM.Num()); // Update UI on game thread AsyncTask(ENamedThreads::GameThread, [this, RMS]() { UpdateVolumeIndicator(RMS); }); }); ``` **Fire the delegate (from audio source implementation):** ```cpp theme={null} // Inside ILCKAudioSource::Render() or similar TArray PCMData = GetAudioBuffer(); OnAudioDataDelegate.ExecuteIfBound(PCMData, 2, 48000, ELCKAudioChannel::Game); ``` *** ## Async Operation Delegates ### FOnLCKRecorderBoolResult **What it's for:** Handle async operation results ```cpp theme={null} DECLARE_DELEGATE_OneParam(FOnLCKRecorderBoolResult, bool /*bSuccess*/); ``` **Usage:** ```cpp theme={null} Recorder->StartRecordingAsync( FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess) { if (bSuccess) { ShowRecordingIndicator(); UE_LOG(LogLCK, Log, TEXT("Recording started")); } else { ShowError(TEXT("Failed to start recording")); } }) ); ``` *** ### FOnLCKRecorderProgress **What it's for:** Track save/encoding progress ```cpp theme={null} DECLARE_DELEGATE_OneParam(FOnLCKRecorderProgress, float /*Progress*/); ``` **Usage:** ```cpp theme={null} Recorder->StopRecordingAsync( FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess) { if (bSuccess) { ShowSuccess(TEXT("Recording saved")); } }), FOnLCKRecorderProgress::CreateLambda([this](float Progress) { ProgressBar->SetPercent(Progress); StatusText->SetText(FText::Format( LOCTEXT("SavingProgress", "Saving... {0}%"), FText::AsPercent(Progress) )); }) ); ``` *** ## Binding Patterns ### Dynamic Delegates (Blueprint-Compatible) Used for `FOnRecordingSaveFinished`, `FOnRecordingSaveProgress`, `FOnRecordingError`, and UI delegates like `FOnTapStarted`. ```cpp theme={null} // Header UCLASS() class AMyActor : public AActor { GENERATED_BODY() UFUNCTION() void HandleButtonTap(); }; // Implementation void AMyActor::BeginPlay() { Super::BeginPlay(); Button->OnTapStarted.AddDynamic(this, &AMyActor::HandleButtonTap); } void AMyActor::HandleButtonTap() { // Handle tap } ``` *** ### Non-Dynamic Multicast (C++ Only) Used for DataModel delegates like `FOnTabletCameraModeChanged`, `FOnMicStateChanged`, `FOnVideoQualityChanged`, `FOnRecordStateChange`. ```cpp theme={null} // Header -- no UFUNCTION() needed UCLASS() class AMyActor : public AActor { GENERATED_BODY() void HandleCameraMode(UClass* ModeClass); void HandleMicState(ELCKMicState NewState); }; // Implementation void AMyActor::BeginPlay() { Super::BeginPlay(); ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Use AddUObject for member functions DataModel->OnTabletCameraModeChanged.AddUObject(this, &AMyActor::HandleCameraMode); DataModel->OnMicStateChanged.AddUObject(this, &AMyActor::HandleMicState); // Or use AddLambda for inline handlers DataModel->OnVideoQualityChanged.AddLambda([this](ELCKVideoQuality NewQuality) { UpdateQualityDisplay(NewQuality); }); } ``` *** ### Lambda Binding (C++ Only) ```cpp theme={null} // Non-dynamic multicast delegate (DataModel events) DataModel->OnTabletCameraModeChanged.AddLambda([this](UClass* ModeClass) { UpdateCameraModeUI(ModeClass); }); // Dynamic multicast delegate (UI events -- use AddDynamic, not AddLambda) Button->OnTapStarted.AddDynamic(this, &AMyActor::OnRecordButtonTapped); // Single delegate (audio) AudioSource->OnAudioDataDelegate.BindLambda([this](auto PCM, auto Channels, auto SampleRate, auto Source) { ProcessAudio(PCM); }); ``` *** ### Removing Bindings ```cpp theme={null} // Dynamic delegate Button->OnTapStarted.RemoveDynamic(this, &AMyActor::HandleButtonTap); // Non-dynamic delegate with handle FDelegateHandle Handle = DataModel->OnMicStateChanged.AddUObject(this, &AMyActor::HandleMicState); DataModel->OnMicStateChanged.Remove(Handle); // Lambda with handle FDelegateHandle LambdaHandle = Delegate.AddLambda([](){ /* ... */ }); Delegate.Remove(LambdaHandle); // Clear all Delegate.Clear(); ``` *** ## Complete Example: Recording UI ```cpp theme={null} UCLASS() class ARecordingUI : public AActor { GENERATED_BODY() protected: virtual void BeginPlay() override { Super::BeginPlay(); ULCKService* Service = GetLCKService(); ALCKTablet* Tablet = FindTablet(); if (!Service || !Tablet) return; ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Recording lifecycle (dynamic multicast -- use AddDynamic) Service->OnRecordingSaveFinished.AddDynamic(this, &ARecordingUI::OnSaveFinished); Service->OnRecordingSaveProgress.AddDynamic(this, &ARecordingUI::OnSaveProgress); Service->OnRecordingError.AddDynamic(this, &ARecordingUI::OnError); // State changes (non-dynamic multicast -- use AddUObject) DataModel->OnRecordStateChanged.AddUObject(this, &ARecordingUI::OnStateChanged); DataModel->OnMicStateChanged.AddUObject(this, &ARecordingUI::OnMicChanged); // UI interactions (dynamic multicast -- use AddDynamic) RecordButton->OnTapStarted.AddDynamic(this, &ARecordingUI::OnRecordButton); MicButton->OnTapStarted.AddDynamic(this, &ARecordingUI::OnMicButton); } UFUNCTION() void OnRecordButton() { if (Service->IsRecording()) Service->StopRecording(); else Service->StartRecording(); } UFUNCTION() void OnMicButton() { bool bCurrentState = Service->IsMicrophoneEnabled(); Service->SetMicrophoneEnabled(!bCurrentState); } // Non-dynamic delegate handler (no UFUNCTION needed) void OnStateChanged(ELCKRecordingState NewState) { switch (NewState) { case ELCKRecordingState::Recording: RecordButton->SetText(FText::FromString("Stop")); RecordingIndicator->SetVisibility(ESlateVisibility::Visible); break; case ELCKRecordingState::Idle: RecordButton->SetText(FText::FromString("Record")); RecordingIndicator->SetVisibility(ESlateVisibility::Hidden); break; case ELCKRecordingState::Saving: ProgressPanel->SetVisibility(ESlateVisibility::Visible); break; } } UFUNCTION() void OnSaveProgress(float Progress) { ProgressBar->SetPercent(Progress); } UFUNCTION() void OnSaveFinished(bool bSuccess) { ProgressPanel->SetVisibility(ESlateVisibility::Hidden); if (bSuccess) ShowNotification(TEXT("Recording saved!")); } UFUNCTION() void OnError(FString ErrorMessage, int32 ErrorCode) { ShowErrorDialog(ErrorMessage); } // Non-dynamic delegate handler (no UFUNCTION needed) void OnMicChanged(ELCKMicState NewState) { switch (NewState) { case ELCKMicState::On: MicIcon->SetBrush(MicOnTexture); break; case ELCKMicState::Off: MicIcon->SetBrush(MicOffTexture); break; case ELCKMicState::No_Access: ShowPermissionDialog(); break; } } }; ``` *** ## Key Takeaways **Use delegates for events** -- Don't poll state every frame **Dynamic vs non-dynamic matters** -- DataModel delegates use AddUObject/AddLambda, Service delegates use AddDynamic **Audio delegates are SINGLE** -- Use BindLambda, not AddLambda **Thread safety matters** -- Audio callbacks may come from any thread **Remove bindings on cleanup** -- Prevent dangling pointers **Dynamic delegates for Blueprint** -- Use UFUNCTION handlers with AddDynamic *** ## Related * [Error Codes](/api-reference/unreal/errors) -- Error handling patterns * [Service Interface](/api-reference/unreal/service-interface) -- ULCKService API reference * [Architecture](/api-reference/unreal/architecture) -- How delegates flow through the system # Device-Specific Settings (Unreal) Source: https://docs.liv.tv/api-reference/unreal/device-overrides Optimize LCK recording settings for different VR headsets in Unreal Engine. ## What Problem Does This Solve? Different VR headsets have different performance limits: * Quest 2 has thermal/power constraints * Quest 3/Pro can handle higher quality * PCVR depends on GPU capabilities Device-specific overrides let you automatically adjust recording settings based on detected hardware, ensuring the best balance between video quality and game performance. ## When to Use This Use device overrides when: * Supporting multiple Quest devices * Targeting both standalone VR and PCVR * Users report performance issues * Optimizing for specific platforms * Want automatic quality adjustment Skip this if: You're only targeting one specific device and have manually tuned settings. *** ## Supported Devices | Device | Max Resolution | Max FPS | Max Bitrate | Notes | | ------------- | -------------- | ------- | ----------- | ----------------------------- | | **Quest 2** | 1080p HD | 30 | 10 Mbps | Conservative (thermal limits) | | **Quest 3** | 4K UHD | 60 | 20 Mbps | Full quality support | | **Quest Pro** | 4K UHD | 60 | 20 Mbps | Full quality support | | **PCVR** | 4K UHD | 60 | 20 Mbps | GPU dependent | *** ## Quest 2 Optimization Quest 2 has thermal and power constraints that limit encoding performance. ### Recommended Quest 2 Settings | Setting | Default | Quest 2 Override | Reason | | -------------- | -------- | ---------------- | ------------------ | | Max Resolution | 4K | **1080p HD** | Thermal limits | | Framerate | 60 | **30** | CPU overhead | | Video Bitrate | 20 Mbps | **10 Mbps** | Encoding load | | Audio Bitrate | 320 Kbps | **192 Kbps** | Sufficient quality | | 2K Profile | Enabled | **Disabled** | Not performant | | 4K Profile | Enabled | **Disabled** | Not performant | *** ### Apply Quest 2 Overrides ```cpp theme={null} void ADeviceManager::ApplyQuest2Overrides() { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); // Override HD profile for Quest 2 FLCKRecordingProfile& HDProfile = Settings->Profile_HD; HDProfile.Width = 1920; HDProfile.Height = 1080; HDProfile.Framerate = 30; HDProfile.VideoBitrate = 10 << 20; // 10 Mbps HDProfile.AudioBitrate = 192000; // 192 Kbps // Disable higher quality profiles /* bEnable2KProfile - custom property, not in SDK */ = false; /* bEnable4KProfile - custom property, not in SDK */ = false; UE_LOG(LogLCK, Log, TEXT("Applied Quest 2 optimizations")); } ``` *** ### Why These Limits? **Thermal management:** * Quest 2 SoC (Snapdragon XR2 Gen 1) generates heat under load * Extended recording → thermal throttling → frame drops * Conservative settings prevent overheating **File size:** * 10 Mbps @ 30fps = \~1.2 GB per hour * 20 Mbps @ 60fps = \~5.4 GB per hour (too much for casual recording) **Battery life:** * Higher quality = faster battery drain * 30fps encoding uses \~30% less power than 60fps *** ## Quest 3 / Quest Pro Optimization Quest 3 and Quest Pro have better cooling and more powerful hardware. ### Recommended Quest 3/Pro Settings | Setting | Value | Notes | | -------------- | -------------- | ---------------- | | Max Resolution | 4K (3840×2160) | Full support | | Framerate | 60 | Smooth encoding | | Video Bitrate | 20 Mbps | High quality | | Audio Bitrate | 320 Kbps | Max quality | | 2K Profile | **Enabled** | Available option | | 4K Profile | **Enabled** | Available option | *** ### Apply Quest 3 Overrides ```cpp theme={null} void ADeviceManager::ApplyQuest3Overrides() { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); // Enable all quality profiles /* bEnable2KProfile - custom property, not in SDK */ = true; /* bEnable4KProfile - custom property, not in SDK */ = true; // Configure 4K profile FLCKRecordingProfile& UHDProfile = Settings->Profile_4K; UHDProfile.Width = 3840; UHDProfile.Height = 2160; UHDProfile.Framerate = 60; UHDProfile.VideoBitrate = 20 << 20; // 20 Mbps UHDProfile.AudioBitrate = 320000; // 320 Kbps UE_LOG(LogLCK, Log, TEXT("Applied Quest 3 optimizations")); } ``` *** ## Automatic Device Detection Detect the device at runtime and apply appropriate settings: ```cpp theme={null} UCLASS() class ADeviceManager : public AActor { GENERATED_BODY() public: void BeginPlay() override { Super::BeginPlay(); ApplyDeviceOverrides(); } private: void ApplyDeviceOverrides() { FString DeviceModel = GetDeviceModel(); UE_LOG(LogLCK, Log, TEXT("Detected device: %s"), *DeviceModel); if (DeviceModel.Contains(TEXT("Quest 2"))) { ApplyQuest2Overrides(); } else if (DeviceModel.Contains(TEXT("Quest 3"))) { ApplyQuest3Overrides(); } else if (DeviceModel.Contains(TEXT("Quest Pro"))) { ApplyQuestProOverrides(); } else { // PCVR or unknown device - use default settings UE_LOG(LogLCK, Log, TEXT("Using default settings for PCVR/unknown device")); } } FString GetDeviceModel() { #if PLATFORM_ANDROID // Get Android device model return FAndroidMisc::GetDeviceModel(); #else // Get VR headset name via XR system if (GEngine && GEngine->XRSystem.IsValid()) { FString DeviceName = GEngine->XRSystem->GetSystemName().ToString(); return DeviceName; } return TEXT("Unknown"); #endif } void ApplyQuest2Overrides() { /* See above */ } void ApplyQuest3Overrides() { /* See above */ } void ApplyQuestProOverrides() { /* Same as Quest 3 */ } }; ``` *** ## Custom Override Configuration ### Using Config Files Create platform-specific config files: **Config/Android\_Quest2/DefaultGame.ini:** ```ini theme={null} [/Script/LCKCore.LCKDeveloperSettings] +Profile_HD=(Width=1920,Height=1080,Framerate=30,VideoBitrate=10000000,AudioBitrate=192000) bEnable2KProfile=false bEnable4KProfile=false ``` **Config/Android\_Quest3/DefaultGame.ini:** ```ini theme={null} [/Script/LCKCore.LCKDeveloperSettings] +Profile_4K=(Width=3840,Height=2160,Framerate=60,VideoBitrate=20000000,AudioBitrate=320000) bEnable2KProfile=true bEnable4KProfile=true ``` *** ### Runtime Configuration System ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKDeviceOverride { GENERATED_BODY() UPROPERTY(EditAnywhere, Category = "Device Override") FString DeviceModelPattern; UPROPERTY(EditAnywhere, Category = "Device Override") ELCKVideoQuality MaxQuality; UPROPERTY(EditAnywhere, Category = "Device Override") int32 MaxFramerate; UPROPERTY(EditAnywhere, Category = "Device Override") int32 VideoBitrate; UPROPERTY(EditAnywhere, Category = "Device Override") int32 AudioBitrate; }; UCLASS(Config=Game, DefaultConfig) class ULCKDeviceSettings : public UDeveloperSettings { GENERATED_BODY() public: UPROPERTY(Config, EditAnywhere, Category = "Device Overrides") TArray DeviceOverrides; UFUNCTION(BlueprintCallable) void ApplyOverrideForDevice(const FString& DeviceModel) { for (const FLCKDeviceOverride& Override : DeviceOverrides) { if (DeviceModel.Contains(Override.DeviceModelPattern)) { ApplyOverride(Override); return; } } } private: void ApplyOverride(const FLCKDeviceOverride& Override) { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); // Apply override settings FLCKRecordingProfile Profile; Profile.Framerate = Override.MaxFramerate; Profile.VideoBitrate = Override.VideoBitrate; Profile.AudioBitrate = Override.AudioBitrate; // Set based on quality switch (Override.MaxQuality) { case ELCKVideoQuality::HD: Settings->Profile_HD = Profile; /* bEnable2KProfile - custom property, not in SDK */ = false; /* bEnable4KProfile - custom property, not in SDK */ = false; break; case ELCKVideoQuality::TWO_K: Settings->Profile_2K = Profile; /* bEnable4KProfile - custom property, not in SDK */ = false; break; case ELCKVideoQuality::FOUR_K: Settings->Profile_4K = Profile; break; } } }; ``` *** ## Performance Monitoring Monitor encoding performance to validate your overrides: ```cpp theme={null} UCLASS() class ULCKPerformanceMonitor : public UActorComponent { GENERATED_BODY() public: virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!Service || !Service->IsRecording()) return; // Track frame times float FrameTimeMs = DeltaTime * 1000.0f; FrameTimeHistory.Add(FrameTimeMs); // Keep last 2 seconds if (FrameTimeHistory.Num() > 60) { FrameTimeHistory.RemoveAt(0); } // Calculate average frame time float AvgFrameTime = 0.0f; for (float Time : FrameTimeHistory) { AvgFrameTime += Time; } AvgFrameTime /= FrameTimeHistory.Num(); // Check for performance issues float TargetFrameTime = 1000.0f / 30.0f; // 33.3ms for 30fps if (AvgFrameTime > TargetFrameTime * 1.2f) // 20% over target { OnPerformanceWarning(); } } private: UPROPERTY() ULCKService* Service; TArray FrameTimeHistory; void OnPerformanceWarning() { UE_LOG(LogLCK, Warning, TEXT("Recording impacting performance - consider lower quality")); // Notify UI if (OnPerformanceIssue.IsBound()) { OnPerformanceIssue.Broadcast( TEXT("Recording may be impacting game performance. " "Consider using a lower quality profile.") ); } } public: DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPerformanceIssue, FString, Message); UPROPERTY(BlueprintAssignable) FOnPerformanceIssue OnPerformanceIssue; }; ``` *** ## Thermal Management (Android) On Android, monitor thermal state to prevent throttling: ```cpp theme={null} #if PLATFORM_ANDROID UCLASS() class ULCKThermalMonitor : public UActorComponent { GENERATED_BODY() public: virtual void BeginPlay() override { Super::BeginPlay(); // Check thermal state every 10 seconds GetWorld()->GetTimerManager().SetTimer( ThermalCheckTimer, this, &ULCKThermalMonitor::CheckThermalState, 10.0f, true ); } private: FTimerHandle ThermalCheckTimer; UPROPERTY() ULCKService* Service; void CheckThermalState() { // Android thermal API int32 ThermalStatus = FAndroidMisc::GetThermalStatus(); switch (ThermalStatus) { case 0: // THERMAL_STATUS_NONE case 1: // THERMAL_STATUS_LIGHT // Normal operation break; case 2: // THERMAL_STATUS_MODERATE UE_LOG(LogLCK, Warning, TEXT("Device warming up")); OnThermalWarning(); break; case 3: // THERMAL_STATUS_SEVERE case 4: // THERMAL_STATUS_CRITICAL UE_LOG(LogLCK, Error, TEXT("Thermal throttling detected")); ReduceQualityForThermal(); break; } } void OnThermalWarning() { // Notify user if (OnThermalAlert.IsBound()) { OnThermalAlert.Broadcast( TEXT("Device temperature rising. Recording quality may be reduced.") ); } } void ReduceQualityForThermal() { if (!Service) { Service = GetLCKService(); } if (Service && !Service->IsRecording()) { return; } // Get current quality ELCKVideoQuality CurrentQuality = DataModel->GetCurrentVideoQuality(); // Drop one quality level ELCKVideoQuality NewQuality = CurrentQuality; switch (CurrentQuality) { case ELCKVideoQuality::FOUR_K: NewQuality = ELCKVideoQuality::TWO_K; break; case ELCKVideoQuality::TWO_K: NewQuality = ELCKVideoQuality::HD; break; case ELCKVideoQuality::HD: NewQuality = ELCKVideoQuality::SD; break; case ELCKVideoQuality::SD: // Already at lowest - stop recording Service->StopRecording(); UE_LOG(LogLCK, Error, TEXT("Stopped recording due to thermal limits")); return; } // Stop current recording Service->StopRecording(); // Apply new quality DataModel->SetVideoQuality(NewQuality); UE_LOG(LogLCK, Warning, TEXT("Reduced quality due to thermal throttling: %d -> %d"), (int32)CurrentQuality, (int32)NewQuality); // Notify user if (OnThermalAlert.IsBound()) { OnThermalAlert.Broadcast( FString::Printf(TEXT("Quality reduced to %s due to device temperature."), *UEnum::GetValueAsString(NewQuality)) ); } } public: DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnThermalAlert, FString, Message); UPROPERTY(BlueprintAssignable) FOnThermalAlert OnThermalAlert; }; #endif // PLATFORM_ANDROID ``` *** ## User-Selectable Performance Presets Let users choose their own balance: ```cpp theme={null} UENUM(BlueprintType) enum class ELCKPerformancePreset : uint8 { Quality UMETA(DisplayName = "Quality"), // Highest quality, may impact performance Balanced UMETA(DisplayName = "Balanced"), // Default, good quality with minimal impact Performance UMETA(DisplayName = "Performance") // Lower quality, prioritize game FPS }; UCLASS() class ULCKPerformancePresetManager : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void ApplyPreset(ELCKPerformancePreset Preset) { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); FString DeviceModel = GetDeviceModel(); if (DeviceModel.Contains(TEXT("Quest 2"))) { ApplyQuest2Preset(Preset); } else if (DeviceModel.Contains(TEXT("Quest 3"))) { ApplyQuest3Preset(Preset); } } private: void ApplyQuest2Preset(ELCKPerformancePreset Preset) { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); FLCKRecordingProfile& HDProfile = Settings->Profile_HD; switch (Preset) { case ELCKPerformancePreset::Quality: // 1080p @ 60fps, 12 Mbps (HD default) HDProfile.Framerate = 60; HDProfile.VideoBitrate = 12 << 20; break; case ELCKPerformancePreset::Balanced: // 1080p @ 30fps, 8 Mbps (reduced for Quest) HDProfile.Framerate = 30; HDProfile.VideoBitrate = 8 << 20; break; case ELCKPerformancePreset::Performance: // 720p @ 30fps, 4 Mbps HDProfile.Width = 1280; HDProfile.Height = 720; HDProfile.Framerate = 30; HDProfile.VideoBitrate = 4 << 20; break; } } }; ``` *** ## Best Practices ### 1. Test on Target Devices Always test recording performance on actual target devices, not in editor or emulator. * Quest 2 has different thermal characteristics than Quest 3 * PCVR performance depends heavily on GPU * Test with your game's most demanding scenes * Monitor frame times during extended recordings *** ### 2. Provide User Options ```cpp theme={null} // Settings menu void ASettingsMenu::BuildQualityOptions() { FString DeviceModel = GetDeviceModel(); if (DeviceModel.Contains(TEXT("Quest 2"))) { // Quest 2: Offer SD and HD only AddQualityOption(ELCKVideoQuality::SD, TEXT("SD (720p)"), TEXT("Best performance")); AddQualityOption(ELCKVideoQuality::HD, TEXT("HD (1080p)"), TEXT("Recommended")); } else if (DeviceModel.Contains(TEXT("Quest 3"))) { // Quest 3: Offer all options AddQualityOption(ELCKVideoQuality::HD, TEXT("HD (1080p)"), TEXT("Recommended")); AddQualityOption(ELCKVideoQuality::TWO_K, TEXT("2K (1440p)"), TEXT("High quality")); AddQualityOption(ELCKVideoQuality::FOUR_K, TEXT("4K (2160p)"), TEXT("Maximum quality")); } } ``` *** ### 3. Document Device Support Clearly communicate what works best: **In-game tooltips:** ```cpp theme={null} FString GetQualityTooltip(ELCKVideoQuality Quality) { if (IsQuest2()) { switch (Quality) { case ELCKVideoQuality::HD: return TEXT("Recommended for Quest 2. Best balance of quality and performance."); case ELCKVideoQuality::SD: return TEXT("Lower quality, but ensures smooth gameplay."); } } return TEXT(""); } ``` *** ## Key Takeaways **Quest 2: HD @ 30fps max** — thermal/power limits **Quest 3/Pro: Up to 4K @ 60fps** — better cooling and hardware **Auto-detect device** — apply appropriate overrides at runtime **Monitor performance** — track frame times during recording **Watch thermal state (Android)** — reduce quality if overheating **Offer user presets** — Quality, Balanced, Performance **Test on target devices** — editor performance ≠ device performance *** ## Related * [Best Practices](/api-reference/unreal/best-practices) — General optimization tips * [Enums Reference](/api-reference/unreal/enums) — ELCKVideoQuality enum values * [Types Reference](/api-reference/unreal/types) — FLCKRecordingProfile struct * [Architecture](/api-reference/unreal/architecture) — How platform detection works # ILCKEncoder Interface (Unreal) Source: https://docs.liv.tv/api-reference/unreal/encoder-interface Abstract encoder interface for platform-specific video encoding in Unreal Engine. ## What Problem Does This Solve? `ILCKEncoder` abstracts platform-specific video encoding so you don't have to: * Windows uses Media Foundation * Android uses NDK MediaCodec * Quest uses Vulkan texture interop The interface lets LCK support multiple platforms with a single API. Most developers never interact with this directly—`ULCKService` handles it all. ## When to Use This **Read this if:** * Building a custom encoder (very advanced) * Debugging encoding issues * Understanding LCK's internal architecture * Extending LCK to new platforms **Skip this if:** You're just using LCK for recording. Use `ULCKService` instead. *** ## Interface Definition ```cpp theme={null} class ILCKEncoder : public TSharedFromThis, public FRunnable { public: // Lifecycle virtual bool Open() noexcept = 0; virtual bool IsEncoding() const noexcept = 0; // Encoding virtual void EncodeTexture(FTextureRHIRef& Texture, float TimeSeconds) = 0; virtual void EncodeAudio(TArrayView PCMData) = 0; // Finalization virtual void Save(TFunction ProgressCallback) = 0; // Queries [[nodiscard]] virtual float GetAudioTime() const noexcept = 0; // Dual output (v1.0) virtual void SetRecordToDisk(bool bRecord) { bRecordToDisk = bRecord; } virtual void AddPacketSink(ILCKPacketSink* Sink) {} virtual void RemovePacketSink(ILCKPacketSink* Sink) {} virtual void* GetNativeEncoderHandle() { return nullptr; } bool bRecordToDisk = true; }; ``` | Method | What It Does | When It's Called | | -------------------- | ------------------------------------------- | ---------------------------- | | `Open()` | Initialize encoder, allocate resources | Before first frame | | `IsEncoding()` | Check if encoder is active | State queries | | `EncodeTexture()` | Encode a video frame | Every frame during recording | | `EncodeAudio()` | Encode audio samples | Audio callbacks | | `Save()` | Finalize and write MP4 file | After last frame | | `GetAudioTime()` | Get current audio timestamp | A/V sync | | `SetRecordToDisk()` | Control disk output (false for stream-only) | Before encoding starts | | `AddPacketSink()` | Register a packet sink for RTMP streaming | Before encoding starts | | `RemovePacketSink()` | Unregister a packet sink | After streaming stops | *** ## Encoder Factory Encoders are discovered and created via Unreal's modular features system: ```cpp theme={null} class ILCKEncoderFactory : public IModularFeature, public TSharedFromThis { public: static FName GetModularFeatureName() noexcept; [[nodiscard]] virtual const FString& GetEncoderName() const noexcept = 0; [[nodiscard]] virtual TSharedPtr CreateEncoder( uint32 Width, // Video width (e.g., 1920) uint32 Height, // Video height (e.g., 1080) uint32 VideoBitrate, // Video bitrate in bps (e.g., 12000000 = 12 Mbps) uint32 Framerate, // Target FPS (e.g., 60) uint32 Samplerate, // Audio sample rate (e.g., 48000 Hz) uint32 AudioBitrate // Audio bitrate in bps (e.g., 256000 = 256 Kbps) ) const noexcept = 0; }; ``` *** ## Finding an Encoder at Runtime ```cpp theme={null} ILCKEncoderFactory* Factory = nullptr; auto& ModularFeatures = IModularFeatures::Get(); if (ModularFeatures.IsModularFeatureAvailable(ILCKEncoderFactory::GetModularFeatureName())) { Factory = &ModularFeatures.GetModularFeature( ILCKEncoderFactory::GetModularFeatureName() ); } if (Factory) { UE_LOG(LogLCK, Log, TEXT("Encoder available: %s"), *Factory->GetEncoderName()); // Create encoder TSharedPtr Encoder = Factory->CreateEncoder( 1920, 1080, // HD resolution 12 << 20, // 12 Mbps video bitrate 60, // 60 FPS 48000, // 48 kHz audio 256000 // 256 Kbps audio bitrate ); } ``` *** ## Platform Implementations ### Windows: FLCKWindowsEncoder **Technologies:** * `IMFSinkWriter` — MP4 muxing * `IMFTransform` — H.264 video encoding * `IMFMediaType` — AAC audio encoding * Direct3D 11 texture interop **Key features:** * Hardware-accelerated encoding via GPU * Triple-buffered texture pool (avoids GPU stalls) * Async encoding thread * Supports DX11 render targets **Error handling example:** ```cpp theme={null} HRESULT hr = SinkWriter->WriteSample(VideoStreamIndex, Sample); if (FAILED(hr)) { UE_LOG(LogLCKEncoding, Error, TEXT("WriteSample failed: 0x%08X"), hr); // Common errors: // 0x80070057 = Invalid parameter // 0xC00D36B4 = Codec not found // 0x8007000E = Out of memory } ``` **Triple-buffered texture pool:** ```cpp theme={null} // Why triple buffering? // 1. GPU is rendering to texture 0 // 2. Encoder is reading from texture 1 // 3. Texture 2 is free for next frame // Result: No GPU-CPU sync stalls class FTexturePool { static constexpr int32 PoolSize = 3; TArray Textures; int32 CurrentIndex = 0; public: FTextureRHIRef GetNextTexture() { FTextureRHIRef Texture = Textures[CurrentIndex]; CurrentIndex = (CurrentIndex + 1) % PoolSize; return Texture; } }; ``` *** ### Android: FLCKAndroidEncoder **Technologies:** * `AMediaCodec` — H.264/AAC hardware encoding * `AMediaMuxer` — MP4 container * Vulkan/EGL texture interop * Android Hardware Buffer **Key features:** * Hardware-accelerated encoding on Quest * Vulkan texture export via EGL * Low-latency pipeline * Direct write to device storage **Vulkan interop flow:** ```cpp theme={null} // 1. Export Vulkan texture to Android Hardware Buffer VkExternalMemoryHandleTypeFlagBits HandleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID; // 2. Import into MediaCodec surface AMediaCodec_queueInputBuffer(Codec, BufferIndex, 0, Size, TimeUs, 0); // 3. MediaCodec encodes directly from GPU memory // No CPU-side texture readback needed! ``` **Why Vulkan interop is critical:** * Quest uses Vulkan for rendering * CPU readback would be too slow (kills performance) * EGL interop lets encoder access GPU memory directly * This is why `LCKVulkan` module must load at `EarliestPossible` phase *** ## Data Flow ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Scene Capture │────>│ Render Target │────>│ Texture Pool │ │ Component │ │ (RenderTarget) │ │ (3 buffers) │ └─────────────────┘ └─────────────────┘ └────────┬────────┘ │ v ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ MP4 File │<────│ Video Encoder │<────│ GPU Readback │ │ (Movies dir) │ │ (H.264, AAC) │ │ (RHI Command) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ↑ │ ┌───────────┴───────────┐ │ Audio Mixer │ │ (Game, Mic, Vivox) │ └───────────────────────┘ ``` *** ## Audio Encoding Audio flows from audio sources → mixer → encoder: ```cpp theme={null} void ILCKEncoder::EncodeAudio(TArrayView PCMData) { // Input: 32-bit float, interleaved stereo // Sample rate: typically 48000 Hz // Format: [L, R, L, R, L, R, ...] // Convert float (-1.0 to 1.0) to int16 TArray IntSamples; IntSamples.SetNum(PCMData.Num()); for (int32 i = 0; i < PCMData.Num(); ++i) { float Sample = FMath::Clamp(PCMData[i], -1.0f, 1.0f); IntSamples[i] = static_cast(Sample * 32767.0f); } // Pass to platform encoder // Windows: IMFTransform (AAC) // Android: AMediaCodec (AAC) } ``` *** ## Thread Safety Encoding runs on a dedicated background thread: ```cpp theme={null} class ILCKEncoder : public FRunnable { protected: FRunnableThread* EncoderThread; FCriticalSection EncodingMutex; TQueue TaskQueue; std::atomic bShouldRun; public: virtual uint32 Run() override { while (bShouldRun) { FEncodingTask Task; if (TaskQueue.Dequeue(Task)) { FScopeLock Lock(&EncodingMutex); ProcessTask(Task); } } return 0; } }; ``` **Why threading matters:** * Encoding is CPU-intensive * Running on game thread would cause stuttering * Background thread keeps game smooth * Queue-based design prevents race conditions *** ## Creating a Custom Encoder **This is advanced usage.** Most developers should use the built-in platform encoders. Only create a custom encoder if: * You need a different codec (HEVC, VP9) * You need a different container (WebM, AVI) * You're porting LCK to a new platform ### Step 1: Implement ILCKEncoder ```cpp theme={null} class FMyCustomEncoder : public ILCKEncoder { public: virtual bool Open() noexcept override { // Initialize your encoder // Allocate buffers, set up codec bIsActive = true; return true; } virtual bool IsEncoding() const noexcept override { return bIsActive; } virtual void EncodeTexture(FTextureRHIRef& Texture, float TimeSeconds) override { // 1. Read texture from GPU // 2. Convert to encoder's expected format // 3. Pass to codec } virtual void EncodeAudio(TArrayView PCMData) override { // 1. Convert float to int16 // 2. Pass to audio codec } virtual void Save(TFunction ProgressCallback) override { // 1. Flush encoder // 2. Write file // 3. Call ProgressCallback(0.0 to 1.0) bIsActive = false; } virtual float GetAudioTime() const noexcept override { return CurrentAudioTime; } private: bool bIsActive = false; float CurrentAudioTime = 0.0f; }; ``` *** ### Step 2: Create Factory ```cpp theme={null} class FMyEncoderFactory : public ILCKEncoderFactory { public: virtual const FString& GetEncoderName() const noexcept override { static const FString Name = TEXT("MyCustomEncoder"); return Name; } virtual TSharedPtr CreateEncoder( uint32 Width, uint32 Height, uint32 VideoBitrate, uint32 Framerate, uint32 Samplerate, uint32 AudioBitrate) const noexcept override { TSharedPtr Encoder = MakeShared(); // Configure encoder with provided parameters // ... return Encoder; } }; ``` *** ### Step 3: Register via Modular Features ```cpp theme={null} class FMyEncoderModule : public IModuleInterface { private: FMyEncoderFactory EncoderFactory; public: virtual void StartupModule() override { // Register encoder factory IModularFeatures::Get().RegisterModularFeature( ILCKEncoderFactory::GetModularFeatureName(), &EncoderFactory ); UE_LOG(LogLCK, Log, TEXT("MyCustomEncoder registered")); } virtual void ShutdownModule() override { // Unregister IModularFeatures::Get().UnregisterModularFeature( ILCKEncoderFactory::GetModularFeatureName(), &EncoderFactory ); } }; IMPLEMENT_MODULE(FMyEncoderModule, MyEncoder) ``` *** ## Debugging Encoder Issues ### Enable Verbose Logging ```ini theme={null} ; DefaultEngine.ini [Core.Log] LogLCKEncoding=VeryVerbose ``` **What you'll see:** ``` LogLCKEncoding: Encoder initialized: Windows Media Foundation LogLCKEncoding: Video: 1920x1080 @ 60fps, 12 Mbps LogLCKEncoding: Audio: 48000 Hz stereo, 256 Kbps LogLCKEncoding: Frame 0 encoded (8.2ms) LogLCKEncoding: Frame 30 encoded (7.9ms) LogLCKEncoding: Audio buffer: 2048 samples, 42.7ms LogLCKEncoding: Finalizing video file... LogLCKEncoding: MP4 saved: C:/Users/.../recording_001.mp4 ``` *** ### Common Encoder Errors **Windows:** ``` LogLCKEncoding: Error: IMFSinkWriter creation failed (0xC00D36B4) ``` **Fix:** H.264 codec not installed (rare on Windows 10/11) *** **Android:** ``` LogLCKEncoding: Error: AMediaCodec configure failed ``` **Fix:** Unsupported resolution or bitrate for device *** **Vulkan interop:** ``` LogLCKEncoding: Error: Vulkan texture export failed ``` **Fix:** Ensure `LCKVulkan` module loads at `EarliestPossible` phase *** ## Key Takeaways **Platform abstraction** — One interface, multiple implementations **Modular features** — Runtime encoder discovery and creation **Triple buffering** — Prevents GPU stalls on texture readback **Background thread** — Encoding doesn't block game thread **Most devs don't need this** — Use ULCKService for recording *** ## Related * [Architecture](/api-reference/unreal/architecture) — How encoders fit into the system * [Module Loading](/api-reference/unreal/module-loading) — Platform-specific encoder modules * [Service Interface](/api-reference/unreal/service-interface) — High-level recording API * [Best Practices](/api-reference/unreal/best-practices) — Encoding performance tips # Enums Reference (Unreal) Source: https://docs.liv.tv/api-reference/unreal/enums Complete enumeration reference for the LCK SDK for Unreal Engine. ## What Problem Does This Solve? Enums define fixed sets of values used throughout LCK: * Recording states (Idle, Recording, Saving) * Video quality presets (SD, HD, 2K, 4K) * Audio channel types (Game, Microphone, VoiceChat) * Screen orientation (Landscape, Portrait) * UI states (Default, Hovered, Pressed) Understanding these enums helps you check states, configure settings, and handle events correctly. ## When to Use This Reference this when: * Checking recording state * Setting video quality * Configuring audio channels * Setting screen orientation * Handling UI interactions * Debugging enum-related issues *** ## Recording & Capture ### ELCKRecordingState **What it's for:** Current state of the recording system ```cpp theme={null} UENUM(BlueprintType) enum class ELCKRecordingState : uint8 { Idle UMETA(DisplayName = "Idle"), Recording UMETA(DisplayName = "Recording"), Saving UMETA(DisplayName = "Saving"), Processing UMETA(DisplayName = "Processing"), Error UMETA(DisplayName = "Error"), Paused UMETA(DisplayName = "Paused") }; ``` | State | Description | What You Can Do | | ------------ | ----------------------------------- | --------------------------------- | | `Idle` | No recording active, ready to start | Start recording, change settings | | `Recording` | Actively recording video/audio | Stop recording, pause, take photo | | `Saving` | Finalizing and saving file to disk | Wait for completion | | `Processing` | Post-processing video (rare) | Wait for completion | | `Error` | An error occurred | Check error message, retry | | `Paused` | Recording paused | Resume or stop | **State machine flow:** ``` Idle -> Recording -> Saving -> Idle | ^ Error -------------------- ``` **Common usage:** ```cpp theme={null} // Recording state is available on ULCKService ELCKRecordingState State = Service->GetRecordingState(); switch (State) { case ELCKRecordingState::Idle: // Show "Start Recording" button break; case ELCKRecordingState::Recording: // Show recording indicator, enable "Stop" button break; case ELCKRecordingState::Saving: // Show progress bar break; case ELCKRecordingState::Error: // Show error message break; } ``` *** ### ELCKVideoQuality **What it's for:** Predefined quality profile selection ```cpp theme={null} UENUM(BlueprintType) enum class ELCKVideoQuality : uint8 { SD UMETA(DisplayName = "SD"), HD UMETA(DisplayName = "HD"), TWO_K UMETA(DisplayName = "2K"), FOUR_K UMETA(DisplayName = "4K"), MAX UMETA(Hidden) }; ``` | Quality | Resolution | Video Bitrate | Audio Bitrate | FPS | Use Case | | -------- | ---------- | ------------- | ------------- | --- | ------------------------------ | | `SD` | 1280x720 | 4 Mbps | 128 Kbps | 30 | Mobile VR, performance mode | | `HD` | 1920x1080 | 12 Mbps | 256 Kbps | 60 | Standard quality (recommended) | | `TWO_K` | 2560x1440 | 20 Mbps | 320 Kbps | 60 | High quality | | `FOUR_K` | 3840x2160 | 35 Mbps | 320 Kbps | 60 | Maximum quality (PC only) | **Quest recommendations:** * Quest 2: Use HD (1080p) for best balance * Quest 3/Pro: Can handle 2K if performance allows * Avoid 4K on Quest devices **Usage:** ```cpp theme={null} // Video quality is managed through ULCKTabletDataModel, not ULCKService ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Set quality profile DataModel->SetVideoQuality(ELCKVideoQuality::HD); // Get current quality ELCKVideoQuality Current = DataModel->GetCurrentVideoQuality(); // Cycle through quality levels (SD -> HD -> 2K -> 4K -> SD) DataModel->CycleVideoQuality(); ``` *** ### ELCKScreenOrientation **What it's for:** Video output orientation ```cpp theme={null} UENUM(BlueprintType) enum class ELCKScreenOrientation : uint8 { Landscape UMETA(DisplayName = "Landscape"), Portrait UMETA(DisplayName = "Portrait") }; ``` | Orientation | Aspect Ratio | Resolution Example | Use Case | | ----------- | ----------------- | ------------------ | -------------------------- | | `Landscape` | 16:9 (horizontal) | 1920x1080 | YouTube, traditional video | | `Portrait` | 9:16 (vertical) | 1080x1920 | TikTok, Instagram Stories | **Usage:** ```cpp theme={null} // Screen orientation is managed through ULCKTabletDataModel, not ULCKService ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Set orientation DataModel->SetScreenOrientation(ELCKScreenOrientation::Portrait); // Get current orientation ELCKScreenOrientation Current = DataModel->GetScreenOrientation(); // Toggle between Landscape and Portrait DataModel->ToggleScreenOrientation(); ``` *** ## Camera Modes ### UClass-Based Camera Mode System Camera modes are **not** selected via an enum. The LCK SDK uses a `UClass*`-based system where each camera mode is a subclass of `ULCKBaseCameraMode`. The three built-in camera mode classes are: | Class | Description | Use Case | | --------------------------- | ------------------------------------------- | -------------------------------- | | `ULCKSelfieCameraMode` | Front/back facing camera attached to tablet | Vlog-style, show player's face | | `ULCKFirstPersonCameraMode` | POV from player's HMD position | Gameplay perspective | | `ULCKThirdPersonCameraMode` | Orbital camera following player | Cinematic, show player character | **Usage:** ```cpp theme={null} // Camera mode is managed through ULCKTabletDataModel ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Switch camera mode by passing the UClass* DataModel->SetCameraMode(ULCKThirdPersonCameraMode::StaticClass()); // Get current camera mode class UClass* CurrentMode = DataModel->GetCameraMode(); // Check which mode is active if (CurrentMode->IsChildOf(ULCKSelfieCameraMode::StaticClass())) { // Selfie mode is active } ``` *** ### ELCKCameraFacing **What it's for:** Selfie camera direction ```cpp theme={null} UENUM(BlueprintType) enum class ELCKCameraFacing : uint8 { Front UMETA(DisplayName = "Front"), Rear UMETA(DisplayName = "Rear") }; ``` | Value | Description | | ------- | --------------------------------------------------------- | | `Front` | Camera faces toward player (like front camera on phone) | | `Rear` | Camera faces away from player (like back camera on phone) | **Only applies to Selfie mode.** **Usage:** ```cpp theme={null} ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Set camera facing DataModel->SetSelfieCameraFacing(ELCKCameraFacing::Rear); // Toggle between Front and Rear DataModel->ToggleSelfieCameraFacing(); ``` *** ## Audio System ### ELCKAudioChannel **What it's for:** Audio source types for capture and mixing ```cpp theme={null} enum ELCKAudioChannel : uint64 { None = 0, // No audio Game = 1, // Game audio output Microphone = 1 << 1, // Microphone input VoiceChat = 1 << 2, // Voice chat (Vivox) Max = 1 << 3 // Marker (don't use) }; ``` | Channel | Bit | Description | Sources | | ------------ | --- | --------------------- | ------------------------------------------ | | `None` | 0 | No audio | - | | `Game` | 1 | Game audio output | UnrealAudio, FMOD, Wwise, Vivox (incoming) | | `Microphone` | 2 | Microphone input | UnrealAudio, Oboe, Vivox (outgoing) | | `VoiceChat` | 4 | Voice chat (reserved) | Not currently used | **LCKVivox mapping:** * Incoming voice chat -> `Game` channel * Outgoing microphone -> `Microphone` channel **Bitwise operations:** ```cpp theme={null} // Combine channels TLCKAudioChannelsMask Channels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; // Check if channel is in mask bool HasMic = (Channels & ELCKAudioChannel::Microphone) != 0; // Remove a channel Channels &= ~ELCKAudioChannel::Microphone; // Add a channel Channels |= ELCKAudioChannel::VoiceChat; ``` *** ### ELCKMicState **What it's for:** Microphone state for recording ```cpp theme={null} UENUM(BlueprintType) enum class ELCKMicState : uint8 { On UMETA(DisplayName = "On"), Off UMETA(DisplayName = "Off"), No_Access UMETA(DisplayName = "No Access") }; ``` | State | Description | UI Action | | ----------- | -------------------------------- | ------------------------------- | | `On` | Microphone enabled and capturing | Show "Mic On" indicator | | `Off` | Microphone disabled | Show "Mic Off" indicator | | `No_Access` | Permission denied (Android) | Prompt user to grant permission | On Android, `No_Access` means the user denied microphone permission. You must guide them to device settings to grant the permission. **Usage:** ```cpp theme={null} // Mic state is managed through ULCKTabletDataModel ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); ELCKMicState MicState = DataModel->GetMicState(); switch (MicState) { case ELCKMicState::On: MicIcon->SetBrush(MicOnTexture); break; case ELCKMicState::Off: MicIcon->SetBrush(MicOffTexture); break; case ELCKMicState::No_Access: ShowPermissionPrompt(); break; } // Toggle mic on/off DataModel->ToggleMicState(); // Set mic state directly DataModel->SetMicState(ELCKMicState::Off); ``` *** ### ELCKGameAudioType **What it's for:** Detected game audio middleware ```cpp theme={null} UENUM(BlueprintType) enum class ELCKGameAudioType : uint8 { None UMETA(DisplayName = "None"), FMOD UMETA(DisplayName = "FMOD"), Wwise UMETA(DisplayName = "Wwise"), UnrealAudio UMETA(DisplayName = "Unreal Audio") }; ``` | Type | Description | Priority | | ------------- | ----------------------------- | ----------- | | `None` | No game audio capture | - | | `FMOD` | FMOD Studio middleware | 1 (highest) | | `Wwise` | Audiokinetic Wwise middleware | 2 | | `UnrealAudio` | Built-in Unreal Engine audio | 3 (lowest) | **Priority order:** FMOD > Wwise > UnrealAudio Only ONE game audio source is active at a time. **Usage:** ```cpp theme={null} // Check which audio middleware is active ELCKGameAudioType ActiveAudio = Settings->GetActiveGameAudioType(); switch (ActiveAudio) { case ELCKGameAudioType::FMOD: UE_LOG(LogLCK, Log, TEXT("Using FMOD for game audio")); break; case ELCKGameAudioType::Wwise: UE_LOG(LogLCK, Log, TEXT("Using Wwise for game audio")); break; case ELCKGameAudioType::UnrealAudio: UE_LOG(LogLCK, Log, TEXT("Using Unreal Audio")); break; } ``` *** ## UI Components ### ELCKButtonType **What it's for:** Button shape and size for 3D UI ```cpp theme={null} UENUM(BlueprintType) enum class ELCKButtonType : uint8 { Square UMETA(DisplayName = "Square"), Rectangle UMETA(DisplayName = "Rectangle"), Tab UMETA(DisplayName = "Tab"), Selector UMETA(DisplayName = "Selector") }; ``` | Type | Dimensions (cm) | Aspect | Use Case | | ----------- | --------------- | ------ | -------------------- | | `Square` | (0.4, 2.4, 2.4) | 1:1 | Icons, single-action | | `Rectangle` | (0.4, 6.0, 2.4) | 2.5:1 | Text buttons, labels | | `Tab` | (0.4, 4.4, 2.4) | 1.83:1 | Tab navigation | | `Selector` | (2.4, 4.4, 0.8) | 5.5:1 | Sliders, selectors | *** ### Button States Button interaction states are managed via method calls and material parameters rather than a centralized enum. The key mechanisms are: * `bIsEnabled` (`bool`) — Controls whether the button accepts interaction * `ButtonPressed(const FLCKTapData&)` — Called on overlap begin (press) * `ButtonReleased(const FLCKTapData&)` — Called on overlap end (release) * `UpdateVisualsOnPressed()` / `UpdateVisualsOnReleased()` — Virtual methods for visual state transitions Visual feedback is applied via dynamic material parameters (`BackColor`, `FrontColor`, `IndicatorValue`) and mesh position offsets. *** ### ELCKButtonInteractionDirection **What it's for:** Touch validation direction for buttons ```cpp theme={null} UENUM(BlueprintType) enum class ELCKButtonInteractionDirection : uint8 { Forward UMETA(DisplayName = "Forward"), Up UMETA(DisplayName = "Up") }; ``` | Value | Description | Use Case | | --------- | -------------------------- | ----------------------------------- | | `Forward` | Press from front of button | Standard VR interaction | | `Up` | Press from above button | Horizontal surfaces (table-mounted) | *** ## Telemetry ### ELCKTelemetryEventType **What it's for:** Analytics events sent to LCK Dashboard ```cpp theme={null} UENUM(BlueprintType) enum class ELCKTelemetryEventType : uint8 { GameInitialized UMETA(DisplayName = "Game Initialized"), ServiceCreated UMETA(DisplayName = "Service Created"), ServiceDisposed UMETA(DisplayName = "Service Disposed"), CameraEnabled UMETA(DisplayName = "Camera Enabled"), CameraDisabled UMETA(DisplayName = "Camera Disabled"), RecordingStarted UMETA(DisplayName = "Recording Started"), RecordingStopped UMETA(DisplayName = "Recording Stopped"), PhotoCaptured UMETA(DisplayName = "Photo Captured"), PhotoCaptureError UMETA(DisplayName = "Photo Capture Error"), RecorderError UMETA(DisplayName = "Recorder Error"), SdkError UMETA(DisplayName = "SDK Error"), Performance UMETA(DisplayName = "Performance"), StreamingStarted UMETA(DisplayName = "Streaming Started"), StreamingStopped UMETA(DisplayName = "Streaming Stopped"), StreamingError UMETA(DisplayName = "Streaming Error") }; ``` | Value | Description | | ------------------- | ----------------------------- | | `GameInitialized` | Game has initialized the SDK | | `ServiceCreated` | LCK Service instance created | | `ServiceDisposed` | LCK Service instance disposed | | `CameraEnabled` | Camera capture enabled | | `CameraDisabled` | Camera capture disabled | | `RecordingStarted` | Recording started | | `RecordingStopped` | Recording stopped | | `PhotoCaptured` | Photo captured successfully | | `PhotoCaptureError` | Photo capture failed | | `RecorderError` | Recording error occurred | | `SdkError` | General SDK error | | `Performance` | Performance metrics event | | `StreamingStarted` | Live streaming started | | `StreamingStopped` | Live streaming stopped | | `StreamingError` | Live streaming error occurred | Telemetry is automatic. You don't need to manually send these events unless building custom analytics. *** ## Enum Usage Examples ### Quality Selection UI ```cpp theme={null} void AQualitySelector::OnQualityButtonPressed(ELCKVideoQuality Quality) { // Visual feedback switch (Quality) { case ELCKVideoQuality::SD: QualityLabel->SetText(FText::FromString("SD (720p)")); break; case ELCKVideoQuality::HD: QualityLabel->SetText(FText::FromString("HD (1080p)")); break; case ELCKVideoQuality::TWO_K: QualityLabel->SetText(FText::FromString("2K (1440p)")); break; case ELCKVideoQuality::FOUR_K: QualityLabel->SetText(FText::FromString("4K (2160p)")); break; } // Apply via DataModel (not Service) DataModel->SetVideoQuality(Quality); } ``` *** ### Recording State Handler ```cpp theme={null} void ARecordingUI::OnRecordingStateChanged(ELCKRecordingState NewState) { switch (NewState) { case ELCKRecordingState::Idle: RecordButton->SetEnabled(true); RecordButton->SetText(FText::FromString("Start Recording")); ProgressBar->SetVisibility(ESlateVisibility::Collapsed); break; case ELCKRecordingState::Recording: RecordButton->SetEnabled(true); RecordButton->SetText(FText::FromString("Stop Recording")); RecordingIndicator->SetVisibility(ESlateVisibility::Visible); break; case ELCKRecordingState::Saving: RecordButton->SetEnabled(false); ProgressBar->SetVisibility(ESlateVisibility::Visible); StatusText->SetText(FText::FromString("Saving...")); break; case ELCKRecordingState::Error: RecordButton->SetEnabled(true); ShowErrorDialog(ErrorMessage); break; } } ``` *** ## Key Takeaways **ELCKRecordingState** -- Track recording lifecycle (Idle -> Recording -> Saving) **ELCKVideoQuality** -- Predefined quality presets (SD, HD, 2K, 4K) **ELCKAudioChannel** -- Bitwise flags for audio sources (Game, Mic, VoiceChat) **Camera modes use UClass**\* -- ULCKSelfieCameraMode, ULCKFirstPersonCameraMode, ULCKThirdPersonCameraMode **ELCKMicState** -- Handle microphone on/off/no access **Settings live on ULCKTabletDataModel** -- Quality, orientation, mic state, and camera mode are managed through DataModel, not ULCKService *** ## Related * [Types & Structs](/api-reference/unreal/types) -- Struct definitions * [Error Codes](/api-reference/unreal/errors) -- Error handling * [Architecture](/api-reference/unreal/architecture) -- How enums are used in the system # Error Codes (Unreal) Source: https://docs.liv.tv/api-reference/unreal/errors Complete error code reference for LCK SDK operations in Unreal Engine. ## What Problem Does This Solve? When LCK operations fail, you need to know why: * Was it a permission issue? * Storage full? * Already recording? * Invalid configuration? Error codes and messages help you handle failures appropriately—show the right error message, retry the operation, or disable features. ## When to Use This Reference this when: * Handling recording failures * Debugging SDK integration issues * Implementing error recovery logic * Displaying user-friendly error messages * Validating configuration before recording *** ## Error Mechanism There is no `ELCKError` enum in the SDK. Errors are reported through the `FOnRecordingError` delegate on `ULCKService`, which provides a human-readable `FString ErrorMessage` and an `int32 ErrorCode`. The following table documents common error codes and their meanings: | Error | Value | What Happened | How to Fix | | ------------------------- | ----- | --------------------------------------- | ---------------------------------------- | | `None` | 0 | Operation succeeded | - | | `InvalidTrackingId` | 1 | Tracking ID missing or invalid format | Add valid UUID v4 from dashboard | | `RecordingAlreadyStarted` | 2 | Start called while already recording | Check `IsRecording()` first | | `NotCurrentlyRecording` | 3 | Stop called when not recording | Check recording state | | `EncoderNotAvailable` | 4 | Platform encoder failed to init | Check platform requirements, GPU support | | `InsufficientStorage` | 5 | Not enough disk space | Free up space, reduce quality | | `PermissionDenied` | 6 | Mic/storage permission denied (Android) | Request permissions, guide to settings | | `EncodingError` | 7 | Video/audio encoding failure | Check logs, reduce quality | | `TextureError` | 8 | Render texture capture failed | Verify SceneCaptureComponent setup | | `AudioError` | 9 | Audio capture/mixing error | Check audio source availability | | `SaveError` | 10 | Failed to save recording to disk | Check permissions, storage, path | | `InvalidState` | 11 | Operation not valid in current state | Check state before calling | *** ## Error Handling Patterns ### Basic Synchronous Check ```cpp theme={null} bool bSuccess = Service->StartRecording(); if (!bSuccess) { UE_LOG(LogLCK, Error, TEXT("Failed to start recording")); ShowErrorToUser(TEXT("Recording failed")); } ``` *** ### Async Error Handling (Recommended) ```cpp theme={null} Recorder->StartRecordingAsync(FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess) { if (bSuccess) { UE_LOG(LogLCK, Log, TEXT("Recording started successfully")); ShowRecordingIndicator(); } else { UE_LOG(LogLCK, Error, TEXT("Failed to start recording")); ShowErrorDialog(TEXT("Could not start recording. Please try again.")); } })); ``` *** ### Validation Before Recording ```cpp theme={null} void AMyRecorder::AttemptStartRecording() { ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); // 1. Check tracking ID if (!Settings->IsTrackingIdValid()) { UE_LOG(LogLCK, Error, TEXT("Invalid Tracking ID - recording disabled")); ShowError(TEXT("Recording not configured. Please contact support.")); return; } // 2. Validate audio config FLCKAudioConfigValidation AudioValidation = Settings->ValidateAudioConfig(); if (!AudioValidation.bIsValid) { for (const FString& Warning : AudioValidation.Warnings) { UE_LOG(LogLCK, Warning, TEXT("Audio warning: %s"), *Warning); } } // 3. Check if already recording if (Service->IsRecording()) { UE_LOG(LogLCK, Warning, TEXT("Already recording")); return; } // 4. Check storage space int64 FreeSpace = FPlatformMisc::GetDiskFreeSpace(FPaths::ProjectSavedDir()); int64 RequiredSpace = 500 * 1024 * 1024; // 500 MB minimum if (FreeSpace < RequiredSpace) { ShowError(TEXT("Not enough storage space to record")); return; } // 5. Proceed with recording Service->StartRecording(); } ``` *** ## Common Error Scenarios ### InvalidTrackingId **Problem:** The Tracking ID in Project Settings is empty or not in valid UUID v4 format **Symptoms:** * Recording immediately fails * Error log: "Invalid Tracking ID - recording disabled" **Solution:** 1. Get your Tracking ID from [LIV Developer Dashboard](https://dashboard.liv.tv) 2. Go to **Project Settings → Plugins → LCK SDK** 3. Enter the Tracking ID in format: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx` **Validation code:** ```cpp theme={null} ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); if (!Settings->IsTrackingIdValid()) { // Show setup instructions to developer UE_LOG(LogLCK, Error, TEXT("Tracking ID not configured")); } ``` *** ### RecordingAlreadyStarted **Problem:** Attempted to start recording while already recording **Symptoms:** * Start recording fails * Confusing UX (button does nothing) **Solution:** Always check state before starting ```cpp theme={null} if (!Service->IsRecording()) { Service->StartRecording(); } else { UE_LOG(LogLCK, Warning, TEXT("Recording already in progress")); } ``` **Better pattern with UI:** ```cpp theme={null} void ARecordButton::OnPressed() { if (Service->IsRecording()) { // Stop recording Service->StopRecording(); UpdateButtonText(TEXT("Start Recording")); } else { // Start recording Service->StartRecording(); UpdateButtonText(TEXT("Stop Recording")); } } ``` *** ### EncoderNotAvailable **Problem:** Platform-specific encoder failed to initialize **Common reasons:** * **Windows:** Media Foundation not available (rare, usually on old/stripped Windows) * **Android:** Hardware encoder not supported (very old devices) * **Vulkan interop failed** (Android Quest devices) **Solution:** 1. Check platform requirements 2. Verify GPU supports H.264 encoding 3. Enable verbose logging: ```ini theme={null} ; DefaultEngine.ini [Core.Log] LogLCKEncoding=VeryVerbose ``` 4. Look for detailed error in `LogLCKEncoding`: * Windows: HRESULT error codes * Android: MediaCodec configuration errors **Fallback strategy:** ```cpp theme={null} Recorder->StartRecordingAsync(FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess) { if (!bSuccess) { // Try lower quality Service->StartRecording(); Service->StartRecording(); } })); ``` *** ### InsufficientStorage **Problem:** Not enough free disk space for recording **How much space is needed:** * **SD (720p @ 30fps):** \~2 GB per hour * **HD (1080p @ 60fps):** \~5 GB per hour * **2K (1440p @ 60fps):** \~9 GB per hour * **4K (2160p @ 60fps):** \~16 GB per hour **Solution:** ```cpp theme={null} // Check before recording int64 FreeSpace = FPlatformMisc::GetDiskFreeSpace(FPaths::ProjectSavedDir()); int64 MinRequired = 500 * 1024 * 1024; // 500 MB if (FreeSpace < MinRequired) { ShowError(FString::Printf( TEXT("Low storage: Only %d MB free. Need at least 500 MB."), FreeSpace / (1024 * 1024) )); return; } ``` **Recovery options:** 1. Prompt user to free up space 2. Automatically reduce quality profile 3. Clear old recordings *** ### PermissionDenied **Problem:** User denied microphone or storage permission on Android **Android permissions required:** * `RECORD_AUDIO` — Microphone capture * `WRITE_EXTERNAL_STORAGE` — Save recordings (API \< 29) **Solution:** ```cpp theme={null} // Request permissions at app startup (Android) #if PLATFORM_ANDROID void AMyGameMode::BeginPlay() { Super::BeginPlay(); TArray Permissions; Permissions.Add(TEXT("android.permission.RECORD_AUDIO")); // Request permissions UAndroidPermissionFunctionLibrary::RequestPermissions(Permissions); } #endif ``` **Handle denial gracefully:** ```cpp theme={null} if (ErrorCode == 6) // Permission denied { ShowDialog( TEXT("Microphone Access Required"), TEXT("Please enable microphone access in your device settings to record audio."), TEXT("Open Settings"), []() { // Open device settings FPlatformProcess::LaunchURL( TEXT("app-settings:"), nullptr, nullptr ); } ); } ``` *** ## Complete Error Handler Example ```cpp theme={null} UCLASS() class ARecordingErrorHandler : public AActor { GENERATED_BODY() public: UFUNCTION() void HandleRecordingError(FString ErrorMessage, int32 ErrorCode) { UE_LOG(LogLCK, Error, TEXT("Recording error %d: %s"), ErrorCode, *ErrorMessage); // Handle by error message content if (ErrorMessage.Contains(TEXT("Tracking"))) { ShowError(TEXT("Recording not configured. Contact support.")); break; case /* ErrorCode */RecordingAlreadyStarted: UE_LOG(LogLCK, Warning, TEXT("Already recording")); break; case /* ErrorCode */EncoderNotAvailable: ShowError(TEXT("Your device does not support recording.")); TryFallbackQuality(); break; case /* ErrorCode */InsufficientStorage: ShowStorageWarning(); OfferToReduceQuality(); break; case /* ErrorCode */PermissionDenied: ShowPermissionPrompt(); break; case /* ErrorCode */TextureError: UE_LOG(LogLCK, Error, TEXT("Camera capture failed")); ResetCameraCapture(); break; case /* ErrorCode */AudioError: UE_LOG(LogLCK, Error, TEXT("Audio capture failed")); DisableAudioRecording(); break; case /* ErrorCode */SaveError: ShowError(TEXT("Could not save recording. Check storage.")); break; default: ShowError(TEXT("Recording failed. Please try again.")); break; } } private: void ShowStorageWarning() { int64 FreeSpace = FPlatformMisc::GetDiskFreeSpace(FPaths::ProjectSavedDir()); ShowDialog(FString::Printf( TEXT("Low storage: %d MB free. Recording may fail."), FreeSpace / (1024 * 1024) )); } void OfferToReduceQuality() { // Show quality reduction option } void TryFallbackQuality() { Service->StartRecording(); Service->StartRecording(); } }; ``` *** ## Error Logging LCK uses Unreal's logging system with these categories: | Category | What It Logs | | ---------------- | ------------------------------------------- | | `LogLCK` | General SDK operations, state changes | | `LogLCKEncoding` | Video/audio encoding, frame capture, muxing | | `LogLCKAudio` | Audio capture, mixing, source registration | | `LogLCKUI` | UI interactions, button presses | | `LogLCKTablet` | Tablet lifecycle, camera modes | **Enable verbose logging:** ```ini theme={null} ; DefaultEngine.ini [Core.Log] LogLCK=VeryVerbose LogLCKEncoding=VeryVerbose LogLCKAudio=VeryVerbose ``` **What you'll see:** ``` LogLCK: Recording started LogLCKEncoding: Encoder initialized: 1920x1080 @ 60fps, 12Mbps LogLCKAudio: Audio source registered: UnrealAudio LogLCKEncoding: Frame 0 encoded (8.2ms) LogLCK: Recording stopped LogLCKEncoding: Finalizing video file... LogLCK: Recording saved: /Game/Movies/recording_001.mp4 ``` *** ## Key Takeaways **Always check IsOk/bSuccess** — Never assume operations succeed **Validate before recording** — Check tracking ID, storage, state **Handle common cases** — Storage, permissions, state conflicts **User-friendly messages** — Don't show raw error codes to players **Enable logging for debugging** — VeryVerbose shows detailed encoder info *** ## Related * [Enums Reference](/api-reference/unreal/enums) — All enum values including error codes * [Delegates Reference](/api-reference/unreal/delegates) — Error event delegates * [Architecture](/api-reference/unreal/architecture) — How errors flow through the system * [Best Practices](/api-reference/unreal/best-practices) — Error handling patterns # Module Loading Source: https://docs.liv.tv/api-reference/unreal/module-loading LCK SDK module hierarchy, loading phases, and dependencies for Unreal Engine projects. ## What Problem Does This Solve? When integrating LCK into your Unreal project, you need to know: * Which modules to enable (core vs. optional) * Loading order (some modules must load before others) * Platform-specific modules (Windows vs. Android) * Audio plugin priorities (FMOD vs. Wwise vs. Unreal Audio) This page explains module dependencies so you can configure your project correctly. ## When to Read This Read this when: * First-time LCK integration * Adding audio middleware (FMOD, Wwise, Vivox) * Troubleshooting module loading errors * Building for multiple platforms * Creating custom audio sources Skip this if you're just using the default tablet with UnrealAudio. *** ## Module Hierarchy ``` LCKVulkan (EarliestPossible) ← Android Vulkan interop ↓ LCKCore (PostDefault) ← Recording subsystem ├── LCKAudio (PostDefault) ← Audio framework ├── LCKWindowsEncoder (PostDefault, Win64 only) ├── LCKAndroidEncoder (PostDefault, Android only) └── LCKAndroidGallery (PostDefault, Android only) ↓ LCKTablet (Default) ← High-level service └── LCKUI (Default) ← UI components ↓ Optional Audio Plugins (Default): ├── LCKFMOD ├── LCKWwise (Win64, Android) ├── LCKVivox └── LCKOboe (Android only) ``` *** ## Loading Phases Explained | Phase | When It Loads | What It's For | | ------------------ | ---------------------------- | ------------------------------------------- | | `EarliestPossible` | Before engine init | Critical low-level systems (Vulkan interop) | | `PostDefault` | After engine, before game | Core functionality (encoders, audio) | | `Default` | Standard game module loading | UI, high-level systems, optional plugins | **`Never change LCKVulkan loading phase.`** It must load at `EarliestPossible` for Android Vulkan interop to work. Changing it will break Quest recording. *** ## Core Modules (Required) ### LCKCore **Phase:** PostDefault\ **Platforms:** All\ **What it does:** * `ULCKRecorderSubsystem` — Low-level recording control * `ULCKTelemetrySubsystem` — Analytics * `ULCKDeveloperSettings` — Project configuration * Encoder factory interface **Dependencies:** None (base module) **When to use:** Always required. This is the foundation. *** ### LCKAudio **Phase:** PostDefault\ **Platforms:** All\ **What it does:** * `ILCKAudioSource` interface * `FLCKAudioMix` for combining sources * Audio channel management **Dependencies:** LCKCore **When to use:** Always required if you want audio in recordings. *** ### LCKTablet **Phase:** Default\ **Platforms:** All\ **What it does:** * `ULCKSubsystem` — World subsystem * `ULCKService` — High-level recording API * `ALCKTablet` — Tablet actor * `ULCKTabletDataModel` — State management **Dependencies:** LCKCore, LCKUI **When to use:** Use this unless you're building completely custom UI. Most developers want this. *** ### LCKUI **Phase:** Default\ **Platforms:** All\ **What it does:** * `ULCKButton` — Interactive 3D buttons * `ULCKSlider` — Slider controls * Recording state visualization **Dependencies:** LCKCore **When to use:** Use if you want 3D UI components (buttons, sliders) in your world. *** ## Platform-Specific Modules Platform-specific modules are automatically loaded by LCK. **You don't need to manually enable them** in your `.Build.cs` file. ### Windows Only #### LCKWindowsEncoder **Phase:** PostDefault\ **Platforms:** Win64\ **What it does:** Windows Media Foundation encoder (H.264, AAC, MP4) **Technologies:** * `IMFSinkWriter` for muxing * `IMFTransform` for H.264 encoding * Direct3D 11 texture interop **Auto-loaded:** Yes (on Windows builds) *** ### Android Only #### LCKAndroidEncoder **Phase:** PostDefault\ **Platforms:** Android\ **What it does:** NDK MediaCodec encoder (H.264, AAC, MP4) **Technologies:** * `AMediaCodec` for encoding * `AMediaMuxer` for MP4 container * Vulkan/EGL texture interop **Auto-loaded:** Yes (on Android builds) *** #### LCKAndroidGallery **Phase:** PostDefault\ **Platforms:** Android\ **What it does:** Save recordings to Android gallery **Features:** * MediaStore integration * Gallery notifications * Scoped storage support **Auto-loaded:** Yes (on Android builds) *** #### LCKVulkan **Phase:** EarliestPossible\ **Platforms:** Android\ **What it does:** Vulkan interop for Quest devices **Critical:** Must load before engine initialization **Auto-loaded:** Yes (on Android builds) *** ## Optional Audio Plugins Audio plugins are **optional** and must be manually enabled if you want to use them. ### Audio Priority Order When multiple audio plugins are available, LCK uses this priority for **game audio**: 1. **LCKFMOD** (highest priority) 2. **LCKWwise** 3. **LCKUnrealAudio** (built-in, always available) Only ONE game audio source is active at a time. Microphone and voice chat can run alongside game audio. ### LCKFMOD **Phase:** Default\ **Platforms:** Win64, Android\ **Requires:** FMODStudio plugin\ **What it does:** Capture FMOD game audio **Features:** * DSP-based capture from master bus * Zero-copy audio pipeline * Automatic FMOD detection **How to enable:** ```csharp theme={null} // YourGame.Build.cs PublicDependencyModuleNames.Add("LCKFMOD"); ``` *** ### LCKWwise **Phase:** Default\ **Platforms:** Win64, Android\ **Requires:** Wwise plugin\ **What it does:** Capture Wwise game audio **Features:** * Output capture callback * 3rd-order ambisonic support * Stereo downmix **How to enable:** ```csharp theme={null} // YourGame.Build.cs PublicDependencyModuleNames.Add("LCKWwise"); ``` *** ### LCKVivox **Phase:** Default\ **Platforms:** All\ **Requires:** VivoxCore plugin\ **What it does:** Capture Vivox voice chat **Features:** * Microphone capture (outgoing voice) * Incoming voice capture (other players) * Thread-safe callbacks **How to enable:** ```csharp theme={null} // YourGame.Build.cs PublicDependencyModuleNames.Add("LCKVivox"); ``` *** ### LCKOboe (Android Low-Latency Mic) **Phase:** Default\ **Platforms:** Android only\ **What it does:** Google Oboe microphone capture **Features:** * Low-latency audio input * AAudio/OpenSL ES abstraction * Optimized for Quest **How to enable:** ```csharp theme={null} // YourGame.Build.cs if (Target.Platform == UnrealTargetPlatform.Android) { PublicDependencyModuleNames.Add("LCKOboe"); } ``` *** ## Build.cs Configuration ### Minimal Setup (Core Only) ```csharp theme={null} // YourGame.Build.cs public class YourGame : ModuleRules { public YourGame(ReadOnlyTargetRules Target) : base(Target) { // Minimum required PublicDependencyModuleNames.AddRange(new string[] { "LCKCore" // Recording subsystem }); } } ``` This gives you low-level recording via `ULCKRecorderSubsystem`. No UI, no service. *** ### Standard Setup (With Tablet UI) ```csharp theme={null} public class YourGame : ModuleRules { public YourGame(ReadOnlyTargetRules Target) : base(Target) { PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", // Required "LCKTablet", // High-level service "LCKUI" // UI components }); } } ``` This gives you `ULCKService` and the default tablet actor. **Recommended for most developers.** *** ### Full Setup (With Audio Middleware) ```csharp theme={null} public class YourGame : ModuleRules { public YourGame(ReadOnlyTargetRules Target) : base(Target) { PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", "LCKTablet", "LCKUI" }); // Optional: FMOD integration if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Android) { PublicDependencyModuleNames.Add("LCKFMOD"); } // Optional: Vivox voice chat PublicDependencyModuleNames.Add("LCKVivox"); // Optional: Android low-latency mic if (Target.Platform == UnrealTargetPlatform.Android) { PublicDependencyModuleNames.Add("LCKOboe"); } } } ``` *** ## Plugin Configuration (.uproject) ### Basic Plugin Entry ```json theme={null} { "Plugins": [ { "Name": "LCK", "Enabled": true } ] } ``` ### Optional Audio Plugins ```json theme={null} { "Plugins": [ { "Name": "LCK", "Enabled": true }, { "Name": "LCKFMOD", "Enabled": true, "Optional": true, "SupportedTargetPlatforms": ["Win64", "Android"] }, { "Name": "LCKWwise", "Enabled": true, "Optional": true, "SupportedTargetPlatforms": ["Win64", "Android"] } ] } ``` **`Why Optional: true?`** If the plugin isn't present (e.g., FMOD plugin not installed), the game will still launch without errors. *** ## Checking Module Availability at Runtime ```cpp theme={null} // Check if FMOD module is loaded bool bFMODLoaded = FModuleManager::Get().IsModuleLoaded("LCKFMOD"); // Check if Wwise module is loaded bool bWwiseLoaded = FModuleManager::Get().IsModuleLoaded("LCKWwise"); // Get all loaded audio plugins ULCKDeveloperSettings* Settings = ULCKDeveloperSettings::Get(); TArray LoadedPlugins = Settings->GetLoadedAudioPlugins(); for (const FLCKAudioPluginInfo& Plugin : LoadedPlugins) { UE_LOG(LogLCK, Log, TEXT("Audio plugin loaded: %s (priority %d)"), *Plugin.Name, Plugin.Priority); } ``` *** ## Common Configurations ### Quest VR Game (Android) ```csharp theme={null} PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", "LCKTablet", "LCKUI", "LCKOboe" // Low-latency mic }); ``` **Auto-loaded:** LCKVulkan, LCKAndroidEncoder, LCKAndroidGallery *** ### PC VR Game (Windows) ```csharp theme={null} PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", "LCKTablet", "LCKUI" }); ``` **Auto-loaded:** LCKWindowsEncoder *** ### Cross-Platform with FMOD ```csharp theme={null} PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", "LCKTablet", "LCKUI", "LCKFMOD" }); ``` *** ### Multiplayer with Vivox Voice Chat ```csharp theme={null} PublicDependencyModuleNames.AddRange(new string[] { "LCKCore", "LCKTablet", "LCKUI", "LCKVivox" // Capture voice chat }); ``` *** ## Plugin Settings UI LCK provides a visual plugin settings UI in Project Settings: **Location:** Project Settings → Plugins → LCK SDK → Audio Plugins **What you can configure:** * Enable/disable audio plugins * Set audio priorities * View warnings when multiple game audio sources are enabled If you enable multiple game audio plugins (e.g., FMOD + Wwise), LCK will show a warning. Only ONE will be active at runtime based on priority order. *** ## Troubleshooting ### "Module 'LCKFMOD' could not be found" **Problem:** You enabled LCKFMOD but don't have the FMOD plugin installed. **Solution:** Either install FMOD plugin, or remove `LCKFMOD` from your `.Build.cs` and set `"Optional": true` in `.uproject`. *** ### "LCKVulkan failed to load on Android" **Problem:** LCKVulkan loading phase was changed or Vulkan isn't enabled. **Solution:** 1. Verify LCKVulkan loads at `EarliestPossible` (check plugin settings) 2. Enable Vulkan in Android Project Settings → Mobile → Vulkan *** ### Multiple audio sources active **Problem:** You have FMOD, Wwise, and UnrealAudio all enabled. **Solution:** Only ONE game audio source can be active. Check priority order or disable unused plugins. *** ## Key Takeaways **Core is required** — Always include LCKCore **Platform modules auto-load** — Don't manually enable LCKWindowsEncoder/LCKAndroidEncoder **Audio plugins are optional** — Only add what you need (FMOD, Wwise, Vivox) **One game audio source** — FMOD > Wwise > UnrealAudio priority **LCKVulkan must load early** — Never change its loading phase *** ## Related * [Architecture](api-reference/unreal/architecture) — System architecture overview * [Audio Overview](unreal/audio/overview) — Audio system details * [Types & Structs](api-reference/unreal/types) — Data structures # ILCKPacketSink (Unreal) Source: https://docs.liv.tv/api-reference/unreal/packet-sink-interface Abstract packet sink interface for custom streaming transport backends in Unreal Engine. ## What Problem Does This Solve? `ILCKPacketSink` defines the transport layer for live streaming. Once the encoder produces compressed H.264 video and AAC audio packets, something needs to send them — to an RTMP server, a WebRTC peer, or a custom protocol. This interface decouples encoding from transport, so you can stream to any custom backend or send packets to multiple destinations simultaneously. ## When to Use This **Read this if:** * Building a custom streaming backend (RTMP, SRT, WebRTC) * Implementing multi-destination streaming * Debugging packet-level streaming issues **Skip this if:** You're using the built-in LIV streaming. The default `FLCKNativePacketBridge` handles RTMP transport automatically. *** ## Interface Definition ```cpp theme={null} class LCKCORE_API ILCKPacketSink { public: virtual ~ILCKPacketSink(); virtual bool Open(uint32 Width, uint32 Height, uint32 Framerate, uint32 Samplerate, uint32 NumChannels) = 0; virtual void Close() = 0; virtual bool IsOpen() const = 0; virtual void OnVideoFormatReady(const uint8* ExtraData, uint32 ExtraDataSize) = 0; virtual void OnAudioFormatReady(uint32 SampleRate, uint32 NumChannels) = 0; virtual void SendVideoPacket(const uint8* Data, uint32 Size, int64 TimestampMs, bool bIsKeyframe) = 0; virtual void SendAudioPacket(const uint8* Data, uint32 Size, int64 TimestampMs) = 0; }; ``` *** ## Methods ### Open ```cpp theme={null} virtual bool Open(uint32 Width, uint32 Height, uint32 Framerate, uint32 Samplerate, uint32 NumChannels) = 0; ``` Initialize the sink and prepare for packet delivery. | Parameter | Type | Description | | ------------- | -------- | --------------------------------- | | `Width` | `uint32` | Video width in pixels | | `Height` | `uint32` | Video height in pixels | | `Framerate` | `uint32` | Target video framerate | | `Samplerate` | `uint32` | Audio sample rate in Hz | | `NumChannels` | `uint32` | Audio channel count (typically 2) | **Returns:** `true` if the sink opened successfully. ### OnVideoFormatReady ```cpp theme={null} virtual void OnVideoFormatReady(const uint8* ExtraData, uint32 ExtraDataSize) = 0; ``` Called when H.264 codec extra data (SPS/PPS in AVCDecoderConfigurationRecord format) is available. Store and transmit this before any video frames. ### OnAudioFormatReady ```cpp theme={null} virtual void OnAudioFormatReady(uint32 SampleRate, uint32 NumChannels) = 0; ``` Called when audio encoder format parameters are finalized. ### SendVideoPacket ```cpp theme={null} virtual void SendVideoPacket(const uint8* Data, uint32 Size, int64 TimestampMs, bool bIsKeyframe) = 0; ``` Deliver a compressed H.264 video packet. | Parameter | Type | Description | | ------------- | -------------- | -------------------------------------- | | `Data` | `const uint8*` | H.264 NAL unit data | | `Size` | `uint32` | Packet size in bytes | | `TimestampMs` | `int64` | Presentation timestamp in milliseconds | | `bIsKeyframe` | `bool` | `true` if this is an IDR frame | ### SendAudioPacket ```cpp theme={null} virtual void SendAudioPacket(const uint8* Data, uint32 Size, int64 TimestampMs) = 0; ``` Deliver a compressed AAC audio packet (raw AAC, no ADTS header). ### Close / IsOpen ```cpp theme={null} virtual void Close() = 0; virtual bool IsOpen() const = 0; ``` Shut down the sink and release resources. `IsOpen()` returns current state. *** ## Registering a Packet Sink Add sinks to the encoder at runtime via `ILCKEncoder`: ```cpp theme={null} Encoder->AddPacketSink(MySink.Get()); // When done: Encoder->RemovePacketSink(MySink.Get()); ``` Call `RemovePacketSink()` before destroying the sink. On Windows, late-attached sinks receive cached codec headers automatically. *** ## Thread Safety `SendVideoPacket` and `SendAudioPacket` are called from the **encoder thread**, not the game thread. Your implementation must be thread-safe. `Open` and `Close` are called from the game thread. *** ## Implementation Example ```cpp theme={null} class FMyPacketSink : public ILCKPacketSink { public: virtual bool Open(uint32 Width, uint32 Height, uint32 Framerate, uint32 Samplerate, uint32 NumChannels) override { bOpen = ConnectToServer(Width, Height, Framerate); return bOpen; } virtual void OnVideoFormatReady(const uint8* ExtraData, uint32 ExtraDataSize) override { CachedSPSPPS.SetNum(ExtraDataSize); FMemory::Memcpy(CachedSPSPPS.GetData(), ExtraData, ExtraDataSize); SendStreamHeader(CachedSPSPPS); } virtual void OnAudioFormatReady(uint32 SampleRate, uint32 NumChannels) override { SendAudioHeader(SampleRate, NumChannels); } virtual void SendVideoPacket(const uint8* Data, uint32 Size, int64 TimestampMs, bool bIsKeyframe) override { // Called from encoder thread SendToServer(Data, Size, TimestampMs, bIsKeyframe); } virtual void SendAudioPacket(const uint8* Data, uint32 Size, int64 TimestampMs) override { SendToServer(Data, Size, TimestampMs); } virtual void Close() override { DisconnectFromServer(); bOpen = false; } virtual bool IsOpen() const override { return bOpen; } private: TArray CachedSPSPPS; bool bOpen = false; }; ``` *** ## Key Takeaways **Transport abstraction** — Decouples encoding from delivery **H.264 + AAC** — Packets are compressed, ready to transmit **Thread safety required** — Packet methods called from encoder thread **Multiple sinks** — Encoder supports multiple simultaneous sinks *** ## Related * [Encoder Interface](/api-reference/unreal/encoder-interface) — Encoder that feeds this sink * [Streaming Feature Interface](/api-reference/unreal/streaming-feature-interface) — High-level streaming control * [Streaming Subsystem](/api-reference/unreal/streaming-subsystem) — Blueprint-friendly streaming API # ULCKService (Unreal) Source: https://docs.liv.tv/api-reference/unreal/service-interface Main public-facing service for LCK recording, photo capture, and audio management in Unreal Engine. ## What Problem Does This Solve? `ULCKService` is your main entry point for LCK functionality: * Start/stop/pause/resume recording * Configure recording resolution, framerate, and bitrate * Control microphone audio capture * Take photos * Monitor recording state and errors Instead of interacting with low-level subsystems, you use this high-level service that handles the complexity for you. ## When to Use This Use `ULCKService` when: * Building custom recording UI * Implementing record buttons, settings menus * Controlling recording from gameplay code * Integrating recording into your game flow **Don't use this directly if:** You're using the default LCK Tablet UI—it already handles everything. *** ## Accessing the Service ### From C++ ```cpp theme={null} ULCKSubsystem* Subsystem = GetWorld()->GetSubsystem(); if (Subsystem) { ULCKService* Service = Subsystem->GetService(); // Use service... } ``` ### From Blueprint ```cpp theme={null} UFUNCTION(BlueprintCallable, Category = "LCK") ULCKService* GetLCKService() { if (UWorld* World = GetWorld()) { if (ULCKSubsystem* Subsystem = World->GetSubsystem()) { return Subsystem->GetService(); } } return nullptr; } ``` **Blueprint usage:** Blueprint Get Service *** ## Recording Methods ### StartRecording / StopRecording ```cpp theme={null} // Start recording — returns true if recording started successfully bool bSuccess = Service->StartRecording(); if (!bSuccess) { UE_LOG(LogLCK, Error, TEXT("Failed to start recording")); } // Stop recording — returns void Service->StopRecording(); ``` | Method | Returns | Description | | ------------------ | ------- | -------------------------------------------------------------------------- | | `StartRecording()` | `bool` | Starts recording. Returns true on success. Only valid when in Idle state. | | `StopRecording()` | `void` | Stops the current recording. Only valid when in Recording or Paused state. | For async recording with callbacks and progress tracking, use `ULCKRecorderSubsystem::StartRecordingAsync()` and `StopRecordingAsync()` directly. See [ULCKRecorderSubsystem](/api-reference/unreal/recorder-subsystem) for details. *** ### PauseRecording / ResumeRecording ```cpp theme={null} // Pause the current recording Service->PauseRecording(); // Resume a paused recording Service->ResumeRecording(); ``` *** ### IsRecording / GetCurrentRecordingDuration ```cpp theme={null} // Check if currently recording bool bIsRecording = Service->IsRecording(); // Get recording duration in seconds float Duration = Service->GetCurrentRecordingDuration(); // Update UI if (bIsRecording) { FTimespan Time = FTimespan::FromSeconds(Duration); TimerText->SetText(FText::Format( LOCTEXT("RecordingTime", "{0}:{1:02}"), Time.GetMinutes(), Time.GetSeconds() )); } ``` *** ### GetRecordingState ```cpp theme={null} ELCKRecordingState State = Service->GetRecordingState(); switch (State) { case ELCKRecordingState::Idle: // Ready to record break; case ELCKRecordingState::Recording: // Currently recording break; // ... } ``` *** ## Photo Capture ```cpp theme={null} // Take a photo Service->TakePhoto(); ``` The photo is captured from the current scene capture component. There is no completion delegate on `ULCKService` for photo saves. *** ## Recording Settings ### ApplyRecordingSettings Configure resolution, framerate, and bitrate for recordings: ```cpp theme={null} Service->ApplyRecordingSettings( 1920, // Width 1080, // Height 30, // Framerate 8 << 20, // Video bitrate (8 Mbps) 256 << 10, // Audio bitrate (256 Kbps) 48000 // Sample rate (optional, defaults to 48000) ); ``` | Parameter | Type | Description | | -------------- | ------- | ---------------------------------- | | `Width` | `int32` | Video width in pixels | | `Height` | `int32` | Video height in pixels | | `Framerate` | `int32` | Frames per second | | `VideoBitrate` | `int32` | Video bitrate in bits per second | | `AudioBitrate` | `int32` | Audio bitrate in bits per second | | `Samplerate` | `int32` | Audio sample rate (default: 48000) | Quality and orientation presets (SD, HD, Landscape, Portrait) are managed via `ULCKTabletDataModel`, not `ULCKService`. See the [Data Model Events](#state-tracking-via-data-model) section below. *** ## Audio Control ### Microphone ```cpp theme={null} // Enable/disable microphone Service->SetMicrophoneEnabled(true); // Get current state bool bMicEnabled = Service->IsMicrophoneEnabled(); // Get microphone audio level (0.0 to 1.0) float MicLevel = Service->GetCurrentMicrophoneAudioLevel(); ``` **Volume indicator example:** ```cpp theme={null} void AMicIndicator::Tick(float DeltaTime) { Super::Tick(DeltaTime); float Volume = Service->GetCurrentMicrophoneAudioLevel(); VolumeBar->SetPercent(Volume); // Visual feedback if (Volume > 0.7f) { VolumeBar->SetFillColorAndOpacity(FLinearColor::Red); } else if (Volume > 0.3f) { VolumeBar->SetFillColorAndOpacity(FLinearColor::Yellow); } else { VolumeBar->SetFillColorAndOpacity(FLinearColor::Green); } } ``` `GetCurrentMicrophoneAudioLevel()` is not exposed to Blueprint. It is available from C++ only. *** ## Preview Mode Preview mode (camera output without recording to disk) is managed by `ULCKRecorderSubsystem`, not `ULCKService`. ```cpp theme={null} // Access via the recorder subsystem directly ULCKRecorderSubsystem* Recorder = GetWorld()->GetSubsystem(); Recorder->StartPreview(); Recorder->StopPreview(); ``` See [ULCKRecorderSubsystem](/api-reference/unreal/recorder-subsystem) for details. *** ## Delegates on ULCKService `ULCKService` exposes the following delegates directly: | Delegate | Type | Description | | ------------------------- | ---------------------------------------------------------- | ----------------------------------------------- | | `OnRecordingError` | `FOnRecordingError(FString ErrorMessage, int32 ErrorCode)` | Fired when a recording error occurs | | `OnCameraModeChanged` | `FOnCameraModeChanged(UClass* ModeClass)` | Fired when the camera mode class changes | | `OnRecordingSaveFinished` | `FOnRecordingSaveFinished(bool Success)` | Fired when the async save process completes | | `OnRecordingSaveProgress` | `FOnRecordingSaveProgress(float Progress)` | Fired during save with progress from 0.0 to 1.0 | **Example: Subscribing to delegates** ```cpp theme={null} // Error handling Service->OnRecordingError.AddDynamic(this, &UMyComponent::HandleRecordingError); // Save completion Service->OnRecordingSaveFinished.AddDynamic(this, &UMyComponent::HandleSaveFinished); // Save progress Service->OnRecordingSaveProgress.AddDynamic(this, &UMyComponent::HandleSaveProgress); // Handlers UFUNCTION() void HandleRecordingError(FString ErrorMessage, int32 ErrorCode) { UE_LOG(LogLCK, Error, TEXT("Recording error %d: %s"), ErrorCode, *ErrorMessage); } UFUNCTION() void HandleSaveFinished(bool bSuccess) { if (bSuccess) { ShowNotification(TEXT("Recording saved!")); } } UFUNCTION() void HandleSaveProgress(float Progress) { ProgressBar->SetPercent(Progress); } ``` *** ## State Tracking via Data Model For UI-level state notifications (recording state changes, quality changes, orientation changes, camera mode switches), use `ULCKTabletDataModel`. ### Get the Data Model ```cpp theme={null} void UMyComponent::BeginPlay() { Super::BeginPlay(); // Find tablet in world ALCKTablet* Tablet = Cast( UGameplayStatics::GetActorOfClass(GetWorld(), ALCKTablet::StaticClass()) ); if (Tablet) { ULCKTabletDataModel* DataModel = Tablet->GetDataModel(); // Subscribe to recording state changes DataModel->OnRecordStateChanged.AddUObject( this, &UMyComponent::HandleStateChanged ); } } ``` The recording state delegate is `OnRecordStateChanged` (type `FOnRecordStateChange`), not `OnRecordingStateChanged`. These are non-dynamic multicast delegates — use `AddUObject` or `AddLambda`, not `AddDynamic`. *** ### Available Data Model Events | Event | Delegate Type | Parameter | Description | | ---------------------------- | ----------------------------- | ----------------------- | ----------------------------------------------- | | `OnRecordStateChanged` | `FOnRecordStateChange` | `ELCKRecordingState` | Recording state changed (Idle, Recording, etc.) | | `OnTabletCameraModeChanged` | `FOnTabletCameraModeChanged` | `UClass*` | Camera mode class changed | | `OnMicStateChanged` | `FOnMicStateChanged` | `ELCKMicState` | Microphone state changed (On, Off, No\_Access) | | `OnVideoQualityChanged` | `FOnVideoQualityChanged` | `ELCKVideoQuality` | Video quality preset changed | | `OnScreenOrientationChanged` | `FOnScreenOrientationChanged` | `ELCKScreenOrientation` | Screen orientation changed | | `OnMicLevelChanged` | `FOnMicLevelChanged` | `float` | Microphone audio level updated | **Example: State-driven UI** ```cpp theme={null} void URecordingUI::HandleStateChanged(ELCKRecordingState NewState) { switch (NewState) { case ELCKRecordingState::Idle: RecordButton->SetText(FText::FromString("Start Recording")); RecordButton->SetIsEnabled(true); ProgressPanel->SetVisibility(ESlateVisibility::Collapsed); break; case ELCKRecordingState::Recording: RecordButton->SetText(FText::FromString("Stop Recording")); RecordButton->SetIsEnabled(true); RecordingIndicator->SetVisibility(ESlateVisibility::Visible); break; case ELCKRecordingState::Saving: RecordButton->SetIsEnabled(false); ProgressPanel->SetVisibility(ESlateVisibility::Visible); StatusText->SetText(FText::FromString("Saving...")); break; case ELCKRecordingState::Error: RecordButton->SetIsEnabled(true); ShowErrorDialog(); break; } } ``` *** ## Streaming Methods `ULCKService` also provides streaming functionality when the streaming feature is available: | Method | Returns | Description | | -------------------------- | --------- | ---------------------------------------------- | | `IsStreamingAvailable()` | `bool` | Whether the streaming feature plugin is loaded | | `StartLogin()` | `void` | Begin the streaming login/pairing flow | | `CancelLogin()` | `void` | Cancel an in-progress login | | `Logout()` | `void` | Log out of the streaming service | | `StartStreaming()` | `bool` | Start streaming; returns true on success | | `StopStreaming()` | `void` | Stop the current stream | | `IsStreaming()` | `bool` | Whether currently streaming | | `IsAuthenticated()` | `bool` | Whether logged in to the streaming service | | `LaunchHub()` | `void` | Launch the LIV Hub companion app | | `IsHubInstalled()` | `bool` | Whether the LIV Hub app is installed | | `GetLastLogoutReason()` | `FString` | Reason for the last logout | | `GetStreamingTargetName()` | `FString` | Name of the current streaming target | ### Streaming Delegates These are non-dynamic multicast delegates (use `AddUObject` or `AddLambda`): | Delegate | Type | Description | | -------------------------- | ----------------------------------------- | ---------------------------------- | | `OnStreamingPairingCode` | `FOnStreamingPairingCode(const FString&)` | Pairing code received during login | | `OnStreamingAuthenticated` | `FOnStreamingAuthenticated()` | Successfully authenticated | | `OnStreamingStarted` | `FOnStreamingStarted()` | Streaming session started | | `OnStreamingStopped` | `FOnStreamingStopped()` | Streaming session stopped | | `OnStreamingError` | `FOnStreamingError(const FString&)` | Streaming error occurred | | `OnStreamingLoggedOut` | `FOnStreamingLoggedOut()` | Logged out of streaming service | | `OnStreamingConfigChanged` | `FOnStreamingConfigChanged()` | Streaming configuration changed | *** ## Complete Example: Recording Manager ```cpp theme={null} UCLASS() class URecordingManager : public UActorComponent { GENERATED_BODY() protected: UPROPERTY() ULCKService* Service; UPROPERTY() ULCKTabletDataModel* DataModel; public: virtual void BeginPlay() override { Super::BeginPlay(); // Get service Service = GetLCKService(); if (!Service) { UE_LOG(LogLCK, Error, TEXT("LCK Service not available")); return; } // Get data model for state tracking ALCKTablet* Tablet = FindTablet(); if (Tablet) { DataModel = Tablet->GetDataModel(); DataModel->OnRecordStateChanged.AddUObject( this, &URecordingManager::OnStateChanged ); } // Subscribe to service events Service->OnRecordingSaveFinished.AddDynamic( this, &URecordingManager::OnSaveFinished ); Service->OnRecordingError.AddDynamic( this, &URecordingManager::OnError ); } UFUNCTION(BlueprintCallable) void ToggleRecording() { if (Service->IsRecording()) { Service->StopRecording(); } else { bool bSuccess = Service->StartRecording(); if (!bSuccess) { UE_LOG(LogLCK, Error, TEXT("Failed to start recording")); } } } void OnStateChanged(ELCKRecordingState NewState) { // Update UI based on state BroadcastStateChange(NewState); } UFUNCTION() void OnSaveFinished(bool bSuccess) { if (bSuccess) { ShowNotification(TEXT("Recording saved!")); } else { ShowError(TEXT("Failed to save recording")); } } UFUNCTION() void OnError(FString ErrorMessage, int32 ErrorCode) { UE_LOG(LogLCK, Error, TEXT("Recording error %d: %s"), ErrorCode, *ErrorMessage); ShowErrorDialog(ErrorMessage); } private: ULCKService* GetLCKService() { if (UWorld* World = GetWorld()) { if (ULCKSubsystem* Subsystem = World->GetSubsystem()) { return Subsystem->GetService(); } } return nullptr; } ALCKTablet* FindTablet() { return Cast( UGameplayStatics::GetActorOfClass(GetWorld(), ALCKTablet::StaticClass()) ); } }; ``` *** ## Key Takeaways **Get via ULCKSubsystem** — Don't create or cache manually **Check IsRecording()** — Before changing settings or starting **Use DataModel for state events** — Subscribe to `OnRecordStateChanged` on `ULCKTabletDataModel` **Preview lives on ULCKRecorderSubsystem** — Not on ULCKService **Quality and orientation are on DataModel** — Use `ULCKTabletDataModel` to change presets *** ## Related * [Delegates Reference](/api-reference/unreal/delegates) — All event delegates * [Error Codes](/api-reference/unreal/errors) — Error handling * [Enums Reference](/api-reference/unreal/enums) — All enum values * [Architecture](/api-reference/unreal/architecture) — How ULCKService fits into the system # ILCKStreamingFeature (Unreal) Source: https://docs.liv.tv/api-reference/unreal/streaming-feature-interface Modular feature interface for live streaming backends in Unreal Engine. ## What Problem Does This Solve? `ILCKStreamingFeature` abstracts live streaming so multiple backends can coexist: * LIV cloud streaming (built-in) * Custom RTMP backends * Third-party streaming services The interface uses Unreal's modular features system, allowing streaming implementations to register at runtime without compile-time dependencies. ## When to Use This **Read this if:** * Building a custom streaming backend * Integrating a third-party streaming service * Understanding how `ULCKService` discovers streaming providers **Skip this if:** You're using the built-in streaming subsystem. Use `ULCKStreamingSubsystem` directly. *** ## Interface Definition ```cpp theme={null} class LCKCORE_API ILCKStreamingFeature : public IModularFeature { public: virtual ~ILCKStreamingFeature() = default; DECLARE_MULTICAST_DELEGATE_OneParam(FOnPairingCode, const FString&); DECLARE_MULTICAST_DELEGATE(FOnAuthenticated); DECLARE_MULTICAST_DELEGATE(FOnStreamStarted); DECLARE_MULTICAST_DELEGATE(FOnStreamStopped); DECLARE_MULTICAST_DELEGATE_OneParam(FOnStreamError, const FString&); DECLARE_MULTICAST_DELEGATE(FOnLoggedOut); DECLARE_MULTICAST_DELEGATE(FOnStreamingConfigChanged); FOnPairingCode OnPairingCodeDelegate; FOnAuthenticated OnAuthenticatedDelegate; FOnStreamStarted OnStreamStartedDelegate; FOnStreamStopped OnStreamStoppedDelegate; FOnStreamError OnStreamErrorDelegate; FOnLoggedOut OnLoggedOutDelegate; FOnStreamingConfigChanged OnStreamingConfigChangedDelegate; static FName GetModularFeatureName() noexcept; virtual void StartLogin() = 0; virtual void CancelLogin() = 0; virtual void Logout() = 0; virtual bool IsAuthenticated() const = 0; virtual bool StartStreaming() = 0; virtual void StopStreaming() = 0; virtual bool IsStreaming() const = 0; virtual bool IsStartingOrStreaming() const; virtual void LaunchHub() = 0; virtual bool IsHubInstalled() const; virtual FString GetStreamingTargetName() const; virtual FString GetLastLogoutReason() const; virtual int32 GetPriority() const; }; ``` *** ## Delegates | Delegate | Signature | Description | | ---------------------------------- | -------------------------------------- | ---------------------------------------- | | `OnPairingCodeDelegate` | `FOnPairingCode(const FString& Code)` | New pairing code generated during login | | `OnAuthenticatedDelegate` | `FOnAuthenticated()` | User successfully authenticated | | `OnStreamStartedDelegate` | `FOnStreamStarted()` | Live stream has started | | `OnStreamStoppedDelegate` | `FOnStreamStopped()` | Live stream ended normally | | `OnStreamErrorDelegate` | `FOnStreamError(const FString& Error)` | Streaming error occurred | | `OnLoggedOutDelegate` | `FOnLoggedOut()` | User logged out or was logged out | | `OnStreamingConfigChangedDelegate` | `FOnStreamingConfigChanged()` | Streaming configuration changed remotely | *** ## Methods ### Authentication | Method | Signature | Description | | ----------------- | ------------------------------------------ | ------------------------------ | | `StartLogin` | `virtual void StartLogin() = 0` | Begin device-code login flow | | `CancelLogin` | `virtual void CancelLogin() = 0` | Cancel in-progress login | | `Logout` | `virtual void Logout() = 0` | Log out and clear credentials | | `IsAuthenticated` | `virtual bool IsAuthenticated() const = 0` | Check if user is authenticated | ### Streaming | Method | Signature | Description | | ----------------------- | -------------------------------------------- | ------------------------------------------------------------------ | | `StartStreaming` | `virtual bool StartStreaming() = 0` | Start the live stream (returns false on failure) | | `StopStreaming` | `virtual void StopStreaming() = 0` | Stop the live stream | | `IsStreaming` | `virtual bool IsStreaming() const = 0` | Check if currently streaming | | `IsStartingOrStreaming` | `virtual bool IsStartingOrStreaming() const` | True during startup or active streaming (default: `IsStreaming()`) | ### Platform & Utility | Method | Signature | Default | Description | | ------------------------ | ------------------------------------------------ | ------- | ------------------------------------------ | | `LaunchHub` | `virtual void LaunchHub() = 0` | — | Open the LIV Hub companion app | | `IsHubInstalled` | `virtual bool IsHubInstalled() const` | `true` | Check if LIV Hub is installed | | `GetStreamingTargetName` | `virtual FString GetStreamingTargetName() const` | `""` | Display name of streaming target | | `GetLastLogoutReason` | `virtual FString GetLastLogoutReason() const` | `""` | Reason for last automatic logout | | `GetPriority` | `virtual int32 GetPriority() const` | `0` | Priority when multiple backends registered | *** ## Discovery via Modular Features The modular feature name is `"LCKStreamingFeature"`. Streaming features are discovered at runtime: ```cpp theme={null} #include "LCKStreamingFeature.h" ILCKStreamingFeature* FindStreamingFeature() { auto& ModularFeatures = IModularFeatures::Get(); FName FeatureName = ILCKStreamingFeature::GetModularFeatureName(); if (ModularFeatures.IsModularFeatureAvailable(FeatureName)) { TArray Features = ModularFeatures.GetModularFeatureImplementations( FeatureName); // Sort by priority (highest first) Features.Sort([](const ILCKStreamingFeature& A, const ILCKStreamingFeature& B) { return A.GetPriority() > B.GetPriority(); }); return Features.Num() > 0 ? Features[0] : nullptr; } return nullptr; } ``` *** ## Usage Example ```cpp theme={null} void UMyStreamingWidget::StartStream() { ILCKStreamingFeature* Feature = FindStreamingFeature(); if (!Feature) return; Feature->OnPairingCodeDelegate.AddLambda([this](const FString& Code) { ShowPairingCode(Code); }); Feature->OnStreamStartedDelegate.AddLambda([this]() { ShowLiveIndicator(); }); if (!Feature->IsAuthenticated()) { Feature->StartLogin(); } else { Feature->StartStreaming(); } } ``` *** ## Key Takeaways **Modular features** — Runtime discovery, no compile-time coupling **Priority system** — Multiple backends can coexist, highest priority wins **Delegate-driven** — Subscribe to events for async state changes **Auth before stream** — Always check `IsAuthenticated()` before `StartStreaming()` *** ## Related * [Streaming Subsystem](/api-reference/unreal/streaming-subsystem) — Blueprint-friendly wrapper * [Streaming Types](/api-reference/unreal/streaming-types) — Enums and data types * [Packet Sink Interface](/api-reference/unreal/packet-sink-interface) — Custom transport layer * [Encoder Interface](/api-reference/unreal/encoder-interface) — Video/audio encoding # ULCKStreamingSubsystem (Unreal) Source: https://docs.liv.tv/api-reference/unreal/streaming-subsystem Game instance subsystem for live streaming with Blueprint support in Unreal Engine. ## What Problem Does This Solve? `ULCKStreamingSubsystem` is the Blueprint-friendly entry point for live streaming: * Authenticate with LIV's streaming service via device pairing * Start and stop live streams to YouTube, Twitch, or custom RTMP targets * Monitor stream state, errors, and configuration changes It implements `ILCKStreamingFeature` and exposes everything as `BlueprintCallable` / `BlueprintAssignable`. ## When to Use This Use `ULCKStreamingSubsystem` when: * Building a "Go Live" button in your game * Creating a streaming dashboard UI * Integrating streaming into your game flow **Don't use this directly if:** You're building a custom streaming backend — implement `ILCKStreamingFeature` instead. *** ## Accessing the Subsystem ```cpp theme={null} ULCKStreamingSubsystem* Streaming = GetGameInstance()->GetSubsystem(); ``` *** ## Delegates All delegates are `BlueprintAssignable`. | Delegate | Signature | Description | | --------------------------- | ------------------------------------------------------------------------------------- | ------------------------------- | | `OnPairingCodeReceived` | `FOnLCKPairingCodeReceived(const FString&, Code)` | Pairing code ready for display | | `OnAuthenticated` | `FOnLCKAuthenticated()` | User successfully authenticated | | `OnStreamingConfigReceived` | `FOnLCKStreamingConfigReceived(ELCKStreamingTargetType, const FLCKUserSubscription&)` | Config loaded from server | | `OnStreamStarted` | `FOnLCKStreamStarted()` | Live stream has started | | `OnStreamStopped` | `FOnLCKStreamStopped()` | Live stream ended normally | | `OnStreamError` | `FOnLCKStreamError(const FString&, ErrorMessage)` | Streaming error occurred | | `OnLoggedOut` | `FOnLCKLoggedOut()` | User logged out | *** ## Authentication Methods | Method | Return | Description | | ------------------- | --------- | ----------------------------------------------- | | `StartLogin()` | `void` | Begin device-code login flow (polls every 2.5s) | | `CancelLogin()` | `void` | Cancel in-progress login | | `GetPairingCode()` | `FString` | Current pairing code (BlueprintPure) | | `IsAuthenticated()` | `bool` | Check if authenticated (BlueprintPure) | | `Logout()` | `void` | Log out and clear credentials | ## Configuration Methods | Method | Return | Description | | -------------------------------------- | ------------------------- | ------------------------------------------------------ | | `RefreshStreamingConfig(bool bSilent)` | `void` | Refresh config from server (bSilent suppresses errors) | | `HasStreamingTarget()` | `bool` | Has YouTube/Twitch/Manual target? (BlueprintPure) | | `HasActiveSubscription()` | `bool` | Has active LIV subscription? (BlueprintPure) | | `GetStreamingTargetType()` | `ELCKStreamingTargetType` | Current target type (BlueprintPure) | ## Streaming Methods | Method | Return | Description | | ------------------------- | ------ | --------------------------------------- | | `StartStreaming()` | `bool` | Start live stream (15s timeout) | | `StopStreaming()` | `void` | Stop live stream | | `IsStreaming()` | `bool` | Is currently streaming? (BlueprintPure) | | `IsStartingOrStreaming()` | `bool` | Starting or streaming? | ## Platform Methods | Method | Return | Description | | -------------------------- | --------- | -------------------------------- | | `LaunchHub()` | `void` | Open LIV Hub (Android only) | | `IsHubInstalled()` | `bool` | Is LIV Hub installed? | | `GetStreamingTargetName()` | `FString` | Display name (e.g., "YouTube") | | `GetLastLogoutReason()` | `FString` | Reason for last automatic logout | *** ## Streaming Bitrate Presets ### YouTube | Quality | Video Bitrate | Audio Bitrate | | ------- | ------------- | ------------- | | SD | 4 Mbps | 128 Kbps | | HD @30 | 8 Mbps | 128 Kbps | | HD @60 | 12 Mbps | 128 Kbps | | 2K | 24 Mbps | 128 Kbps | | 4K | 35 Mbps | 128 Kbps | ### Twitch | Quality | Video Bitrate | Audio Bitrate | | ------- | ------------- | ------------- | | SD | 3 Mbps | 96 Kbps | | HD @30 | 4.5 Mbps | 96 Kbps | | HD @60 | 6 Mbps | 96 Kbps | | 2K | 9 Mbps | 96 Kbps | | 4K | 10 Mbps | 96 Kbps | Keyframe interval is fixed at 2 seconds. Bitrates are applied automatically based on the streaming target and quality profile. *** ## Timing Constants | Constant | Value | Description | | ---------------------- | ----- | ------------------------------------ | | Auth poll interval | 2.5s | Pairing confirmation polling | | Stream start timeout | 15s | Maximum RTMP connection wait | | Health check interval | 5s | Encoder health monitoring | | Health check tolerance | 2 | Consecutive failures before stopping | *** ## Usage Example ```cpp theme={null} UCLASS() class UStreamingManager : public UActorComponent { GENERATED_BODY() public: virtual void BeginPlay() override { Super::BeginPlay(); auto* Streaming = GetWorld()->GetGameInstance() ->GetSubsystem(); if (!Streaming) return; Streaming->OnPairingCodeReceived.AddDynamic( this, &UStreamingManager::OnCode); Streaming->OnAuthenticated.AddDynamic( this, &UStreamingManager::OnAuth); Streaming->OnStreamStarted.AddDynamic( this, &UStreamingManager::OnLive); Streaming->OnStreamError.AddDynamic( this, &UStreamingManager::OnError); } UFUNCTION(BlueprintCallable) void Login() { auto* S = GetWorld()->GetGameInstance() ->GetSubsystem(); S->StartLogin(); } UFUNCTION(BlueprintCallable) void ToggleStream() { auto* S = GetWorld()->GetGameInstance() ->GetSubsystem(); if (S->IsStreaming()) S->StopStreaming(); else if (S->HasStreamingTarget()) S->StartStreaming(); } private: UFUNCTION() void OnCode(const FString& Code) { ShowPairingUI(Code); } UFUNCTION() void OnAuth() { ShowStreamControls(); } UFUNCTION() void OnLive() { ShowLiveIndicator(); } UFUNCTION() void OnError(const FString& E) { ShowError(E); } }; ``` *** ## Key Takeaways **GameInstance subsystem** — Available globally, survives level transitions **Blueprint-ready** — All methods and delegates are Blueprint accessible **Auth-first flow** — Login, configure target, then stream **Check prerequisites** — Verify `IsAuthenticated()` and `HasStreamingTarget()` before streaming *** ## Related * [Streaming Feature Interface](/api-reference/unreal/streaming-feature-interface) — Underlying C++ interface * [Streaming Types](/api-reference/unreal/streaming-types) — Enums and data types * [Packet Sink Interface](/api-reference/unreal/packet-sink-interface) — Custom transport layer * [Project Settings](/unreal/core-sdk/project-settings) — Enable streaming via `bEnableStreaming` # Streaming Types (Unreal) Source: https://docs.liv.tv/api-reference/unreal/streaming-types Enums and structs used by the LCK streaming system in Unreal Engine. ## Overview These types define the data structures used across the LCK streaming API for checking streaming targets, authentication state, login flow data, and subscription status. *** ## ELCKStreamingTargetType The type of streaming destination configured by the user. ```cpp theme={null} UENUM(BlueprintType) enum class ELCKStreamingTargetType : uint8 { None UMETA(DisplayName = "None"), YouTube UMETA(DisplayName = "YouTube"), Twitch UMETA(DisplayName = "Twitch"), Manual UMETA(DisplayName = "Manual RTMP") }; ``` | Value | Description | | --------- | ------------------------------ | | `None` | No streaming target configured | | `YouTube` | Streaming to YouTube Live | | `Twitch` | Streaming to Twitch | | `Manual` | Custom RTMP URL | *** ## ELCKStreamingState The current state of the streaming UI system (defined in LCKUI). ```cpp theme={null} UENUM(BlueprintType) enum class ELCKStreamingState : uint8 { Idle, Pairing, Paired, Streaming, Error, MAX }; ``` | Value | Description | | ----------- | ------------------------------ | | `Idle` | No active streaming session | | `Pairing` | Device login in progress | | `Paired` | Authenticated, ready to stream | | `Streaming` | Live stream is active | | `Error` | An error occurred | *** ## FLCKUserSubscription Represents a user's LIV subscription status. ```cpp theme={null} USTRUCT(BlueprintType) struct LCKSTREAMING_API FLCKUserSubscription { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") FString Sku; UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") bool bIsActive = false; }; ``` | Field | Type | Description | | ----------- | --------- | ---------------------------------------------- | | `Sku` | `FString` | Subscription SKU identifier | | `bIsActive` | `bool` | `true` if the subscription is currently active | *** ## FLCKLoginAttempt Data for an in-progress device-code login attempt. ```cpp theme={null} USTRUCT(BlueprintType) struct LCKSTREAMING_API FLCKLoginAttempt { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") FString Code; UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") FString Id; UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") FString ExpiresAt; }; ``` | Field | Type | Description | | ----------- | --------- | ----------------------------------------------------- | | `Code` | `FString` | The pairing code the user enters at the pairing URL | | `Id` | `FString` | Internal identifier for this login attempt | | `ExpiresAt` | `FString` | ISO 8601 timestamp for when this pairing code expires | *** ## FLCKAuthState Represents the current authentication state. ```cpp theme={null} USTRUCT(BlueprintType) struct LCKSTREAMING_API FLCKAuthState { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") FString UserId; UPROPERTY(BlueprintReadOnly, Category = "LCK|Streaming") bool bIsAuthenticated = false; }; ``` | Field | Type | Description | | ------------------ | --------- | --------------------------------------------- | | `UserId` | `FString` | The authenticated user's unique identifier | | `bIsAuthenticated` | `bool` | `true` if the user is currently authenticated | *** ## Key Takeaways **All types are BlueprintType** — Usable in Blueprint graphs **Check target type** — Show appropriate platform icon in UI **Subscription gating** — Check `HasActiveSubscription()` before enabling streaming **ExpiresAt is ISO 8601** — Parse if you need a countdown timer *** ## Related * [Streaming Subsystem](/api-reference/unreal/streaming-subsystem) — Uses these types * [Streaming Feature Interface](/api-reference/unreal/streaming-feature-interface) — C++ interface * [Enums Reference](/api-reference/unreal/enums) — All LCK enums * [Types Reference](/api-reference/unreal/types) — Core LCK types # Types & Structs (Unreal) Source: https://docs.liv.tv/api-reference/unreal/types Common structs and type definitions used across the LCK SDK for Unreal Engine. ## What Problem Does This Solve? When working with LCK, you'll pass configuration structs to methods and handle interaction data from UI. This page documents: * Recording parameters (resolution, bitrate, framerate) * Camera mode settings (FOV, smoothness, distance) * UI interaction data (touch events, button states) * Color palette constants * Audio source channel configuration and validation * Audio plugin runtime information * Telemetry event types Understanding these types helps you configure recording quality, customize camera behavior, build custom UI, and inspect audio system state. ## When to Use This Reference this when: * Configuring recording parameters * Setting up camera modes * Handling UI events * Styling custom UI components * Working with audio channels and source configuration * Validating audio plugin configuration before recording * Querying registered audio plugin capabilities * Working with telemetry events *** ## Recording Configuration ### FLCKRecorderParams **What it's for:** Configure video/audio recording settings ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKRecorderParams { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Width = 1920; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Height = 1080; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Framerate = 30; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 VideoBitrate = 2 << 20; // 2097152 (~2 Mbps) UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 Samplerate = 48000; UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 AudioBitrate = 256 << 10; // 262144 (~256 Kbps) }; ``` | Field | Type | Default | Description | Range | | -------------- | ------- | -------------------- | ----------------------- | ------------ | | `Width` | `int32` | 1920 | Video width in pixels | 640-3840 | | `Height` | `int32` | 1080 | Video height in pixels | 480-2160 | | `Framerate` | `int32` | 30 | Frames per second | 15-120 | | `VideoBitrate` | `int32` | `2 << 20` (2097152) | Video bitrate in bps | 500k-100M | | `Samplerate` | `int32` | 48000 | Audio sample rate in Hz | 44100, 48000 | | `AudioBitrate` | `int32` | `256 << 10` (262144) | Audio bitrate in bps | 64k-512k | **Common use:** ```cpp theme={null} // HD recording at 60 FPS FLCKRecorderParams Params; Params.Width = 1920; Params.Height = 1080; Params.Framerate = 60; Params.VideoBitrate = 12 << 20; // 12 Mbps Params.AudioBitrate = 256000; // 256 Kbps Recorder->SetupRecorder(Params, CaptureComponent); ``` *** ### FLCKRecordingProfileSettings **What it's for:** Predefined quality profiles (configured in Project Settings) ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKRecordingProfileSettings { GENERATED_BODY() UPROPERTY(VisibleAnywhere, BlueprintReadOnly) int32 Width = 1280; UPROPERTY(VisibleAnywhere, BlueprintReadOnly) int32 Height = 720; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "15", ClampMax = "120")) int32 Framerate = 30; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "500000", ClampMax = "100000000")) int32 VideoBitrate = 4000000; // 4 Mbps UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ClampMin = "64000", ClampMax = "512000")) int32 AudioBitrate = 128000; // 128 Kbps UPROPERTY(BlueprintReadOnly) int32 Samplerate = 48000; }; ``` **Typical profiles:** | Quality | Resolution | Bitrate | FPS | Use Case | | ------- | ---------- | ------- | --- | ------------------------- | | SD | 1280×720 | 4 Mbps | 30 | Mobile, performance mode | | HD | 1920×1080 | 12 Mbps | 60 | Standard quality | | 2K | 2560×1440 | 20 Mbps | 60 | High quality | | 4K | 3840×2160 | 35 Mbps | 60 | Maximum quality (PC only) | *** ## Camera Mode Settings ### FLCKSelfieModeDefaults **What it's for:** Default settings for Selfie camera mode ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKSelfieModeDefaults { GENERATED_BODY() UPROPERTY(EditAnywhere, meta = (ClampMin = "20.0", ClampMax = "120.0")) float FOV = 80.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "100.0")) float Smoothness = 50.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "0.5", ClampMax = "10.0")) float FollowDistance = 2.0f; UPROPERTY(EditAnywhere) bool bFollowEnabled = false; }; ``` | Field | Type | Default | Description | Range | | ---------------- | ------- | ------- | ------------------------------ | -------- | | `FOV` | `float` | 80.0 | Field of view in degrees | 20-120 | | `Smoothness` | `float` | 50.0 | Camera movement smoothing | 0-100 | | `FollowDistance` | `float` | 2.0 | Distance from player in meters | 0.5-10.0 | | `bFollowEnabled` | `bool` | false | Auto-follow player movement | - | **Example:** ```cpp theme={null} // Selfie mode with auto-follow FLCKSelfieModeDefaults Selfie; Selfie.FOV = 75.0f; Selfie.Smoothness = 60.0f; Selfie.FollowDistance = 1.5f; Selfie.bFollowEnabled = true; ``` *** ### FLCKFirstPersonModeDefaults **What it's for:** Default settings for First Person camera mode ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKFirstPersonModeDefaults { GENERATED_BODY() UPROPERTY(EditAnywhere, meta = (ClampMin = "20.0", ClampMax = "120.0")) float FOV = 90.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "100.0")) float Smoothness = 75.0f; }; ``` | Field | Type | Default | Description | | ------------ | ------- | ------- | ----------------------------- | | `FOV` | `float` | 90.0 | Field of view (wider for FPS) | | `Smoothness` | `float` | 75.0 | Camera lag for head movement | **Typical values:** * **Low smoothness (0-30):** Sharp, responsive (competitive FPS) * **Medium smoothness (40-70):** Balanced (standard FPS) * **High smoothness (80-100):** Cinematic feel (story-driven) *** ### FLCKThirdPersonModeDefaults **What it's for:** Default settings for Third Person camera mode ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKThirdPersonModeDefaults { GENERATED_BODY() UPROPERTY(EditAnywhere, meta = (ClampMin = "20.0", ClampMax = "120.0")) float FOV = 90.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", ClampMax = "100.0")) float Smoothness = 100.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "0.5", ClampMax = "10.0")) float Distance = 2.0f; UPROPERTY(EditAnywhere, meta = (ClampMin = "-90.0", ClampMax = "90.0")) float PitchAngle = -30.0f; }; ``` | Field | Type | Default | Description | | ------------ | ------- | ------- | ----------------------------------- | | `FOV` | `float` | 90.0 | Field of view | | `Smoothness` | `float` | 100.0 | Camera lag (very smooth) | | `Distance` | `float` | 2.0 | Distance behind player in meters | | `PitchAngle` | `float` | -30.0 | Vertical angle (-90 to +90 degrees) | **Example:** ```cpp theme={null} // Over-the-shoulder camera FLCKThirdPersonModeDefaults ThirdPerson; ThirdPerson.FOV = 85.0f; ThirdPerson.Distance = 1.5f; ThirdPerson.PitchAngle = -20.0f; // Slightly above shoulder ThirdPerson.Smoothness = 90.0f; ``` *** ## UI Interaction Data ### FLCKTapData **What it's for:** Touch/pointer interaction event data from UI buttons ```cpp theme={null} USTRUCT() struct LCKUI_API FLCKTapData { GENERATED_BODY() UPROPERTY() FVector ButtonLocation = FVector::ZeroVector; UPROPERTY() FVector ButtonRightVector = FVector::ZeroVector; UPROPERTY() FVector ButtonForwardVector = FVector::ZeroVector; UPROPERTY() FVector TapLocation = FVector::ZeroVector; UPROPERTY() bool IsPressed = false; }; ``` | Field | Type | Description | | --------------------- | --------- | ----------------------------------- | | `ButtonLocation` | `FVector` | World position of the button | | `ButtonRightVector` | `FVector` | Button's local right direction | | `ButtonForwardVector` | `FVector` | Button's local forward direction | | `TapLocation` | `FVector` | World position of tap/click | | `IsPressed` | `bool` | Whether button is currently pressed | **When you'll see this:** Inside button interaction callbacks ```cpp theme={null} Button->OnTapStarted.AddDynamic(this, &AMyActor::HandleButtonTap); void AMyActor::HandleButtonTap(const FLCKTapData& TapData) { UE_LOG(LogTemp, Log, TEXT("Button at %s pressed"), *TapData.ButtonLocation.ToString()); } ``` *** ## UI Styling ### FLCKColor **What it's for:** Standard color palette for LCK UI components ```cpp theme={null} USTRUCT() struct LCKUI_API FLCKColor { static constexpr FColor Primary {8, 8, 8}; // #080808 static constexpr FColor PrimaryText {220, 220, 220}; // #DCDCDC static constexpr FColor Secondary {16, 16, 16}; // #101010 static constexpr FColor Disabled {96, 96, 96}; // #606060 static constexpr FColor ButtonDefault {40, 40, 40}; // #282828 static constexpr FColor ButtonIconDefault {220, 220, 220}; // #DCDCDC static constexpr FColor ButtonActive {94, 69, 255}; // #5E45FF static constexpr FColor Debug {255, 0, 255}; // #FF00FF static constexpr FColor Alert {255, 42, 42}; // #FF2A2A static constexpr FColor Success {32, 196, 64}; // #20C440 static constexpr FColor White {255, 255, 255}; // #FFFFFF static constexpr FColor Black {0, 0, 0}; // #000000 }; ``` | Color | RGB | Hex | Use Case | | --------------- | --------------- | --------- | --------------------------- | | `Primary` | (8, 8, 8) | `#080808` | Main background | | `PrimaryText` | (220, 220, 220) | `#DCDCDC` | Primary text | | `Secondary` | (16, 16, 16) | `#101010` | Secondary background | | `Disabled` | (96, 96, 96) | `#606060` | Disabled UI elements | | `ButtonDefault` | (40, 40, 40) | `#282828` | Default button color | | `ButtonActive` | (94, 69, 255) | `#5E45FF` | Active/pressed button | | `Alert` | (255, 42, 42) | `#FF2A2A` | Recording indicator, errors | | `Success` | (32, 196, 64) | `#20C440` | Success state | **Example usage:** ```cpp theme={null} // Set button color based on state if (bIsRecording) { ButtonMaterial->SetVectorParameterValue( TEXT("BaseColor"), FLinearColor(FLCKColor::Alert) ); } else { ButtonMaterial->SetVectorParameterValue( TEXT("BaseColor"), FLinearColor(FLCKColor::ButtonDefault) ); } ``` *** ## Audio Channel Bitmask ### TLCKAudioChannelsMask **What it's for:** Combine multiple audio channels using bitwise operations ```cpp theme={null} typedef uint64 TLCKAudioChannelsMask; ``` **Usage:** ```cpp theme={null} // Combine game audio + microphone TLCKAudioChannelsMask Channels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; // Check if mask contains microphone bool HasMic = (Channels & ELCKAudioChannel::Microphone) != 0; // Start capture with both channels AudioSource->StartCapture(Channels); ``` **Common combinations:** ```cpp theme={null} // Game audio only TLCKAudioChannelsMask GameOnly = ELCKAudioChannel::Game; // Microphone only TLCKAudioChannelsMask MicOnly = ELCKAudioChannel::Microphone; // Game + Mic (typical recording) TLCKAudioChannelsMask Standard = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; // Game + Mic + Voice chat TLCKAudioChannelsMask AllChannels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone | ELCKAudioChannel::VoiceChat; ``` *** ## Button UI Types ### ELCKButtonType **What it's for:** Define button shape and size for 3D UI ```cpp theme={null} UENUM(BlueprintType) enum class ELCKButtonType : uint8 { Square UMETA(DisplayName = "Square"), Rectangle UMETA(DisplayName = "Rectangle"), Tab UMETA(DisplayName = "Tab"), Selector UMETA(DisplayName = "Selector") }; ``` | Type | Box Extent (cm) | Aspect Ratio | Use Case | | ----------- | --------------- | ------------ | ---------------------------- | | `Square` | (0.4, 2.4, 2.4) | 1:1 | Icons, single-action buttons | | `Rectangle` | (0.4, 6.0, 2.4) | 2.5:1 | Text buttons, labels | | `Tab` | (0.4, 4.4, 2.4) | 1.83:1 | Tab navigation | | `Selector` | (2.4, 4.4, 0.8) | 5.5:1 | Sliders, selectors | *** ## Complete Configuration Example ```cpp theme={null} void AMyRecorder::SetupRecording() { // 1. Configure recorder params FLCKRecorderParams Params; Params.Width = 1920; Params.Height = 1080; Params.Framerate = 60; Params.VideoBitrate = 10 << 20; // 10 Mbps Params.AudioBitrate = 256000; // 256 Kbps // 2. Set up camera mode FLCKThirdPersonModeDefaults ThirdPerson; ThirdPerson.FOV = 85.0f; ThirdPerson.Distance = 2.0f; ThirdPerson.PitchAngle = -25.0f; ThirdPerson.Smoothness = 90.0f; // 3. Configure audio channels TLCKAudioChannelsMask Channels = ELCKAudioChannel::Game | ELCKAudioChannel::Microphone; // 4. Start recording ULCKRecorderSubsystem* Recorder = GetWorld()->GetSubsystem(); Recorder->SetupRecorder(Params, CaptureComponent); Recorder->StartRecording(); } ``` *** ## Audio Source Channel Presets ### FLCKAudioSourceChannels **What it's for:** Default channel configuration enabling both microphone and game audio capture. ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioSourceChannels { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) bool bMicrophoneEnabled = true; UPROPERTY(EditAnywhere, BlueprintReadWrite) bool bGameAudioEnabled = true; }; ``` | Field | Type | Default | Description | | -------------------- | ------ | ------- | ------------------------- | | `bMicrophoneEnabled` | `bool` | `true` | Enable microphone capture | | `bGameAudioEnabled` | `bool` | `true` | Enable game audio capture | *** ### FLCKAudioSourceGameOnly **What it's for:** Channel preset for sources that only provide game audio (e.g., FMOD, Wwise). ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioSourceGameOnly { GENERATED_BODY() UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Audio") bool bGameAudioEnabled = true; }; ``` | Field | Type | Default | Description | | ------------------- | ------ | ------- | ------------------------- | | `bGameAudioEnabled` | `bool` | `true` | Enable game audio capture | *** ### FLCKAudioSourceMicOnly **What it's for:** Channel preset for sources that only provide microphone input (e.g., Oboe on Android). ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioSourceMicOnly { GENERATED_BODY() UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Audio") bool bMicrophoneEnabled = true; }; ``` | Field | Type | Default | Description | | -------------------- | ------ | ------- | ------------------------- | | `bMicrophoneEnabled` | `bool` | `true` | Enable microphone capture | *** ### FLCKAudioSourceVoiceChat **What it's for:** Channel preset for voice chat sources that capture microphone and incoming voice audio (e.g., Vivox). ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioSourceVoiceChat { GENERATED_BODY() UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Audio") bool bMicrophoneEnabled = true; UPROPERTY(Config, EditAnywhere, BlueprintReadWrite, Category = "Audio") bool bVoiceAudioEnabled = true; }; ``` | Field | Type | Default | Description | | -------------------- | ------ | ------- | --------------------------- | | `bMicrophoneEnabled` | `bool` | `true` | Outgoing microphone capture | | `bVoiceAudioEnabled` | `bool` | `true` | Incoming voice chat audio | *** ## Audio Configuration Validation ### FLCKAudioConfigValidation **What it's for:** Result of validating the current audio plugin configuration. Returned by the audio system to report issues such as missing microphone sources or duplicate game audio plugins. ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioConfigValidation { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "LCK") bool bIsValid = true; UPROPERTY(BlueprintReadOnly, Category = "LCK") TArray Warnings; UPROPERTY(BlueprintReadOnly, Category = "LCK") ELCKGameAudioType ActiveGameAudio = ELCKGameAudioType::None; UPROPERTY(BlueprintReadOnly, Category = "LCK") bool bHasMicrophoneSource = false; UPROPERTY(BlueprintReadOnly, Category = "LCK") int32 MicrophoneSourceCount = 0; UPROPERTY(BlueprintReadOnly, Category = "LCK") TArray EnabledMicrophoneSources; }; ``` | Field | Type | Default | Description | | -------------------------- | ------------------- | ------- | ----------------------------------------------- | | `bIsValid` | `bool` | `true` | Whether the audio config is valid for recording | | `Warnings` | `TArray` | `[]` | Human-readable warning messages | | `ActiveGameAudio` | `ELCKGameAudioType` | `None` | The active game audio middleware type | | `bHasMicrophoneSource` | `bool` | `false` | Whether at least one mic source is available | | `MicrophoneSourceCount` | `int32` | `0` | Number of registered microphone sources | | `EnabledMicrophoneSources` | `TArray` | `[]` | Names of all enabled microphone sources | **Common use:** ```cpp theme={null} FLCKAudioConfigValidation Validation = AudioSystem->ValidateConfig(); if (!Validation.bIsValid) { for (const FString& Warning : Validation.Warnings) { UE_LOG(LogLCK, Warning, TEXT("Audio config: %s"), *Warning); } } ``` *** ## Audio Plugin Info ### FLCKAudioPluginInfo **What it's for:** Describes a registered audio plugin and its capabilities. Used by the audio system to enumerate available plugins at runtime. ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKAudioPluginInfo { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "LCK") FString ModuleName; UPROPERTY(BlueprintReadOnly, Category = "LCK") FString DisplayName; UPROPERTY(BlueprintReadOnly, Category = "LCK") bool bIsLoaded = false; UPROPERTY(BlueprintReadOnly, Category = "LCK") bool bMicrophoneEnabled = false; UPROPERTY(BlueprintReadOnly, Category = "LCK") bool bGameAudioEnabled = false; }; ``` | Field | Type | Default | Description | | -------------------- | --------- | ------- | ------------------------------------------- | | `ModuleName` | `FString` | `""` | Module name (e.g., `"LCKFMOD"`) | | `DisplayName` | `FString` | `""` | Human-readable name (e.g., `"FMOD Studio"`) | | `bIsLoaded` | `bool` | `false` | Whether the module is currently loaded | | `bMicrophoneEnabled` | `bool` | `false` | Plugin supports microphone capture | | `bGameAudioEnabled` | `bool` | `false` | Plugin supports game audio capture | *** ## Telemetry Types ### FLCKTelemetryValue **What it's for:** A variant value type used in telemetry events to hold different data types in a single field. ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKTelemetryValue { GENERATED_BODY() UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") int32 IntValue = 0; UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") float FloatValue = 0.0f; UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") FString StringValue; UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") bool BoolValue = false; UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") uint8 ValueType = 0; // 0 = Int, 1 = Float, 2 = String, 3 = Bool }; ``` | Field | Type | Default | Description | | ------------- | --------- | ------- | ------------------------------------------------------- | | `IntValue` | `int32` | `0` | Integer payload (used when `ValueType == "Int"`) | | `FloatValue` | `float` | `0.0` | Float payload (used when `ValueType == "Float"`) | | `StringValue` | `FString` | `""` | String payload (used when `ValueType == "String"`) | | `BoolValue` | `bool` | `false` | Boolean payload (used when `ValueType == "Bool"`) | | `ValueType` | `uint8` | `0` | Discriminator: 0 = Int, 1 = Float, 2 = String, 3 = Bool | *** ### FLCKTelemetryEvent **What it's for:** Represents a single telemetry event with a type identifier and a map of contextual key-value pairs. ```cpp theme={null} USTRUCT(BlueprintType) struct FLCKTelemetryEvent { GENERATED_BODY() UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") ELCKTelemetryEventType EventType = ELCKTelemetryEventType::GameInitialized; UPROPERTY(BlueprintReadWrite, Category = "LCK Telemetry") TMap Context; }; ``` | Field | Type | Default | Description | | ----------- | ----------------------------------- | ----------------- | --------------------------------- | | `EventType` | `ELCKTelemetryEventType` | `GameInitialized` | Event type enum value | | `Context` | `TMap` | `{}` | Key-value pairs of event metadata | **Common use:** ```cpp theme={null} FLCKTelemetryEvent Event; Event.EventType = ELCKTelemetryEventType::RecordingStarted; FLCKTelemetryValue ResolutionValue; ResolutionValue.StringValue = TEXT("1920x1080"); ResolutionValue.ValueType = 2; // String Event.Context.Add(TEXT("Resolution"), ResolutionValue); ``` *** ## Key Takeaways **FLCKRecorderParams** — Configure video/audio quality **Camera mode structs** — Customize camera behavior (FOV, smoothness, distance) **FLCKTapData** — Handle UI button interactions **FLCKColor** — Use standard color palette for consistency **TLCKAudioChannelsMask** — Combine audio channels with bitwise OR **Audio channel presets** — FLCKAudioSourceChannels, GameOnly, MicOnly, VoiceChat **FLCKAudioConfigValidation** — Validate audio plugin configuration before recording **FLCKAudioPluginInfo** — Enumerate registered audio plugins at runtime **Telemetry types** — FLCKTelemetryValue and FLCKTelemetryEvent for analytics *** ## Related * [Enums Reference](/api-reference/unreal/enums) — All enum values * [Architecture](/api-reference/unreal/architecture) — How these types are used * [Recording Guide](/unreal/core-sdk/recording) — Practical recording setup # Getting the LIV SDK Source: https://docs.liv.tv/mixed-reality-capture/home/getting-the-liv-sdk The LIV SDKs can be downloaded through our [Developer Portal](https://dev.liv.tv/). The process is the same whether you need the Unity or Unreal SDK.