Android实践:《简易影音播放器》的实现及简要说明

本文实现的简易影音播放器主要实现的功能,包括选取音乐文件,播放、暂停、停止、快进、快退,选取视频文件播放,音乐文件和视频文件都会给出文件名、文件路径的详细信息。主要涉及到MediaPlayer和VideoView的操作、旋转屏幕后重启活动还能继续上一次未完成的播放、还有Activity的生命周期等知识。

一、MediaPlayer播放音乐的流程

1、创建MediaPlayer对象
mper = new MediaPlayer();
2、要播放一首音乐时,必须先设置音乐的Uri并做好准备工作
mper.reset();					//如果之前播放过其他音乐,那么要先reset
mper.setDataSource(this,uri);	//指定音乐文件的Uri
mper.prepareAsync();			//准备播放,此时会引发一个“音乐准备好了”事件,稍后介绍
3、当音乐准备好之后,就可以用以下方法播放、暂停或停止播放,还可以设置是否要重复播放
mper.start();			//开始播放
mper.pause();			//暂停播放
mper.stop();			//停止播放
mper.setLooping(true);	//设置是否要重复播放(true为要)
注意:执行stop()停止播放后,若要再次播放相同的歌曲,则必须重新执行前面的mper.prepareAsync()准备播放,
而执行pause()暂停后,可以直接用start()继续播放。
4、MediaPlayer会记住当前的播放位置(以秒数为单位),我们可以用程序获取或移动播放位置
int len = mper.getDuration();		//获取音乐的总长度(秒数)
int pos = mper.getCurrentPosition();//获取当前的播放位置(秒数)
mper.seekTo(pos);					//移动播放位置到第 pos 秒的位置
5、当不再需要播放时,必须将MediaPlayer对象释放掉
mper.release();		

二、MediaPlayer的三个重要事件

1、实现MediaPlayer的三个事件监听接口
public class MainActivity extends Activity implements
        MediaPlayer.OnPreparedListener,   	//音乐准备好 监听事件
        MediaPlayer.OnErrorListener,		//发生错误时 监听事件
        MediaPlayer.OnCompletionListener{}	//播放完毕时 监听事件
2、可以使用当前活动对象的this作为事件监听器
mper.setOnPreparedListener(this);   
mper.setOnErrorListener(this);		
mper.setOnCompletionListener(this); 
3、3个监听接口的事件处理方法
public void onPrepared(MediaPlayer mp) {
	//音乐准备好时,准备要做的事情。。。
}
	
public boolean onError(MediaPlayer mp, int what, int extra) {
	//发生错误时要处理的事情。。。
	return false;
}

public void onCompletion(MediaPlayer mp) {
	//播放完毕时要处理的事情。。。
}

三、活动的生命周期
播放音乐时,用户可能会突然切换程序,然后又切换回来。再或者结束程序,这些都要妥善处理。
用户每次切换程序,回到程序,都是在Activity的生命周期内,直到活动结束。几个常用的事件如下所示:

onCreate()		//当活动启动时
onResume()		//当活动获得输入焦点时
onPause()		//当活动失去输入焦点时
onDestory()		//当活动结束时

以音乐播放为例,则音乐播放活动的生命周期如下:

Android实践:《简易影音播放器》的实现及简要说明_第1张图片

(画图的时候少画了一个箭头,手动补上,啊哈哈。。)

当Activity失去输入焦点时就暂停音乐播放,重新获得输入焦点时,一般来说会继续音乐播放,但是直接播放又不合用户使用习惯,因为突然就响了,太吓人。所以最好还是保持暂停,等待用户操作。Activity结束时,要将MediaPlayer对象释放。

当然这个过程涉及到很多事情。例如切换程序的时候,正常的音乐播放器还是会继续播放音乐的,如果用户切换的是文本阅读程序,音乐不会暂停,如果是视频播放程序,音乐就会直接暂停,这里面涉及到很多知识,所以这个简单的播放器就直接切换程序就暂停音乐。手机关闭屏幕进入休眠状态也会失去输入焦点,从而触发onPause()事件,导致音乐暂停,这肯定有解决方案,本程序不打算处理这个问题,而是直接让手机不进入休眠状态,虽然反人类,不管了。在onCreate()中加入以下代码:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

四、使用VideoView搭配MediaController播放视频

1、VideoView可以播放视频文件,但是并没有提供现成的控制菜单,如进度条、控制按钮等。而MediaController可以提供播放时需要的控制组件,搭配VideoView非常合适。关联代码如下:

vdv = (VideoView)findViewById(R.id.videoView); //获取 VideoView 组件
MediaController mediaCtrl = new MediaController(this); //新建播放控制对象
vdv.setMediaController(mediaCtrl);  //设置要用播放控制对象来控制播放
注:使用MediaController需要导入android.widget.MediaController

