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
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<bool> 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
- Start login → Get a short code (e.g., “ABC123”)
- Display code → User enters it on a website
- Poll for completion → Check if login succeeded
- Handle result → Proceed with authenticated features
// 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
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
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
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
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:
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.
Task<Result<string>> StartLoginAttemptAsync()
Returns: Result<string> containing the login code (e.g., “ABC123”)
Example:
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.
Task<Result<bool>> CheckLoginCompletedAsync()
Returns: Result<bool> — true if login completed, false if still pending
Example:
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.
Task<Result<bool>> IsUserSubscribed()
Returns: Result<bool> — true if user has active subscription
Example:
var result = await _lckCore.IsUserSubscribed();
if (result.IsOk && result.Ok)
{
EnablePremiumFeatures();
}
else
{
ShowUpgradePrompt();
}
Checks if the user has configured their streaming setup (platforms, keys, etc.).
Task<Result<bool>> HasUserConfiguredStreaming()
Returns: Result<bool> — true if streaming is configured
Example:
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
async Task<bool> 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<T> which can contain CoreError:
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:
#if UNITY_EDITOR
public class MockLckCore : ILckCore
{
public async Task<Result<string>> StartLoginAttemptAsync()
{
await Task.Delay(100);
return Result<string>.Ok("TEST123");
}
public async Task<Result<bool>> CheckLoginCompletedAsync()
{
await Task.Delay(100);
return Result<bool>.Ok(true); // Always succeeds in test
}
public async Task<Result<bool>> IsUserSubscribed()
{
await Task.Delay(100);
return Result<bool>.Ok(true); // Test with subscription
}
public async Task<Result<bool>> HasUserConfiguredStreaming()
{
await Task.Delay(100);
return Result<bool>.Ok(false); // Test without streaming
}
}
#endif