实现效果图:
相关内容划分效果分析:
播放界面大致构造图:
播放界面实现原理:
播放相关代码联系:
媒体播放与视图联系关键代码:mediaPlayer.setDisplay(surfaceHolder);
媒体播放控制:
imageView_main_play
// 隐藏
v.setVisibility( View.GONE );
// 开始播放
mediaPlayerHolder.play();
imageView_play
// 隐藏
if( imageView_main_play.getVisibility() != View.GONE )
imageView_main_play.setVisibility( View.GONE );
isStop = !isStop;
// 播放按钮点击的样式改变
if ( isStop ) {
// 开始播放
mediaPlayerHolder.play();
}else{
// 停止播放
mediaPlayerHolder.pause();
}
媒体动作控制:
//注册,播放完成后的监听
mMediaPlayer.setOnCompletionListener
//监听媒体流是否装载完成
mMediaPlayer.setOnPreparedListener
// 监听媒体播放所有异常
mMediaPlayer.setOnErrorListener
seekBar拖动控制事件:
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// 改变后的值
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser){
}
// 触碰
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
// 放开
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
部分源码共享,以及查找资料相关链接共享:
XML: 注:随便放在一个位置就好了
<!-- 播放界面 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<SurfaceView
android:id="@+id/topic_details_video_surfaceView"
android:layout_width="match_parent"
android:layout_height="200dp"/>
<ImageView
android:id="@+id/topic_details_video_main_play"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="70dp"
android:onClick="clickButton"
android:src="@drawable/ic_mv_play_24dp"/>
</RelativeLayout>
popupwindow.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="center|center_vertical|center_horizontal"
android:orientation="horizontal"
android:background="#000000"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:weightSum="10" >
<ImageView
android:id="@+id/topic_details_video_play"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_weight="1"
android:src="@drawable/ic_mv_play_24dp" />
<SeekBar
android:id="@+id/topic_details_video_seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="5.0"
android:max="100"
android:maxHeight="5dp"
android:minHeight="5dp"
android:layout_marginLeft="5dp"
android:progress="0"
android:thumbOffset="0dp" />
<TextView
android:id="@+id/topic_details_video_playtime"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.3"
android:gravity="center"
android:text="00:00"
android:textColor="@android:color/white"
android:textSize="12sp" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2"
android:gravity="center"
android:text="/"
android:textColor="@android:color/white"
android:textSize="12sp" />
<TextView
android:id="@+id/topic_details_video_totaltime"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.3"
android:gravity="center"
android:text="00:00"
android:textColor="@android:color/white"
android:textSize="12sp" />
<ImageView
android:id="@+id/topic_details_video_fullscreen"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_weight="1"
android:src="@mipmap/fullscreen" />
</LinearLayout>
控制代码:
public void init(){
try {
popupWindow = new PopupWindow(controllerView, RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT, true);
// 播放工具
mediaPlayer= new MediaPlayer();
// 显示组件 , 拖动条 , 播放媒体
timerDesign = new TimerDesign( textView_playTime , seekBar , mediaPlayer );
// 播放工具类
mediaPlayerHolder = new MediaPlayerHolder();
// 相关资源配置
playBackInfoHolder = new PlayBackInfoHolder( seekBar , textView_playTime , timerDesign , textView_duration );
// 相关资源配置
mediaPlayerHolder.setmPlaybackInfoListener( playBackInfoHolder );
// 媒体播放工具
mediaPlayerHolder.setMediaPlayer( mediaPlayer );
// 媒体控制 播放器
mediaPlayerHolder.setControl( imageView_play , imageView_main_play);
// 加载网络资源
mediaPlayerHolder.loadMedia( filePath );
// 加载完成后的回调
mediaPlayerHolder.medisaPreparedCompled();
// 视图提醒
mediaPlayerHolder.setView( view );
// 相关事件绑定
initSurfaceView();
clickControl();
initSeekbar();
}catch ( Exception e){
e.printStackTrace();
}
}
// 点击控制进度条出现
public void initSurfaceView() {
// 媒体绑定
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (mediaPlayer != null) {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDisplay(surfaceHolder);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format , int width, int height) {
}
});
// 设置屏幕的触摸监听
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 表示在点击的瞬间就显示控制条
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
showOrHiddenController();
break;
default:
break;
}
return true;
}
});
}
// 进度条初始化
public void initSeekbar() {
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// 改变后的值
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
seekBar.setProgress( progress );
}
// 触碰
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
// 放开
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if( mediaPlayerHolder.isPlaying() )
mediaPlayerHolder.seekTo( seekBar.getProgress() );
else{
mediaPlayerHolder.seekTo( seekBar.getProgress() );
// 暂停
mediaPlayerHolder.pause();
}
}
});
}
// 点击事件初始化
public void clickControl(){
imageView_main_play.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// 隐藏
v.setVisibility( View.GONE );
// 开始播放
mediaPlayerHolder.play();
// 图片
imageView_play.setImageDrawable(ContextCompat.getDrawable( view.getContext() , R.drawable.ic_mv_wait_24dp));
}
}
);
imageView_play.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// 隐藏
if( imageView_main_play.getVisibility() != View.GONE )
imageView_main_play.setVisibility( View.GONE );
ImageView imageView = (ImageView) v;
// 相关状态更新
if( mediaPlayer.getCurrentPosition() == 0){
isStop = false;
}
isStop = !isStop;
// 播放按钮点击的样式改变
if ( isStop ) {
// 开始播放
mediaPlayerHolder.play();
// 图片
imageView.setImageDrawable(ContextCompat.getDrawable( view.getContext() , R.drawable.ic_mv_wait_24dp));
}else{
// 停止播放
mediaPlayerHolder.pause();
// 图片
imageView.setImageDrawable(ContextCompat.getDrawable( view.getContext() , R.drawable.ic_mv_play_24dp));
}
}
}
);
}
// 控制View的展示和隐藏
public void showOrHiddenController() {
// 隐藏
if (popupWindow.isShowing()) {
popupWindow.dismiss();
} else {
// 显示 // 将dp转换为px
int controllerHeightPixel = (int) ( PhoneParam.getDensity() * 20);
popupWindow.showAsDropDown(surfaceView, 0, -controllerHeightPixel);
}
}
// 相关销毁操作
public void destory() {
// 处理视频播放监听 类
if (mediaPlayerHolder != null){
mediaPlayerHolder.release();
mediaPlayerHolder = null;
}
// 处理视频播放 处理时间 类
if( playBackInfoHolder != null ) {
playBackInfoHolder.onPlaybackCompleted();
playBackInfoHolder = null;
}
// 处理计时器
if( timerDesign != null){
timerDesign.stopTimer();
timerDesign = null;
}
System.gc();
}
}
读取pupopwindow.xml 视图文件:
// 控制进度条的页面
View controller = getLayoutInflater().inflate( R.layout.popupwindow , null);
重要的两个接口文件:
/**
* @description:播放回调
**/
public interface PlaybackInfoListener {
void onDurationChanged(int duration);//总时长
void onPositionChanged(int position);//当前时长进度
void onStateChanged(int state);//记录当前的状态
void onPlaybackCompleted();//播放完成回调
}
/**
* 针对媒体文件的适配器 监听文件
*/
public interface PlayerAdapterListener {
void loadMedia(String musiUrl);//加载媒体资源
void release();//释放资源
boolean isPlaying();//判断是否在播放
void play();//开始播放
void reset();//重置
void pause();//暂停
void medisaPreparedCompled();//完成媒体流的装载
void seekTo(int position);//滑动到某个位置
}
相关实现的代码:【其中一个类的实现类】
/**
* MP3/Video 播放设计类 : 类似监听器的存在
*/
public class MediaPlayerHolder implements PlayerAdapterListener{
public static int PLAYSTATUS0=0;//正在播放
public static int PLAYSTATUS1=1;//暂停播放
public static int PLAYSTATUS2=2;//重置
public static int PLAYSTATUS3=3;//播放完成
public static int PLAYSTATUS4=4;//媒体流装载完成
public static int PLAYSTATUS5=5;//媒体流加载中
public int PLAYBACK_POSITION_REFRESH_INTERVAL_MS = 1000;
private String TAG = MediaPlayerHolder.class.getSimpleName();
public MediaPlayer mMediaPlayer;
private ScheduledExecutorService mExecutor;//开启线程
private PlaybackInfoListener mPlaybackInfoListener;
private Runnable mSeekbarPositionUpdateTask;
private String musiUrl;//音乐地址,可以是本地的音乐,可以是网络的音乐
private View view , control , control2;
public void setmPlaybackInfoListener(PlaybackInfoListener mPlaybackInfoListener) {
this.mPlaybackInfoListener = mPlaybackInfoListener;
}
public void setMediaPlayer( MediaPlayer mediaPlayer ){
this.mMediaPlayer = mediaPlayer;
}
public void setView( View view){
this.view = view;
}
public void setControl( View control){
this.control = control;
}
public void setControl( View control , View control2 ){
this.control = control;
this.control2 = control2;
}
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:初始化MediaPlayer
**/
private void initializeMediaPlayer(){
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
}
//注册,播放完成后的监听
mMediaPlayer.setOnCompletionListener( new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
stopUpdatingCallbackWithPosition(true);
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onStateChanged( PLAYSTATUS3 );
mPlaybackInfoListener.onPlaybackCompleted();
}
}
});
//监听媒体流是否装载完成
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
medisaPreparedCompled();
}
});
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:监听媒体错误信息
**/
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Toast.makeText( view.getContext() , "媒体,缓存中...." , Toast.LENGTH_LONG ).show();
return false;
}
});
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description: 加载媒体资源
**/
@Override
public void loadMedia(String musiUrl) {
if ( musiUrl == null ){
Log.i(TAG,"地址为空");
return;
}
if (mPlaybackInfoListener != null){
mPlaybackInfoListener.onStateChanged( PLAYSTATUS5 );
}
this.musiUrl = musiUrl;
initializeMediaPlayer();
try {
mMediaPlayer.reset();//防止再次添加进来出现崩溃信息
mMediaPlayer.setDataSource(musiUrl);
mMediaPlayer.prepareAsync(); // 异步加载
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:释放媒体资源
**/
@Override
public void release() {
if (mMediaPlayer != null) {
stopUpdatingCallbackWithPosition(false);
mMediaPlayer.release();
mMediaPlayer = null;
}
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:判断是否正在播放
**/
@Override
public boolean isPlaying() {
if (mMediaPlayer != null) {
return mMediaPlayer.isPlaying();
}
return false;
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:播放开始
**/
@Override
public void play() {
if ( mMediaPlayer != null && !mMediaPlayer.isPlaying()) {
mMediaPlayer.start();
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onStateChanged( PLAYSTATUS0 );
}
startUpdatingCallbackWithPosition();
}
}
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:开启线程,获取当前播放的进度
**/
private void startUpdatingCallbackWithPosition() {
if (mExecutor == null) {
mExecutor = Executors.newSingleThreadScheduledExecutor();
}
if (mSeekbarPositionUpdateTask == null) {
mSeekbarPositionUpdateTask = new Runnable() {
@Override
public void run() {
updateProgressCallbackTask();
}
};
}
mExecutor.scheduleAtFixedRate(
mSeekbarPositionUpdateTask,
0,
PLAYBACK_POSITION_REFRESH_INTERVAL_MS,
TimeUnit.MILLISECONDS
);
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:重置
**/
@Override
public void reset() {
if (mMediaPlayer != null) {
loadMedia( musiUrl );
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onStateChanged( PLAYSTATUS2 );
}
stopUpdatingCallbackWithPosition(true);
}
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:暂停
**/
@Override
public void pause() {
if ( mMediaPlayer != null && mMediaPlayer.isPlaying() ) {
mMediaPlayer.pause();
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onStateChanged( PLAYSTATUS1 );
}
}
}
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:更新当前的进度
**/
private void updateProgressCallbackTask() {
if ( mMediaPlayer != null && mMediaPlayer.isPlaying() ) {
int currentPosition = mMediaPlayer.getCurrentPosition();
if (mPlaybackInfoListener != null) {
mPlaybackInfoListener.onPositionChanged( currentPosition );
}
}
}
/**
* @date: 2019/6/24 0024
* @author: gaoxiaoxiong
* @description:加载完成回调
**/
@Override
public void medisaPreparedCompled() {
int duration = mMediaPlayer.getDuration();//获取总时长
if (mPlaybackInfoListener != null) {
// 让视频 出现画面
if( control2 != null)
mMediaPlayer.seekTo( 0 ); // 视频播放界面 显示刚开始的画面。【优化点】
mPlaybackInfoListener.onDurationChanged ( duration );
mPlaybackInfoListener.onPositionChanged( 0 );
mPlaybackInfoListener.onStateChanged( PLAYSTATUS4 );
}
}
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:滑动播放到某个位置
**/
@Override
public void seekTo(int position) {
if (mMediaPlayer != null) {
// 媒体设置该位置
mMediaPlayer.seekTo( position );
// 更新进度条位置
if ( mPlaybackInfoListener != null ) {
mPlaybackInfoListener.onPositionChanged( position );
}
}
}
/**
* @date: 2019/6/21 0021
* @author: gaoxiaoxiong
* @description:播放完成回调监听
**/
private void stopUpdatingCallbackWithPosition(boolean resetUIPlaybackPosition) {
if (mExecutor != null) {
mExecutor.shutdownNow();
mExecutor = null;
mSeekbarPositionUpdateTask = null;
// 图片设置 // 视频播放界面 控制按钮 效果展示。【优化点】
if( control != null) {
((ImageView) control).setImageDrawable(ContextCompat.getDrawable(view.getContext(), R.drawable.ic_mv_play_24dp));
}
if( control2 != null) {
// 图片出现
control2.setVisibility( View.VISIBLE );
((ImageView) control2).setImageDrawable(ContextCompat.getDrawable(view.getContext(), R.drawable.ic_mv_play_24dp));
}
// true 表示 可能是重置,也可以是注册后
if (resetUIPlaybackPosition && mPlaybackInfoListener != null) {
mPlaybackInfoListener.onPositionChanged( 0 );
// 重置
mPlaybackInfoListener.onStateChanged( 2 );
// 播放完成后的回调函数
}else if( mPlaybackInfoListener != null){
mPlaybackInfoListener.onPlaybackCompleted();
}
}
}
}
PlayBackInfoHolder 对象:
/**
* @ see 处理 视频以及音频播放 期间的控制条事件
* @ time 20191121
*/
public class PlayBackInfoHolder implements PlaybackInfoListener {
private SeekBar seekBar;
private TextView textView;
private TimerDesign timerDesign;
private TextView endCount;
private MediaPlayer mediaPlayer;
public void setMediaPlayer(MediaPlayer mediaPlayer) {
this.mediaPlayer = mediaPlayer;
}
public PlayBackInfoHolder(SeekBar seekBar, TextView textView, TimerDesign timerDesign
, TextView endCount) {
this.seekBar = seekBar;
this.textView = textView;
this.timerDesign = timerDesign;
this.endCount = endCount;
}
//总时长
@Override
public void onDurationChanged(int position) {
// seebar 的总长度 设置为音频获取的总长度
seekBar.setMax(100);
textView.setText("00:00");
// 结束 计数 组件
endCount.setText( TimerDesign.setPlayInfo( position / 1000 ) );
System.out.println( WebPathRes.TAG + "媒体流信息 总时间设置..." );
}
//当前时长进度 : 更新当前进度
@Override
public void onPositionChanged(int position) {
if (mediaPlayer != null) {
// 更新为当前进度
seekBar.setProgress(position * 100 / mediaPlayer.getDuration());
// 修改文件上面的显示
textView.setText( TimerDesign.setPlayInfo( position / 1000 ) );
}
}
//记录当前的状态
@Override
public void onStateChanged(int state) {
switch (state) {
case 0: // 正在播放
timerDesign.startTimer();
break;
case 1: // 暂停
timerDesign.stopTimer();
break;
case 3: // 播放完成
timerDesign.stopTimer();
case 2: // 重置
case 4: // 媒体流加载完成
seekBar.setProgress(0);
textView.setText("00:00");
System.out.println( WebPathRes.TAG + "媒体流信息 完成..." );
break;
case 5:
seekBar.setProgress(0);
textView.setText("00:00");
System.out.println( WebPathRes.TAG + "媒体流信息 load..." );
break;
default:
break;
}
}
//播放完成回调
@Override
public void onPlaybackCompleted() {
seekBar.setProgress(0);
textView.setText("00:00");
timerDesign.stopTimer();
System.out.println( WebPathRes.TAG + "媒体流信息 结束..." );
}
}
相关坑点:
SurfaceView android:layout_height="200dp" // 如果大于200dp,将出现 有声音,没有界面
具体值 , 实测 就知道。
注:相关 博文/简书 阅读 完成 灵感来源
1 . Android MediaPlayer的封装 :https://www.jianshu.com/p/c15a0c2a00a5
2.定时器和SeekBar的封装:https://blog.csdn.net/weixin_43359405/article/details/102989893