Removed trailing whitespaces, replaced space intendation with tabs.

This commit is contained in:
Felix Ableitner 2014-02-08 21:16:30 +01:00
parent 10e5f86ecf
commit 2314e895d1
14 changed files with 1428 additions and 1437 deletions

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -61,66 +61,66 @@ import com.github.nutomic.controldlna.R;
* *
*/ */
public class MainActivity extends ActionBarActivity { public class MainActivity extends ActionBarActivity {
/** /**
* Interface which allows listening to "back" button presses. * Interface which allows listening to "back" button presses.
*/ */
public interface OnBackPressedListener { public interface OnBackPressedListener {
boolean onBackPressed(); boolean onBackPressed();
} }
FragmentStatePagerAdapter mSectionsPagerAdapter =
new FragmentStatePagerAdapter(getSupportFragmentManager()) {
@Override FragmentStatePagerAdapter mSectionsPagerAdapter =
public Fragment getItem(int position) { new FragmentStatePagerAdapter(getSupportFragmentManager()) {
switch (position) {
case 0: return mServerFragment;
case 1: return mRouteFragment;
default: return null;
}
}
@Override @Override
public int getCount() { public Fragment getItem(int position) {
return 2; switch (position) {
} case 0: return mServerFragment;
case 1: return mRouteFragment;
}; default: return null;
}
}
private ServerFragment mServerFragment; @Override
public int getCount() {
private RouteFragment mRouteFragment; return 2;
}
ViewPager mViewPager;
/** };
* Initializes tab navigation. If wifi is not connected,
* shows a warning dialog.
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); private ServerFragment mServerFragment;
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
TabListener tabListener = new ActionBar.TabListener() { private RouteFragment mRouteFragment;
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition()); ViewPager mViewPager;
}
/**
* Initializes tab navigation. If wifi is not connected,
* shows a warning dialog.
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActionBar actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
});
TabListener tabListener = new ActionBar.TabListener() {
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
mViewPager.setCurrentItem(tab.getPosition());
}
@Override @Override
public void onTabReselected(Tab arg0, FragmentTransaction arg1) { public void onTabReselected(Tab arg0, FragmentTransaction arg1) {
@ -129,112 +129,112 @@ public class MainActivity extends ActionBarActivity {
@Override @Override
public void onTabUnselected(Tab arg0, FragmentTransaction arg1) { public void onTabUnselected(Tab arg0, FragmentTransaction arg1) {
} }
}; };
actionBar.addTab(actionBar.newTab() actionBar.addTab(actionBar.newTab()
.setText(R.string.title_server) .setText(R.string.title_server)
.setTabListener(tabListener)); .setTabListener(tabListener));
actionBar.addTab(actionBar.newTab() actionBar.addTab(actionBar.newTab()
.setText(R.string.title_route) .setText(R.string.title_route)
.setTabListener(tabListener)); .setTabListener(tabListener));
ConnectivityManager connManager = (ConnectivityManager)
getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
final SharedPreferences prefs = getSharedPreferences("preferences.db", 0);
if (!wifi.isConnected() && !prefs.getBoolean("wifi_skip_dialog", false)) { ConnectivityManager connManager = (ConnectivityManager)
View checkBoxView = View.inflate(this, R.layout.dialog_wifi_disabled, null); getSystemService(CONNECTIVITY_SERVICE);
CheckBox checkBox = (CheckBox) checkBoxView.findViewById(R.id.dont_show_again); NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { final SharedPreferences prefs = getSharedPreferences("preferences.db", 0);
@Override if (!wifi.isConnected() && !prefs.getBoolean("wifi_skip_dialog", false)) {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { View checkBoxView = View.inflate(this, R.layout.dialog_wifi_disabled, null);
prefs.edit().putBoolean("wifi_skip_dialog", isChecked) CheckBox checkBox = (CheckBox) checkBoxView.findViewById(R.id.dont_show_again);
.commit(); checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
}
}); @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
new AlertDialog.Builder(this) prefs.edit().putBoolean("wifi_skip_dialog", isChecked)
.setView(checkBoxView) .commit();
.setTitle(R.string.warning_wifi_not_connected) }
.setPositiveButton(android.R.string.ok, null) });
.show();
} new AlertDialog.Builder(this)
.setView(checkBoxView)
if (savedInstanceState != null) { .setTitle(R.string.warning_wifi_not_connected)
FragmentManager fm = getSupportFragmentManager(); .setPositiveButton(android.R.string.ok, null)
mServerFragment = (ServerFragment) fm.getFragment( .show();
savedInstanceState, ServerFragment.class.getName()); }
mRouteFragment = (RouteFragment) fm.getFragment(
savedInstanceState, RouteFragment.class.getName()); if (savedInstanceState != null) {
mViewPager.setCurrentItem(savedInstanceState.getInt("currentTab")); FragmentManager fm = getSupportFragmentManager();
} mServerFragment = (ServerFragment) fm.getFragment(
else { savedInstanceState, ServerFragment.class.getName());
mServerFragment = new ServerFragment(); mRouteFragment = (RouteFragment) fm.getFragment(
mRouteFragment = new RouteFragment(); savedInstanceState, RouteFragment.class.getName());
} mViewPager.setCurrentItem(savedInstanceState.getInt("currentTab"));
onNewIntent(getIntent()); }
} else {
mServerFragment = new ServerFragment();
/** mRouteFragment = new RouteFragment();
* Displays the RouteFragment immediately (instead of ServerFragment). }
*/ onNewIntent(getIntent());
}
/**
* Displays the RouteFragment immediately (instead of ServerFragment).
*/
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
if (intent.getAction().equals("showRouteFragment")) if (intent.getAction().equals("showRouteFragment"))
mViewPager.setCurrentItem(1); mViewPager.setCurrentItem(1);
} }
/** /**
* Saves fragments. * Saves fragments.
*/ */
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
fm.putFragment(outState, ServerFragment.class.getName(), mServerFragment); fm.putFragment(outState, ServerFragment.class.getName(), mServerFragment);
fm.putFragment(outState, RouteFragment.class.getName(), mRouteFragment); fm.putFragment(outState, RouteFragment.class.getName(), mRouteFragment);
outState.putInt("currentTab", mViewPager.getCurrentItem()); outState.putInt("currentTab", mViewPager.getCurrentItem());
} }
/**
* Forwards back press to active Fragment (unless the fragment is
* showing its root view).
*/
@Override
public void onBackPressed() {
OnBackPressedListener currentFragment = (OnBackPressedListener)
mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
if (!currentFragment.onBackPressed())
super.onBackPressed();
}
/**
* Changes volume on key press (via RouteFragment).
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRouteFragment.increaseVolume();
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRouteFragment.decreaseVolume();
return true;
default:
return super.dispatchKeyEvent(event);
}
}
/** /**
* Starts playing the playlist from item start (via RouteFragment). * Forwards back press to active Fragment (unless the fragment is
*/ * showing its root view).
*/
@Override
public void onBackPressed() {
OnBackPressedListener currentFragment = (OnBackPressedListener)
mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
if (!currentFragment.onBackPressed())
super.onBackPressed();
}
/**
* Changes volume on key press (via RouteFragment).
*/
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRouteFragment.increaseVolume();
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (event.getAction() == KeyEvent.ACTION_DOWN)
mRouteFragment.decreaseVolume();
return true;
default:
return super.dispatchKeyEvent(event);
}
}
/**
* Starts playing the playlist from item start (via RouteFragment).
*/
public void play(List<Item> playlist, int start) { public void play(List<Item> playlist, int start) {
mViewPager.setCurrentItem(1); mViewPager.setCurrentItem(1);
mRouteFragment.play(playlist, start); mRouteFragment.play(playlist, start);
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -73,18 +73,18 @@ import com.github.nutomic.controldlna.utility.FileArrayAdapter;
import com.github.nutomic.controldlna.utility.RouteAdapter; import com.github.nutomic.controldlna.utility.RouteAdapter;
/** /**
* Controls media playback by showing a list of routes, and after selecting one, * Controls media playback by showing a list of routes, and after selecting one,
* the current playlist and playback controls. * the current playlist and playback controls.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
public class RouteFragment extends MediaRouteDiscoveryFragment implements public class RouteFragment extends MediaRouteDiscoveryFragment implements
OnBackPressedListener, OnItemClickListener, OnClickListener, OnBackPressedListener, OnItemClickListener, OnClickListener,
OnSeekBarChangeListener, OnScrollListener { OnSeekBarChangeListener, OnScrollListener {
private ListView mListView; private ListView mListView;
private View mControls; private View mControls;
private SeekBar mProgressBar; private SeekBar mProgressBar;
private ImageButton mPlayPause; private ImageButton mPlayPause;
@ -92,32 +92,32 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
private ImageButton mRepeat; private ImageButton mRepeat;
private TextView mCurrentTimeView; private TextView mCurrentTimeView;
private TextView mTotalTimeView; private TextView mTotalTimeView;
private View mCurrentTrackView; private View mCurrentTrackView;
private boolean mPlaying; private boolean mPlaying;
private boolean mRestorePlaylistMode; private boolean mRestorePlaylistMode;
private RouteAdapter mRouteAdapter; private RouteAdapter mRouteAdapter;
private FileArrayAdapter mPlaylistAdapter; private FileArrayAdapter mPlaylistAdapter;
private RouteInfo mSelectedRoute; private RouteInfo mSelectedRoute;
/** /**
* Count of the number of taps on the previous button within the * Count of the number of taps on the previous button within the
* doubletap interval. * doubletap interval.
*/ */
private int mPreviousTapCount = 0; private int mPreviousTapCount = 0;
/** /**
* If true, the item at this position will be played as soon as a route is selected. * If true, the item at this position will be played as soon as a route is selected.
*/ */
private int mStartPlayingOnSelect = -1; private int mStartPlayingOnSelect = -1;
private MediaRouterPlayServiceBinder mMediaRouterPlayService; private MediaRouterPlayServiceBinder mMediaRouterPlayService;
private ServiceConnection mPlayServiceConnection = new ServiceConnection() { private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
@ -128,173 +128,172 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
applyColors(); applyColors();
if (mRestorePlaylistMode) if (mRestorePlaylistMode)
playlistMode(mMediaRouterPlayService.getService().getCurrentRoute()); playlistMode(mMediaRouterPlayService.getService().getCurrentRoute());
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
mMediaRouterPlayService = null; mMediaRouterPlayService = null;
} }
}; };
/** /**
* Selects remote playback route category. * Selects remote playback route category.
*/ */
public RouteFragment() { public RouteFragment() {
MediaRouteSelector mSelector = new MediaRouteSelector.Builder() MediaRouteSelector mSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build(); .build();
setRouteSelector(mSelector); setRouteSelector(mSelector);
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
return inflater.inflate(R.layout.route_fragment, null); return inflater.inflate(R.layout.route_fragment, null);
}; };
/** /**
* Initializes views, connects to service, adds default route. * Initializes views, connects to service, adds default route.
*/ */
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
mRouteAdapter = new RouteAdapter(getActivity()); mRouteAdapter = new RouteAdapter(getActivity());
mRouteAdapter.add(MediaRouter.getInstance(getActivity()).getRoutes()); mRouteAdapter.add(MediaRouter.getInstance(getActivity()).getRoutes());
mRouteAdapter.remove(MediaRouter.getInstance(getActivity()).getDefaultRoute()); mRouteAdapter.remove(MediaRouter.getInstance(getActivity()).getDefaultRoute());
mPlaylistAdapter = new FileArrayAdapter(getActivity()); mPlaylistAdapter = new FileArrayAdapter(getActivity());
mListView = (ListView) getView().findViewById(R.id.listview); mListView = (ListView) getView().findViewById(R.id.listview);
mListView.setAdapter(mRouteAdapter); mListView.setAdapter(mRouteAdapter);
mListView.setOnItemClickListener(this); mListView.setOnItemClickListener(this);
mListView.setOnScrollListener(this); mListView.setOnScrollListener(this);
mListView.setEmptyView(getView().findViewById(android.R.id.empty)); mListView.setEmptyView(getView().findViewById(android.R.id.empty));
mControls = getView().findViewById(R.id.controls);
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_action_previous);
previous.setOnClickListener(this);
ImageButton next = (ImageButton) getView().findViewById(R.id.next);
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_action_play);
mCurrentTimeView = (TextView) getView().findViewById(R.id.current_time);
mTotalTimeView = (TextView) getView().findViewById(R.id.total_time);
getActivity().getApplicationContext().startService( mControls = getView().findViewById(R.id.controls);
new Intent(getActivity(), MediaRouterPlayService.class)); mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
getActivity().getApplicationContext().bindService( mProgressBar.setOnSeekBarChangeListener(this);
new Intent(getActivity(), MediaRouterPlayService.class),
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
);
if (savedInstanceState != null) {
mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
mRestorePlaylistMode = savedInstanceState.getBoolean("route_selected");
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("route_selected", mSelectedRoute != null);
outState.putParcelable("list_state", mListView.onSaveInstanceState());
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
}
/** mShuffle = (ImageButton) getView().findViewById(R.id.shuffle);
* Starts active route discovery (which is automatically stopped on mShuffle.setImageResource(R.drawable.ic_action_shuffle);
* fragment stop by parent class). mShuffle.setOnClickListener(this);
*/
@Override
public int onPrepareCallbackFlags() {
return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY
| MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN;
}
@Override
public Callback onCreateCallback() {
return new MediaRouter.Callback() {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
for (int i = 0; i < mRouteAdapter.getCount(); i++) {
if (mRouteAdapter.getItem(i).getId().equals(route.getId())) {
mRouteAdapter.remove(mRouteAdapter.getItem(i));
break;
}
}
mRouteAdapter.add(route);
}
@Override ImageButton previous = (ImageButton) getView().findViewById(R.id.previous);
public void onRouteChanged(MediaRouter router, RouteInfo route) { previous.setImageResource(R.drawable.ic_action_previous);
mRouteAdapter.notifyDataSetChanged(); previous.setOnClickListener(this);
}
@Override ImageButton next = (ImageButton) getView().findViewById(R.id.next);
public void onRouteRemoved(MediaRouter router, RouteInfo route) { next.setImageResource(R.drawable.ic_action_next);
mRouteAdapter.remove(route); next.setOnClickListener(this);
if (route.equals(mSelectedRoute)) {
mPlaying = false;
onBackPressed();
}
}
@Override mRepeat = (ImageButton) getView().findViewById(R.id.repeat);
public void onRouteSelected(MediaRouter router, RouteInfo route) { mRepeat.setImageResource(R.drawable.ic_action_repeat);
} mRepeat.setOnClickListener(this);
@Override mPlayPause = (ImageButton) getView().findViewById(R.id.playpause);
public void onRouteUnselected(MediaRouter router, RouteInfo route) { mPlayPause.setOnClickListener(this);
} mPlayPause.setImageResource(R.drawable.ic_action_play);
@Override mCurrentTimeView = (TextView) getView().findViewById(R.id.current_time);
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) { mTotalTimeView = (TextView) getView().findViewById(R.id.total_time);
}
@Override getActivity().getApplicationContext().startService(
public void onRoutePresentationDisplayChanged( new Intent(getActivity(), MediaRouterPlayService.class));
MediaRouter router, RouteInfo route) { getActivity().getApplicationContext().bindService(
} new Intent(getActivity(), MediaRouterPlayService.class),
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
);
@Override if (savedInstanceState != null) {
public void onProviderAdded(MediaRouter router, ProviderInfo provider) { mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
} mRestorePlaylistMode = savedInstanceState.getBoolean("route_selected");
}
}
@Override @Override
public void onProviderRemoved(MediaRouter router, ProviderInfo provider) { public void onSaveInstanceState(Bundle outState) {
} super.onSaveInstanceState(outState);
outState.putBoolean("route_selected", mSelectedRoute != null);
outState.putParcelable("list_state", mListView.onSaveInstanceState());
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
}
/**
* Starts active route discovery (which is automatically stopped on
* fragment stop by parent class).
*/
@Override
public int onPrepareCallbackFlags() {
return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY
| MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN;
}
@Override
public Callback onCreateCallback() {
return new MediaRouter.Callback() {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo route) {
for (int i = 0; i < mRouteAdapter.getCount(); i++)
if (mRouteAdapter.getItem(i).getId().equals(route.getId())) {
mRouteAdapter.remove(mRouteAdapter.getItem(i));
break;
}
mRouteAdapter.add(route);
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo route) {
mRouteAdapter.notifyDataSetChanged();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) {
mRouteAdapter.remove(route);
if (route.equals(mSelectedRoute)) {
mPlaying = false;
onBackPressed();
}
}
@Override
public void onRouteSelected(MediaRouter router, RouteInfo route) {
}
@Override
public void onRouteUnselected(MediaRouter router, RouteInfo route) {
}
@Override
public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
}
@Override
public void onRoutePresentationDisplayChanged(
MediaRouter router, RouteInfo route) {
}
@Override
public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
}
@Override
public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
}
@Override
public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
}
};
}
@Override
public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
}
};
}
/** /**
* Selects a route or starts playback (depending on current ListAdapter). * Selects a route or starts playback (depending on current ListAdapter).
*/ */
@ -307,7 +306,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
changePlayPauseState(true); changePlayPauseState(true);
} }
} }
/** /**
* Displays UPNP devices in the ListView. * Displays UPNP devices in the ListView.
*/ */
@ -319,7 +318,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
TextView emptyView = (TextView) mListView.getEmptyView(); TextView emptyView = (TextView) mListView.getEmptyView();
emptyView.setText(R.string.route_list_empty); emptyView.setText(R.string.route_list_empty);
} }
/** /**
* Displays playlist for route in the ListView. * Displays playlist for route in the ListView.
*/ */
@ -334,25 +333,25 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mStartPlayingOnSelect = -1; mStartPlayingOnSelect = -1;
} }
TextView emptyView = (TextView) mListView.getEmptyView(); TextView emptyView = (TextView) mListView.getEmptyView();
emptyView.setText(R.string.playlist_empty); emptyView.setText(R.string.playlist_empty);
} }
/** /**
* Sets colored background on the item that is currently playing. * Sets colored background on the item that is currently playing.
*/ */
private void enableTrackHighlight() { private void enableTrackHighlight() {
if (mListView.getAdapter() == mRouteAdapter || mMediaRouterPlayService == null || !isVisible()) if (mListView.getAdapter() == mRouteAdapter || mMediaRouterPlayService == null || !isVisible())
return; return;
disableTrackHighlight(); disableTrackHighlight();
mCurrentTrackView = mListView.getChildAt(mMediaRouterPlayService.getService() mCurrentTrackView = mListView.getChildAt(mMediaRouterPlayService.getService()
.getCurrentTrack() .getCurrentTrack()
- mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount()); - mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
if (mCurrentTrackView != null) if (mCurrentTrackView != null)
mCurrentTrackView.setBackgroundColor( mCurrentTrackView.setBackgroundColor(
getResources().getColor(R.color.currently_playing_background)); getResources().getColor(R.color.currently_playing_background));
} }
/** /**
* Removes highlight from the item that was last highlighted. * Removes highlight from the item that was last highlighted.
*/ */
@ -367,25 +366,24 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
@Override @Override
public boolean onBackPressed() { public boolean onBackPressed() {
if (mListView.getAdapter() == mPlaylistAdapter) { if (mListView.getAdapter() == mPlaylistAdapter) {
if (mPlaying) { if (mPlaying)
new AlertDialog.Builder(getActivity()) new AlertDialog.Builder(getActivity())
.setMessage(R.string.exit_renderer) .setMessage(R.string.exit_renderer)
.setPositiveButton(android.R.string.yes, .setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
mMediaRouterPlayService.getService().stop(); mMediaRouterPlayService.getService().stop();
deviceListMode(); deviceListMode();
} }
}) })
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show(); .show();
}
else else
deviceListMode(); deviceListMode();
return true; return true;
} }
return false; return false;
} }
@ -413,24 +411,24 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
break; break;
case R.id.previous: case R.id.previous:
mPreviousTapCount++; mPreviousTapCount++;
Handler handler = new Handler(); Handler handler = new Handler();
Runnable r = new Runnable() { Runnable r = new Runnable() {
@Override @Override
public void run() { public void run() {
// Single tap. // Single tap.
mPreviousTapCount = 0; mPreviousTapCount = 0;
s.play(s.getCurrentTrack()); s.play(s.getCurrentTrack());
changePlayPauseState(true); changePlayPauseState(true);
} }
}; };
if (mPreviousTapCount == 1) if (mPreviousTapCount == 1)
handler.postDelayed(r, ViewConfiguration.getDoubleTapTimeout()); handler.postDelayed(r, ViewConfiguration.getDoubleTapTimeout());
else if(mPreviousTapCount == 2) { else if(mPreviousTapCount == 2) {
// Double tap. // Double tap.
mPreviousTapCount = 0; mPreviousTapCount = 0;
s.playPrevious(); s.playPrevious();
} }
break; break;
case R.id.next: case R.id.next:
boolean stillPlaying = s.playNext(); boolean stillPlaying = s.playNext();
@ -440,9 +438,9 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
s.toggleRepeatEnabled(); s.toggleRepeatEnabled();
applyColors(); applyColors();
break; break;
} }
} }
/** /**
* Enables or disables highlighting on shuffle/repeat buttons (depending * Enables or disables highlighting on shuffle/repeat buttons (depending
* if they are enabled or disabled). * if they are enabled or disabled).
@ -451,20 +449,20 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
MediaRouterPlayService s = mMediaRouterPlayService.getService(); MediaRouterPlayService s = mMediaRouterPlayService.getService();
int highlight = getResources().getColor(R.color.button_highlight); int highlight = getResources().getColor(R.color.button_highlight);
int transparent = getResources().getColor(android.R.color.transparent); int transparent = getResources().getColor(android.R.color.transparent);
mShuffle.setColorFilter((s.getShuffleEnabled()) mShuffle.setColorFilter((s.getShuffleEnabled())
? highlight ? highlight
: transparent); : transparent);
mRepeat.setColorFilter((s.getRepeatEnabled()) mRepeat.setColorFilter((s.getRepeatEnabled())
? highlight ? highlight
: transparent); : transparent);
} }
/** /**
* Sends manual seek on progress bar to renderer. * Sends manual seek on progress bar to renderer.
*/ */
@Override @Override
public void onProgressChanged(SeekBar seekBar, int progress, public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) { boolean fromUser) {
if (fromUser) if (fromUser)
mMediaRouterPlayService.getService().seek(progress); mMediaRouterPlayService.getService().seek(progress);
@ -490,11 +488,11 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
public void onScrollStateChanged(AbsListView arg0, int arg1) { public void onScrollStateChanged(AbsListView arg0, int arg1) {
enableTrackHighlight(); enableTrackHighlight();
} }
public void increaseVolume() { public void increaseVolume() {
mMediaRouterPlayService.getService().increaseVolume(); mMediaRouterPlayService.getService().increaseVolume();
} }
public void decreaseVolume() { public void decreaseVolume() {
mMediaRouterPlayService.getService().decreaseVolume(); mMediaRouterPlayService.getService().decreaseVolume();
} }
@ -506,13 +504,13 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
mPlaylistAdapter.clear(); mPlaylistAdapter.clear();
mPlaylistAdapter.add(playlist); mPlaylistAdapter.add(playlist);
mMediaRouterPlayService.getService().setPlaylist(playlist); mMediaRouterPlayService.getService().setPlaylist(playlist);
if (mSelectedRoute != null) { if (mSelectedRoute != null) {
mMediaRouterPlayService.getService().play(start); mMediaRouterPlayService.getService().play(start);
changePlayPauseState(true); changePlayPauseState(true);
} else { } else {
Toast.makeText(getActivity(), R.string.select_route, Toast.LENGTH_SHORT) Toast.makeText(getActivity(), R.string.select_route, Toast.LENGTH_SHORT)
.show(); .show();
mStartPlayingOnSelect = start; mStartPlayingOnSelect = start;
} }
} }
@ -525,17 +523,17 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
*/ */
private String generateTimeString(int time) { private String generateTimeString(int time) {
assert(time >= 0); assert(time >= 0);
int seconds = (int) time % 60; int seconds = time % 60;
int minutes = (int) time / 60; int minutes = time / 60;
if (minutes > 99) if (minutes > 99)
return "99:99"; return "99:99";
else else
return Integer.toString(minutes) + ":" + return Integer.toString(minutes) + ":" +
((seconds > 9) ((seconds > 9)
? seconds ? seconds
: "0" + Integer.toString(seconds)); : "0" + Integer.toString(seconds));
} }
/** /**
* Sent by MediaRouterPlayService when playback of a new track is started. * Sent by MediaRouterPlayService when playback of a new track is started.
* *
@ -544,7 +542,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
public void receiveIsPlaying(int track) { public void receiveIsPlaying(int track) {
mListView.smoothScrollToPosition(track); mListView.smoothScrollToPosition(track);
} }
/** /**
* Receives information from MediaRouterPlayService about playback status. * Receives information from MediaRouterPlayService about playback status.
*/ */
@ -552,13 +550,13 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
// Views may not exist if fragment was just created/destroyed. // Views may not exist if fragment was just created/destroyed.
if (getView() == null) if (getView() == null)
return; return;
int currentTime = (int) status.getContentPosition() / 1000; int currentTime = (int) status.getContentPosition() / 1000;
int totalTime = (int) status.getContentDuration() / 1000; int totalTime = (int) status.getContentDuration() / 1000;
mCurrentTimeView.setText(generateTimeString(currentTime)); mCurrentTimeView.setText(generateTimeString(currentTime));
mTotalTimeView.setText(generateTimeString(totalTime)); mTotalTimeView.setText(generateTimeString(totalTime));
mProgressBar.setProgress(currentTime); mProgressBar.setProgress(currentTime);
mProgressBar.setMax(totalTime); mProgressBar.setMax(totalTime);
@ -568,13 +566,13 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
changePlayPauseState(true); changePlayPauseState(true);
else else
changePlayPauseState(false); changePlayPauseState(false);
if (mListView.getAdapter() == mPlaylistAdapter) if (mListView.getAdapter() == mPlaylistAdapter)
enableTrackHighlight(); enableTrackHighlight();
} }
/** /**
* Changes the state of mPlayPause button to pause/resume according to * Changes the state of mPlayPause button to pause/resume according to
* current playback state, also sets mPlaying. * current playback state, also sets mPlaying.
* *
* @param playing True if an item is currently being played, false otherwise. * @param playing True if an item is currently being played, false otherwise.
@ -587,7 +585,7 @@ public class RouteFragment extends MediaRouteDiscoveryFragment implements
} }
else { else {
mPlayPause.setImageResource(R.drawable.ic_action_play); mPlayPause.setImageResource(R.drawable.ic_action_play);
mPlayPause.setContentDescription(getResources().getString(R.string.play)); mPlayPause.setContentDescription(getResources().getString(R.string.play));
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -75,293 +75,290 @@ import com.github.nutomic.controldlna.utility.FileArrayAdapter;
* directories. * directories.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
public class ServerFragment extends ListFragment implements OnBackPressedListener { public class ServerFragment extends ListFragment implements OnBackPressedListener {
private final static String TAG = "ServerFragment"; private final static String TAG = "ServerFragment";
private final static String ROOT_DIRECTORY = "0"; private final static String ROOT_DIRECTORY = "0";
/** /**
* ListView adapter for showing a list of DLNA media servers. * ListView adapter for showing a list of DLNA media servers.
*/ */
private DeviceArrayAdapter mServerAdapter; private DeviceArrayAdapter mServerAdapter;
/** /**
* Reference to the media server of which folders are currently shown. * Reference to the media server of which folders are currently shown.
* Null if media servers are shown. * Null if media servers are shown.
*/ */
private Device<?, ?, ?> mCurrentServer; private Device<?, ?, ?> mCurrentServer;
private String mRestoreServer; private String mRestoreServer;
/** /**
* ListView adapter for showing a list of files/folders. * ListView adapter for showing a list of files/folders.
*/ */
private FileArrayAdapter mFileAdapter; private FileArrayAdapter mFileAdapter;
/** /**
* Holds path to current directory on top, paths for higher directories * Holds path to current directory on top, paths for higher directories
* behind that. * behind that.
*/ */
private Stack<String> mCurrentPath = new Stack<String>(); private Stack<String> mCurrentPath = new Stack<String>();
/** /**
* Holds the scroll position in the list view at each directory level. * Holds the scroll position in the list view at each directory level.
*/ */
private Stack<Parcelable> mListState = new Stack<Parcelable>(); private Stack<Parcelable> mListState = new Stack<Parcelable>();
protected AndroidUpnpService mUpnpService;
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() { protected AndroidUpnpService mUpnpService;
/** private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
* Registers DeviceListener, adds known devices and starts search if requested.
*/ /**
* Registers DeviceListener, adds known devices and starts search if requested.
*/
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service; mUpnpService = (AndroidUpnpService) service;
mUpnpService.getRegistry().addListener(mServerAdapter); mUpnpService.getRegistry().addListener(mServerAdapter);
for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices())
mServerAdapter.deviceAdded(d); mServerAdapter.deviceAdded(d);
mUpnpService.getControlPoint().search(); mUpnpService.getControlPoint().search();
if (mRestoreServer != null) {
mCurrentServer = mUpnpService.getControlPoint().getRegistry()
.getDevice(new UDN(mRestoreServer.replace("uuid:", "")), false);
if (mCurrentServer != null) {
setListAdapter(mFileAdapter);
// Duplicate the top element because getFiles will remove it.
mListState.add(mListState.peek());
getFiles(true);
}
getListView().onRestoreInstanceState(mListState.peek()); if (mRestoreServer != null) {
} mCurrentServer = mUpnpService.getControlPoint().getRegistry()
} .getDevice(new UDN(mRestoreServer.replace("uuid:", "")), false);
if (mCurrentServer != null) {
setListAdapter(mFileAdapter);
// Duplicate the top element because getFiles will remove it.
mListState.add(mListState.peek());
getFiles(true);
}
getListView().onRestoreInstanceState(mListState.peek());
}
}
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
return inflater.inflate(R.layout.server_fragment, null); return inflater.inflate(R.layout.server_fragment, null);
}; };
/** /**
* Initializes ListView adapters, launches Cling UPNP service, registers * Initializes ListView adapters, launches Cling UPNP service, registers
* wifi state change listener and restores instance state if possible. * wifi state change listener and restores instance state if possible.
*/ */
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
mFileAdapter = new FileArrayAdapter(getActivity()); mFileAdapter = new FileArrayAdapter(getActivity());
mServerAdapter = new DeviceArrayAdapter( mServerAdapter = new DeviceArrayAdapter(
getActivity(), DeviceArrayAdapter.SERVER); getActivity(), DeviceArrayAdapter.SERVER);
setListAdapter(mServerAdapter); setListAdapter(mServerAdapter);
getActivity().getApplicationContext().bindService( getActivity().getApplicationContext().bindService(
new Intent(getActivity(), AndroidUpnpServiceImpl.class), new Intent(getActivity(), AndroidUpnpServiceImpl.class),
mUpnpServiceConnection, mUpnpServiceConnection,
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE
); );
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mWifiReceiver, filter);
if (savedInstanceState != null) {
mRestoreServer = savedInstanceState.getString("current_server");
mCurrentPath.addAll(savedInstanceState.getStringArrayList("path"));
mListState.addAll(savedInstanceState.getParcelableArrayList("list_state"));
} else
mListState.push(getListView().onSaveInstanceState());
}
/**
* Stores current server and path/list state stacks.
*/
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_server", (mCurrentServer != null)
? mCurrentServer.getIdentity().getUdn().toString()
: "");
outState.putStringArrayList("path", new ArrayList<String>(mCurrentPath));
mListState.pop();
mListState.push(getListView().onSaveInstanceState());
outState.putParcelableArrayList("list_state", new ArrayList<Parcelable>(mListState));
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
getActivity().unregisterReceiver(mWifiReceiver);
}
/**
* Enters directory browsing mode or enters a deeper level directory.
*/
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (getListAdapter() == mServerAdapter)
browsingMode(mServerAdapter.getItem(position));
else if (getListAdapter() == mFileAdapter) {
if (mFileAdapter.getItem(position) instanceof Container)
getFiles(((Container) mFileAdapter.getItem(position)).getId());
else {
List<Item> playlist = new ArrayList<Item>();
for (int i = 0; i < mFileAdapter.getCount(); i++) {
if (mFileAdapter.getItem(i) instanceof Item)
playlist.add((Item) mFileAdapter.getItem(i));
}
MainActivity activity = (MainActivity) getActivity();
activity.play(playlist, position);
}
}
}
/** IntentFilter filter = new IntentFilter();
* Displays available servers in the ListView. filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
*/ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
private void serverMode() { getActivity().registerReceiver(mWifiReceiver, filter);
if (savedInstanceState != null) {
mRestoreServer = savedInstanceState.getString("current_server");
mCurrentPath.addAll(savedInstanceState.getStringArrayList("path"));
mListState.addAll(savedInstanceState.getParcelableArrayList("list_state"));
} else
mListState.push(getListView().onSaveInstanceState());
}
/**
* Stores current server and path/list state stacks.
*/
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_server", (mCurrentServer != null)
? mCurrentServer.getIdentity().getUdn().toString()
: "");
outState.putStringArrayList("path", new ArrayList<String>(mCurrentPath));
mListState.pop();
mListState.push(getListView().onSaveInstanceState());
outState.putParcelableArrayList("list_state", new ArrayList<Parcelable>(mListState));
}
@Override
public void onDestroy() {
super.onDestroy();
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
getActivity().unregisterReceiver(mWifiReceiver);
}
/**
* Enters directory browsing mode or enters a deeper level directory.
*/
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
if (getListAdapter() == mServerAdapter)
browsingMode(mServerAdapter.getItem(position));
else if (getListAdapter() == mFileAdapter)
if (mFileAdapter.getItem(position) instanceof Container)
getFiles(((Container) mFileAdapter.getItem(position)).getId());
else {
List<Item> playlist = new ArrayList<Item>();
for (int i = 0; i < mFileAdapter.getCount(); i++)
if (mFileAdapter.getItem(i) instanceof Item)
playlist.add((Item) mFileAdapter.getItem(i));
MainActivity activity = (MainActivity) getActivity();
activity.play(playlist, position);
}
}
/**
* Displays available servers in the ListView.
*/
private void serverMode() {
setListAdapter(mServerAdapter); setListAdapter(mServerAdapter);
mCurrentServer = null; mCurrentServer = null;
TextView emptyView = (TextView) getListView().getEmptyView(); TextView emptyView = (TextView) getListView().getEmptyView();
emptyView.setText(R.string.device_list_empty); emptyView.setText(R.string.device_list_empty);
getListView().onRestoreInstanceState(mListState.pop()); getListView().onRestoreInstanceState(mListState.pop());
} }
/** /**
* Displays files for server (starting from root). * Displays files for server (starting from root).
*/ */
private void browsingMode(Device<?, ?, ?> server) { private void browsingMode(Device<?, ?, ?> server) {
setListAdapter(mFileAdapter); setListAdapter(mFileAdapter);
mCurrentServer = server; mCurrentServer = server;
getFiles(ROOT_DIRECTORY); getFiles(ROOT_DIRECTORY);
TextView emptyView = (TextView) getListView().getEmptyView(); TextView emptyView = (TextView) getListView().getEmptyView();
emptyView.setText(R.string.folder_list_empty); emptyView.setText(R.string.folder_list_empty);
} }
/**
* Opens a new directory and displays it. /**
*/ * Opens a new directory and displays it.
private void getFiles(String directory) { */
mListState.push(getListView().onSaveInstanceState()); private void getFiles(String directory) {
mCurrentPath.push(directory); mListState.push(getListView().onSaveInstanceState());
getFiles(false); mCurrentPath.push(directory);
} getFiles(false);
}
/**
* Displays the current directory on the ListView. /**
* * Displays the current directory on the ListView.
* @param restoreListState True if we are going back up the directory tree, *
* which means we restore scroll position etc. This pops * @param restoreListState True if we are going back up the directory tree,
* mListState. * which means we restore scroll position etc. This pops
*/ * mListState.
private void getFiles(final boolean restoreListState) { */
if (mCurrentServer == null) private void getFiles(final boolean restoreListState) {
return; if (mCurrentServer == null)
return;
Service<?, ?> service = mCurrentServer.findService(
new ServiceType("schemas-upnp-org", "ContentDirectory")); Service<?, ?> service = mCurrentServer.findService(
mUpnpService.getControlPoint().execute(new Browse(service, new ServiceType("schemas-upnp-org", "ContentDirectory"));
mUpnpService.getControlPoint().execute(new Browse(service,
mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) { mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override
public void received(ActionInvocation actionInvocation,
final DIDLContent didl) {
getActivity().runOnUiThread(new Runnable() {
@Override @Override
public void received(ActionInvocation actionInvocation, public void run() {
final DIDLContent didl) { mFileAdapter.clear();
getActivity().runOnUiThread(new Runnable() { for (Container c : didl.getContainers())
mFileAdapter.add(c);
@Override for (Item i : didl.getItems())
public void run() { mFileAdapter.add(i);
mFileAdapter.clear(); if (restoreListState)
for (Container c : didl.getContainers()) getListView().onRestoreInstanceState(mListState.pop());
mFileAdapter.add(c); else
for (Item i : didl.getItems()) getListView().setSelectionFromTop(0, 0);
mFileAdapter.add(i);
if (restoreListState)
getListView().onRestoreInstanceState(mListState.pop());
else
getListView().setSelectionFromTop(0, 0);
}
});
} }
});
@Override }
public void updateStatus(Status status) {
} @Override
public void updateStatus(Status status) {
@SuppressWarnings("rawtypes") }
@Override
public void failure(ActionInvocation actionInvocation, @SuppressWarnings("rawtypes")
UpnpResponse operation, String defaultMessage) { @Override
Log.w(TAG, "Failed to load directory contents: " + public void failure(ActionInvocation actionInvocation,
defaultMessage); UpnpResponse operation, String defaultMessage) {
} Log.w(TAG, "Failed to load directory contents: " +
defaultMessage);
}); }
}
});
/** }
* Handles back button press to traverse directories (while browsing
* directories). /**
*/ * Handles back button press to traverse directories (while browsing
* directories).
*/
public boolean onBackPressed() { public boolean onBackPressed() {
if (getListAdapter() == mServerAdapter) if (getListAdapter() == mServerAdapter)
return false; return false;
mCurrentPath.pop(); mCurrentPath.pop();
if (mCurrentPath.empty()) if (mCurrentPath.empty())
serverMode(); serverMode();
else else
getFiles(true); getFiles(true);
return true; return true;
} }
/** /**
* Starts device search on wifi connect, removes unreachable * Starts device search on wifi connect, removes unreachable
* devices on wifi disconnect. * devices on wifi disconnect.
*/ */
private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() { private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
getActivity(); getActivity();
ConnectivityManager connManager = (ConnectivityManager) ConnectivityManager connManager = (ConnectivityManager)
getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifi.isConnected()) { if (wifi.isConnected()) {
if (mUpnpService != null) { if (mUpnpService != null) {
for (Device<?, ?, ?> d : mUpnpService.getControlPoint() for (Device<?, ?, ?> d : mUpnpService.getControlPoint()
.getRegistry().getDevices()) .getRegistry().getDevices())
mServerAdapter.deviceAdded(d); mServerAdapter.deviceAdded(d);
mUpnpService.getControlPoint().search(); mUpnpService.getControlPoint().search();
} }
} } else
else { for (int i = 0; i < mServerAdapter.getCount(); i++) {
for (int i = 0; i < mServerAdapter.getCount(); i++) { Device<?, ?, ?> d = mServerAdapter.getItem(i);
Device<?, ?, ?> d = mServerAdapter.getItem(i); UDN udn = new UDN(d.getIdentity().getUdn().toString());
UDN udn = new UDN(d.getIdentity().getUdn().toString()); if (mUpnpService.getControlPoint().getRegistry()
if (mUpnpService.getControlPoint().getRegistry() .getDevice(udn, false) == null) {
.getDevice(udn, false) == null) { mServerAdapter.deviceRemoved(d);
mServerAdapter.deviceRemoved(d); if (d.equals(mCurrentServer)) {
if (d.equals(mCurrentServer)) { mListState.setSize(2);
mListState.setSize(2); mCurrentPath.clear();
mCurrentPath.clear(); serverMode();
serverMode(); }
} }
} }
} }
}
}
}; };
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -70,61 +70,61 @@ import com.github.nutomic.controldlna.utility.LoadImageTask;
public class MediaRouterPlayService extends Service { public class MediaRouterPlayService extends Service {
private static final String TAG = "PlayService"; private static final String TAG = "PlayService";
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private final MediaRouterPlayServiceBinder mBinder = new MediaRouterPlayServiceBinder(this); private final MediaRouterPlayServiceBinder mBinder = new MediaRouterPlayServiceBinder(this);
private MediaRouter mMediaRouter; private MediaRouter mMediaRouter;
/** /**
* Media items that should be played. * Media items that should be played.
*/ */
private List<Item> mPlaylist = new ArrayList<Item>(); private List<Item> mPlaylist = new ArrayList<Item>();
/** /**
* The track that is currently being played. * The track that is currently being played.
*/ */
private int mCurrentTrack = -1; private int mCurrentTrack = -1;
private boolean mShuffle = false; private boolean mShuffle = false;
private boolean mRepeat = false; private boolean mRepeat = false;
private String mItemId; private String mItemId;
private String mSessionId; private String mSessionId;
private WeakReference<RouteFragment> mRouterFragment = private WeakReference<RouteFragment> mRouterFragment =
new WeakReference<RouteFragment>(null); new WeakReference<RouteFragment>(null);
private boolean mPollingStatus = false; private boolean mPollingStatus = false;
private boolean mBound; private boolean mBound;
/** /**
* Route that is currently being played to. May be invalid. * Route that is currently being played to. May be invalid.
*/ */
private RouteInfo mCurrentRoute; private RouteInfo mCurrentRoute;
/* /*
* Stops foreground mode and notification if the current route * Stops foreground mode and notification if the current route
* has been removed. * has been removed.
*/ */
private MediaRouter.Callback mRouteRemovedCallback = private MediaRouter.Callback mRouteRemovedCallback =
new MediaRouter.Callback() { new MediaRouter.Callback() {
@Override @Override
public void onRouteRemoved(MediaRouter router, RouteInfo route) { public void onRouteRemoved(MediaRouter router, RouteInfo route) {
if (route.equals(mCurrentRoute)) if (route.equals(mCurrentRoute))
stopForeground(true); stopForeground(true);
} }
}; };
/** /**
* Creates a notification after the icon bitmap is loaded. * Creates a notification after the icon bitmap is loaded.
*/ */
private class CreateNotificationTask extends LoadImageTask { private class CreateNotificationTask extends LoadImageTask {
@Override @Override
protected void onPostExecute(Bitmap result) { protected void onPostExecute(Bitmap result) {
String title = ""; String title = "";
@ -132,16 +132,16 @@ public class MediaRouterPlayService extends Service {
if (mCurrentTrack < mPlaylist.size()) { if (mCurrentTrack < mPlaylist.size()) {
title = mPlaylist.get(mCurrentTrack).getTitle(); title = mPlaylist.get(mCurrentTrack).getTitle();
if (mPlaylist.get(mCurrentTrack) instanceof MusicTrack) { if (mPlaylist.get(mCurrentTrack) instanceof MusicTrack) {
MusicTrack track = (MusicTrack) mPlaylist.get(mCurrentTrack); MusicTrack track = (MusicTrack) mPlaylist.get(mCurrentTrack);
if (track.getArtists().length > 0) if (track.getArtists().length > 0)
artist = track.getArtists()[0].getName(); artist = track.getArtists()[0].getName();
} }
} }
Intent intent = new Intent(MediaRouterPlayService.this, MainActivity.class); Intent intent = new Intent(MediaRouterPlayService.this, MainActivity.class);
intent.setAction("showRouteFragment"); intent.setAction("showRouteFragment");
Notification notification = new NotificationCompat.Builder(MediaRouterPlayService.this) Notification notification = new NotificationCompat.Builder(MediaRouterPlayService.this)
.setContentIntent(PendingIntent.getActivity(MediaRouterPlayService.this, 0, .setContentIntent(PendingIntent.getActivity(MediaRouterPlayService.this, 0,
intent, 0)) intent, 0))
.setContentTitle(title) .setContentTitle(title)
.setContentText(artist) .setContentText(artist)
.setLargeIcon(result) .setLargeIcon(result)
@ -150,68 +150,68 @@ public class MediaRouterPlayService extends Service {
notification.flags |= Notification.FLAG_ONGOING_EVENT; notification.flags |= Notification.FLAG_ONGOING_EVENT;
startForeground(NOTIFICATION_ID, notification); startForeground(NOTIFICATION_ID, notification);
} }
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
mMediaRouter = MediaRouter.getInstance(this); mMediaRouter = MediaRouter.getInstance(this);
pollStatus(); pollStatus();
} }
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
mBound = true; mBound = true;
return mBinder; return mBinder;
} }
/** /**
* Stops service after a delay if no media is playing (delay in case the * Stops service after a delay if no media is playing (delay in case the
* fragment is recreated for screen rotation). * fragment is recreated for screen rotation).
*/ */
@Override @Override
public boolean onUnbind(Intent intent) { public boolean onUnbind(Intent intent) {
new Handler().postDelayed(new Runnable() { new Handler().postDelayed(new Runnable() {
public void run() { public void run() {
if (!mPollingStatus && !mBound) if (!mPollingStatus && !mBound)
stopSelf(); stopSelf();
} }
}, 5000); }, 5000);
mBound = false; mBound = false;
return super.onUnbind(intent); return super.onUnbind(intent);
} }
public void setRouterFragment(RouteFragment rf) { public void setRouterFragment(RouteFragment rf) {
mRouterFragment = new WeakReference<RouteFragment>(rf); mRouterFragment = new WeakReference<RouteFragment>(rf);
} }
public void selectRoute(RouteInfo route) { public void selectRoute(RouteInfo route) {
mMediaRouter.removeCallback(mRouteRemovedCallback); mMediaRouter.removeCallback(mRouteRemovedCallback);
mMediaRouter.selectRoute(route); mMediaRouter.selectRoute(route);
MediaRouteSelector selector = new MediaRouteSelector.Builder() MediaRouteSelector selector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.build(); .build();
mMediaRouter.addCallback(selector, mRouteRemovedCallback, 0); mMediaRouter.addCallback(selector, mRouteRemovedCallback, 0);
mCurrentRoute = route; mCurrentRoute = route;
} }
public void sendControlRequest(Intent intent) { public void sendControlRequest(Intent intent) {
mMediaRouter.getSelectedRoute().sendControlRequest(intent, null); mMediaRouter.getSelectedRoute().sendControlRequest(intent, null);
} }
/** /**
* Sets current track in renderer to specified item in playlist, then * Sets current track in renderer to specified item in playlist, then
* starts playback. * starts playback.
*/ */
public void play(int trackNumber) { public void play(int trackNumber) {
if (trackNumber < 0 || trackNumber >= mPlaylist.size()) if (trackNumber < 0 || trackNumber >= mPlaylist.size())
return; return;
mCurrentTrack = trackNumber; mCurrentTrack = trackNumber;
Item track = mPlaylist.get(trackNumber); Item track = mPlaylist.get(trackNumber);
DIDLParser parser = new DIDLParser(); DIDLParser parser = new DIDLParser();
DIDLContent didl = new DIDLContent(); DIDLContent didl = new DIDLContent();
didl.addItem(track); didl.addItem(track);
String metadata = ""; String metadata = "";
@ -221,88 +221,88 @@ public class MediaRouterPlayService extends Service {
catch (Exception e) { catch (Exception e) {
Log.w(TAG, "Metadata generation failed", e); Log.w(TAG, "Metadata generation failed", e);
} }
Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
intent.setData(Uri.parse(track.getFirstResource().getValue()));
intent.putExtra(MediaControlIntent.EXTRA_ITEM_METADATA, metadata);
mMediaRouter.getSelectedRoute().sendControlRequest(intent,
new ControlRequestCallback() {
@Override
public void onResult(Bundle data) {
mSessionId = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
mItemId = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
mPollingStatus = true;
new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack)
.getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
if (mRouterFragment.get() != null) Intent intent = new Intent(MediaControlIntent.ACTION_PLAY);
mRouterFragment.get().receiveIsPlaying(mCurrentTrack); intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
} intent.setData(Uri.parse(track.getFirstResource().getValue()));
}); intent.putExtra(MediaControlIntent.EXTRA_ITEM_METADATA, metadata);
mMediaRouter.getSelectedRoute().sendControlRequest(intent,
new ControlRequestCallback() {
@Override
public void onResult(Bundle data) {
mSessionId = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
mItemId = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
mPollingStatus = true;
new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack)
.getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
if (mRouterFragment.get() != null)
mRouterFragment.get().receiveIsPlaying(mCurrentTrack);
}
});
} }
/** /**
* Sends 'pause' signal to current renderer. * Sends 'pause' signal to current renderer.
*/ */
public void pause() { public void pause() {
if (mPlaylist.isEmpty()) if (mPlaylist.isEmpty())
return; 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);
mMediaRouter.getSelectedRoute().sendControlRequest(intent, null); mMediaRouter.getSelectedRoute().sendControlRequest(intent, null);
mPollingStatus = false; mPollingStatus = false;
stopForeground(true); stopForeground(true);
} }
/** /**
* Sends 'resume' signal to current renderer. * Sends 'resume' signal to current renderer.
*/ */
public void resume() { public void resume() {
if (mPlaylist.isEmpty()) if (mPlaylist.isEmpty())
return; 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);
mMediaRouter.getSelectedRoute().sendControlRequest(intent, null); mMediaRouter.getSelectedRoute().sendControlRequest(intent, null);
mPollingStatus = true; mPollingStatus = true;
new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack) new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack)
.getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class)); .getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
} }
/** /**
* Sends 'stop' signal to current renderer. * Sends 'stop' signal to current renderer.
*/ */
public void stop() { public void stop() {
if (mPlaylist.isEmpty()) if (mPlaylist.isEmpty())
return; 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);
mMediaRouter.getSelectedRoute().sendControlRequest(intent, null); mMediaRouter.getSelectedRoute().sendControlRequest(intent, null);
mPollingStatus = false; mPollingStatus = false;
stopForeground(true); stopForeground(true);
} }
public void seek(int seconds) { public void seek(int seconds) {
if (mPlaylist.isEmpty()) if (mPlaylist.isEmpty())
return; 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);
intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, mItemId); intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, mItemId);
intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION,
(long) seconds * 1000); (long) seconds * 1000);
mMediaRouter.getSelectedRoute().sendControlRequest(intent, null); mMediaRouter.getSelectedRoute().sendControlRequest(intent, null);
} }
/** /**
* Sets a new playlist and starts playing. * Sets a new playlist and starts playing.
* *
@ -311,17 +311,17 @@ public class MediaRouterPlayService extends Service {
public void setPlaylist(List<Item> playlist) { public void setPlaylist(List<Item> playlist) {
mPlaylist = playlist; mPlaylist = playlist;
} }
/** /**
* Plays the track after current in the playlist. * Plays the track after current in the playlist.
* *
* @return True if another item is played, false if the end * @return True if another item is played, false if the end
* of the playlist is reached. * of the playlist is reached.
*/ */
public boolean playNext() { public boolean playNext() {
if (mCurrentTrack == -1) if (mCurrentTrack == -1)
return false; return false;
if (mShuffle) { if (mShuffle) {
// Play random item. // Play random item.
play(new Random().nextInt(mPlaylist.size())); play(new Random().nextInt(mPlaylist.size()));
@ -346,21 +346,21 @@ 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) if (mCurrentTrack == -1)
return; return;
if (mShuffle) if (mShuffle)
// Play random item. // Play random item.
play(new Random().nextInt(mPlaylist.size())); play(new Random().nextInt(mPlaylist.size()));
else else
play(mCurrentTrack - 1); play(mCurrentTrack - 1);
} }
/** /**
* Returns index of the track that is currently played (zero-based). * Returns index of the track that is currently played (zero-based).
* @return * @return
@ -368,7 +368,7 @@ public class MediaRouterPlayService extends Service {
public int getCurrentTrack() { public int getCurrentTrack() {
return mCurrentTrack; return mCurrentTrack;
} }
/** /**
* Requests playback information every second, as long as RendererFragment * Requests playback information every second, as long as RendererFragment
* is attached or media is playing. * is attached or media is playing.
@ -379,60 +379,60 @@ public class MediaRouterPlayService extends Service {
i.setAction(MediaControlIntent.ACTION_GET_STATUS); i.setAction(MediaControlIntent.ACTION_GET_STATUS);
i.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId); i.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
i.putExtra(MediaControlIntent.EXTRA_ITEM_ID, mItemId); i.putExtra(MediaControlIntent.EXTRA_ITEM_ID, mItemId);
mMediaRouter.getSelectedRoute().sendControlRequest(i, mMediaRouter.getSelectedRoute().sendControlRequest(i,
new ControlRequestCallback() { new ControlRequestCallback() {
@Override @Override
public void onResult(Bundle data) { public void onResult(Bundle data) {
MediaItemStatus status = MediaItemStatus.fromBundle(data); MediaItemStatus status = MediaItemStatus.fromBundle(data);
if (status == null) if (status == null)
return; return;
if (mRouterFragment.get() != null) if (mRouterFragment.get() != null)
mRouterFragment.get().receivePlaybackStatus(status); mRouterFragment.get().receivePlaybackStatus(status);
if (status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_PENDING && if (status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_PENDING &&
status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_BUFFERING && status.getPlaybackState() != MediaItemStatus.PLAYBACK_STATE_BUFFERING &&
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)
playNext(); playNext();
} }
}); });
} }
new Handler().postDelayed(new Runnable() { new Handler().postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
pollStatus(); pollStatus();
} }
}, 1000); }, 1000);
} }
public void increaseVolume() { public void increaseVolume() {
mMediaRouter.getSelectedRoute().requestUpdateVolume(1); mMediaRouter.getSelectedRoute().requestUpdateVolume(1);
} }
public void decreaseVolume() { public void decreaseVolume() {
mMediaRouter.getSelectedRoute().requestUpdateVolume(-1); mMediaRouter.getSelectedRoute().requestUpdateVolume(-1);
} }
public List<Item> getPlaylist() { public List<Item> getPlaylist() {
return mPlaylist; return mPlaylist;
} }
public void toggleShuffleEnabled() { public void toggleShuffleEnabled() {
mShuffle = !mShuffle; mShuffle = !mShuffle;
} }
public boolean getShuffleEnabled() { public boolean getShuffleEnabled() {
return mShuffle; return mShuffle;
} }
public void toggleRepeatEnabled() { public void toggleRepeatEnabled() {
mRepeat = !mRepeat; mRepeat = !mRepeat;
} }
public boolean getRepeatEnabled() { public boolean getRepeatEnabled() {
return mRepeat; return mRepeat;
} }
@ -440,4 +440,5 @@ public class MediaRouterPlayService extends Service {
public RouteInfo getCurrentRoute() { public RouteInfo getCurrentRoute() {
return mCurrentRoute; return mCurrentRoute;
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -36,14 +36,15 @@ import android.os.Binder;
* *
*/ */
public class MediaRouterPlayServiceBinder extends Binder { public class MediaRouterPlayServiceBinder extends Binder {
MediaRouterPlayService mService; MediaRouterPlayService mService;
public MediaRouterPlayServiceBinder(MediaRouterPlayService service) { public MediaRouterPlayServiceBinder(MediaRouterPlayService service) {
mService = service; mService = service;
} }
public MediaRouterPlayService getService() { public MediaRouterPlayService getService() {
return mService; return mService;
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -66,51 +66,54 @@ import com.github.nutomic.controldlna.R;
* @author Felix Ableitner * @author Felix Ableitner
*/ */
final class Provider extends MediaRouteProvider { final class Provider extends MediaRouteProvider {
// Device has been added. // Device has been added.
// param: Device device // param: Device device
public static final int MSG_RENDERER_ADDED = 1; public static final int MSG_RENDERER_ADDED = 1;
// Device has been removed. // Device has been removed.
// param: int id // param: int id
public static final int MSG_RENDERER_REMOVED = 2; public static final int MSG_RENDERER_REMOVED = 2;
// Playback status information, retrieved after RemotePlayService.MSG_GET_STATUS. // Playback status information, retrieved after RemotePlayService.MSG_GET_STATUS.
// param: bundle media_item_status // param: bundle media_item_status
// param: int hash // param: int hash
public static final int MSG_STATUS_INFO = 3; public static final int MSG_STATUS_INFO = 3;
// Indicates an error in communication between RemotePlayService and renderer. // Indicates an error in communication between RemotePlayService and renderer.
// param: String error // param: String error
public static final int MSG_ERROR = 4; public static final int MSG_ERROR = 4;
/** /**
* Allows passing and storing basic information about a device. * Allows passing and storing basic information about a device.
*/ */
static public class Device implements Parcelable { static public class Device implements Parcelable {
public String id; public String id;
public String name; public String name;
public String description; public String description;
public int volume; public int volume;
public int volumeMax; public int volumeMax;
public static final Parcelable.Creator<Device> CREATOR
= new Parcelable.Creator<Device>() {
public Device createFromParcel(Parcel in) {
return new Device(in);
}
public Device[] newArray(int size) { public static final Parcelable.Creator<Device> CREATOR
return new Device[size]; = new Parcelable.Creator<Device>() {
} public Device createFromParcel(Parcel in) {
}; return new Device(in);
}
public Device[] newArray(int size) {
return new Device[size];
}
};
private Device(Parcel in) { private Device(Parcel in) {
id = in.readString(); id = in.readString();
name = in.readString(); name = in.readString();
description = in.readString(); description = in.readString();
volume = in.readInt(); volume = in.readInt();
volumeMax = in.readInt(); volumeMax = in.readInt();
} }
public Device(String id, String name, String description, int volume, int volumeMax) { public Device(String id, String name, String description, int volume, int volumeMax) {
this.id = id; this.id = id;
this.name = name; this.name = name;
@ -131,271 +134,271 @@ final class Provider extends MediaRouteProvider {
dest.writeString(description); dest.writeString(description);
dest.writeInt(volume); dest.writeInt(volume);
dest.writeInt(volumeMax); dest.writeInt(volumeMax);
} }
} }
private HashMap<String, Device> mDevices = new HashMap<String, Device>();
private SparseArray<Pair<Intent, ControlRequestCallback>> mRequests =
new SparseArray<Pair<Intent, ControlRequestCallback>>();
IRemotePlayService mIRemotePlayService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mIRemotePlayService = IRemotePlayService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) { private HashMap<String, Device> mDevices = new HashMap<String, Device>();
mIRemotePlayService = null;
}
};
private static final ArrayList<IntentFilter> CONTROL_FILTERS; private SparseArray<Pair<Intent, ControlRequestCallback>> mRequests =
static { new SparseArray<Pair<Intent, ControlRequestCallback>>();
IntentFilter f = new IntentFilter();
f.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK); IRemotePlayService mIRemotePlayService;
f.addAction(MediaControlIntent.ACTION_PLAY);
f.addAction(MediaControlIntent.ACTION_PAUSE); private ServiceConnection mConnection = new ServiceConnection() {
f.addAction(MediaControlIntent.ACTION_SEEK); public void onServiceConnected(ComponentName className, IBinder service) {
f.addAction(MediaControlIntent.ACTION_STOP); mIRemotePlayService = IRemotePlayService.Stub.asInterface(service);
f.addDataScheme("http"); }
f.addDataScheme("https");
try { public void onServiceDisconnected(ComponentName className) {
f.addDataType("video/*"); mIRemotePlayService = null;
f.addDataType("audio/*"); }
} catch (MalformedMimeTypeException ex) { };
throw new RuntimeException(ex);
} private static final ArrayList<IntentFilter> CONTROL_FILTERS;
static {
IntentFilter f = new IntentFilter();
f.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
f.addAction(MediaControlIntent.ACTION_PLAY);
f.addAction(MediaControlIntent.ACTION_PAUSE);
f.addAction(MediaControlIntent.ACTION_SEEK);
f.addAction(MediaControlIntent.ACTION_STOP);
f.addDataScheme("http");
f.addDataScheme("https");
try {
f.addDataType("video/*");
f.addDataType("audio/*");
} catch (MalformedMimeTypeException ex) {
throw new RuntimeException(ex);
}
CONTROL_FILTERS = new ArrayList<IntentFilter>();
CONTROL_FILTERS.add(f);
}
CONTROL_FILTERS = new ArrayList<IntentFilter>();
CONTROL_FILTERS.add(f);
}
/** /**
* Listens for messages about devices. * Listens for messages about devices.
*/ */
static private class DeviceListener extends Handler { static private class DeviceListener extends Handler {
private final WeakReference<Provider> mService;
DeviceListener(Provider provider) { private final WeakReference<Provider> mService;
mService = new WeakReference<Provider>(provider);
}
@Override
public void handleMessage(Message msg) {
if (mService.get() != null)
mService.get().handleMessage(msg);
}
}
final Messenger mListener = new Messenger(new DeviceListener(this));
public Provider(Context context) { DeviceListener(Provider provider) {
super(context); mService = new WeakReference<Provider>(provider);
context.bindService( }
new Intent(context, RemotePlayService.class),
mConnection, @Override
Context.BIND_AUTO_CREATE public void handleMessage(Message msg) {
); if (mService.get() != null)
} mService.get().handleMessage(msg);
}
}
final Messenger mListener = new Messenger(new DeviceListener(this));
public Provider(Context context) {
super(context);
context.bindService(
new Intent(context, RemotePlayService.class),
mConnection,
Context.BIND_AUTO_CREATE
);
}
public void close() { public void close() {
getContext().unbindService(mConnection); getContext().unbindService(mConnection);
} }
@Override @Override
public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
try { try {
if (request != null && request.isActiveScan()) if (request != null && request.isActiveScan())
mIRemotePlayService.startSearch(mListener); mIRemotePlayService.startSearch(mListener);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
public RouteController onCreateRouteController(String routeId) { public RouteController onCreateRouteController(String routeId) {
return new RouteController(routeId); return new RouteController(routeId);
} }
private void updateRoutes() { private void updateRoutes() {
Builder builder = new Builder(); Builder builder = new Builder();
for (Entry<String, Device> d : mDevices.entrySet()) { for (Entry<String, Device> d : mDevices.entrySet()) {
MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder( MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
d.getValue().id, d.getValue().id,
d.getValue().name) d.getValue().name)
.setDescription(getContext().getResources() .setDescription(getContext().getResources()
.getString(R.string.route_description)) .getString(R.string.route_description))
.addControlFilters(CONTROL_FILTERS) .addControlFilters(CONTROL_FILTERS)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE) .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(d.getValue().volumeMax) .setVolumeMax(d.getValue().volumeMax)
.setVolume(d.getValue().volume) .setVolume(d.getValue().volume)
.build(); .build();
builder.addRoute(routeDescriptor); builder.addRoute(routeDescriptor);
} }
setDescriptor(builder.build()); setDescriptor(builder.build());
} }
/** /**
* Receives and forwards device selections, volume change * Receives and forwards device selections, volume change
* requests and control requests. * requests and control requests.
*/ */
private final class RouteController extends MediaRouteProvider.RouteController { private final class RouteController extends MediaRouteProvider.RouteController {
private final String mRouteId;
public RouteController(String routeId) { private final String mRouteId;
mRouteId = routeId;
}
@Override public RouteController(String routeId) {
public void onRelease() { mRouteId = routeId;
} }
@Override @Override
public void onSelect() { public void onRelease() {
try { }
@Override
public void onSelect() {
try {
mIRemotePlayService.selectRenderer(mRouteId); mIRemotePlayService.selectRenderer(mRouteId);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
public void onUnselect() { public void onUnselect() {
try { try {
mIRemotePlayService.unselectRenderer(mRouteId); mIRemotePlayService.unselectRenderer(mRouteId);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
public void onSetVolume(int volume) { public void onSetVolume(int volume) {
if (volume < 0 || volume > mDevices.get(mRouteId).volumeMax) if (volume < 0 || volume > mDevices.get(mRouteId).volumeMax)
return; return;
try { try {
mIRemotePlayService.setVolume(volume); mIRemotePlayService.setVolume(volume);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
mDevices.get(mRouteId).volume = volume; mDevices.get(mRouteId).volume = volume;
updateRoutes(); updateRoutes();
} }
@Override @Override
public void onUpdateVolume(int delta) { public void onUpdateVolume(int delta) {
onSetVolume(mDevices.get(mRouteId).volume + delta); onSetVolume(mDevices.get(mRouteId).volume + delta);
} }
/** /**
* Handles play, pause, resume, stop, seek and get_status requests for this route. * Handles play, pause, resume, stop, seek and get_status requests for this route.
*/ */
@Override @Override
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
try { try {
if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) { if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
String metadata = (intent.hasExtra(MediaControlIntent.EXTRA_ITEM_METADATA)) String metadata = (intent.hasExtra(MediaControlIntent.EXTRA_ITEM_METADATA))
? intent.getExtras().getString(MediaControlIntent.EXTRA_ITEM_METADATA) ? intent.getExtras().getString(MediaControlIntent.EXTRA_ITEM_METADATA)
: null; : null;
mIRemotePlayService.play(intent.getDataString(), metadata); mIRemotePlayService.play(intent.getDataString(), metadata);
// Store in intent extras for later. // Store in intent extras for later.
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mRouteId); intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mRouteId);
intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, intent.getDataString()); intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, intent.getDataString());
getItemStatus(intent, callback); getItemStatus(intent, callback);
return true; return true;
} }
else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) { else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) {
mIRemotePlayService.pause(mRouteId); mIRemotePlayService.pause(mRouteId);
return true; return true;
} }
else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) { else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) {
mIRemotePlayService.resume(mRouteId); mIRemotePlayService.resume(mRouteId);
return true; return true;
} }
else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) { else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) {
mIRemotePlayService.stop(mRouteId); mIRemotePlayService.stop(mRouteId);
return true; return true;
} }
else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) { else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) {
mIRemotePlayService.seek(mRouteId, mIRemotePlayService.seek(mRouteId,
intent.getStringExtra( intent.getStringExtra(
MediaControlIntent.EXTRA_ITEM_ID), MediaControlIntent.EXTRA_ITEM_ID),
intent.getLongExtra( intent.getLongExtra(
MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0)); MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
getItemStatus(intent, callback); getItemStatus(intent, callback);
return true; return true;
} }
else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) { else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) {
getItemStatus(intent, callback); getItemStatus(intent, callback);
return true; return true;
} }
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
return false; return false;
} }
}
/**
* Requests status info via RemotePlayService, stores intent and callback to
* access later in handleMessage.
*/
private void getItemStatus(Intent intent, ControlRequestCallback callback)
throws RemoteException {
if (callback == null)
return;
Pair<Intent, ControlRequestCallback> pair = }
new Pair<Intent, ControlRequestCallback>(intent, callback);
int r = new Random().nextInt(); /**
mRequests.put(r, pair); * Requests status info via RemotePlayService, stores intent and callback to
mIRemotePlayService.getItemStatus( * access later in handleMessage.
intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID), */
intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID), private void getItemStatus(Intent intent, ControlRequestCallback callback)
r); throws RemoteException {
} if (callback == null)
return;
/**
* Handles device add and remove as well as sending status info requested earlier. Pair<Intent, ControlRequestCallback> pair =
*/ new Pair<Intent, ControlRequestCallback>(intent, callback);
public void handleMessage(Message msg) { int r = new Random().nextInt();
Bundle data = msg.getData(); mRequests.put(r, pair);
mIRemotePlayService.getItemStatus(
intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID),
intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID),
r);
}
/**
* Handles device add and remove as well as sending status info requested earlier.
*/
public void handleMessage(Message msg) {
Bundle data = msg.getData();
switch (msg.what) { switch (msg.what) {
case MSG_RENDERER_ADDED: case MSG_RENDERER_ADDED:
msg.getData().setClassLoader(Device.class.getClassLoader()); msg.getData().setClassLoader(Device.class.getClassLoader());
Device device = (Device) data.getParcelable("device"); Device device = (Device) data.getParcelable("device");
mDevices.put(device.id, device); mDevices.put(device.id, device);
updateRoutes(); updateRoutes();
break; break;
case MSG_RENDERER_REMOVED: case MSG_RENDERER_REMOVED:
mDevices.remove(data.getString("id")); mDevices.remove(data.getString("id"));
updateRoutes(); updateRoutes();
break; break;
case MSG_STATUS_INFO: case MSG_STATUS_INFO:
Pair<Intent, ControlRequestCallback> pair = Pair<Intent, ControlRequestCallback> pair =
mRequests.get(data.getInt("hash")); mRequests.get(data.getInt("hash"));
Bundle status = data.getBundle("media_item_status"); Bundle status = data.getBundle("media_item_status");
if (pair.first.hasExtra(MediaControlIntent.EXTRA_SESSION_ID)) { if (pair.first.hasExtra(MediaControlIntent.EXTRA_SESSION_ID))
status.putString(MediaControlIntent.EXTRA_SESSION_ID, status.putString(MediaControlIntent.EXTRA_SESSION_ID,
pair.first.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID)); pair.first.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID));
} if (pair.first.hasExtra(MediaControlIntent.EXTRA_ITEM_ID))
if (pair.first.hasExtra(MediaControlIntent.EXTRA_ITEM_ID)) { status.putString(MediaControlIntent.EXTRA_ITEM_ID,
status.putString(MediaControlIntent.EXTRA_ITEM_ID, pair.first.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID));
pair.first.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID)); pair.second.onResult(status);
} break;
pair.second.onResult(status);
break;
case MSG_ERROR: case MSG_ERROR:
Toast.makeText(getContext(), data.getString("error"), Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), data.getString("error"), Toast.LENGTH_SHORT).show();
break; break;
} }
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -33,18 +33,19 @@ import android.support.v7.media.MediaRouteProviderService;
public class ProviderService extends MediaRouteProviderService { public class ProviderService extends MediaRouteProviderService {
private Provider mProvider; private Provider mProvider;
@Override @Override
public MediaRouteProvider onCreateMediaRouteProvider() { public MediaRouteProvider onCreateMediaRouteProvider() {
if (mProvider == null) { if (mProvider == null)
mProvider = new Provider(this); mProvider = new Provider(this);
}
return mProvider; return mProvider;
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
mProvider.close(); mProvider.close();
mProvider = null; mProvider = null;
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -61,7 +61,6 @@ import android.os.Messenger;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
/** /**
* Allows UPNP playback from different apps by providing a proxy interface. * Allows UPNP playback from different apps by providing a proxy interface.
* You can communicate to this service via RemotePlayServiceBinder. * You can communicate to this service via RemotePlayServiceBinder.
@ -72,139 +71,133 @@ import android.util.Log;
public class RemotePlayService extends Service implements RegistryListener { public class RemotePlayService extends Service implements RegistryListener {
private static final String TAG = "RemotePlayService"; private static final String TAG = "RemotePlayService";
Messenger mListener;
ConcurrentHashMap<String, Device<?, ?, ?>> mDevices =
new ConcurrentHashMap<String, Device<?, ?, ?>>();
protected AndroidUpnpService mUpnpService;
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() { Messenger mListener;
/** ConcurrentHashMap<String, Device<?, ?, ?>> mDevices =
* Registers DeviceListener, adds known devices and starts search if requested. new ConcurrentHashMap<String, Device<?, ?, ?>>();
*/
protected AndroidUpnpService mUpnpService;
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
/**
* Registers DeviceListener, adds known devices and starts search if requested.
*/
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service; mUpnpService = (AndroidUpnpService) service;
mUpnpService.getRegistry().addListener(RemotePlayService.this); mUpnpService.getRegistry().addListener(RemotePlayService.this);
for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) { for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices())
if (d instanceof LocalDevice) if (d instanceof LocalDevice)
localDeviceAdded(mUpnpService.getRegistry(), (LocalDevice) d); localDeviceAdded(mUpnpService.getRegistry(), (LocalDevice) d);
else else
remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d); remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
} mUpnpService.getControlPoint().search();
mUpnpService.getControlPoint().search(); }
}
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
mUpnpService = null; mUpnpService = null;
} }
}; };
/**
* All active binders. The Hashmap value is unused.
*/
WeakHashMap<RemotePlayServiceBinder, Boolean> mBinders =
new WeakHashMap<RemotePlayServiceBinder, Boolean>();
/**
* All active binders. The Hashmap value is unused.
*/
WeakHashMap<RemotePlayServiceBinder, Boolean> mBinders =
new WeakHashMap<RemotePlayServiceBinder, Boolean>();
@Override @Override
public IBinder onBind(Intent itnent) { public IBinder onBind(Intent itnent) {
RemotePlayServiceBinder b = new RemotePlayServiceBinder(this); RemotePlayServiceBinder b = new RemotePlayServiceBinder(this);
mBinders.put(b, true); mBinders.put(b, true);
return b; return b;
} }
/** /**
* Binds to cling service, registers wifi state change listener. * Binds to cling service, registers wifi state change listener.
*/ */
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
bindService( bindService(
new Intent(this, AndroidUpnpServiceImpl.class), new Intent(this, AndroidUpnpServiceImpl.class),
mUpnpServiceConnection, mUpnpServiceConnection,
Context.BIND_AUTO_CREATE Context.BIND_AUTO_CREATE
); );
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mWifiReceiver, filter); registerReceiver(mWifiReceiver, filter);
} }
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
unbindService(mUpnpServiceConnection); unbindService(mUpnpServiceConnection);
unregisterReceiver(mWifiReceiver); unregisterReceiver(mWifiReceiver);
} }
/** /**
* Sends msg via Messenger to Provider. * Sends msg via Messenger to Provider.
*/ */
void sendMessage(Message msg) { void sendMessage(Message msg) {
try { try {
mListener.send(msg); mListener.send(msg);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
/** /**
* Sends the error as a message via Messenger. * Sends the error as a message via Messenger.
* @param error * @param error
*/ */
void sendError(String error) { void sendError(String error) {
Message msg = Message.obtain(null, Provider.MSG_ERROR, 0, 0); Message msg = Message.obtain(null, Provider.MSG_ERROR, 0, 0);
msg.getData().putString("error", error); msg.getData().putString("error", error);
sendMessage(msg); sendMessage(msg);
} }
/** /**
* Starts device search on wifi connect, removes unreachable * Starts device search on wifi connect, removes unreachable
* devices on wifi disconnect. * devices on wifi disconnect.
*/ */
private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() { private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
ConnectivityManager connManager = (ConnectivityManager) ConnectivityManager connManager = (ConnectivityManager)
getSystemService(CONNECTIVITY_SERVICE); getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); NetworkInfo wifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifi.isConnected()) { if (wifi.isConnected()) {
if (mUpnpService != null) { if (mUpnpService != null) {
for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices())
deviceAdded(d); deviceAdded(d);
mUpnpService.getControlPoint().search(); mUpnpService.getControlPoint().search();
} }
} } else
else { for (Entry<String, Device<?, ?, ?>> d : mDevices.entrySet())
for (Entry<String, Device<?, ?, ?>> d : mDevices.entrySet()) { if (mUpnpService.getControlPoint().getRegistry()
if (mUpnpService.getControlPoint().getRegistry() .getDevice(new UDN(d.getKey()), false) == null) {
.getDevice(new UDN(d.getKey()), false) == null) { deviceRemoved(d.getValue());
deviceRemoved(d.getValue()); for (RemotePlayServiceBinder b : mBinders.keySet())
for (RemotePlayServiceBinder b : mBinders.keySet()) { if (b.mCurrentRenderer.equals(d.getValue())) {
if (b.mCurrentRenderer.equals(d.getValue())) { b.mSubscriptionCallback.end();
b.mSubscriptionCallback.end(); b.mCurrentRenderer = null;
b.mCurrentRenderer = null; }
} }
} }
}
}
}
}
}; };
/** /**
* Returns a device service by name for direct queries. * Returns a device service by name for direct queries.
*/ */
org.teleal.cling.model.meta.Service<?, ?> getService( org.teleal.cling.model.meta.Service<?, ?> getService(
Device<?, ?, ?> device, String name) { Device<?, ?, ?> device, String name) {
return device.findService( return device.findService(
new ServiceType("schemas-upnp-org", name)); new ServiceType("schemas-upnp-org", name));
} }
/** /**
@ -213,52 +206,52 @@ public class RemotePlayService extends Service implements RegistryListener {
private void deviceAdded(final Device<?, ?, ?> device) { private void deviceAdded(final Device<?, ?, ?> device) {
if (mDevices.containsValue(device)) if (mDevices.containsValue(device))
return; return;
final org.teleal.cling.model.meta.Service<?, ?> rc = final org.teleal.cling.model.meta.Service<?, ?> rc =
getService(device, "RenderingControl"); getService(device, "RenderingControl");
if (rc == null || mListener == null) if (rc == null || mListener == null)
return; return;
if (device.getType().getType().equals("MediaRenderer") &&
device instanceof RemoteDevice) {
mDevices.put(device.getIdentity().getUdn().toString(), device);
try { if (device.getType().getType().equals("MediaRenderer") &&
mUpnpService.getControlPoint().execute(new GetVolume(rc) { device instanceof RemoteDevice) {
mDevices.put(device.getIdentity().getUdn().toString(), device);
@SuppressWarnings("rawtypes")
@Override try {
public void failure(ActionInvocation invocation, mUpnpService.getControlPoint().execute(new GetVolume(rc) {
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Failed to get current Volume: " + defaultMessage); @SuppressWarnings("rawtypes")
sendError("Failed to get current Volume: " + defaultMessage); @Override
} public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
@SuppressWarnings("rawtypes") Log.w(TAG, "Failed to get current Volume: " + defaultMessage);
@Override sendError("Failed to get current Volume: " + defaultMessage);
public void received(ActionInvocation invocation, int currentVolume) { }
int maxVolume = 100;
if (rc.getStateVariable("Volume") != null) { @SuppressWarnings("rawtypes")
StateVariableAllowedValueRange volumeRange = @Override
rc.getStateVariable("Volume").getTypeDetails().getAllowedValueRange(); public void received(ActionInvocation invocation, int currentVolume) {
maxVolume = (int) volumeRange.getMaximum(); int maxVolume = 100;
} if (rc.getStateVariable("Volume") != null) {
StateVariableAllowedValueRange volumeRange =
Message msg = Message.obtain(null, Provider.MSG_RENDERER_ADDED, 0, 0); rc.getStateVariable("Volume").getTypeDetails().getAllowedValueRange();
msg.getData().putParcelable("device", new Provider.Device( maxVolume = (int) volumeRange.getMaximum();
device.getIdentity().getUdn().toString(), }
device.getDisplayString(),
device.getDetails().getManufacturerDetails().getManufacturer(), Message msg = Message.obtain(null, Provider.MSG_RENDERER_ADDED, 0, 0);
currentVolume, msg.getData().putParcelable("device", new Provider.Device(
maxVolume)); device.getIdentity().getUdn().toString(),
sendMessage(msg); device.getDisplayString(),
} device.getDetails().getManufacturerDetails().getManufacturer(),
}); currentVolume,
} maxVolume));
catch (IllegalArgumentException e) { sendMessage(msg);
e.printStackTrace(); }
return; });
} }
catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
} }
} }
@ -266,25 +259,25 @@ public class RemotePlayService extends Service implements RegistryListener {
* Remove the device from Provider. * Remove the device from Provider.
*/ */
private void deviceRemoved(Device<?, ?, ?> device) { private void deviceRemoved(Device<?, ?, ?> device) {
if (device.getType().getType().equals("MediaRenderer") && if (device.getType().getType().equals("MediaRenderer") &&
device instanceof RemoteDevice) { device instanceof RemoteDevice) {
Message msg = Message.obtain(null, Provider.MSG_RENDERER_REMOVED, 0, 0); Message msg = Message.obtain(null, Provider.MSG_RENDERER_REMOVED, 0, 0);
String udn = device.getIdentity().getUdn().toString(); String udn = device.getIdentity().getUdn().toString();
msg.getData().putString("id", udn); msg.getData().putString("id", udn);
mDevices.remove(udn); mDevices.remove(udn);
sendMessage(msg); sendMessage(msg);
} }
} }
/** /**
* If a device was updated, we just add it again (devices are stored in * If a device was updated, we just add it again (devices are stored in
* maps, so adding the same one again just overwrites the old one). * maps, so adding the same one again just overwrites the old one).
*/ */
private void deviceUpdated(Device<?, ?, ?> device) { private void deviceUpdated(Device<?, ?, ?> device) {
deviceAdded(device); deviceAdded(device);
} }
@Override @Override
public void afterShutdown() { public void afterShutdown() {
} }
@ -327,4 +320,5 @@ public class RemotePlayService extends Service implements RegistryListener {
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) { public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
deviceUpdated(device); deviceUpdated(device);
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -66,40 +66,39 @@ import android.util.Log;
* *
*/ */
public class RemotePlayServiceBinder extends IRemotePlayService.Stub { public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
private static final String TAG = "RemotePlayServiceBinder"; private static final String TAG = "RemotePlayServiceBinder";
Device<?, ?, ?> mCurrentRenderer; Device<?, ?, ?> mCurrentRenderer;
private int mPlaybackState; private int mPlaybackState;
private boolean mManuallyStopped; private boolean mManuallyStopped;
SubscriptionCallback mSubscriptionCallback; SubscriptionCallback mSubscriptionCallback;
private RemotePlayService mRps; private RemotePlayService mRps;
public RemotePlayServiceBinder(RemotePlayService rps) { public RemotePlayServiceBinder(RemotePlayService rps) {
mRps = rps; mRps = rps;
} }
@Override @Override
public void startSearch(Messenger listener) public void startSearch(Messenger listener)
throws RemoteException { throws RemoteException {
mRps.mListener = listener; mRps.mListener = listener;
} }
@Override @Override
public void selectRenderer(String id) throws RemoteException { public void selectRenderer(String id) throws RemoteException {
mCurrentRenderer = mRps.mDevices.get(id); mCurrentRenderer = mRps.mDevices.get(id);
for (RemotePlayServiceBinder b : mRps.mBinders.keySet()) { for (RemotePlayServiceBinder b : mRps.mBinders.keySet())
if (b != this && mCurrentRenderer.equals(b.mCurrentRenderer)) if (b != this && mCurrentRenderer.equals(b.mCurrentRenderer))
b.unselectRenderer(""); b.unselectRenderer("");
}
mSubscriptionCallback = new SubscriptionCallback( mSubscriptionCallback = new SubscriptionCallback(
mCurrentRenderer.findService( mCurrentRenderer.findService(
new ServiceType("schemas-upnp-org", "AVTransport")), 600) { new ServiceType("schemas-upnp-org", "AVTransport")), 600) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
@ -109,69 +108,69 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void ended(GENASubscription sub, CancelReason reason, protected void ended(GENASubscription sub, CancelReason reason,
UpnpResponse response) { UpnpResponse response) {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void eventReceived(final GENASubscription sub) { protected void eventReceived(final GENASubscription sub) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, StateVariableValue> m = sub.getCurrentValues(); Map<String, StateVariableValue> m = sub.getCurrentValues();
try { try {
LastChange lastChange = new LastChange( LastChange lastChange = new LastChange(
new AVTransportLastChangeParser(), new AVTransportLastChangeParser(),
m.get("LastChange").toString()); m.get("LastChange").toString());
if (lastChange.getEventedValue(0, if (lastChange.getEventedValue(0,
AVTransportVariable.TransportState.class) == null) AVTransportVariable.TransportState.class) == null)
return; return;
switch (lastChange.getEventedValue(0, switch (lastChange.getEventedValue(0,
AVTransportVariable.TransportState.class) AVTransportVariable.TransportState.class)
.getValue()) { .getValue()) {
case PLAYING: case PLAYING:
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
break; break;
case PAUSED_PLAYBACK: case PAUSED_PLAYBACK:
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PAUSED; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PAUSED;
break; break;
case STOPPED: case STOPPED:
if (mManuallyStopped) { if (mManuallyStopped) {
mManuallyStopped = false; mManuallyStopped = false;
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_CANCELED; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
} }
else else
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
break; break;
case TRANSITIONING: case TRANSITIONING:
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
break; break;
case NO_MEDIA_PRESENT: case NO_MEDIA_PRESENT:
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_ERROR; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_ERROR;
break; break;
default: default:
} }
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Failed to parse UPNP event", e); Log.w(TAG, "Failed to parse UPNP event", e);
mRps.sendError("Failed to parse UPNP event"); mRps.sendError("Failed to parse UPNP event");
} }
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void eventsMissed(GENASubscription sub, protected void eventsMissed(GENASubscription sub,
int numberOfMissedEvents) { int numberOfMissedEvents) {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void failed(GENASubscription sub, UpnpResponse responseStatus, protected void failed(GENASubscription sub, UpnpResponse responseStatus,
Exception exception, String defaultMsg) { Exception exception, String defaultMsg) {
Log.w(TAG, "Register Subscription Callback failed: " + defaultMsg, exception); Log.w(TAG, "Register Subscription Callback failed: " + defaultMsg, exception);
mRps.sendError("Register Subscription Callback failed: " + defaultMsg); mRps.sendError("Register Subscription Callback failed: " + defaultMsg);
} }
}; };
mRps.mUpnpService.getControlPoint().execute(mSubscriptionCallback); mRps.mUpnpService.getControlPoint().execute(mSubscriptionCallback);
} }
/** /**
@ -183,70 +182,70 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
stop(sessionId); stop(sessionId);
if (mSubscriptionCallback != null) if (mSubscriptionCallback != null)
mSubscriptionCallback.end(); mSubscriptionCallback.end();
mCurrentRenderer = null; mCurrentRenderer = null;
} }
/** /**
* Sets an absolute volume. The value is assumed to be within the valid * Sets an absolute volume. The value is assumed to be within the valid
* volume range. * volume range.
*/ */
@Override @Override
public void setVolume(int volume) throws RemoteException { public void setVolume(int volume) throws RemoteException {
mRps.mUpnpService.getControlPoint().execute( mRps.mUpnpService.getControlPoint().execute(
new SetVolume(mRps.getService(mCurrentRenderer, new SetVolume(mRps.getService(mCurrentRenderer,
"RenderingControl"), volume) { "RenderingControl"), volume) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Set volume failed: " + defaultMessage); Log.w(TAG, "Set volume failed: " + defaultMessage);
mRps.sendError("Set volume failed: " + defaultMessage); mRps.sendError("Set volume failed: " + defaultMessage);
} }
}); });
} }
/** /**
* Sets playback source and metadata, then starts playing on * Sets playback source and metadata, then starts playing on
* current renderer. * current renderer.
*/ */
@Override @Override
public void play(String uri, String metadata) throws RemoteException { public void play(String uri, String metadata) throws RemoteException {
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_BUFFERING; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_BUFFERING;
mRps.mUpnpService.getControlPoint().execute(new SetAVTransportURI( mRps.mUpnpService.getControlPoint().execute(new SetAVTransportURI(
mRps.getService(mCurrentRenderer, "AVTransport"), mRps.getService(mCurrentRenderer, "AVTransport"),
uri, metadata) { uri, metadata) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMsg) { UpnpResponse operation, String defaultMsg) {
Log.w(TAG, "Set URI failed: " + defaultMsg); Log.w(TAG, "Set URI failed: " + defaultMsg);
mRps.sendError("Set URI failed: " + defaultMsg); mRps.sendError("Set URI failed: " + defaultMsg);
}
@SuppressWarnings("rawtypes")
@Override
public void success(ActionInvocation invocation) {
mRps.mUpnpService.getControlPoint().execute(
new Play(mRps.getService(mCurrentRenderer,
"AVTransport")) {
@Override
public void success(ActionInvocation invocation) {
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
}
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Play failed: " + defaultMessage);
mRps.sendError("Play failed: " + defaultMessage);
}
});
} }
});
@SuppressWarnings("rawtypes")
@Override
public void success(ActionInvocation invocation) {
mRps.mUpnpService.getControlPoint().execute(
new Play(mRps.getService(mCurrentRenderer,
"AVTransport")) {
@Override
public void success(ActionInvocation invocation) {
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
}
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Play failed: " + defaultMessage);
mRps.sendError("Play failed: " + defaultMessage);
}
});
}
});
} }
/** /**
* Pauses playback on current renderer. * Pauses playback on current renderer.
*/ */
@ -254,37 +253,37 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
public void pause(final String sessionId) throws RemoteException { public void pause(final String sessionId) throws RemoteException {
mRps.mUpnpService.getControlPoint().execute( mRps.mUpnpService.getControlPoint().execute(
new Pause(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) { new Pause(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Pause failed, trying stop: " + defaultMessage); Log.w(TAG, "Pause failed, trying stop: " + defaultMessage);
mRps.sendError("Pause failed, trying stop: " + defaultMessage); mRps.sendError("Pause failed, trying stop: " + defaultMessage);
// Sometimes stop works even though pause does not. // Sometimes stop works even though pause does not.
try { try {
stop(sessionId); stop(sessionId);
} catch (RemoteException e) { } catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
}); });
} }
@Override @Override
public void resume(String sessionId) throws RemoteException { public void resume(String sessionId) throws RemoteException {
mRps.mUpnpService.getControlPoint().execute( mRps.mUpnpService.getControlPoint().execute(
new Play(mRps.getService(mRps.mDevices.get(sessionId), new Play(mRps.getService(mRps.mDevices.get(sessionId),
"AVTransport")) { "AVTransport")) {
@Override @Override
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Resume failed: " + defaultMessage); Log.w(TAG, "Resume failed: " + defaultMessage);
mRps.sendError("Resume failed: " + defaultMessage); mRps.sendError("Resume failed: " + defaultMessage);
} }
}); });
} }
/** /**
@ -294,44 +293,44 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
public void stop(String sessionId) throws RemoteException { public void stop(String sessionId) throws RemoteException {
mManuallyStopped = true; mManuallyStopped = true;
mRps.mUpnpService.getControlPoint().execute( mRps.mUpnpService.getControlPoint().execute(
new Stop(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) { new Stop(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
org.teleal.cling.model.message.UpnpResponse operation,
String defaultMessage) {
Log.w(TAG, "Stop failed: " + defaultMessage);
mRps.sendError("Stop failed: " + defaultMessage);
}
});
}
/**
* Seeks to the given absolute time in seconds.
*/
@Override
public void seek(String sessionId, String itemId, long milliseconds)
throws RemoteException {
mRps.mUpnpService.getControlPoint().execute(new Seek(
mRps.getService(mRps.mDevices.get(sessionId), "AVTransport"),
SeekMode.REL_TIME,
Integer.toString((int) milliseconds / 1000)) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
org.teleal.cling.model.message.UpnpResponse operation,
String defaultMessage) {
Log.w(TAG, "Stop failed: " + defaultMessage);
mRps.sendError("Stop failed: " + defaultMessage);
}
});
}
/**
* Seeks to the given absolute time in seconds.
*/
@Override
public void seek(String sessionId, String itemId, long milliseconds)
throws RemoteException {
mRps.mUpnpService.getControlPoint().execute(new Seek(
mRps.getService(mRps.mDevices.get(sessionId), "AVTransport"),
SeekMode.REL_TIME,
Integer.toString((int) milliseconds / 1000)) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Seek failed: " + defaultMessage); Log.w(TAG, "Seek failed: " + defaultMessage);
mRps.sendError("Seek failed: " + defaultMessage); mRps.sendError("Seek failed: " + defaultMessage);
} }
}); });
} }
/** /**
* Sends a message with current status for the route and item. * Sends a message with current status for the route and item.
* *
* If itemId does not match with the item currently played, * If itemId does not match with the item currently played,
* MediaItemStatus.PLAYBACK_STATE_INVALIDATED is returned. * MediaItemStatus.PLAYBACK_STATE_INVALIDATED is returned.
* *
* @param sessionId Identifier of the session (equivalent to route) to get info for. * @param sessionId Identifier of the session (equivalent to route) to get info for.
@ -339,42 +338,41 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
* @param requestHash Passed back in message to find original request object. * @param requestHash Passed back in message to find original request object.
*/ */
@Override @Override
public void getItemStatus(String sessionId, final String itemId, final int requestHash) public void getItemStatus(String sessionId, final String itemId, final int requestHash)
throws RemoteException { throws RemoteException {
mRps.mUpnpService.getControlPoint().execute(new GetPositionInfo( mRps.mUpnpService.getControlPoint().execute(new GetPositionInfo(
mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) { mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Get position failed: " + defaultMessage); Log.w(TAG, "Get position failed: " + defaultMessage);
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void received(ActionInvocation invocation, PositionInfo positionInfo) { public void received(ActionInvocation invocation, PositionInfo positionInfo) {
if (positionInfo.getTrackURI() == null) if (positionInfo.getTrackURI() == null)
return; return;
Message msg = Message.obtain(null, Provider.MSG_STATUS_INFO, 0, 0); Message msg = Message.obtain(null, Provider.MSG_STATUS_INFO, 0, 0);
Builder status = null; Builder status = null;
if (positionInfo.getTrackURI().equals(itemId)) { if (positionInfo.getTrackURI().equals(itemId))
status = new MediaItemStatus.Builder(mPlaybackState) status = new MediaItemStatus.Builder(mPlaybackState)
.setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000) .setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000)
.setContentDuration(positionInfo.getTrackDurationSeconds() * 1000) .setContentDuration(positionInfo.getTrackDurationSeconds() * 1000)
.setTimestamp(positionInfo.getAbsCount()); .setTimestamp(positionInfo.getAbsCount());
} else
else { status = new MediaItemStatus.Builder(
status = new MediaItemStatus.Builder( MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
} msg.getData().putBundle("media_item_status", status.build().asBundle());
msg.getData().putInt("hash", requestHash);
msg.getData().putBundle("media_item_status", status.build().asBundle()); mRps.sendMessage(msg);
msg.getData().putInt("hash", requestHash); }
mRps.sendMessage(msg);
}
}); });
} }
}; };

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -47,27 +47,26 @@ import android.widget.TextView;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
/** /**
* Displays the devices that are inserted through the RegistryListener (either * Displays the devices that are inserted through the RegistryListener (either
* of type RENDERER or SERVER). * of type RENDERER or SERVER).
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>> public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
implements RegistryListener { implements RegistryListener {
private static final String TAG = "DeviceArrayAdapter"; private static final String TAG = "DeviceArrayAdapter";
public static final String RENDERER = "MediaRenderer"; public static final String RENDERER = "MediaRenderer";
public static final String SERVER = "MediaServer"; public static final String SERVER = "MediaServer";
private Activity mActivity; private Activity mActivity;
private String mDeviceType; private String mDeviceType;
/** /**
* @param deviceType One of RENDERER or SERVER. * @param deviceType One of RENDERER or SERVER.
*/ */
@ -76,46 +75,44 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
mActivity = activity; mActivity = activity;
mDeviceType = deviceType; mDeviceType = deviceType;
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false); convertView = inflater.inflate(R.layout.list_item, parent, false);
} }
TextView tv = (TextView) convertView.findViewById(R.id.title); TextView tv = (TextView) convertView.findViewById(R.id.title);
RemoteImageView image = RemoteImageView image =
(RemoteImageView) convertView.findViewById(R.id.image); (RemoteImageView) convertView.findViewById(R.id.image);
tv.setText(getItem(position).getDetails().getFriendlyName()); tv.setText(getItem(position).getDetails().getFriendlyName());
if (getItem(position).hasIcons()) { if (getItem(position).hasIcons()) {
URI uri = getItem(position).getIcons()[0].getUri(); URI uri = getItem(position).getIcons()[0].getUri();
if (getItem(position) instanceof RemoteDevice) { if (getItem(position) instanceof RemoteDevice)
try { try {
RemoteDevice device = (RemoteDevice) getItem(position); RemoteDevice device = (RemoteDevice) getItem(position);
uri = device.normalizeURI(uri).toURI(); uri = device.normalizeURI(uri).toURI();
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
Log.w(TAG, "Failed to get device icon URI", e); Log.w(TAG, "Failed to get device icon URI", e);
} }
}
image.setImageUri(uri); image.setImageUri(uri);
} }
return convertView; return convertView;
} }
/** /**
* Adds a new device to the list if its type equals mDeviceType. * Adds a new device to the list if its type equals mDeviceType.
*/ */
public void deviceAdded(final Device<?, ?, ?> device) { public void deviceAdded(final Device<?, ?, ?> device) {
for (int i = 0; i < getCount(); i++) { for (int i = 0; i < getCount(); i++)
if (getItem(i).equals(device)) if (getItem(i).equals(device))
return; return;
}
mActivity.runOnUiThread(new Runnable() { mActivity.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (device.getType().getType().equals(mDeviceType)) if (device.getType().getType().equals(mDeviceType))
@ -124,18 +121,18 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
}); });
} }
/** /**
* Removes the device from the list (if it is an element). * Removes the device from the list (if it is an element).
*/ */
public void deviceRemoved(final Device<?, ?, ?> device) { public void deviceRemoved(final Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() { mActivity.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (getPosition(device) != -1) if (getPosition(device) != -1)
remove(device); remove(device);
} }
}); });
} }
@Override @Override
@ -179,4 +176,5 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
@Override @Override
public void afterShutdown() { public void afterShutdown() {
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -51,7 +51,7 @@ import com.github.nutomic.controldlna.R;
* *
*/ */
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> { public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
/** /**
* Provides sorting of elements by track number. * Provides sorting of elements by track number.
*/ */
@ -61,7 +61,7 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
@Override @Override
public int compare(DIDLObject lhs, DIDLObject rhs) { public int compare(DIDLObject lhs, DIDLObject rhs) {
if (lhs instanceof MusicTrack && rhs instanceof MusicTrack) if (lhs instanceof MusicTrack && rhs instanceof MusicTrack)
return ((MusicTrack) rhs).getOriginalTrackNumber() - return ((MusicTrack) rhs).getOriginalTrackNumber() -
((MusicTrack) lhs).getOriginalTrackNumber(); ((MusicTrack) lhs).getOriginalTrackNumber();
else if (lhs instanceof Item && rhs instanceof Container) else if (lhs instanceof Item && rhs instanceof Container)
@ -75,37 +75,37 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
} }
}); });
} }
/** /**
* Returns a view with folder/media title, and artist name (for audio only). * Returns a view with folder/media title, and artist name (for audio only).
*/ */
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false); convertView = inflater.inflate(R.layout.list_item, parent, false);
} }
DIDLObject item = getItem(position); DIDLObject item = getItem(position);
TextView title = (TextView) convertView.findViewById(R.id.title); TextView title = (TextView) convertView.findViewById(R.id.title);
TextView artist = (TextView) convertView.findViewById(R.id.subtitle); TextView artist = (TextView) convertView.findViewById(R.id.subtitle);
artist.setText(""); artist.setText("");
RemoteImageView image = (RemoteImageView) convertView.findViewById(R.id.image); RemoteImageView image = (RemoteImageView) convertView.findViewById(R.id.image);
if (item instanceof MusicTrack) { if (item instanceof MusicTrack) {
MusicTrack track = (MusicTrack) item; MusicTrack track = (MusicTrack) item;
String trackNumber = (track.getOriginalTrackNumber() != null) String trackNumber = (track.getOriginalTrackNumber() != null)
? Integer.toString(track.getOriginalTrackNumber()) + ". " ? Integer.toString(track.getOriginalTrackNumber()) + ". "
: ""; : "";
title.setText(trackNumber + item.getTitle()); title.setText(trackNumber + item.getTitle());
if (track.getArtists().length > 0) if (track.getArtists().length > 0)
artist.setText(track.getArtists()[0].getName()); artist.setText(track.getArtists()[0].getName());
} }
else else
title.setText(item.getTitle()); title.setText(item.getTitle());
image.setImageUri(item.getFirstPropertyValue( image.setImageUri(item.getFirstPropertyValue(
DIDLObject.Property.UPNP.ALBUM_ART_URI.class)); DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
return convertView; return convertView;
} }
/** /**
@ -115,5 +115,5 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
for (DIDLObject d : playlist) for (DIDLObject d : playlist)
add(d); add(d);
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -46,28 +46,28 @@ import android.util.Log;
* *
*/ */
public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> { public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> {
private static final String TAG = "LoadImageTask"; private static final String TAG = "LoadImageTask";
@Override @Override
protected Bitmap doInBackground(URI... uri) { protected Bitmap doInBackground(URI... uri) {
if (uri[0] == null) if (uri[0] == null)
return null; return null;
Bitmap bm = null; Bitmap bm = null;
try { try {
URLConnection conn = new URL(uri[0].toString()) URLConnection conn = new URL(uri[0].toString())
.openConnection(); .openConnection();
conn.connect(); conn.connect();
InputStream is = conn.getInputStream(); InputStream is = conn.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is); BufferedInputStream bis = new BufferedInputStream(is);
bm = BitmapFactory.decodeStream(bis); bm = BitmapFactory.decodeStream(bis);
bis.close(); bis.close();
is.close(); is.close();
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Failed to load artwork image", e); Log.w(TAG, "Failed to load artwork image", e);
} }
return bm; return bm;
} }
} }

