Android實(shí)現(xiàn)簡(jiǎn)單音樂(lè)播放器(MediaPlayer)
Android實(shí)現(xiàn)簡(jiǎn)單音樂(lè)播放器(MediaPlayer),供大家參考,具體內(nèi)容如下
開(kāi)發(fā)工具:Andorid Studio 1.3
運(yùn)行環(huán)境:Android 4.4 KitKat
工程內(nèi)容
實(shí)現(xiàn)一個(gè)簡(jiǎn)單的音樂(lè)播放器,要求功能有:
- 播放、暫停功能;
- 進(jìn)度條顯示播放進(jìn)度功能
- 拖動(dòng)進(jìn)度條改變進(jìn)度功能;
- 后臺(tái)播放功能;
- 停止功能;
- 退出功能;
代碼實(shí)現(xiàn)
導(dǎo)入歌曲到手機(jī)SD卡的Music目錄中,這里我導(dǎo)入了4首歌曲:仙劍六里面的《誓言成暉》、《劍客不能說(shuō)》、《鏡中人》和《浪花》,也推薦大家聽(tīng)喔(捂臉
然后新建一個(gè)類(lèi)MusicService繼承Service,在類(lèi)中定義一個(gè)MyBinder,有一個(gè)方法用于返回MusicService本身,在重載onBind()方法的時(shí)候返回
public class MusicService extends Service { public final IBinder binder = new MyBinder(); public class MyBinder extends Binder{ MusicService getService() { return MusicService.this; } } @Override public IBinder onBind(Intent intent) { return binder; } }
在MusicService中,聲明一個(gè)MediaPlayer變量,進(jìn)行設(shè)置歌曲路徑,這里我選擇歌曲1作為初始化時(shí)候的歌曲
private String[] musicDir = new String[]{ Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《誓言成暉》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《劍客不能說(shuō)》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《鏡中人》.mp3", Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music/仙劍奇?zhèn)b傳六-主題曲-《浪花》.mp3"}; private int musicIndex = 1; public static MediaPlayer mp = new MediaPlayer(); public MusicService() { try { musicIndex = 1; mp.setDataSource(musicDir[musicIndex]); mp.prepare(); } catch (Exception e) { Log.d("hint","can't get to the song"); e.printStackTrace(); } }
設(shè)計(jì)一些歌曲播放、暫停、停止、退出相應(yīng)的邏輯,此外我還設(shè)計(jì)了上一首和下一首的邏輯
public void playOrPause() { if(mp.isPlaying()){ mp.pause(); } else { mp.start(); } } public void stop() { if(mp != null) { mp.stop(); try { mp.prepare(); mp.seekTo(0); } catch (Exception e) { e.printStackTrace(); } } } public void nextMusic() { if(mp != null && musicIndex < 3) { mp.stop(); try { mp.reset(); mp.setDataSource(musicDir[musicIndex+1]); musicIndex++; mp.prepare(); mp.seekTo(0); mp.start(); } catch (Exception e) { Log.d("hint", "can't jump next music"); e.printStackTrace(); } } } public void preMusic() { if(mp != null && musicIndex > 0) { mp.stop(); try { mp.reset(); mp.setDataSource(musicDir[musicIndex-1]); musicIndex--; mp.prepare(); mp.seekTo(0); mp.start(); } catch (Exception e) { Log.d("hint", "can't jump pre music"); e.printStackTrace(); } } }
注冊(cè)MusicService并賦予權(quán)限,允許讀取外部存儲(chǔ)空間
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <service android:name="com.wsine.west.exp5_AfterClass.MusicService" android:exported="true"></service>
在MainAcitvity中聲明ServiceConnection,調(diào)用bindService保持與MusicService通信,通過(guò)intent的事件進(jìn)行通信,在onCreate()函數(shù)中綁定Service
private ServiceConnection sc = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { musicService = ((MusicService.MyBinder)iBinder).getService(); } @Override public void onServiceDisconnected(ComponentName componentName) { musicService = null; } }; private void bindServiceConnection() { Intent intent = new Intent(MainActivity.this, MusicService.class); startService(intent); bindService(intent, sc, this.BIND_AUTO_CREATE); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("hint", "ready to new MusicService"); musicService = new MusicService(); Log.d("hint", "finish to new MusicService"); bindServiceConnection(); seekBar = (SeekBar)this.findViewById(R.id.MusicSeekBar); seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setMax(musicService.mp.getDuration()); musicStatus = (TextView)this.findViewById(R.id.MusicStatus); musicTime = (TextView)this.findViewById(R.id.MusicTime); btnPlayOrPause = (Button)this.findViewById(R.id.BtnPlayorPause); Log.d("hint", Environment.getExternalStorageDirectory().getAbsolutePath()+"/You.mp3"); }
bindService函數(shù)回調(diào)onSerciceConnented函數(shù),通過(guò)MusiceService函數(shù)下的onBind()方法獲得binder對(duì)象并實(shí)現(xiàn)綁定
通過(guò)Handle實(shí)時(shí)更新UI,這里主要使用了post方法并在Runnable中調(diào)用postDelay方法實(shí)現(xiàn)實(shí)時(shí)更新UI,Handle.post方法在onResume()中調(diào)用,使得程序剛開(kāi)始時(shí)和重新進(jìn)入應(yīng)用時(shí)能夠更新UI
在Runnable中更新SeekBar的狀態(tài),并設(shè)置SeekBar滑動(dòng)條的響應(yīng)函數(shù),使歌曲跳動(dòng)到指定位置
public android.os.Handler handler = new android.os.Handler(); public Runnable runnable = new Runnable() { @Override public void run() { if(musicService.mp.isPlaying()) { musicStatus.setText(getResources().getString(R.string.playing)); btnPlayOrPause.setText(getResources().getString(R.string.pause).toUpperCase()); } else { musicStatus.setText(getResources().getString(R.string.pause)); btnPlayOrPause.setText(getResources().getString(R.string.play).toUpperCase()); } musicTime.setText(time.format(musicService.mp.getCurrentPosition()) + "/" + time.format(musicService.mp.getDuration())); seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { musicService.mp.seekTo(seekBar.getProgress()); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); handler.postDelayed(runnable, 100); } }; @Override protected void onResume() { if(musicService.mp.isPlaying()) { musicStatus.setText(getResources().getString(R.string.playing)); } else { musicStatus.setText(getResources().getString(R.string.pause)); } seekBar.setProgress(musicService.mp.getCurrentPosition()); seekBar.setMax(musicService.mp.getDuration()); handler.post(runnable); super.onResume(); Log.d("hint", "handler post runnable"); }
給每個(gè)按鈕設(shè)置響應(yīng)函數(shù),在onDestroy()中添加解除綁定,避免內(nèi)存泄漏
public void onClick(View view) { switch (view.getId()) { case R.id.BtnPlayorPause: musicService.playOrPause(); break; case R.id.BtnStop: musicService.stop(); seekBar.setProgress(0); break; case R.id.BtnQuit: handler.removeCallbacks(runnable); unbindService(sc); try { System.exit(0); } catch (Exception e) { e.printStackTrace(); } break; case R.id.btnPre: musicService.preMusic(); break; case R.id.btnNext: musicService.nextMusic(); break; default: break; } } @Override public void onDestroy() { unbindService(sc); super.onDestroy(); }
在Button中賦予onClick屬性指向接口函數(shù)
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/BtnPlayorPause" android:text="@string/btnPlayorPause" android:onClick="onClick"/>
效果圖
打開(kāi)界面->播放一會(huì)兒進(jìn)度條實(shí)時(shí)變化->拖動(dòng)進(jìn)度條->點(diǎn)擊暫停->點(diǎn)擊Stop->點(diǎn)擊下一首(歌曲時(shí)間變化)->點(diǎn)擊上一首->點(diǎn)擊退出
一些總結(jié)
- 讀取SD卡內(nèi)存的時(shí)候,應(yīng)該使用android.os.Environment庫(kù)中的getExternalStorageDirectory()方法,然而并不能生效。應(yīng)該再使用getAbsolutePath()獲取絕對(duì)路徑后讀取音樂(lè)才生效。
- 切換歌曲的時(shí)候try塊不能正確執(zhí)行。檢查過(guò)后,也是執(zhí)行了stop()函數(shù)后再重新setDataSource()來(lái)切換歌曲的,但是沒(méi)有效果。查閱資料后,發(fā)現(xiàn)setDataSource()之前需要調(diào)用reSet()方法,才可以重新設(shè)置歌曲。
了解Service中startService(service)和bindService(service, conn, flags)兩種模式的執(zhí)行方法特點(diǎn)及其生命周期,還有為什么這次要一起用
startService方法是讓Service啟動(dòng),讓Service進(jìn)入后臺(tái)running狀態(tài);但是這種方法,service與用戶(hù)是不能交互的,更準(zhǔn)確的說(shuō)法是,service與用戶(hù)不能進(jìn)行直接的交互。
因此需要使用bindService方法綁定Service服務(wù),bindService返回一個(gè)binder接口實(shí)例,用戶(hù)就可以通過(guò)該實(shí)例與Service進(jìn)行交互。
Service的生命周期簡(jiǎn)單到不能再簡(jiǎn)單了,一條流水線(xiàn)表達(dá)了整個(gè)生命周期。
service的活動(dòng)生命周期是在onStart()之后,這個(gè)方法會(huì)處理通過(guò)startServices()方法傳遞來(lái)的Intent對(duì)象。音樂(lè)service可以通過(guò)開(kāi)打intent對(duì)象來(lái)找到要播放的音樂(lè),然后開(kāi)始后臺(tái)播放。注: service停止時(shí)沒(méi)有相應(yīng)的回調(diào)方法,即沒(méi)有onStop()方法,只有onDestroy()銷(xiāo)毀方法。
onCreate()方法和onDestroy()方法是針對(duì)所有的services,無(wú)論它們是否啟動(dòng),通過(guò)Context.startService()和Context.bindService()方法都可以訪(fǎng)問(wèn)執(zhí)行。然而,只有通過(guò)startService()方法啟動(dòng)service服務(wù)時(shí)才會(huì)調(diào)用onStart()方法。
圖片來(lái)自網(wǎng)絡(luò),忘記出處了
簡(jiǎn)述如何使用Handler實(shí)時(shí)更新UI
方法一:
Handle的post方法,在post的Runable的run方法中,使用postDelay方法再次post該Runable對(duì)象,在Runable中更新UI,達(dá)到實(shí)時(shí)更新UI的目的
方法二:
多開(kāi)一個(gè)線(xiàn)程,線(xiàn)程寫(xiě)一個(gè)持續(xù)循環(huán),每次進(jìn)入循環(huán)內(nèi)即post一次Runable,然后休眠1000ms,亦可做到實(shí)時(shí)更新UI
工程下載
傳送門(mén):下載
更多關(guān)于播放器的內(nèi)容請(qǐng)點(diǎn)擊《java播放器功能》進(jìn)行學(xué)習(xí)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android音視頻開(kāi)發(fā)之MediaPlayer使用教程
- Android MediaPlayer 音頻倍速播放 調(diào)整播放速度問(wèn)題
- Android MediaPlayer 播放音頻的方式
- Android多媒體應(yīng)用使用MediaPlayer播放音頻
- Android開(kāi)發(fā)之MediaPlayer多媒體(音頻,視頻)播放工具類(lèi)
- Android編程之播放器MediaPlayer實(shí)現(xiàn)均衡器效果示例
- Android編程視頻播放API之MediaPlayer用法示例
- Android MediaPlayer音頻播放器封裝示例淺析
相關(guān)文章
Android實(shí)現(xiàn)簡(jiǎn)單圖庫(kù)輔助器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單圖庫(kù)輔助器的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)箭頭沿圓轉(zhuǎn)動(dòng)實(shí)例代碼,需要的朋友可以參考下2017-09-09Android開(kāi)發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法示例
這篇文章主要介紹了Android開(kāi)發(fā)之FloatingActionButton懸浮按鈕基本使用、字體、顏色用法,結(jié)合實(shí)例形式分析了Android FloatingActionButton懸浮按鈕的基本功能、布局、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2019-03-03Eclipse工程轉(zhuǎn)為兼容Android Studio模式的方法步驟圖文詳解
這篇文章主要介紹了Eclipse工程轉(zhuǎn)為兼容Android Studio模式的方法步驟,本文圖文并茂給大家介紹的非常詳細(xì),需要的朋友可以參考下2017-12-12Android開(kāi)發(fā)實(shí)現(xiàn)跟隨手指的小球效果示例
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)現(xiàn)跟隨手指的小球效果,涉及Android圖形繪制、事件響應(yīng)、界面布局等相關(guān)操作技巧,需要的朋友可以參考下2019-04-04Android中自定義ImageView添加文字說(shuō)明詳解
Android中的ImageView只能顯示矩形的圖片,為了用戶(hù)體驗(yàn)更多,下面這篇文章主要給大家介紹了關(guān)于Android中自定義ImageView實(shí)現(xiàn)添加文字說(shuō)明的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-08-08解決Android MediaRecorder錄制視頻過(guò)短問(wèn)題
本文主要介紹Android MediaRecorder,在使用MediaRecorder時(shí)經(jīng)常會(huì)遇到視頻錄制太短問(wèn)題,這里提供解決問(wèn)題的實(shí)例代碼以供大家參考2016-07-07