当前位置: 首页 > news >正文

Compose笔记(十六)--ExoPlayer

        这一节了解一下Compose中的ExoPlayer的使用,我们在开发Android应用时,经常会使用到播放器这个ExoPlayer框架就相对成熟,易上手,现简单总结如下:

1. ExoPlayer 核心类
       ExoPlayer 是 ExoPlayer库的核心类,负责管理媒体播放的整个生命周期,包括加载、播放、暂停、停止等操作。
       作用:1 创建播放器实例。2 管理播放状态(播放、暂停、停止)。3 控制播放进度(如跳转到指定位置)。4 监听播放事件(如播放完成、错误等)。
2. SimpleExoPlayer
      SimpleExoPlayer 是 ExoPlayer 的一个简化实现,适用于大多数基本播放场景。
      作用:1 提供了更简单的 API 来管理播放。2 支持音频和视频播放。3 通常用于不需要复杂自定义的场景。
3. Player.Listener
      含义:Player.Listener 是 ExoPlayer 的监听器接口,用于接收播放状态变化和事件通知。
      作用:1 监听播放状态变化(如播放、暂停、停止)。2 监听播放进度变化。3 监听错误事件。4 监听媒体加载状态(如缓冲完成)。
4. MediaItem
      含义:MediaItem 表示要播放的媒体资源,可以是本地文件或网络资源。
      作用:1 定义媒体资源的 URI(如本地路径或网络 URL)。2 设置媒体元数据(如标题、描述等)。3添加到播放列表中。
5. PlayerView
      含义:PlayerView 是 ExoPlayer 提供的 UI 组件,用于显示视频画面和控制播放。
     作用:1 显示视频画面。2 提供内置的播放控制按钮(播放、暂停、进度条等)。3 可以通过Compose的AndroidView或ComposeView集成到ComposeUI中。
6. 播放控制方法
play():开始播放。
pause():暂停播放。
stop():停止播放并重置播放器状态。
seekTo(position: Long):跳转到指定位置(以毫秒为单位)。
setPlayWhenReady(playWhenReady: Boolean):设置播放器是否准备好时自动播放。
7. 播放状态
ExoPlayer 提供了多种播放状态,可以通过 Player.State 或 playbackState 属性获取:
STATE_IDLE:播放器空闲状态,未准备播放。
STATE_BUFFERING:播放器正在缓冲数据。
STATE_READY:播放器已准备好,可以播放。
STATE_ENDED:播放结束。
8. 事件监听(如播放完成)
含义:通过监听器可以捕获播放完成事件。

栗子

app模块下gradle:

implementation ("com.google.android.exoplayer:exoplayer-core:2.19.1")
implementation ("com.google.android.exoplayer:exoplayer-hls:2.19.1")
implementation ("com.google.android.exoplayer:exoplayer-ui:2.19.1")
implementation("androidx.preference:preference-ktx:1.2.1")
<activity
            android:name=".testexoplayer.YourVideoActivity"
            android:exported="true"
            android:theme="@style/Theme.ComposeNavigationDemo"
            android:supportsPictureInPicture="true"
            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
package com.example.composenavigationdemo.testexoplayer

import android.content.Context
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.preference.PreferenceManager

