本文实现的简易影音播放器主要实现的功能,包括选取音乐文件,播放、暂停、停止、快进、快退,选取视频文件播放,音乐文件和视频文件都会给出文件名、文件路径的详细信息。主要涉及到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) {
//播放完毕时要处理的事情。。。
}
onCreate() //当活动启动时
onResume() //当活动获得输入焦点时
onPause() //当活动失去输入焦点时
onDestory() //当活动结束时
(画图的时候少画了一个箭头,手动补上,啊哈哈。。)
当Activity失去输入焦点时就暂停音乐播放,重新获得输入焦点时,一般来说会继续音乐播放,但是直接播放又不合用户使用习惯,因为突然就响了,太吓人。所以最好还是保持暂停,等待用户操作。Activity结束时,要将MediaPlayer对象释放。
当然这个过程涉及到很多事情。例如切换程序的时候,正常的音乐播放器还是会继续播放音乐的,如果用户切换的是文本阅读程序,音乐不会暂停,如果是视频播放程序,音乐就会直接暂停,这里面涉及到很多知识,所以这个简单的播放器就直接切换程序就暂停音乐。手机关闭屏幕进入休眠状态也会失去输入焦点,从而触发onPause()事件,导致音乐暂停,这肯定有解决方案,本程序不打算处理这个问题,而是直接让手机不进入休眠状态,虽然反人类,不管了。在onCreate()中加入以下代码:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
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(); //设置播放位置(秒)
播放视频最好是充满屏幕,所以本程序选择新建一个活动来播放视频,并且去掉该活动的状态栏和标题栏。并且为了不让视频播放一会就进入休眠状态,需要设置屏幕不休眠。如下所示:
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);
}
getActionBar().hide();
4、播放视频时旋转屏幕的处理:
布局只有两个,一个是主程序的,一个是视频播放的。
1、主程序活动的布局
布局组件的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
布局组件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,这首歌会作为程序里的默认播放音乐存在。
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();
}
}
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();
}
}
七:运行结果
(调了大小,总算没有那么大了。。。)
程序并不是很智能,只为梳理学习的知识,并做巩固。编写很简单,只是我整理的比较乱,看着麻烦了点。
// 总结参考:《Android App开发入门 第2版》 机械工业出版社 施威铭 著 2017.8