Android(Compose)使用 LibVLC 播放 RTSP 视频流

Android (Compose)使用 LibVLC 播放 RTSP 视频流

问题描述

在 Android 应用中播放 RTSP 视频流时,常见问题:

  1. 无法播放:RTSP 地址正确,但播放器黑屏
  2. 延迟过高:实时性差,画面延迟数秒
  3. 播放卡顿:画面时断时续
  4. 内存泄漏:长时间播放后内存持续增长

依赖版本

kotlin 复制代码
// app/build.gradle.kts
dependencies {
    implementation("org.videolan.android:libvlc-all:3.6.5")
}

问题原因分析

与 iOS 端的关键差异

项目 iOS (MobileVLCKit) Android (LibVLC)
Media 创建 Media(url) 字符串 需要 Uri.parse(url)
硬件解码 默认启用 需要手动 setHWDecoderEnabled
视图绑定 player.drawable = view attachViews(layout, null, false, false)
Media 释放 自动管理 设置后需 media.release()

常见错误写法

kotlin 复制代码
// ❌ 错误:直接用字符串创建 Media
val media = Media(libVLC, rtspUrl)

// ❌ 错误:未启用硬件解码

// ❌ 错误:attachViews 参数不对
mediaPlayer.attachViews(videoLayout, null, true, true)

// ❌ 错误:选项太多太复杂
media.addOption(":network-caching=3000")
media.addOption(":rtsp-tcp")
media.addOption(":live-caching=3000")
// ... 更多选项

解决方案

完整代码实现

1. 权限配置
xml 复制代码
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
    android:networkSecurityConfig="@xml/network_security_config">
xml 复制代码
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>
2. VlcPlayer.kt(Compose 组件)
kotlin 复制代码
package com.example.rtspplayer.ui.components

import android.net.Uri
import android.util.Log
import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import org.videolan.libvlc.LibVLC
import org.videolan.libvlc.Media
import org.videolan.libvlc.MediaPlayer
import org.videolan.libvlc.util.VLCVideoLayout

private const val TAG = "VlcPlayer"

@Composable
fun VlcPlayer(
    mediaUrl: String,
    modifier: Modifier = Modifier
) {
    val context = LocalContext.current
    Log.d(TAG, "播放: $mediaUrl")

    val libVLC = remember { LibVLC(context) }
    val mediaPlayer = remember {
        MediaPlayer(libVLC).apply {
            setEventListener { event ->
                when (event.type) {
                    MediaPlayer.Event.Opening -> Log.d(TAG, "打开中...")
                    MediaPlayer.Event.Playing -> Log.d(TAG, "播放中")
                    MediaPlayer.Event.Buffering -> Log.d(TAG, "缓冲 ${event.buffering}%")
                    MediaPlayer.Event.EncounteredError -> Log.e(TAG, "播放错误")
                    MediaPlayer.Event.Stopped -> Log.d(TAG, "已停止")
                }
            }
        }
    }
    val videoLayout = remember {
        VLCVideoLayout(context).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        }
    }

    DisposableEffect(mediaUrl) {
        // 1. 绑定视图(参数:layout, subitems, esSelected)
        mediaPlayer.attachViews(videoLayout, null, false, false)

        // 2. 创建 Media(关键:使用 Uri.parse)
        val media = Media(libVLC, Uri.parse(mediaUrl))

        // 3. 启用硬件解码
        media.setHWDecoderEnabled(true, false)

        // 4. 设置 RTSP 播放选项
        media.addOption(":network-caching=3000")
        media.addOption(":rtsp-tcp")
        media.addOption(":live-caching=3000")
        media.addOption(":file-caching=3000")
        media.addOption(":rtsp-frame-buffer-size=5000000")
        media.addOption(":rtsp-max-frames=5000")

        // 5. 设置后立即释放
        mediaPlayer.media = media
        media.release()

        // 6. 播放
        mediaPlayer.play()

        onDispose {
            mediaPlayer.stop()
            mediaPlayer.detachViews()
        }
    }

    AndroidView(factory = { videoLayout }, modifier = modifier)
}
3. 传统 Activity 方式
kotlin 复制代码
class RtspPlayerActivity : AppCompatActivity() {
    
