android MediaPlayer正确使用姿势

ops/2024/9/23 18:43:37/

android MediaPlayer正确使用姿势!!!

mediaplayer在使用时候还好需要注意很多细节,现在很多播放都是推荐使用exoplayer ,但是毕竟原生的使用掌握还是很有必要的,为什么mediaplayer使用时候会有很多不顺手地方:

MediaPlayer 在不同状态下使用某些 API 会出现异常,主要有以下原因:

一、MediaPlayer 的状态机模型

MediaPlayer 遵循特定的状态机模型,它有多个状态,如 Idle(空闲)、Initialized(初始化)、Prepared(准备好)、Started(开始播放)、Paused(暂停)、Stopped(停止)、Ended(播放结束)等。在不同的状态下,只有特定的 API 调用是合法的,否则会引发异常。
例如,在 Idle 状态下,直接调用 start() 方法会抛出异常,因为此时 MediaPlayer 还没有被初始化或设置媒体源。
同样,在未经过正确的状态转换就调用某些方法也会导致问题。比如,在没有调用 prepare() 或 prepareAsync() 使 MediaPlayer 进入 Prepared 状态之前,调用 start() 也是不允许的。

二、资源管理和同步问题

MediaPlayer 在不同状态下对资源的管理和同步有严格的要求。
当处于特定状态时,某些 API 可能会触发对资源的访问或操作,如果此时资源未正确初始化或处于不一致的状态,就会引发异常。
例如,在播放过程中(Started 状态),如果尝试设置新的媒体源而没有先停止当前播放并正确处理状态转换,可能会导致混乱和异常。

三、内部逻辑和线程安全考虑

MediaPlayer 内部可能涉及多个线程的协作,如播放线程、解码线程等。
在不同状态下,这些线程的行为和交互是有特定规则的。如果不按照正确的状态顺序调用 API,可能会破坏这些规则,导致线程之间的同步问题和异常。
例如,在某些状态下,某些 API 可能会触发异步操作,如果在不适当的时候调用这些 API,可能会导致与其他正在进行的操作冲突,引发异常。

总之,为了避免在使用 MediaPlayer 时出现异常,必须严格按照其状态机模型正确地进行状态转换和 API 调用,以确保资源的正确管理和线程的安全协作。

下面编写两个工具类可以解决不顺手问题:

