Formatting fixes.

- add braces to if/for
- indent with tabs instead of spaces
- remove trailing whitespaces
This commit is contained in:
Felix Ableitner 2014-05-05 16:19:06 +02:00
parent 59df28b2bd
commit 105bb9de94
30 changed files with 980 additions and 913 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager <android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />

View file

@ -1,38 +1,38 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight" android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal" > android:orientation="horizontal" >
<com.github.nutomic.controldlna.utility.RemoteImageView
android:id="@+id/image"
android:layout_width="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="4dip"
android:contentDescription="@string/album_art" />
<com.github.nutomic.controldlna.utility.RemoteImageView
android:id="@+id/image"
android:layout_width="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="4dip"
android:contentDescription="@string/album_art" />
<LinearLayout <LinearLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginTop="4dip" > android:layout_marginTop="4dip" >
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:lines="1" android:lines="1"
android:ellipsize="end" /> android:ellipsize="end" />
<TextView <TextView
android:id="@+id/subtitle" android:id="@+id/subtitle"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:lines="1" android:lines="1"
android:ellipsize="end" /> android:ellipsize="end" />
</LinearLayout> </LinearLayout>

View file

@ -1,102 +1,102 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" > android:orientation="vertical" >
<ListView <ListView
android:id="@+id/listview" android:id="@+id/listview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_above="@+id/controls" android:layout_above="@+id/controls"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentTop="true" /> android:layout_alignParentTop="true" />
<TextView
android:id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dip"
android:text="@string/route_list_empty"
android:layout_centerInParent="true"
android:gravity="center" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:id="@+id/controls"
android:visibility="gone"
android:background="@android:color/white" >
<SeekBar <TextView
android:id="@+id/progressBar" android:id="@id/android:empty"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_margin="10dip"
android:text="@string/route_list_empty"
android:layout_centerInParent="true"
android:gravity="center" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:id="@+id/controls"
android:visibility="gone"
android:background="@android:color/white" >
<SeekBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:paddingLeft="10dip" android:paddingLeft="10dip"
android:paddingRight="10dip" > android:paddingRight="10dip" >
<TextView <TextView
android:id="@+id/current_time" android:id="@+id/current_time"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:gravity="right" android:gravity="right"
android:minEms="2" /> android:minEms="2" />
<ImageButton
android:id="@+id/shuffle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/shuffle"
android:layout_toLeftOf="@+id/previous" />
<ImageButton
android:id="@+id/previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/previous"
android:layout_toLeftOf="@+id/playpause" />
<ImageButton
android:id="@+id/playpause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:layout_centerHorizontal="true"/>
<ImageButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/next"
android:layout_toRightOf="@id/playpause" />
<ImageButton
android:id="@+id/repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/repeat"
android:layout_toRightOf="@id/next" />
<TextView <ImageButton
android:id="@+id/total_time" android:id="@+id/shuffle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:contentDescription="@string/shuffle"
android:layout_alignParentTop="true" android:layout_toLeftOf="@+id/previous" />
android:gravity="right"
android:minEms="2"/> <ImageButton
android:id="@+id/previous"
</RelativeLayout> android:layout_width="wrap_content"
android:layout_height="wrap_content"
</LinearLayout> android:contentDescription="@string/previous"
android:layout_toLeftOf="@+id/playpause" />
<ImageButton
android:id="@+id/playpause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/play"
android:layout_centerHorizontal="true"/>
<ImageButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/next"
android:layout_toRightOf="@id/playpause" />
<ImageButton
android:id="@+id/repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/repeat"
android:layout_toRightOf="@id/next" />
<TextView
android:id="@+id/total_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:gravity="right"
android:minEms="2"/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" > android:orientation="vertical" >
<ListView <ListView
android:id="@id/android:list" android:id="@id/android:list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<TextView <TextView
android:id="@id/android:empty" android:id="@id/android:empty"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dip" android:layout_margin="10dip"
android:text="@string/device_list_empty" android:text="@string/device_list_empty"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:gravity="center" /> android:gravity="center" />
</RelativeLayout> </RelativeLayout>

View file

@ -2,8 +2,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/preferences" android:id="@+id/preferences"
android:title="@string/settings_title" /> android:title="@string/settings_title" />
</menu> </menu>

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<array name="enable_wifi_values"> <array name="enable_wifi_values">
<item>yes</item> <item>yes</item>
<item>no</item> <item>no</item>
<item>ask</item> <item>ask</item>
</array> </array>
</resources> </resources>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="currently_playing_background">#BB99CC00</color> <color name="currently_playing_background">#BB99CC00</color>
<color name="button_highlight">#ff33b5e5</color> <color name="button_highlight">#ff33b5e5</color>
</resources> </resources>

View file

@ -1,7 +1,7 @@
<resources> <resources>
<!-- Default screen margins, per the Android Design guidelines. --> <!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
</resources> </resources>

View file

@ -1,42 +1,42 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">ControlDLNA</string> <string name="app_name">ControlDLNA</string>
<!-- Server Fragment -->
<string name="title_server">Server</string>
<string name="folder_list_empty">Folder is empty</string>
<string name="device_list_empty">No UPNP devices found.
\nNew devices will be added automatically when they are available.</string>
<!-- Route Fragment -->
<string name="title_route">Route</string>
<string name="playlist_empty">Playlist is empty</string>
<string name="route_list_empty">No routes found.
\nNew routes will be added automatically when they are available.</string>
<!-- Playback controls -->
<string name="play">Play</string>
<string name="pause">Pause</string>
<string name="previous">Previous</string>
<string name="next">Next</string>
<string name="shuffle">Shuffle</string>
<string name="repeat">Repeat</string>
<!-- Toasts/Dialogs -->
<string name="exit_renderer">Do you really want to exit the renderer?
Playback will be stopped.</string>
<string name="select_route">Please select a route</string>
<string name="enable_wifi_dialog">Wifi needs to be connected for playback.
Do you want to enable it now?</string>
<!-- Description for local playback device --> <!-- Server Fragment -->
<string name="local_device">Local Device</string> <string name="title_server">Server</string>
<string name="folder_list_empty">Folder is empty</string>
<!-- Image alt text --> <string name="device_list_empty">No UPNP devices found.
<string name="album_art">Album Art</string> \nNew devices will be added automatically when they are available.</string>
<!-- MediaRouter strings --> <!-- Route Fragment -->
<string name="title_route">Route</string>
<string name="playlist_empty">Playlist is empty</string>
<string name="route_list_empty">No routes found.
\nNew routes will be added automatically when they are available.</string>
<!-- Playback controls -->
<string name="play">Play</string>
<string name="pause">Pause</string>
<string name="previous">Previous</string>
<string name="next">Next</string>
<string name="shuffle">Shuffle</string>
<string name="repeat">Repeat</string>
<!-- Toasts/Dialogs -->
<string name="exit_renderer">Do you really want to exit the renderer?
Playback will be stopped.</string>
<string name="select_route">Please select a route</string>
<string name="enable_wifi_dialog">Wifi needs to be connected for playback.
Do you want to enable it now?</string>
<!-- Description for local playback device -->
<string name="local_device">Local Device</string>
<!-- Image alt text -->
<string name="album_art">Album Art</string>
<!-- MediaRouter strings -->
<string name="upnp_route_provider_service">UPNP Route Provider Service</string> <string name="upnp_route_provider_service">UPNP Route Provider Service</string>
<!-- Developer contact mail with '@', '.' replaced --> <!-- Developer contact mail with '@', '.' replaced -->
@ -45,18 +45,18 @@
<!-- Title for the SettingsActivity --> <!-- Title for the SettingsActivity -->
<string name="settings_title">Preferences</string> <string name="settings_title">Preferences</string>
<!-- Title for preference to enable wifi on start --> <!-- Title for preference to enable wifi on start -->
<string name="enable_wifi_title">Automatically enable Wifi on start</string> <string name="enable_wifi_title">Automatically enable Wifi on start</string>
<!-- Values for preference to auto-enable wifi on start --> <!-- Values for preference to auto-enable wifi on start -->
<string-array name="enable_wifi_value_titles"> <string-array name="enable_wifi_value_titles">
<item>yes</item> <item>yes</item>
<item>no</item> <item>no</item>
<item>ask</item> <item>ask</item>
</string-array> </string-array>
<!-- Title for the pause playback on call preference --> <!-- Title for the pause playback on call preference -->
<string name="incoming_phone_call_pause">Pause playback on incoming phone call</string> <string name="incoming_phone_call_pause">Pause playback on incoming phone call</string>
<!-- Title for the contact developer preference --> <!-- Title for the contact developer preference -->
<string name="contact_dev_title">Contact Developer</string> <string name="contact_dev_title">Contact Developer</string>