MediaController只是交给用户操作,而在程序中控制播放的还是要使用VideoView提供的方法:

vdv.setVideoURI(uri);			//设置要播放视频的Uri
vdv.start();				//开始播放
vdv.pause();				//暂停播放
vdv.stopPlayback();			//停止播放
		
boolean b = vdv.isPlaying();		//是否在播放中

int len = vdv.getDuration();		//读取视频长度(秒)
int pos = vdv.getCurrentPosition();	//读取当前的播放位置(秒)
vdv.seekTo();				//设置播放位置(秒)

2、播放视频时切换到其他程序时,应该暂停程序,回来的时候可以继续播放程序。这就要用到Activity的生命周期,利用onPause()和onResume()。在切换程序时,触发onPause()事件,此时记录下当前播放位置,在获得输入焦点时,根据保存的播放位置信息,移动到该位置进行继续播放。具体流程如下所示:

Android实践:《简易影音播放器》的实现及简要说明_第2张图片

播放视频最好是充满屏幕,所以本程序选择新建一个活动来播放视频,并且去掉该活动的状态栏和标题栏。并且为了不让视频播放一会就进入休眠状态,需要设置屏幕不休眠。如下所示:

protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//隐藏系统的状态栏
        getSupportActionBar().hide();           //隐藏标题栏

        setContentView(R.layout.activity_video);
		//保持屏幕一直开着 (不要自动休眠)
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}

这两个语句必须在setContentView()之前,另外,上述代码是建立在活动类继承了AppCompatActivity类的基础,目的是与旧版Android系统相兼容,所以,因为使用到了兼容函数库(Support Library),所以隐藏标题栏是这样的书写格式,若当前活动类继承的是Activity类,则代表不使用兼容函数库,隐藏标题栏的代码应改为:

getActionBar().hide();
4、播放视频时旋转屏幕的处理:
因为旋转屏幕会造成Activity重启前,引发savedInstanceState(Bundle sb)如果不对旋转前的数据进行记录,旋转后的程序就回到解放前了。
所以要对旋转前的数据进行记录,记得onCreate()方法的参数吗?Bundle savedInstanceState,这个参数平时没有什么作用,这个时候就有作用了,我们可以把视频的播放位置放到该参数中。当活动重启后,可以从该参数中获得上一次播放的位置信息。
注:由于引发了savedInstanceState()事件时,视频已经停止播放,因此不可在此事件中读取视频的播放位置,因为引发该事件之前,会先触发onPause()事件,所以在暂停事件中存储位置信息即可。详细流程如下所示:

Android实践:《简易影音播放器》的实现及简要说明_第3张图片
五、布局

布局只有两个,一个是主程序的,一个是视频播放的。

1、主程序活动的布局

Android实践:《简易影音播放器》的实现及简要说明_第4张图片


布局组件的ID:(请通过按照的text字段对应组件)

id:txvName 
text:文件名
所属组件:TextView

id:txvUri
text:路径
所属组件:TextView

id:btnPickAudio
text:选取歌曲
onClick:onPick
所属组件:Button

id:btnPickVideo
text:选取视频
onClick:onPick
所属组件:Button

id:btnPlay
text:播放
onClick:onMpPlay
所属组件:Button

id:btnStop
text:停止
onClick:onMpStop
所属组件:Button

id:ckbLoop
text:重复播放
onClick:onMpLoop
checked:false
所属组件:CheckButton

id:igbBackward
src:android:drawable/ic_media_rew
onClick:onMpBackward
所属组件:ImageButton

id:igbForward
src:android:drawable/ic_media_ff
onClick:onMpForward
所属组件:ImageButton

2、播放视频活动布局

Android实践:《简易影音播放器》的实现及简要说明_第5张图片


布局组件ID:(清空RelativeLayout的Padding属性,避免边缘留白)

id:videoView
width:match_parent
height:wrap_content

六、所有活动代码

准备:选择一首下载好的音乐文件,在Android Studio的Android模式下的目录结构上,找到res,右击res,选择new/Directory,新建一个名为raw的文件夹,将音乐文件直接Ctrl + C ,Ctrl + V 复制到该文件夹中,并在弹出的窗口中将音乐文件命名为welcome.mp3,这首歌会作为程序里的默认播放音乐存在。

Android实践:《简易影音播放器》的实现及简要说明_第6张图片


1、主程序MainActivity.java代码