package com.github.jasonhancn.tvcursor.util;import android.content.Context;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceHolder;import com.realtop.mqttutils.MUtils;public class NewPlayer {private static final String TAG = "NativePlayer";private final Handler handler;private final Handler.Callback callback;private final String path;private final SurfaceHolder mSurfaceHolder;private final Context context;private MediaPlayer mediaPlayer;private boolean isCloseByUser;private long start;public static final int end_state = 1;public static final int error_state = 3;public static final int prepare_state = 5;public static final int progress_state = 6;private boolean isPrepared;//全部进入状态 可以关闭释放了private boolean isEnterComplete = false;//防止完成多次调用private boolean isEnterPrepare = false;//防止准备完成多次调用private boolean isEnterError = false;//防止错误多次调用boolean isWorking = false;long seekTime = -1;private int isPause = -1;int videoWidth = -1;int videoHeight = -1;long videoTotalTime = 0;public void setSeekTime(long seekTime) {this.seekTime = seekTime;syncSeek();}public NewPlayer(Context context, String path, SurfaceHolder mSurfaceHolder, Handler.Callback callback) {this.callback = callback;this.path = path;this.mSurfaceHolder = mSurfaceHolder;this.context = context;this.handler = new Handler(Looper.getMainLooper());initPlayer();}public void initPlayer() {start = System.currentTimeMillis();try {mediaPlayer = new MediaPlayer();mediaPlayer.setDataSource(path);Log.i(TAG, "Init: player video path:" + path);if (mSurfaceHolder != null) mediaPlayer.setDisplay(mSurfaceHolder);mediaPlayer.setOnPreparedListener(iMediaPlayer -> handler.post(this::onPrepareCallback));mediaPlayer.setOnCompletionListener(iMediaPlayer -> handler.post(this::onCompleteCallback));mediaPlayer.setOnErrorListener((iMediaPlayer, i, code) -> {handler.post(() -> onErrorCallback(i, "error code:" + code));return true;});mediaPlayer.prepareAsync();} catch (Exception e) {final String errorMsg = e.getMessage();handler.post(() -> onErrorCallback(-118, errorMsg));}}public long getCurrentTime() {if (mediaPlayer != null && isWorking) {return mediaPlayer.getCurrentPosition();}return 0;}public long getTotalTime() {return videoTotalTime;}public int getWidth() {return videoWidth;}public int getHeight() {return videoHeight;}public boolean isPlaying() {if (mediaPlayer != null && isWorking) {return mediaPlayer.isPlaying();}return false;}void syncSeek() {if (seekTime != -1 && isWorking && mediaPlayer != null) {seekTime = Math.min(seekTime, getTotalTime());seekTime = Math.max(0, seekTime);mediaPlayer.seekTo((int) seekTime);seekTime = -1;}}public void setPause(boolean pause) {isPause = pause ? 1 : 0;syncPause();}void syncPause() {if (isWorking && mediaPlayer != null && isPause != -1) {if (isPause == 1 && mediaPlayer.isPlaying()) {mediaPlayer.pause();} else if (isPause == 0 && !mediaPlayer.isPlaying()) {mediaPlayer.start();}isPause = -1;}}private void onPrepareCallback() {isPrepared = true;isWorking = true;//获取视频信息videoWidth = mediaPlayer.getVideoWidth();videoHeight = mediaPlayer.getVideoHeight();videoTotalTime = mediaPlayer.getDuration();// 准备好前已经人工停止了if (isCloseByUser) {Log.i(TAG, "onPrepareCallback: user close media play:" + mediaPlayer);releasePlayer();return;}if (isEnterPrepare) {Log.i(TAG, "onPrepareCallback: again enter prepare");return;}isEnterPrepare = true;syncSeek();if (isPause == -1) { // 如果用户没有暂停操作的话执行mediaPlayer.start();}syncPause();handler.post(() -> {Message msg = Message.obtain();msg.what = prepare_state;callback.handleMessage(msg);});Log.i(TAG, "onPrepareCallback: prepare cost time:" + (System.currentTimeMillis() - start) + "; is playing:" + mediaPlayer.isPlaying());}private void onCompleteCallback() {isPrepared = true;isWorking = false;if (isEnterComplete) {Log.i(TAG, "onCompleteCallback: again enter");return;}isEnterComplete = true;releasePlayer();handler.post(() -> {Message msg = Message.obtain();msg.what = end_state;msg.obj = "end";callback.handleMessage(msg);});Log.i(TAG, "completeCallback: video player end:" + (System.currentTimeMillis() - start) / 1000);}private void onErrorCallback(int i, String str) {isPrepared = true;isWorking = false;if (isEnterError) {Log.i(TAG, "onErrorCallback: again enter");return;}isEnterError = true;releasePlayer();handler.post(() -> {Message msg = Message.obtain();msg.what = error_state;msg.obj = "err:" + str;callback.handleMessage(msg);});Log.i(TAG, "errorCallback: play error code:" + i + "; use time:" + (System.currentTimeMillis() - start) / 1000);MUtils.showMsg(context, "Video play failure ! error code:" + i + "; msg:" + str);}// 主线程public void stopPlayerRelease() {isCloseByUser = true;releasePlayer();}public boolean isEnd() {return mediaPlayer == null;}private void releasePlayer() {handler.removeCallbacksAndMessages(null);Log.i(TAG, "Release: enter: isPrepared:" + isPrepared+ "; isCloseByUser:" + isCloseByUser + "; media player:" + mediaPlayer);if (!isPrepared || mediaPlayer == null) return;try {if (mediaPlayer.isPlaying()) {mediaPlayer.stop();Log.i(TAG, "Release: enter stop player");}} catch (Exception e) {Log.i(TAG, "Release: pause error:" + e.getMessage());}try {mediaPlayer.setOnCompletionListener(null);mediaPlayer.setOnPreparedListener(null);mediaPlayer.setOnErrorListener(null);} catch (Exception e) {Log.i(TAG, "Release: close listener error:" + e.getMessage());}try {mediaPlayer.setDisplay(null);} catch (Exception e) {Log.i(TAG, "releasePlayer: set display null error:" + e.getMessage());}try {mediaPlayer.release();} catch (Exception e) {Log.i(TAG, "Release: release error:" + e.getMessage());}mediaPlayer = null;Log.i(TAG, "Release: handle ok");}public static int[] calculateImageSize(Context context, int imageWidth, int imageHeight) {DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();int screenWidth = displayMetrics.widthPixels;int screenHeight = displayMetrics.heightPixels;int newWidth = imageWidth;int newHeight = imageHeight;if (imageWidth > screenWidth || imageHeight > screenHeight) {float ratioWidth = (float) imageWidth / screenWidth;float ratioHeight = (float) imageHeight / screenHeight;float ratio = Math.max(ratioWidth, ratioHeight);newWidth = (int) (imageWidth / ratio);newHeight = (int) (imageHeight / ratio);}return new int[]{newWidth, newHeight};}}

还有一个类似videoview的工具类

package com.github.jasonhancn.tvcursor.util;import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.ViewParent;import androidx.annotation.NonNull;public class NewPlayerView extends SurfaceView implements SurfaceHolder.Callback2, Handler.Callback {private static final String TAG = "JasonVideoView";private SurfaceHolder mHolder;NewPlayer player;String videoPath;String lastVideoPath;long currentSeekTime;private Context mContext;Handler handler = new Handler(Looper.getMainLooper());Handler.Callback callback;private long userSeekTime = -1;private int userPause = -1;int videoWidth = -1;int videoHeight = -1;long videoTotalTime = 0;public void setCallback(Handler.Callback callback) {this.callback = callback;}public void setVideoPath(String videoPath) {this.videoPath = videoPath;this.currentSeekTime = 0;this.userPause = -1;this.userSeekTime = -1;this.lastVideoPath = videoPath;resetVideoInfo();releasePlayer();initPlayer();}public NewPlayerView(Context context) {super(context);init(context);}public NewPlayerView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public NewPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}public NewPlayerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);init(context);}private void initPlayer() {if (player != null || mHolder == null || TextUtils.isEmpty(videoPath)) return;player = new NewPlayer(mContext, videoPath, mHolder, this);if (userSeekTime == -1) {player.setSeekTime(currentSeekTime);}syncSeek();syncPause();handler.post(syncTimeStateRunnable);Log.i(TAG, "initPlayer: enter:" + player);}public void goForward() {if (getTotalTime() > 0) {long go = (long) (getCurrentTime() + getTotalTime() * 0.1f);setSeek(go);}}public void goBack() {if (getTotalTime() > 0) {long go = (long) (getCurrentTime() - getTotalTime() * 0.1f);setSeek(go);}}public void setSeek(long time) {if (isEnd() && !TextUtils.isEmpty(lastVideoPath)) {setVideoPath(lastVideoPath);}userSeekTime = time;syncSeek();}private void syncSeek() {if (player != null && userSeekTime != -1) {player.setSeekTime(userSeekTime);userSeekTime = -1;}}public int getVideoWidth() {return videoWidth;}public int getVideoHeight() {return videoHeight;}public long getCurrentTime() {if (player != null) {return player.getCurrentTime();}return 0;}public long getTotalTime() {return videoTotalTime;}public boolean isPlaying() {if (player != null) return player.isPlaying();return false;}public boolean isEnd() {if (player != null) return player.isEnd();return true;}public void setPause(boolean pause) {if (isEnd() && !pause && !TextUtils.isEmpty(lastVideoPath)) {setVideoPath(lastVideoPath);return;}userPause = pause ? 1 : 0;syncPause();}private void syncPause() {if (player != null && userPause != -1) {player.setPause(userPause == 1);}}public void stopPlayer() {videoPath = null;currentSeekTime = 0;userPause = -1;userSeekTime = -1;releasePlayer();}private void releasePlayer() {if (player == null) return;handler.removeCallbacks(syncTimeStateRunnable);player.stopPlayerRelease();Log.i(TAG, "releasePlayer: enter:"+player);player = null;}Runnable syncTimeStateRunnable = new Runnable() {@Overridepublic void run() {if (player != null) {if (getCurrentTime() > 0) {currentSeekTime = getCurrentTime();}if (callback != null) {Message msg = Message.obtain();msg.what = NewPlayer.progress_state;callback.handleMessage(msg);}}handler.postDelayed(syncTimeStateRunnable, 128);}};@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {mHolder = holder;releasePlayer();initPlayer();}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {mHolder = null;releasePlayer();}private void init(Context context) {mContext = context;getHolder().addCallback(this);}public void release() {handler.removeCallbacksAndMessages(null);callback = null;getHolder().removeCallback(this);ViewParent parent = getParent();if (parent instanceof ViewGroup) {((ViewGroup) parent).removeView(this);}}@Overridepublic void surfaceRedrawNeeded(@NonNull SurfaceHolder holder) {}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {}@Overridepublic boolean handleMessage(@NonNull Message msg) {if (msg.what == NewPlayer.prepare_state) {videoWidth = player.getWidth();videoHeight = player.getHeight();videoTotalTime = player.getTotalTime();}if (msg.what == NewPlayer.error_state) {lastVideoPath = null;resetVideoInfo();}if (msg.what == NewPlayer.end_state || msg.what == NewPlayer.error_state) {stopPlayer();}if (msg.what == NewPlayer.prepare_state || msg.what == NewPlayer.end_state || msg.what == NewPlayer.error_state) {if (callback != null) {callback.handleMessage(msg);}}return false;}private void resetVideoInfo() {videoTotalTime = 0;videoHeight = -1;videoWidth = -1;}
}

