What Problem Does This Solve?
ULCKService is your main entry point for LCK functionality:
- Start/stop recording
- Configure quality settings
- Control audio capture (mic, game audio)
- Switch camera modes
- Take photos
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++
ULCKSubsystem* Subsystem = GetWorld()->GetSubsystem<ULCKSubsystem>();
if (Subsystem)
{
ULCKService* Service = Subsystem->GetService();
// Use service...
}
From Blueprint
UFUNCTION(BlueprintCallable, Category = "LCK")
ULCKService* GetLCKService()
{
if (UWorld* World = GetWorld())
{
if (ULCKSubsystem* Subsystem = World->GetSubsystem<ULCKSubsystem>())
{
return Subsystem->GetService();
}
}
return nullptr;
}
Blueprint usage:
Recording Methods
StartRecording / StopRecording (Synchronous)
// Start recording
bool bSuccess = Service->StartRecording();
if (!bSuccess)
{
UE_LOG(LogLCK, Error, TEXT("Failed to start recording"));
}
// Stop recording
bool bSuccess = Service->StopRecording();
Returns: bool — True if operation succeeded
Synchronous methods don’t provide detailed error feedback. Use async methods for better error handling.
StartRecordingAsync / StopRecordingAsync (Recommended)
// Start with callback
Service->StartRecordingAsync(
FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess)
{
if (bSuccess)
{
ShowRecordingIndicator();
UE_LOG(LogLCK, Log, TEXT("Recording started"));
}
else
{
ShowError(TEXT("Failed to start recording"));
}
})
);
// Stop with progress callback
Service->StopRecordingAsync(
FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess)
{
if (bSuccess)
{
ShowNotification(TEXT("Recording saved!"));
}
}),
FOnLCKRecorderProgress::CreateLambda([this](float Progress)
{
// Progress: 0.0 to 1.0
ProgressBar->SetPercent(Progress);
})
);
Why async is better:
- Get detailed success/failure callbacks
- Track save progress
- Non-blocking—doesn’t freeze game
- Better error handling
IsRecording / GetRecordingDuration
// Check if currently recording
bool bIsRecording = Service->IsRecording();
// Get recording duration in seconds
float Duration = Service->GetRecordingDuration();
// Update UI
if (bIsRecording)
{
FTimespan Time = FTimespan::FromSeconds(Duration);
TimerText->SetText(FText::Format(
LOCTEXT("RecordingTime", "{0}:{1:02}"),
Time.GetMinutes(),
Time.GetSeconds()
));
}
Photo Capture
// Take a photo (synchronous)
Service->TakePhoto();
// Listen for completion
Service->OnPhotoSaved.AddLambda([](const FString& PhotoPath)
{
UE_LOG(LogLCK, Log, TEXT("Photo saved: %s"), *PhotoPath);
ShowNotification(FString::Printf(TEXT("Photo saved to %s"), *PhotoPath));
});
Photo format: PNG, saved to platform-specific photos directory
Preview Mode
Preview mode shows the camera output without recording (no file is saved).
// Start preview
Service->StartPreview();
// Stop preview
Service->StopPreview();
// Check if previewing
bool bPreviewing = Service->IsPreviewActive();
When to use preview mode:
- Let users adjust camera settings before recording
- Show “what you see is what you get” preview
- Test camera placement without wasting storage
Quality Configuration
SetQualityProfile
// Change quality preset
Service->SetQualityProfile(ELCKVideoQuality::HD);
// Available presets:
// - ELCKVideoQuality::SD (720p, 4 Mbps)
// - ELCKVideoQuality::HD (1080p, 8 Mbps)
// - ELCKVideoQuality::TWO_K (1440p, 12 Mbps)
// - ELCKVideoQuality::FOUR_K (2160p, 20 Mbps)
Cannot change quality while recording. Check IsRecording() first.
Quality selection UI example:
UCLASS()
class AQualityMenu : public AActor
{
GENERATED_BODY()
public:
UFUNCTION()
void OnQualityButton(ELCKVideoQuality Quality)
{
if (Service->IsRecording())
{
ShowError(TEXT("Cannot change quality while recording"));
return;
}
Service->SetQualityProfile(Quality);
UpdateUI(Quality);
}
};
GetCurrentQuality
ELCKVideoQuality Current = Service->GetCurrentQuality();
switch (Current)
{
case ELCKVideoQuality::SD:
QualityText->SetText(FText::FromString("SD (720p)"));
break;
case ELCKVideoQuality::HD:
QualityText->SetText(FText::FromString("HD (1080p)"));
break;
// ...
}
Orientation
// Set orientation
Service->SetOrientation(ELCKScreenOrientation::Portrait); // 9:16
Service->SetOrientation(ELCKScreenOrientation::Landscape); // 16:9
// Get current
ELCKScreenOrientation Current = Service->GetOrientation();
Use cases:
- Landscape: YouTube, traditional video
- Portrait: TikTok, Instagram Stories
Cannot change orientation while recording.
Camera Modes
SetCameraMode
// Switch camera mode
Service->SetCameraMode(ELCKCameraMode::Selfie);
Service->SetCameraMode(ELCKCameraMode::FirstPerson);
Service->SetCameraMode(ELCKCameraMode::ThirdPerson);
Camera mode descriptions:
| Mode | Description | Use Case |
|---|
Selfie | Front/back facing camera on tablet | Vlog-style, show player’s face |
FirstPerson | POV from HMD position | Gameplay perspective |
ThirdPerson | Orbital camera following player | Cinematic, show player character |
Example: Camera mode switcher
void ACameraSwitcher::OnModeButton(int32 ModeIndex)
{
switch (ModeIndex)
{
case 0:
Service->SetCameraMode(ELCKCameraMode::Selfie);
ModeText->SetText(FText::FromString("Selfie"));
break;
case 1:
Service->SetCameraMode(ELCKCameraMode::FirstPerson);
ModeText->SetText(FText::FromString("First Person"));
break;
case 2:
Service->SetCameraMode(ELCKCameraMode::ThirdPerson);
ModeText->SetText(FText::FromString("Third Person"));
break;
}
}
Audio Control
Microphone
// Enable/disable microphone
Service->SetMicrophoneEnabled(true);
// Get current state
bool bMicEnabled = Service->IsMicrophoneEnabled();
// Get volume level (0.0 to 1.0)
float MicLevel = Service->GetMicrophoneVolume();
Volume indicator example:
void AMicIndicator::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
float Volume = Service->GetMicrophoneVolume();
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);
}
}
Game Audio
// Enable/disable game audio capture
Service->SetGameAudioEnabled(true);
// Get current state
bool bGameAudioEnabled = Service->IsGameAudioEnabled();
Audio source priority: FMOD > Wwise > Unreal Audio (only one active at a time)
State Tracking via Data Model
ULCKService does not expose state change delegates directly. Use ULCKTabletDataModel for state notifications.
Get the Data Model
void UMyComponent::BeginPlay()
{
Super::BeginPlay();
// Find tablet in world
ALCKTablet* Tablet = Cast<ALCKTablet>(
UGameplayStatics::GetActorOfClass(GetWorld(), ALCKTablet::StaticClass())
);
if (Tablet)
{
ULCKTabletDataModel* DataModel = Tablet->GetDataModel();
// Subscribe to state changes
DataModel->OnRecordingStateChanged.AddDynamic(
this, &UMyComponent::HandleStateChanged
);
}
}
Available Data Model Events
| Event | Type | Description |
|---|
OnRecordingStateChanged | FOnRecordingStateChanged | Recording state changed (Idle, Recording, Saving) |
OnCameraModeChanged | FOnCameraModeChanged | Camera mode changed |
OnMicStateChanged | FOnMicStateChanged | Microphone state changed (On, Off, No_Access) |
OnQualityChanged | FOnQualityChanged | Quality profile changed |
Example: State-driven UI
UFUNCTION()
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;
}
}
Complete Example: Recording Manager
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
ALCKTablet* Tablet = FindTablet();
if (Tablet)
{
DataModel = Tablet->GetDataModel();
DataModel->OnRecordingStateChanged.AddDynamic(
this, &URecordingManager::OnStateChanged
);
}
// Subscribe to events
Service->OnRecordingSaveFinished.AddDynamic(
this, &URecordingManager::OnSaveFinished
);
Service->OnRecordingError.AddDynamic(
this, &URecordingManager::OnError
);
}
UFUNCTION(BlueprintCallable)
void ToggleRecording()
{
if (Service->IsRecording())
{
StopRecording();
}
else
{
StartRecording();
}
}
void StartRecording()
{
if (Service->IsRecording())
{
UE_LOG(LogLCK, Warning, TEXT("Already recording"));
return;
}
Service->StartRecordingAsync(
FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess)
{
if (bSuccess)
{
OnRecordingStarted();
}
else
{
OnRecordingFailed();
}
})
);
}
void StopRecording()
{
if (!Service->IsRecording())
{
return;
}
Service->StopRecordingAsync(
FOnLCKRecorderBoolResult::CreateLambda([this](bool bSuccess)
{
OnRecordingStopped(bSuccess);
}),
FOnLCKRecorderProgress::CreateLambda([this](float Progress)
{
OnSaveProgress(Progress);
})
);
}
UFUNCTION()
void OnStateChanged(ELCKRecordingState NewState)
{
// Update UI based on state
BroadcastStateChange(NewState);
}
UFUNCTION()
void OnSaveFinished(bool bSuccess)
{
if (bSuccess)
{
ShowNotification(TEXT("Recording saved!"));
PlaySuccessSound();
}
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:
void OnRecordingStarted() { /* Broadcast event */ }
void OnRecordingFailed() { /* Show error */ }
void OnRecordingStopped(bool bSuccess) { /* Handle completion */ }
void OnSaveProgress(float Progress) { /* Update progress bar */ }
ULCKService* GetLCKService()
{
if (UWorld* World = GetWorld())
{
if (ULCKSubsystem* Subsystem = World->GetSubsystem<ULCKSubsystem>())
{
return Subsystem->GetService();
}
}
return nullptr;
}
ALCKTablet* FindTablet()
{
return Cast<ALCKTablet>(
UGameplayStatics::GetActorOfClass(GetWorld(), ALCKTablet::StaticClass())
);
}
};
Key Takeaways
Get via ULCKSubsystem — Don’t create or cache manually
Use async methods — Better error handling and progress tracking
Check IsRecording() — Before changing settings or starting
Use DataModel for state events — Service doesn’t expose state delegates
Don’t modify during recording — Quality, orientation locked while active