> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liv.tv/llms.txt
> Use this file to discover all available pages before exploring further.

# State Management

> How recording states, visual feedback, and UI state synchronization work in the LIV Camera Kit (LCK) SDK for Unreal Engine, including ELCKRecordingState, state transitions, and subscribing to state changes.

## Overview

LCK UI components automatically reflect the current recording state. This page covers recording state enumeration, visual feedback per state, state transitions, and synchronizing custom UI with LCK state.

***

## ELCKRecordingState

The recording system has six states:

| State        | Description          | UI Behavior                          |
| ------------ | -------------------- | ------------------------------------ |
| `Idle`       | Ready to record      | Default colors, all buttons enabled  |
| `Recording`  | Actively capturing   | Red record indicator, timer visible  |
| `Saving`     | Writing file to disk | Progress indicator visible           |
| `Processing` | Finalizing video     | Progress indicator, buttons disabled |
| `Error`      | Something went wrong | Error indicator, retry available     |
| `Paused`     | Recording paused     | Pause indicator, resume available    |

```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 Machine

```
                    StartRecording()
           ┌────────────────────────────┐
           │                            │
           v                            │
     ┌─────────┐                  ┌─────────┐
     │  Idle   │                  │Recording│◄──────┐
     └─────────┘                  └─────────┘       │
           ^                       │      │        │
           │                Pause()│      │Resume()│
           │                       v      │        │
           │                  ┌──────────┐│        │
           │                  │  Paused  │┘        │
           │                  └──────────┘         │
           │                            │          │
           │         StopRecording()    │          │
           │                            v          │
     ┌─────────┐                  ┌──────────┐     │
     │  Error  │<─────────────────│  Saving  │     │
     └─────────┘   Save Failed    └──────────┘     │
           │                            │          │
           │                            v          │
           │                    ┌──────────┐       │
           │                    │Processing│       │
           │                    └──────────┘       │
           │                            │          │
           └────────────────────────────┴──────────┘
                     Complete
```

***

## Visual Feedback

### Default State Colors

| State      | Primary Color | Recommended Hex       |
| ---------- | ------------- | --------------------- |
| Idle       | Blue/Gray     | `#5E45FF` / `#808080` |
| Recording  | Red           | `#FF4444`             |
| Saving     | Yellow/Orange | `#FFAA00`             |
| Processing | Yellow/Orange | `#FFAA00`             |
| Error      | Red/Dark      | `#CC0000`             |
| Paused     | Gray/Blue     | `#666699`             |

### Recording Indicator

During recording, the UI shows a pulsing red record dot, recording duration timer, and microphone level meter.

```cpp theme={null}
void ULCKRecordingIndicator::UpdateRecordingState(ELCKRecordingState State)
{
    switch (State)
    {
        case ELCKRecordingState::Idle:
            RecordDot->SetVisibility(ESlateVisibility::Hidden);
            TimerText->SetVisibility(ESlateVisibility::Hidden);
            ProgressBar->SetVisibility(ESlateVisibility::Hidden);
            break;

        case ELCKRecordingState::Recording:
            RecordDot->SetVisibility(ESlateVisibility::Visible);
            TimerText->SetVisibility(ESlateVisibility::Visible);
            StartPulseAnimation();
            break;

        case ELCKRecordingState::Paused:
            RecordDot->SetVisibility(ESlateVisibility::Visible);
            TimerText->SetVisibility(ESlateVisibility::Visible);
            StopPulseAnimation();  // Static indicator when paused
            break;

        case ELCKRecordingState::Saving:
        case ELCKRecordingState::Processing:
            RecordDot->SetVisibility(ESlateVisibility::Hidden);
            ProgressBar->SetVisibility(ESlateVisibility::Visible);
            break;

        case ELCKRecordingState::Error:
            ErrorIcon->SetVisibility(ESlateVisibility::Visible);
            break;
    }
}
```

***

## Button State Transitions

### Cooldown Timer

Buttons have a cooldown to prevent accidental double-presses:

```cpp theme={null}
// Default cooldown: 0.25 seconds
static constexpr float ButtonCooldownTime = 0.25f;

void ULCKButton::OnPressed()
{
    if (bIsOnCooldown)
    {
        return;  // Ignore press during cooldown
    }

    // Execute button action
    OnButtonPressed.Broadcast();

    // Start cooldown
    bIsOnCooldown = true;
    GetWorld()->GetTimerManager().SetTimer(
        CooldownHandle,
        this,
        &ULCKButton::EndCooldown,
        ButtonCooldownTime
    );
}
```

### Two-Phase Interaction

LCK buttons use a two-phase interaction model: Overlap Begin (hand enters button collision) and Overlap End (hand exits button collision).