调用案例:

package com.github.jasonhancn.tvcursor;import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ProgressBar;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;import com.example.mqttdome.R;
import com.github.jasonhancn.tvcursor.util.NewPlayer;
import com.github.jasonhancn.tvcursor.util.NewPlayerView;public class TestActivity extends AppCompatActivity implements Handler.Callback {private static final String TAG = "TestActivity";NewPlayerView playerView;private ProgressBar progress;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.test_activity);setTitle("信发视频小组件测试");progress = findViewById(R.id.progressBar);playerView = findViewById(R.id.playerView);playerView.setCallback(this);playerView.setVideoPath("http://em.21dtv.com/songs/60052907.mkv");}@Overrideprotected void onDestroy() {playerView.release();super.onDestroy();}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.test_menu, menu);return super.onCreateOptionsMenu(menu);}@SuppressLint("SetTextI18n")@Overridepublic boolean onOptionsItemSelected(@NonNull MenuItem item) {if (item.getItemId() == R.id.pause) {playerView.setPause(true);}if (item.getItemId() == R.id.player) {playerView.setPause(false);}if (item.getItemId() == R.id.first) {playerView.goForward();}if (item.getItemId() == R.id.back) {playerView.goBack();}if (item.getItemId() == R.id.url) {EditText edit = new EditText(this);edit.setText("http://em.21dtv.com/songs/60052907.mkv");new AlertDialog.Builder(this).setTitle("设置地址").setView(edit).setPositiveButton("ok", (dialog, which) -> playerView.setVideoPath(edit.getText().toString())).show();ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) edit.getLayoutParams();layoutParams.leftMargin = 38;layoutParams.rightMargin = 38;edit.setLayoutParams(layoutParams);}return super.onOptionsItemSelected(item);}@Overridepublic boolean handleMessage(@NonNull Message msg) {if (msg.what == NewPlayer.progress_state) {if (playerView.getTotalTime() > 0) {float jd = playerView.getCurrentTime() * 1f / playerView.getTotalTime() * 100;progress.setProgress((int) jd);} else {progress.setProgress(0);}}if (msg.what == NewPlayer.end_state) {progress.setProgress(100);}if (msg.what == NewPlayer.error_state) {progress.setProgress(0);}if (msg.what == NewPlayer.prepare_state) {ViewGroup.LayoutParams layoutParams = playerView.getLayoutParams();int[] size = NewPlayer.calculateImageSize(getApplicationContext(), playerView.getVideoWidth(), playerView.getVideoHeight());layoutParams.width = size[0];layoutParams.height = size[1];playerView.setLayoutParams(layoutParams);Log.i(TAG, "handleMessage: size:" + playerView.getVideoWidth() + "; " + playerView.getVideoHeight());}return false;}
}

