OSDN Git Service

c4a9383d6f6c1e07b8562f0bccb908abf020de98
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / ui / fragments / AudioPlayerFragment.java
1 /*
2  * Copyright (C) 2014 Cyanogen, Inc.
3  */
4 package com.cyngn.eleven.ui.fragments;
5
6 import android.app.Activity;
7 import android.content.BroadcastReceiver;
8 import android.content.ComponentName;
9 import android.content.Context;
10 import android.content.Intent;
11 import android.content.IntentFilter;
12 import android.content.ServiceConnection;
13 import android.media.AudioManager;
14 import android.net.Uri;
15 import android.os.Bundle;
16 import android.os.Handler;
17 import android.os.IBinder;
18 import android.os.Message;
19 import android.os.SystemClock;
20 import android.support.v4.app.Fragment;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.widget.ImageView;
25 import android.widget.LinearLayout;
26 import android.widget.SeekBar;
27 import android.widget.TextView;
28
29 import android.provider.MediaStore.Audio.Playlists;
30 import android.provider.MediaStore.Audio.Albums;
31 import android.provider.MediaStore.Audio.Artists;
32 import com.cyngn.eleven.MusicPlaybackService;
33 import com.cyngn.eleven.R;
34 import com.cyngn.eleven.cache.ImageFetcher;
35 import com.cyngn.eleven.utils.ApolloUtils;
36 import com.cyngn.eleven.utils.MusicUtils;
37 import com.cyngn.eleven.utils.NavUtils;
38 import com.cyngn.eleven.widgets.PlayPauseButton;
39 import com.cyngn.eleven.widgets.RepeatButton;
40 import com.cyngn.eleven.widgets.RepeatingImageButton;
41 import com.cyngn.eleven.widgets.ShuffleButton;
42
43 import java.lang.ref.WeakReference;
44
45 import static com.cyngn.eleven.utils.MusicUtils.mService;
46
47 public class AudioPlayerFragment extends Fragment implements ServiceConnection,
48         SeekBar.OnSeekBarChangeListener {
49
50     // fragment view
51     private ViewGroup mRootView;
52
53     // Message to refresh the time
54     private static final int REFRESH_TIME = 1;
55
56     // The service token
57     private MusicUtils.ServiceToken mToken;
58
59     // Play and pause button
60     private PlayPauseButton mPlayPauseButton;
61
62     // Repeat button
63     private RepeatButton mRepeatButton;
64
65     // Shuffle button
66     private ShuffleButton mShuffleButton;
67
68     // Previous button
69     private RepeatingImageButton mPreviousButton;
70
71     // Next button
72     private RepeatingImageButton mNextButton;
73
74     // Track name
75     private TextView mTrackName;
76
77     // Artist name
78     private TextView mArtistName;
79
80     // Album art
81     private ImageView mAlbumArt;
82
83     // Tiny artwork
84     private ImageView mAlbumArtSmall;
85
86     // Current time
87     private TextView mCurrentTime;
88
89     // Total time
90     private TextView mTotalTime;
91
92     // Progess
93     private SeekBar mProgress;
94
95     // Broadcast receiver
96     private PlaybackStatus mPlaybackStatus;
97
98     // Handler used to update the current time
99     private TimeHandler mTimeHandler;
100
101     // Header
102     private LinearLayout mAudioPlayerHeader;
103
104     // Image cache
105     private ImageFetcher mImageFetcher;
106
107     private long mPosOverride = -1;
108
109     private long mStartSeekPos = 0;
110
111     private long mLastSeekEventTime;
112
113     private long mLastShortSeekEventTime;
114
115     private boolean mIsPaused = false;
116
117     private boolean mFromTouch = false;
118
119     @Override
120     public void onActivityCreated(Bundle savedInstanceState) {
121         super.onActivityCreated(savedInstanceState);
122
123         // Control the media volume
124         getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
125
126         // Bind Apollo's service
127         mToken = MusicUtils.bindToService(getActivity(), this);
128
129         // Initialize the image fetcher/cache
130         mImageFetcher = ApolloUtils.getImageFetcher(getActivity());
131
132         // Initialize the handler used to update the current time
133         mTimeHandler = new TimeHandler(this);
134
135         // Initialize the broadcast receiver
136         mPlaybackStatus = new PlaybackStatus(this);
137     }
138
139     /**
140      * {@inheritDoc}
141      */
142     @Override
143     public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
144                              final Bundle savedInstanceState) {
145         // The View for the fragment's UI
146         mRootView = (ViewGroup) inflater.inflate(R.layout.activity_player_fragment, null);
147         initPlaybackControls();
148         return mRootView;
149     }
150
151     /**
152      * {@inheritDoc}
153      */
154     @Override
155     public void onServiceConnected(final ComponentName name, final IBinder service) {
156         // Check whether we were asked to start any playback
157         startPlayback();
158         // Set the playback drawables
159         updatePlaybackControls();
160         // Current info
161         updateNowPlayingInfo();
162         // Update the favorites icon
163         // TODO: Revisit
164         // invalidateOptionsMenu();
165     }
166
167     @Override
168     public void onServiceDisconnected(ComponentName name) {
169     }
170
171     /**
172      * {@inheritDoc}
173      */
174     @Override
175     public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {
176         if (!fromuser || mService == null) {
177             return;
178         }
179         final long now = SystemClock.elapsedRealtime();
180         if (now - mLastSeekEventTime > 250) {
181             mLastSeekEventTime = now;
182             mLastShortSeekEventTime = now;
183             mPosOverride = MusicUtils.duration() * progress / 1000;
184             MusicUtils.seek(mPosOverride);
185             if (!mFromTouch) {
186                 // refreshCurrentTime();
187                 mPosOverride = -1;
188             }
189         } else if (now - mLastShortSeekEventTime > 5) {
190             mLastShortSeekEventTime = now;
191             mPosOverride = MusicUtils.duration() * progress / 1000;
192             refreshCurrentTimeText(mPosOverride);
193         }
194     }
195
196     /**
197      * {@inheritDoc}
198      */
199     @Override
200     public void onStartTrackingTouch(final SeekBar bar) {
201         mLastSeekEventTime = 0;
202         mFromTouch = true;
203         mCurrentTime.setVisibility(View.VISIBLE);
204     }
205
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public void onStopTrackingTouch(final SeekBar bar) {
211         if (mPosOverride != -1) {
212             MusicUtils.seek(mPosOverride);
213         }
214         mPosOverride = -1;
215         mFromTouch = false;
216     }
217
218     public void onDelete(long[] ids) {
219         if (MusicUtils.getQueue().length == 0) {
220             NavUtils.goHome(getActivity());
221         }
222     }
223
224     public void onVisible() {
225         // Set the playback drawables
226         updatePlaybackControls();
227         // Current info
228         updateNowPlayingInfo();
229     }
230
231     public void onHidden() {
232
233     }
234
235     @Override
236     public void onStart() {
237         super.onStart();
238
239         final IntentFilter filter = new IntentFilter();
240         // Play and pause changes
241         filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
242         // Shuffle and repeat changes
243         filter.addAction(MusicPlaybackService.SHUFFLEMODE_CHANGED);
244         filter.addAction(MusicPlaybackService.REPEATMODE_CHANGED);
245         // Track changes
246         filter.addAction(MusicPlaybackService.META_CHANGED);
247         // Update a list, probably the playlist fragment's
248         filter.addAction(MusicPlaybackService.REFRESH);
249         getActivity().registerReceiver(mPlaybackStatus, filter);
250         // Refresh the current time
251         final long next = refreshCurrentTime();
252         queueNextRefresh(next);
253     }
254
255     @Override
256     public void onStop() {
257         super.onStop();
258
259         mImageFetcher.flush();
260     }
261
262     @Override
263     public void onDestroy() {
264         super.onDestroy();
265
266         mIsPaused = false;
267         mTimeHandler.removeMessages(REFRESH_TIME);
268         // Unbind from the service
269         if (mService != null) {
270             MusicUtils.unbindFromService(mToken);
271             mToken = null;
272         }
273
274         // Unregister the receiver
275         try {
276             getActivity().unregisterReceiver(mPlaybackStatus);
277         } catch (final Throwable e) {
278             //$FALL-THROUGH$
279         }
280     }
281
282     /**
283      * Initializes the items in the now playing screen
284      */
285     @SuppressWarnings("deprecation")
286     private void initPlaybackControls() {
287         // Now playing header
288         mAudioPlayerHeader = (LinearLayout)mRootView.findViewById(R.id.audio_player_header);
289         // Opens the currently playing album profile
290         mAudioPlayerHeader.setOnClickListener(mOpenAlbumProfile);
291
292         // Play and pause button
293         mPlayPauseButton = (PlayPauseButton)mRootView.findViewById(R.id.action_button_play);
294         // Shuffle button
295         mShuffleButton = (ShuffleButton)mRootView.findViewById(R.id.action_button_shuffle);
296         // Repeat button
297         mRepeatButton = (RepeatButton)mRootView.findViewById(R.id.action_button_repeat);
298         // Previous button
299         mPreviousButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_previous);
300         // Next button
301         mNextButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_next);
302         // Track name
303         mTrackName = (TextView)mRootView.findViewById(R.id.audio_player_track_name);
304         // Artist name
305         mArtistName = (TextView)mRootView.findViewById(R.id.audio_player_artist_name);
306         // Album art
307         mAlbumArt = (ImageView)mRootView.findViewById(R.id.audio_player_album_art);
308         // Small album art
309         mAlbumArtSmall = (ImageView)mRootView.findViewById(R.id.audio_player_switch_album_art);
310         // Current time
311         mCurrentTime = (TextView)mRootView.findViewById(R.id.audio_player_current_time);
312         // Total time
313         mTotalTime = (TextView)mRootView.findViewById(R.id.audio_player_total_time);
314         // Progress
315         mProgress = (SeekBar)mRootView.findViewById(android.R.id.progress);
316
317         // Set the repeat listner for the previous button
318         mPreviousButton.setRepeatListener(mRewindListener);
319         // Set the repeat listner for the next button
320         mNextButton.setRepeatListener(mFastForwardListener);
321         // Update the progress
322         mProgress.setOnSeekBarChangeListener(this);
323     }
324
325     /**
326      * Sets the track name, album name, and album art.
327      */
328     private void updateNowPlayingInfo() {
329         // Set the track name
330         mTrackName.setText(MusicUtils.getTrackName());
331         // Set the artist name
332         mArtistName.setText(MusicUtils.getArtistName());
333         // Set the total time
334         mTotalTime.setText(MusicUtils.makeTimeString(getActivity(), MusicUtils.duration() / 1000));
335         // Set the album art
336         mImageFetcher.loadCurrentArtwork(mAlbumArt);
337         // Set the small artwork
338         mImageFetcher.loadCurrentArtwork(mAlbumArtSmall);
339         // Update the current time
340         queueNextRefresh(1);
341
342     }
343
344     private long parseIdFromIntent(Intent intent, String longKey,
345                                    String stringKey, long defaultId) {
346         long id = intent.getLongExtra(longKey, -1);
347         if (id < 0) {
348             String idString = intent.getStringExtra(stringKey);
349             if (idString != null) {
350                 try {
351                     id = Long.parseLong(idString);
352                 } catch (NumberFormatException e) {
353                     // ignore
354                 }
355             }
356         }
357         return id;
358     }
359
360     /**
361      * Checks whether the passed intent contains a playback request,
362      * and starts playback if that's the case
363      * @return true if the intent was consumed
364      */
365     public boolean startPlayback() {
366         Intent intent = getActivity().getIntent();
367
368         if (intent == null || mService == null || getActivity() == null) {
369             return false;
370         }
371
372         Uri uri = intent.getData();
373         String mimeType = intent.getType();
374         boolean handled = false;
375
376         if (uri != null && uri.toString().length() > 0) {
377             MusicUtils.playFile(getActivity(), uri);
378             handled = true;
379         } else if (Playlists.CONTENT_TYPE.equals(mimeType)) {
380             long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
381             if (id >= 0) {
382                 MusicUtils.playPlaylist(getActivity(), id);
383                 handled = true;
384             }
385         } else if (Albums.CONTENT_TYPE.equals(mimeType)) {
386             long id = parseIdFromIntent(intent, "albumId", "album", -1);
387             if (id >= 0) {
388                 int position = intent.getIntExtra("position", 0);
389                 MusicUtils.playAlbum(getActivity(), id, position);
390                 handled = true;
391             }
392         } else if (Artists.CONTENT_TYPE.equals(mimeType)) {
393             long id = parseIdFromIntent(intent, "artistId", "artist", -1);
394             if (id >= 0) {
395                 int position = intent.getIntExtra("position", 0);
396                 MusicUtils.playArtist(getActivity(), id, position);
397                 handled = true;
398             }
399         }
400
401         if (handled) {
402             // Make sure to process intent only once
403             getActivity().setIntent(new Intent());
404             // Refresh the queue
405             // TODO: Refresh queue or have it self-aware
406             return true;
407         }
408
409         return false;
410     }
411
412     /**
413      * Sets the correct drawable states for the playback controls.
414      */
415     private void updatePlaybackControls() {
416         // Set the play and pause image
417         mPlayPauseButton.updateState();
418         // Set the shuffle image
419         mShuffleButton.updateShuffleState();
420         // Set the repeat image
421         mRepeatButton.updateRepeatState();
422     }
423
424     /**
425      * @param delay When to update
426      */
427     private void queueNextRefresh(final long delay) {
428         if (!mIsPaused) {
429             final Message message = mTimeHandler.obtainMessage(REFRESH_TIME);
430             mTimeHandler.removeMessages(REFRESH_TIME);
431             mTimeHandler.sendMessageDelayed(message, delay);
432         }
433     }
434
435     /**
436      * Used to scan backwards in time through the curren track
437      *
438      * @param repcnt The repeat count
439      * @param delta The long press duration
440      */
441     private void scanBackward(final int repcnt, long delta) {
442         if (mService == null) {
443             return;
444         }
445         if (repcnt == 0) {
446             mStartSeekPos = MusicUtils.position();
447             mLastSeekEventTime = 0;
448         } else {
449             if (delta < 5000) {
450                 // seek at 10x speed for the first 5 seconds
451                 delta = delta * 10;
452             } else {
453                 // seek at 40x after that
454                 delta = 50000 + (delta - 5000) * 40;
455             }
456             long newpos = mStartSeekPos - delta;
457             if (newpos < 0) {
458                 // move to previous track
459                 MusicUtils.previous(getActivity());
460                 final long duration = MusicUtils.duration();
461                 mStartSeekPos += duration;
462                 newpos += duration;
463             }
464             if (delta - mLastSeekEventTime > 250 || repcnt < 0) {
465                 MusicUtils.seek(newpos);
466                 mLastSeekEventTime = delta;
467             }
468             if (repcnt >= 0) {
469                 mPosOverride = newpos;
470             } else {
471                 mPosOverride = -1;
472             }
473             refreshCurrentTime();
474         }
475     }
476
477     /**
478      * Used to scan forwards in time through the curren track
479      *
480      * @param repcnt The repeat count
481      * @param delta The long press duration
482      */
483     private void scanForward(final int repcnt, long delta) {
484         if (mService == null) {
485             return;
486         }
487         if (repcnt == 0) {
488             mStartSeekPos = MusicUtils.position();
489             mLastSeekEventTime = 0;
490         } else {
491             if (delta < 5000) {
492                 // seek at 10x speed for the first 5 seconds
493                 delta = delta * 10;
494             } else {
495                 // seek at 40x after that
496                 delta = 50000 + (delta - 5000) * 40;
497             }
498             long newpos = mStartSeekPos + delta;
499             final long duration = MusicUtils.duration();
500             if (newpos >= duration) {
501                 // move to next track
502                 MusicUtils.next();
503                 mStartSeekPos -= duration; // is OK to go negative
504                 newpos -= duration;
505             }
506             if (delta - mLastSeekEventTime > 250 || repcnt < 0) {
507                 MusicUtils.seek(newpos);
508                 mLastSeekEventTime = delta;
509             }
510             if (repcnt >= 0) {
511                 mPosOverride = newpos;
512             } else {
513                 mPosOverride = -1;
514             }
515             refreshCurrentTime();
516         }
517     }
518
519     private void refreshCurrentTimeText(final long pos) {
520         mCurrentTime.setText(MusicUtils.makeTimeString(getActivity(), pos / 1000));
521     }
522
523     /* Used to update the current time string */
524     private long refreshCurrentTime() {
525         if (mService == null) {
526             return 500;
527         }
528         try {
529             final long pos = mPosOverride < 0 ? MusicUtils.position() : mPosOverride;
530             if (pos >= 0 && MusicUtils.duration() > 0) {
531                 refreshCurrentTimeText(pos);
532                 final int progress = (int)(1000 * pos / MusicUtils.duration());
533                 mProgress.setProgress(progress);
534
535                 if (mFromTouch) {
536                     return 500;
537                 } else if (MusicUtils.isPlaying()) {
538                     mCurrentTime.setVisibility(View.VISIBLE);
539                 } else {
540                     // blink the counter
541                     final int vis = mCurrentTime.getVisibility();
542                     mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE
543                             : View.INVISIBLE);
544                     return 500;
545                 }
546             } else {
547                 mCurrentTime.setText("--:--");
548                 mProgress.setProgress(1000);
549             }
550             // calculate the number of milliseconds until the next full second,
551             // so
552             // the counter can be updated at just the right time
553             final long remaining = 1000 - pos % 1000;
554             // approximate how often we would need to refresh the slider to
555             // move it smoothly
556             int width = mProgress.getWidth();
557             if (width == 0) {
558                 width = 320;
559             }
560             final long smoothrefreshtime = MusicUtils.duration() / width;
561             if (smoothrefreshtime > remaining) {
562                 return remaining;
563             }
564             if (smoothrefreshtime < 20) {
565                 return 20;
566             }
567             return smoothrefreshtime;
568         } catch (final Exception ignored) {
569
570         }
571         return 500;
572     }
573
574     /**
575      * /** Used to shared what the user is currently listening to
576      */
577     private void shareCurrentTrack() {
578         if (MusicUtils.getTrackName() == null || MusicUtils.getArtistName() == null) {
579             return;
580         }
581         final Intent shareIntent = new Intent();
582         final String shareMessage = getString(R.string.now_listening_to,
583                 MusicUtils.getTrackName(), MusicUtils.getArtistName());
584
585         shareIntent.setAction(Intent.ACTION_SEND);
586         shareIntent.setType("text/plain");
587         shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage);
588         startActivity(Intent.createChooser(shareIntent, getString(R.string.share_track_using)));
589     }
590
591     /**
592      * Used to scan backwards through the track
593      */
594     private final RepeatingImageButton.RepeatListener mRewindListener = new RepeatingImageButton.RepeatListener() {
595         /**
596          * {@inheritDoc}
597          */
598         @Override
599         public void onRepeat(final View v, final long howlong, final int repcnt) {
600             scanBackward(repcnt, howlong);
601         }
602     };
603
604     /**
605      * Used to scan ahead through the track
606      */
607     private final RepeatingImageButton.RepeatListener mFastForwardListener = new RepeatingImageButton.RepeatListener() {
608         /**
609          * {@inheritDoc}
610          */
611         @Override
612         public void onRepeat(final View v, final long howlong, final int repcnt) {
613             scanForward(repcnt, howlong);
614         }
615     };
616
617     /**
618      * Opens to the current album profile
619      */
620     private final View.OnClickListener mOpenAlbumProfile = new View.OnClickListener() {
621
622         @Override
623         public void onClick(final View v) {
624             NavUtils.openAlbumProfile(getActivity(), MusicUtils.getAlbumName(),
625                     MusicUtils.getArtistName(), MusicUtils.getCurrentAlbumId());
626         }
627     };
628
629     /**
630      * Used to update the current time string
631      */
632     private static final class TimeHandler extends Handler {
633
634         private final WeakReference<AudioPlayerFragment> mAudioPlayer;
635
636         /**
637          * Constructor of <code>TimeHandler</code>
638          */
639         public TimeHandler(final AudioPlayerFragment player) {
640             mAudioPlayer = new WeakReference<AudioPlayerFragment>(player);
641         }
642
643         @Override
644         public void handleMessage(final Message msg) {
645             switch (msg.what) {
646                 case REFRESH_TIME:
647                     final long next = mAudioPlayer.get().refreshCurrentTime();
648                     mAudioPlayer.get().queueNextRefresh(next);
649                     break;
650                 default:
651                     break;
652             }
653         }
654     };
655
656     /**
657      * Used to monitor the state of playback
658      */
659     private static final class PlaybackStatus extends BroadcastReceiver {
660
661         private final WeakReference<AudioPlayerFragment> mReference;
662
663         /**
664          * Constructor of <code>PlaybackStatus</code>
665          */
666         public PlaybackStatus(final AudioPlayerFragment fragment) {
667             mReference = new WeakReference<AudioPlayerFragment>(fragment);
668         }
669
670         /**
671          * {@inheritDoc}
672          */
673         @Override
674         public void onReceive(final Context context, final Intent intent) {
675             final String action = intent.getAction();
676             if (action.equals(MusicPlaybackService.META_CHANGED)) {
677                 // Current info
678                 mReference.get().updateNowPlayingInfo();
679                 // Update the favorites icon
680                 // TODO: Revist
681                 // mReference.get().invalidateOptionsMenu();
682             } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
683                 // Set the play and pause image
684                 mReference.get().mPlayPauseButton.updateState();
685             } else if (action.equals(MusicPlaybackService.REPEATMODE_CHANGED)
686                     || action.equals(MusicPlaybackService.SHUFFLEMODE_CHANGED)) {
687                 // Set the repeat image
688                 mReference.get().mRepeatButton.updateRepeatState();
689                 // Set the shuffle image
690                 mReference.get().mShuffleButton.updateShuffleState();
691             }
692         }
693     }
694 }