Extracted UPNP functionality from GUI into seperate classes.

This commit is contained in:
Felix Ableitner 2013-09-23 02:10:35 +02:00
parent d29680d695
commit c236173a45
12 changed files with 523 additions and 300 deletions

View file

@ -29,16 +29,9 @@ package com.github.nutomic.controldlna;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Collection;
import org.teleal.cling.model.meta.Device; 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.model.meta.RemoteDevice;
import org.teleal.cling.registry.Registry;
import org.teleal.cling.registry.RegistryListener;
import com.github.nutomic.controldlna.RemoteImageView;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
@ -49,15 +42,17 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
import com.github.nutomic.controldlna.DeviceListener.DeviceListenerCallback;
/** /**
* Displays the devices that are inserted through the RegistryListener (either * Displays the devices that are inserted through the RegistryListener (either
* of type RENDERER or SERVER). * of type RENDERER or SERVER).
* *
* @author Felix * @author Felix Ableitner
* *
*/ */
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>> public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
implements RegistryListener { implements DeviceListenerCallback {
private static final String TAG = "DeviceArrayAdapter"; private static final String TAG = "DeviceArrayAdapter";
@ -106,55 +101,11 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
return convertView; return convertView;
} }
@Override
public void afterShutdown() {
}
@Override
public void beforeShutdown(Registry registry) {
}
@Override
public void localDeviceAdded(Registry registry, LocalDevice device) {
deviceAdded(device);
}
@Override
public void localDeviceRemoved(Registry registry, LocalDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
deviceAdded(device);
}
@Override
public void remoteDeviceDiscoveryFailed(Registry registry,
RemoteDevice device, Exception exception) {
Log.w(TAG, "Device discovery failed", exception);
}
@Override
public void remoteDeviceDiscoveryStarted(Registry registry,
RemoteDevice device) {
}
@Override
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
if (!(device.getType().getType().equals(mDeviceType)))
deviceRemoved(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.
*/ */
private void deviceAdded(final Device<?, ?, ?> device) { @Override
public void deviceAdded(final Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() { mActivity.runOnUiThread(new Runnable() {
@Override @Override
@ -168,7 +119,8 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
/** /**
* Removes the device from the list (if it is an element). * Removes the device from the list (if it is an element).
*/ */
private void deviceRemoved(final Device<?, ?, ?> device) { @Override
public void deviceRemoved(final Device<?, ?, ?> device) {
mActivity.runOnUiThread(new Runnable() { mActivity.runOnUiThread(new Runnable() {
@Override @Override
@ -178,13 +130,4 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
} }
}); });
} }
/**
* Replacement for addAll, which is not implemented on lower API levels.
*/
@SuppressWarnings("rawtypes")
public void add(Collection<Device> collection) {
for (Device<?, ?, ?> d : collection)
add(d);
}
} }

View file

@ -0,0 +1,106 @@
package com.github.nutomic.controldlna;
import java.util.ArrayList;
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.util.Log;
/**
* Provides an interface that informs about UPNP devices being added or removed.
*
* @author Felix Ableitner
*
*/
public class DeviceListener implements RegistryListener {
private static final String TAG = "DeviceListener";
/**
* Callbacks may be called from a background thread.
*
* @author Felix Ableitner
*
*/
public interface DeviceListenerCallback {
public void deviceAdded(Device<?, ?, ?> device);
public void deviceRemoved(Device<?, ?, ?> device);
}
private ArrayList<Device<?, ?, ?>> mDevices = new ArrayList<Device<?, ?, ?>>();
private ArrayList<DeviceListenerCallback> mListeners = new ArrayList<DeviceListenerCallback>();
public void addCallback(DeviceListenerCallback callback) {
mListeners.add(callback);
for (Device<?, ?, ?> d : mDevices)
callback.deviceAdded(d);
}
public void removeCallback(DeviceListenerCallback callback) {
mListeners.remove(callback);
}
private void deviceAdded(Device<?, ?, ?> device) {
mDevices.add(device);
for (DeviceListenerCallback l : mListeners)
l.deviceAdded(device);
}
private void deviceRemoved(Device<?, ?, ?> device) {
mDevices.remove(device);
for (DeviceListenerCallback l : mListeners)
l.deviceRemoved(device);
}
@Override
public void afterShutdown() {
}
@Override
public void beforeShutdown(Registry registry) {
}
@Override
public void localDeviceAdded(Registry registry, LocalDevice device) {
deviceAdded(device);
}
@Override
public void localDeviceRemoved(Registry registry, LocalDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
deviceAdded(device);
}
@Override
public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device,
Exception exception) {
Log.w(TAG, "Remote device discovery failed", exception);
}
@Override
public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
}
@Override
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
deviceRemoved(device);
}
@Override
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
deviceRemoved(device);
deviceAdded(device);
}
}

