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.
|
Binaries for Cling are included in the libs folder.
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
|
||||||
|
All icons are taken from the Android project.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[BSD 3-Clause License](LICENSE.md)
|
[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 |
|
@ -51,6 +51,13 @@
|
||||||
android:gravity="right"
|
android:gravity="right"
|
||||||
android:minEms="2" />
|
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
|
<ImageButton
|
||||||
android:id="@+id/previous"
|
android:id="@+id/previous"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -72,6 +79,13 @@
|
||||||
android:contentDescription="@string/next"
|
android:contentDescription="@string/next"
|
||||||
android:layout_toRightOf="@id/playpause" />
|
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
|
<TextView
|
||||||
android:id="@+id/total_time"
|
android:id="@+id/total_time"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="currently_playing_background">#BB99CC00</color>
|
<color name="currently_playing_background">#BB99CC00</color>
|
||||||
|
<color name="button_highlight">#ff33b5e5</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
<string name="exit_renderer">Do you really want to exit the renderer? Playback will be stopped.</string>
|
<string name="exit_renderer">Do you really want to exit the renderer? Playback will be stopped.</string>
|
||||||
<string name="previous">Previous</string>
|
<string name="previous">Previous</string>
|
||||||
<string name="next">Next</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="select_route">Please select a route</string>
|
||||||
<string name="album_art">Album Art</string>
|
<string name="album_art">Album Art</string>
|
||||||
<string name="route_description">DLNA Playback</string>
|
<string name="route_description">DLNA Playback</string>
|
||||||
|
|
|
@ -86,6 +86,8 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
||||||
private View mControls;
|
private View mControls;
|
||||||
private SeekBar mProgressBar;
|
private SeekBar mProgressBar;
|
||||||
private ImageButton mPlayPause;
|
private ImageButton mPlayPause;
|
||||||
|
private ImageButton mShuffle;
|
||||||
|
private ImageButton mRepeat;
|
||||||
|
|
||||||
private View mCurrentTrackView;
|
private View mCurrentTrackView;
|
||||||
|
|
||||||
|
@ -111,6 +113,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
||||||
mMediaRouterPlayService.getService().setRouterFragment(RouteFragment.this);
|
mMediaRouterPlayService.getService().setRouterFragment(RouteFragment.this);
|
||||||
mPlaylistAdapter.addAll(mMediaRouterPlayService.getService().getPlaylist());
|
mPlaylistAdapter.addAll(mMediaRouterPlayService.getService().getPlaylist());
|
||||||
receiveIsPlaying(mMediaRouterPlayService.getService().getCurrentTrack());
|
receiveIsPlaying(mMediaRouterPlayService.getService().getCurrentTrack());
|
||||||
|
applyColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
@ -157,17 +160,25 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
||||||
mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
|
mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
|
||||||
mProgressBar.setOnSeekBarChangeListener(this);
|
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);
|
ImageButton previous = (ImageButton) getView().findViewById(R.id.previous);
|
||||||
previous.setImageResource(R.drawable.ic_media_previous);
|
previous.setImageResource(R.drawable.ic_action_previous);
|
||||||
getView().findViewById(R.id.previous).setOnClickListener(this);
|
previous.setOnClickListener(this);
|
||||||
|
|
||||||
ImageButton next = (ImageButton) getView().findViewById(R.id.next);
|
ImageButton next = (ImageButton) getView().findViewById(R.id.next);
|
||||||
next.setImageResource(R.drawable.ic_media_next);
|
next.setImageResource(R.drawable.ic_action_next);
|
||||||
getView().findViewById(R.id.next).setOnClickListener(this);
|
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 = (ImageButton) getView().findViewById(R.id.playpause);
|
||||||
mPlayPause.setOnClickListener(this);
|
mPlayPause.setOnClickListener(this);
|
||||||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
mPlayPause.setImageResource(R.drawable.ic_action_play);
|
||||||
|
|
||||||
getActivity().getApplicationContext().startService(
|
getActivity().getApplicationContext().startService(
|
||||||
new Intent(getActivity(), MediaRouterPlayService.class));
|
new Intent(getActivity(), MediaRouterPlayService.class));
|
||||||
|
@ -361,22 +372,48 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
MediaRouterPlayService s = mMediaRouterPlayService.getService();
|
||||||
switch (v.getId()) {
|
switch (v.getId()) {
|
||||||
case R.id.playpause:
|
case R.id.playpause:
|
||||||
if (mPlaying)
|
if (mPlaying)
|
||||||
mMediaRouterPlayService.getService().pause();
|
s.pause();
|
||||||
else
|
else
|
||||||
mMediaRouterPlayService.getService().resume();
|
s.resume();
|
||||||
|
break;
|
||||||
|
case R.id.shuffle:
|
||||||
|
s.toggleShuffleEnabled();
|
||||||
|
applyColors();
|
||||||
break;
|
break;
|
||||||
case R.id.previous:
|
case R.id.previous:
|
||||||
mMediaRouterPlayService.getService().playPrevious();
|
s.playPrevious();
|
||||||
break;
|
break;
|
||||||
case R.id.next:
|
case R.id.next:
|
||||||
mMediaRouterPlayService.getService().playNext();
|
s.playNext();
|
||||||
|
break;
|
||||||
|
case R.id.repeat:
|
||||||
|
s.toggleRepeatEnabled();
|
||||||
|
applyColors();
|
||||||
break;
|
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.
|
* 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_BUFFERING ||
|
||||||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
|
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
|
||||||
mPlaying = true;
|
mPlaying = true;
|
||||||
mPlayPause.setImageResource(R.drawable.ic_media_pause);
|
mPlayPause.setImageResource(R.drawable.ic_action_pause);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mPlaying = false;
|
mPlaying = false;
|
||||||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
mPlayPause.setImageResource(R.drawable.ic_action_play);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mListView.getAdapter() == mPlaylistAdapter)
|
if (mListView.getAdapter() == mPlaylistAdapter)
|
||||||
|
|
|
@ -30,6 +30,7 @@ package com.github.nutomic.controldlna.mediarouter;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import org.teleal.cling.support.contentdirectory.DIDLParser;
|
import org.teleal.cling.support.contentdirectory.DIDLParser;
|
||||||
import org.teleal.cling.support.model.DIDLContent;
|
import org.teleal.cling.support.model.DIDLContent;
|
||||||
|
@ -86,6 +87,10 @@ public class MediaRouterPlayService extends Service {
|
||||||
*/
|
*/
|
||||||
private int mCurrentTrack = -1;
|
private int mCurrentTrack = -1;
|
||||||
|
|
||||||
|
private boolean mShuffle = false;
|
||||||
|
|
||||||
|
private boolean mRepeat = false;
|
||||||
|
|
||||||
private String mItemId;
|
private String mItemId;
|
||||||
|
|
||||||
private String mSessionId;
|
private String mSessionId;
|
||||||
|
@ -243,6 +248,9 @@ public class MediaRouterPlayService extends Service {
|
||||||
* Sends 'pause' signal to current renderer.
|
* Sends 'pause' signal to current renderer.
|
||||||
*/
|
*/
|
||||||
public void pause() {
|
public void pause() {
|
||||||
|
if (mPlaylist.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
|
Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
|
||||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||||
|
@ -253,6 +261,9 @@ public class MediaRouterPlayService extends Service {
|
||||||
* Sends 'resume' signal to current renderer.
|
* Sends 'resume' signal to current renderer.
|
||||||
*/
|
*/
|
||||||
public void resume() {
|
public void resume() {
|
||||||
|
if (mPlaylist.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
|
Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
|
||||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||||
|
@ -267,6 +278,9 @@ public class MediaRouterPlayService extends Service {
|
||||||
* Sends 'stop' signal to current renderer.
|
* Sends 'stop' signal to current renderer.
|
||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
if (mPlaylist.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
|
Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
|
||||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||||
|
@ -274,6 +288,9 @@ public class MediaRouterPlayService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seek(int seconds) {
|
public void seek(int seconds) {
|
||||||
|
if (mPlaylist.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
|
Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
|
||||||
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
|
||||||
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
|
||||||
|
@ -296,7 +313,23 @@ public class MediaRouterPlayService extends Service {
|
||||||
* Plays the track after current in the playlist.
|
* Plays the track after current in the playlist.
|
||||||
*/
|
*/
|
||||||
public void playNext() {
|
public void playNext() {
|
||||||
|
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);
|
play(mCurrentTrack + 1);
|
||||||
|
else if (mRepeat)
|
||||||
|
// Playlist over, repeat it.
|
||||||
|
play(0);
|
||||||
|
else if (!mBound) {
|
||||||
|
// Playlist over, stop playback.
|
||||||
|
stopSelf();
|
||||||
|
mPollingStatus = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -304,6 +337,13 @@ public class MediaRouterPlayService extends Service {
|
||||||
* Plays the track before current in the playlist.
|
* Plays the track before current in the playlist.
|
||||||
*/
|
*/
|
||||||
public void playPrevious() {
|
public void playPrevious() {
|
||||||
|
if (mCurrentTrack == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mShuffle)
|
||||||
|
// Play random item.
|
||||||
|
play(new Random().nextInt(mPlaylist.size()));
|
||||||
|
else
|
||||||
play(mCurrentTrack - 1);
|
play(mCurrentTrack - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,15 +379,8 @@ public class MediaRouterPlayService extends Service {
|
||||||
status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_PLAYING)
|
status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_PLAYING)
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
|
|
||||||
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED) {
|
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_FINISHED)
|
||||||
if (mCurrentTrack + 1 < mPlaylist.size())
|
|
||||||
playNext();
|
playNext();
|
||||||
else {
|
|
||||||
if (!mBound)
|
|
||||||
stopSelf();
|
|
||||||
mPollingStatus = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -373,4 +406,20 @@ public class MediaRouterPlayService extends Service {
|
||||||
return mPlaylist;
|
return mPlaylist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void toggleShuffleEnabled() {
|
||||||
|
mShuffle = !mShuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getShuffleEnabled() {
|
||||||
|
return mShuffle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void toggleRepeatEnabled() {
|
||||||
|
mRepeat = !mRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getRepeatEnabled() {
|
||||||
|
return mRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|