Added MediaRouter support.

This commit is contained in:
Felix Ableitner 2013-09-26 18:29:26 +02:00
parent 048fad32f6
commit 5edbd2886f
10 changed files with 719 additions and 30 deletions

View file

@ -36,6 +36,16 @@
<service android:name="com.github.nutomic.controldlna.upnp.PlayService" />
<service android:name="com.github.nutomic.controldlna.mediarouter.RemotePlayService" />
<service android:name=".mediarouter.ProviderService"
android:label="sample_media_route_provider_service"
android:process=":mrp">
<intent-filter>
<action android:name="android.media.MediaRouteProviderService" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -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

View file

@ -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,7 +147,7 @@ public class RendererFragment extends Fragment implements
* playback is active.
*/
private void pollTimePosition() {
Service<?, ?> service = getPlayer()
Service<?, ?> service = UpnpController
.getService(mCurrentRenderer, "AVTransport");
getPlayer().execute(
new GetPositionInfo(service) {

View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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<Device> CREATOR
= new Parcelable.Creator<Device>() {
public Device createFromParcel(Parcel in) {
return new Device(in);
}
public Device[] newArray(int size) {
return new Device[size];
}
};
private Device(Parcel in) {
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<String, Device> mDevices = new HashMap<String, Device>();
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<IntentFilter> 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<IntentFilter>();
CONTROL_FILTERS.add(f);
}
/**
* Listens for messages about devices.
*/
static private class DeviceListener extends Handler {
private final WeakReference<Provider> mService;
DeviceListener(Provider provider) {
mService = new WeakReference<Provider>(provider);
}
@Override
public void handleMessage(Message msg) {
if (mService.get() != null) {
mService.get().handleMessage(msg);
}
}
}
final Messenger mListener = new Messenger(new DeviceListener(this));
public Provider(Context context) {
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<String, Device> 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;
}
}
}

View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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;
}
}

View file

@ -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 <organization> 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 <COPYRIGHT HOLDER> 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<RemotePlayService> mService;
IncomingHandler(RemotePlayService service) {
mService = new WeakReference<RemotePlayService>(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<String, Device<?, ?, ?>> mDevices = new HashMap<String, Device<?, ?, ?>>();
@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<Item> playlist = new ArrayList<Item>();
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;
}
}
}

View file

@ -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,15 +75,17 @@ 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
public void afterShutdown() {
@ -127,7 +129,6 @@ public class DeviceListener implements RegistryListener {
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
deviceRemoved(device);
deviceAdded(device);
}
}

View file

@ -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";
}
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() {
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,17 +268,28 @@ 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.
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,
public void failure( ActionInvocation invocation,
UpnpResponse operation, String defaultMessage) {
Log.w(TAG, "Stop failed: " + defaultMessage);
}
});
}
});
}
public void setRenderer(Device<?, ?, ?> renderer) {
@ -390,4 +407,8 @@ public class PlayService extends Service {
return mCurrentTrack;
}
public void setShowNotification(boolean value) {
mShowNotification = value;
}
}

View file

@ -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));
}

View file

@ -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) {
@ -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;
}
}