package com.my.player;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements
        MediaPlayer.OnPreparedListener,   //实现 MediaPlayer 的 3 个的事件监听界面
        MediaPlayer.OnErrorListener,
        MediaPlayer.OnCompletionListener{

    Uri uri;                   //存储影音文件的 Uri
    TextView txvName, txvUri;  //引用到画面中的组件
    boolean isVideo = false;   //记录是否为视频文件

    Button btnPlay, btnStop;  //用来引用播放按钮、停止按钮
    CheckBox ckbLoop;         //用来引用重复播放多选按钮
    MediaPlayer mper;         //用来引用 MediaPlayer 对象
    Toast tos;                //用来引用 Toast 对象 (显示信息之用)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);//设置屏幕不随手机旋转
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//设置屏幕直向显示
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//设置屏幕不进入休眠

        txvName = (TextView)findViewById(R.id.txvName); //引用到第1个文字组件
        txvUri = (TextView)findViewById(R.id.txvUri);   //引用到第2个文字组件
        btnPlay = (Button)findViewById(R.id.btnPlay);   //引用到播放按钮
        btnStop = (Button)findViewById(R.id.btnStop);   //引用到停止按钮
        ckbLoop = (CheckBox)findViewById(R.id.ckbLoop); //引用到重复播放多选按钮

        uri = Uri.parse("android.resource://" + //默认会播放程序内的音乐文件
                getPackageName() + "/" + R.raw.welcome);
        txvName.setText("welcome.mp3");         //在画面中显示文件名
        txvUri.setText("程序内的乐曲:" + uri.toString());//显示 Uri

        mper = new MediaPlayer();              //新建 MediaPlayer 对象
        mper.setOnPreparedListener(this);      //设置 3 个事件监听器
        mper.setOnErrorListener(this);
        mper.setOnCompletionListener(this);
        tos = Toast.makeText(this, "", Toast.LENGTH_SHORT); //创建 Toast 对象

        prepareMedia();                       //准备播放指定的影音文件

    }

    void prepareMedia() {
        btnPlay.setText("播放");      //将按钮文字恢复为 "播放"
        btnPlay.setEnabled(false);   //使播放钮不能按 (要等准备好才能按)
        btnStop.setEnabled(false);   //使停止按钮不能按

        try {
            mper.reset();                        //如果之前播放过, 必须 reset 后才能更换
            mper.setDataSource(this, uri);       //指定影音文件来源
            mper.setLooping(ckbLoop.isChecked());//设置是否重复播放
            mper.prepareAsync();                 //要求 MediaPlayer 准备播放指定的影音文件
        } catch (Exception e) {                  //拦截错误并显示信息
            tos.setText("指定的影音文件错误!" + e.toString());
            tos.show();
        }
    }

    public void onPick(View v) {
        Intent it = new Intent(Intent.ACTION_GET_CONTENT);  //新建动作为 "选取" 的 Intent
        if (v.getId() == R.id.btnPickAudio) {               //如果是 "选取歌曲" 按钮的 ID
            it.setType("audio/*");                          //要选取所有音乐类型
            startActivityForResult(it, 100);
        } else {                                            //否则就是 "选取视频" 按钮
            it.setType("video/*");                          //要选取所有视频类型
            startActivityForResult(it, 101);
        }
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == Activity.RESULT_OK) {
            isVideo = (requestCode == 101);   //记录是否选取了视频文件 (当标识符为101时)
            uri = data.getData();             //获取选取文件的 Uri
            txvName.setText(isVideo?"视频:":"音乐:" + getFilename(uri));  //显示文件名
            txvUri.setText("文件URI:" + uri.toString());                   //显示文件的 URI
            if(!isVideo) prepareMedia();      //重新准备播放刚选择的影音文件
        }
    }

    String getFilename(Uri uri) {                                   //以 URL 向内容数据库查询文件名
        String fileName = null;
        String[] colName = {MediaStore.MediaColumns.DISPLAY_NAME};  //声明要查询的字段
        Cursor cursor = getContentResolver().query(uri, colName,    //以 uri 进行查询
                null, null, null);
        cursor.moveToFirst();                                       //移到查询结果的第一条记录
        fileName = cursor.getString(0);
        cursor.close();                                             //关闭查询结果
        return fileName;                                            //返回文件名
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        mper.seekTo(0);              //将播放位置归 0
        btnPlay.setText("播放");     //让播放按钮显示 "播放"
        btnStop.setEnabled(false);   //让停止按钮不能按
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        tos.setText("发生错误,停止播放");  //显示错误信息
        tos.show();
        return true;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {

        btnPlay.setEnabled(true);   //当准备好时, 只需让【播放】按钮可以按即可
    }

    public void onMpPlay(View v) {  //按下【播放】按钮时

        if(isVideo) {               //如果是视频
            Intent it = new Intent(this, Video.class); //新建启动 Video Activity 的 Intent
            it.putExtra("uri", uri.toString());        //将视频的 Uri 以 "uri" 为名加入 Intent 中
            startActivity(it);     //启动 Video Activity
            return;
        }

        if (mper.isPlaying()) {        //如果正在播放, 就暂停
            mper.pause();              //暂停播放
            btnPlay.setText("继续");
        }
        else {                         //如果没有在播放, 就开始播放
            mper.start();              //开始播放
            btnPlay.setText("暂停");
            btnStop.setEnabled(true);
        }
    }

    public void onMpStop(View v) {   //按下【停止】按钮时
        mper.pause();                //暂停播放
        mper.seekTo(0);              //移到音乐中 0 秒的位置
        btnPlay.setText("播放");
        btnStop.setEnabled(false);
    }

    public void onMpLoop(View v) {   //按下【重复播放】多选按钮时
        if (ckbLoop.isChecked())
            mper.setLooping(true);   //设置要重复播放
        else
            mper.setLooping(false);  //设置不要重复播放
    }

    public void onMpBackward(View v) {      //按下倒退按钮时
        if(!btnPlay.isEnabled()) return;    //如果还没准备好(播放按钮不能按), 则不处理
        int len = mper.getDuration();       //读取音乐长度
        int pos = mper.getCurrentPosition();//读取当前的播放位置
        pos -= 10000;                       //倒退 10 秒 (10000ms)
        if(pos <0) pos = 0;                 //不可小于 0
        mper.seekTo(pos);                   //移动播放位置
        tos.setText("倒退10秒:" + pos/1000 + "/" + len/1000);  //显示信息
        tos.show();
    }

    public void onMpForward(View v) {       //按下前进按钮时
        if(!btnPlay.isEnabled()) return;    //如果还没准备好(播放按钮不能按), 则不处理
        int len = mper.getDuration();       //读取音乐长度
        int pos = mper.getCurrentPosition();//读取当前的播放位置
        pos += 10000;                       //前进 10 秒 (10000ms)
        if(pos > len) pos = len;            //不可大于总秒数
        mper.seekTo(pos);                   //移动播放位置
        tos.setText("前进10秒:" + pos/1000 + "/" + len/1000);  //显示信息
        tos.show();
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mper.isPlaying()) {  //如果正在播放, 就暂停
            btnPlay.setText("继续");
            mper.pause();        //暂停播放
        }
    }

    @Override
    protected void onDestroy() {
        mper.release();         //释放 MediaPlayer 对象
        super.onDestroy();
    }

}

