一,MediaPlayer类概述
MediaPlayer是ANdroid自带的多媒体库,可用于实现音频操作,若结合SurfaceView和VideoView等控件可是实现视频的播放。
1,创建MediaPlayer对象,并装载音频文件
①可以直接使用new的方式
MediaPlayer mp=new MediaPlayer();
此时,需要调用setDataSource()方法指定要播放的资源文件,调用prepare(),调用start()进行播放。
②使用create的方式
MediaPlayer mp=MediaPlayer.create(this,T.raw.test);
此时,不用调用setDataSource(),也不需要调用prepare(),可直接调用start()函数进行播放。
2,设置要播放的文件
通过setDataSource()方法
①void setDataSource(String path); 指定装载path路径所代表的文件。
②void setDataSource(FileDescriptor fd, long offset, long length); 指定装载fd所代表的文件中从offset开始、长度为length的文件内容。
③void setDataSource(FileDescroptor fd); 指定装载fd所代表的文件。
④void setDateSource(Context context, Uri uri); 指定装载Uri代表的文件。
注意:在使用setDataSource()方法装载音频文件后,实际上MediaPlayer并真正装载该音频文件,还需要调用MediaPlayer的prepare()方法真正装载音频文件。
3,播放的文件的三个来源
(1)播放应用中事先自带的resource资源
*res文件夹与assert文件夹的区别
Android中子源分为两种,
res下的可编译的资源文件,这种资源文件会在R中自动生成该资源文件的ID,直接通过R.xxx.id访问;res/raw不可以有目录结构
assets文件夹下的原生资源文件,不会被R文件编译;可以有目录结构,既是可以在assets目录下在建立文件夹。
*
Ⅰ,播放应用的资源文件
①调用MediaPlayer的create(Context context , int resid)方法加载指定资源文件
②调用MediaPlayer的start()、pause()、stop()等方法控制播放即可
MediaPlayer.create(this,R.raw.song);
Uri uri=Uri.parse("android.resource://"+this.getPackageName()+"/"+R.raw.da);
player=new MediaPlayer();
player.setDataSource(this,uri);
player.perpare();
Ⅱ,播放应用的原始资源文件
①,调用Activity中的getAssets()方法获取应用的AssetManager
②,调用AssetManager对象的openFd(STring name)方法打开指定的原生资源,该方法返回一个AssetFileDescriptor对象。
③,调用AssetFileDescriptor的getFileDescriptor()和getLength()方法来获取音频文件的FileDescriptor、开始位置、长度
④,创建MediaPlayer对象,并调用MediaPlayer对象的setDataResource(FileDescriptor fd,long offeset,long length)方法来装在音频资源
⑤,调用MediaPlayer对象的prepare()方法准备音频
⑥,调用MediaPlayer的start(),pause()、stop()等方法控制播放即可。
🔺(2)存储在SD卡或其他文件路径下的媒体文件
①创建MediaPlayer对象,并调用MediaPlayer对象的setDataSource(String path)方法装载指定音频文件
②调用MediaPlayer对象的prepare()方法准备音频
③调用MediaPlayer的start()、stop()等方法控制播放即可
player = new MediaPlayre();
player.setDataSource("/sdcard/pm.mp3");
//或
//paler.setDataSource(Environment.getExternalStorageDirectory()+"/pm.mp3);
player.prepare();
注意:对sdcard的访问需要在AndroidManifest.xml中设置相应的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
在安卓23.0版本以上,还需要动态申请权限
//判断是否6.0以上的手机
if(Build.VERSION.SDK_INT>=23){
//判断是否有这个权限
int permmission=ActivityCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE);
//checkSelfPremission检查对应的权限是否打开,如果是PackageManager.PERMISSION_GRANTED表示打开
if(permission!=PackageManager.PERMISSION_GRANTED){
//请求权限
ActivityCompat.requestPermissions(this,new String[]{Manifes.permission.READ_EXTERNAL_STORAGE},ECTERNAL_STORAGE_REQ_CODE);
}
(3)网络上的媒体文件
①直接使用create(Context context ,Uri uri)
MediaPlayer mp=MediaPlayer.create(this,Uri.parse(http://www.test.cn/music/m1.mp3))
②调用setDataSource(Context context,Uri uri)
mp.setDataSource(this,Uri.parse("htp://www.tst.cn/music/m1.mp3"));
此时,需要在AndroidManifest.xml中授权程序访问网络的权限
<users-permission android:name="android.permission.INTERNET"/>
4,对播放器的主要控制方法
①prepare():同步
同步的为播放器的回放做准备,设置数据源和显示表面后调用;对于文件,可以调用prepare(),它会阻塞,直到MediaPlayer准备好播放为止。
②prepareAsync():异步
异步的为播放器的回放做准备。设置好数据源和显示表面后调用。对于流,应该调用prepareAsync(),它会立即返回,而不是阻塞,直到缓冲了足够的数据。
注意:prepare()方法,是将资源同步缓存到内存中,一般加载本地较小的资源使用。如果较大的资源或网络资源建议使用prepareAsync()
▲对于异步装载考虑是否能在资源装载完成后才可以操作
需要设置监听事件setOnPreparedListener();来通知MediaPlayer资源已经获取到了,然后实现onPrepared(MediaPlayre mp)方法,在里面启动MediaPlayer
viderView.setOnPreparedListener(
new OnpreparedListener(){
public void onPrepared(MediaPlayer mp){
mp.play();
}
}
);
5,MediaPlayer的方法
start(): 开始播放或恢复已经暂停的音频的播放
stop(): 停止正在播放的音频
pause(): 暂停正在播放的音频
void seekTo(int msec): 设置当前MediaPlayre的播放位置,可以让播放器从指定位置开始播放,单位是毫秒。
int getDuration(): 获取流媒体的总播放时长,单位是毫秒
int getCurrentPosition():获取当流媒体的播放位置,单位是毫秒
void setLooping(boolean looping); 设置是否循环播放。播放完毕后,不会回调OnCompletionListener,而是从头播放当前音频
boolean isLooping(); 判断是否循环播放
boolean isPlaying(); 判断是否正在播放
void release(); 回收流媒体资源
void setAudioStreamType(int streamtype); 设置播放流媒体类型
void setWakeMode(Context context,int mode); 设置CPU唤醒的状态
setNextMediaPlayer(MedaiPlayer next); 设置当前流媒体播放完毕,下一个播放的MediaPlayer
6,MediaPlayer的监听事件
通常在新建一个MedaiPlayer实体后,会对给它增加需要的监听事件
MediaPlayer.onPreparedListener: MediaPlayer进入准备完毕的状态出发,表示流媒体可以开始播放了
MediaPlayer.OnSeekCompleteListener: 调用MediaPlayer的seekTo方法后,MediaPlayer会跳转到媒体指定的位置,当跳转完成时触发。(注意:seekTo不能精确的跳转,它的跳转点必须是媒体资源的关键帧)
MediaPlayer.OnBufferingUpdateListener: 网络上的媒体资源缓存进度更新的时候被触发,比如在加载网络音频的时候,常用这个监听器来监听缓存进度。
MediaPlayer.OnCompletionListener: 媒体播放完毕时会触发。(由于playere占有的系统资源比较大,因此在播放结束后,就应该掉哟ing该方法,把player占有的资源给释放掉。当OnErrorLister返回false,或MediaPlayer没有设置OnErrorListener时,这个监听会发触发)
MediaPlayer.OnvideoSizeChangeListener: 视频宽高发生改变的时候会触发。当所设置的媒体资源没有视频图像、MediaPlayer没有设置展示的holder或者视频大小还没有测量出来的时候,获取宽高得到的都是0
MediaPlayer.OnErrorListerner: MediaPlayer出错时会触发,无论是播放过程中出错,还是准备过程中出错,都会触发。
6,MediaPlayer的状态图和生命周期
二、MP3音乐播放器
1,功能
从SD卡中读取MP3文件并加载到播放列表中,控制音乐的播放和停止,实现对播放列表中音乐的上一首、下一首的切换,播放完成时自动给跳转到下一首播放,当播放到最后一首音乐时自动切换到第一首进行播放。通过进度表跟踪播放进度和拖动进度条拖动进度。
2,代码
MainActivity.java文件:
package com.example.mp3;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class MainActivity extends Activity {//默认继承AppCompatActivity,会报错
private MediaPlayer player=new MediaPlayer();
private List<String> musicList=new ArrayList<>();//保存SD卡中的所有音乐文件的名称
private int currentListItem=0;//当前正在播放音乐的下表
private boolean isPlaying;//表示当前是否正在播放音乐
private SeekBar seekBar;
private Timer timer;
private TimerTask task;
private int currentposition;//保存当前播放的音乐的位置
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar=findViewById(R.id.sb_seekbar);
//动态申请权限
final int permission= ActivityCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE);
if(permission!= PackageManager.PERMISSION_GRANTED){
//请求权限
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},10);
}
//获取sdcard/Music目录下所有的音乐文件并保存至musicList
//获取这个目录下所有的文件
// 得到sd卡内Music文件夹的路径
String filePath = Environment.getExternalStorageDirectory().toString() + File.separator + "Music";
// 得到该路径文件夹下所有的文件
File fileAll = new File(filePath);
File[] files = fileAll.listFiles();
//判断files是否有音乐文件
if(files!=null)
{
//获得所有音乐文件的路径
for(File f:files)
musicList.add(f.getName());
}
//获得ListView
ListView lv_list=findViewById(R.id.lv_list);
//将musicList包装成ArrayAdapter
ArrayAdapter adapter=new ArrayAdapter<>(this,R.layout.array_item,musicList);//需要<String>,否则警告
//为ListVeiw设置Adapter
lv_list.setAdapter(adapter);
//设置ListVIew的事件监听器
lv_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
currentListItem=position;
play();
}
});
//通过保持在MusicLst中的路径找到音乐文件并播放
File file = new File(filePath+"/"+musicList.get(currentListItem));
//判断音频文件是否存在,
//进行播放设置
if(file.exists()){
try{
player.setDataSource(file.getAbsolutePath());
player.prepare();
player.start();
followSeekBar();
}
catch(Exception e){
Toast.makeText(MainActivity.this,"播放出dfdf错!",Toast.LENGTH_SHORT).show();
}
}
else{
//提示
Toast.makeText(MainActivity.this,"音乐不存在!",Toast.LENGTH_SHORT).show();
}
//MedaiPlayer完成事件监听器,当前音乐播放完成后制动开始播放下一首
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//当播放结束时取消计时
timer.cancel();
task.cancel();
//播放下一首
currentListItem=(currentListItem+1) % musicList.size();//播放到最后一首后,从头开始
play();
}
});
//上一首按钮的事件监听器
Button bt_frontMusic=findViewById(R.id.bt_frontMusic);
bt_frontMusic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
frontMusic();//播放上一首
}
});
//下一首按钮的事件监听器
Button bt_nextMusic=findViewById(R.id.bt_nextMusic);
bt_nextMusic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nextMusic();//播放下一首
}
});
//播放暂停按钮的事件监听器
Button bt_playPause=findViewById(R.id.bt_playPause);
bt_playPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
playMusic();
}
});
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) {
currentposition=seekBar.getProgress();
player.seekTo(currentposition);
}
});
}//onCreate
private void play(){
try {
player.reset();
player.setDataSource(Environment.getExternalStorageDirectory().toString() + File.separator + "Music"+ File.separator+musicList.get(currentListItem));
player.prepare();
player.start();
isPlaying=true;//正在播放标记置为true
followSeekBar();
}catch (Exception e){
Toast.makeText(MainActivity.this,"播放出错!",Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(player.isPlaying()){
player.stop();
}
player.release();
}
//播放上一首
private void frontMusic()
{
if(currentListItem==0){
Toast.makeText(MainActivity.this,"已经是第一首了!",Toast.LENGTH_LONG).show();
}
else{
--currentListItem;
play();
}
}
//播放下一首
private void nextMusic(){
if(currentListItem+1==musicList.size()){
Toast.makeText(MainActivity.this,"已经是最后一首了!",Toast.LENGTH_LONG).show();
}
else{
++currentListItem;
play();
}
}
//播放暂停
public void playMusic(){
if(isPlaying)
{
player.pause();
isPlaying=false;
}else{
player.start();
isPlaying=true;
}
}
private void followSeekBar()
{
//获取当前歌曲的总时长
int duration=player.getDuration();
seekBar.setMax(duration);
//计时器对象
timer=new Timer();
task=new TimerTask(){
public void run(){
//开启线程定时获取当前播放进度
currentposition=player.getCurrentPosition();
seekBar.setProgress(currentposition);
}
};
timer.schedule(task,300,500);
}
}
activity_main.xml文件:
```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/tv_musicList"
android:layout_width="match_parent"
android:layout_height="20pt"
android:text="歌曲列表"
android:textSize="12pt"
android:gravity="center_horizontal"/>
<ListView
android:id="@+id/lv_list"
android:layout_width="match_parent"
android:layout_height="560dp"
android:divider="#666"
android:dividerHeight="1dp"
android:headerDividersEnabled="false" />
<SeekBar android:id="@+id/sb_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="100"
android:progress="0"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/LL"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="bottom"
android:orientation="horizontal">
<Button
android:id="@+id/bt_frontMusic"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="上一首"
android:textSize="10pt"
android:layout_weight="1"/>
<Button
android:id="@+id/bt_playPause"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="播放/暂停"
android:textSize="10pt" />
<Button
android:id="@+id/bt_nextMusic"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="下一首"
android:textSize="10pt" />
</LinearLayout>
</LinearLayout>
array_item.xml文件:
```xml
<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/TextView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12pt"
android:padding="5dp"
android:shadowColor="#89a"
android:shadowDx="4"
android:shadowDy="4"
android:shadowRadius="2"
/>
因为需要读取SD卡,所以需要在AndroidManifest.xml中配置相应权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE "></uses-permission>
本代码还存在着较多bug,比如当SD卡没有音乐文件的时候会报错,所以需要进行完整的系统测试和bug修改,才能正常的时候。
在运行代码时,在如下代码中
//获取sdcard/Music目录下所有的音乐文件并保存至musicList
//获取这个目录下所有的文件
// 得到sd卡内Music文件夹的路径
String filePath = Environment.getExternalStorageDirectory().toString() + File.separator + "Music";
// 得到该路径文件夹下所有的文件
File fileAll = new File(filePath);
File[] files = fileAll.listFiles();
files对象一直为空,经过不断检查和debug依然没有发现代码存在的问题,最后无意间换上Genymotion的模拟器问题直接消失,最终确定应该是Android Studio自带的模拟器的问题。