Android自定义音视频播放器

随着快手,抖音,西瓜视频等视频APP的崛起,视频播放已经成为主流,此时做为Android研发的你,想要提升本身的能力还不知道怎么开发视频播放器怎么行?

视频播放器有原生的VideoView、开源的Ijkplayer ExoPlayerJieCaoVideoPlayer等等,这些部分的播放器我们在开发过程中,使用过这些视频播放框架来播放本地视频或者网络视频,但不一定会满足业务需求的。因此,我们可以去自定义一个播放器,在开发Android应用的过程中,难免需要自定义View,其实自定义View不难,只要了解原理,实现起来就没有那么难从而满足业务需求。

先看效果图。

 我自定义一个VideoPlayerView类,主要是由MediaPlayer与SurfaceView相结合组成一个视频播放器,从Android原生的VideoView视频框架,也是基于实现的。

 

  1. 首先编写布局文件layout_video_player.xml


2.编写VideoPlayerView.java文件

public class VideoPlayerView extends FrameLayout implements View.OnClickListener {private static final String TAG = "VideoPlayerView";private SurfaceView mSurfaceView;private MediaPlayer mMediaPlayer = null;private int currentPosition = 0;private SurfaceHolder mSurfaceHolder;private boolean isPlaying;private SeekBar mSeekBar;private Button mPlyer;private String mUrl;private TextView tvCurrentTime, tvTotalTime;private Handler handler = new Handler(Looper.getMainLooper());public VideoPlayerView(Context context) {this(context, null);}public VideoPlayerView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public VideoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}private void init(Context context) {initView(context);// 为SurfaceHolder添加回调mSurfaceHolder = mSurfaceView.getHolder();mSurfaceHolder.addCallback(callback);//设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);}private void initView(Context context) {View view = LayoutInflater.from(context).inflate(R.layout.layout_video_player,null,false);mSurfaceView = view.findViewById(R.id.surfaceview);mSeekBar = view.findViewById(R.id.seekBar);mPlyer = view.findViewById(R.id.player);tvCurrentTime = view.findViewById(R.id.tvCurrentTime);tvTotalTime = view.findViewById(R.id.tvTotalTime);addView(view);mPlyer.setOnClickListener(this);mSeekBar.setOnSeekBarChangeListener(change);}private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {LogUtil.info(TAG, "surfaceCreated被创建");if (currentPosition > 0) {// 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放play(currentPosition);currentPosition = 0;}}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {LogUtil.info(TAG, "surfaceChanged大小改变");}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {LogUtil.info(TAG, "surfaceDestroyed被销毁");if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {currentPosition = mMediaPlayer.getCurrentPosition();mMediaPlayer.stop();mMediaPlayer.release();}}};private void play(int currentPosition) {if (mMediaPlayer != null) {mMediaPlayer.seekTo(currentPosition);}}/*** 播放本地视频* @param mUrl*/public void setPlayerVideo(String mUrl) {this.mUrl = mUrl;LogUtil.info(TAG, "setPlayerVideo mUrl: " + mUrl);if (mUrl.contains("http")) {showRemoteVideo(0);} else {playerLocalView(0);}}// 播放本地视频private void playerLocalView(final int msec) {LogUtil.info(TAG, " 获取视频文件地址");String path = mUrl;File file = new File(path);if (!file.exists()) {Toast.makeText(AppContextUtil.getContext(), "视频文件路径错误", Toast.LENGTH_SHORT).show();return;}LogUtil.info(TAG, "指定视频源路径");try {mMediaPlayer = new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置播放的视频源mMediaPlayer.setDataSource(file.getAbsolutePath());// 设置显示视频的SurfaceHoldermMediaPlayer.setDisplay(mSurfaceHolder);LogUtil.info(TAG, "开始装载");mMediaPlayer.prepareAsync();mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {LogUtil.info(TAG, "装载完成");mMediaPlayer.start();// 按照初始位置播放mMediaPlayer.seekTo(msec);// 设置进度条的最大进度为视频流的最大播放时长mSeekBar.setMax(mMediaPlayer.getDuration());tvCurrentTime.setText(DateUtil.getDate(msec, DateUtil.PARAMETER));tvTotalTime.setText(DateUtil.getDate(mMediaPlayer.getDuration(), DateUtil.PARAMETER));// 开始线程,更新进度条的刻度new Thread() {@Overridepublic void run() {try {isPlaying = true;while (isPlaying) {// 如果正在播放,没0.5.毫秒更新一次进度条int current = mMediaPlayer.getCurrentPosition();mSeekBar.setProgress(current);handler.post(new Runnable() {@Overridepublic void run() {tvCurrentTime.setText(DateUtil.getDate(current, DateUtil.PARAMETER));}});sleep(500);}} catch (Exception e) {e.printStackTrace();}}}.start();}});mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {// 在播放完毕被回调mPlyer.setEnabled(true);}});mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// 发生错误重新播放play(0);isPlaying = false;return false;}});} catch (Exception e) {e.printStackTrace();}}// 播放远程视频private void showRemoteVideo(final int msec) {String videoUrl2 = mUrl;Uri uri = Uri.parse(videoUrl2);try {mMediaPlayer = new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置播放的视频源mMediaPlayer.setDataSource(AppContextUtil.getContext(), uri);// 设置显示视频的SurfaceHoldermMediaPlayer.setDisplay(mSurfaceHolder);LogUtil.info(TAG, "开始装载");mMediaPlayer.prepareAsync();mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {LogUtil.info(TAG, "装载完成");mMediaPlayer.start();// 按照初始位置播放mMediaPlayer.seekTo(msec);// 设置进度条的最大进度为视频流的最大播放时长mSeekBar.setMax(mMediaPlayer.getDuration());tvCurrentTime.setText(DateUtil.getDate(msec, DateUtil.PARAMETER));tvTotalTime.setText(DateUtil.getDate(mMediaPlayer.getDuration(), DateUtil.PARAMETER));// 开始线程,更新进度条的刻度new Thread() {@Overridepublic void run() {try {isPlaying = true;while (isPlaying) {// 如果正在播放,没0.5.毫秒更新一次进度条int current = mMediaPlayer.getCurrentPosition();mSeekBar.setProgress(current);handler.post(new Runnable() {@Overridepublic void run() {tvCurrentTime.setText(DateUtil.getDate(current, DateUtil.PARAMETER));}});sleep(500);}} catch (Exception e) {e.printStackTrace();}}}.start();}});mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mp.release();mp = null;// 在播放完毕被回调mPlyer.setEnabled(true);}});mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {// 发生错误重新播放play(0);isPlaying = false;return false;}});} catch (Exception e) {e.printStackTrace();}}private SeekBar.OnSeekBarChangeListener change = new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 当进度条停止修改的时候触发// 取得当前进度条的刻度int progress = seekBar.getProgress();
//            if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {// 设置当前播放的位置mMediaPlayer.seekTo(progress);tvCurrentTime.setText(DateUtil.getDate(progress, DateUtil.PARAMETER));
//            }}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {}};@Overridepublic void onClick(View v) {if (v.getId() == R.id.player) {if (mMediaPlayer == null) {return;}if (mMediaPlayer.isPlaying()) {mMediaPlayer.pause();mPlyer.setBackgroundResource(R.drawable.ic_play_24);} else {mMediaPlayer.start();mPlyer.setBackgroundResource(R.drawable.ic_pause_24);}}}public void pause () {if (mMediaPlayer == null) {return;}if (mMediaPlayer.isPlaying()) {mMediaPlayer.pause();mPlyer.setBackgroundResource(R.drawable.ic_pause_24);}}public void destroy() {if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.reset();mMediaPlayer.release();mMediaPlayer = null;}isPlaying = false;LogUtil.info(TAG, "MediaPlayer is release");}
}