View file

@ -42,8 +42,17 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
/**
* Allows displaying UPNP media server directory contents in a ListView.
*
* @author Felix Ableitner
*
*/
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> { public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
/**
* Provides sorting of elements by track number.
*/
public FileArrayAdapter(Context context) { public FileArrayAdapter(Context context) {
super(context, R.layout.list_item); super(context, R.layout.list_item);
sort(new Comparator<DIDLObject>() { sort(new Comparator<DIDLObject>() {
@ -65,6 +74,9 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
}); });
} }
/**
* Returns a view with folder/media title, and artist name (for audio only).
*/
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) { if (convertView == null) {

View file

@ -15,7 +15,7 @@ import android.util.Log;
/** /**
* Handles background task of loading a bitmap by URI. * Handles background task of loading a bitmap by URI.
* *
* @author Felix * @author Felix Ableitner
* *
*/ */
public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> { public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> {

View file

@ -13,7 +13,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 * @author Felix Ableitner
* *
*/ */
public class RemoteImageView extends ImageView { public class RemoteImageView extends ImageView {

View file

@ -0,0 +1,103 @@
package com.github.nutomic.controldlna;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.android.AndroidUpnpServiceImpl;
import org.teleal.cling.controlpoint.ActionCallback;
import org.teleal.cling.controlpoint.SubscriptionCallback;
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.model.meta.Service;
import org.teleal.cling.model.types.ServiceType;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
/**
* Handles UPNP connection, including device discovery and general queries.
*
* @author Felix Ableitner
*
*/
public class UpnpController {
private static final String TAG = "DlnaController";
private DeviceListener mDeviceListener = new DeviceListener();
protected AndroidUpnpService mUpnpService;
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
/**
* Registers DeviceListener, adds known devices and starts search.
*/
public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service;
Log.i(TAG, "Starting device search");
mUpnpService.getRegistry().addListener(mDeviceListener);
for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) {
if (d instanceof LocalDevice) {
mDeviceListener.localDeviceAdded(mUpnpService.getRegistry(), (LocalDevice) d);
}
else {
mDeviceListener.remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
}
}
mUpnpService.getControlPoint().search();
}
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
/**
* Opens connection to the Cling UPNP service.
*
* @param context Application context.
*/
public void open(Context context) {
context.bindService(
new Intent(context, AndroidUpnpServiceImpl.class),
mUpnpServiceConnection,
Context.BIND_AUTO_CREATE
);
}
/**
* Closes the connection to the Cling UPNP service.
*
* @param context Application context.
*/
public void close(Context context) {
mUpnpService.getRegistry().removeListener(mDeviceListener);
context.unbindService(mUpnpServiceConnection);
}
/**
* Returns a device service by name for direct queries.
*/
public Service<?, ?> getService(Device<?, ?, ?> device, String name) {
return device.findService(
new ServiceType("schemas-upnp-org", name));
}
public void execute(ActionCallback callback) {
mUpnpService.getControlPoint().execute(callback);
}
public void execute(SubscriptionCallback callback) {
mUpnpService.getControlPoint().execute(callback);
}
public DeviceListener getDeviceListener() {
return mDeviceListener;
}
}

View file

@ -0,0 +1,192 @@
package com.github.nutomic.controldlna;
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.meta.StateVariableAllowedValueRange;
import org.teleal.cling.support.avtransport.callback.Seek;
import org.teleal.cling.support.model.SeekMode;
import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.github.nutomic.controldlna.service.PlayService;
import com.github.nutomic.controldlna.service.PlayServiceBinder;
/**
* Handles connection to PlayService and provides methods related to playback.
*
* @author Felix Ableitner
*
*/
public class UpnpPlayer extends UpnpController {
private static final String TAG = "UpnpPlayer";
private PlayServiceBinder mPlayService;
private long mMinVolume;
private long mMaxVolume;
private long mVolumeStep;
private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mPlayService = (PlayServiceBinder) service;
}
public void onServiceDisconnected(ComponentName className) {
mPlayService = null;
}
};
@Override
public void open(Context context) {
super.open(context);
context.bindService(
new Intent(context, PlayService.class),
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
);
}
@Override
public void close(Context context) {
super.close(context);
context.unbindService(mPlayServiceConnection);
}
/**
* Returns a device service by name for direct queries.
*/
public Service<?, ?> getService(String name) {
return getService(mPlayService.getService().getRenderer(), name);
}
/**
* Sets an absolute volume.
*/
public void setVolume(long newVolume) {
if (mPlayService.getService().getRenderer() == null)
return;
if (newVolume > mMaxVolume) newVolume = mMaxVolume;
if (newVolume < mMinVolume) newVolume = mMinVolume;
mUpnpService.getControlPoint().execute(
new SetVolume(getService("RenderingControl"), newVolume) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.d(TAG, "Failed to set new Volume: " + defaultMessage);
}
});
}
/**
* Increases or decreases volume relative to current one.
*
* @param amount Amount to change volume by (negative to lower volume).
*/
private void changeVolume(final long amount) {
if (mPlayService.getService().getRenderer() == null)
return;
mUpnpService.getControlPoint().execute(
new GetVolume(getService("RenderingControl")) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Failed to get current Volume: " + defaultMessage);
}
@SuppressWarnings("rawtypes")
@Override
public void received(ActionInvocation invocation, int currentVolume) {
setVolume(currentVolume + amount);
}
});
}
/**
* Increases the device volume by a minimum volume step.
*/
public void increaseVolume() {
changeVolume(mVolumeStep);
}
/**
* Decreases the device volume by a minimum volume step.
*/
public void decreaseVolume() {
changeVolume(- mVolumeStep);
}
/**
* Selects the renderer for playback, applying its minimum and maximum volume.
*/
public void selectRenderer(Device<?, ?, ?> renderer) {
mPlayService.getService().setRenderer(renderer);
if (getService("RenderingControl").getStateVariable("Volume") != null) {
StateVariableAllowedValueRange volumeRange =
getService("RenderingControl").getStateVariable("Volume")
.getTypeDetails().getAllowedValueRange();
mMinVolume = volumeRange.getMinimum();
mMaxVolume = volumeRange.getMaximum();
mVolumeStep = volumeRange.getStep();
}
else {
mMinVolume = 0;
mMaxVolume = 100;
mVolumeStep = 1;
}
// Hack, needed as using a smaller step seems to not
// increase volume on some devices.
mVolumeStep = 4;
}
/**
* Seeks to the given absolute time in seconds.
*/
public void seek(int absoluteTime) {
if (mPlayService.getService().getRenderer() == null)
return;
mUpnpService.getControlPoint().execute(new Seek(
getService(mPlayService.getService().getRenderer(), "AVTransport"),
SeekMode.REL_TIME,
Integer.toString(absoluteTime)) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Seek failed: " + defaultMessage);
}
});
}
/**
* Returns the service that handles actual playback.
*/
public PlayService getPlayService() {
return mPlayService.getService();
}
}

