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 监听播放状态,处理错误情况

参考

相关推荐
Carson带你学Android9 小时前
Compose 终于上线 FlexBox:换行与弹性伸缩 都轻松搞定!
android·composer
私人珍藏库9 小时前
[Android] 三维山水全景地图-3D地形全景观测地图
android·3d·app·工具·软件·多功能
dengyuezhe806010 小时前
《C++ 异常机制与智能指针:从原理到实现》
android·java·c++
Wonderful U10 小时前
Python+Django实战|企业办公用品申领管理系统:物资入库、库存预警、申领审批、归还登记、损耗统计、供应商对账
android·python·django
plainGeekDev10 小时前
网络状态监听 → ConnectivityManager + Flow
android·java·kotlin
楠目10 小时前
CVE-2013-4547 Nginx URI解析漏洞利用总结
android
Coffeeee10 小时前
不能用公司的打包机,AI帮我实现了一套比打包机更好用的Android包构建/分发流程
android·人工智能·ai编程
多彩电脑11 小时前
向AIDE(安卓设备上的Android Studio)导入aar库
android·java·开发语言·androidx
恋猫de小郭11 小时前
解析华为 DevEco Code 和小米 MiMo Code,都基于 OpenCode ,有什么区别?
android·前端·ios
2501_9327502611 小时前
Android 控件与布局全面解析
android