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

参考

相关推荐
白雪落青衣14 分钟前
buuoj course 1详细解析
android
恋猫de小郭31 分钟前
Android 发布全新性能分析器,实用性和性能大升级
android·前端·flutter
Kapaseker41 分钟前
为什么 Java 的数组需要 new 出来
android·java·kotlin
黄林晴1 小时前
颠覆开发!Google AI Studio 一句话生成原生 Android App
android·google io
恋猫de小郭1 小时前
Flutter 3.44 发布啦,超级大版本更新!!!
android·flutter·ios
zb200641201 小时前
Laravel10.x重磅升级:新特性全解析
android
2601_957418801 小时前
深入解析Android相机有线连接:PTP与MTP协议栈实现原理与实践
android·数码相机·智能手机
努力努力再努力wz2 小时前
【QT入门系列】QWidget 六大常用属性详解:windowOpacity、cursor、font、focus、toolTip 与 styleSheet
android·开发语言·数据结构·c++·qt·mysql·算法
撩得Android一次心动2 小时前
C语言基础笔记3【个人用】
android·c语言·开发语言·笔记
小离a_a2 小时前
uniapp小程序封装圆环显示比例数据
android·小程序·uni-app