Page:
SRT implementation with mediamtx
Clone
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
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
<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
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
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
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
- Replace
YOUR_MTX_HOST
with your server’s address insrtUrl
. - Grant permissions at runtime for camera, mic, and storage.
- Test via
Start Recording
button and verify local MP4 plus server-side recordings. - Enhance: compute SHA‑256 on the MP4 chunks or on file completion and log for audit.
- 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.