Android 多媒体 - 播放音视频

Android 多媒体 —— 播放音视频

播放音频

在 Android 中播放音频一般时通过 MediaPlayer 去实现的,它提供了非常全面的方法去控制音频的播放。

  • setDataSource():设置音频数据源,设置方式较多
  • prepare():执行准备工作。可能涉及获取和解码媒体数据,需要很长时间来执行,主线程中应使用 prepareAsync() 方法
  • prepareAsync():异步执行准备工作,需实现 MediaPlayer.OnPreparedListener,以便在准备工作完成后获得通知并开始播放。
  • start():开始播放
  • pause():暂停播放
  • reset():重置 MediaPlayer 对象为 new 状态
  • seekto(int msec):指定播放的位置(以毫秒为单位的时间)
  • stop():停止,调用该方法后该 MediaPlayer 对象无法再播放音频
  • release():释放 MediaPlayer 对象
  • isPlaying():是否正在播放
  • getDuration():获取音频总时长
  • getCurrentPosition():获取当前的播放位置
  • setLooping(boolean looping):设置是否循环播放
  • setVolume(float leftVolume, float rightVolume):设置音量
  • setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener): 网络流媒体的缓冲监听
  • setOnCompletionListener(MediaPlayer.OnCompletionListener listener): 网络流媒体播放结束监听
  • setOnErrorListener(MediaPlayer.OnErrorListener listener): 设置错误信息监听

这里不作详细介绍,请参考代码和注释。

// 简单实现带滑动进度条的音频播放器,布局自行设计
public class MediaPlayerActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {

    private TextView musicName;
    private TextView total;
    private TextView current;
    private SeekBar seekBar;
    private Button startMusic;
    private Button pauseMusic;
    private Button stopMusic;
    private Button resetMusic;
    private MediaPlayer mediaPlayer = new MediaPlayer();

    private Timer timer;
    private boolean isSeekBarChanging; // 互斥变量,防止进度条与定时器冲突。
    private int currentPosition; // 当前音乐播放的进度
    private SimpleDateFormat sd = new SimpleDateFormat("mm:ss");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_player);
        initView();
        initMediaPlayer();
    }

    private void initMediaPlayer() {
        Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.tell_me_a_story);
        try {
            mediaPlayer.setDataSource(this, uri);
            // res/raw 下的资源 tell_me_a_story.mp3:可以通过 MediaPlayer.create() 方法便捷创建对象,且无需手动 setDataSource()
            // mediaPlayer = MediaPlayer.create(this, R.raw.tell_me_a_story);
            // 本地文件路径:
            // mediaPlayer.setDataSource("/sdcard/tell_me_a_story.mp3");
            // 网络 URL 文件:
            // mediaPlayer.setDataSource("http://www.xxx.com/music/tell_me_a_story.mp3");
            mediaPlayer.prepare();  // 一定是先执行 prepare() 才能执行其他操作,这里仅作演示不使用 prepareAsync(),
            int duration = mediaPlayer.getDuration(); // 获取音频总时长
            seekBar.setMax(duration); // 设置拖动条最大长度
            musicName.setText("tell_me_a_story");
            total.setText(sd.format(duration));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initView() { /**...*/ }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        // 让当前时间进度跟随滑动变化
        current.setText(sd.format(progress));
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        // 滑动时暂停后台定时器
        isSeekBarChanging = true;
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        // 滑动结束后,重新设置值
        isSeekBarChanging = false;
        currentPosition = seekBar.getProgress();
        mediaPlayer.seekTo(currentPosition);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_start_music:
                if (! mediaPlayer.isPlaying()) {
                    mediaPlayer.start();
                    mediaPlayer.seekTo(currentPosition); // 从指定位置播放
                    timer = new Timer();
                    timer.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            if (! isSeekBarChanging) {
                                currentPosition = mediaPlayer.getCurrentPosition();
                                seekBar.setProgress(currentPosition);
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        current.setText(sd.format(currentPosition));
                                    }
                                });
                            }
                        }
                    }, 0, 50);
                }
                break;
            case R.id.btn_pause_music:
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                }
                break;
            case R.id.btn_stop_music:
                if (mediaPlayer.isPlaying()) {
                    // 调用 stop() 方法后该 MediaPlayer 对象无法再播放音频
                    // 一般在 onDestroy() 方法中调用 stop() 和 release()
                    // mediaPlayer.stop();
                }
                break;
            case R.id.btn_reset_music:
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.reset(); // 重置为刚刚创建的状态
                    initMediaPlayer();   // 需要重新对 MediaPlayer 信息进行初始化
                }
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isSeekBarChanging = true;
        if (mediaPlayer != null) {
            mediaPlayer.stop();
            mediaPlayer.release();
            mediaPlayer = null;
        }
        if (timer != null){
            timer.cancel();
            timer = null;
        }
    }
}

