diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f30a9dd..a0eeb53 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -35,6 +35,16 @@
+
+
+
+
+
+
+
+
diff --git a/project.properties b/project.properties
index b2b2557..a62728b 100644
--- a/project.properties
+++ b/project.properties
@@ -13,3 +13,4 @@
# Project target.
target=android-18
android.library.reference.1=../android-support-v7-appcompat
+android.library.reference.2=../android-support-v7-mediarouter
diff --git a/src/com/github/nutomic/controldlna/gui/RendererFragment.java b/src/com/github/nutomic/controldlna/gui/RendererFragment.java
index 24dd7f7..0276181 100644
--- a/src/com/github/nutomic/controldlna/gui/RendererFragment.java
+++ b/src/com/github/nutomic/controldlna/gui/RendererFragment.java
@@ -65,6 +65,7 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
import com.github.nutomic.controldlna.R;
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
+import com.github.nutomic.controldlna.upnp.UpnpController;
import com.github.nutomic.controldlna.upnp.UpnpPlayer;
import com.github.nutomic.controldlna.utility.DeviceArrayAdapter;
import com.github.nutomic.controldlna.utility.FileArrayAdapter;
@@ -146,8 +147,8 @@ public class RendererFragment extends Fragment implements
* playback is active.
*/
private void pollTimePosition() {
- Service, ?> service = getPlayer()
- .getService(mCurrentRenderer, "AVTransport");
+ Service, ?> service = UpnpController
+ .getService(mCurrentRenderer, "AVTransport");
getPlayer().execute(
new GetPositionInfo(service) {
diff --git a/src/com/github/nutomic/controldlna/mediarouter/Provider.java b/src/com/github/nutomic/controldlna/mediarouter/Provider.java
new file mode 100644
index 0000000..de21fcc
--- /dev/null
+++ b/src/com/github/nutomic/controldlna/mediarouter/Provider.java
@@ -0,0 +1,358 @@
+/*
+Copyright (c) 2013, Felix Ableitner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+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
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+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
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.github.nutomic.controldlna.mediarouter;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteDescriptor;
+import android.support.v7.media.MediaRouteDiscoveryRequest;
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderDescriptor.Builder;
+import android.support.v7.media.MediaRouter;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+
+/**
+ * Allows playing to a DLNA renderer from a remote app.
+ *
+ * @author Felix Ableitner
+ */
+final class Provider extends MediaRouteProvider {
+
+ // Device has been added.
+ // param: Device device
+ public static final int MSG_RENDERER_ADDED = 1;
+ // Device has been removed.
+ // param: int id
+ public static final int MSG_RENDERER_REMOVED = 2;
+
+ /**
+ * Allows passing and storing basic information about a device.
+ */
+ static public class Device implements Parcelable {
+
+ public String id;
+ public String name;
+ public String description;
+ public int volume;
+ public int volumeMax;
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public Device createFromParcel(Parcel in) {
+ return new Device(in);
+ }
+
+ public Device[] newArray(int size) {
+ return new Device[size];
+ }
+ };
+
+ private Device(Parcel in) {
+ id = in.readString();
+ name = in.readString();
+ description = in.readString();
+ volume = in.readInt();
+ volumeMax = in.readInt();
+ }
+
+ public Device(String id, String name, String description, int volume, int volumeMax) {
+ this.id = id;
+ this.name = name;
+ this.description = description;
+ this.volume = volume;
+ this.volumeMax = volumeMax;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(id);
+ dest.writeString(name);
+ dest.writeString(description);
+ dest.writeInt(volume);
+ dest.writeInt(volumeMax);
+ }
+
+ }
+
+ private HashMap mDevices = new HashMap();
+
+ private Messenger mRemotePlayService;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ mRemotePlayService = new Messenger(service);
+ Message msg = Message.obtain(null, RemotePlayService.MSG_OPEN, 0, 0);
+ msg.replyTo = mListener;
+ try {
+ mRemotePlayService.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ mRemotePlayService = null;
+ }
+ };
+
+ private static final ArrayList CONTROL_FILTERS;
+ // Static constructor for CONTROL_FILTERS.
+ static {
+
+ IntentFilter f = new IntentFilter();
+ f.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f.addAction(MediaControlIntent.ACTION_PLAY);
+ f.addAction(MediaControlIntent.ACTION_PAUSE);
+ f.addAction(MediaControlIntent.ACTION_SEEK);
+ f.addAction(MediaControlIntent.ACTION_STOP);
+ f.addDataScheme("http");
+ f.addDataScheme("https");
+ try {
+ f.addDataType("video/*");
+ f.addDataType("audio/*");
+ } catch (MalformedMimeTypeException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ CONTROL_FILTERS = new ArrayList();
+ CONTROL_FILTERS.add(f);
+ }
+
+ /**
+ * Listens for messages about devices.
+ */
+ static private class DeviceListener extends Handler {
+
+ private final WeakReference mService;
+
+ DeviceListener(Provider provider) {
+ mService = new WeakReference(provider);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mService.get() != null) {
+ mService.get().handleMessage(msg);
+ }
+ }
+ }
+
+ final Messenger mListener = new Messenger(new DeviceListener(this));
+
+ public Provider(Context context) {
+ super(context);
+ context.bindService(
+ new Intent(context, RemotePlayService.class),
+ mConnection,
+ Context.BIND_AUTO_CREATE
+ );
+ }
+
+ public void close() {
+ getContext().unbindService(mConnection);
+ }
+
+ @Override
+ public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+ if (request == null) return;
+ Message msg;
+ if (request.isActiveScan()) {
+ msg = Message.obtain(null, RemotePlayService.MSG_OPEN, 0, 0);
+ msg.replyTo = mListener;
+ }
+ else {
+ msg = Message.obtain(null, RemotePlayService.MSG_CLOSE, 0, 0);
+ }
+ try {
+ if (mRemotePlayService != null) {
+ mRemotePlayService.send(msg);
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ return new RouteController(routeId);
+ }
+
+ private void updateRoutes() {
+ Builder builder = new Builder();
+ for (Entry d : mDevices.entrySet()) {
+ MediaRouteDescriptor routeDescriptor = new MediaRouteDescriptor.Builder(
+ d.getValue().id,
+ d.getValue().name)
+ .setDescription("DLNA Playback")
+ .addControlFilters(CONTROL_FILTERS)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(d.getValue().volumeMax)
+ .setVolume(d.getValue().volume)
+ .build();
+ builder.addRoute(routeDescriptor);
+ }
+ setDescriptor(builder.build());
+ }
+
+ /**
+ * Receives and forwards device selections, volume change
+ * requests and control requests.
+ */
+ private final class RouteController extends MediaRouteProvider.RouteController {
+ private final String mRouteId;
+
+ public RouteController(String routeId) {
+ mRouteId = routeId;
+ }
+
+ @Override
+ public void onRelease() {
+ }
+
+ @Override
+ public void onSelect() {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_SELECT, 0, 0);
+ msg.getData().putString("id", mRouteId);
+ try {
+ mRemotePlayService.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onUnselect() {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_UNSELECT, 0, 0);
+ msg.getData().putString("id", mRouteId);
+ try {
+ mRemotePlayService.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onSetVolume(int volume) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_SET_VOPLUME, 0, 0);
+ msg.getData().putInt("volume", volume);
+ mDevices.get(mRouteId).volume = volume;
+ try {
+ mRemotePlayService.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateRoutes();
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_CHANGE_VOLUME, 0, 0);
+ msg.getData().putInt("delta", delta);
+ mDevices.get(mRouteId).volume += delta;
+ try {
+ mRemotePlayService.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ updateRoutes();
+ }
+
+ @Override
+ public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+ try {
+ if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_PLAY, 0, 0);
+ msg.getData().putString("uri", intent.getDataString());
+ mRemotePlayService.send(msg);
+ return true;
+ }
+ else if (intent.getAction().equals(MediaControlIntent.ACTION_PAUSE)) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_PAUSE, 0, 0);
+ mRemotePlayService.send(msg);
+ return true;
+ }
+ else if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_STOP, 0, 0);
+ mRemotePlayService.send(msg);
+ return true;
+ }
+ else if (intent.getAction().equals(MediaControlIntent.ACTION_PLAY)) {
+ Message msg = Message.obtain(null, RemotePlayService.MSG_PLAY, 0, 0);
+ msg.getData().putLong("milliseconds",
+ intent.getIntExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0));
+ mRemotePlayService.send(msg);
+ return true;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+ }
+
+ public void handleMessage(Message msg) {
+ Bundle data = msg.getData();
+ switch (msg.what) {
+ case MSG_RENDERER_ADDED:
+ msg.getData().setClassLoader(Device.class.getClassLoader());
+ Device device = (Device) data.getParcelable("device");
+ mDevices.put(device.id, device);
+ updateRoutes();
+ break;
+ case MSG_RENDERER_REMOVED:
+ mDevices.remove(data.getString("id"));
+ updateRoutes();
+ break;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/github/nutomic/controldlna/mediarouter/ProviderService.java b/src/com/github/nutomic/controldlna/mediarouter/ProviderService.java
new file mode 100644
index 0000000..ad5798f
--- /dev/null
+++ b/src/com/github/nutomic/controldlna/mediarouter/ProviderService.java
@@ -0,0 +1,50 @@
+/*
+Copyright (c) 2013, Felix Ableitner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+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
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+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
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.github.nutomic.controldlna.mediarouter;
+
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderService;
+
+public class ProviderService extends MediaRouteProviderService {
+
+ private Provider mProvider;
+ @Override
+ public MediaRouteProvider onCreateMediaRouteProvider() {
+ if (mProvider == null) {
+ mProvider = new Provider(this);
+ }
+ return mProvider;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mProvider.close();
+ mProvider = null;
+ }
+}
diff --git a/src/com/github/nutomic/controldlna/mediarouter/RemotePlayService.java b/src/com/github/nutomic/controldlna/mediarouter/RemotePlayService.java
new file mode 100644
index 0000000..676bd99
--- /dev/null
+++ b/src/com/github/nutomic/controldlna/mediarouter/RemotePlayService.java
@@ -0,0 +1,247 @@
+/*
+Copyright (c) 2013, Felix Ableitner
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+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
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+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
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.github.nutomic.controldlna.mediarouter;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+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.RemoteDevice;
+import org.teleal.cling.model.meta.StateVariableAllowedValueRange;
+import org.teleal.cling.support.model.Res;
+import org.teleal.cling.support.model.item.Item;
+import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.github.nutomic.controldlna.upnp.DeviceListener.DeviceListenerCallback;
+import com.github.nutomic.controldlna.upnp.UpnpController;
+import com.github.nutomic.controldlna.upnp.UpnpPlayer;
+
+/**
+ * Allows UPNP playback from within different apps by providing a proxy interface.
+ *
+ * @author Felix Ableitner
+ *
+ */
+public class RemotePlayService extends Service implements DeviceListenerCallback {
+
+ private static final String TAG = "RemotePlayService";
+
+ // Start device discovery.
+ public static final int MSG_OPEN = 1;
+ // Stop device discovery.
+ public static final int MSG_CLOSE = 2;
+ // Select renderer.
+ // param: string device_id
+ public static final int MSG_SELECT = 3;
+ // Unselect renderer.
+ // param: int device_id
+ public static final int MSG_UNSELECT = 4;
+ // Set absolute volume.
+ // param: int volume
+ public static final int MSG_SET_VOPLUME = 5;
+ // Change volume relative to current volume.
+ // param: int delta
+ public static final int MSG_CHANGE_VOLUME = 6;
+ // Play from uri.
+ // param: String uri
+ public static final int MSG_PLAY = 7;
+ // Pause playback.
+ public static final int MSG_PAUSE = 8;
+ // Stop playback.
+ public static final int MSG_STOP = 9;
+ // Seek to absolute time in ms.
+ // param: long milliseconds
+ public static final int MSG_SEEK = 10;
+
+ /**
+ * Handles incoming messages from clients.
+ */
+ private static class IncomingHandler extends Handler {
+
+ private final WeakReference mService;
+
+ IncomingHandler(RemotePlayService service) {
+ mService = new WeakReference(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mService.get() != null) {
+ mService.get().handleMessage(msg);
+ }
+ }
+ }
+
+ private final Messenger mMessenger = new Messenger(new IncomingHandler(this));
+
+ private final UpnpPlayer mPlayer = new UpnpPlayer();
+
+ private Messenger mListener;
+
+ private HashMap> mDevices = new HashMap>();
+
+ @Override
+ public IBinder onBind(Intent itnent) {
+ return mMessenger.getBinder();
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mPlayer.open(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mPlayer.getPlayService().getRenderer() != null) {
+ mPlayer.getPlayService().stop();
+ }
+ mPlayer.close(this);
+ }
+
+ @Override
+ public void deviceAdded(final Device, ?, ?> device) {
+ if (device.getType().getType().equals("MediaRenderer") &&
+ device instanceof RemoteDevice) {
+ mDevices.put(device.getIdentity().getUdn().toString(), device);
+
+
+
+ mPlayer.execute(
+ new GetVolume(UpnpController.getService(device, "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) {
+ int maxVolume = 100;
+ if (UpnpPlayer.getService(device, "RenderingControl").getStateVariable("Volume") != null) {
+ StateVariableAllowedValueRange volumeRange =
+ UpnpPlayer.getService(device, "RenderingControl").getStateVariable("Volume")
+ .getTypeDetails().getAllowedValueRange();
+ maxVolume = (int) volumeRange.getMaximum();
+ }
+
+ Message msg = Message.obtain(null, Provider.MSG_RENDERER_ADDED, 0, 0);
+ msg.getData().putParcelable("device", new Provider.Device(
+ device.getIdentity().getUdn().toString(),
+ device.getDisplayString(),
+ device.getDetails().getManufacturerDetails().getManufacturer(),
+ currentVolume,
+ maxVolume));
+ try {
+ mListener.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void deviceRemoved(Device, ?, ?> device) {
+ if (device.getType().getType().equals("MediaRenderer") &&
+ device instanceof RemoteDevice) {
+ Message msg = Message.obtain(null, Provider.MSG_RENDERER_REMOVED, 0, 0);
+
+ String udn = device.getIdentity().getUdn().toString();
+ msg.getData().putString("id", udn);
+ mDevices.remove(udn);
+ try {
+ mListener.send(msg);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void handleMessage(Message msg) {
+ Bundle data = msg.getData();
+ switch (msg.what) {
+ case MSG_OPEN:
+ mPlayer.getDeviceListener().addCallback(this);
+ mListener = msg.replyTo;
+ break;
+ case MSG_CLOSE:
+ break;
+ case MSG_SELECT:
+ mPlayer.selectRenderer(mDevices.get(data.getString("id")));
+ break;
+ case MSG_UNSELECT:
+ mPlayer.getPlayService().stop();
+ break;
+ case MSG_SET_VOPLUME:
+ mPlayer.setVolume(data.getInt("volume"));
+ break;
+ case MSG_CHANGE_VOLUME:
+ mPlayer.changeVolume(data.getInt("delta"));
+ break;
+ case MSG_PLAY:
+ mPlayer.getPlayService().setShowNotification(false);
+ Item item = new Item();
+ item.addResource(new Res());
+ item.getFirstResource().setValue(data.getString("uri"));
+ List- playlist = new ArrayList
- ();
+ playlist.add(item);
+ mPlayer.getPlayService().setPlaylist(playlist, 0);
+ break;
+ case MSG_PAUSE:
+ mPlayer.getPlayService().pause();
+ break;
+ case MSG_STOP:
+ mPlayer.getPlayService().stop();
+ break;
+ case MSG_SEEK:
+ mPlayer.seek((int) data.getLong("milliseconds") / 1000);
+ break;
+ }
+ }
+
+}
diff --git a/src/com/github/nutomic/controldlna/upnp/DeviceListener.java b/src/com/github/nutomic/controldlna/upnp/DeviceListener.java
index 89a7f21..131cf8b 100644
--- a/src/com/github/nutomic/controldlna/upnp/DeviceListener.java
+++ b/src/com/github/nutomic/controldlna/upnp/DeviceListener.java
@@ -64,9 +64,9 @@ public class DeviceListener implements RegistryListener {
public void addCallback(DeviceListenerCallback callback) {
mListeners.add(callback);
- for (Device, ?, ?> d : mDevices)
+ for (Device, ?, ?> d : mDevices) {
callback.deviceAdded(d);
-
+ }
}
public void removeCallback(DeviceListenerCallback callback) {
@@ -75,14 +75,16 @@ public class DeviceListener implements RegistryListener {
private void deviceAdded(Device, ?, ?> device) {
mDevices.add(device);
- for (DeviceListenerCallback l : mListeners)
+ for (DeviceListenerCallback l : mListeners) {
l.deviceAdded(device);
+ }
}
private void deviceRemoved(Device, ?, ?> device) {
mDevices.remove(device);
- for (DeviceListenerCallback l : mListeners)
+ for (DeviceListenerCallback l : mListeners) {
l.deviceRemoved(device);
+ }
}
@Override
@@ -126,8 +128,7 @@ public class DeviceListener implements RegistryListener {
@Override
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
deviceRemoved(device);
- deviceAdded(device);
-
+ deviceAdded(device);
}
}
diff --git a/src/com/github/nutomic/controldlna/upnp/PlayService.java b/src/com/github/nutomic/controldlna/upnp/PlayService.java
index fde1685..11b29a5 100644
--- a/src/com/github/nutomic/controldlna/upnp/PlayService.java
+++ b/src/com/github/nutomic/controldlna/upnp/PlayService.java
@@ -85,6 +85,8 @@ public class PlayService extends Service {
private static final int NOTIFICATION_ID = 1;
+ private boolean mShowNotification = true;
+
private final PlayServiceBinder mBinder = new PlayServiceBinder(this);
/**
@@ -205,9 +207,14 @@ public class PlayService extends Service {
Log.w(TAG, "Metadata serialization failed", e);
metadata = "NO METADATA";
}
- mUpnpService.getControlPoint().execute(new SetAVTransportURI(
+ setTransportUri(metadata,
+ mPlaylist.get(track).getFirstResource().getValue());
+ }
+
+ private void setTransportUri(String metadata, final String uri) {
+ mUpnpService.getControlPoint().execute(new SetAVTransportURI(
mAvTransportService,
- mPlaylist.get(track).getFirstResource().getValue(), metadata) {
+ uri, metadata) {
@SuppressWarnings("rawtypes")
@Override
public void failure(ActionInvocation invocation,
@@ -224,17 +231,16 @@ public class PlayService extends Service {
}
private void updateNotification() {
- new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack)
- .getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
+ if (mShowNotification) {
+ new CreateNotificationTask().execute(mPlaylist.get(mCurrentTrack)
+ .getFirstPropertyValue(DIDLObject.Property.UPNP.ALBUM_ART_URI.class));
+ }
}
/**
* Sends 'play' signal to current renderer.
*/
public void play() {
- if (mPlaylist.size() == 0)
- return;
-
updateNotification();
mUpnpService.getControlPoint().execute(
new Play(mAvTransportService) {
@@ -262,19 +268,30 @@ public class PlayService extends Service {
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Pause failed, trying stop: " + defaultMessage);
// Sometimes stop works even though pause does not.
- mUpnpService.getControlPoint().execute(
- new Stop(mAvTransportService) {
-
- @Override
- public void failure(ActionInvocation invocation,
- UpnpResponse operation, String defaultMessage) {
- Log.w(TAG, "Stop failed: " + defaultMessage);
- }
- });
+ stop();
}
});
}
+ /**
+ * Sends 'stop' signal to current renderer.
+ */
+ public void stop() {
+ mManuallyStopped.set(true);
+
+ mUpnpService.getControlPoint().execute(
+ new Stop(mAvTransportService) {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void failure( ActionInvocation invocation,
+ UpnpResponse operation, String defaultMessage) {
+ Log.w(TAG, "Stop failed: " + defaultMessage);
+ }
+ });
+
+ }
+
public void setRenderer(Device, ?, ?> renderer) {
if (mSubscriptionCallback != null)
mSubscriptionCallback.end();
@@ -389,5 +406,9 @@ public class PlayService extends Service {
public int getCurrentTrack() {
return mCurrentTrack;
}
+
+ public void setShowNotification(boolean value) {
+ mShowNotification = value;
+ }
}
diff --git a/src/com/github/nutomic/controldlna/upnp/UpnpController.java b/src/com/github/nutomic/controldlna/upnp/UpnpController.java
index ea5a7b8..9864415 100644
--- a/src/com/github/nutomic/controldlna/upnp/UpnpController.java
+++ b/src/com/github/nutomic/controldlna/upnp/UpnpController.java
@@ -110,7 +110,7 @@ public class UpnpController {
/**
* Returns a device service by name for direct queries.
*/
- public Service, ?> getService(Device, ?, ?> device, String name) {
+ public static Service, ?> getService(Device, ?, ?> device, String name) {
return device.findService(
new ServiceType("schemas-upnp-org", name));
}
diff --git a/src/com/github/nutomic/controldlna/upnp/UpnpPlayer.java b/src/com/github/nutomic/controldlna/upnp/UpnpPlayer.java
index 1f56d6d..6885373 100644
--- a/src/com/github/nutomic/controldlna/upnp/UpnpPlayer.java
+++ b/src/com/github/nutomic/controldlna/upnp/UpnpPlayer.java
@@ -83,7 +83,7 @@ public class UpnpPlayer extends UpnpController {
new Intent(context, PlayService.class),
mPlayServiceConnection,
Context.BIND_AUTO_CREATE
- );
+ );
}
@Override
@@ -110,7 +110,6 @@ public class UpnpPlayer extends UpnpController {
if (newVolume < mMinVolume) newVolume = mMinVolume;
mCurrentVolume = newVolume;
- Log.d(TAG, "volume: " + Integer.toString(mCurrentVolume));
mUpnpService.getControlPoint().execute(
new SetVolume(getService("RenderingControl"), newVolume) {
@@ -145,7 +144,7 @@ public class UpnpPlayer extends UpnpController {
public void selectRenderer(Device, ?, ?> renderer) {
mPlayService.getService().setRenderer(renderer);
- if (getService("RenderingControl").getStateVariable("Volume") != null) {
+ if (getService("RenderingControl").getStateVariable("Volume") != null) {
StateVariableAllowedValueRange volumeRange =
getService("RenderingControl").getStateVariable("Volume")
.getTypeDetails().getAllowedValueRange();
@@ -156,7 +155,6 @@ public class UpnpPlayer extends UpnpController {
else {
mMinVolume = 0;
mMaxVolume = 100;
- mVolumeStep = 1;
}
mUpnpService.getControlPoint().execute(
@@ -203,7 +201,9 @@ public class UpnpPlayer extends UpnpController {
* Returns the service that handles actual playback.
*/
public PlayService getPlayService() {
- return mPlayService.getService();
+ return (mPlayService != null)
+ ? mPlayService.getService()
+ : null;
}
}