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

参考

相关推荐
冬奇Lab5 小时前
相机录像流程:MediaRecorder与Camera2的协作之道
android·音视频开发·源码阅读
麦客奥德彪6 小时前
Jetpack Compose 常用开发总结
android
麦客奥德彪6 小时前
Jetpack Compose Modifier 完全指南
android
Mac的实验室8 小时前
(2026年最新)解决谷歌账号注册设备扫码短信发送失败无法验证难题(100%通过无需扫码验证)
android·google·程序员
半条咸鱼9 小时前
如何通过 ADB 连接安卓设备(USB + 无线 TCP/IP)
android
huwuhang9 小时前
斐讯盒子N1_YYFROM固件_webview119更新版附安卓专用遥控器刷机固工具USB_Burning_Tool
android
qq_352018689 小时前
android 状态栏高度获取
android
AirDroid_cn9 小时前
安卓15平板分屏比例能到1:9吗?极限分屏设置教程
android·智能手机
菜鸟国国10 小时前
还在为 Compose 屏幕适配发愁?一个 Density 搞定所有机型!
android
卡尔特斯10 小时前
Android Studio 代理配置指南
android·前端·android studio