Add SRT implementation with mediamtx

urko 2025-05-18 19:22:39 +01:00
parent f95c8d7620
commit 6fb843a27a

@ -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
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.urkob.wittrail_android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:theme="@style/Theme.WittrailAndroid">
<service
android:name=".service.StreamRecordingService"
android:exported="false"
android:foregroundServiceType="camera|microphone"
android:enabled="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
```
---
## 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 servers 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 SHA256 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.