播放视频

MediaPlayer 主要用于播放音频,事实上也可以借助 SurfaceView 来播放视频。另外 Android 还提供了一个可以播放视频的 VideoView 控件。

使用 MediaPlayer 播放视频

使用起来与播放音频区别不大,只是还需要对 SurfaceView 进行初始化。当然,也需要在布局文件中添加 <SurfaceView> 控件标签。

<SurfaceView
    android:id="@+id/sfv_video"
    android:layout_width="match_parent"
    android:layout_height="220dp" />
public class MediaPlayerActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener, View.OnClickListener, SurfaceHolder.Callback {

    // ...
    private MediaPlayer mediaPlayer = new MediaPlayer();

    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_player);
        initView();
        initMediaPlayer();
    }

    private void initView() {
        // ...
        // 初始化 SurfaceHolder 类,SurfaceView 的控制器
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this); // 需要实现回调方法
        surfaceHolder.setFixedSize(320, 220);   // 设置分辨率,不设置则为视频默认分辨率
    }

    private void initMediaPlayer() { /**...*/ }

    @Override
    public void onClick(View v) { /**...*/ }

    @Override
    protected void onDestroy() { /**...*/ }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        // 设置音频流类型
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        // 设置视频显示在 SurfaceView 上
        mediaPlayer.setDisplay(holder); // 这里的 holder 与 surfaceHolder 是同一对象
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

    }
}

使用 VideoView 播放视频

VideoView 继承自 SurfaceView,里面封装了一个 MediaPlayer 用于具体的播放业务,并自带了一个简单的控制界面 MediaController。

与 MediaPlayer 配合 SurfaceView 播放视频不同,VideoView 播放之前无需编码装载视频,它会在 start() 开始播放的时候自动装载视频,并且 VideoView 在使用完之后,无需编码回收资源。

VideoView 的功能比较简单,非常适用于那些只单纯地播放视频的场景,如不断循环播放的广告视频。不适用于交互性多的视频播放场景,像调节亮度、调节音量、双击暂停等交互逻辑,VideoView 是无法实现的。

  • setVideoPath(String path):以文件路径的方式设置 VideoView 播放的视频源
  • setVideoURI(Uri uri):以 Uri 的方式设置视频源,可以是网络 Uri 或本地 Uri
  • start():开始或继续播放
  • pause():暂停播放
  • resume():将视频从头开始播放
  • seekto(int msec):从指定的位置开始播放视频(以毫秒为单位的时间)
  • isPlaying():是否正在播放
  • getDuration():获取视频总时长
  • getCurrentPosition():获取当前的播放位置
  • setMediaController(MediaController controller):设置 MediaController 控制器
  • setOnCompletionListener(MediaPlayer.onCompletionListener l):监听播放完成的事件
  • setOnErrorListener(MediaPlayer.OnErrorListener l):监听播放发生错误时候的事件
  • setOnPreparedListener(MediaPlayer.OnPreparedListener l):监听视频装载完成的事件

下面是 VideoView 最简单使用,MediaController 暂不介绍:

  1. 在布局文件中添加 <VideoView> 控件标签。

    <VideoView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="220dp" />
    
  2. 设置视频源,设置按钮点击事件控制视频播放暂停。

    public class VideoViewActivity extends AppCompatActivity implements View.OnClickListener {
    
        private VideoView videoView;
        private Button startVideoView;
        private Button pauseVideoView;
        private Button resumeVideoView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_video_view);
            initView();
            initVideoPath();
        }
    
        private void initView() { /**...*/ }
    
        private void initVideoPath() {
            // 根据文件路径播放
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/test.mp4");
            }
    
            // 读取放在 raw 目录下的文件
            videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.test));
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_start_video_view:
                    if (! videoView.isPlaying()) {
                        videoView.start();
                    }
                    break;
                case R.id.btn_pause_video_view:
                    if (videoView.isPlaying()) {
                        videoView.pause();
                    }
                    break;
                case R.id.btn_resume_video_view:
                    if (videoView.isPlaying()) {
                        videoView.resume();
                    }
                    break;
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (videoView != null) {
                videoView.suspend();    // 释放 VideoView
            }
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿咩AmieVastness

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值