YouTube/Twitch live chat integration

- WebSocket chat client with auto-reconnect and re-subscribe on connect
- ChatScreen with message bubbles, mod/broadcaster badges, send capability
- Dashboard Live Chat section with unread badges per destination
- Chat notification manager for background notifications
- Chat navigation route and ViewModel
This commit is contained in:
2026-03-01 22:19:17 +01:00
parent ef221ca132
commit 870139b054
11 changed files with 803 additions and 0 deletions

View File

@@ -13,12 +13,14 @@ import com.meta.horizon.platform.ovr.Core
import com.meta.horizon.platform.ovr.requests.Request
import com.meta.horizon.platform.ovr.requests.Users
import com.omixlab.lckcontrol.R
import com.omixlab.lckcontrol.chat.ChatNotificationManager
import com.omixlab.lckcontrol.data.local.AppPreferences
import com.omixlab.lckcontrol.data.local.TokenStore
import com.omixlab.lckcontrol.data.remote.LckApiService
import com.omixlab.lckcontrol.data.remote.MetaCallbackRequest
import com.omixlab.lckcontrol.data.remote.RefreshRequest
import com.omixlab.lckcontrol.data.repository.AccountRepository
import com.omixlab.lckcontrol.data.repository.ChatRepository
import com.omixlab.lckcontrol.data.repository.StreamPlanRepository
import com.omixlab.lckcontrol.shared.ConnectedClientInfo
import com.omixlab.lckcontrol.shared.ILckControlCallback
@@ -61,6 +63,8 @@ class LckControlService : Service() {
@Inject lateinit var tokenStore: TokenStore
@Inject lateinit var apiService: LckApiService
@Inject lateinit var streamingManager: StreamingManager
@Inject lateinit var chatRepository: ChatRepository
@Inject lateinit var chatNotificationManager: ChatNotificationManager
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val clientTracker = ClientTracker()
@@ -295,6 +299,30 @@ class LckControlService : Service() {
streamingManager.onBufferReleased = { bufferIndex ->
streamingServiceImpl?.broadcastBufferReleased(bufferIndex)
}
// Initialize chat notification manager and connect WebSocket
chatNotificationManager.init()
chatRepository.connect()
// Auto-subscribe/unsubscribe chat when plans go LIVE/ENDED
serviceScope.launch {
streamPlanRepository.observePlans().collect { plans ->
Log.d(TAG, "observePlans emitted ${plans.size} plans")
for (plan in plans) {
val destServices = plan.destinations.map { it.service }
Log.d(TAG, "Plan ${plan.planId} status=${plan.status} destinations=$destServices")
val hasChat = plan.destinations.any {
it.service == "YOUTUBE" || it.service == "TWITCH"
}
if (plan.status == "LIVE" && hasChat) {
Log.d(TAG, "Subscribing chat for plan ${plan.planId}")
chatRepository.subscribe(plan.planId)
} else if (plan.status == "ENDED") {
chatRepository.unsubscribe(plan.planId)
}
}
}
}
}
override fun onBind(intent: Intent?): IBinder? {
@@ -310,6 +338,7 @@ class LckControlService : Service() {
}
override fun onDestroy() {
chatRepository.disconnect()
streamingManager.stopStreaming()
streamingServiceImpl?.kill()
serviceScope.cancel()