    private lateinit var libVLC: LibVLC
    private lateinit var mediaPlayer: MediaPlayer
    private lateinit var videoLayout: VLCVideoLayout
    
    private val rtspUrl = "rtsp://admin:123456@192.168.1.100:554/stream1"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_player)
        
        videoLayout = findViewById(R.id.videoLayout)
        libVLC = LibVLC(this)
        mediaPlayer = MediaPlayer(libVLC)
    }

    override fun onStart() {
        super.onStart()
        
        mediaPlayer.attachViews(videoLayout, null, false, false)
        
        val media = Media(libVLC, Uri.parse(rtspUrl))
        media.setHWDecoderEnabled(true, false)
        media.addOption(":network-caching=3000")
        media.addOption(":rtsp-tcp")
        media.addOption(":live-caching=3000")
        media.addOption(":file-caching=3000")
        media.addOption(":rtsp-frame-buffer-size=5000000")
        media.addOption(":rtsp-max-frames=5000")
        
        mediaPlayer.media = media
        media.release()
        mediaPlayer.play()
    }

    override fun onStop() {
        super.onStop()
        mediaPlayer.stop()
        mediaPlayer.detachViews()
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer.release()
        libVLC.release()
    }
}

关键点总结

1. Media 创建必须用 Uri.parse

kotlin 复制代码
val media = Media(libVLC, Uri.parse(rtspUrl))  // ✅ 正确
// val media = Media(libVLC, rtspUrl)          // ❌ 错误

2. 启用硬件解码

kotlin 复制代码
media.setHWDecoderEnabled(true, false)  // ✅ 必须

3. attachViews 参数

kotlin 复制代码
mediaPlayer.attachViews(videoLayout, null, false, false)  // ✅ 正确参数
// mediaPlayer.attachViews(videoLayout, null, true, true) // ❌ 错误参数

4. Media 设置后立即释放

kotlin 复制代码
mediaPlayer.media = media
media.release()  // ✅ 立即释放

5. 缓存值根据网络调整

kotlin 复制代码
// RTSP 播放完整选项配置
media.addOption(":network-caching=3000")      // 网络缓存 3000ms
media.addOption(":rtsp-tcp")                 // 使用 TCP 传输
media.addOption(":live-caching=3000")        // 直播缓存 3000ms
media.addOption(":file-caching=3000")        // 文件缓存 3000ms
media.addOption(":rtsp-frame-buffer-size=5000000")  // 帧缓冲大小
media.addOption(":rtsp-max-frames=5000")     // 最大帧数

避坑指南

  1. 不要设置过多选项 :只需 network-caching 即可,其他选项可能干扰播放
  2. 生命周期管理onStopstop()detachViews()onDestroyrelease()
  3. 状态监听 :通过 setEventListener 监听播放状态,处理错误情况

参考

相关推荐
alexhilton14 分钟前
响应式的Android身份验证架构
android·kotlin·android jetpack
喵都学不动了1 小时前
Android 自动化测试完全指南(新人版)
android
码云骑士1 小时前
76-全量微调vs-LoRA-vs-QLoRA-三种微调方式对比与选型
android
光头闪亮亮2 小时前
Fyne ( go跨平台GUI )项目实战-WebView 组件开发技术详解
android·go
嵌入式小周2 小时前
Genymotion 安卓模拟器在 Intel 芯片 Mac 上的运行(附带下载方式)
android·macos
zzq77974 小时前
Android 16 API 36 升级后 APP 加固兼容性问题解析
android·开发语言·安全·kotlin·安卓·安全架构
2601_961391465 小时前
KMP全栈开发:从Android到AI Agent的技术演进与实践
android·人工智能
2501_9159184114 小时前
深入对比iOS开发中常用性能监控工具的底层原理与优缺点分析
android·ios·小程序·https·uni-app·iphone·webview
my_power52015 小时前
android中Activity生命周期函数的职责
android
Sirens.15 小时前
从参考 iCost 到做自己的 OneLedger:一个 Android 本地记账 App 的开发记录
android·kotlin·room·jetpack compose·记账 app