在前一篇文档中介绍了如何在AR\三维场景创建几种背景
【Sceneform-EQR】scenefrom-eqr中的几种背景实现(不仅用于AR、三维场景,在图片、视频播放器中也适用)
本文将侧重介绍如何使用安卓MediaPlayer实现视频播放。
↓↓↓↓↓↓↓↓↓↓↓↓ 以下正文 ↓↓↓↓↓↓↓↓↓↓↓↓
Sceneform-EQR
简介
Sceneform-EQR是EQ基于sceneform(filament)扩展的一个用于安卓端的三维渲染器。
相关链接
Git仓库
- Sceneform-EQR
码云
- EQ-Renderer的示例工程
EQ-R相关文档
- 文档目录
- CSDN专栏
MediaPlayer基础知识
若已熟悉MediaPlayer的使用,则可跳过本小节内容,直接看下一节 “使用MediaPlayer实现视频播放”
介绍
Android 的 MediaPlayer 是一个用于播放音频和视频的类,它支持多种格式的媒体文件和流媒体。它提供了非常高层次的接口,使开发者可以轻松实现媒体播放功能,如播放、暂停、停止、快进、倒退等操作。MediaPlayer 适用于需要播放本地或网络媒体资源的 Android 应用。
功能
-
支持的媒体类型:MediaPlayer 支持各种常见的媒体文件格式,如 MP3、MP4、MPEG、3GP、AAC、WAV、OGG 等,以及通过网络流式传输的音视频文件。
-
状态管理:MediaPlayer 有多个状态(如 Idle、Initialized、Prepared、Started、Paused 等),开发者需要在不同状态下正确调用方法以避免崩溃或错误。
-
事件监听:MediaPlayer 提供了多种监听器(如 OnPreparedListener、OnCompletionListener、OnErrorListener),以处理播放开始、完成、错误等事件。
使用步骤
-
初始化 MediaPlayer:首先,创建一个 MediaPlayer 对象,可以通过调用 new MediaPlayer() 或者使用静态方法 create() 进行初始化。
-
设置数据源:使用 setDataSource() 方法为 MediaPlayer 设置音频或视频文件的路径,数据源可以是本地文件、网络 URL 或者其他 URI。
-
准备播放:调用 prepare() 或 prepareAsync() 方法准备播放资源。对于大文件或网络资源,推荐使用异步准备(prepareAsync()),避免阻塞主线程。
-
开始播放:当资源准备好后,可以调用 start() 方法开始播放。
-
暂停和停止:可以使用 pause() 暂停播放,使用 stop() 完全停止播放。
-
释放资源:当不再需要 MediaPlayer 时,应该调用 release() 方法释放资源,避免内存泄漏。
监听事件
- OnPreparedListener:当调用 prepareAsync() 后,资源准备完成时触发该监听器。
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
// 开始播放
mediaPlayer.start();
}
});
- OnCompletionListener:当播放完成时触发该监听器,可以用来处理播放结束后的操作。
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// 播放完成的逻辑
mediaPlayer.stop();
}
});
- OnErrorListener:当播放过程中出现错误时触发该监听器,便于处理错误。
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 错误处理逻辑
return true;
}
});
常见问题
-
主线程阻塞问题:prepare() 是同步方法,可能会阻塞主线程,尤其是在处理大文件或网络流媒体时。推荐使用 prepareAsync() 异步方法,它不会阻塞主线程,并在准备完成时通过 OnPreparedListener 通知。
-
内存泄漏问题:未释放 MediaPlayer 资源可能会导致内存泄漏。在 Activity 或 Fragment 销毁时,一定要调用 release() 释放资源。
-
音视频同步:MediaPlayer 支持同时播放音频和视频,但对于视频播放,可能会遇到音画不同步的问题。这种情况下,可能需要更高级的播放器(如 ExoPlayer)进行优化。
使用MediaPlayer实现视频播放
按惯例,先看结果,再贴代码,最后在补充。
示例结果
示例中实现了基础的视频播放,运行截图如下:
示例代码
Layout
- 添加场景布局控件SceneLayout,用于渲染视频。
- 添加VideoTimeLine组件,用于显示播放进度。
完整xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".VideoActivity">
<com.eqgis.eqr.layout.SceneLayout
android:id="@+id/video_scene_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.eqgis.media.component.VideoTimeLine
android:layout_alignParentBottom="true"
android:layout_marginBottom="20dp"
android:id="@+id/time_line"
android:layout_width="match_parent"
android:layout_height="20dp"/>
<TextView
android:textSize="24sp"
android:text="样例视频"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
VideoActivity
在Sceneform-EQR的SmapleProj中,写了个示例VideoActivity供参考。
当ExSceneView初始化成功后,使用MediaPlaer加载默认的视频。
public class VideoActivity extends BaseActivity{
private ExternalTexture externalTexture;
private MediaPlayer mediaPlayer;
private VideoTimeLine videoTimeLine;
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//普通三维场景(场景3选1)
setContentView(R.layout.activity_video_scene);
sceneLayout = findViewById(R.id.video_scene_layout);
sceneLayout.enableExSceneView(true).init(this);
videoTimeLine = findViewById(R.id.time_line);
sceneLayout.getExSceneView().setInitializeListener(new ExSceneView.InitializeListener() {
@Override
public void initializeTexture(ExternalTexture texture) {
//纹理初始化成功时,触发回调
externalTexture = texture;
try {
loadDefaultVideo();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
/**
* 加载默认视频
* @throws IOException
*/
private void loadDefaultVideo() throws IOException {
//这里使用eq_test_video.mp4为例,实际上,你也可以通过其它方式创建MediaPlayer,并设置数据源
mediaPlayer = MediaPlayer.create(this,R.raw.eq_test_video);
videoTimeLine.bindView(sceneLayout.getExSceneView(),mediaPlayer);
mediaPlayer.setLooping(true);//循环播放
mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int w, int h) {
if (externalTexture != null){
externalTexture.getSurfaceTexture().setDefaultBufferSize(w, h);
mediaPlayer.setSurface(externalTexture.getSurface());
}
}
});
//就绪时,自动播放
mediaPlayer.start();
}
}
注意:在SceneLayout初始化(init方法)前,需要启用ExSceneView模式,因为只有ExSceneView实现了背景的扩展(能够获取SurfaceTexture对象),而这里我们要基于此去渲染视频。
补充内容
VideoTimeLine组件
源码:VideoTimeline.java
方式:
使用的是Seekbar去做的播放器的进度条,使用TextView显示时间文本。
关键点:
- 在Scene的onUpdate事件中,实时更新seekBar的进度。
- 在拖拽Seekbar时,通过mediaPlayer.seekTo更新视频进度。
- 此外,需要注意避免上述两点的相互调用引起的冲突。
视频播放
ExSceneView继承SceneView,可用于渲染任何被安卓Surface支持绘制的内容。
SceneLayout基于SceneView实现,通过两步即可将MediaPlayer的播放内容,绘制在SceneLayout组件中。
- 获取SceneLayout的externalTexture(通过ExSceneView获取)
方式1:初始化成功后回调
sceneLayout.getExSceneView().setInitializeListener(new ExSceneView.InitializeListener() {
@Override
public void initializeTexture(ExternalTexture texture) {
//纹理初始化成功时,触发回调
externalTexture = texture;
try {
loadDefaultVideo();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
方式2:直接通过ExSceneView的getExternalTexture方法
/**
* 获取拓展纹理
* @return {@link ExternalTexture}
*/
@Nullable
public ExternalTexture getExternalTexture() {
return externalTexture;
}
- 给MediaPlayer设置surface
mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(MediaPlayer mediaPlayer, int w, int h) {
if (externalTexture != null){
externalTexture.getSurfaceTexture().setDefaultBufferSize(w, h);
mediaPlayer.setSurface(externalTexture.getSurface());
}
}
});