Приложение Kotlin/Android: потоковая передача RTMP не работает с триггером WebSocket, проблемы с импортомAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Приложение Kotlin/Android: потоковая передача RTMP не работает с триггером WebSocket, проблемы с импортом

Сообщение Anonymous »

Я пытаюсь создать простое приложение для Android на Kotlin, которое подключается к серверу WebSocket и запускает поток RTMP при отправке команды. Я хочу, чтобы приложение оставалось бездействующим, пока пользователь не скажет ему начать потоковую передачу, чтобы сэкономить ресурсы.
Что я хочу:
  • Подключиться к серверу WebSocket.
  • Когда я отправляю сообщение «Начать потоковую передачу», начать потоковую передачу через RTMP.
  • Оставить приложение бездействующим пока не будет запущено.
Проблема:
  • У меня проблемы с импортом RTMP.
  • Некоторые классы или методы кажутся устаревшими, или Android Studio помечает их как неразрешенные.
  • Я не знаю, какие Библиотека RTMP для Kotlin/Android, которая работает с моей настройкой.
Что я пробовал:
  • Использование библиотеки com.github.pedroSG94.rtmp-rtsp-stream-client-java (последняя версия).
  • Обновление зависимостей, но все равно получение неразрешенных ссылок.
  • Ищу альтернативы, но мне нужно простое решение, запускающее потоковую передачу через WebSocket.
Вопрос:
  • Для какой библиотеки RTMP рекомендуется использовать Android/Kotlin, который работает с триггерами WebSocket?
  • Как исправить ошибки импорта/неразрешенные ссылки?
  • Есть ли простой способ структурировать приложение так, чтобы оно начинало потоковую передачу только при срабатывании, не поддерживая постоянную работу камеры и потока?
    Уровень модели build.gradle.kt:
    plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    id("com.google.gms.google-services")
    id("com.google.devtools.ksp")
    }

    android {
    namespace = "com.yassine.test_call"
    compileSdk = 36

    defaultConfig {
    applicationId = "com.yassine.test_call"
    minSdk = 24
    targetSdk = 36
    versionCode = 1
    versionName = "1.0"

    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    ndk.abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
    }

    packaging {
    jniLibs {
    keepDebugSymbols.add("*/arm64-v8a/libjingle_peerconnection_so.so")
    }
    }

    buildTypes {
    release {
    isMinifyEnabled = false
    proguardFiles(
    getDefaultProguardFile("proguard-android-optimize.txt"),
    "proguard-rules.pro"
    )
    }
    }
    compileOptions {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
    jvmTarget = "11"
    }
    buildFeatures {
    compose = true
    }
    }

    dependencies {

    implementation("com.github.pedroSG94.RootEncoder:library:2.6.6") {
    exclude(group = "com.android.support")
    }
    implementation("com.github.pedroSG94.RootEncoder:extra-sources:2.6.6") {
    exclude(group = "com.android.support")
    }
    implementation("live.videosdk:rtc-android-sdk:0.1.26")
    // library to perform Network call to generate a meeting id
    implementation("com.amitshekhar.android:android-networking:1.0.2")
    implementation("com.github.pedroSG94.RootEncoder:library:2.6.6")
    implementation("com.google.android.exoplayer:exoplayer:2.19.1")
    implementation("com.google.android.exoplayer:extension-rtmp:2.19.1")
    implementation("io.coil-kt:coil-compose:2.7.0")
    implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
    implementation("io.socket:socket.io-client:2.1.2")
    implementation("com.google.firebase:firebase-auth")
    implementation("com.google.firebase:firebase-firestore")
    implementation("com.google.firebase:firebase-messaging")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("androidx.compose.material:material-icons-extended")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2")
    implementation("androidx.navigation:navigation-compose:2.8.0-beta05")
    implementation("androidx.datastore:datastore-preferences:1.1.1")
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
    }

    а это settings.gradle.kt :pluginManagement {
    repositories {
    google {
    content {
    includeGroupByRegex("com\\.android.*")
    includeGroupByRegex("com\\.google.*")
    includeGroupByRegex("androidx.*")
    }
    }
    mavenCentral()
    gradlePluginPortal()
    }
    }
    dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
    google()
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
    }
    }

    rootProject.name = "Test_call"
    include(":app")
и это модель представления, с которой у меня проблема
package com.yassine.test_call

import android.app.Application
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import com.google.firebase.Firebase
import com.google.firebase.auth.auth
import com.google.firebase.firestore.firestore
import com.pedro.library.rtmp.RtmpCamera2
import com.pedro.library.view.OpenGlView
import com.pedro.rtmp.utils.ConnectCheckerRtmp
import com.pedro.rtplibrary.rtmp.RtmpCamera2
import com.pedro.rtplibrary.view.OpenGlView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.withContext