View file

@ -1,20 +1,20 @@
<resources> <resources>
<!-- <!--
Base application theme, dependent on API level. This theme is replaced Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices. by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
--> -->
<style name="AppBaseTheme" parent="android:Theme.Light"> <style name="AppBaseTheme" parent="android:Theme.Light">
<!-- <!--
Theme customizations available in newer API levels can go in Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here. backward-compatibility can go here.
--> -->
</style> </style>
<!-- Application theme. --> <!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme"> <style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. --> <!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style> </style>
</resources> </resources>

View file

@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference <ListPreference
android:key="enable_wifi_on_start" android:key="enable_wifi_on_start"
android:title="@string/enable_wifi_title" android:title="@string/enable_wifi_title"
android:entries="@array/enable_wifi_value_titles" android:entries="@array/enable_wifi_value_titles"
android:entryValues="@array/enable_wifi_values" android:entryValues="@array/enable_wifi_values"
android:defaultValue="ask"/> android:defaultValue="ask"/>
<CheckBoxPreference <CheckBoxPreference
android:key="incoming_phone_call_pause" android:key="incoming_phone_call_pause"
android:title="@string/incoming_phone_call_pause" android:title="@string/incoming_phone_call_pause"
android:defaultValue="true" /> android:defaultValue="true" />
<Preference
android:key="contact_dev"
android:title="@string/contact_dev_title" />
<Preference
android:key="contact_dev"
android:title="@string/contact_dev_title" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,17 +27,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.gui; package com.github.nutomic.controldlna.gui;
import java.util.List;
import org.teleal.cling.support.model.item.Item;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -50,17 +43,16 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBar.Tab; import android.support.v7.app.ActionBar.Tab;
import android.support.v7.app.ActionBar.TabListener; import android.support.v7.app.ActionBar.TabListener;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
import org.teleal.cling.support.model.item.Item;
import java.util.List;
/** /**
* Main activity, with tabs for media servers and media routes. * Main activity, with tabs for media servers and media routes.
* *
@ -114,8 +106,7 @@ public class MainActivity extends ActionBarActivity {
mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter); mViewPager.setAdapter(mSectionsPagerAdapter);
mViewPager.setOnPageChangeListener( mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
new ViewPager.SimpleOnPageChangeListener() {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position); actionBar.setSelectedNavigationItem(position);
@ -143,27 +134,27 @@ public class MainActivity extends ActionBarActivity {
.setText(R.string.title_route) .setText(R.string.title_route)
.setTabListener(tabListener)); .setTabListener(tabListener));
final WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); final WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
if (!wifi.isWifiEnabled()){ if (!wifi.isWifiEnabled()) {
String value = PreferenceManager.getDefaultSharedPreferences(this) String value = PreferenceManager.getDefaultSharedPreferences(this)
.getString(PreferencesActivity.KEY_ENABLE_WIFI_ON_START, "ask"); .getString(PreferencesActivity.KEY_ENABLE_WIFI_ON_START, "ask");
if (value.equals("yes")) { if (value.equals("yes")) {
wifi.setWifiEnabled(true); wifi.setWifiEnabled(true);
} }
else if (value.equals("ask")) { else if (value.equals("ask")) {
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setMessage(R.string.enable_wifi_dialog) .setMessage(R.string.enable_wifi_dialog)
.setPositiveButton(android.R.string.yes, .setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, int i) { public void onClick(DialogInterface dialogInterface, int i) {
wifi.setWifiEnabled(true); wifi.setWifiEnabled(true);
} }
}) })
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.show(); .show();
} }
} }
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -181,30 +172,30 @@ public class MainActivity extends ActionBarActivity {
onNewIntent(getIntent()); onNewIntent(getIntent());
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu); getMenuInflater().inflate(R.menu.menu, menu);
return true; return true;
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.preferences: case R.id.preferences:
Intent i = new Intent(this, PreferencesActivity.class); Intent i = new Intent(this, PreferencesActivity.class);
startActivity(i); startActivity(i);
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
/** /**
* Displays the RouteFragment immediately (instead of ServerFragment). * 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() != null && intent.getAction().equals("showRouteFragment")) {
mViewPager.setCurrentItem(1); mViewPager.setCurrentItem(1);
mRouteFragment.scrollToCurrent(); mRouteFragment.scrollToCurrent();
} }
@ -216,13 +207,13 @@ public class MainActivity extends ActionBarActivity {
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
// Avoid crash if called during startup. // Avoid crash if called during startup.
if (mServerFragment != null && mRouteFragment != null) { if (mServerFragment != null && mRouteFragment != null) {
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());
} }
} }
/** /**
@ -233,8 +224,9 @@ public class MainActivity extends ActionBarActivity {
public void onBackPressed() { public void onBackPressed() {
OnBackPressedListener currentFragment = (OnBackPressedListener) OnBackPressedListener currentFragment = (OnBackPressedListener)
mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem()); mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
if (!currentFragment.onBackPressed()) if (!currentFragment.onBackPressed()) {
super.onBackPressed(); super.onBackPressed();
}
} }
/** /**
@ -244,12 +236,14 @@ public class MainActivity extends ActionBarActivity {
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
switch (event.getKeyCode()) { switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_UP:
if (event.getAction() == KeyEvent.ACTION_DOWN) if (event.getAction() == KeyEvent.ACTION_DOWN) {
mRouteFragment.increaseVolume(); mRouteFragment.increaseVolume();
}
return true; return true;
case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_DOWN:
if (event.getAction() == KeyEvent.ACTION_DOWN) if (event.getAction() == KeyEvent.ACTION_DOWN) {
mRouteFragment.decreaseVolume(); mRouteFragment.decreaseVolume();
}
return true; return true;
default: default:
return super.dispatchKeyEvent(event); return super.dispatchKeyEvent(event);

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -43,73 +43,72 @@ import android.view.MenuItem;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
public class PreferencesActivity extends PreferenceActivity public class PreferencesActivity extends PreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String KEY_ENABLE_WIFI_ON_START = "enable_wifi_on_start"; public static final String KEY_ENABLE_WIFI_ON_START = "enable_wifi_on_start";
public static final String KEY_INCOMING_PHONE_CALL_PAUSE = "incoming_phone_call_pause"; public static final String KEY_INCOMING_PHONE_CALL_PAUSE = "incoming_phone_call_pause";
private static final String KEY_CONTACT_DEV = "contact_dev"; private static final String KEY_CONTACT_DEV = "contact_dev";
private ListPreference mEnableWifiOnStart; private ListPreference mEnableWifiOnStart;
private Preference mContactDev; private Preference mContactDev;
/** /**
* Initializes preferences from xml. * Initializes preferences from xml.
*/ */
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// There is currently no way to get ActionBar in PreferenceActivity on pre-honeycomb with // There is currently no way to get ActionBar in PreferenceActivity on pre-honeycomb with
// compatibility library, so we'll have to do a version check. // compatibility library, so we'll have to do a version check.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
} }
PreferenceManager.getDefaultSharedPreferences(this) PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this); .registerOnSharedPreferenceChangeListener(this);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
final PreferenceScreen screen = getPreferenceScreen(); final PreferenceScreen screen = getPreferenceScreen();
mEnableWifiOnStart = (ListPreference) screen.findPreference(KEY_ENABLE_WIFI_ON_START); mEnableWifiOnStart = (ListPreference) screen.findPreference(KEY_ENABLE_WIFI_ON_START);
mEnableWifiOnStart.setSummary(mEnableWifiOnStart.getEntry()); mEnableWifiOnStart.setSummary(mEnableWifiOnStart.getEntry());
mContactDev = screen.findPreference(KEY_CONTACT_DEV); mContactDev = screen.findPreference(KEY_CONTACT_DEV);
} }
/** /**
* Navigates up from activity on ActionBar back click. * Navigates up from activity on ActionBar back click.
*/ */
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
NavUtils.navigateUpFromSameTask(this); NavUtils.navigateUpFromSameTask(this);
return true; return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
/** /**
* Sends mail intent on contact dev preference click. * Sends mail intent on contact dev preference click.
*/ */
@Override @Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference) { Preference preference) {
if (preference == mContactDev) { if (preference == mContactDev) {
Intent i = new Intent(Intent.ACTION_SENDTO, Uri.fromParts( startActivity(new Intent(Intent.ACTION_SENDTO, Uri.fromParts(
"mailto", getString(R.string.contact_mail, "@", "."), null)); "mailto", getString(R.string.contact_mail, "@", "."), null)));
startActivity(i); return true;
return true; }
} return super.onPreferenceTreeClick(preferenceScreen, preference);
return super.onPreferenceTreeClick(preferenceScreen, preference); }
}
/**
* Updates summary of list preference (from current item).
*/
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_ENABLE_WIFI_ON_START)) {
mEnableWifiOnStart.setSummary(mEnableWifiOnStart.getEntry());
}
}
/**
* Updates summary of list preference (from current item).
*/
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_ENABLE_WIFI_ON_START)) {
mEnableWifiOnStart.setSummary(mEnableWifiOnStart.getEntry());
}
}
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,10 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.gui; package com.github.nutomic.controldlna.gui;
import java.util.List;
import org.teleal.cling.support.model.item.Item;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -49,7 +45,6 @@ import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.Callback; import android.support.v7.media.MediaRouter.Callback;
import android.support.v7.media.MediaRouter.ProviderInfo; import android.support.v7.media.MediaRouter.ProviderInfo;
import android.support.v7.media.MediaRouter.RouteInfo; import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
@ -73,6 +68,10 @@ import com.github.nutomic.controldlna.mediarouter.MediaRouterPlayServiceBinder;
import com.github.nutomic.controldlna.utility.FileArrayAdapter; import com.github.nutomic.controldlna.utility.FileArrayAdapter;
import com.github.nutomic.controldlna.utility.RouteAdapter; import com.github.nutomic.controldlna.utility.RouteAdapter;
import org.teleal.cling.support.model.item.Item;
import java.util.List;
/** /**
* 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.
@ -81,8 +80,8 @@ import com.github.nutomic.controldlna.utility.RouteAdapter;
* *
*/ */
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;
@ -93,6 +92,7 @@ OnSeekBarChangeListener, OnScrollListener {
private ImageButton mRepeat; private ImageButton mRepeat;
private TextView mCurrentTimeView; private TextView mCurrentTimeView;
private TextView mTotalTimeView; private TextView mTotalTimeView;
private TextView mEmptyView;
private View mCurrentTrackView; private View mCurrentTrackView;
@ -126,8 +126,9 @@ OnSeekBarChangeListener, OnScrollListener {
mPlaylistAdapter.add(mMediaRouterPlayService.getPlaylist()); mPlaylistAdapter.add(mMediaRouterPlayService.getPlaylist());
applyColors(); applyColors();
RouteInfo currentRoute = mMediaRouterPlayService.getCurrentRoute(); RouteInfo currentRoute = mMediaRouterPlayService.getCurrentRoute();
if (currentRoute != null) if (currentRoute != null) {
playlistMode(currentRoute); playlistMode(currentRoute);
}
} }
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
@ -168,7 +169,9 @@ OnSeekBarChangeListener, OnScrollListener {
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));
mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
mListView.setEmptyView(mEmptyView);
mControls = getView().findViewById(R.id.controls); mControls = getView().findViewById(R.id.controls);
mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar); mProgressBar = (SeekBar) getView().findViewById(R.id.progressBar);
@ -201,19 +204,17 @@ OnSeekBarChangeListener, OnScrollListener {
new Intent(getActivity(), MediaRouterPlayService.class)); new Intent(getActivity(), MediaRouterPlayService.class));
getActivity().getApplicationContext().bindService( getActivity().getApplicationContext().bindService(
new Intent(getActivity(), MediaRouterPlayService.class), new Intent(getActivity(), MediaRouterPlayService.class),
mPlayServiceConnection, mPlayServiceConnection, Context.BIND_AUTO_CREATE);
Context.BIND_AUTO_CREATE
);
if (savedInstanceState != null) if (savedInstanceState != null) {
mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state")); mListView.onRestoreInstanceState(savedInstanceState.getParcelable("list_state"));
}
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
//outState.putBoolean("route_selected", mSelectedRoute != null);
outState.putParcelable("list_state", mListView.onSaveInstanceState()); outState.putParcelable("list_state", mListView.onSaveInstanceState());
} }
@ -229,8 +230,8 @@ OnSeekBarChangeListener, OnScrollListener {
*/ */
@Override @Override
public int onPrepareCallbackFlags() { public int onPrepareCallbackFlags() {
return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY return MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY |
| MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN; MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN;
} }
@Override @Override
@ -238,16 +239,18 @@ OnSeekBarChangeListener, OnScrollListener {
return new MediaRouter.Callback() { return new MediaRouter.Callback() {
@Override @Override
public void onRouteAdded(MediaRouter router, RouteInfo route) { public void onRouteAdded(MediaRouter router, RouteInfo route) {
for (int i = 0; i < mRouteAdapter.getCount(); i++) for (int i = 0; i < mRouteAdapter.getCount(); i++) {
if (mRouteAdapter.getItem(i).getId().equals(route.getId())) { if (mRouteAdapter.getItem(i).getId().equals(route.getId())) {
mRouteAdapter.remove(mRouteAdapter.getItem(i)); mRouteAdapter.remove(mRouteAdapter.getItem(i));
break; break;
} }
}
mRouteAdapter.add(route); mRouteAdapter.add(route);
RouteInfo current = mMediaRouterPlayService.getCurrentRoute(); RouteInfo current = mMediaRouterPlayService.getCurrentRoute();
if (current != null && route.getId().equals(current.getId())) if (current != null && route.getId().equals(current.getId())) {
playlistMode(current); playlistMode(current);
}
} }
@Override @Override
@ -301,8 +304,9 @@ OnSeekBarChangeListener, OnScrollListener {
*/ */
@Override @Override
public void onItemClick(AdapterView<?> a, View v, final int position, long id) { public void onItemClick(AdapterView<?> a, View v, final int position, long id) {
if (mListView.getAdapter() == mRouteAdapter) if (mListView.getAdapter() == mRouteAdapter) {
playlistMode(mRouteAdapter.getItem(position)); playlistMode(mRouteAdapter.getItem(position));
}
else { else {
mMediaRouterPlayService.play(position); mMediaRouterPlayService.play(position);
changePlayPauseState(true); changePlayPauseState(true);
@ -317,8 +321,7 @@ OnSeekBarChangeListener, OnScrollListener {
mListView.setAdapter(mRouteAdapter); mListView.setAdapter(mRouteAdapter);
disableTrackHighlight(); disableTrackHighlight();
mSelectedRoute = null; mSelectedRoute = null;
TextView emptyView = (TextView) mListView.getEmptyView(); mEmptyView.setText(R.string.route_list_empty);
emptyView.setText(R.string.route_list_empty);
} }
/** /**
@ -334,37 +337,39 @@ OnSeekBarChangeListener, OnScrollListener {
changePlayPauseState(true); changePlayPauseState(true);
mStartPlayingOnSelect = -1; mStartPlayingOnSelect = -1;
} }
TextView emptyView = (TextView) mListView.getEmptyView(); mEmptyView.setText(R.string.playlist_empty);
emptyView.setText(R.string.playlist_empty); mListView.post(new Runnable() {
mListView.post(new Runnable() { @Override
@Override public void run() {
public void run() { scrollToCurrent();
scrollToCurrent(); }
} });
});
} }
/** /**
* 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.getCurrentTrack() mCurrentTrackView = mListView.getChildAt(mMediaRouterPlayService.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.
*/ */
private void disableTrackHighlight() { private void disableTrackHighlight() {
if (mCurrentTrackView != null) if (mCurrentTrackView != null) {
mCurrentTrackView.setBackgroundColor(Color.TRANSPARENT); mCurrentTrackView.setBackgroundColor(Color.TRANSPARENT);
}
} }
/** /**
@ -373,24 +378,25 @@ OnSeekBarChangeListener, OnScrollListener {
@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.stop();
mMediaRouterPlayService.stop(); changePlayPauseState(false);
changePlayPauseState(false); 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;
@ -470,8 +476,9 @@ OnSeekBarChangeListener, OnScrollListener {
@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.seek(progress); mMediaRouterPlayService.seek(progress);
}
} }
@Override @Override
@ -514,9 +521,10 @@ OnSeekBarChangeListener, OnScrollListener {
if (mSelectedRoute != null) { if (mSelectedRoute != null) {
mMediaRouterPlayService.play(start); mMediaRouterPlayService.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;
} }
} }
@ -528,16 +536,17 @@ OnSeekBarChangeListener, OnScrollListener {
* @return Formatted time string. * @return Formatted time string.
*/ */
private String generateTimeString(int time) { private String generateTimeString(int time) {
assert(time >= 0);
int seconds = time % 60; int seconds = time % 60;
int minutes = 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));
}
} }
/** /**
@ -559,13 +568,16 @@ OnSeekBarChangeListener, OnScrollListener {
if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PLAYING || if (status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PLAYING ||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_BUFFERING || status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_BUFFERING ||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
changePlayPauseState(true); changePlayPauseState(true);
else }
else {
changePlayPauseState(false); changePlayPauseState(false);
}
if (mListView.getAdapter() == mPlaylistAdapter) if (mListView.getAdapter() == mPlaylistAdapter) {
enableTrackHighlight(); enableTrackHighlight();
}
} }
/** /**

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,24 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.gui; package com.github.nutomic.controldlna.gui;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.android.AndroidUpnpServiceImpl;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.support.contentdirectory.callback.Browse;
import org.teleal.cling.support.model.BrowseFlag;
import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.container.Container;
import org.teleal.cling.support.model.item.Item;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@ -70,12 +52,30 @@ import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
import com.github.nutomic.controldlna.utility.DeviceArrayAdapter; import com.github.nutomic.controldlna.utility.DeviceArrayAdapter;
import com.github.nutomic.controldlna.utility.FileArrayAdapter; import com.github.nutomic.controldlna.utility.FileArrayAdapter;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.android.AndroidUpnpServiceImpl;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.support.contentdirectory.callback.Browse;
import org.teleal.cling.support.model.BrowseFlag;
import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.container.Container;
import org.teleal.cling.support.model.item.Item;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/** /**
* Shows a list of media servers, upon selecting one, allows browsing their * Shows a list of media servers, upon selecting one, allows browsing their
* directories. * directories.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
public class ServerFragment extends ListFragment implements OnBackPressedListener { public class ServerFragment extends ListFragment implements OnBackPressedListener {
@ -107,6 +107,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
*/ */
private Stack<String> mCurrentPath = new Stack<String>(); private Stack<String> mCurrentPath = new Stack<String>();
private TextView mEmptyView;
/** /**
* Holds the scroll position in the list view at each directory level. * Holds the scroll position in the list view at each directory level.
*/ */
@ -148,7 +150,6 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
@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);
}; };
@ -166,21 +167,23 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
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(); 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);
getActivity().registerReceiver(mWifiReceiver, filter); getActivity().registerReceiver(mWifiReceiver, filter);
mEmptyView = (TextView) getListView().getEmptyView();
if (savedInstanceState != null) { if (savedInstanceState != null) {
mRestoreServer = savedInstanceState.getString("current_server"); mRestoreServer = savedInstanceState.getString("current_server");
mCurrentPath.addAll(savedInstanceState.getStringArrayList("path")); mCurrentPath.addAll(savedInstanceState.getStringArrayList("path"));
mListState.addAll(savedInstanceState.getParcelableArrayList("list_state")); mListState.addAll(savedInstanceState.getParcelableArrayList("list_state"));
} else }
else {
mListState.push(getListView().onSaveInstanceState()); mListState.push(getListView().onSaveInstanceState());
}
} }
/** /**
@ -191,7 +194,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putString("current_server", (mCurrentServer != null) outState.putString("current_server", (mCurrentServer != null)
? mCurrentServer.getIdentity().getUdn().toString() ? mCurrentServer.getIdentity().getUdn().toString()
: ""); : "");
outState.putStringArrayList("path", new ArrayList<String>(mCurrentPath)); outState.putStringArrayList("path", new ArrayList<String>(mCurrentPath));
mListState.pop(); mListState.pop();
mListState.push(getListView().onSaveInstanceState()); mListState.push(getListView().onSaveInstanceState());
@ -210,19 +213,23 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
*/ */
@Override @Override
public void onListItemClick(ListView l, View v, int position, long id) { public void onListItemClick(ListView l, View v, int position, long id) {
if (getListAdapter() == mServerAdapter) if (getListAdapter() == mServerAdapter) {
browsingMode(mServerAdapter.getItem(position)); browsingMode(mServerAdapter.getItem(position));
else if (getListAdapter() == mFileAdapter) }
else if (getListAdapter() == mFileAdapter) {
if (mFileAdapter.getItem(position) instanceof Container) if (mFileAdapter.getItem(position) instanceof Container)
getFiles(((Container) mFileAdapter.getItem(position)).getId()); getFiles(((Container) mFileAdapter.getItem(position)).getId());
else { else {
List<Item> playlist = new ArrayList<Item>(); List<Item> playlist = new ArrayList<Item>();
for (int i = 0; i < mFileAdapter.getCount(); i++) for (int i = 0; i < mFileAdapter.getCount(); i++) {
if (mFileAdapter.getItem(i) instanceof Item) if (mFileAdapter.getItem(i) instanceof Item) {
playlist.add((Item) mFileAdapter.getItem(i)); playlist.add((Item) mFileAdapter.getItem(i));
}
}
MainActivity activity = (MainActivity) getActivity(); MainActivity activity = (MainActivity) getActivity();
activity.play(playlist, position); activity.play(playlist, position);
} }
}
} }
/** /**
@ -231,8 +238,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
private void serverMode() { private void serverMode() {
setListAdapter(mServerAdapter); setListAdapter(mServerAdapter);
mCurrentServer = null; mCurrentServer = null;
TextView emptyView = (TextView) getListView().getEmptyView(); mEmptyView.setText(R.string.device_list_empty);
emptyView.setText(R.string.device_list_empty);
getListView().onRestoreInstanceState(mListState.pop()); getListView().onRestoreInstanceState(mListState.pop());
} }
@ -243,8 +249,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
setListAdapter(mFileAdapter); setListAdapter(mFileAdapter);
mCurrentServer = server; mCurrentServer = server;
getFiles(ROOT_DIRECTORY); getFiles(ROOT_DIRECTORY);
TextView emptyView = (TextView) getListView().getEmptyView(); mEmptyView.setText(R.string.folder_list_empty);
emptyView.setText(R.string.folder_list_empty);
} }
/** /**
@ -258,7 +263,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
/** /**
* 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, * @param restoreListState True if we are going back up the directory tree,
* which means we restore scroll position etc. This pops * which means we restore scroll position etc. This pops
* mListState. * mListState.
@ -281,14 +286,18 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
@Override @Override
public void run() { public void run() {
mFileAdapter.clear(); mFileAdapter.clear();
for (Container c : didl.getContainers()) for (Container c : didl.getContainers()) {
mFileAdapter.add(c); mFileAdapter.add(c);
for (Item i : didl.getItems()) }
for (Item i : didl.getItems()) {
mFileAdapter.add(i); mFileAdapter.add(i);
if (restoreListState) }
if (restoreListState) {
getListView().onRestoreInstanceState(mListState.pop()); getListView().onRestoreInstanceState(mListState.pop());
else }
else {
getListView().setSelectionFromTop(0, 0); getListView().setSelectionFromTop(0, 0);
}
} }
}); });
} }
@ -317,10 +326,12 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
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;
} }
@ -340,11 +351,13 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
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());
@ -358,6 +371,7 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
} }
} }
} }
}
} }
}; };

View file

@ -4,14 +4,14 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,8 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.localroute; package com.github.nutomic.controldlna.localroute;
import java.io.IOException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
@ -41,13 +39,15 @@ import android.support.v7.media.MediaRouteProvider;
import android.support.v7.media.MediaRouter.ControlRequestCallback; import android.support.v7.media.MediaRouter.ControlRequestCallback;
import android.util.Log; import android.util.Log;
import java.io.IOException;
/** /**
* Receives control intents through media route and executes them on a MediaPlayer. * Receives control intents through media route and executes them on a MediaPlayer.
* *
* @author felix * @author felix
* *
*/ */
public class Controller extends MediaRouteProvider.RouteController implements public class Controller extends MediaRouteProvider.RouteController implements
MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener { MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener {
private static final String TAG = "Controller"; private static final String TAG = "Controller";
@ -55,104 +55,106 @@ public class Controller extends MediaRouteProvider.RouteController implements
private Context mContext; private Context mContext;
private AudioManager mAudio; private AudioManager mAudio;
AudioManager.OnAudioFocusChangeListener mFocusListener; AudioManager.OnAudioFocusChangeListener mFocusListener;
private final String mRouteId; private final String mRouteId;
private String mItemId; private String mItemId;
private int mState; private int mState;
private MediaPlayer mPlayer = new MediaPlayer(); private MediaPlayer mPlayer = new MediaPlayer();
public Controller(String routeId, Context context) { public Controller(String routeId, Context context) {
mContext = context; mContext = context;
mRouteId = routeId; mRouteId = routeId;
mAudio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mAudio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mPlayer.setOnPreparedListener(this); mPlayer.setOnPreparedListener(this);
} }
@Override @Override
public void onRelease() { public void onRelease() {
mPlayer.release(); mPlayer.release();
} }
@Override @Override
public void onSelect() { public void onSelect() {
mAudio.requestAudioFocus(mFocusListener, AudioManager.STREAM_MUSIC, mAudio.requestAudioFocus(mFocusListener, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN); AudioManager.AUDIOFOCUS_GAIN);
} }
@Override @Override
public void onUnselect() { public void onUnselect() {
mAudio.abandonAudioFocus(mFocusListener); mAudio.abandonAudioFocus(mFocusListener);
} }
@Override @Override
public void onSetVolume(int volume) { public void onSetVolume(int volume) {
mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0); mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
} }
@Override @Override
public void onUpdateVolume(int delta) { public void onUpdateVolume(int delta) {
int currentVolume = mAudio.getStreamVolume(AudioManager.STREAM_MUSIC); int currentVolume = mAudio.getStreamVolume(AudioManager.STREAM_MUSIC);
mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume + delta, 0); mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume + delta, 0);
} }
@Override @Override
public boolean onControlRequest(Intent intent, ControlRequestCallback callback) { public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
String sessionId = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID); String sessionId = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
String itemId = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID); String itemId = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) { if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
try { try {
mPlayer.reset(); mPlayer.reset();
mPlayer.setDataSource(mContext, intent.getData()); mPlayer.setDataSource(mContext, intent.getData());
mPlayer.prepareAsync(); mPlayer.prepareAsync();
mItemId = intent.getDataString(); mItemId = intent.getDataString();
mState = MediaItemStatus.PLAYBACK_STATE_BUFFERING; mState = MediaItemStatus.PLAYBACK_STATE_BUFFERING;
getStatus(mItemId, mRouteId, callback); getStatus(mItemId, mRouteId, callback);
return true; return true;
} catch (IllegalArgumentException e) { }
mState = MediaItemStatus.PLAYBACK_STATE_ERROR; catch (IllegalArgumentException e) {
Log.d(TAG, "Failed to start playback", e);
} catch (IOException e) {
mState = MediaItemStatus.PLAYBACK_STATE_ERROR; mState = MediaItemStatus.PLAYBACK_STATE_ERROR;
Log.d(TAG, "Failed to start playback", e); Log.d(TAG, "Failed to start playback", e);
} }
} catch (IOException e) {
else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) { mState = MediaItemStatus.PLAYBACK_STATE_ERROR;
mPlayer.pause(); Log.d(TAG, "Failed to start playback", e);
mState = MediaItemStatus.PLAYBACK_STATE_PAUSED; }
return true; }
} else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) {
else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) { mPlayer.pause();
mPlayer.start(); mState = MediaItemStatus.PLAYBACK_STATE_PAUSED;
mState = MediaItemStatus.PLAYBACK_STATE_PLAYING; return true;
return true; }
} else if (intent.getAction().equals(MediaControlIntent.ACTION_RESUME)) {
else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) { mPlayer.start();
mPlayer.stop(); mState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
mState = MediaItemStatus.PLAYBACK_STATE_CANCELED; return true;
return true; }
} else if (intent.getAction().equals(MediaControlIntent.ACTION_STOP)) {
else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) { mPlayer.stop();
mPlayer.seekTo((int) intent.getLongExtra( mState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0)); return true;
getStatus(itemId, sessionId, callback); }
return true; else if (intent.getAction().equals(MediaControlIntent.ACTION_SEEK)) {
} mPlayer.seekTo((int) intent.getLongExtra(
else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) { MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
getStatus(itemId, sessionId, callback); getStatus(itemId, sessionId, callback);
return true; return true;
} }
else if(intent.getAction().equals(MediaControlIntent.ACTION_GET_STATUS)) {
getStatus(itemId, sessionId, callback);
return true;
}
return false; return false;
} }
private void getStatus(String itemId, String sessionId, ControlRequestCallback callback) { private void getStatus(String itemId, String sessionId, ControlRequestCallback callback) {
if (callback == null) if (callback == null)
return; return;
Bundle status = null; Bundle status = null;
@ -161,24 +163,26 @@ public class Controller extends MediaRouteProvider.RouteController implements
.setContentPosition(mPlayer.getCurrentPosition()) .setContentPosition(mPlayer.getCurrentPosition())
.setContentDuration(mPlayer.getDuration()) .setContentDuration(mPlayer.getDuration())
.setTimestamp(SystemClock.elapsedRealtime()) .setTimestamp(SystemClock.elapsedRealtime())
.build().asBundle(); .build()
.asBundle();
status.putString(MediaControlIntent.EXTRA_SESSION_ID, mRouteId); status.putString(MediaControlIntent.EXTRA_SESSION_ID, mRouteId);
status.putString(MediaControlIntent.EXTRA_ITEM_ID, mItemId); status.putString(MediaControlIntent.EXTRA_ITEM_ID, mItemId);
} }
else else {
status = new MediaItemStatus.Builder(MediaItemStatus.PLAYBACK_STATE_INVALIDATED) status = new MediaItemStatus.Builder(MediaItemStatus.PLAYBACK_STATE_INVALIDATED)
.build().asBundle(); .build().asBundle();
}
callback.onResult(status); callback.onResult(status);
} }
/** /**
* Sets state to finished. * Sets state to finished.
* *
* Note: Do not set the listener before play() is called * Note: Do not set the listener before play() is called
* (or this will be called immediately). * (or this will be called immediately).
*/ */
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(MediaPlayer mp) {
mState = MediaItemStatus.PLAYBACK_STATE_FINISHED; mState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
@ -191,7 +195,7 @@ public class Controller extends MediaRouteProvider.RouteController implements
@Override @Override
public void onPrepared(MediaPlayer mp) { public void onPrepared(MediaPlayer mp) {
mPlayer.start(); mPlayer.start();
mState = MediaItemStatus.PLAYBACK_STATE_PLAYING; mState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
mPlayer.setOnCompletionListener(this); mPlayer.setOnCompletionListener(this);
} }
} }