View file

@ -27,31 +27,34 @@ 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.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.UpnpPlayer;
/** /**
* Main activity, with tabs for media servers and media renderers. * Main activity, with tabs for media servers and media renderers.
* *
* @author Felix * @author Felix Ableitner
* *
*/ */
public class MainActivity extends SherlockFragmentActivity implements public class MainActivity extends SherlockFragmentActivity implements
ActionBar.TabListener { ActionBar.TabListener {
/**
* Manages all UPNP connections including playback.
*/
private UpnpPlayer mPlayer = new UpnpPlayer();
/** /**
* Provides Fragments, holding all of them in memory. * Provides Fragments, holding all of them in memory.
*/ */
@ -102,6 +105,14 @@ public class MainActivity extends SherlockFragmentActivity implements
actionBar.addTab(actionBar.newTab() actionBar.addTab(actionBar.newTab()
.setText(R.string.title_renderer) .setText(R.string.title_renderer)
.setTabListener(this)); .setTabListener(this));
mPlayer.open(getApplicationContext());
}
@Override
protected void onDestroy() {
super.onDestroy();
mPlayer.close(getApplicationContext());
} }
@Override @Override
@ -148,15 +159,12 @@ public class MainActivity extends SherlockFragmentActivity implements
* Listener for the 'back' key. * Listener for the 'back' key.
*/ */
public interface OnBackPressedListener { public interface OnBackPressedListener {
/**
* Returns true if the press was consumed, false otherwise.
*/
public boolean onBackPressed(); public boolean onBackPressed();
} }
/** /**
* Forwards back press to active Fragment. * Forwards back press to active Fragment (unless the fragment is
* showing its root view).
*/ */
@Override @Override
public void onBackPressed() { public void onBackPressed() {
@ -167,31 +175,37 @@ public class MainActivity extends SherlockFragmentActivity implements
} }
/** /**
* Utility function to call RendererFragment.play from ServerFragment. * Changes volume on key press.
*/
public void play(List<Item> playlist, int start) {
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
mRendererFragment.setPlaylist(playlist, start);
}
/**
* Sends volume key events to RendererFragment (which sends them to
* media renderer).
*/ */
@Override @Override
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)
mRendererFragment.changeVolume(true); mPlayer.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)
mRendererFragment.changeVolume(false); mPlayer.decreaseVolume();
return true; return true;
default: default:
return super.dispatchKeyEvent(event); return super.dispatchKeyEvent(event);
} }
} }
/**
* Returns shared instance of UpnpPlayer.
*/
public UpnpPlayer getUpnpPlayer() {
return mPlayer;
}
/**
* Opens the UPNP renderer tab in the activity.
*/
public void switchToRendererTab() {
Log.d("tag", "called");
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
}
} }