class CameraViewModel(application: Application) : AndroidViewModel(application), ConnectCheckerRtmp {

/* ---------- UI ---------- */
val allCodes = mutableListOf()
var fetchStatus = "Loading codes…"
var selectedCode: String? = null
private set

/* ---------- RTMP/Camera ---------- */
private var rtmpCamera: RtmpCamera2? = null
private var openGlView: OpenGlView? = null
private var isStreaming = false

// TODO: Update with your actual RTMP server IP/Domain
private val RTMP_URL_BASE = "rtmp://10.0.2.2:1935/live/"

/* ---------- surface life-cycle ---------- */
fun setSurface(s: OpenGlView) {
openGlView = s
// Initialize RtmpCamera2 with the OpenGlView
rtmpCamera = RtmpCamera2(getApplication(), this, s)
// Set up camera properties (e.g., use the front camera for a plant monitor)
rtmpCamera?.setCameraFace(true) // true for front camera, false for back
// Start camera preview immediately
if (rtmpCamera?.is and rtmpCamera?.isStreaming == false) {
rtmpCamera?.startPreview()
}
}

fun clearSurface() {
rtmpCamera?.stopPreview()
rtmpCamera?.stopStream()
openGlView = null
isStreaming = false
}

/* ---------- Firebase: load codes once (Keep this logic) ---------- */
suspend fun loadAllCameraCodes() {
// ... (Existing Firebase logic to load codes)
val codes = mutableListOf()
try {
val auth = Firebase.auth
if (auth.currentUser == null) auth.signInAnonymously().await()

val store = Firebase.firestore
val users = store.collection("users").get().await()
for (user in users) {
val plants = store.collection("users")
.document(user.id)
.collection("plants")
.get()
.await()
for (plant in plants) {
plant.getString("cameraCode")?.trim()?.let { codes.add(it) }
}
}
} catch (e: Exception) {
e.printStackTrace()
}
withContext(Dispatchers.Main) {
allCodes.clear()
allCodes.addAll(codes.distinct())
fetchStatus = when {
codes.isEmpty() -> "No camera codes found – check Firestore / network"
else -> "Found ${codes.size} codes"
}
if (codes.isEmpty()) Toast.makeText(
getApplication(),
fetchStatus,
Toast.LENGTH_LONG
).show()
}
}

/* ---------- user picked a code ---------- */
fun selectCode(code: String) {
if (code == selectedCode) return
shutdown()
selectedCode = code
// Start Socket.IO connection and register as camera
SocketManager.connect()
SocketManager.socket?.emit("camera-register", mapOf("cameraCode" to code, "plantId" to "plant_id_placeholder"))
setupSocketListeners()
}

/* ---------- shutdown ---------- */
fun shutdown() {
rtmpCamera?.stopStream()
rtmpCamera?.stopPreview()
isStreaming = false
SocketManager.disconnect()
}

/* ---------- RTMP Streaming Control ---------- */
private fun startStream() {
if (isStreaming) return
val code = selectedCode ?: return
if (rtmpCamera?.prepareVideo(640, 480, 30, 1200 * 1024, 0, 0) == true && rtmpCamera?.prepareAudio() == true) {
val rtmpUrl = RTMP_URL_BASE + code
rtmpCamera?.startStream(rtmpUrl)
isStreaming = true
Log.d("CAM", "Attempting to start stream to $rtmpUrl")
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to code, "status" to "starting"))
} else {
Log.e("CAM", "Error preparing video/audio for streaming.")
}
}

private fun stopStream() {
if (!isStreaming) return
rtmpCamera?.stopStream()
isStreaming = false
val code = selectedCode ?: return
Log.d("CAM", "Stream stopped for $code")
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to code, "status" to "stopped"))
}

/* ---------- Socket.IO Listeners for Camera Control ---------- */
private fun setupSocketListeners() {
SocketManager.socket?.on("start-stream") {
Log.d("CAM", "Received start-stream command")
startStream()
}
SocketManager.socket?.on("stop-stream") {
Log.d("CAM", "Received stop-stream command")
stopStream()
}
}

/* ---------- ConnectCheckerRtmp Implementation ---------- */
override fun onConnectionStartedRtmp(rtmpUrl: String) {
Log.i("RTMP", "Connection started: $rtmpUrl")
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to selectedCode, "status" to "streaming"))
}

override fun onConnectionSuccessRtmp() {
Log.i("RTMP", "Connection success")
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to selectedCode, "status" to "streaming"))
}