View file

@ -4,12 +4,12 @@ All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer. notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution. documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the * Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission. derived from this software without specific prior written permission.
@ -41,7 +41,7 @@ import android.widget.ImageView;
* *
*/ */
public class RemoteImageView extends ImageView { public class RemoteImageView extends ImageView {
/** /**
* Assigns the icon as image drawable when it is loaded. * Assigns the icon as image drawable when it is loaded.
* *
@ -49,28 +49,28 @@ public class RemoteImageView extends ImageView {
* *
*/ */
private class AssignImageTask extends LoadImageTask { private class AssignImageTask extends LoadImageTask {
@Override @Override
protected void onPostExecute(Bitmap bm) { protected void onPostExecute(Bitmap bm) {
if (bm != null) if (bm != null)
setImageBitmap(bm); setImageBitmap(bm);
else else
setImageDrawable(null); setImageDrawable(null);
} }
}; };
public RemoteImageView(Context context) { public RemoteImageView(Context context) {
super(context); super(context);
} }
public RemoteImageView(Context context, AttributeSet attrs) { public RemoteImageView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
public RemoteImageView(Context context, AttributeSet attrs, int defStyle) { public RemoteImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
} }
/** /**
* Sets the URI where the image should be loaded from, loads and assigns it. * Sets the URI where the image should be loaded from, loads and assigns it.
@ -79,5 +79,5 @@ public class RemoteImageView extends ImageView {
setImageDrawable(null); setImageDrawable(null);
new AssignImageTask().execute(uri); new AssignImageTask().execute(uri);
} }
} }

View file

@ -17,22 +17,22 @@ public class RouteAdapter extends ArrayAdapter<RouteInfo> {
public RouteAdapter(Context context) { public RouteAdapter(Context context) {
super(context, R.layout.list_item); super(context, R.layout.list_item);
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) getContext() LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false); convertView = inflater.inflate(R.layout.list_item, parent, false);
} }
TextView title = (TextView) convertView.findViewById(R.id.title); TextView title = (TextView) convertView.findViewById(R.id.title);
title.setText(getItem(position).getName()); title.setText(getItem(position).getName());
TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle); TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle);
subtitle.setText(getItem(position).getDescription()); subtitle.setText(getItem(position).getDescription());
return convertView; return convertView;
} }
/** /**