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

参考

相关推荐
海天鹰7 分钟前
安卓分割空字符串数组长度为1
android
Coffeeee36 分钟前
从 Android 15 到 Android 17,谷歌猝不及防的音频改动
android·前端·google
用户69371750013841 小时前
AI 领域的 Harness,到底是什么意思?
android·前端·后端
恋猫de小郭2 小时前
Flutter 开发怎么做 Agent ?从工程实战详细给你解读下
android·前端·flutter
安卓修改大师2 小时前
安卓修改大师实战:Activity生命周期全解析与Smali插桩教程
android
zhangphil2 小时前
Android集成封装libjpeg-turbo解码库,Kotlin语言实现
android
码上有光2 小时前
c++:map和set的底层实现
android·java·c++·set·map
峥嵘life2 小时前
Android GMS 开机向导横竖屏切换配置总结
android·学习
apihz2 小时前
PING 接口 PLUS 全球版 —— 免费全球多节点 Ping 检测 API教程
android
阿pin2 小时前
Android随笔-epoll机制
android·epoll