使用上面工具类,不用考虑状态问题!!

对你有用请点赞收藏,谢谢!!!


http://www.ppmy.cn/ops/114906.html

相关文章

ChatGPT 在国内使用的方法

AI如今很强大,聊聊天、写论文、搞翻译、写代码、写文案、审合同等等,ChatGPT 真是无所不能~ 作为一款出色的大语言模型,ChatGPT 实现了人类般的对话交流,最主要是能根据上下文进行互动。 接下来,我将介绍 ChatGPT 在国…

docker容器的172的ip地址,如何与宿主机之外的网络设备通信?

宿主机的地址比如是192.168.1.51 而docker容器比如web1,的ip地址是172.17.0.3 容器如何与外界通信? 通过NAT,网络地址转换 首先,宿主机需要设置 echo net.ipv4.ip_forward 1 >> /etc/sysctl.conf sysctl -p 开启…

计算机网络笔记002

### 课堂讨论对话 **学生A**: 老师,计算机网络的组成是怎样的?🤔 **老师**: 非常好的问题!计算机网络主要由硬件、软件和通信协议三部分组成。我们先从硬件开始讨论吧。 **学生B**: 硬件包括哪些设备呢?&#x1f60…

weblogic中间件漏洞复现

后台弱口令getshell 1.开启环境 cd vulhub-master/weblogic/weak_password docker-compose up -d docker ps 2.f访问靶场 访问/console/login/LoginForm.jsp这个目录进行登录, 默认账号密码:weblogic/Oracle123 需要注意的是单个账号进行登录时&…