2、播放视频的Video.java代码

package com.my.player;

import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import android.widget.MediaController;
import android.widget.VideoView;

public class Video extends AppCompatActivity implements MediaPlayer.OnCompletionListener{

    VideoView vdv;    //播放视频组件
    int pos = 0;      //记录播放位置

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);//隐藏系统的状态栏
        getSupportActionBar().hide();                                    //隐藏标题栏

        setContentView(R.layout.activity_video);
        //保持屏幕一直开着 (不要自动休眠)
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        Intent it = getIntent();                       //获取传入的 Intent 对象
        Uri uri = Uri.parse(it.getStringExtra("uri")); //获取要播放视频的 Uri
        if(savedInstanceState != null)                 //如果是因旋转而重新启动 Activity
            pos = savedInstanceState.getInt("pos", 0); //获取旋转前所保存的播放位置

        vdv = (VideoView)findViewById(R.id.videoView); //引用到画面中的 VideoView 组件
        MediaController mediaCtrl = new MediaController(this); //新建播放控制对象
        vdv.setMediaController(mediaCtrl);             //设置要用播放控制对象来控制播放
        vdv.setVideoURI(uri);                          //设置要播放视频的 Uri

        vdv.setOnCompletionListener(this);             //设置播放完毕时的监听器

    }

    @Override
    protected void onResume() { //当 Activity 启动、或从暂停状态回到互动状态时
        super.onResume();
        vdv.seekTo(pos);        //移到 pos 的播放位置
        vdv.start();            //开始播放
    }

    @Override
    protected void onPause() { //当 Activity 进入暂停状态时 (例如切换到其他程序)
        super.onPause();
        pos = vdv.getCurrentPosition(); //保存播放位置
        vdv.stopPlayback();             //停止播放
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("pos", pos);     //将暂停时所获取的当前播放位置保存起来
    }

    @Override
    public void onCompletion(MediaPlayer mp) {  //播放完毕 结束活动
        finish();
    }
}

3、所有代码编写完毕后的APP目录层级如下所示:

Android实践:《简易影音播放器》的实现及简要说明_第7张图片


七:运行结果

Android实践:《简易影音播放器》的实现及简要说明_第8张图片

(调了大小,总算没有那么大了。。。)


程序并不是很智能,只为梳理学习的知识,并做巩固。编写很简单,只是我整理的比较乱,看着麻烦了点。


//                     总结参考:《Android App开发入门 第2版》 机械工业出版社 施威铭 著 2017.8 


你可能感兴趣的:(Android,Android,Android,Studio,mediaplayer,影音播放)