View file

@ -4,14 +4,14 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -43,63 +43,64 @@ import android.support.v7.media.MediaRouter;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
/** /**
* MediaRouteProvider that details the local audio route with its * MediaRouteProvider that details the local audio route with its
* controls to the system. * controls to the system.
* *
* @author felix * @author felix
* *
*/ */
final class Provider extends MediaRouteProvider { final class Provider extends MediaRouteProvider {
private static final String ROUTE_ID = "local_route"; private static final String ROUTE_ID = "local_route";
AudioManager mAudio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
private static final ArrayList<IntentFilter> CONTROL_FILTERS; AudioManager mAudio = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
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");
addDataTypeUnchecked(f, "audio/*");
CONTROL_FILTERS = new ArrayList<IntentFilter>(); private static final ArrayList<IntentFilter> CONTROL_FILTERS;
CONTROL_FILTERS.add(f); 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");
addDataTypeUnchecked(f, "audio/*");
private static void addDataTypeUnchecked(IntentFilter filter, String type) { CONTROL_FILTERS = new ArrayList<IntentFilter>();
try { CONTROL_FILTERS.add(f);
filter.addDataType(type); }
} catch (MalformedMimeTypeException ex) {
throw new RuntimeException(ex);
}
}
public Provider(Context context) { private static void addDataTypeUnchecked(IntentFilter filter, String type) {
super(context); try {
filter.addDataType(type);
}
catch (MalformedMimeTypeException ex) {
throw new RuntimeException(ex);
}
}
MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder( public Provider(Context context) {
ROUTE_ID, context.getResources().getString(R.string.local_device)) super(context);
.addControlFilters(CONTROL_FILTERS)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolume(mAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.build();
MediaRouteProviderDescriptor providerDescriptor = MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
new MediaRouteProviderDescriptor.Builder() ROUTE_ID, context.getResources().getString(R.string.local_device))
.addRoute(routeDescriptor) .addControlFilters(CONTROL_FILTERS)
.build(); .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
setDescriptor(providerDescriptor); .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
} .setVolume(mAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.build();
@Override
public RouteController onCreateRouteController(String routeId) { MediaRouteProviderDescriptor providerDescriptor =
return new Controller(routeId, getContext()); new MediaRouteProviderDescriptor.Builder()
} .addRoute(routeDescriptor)
.build();
setDescriptor(providerDescriptor);
}
@Override
public RouteController onCreateRouteController(String routeId) {
return new Controller(routeId, getContext());
}
} }