该类主要做了如下工作:

  1. .初始化控件;
  2. .SurfaceHolder回调监听;
  3. .播放本地视频;
  4. .播放网络视频;
  5. .常用SeekBar显示进度及改变进度;
  6. .额外工作,播放、暂停、总时长、当前时长的View控件。

3.编写Activity的布局文件activity_player_video.xml


 从上面可以看出,里面没有任何子view,仅仅声明FrameLayout和id即可。

4.PlayerVideoActivity.java

public class PlayerVideoActivity extends Activity {private FrameLayout playerLayout;private VideoPlayerView videoPlayerView;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_player_video);initPlayer();}private void initPlayer() {String localPath = Environment.getExternalStorageDirectory() +"/xlk-player.mp4";playerLayout = findViewById(R.id.player_layout);videoPlayerView = new VideoPlayerView(this);playerLayout.addView(videoPlayerView);new Handler().postDelayed(new Runnable() {@Overridepublic void run() {//这里传入本地视频的路径或者网络视频的urlvideoPlayerView.setPlayerVideo(localPath);}},500);}@Overrideprotected void onPause() {super.onPause();if (videoPlayerView != null) {videoPlayerView.pause();}}@Overrideprotected void onDestroy() {super.onDestroy();if (videoPlayerView != null) {videoPlayerView.destroy();videoPlayerView = null;}}
}

上面实例化一个VideoPlayerView,然后通过VideoPlayerView对象来调用setPlayerVideo的方法传入本地视频的路径或者网络视频的url,实现音视频播放器,运行此项目,效果如开头显示的一幕。

自定义View播放器已经实现了,但我们需要掌握MediaPlayer的初始化过程是怎么样的,MediaPlayer做了哪些工作,音视频数据如何渲染到SurfaceView上面的,这需要把MediaPlayer框架原理搞清楚。就像下面的。

 

每个方法调用过程是怎么样子的,做了哪些工作,而不是简单写几行代码就可以实现播放视频或者音频。

小伙伴们有兴趣的话,搜索并关注公众号“Android技术迷”关注后可文章,感谢各位关注。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部