App streaming pipeline, dashboard server status, account enable/disable, game-linked plans
- Add C++ native streaming engine (RTMP client, EGL context, streaming engine, JNI bridge) - Add pre-built arm64-v8a libs (librtmp, libssl, libcrypto, libz) and headers - Add Kotlin streaming layer (NativeStreamingEngine, StreamingManager, StreamingStats) - Add AIDL streaming interface (ILckStreamingService, ILckStreamingCallback, StreamingConfig) - Add LckStreamingServiceImpl with BIND_STREAMING action support - Add APP_STREAMING execution mode with auto-start/stop on plan lifecycle - SDK: add bindStreaming(), submitVideoFrame(), submitAudioFrame() to LckControlClient - Dashboard: replace linked accounts with server status card, move health polling from nav - Remove health check dot overlay from Dashboard nav icon - Accounts: add enable/disable toggle per account (persists locally, excluded from default plans) - Plans: add gameId field linked to game package ID, resolved from ClientTracker for default plans - Service: pass executionMode+gameId through createStreamPlan, filter enabled accounts in createDefaultPlan - Room DB migration 4→5: add isEnabled column to linked_accounts, gameId column to stream_plans - Add docs (hub vs control comparison)
This commit is contained in:
@@ -26,6 +26,9 @@ import com.omixlab.lckcontrol.shared.LinkedAccount
|
||||
import com.omixlab.lckcontrol.shared.StreamDestination
|
||||
import com.omixlab.lckcontrol.shared.StreamPlan
|
||||
import com.omixlab.lckcontrol.shared.StreamPlanConfig
|
||||
import com.omixlab.lckcontrol.shared.StreamingConfig
|
||||
import com.omixlab.lckcontrol.streaming.StreamingManager
|
||||
import com.omixlab.lckcontrol.streaming.StreamingState
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -48,15 +51,18 @@ class LckControlService : Service() {
|
||||
private const val NOTIFICATION_ID = 1
|
||||
private const val QUEST_APP_ID = "25653777174321448"
|
||||
private const val TOKEN_REFRESH_INTERVAL_MS = 60_000L
|
||||
private const val ACTION_BIND_STREAMING = "com.omixlab.lckcontrol.BIND_STREAMING"
|
||||
}
|
||||
|
||||
@Inject lateinit var accountRepository: AccountRepository
|
||||
@Inject lateinit var streamPlanRepository: StreamPlanRepository
|
||||
@Inject lateinit var tokenStore: TokenStore
|
||||
@Inject lateinit var apiService: LckApiService
|
||||
@Inject lateinit var streamingManager: StreamingManager
|
||||
|
||||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
private val clientTracker = ClientTracker()
|
||||
private var streamingServiceImpl: LckStreamingServiceImpl? = null
|
||||
private val callbacks = object : RemoteCallbackList<ILckControlCallback>() {
|
||||
override fun onCallbackDied(callback: ILckControlCallback, cookie: Any?) {
|
||||
val uid = cookie as? Int ?: return
|
||||
@@ -95,13 +101,20 @@ class LckControlService : Service() {
|
||||
// ── Stream plans ────────────────────────────────────
|
||||
|
||||
override fun createStreamPlan(config: StreamPlanConfig): StreamPlan = runBlocking {
|
||||
val plan = streamPlanRepository.createPlan(config.name, config.destinations)
|
||||
val plan = streamPlanRepository.createPlan(
|
||||
name = config.name,
|
||||
destinations = config.destinations,
|
||||
executionMode = config.executionMode,
|
||||
gameId = config.gameId,
|
||||
)
|
||||
broadcastPlansChanged()
|
||||
plan
|
||||
}
|
||||
|
||||
override fun createDefaultPlan(clientName: String): StreamPlan = runBlocking {
|
||||
val accounts = accountRepository.getAccounts()
|
||||
val accounts = accountRepository.getAccounts().filter { it.isEnabled }
|
||||
val gameId = clientTracker.getAll()
|
||||
.find { it.clientName == clientName }?.packageName ?: ""
|
||||
val destinations = accounts.map { account ->
|
||||
StreamDestination(
|
||||
service = account.serviceId,
|
||||
@@ -110,7 +123,11 @@ class LckControlService : Service() {
|
||||
privacyStatus = "unlisted",
|
||||
)
|
||||
}
|
||||
val plan = streamPlanRepository.createPlan("$clientName Stream", destinations)
|
||||
val plan = streamPlanRepository.createPlan(
|
||||
name = "$clientName Stream",
|
||||
destinations = destinations,
|
||||
gameId = gameId,
|
||||
)
|
||||
broadcastPlansChanged()
|
||||
plan
|
||||
}
|
||||
@@ -137,6 +154,17 @@ class LckControlService : Service() {
|
||||
try {
|
||||
streamPlanRepository.startPlan(planId)
|
||||
val updated = streamPlanRepository.getPlan(planId)
|
||||
|
||||
// If APP_STREAMING mode, start the streaming engine
|
||||
if (updated?.executionMode == "APP_STREAMING") {
|
||||
streamingManager.startStreaming(
|
||||
plan = updated,
|
||||
config = StreamingConfig(),
|
||||
width = 1920,
|
||||
height = 1080,
|
||||
)
|
||||
}
|
||||
|
||||
if (updated != null) broadcastPlanUpdated(updated)
|
||||
true
|
||||
} catch (_: Exception) { false }
|
||||
@@ -147,6 +175,11 @@ class LckControlService : Service() {
|
||||
if (plan.status == "ENDED") return@runBlocking true
|
||||
if (plan.status != "LIVE" && plan.status != "READY") return@runBlocking false
|
||||
try {
|
||||
// Stop streaming engine if running
|
||||
if (plan.executionMode == "APP_STREAMING") {
|
||||
streamingManager.stopStreaming()
|
||||
}
|
||||
|
||||
streamPlanRepository.endPlan(planId)
|
||||
val updated = streamPlanRepository.getPlan(planId)
|
||||
if (updated != null) broadcastPlanUpdated(updated)
|
||||
@@ -222,11 +255,37 @@ class LckControlService : Service() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward streaming state changes to AIDL callbacks
|
||||
serviceScope.launch {
|
||||
streamingManager.state.collect { state ->
|
||||
streamingServiceImpl?.broadcastStateChanged(state)
|
||||
}
|
||||
}
|
||||
serviceScope.launch {
|
||||
streamingManager.stats.collect { stats ->
|
||||
streamingServiceImpl?.broadcastStats(
|
||||
stats.videoBitrate, stats.audioBitrate, stats.fps, stats.droppedFrames,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder = binder
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return when (intent?.action) {
|
||||
ACTION_BIND_STREAMING -> {
|
||||
if (streamingServiceImpl == null) {
|
||||
streamingServiceImpl = LckStreamingServiceImpl(streamingManager)
|
||||
}
|
||||
streamingServiceImpl!!.asBinder()
|
||||
}
|
||||
else -> binder
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
streamingManager.stopStreaming()
|
||||
streamingServiceImpl?.kill()
|
||||
serviceScope.cancel()
|
||||
callbacks.kill()
|
||||
super.onDestroy()
|
||||
|
||||
Reference in New Issue
Block a user