Android mediaPlayer + surfaceView 之 Video播放

 

实现效果图:

相关内容划分效果分析:

播放界面大致构造图:

 

播放界面实现原理:

播放相关代码联系:

媒体播放与视图联系关键代码: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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值