Added MediaRouter support.
This commit is contained in:
parent
048fad32f6
commit
5edbd2886f
10 changed files with 719 additions and 30 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
358
src/com/github/nutomic/controldlna/mediarouter/Provider.java
Normal file
358
src/com/github/nutomic/controldlna/mediarouter/Provider.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Reference in a new issue