diff --git a/SRT-implementation-with-mediamtx.md b/SRT-implementation-with-mediamtx.md new file mode 100644 index 0000000..ed5e139 --- /dev/null +++ b/SRT-implementation-with-mediamtx.md @@ -0,0 +1,252 @@ +### Overview +Add dual-pipeline recording & streaming (MediaMuxer ↔ local + SRT → MediaMTX) to your existing Android project using StreamPack. + +--- +## 1. Update Gradle Dependencies +**File:** `app/build.gradle.kts` +```kotlin +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.urkob.wittrail_android" + compileSdk = 34 + defaultConfig { + applicationId = "com.urkob.wittrail_android" + minSdk = 24 + targetSdk = 34 + versionCode = 2 + versionName = "2.0" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.0" + } +} + +dependencies { + // Compose & Lifecycle + implementation(platform("androidx.compose:compose-bom:2023.08.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.material3:material3") + implementation("androidx.lifecycle:lifecycle-service:2.7.0") + + // CameraX + implementation("androidx.camera:camera-camera2:1.2.0") + implementation("androidx.camera:camera-lifecycle:1.2.0") + implementation("androidx.camera:camera-view:1.2.0") + + // StreamPack for dual-record & SRT + implementation("io.github.thibaultbee.streampack:streampack-core:3.0.0-RC") + implementation("io.github.thibaultbee.streampack:streampack-ui:3.0.0-RC") + implementation("io.github.thibaultbee.streampack:streampack-extension-srt:3.0.0-RC") + + // Other + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} +``` + +--- +## 2. AndroidManifest Modifications +**File:** `app/src/main/AndroidManifest.xml` +```xml + + + + + + + + + + + + + + + + + + + +``` + +--- +## 3. Create StreamRecordingService +**File:** `app/src/main/java/com/urkob/wittrail_android/service/StreamRecordingService.kt` +```kotlin +package com.urkob.wittrail_android.service + +import android.app.* +import android.content.Intent +import android.os.Build +import android.os.IBinder +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.lifecycle.LifecycleService +import io.github.thibaultbee.streampack.Streamer +import io.github.thibaultbee.streampack.extension.srt.UriMediaDescriptor +import io.github.thibaultbee.streampack.ext.cameraDualStreamer +import io.github.thibaultbee.streampack.ext.setAudioConfig +import io.github.thibaultbee.streampack.ext.setVideoConfig +import io.github.thibaultbee.streampack.media.AudioConfig +import io.github.thibaultbee.streampack.media.VideoConfig +import android.util.Size +import java.io.File + +class StreamRecordingService : LifecycleService() { + private lateinit var streamer: Streamer + private val channelId = "StreamRecordingChannel" + private val notificationId = 1001 + + override fun onCreate() { + super.onCreate() + createNotificationChannel() + startForeground(notificationId, buildNotification("Initializing...")) + + streamer = cameraDualStreamer(context = this) + streamer.setVideoConfig( + VideoConfig( + resolution = Size(1280, 720), + fps = 30, + startBitrate = 2_000_000 + ) + ) + streamer.setAudioConfig( + AudioConfig( + sampleRate = 44100, + channelConfig = android.media.AudioFormat.CHANNEL_IN_STEREO, + startBitrate = 128_000 + ) + ) + streamer.startPreview() + + val localFile = File(getExternalFilesDir(null), + "session_${System.currentTimeMillis()}.mp4") + streamer.startRecord(localFile.absolutePath) + + val srtUrl = "srt://YOUR_MTX_HOST:8890/camera?passphrase=mySecretKey&pbkeylen=16" + streamer.startStream(UriMediaDescriptor(srtUrl)) + + updateNotification("Streaming to MTX server...") + } + + override fun onDestroy() { + streamer.stopStream() + streamer.stopRecord() + streamer.release() + super.onDestroy() + } + + override fun onBind(intent: Intent): IBinder? { + super.onBind(intent) + return null + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + channelId, + "Stream Recording", + NotificationManager.IMPORTANCE_LOW + ) + getSystemService(NotificationManager::class.java) + .createNotificationChannel(channel) + } + } + + private fun buildNotification(content: String): Notification { + return NotificationCompat.Builder(this, channelId) + .setContentTitle("Wittrail Recording") + .setContentText(content) + .setSmallIcon(R.drawable.ic_notification) + .setOngoing(true) + .build() + } +} +``` + +--- +## 4. MainActivity: Control Buttons +**File:** `app/src/main/java/com/urkob/wittrail_android/MainActivity.kt` +```kotlin +package com.urkob.wittrail_android + +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.urkob.wittrail_android.service.StreamRecordingService +import com.urkob.wittrail_android.ui.theme.WittrailAndroidTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + WittrailAndroidTheme { + val context = LocalContext.current + val isRecording = remember { mutableStateOf(false) } + + Button(onClick = { + Intent(context, StreamRecordingService::class.java).also { intent -> + if (!isRecording.value) { + context.startForegroundService(intent) + } else { + context.stopService(intent) + } + isRecording.value = !isRecording.value + } + }) { + Text(if (!isRecording.value) "Start Recording" else "Stop Recording") + } + } + } + } +} +``` + +--- +## 5. Architecture Diagram +```mermaid +flowchart TB + subgraph Android_Client + A[CameraX + Mic] -->|DualStreamer| B[StreamPack] + B --> C[Local MP4 via MediaMuxer] + B --> D[SRT → MediaMTX] + end + + subgraph Server + D --> E[MediaMTX SRT Ingest] + E --> F[runOnPublish → FFmpeg MP4] + F --> G[Storage + Audit] + end +``` + +--- +### Next Steps +1. **Replace** `YOUR_MTX_HOST` with your server’s address in `srtUrl`. +2. **Grant permissions** at runtime for camera, mic, and storage. +3. **Test** via `Start Recording` button and verify local MP4 plus server-side recordings. +4. **Enhance**: compute SHA‑256 on the MP4 chunks or on file completion and log for audit. +5. **Extend**: add error callbacks (`streamer.setStreamErrorCallback`) to update UI. + +This setup gives you a production-ready, dual-pipeline Android client—local backup + secure, low-latency SRT streaming to your MediaMTX server.