View file

@ -4,14 +4,14 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -36,8 +36,9 @@ public class ProviderService extends MediaRouteProviderService {
@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;
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,17 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.mediarouter; package com.github.nutomic.controldlna.mediarouter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.teleal.cling.support.contentdirectory.DIDLParser;
import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.DIDLObject;
import org.teleal.cling.support.model.item.Item;
import org.teleal.cling.support.model.item.MusicTrack;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -66,9 +55,20 @@ import com.github.nutomic.controldlna.gui.PreferencesActivity;
import com.github.nutomic.controldlna.gui.RouteFragment; import com.github.nutomic.controldlna.gui.RouteFragment;
import com.github.nutomic.controldlna.utility.LoadImageTask; import com.github.nutomic.controldlna.utility.LoadImageTask;
import org.teleal.cling.support.contentdirectory.DIDLParser;
import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.DIDLObject;
import org.teleal.cling.support.model.item.Item;
import org.teleal.cling.support.model.item.MusicTrack;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/** /**
* Background service that handles media playback to a single UPNP media renderer. * Background service that handles media playback to a single UPNP media renderer.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
@ -120,22 +120,24 @@ public class MediaRouterPlayService extends Service {
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);
}
if (!mBound && !mPollingStatus) if (!mBound && !mPollingStatus) {
stopSelf(); stopSelf();
}
} }
@Override @Override
public void onRouteAdded(MediaRouter router, RouteInfo route) { public void onRouteAdded(MediaRouter router, RouteInfo route) {
if (route.getId().equals(mCurrentRoute.getId())) { if (route.getId().equals(mCurrentRoute.getId())) {
selectRoute(route); selectRoute(route);
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));
} }
} }
}; };
/** /**
* Creates a notification after the icon bitmap is loaded. * Creates a notification after the icon bitmap is loaded.
@ -150,15 +152,16 @@ public class MediaRouterPlayService extends Service {
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)
@ -170,35 +173,35 @@ public class MediaRouterPlayService extends Service {
} }
/** /**
* Listens for incoming phone calls and pauses playback then. * Listens for incoming phone calls and pauses playback then.
*/ */
private class PhoneCallListener extends PhoneStateListener { private class PhoneCallListener extends PhoneStateListener {
private boolean mPausedForCall = false; private boolean mPausedForCall = false;
@Override @Override
public void onCallStateChanged(int state, String incomingNumber) { public void onCallStateChanged(int state, String incomingNumber) {
if (!PreferenceManager.getDefaultSharedPreferences(MediaRouterPlayService.this) if (!PreferenceManager.getDefaultSharedPreferences(MediaRouterPlayService.this)
.getBoolean(PreferencesActivity.KEY_INCOMING_PHONE_CALL_PAUSE, true)) { .getBoolean(PreferencesActivity.KEY_INCOMING_PHONE_CALL_PAUSE, true)) {
return; return;
} }
if (TelephonyManager.CALL_STATE_RINGING == state || if (TelephonyManager.CALL_STATE_RINGING == state ||
TelephonyManager.CALL_STATE_OFFHOOK == state) { TelephonyManager.CALL_STATE_OFFHOOK == state) {
// phone ringing or call active // phone ringing or call active
pause(); pause();
mPausedForCall = true; mPausedForCall = true;
} }
if (mPausedForCall && TelephonyManager.CALL_STATE_IDLE == state) { if (mPausedForCall && TelephonyManager.CALL_STATE_IDLE == state) {
// run when class initial and phone call ended // run when class initial and phone call ended
resume(); resume();
mPausedForCall = false; mPausedForCall = false;
} }
} }
} }
@Override @Override
public void onCreate() { public void onCreate() {
@ -206,11 +209,10 @@ public class MediaRouterPlayService extends Service {
mMediaRouter = MediaRouter.getInstance(this); mMediaRouter = MediaRouter.getInstance(this);
pollStatus(); pollStatus();
PhoneCallListener phoneListener = new PhoneCallListener(); PhoneCallListener phoneListener = new PhoneCallListener();
TelephonyManager telephonyManager = (TelephonyManager) this TelephonyManager telephonyManager =
.getSystemService(Context.TELEPHONY_SERVICE); (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneListener, telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE);
PhoneStateListener.LISTEN_CALL_STATE);
} }
@Override @Override
@ -225,8 +227,9 @@ public class MediaRouterPlayService extends Service {
*/ */
@Override @Override
public boolean onUnbind(Intent intent) { public boolean onUnbind(Intent intent) {
if (!mPollingStatus) if (!mPollingStatus) {
stopSelf(); stopSelf();
}
mBound = false; mBound = false;
return super.onUnbind(intent); return super.onUnbind(intent);
} }
@ -239,8 +242,8 @@ public class MediaRouterPlayService extends Service {
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;
@ -287,8 +290,9 @@ public class MediaRouterPlayService extends Service {
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));
if (mRouterFragment.get() != null) if (mRouterFragment.get() != null) {
mRouterFragment.get().scrollToCurrent(); mRouterFragment.get().scrollToCurrent();
}
} }
}); });
} }
@ -347,23 +351,25 @@ public class MediaRouterPlayService extends Service {
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.
* *
* @param playlist The media files in the playlist. * @param playlist The media files in the playlist.
*/ */
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.
*/ */
@ -389,8 +395,9 @@ public class MediaRouterPlayService extends Service {
else { else {
// Playlist over, stop playback. // Playlist over, stop playback.
stop(); stop();
if (!mBound) if (!mBound) {
stopSelf(); stopSelf();
}
mPollingStatus = false; mPollingStatus = false;
return false; return false;
} }
@ -404,11 +411,13 @@ public class MediaRouterPlayService extends Service {
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);
}
} }
/** /**
@ -437,16 +446,19 @@ public class MediaRouterPlayService extends Service {
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 ||
status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_CANCELED) status.getPlaybackState() == MediaItemStatus.PLAYBACK_STATE_CANCELED) {
playNext(); playNext();
}
} }
}); });
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -31,7 +31,7 @@ import android.os.Binder;
/** /**
* Provides connection to PlayService. * Provides connection to PlayService.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */

View file

@ -4,14 +4,14 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -24,11 +24,11 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.github.nutomic.controldlna.upnp; package com.github.nutomic.controldlna.upnp;
import android.os.Messenger; import android.os.Messenger;
interface IRemotePlayService { interface IRemotePlayService {
void startSearch(in Messenger listener); void startSearch(in Messenger listener);
void selectRenderer(String id); void selectRenderer(String id);

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,12 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.upnp; package com.github.nutomic.controldlna.upnp;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -54,10 +48,17 @@ import android.support.v7.media.MediaRouteProvider;
import android.support.v7.media.MediaRouteProviderDescriptor.Builder; import android.support.v7.media.MediaRouteProviderDescriptor.Builder;
import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.ControlRequestCallback; import android.support.v7.media.MediaRouter.ControlRequestCallback;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
import android.widget.Toast; import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
/** /**
* Allows playing to a DLNA renderer from a remote app. * Allows playing to a DLNA renderer from a remote app.
* *
@ -65,6 +66,8 @@ import android.widget.Toast;
*/ */
final class Provider extends MediaRouteProvider { final class Provider extends MediaRouteProvider {
private static final String TAG = "Provider";
// 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;
@ -93,8 +96,8 @@ final class Provider extends MediaRouteProvider {
public int volume; public int volume;
public int volumeMax; public int volumeMax;
public static final Parcelable.Creator<Device> CREATOR public static final Parcelable.Creator<Device> CREATOR =
= new Parcelable.Creator<Device>() { new Parcelable.Creator<Device>() {
public Device createFromParcel(Parcel in) { public Device createFromParcel(Parcel in) {
return new Device(in); return new Device(in);
} }
@ -187,8 +190,9 @@ final class Provider extends MediaRouteProvider {
@Override @Override
public void handleMessage(Message msg) { public void handleMessage(Message msg) {
if (mService.get() != null) if (mService.get() != null) {
mService.get().handleMessage(msg); mService.get().handleMessage(msg);
}
} }
} }
@ -196,11 +200,8 @@ final class Provider extends MediaRouteProvider {
public Provider(Context context) { public Provider(Context context) {
super(context); super(context);
context.bindService( context.bindService(new Intent(context, RemotePlayService.class),
new Intent(context, RemotePlayService.class), mConnection, Context.BIND_AUTO_CREATE);
mConnection,
Context.BIND_AUTO_CREATE
);
} }
public void close() { public void close() {
@ -210,9 +211,11 @@ final class Provider extends MediaRouteProvider {
@Override @Override
public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) { public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
try { try {
if (request != null && request.isActiveScan() && mIRemotePlayService != null) if (request != null && request.isActiveScan() && mIRemotePlayService != null) {
mIRemotePlayService.startSearch(mListener); mIRemotePlayService.startSearch(mListener);
} catch (RemoteException e) { }
}
catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -259,7 +262,8 @@ final class Provider extends MediaRouteProvider {
public void onSelect() { public void onSelect() {
try { try {
mIRemotePlayService.selectRenderer(mRouteId); mIRemotePlayService.selectRenderer(mRouteId);
} catch (RemoteException e) { }
catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -268,7 +272,8 @@ final class Provider extends MediaRouteProvider {
public void onUnselect() { public void onUnselect() {
try { try {
mIRemotePlayService.unselectRenderer(mRouteId); mIRemotePlayService.unselectRenderer(mRouteId);
} catch (RemoteException e) { }
catch (RemoteException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -280,7 +285,8 @@ final class Provider extends MediaRouteProvider {
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;
@ -301,13 +307,13 @@ final class Provider extends MediaRouteProvider {
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);
@ -323,10 +329,8 @@ final class Provider extends MediaRouteProvider {
} }
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(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
intent.getLongExtra(
MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
getItemStatus(intent, callback); getItemStatus(intent, callback);
return true; return true;
} }
@ -334,8 +338,9 @@ final class Provider extends MediaRouteProvider {
getItemStatus(intent, callback); getItemStatus(intent, callback);
return true; return true;
} }
} catch (RemoteException e) { }
e.printStackTrace(); catch (RemoteException e) {
Log.w(TAG, "Failed to execute control request", e);
} }
return false; return false;
} }
@ -357,8 +362,7 @@ final class Provider extends MediaRouteProvider {
mRequests.put(r, pair); mRequests.put(r, pair);
mIRemotePlayService.getItemStatus( mIRemotePlayService.getItemStatus(
intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID), intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID),
intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID), intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID), r);
r);
} }
/** /**
@ -382,12 +386,14 @@ final class Provider extends MediaRouteProvider {
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); pair.second.onResult(status);
break; break;
case MSG_ERROR: case MSG_ERROR:

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -36,8 +36,9 @@ public class ProviderService extends MediaRouteProviderService {
@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;
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -64,7 +64,7 @@ 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.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
@ -87,11 +87,14 @@ public class RemotePlayService extends Service implements RegistryListener {
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();
} }
@ -119,11 +122,8 @@ public class RemotePlayService extends Service implements RegistryListener {
@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, Context.BIND_AUTO_CREATE);
mUpnpServiceConnection,
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);
@ -144,7 +144,8 @@ public class RemotePlayService extends Service implements RegistryListener {
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();
} }
} }
@ -173,21 +174,28 @@ public class RemotePlayService extends Service implements RegistryListener {
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 }
for (Entry<String, Device<?, ?, ?>> d : mDevices.entrySet()) else {
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 != null && b.mCurrentRenderer.equals(d.getValue())) { if (b.mCurrentRenderer != null &&
b.mCurrentRenderer.equals(d.getValue())) {
b.mSubscriptionCallback.end(); b.mSubscriptionCallback.end();
b.mCurrentRenderer = null; b.mCurrentRenderer = null;
} }
}
} }
}
}
} }
}; };
@ -196,8 +204,7 @@ public class RemotePlayService extends Service implements RegistryListener {
*/ */
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));
} }
/** /**
@ -207,8 +214,7 @@ public class RemotePlayService extends Service implements RegistryListener {
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;
@ -233,7 +239,8 @@ public class RemotePlayService extends Service implements RegistryListener {
int maxVolume = 100; int maxVolume = 100;
if (rc.getStateVariable("Volume") != null) { if (rc.getStateVariable("Volume") != null) {
StateVariableAllowedValueRange volumeRange = StateVariableAllowedValueRange volumeRange =
rc.getStateVariable("Volume").getTypeDetails().getAllowedValueRange(); rc.getStateVariable("Volume")
.getTypeDetails().getAllowedValueRange();
maxVolume = (int) volumeRange.getMaximum(); maxVolume = (int) volumeRange.getMaximum();
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -83,26 +83,27 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
private RemotePlayService mRps; private RemotePlayService mRps;
private boolean mStartingPlayback = false; private boolean mStartingPlayback = false;
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
@ -111,8 +112,7 @@ 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")
@ -121,16 +121,14 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
@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 (mStartingPlayback || lastChange.getEventedValue(0, if (mStartingPlayback || 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;
@ -149,7 +147,8 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
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");
} }
@ -157,8 +156,7 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
protected void eventsMissed(GENASubscription sub, protected void eventsMissed(GENASubscription sub, int numberOfMissedEvents) {
int numberOfMissedEvents) {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -177,10 +175,12 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
*/ */
@Override @Override
public void unselectRenderer(String sessionId) throws RemoteException { public void unselectRenderer(String sessionId) throws RemoteException {
if (mRps.mDevices.get(sessionId) != null) if (mRps.mDevices.get(sessionId) != null) {
stop(sessionId); stop(sessionId);
if (mSubscriptionCallback != null) }
if (mSubscriptionCallback != null) {
mSubscriptionCallback.end(); mSubscriptionCallback.end();
}
mCurrentRenderer = null; mCurrentRenderer = null;
} }
@ -246,19 +246,19 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
new Play(mRps.getService(mCurrentRenderer, new Play(mRps.getService(mCurrentRenderer,
"AVTransport")) { "AVTransport")) {
@Override @Override
public void success(ActionInvocation invocation) { public void success(ActionInvocation invocation) {
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING; mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
mStartingPlayback = false; mStartingPlayback = false;
} }
@Override @Override
public void failure(ActionInvocation invocation, public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) { UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Play failed: " + defaultMessage); Log.w(TAG, "Play failed: " + defaultMessage);
mRps.sendError("Play failed: " + defaultMessage); mRps.sendError("Play failed: " + defaultMessage);
mStartingPlayback = false; mStartingPlayback = false;
} }
}); });
} }
}); });
@ -284,8 +284,10 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
// 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) { }
e.printStackTrace(); catch (RemoteException e) {
Log.w(TAG, "Calling stop failed", e);
mRps.sendError("Calling stop failed: " + e.toString());
} }
} }
}); });
@ -372,6 +374,7 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
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);
mRps.sendError("Get position failed: " + defaultMessage);
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -381,17 +384,20 @@ public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
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;
status = new MediaItemStatus.Builder(mPlaybackState); status = new MediaItemStatus.Builder(mPlaybackState);
if (positionInfo.getTrackURI() != null && positionInfo.getTrackURI().equals(itemId)) { if (positionInfo.getTrackURI() != null &&
try { positionInfo.getTrackURI().equals(itemId)) {
status.setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000) try {
.setContentDuration(positionInfo.getTrackDurationSeconds() * 1000) status.setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000)
.setTimestamp(positionInfo.getAbsCount()); .setContentDuration(positionInfo.getTrackDurationSeconds() * 1000)
} .setTimestamp(positionInfo.getAbsCount());
catch (NumberFormatException e) { }
Log.d(TAG, "Failed to read track position or duration", e); catch (NumberFormatException e) {
} Log.d(TAG, "Failed to read track position or duration", e);
} mRps.sendError("Failed to read track position or duration: " +
e.toString());
}
}
msg.getData().putBundle("media_item_status", status.build().asBundle()); msg.getData().putBundle("media_item_status", status.build().asBundle());
msg.getData().putInt("hash", requestHash); msg.getData().putInt("hash", requestHash);

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,16 +27,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.utility; package com.github.nutomic.controldlna.utility;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Comparator;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
@ -48,6 +38,16 @@ import android.widget.TextView;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Comparator;
/** /**
* 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).
@ -60,8 +60,6 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
private static final String TAG = "DeviceArrayAdapter"; private static final String TAG = "DeviceArrayAdapter";
public static final String RENDERER = "MediaRenderer";
public static final String SERVER = "MediaServer"; public static final String SERVER = "MediaServer";
private Activity mActivity; private Activity mActivity;
@ -91,15 +89,16 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
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);
} }
RemoteImageView icon = }
(RemoteImageView) convertView.findViewById(R.id.image); RemoteImageView icon = (RemoteImageView) convertView.findViewById(R.id.image);
icon.setImageUri(uri); icon.setImageUri(uri);
} }
@ -110,9 +109,11 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
* 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() {
@ -141,8 +142,9 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
@Override @Override
public void run() { public void run() {
if (getPosition(device) != -1) if (getPosition(device) != -1) {
remove(device); remove(device);
}
} }
}); });
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.github.nutomic.controldlna.utility; package com.github.nutomic.controldlna.utility;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import org.teleal.cling.support.model.DIDLObject; import org.teleal.cling.support.model.DIDLObject;
import org.teleal.cling.support.model.item.AudioItem; import org.teleal.cling.support.model.item.AudioItem;
@ -78,32 +78,38 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
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());
}
RemoteImageView image = (RemoteImageView) convertView.findViewById(R.id.image); RemoteImageView image = (RemoteImageView) convertView.findViewById(R.id.image);
URI icon = item.getFirstPropertyValue( URI icon = item.getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class);
DIDLObject.Property.UPNP.ALBUM_ART_URI.class);
if (icon != null) { if (icon != null) {
image.setImageUri(icon); image.setImageUri(icon);
} }
else { else {
int resId; int resId;
if (item instanceof AudioItem) if (item instanceof AudioItem) {
resId = R.drawable.ic_doc_audio_am; resId = R.drawable.ic_doc_audio_am;
else if (item instanceof VideoItem) }
else if (item instanceof VideoItem) {
resId = R.drawable.ic_doc_video_am; resId = R.drawable.ic_doc_video_am;
else if (item instanceof ImageItem) }
else if (item instanceof ImageItem) {
resId = R.drawable.ic_doc_image; resId = R.drawable.ic_doc_image;
else if (item instanceof PlaylistItem) }
else if (item instanceof PlaylistItem) {
resId = R.drawable.ic_doc_album; resId = R.drawable.ic_doc_album;
else }
else {
resId = R.drawable.ic_root_folder_am; resId = R.drawable.ic_root_folder_am;
}
image.setImageResource(resId); image.setImageResource(resId);
} }
@ -114,8 +120,9 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
* Replacement for addAll, which is not implemented on lower API levels. * Replacement for addAll, which is not implemented on lower API levels.
*/ */
public void add(List<Item> playlist) { public void add(List<Item> playlist) {
for (DIDLObject d : playlist) for (DIDLObject d : playlist) {
add(d); add(d);
}
} }
} }

View file

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -40,8 +40,8 @@ import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
/** /**
* Handles background task of loading a bitmap by URI. * Loads an image by URI in the background and returns it.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
@ -56,15 +56,15 @@ public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> {
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

@ -5,13 +5,13 @@ 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -36,7 +36,7 @@ import android.widget.ImageView;
/** /**
* ImageView that can directly load from a UPNP URI. * ImageView that can directly load from a UPNP URI.
* *
* @author Felix Ableitner * @author Felix Ableitner
* *
*/ */
@ -44,7 +44,7 @@ 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.
* *
* @author Felix * @author Felix
* *
*/ */
@ -52,8 +52,9 @@ public class RemoteImageView extends ImageView {
@Override @Override
protected void onPostExecute(Bitmap bm) { protected void onPostExecute(Bitmap bm) {
if (bm != null) if (bm != null) {
setImageBitmap(bm); setImageBitmap(bm);
}
} }
}; };

View file

@ -41,7 +41,7 @@ public class RouteAdapter extends ArrayAdapter<RouteInfo> {
public void add(List<RouteInfo> routes) { public void add(List<RouteInfo> routes) {
for (RouteInfo r : routes) { for (RouteInfo r : routes) {
add(r); add(r);
} }
} }
} }