大家好,作爲小白,這是我第一篇文章,也是我的一個學習記錄。廢話就不多說了,開始進入正題吧。
這篇文章是我做Mp3播放器的一個過程,首先整理一下思路,我們做播放器會需要什麼東西呢?
一:獲取本地的音頻文件。並且保存到一個List裏面方便我們來讀取。
二:需要一個界面來展示出我們所獲取到的音頻文件。
三:播放界面,需要有播放,下一首,等一系列操作。
四:Service,我們需要運用Service來達到後臺播放目的,當然使用它的前提就是我們能夠先瞭解到Service的基本使用方法,這裏我就不多做說明了.等以後我會寫一點點關於這個的東西。
好了,大概的流程就是這樣,下面我們來用代碼實現。
一:獲取本地音頻文件
public class Audio {
private String mTitle,
mTitleKey,//標題
mArtist,//藝術家名
mArtistKey,
mComposer,
mAlbum,
mAlbumKey,
mDisplayName,//歌曲名
mMimeType,
mPath;//路徑
private int mId,
mArtistId,
mAlbumId,
mYear,
mTrack;
private int mDuration = 0,//時長
mSize = 0;//大小
private boolean isRingtone = false,
isPodcast = false,
isAlarm = false,
isMusic = false,
isNotification = false;
public Audio(Bundle bundle) {
mId = bundle.getInt(MediaStore.Audio.Media._ID);
mTitle = bundle.getString(MediaStore.Audio.Media.TITLE);
mTitleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY);
mArtist = bundle.getString(MediaStore.Audio.Media.ARTIST);
mArtistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY);
mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER);
mAlbum = bundle.getString(MediaStore.Audio.Media.ALBUM);
mAlbumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY);
mDisplayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME);
mYear = bundle.getInt(MediaStore.Audio.Media.YEAR);
mMimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE);
mPath = bundle.getString(MediaStore.Audio.Media.DATA);
mArtistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID);
mAlbumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID);
mTrack = bundle.getInt(MediaStore.Audio.Media.TRACK);
mDuration = bundle.getInt(MediaStore.Audio.Media.DURATION);
mSize = bundle.getInt(MediaStore.Audio.Media.SIZE);
isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1;
isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1;
isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1;
isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1;
isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1;
}
public int getId() {
return mId;
}
public String getMimeType () {
return mMimeType;
}
public int getDuration () {
return mDuration;
}
public int getSize () {
return mSize;
}
public boolean isRingtone () {
return isRingtone;
}
public boolean isPodcast () {
return isPodcast;
}
public boolean isAlarm () {
return isAlarm;
}
public boolean isMusic () {
return isMusic;
}
public boolean isNotification () {
return isNotification;
}
public String getTitle () {
return mTitle;
}
public String getTitleKey () {
return mTitleKey;
}
public String getArtist () {
return mArtist;
}
public int getArtistId () {
return mArtistId;
}
public String getArtistKey () {
return mArtistKey;
}
public String getComposer () {
return mComposer;
}
public String getAlbum () {
return mAlbum;
}
public int getAlbumId () {
return mAlbumId;
}
public String getAlbumKey () {
return mAlbumKey;
}
public String getDisplayName () {
return mDisplayName;
}
public int getYear () {
return mYear;
}
public int getTrack () {
return mTrack;
}
public String getPath () {
return mPath;
}
}
這裏我們寫了一個Audio的音頻文件類,它所有的屬性有mTitle,
mTitleKey,
mArtist,
mArtistKey,
mComposer,
mAlbum,
mAlbumKey,
mDisplayName,
mMimeType,
mPath;
我們從名字中就可以看到其作用,歌曲標題,藝術家名等.
public Audio(Bundle bundle) 這個構造的函數的作用呢,也就相當於我們取到本地音頻的一系列值,然後存儲到這個類中來。
然後就是實現這個類的屬性的get方法,從類中獲取到這些屬性。
接下來,我們需要去瀏覽本地文件,選出音頻文件進行保存:
public class MediaUtils {
public static final String[] AUDIO_KEYS = new String[]{
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.TITLE_KEY,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.ARTIST_KEY,
MediaStore.Audio.Media.COMPOSER,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ALBUM_KEY,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.YEAR,
MediaStore.Audio.Media.TRACK,
MediaStore.Audio.Media.IS_RINGTONE,
MediaStore.Audio.Media.IS_PODCAST,
MediaStore.Audio.Media.IS_ALARM,
MediaStore.Audio.Media.IS_MUSIC,
MediaStore.Audio.Media.IS_NOTIFICATION,
MediaStore.Audio.Media.MIME_TYPE,
MediaStore.Audio.Media.DATA
};
//讀取本地音頻文件,保存到list裏並返回
public static List<Audio> getAudioList(Context context){
List<Audio> audioList = new ArrayList<Audio>();
ContentResolver resolver = context.getContentResolver();
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
AUDIO_KEYS,
null,
null,
null);
for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
Bundle bundle = new Bundle();
for(int i=0;i<AUDIO_KEYS.length;i++){
final String key = AUDIO_KEYS[i];
final int columnIndex = cursor.getColumnIndex(key);
final int type = cursor.getType(columnIndex);
switch (type) {
case Cursor.FIELD_TYPE_BLOB:
break;
case Cursor.FIELD_TYPE_FLOAT:
float floatValue = cursor.getFloat(columnIndex);
bundle.putFloat(key, floatValue);
break;
case Cursor.FIELD_TYPE_INTEGER:
int intValue = cursor.getInt(columnIndex);
bundle.putInt(key, intValue);
break;
case Cursor.FIELD_TYPE_NULL:
break;
case Cursor.FIELD_TYPE_STRING:
String strValue = cursor.getString(columnIndex);
bundle.putString(key, strValue);
break;
}
}
Audio audio = new Audio (bundle);
audioList.add(audio);
}
cursor.close();
return audioList;
}
}
這一個類的主要目的就是去通過Cusor索引,然後去獲取到本地的音頻文件,然後保存到List裏面,在返回一個List
這裏我們已經拿到了步驟一種所說的,保存有本地所有音頻文件的List
然後如何將這個List顯示在手機界面上,並且可以點擊的呢。
MainActivity
@ContentView(R.layout.activity_main)
public class MainActivity extends Activity {
@ViewInject(R.id.mp3_ListView)
ListView mp3ListView;
private Context mContext;
static List<Audio> mp3Infos = new ArrayList<Audio>();
private Mp3Adapter adapter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
init();
//給MusicPlayActivity這個類的mp3Infos賦值
MusicPlayActivity.mp3Infos=mp3Infos;
}
public void init(){
this.mContext=this;
mp3Infos = (ArrayList<Audio>) MediaUtils.getAudioList(this.mContext);
adapter = new Mp3Adapter(this.mContext,mp3Infos);
mp3ListView.setAdapter(adapter);
//設置ListView Item點擊監聽器
mp3ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent1 = new Intent();
//啓動音樂播放界面
intent1 .setClass(MainActivity.this,MusicPlayActivity.class);
//帶值跳轉,傳入點擊的歌曲位置
intent1.putExtra("position", position);
startActivity(intent1);
}
});
}
}
Mp3Adapter
public class Mp3Adapter extends BaseAdapter{
private Context context=null;
private List<Audio> mp3Infos ;
public Mp3Adapter(Context context){
this.context=context;
}
public Mp3Adapter(Context context, List<Audio> mp3Infos){
this.context=context;
this.mp3Infos=mp3Infos;
}
public int getCount() {
return mp3Infos.size();
}
@Override
public Object getItem(int position) {
return mp3Infos.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(mp3Infos==null){
return null;
}
if(convertView==null){
AppView view = new AppView(this.context);
view.update(mp3Infos.get(position));
convertView=view;
convertView.setTag(view);
}else{
convertView= (View) convertView.getTag();
}
return convertView;
}
}
AppView
public class AppView extends RelativeLayout{
ImageView mp3ImageView;
TextView mp3ArtistName;
TextView mp3Name;
TextView mp3Duration;
private Context context;
public AppView(Context context){
super(context);
this.context=context;
init(context);
}
public void init(Context context){
this.context=context;
View view = LayoutInflater.from(this.context).inflate(R.layout.listview_item_activity,null);
mp3ImageView = (ImageView) view.findViewById(R.id.mpe3_imageView);
mp3ArtistName = (TextView) view.findViewById(R.id.mp3_ArttistName);
mp3Duration = (TextView) view.findViewById(R.id.mp3_dration);
mp3Name = (TextView) view.findViewById(R.id.Mp3_name);
addView(view);
}
public void update(Audio info){
mp3ArtistName.setText(info.getArtist());
mp3ImageView.setImageResource(R.mipmap.xxx);
mp3Duration.setText(TimeTransform.secToTime(info.getDuration()/1000));
String x = info.getDisplayName();
if(x.length()>=15){
mp3Name.setText(x.subSequence(0,15)+"...");
}else
mp3Name.setText(x);
}
}
上述代碼是三個類,一個是Activity,一個是Adapter,一個是View
這三個類就是比較標準的實現顯示一個ListView的操作吧,相信大家不會陌生,就是拿着我們返回的所有音頻的List並且給他寫好適配器,然後進行顯示操作。當然代碼中也有建立ListView的Item監聽器。
現在我們實現了獲取所有音頻文件,然後將其顯示在界面上,並且可以點擊,現在點擊後會需要跳轉到播放界面。
上面代碼中我已經寫了跳轉部分,現在我們實現以下需要跳轉的類:
MusicPlayActivity
public class MusicPlayActivity extends Activity {
@ViewInject(R.id.play_music)
ImageView playMusicIv;
@ViewInject(R.id.next_music)
ImageView nextMusicIv;
@ViewInject(R.id.pre_music)
ImageView preMusicIv;
@ViewInject(R.id.play_mode)
ImageView playMusicModeIv;
@ViewInject(R.id.music_name)
TextView musicName;
@ViewInject(R.id.artist_name)
TextView artistName;
@ViewInject(R.id.seekBar)
SeekBar seekBar;
@ViewInject(R.id.MusicCurrentTime)
TextView MusicCurrentTime;
@ViewInject(R.id.MusicTime)
TextView MusicTime;
int array[];
public static List<Audio> mp3Infos;
boolean mBound=false;//控制綁定和開啓Service
boolean flag = false;//判斷播放狀態
Thread myThread;
int position;
private int playFlag = 0;//0 順序,1 單曲, 2,隨機
boolean playStatus=true;//控制線程的開啓和關閉
Mp3Service mService;
static String TAG="MusicPlayerActivity";
//Handler用於時時更新進度條
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
double progress = msg.getData().getDouble("progress");
int currentTime = msg.getData().getInt("currentTime");
int max = seekBar.getMax();
int position = (int) (max * progress);
//設置seekbar的實際位置
seekBar.setProgress(position);
MusicCurrentTime.setText(TimeTransform.secToTime(currentTime/1000));//設置總時間顯示
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
x.view().inject(this);
//獲取到從MainActivity傳過來的歌曲的位置
Intent intent = getIntent();
position = intent.getIntExtra("position",-1);
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
init();
}
public void init(){
//通過Mp3Service類中的isRunning來判斷服務是否處於開啓狀態,如果是的話,則需要關閉輔助在進行別的操作
if(Mp3Service.isRunning==true){
// unbindService(mConnection);
stopService(new Intent(MusicPlayActivity.this, Mp3Service.class));
}
//獲取到Mp3的信息
mp3Infos = MainActivity.mp3Infos;
//設置界面 musicName.setText(mp3Infos.get(position).getDisplayName());
MusicTime.setText(TimeTransform.secToTime(mp3Infos.get(position).getDuration()/1000));
artistName.setText(mp3Infos.get(position).getArtist());
Intent serviceIntent = new Intent(MusicPlayActivity.this,Mp3Service.class);
serviceIntent.putExtra("position",position);
if(mBound==false){
//開啓和綁定服務
startService(serviceIntent);
bindService(serviceIntent,mConnection,BIND_AUTO_CREATE);
}
myThread = new Thread(new UpdateProgress());
//拖動seekBar調整播放進度
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) {
int dest = seekBar.getProgress();
//seekbar的最大值
int max = seekBar.getMax();
//調用service調節播放進度
mService.setProgress(max, dest);
}
});
}
//播放界面按鈕的監聽器
@Event(value={R.id.play_music,R.id.pre_music,R.id.next_music,R.id.play_mode})
private void setOnClickListener(View view){
switch (view.getId()) {
case R.id.play_music:
if (mBound && flag) {
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.playmusic_selector));
mService.pause();
flag = false;
} else {
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
mService.play();
flag = true;
}
break;
case R.id.pre_music:
position=position-1;
if(position<0)
position = mp3Infos.size() - 1;
playStatus = false;
mBound=false;
unbindService(mConnection);
init();
break;
case R.id.next_music:
switch (playFlag){
case 0:
position = position+1;
if(position>=mp3Infos.size()){
position=0;
}
break;
case 1:
position = position;
Toast.makeText(MusicPlayActivity.this, "單曲播放中", Toast.LENGTH_SHORT).show();
break;
case 2:
int x=(int)(Math.random()*mp3Infos.size()-1);
position=x;
break;
}
playStatus = false;
mBound=false;
unbindService(mConnection);
init();
break;
case R.id.play_mode:
switch (playFlag){
case 0:
playFlag++;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.danqu_play_selector));
break;
case 1:
playFlag++;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.suiji_play_selector));
break;
case 2:
playFlag=0;
playMusicModeIv.setImageDrawable(getResources().getDrawable(R.drawable.xunhuan_play_selector));
break;
}
break;
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Mp3Service.MyBinder myBinder = (Mp3Service.MyBinder) service;
mService = (Mp3Service) myBinder.getService();
mBound=true;
playStatus=true;
playMusicIv.setImageDrawable(getResources().getDrawable(R.drawable.pause_music_selector));
// mService.play();
myThread.start();
flag=true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound=false;
}
};
public void onDestroy() {
//銷燬activity時,要記得銷燬線程
playStatus = false;
super.onDestroy();
}
//更新進度線程,單位時間獲取Service返回的
private class UpdateProgress implements Runnable {
Bundle data = new Bundle();
int millisecond=100;
double progress;
int currentTime;
@Override
public void run() {
while(playStatus){
try {
if(mBound){
Message msg = new Message();
data.clear();
progress = mService.getProgress();
currentTime=mService.getDurationTime();
msg.what=0;
data.putDouble("progress",progress);
data.putInt("currentTime",currentTime);
msg.setData(data);
mHandler.sendMessage(msg);
}
Thread.sleep(millisecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static boolean isServiceRunning(Context mContext, String className) {
boolean isRunning = false;
ActivityManager activityManager = (ActivityManager)
mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> serviceList= activityManager.getRunningServices(50);
if (!(serviceList.size()>0)) {
return false;
}
for (int i=0; i<serviceList.size(); i++) {
String a =serviceList.get(i).service.getClassName();
if (serviceList.get(i).service.getClassName().equals(className) == true) {
isRunning = true;
break;
}
}
return isRunning;
}
}
這個類就是音樂的播放界面,在這裏我們首先獲取到了由ListView點擊後傳過來的位置參數,然後在開啓服務之前首要去判斷這個服務是否處於正在運行狀態,如果是的話,就首先關閉服務,然後在進行別的操作,因爲我們聽歌的時候,切歌,必須先關閉前一首歌,再去播放另一首,然後isRunning就是Service裏的屬性,從這裏判斷是否服務正在進行。
當然更新進度也的放在這個類裏,具體思想就是我們通過Thread和Handler去實現,線程不斷從Service中獲取歌曲播放進度,然後send給Handler,在由Handler去進行seekBar的更新。
下面就說一下我們比較核心的Service類,這個類就是後臺播放歌曲的實現。
public class Mp3Service extends Service {
int position;
MediaPlayer mediaPlayer;//MediaPlayer 對象
//判斷是否服務開啓了
public static boolean isRunning=false;
//構建Binder,通過binder和Activity進行交互
MyBinder myBinder= new MyBinder();
public static List<Audio> mp3Infos;
public IBinder onBind(Intent intent) {
return myBinder;
}
public class MyBinder extends Binder{
//獲取到Service
public Service getService(){
return Mp3Service.this;
}
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
//開啓服務
public int onStartCommand(Intent intent, int flags, int startId) {
isRunning=true;
//取到帶值跳轉傳過來的歌曲位置參數
position=intent.getIntExtra("position",-1);
mp3Infos= MusicPlayActivity.mp3Infos;
init();
return Service.START_NOT_STICKY;
}
//初始化音樂MediaPlayer
public void init(){
mediaPlayer = new MediaPlayer();
//拿到歌曲位置進行初始化
try {
mediaPlayer.reset();
mediaPlayer.setDataSource(mp3Infos.get(position).getPath());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//獲取進度
public double getProgress(){
int position = mediaPlayer.getCurrentPosition();
int time = mediaPlayer.getDuration();
double progress = (double)position / (double)time;
return progress;
}
//獲取播放時間
public int getDurationTime(){
return mediaPlayer.getCurrentPosition();
}
//設置進度
public void setProgress(int max , int dest){
int time = mediaPlayer.getDuration();
mediaPlayer.seekTo(time*dest/max);
}
public void play(){
if(mediaPlayer!=null){
mediaPlayer.start();
}
}
public void pause(){
if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
@Override
//服務銷燬
public void onDestroy() {
if(mediaPlayer!=null&&mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
isRunning=false;
}
super.onDestroy();
}
}
這個Mp3Service類就是實現歌曲後臺播放的類,在這裏我們首先接收到了由MusicPlayActivity傳入的歌曲位置,當MusicPlayActivity類開啓和綁定服務之後,我服務類進行了初始化,首先實例化了mediaPlayer並且設置好了他的播放設置。然後這個類裏寫了歌曲的暫停和播放功能。
在這裏要知道什麼時候服務開啓,什麼時候服務關閉,然後在合適的地方設置好isRunning的值。
這個類中我們也寫了獲取進度和設置進度的方法,獲取進度就是爲了方便MusicPlayActivity去時時獲取進度進行界面進度條更新,設置進度就是爲了拖動bar的時候能夠調節進度。
當然要完善銷燬方法,釋放Mediaplayer的一系列操作。
好了差不多就是這樣,然後還有用到的工具類,我也複製在底下:
public class TimeTransform {
public static String secToTime(int time) {
String timeStr = null;
int hour = 0;
int minute = 0;
int second = 0;
if (time <= 0)
return "00:00";
else {
minute = time / 60;
if (minute < 60) {
second = time % 60;
timeStr = unitFormat(minute) + ":" + unitFormat(second);
} else {
hour = minute / 60;
if (hour > 99)
return "99:59:59";
minute = minute % 60;
second = time - hour * 3600 - minute * 60;
timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
}
}
return timeStr;
}
public static String unitFormat(int i) {
String retStr = null;
if (i >= 0 && i < 10)
retStr = "0" + Integer.toString(i);
else
retStr = "" + i;
return retStr;
}
}
這個類的作用就是傳入一個秒數,然後他會返回一個標準化時間輸出的字符串,例如傳入xxxxxx,他會 返回xx:xx。因爲音樂播放界面的時間應該是標準化的。
要記住Mediaplayer獲取到的時間都是以毫秒爲單位的,所以傳入的時候必須轉化成秒在傳入。xxxx/1000
主要進行傳值我們都用的是帶值跳轉和static 聲明存放歌曲的List,這樣他們就可以共享這些數據了。佈局文件我就不貼出來了,這個可以自己實現,大體的思路就是這樣,可能有些太長了,因爲我把所有的代碼基本上都放出來了。
好了,基本就這麼多,這篇文章主要爲了記錄學習過程,還有很多不完善和不規範的地方,請大神勿噴。作爲新手僅僅是想做一點學習記錄而已。