override fun onConnectionFailedRtmp(reason: String) {
Log.e("RTMP", "Connection failed: $reason")
isStreaming = false
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to selectedCode, "status" to "failed"))
// Optionally, retry connection
// rtmpCamera?.reTry(5000, reason)
}

override fun onNewBitrateRtmp(bitrate: Long) {
// Log.d("RTMP", "New bitrate: $bitrate")
}

override fun onDisconnectRtmp() {
Log.i("RTMP", "Disconnected")
isStreaming = false
SocketManager.socket?.emit("stream-status", mapOf("cameraCode" to selectedCode, "status" to "stopped"))
}

override fun onAuthErrorRtmp() {
Log.e("RTMP", "Auth error")
}

override fun onAuthSuccessRtmp() {
Log.i("RTMP", "Auth success")
}
}

моя ошибка заключается в следующем:
Неразрешенная ссылка «ConnectCheckerRtmp».
и это когда я пытаюсь запустить
> Task :app:compileDebugKotlin FAILED
e: org.jetbrains.kotlin.util.FileAnalysisException: While analysing C:/Users/larib/AndroidStudioProjects/Test_call/app/src/main/java/com/yassine/test_call/CameraViewModel.kt:33:5: java.lang.IllegalArgumentException: source must not be null
at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57)
at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:249)
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:46)
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:77)
at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.buildResolveAndCheckFirViaLightTree(firUtils.kt:88)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModuleToAnalyzedFir(jvmCompilerPipeline.kt:319)
at org.jetbrains.kotlin.cli.jvm.compiler.pipeline.JvmCompilerPipelineKt.compileModulesUsingFrontendIrAndLightTree(jvmCompilerPipeline.kt:118)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:148)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:464)
at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:73)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:506)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:423)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:301)
at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:129)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675)
at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.IllegalArgumentException: source must not be null
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.requireNotNull(KtDiagnosticReportHelpers.kt:68)
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn(KtDiagnosticReportHelpers.kt:39)
at org.jetbrains.kotlin.diagnostics.KtDiagnosticReportHelpersKt.reportOn$default(KtDiagnosticReportHelpers.kt:31)
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkSourceElement(FirIncompatibleClassExpressionChecker.kt:50)
at org.jetbrains.kotlin.fir.analysis.checkers.expression.FirIncompatibleClassExpressionChecker.checkType$checkers(FirIncompatibleClassExpressionChecker.kt:42)
at org.jetbrains.kotlin.fir.analysis.checkers.type.FirIncompatibleClassTypeChecker.check(FirIncompatibleClassTypeChecker.kt:17)
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.check(TypeCheckersDiagnosticComponent.kt:81)
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:53)
at org.jetbrains.kotlin.fir.analysis.collectors.components.TypeCheckersDiagnosticComponent.visitResolvedTypeRef(TypeCheckersDiagnosticComponent.kt:19)
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
at org.jetbrains.kotlin.fir.analysis.collectors.CheckerRunningDiagnosticCollectorVisitor.checkElement(CheckerRunningDiagnosticCollectorVisitor.kt:24)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:248)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitResolvedTypeRef(AbstractDiagnosticCollectorVisitor.kt:30)
at org.jetbrains.kotlin.fir.types.FirResolvedTypeRef.accept(FirResolvedTypeRef.kt:28)
at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.acceptChildren(FirSimpleFunctionImpl.kt:63)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:118)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitSimpleFunction(AbstractDiagnosticCollectorVisitor.kt:30)
at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.accept(FirSimpleFunction.kt:51)
at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.acceptChildren(FirRegularClassImpl.kt:63)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitWithDeclarationAndReceiver(AbstractDiagnosticCollectorVisitor.kt:311)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitClassAndChildren(AbstractDiagnosticCollectorVisitor.kt:87)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:92)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitRegularClass(AbstractDiagnosticCollectorVisitor.kt:30)
at org.jetbrains.kotlin.fir.declarations.FirRegularClass.accept(FirRegularClass.kt:48)
at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.acceptChildren(FirFileImpl.kt:57)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitNestedElements(AbstractDiagnosticCollectorVisitor.kt:38)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:1151)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor.visitFile(AbstractDiagnosticCollectorVisitor.kt:30)
at org.jetbrains.kotlin.fir.declarations.FirFile.accept(FirFile.kt:42)
at org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollector.collectDiagnostics(AbstractDiagnosticCollector.kt:36)
at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runCheckers(analyse.kt:34)
... 33 more


Подробнее здесь: https://stackoverflow.com/questions/798 ... -import-is
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»