MusicPlayer音乐播放器Android
【音频采集】
你可以使用手机进行现场录音,实现步骤如下:
第一步:在功能清单文件AndroidManifest.xml中添加音频刻录权限:
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
第二步:编写音频刻录代码:
MediaRecorder recorder = new MediaRecorder();recorder.setAudioSource(MediaRecorder.AudioSource.MIC);//从麦克风采集声音recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);//内容输出格式recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//音频编码方式recorder.setOutputFile("/mnt/sdcard/glsite.amr");recorder.prepare();//预期准备recorder.start(); //开始刻录...recorder.stop();//停止刻录recorder.reset(); //重设recorder.release(); //刻录完成一定要释放资源
在Android开发中我们经常使用MediaPlayer来播放音频文件,但是MediaPlayer存在一些不足,例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。
在游戏开发中我们经常需要播放一些游戏音效(比如:子弹爆炸,物体撞击等),这些音效的共同特点是短促、密集、延迟程度小。在这样的场景下,我们可以使用SoundPool代替MediaPlayer来播放这些音效。
SoundPool(android.media.SoundPool),顾名思义是声音池的意思,主要用于播放一些较短的声音片段,支持从程序的资源或文件系统加载。与MediaPlayer相比,SoundPool的优势在于CPU资源占用量低和反应延迟小。另外,SoundPool还支持自行设置声音的品质、音量、播放比率等参数,支持通过ID对多个音频流进行管理。
就现在已知的资料来说,SoundPool有一些设计上的BUG,从固件版本1.0开始有些还没有修复,我们在使用中应该小心再小心。相信将来Google会修复这些问题,但我们最好还是列出来:
1. SoundPool最大只能申请1M的内存空间,这就意味着我们只能用一些很短的声音片段,而不是用它来播放歌曲或者做游戏背景音乐。
2. SoundPool提供了pause和stop方法,但这些方法建议最好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。建议使用这两个方法的时候尽可能多做测试工作,还有些朋友反映它们不会立即中止播放声音,而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。
3. SoundPool的效率问题。其实SoundPool的效率在这些播放类中算是很好的了,但是有的朋友在G1中测试它还是有100ms左右的延迟,这可能会影响用户体验。也许这不能怪SoundPool本身,因为到了性能比较好的Droid中这个延迟就可以让人接受了。
在现阶段SoundPool有这些缺陷,但也有着它不可替代的优点,基于这些我们建议大在如下情况中多使用SoundPool:1.应用程序中的声效(按键提示音,消息等)2.游戏中密集而短暂的声音(如多个飞船同时爆炸)
【SoundPool】
开发步骤:
1> 往项目的res/raw目录中放入音效文件。
2> 新建SoundPool对象,然后调用SoundPool.load()加载音效,调用SoundPool.play()方法播放指定音效文件。
public class AudioActivity extends Activity {private SoundPool pool;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);//指定声音池的最大音频流数目为10,声音品质为5pool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);final int sourceid = pool.load(this, R.raw.pj, 0);//载入音频流,返回在池中的idButton button = (Button)this.findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {//播放音频,第二个参数为左声道音量;第三个参数为右声道音量;第四个参数为优先级;第五个参数为循环次数,0不循环,-1循环;第六个参数为速率,速率最低0.5最高为2,1代表正常速度pool.play(sourceid, 1, 1, 0, -1, 1);}});}}
demo:
1. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="/apk/res/android"package="com.glsite.musicplayer"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><serviceandroid:name=".MusicPlayerService"android:enabled="true"android:exported="true"></service><activity android:name=".SettingActivity" /><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
2. MusicPlayer\app\src\main\res\menu\activity_main.xml 菜单
<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="/apk/res/android"><item android:id="@+id/item_setting"android:title="设置界面"/><item android:id="@+id/item_exit"android:title="关闭播放器"/></menu>
3.MusicPlayer\app\src\main\res\layout
3.1activity_main.xml
<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><ListViewandroid:id="@+id/lv"android:layout_width="match_parent"android:layout_height="match_parent"></ListView></android.support.constraint.ConstraintLayout>
3.2 activity_setting.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".SettingActivity"android:orientation="vertical"><RadioGroupandroid:id="@+id/rg_mode"android:layout_width="wrap_content"android:layout_height="wrap_content" ><RadioButtonandroid:id="@+id/rb_cycle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="单曲循环" /><RadioButtonandroid:id="@+id/rb_next"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="播放下一曲" /><RadioButtonandroid:id="@+id/rb_stop"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="播放后停止" /></RadioGroup></LinearLayout>
3.3 item_music.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_item_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="5dp"android:layout_marginTop="5dp"android:paddingTop="5dp"android:paddingBottom="5dp"android:text="歌曲名称"android:textColor="#99ff0000"android:textSize="20sp" /><ImageViewandroid:id="@+id/imageView"android:layout_width="25dp"android:layout_height="25dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"android:layout_marginRight="5dp"app:srcCompat="@mipmap/ic_launcher" /></RelativeLayout>
4.MusicPlayer\app\src\main\java\com\glsite\musicplayer
4.1BaseActivity 运行时权限
package com.glsite.musicplayer;import android.content.DialogInterface;import android.content.pm.PackageManager;import android.os.Build;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.support.v7.app.AlertDialog;import android.support.v7.app.AppCompatActivity;import android.widget.Toast;/*** @author Admin* @version $Rev$* @des ${TODO}* @updateAuthor $Author$* @updateDes ${TODO}*/public class BaseActivity extends AppCompatActivity {//**************** Android M Permission (Android 6.0权限控制代码封装)private int permissionRequestCode = 88;private PermissionCallback permissionRunnable;public interface PermissionCallback {void hasPermission();void noPermission();}/*** Android M运行时权限请求封装** @param permissionDes 权限描述* @param runnable请求权限回调* @param permissions 请求的权限(数组类型),直接从Manifest中读取相应的值,比如Manifest.permission.WRITE_CONTACTS*/public void performCodeWithPermission(@NonNull String permissionDes, PermissionCallback runnable, @NonNull String... permissions) {if (permissions == null || permissions.length == 0)return;// this.permissionrequestCode = requestCode;this.permissionRunnable = runnable;if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.M) || checkPermissionGranted(permissions)) {if (permissionRunnable != null) {permissionRunnable.hasPermission();permissionRunnable = null;}} else {//permission has not been granted.requestPermission(permissionDes, permissionRequestCode, permissions);}}private boolean checkPermissionGranted(String[] permissions) {boolean flag = true;for (String p : permissions) {if (ActivityCompat.checkSelfPermission(this, p) != PackageManager.PERMISSION_GRANTED) {flag = false;break;}}return flag;}private void requestPermission(String permissionDes, final int requestCode, final String[] permissions) {if (shouldShowRequestPermissionRationale(permissions)) {/*1. 第一次请求权限时,用户拒绝了,下一次:shouldShowRequestPermissionRationale() 返回 true,应该显示一些为什么需要这个权限的说明2.第二次请求权限时,用户拒绝了,并选择了“不在提醒”的选项时:shouldShowRequestPermissionRationale() 返回 false3. 设备的策略禁止当前应用获取这个权限的授权:shouldShowRequestPermissionRationale() 返回 false*/// Provide an additional rationale to the user if the permission was not granted// and the user would benefit from additional context for the use of the permission.// For example, if the request has been denied previously.// Snackbar.make(getWindow().getDecorView(), requestName,//Snackbar.LENGTH_INDEFINITE)//.setAction(mon_ok, new View.OnClickListener() {// @Override// public void onClick(View view) {// ActivityCompat.requestPermissions(BaseAppCompatActivity.this,//permissions,//requestCode);// }//})//.show();//如果用户之前拒绝过此权限,再提示一次准备授权相关权限new AlertDialog.Builder(this).setTitle("提示").setMessage(permissionDes).setPositiveButton("授权", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {ActivityCompat.requestPermissions(BaseActivity.this, permissions, requestCode);}}).show();} else {// Contact permissions have not been granted yet. Request them directly.ActivityCompat.requestPermissions(BaseActivity.this, permissions, requestCode);}}private boolean shouldShowRequestPermissionRationale(String[] permissions) {boolean flag = false;for (String p : permissions) {if (ActivityCompat.shouldShowRequestPermissionRationale(this, p)) {flag = true;break;}}return flag;}/*** Callback received when a permissions request has been completed.*/@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {if (requestCode == permissionRequestCode) {if (verifyPermissions(grantResults)) {if (permissionRunnable != null) {permissionRunnable.hasPermission();permissionRunnable = null;}} else {Toast.makeText(this, "暂无权限执行相关操作!", Toast.LENGTH_SHORT).show();if (permissionRunnable != null) {permissionRunnable.noPermission();permissionRunnable = null;}}} else {super.onRequestPermissionsResult(requestCode, permissions, grantResults);}}public boolean verifyPermissions(int[] grantResults) {// At least one result must be checked.if (grantResults.length < 1) {return false;}// Verify that each required permission has been granted, otherwise return false.for (int result : grantResults) {if (result != PackageManager.PERMISSION_GRANTED) {return false;}}return true;}//********************** END Android M Permission ****************************************}
4.2MainActivity.java
package com.glsite.musicplayer;import android.Manifest;import ponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Environment;import android.os.IBinder;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.ListAdapter;import android.widget.ListView;import android.widget.TextView;import android.widget.Toast;import java.io.File;import java.util.ArrayList;public class MainActivity extends BaseActivity {private ListView mLv;private IMusicService mIMusicService;public static final String MP3DIR = Environment.getExternalStorageDirectory() + "/Download/";private ArrayList<String> mMp3List;private MyConn mConn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mLv = findViewById(R.id.lv);performCodeWithPermission("读取SD卡需要用到的权限", new PermissionCallback() {@Overridepublic void hasPermission() {initPlayList();}@Overridepublic void noPermission() {}}, Manifest.permission.READ_EXTERNAL_STORAGE);Intent intent = new Intent(this, MusicPlayerService.class);startService(intent);mConn = new MyConn();bindService(intent, mConn, BIND_AUTO_CREATE);mLv.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {if (mIMusicService != null) {mIMusicService.callPlay(mMp3List, position);} else {Toast.makeText(MainActivity.this,"还没有绑定服务吧", Toast.LENGTH_SHORT).show();}}});}private class MyConn implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mIMusicService = (IMusicService) service;}@Overridepublic void onServiceDisconnected(ComponentName name) {}}/*** 初始化播放列表*/private void initPlayList() {File file = new File(MP3DIR);File[] files = file.listFiles();mMp3List = new ArrayList<>();for (File f : files) {if (f.getName().endsWith(".mp3")) {mMp3List.add(f.getAbsolutePath());System.out.println(f.getAbsolutePath());}}mLv.setAdapter(new MusicListAdapter());}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater = new MenuInflater(this);inflater.inflate(R.menu.activity_main, menu);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {if (item.getItemId() == R.id.item_setting) {// 启动一个设置界面Intent intent = new Intent(this, SettingActivity.class);startActivity(intent);} else if (item.getItemId() == R.id.item_exit){// 停止播放并且退出playerExit();}return super.onOptionsItemSelected(item);}/*** 停止播放并退出*/private void playerExit() {if (mIMusicService != null) {mIMusicService.callStop();}if (mConn != null) {unbindService(mConn);mConn = null;}Intent intent = new Intent(this, MusicPlayerService.class);stopService(intent);finish();}@Overrideprotected void onDestroy() {playerExit();super.onDestroy();}private class MusicListAdapter extends BaseAdapter {@Overridepublic int getCount() {return mMp3List.size();}@Overridepublic Object getItem(int position) {return mMp3List.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View view = View.inflate(MainActivity.this, R.layout.item_music, null);TextView tv_name = (TextView) view.findViewById(R.id.tv_item_name);String path = mMp3List.get(position);tv_name.setText(path.substring(path.lastIndexOf("/") + 1));return view;}}}
4.3IMusicService.java
package com.glsite.musicplayer;import java.util.List;/*** @author * @version $Rev$* @des ${TODO}* @updateAuthor $Author$* @updateDes ${TODO}*/public interface IMusicService {/*** 调用服务里面的播放逻辑** @param playList*音乐资源的路径集合* @param position*/public void callPlay(List<String> playList, int position);/*** 调用停止播放的方法*/public void callStop();}
4.4MusicPlayerService 音乐服务
package com.glsite.musicplayer;import android.app.Notification;import android.app.NotificationChannel;import android.app.NotificationManager;import android.app.Service;import android.content.Intent;import android.content.SharedPreferences;import android.graphics.BitmapFactory;import android.media.AudioManager;import android.media.MediaPlayer;import android.os.Binder;import android.os.IBinder;import java.io.IOException;import java.util.List;public class MusicPlayerService extends Service {public static final int MUSIC_STOP = 0;public static final int MUSIC_PLAYING = 1;public static final int MUSIC_PAUSE = 2;private SharedPreferences mSp;private MediaPlayer mMediaPlayer;public static int playingStatus = 0;public MusicPlayerService() {}@Overridepublic IBinder onBind(Intent intent) {return new MyBinder();}@Overridepublic void onCreate() {System.out.println("音乐播放器服务开启了");mSp = getSharedPreferences("config", MODE_PRIVATE);mMediaPlayer = new MediaPlayer();super.onCreate();}@Overridepublic void onDestroy() {System.out.println("音乐播放服务关闭了");super.onDestroy();}private class MyBinder extends Binder implements IMusicService {@Overridepublic void callPlay(List<String> playList, int position) {play(playList, position);}@Overridepublic void callStop() {stopPlayer();}}/*** 停止播放*/private void stopPlayer() {if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = null;}NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);nm.cancelAll();}/*** 播放音乐** @param playList*所有音频列表* @param position*当前的位置*/private void play(final List<String> playList, final int position) {try {if (mMediaPlayer.isPlaying()) {mMediaPlayer.stop();}mMediaPlayer.reset();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);System.out.println(playList.get(position));mMediaPlayer.setDataSource(playList.get(position));mMediaPlayer.prepare();mMediaPlayer.start();String path = playList.get(position);System.out.println(path.substring(path.lastIndexOf("/") + 1));showNotification(path.substring(path.lastIndexOf("/") + 1));mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {int mode = mSp.getInt("mode", 0);if (mode == SettingActivity.CYCLE) {play(playList, position);} else if (mode == SettingActivity.NEXT){// 播放下一曲int newPosition = position + 1;if (newPosition >= playList.size()) {newPosition = 0;}play(playList, newPosition);} else if (mode == SettingActivity.STOP) {// 设置状态为停止playingStatus = MUSIC_STOP;}}});} catch (Exception e) {e.printStackTrace();playingStatus = MUSIC_STOP;}}/*** 显示播放音乐的通知提醒** @param filename*音乐名*/private void showNotification(String filename) {// 判断如果当前的SDK版本>=8.0if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {String id = "channel_1";int importance = NotificationManager.IMPORTANCE_LOW;NotificationChannel channel = new NotificationChannel(id, "123", importance);Notification noti = new Notification.Builder(this, id).setContentTitle("酷狗音乐正在播放").setContentText(filename).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).build();NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);nm.createNotificationChannel(channel);nm.notify(1, noti);// 添加channel} else {// 如果当前的SDK版本<8.0Notification noti = new Notification.Builder(this).setContentTitle("酷狗音乐正在播放").setContentText(filename).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).build();NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);nm.notify(1, noti);}}}
4.5SettingActivity.java
package com.glsite.musicplayer;import android.content.SharedPreferences;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.RadioButton;import android.widget.RadioGroup;public class SettingActivity extends AppCompatActivity {public static final int CYCLE = 1;public static final int NEXT = 2;public static final int STOP = 3;private RadioGroup mRgMode;private SharedPreferences mSp;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_setting);mRgMode = findViewById(R.id.rg_mode);mSp = getSharedPreferences("config", MODE_PRIVATE);int mode = mSp.getInt("mode", 0);RadioButton rb;switch (mode) {case CYCLE:rb = findViewById(R.id.rb_cycle);rb.setChecked(true);break;case NEXT:rb = findViewById(R.id.rb_next);rb.setChecked(true);break;case STOP:rb = findViewById(R.id.rb_stop);rb.setChecked(true);break;default:break;}mRgMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {SharedPreferences.Editor editor = mSp.edit();switch (checkedId) {case R.id.rb_cycle:editor.putInt("mode", CYCLE);break;case R.id.rb_next:editor.putInt("mode", NEXT);break;case R.id.rb_stop:editor.putInt("mode", STOP);break;default:break;}mit();}});}}
如果觉得《MusicPlayer音乐播放器Android》对你有帮助,请点赞、收藏,并留下你的观点哦!