Custom RTMP saved accounts, RTMP test server, composition pipeline

- Backend: POST /providers/accounts/custom-rtmp to save reusable RTMP servers
- Backend: Encrypt rtmpUrl/streamKey in existing token fields, decrypt on GET
- Backend: Skip token revocation on DELETE for CUSTOM_RTMP accounts
- Backend: Decrypt CUSTOM_RTMP credentials into destinations on plan create/update
- Android: Add rtmpUrl/streamKey to LinkedAccount entity + shared parcelable (Room v6)
- Android: Add Custom RTMP dialog in AccountsScreen, auto-fill in plan destination picker
- Android: Handle CUSTOM_RTMP accounts in CreatePlanViewModel.loadExistingPlan
- Add local RTMP test server (tools/rtmp-server.js) with auto-ffplay on publish
- Add composition pipeline native code
This commit is contained in:
2026-03-01 10:50:23 +01:00
parent c1ff5351b7
commit c632e22033
35 changed files with 2822 additions and 98 deletions

View File

@@ -302,15 +302,28 @@ class LckControlService : Service() {
// ── Auth logic ──────────────────────────────────────────
private fun extractJwtSub(jwt: String): String? {
return try {
val parts = jwt.split(".")
if (parts.size < 2) return null
val payload = String(android.util.Base64.decode(parts[1], android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP))
org.json.JSONObject(payload).optString("sub", "").ifEmpty { null }
} catch (_: Exception) { null }
}
private suspend fun doAutoLogin() {
// Try token refresh first
val refreshToken = tokenStore.getRefreshToken()
val oldJwt = tokenStore.getJwt()
val oldSub = oldJwt?.let { extractJwtSub(it) }
Log.d(TAG, "doAutoLogin: hasRefreshToken=${refreshToken != null}, currentUserId=$oldSub")
if (refreshToken != null) {
Log.d(TAG, "Attempting token refresh...")
try {
val response = apiService.refreshSession(RefreshRequest(refreshToken))
val newSub = extractJwtSub(response.accessToken)
Log.d(TAG, "Token refresh successful, userId=$newSub (was $oldSub)")
tokenStore.saveSession(response.accessToken, response.refreshToken)
Log.d(TAG, "Token refresh successful")
broadcastAuthStateChanged(true)
return
} catch (e: Exception) {
@@ -320,6 +333,7 @@ class LckControlService : Service() {
}
// Full Quest SDK login
Log.d(TAG, "Starting Quest SDK login (previous userId=$oldSub)")
doQuestLogin()
}
@@ -358,8 +372,9 @@ class LckControlService : Service() {
)
)
val loginSub = extractJwtSub(response.accessToken)
tokenStore.saveSession(response.accessToken, response.refreshToken)
Log.d(TAG, "Quest SDK login successful")
Log.d(TAG, "Quest SDK login successful, userId=$loginSub")
broadcastAuthStateChanged(true)
}