class YourVideoActivity : ComponentActivity() {
    private lateinit var exoPlayer: ExoPlayer
    private val PREF_KEY_PLAYBACK_POSITION = "playback_position"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            VideoPlayerScreen()
        }
    }

    @Composable
    fun VideoPlayerScreen() {
        val videoUrl = "https://vdept3.bdstatic.com/mda-rbq74um403w6qq1c/cae_h264/1740460540715545764/mda-rbq74um403w6qq1c.mp4?v_from_s=hkapp-haokan-suzhou&auth_key=1743250028-0-0-dbffc551f0aea2bffc89d537dba36202&bcevod_channel=searchbox_feed&cr=0&cd=0&pd=1&pt=3&logid=0428327789&vid=10916160709390273754&klogid=0428327789&abtest=\n" 
        var isPlaying by remember { mutableStateOf(true) }
        val context = LocalContext.current

        val playerView = remember {
            StyledPlayerView(this).apply {
                layoutParams = FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
            }
        }

        val player = remember {
            ExoPlayer.Builder(this).build().apply {
                setMediaItem(MediaItem.fromUri(videoUrl))
                // 读取播放进度
                val playbackPosition = getPlaybackPosition(context)
                if (playbackPosition > 0) {
                    seekTo(playbackPosition)
                }
                prepare()
                playWhenReady = isPlaying
            }
        }

        DisposableEffect(Unit) {
            playerView.player = player
            onDispose {
                // 保存播放进度
                savePlaybackPosition(context, player.currentPosition)
                player.release()
            }
        }

        Box(modifier = Modifier.fillMaxSize()) {
            AndroidView(
                factory = { playerView },
                modifier = Modifier.fillMaxSize()
            )

            Button(
                onClick = {
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                        enterPictureInPictureMode()
                    }
                },
                modifier = Modifier.align(androidx.compose.ui.Alignment.BottomEnd)
            ) {
                Text(text = "进入画中画")
            }
        }
    }

    private fun savePlaybackPosition(context: Context, position: Long) {
        val prefs = PreferenceManager.getDefaultSharedPreferences(context)
        prefs.edit().putLong(PREF_KEY_PLAYBACK_POSITION, position).apply()
    }

    private fun getPlaybackPosition(context: Context): Long {
        val prefs = PreferenceManager.getDefaultSharedPreferences(context)
        return prefs.getLong(PREF_KEY_PLAYBACK_POSITION, 0)
    }

    override fun onPictureInPictureModeChanged(
        isInPictureInPictureMode: Boolean,
        newConfig: android.content.res.Configuration
    ) {
        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
        if (isInPictureInPictureMode) {
            // 进入画中画模式
            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
        } else {
            // 退出画中画模式
//            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
            requestedOrientation = ActivityInfo.SCREEN_O RIENTATION_SENSOR_PORTRAIT
        }
    }
}

分析:上面实现了一个画中画的播放效果。

注意:

1. 依赖管理与版本选择
版本兼容性:ExoPlayer 的版本需要与项目的 Android Gradle 插件版本和 Compose 版本兼容,避免因版本不匹配导致的问题
2. 生命周期管理
释放资源:ExoPlayer是资源密集型组件,必须在不再使用时释放资源。可以通过DisposableEffect或onDispose 来管理生命周期,确保在onStop或onDestroy时调用player.release()。

DisposableEffect(lifecycleOwner) {
    onDispose {
        exoPlayer.release()
    }
}

3. 媒体源设置
动态设置媒体源:在播放不同媒体时,需要动态设置 MediaItem,并调用 player.setMediaItem() 和 player.prepare()。

4. 线程与异步操作
后台线程加载:避免在主线程中进行媒体加载或解码操作,ExoPlayer会自动处理大部分异步操作,但确保网络请求或大数据处理在后台线程中完成。
异步准备:如果需要预加载媒体,可以在后台线程中调用 player.prepare(),避免阻塞UI。

相关文章:

  • 一、绪论(Introduction of Artificial Intelligence)
  • 多模态大语言模型arxiv论文略读(十五)
  • 【技术派部署篇】Windows本地部署技术派
  • 果篮问题 Python
  • 【论文阅读】RMA: Rapid Motor Adaptation for Legged Robots
  • 最近在工作中感受到了设计模式的重要性
  • SDC命令详解:使用相对路径访问设计对象(current_instance命令)
  • OpenFlow v1.1+流表匹配流程解析
  • DeepSeek私有化部署性能怎么样?企业级AI落地实战解析!
  • vue+leaflet 区域划分_反向遮罩层
  • 深入解析 QuickAPI 三大核心能力:SQL 编辑器、数据 API、数据市场
  • 自然语言处理、计算机视觉与语音处理-AI学习Day3
  • swift菜鸟教程24-25(可选链,自动引用计数)
  • PyTorch张量操作指南:cat、stack、split与chunk的实战拆解
  • vue3中,element-plus中el-input的v-model和value的用法示例
  • 数据结构:哈希表 | C++中的set与map
  • muduo库源码分析: TcpConnection
  • 你的 Linux 服务器连不上网?10 分钟入门网络故障排查
  • 用户态视角理解内核ROP利用:快速从shell到root的进阶
  • 对称加密与非对称加密的特点
  • 商旅网站制作/百度客服电话人工服务
  • 香港主机网站速度/市场营销推广方案怎么做
  • 邯郸网站设计怎么做/推广什么app佣金高
  • 网站建设创新能力痛点/google优化推广
  • 台州电子商务网站建设/免费推广app软件下载
  • wordpress文章中插入代码/百度seo优化培训