View file

@ -30,8 +30,6 @@ package com.github.nutomic.controldlna.gui;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.teleal.cling.android.AndroidUpnpService;
import org.teleal.cling.android.AndroidUpnpServiceImpl;
import org.teleal.cling.controlpoint.SubscriptionCallback; import org.teleal.cling.controlpoint.SubscriptionCallback;
import org.teleal.cling.model.action.ActionInvocation; import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.gena.CancelReason; import org.teleal.cling.model.gena.CancelReason;
@ -40,28 +38,18 @@ import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device; import org.teleal.cling.model.meta.Device;
import org.teleal.cling.model.meta.Service; import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.state.StateVariableValue; import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.ServiceType;
import org.teleal.cling.support.avtransport.callback.GetPositionInfo; import org.teleal.cling.support.avtransport.callback.GetPositionInfo;
import org.teleal.cling.support.avtransport.callback.Seek;
import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser; import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser;
import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable; import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable;
import org.teleal.cling.support.lastchange.LastChange; import org.teleal.cling.support.lastchange.LastChange;
import org.teleal.cling.support.model.PositionInfo; import org.teleal.cling.support.model.PositionInfo;
import org.teleal.cling.support.model.SeekMode;
import org.teleal.cling.support.model.item.Item; import org.teleal.cling.support.model.item.Item;
import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color; import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -76,19 +64,17 @@ import android.widget.ImageButton;
import android.widget.ListView; import android.widget.ListView;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
import com.github.nutomic.controldlna.DeviceArrayAdapter; import com.github.nutomic.controldlna.DeviceArrayAdapter;
import com.github.nutomic.controldlna.FileArrayAdapter; import com.github.nutomic.controldlna.FileArrayAdapter;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.UpnpPlayer;
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener; import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
import com.github.nutomic.controldlna.service.PlayService;
import com.github.nutomic.controldlna.service.PlayServiceBinder;
/** /**
* Shows a list of media servers, allowing to select one for playback. * Shows a list of media servers, allowing to select one for playback.
* *
* @author Felix * @author Felix Ableitner
* *
*/ */
public class RendererFragment extends Fragment implements public class RendererFragment extends Fragment implements
@ -118,43 +104,6 @@ public class RendererFragment extends Fragment implements
private SubscriptionCallback mSubscriptionCallback; private SubscriptionCallback mSubscriptionCallback;
private PlayServiceBinder mPlayService;
private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mPlayService = (PlayServiceBinder) service;
}
public void onServiceDisconnected(ComponentName className) {
mPlayService = null;
}
};
/**
* Cling UPNP service.
*/
private AndroidUpnpService mUpnpService;
/**
* Connection Cling to UPNP service.
*/
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service;
Log.i(TAG, "Starting device search");
mUpnpService.getRegistry().addListener(mRendererAdapter);
mUpnpService.getControlPoint().search();
mRendererAdapter.add(
mUpnpService.getControlPoint().getRegistry().getDevices());
}
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@ -186,29 +135,22 @@ public class RendererFragment extends Fragment implements
mPlayPause.setImageResource(R.drawable.ic_media_play); mPlayPause.setImageResource(R.drawable.ic_media_play);
getView().findViewById(R.id.previous).setOnClickListener(this); getView().findViewById(R.id.previous).setOnClickListener(this);
getView().findViewById(R.id.next).setOnClickListener(this); getView().findViewById(R.id.next).setOnClickListener(this);
getPlayer().getDeviceListener().addCallback(mRendererAdapter);
}
getActivity().startService(new Intent(getActivity(), PlayService.class)); private UpnpPlayer getPlayer() {
getActivity().getApplicationContext().bindService( MainActivity activity = (MainActivity) getActivity();
new Intent(getActivity(), PlayService.class), return activity.getUpnpPlayer();
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
);
getActivity().getApplicationContext().bindService(
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
mUpnpServiceConnection,
Context.BIND_AUTO_CREATE
);
} }
/** /**
* Polls the renderer for the current play progress as long as * Polls the renderer for the current play progress as long as
* mPlaying is true. * playback is active.
*/ */
private void pollTimePosition() { private void pollTimePosition() {
final Service<?, ?> service = mCurrentRenderer.findService( Service<?, ?> service = getPlayer()
new ServiceType("schemas-upnp-org", "AVTransport")); .getService(mCurrentRenderer, "AVTransport");
mUpnpService.getControlPoint().execute( getPlayer().execute(
new GetPositionInfo(service) { new GetPositionInfo(service) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -237,24 +179,13 @@ public class RendererFragment extends Fragment implements
} }
} }
/**
* Closes Cling UPNP service.
*/
@Override
public void onDestroy() {
super.onDestroy();
mUpnpService.getRegistry().removeListener(mRendererAdapter);
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
}
/** /**
* Sets the new playlist. * Sets the new playlist.
*/ */
public void setPlaylist(List<Item> playlist, int start) { public void setPlaylist(List<Item> playlist, int start) {
mPlaylistAdapter.clear(); mPlaylistAdapter.clear();
mPlaylistAdapter.add(playlist); mPlaylistAdapter.add(playlist);
mPlayService.getService().setPlaylist(playlist, start); getPlayer().getPlayService().setPlaylist(playlist, start);
} }
/** /**
@ -288,7 +219,7 @@ public class RendererFragment extends Fragment implements
} }
} }
else if (mListView.getAdapter() == mPlaylistAdapter) else if (mListView.getAdapter() == mPlaylistAdapter)
mPlayService.getService().playTrack(position); getPlayer().getPlayService().playTrack(position);
} }
/** /**
@ -302,9 +233,9 @@ public class RendererFragment extends Fragment implements
mSubscriptionCallback.end(); mSubscriptionCallback.end();
mCurrentRenderer = renderer; mCurrentRenderer = renderer;
mPlayService.getService().setRenderer(renderer); getPlayer().selectRenderer(renderer);
mSubscriptionCallback = new SubscriptionCallback( mSubscriptionCallback = new SubscriptionCallback(
getService("AVTransport"), 600) { getPlayer().getService("AVTransport"), 600) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
@ -374,10 +305,10 @@ public class RendererFragment extends Fragment implements
Log.d(TAG, defaultMsg); Log.d(TAG, defaultMsg);
} }
}; };
mUpnpService.getControlPoint().execute(mSubscriptionCallback); getPlayer().execute(mSubscriptionCallback);
} }
mPlaylistAdapter.clear(); mPlaylistAdapter.clear();
mPlaylistAdapter.add(mPlayService.getService().getPlaylist()); mPlaylistAdapter.add(getPlayer().getPlayService().getPlaylist());
mListView.setAdapter(mPlaylistAdapter); mListView.setAdapter(mPlaylistAdapter);
} }
@ -388,7 +319,7 @@ public class RendererFragment extends Fragment implements
if (mListView.getAdapter() == mRendererAdapter) if (mListView.getAdapter() == mRendererAdapter)
return; return;
disableTrackHighlight(); disableTrackHighlight();
mCurrentTrackView = mListView.getChildAt(mPlayService.getService() mCurrentTrackView = mListView.getChildAt(getPlayer().getPlayService()
.getCurrentTrack() .getCurrentTrack()
- mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount()); - mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
if (mCurrentTrackView != null) if (mCurrentTrackView != null)
@ -426,15 +357,15 @@ public class RendererFragment extends Fragment implements
switch (v.getId()) { switch (v.getId()) {
case R.id.playpause: case R.id.playpause:
if (mPlaying) if (mPlaying)
mPlayService.getService().pause(); getPlayer().getPlayService().pause();
else else
mPlayService.getService().play(); getPlayer().getPlayService().play();
break; break;
case R.id.previous: case R.id.previous:
mPlayService.getService().playPrevious(); getPlayer().getPlayService().playPrevious();
break; break;
case R.id.next: case R.id.next:
mPlayService.getService().playNext(); getPlayer().getPlayService().playNext();
break; break;
} }
} }
@ -446,17 +377,7 @@ public class RendererFragment extends Fragment implements
public void onProgressChanged(SeekBar seekBar, int progress, public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) { boolean fromUser) {
if (fromUser) { if (fromUser) {
mUpnpService.getControlPoint().execute(new Seek( getPlayer().seek(progress);
getService("AVTransport"), SeekMode.REL_TIME,
Integer.toString(progress)) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Seek failed: " + defaultMessage);
}
});
} }
} }
@ -468,50 +389,6 @@ public class RendererFragment extends Fragment implements
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
} }
/**
* Increases or decreases volume.
*
* @param increase True to increase volume, false to decrease.
*/
@SuppressWarnings("rawtypes")
public void changeVolume(final boolean increase) {
if (mCurrentRenderer == null) {
Toast.makeText(getActivity(), R.string.select_renderer,
Toast.LENGTH_SHORT).show();
return;
}
final Service<?, ?> service = getService("RenderingControl");
mUpnpService.getControlPoint().execute(
new GetVolume(service) {
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.d(TAG, "Failed to get current Volume: " + defaultMessage);
}
@Override
public void received(ActionInvocation invocation, int volume) {
int newVolume = volume + ((increase) ? 4 : -4);
if (newVolume < 0) newVolume = 0;
mUpnpService.getControlPoint().execute(
new SetVolume(service, newVolume) {
@Override
public void failure(ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.d(TAG, "Failed to set new Volume: " + defaultMessage);
}
});
}
});
}
private Service<?, ?> getService(String name) {
return mCurrentRenderer.findService(
new ServiceType("schemas-upnp-org", name));
}
@Override @Override
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) { public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
enableTrackHighlight(); enableTrackHighlight();

View file

@ -31,8 +31,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Stack; 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.action.ActionInvocation;
import org.teleal.cling.model.message.UpnpResponse; import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Device; import org.teleal.cling.model.meta.Device;
@ -44,12 +42,7 @@ import org.teleal.cling.support.model.DIDLContent;
import org.teleal.cling.support.model.container.Container; import org.teleal.cling.support.model.container.Container;
import org.teleal.cling.support.model.item.Item; import org.teleal.cling.support.model.item.Item;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.v4.app.ListFragment; import android.support.v4.app.ListFragment;
import android.util.Log; import android.util.Log;
@ -61,10 +54,10 @@ import com.github.nutomic.controldlna.FileArrayAdapter;
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener; import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
/** /**
* Shows a list of media servers, upon selecting one, allows browsing theur * Shows a list of media servers, upon selecting one, allows browsing their
* directories. * directories.
* *
* @author Felix * @author Felix Ableitner
* *
*/ */
public class ServerFragment extends ListFragment implements OnBackPressedListener { public class ServerFragment extends ListFragment implements OnBackPressedListener {
@ -100,30 +93,6 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
*/ */
private Stack<Parcelable> mListState = new Stack<Parcelable>(); private Stack<Parcelable> mListState = new Stack<Parcelable>();
/**
* Cling UPNP service.
*/
private AndroidUpnpService mUpnpService;
/**
* Connection Cling to UPNP service.
*/
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mUpnpService = (AndroidUpnpService) service;
Log.i(TAG, "Starting device search");
mUpnpService.getRegistry().addListener(mServerAdapter);
mUpnpService.getControlPoint().search();
mServerAdapter.add(
mUpnpService.getControlPoint().getRegistry().getDevices());
}
public void onServiceDisconnected(ComponentName className) {
mUpnpService = null;
}
};
/** /**
* Initializes ListView adapters, launches Cling UPNP service. * Initializes ListView adapters, launches Cling UPNP service.
*/ */
@ -135,23 +104,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
mServerAdapter = new DeviceArrayAdapter( mServerAdapter = new DeviceArrayAdapter(
getActivity(), DeviceArrayAdapter.SERVER); getActivity(), DeviceArrayAdapter.SERVER);
setListAdapter(mServerAdapter); setListAdapter(mServerAdapter);
MainActivity activity = (MainActivity) getActivity();
getActivity().getApplicationContext().bindService( activity.getUpnpPlayer().getDeviceListener().addCallback(mServerAdapter);
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
mUpnpServiceConnection,
Context.BIND_AUTO_CREATE
);
}
/**
* Closes Cling UPNP service.
*/
@Override
public void onDestroy() {
super.onDestroy();
if (mUpnpService != null)
mUpnpService.getRegistry().removeListener(mServerAdapter);
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
} }
/** /**
@ -175,7 +129,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
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.switchToRendererTab();
activity.getUpnpPlayer().getPlayService()
.setPlaylist(playlist, position);
} }
} }
} }
@ -191,11 +147,15 @@ 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,
* which means we restore scroll position etc.
*/ */
private void getFiles(final boolean restoreListState) { private void getFiles(final boolean restoreListState) {
Service<?, ?> service = mCurrentServer.findService( Service<?, ?> service = mCurrentServer.findService(
new ServiceType("schemas-upnp-org", "ContentDirectory")); new ServiceType("schemas-upnp-org", "ContentDirectory"));
mUpnpService.getControlPoint().execute(new Browse(service, MainActivity activity = (MainActivity) getActivity();
activity.getUpnpPlayer().execute(new Browse(service,
mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) { mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")

View file

@ -73,6 +73,12 @@ import com.github.nutomic.controldlna.LoadImageTask;
import com.github.nutomic.controldlna.R; import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.gui.MainActivity; import com.github.nutomic.controldlna.gui.MainActivity;
/**
* Background service that handles media playback to a single UPNP media renderer.
*
* @author Felix Ableitner
*
*/
public class PlayService extends Service { public class PlayService extends Service {
private static final String TAG = "PlayService"; private static final String TAG = "PlayService";
@ -82,7 +88,7 @@ public class PlayService extends Service {
private final PlayServiceBinder mBinder = new PlayServiceBinder(this); private final PlayServiceBinder mBinder = new PlayServiceBinder(this);
/** /**
* The DLNA media renderer device that is currently active. * The DLNA media renderer device that is currently active for playback.
*/ */
private Device<?, ?, ?> mRenderer; private Device<?, ?, ?> mRenderer;
@ -115,7 +121,7 @@ public class PlayService extends Service {
private AndroidUpnpService mUpnpService; private AndroidUpnpService mUpnpService;
/** /**
* Connection Cling to UPNP service. * Cling connection to UPNP service.
*/ */
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() { private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
@ -346,6 +352,10 @@ public class PlayService extends Service {
playTrack(mCurrentTrack); playTrack(mCurrentTrack);
} }
public Device<?, ?, ?> getRenderer() {
return mRenderer;
}
/** /**
* Sets a new playlist and starts playing. * Sets a new playlist and starts playing.
* *

View file

@ -29,6 +29,12 @@ package com.github.nutomic.controldlna.service;
import android.os.Binder; import android.os.Binder;
/**
* Provides connection to PlayService.
*
* @author Felix Ableitner
*
*/
public class PlayServiceBinder extends Binder { public class PlayServiceBinder extends Binder {
PlayService mService; PlayService mService;