Flink Task 日志文件隔离

Flink Task 日志文件隔离 任务在启动时会先通过 MdcUtils 启动一个 slf4j 的 MDC 环境,然后将 jobId 添加到 slf4j 的 MDC 容器中,随后任务输出的日志都将附带 joid。 MDC 介绍如下: MDC ( Mapped Diagnostic Contexts ),它是一个…

深度学习基础案例5--VGG16人脸识别(体验学习的痛苦与乐趣)

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前言 这次目标本来要达到60%,但是却非常稳定的达到了40%,​😢​​😢​​😢​​😢​&am…

2024年华为杯数学建模E题-高速公路应急车道启用建模-基于YOLO8的数据处理代码参考(无偿分享)

利用YOLO模型进行高速公路交通流量分析 识别效果: 免责声明 本文所提供的信息和内容仅供参考。尽管我尽力确保所提供信息的准确性和可靠性,但我们不对其完整性、准确性或及时性作出任何保证。使用本文信息所造成的任何直接或间接损失,本人…

【docker】命令之容器操作

一、前言 在上篇博客介绍了关于如何从应用市场,下载镜像后,对镜像的相关操作了。这篇博客呢我们就要讲解我们把镜像下载下来了,启动这个镜像后,就是我们说的容器了,那么容器的具体操作又有那些呢? 二、容器…