Added shuffle and repeat functionality.
|
@ -16,6 +16,10 @@ Android Support Library and [Cling](http://4thline.org/projects/cling/) are requ
|
|||
|
||||
Binaries for Cling are included in the libs folder.
|
||||
|
||||
## Icons
|
||||
|
||||
All icons are taken from the Android project.
|
||||
|
||||
## License
|
||||
|
||||
[BSD 3-Clause License](LICENSE.md)
|
BIN
res/drawable-hdpi/ic_action_next.png
Normal file
After Width: | Height: | Size: 455 B |
BIN
res/drawable-hdpi/ic_action_pause.png
Normal file
After Width: | Height: | Size: 185 B |
BIN
res/drawable-hdpi/ic_action_play.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
res/drawable-hdpi/ic_action_previous.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
res/drawable-hdpi/ic_action_repeat.png
Normal file
After Width: | Height: | Size: 608 B |
BIN
res/drawable-hdpi/ic_action_shuffle.png
Normal file
After Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB |
BIN
res/drawable-mdpi/ic_action_next.png
Normal file
After Width: | Height: | Size: 334 B |
BIN
res/drawable-mdpi/ic_action_pause.png
Normal file
After Width: | Height: | Size: 181 B |
BIN
res/drawable-mdpi/ic_action_play.png
Normal file
After Width: | Height: | Size: 339 B |
BIN
res/drawable-mdpi/ic_action_previous.png
Normal file
After Width: | Height: | Size: 337 B |
BIN
res/drawable-mdpi/ic_action_repeat.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
res/drawable-mdpi/ic_action_shuffle.png
Normal file
After Width: | Height: | Size: 482 B |
Before Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 897 B |
Before Width: | Height: | Size: 837 B |
BIN
res/drawable-xhdpi/ic_action_next.png
Normal file
After Width: | Height: | Size: 510 B |
BIN
res/drawable-xhdpi/ic_action_pause.png
Normal file
After Width: | Height: | Size: 232 B |
BIN
res/drawable-xhdpi/ic_action_play.png
Normal file
After Width: | Height: | Size: 522 B |
BIN
res/drawable-xhdpi/ic_action_previous.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
res/drawable-xhdpi/ic_action_repeat.png
Normal file
After Width: | Height: | Size: 774 B |
BIN
res/drawable-xhdpi/ic_action_shuffle.png
Normal file
After Width: | Height: | Size: 853 B |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 685 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-xxhdpi/ic_action_next.png
Normal file
After Width: | Height: | Size: 692 B |
BIN
res/drawable-xxhdpi/ic_action_pause.png
Normal file
After Width: | Height: | Size: 297 B |
BIN
res/drawable-xxhdpi/ic_action_play.png
Normal file
After Width: | Height: | Size: 669 B |
BIN
res/drawable-xxhdpi/ic_action_previous.png
Normal file
After Width: | Height: | Size: 714 B |
BIN
res/drawable-xxhdpi/ic_action_repeat.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-xxhdpi/ic_action_shuffle.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -50,6 +50,13 @@
|
|||
android:layout_alignParentTop="true"
|
||||
android:gravity="right"
|
||||
android:minEms="2" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shuffle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/shuffle"
|
||||
android:layout_toLeftOf="@+id/previous" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/previous"
|
||||
|
@ -71,6 +78,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/next"
|
||||
android:layout_toRightOf="@id/playpause" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/repeat"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/repeat"
|
||||
android:layout_toRightOf="@id/next" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/total_time"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="currently_playing_background">#BB99CC00</color>
|
||||
<color name="button_highlight">#ff33b5e5</color>
|
||||
</resources>
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
<string name="exit_renderer">Do you really want to exit the renderer? Playback will be stopped.</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="shuffle">Shuffle</string>
|
||||
<string name="repeat">Repeat</string>
|
||||
<string name="select_route">Please select a route</string>
|
||||
<string name="album_art">Album Art</string>
|
||||
<string name="route_description">DLNA Playback</string>
|
||||
|
|
|
@ -86,6 +86,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
|||
private View mControls;
|
||||
private SeekBar mProgressBar;
|
||||
private ImageButton mPlayPause;
|
||||
private ImageButton mShuffle;
|
||||
private ImageButton mRepeat;
|
||||
|
||||
private View mCurrentTrackView;
|
||||
|
||||
|
@ -111,6 +113,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
|||
mMediaRouterPlayService.getService().setRouterFragment(RouteFragment.this);
|
||||
mPlaylistAdapter.addAll(mMediaRouterPlayService.getService().getPlaylist());
|
||||
receiveIsPlaying(mMediaRouterPlayService.getService().getCurrentTrack());
|
||||
applyColors();
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
|
@ -157,17 +160,25 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
|||
mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
|
||||
mProgressBar.setOnSeekBarChangeListener(this);
|
||||
|
||||
mShuffle = (ImageButton) getView().findViewById(R.id.shuffle);
|
||||
mShuffle.setImageResource(R.drawable.ic_action_shuffle);
|
||||
mShuffle.setOnClickListener(this);
|
||||
|
||||
ImageButton previous = (ImageButton) getView().findViewById(R.id.previous);
|
||||
previous.setImageResource(R.drawable.ic_media_previous);
|
||||
getView().findViewById(R.id.previous).setOnClickListener(this);
|
||||
previous.setImageResource(R.drawable.ic_action_previous);
|
||||
previous.setOnClickListener(this);
|
||||
|
||||
ImageButton next = (ImageButton) getView().findViewById(R.id.next);
|
||||
next.setImageResource(R.drawable.ic_media_next);
|
||||
getView().findViewById(R.id.next).setOnClickListener(this);
|
||||
next.setImageResource(R.drawable.ic_action_next);
|
||||
next.setOnClickListener(this);
|
||||
|
||||
mRepeat = (ImageButton) getView().findViewById(R.id.repeat);
|
||||
mRepeat.setImageResource(R.drawable.ic_action_repeat);
|
||||
mRepeat.setOnClickListener(this);
|
||||
|
||||
mPlayPause = (ImageButton) getView().findViewById(R.id.playpause);
|
||||
mPlayPause.setOnClickListener(this);
|
||||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
||||
mPlayPause.setImageResource(R.drawable.ic_action_play);
|
||||
|
||||
getActivity().getApplicationContext().startService(
|
||||
new Intent(getActivity(), MediaRouterPlayService.class));
|
||||
|
@ -361,21 +372,47 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
|||
*/
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
MediaRouterPlayService s = mMediaRouterPlayService.getService();
|
||||
switch (v.getId()) {
|
||||
case R.id.playpause:
|
||||
if (mPlaying)
|
||||
mMediaRouterPlayService.getService().pause();
|
||||
s.pause();
|
||||
else
|
||||
mMediaRouterPlayService.getService().resume();
|
||||
s.resume();
|
||||
break;
|
||||
case R.id.shuffle:
|
||||
s.toggleShuffleEnabled();
|
||||
applyColors();
|
||||
break;
|
||||
case R.id.previous:
|
||||
mMediaRouterPlayService.getService().playPrevious();
|
||||
s.playPrevious();
|
||||
break;
|
||||
case R.id.next:
|
||||
mMediaRouterPlayService.getService().playNext();
|
||||
s.playNext();
|
||||
break;
|
||||
case R.id.repeat:
|
||||
s.toggleRepeatEnabled();
|
||||
applyColors();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables highlighting on shuffle/repeat buttons (depending
|
||||
* if they are enabled or disabled).
|
||||
*/
|
||||
private void applyColors() {
|
||||
MediaRouterPlayService s = mMediaRouterPlayService.getService();
|
||||
int highlight = getResources().getColor(R.color.button_highlight);
|
||||
int transparent = getResources().getColor(android.R.color.transparent);
|
||||
|
||||
mShuffle.setColorFilter((s.getShuffleEnabled())
|
||||
? highlight
|
||||
: transparent);
|
||||
mRepeat.setColorFilter((s.getRepeatEnabled())
|
||||
? highlight
|
||||
: transparent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends manual seek on progress bar to renderer.
|
||||
|
@ -484,11 +521,11 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
|||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_BUFFERING ||
|
||||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
|
||||
mPlaying = true;
|
||||
mPlayPause.setImageResource(R.drawable.ic_media_pause);
|
||||
mPlayPause.setImageResource(R.drawable.ic_action_pause);
|
||||
}
|
||||
else {
|
||||
mPlaying = false;
|
||||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
||||
mPlayPause.setImageResource(R.drawable.ic_action_play);
|
||||
}
|
||||
|
||||
if (mListView.getAdapter() == mPlaylistAdapter)
|
||||
|
|
|
@ -30,6 +30,7 @@ package com.github.nutomic.controldlna.mediarouter;
|
|||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.teleal.cling.support.contentdirectory.DIDLParser;
|
||||
import org.teleal.cling.support.model.DIDLContent;
|
||||
|
@ -86,6 +87,10 @@ public class MediaRouterPlayService extends Service {
|
|||
*/
|
||||
private int mCurrentTrack = -1;
|
||||
|
||||
private boolean mShuffle = false;
|
||||
|
||||
private boolean mRepeat = false;
|
||||
|
||||
private String mItemId;
|
||||
|
||||
private String mSessionId;
|
||||
|
@ -243,6 +248,9 @@ public class MediaRouterPlayService extends Service {
|
|||
* Sends 'pause' signal to current renderer.
|
||||
*/
|
||||
public void pause() {
|
||||
if (mPlaylist.isEmpty())
|
||||
return;
|
||||
|
||||
Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
|
||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||
|
@ -253,6 +261,9 @@ public class MediaRouterPlayService extends Service {
|
|||
* Sends 'resume' signal to current renderer.
|
||||
*/
|
||||
public void resume() {
|
||||
if (mPlaylist.isEmpty())
|
||||
return;
|
||||
|
||||
Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
|
||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||
|
@ -267,6 +278,9 @@ public class MediaRouterPlayService extends Service {
|
|||
* Sends 'stop' signal to current renderer.
|
||||
*/
|
||||
public void stop() {
|
||||
if (mPlaylist.isEmpty())
|
||||
return;
|
||||
|
||||
Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
|
||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||
|
@ -274,6 +288,9 @@ public class MediaRouterPlayService extends Service {
|
|||
}
|
||||
|
||||
public void seek(int seconds) {
|
||||
if (mPlaylist.isEmpty())
|
||||
return;
|
||||
|
||||
Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
|
||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||
|
@ -296,7 +313,23 @@ public class MediaRouterPlayService extends Service {
|
|||
* Plays the track after current in the playlist.
|
||||
*/
|
||||
public void playNext() {
|
||||
play(mCurrentTrack + 1);
|
||||
if (mCurrentTrack == -1)
|
||||
return;
|
||||
|
||||
if (mShuffle)
|
||||
// Play random item.
|
||||
play(new Random().nextInt(mPlaylist.size()));
|
||||
else if (mCurrentTrack + 1 < mPlaylist.size())
|
||||
// Playlist not over, play next item.
|
||||
play(mCurrentTrack + 1);
|
||||
else if (mRepeat)
|
||||
// Playlist over, repeat it.
|
||||
play(0);
|
||||
else if (!mBound) {
|
||||
// Playlist over, stop playback.
|
||||
stopSelf();
|
||||
mPollingStatus = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -304,7 +337,14 @@ public class MediaRouterPlayService extends Service {
|
|||
* Plays the track before current in the playlist.
|
||||
*/
|
||||
public void playPrevious() {
|
||||
play(mCurrentTrack - 1);
|
||||
if (mCurrentTrack == -1)
|
||||
return;
|
||||
|
||||
if (mShuffle)
|
||||
// Play random item.
|
||||
play(new Random().nextInt(mPlaylist.size()));
|
||||
else
|
||||
play(mCurrentTrack - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,15 +379,8 @@ public class MediaRouterPlayService extends Service {
|
|||
status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_PLAYING)
|
||||
stopForeground(true);
|
||||
|
||||
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED) {
|
||||
if (mCurrentTrack + 1 < mPlaylist.size())
|
||||
playNext();
|
||||
else {
|
||||
if (!mBound)
|
||||
stopSelf();
|
||||
mPollingStatus = false;
|
||||
}
|
||||
}
|
||||
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED)
|
||||
playNext();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -372,5 +405,21 @@ public class MediaRouterPlayService extends Service {
|
|||
public List<Item> getPlaylist() {
|
||||
return mPlaylist;
|
||||
}
|
||||
|
||||
public void toggleShuffleEnabled() {
|
||||
mShuffle = !mShuffle;
|
||||
}
|
||||
|
||||
public boolean getShuffleEnabled() {
|
||||
return mShuffle;
|
||||
}
|
||||
|
||||
public void toggleRepeatEnabled() {
|
||||
mRepeat = !mRepeat;
|
||||
}
|
||||
|
||||
public boolean getRepeatEnabled() {
|
||||
return mRepeat;
|
||||
}
|
||||
|
||||
}
|
||||
|
|