ورود / ثبت نام سبد خرید 0
در این آموزش قصد داریم یک موزیک پلیر در اندروید استودیو بسازیم که از بهترین متد ها و API ها برای پیاده سازی این اپلیکیشن استفاده خواهیم کرد . بخاطر اینکه این آموزش یک مقاله ی آموزشی طولانی است آنرا به دو بخش تقسیم کردیم که یادگیری موارد بسیار راحت تر شود :)
محیط Android Studio خودتان را باز کنید و یک پروژه ی جدید ایجاد کنید و در AndroidManifest.xml پروژه مجوز های زیر را اضافه کنید .
<uses-permission android:name="android.permission.INTERNET" /> <permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
در کد های بالا ابتدا مجوز اینترنت به برنامه داده میشود تا اگر خواستیم موسیقی های انلاین را هم پخش کنیم و عمل استریم موزیک انلاین را هم انجام بدیم .
برای کنترل کردن پخش مدیا به مجوز MEDIA_CONTENT_CONTROL نیاز است .
با مجوز READ_PHONE_STATE وضعیت تلفن را میخوانید و این مجوز به این خاطر است که اگر هنگام پخش موسیقی کسی تماس گرفت اپلیکیشن شما متوجه شود .
هسته ی اصلی اپلیکیشن های موزیک پلیر یک سرویس است ، media player service .
کلاس زیر مثالی است از این سرویس .
این کلاس چندنوع MediaPlayer برای کنترل رویداد ها در هنگام پخش موزیک دارد . همانطور که میبینید از کلاس های زیادی استفاده شده است ولی اخرین پیاده سازی از روی AudioManager.OnAudioFocusChangeListener است که به برای کنترل کردن درخواست های AudioFocus از دیگر اپلیکیشن ها که میخواهند فایل های مدیا را پخش کنند.
public class MediaPlayerService extends Service implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener, AudioManager.OnAudioFocusChangeListener { // Binder given to clients private final IBinder iBinder = new LocalBinder(); @Override public IBinder onBind(Intent intent) { return iBinder; } @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { //Invoked indicating buffering status of //a media resource being streamed over the network. } @Override public void onCompletion(MediaPlayer mp) { //Invoked when playback of a media source has completed. } //Handle errors @Override public boolean onError(MediaPlayer mp, int what, int extra) { //Invoked when there has been an error during an asynchronous operation. return false; } @Override public boolean onInfo(MediaPlayer mp, int what, int extra) { //Invoked to communicate some info. return false; } @Override public void onPrepared(MediaPlayer mp) { //Invoked when the media source is ready for playback. } @Override public void onSeekComplete(MediaPlayer mp) { //Invoked indicating the completion of a seek operation. } @Override public void onAudioFocusChange(int focusChange) { //Invoked when the audio focus of the system is updated. } public class LocalBinder extends Binder { public MediaPlayerService getService() { return MediaPlayerService.this; } } }
نمونه کد بالا یک قالب از تمام متدهای MediaPlayer برای کنترل کردن رویدادها است .
میشه گفت کد بالا نیازمندی های کلی Service شماست . شما باید این Service را تعریف کنید تا با Activity شما در ارتباط باشد و فایل های audio را دریافت کند .
شما باید Service را به فایل AndroidManifest.xml معرفی کنید . طبق کد زیر این کار را انجام دهید .
<application <service android:name=".MediaPlayerService" /> ... </application>
فریمورک مالتی مدیای اندروید از بسیار فرمت های رسانه ای پشتیبانی میکند .
یکی از کامپوننت های مهم این بخش کلاس MediaPlayer است که با تنظیمات کم و انجام کارهای ساده میتوانید فایل های صوتی و ویدیویی را پخش کنید .
در ادامه متد های لازم از کلاس MediaPlayerService را به شما آموزش می دهیم .
دو تعریف زیر را انجام دهید که یکی از نوع MediaPlayer و یکی از نوع String است .
private MediaPlayer mediaPlayer; //path to the audio file private String mediaFile;
حالا mediaPlayer را طبق کد زیر پیاده سازی کنید .
private void initMediaPlayer() { mediaPlayer = new MediaPlayer(); //Set up MediaPlayer event listeners mediaPlayer.setOnCompletionListener(this); mediaPlayer.setOnErrorListener(this); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer.setOnSeekCompleteListener(this); mediaPlayer.setOnInfoListener(this); //Reset so that the MediaPlayer is not pointing to another data source mediaPlayer.reset(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); try { // Set the data source to the mediaFile location mediaPlayer.setDataSource(mediaFile); } catch (IOException e) { e.printStackTrace(); stopSelf(); } mediaPlayer.prepareAsync(); }
زمانی که میخواهید با مدیا کار کنید باید یه سری متد ها را override کنید تا بتوانید مدیا را کنترل کنید . این متد ها چیزهایی مثل Play, Stop, Pause و Resume هستند.
ابتدا یک متغیر گلوبال برای عملیات pause/resume را مینویسیم.
//Used to pause/resume MediaPlayer private int resumePosition;
و یک if نیاز است تا مطمئن شوید تا خطایی هنگام مدیا وجود ندارد .
private void playMedia() { if (!mediaPlayer.isPlaying()) { mediaPlayer.start(); } } private void stopMedia() { if (mediaPlayer == null) return; if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } } private void pauseMedia() { if (mediaPlayer.isPlaying()) { mediaPlayer.pause(); resumePosition = mediaPlayer.getCurrentPosition(); } } private void resumeMedia() { if (!mediaPlayer.isPlaying()) { mediaPlayer.seekTo(resumePosition); mediaPlayer.start(); } }
اکنون شما متدهای ابتدایی مورد نیاز برای راه اندازی را نوشتید و وقت این است که متد های @Override را پیاده سازی کنیم تا قالب اولیه Service ساخته شود .
این متد ها برای MediaPlayer مهم هستند چون زیرا تمام عملیات کلیدی پخش کننده موزیک از اینها استفاده خواهد کرد .
کد درون Service را با کدهای زیر جایگزین کنید .
@Override public void onCompletion(MediaPlayer mp) { //Invoked when playback of a media source has completed. stopMedia(); //stop the service stopSelf(); } //Handle errors @Override public boolean onError(MediaPlayer mp, int what, int extra) { //Invoked when there has been an error during an asynchronous operation switch (what) { case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: Log.d("MediaPlayer Error", "MEDIA ERROR NOT VALID FOR PROGRESSIVE PLAYBACK " + extra); break; case MediaPlayer.MEDIA_ERROR_SERVER_DIED: Log.d("MediaPlayer Error", "MEDIA ERROR SERVER DIED " + extra); break; case MediaPlayer.MEDIA_ERROR_UNKNOWN: Log.d("MediaPlayer Error", "MEDIA ERROR UNKNOWN " + extra); break; } return false; } @Override public void onPrepared(MediaPlayer mp) { //Invoked when the media source is ready for playback. playMedia(); }
برای اینکه یک موزیک پلیر دلچسب در اندروید استودیو بسازید باید به دیگر رسانه های پخش شده در سیستم اندروید یا دیگر اپ ها هم توجه کنید .
برای اینکه مطمئن شوید چنین تجربه ی مثبتی ایجاد کنید MediaPlayerService رخداد های AudioFocus را کنترل میکند و توسط متد onAudioFocusChange() قابل انجام و مدیریت است .
این متد مثل یک switch است و رخداد های focus مثل case های مختلف است .
توجه کنید که این متد بعد از رخداد AudioFocus اجرا میشود که توسط سیستم یا دیگر اپ ها اتفاق میوفتد .
برای حالت های مختلف case: چه اتفاق هایی میوفتد ؟
شما به override دو متد دیگر نیز نیاز دارید تا focus صدای MediaPlayer را درخواست یا آزاد کنید . در زیر نمونه کدهایی را میبینید که تمامی چیزهای مربوط به focus که بالا توضیح دادیم را نشان میدهد . از متد onAudioFocusChange() استفاده شده است . شما میتوانید درباره ی این متد در مستندات توسعه دهندگان اندروید بصورت کامل از اینجا بخوانید .
در ابتدا برای Service یک متغیر گلوبال تعریف میکنیم .
private AudioManager audioManager;
متد onAudioFocusChange() را با کدهای زیر جایگزین کنید .
@Override public void onAudioFocusChange(int focusState) { //Invoked when the audio focus of the system is updated. switch (focusState) { case AudioManager.AUDIOFOCUS_GAIN: // resume playback if (mediaPlayer == null) initMediaPlayer(); else if (!mediaPlayer.isPlaying()) mediaPlayer.start(); mediaPlayer.setVolume(1.0f, 1.0f); break; case AudioManager.AUDIOFOCUS_LOSS: // Lost focus for an unbounded amount of time: stop playback and release media player if (mediaPlayer.isPlaying()) mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: // Lost focus for a short time, but we have to stop // playback. We don't release the media player because playback // is likely to resume if (mediaPlayer.isPlaying()) mediaPlayer.pause(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // Lost focus for a short time, but it's ok to keep playing // at an attenuated level if (mediaPlayer.isPlaying()) mediaPlayer.setVolume(0.1f, 0.1f); break; } } private boolean requestAudioFocus() { audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { //Focus gained return true; } //Could not gain focus return false; } private boolean removeAudioFocus() { return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == audioManager.abandonAudioFocus(this); }
در این بخش روی چرخه ی حیات Service تمرکز میکنیم . این متد ها برای MediaPlayer بسیار مهم هستند چون چرخه ی حیات Service ارتباط نزدیکی با MediaPlayer دارد .
این متد ها پیاده سازی اولیه و مدیریت منابع MediaPlayer را انجام میدهند .
برای درک بهتر لابلای کدهای زیر کامنت گذاشته شده است
//The system calls this method when an activity, requests the service be started @Override public int onStartCommand(Intent intent, int flags, int startId) { try { //An audio file is passed to the service through putExtra(); mediaFile = intent.getExtras().getString("media"); } catch (NullPointerException e) { stopSelf(); } //Request audio focus if (requestAudioFocus() == false) { //Could not gain focus stopSelf(); } if (mediaFile != null && mediaFile != "") initMediaPlayer(); return super.onStartCommand(intent, flags, startId); }
متد onStartCommand() عمل پیاده سازی MediaPlayer را انجام میدهد و همچنین پیاده سازی اولیه focus request را انجام میدهد تا مطمئن شود در اندروید چیزی از قبل پخش نمیشود که صدا رو صدا نیوفته
در متد onStartCommand() یک بلاک try-catch اضافه شده است تا مطمئن شویم که توسط متد getExtras() خطای NullPointerException اتفاق نمیوفته .
نکته : برای خلاصی از شر خطای معروف NullPointerException بجای جاوا از کاتلین استفاده کنید . اگر کاتلین را بلد نیستید ما قبلا دوره ی آموزش کاتلین تهیه کردیم
یکی از متد های مهم دیگر که باید پیاده سازی کنید متد onDestroy() است . در این متد باید منبع MediaPlayer آزاد گردد و Service باید نابود شود
@Override public void onDestroy() { super.onDestroy(); if (mediaPlayer != null) { stopMedia(); mediaPlayer.release(); } removeAudioFocus(); }
متد onDestroy() همچنین audio focus را آزاد میکند این یک انتخاب شخصی است و اگر از این روش استفاده کنید MediaPlayerService تا زمانی که نابود نشده audio focus را خواهد داشت ، البته اگر مزاحمتی توسط دیگر برنامه ها برای focus نباشد .
اگر شما کنترل focus پویاتری میخواهید شما میتوانید هنگام شروع پخش موزیک audio focus را در متد onCompletion() درخواست کنید .
در این بخش آخرین قدم ها برای bind کردن MediaPlayerService در MainActivity و آماده کردن آن برای پخش موزیک را پوشش خواهم داد .
شما باید Service را به activity بایند کنید بنابراین این دو میتوانند با هم دیگر در ارتباط باشند .
متغیرهای گلوبال زیر را به MainActivity اضافه کنید .
private MediaPlayerService player; boolean serviceBound = false;
اولی یک instance از Service است و دومی وضعیت service را مشخص میکند که مشخص شود برای اکتیویتی bind شده است یا نه !
برای کنترل کردن bind شدن Service کدهای زیر را به MainActivity اضافه کنید .
//Binding this Client to the AudioPlayer Service private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance MediaPlayerService.LocalBinder binder = (MediaPlayerService.LocalBinder) service; player = binder.getService(); serviceBound = true; Toast.makeText(MainActivity.this, "Service Bound", Toast.LENGTH_SHORT).show(); } @Override public void onServiceDisconnected(ComponentName name) { serviceBound = false; } };
خب الان وقت این است که چند موزیک پخش کنیم . تابع زیر یک instance از MediaPlayerService میسازد و یک فایل صوتی را برای پخش شدن میفرستد . خب پس کدهای زیر را هم به MainActivity خود اضافه کنید .
private void playAudio(String media) { //Check is service is active if (!serviceBound) { Intent playerIntent = new Intent(this, MediaPlayerService.class); playerIntent.putExtra("media", media); startService(playerIntent); bindService(playerIntent, serviceConnection, Context.BIND_AUTO_CREATE); } else { //Service is active //Send media with BroadcastReceiver } }
تابع playAudio() کامل نیست
من بعدا به این متد دوباره باز خواهم گشت زمانی که فایل صوتی به Service توسط BroadcastReceiver ارسال شود .
در متد onCreate() درون اکتیویتی تابع playAudio() را صدا بزنید و منبع صوتی را به آن معرفی کنید .
playAudio("https://upload.wikimedia.org/wikipedia/commons/6/6c/Grieg_Lyric_Pieces_Kobold.ogg");
در این بخش قصد داریم متد های چرخه ی حیات یا life cycle اکتیویتی MainActivity را بررسی کنیم . اگر تابع playAudio() را درون متد onCreate اکتیویتی صدا زده اید Service شروع به پخش موزیک میکند ولی برنامه به سادگی کرش میکند .
برای حل این مشکل متد زیر را به MainActivity اضافه کنید . تمام این متدها وضعیت serviceBound را ذخیره و بازیابی میکنند و service را زمانی که اپ بسته میشود unbind میکند .
@Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean("ServiceState", serviceBound); super.onSaveInstanceState(savedInstanceState); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); serviceBound = savedInstanceState.getBoolean("ServiceState"); } @Override protected void onDestroy() { super.onDestroy(); if (serviceBound) { unbindService(serviceConnection); //service is active player.stopSelf(); } }
یک موزیک پلیر خوب باید قابلیت پخش موزیک ها از روی حافظه ی دستگاه را علاوه بر استریم کردن آنلاین ، داشته باشد . شما میتوانید فایل های صوتی در حافظه ی لوکال را با استفاده از ContentResolver انجام دهید .
یک کلاس جدید جاوایی بسازید و بعنوان یک آبجکت audio در نظر بگیرید .
در کد زیر شما موارد مورد نیاز یک فایل موزیک را مشاهده میکنید شما میتوانید خصوصیات بیشتری را خودتان اضافه کنید .
public class Audio implements Serializable { private String data; private String title; private String album; private String artist; public Audio(String data, String title, String album, String artist) { this.data = data; this.title = title; this.album = album; this.artist = artist; } public String getData() { return data; } public void setData(String data) { this.data = data; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAlbum() { return album; } public void setAlbum(String album) { this.album = album; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } }
مجوز زیر را به فایل AndroidManifest.xml اضافه کنید .
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
این مجوز برای خواندن فایل ها از حافظه ی دستگاه اندرویدی نیاز است .
در کلاس MainActivity یک متغیر گلوبال از نوع ArrayList بسازید که وظیفه اش نگهداری آبجکت های از نوع Audio است .
ArrayList<Audio> audioList;
برای دریافت دیتا از دستگاه تابع زیر را به MainActivity اضافه کنید . این تابع اطلاعات را از دستگاه به ترتیب صعودی بازیابی می کند
private void loadAudio() { ContentResolver contentResolver = getContentResolver(); Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; String selection = MediaStore.Audio.Media.IS_MUSIC + "!= 0"; String sortOrder = MediaStore.Audio.Media.TITLE + " ASC"; Cursor cursor = contentResolver.query(uri, null, selection, null, sortOrder); if (cursor != null && cursor.getCount() > 0) { audioList = new ArrayList<>(); while (cursor.moveToNext()) { String data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)); String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)); String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM)); String artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)); // Save to audioList audioList.add(new Audio(data, title, album, artist)); } } cursor.close(); }
بعد از اینکه داده ها از دستگاه فراخوانی میشود تابع playAudio() میتواند آنها را در Service پخش کند .
در تابع onCreate() اکتیویتی MainActivity کد های زیر را اضافه کنید .
مطمئن باشید که حداقل یک فایل صوتی را دارید که Service بتواند پخش کند در غیر اینصورت برنامه ممکن است کرش کند .
loadAudio(); //play the first audio in the ArrayList playAudio(audioList.get(0).getData());
استراحت ! تا اینجا کار خوب پیش اومدید ولی اگه بدون وقفه مقاله را خوانده یا تمرین کرده باشید حتما خسته شدید . اگر دوست داشتید یک استراحت کوتاه کنید و دوباره برگردید و ادامه ی مقاله را بخوانید .
در ادامه من قصد دارم روی ارتباط کاربر با MediaPlayerService تمرکز کنم و اتفاقات را کنترل کنیم . اتفاقاتی مثل زنگ زدن موبایل هنگام پخش موزیک ، تغییر خروجی صوتی دستگاه یا هر چیز دیگری که برای موزیک پلیر کامل نیاز است .
کلید اصلی این قسمت کار کردن با BroadcastReceiver است .
کامپوننت ها و اپ های اندروید ارتباطات گسترده ای با دیگر اپ ها و یا خود سیستم اندروید با استفاده از متد های sendBroadcast() ، sendStickyBroadcast() و sendOrderedBroadcast() برقرار میکنند .
intent های Broadcast برای ساختن یک سیستم event و ارسال پیام از طرف کامپوننت های اپ یا خود سیستم اندرویدی به سمت اپلیکیشینی که منتظر یک اتفاق است و اتفاقات کلیدی سیستم به این شکل به تمام اپ ها اعلام میشود و آنها دریافت میکنند .
وظیفه ی اصلی BroadcastReceiver گوش به زنگ بودن برای اتفاق خاصی است و اگر اون اتفاق خاص رخ داد باید BroadcastReceiver کاری که سپرده شده است را انجام دهد .
زمانی که اتفاق متناظر با چیزی که منتظر است رخ میدهد متد onReceive() اجرا میشود .
شما میتوانید به دو روش یک BroadcastReceiver را رجیستر کنید : بصورت استاتیک در فایل AndroidManifest.xml یا با صدا زدن تابع registerReceiver() در هر جایی .
برای این آموزش ما بصورت پویا BroadcastReciver را تعریف کردیم تا زمانی که MediaPlayerService قصد دارد به اتفاقات سیستم گوش دهد .
برای ساخت یک اپ موزیک پلیر خوب در اندروید استودیو من از RecyclerView استفاده میکنم و با کمک تابع loadAudio() فایل های صوتی موزیک موجود در دستگاه را در RecyclerView نمایش میدهیم . همچنین تغییرات گرافیکی مثل رنگ و ... روی این عنصر انجام خواهیم داد . در این آموزش قصد نداریم به صورت جز به جز به بحث RecyclerView بپردازیم . اگر RecyclerView را کار نکردید از کارگاه آموزشی کار با ریسایکلر ویو در زبان کاتلین استفاده کنید تا به این مورد مسلط شوید .
در موزیک پلیرها مرسوم است زمانی که استفاده کننده ، هدفون یا هندزفری خود را از گوشی جدا میکند موسیقی در حال پخش بصورت خودکار متوقف میشود .
در کلاس MediaPlayerService یک BroadcastReceiver بسازید که به رویداد ACTION_AUDIO_BECOMING_NOISY گوش میدهد . تابع زیر را به کلاس service اضافه کنید .
//Becoming noisy private BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //pause audio on ACTION_AUDIO_BECOMING_NOISY pauseMedia(); buildNotification(PlaybackStatus.PAUSED); } }; private void registerBecomingNoisyReceiver() { //register after getting audio focus IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); registerReceiver(becomingNoisyReceiver, intentFilter); }
BroadcastReceiver گوش به فرمان باقی میماند و زمانی که سیستم پیام مربوط به ACTION_AUDIO_BECOMING_NOISY را منتشر میکند متوجه میشود و MediaPlayer را متوقف میکند .
برای اینکه BroadcastReceiver را بتوانید استفاده کنید باید آنرا رجیستر کنید .
تابع registerBecomingNoisyReceiver() این کار را انجام میدهد .
هنوز نیازی نیست تابع buildNotification() را پیاده سازی کنیم پس نگران خطاهایی که نمایش داده میشود نباشید .
تابع بعدی که میخواهیم داشته باشیم برای جلوگیری از پخش موسیقی در هنگام زنگ خوردن موبایل است . خب وقتی دستگاه داره زنگ میزنه و کاربر میخاد صحبت بکنه باید موسیقی در حال پخش متوقف بشه .
ابتدا در کلاس MediaPlayerService متغیرهای گلوبال زیر را تعریف کنید .
//Handle incoming phone calls private boolean ongoingCall = false; private PhoneStateListener phoneStateListener; private TelephonyManager telephonyManager;
و تابع زیر را اضافه کنید
//Handle incoming phone calls private void callStateListener() { // Get the telephony manager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); //Starting listening for PhoneState changes phoneStateListener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { //if at least one call exists or the phone is ringing //pause the MediaPlayer case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_RINGING: if (mediaPlayer != null) { pauseMedia(); ongoingCall = true; } break; case TelephonyManager.CALL_STATE_IDLE: // Phone idle. Start playing. if (mediaPlayer != null) { if (ongoingCall) { ongoingCall = false; resumeMedia(); } } break; } } }; // Register the listener with the telephony manager // Listen for changes to the device call state. telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); }
تابع callStateListener() یک پیاده سازی از PhoneStateListener است که به تغییرات TelephonyManager گوش میدهد . TelephonyManager دسترسی به اطلاعات سرویس telephony را فراهم میکند و منتظر میماند تا اگر اتفاقی مثل تماس افتاد عکس العمل نشان دهد .
من در این مقاله اشاره کردم که تغییراتی در متد ها د اده ام . همچنین روش واگزاری فایل های صوتی به Service را نیز تغییر دادم .
فایل های صوتی توسط تابع loadAudio() از حافظه ی دستگاه فراخوانی میشود وقتی کاربر میخواهد موزیک پخش شود تابع playAudio(int audioIndex) اجرا میشود - ورودی این تابع شماره index فایلی است که در ArrayList قرار دارد .
وقتی برای اولین بار تابع playAudio() فراخوانی میشود ، ArrayList در حافظه ی SharedPreferences ذخیره میشود که شماره ی index فایل های صوتی ذخیره میشود که برای پخش موسیقی توسط MediaPlayerService از حافظه ی SharedPreferences به کار میرود .
این یکی از راه های فراخوانی آرایه ی Audio به سرویس است ولی روش های دیگری هم هست .
اکنون فایل build.gradle (app) را باز کنید و وابستگی های مورد نیاز برای کتابخانه ی GSON را اضافه کنید .
dependencies { ... compile group: 'com.google.code.gson', name: 'gson', version: '2.7', changing: true }
کلاس زیر ذخیره سازی داده ها را کنترل می کند.
public class StorageUtil { private final String STORAGE = " com.valdioveliu.valdio.audioplayer.STORAGE"; private SharedPreferences preferences; private Context context; public StorageUtil(Context context) { this.context = context; } public void storeAudio(ArrayList<Audio> arrayList) { preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); Gson gson = new Gson(); String json = gson.toJson(arrayList); editor.putString("audioArrayList", json); editor.apply(); } public ArrayList<Audio> loadAudio() { preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE); Gson gson = new Gson(); String json = preferences.getString("audioArrayList", null); Type type = new TypeToken<ArrayList<Audio>>() { }.getType(); return gson.fromJson(json, type); } public void storeAudioIndex(int index) { preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("audioIndex", index); editor.apply(); } public int loadAudioIndex() { preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE); return preferences.getInt("audioIndex", -1);//return -1 if no data found } public void clearCachedAudioPlaylist() { preferences = context.getSharedPreferences(STORAGE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.clear(); editor.commit(); } }
خب الان وقت این رسیده که تابع playAudio() را تغییر دهیم . ابتدا در MainActivity یک متغیر گلوبال و استاتیک از نوع string بسازید .
public static final String Broadcast_PLAY_NEW_AUDIO = "ir.avasam.musicplayer"; // یادتون نره نام پکیج را در بالا اصلاح کنید و همنام با پروژه ی خود بکنید
این متغیر string قرار است که intent های broadcast را به MediaPlayerService بفرستد برای اینکه کاربر میخواهد فایل صوتی جدیدی را پخش کند و کش index فایل های صوتی که قرار است پخش شود ، بروزرسانی میشود .
BroadcastReceiver که این intent را مدیریت میکند هنوز ساخته نشده است خب حالا تابع playAudio() که در MainActivity است بروزرسانی کنید و کد زیر را جایگزین کد قدیمی بکنید
private void playAudio(int audioIndex) { //Check is service is active if (!serviceBound) { //Store Serializable audioList to SharedPreferences StorageUtil storage = new StorageUtil(getApplicationContext()); storage.storeAudio(audioList); storage.storeAudioIndex(audioIndex); Intent playerIntent = new Intent(this, MediaPlayerService.class); startService(playerIntent); bindService(playerIntent, serviceConnection, Context.BIND_AUTO_CREATE); } else { //Store the new audioIndex to SharedPreferences StorageUtil storage = new StorageUtil(getApplicationContext()); storage.storeAudioIndex(audioIndex); //Service is active //Send a broadcast to the service -> PLAY_NEW_AUDIO Intent broadcastIntent = new Intent(Broadcast_PLAY_NEW_AUDIO); sendBroadcast(broadcastIntent); } }
فایل صوتی به Service توسط putExtra() ارسال نشده است ، بنابراین service باید اطلاعات را از SharedPreferences بخواند و به همین خاطر است که تابع onStartCommand() بازنویسی شود .
در آخر این مقاله ی آموزشی دوباره به تابع onStartCommand() باز خواهیم گشت تا پیاده سازی آنرا کامل کنیم .
حالا متغیرهای گلوبایل زیر را به MediaPlayerService اضافه کنید .
//List of available Audio files private ArrayList<Audio> audioList; private int audioIndex = -1; private Audio activeAudio; //an object of the currently playing audio
وقتی که MediaPlayerService در حال پخش کردن یک موزیک است و کاربر استفاده کننده میخواهد که موزیک دیگری پخش کند ، باید به service اعلام کنید که برو موزیک جدید را پخش کن .
برای این کار باید راهی پیاده سازی شود تا Service گوش به زنگ باشد برای تغییر موزیک !
برای این کار یک BroadcastReceiver دیگر نیاز داریم
در MediaPlayerService توابع زیر را اضافه کنید
private BroadcastReceiver playNewAudio = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //Get the new media index form SharedPreferences audioIndex = new StorageUtil(getApplicationContext()).loadAudioIndex(); if (audioIndex != -1 && audioIndex < audioList.size()) { //index is in a valid range activeAudio = audioList.get(audioIndex); } else { stopSelf(); } //A PLAY_NEW_AUDIO action received //reset mediaPlayer to play the new Audio stopMedia(); mediaPlayer.reset(); initMediaPlayer(); updateMetaData(); buildNotification(PlaybackStatus.PLAYING); } }; private void register_playNewAudio() { //Register playNewMedia receiver IntentFilter filter = new IntentFilter(MainActivity.Broadcast_PLAY_NEW_AUDIO); registerReceiver(playNewAudio, filter); }
موقع رهگیری PLAY_NEW_AUDIO ، این BroadcastReceiver بروزرسانی index و موزیک جدید را در activeAudio قرار میدهد و MediaPlayer ریست میشود و شروع میکند به پخش موزیک جدید . تابع buildNotification() هنوز ساخته نشده پس خطا میدهد .
در تابع onCreate مربوط به service رجیسری کردن BroadcastReceiver را انجام دهید .
@Override public void onCreate() { super.onCreate(); // Perform one-time setup procedures // Manage incoming phone calls during playback. // Pause MediaPlayer on incoming call, // Resume on hangup. callStateListener(); //ACTION_AUDIO_BECOMING_NOISY -- change in audio outputs -- BroadcastReceiver registerBecomingNoisyReceiver(); //Listen for new Audio to play -- BroadcastReceiver register_playNewAudio(); }
وقتی که نیازی به BroadcastReceiver ها نیست باید آنها را از حالت رجیستری در بیاورید ، این اتفاق در متد onDestroy() مربوط به service انجام میگیرد . متد onDestroy() هم اکنون خود را با کدهای زیر جایگزین کنید . بازم بگم که نگران removeNotification() نباشید ، آنرا بعدا پیاده خواهیم کرد .
@Override public void onDestroy() { super.onDestroy(); if (mediaPlayer != null) { stopMedia(); mediaPlayer.release(); } removeAudioFocus(); //Disable the PhoneStateListener if (phoneStateListener != null) { telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE); } removeNotification(); //unregister BroadcastReceivers unregisterReceiver(becomingNoisyReceiver); unregisterReceiver(playNewAudio); //clear cached playlist new StorageUtil(getApplicationContext()).clearCachedAudioPlaylist(); }
زمانی که Service نابود میشود باید گوش به زنگ بودن برای تماس تلفنی متوقف شود و منابع TelephonyManager را آزاد کند .
یکی از کارهای دیگری که Service موقع نابود شدن انجام میدهد این است که داده های ذخیره شده در SharedPreferences را پاکسازی کند .
ارتباط برقرار کردن با MediaPlayerService یکی از ابزارهای کلیدی اپ موزیک پلیر است زیرا کاربر صرفا فقط نیاز به پخش موزیک ندارد بلکه باید بتواند کنترلی روی اپ داشته باشد .
کار با اپلیکیشن سخت به نظر میرسد چون servic که در پشت زمینه موزیک پخش میکند رابط کاربری ندارد
نسخه ی Lollipop اندروید ابزار جدیدی معرفی کرده است که MediaStyle notifications نام دارد .
Notification.MediaStyle به شما اجازه ی ساختن دکمه های مدیا بدون نیاز به ساختن نوتیفیکشن های اختصاصی را میدهد . در این مثال ما از کتابخانه ی MediaStyles و NotificationCompat.MediaStyle برای ساپورت کردن نسخه های قدیمی اندروید استفاده خواهیم کرد .
برای داشتن دسترسی کامل روی موزیک در حال پخش در MediaPlayerService باید یک instance از روی MediaSession بسازیم . MediaSession اجازه ی تعامل با کنترلرهای مدیا ، کلید های ولوم ، دکمه های مدیا و کنترل انتقال داده ها را میدهد . یک اپ یک instance از نوع MediaSession زمانی که میخواهد اطلاعات پخش یا کنترل کلید های مدیا را داشته باشد .
برای ساختن نوتیفیکشن MediaStyle برای این مثال ، MediaPlayerService از MediaSession استفاده خواهد کرد تا به نوتیفیکشن کنترل کننده ها را اضافه کند و MetaData را منتشر کند بنابراین اندروید متوجه میشود که موزیک در حال پخش شدن است
قبل از انجام ، متغیرهای زیر را در کلاس MediaPlayerService اضافه کنید.
public static final String ACTION_PLAY = "com.valdioveliu.valdio.audioplayer.ACTION_PLAY"; public static final String ACTION_PAUSE = "com.valdioveliu.valdio.audioplayer.ACTION_PAUSE"; public static final String ACTION_PREVIOUS = "com.valdioveliu.valdio.audioplayer.ACTION_PREVIOUS"; public static final String ACTION_NEXT = "com.valdioveliu.valdio.audioplayer.ACTION_NEXT"; public static final String ACTION_STOP = "com.valdioveliu.valdio.audioplayer.ACTION_STOP"; //MediaSession private MediaSessionManager mediaSessionManager; private MediaSessionCompat mediaSession; private MediaControllerCompat.TransportControls transportControls; //AudioPlayer notification ID private static final int NOTIFICATION_ID = 101;
متغیرهای نوع String نشان میدهد که کدام اکشن از MediaSession صادر شده است .
بقیه موارد به MediaSession و شناسه اطلاع رسانی مربوط می شوند تا به طور منحصر به فرد اعلان MediaStyle را شناسایی کنند.
توابع زیر دسته بندی اولیه MediaSession و تنظیم MetaData در یک سشن مربوط به اکشن است .
بخش مهمی از تابع initMediaSession() زیر تنظیم پاسخ های MediaSession برای رسیدگی به حوادث ناشی از دکمه های اعلان است.
توابع زیر را در کلاس MediaPlayerService اضافه کنید.
private void initMediaSession() throws RemoteException { if (mediaSessionManager != null) return; //mediaSessionManager exists mediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE); // Create a new MediaSession mediaSession = new MediaSessionCompat(getApplicationContext(), "AudioPlayer"); //Get MediaSessions transport controls transportControls = mediaSession.getController().getTransportControls(); //set MediaSession -> ready to receive media commands mediaSession.setActive(true); //indicate that the MediaSession handles transport control commands // through its MediaSessionCompat.Callback. mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); //Set mediaSession's MetaData updateMetaData(); // Attach Callback to receive MediaSession updates mediaSession.setCallback(new MediaSessionCompat.Callback() { // Implement callbacks @Override public void onPlay() { super.onPlay(); resumeMedia(); buildNotification(PlaybackStatus.PLAYING); } @Override public void onPause() { super.onPause(); pauseMedia(); buildNotification(PlaybackStatus.PAUSED); } @Override public void onSkipToNext() { super.onSkipToNext(); skipToNext(); updateMetaData(); buildNotification(PlaybackStatus.PLAYING); } @Override public void onSkipToPrevious() { super.onSkipToPrevious(); skipToPrevious(); updateMetaData(); buildNotification(PlaybackStatus.PLAYING); } @Override public void onStop() { super.onStop(); removeNotification(); //Stop the service stopSelf(); } @Override public void onSeekTo(long position) { super.onSeekTo(position); } }); } private void updateMetaData() { Bitmap albumArt = BitmapFactory.decodeResource(getResources(), R.drawable.image); //replace with medias albumArt // Update the current metadata mediaSession.setMetadata(new MediaMetadataCompat.Builder() .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, activeAudio.getArtist()) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, activeAudio.getAlbum()) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, activeAudio.getTitle()) .build()); }
تابع updateMetaData() یک تصویر Bitmap دارد و شما باید آنرا بسازید پس یک تصویر به پوشه ی drawable اضافه کنید .
توابع override شده ی Callback() از توابع اصلی پخش کننده رسانه استفاده میکند که قبلا آموزش داده ایم .
سپس توابع مدیا پلایر که قبلا توضیح دادیم به service اضافه کنید .
private void skipToNext() { if (audioIndex == audioList.size() - 1) { //if last in playlist audioIndex = 0; activeAudio = audioList.get(audioIndex); } else { //get next in playlist activeAudio = audioList.get(++audioIndex); } //Update stored index new StorageUtil(getApplicationContext()).storeAudioIndex(audioIndex); stopMedia(); //reset mediaPlayer mediaPlayer.reset(); initMediaPlayer(); } private void skipToPrevious() { if (audioIndex == 0) { //if first in playlist //set index to the last of audioList audioIndex = audioList.size() - 1; activeAudio = audioList.get(audioIndex); } else { //get previous in playlist activeAudio = audioList.get(--audioIndex); } //Update stored index new StorageUtil(getApplicationContext()).storeAudioIndex(audioIndex); stopMedia(); //reset mediaPlayer mediaPlayer.reset(); initMediaPlayer(); }
حالا service یک روشی نیاز دارد تا بتواند نوتیفیکشن MediaStyle را بسازد اما service به روشی نیاز دارد که وضعیت پخش را بفهمد به همین مظور یک شمارشگر جدید باید بسازید .
در پروژه ی خود کلاس های زیر را بسازید .
public enum PlaybackStatus { PLAYING, PAUSED }
اکنون این service راهی برای پیگیری وضعیت پخش خود ، عملکرد زیر را برای ساخت نوتیفیکیشن ها اضافه کرده است.
private void buildNotification(PlaybackStatus playbackStatus) { int notificationAction = android.R.drawable.ic_media_pause;//needs to be initialized PendingIntent play_pauseAction = null; //Build a new notification according to the current state of the MediaPlayer if (playbackStatus == PlaybackStatus.PLAYING) { notificationAction = android.R.drawable.ic_media_pause; //create the pause action play_pauseAction = playbackAction(1); } else if (playbackStatus == PlaybackStatus.PAUSED) { notificationAction = android.R.drawable.ic_media_play; //create the play action play_pauseAction = playbackAction(0); } Bitmap largeIcon = BitmapFactory.decodeResource(getResources(), R.drawable.image); //replace with your own image // Create a new Notification NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this) .setShowWhen(false) // Set the Notification style .setStyle(new NotificationCompat.MediaStyle() // Attach our MediaSession token .setMediaSession(mediaSession.getSessionToken()) // Show our playback controls in the compact notification view. .setShowActionsInCompactView(0, 1, 2)) // Set the Notification color .setColor(getResources().getColor(R.color.colorPrimary)) // Set the large and small icons .setLargeIcon(largeIcon) .setSmallIcon(android.R.drawable.stat_sys_headset) // Set Notification content information .setContentText(activeAudio.getArtist()) .setContentTitle(activeAudio.getAlbum()) .setContentInfo(activeAudio.getTitle()) // Add playback actions .addAction(android.R.drawable.ic_media_previous, "previous", playbackAction(3)) .addAction(notificationAction, "pause", play_pauseAction) .addAction(android.R.drawable.ic_media_next, "next", playbackAction(2)); ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, notificationBuilder.build()); } private void removeNotification() { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(NOTIFICATION_ID); }
زمانی که این تابع صدا زده میشود یک نوتیفیکشن مطابق با PlaybackStatus ساخته میشود .
هدف اصلی buildNotification() اینست که رابط کاربری نوتیفیکشن را بسازد و همچنین تمام عملیات هایی که کاربر با کلیک روی دکمه های روی نوتیفیکشن انتظار دارد .
شما عملیات های متناسب با PendingIntent را از تابع playbackAction() میسازید .
آنرا به MediaPlayerService اضافه کنید .
private PendingIntent playbackAction(int actionNumber) { Intent playbackAction = new Intent(this, MediaPlayerService.class); switch (actionNumber) { case 0: // Play playbackAction.setAction(ACTION_PLAY); return PendingIntent.getService(this, actionNumber, playbackAction, 0); case 1: // Pause playbackAction.setAction(ACTION_PAUSE); return PendingIntent.getService(this, actionNumber, playbackAction, 0); case 2: // Next track playbackAction.setAction(ACTION_NEXT); return PendingIntent.getService(this, actionNumber, playbackAction, 0); case 3: // Previous track playbackAction.setAction(ACTION_PREVIOUS); return PendingIntent.getService(this, actionNumber, playbackAction, 0); default: break; } return null; }
زمانی که کاربر روی دکمه های روی نوتیفیکشن کلیک میکند باید بتوانید آن عملیات را مدیریت کنید . برای این کار کدهای زیر را به service اضافه کنید .
private void handleIncomingActions(Intent playbackAction) { if (playbackAction == null || playbackAction.getAction() == null) return; String actionString = playbackAction.getAction(); if (actionString.equalsIgnoreCase(ACTION_PLAY)) { transportControls.play(); } else if (actionString.equalsIgnoreCase(ACTION_PAUSE)) { transportControls.pause(); } else if (actionString.equalsIgnoreCase(ACTION_NEXT)) { transportControls.skipToNext(); } else if (actionString.equalsIgnoreCase(ACTION_PREVIOUS)) { transportControls.skipToPrevious(); } else if (actionString.equalsIgnoreCase(ACTION_STOP)) { transportControls.stop(); } }
تابع متوجه میشود که کدام یک از تابع ها درخواست شده است و یکی از توابع MediaSession را اجرا میکند .
متد های callback در تابع initMediaSession پیاده سازی شده اند تا تمام MediaPlayer را مدیریت کنند .
خب داریم کم کم به آخر این آموزش میرسیم و تنها چیزی که باقی مانده است تعریف کردن متد onStartCommand() در service است .
این متد قرار است کار پیاده سازی اولیه MediaSession ، MediaPlayer ، فراخوانی کردن کش پلی لیست آهنگ ها و ساختن نوتیفیکشن MediaStyle را انجام دهد .
در کلاس service تابع قدیمی onStartCommand() را با کدهای زیر جایگزین کنید .
@Override public int onStartCommand(Intent intent, int flags, int startId) { try { //Load data from SharedPreferences StorageUtil storage = new StorageUtil(getApplicationContext()); audioList = storage.loadAudio(); audioIndex = storage.loadAudioIndex(); if (audioIndex != -1 && audioIndex < audioList.size()) { //index is in a valid range activeAudio = audioList.get(audioIndex); } else { stopSelf(); } } catch (NullPointerException e) { stopSelf(); } //Request audio focus if (requestAudioFocus() == false) { //Could not gain focus stopSelf(); } if (mediaSessionManager == null) { try { initMediaSession(); initMediaPlayer(); } catch (RemoteException e) { e.printStackTrace(); stopSelf(); } buildNotification(PlaybackStatus.PLAYING); } //Handle Intent action from MediaSession.TransportControls handleIncomingActions(intent); return super.onStartCommand(intent, flags, startId); }
در تابع initMediaPlayer() صدا زدن setDataSource() را با کد زیر جایگزین کنید .
mediaPlayer.setDataSource(activeAudio.getData())
این جمع بندی برای پخش صوتی در یک service پس زمینه در Android است. اکنون برنامه را اجرا کرده و صوت را به روش صحیح پخش کنید. در زیر مثالی است از اینکه اپ من چطور کار میکنه . من یک RecyclerView به برنامه اضافه کردم و طرح ممکن است متفاوت به نظر برسد ، اما ظاهر اعلان و کنترل ها یکسان هستند.
مقالات دیگر مرتبط با این مقاله ی آموزشی :
دوره های آموزشی مرتبط با این مقاله :
سلام وقت بخیر . در مورد آموزش
متاسفانه این آموزش ناقص می باشد. یا تکمیلش کنید لطفا یا سورس رو بگذارید لطفا.
سریع تر هم جواب بدین . متشکرم
سلام وقت بخیر . این آموزش ناقصه یا تکمیلش کنید یا سورسشو بگذارید لطفا. سریع جواب بدین لطفا
ادامه نداره آیا؟ آگه داره کجاس؟