```cpp theme={null}
void ULCKButton::OnOverlapBegin(AActor* OtherActor)
{
    // Validate it's a hand
    if (!OtherActor->ActorHasTag(TEXT("Hand")))
    {
        return;
    }

    // Check touch direction (dot product validation)
    FVector TouchDirection = GetTouchDirection(OtherActor);
    if (FVector::DotProduct(TouchDirection, GetForwardVector()) > 0.5f)
    {
        // Valid front-facing touch
        SetState(ELCKButtonState::Pressed);
        OnPressed();
    }
}
```

***

## Subscribing to State Changes

### ULCKTabletDataModel

The tablet data model provides state change delegates:

```cpp theme={null}
// Get data model
ULCKTabletDataModel* DataModel = Tablet->GetDataModel();

// Subscribe to state changes (non-dynamic delegate — use AddLambda or AddUObject)
DataModel->OnRecordStateChanged.AddLambda([this](ELCKRecordingState NewState)
{
    switch (NewState)
    {
        case ELCKRecordingState::Recording:
            // Show recording UI
            break;
        case ELCKRecordingState::Saving:
        case ELCKRecordingState::Processing:
            // Show progress bar
            break;
        case ELCKRecordingState::Paused:
            // Show pause indicator
            break;
        case ELCKRecordingState::Error:
            // Show error message
            break;
        case ELCKRecordingState::Idle:
            // Reset to default state
            break;
    }
});

// Or bind to a UObject member function
DataModel->OnRecordStateChanged.AddUObject(this, &UMyComponent::HandleStateChange);
```

### Reactive Properties

The data model uses non-dynamic multicast delegates that broadcast on change:

```cpp theme={null}
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRecordStateChange, ELCKRecordingState);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnMicStateChanged, ELCKMicState);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnScreenOrientationChanged, ELCKScreenOrientation);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnVideoQualityChanged, ELCKVideoQuality);
```

<Warning>
  These are **non-dynamic** delegates. Use `AddLambda()` or `AddUObject()` to subscribe — NOT `AddDynamic()`.
</Warning>

***

## Synchronizing Custom UI

### Example: Custom Recording Indicator

```cpp theme={null}
UCLASS()
class UCustomRecordingUI : public UUserWidget
{
    GENERATED_BODY()

protected:
    virtual void NativeConstruct() override
    {
        Super::NativeConstruct();

        // Find tablet and subscribe
        ALCKTablet* Tablet = FindLCKTablet();
        if (Tablet)
        {
            ULCKTabletDataModel* DataModel = Tablet->GetDataModel();
            DataModel->OnRecordStateChanged.AddUObject(
                this, &UCustomRecordingUI::OnStateChanged
            );
        }
    }

    void OnStateChanged(ELCKRecordingState NewState)
    {
        // Update custom UI
        RecordingIcon->SetBrushFromTexture(
            GetIconForState(NewState)
        );

        StatusText->SetText(
            GetTextForState(NewState)
        );
    }

private:
    UPROPERTY(meta = (BindWidget))
    UImage* RecordingIcon;

    UPROPERTY(meta = (BindWidget))
    UTextBlock* StatusText;
};
```

### Example: Duration Timer

```cpp theme={null}
void URecordingTimer::Tick(float DeltaTime)
{
    if (ULCKService* Service = GetLCKService())
    {
        if (Service->IsRecording())
        {
            float Duration = Service->GetCurrentRecordingDuration();

            // Format as MM:SS
            int32 Minutes = FMath::FloorToInt(Duration / 60.0f);
            int32 Seconds = FMath::FloorToInt(FMath::Fmod(Duration, 60.0f));

            TimerText->SetText(FText::FromString(
                FString::Printf(TEXT("%02d:%02d"), Minutes, Seconds)
            ));
        }
    }
}
```

***

## State Persistence

Recording state does NOT persist across sessions. On game start, state is always `Idle`.

Settings that DO persist: last used camera mode, last used quality profile, and audio configuration.

```cpp theme={null}
// Load saved settings on startup
void ALCKTablet::BeginPlay()
{
    Super::BeginPlay();

    // Recording state always starts Idle
    DataModel->SetRecordingState(ELCKRecordingState::Idle);

    // But camera mode is loaded from saved settings
    DataModel->SetCameraMode(LoadSavedCameraMode());
}
```

***

## Error Handling

### Error State Recovery

```cpp theme={null}
void ULCKTabletDataModel::HandleRecordingError(ELCKError Error)
{
    // Transition to error state
    SetRecordingState(ELCKRecordingState::Error);

    // Broadcast error for UI to display
    OnRecordingError.Broadcast(Error);

    // After timeout, return to Idle
    FTimerHandle Handle;
    GetWorld()->GetTimerManager().SetTimer(
        Handle,
        [this]() { SetRecordingState(ELCKRecordingState::Idle); },
        3.0f,   // 3 second error display
        false
    );
}
```

***

## See Also

<CardGroup cols={2}>
  <Card title="Customization" icon="palette" href="/unreal/tablet-customization/customization">
    Customize UI appearance
  </Card>

  <Card title="Core SDK API Reference" icon="code" href="/api-reference/unreal/core-sdk">
    Complete API documentation
  </Card>
</CardGroup>
