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:
@@ -0,0 +1,8 @@
|
||||
package com.omixlab.lckcontrol.shared;
|
||||
|
||||
interface ILckStreamingCallback {
|
||||
oneway void onBufferReleased(int bufferIndex);
|
||||
oneway void onStreamingStateChanged(String state);
|
||||
oneway void onStreamingError(int code, String message);
|
||||
oneway void onStreamingStats(long videoBitrate, long audioBitrate, int fps, int droppedFrames);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.omixlab.lckcontrol.shared;
|
||||
|
||||
import android.hardware.HardwareBuffer;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import com.omixlab.lckcontrol.shared.ILckStreamingCallback;
|
||||
|
||||
interface ILckStreamingService {
|
||||
// Texture pool (game allocates, app receives)
|
||||
void registerTexturePool(in HardwareBuffer[] buffers, int width, int height, int format);
|
||||
void unregisterTexturePool();
|
||||
|
||||
// Frame submission (game -> app, one-way for performance)
|
||||
oneway void submitVideoFrame(int bufferIndex, long timestampNs, in ParcelFileDescriptor gpuFence);
|
||||
oneway void submitAudioFrame(in byte[] pcmData, long timestampNs, int sampleRate, int channels, int bitsPerSample);
|
||||
|
||||
// Streaming lifecycle
|
||||
boolean isStreaming();
|
||||
|
||||
// Callbacks
|
||||
void registerStreamingCallback(ILckStreamingCallback callback);
|
||||
void unregisterStreamingCallback(ILckStreamingCallback callback);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.omixlab.lckcontrol.shared;
|
||||
|
||||
parcelable StreamingConfig;
|
||||
@@ -10,6 +10,7 @@ data class LinkedAccount(
|
||||
val accountId: String,
|
||||
val avatarUrl: String? = null,
|
||||
val isAuthenticated: Boolean = false,
|
||||
val isEnabled: Boolean = true,
|
||||
) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
@@ -19,6 +20,7 @@ data class LinkedAccount(
|
||||
accountId = parcel.readString()!!,
|
||||
avatarUrl = parcel.readString(),
|
||||
isAuthenticated = parcel.readInt() != 0,
|
||||
isEnabled = if (parcel.dataAvail() > 0) parcel.readInt() != 0 else true,
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
@@ -28,6 +30,7 @@ data class LinkedAccount(
|
||||
parcel.writeString(accountId)
|
||||
parcel.writeString(avatarUrl)
|
||||
parcel.writeInt(if (isAuthenticated) 1 else 0)
|
||||
parcel.writeInt(if (isEnabled) 1 else 0)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
@@ -8,6 +8,8 @@ data class StreamPlan(
|
||||
val name: String,
|
||||
val status: String = "DRAFT",
|
||||
val destinations: List<StreamDestination> = emptyList(),
|
||||
val executionMode: String = "IN_GAME",
|
||||
val gameId: String = "",
|
||||
) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
@@ -15,6 +17,8 @@ data class StreamPlan(
|
||||
name = parcel.readString()!!,
|
||||
status = parcel.readString() ?: "DRAFT",
|
||||
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
|
||||
executionMode = if (parcel.dataAvail() > 0) parcel.readString() ?: "IN_GAME" else "IN_GAME",
|
||||
gameId = if (parcel.dataAvail() > 0) parcel.readString() ?: "" else "",
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
@@ -22,6 +26,8 @@ data class StreamPlan(
|
||||
parcel.writeString(name)
|
||||
parcel.writeString(status)
|
||||
parcel.writeTypedList(destinations)
|
||||
parcel.writeString(executionMode)
|
||||
parcel.writeString(gameId)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
@@ -6,16 +6,22 @@ import android.os.Parcelable
|
||||
data class StreamPlanConfig(
|
||||
val name: String,
|
||||
val destinations: List<StreamDestination> = emptyList(),
|
||||
val executionMode: String = "IN_GAME",
|
||||
val gameId: String = "",
|
||||
) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
name = parcel.readString()!!,
|
||||
destinations = parcel.createTypedArrayList(StreamDestination.CREATOR) ?: emptyList(),
|
||||
executionMode = if (parcel.dataAvail() > 0) parcel.readString() ?: "IN_GAME" else "IN_GAME",
|
||||
gameId = if (parcel.dataAvail() > 0) parcel.readString() ?: "" else "",
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(name)
|
||||
parcel.writeTypedList(destinations)
|
||||
parcel.writeString(executionMode)
|
||||
parcel.writeString(gameId)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.omixlab.lckcontrol.shared
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
data class StreamingConfig(
|
||||
val videoBitrate: Int = 6_000_000,
|
||||
val videoCodec: String = "h264",
|
||||
val audioBitrate: Int = 128_000,
|
||||
val audioSampleRate: Int = 48_000,
|
||||
val audioChannels: Int = 2,
|
||||
val keyFrameInterval: Int = 2,
|
||||
) : Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
videoBitrate = parcel.readInt(),
|
||||
videoCodec = parcel.readString() ?: "h264",
|
||||
audioBitrate = parcel.readInt(),
|
||||
audioSampleRate = parcel.readInt(),
|
||||
audioChannels = parcel.readInt(),
|
||||
keyFrameInterval = parcel.readInt(),
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeInt(videoBitrate)
|
||||
parcel.writeString(videoCodec)
|
||||
parcel.writeInt(audioBitrate)
|
||||
parcel.writeInt(audioSampleRate)
|
||||
parcel.writeInt(audioChannels)
|
||||
parcel.writeInt(keyFrameInterval)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int = 0
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<StreamingConfig> {
|
||||
override fun createFromParcel(parcel: Parcel) = StreamingConfig(parcel)
|
||||
override fun newArray(size: Int) = arrayOfNulls<StreamingConfig>(size)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user