Extracted RemotePlayService.Binder into RemotePlayServiceBinder.
This commit is contained in:
parent
c0146efb72
commit
10e5f86ecf
2 changed files with 392 additions and 334 deletions
|
@ -27,40 +27,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
package com.github.nutomic.controldlna.upnp;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.teleal.cling.android.AndroidUpnpService;
|
||||
import org.teleal.cling.android.AndroidUpnpServiceImpl;
|
||||
import org.teleal.cling.controlpoint.SubscriptionCallback;
|
||||
import org.teleal.cling.model.action.ActionInvocation;
|
||||
import org.teleal.cling.model.gena.CancelReason;
|
||||
import org.teleal.cling.model.gena.GENASubscription;
|
||||
import org.teleal.cling.model.message.UpnpResponse;
|
||||
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.StateVariableAllowedValueRange;
|
||||
import org.teleal.cling.model.state.StateVariableValue;
|
||||
import org.teleal.cling.model.types.ServiceType;
|
||||
import org.teleal.cling.model.types.UDN;
|
||||
import org.teleal.cling.registry.Registry;
|
||||
import org.teleal.cling.registry.RegistryListener;
|
||||
import org.teleal.cling.support.avtransport.callback.GetPositionInfo;
|
||||
import org.teleal.cling.support.avtransport.callback.Pause;
|
||||
import org.teleal.cling.support.avtransport.callback.Play;
|
||||
import org.teleal.cling.support.avtransport.callback.Seek;
|
||||
import org.teleal.cling.support.avtransport.callback.SetAVTransportURI;
|
||||
import org.teleal.cling.support.avtransport.callback.Stop;
|
||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser;
|
||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable;
|
||||
import org.teleal.cling.support.lastchange.LastChange;
|
||||
import org.teleal.cling.support.model.PositionInfo;
|
||||
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.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
|
@ -76,13 +59,12 @@ import android.os.IBinder;
|
|||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
import android.support.v7.media.MediaItemStatus.Builder;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* Allows UPNP playback from within different apps by providing a proxy interface.
|
||||
* Allows UPNP playback from different apps by providing a proxy interface.
|
||||
* You can communicate to this service via RemotePlayServiceBinder.
|
||||
*
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
|
@ -91,9 +73,9 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
|
||||
private static final String TAG = "RemotePlayService";
|
||||
|
||||
private Messenger mListener;
|
||||
Messenger mListener;
|
||||
|
||||
private ConcurrentHashMap<String, Device<?, ?, ?>> mDevices =
|
||||
ConcurrentHashMap<String, Device<?, ?, ?>> mDevices =
|
||||
new ConcurrentHashMap<String, Device<?, ?, ?>>();
|
||||
|
||||
protected AndroidUpnpService mUpnpService;
|
||||
|
@ -120,320 +102,16 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* All active binders. The Hashmap value is unused.
|
||||
*/
|
||||
WeakHashMap<Binder, Boolean> mBinders = new WeakHashMap<Binder, Boolean>();
|
||||
|
||||
private class Binder extends IRemotePlayService.Stub {
|
||||
|
||||
private Device<?, ?, ?> mCurrentRenderer;
|
||||
|
||||
private int mPlaybackState;
|
||||
|
||||
private boolean mManuallyStopped;
|
||||
|
||||
private SubscriptionCallback mSubscriptionCallback;
|
||||
|
||||
@Override
|
||||
public void startSearch(Messenger listener)
|
||||
throws RemoteException {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectRenderer(String id) throws RemoteException {
|
||||
mCurrentRenderer = mDevices.get(id);
|
||||
for (Binder b : mBinders.keySet()) {
|
||||
if (b != this && mCurrentRenderer.equals(b.mCurrentRenderer))
|
||||
b.unselectRenderer("");
|
||||
}
|
||||
|
||||
mSubscriptionCallback = new SubscriptionCallback(
|
||||
mCurrentRenderer.findService(
|
||||
new ServiceType("schemas-upnp-org", "AVTransport")), 600) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void established(GENASubscription sub) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void ended(GENASubscription sub, CancelReason reason,
|
||||
UpnpResponse response) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void eventReceived(final GENASubscription sub) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, StateVariableValue> m = sub.getCurrentValues();
|
||||
try {
|
||||
LastChange lastChange = new LastChange(
|
||||
new AVTransportLastChangeParser(),
|
||||
m.get("LastChange").toString());
|
||||
if (lastChange.getEventedValue(0,
|
||||
AVTransportVariable.TransportState.class) == null)
|
||||
return;
|
||||
|
||||
switch (lastChange.getEventedValue(0,
|
||||
AVTransportVariable.TransportState.class)
|
||||
.getValue()) {
|
||||
case PLAYING:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
break;
|
||||
case PAUSED_PLAYBACK:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PAUSED;
|
||||
break;
|
||||
case STOPPED:
|
||||
if (mManuallyStopped) {
|
||||
mManuallyStopped = false;
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
|
||||
}
|
||||
else
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
|
||||
break;
|
||||
case TRANSITIONING:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
|
||||
break;
|
||||
case NO_MEDIA_PRESENT:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_ERROR;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to parse UPNP event", e);
|
||||
sendError("Failed to parse UPNP event");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void eventsMissed(GENASubscription sub,
|
||||
int numberOfMissedEvents) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void failed(GENASubscription sub, UpnpResponse responseStatus,
|
||||
Exception exception, String defaultMsg) {
|
||||
Log.w(TAG, "Register Subscription Callback failed: " + defaultMsg, exception);
|
||||
sendError("Register Subscription Callback failed: " + defaultMsg);
|
||||
}
|
||||
};
|
||||
mUpnpService.getControlPoint().execute(mSubscriptionCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends selection, stops playback if possible.
|
||||
*/
|
||||
@Override
|
||||
public void unselectRenderer(String sessionId) throws RemoteException {
|
||||
if (mDevices.get(sessionId) != null)
|
||||
stop(sessionId);
|
||||
if (mSubscriptionCallback != null)
|
||||
mSubscriptionCallback.end();
|
||||
mCurrentRenderer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an absolute volume. The value is assumed to be within the valid
|
||||
* volume range.
|
||||
*/
|
||||
@Override
|
||||
public void setVolume(int volume) throws RemoteException {
|
||||
mUpnpService.getControlPoint().execute(
|
||||
new SetVolume(getService(mCurrentRenderer,
|
||||
"RenderingControl"), volume) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Set volume failed: " + defaultMessage);
|
||||
sendError("Set volume failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets playback source and metadata, then starts playing on
|
||||
* current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void play(String uri, String metadata) throws RemoteException {
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_BUFFERING;
|
||||
mUpnpService.getControlPoint().execute(new SetAVTransportURI(
|
||||
getService(mCurrentRenderer, "AVTransport"),
|
||||
uri, metadata) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMsg) {
|
||||
Log.w(TAG, "Set URI failed: " + defaultMsg);
|
||||
sendError("Set URI failed: " + defaultMsg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void success(ActionInvocation invocation) {
|
||||
mUpnpService.getControlPoint().execute(
|
||||
new Play(getService(mCurrentRenderer,
|
||||
"AVTransport")) {
|
||||
|
||||
@Override
|
||||
public void success(ActionInvocation invocation) {
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Play failed: " + defaultMessage);
|
||||
sendError("Play failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses playback on current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void pause(final String sessionId) throws RemoteException {
|
||||
mUpnpService.getControlPoint().execute(
|
||||
new Pause(getService(mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Pause failed, trying stop: " + defaultMessage);
|
||||
sendError("Pause failed, trying stop: " + defaultMessage);
|
||||
// Sometimes stop works even though pause does not.
|
||||
try {
|
||||
stop(sessionId);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(String sessionId) throws RemoteException {
|
||||
mUpnpService.getControlPoint().execute(
|
||||
new Play(getService(mDevices.get(sessionId),
|
||||
"AVTransport")) {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Resume failed: " + defaultMessage);
|
||||
sendError("Resume failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops playback on current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void stop(String sessionId) throws RemoteException {
|
||||
mManuallyStopped = true;
|
||||
mUpnpService.getControlPoint().execute(
|
||||
new Stop(getService(mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
org.teleal.cling.model.message.UpnpResponse operation,
|
||||
String defaultMessage) {
|
||||
Log.w(TAG, "Stop failed: " + defaultMessage);
|
||||
sendError("Stop failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the given absolute time in seconds.
|
||||
*/
|
||||
@Override
|
||||
public void seek(String sessionId, String itemId, long milliseconds)
|
||||
throws RemoteException {
|
||||
mUpnpService.getControlPoint().execute(new Seek(
|
||||
getService(mDevices.get(sessionId), "AVTransport"),
|
||||
SeekMode.REL_TIME,
|
||||
Integer.toString((int) milliseconds / 1000)) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Seek failed: " + defaultMessage);
|
||||
sendError("Seek failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with current status for the route and item.
|
||||
*
|
||||
* If itemId does not match with the item currently played,
|
||||
* MediaItemStatus.PLAYBACK_STATE_INVALIDATED is returned.
|
||||
*
|
||||
* @param sessionId Identifier of the session (equivalent to route) to get info for.
|
||||
* @param itemId Identifier of the item to get info for.
|
||||
* @param requestHash Passed back in message to find original request object.
|
||||
*/
|
||||
@Override
|
||||
public void getItemStatus(String sessionId, final String itemId, final int requestHash)
|
||||
throws RemoteException {
|
||||
mUpnpService.getControlPoint().execute(new GetPositionInfo(
|
||||
getService(mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Get position failed: " + defaultMessage);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void received(ActionInvocation invocation, PositionInfo positionInfo) {
|
||||
if (positionInfo.getTrackURI() == null)
|
||||
return;
|
||||
|
||||
Message msg = Message.obtain(null, Provider.MSG_STATUS_INFO, 0, 0);
|
||||
Builder status = null;
|
||||
|
||||
if (positionInfo.getTrackURI().equals(itemId)) {
|
||||
status = new MediaItemStatus.Builder(mPlaybackState)
|
||||
.setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000)
|
||||
.setContentDuration(positionInfo.getTrackDurationSeconds() * 1000)
|
||||
.setTimestamp(positionInfo.getAbsCount());
|
||||
}
|
||||
else {
|
||||
status = new MediaItemStatus.Builder(
|
||||
MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
|
||||
}
|
||||
|
||||
msg.getData().putBundle("media_item_status", status.build().asBundle());
|
||||
msg.getData().putInt("hash", requestHash);
|
||||
sendMessage(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
WeakHashMap<RemotePlayServiceBinder, Boolean> mBinders =
|
||||
new WeakHashMap<RemotePlayServiceBinder, Boolean>();
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent itnent) {
|
||||
Binder b = new Binder();
|
||||
RemotePlayServiceBinder b = new RemotePlayServiceBinder(this);
|
||||
mBinders.put(b, true);
|
||||
return b;
|
||||
}
|
||||
|
@ -466,7 +144,7 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
/**
|
||||
* Sends msg via Messenger to Provider.
|
||||
*/
|
||||
private void sendMessage(Message msg) {
|
||||
void sendMessage(Message msg) {
|
||||
try {
|
||||
mListener.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
|
@ -478,7 +156,7 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
* Sends the error as a message via Messenger.
|
||||
* @param error
|
||||
*/
|
||||
private void sendError(String error) {
|
||||
void sendError(String error) {
|
||||
Message msg = Message.obtain(null, Provider.MSG_ERROR, 0, 0);
|
||||
msg.getData().putString("error", error);
|
||||
sendMessage(msg);
|
||||
|
@ -508,7 +186,7 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
if (mUpnpService.getControlPoint().getRegistry()
|
||||
.getDevice(new UDN(d.getKey()), false) == null) {
|
||||
deviceRemoved(d.getValue());
|
||||
for (Binder b : mBinders.keySet()) {
|
||||
for (RemotePlayServiceBinder b : mBinders.keySet()) {
|
||||
if (b.mCurrentRenderer.equals(d.getValue())) {
|
||||
b.mSubscriptionCallback.end();
|
||||
b.mCurrentRenderer = null;
|
||||
|
@ -523,7 +201,7 @@ public class RemotePlayService extends Service implements RegistryListener {
|
|||
/**
|
||||
* Returns a device service by name for direct queries.
|
||||
*/
|
||||
private org.teleal.cling.model.meta.Service<?, ?> getService(
|
||||
org.teleal.cling.model.meta.Service<?, ?> getService(
|
||||
Device<?, ?, ?> device, String name) {
|
||||
return device.findService(
|
||||
new ServiceType("schemas-upnp-org", name));
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
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.upnp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.teleal.cling.controlpoint.SubscriptionCallback;
|
||||
import org.teleal.cling.model.action.ActionInvocation;
|
||||
import org.teleal.cling.model.gena.CancelReason;
|
||||
import org.teleal.cling.model.gena.GENASubscription;
|
||||
import org.teleal.cling.model.message.UpnpResponse;
|
||||
import org.teleal.cling.model.meta.Device;
|
||||
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.Pause;
|
||||
import org.teleal.cling.support.avtransport.callback.Play;
|
||||
import org.teleal.cling.support.avtransport.callback.Seek;
|
||||
import org.teleal.cling.support.avtransport.callback.SetAVTransportURI;
|
||||
import org.teleal.cling.support.avtransport.callback.Stop;
|
||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser;
|
||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable;
|
||||
import org.teleal.cling.support.lastchange.LastChange;
|
||||
import org.teleal.cling.support.model.PositionInfo;
|
||||
import org.teleal.cling.support.model.SeekMode;
|
||||
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
|
||||
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v7.media.MediaItemStatus;
|
||||
import android.support.v7.media.MediaItemStatus.Builder;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Binder for RemotePlayService. Provides a direct interface to a specific route.
|
||||
*
|
||||
* Clients should use the MediaRouter api through Provider.
|
||||
*
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class RemotePlayServiceBinder extends IRemotePlayService.Stub {
|
||||
|
||||
private static final String TAG = "RemotePlayServiceBinder";
|
||||
|
||||
Device<?, ?, ?> mCurrentRenderer;
|
||||
|
||||
private int mPlaybackState;
|
||||
|
||||
private boolean mManuallyStopped;
|
||||
|
||||
SubscriptionCallback mSubscriptionCallback;
|
||||
|
||||
private RemotePlayService mRps;
|
||||
|
||||
public RemotePlayServiceBinder(RemotePlayService rps) {
|
||||
mRps = rps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startSearch(Messenger listener)
|
||||
throws RemoteException {
|
||||
mRps.mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectRenderer(String id) throws RemoteException {
|
||||
mCurrentRenderer = mRps.mDevices.get(id);
|
||||
for (RemotePlayServiceBinder b : mRps.mBinders.keySet()) {
|
||||
if (b != this && mCurrentRenderer.equals(b.mCurrentRenderer))
|
||||
b.unselectRenderer("");
|
||||
}
|
||||
|
||||
mSubscriptionCallback = new SubscriptionCallback(
|
||||
mCurrentRenderer.findService(
|
||||
new ServiceType("schemas-upnp-org", "AVTransport")), 600) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void established(GENASubscription sub) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void ended(GENASubscription sub, CancelReason reason,
|
||||
UpnpResponse response) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void eventReceived(final GENASubscription sub) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, StateVariableValue> m = sub.getCurrentValues();
|
||||
try {
|
||||
LastChange lastChange = new LastChange(
|
||||
new AVTransportLastChangeParser(),
|
||||
m.get("LastChange").toString());
|
||||
if (lastChange.getEventedValue(0,
|
||||
AVTransportVariable.TransportState.class) == null)
|
||||
return;
|
||||
|
||||
switch (lastChange.getEventedValue(0,
|
||||
AVTransportVariable.TransportState.class)
|
||||
.getValue()) {
|
||||
case PLAYING:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
break;
|
||||
case PAUSED_PLAYBACK:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PAUSED;
|
||||
break;
|
||||
case STOPPED:
|
||||
if (mManuallyStopped) {
|
||||
mManuallyStopped = false;
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_CANCELED;
|
||||
}
|
||||
else
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_FINISHED;
|
||||
break;
|
||||
case TRANSITIONING:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
|
||||
break;
|
||||
case NO_MEDIA_PRESENT:
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_ERROR;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to parse UPNP event", e);
|
||||
mRps.sendError("Failed to parse UPNP event");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void eventsMissed(GENASubscription sub,
|
||||
int numberOfMissedEvents) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
protected void failed(GENASubscription sub, UpnpResponse responseStatus,
|
||||
Exception exception, String defaultMsg) {
|
||||
Log.w(TAG, "Register Subscription Callback failed: " + defaultMsg, exception);
|
||||
mRps.sendError("Register Subscription Callback failed: " + defaultMsg);
|
||||
}
|
||||
};
|
||||
mRps.mUpnpService.getControlPoint().execute(mSubscriptionCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends selection, stops playback if possible.
|
||||
*/
|
||||
@Override
|
||||
public void unselectRenderer(String sessionId) throws RemoteException {
|
||||
if (mRps.mDevices.get(sessionId) != null)
|
||||
stop(sessionId);
|
||||
if (mSubscriptionCallback != null)
|
||||
mSubscriptionCallback.end();
|
||||
mCurrentRenderer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an absolute volume. The value is assumed to be within the valid
|
||||
* volume range.
|
||||
*/
|
||||
@Override
|
||||
public void setVolume(int volume) throws RemoteException {
|
||||
mRps.mUpnpService.getControlPoint().execute(
|
||||
new SetVolume(mRps.getService(mCurrentRenderer,
|
||||
"RenderingControl"), volume) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Set volume failed: " + defaultMessage);
|
||||
mRps.sendError("Set volume failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets playback source and metadata, then starts playing on
|
||||
* current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void play(String uri, String metadata) throws RemoteException {
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_BUFFERING;
|
||||
mRps.mUpnpService.getControlPoint().execute(new SetAVTransportURI(
|
||||
mRps.getService(mCurrentRenderer, "AVTransport"),
|
||||
uri, metadata) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMsg) {
|
||||
Log.w(TAG, "Set URI failed: " + defaultMsg);
|
||||
mRps.sendError("Set URI failed: " + defaultMsg);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void success(ActionInvocation invocation) {
|
||||
mRps.mUpnpService.getControlPoint().execute(
|
||||
new Play(mRps.getService(mCurrentRenderer,
|
||||
"AVTransport")) {
|
||||
|
||||
@Override
|
||||
public void success(ActionInvocation invocation) {
|
||||
mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PLAYING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Play failed: " + defaultMessage);
|
||||
mRps.sendError("Play failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses playback on current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void pause(final String sessionId) throws RemoteException {
|
||||
mRps.mUpnpService.getControlPoint().execute(
|
||||
new Pause(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Pause failed, trying stop: " + defaultMessage);
|
||||
mRps.sendError("Pause failed, trying stop: " + defaultMessage);
|
||||
// Sometimes stop works even though pause does not.
|
||||
try {
|
||||
stop(sessionId);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resume(String sessionId) throws RemoteException {
|
||||
mRps.mUpnpService.getControlPoint().execute(
|
||||
new Play(mRps.getService(mRps.mDevices.get(sessionId),
|
||||
"AVTransport")) {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Resume failed: " + defaultMessage);
|
||||
mRps.sendError("Resume failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops playback on current renderer.
|
||||
*/
|
||||
@Override
|
||||
public void stop(String sessionId) throws RemoteException {
|
||||
mManuallyStopped = true;
|
||||
mRps.mUpnpService.getControlPoint().execute(
|
||||
new Stop(mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
org.teleal.cling.model.message.UpnpResponse operation,
|
||||
String defaultMessage) {
|
||||
Log.w(TAG, "Stop failed: " + defaultMessage);
|
||||
mRps.sendError("Stop failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the given absolute time in seconds.
|
||||
*/
|
||||
@Override
|
||||
public void seek(String sessionId, String itemId, long milliseconds)
|
||||
throws RemoteException {
|
||||
mRps.mUpnpService.getControlPoint().execute(new Seek(
|
||||
mRps.getService(mRps.mDevices.get(sessionId), "AVTransport"),
|
||||
SeekMode.REL_TIME,
|
||||
Integer.toString((int) milliseconds / 1000)) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Seek failed: " + defaultMessage);
|
||||
mRps.sendError("Seek failed: " + defaultMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with current status for the route and item.
|
||||
*
|
||||
* If itemId does not match with the item currently played,
|
||||
* MediaItemStatus.PLAYBACK_STATE_INVALIDATED is returned.
|
||||
*
|
||||
* @param sessionId Identifier of the session (equivalent to route) to get info for.
|
||||
* @param itemId Identifier of the item to get info for.
|
||||
* @param requestHash Passed back in message to find original request object.
|
||||
*/
|
||||
@Override
|
||||
public void getItemStatus(String sessionId, final String itemId, final int requestHash)
|
||||
throws RemoteException {
|
||||
mRps.mUpnpService.getControlPoint().execute(new GetPositionInfo(
|
||||
mRps.getService(mRps.mDevices.get(sessionId), "AVTransport")) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void failure(ActionInvocation invocation,
|
||||
UpnpResponse operation, String defaultMessage) {
|
||||
Log.w(TAG, "Get position failed: " + defaultMessage);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void received(ActionInvocation invocation, PositionInfo positionInfo) {
|
||||
if (positionInfo.getTrackURI() == null)
|
||||
return;
|
||||
|
||||
Message msg = Message.obtain(null, Provider.MSG_STATUS_INFO, 0, 0);
|
||||
Builder status = null;
|
||||
|
||||
if (positionInfo.getTrackURI().equals(itemId)) {
|
||||
status = new MediaItemStatus.Builder(mPlaybackState)
|
||||
.setContentPosition(positionInfo.getTrackElapsedSeconds() * 1000)
|
||||
.setContentDuration(positionInfo.getTrackDurationSeconds() * 1000)
|
||||
.setTimestamp(positionInfo.getAbsCount());
|
||||
}
|
||||
else {
|
||||
status = new MediaItemStatus.Builder(
|
||||
MediaItemStatus.PLAYBACK_STATE_INVALIDATED);
|
||||
}
|
||||
|
||||
msg.getData().putBundle("media_item_status", status.build().asBundle());
|
||||
msg.getData().putInt("hash", requestHash);
|
||||
mRps.sendMessage(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
Reference in a new issue