Add SRT implementation with mediamtx
							parent
							
								
									f95c8d7620
								
							
						
					
					
						commit
						6fb843a27a
					
				
							
								
								
									
										252
									
								
								SRT-implementation-with-mediamtx.md
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
								
									
										
										
									
									
								
							
						
						
									
										252
									
								
								SRT-implementation-with-mediamtx.md
									
									
									
									
									
										Normal file
									
								
							@ -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 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.
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user