Extracted UPNP functionality from GUI into seperate classes.
This commit is contained in:
parent
d29680d695
commit
c236173a45
12 changed files with 523 additions and 300 deletions
|
@ -29,16 +29,9 @@ package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.teleal.cling.model.meta.Device;
|
import org.teleal.cling.model.meta.Device;
|
||||||
import org.teleal.cling.model.meta.LocalDevice;
|
|
||||||
import org.teleal.cling.model.meta.RemoteDevice;
|
import org.teleal.cling.model.meta.RemoteDevice;
|
||||||
import org.teleal.cling.registry.Registry;
|
|
||||||
import org.teleal.cling.registry.RegistryListener;
|
|
||||||
|
|
||||||
import com.github.nutomic.controldlna.RemoteImageView;
|
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -49,15 +42,17 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.github.nutomic.controldlna.DeviceListener.DeviceListenerCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the devices that are inserted through the RegistryListener (either
|
* Displays the devices that are inserted through the RegistryListener (either
|
||||||
* of type RENDERER or SERVER).
|
* of type RENDERER or SERVER).
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
||||||
implements RegistryListener {
|
implements DeviceListenerCallback {
|
||||||
|
|
||||||
private static final String TAG = "DeviceArrayAdapter";
|
private static final String TAG = "DeviceArrayAdapter";
|
||||||
|
|
||||||
|
@ -105,56 +100,12 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
||||||
}
|
}
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterShutdown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeShutdown(Registry registry) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localDeviceAdded(Registry registry, LocalDevice device) {
|
|
||||||
deviceAdded(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localDeviceRemoved(Registry registry, LocalDevice device) {
|
|
||||||
deviceRemoved(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
|
|
||||||
deviceAdded(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteDeviceDiscoveryFailed(Registry registry,
|
|
||||||
RemoteDevice device, Exception exception) {
|
|
||||||
Log.w(TAG, "Device discovery failed", exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteDeviceDiscoveryStarted(Registry registry,
|
|
||||||
RemoteDevice device) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
|
|
||||||
deviceRemoved(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
|
|
||||||
if (!(device.getType().getType().equals(mDeviceType)))
|
|
||||||
deviceRemoved(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new device to the list if its type equals mDeviceType.
|
* Adds a new device to the list if its type equals mDeviceType.
|
||||||
*/
|
*/
|
||||||
private void deviceAdded(final Device<?, ?, ?> device) {
|
@Override
|
||||||
|
public void deviceAdded(final Device<?, ?, ?> device) {
|
||||||
mActivity.runOnUiThread(new Runnable() {
|
mActivity.runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -168,7 +119,8 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
||||||
/**
|
/**
|
||||||
* Removes the device from the list (if it is an element).
|
* Removes the device from the list (if it is an element).
|
||||||
*/
|
*/
|
||||||
private void deviceRemoved(final Device<?, ?, ?> device) {
|
@Override
|
||||||
|
public void deviceRemoved(final Device<?, ?, ?> device) {
|
||||||
mActivity.runOnUiThread(new Runnable() {
|
mActivity.runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -178,13 +130,4 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Replacement for addAll, which is not implemented on lower API levels.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public void add(Collection<Device> collection) {
|
|
||||||
for (Device<?, ?, ?> d : collection)
|
|
||||||
add(d);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
106
src/com/github/nutomic/controldlna/DeviceListener.java
Normal file
106
src/com/github/nutomic/controldlna/DeviceListener.java
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.teleal.cling.model.meta.Device;
|
||||||
|
import org.teleal.cling.model.meta.LocalDevice;
|
||||||
|
import org.teleal.cling.model.meta.RemoteDevice;
|
||||||
|
import org.teleal.cling.registry.Registry;
|
||||||
|
import org.teleal.cling.registry.RegistryListener;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an interface that informs about UPNP devices being added or removed.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DeviceListener implements RegistryListener {
|
||||||
|
|
||||||
|
private static final String TAG = "DeviceListener";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks may be called from a background thread.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface DeviceListenerCallback {
|
||||||
|
public void deviceAdded(Device<?, ?, ?> device);
|
||||||
|
public void deviceRemoved(Device<?, ?, ?> device);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Device<?, ?, ?>> mDevices = new ArrayList<Device<?, ?, ?>>();
|
||||||
|
|
||||||
|
private ArrayList<DeviceListenerCallback> mListeners = new ArrayList<DeviceListenerCallback>();
|
||||||
|
|
||||||
|
public void addCallback(DeviceListenerCallback callback) {
|
||||||
|
mListeners.add(callback);
|
||||||
|
for (Device<?, ?, ?> d : mDevices)
|
||||||
|
callback.deviceAdded(d);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCallback(DeviceListenerCallback callback) {
|
||||||
|
mListeners.remove(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deviceAdded(Device<?, ?, ?> device) {
|
||||||
|
mDevices.add(device);
|
||||||
|
for (DeviceListenerCallback l : mListeners)
|
||||||
|
l.deviceAdded(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deviceRemoved(Device<?, ?, ?> device) {
|
||||||
|
mDevices.remove(device);
|
||||||
|
for (DeviceListenerCallback l : mListeners)
|
||||||
|
l.deviceRemoved(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeShutdown(Registry registry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceAdded(Registry registry, LocalDevice device) {
|
||||||
|
deviceAdded(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceRemoved(Registry registry, LocalDevice device) {
|
||||||
|
deviceRemoved(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
|
||||||
|
deviceAdded(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device,
|
||||||
|
Exception exception) {
|
||||||
|
Log.w(TAG, "Remote device discovery failed", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryStarted(Registry registry, RemoteDevice device) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
|
||||||
|
deviceRemoved(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
|
||||||
|
deviceRemoved(device);
|
||||||
|
deviceAdded(device);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -42,8 +42,17 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows displaying UPNP media server directory contents in a ListView.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
|
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides sorting of elements by track number.
|
||||||
|
*/
|
||||||
public FileArrayAdapter(Context context) {
|
public FileArrayAdapter(Context context) {
|
||||||
super(context, R.layout.list_item);
|
super(context, R.layout.list_item);
|
||||||
sort(new Comparator<DIDLObject>() {
|
sort(new Comparator<DIDLObject>() {
|
||||||
|
@ -65,6 +74,9 @@ public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a view with folder/media title, and artist name (for audio only).
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import android.util.Log;
|
||||||
/**
|
/**
|
||||||
* Handles background task of loading a bitmap by URI.
|
* Handles background task of loading a bitmap by URI.
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> {
|
public class LoadImageTask extends AsyncTask<URI, Void, Bitmap> {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import android.widget.ImageView;
|
||||||
/**
|
/**
|
||||||
* ImageView that can directly load from a UPNP URI.
|
* ImageView that can directly load from a UPNP URI.
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class RemoteImageView extends ImageView {
|
public class RemoteImageView extends ImageView {
|
||||||
|
|
103
src/com/github/nutomic/controldlna/UpnpController.java
Normal file
103
src/com/github/nutomic/controldlna/UpnpController.java
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
import org.teleal.cling.android.AndroidUpnpService;
|
||||||
|
import org.teleal.cling.android.AndroidUpnpServiceImpl;
|
||||||
|
import org.teleal.cling.controlpoint.ActionCallback;
|
||||||
|
import org.teleal.cling.controlpoint.SubscriptionCallback;
|
||||||
|
import org.teleal.cling.model.meta.Device;
|
||||||
|
import org.teleal.cling.model.meta.LocalDevice;
|
||||||
|
import org.teleal.cling.model.meta.RemoteDevice;
|
||||||
|
import org.teleal.cling.model.meta.Service;
|
||||||
|
import org.teleal.cling.model.types.ServiceType;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles UPNP connection, including device discovery and general queries.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UpnpController {
|
||||||
|
|
||||||
|
private static final String TAG = "DlnaController";
|
||||||
|
|
||||||
|
private DeviceListener mDeviceListener = new DeviceListener();
|
||||||
|
|
||||||
|
protected AndroidUpnpService mUpnpService;
|
||||||
|
|
||||||
|
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers DeviceListener, adds known devices and starts search.
|
||||||
|
*/
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
mUpnpService = (AndroidUpnpService) service;
|
||||||
|
Log.i(TAG, "Starting device search");
|
||||||
|
mUpnpService.getRegistry().addListener(mDeviceListener);
|
||||||
|
for (Device<?, ?, ?> d : mUpnpService.getControlPoint().getRegistry().getDevices()) {
|
||||||
|
if (d instanceof LocalDevice) {
|
||||||
|
mDeviceListener.localDeviceAdded(mUpnpService.getRegistry(), (LocalDevice) d);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mDeviceListener.remoteDeviceAdded(mUpnpService.getRegistry(), (RemoteDevice) d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mUpnpService.getControlPoint().search();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
mUpnpService = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens connection to the Cling UPNP service.
|
||||||
|
*
|
||||||
|
* @param context Application context.
|
||||||
|
*/
|
||||||
|
public void open(Context context) {
|
||||||
|
context.bindService(
|
||||||
|
new Intent(context, AndroidUpnpServiceImpl.class),
|
||||||
|
mUpnpServiceConnection,
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connection to the Cling UPNP service.
|
||||||
|
*
|
||||||
|
* @param context Application context.
|
||||||
|
*/
|
||||||
|
public void close(Context context) {
|
||||||
|
mUpnpService.getRegistry().removeListener(mDeviceListener);
|
||||||
|
context.unbindService(mUpnpServiceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a device service by name for direct queries.
|
||||||
|
*/
|
||||||
|
public Service<?, ?> getService(Device<?, ?, ?> device, String name) {
|
||||||
|
return device.findService(
|
||||||
|
new ServiceType("schemas-upnp-org", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(ActionCallback callback) {
|
||||||
|
mUpnpService.getControlPoint().execute(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(SubscriptionCallback callback) {
|
||||||
|
mUpnpService.getControlPoint().execute(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceListener getDeviceListener() {
|
||||||
|
return mDeviceListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
192
src/com/github/nutomic/controldlna/UpnpPlayer.java
Normal file
192
src/com/github/nutomic/controldlna/UpnpPlayer.java
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
import org.teleal.cling.model.action.ActionInvocation;
|
||||||
|
import org.teleal.cling.model.message.UpnpResponse;
|
||||||
|
import org.teleal.cling.model.meta.Device;
|
||||||
|
import org.teleal.cling.model.meta.Service;
|
||||||
|
import org.teleal.cling.model.meta.StateVariableAllowedValueRange;
|
||||||
|
import org.teleal.cling.support.avtransport.callback.Seek;
|
||||||
|
import org.teleal.cling.support.model.SeekMode;
|
||||||
|
import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
|
||||||
|
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.github.nutomic.controldlna.service.PlayService;
|
||||||
|
import com.github.nutomic.controldlna.service.PlayServiceBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles connection to PlayService and provides methods related to playback.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class UpnpPlayer extends UpnpController {
|
||||||
|
|
||||||
|
private static final String TAG = "UpnpPlayer";
|
||||||
|
|
||||||
|
private PlayServiceBinder mPlayService;
|
||||||
|
|
||||||
|
private long mMinVolume;
|
||||||
|
|
||||||
|
private long mMaxVolume;
|
||||||
|
|
||||||
|
private long mVolumeStep;
|
||||||
|
|
||||||
|
private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
mPlayService = (PlayServiceBinder) service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
mPlayService = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void open(Context context) {
|
||||||
|
super.open(context);
|
||||||
|
context.bindService(
|
||||||
|
new Intent(context, PlayService.class),
|
||||||
|
mPlayServiceConnection,
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close(Context context) {
|
||||||
|
super.close(context);
|
||||||
|
context.unbindService(mPlayServiceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a device service by name for direct queries.
|
||||||
|
*/
|
||||||
|
public Service<?, ?> getService(String name) {
|
||||||
|
return getService(mPlayService.getService().getRenderer(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an absolute volume.
|
||||||
|
*/
|
||||||
|
public void setVolume(long newVolume) {
|
||||||
|
if (mPlayService.getService().getRenderer() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (newVolume > mMaxVolume) newVolume = mMaxVolume;
|
||||||
|
if (newVolume < mMinVolume) newVolume = mMinVolume;
|
||||||
|
|
||||||
|
mUpnpService.getControlPoint().execute(
|
||||||
|
new SetVolume(getService("RenderingControl"), newVolume) {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void failure(ActionInvocation invocation,
|
||||||
|
UpnpResponse operation, String defaultMessage) {
|
||||||
|
Log.d(TAG, "Failed to set new Volume: " + defaultMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases or decreases volume relative to current one.
|
||||||
|
*
|
||||||
|
* @param amount Amount to change volume by (negative to lower volume).
|
||||||
|
*/
|
||||||
|
private void changeVolume(final long amount) {
|
||||||
|
if (mPlayService.getService().getRenderer() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mUpnpService.getControlPoint().execute(
|
||||||
|
new GetVolume(getService("RenderingControl")) {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void failure(ActionInvocation invocation,
|
||||||
|
UpnpResponse operation, String defaultMessage) {
|
||||||
|
Log.w(TAG, "Failed to get current Volume: " + defaultMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void received(ActionInvocation invocation, int currentVolume) {
|
||||||
|
setVolume(currentVolume + amount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the device volume by a minimum volume step.
|
||||||
|
*/
|
||||||
|
public void increaseVolume() {
|
||||||
|
changeVolume(mVolumeStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases the device volume by a minimum volume step.
|
||||||
|
*/
|
||||||
|
public void decreaseVolume() {
|
||||||
|
changeVolume(- mVolumeStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the renderer for playback, applying its minimum and maximum volume.
|
||||||
|
*/
|
||||||
|
public void selectRenderer(Device<?, ?, ?> renderer) {
|
||||||
|
mPlayService.getService().setRenderer(renderer);
|
||||||
|
|
||||||
|
if (getService("RenderingControl").getStateVariable("Volume") != null) {
|
||||||
|
StateVariableAllowedValueRange volumeRange =
|
||||||
|
getService("RenderingControl").getStateVariable("Volume")
|
||||||
|
.getTypeDetails().getAllowedValueRange();
|
||||||
|
mMinVolume = volumeRange.getMinimum();
|
||||||
|
mMaxVolume = volumeRange.getMaximum();
|
||||||
|
mVolumeStep = volumeRange.getStep();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mMinVolume = 0;
|
||||||
|
mMaxVolume = 100;
|
||||||
|
mVolumeStep = 1;
|
||||||
|
}
|
||||||
|
// Hack, needed as using a smaller step seems to not
|
||||||
|
// increase volume on some devices.
|
||||||
|
mVolumeStep = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks to the given absolute time in seconds.
|
||||||
|
*/
|
||||||
|
public void seek(int absoluteTime) {
|
||||||
|
if (mPlayService.getService().getRenderer() == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mUpnpService.getControlPoint().execute(new Seek(
|
||||||
|
getService(mPlayService.getService().getRenderer(), "AVTransport"),
|
||||||
|
SeekMode.REL_TIME,
|
||||||
|
Integer.toString(absoluteTime)) {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void failure(ActionInvocation invocation,
|
||||||
|
UpnpResponse operation, String defaultMessage) {
|
||||||
|
Log.w(TAG, "Seek failed: " + defaultMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the service that handles actual playback.
|
||||||
|
*/
|
||||||
|
public PlayService getPlayService() {
|
||||||
|
return mPlayService.getService();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,30 +27,33 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
package com.github.nutomic.controldlna.gui;
|
package com.github.nutomic.controldlna.gui;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.teleal.cling.support.model.item.Item;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
|
||||||
import com.actionbarsherlock.app.ActionBar;
|
import com.actionbarsherlock.app.ActionBar;
|
||||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||||
import com.github.nutomic.controldlna.R;
|
import com.github.nutomic.controldlna.R;
|
||||||
|
import com.github.nutomic.controldlna.UpnpPlayer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main activity, with tabs for media servers and media renderers.
|
* Main activity, with tabs for media servers and media renderers.
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class MainActivity extends SherlockFragmentActivity implements
|
public class MainActivity extends SherlockFragmentActivity implements
|
||||||
ActionBar.TabListener {
|
ActionBar.TabListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages all UPNP connections including playback.
|
||||||
|
*/
|
||||||
|
private UpnpPlayer mPlayer = new UpnpPlayer();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Fragments, holding all of them in memory.
|
* Provides Fragments, holding all of them in memory.
|
||||||
|
@ -102,6 +105,14 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
actionBar.addTab(actionBar.newTab()
|
actionBar.addTab(actionBar.newTab()
|
||||||
.setText(R.string.title_renderer)
|
.setText(R.string.title_renderer)
|
||||||
.setTabListener(this));
|
.setTabListener(this));
|
||||||
|
|
||||||
|
mPlayer.open(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
mPlayer.close(getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,15 +159,12 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
* Listener for the 'back' key.
|
* Listener for the 'back' key.
|
||||||
*/
|
*/
|
||||||
public interface OnBackPressedListener {
|
public interface OnBackPressedListener {
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the press was consumed, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean onBackPressed();
|
public boolean onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forwards back press to active Fragment.
|
* Forwards back press to active Fragment (unless the fragment is
|
||||||
|
* showing its root view).
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
|
@ -167,31 +175,37 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to call RendererFragment.play from ServerFragment.
|
* Changes volume on key press.
|
||||||
*/
|
*/
|
||||||
public void play(List<Item> playlist, int start) {
|
@Override
|
||||||
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
mRendererFragment.setPlaylist(playlist, start);
|
switch (event.getKeyCode()) {
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN)
|
||||||
|
mPlayer.increaseVolume();
|
||||||
|
return true;
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
if (event.getAction() == KeyEvent.ACTION_DOWN)
|
||||||
|
mPlayer.decreaseVolume();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns shared instance of UpnpPlayer.
|
||||||
|
*/
|
||||||
|
public UpnpPlayer getUpnpPlayer() {
|
||||||
|
return mPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends volume key events to RendererFragment (which sends them to
|
* Opens the UPNP renderer tab in the activity.
|
||||||
* media renderer).
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public void switchToRendererTab() {
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
Log.d("tag", "called");
|
||||||
switch (event.getKeyCode()) {
|
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
|
||||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
}
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN)
|
|
||||||
mRendererFragment.changeVolume(true);
|
|
||||||
return true;
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN)
|
|
||||||
mRendererFragment.changeVolume(false);
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.dispatchKeyEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ package com.github.nutomic.controldlna.gui;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.teleal.cling.android.AndroidUpnpService;
|
|
||||||
import org.teleal.cling.android.AndroidUpnpServiceImpl;
|
|
||||||
import org.teleal.cling.controlpoint.SubscriptionCallback;
|
import org.teleal.cling.controlpoint.SubscriptionCallback;
|
||||||
import org.teleal.cling.model.action.ActionInvocation;
|
import org.teleal.cling.model.action.ActionInvocation;
|
||||||
import org.teleal.cling.model.gena.CancelReason;
|
import org.teleal.cling.model.gena.CancelReason;
|
||||||
|
@ -40,28 +38,18 @@ import org.teleal.cling.model.message.UpnpResponse;
|
||||||
import org.teleal.cling.model.meta.Device;
|
import org.teleal.cling.model.meta.Device;
|
||||||
import org.teleal.cling.model.meta.Service;
|
import org.teleal.cling.model.meta.Service;
|
||||||
import org.teleal.cling.model.state.StateVariableValue;
|
import org.teleal.cling.model.state.StateVariableValue;
|
||||||
import org.teleal.cling.model.types.ServiceType;
|
|
||||||
import org.teleal.cling.support.avtransport.callback.GetPositionInfo;
|
import org.teleal.cling.support.avtransport.callback.GetPositionInfo;
|
||||||
import org.teleal.cling.support.avtransport.callback.Seek;
|
|
||||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser;
|
import org.teleal.cling.support.avtransport.lastchange.AVTransportLastChangeParser;
|
||||||
import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable;
|
import org.teleal.cling.support.avtransport.lastchange.AVTransportVariable;
|
||||||
import org.teleal.cling.support.lastchange.LastChange;
|
import org.teleal.cling.support.lastchange.LastChange;
|
||||||
import org.teleal.cling.support.model.PositionInfo;
|
import org.teleal.cling.support.model.PositionInfo;
|
||||||
import org.teleal.cling.support.model.SeekMode;
|
|
||||||
import org.teleal.cling.support.model.item.Item;
|
import org.teleal.cling.support.model.item.Item;
|
||||||
import org.teleal.cling.support.renderingcontrol.callback.GetVolume;
|
|
||||||
import org.teleal.cling.support.renderingcontrol.callback.SetVolume;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -76,19 +64,17 @@ import android.widget.ImageButton;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.github.nutomic.controldlna.DeviceArrayAdapter;
|
import com.github.nutomic.controldlna.DeviceArrayAdapter;
|
||||||
import com.github.nutomic.controldlna.FileArrayAdapter;
|
import com.github.nutomic.controldlna.FileArrayAdapter;
|
||||||
import com.github.nutomic.controldlna.R;
|
import com.github.nutomic.controldlna.R;
|
||||||
|
import com.github.nutomic.controldlna.UpnpPlayer;
|
||||||
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
|
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
|
||||||
import com.github.nutomic.controldlna.service.PlayService;
|
|
||||||
import com.github.nutomic.controldlna.service.PlayServiceBinder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a list of media servers, allowing to select one for playback.
|
* Shows a list of media servers, allowing to select one for playback.
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class RendererFragment extends Fragment implements
|
public class RendererFragment extends Fragment implements
|
||||||
|
@ -117,43 +103,6 @@ public class RendererFragment extends Fragment implements
|
||||||
private FileArrayAdapter mPlaylistAdapter;
|
private FileArrayAdapter mPlaylistAdapter;
|
||||||
|
|
||||||
private SubscriptionCallback mSubscriptionCallback;
|
private SubscriptionCallback mSubscriptionCallback;
|
||||||
|
|
||||||
private PlayServiceBinder mPlayService;
|
|
||||||
|
|
||||||
private ServiceConnection mPlayServiceConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
mPlayService = (PlayServiceBinder) service;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
mPlayService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cling UPNP service.
|
|
||||||
*/
|
|
||||||
private AndroidUpnpService mUpnpService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connection Cling to UPNP service.
|
|
||||||
*/
|
|
||||||
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
mUpnpService = (AndroidUpnpService) service;
|
|
||||||
Log.i(TAG, "Starting device search");
|
|
||||||
mUpnpService.getRegistry().addListener(mRendererAdapter);
|
|
||||||
mUpnpService.getControlPoint().search();
|
|
||||||
mRendererAdapter.add(
|
|
||||||
mUpnpService.getControlPoint().getRegistry().getDevices());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
mUpnpService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
@ -186,29 +135,22 @@ public class RendererFragment extends Fragment implements
|
||||||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
||||||
getView().findViewById(R.id.previous).setOnClickListener(this);
|
getView().findViewById(R.id.previous).setOnClickListener(this);
|
||||||
getView().findViewById(R.id.next).setOnClickListener(this);
|
getView().findViewById(R.id.next).setOnClickListener(this);
|
||||||
|
getPlayer().getDeviceListener().addCallback(mRendererAdapter);
|
||||||
getActivity().startService(new Intent(getActivity(), PlayService.class));
|
}
|
||||||
getActivity().getApplicationContext().bindService(
|
|
||||||
new Intent(getActivity(), PlayService.class),
|
private UpnpPlayer getPlayer() {
|
||||||
mPlayServiceConnection,
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
Context.BIND_AUTO_CREATE
|
return activity.getUpnpPlayer();
|
||||||
);
|
|
||||||
|
|
||||||
getActivity().getApplicationContext().bindService(
|
|
||||||
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
|
|
||||||
mUpnpServiceConnection,
|
|
||||||
Context.BIND_AUTO_CREATE
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polls the renderer for the current play progress as long as
|
* Polls the renderer for the current play progress as long as
|
||||||
* mPlaying is true.
|
* playback is active.
|
||||||
*/
|
*/
|
||||||
private void pollTimePosition() {
|
private void pollTimePosition() {
|
||||||
final Service<?, ?> service = mCurrentRenderer.findService(
|
Service<?, ?> service = getPlayer()
|
||||||
new ServiceType("schemas-upnp-org", "AVTransport"));
|
.getService(mCurrentRenderer, "AVTransport");
|
||||||
mUpnpService.getControlPoint().execute(
|
getPlayer().execute(
|
||||||
new GetPositionInfo(service) {
|
new GetPositionInfo(service) {
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
|
@ -236,17 +178,6 @@ public class RendererFragment extends Fragment implements
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes Cling UPNP service.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
mUpnpService.getRegistry().removeListener(mRendererAdapter);
|
|
||||||
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
|
|
||||||
getActivity().getApplicationContext().unbindService(mPlayServiceConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the new playlist.
|
* Sets the new playlist.
|
||||||
|
@ -254,7 +185,7 @@ public class RendererFragment extends Fragment implements
|
||||||
public void setPlaylist(List<Item> playlist, int start) {
|
public void setPlaylist(List<Item> playlist, int start) {
|
||||||
mPlaylistAdapter.clear();
|
mPlaylistAdapter.clear();
|
||||||
mPlaylistAdapter.add(playlist);
|
mPlaylistAdapter.add(playlist);
|
||||||
mPlayService.getService().setPlaylist(playlist, start);
|
getPlayer().getPlayService().setPlaylist(playlist, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,7 +219,7 @@ public class RendererFragment extends Fragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mListView.getAdapter() == mPlaylistAdapter)
|
else if (mListView.getAdapter() == mPlaylistAdapter)
|
||||||
mPlayService.getService().playTrack(position);
|
getPlayer().getPlayService().playTrack(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -302,9 +233,9 @@ public class RendererFragment extends Fragment implements
|
||||||
mSubscriptionCallback.end();
|
mSubscriptionCallback.end();
|
||||||
|
|
||||||
mCurrentRenderer = renderer;
|
mCurrentRenderer = renderer;
|
||||||
mPlayService.getService().setRenderer(renderer);
|
getPlayer().selectRenderer(renderer);
|
||||||
mSubscriptionCallback = new SubscriptionCallback(
|
mSubscriptionCallback = new SubscriptionCallback(
|
||||||
getService("AVTransport"), 600) {
|
getPlayer().getService("AVTransport"), 600) {
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
@Override
|
@Override
|
||||||
|
@ -374,10 +305,10 @@ public class RendererFragment extends Fragment implements
|
||||||
Log.d(TAG, defaultMsg);
|
Log.d(TAG, defaultMsg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mUpnpService.getControlPoint().execute(mSubscriptionCallback);
|
getPlayer().execute(mSubscriptionCallback);
|
||||||
}
|
}
|
||||||
mPlaylistAdapter.clear();
|
mPlaylistAdapter.clear();
|
||||||
mPlaylistAdapter.add(mPlayService.getService().getPlaylist());
|
mPlaylistAdapter.add(getPlayer().getPlayService().getPlaylist());
|
||||||
mListView.setAdapter(mPlaylistAdapter);
|
mListView.setAdapter(mPlaylistAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,7 +319,7 @@ public class RendererFragment extends Fragment implements
|
||||||
if (mListView.getAdapter() == mRendererAdapter)
|
if (mListView.getAdapter() == mRendererAdapter)
|
||||||
return;
|
return;
|
||||||
disableTrackHighlight();
|
disableTrackHighlight();
|
||||||
mCurrentTrackView = mListView.getChildAt(mPlayService.getService()
|
mCurrentTrackView = mListView.getChildAt(getPlayer().getPlayService()
|
||||||
.getCurrentTrack()
|
.getCurrentTrack()
|
||||||
- mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
|
- mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
|
||||||
if (mCurrentTrackView != null)
|
if (mCurrentTrackView != null)
|
||||||
|
@ -426,15 +357,15 @@ public class RendererFragment extends Fragment implements
|
||||||
switch (v.getId()) {
|
switch (v.getId()) {
|
||||||
case R.id.playpause:
|
case R.id.playpause:
|
||||||
if (mPlaying)
|
if (mPlaying)
|
||||||
mPlayService.getService().pause();
|
getPlayer().getPlayService().pause();
|
||||||
else
|
else
|
||||||
mPlayService.getService().play();
|
getPlayer().getPlayService().play();
|
||||||
break;
|
break;
|
||||||
case R.id.previous:
|
case R.id.previous:
|
||||||
mPlayService.getService().playPrevious();
|
getPlayer().getPlayService().playPrevious();
|
||||||
break;
|
break;
|
||||||
case R.id.next:
|
case R.id.next:
|
||||||
mPlayService.getService().playNext();
|
getPlayer().getPlayService().playNext();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -446,17 +377,7 @@ public class RendererFragment extends Fragment implements
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress,
|
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||||
boolean fromUser) {
|
boolean fromUser) {
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
mUpnpService.getControlPoint().execute(new Seek(
|
getPlayer().seek(progress);
|
||||||
getService("AVTransport"), SeekMode.REL_TIME,
|
|
||||||
Integer.toString(progress)) {
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
@Override
|
|
||||||
public void failure(ActionInvocation invocation,
|
|
||||||
UpnpResponse operation, String defaultMessage) {
|
|
||||||
Log.w(TAG, "Seek failed: " + defaultMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,50 +389,6 @@ public class RendererFragment extends Fragment implements
|
||||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases or decreases volume.
|
|
||||||
*
|
|
||||||
* @param increase True to increase volume, false to decrease.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public void changeVolume(final boolean increase) {
|
|
||||||
if (mCurrentRenderer == null) {
|
|
||||||
Toast.makeText(getActivity(), R.string.select_renderer,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Service<?, ?> service = getService("RenderingControl");
|
|
||||||
mUpnpService.getControlPoint().execute(
|
|
||||||
new GetVolume(service) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(ActionInvocation invocation,
|
|
||||||
UpnpResponse operation, String defaultMessage) {
|
|
||||||
Log.d(TAG, "Failed to get current Volume: " + defaultMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void received(ActionInvocation invocation, int volume) {
|
|
||||||
int newVolume = volume + ((increase) ? 4 : -4);
|
|
||||||
if (newVolume < 0) newVolume = 0;
|
|
||||||
mUpnpService.getControlPoint().execute(
|
|
||||||
new SetVolume(service, newVolume) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failure(ActionInvocation invocation,
|
|
||||||
UpnpResponse operation, String defaultMessage) {
|
|
||||||
Log.d(TAG, "Failed to set new Volume: " + defaultMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Service<?, ?> getService(String name) {
|
|
||||||
return mCurrentRenderer.findService(
|
|
||||||
new ServiceType("schemas-upnp-org", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
|
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
|
||||||
enableTrackHighlight();
|
enableTrackHighlight();
|
||||||
|
|
|
@ -31,8 +31,6 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
import org.teleal.cling.android.AndroidUpnpService;
|
|
||||||
import org.teleal.cling.android.AndroidUpnpServiceImpl;
|
|
||||||
import org.teleal.cling.model.action.ActionInvocation;
|
import org.teleal.cling.model.action.ActionInvocation;
|
||||||
import org.teleal.cling.model.message.UpnpResponse;
|
import org.teleal.cling.model.message.UpnpResponse;
|
||||||
import org.teleal.cling.model.meta.Device;
|
import org.teleal.cling.model.meta.Device;
|
||||||
|
@ -44,12 +42,7 @@ import org.teleal.cling.support.model.DIDLContent;
|
||||||
import org.teleal.cling.support.model.container.Container;
|
import org.teleal.cling.support.model.container.Container;
|
||||||
import org.teleal.cling.support.model.item.Item;
|
import org.teleal.cling.support.model.item.Item;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -61,10 +54,10 @@ import com.github.nutomic.controldlna.FileArrayAdapter;
|
||||||
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
|
import com.github.nutomic.controldlna.gui.MainActivity.OnBackPressedListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a list of media servers, upon selecting one, allows browsing theur
|
* Shows a list of media servers, upon selecting one, allows browsing their
|
||||||
* directories.
|
* directories.
|
||||||
*
|
*
|
||||||
* @author Felix
|
* @author Felix Ableitner
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class ServerFragment extends ListFragment implements OnBackPressedListener {
|
public class ServerFragment extends ListFragment implements OnBackPressedListener {
|
||||||
|
@ -99,30 +92,6 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
||||||
* Holds the scroll position in the list view at each directory level.
|
* Holds the scroll position in the list view at each directory level.
|
||||||
*/
|
*/
|
||||||
private Stack<Parcelable> mListState = new Stack<Parcelable>();
|
private Stack<Parcelable> mListState = new Stack<Parcelable>();
|
||||||
|
|
||||||
/**
|
|
||||||
* Cling UPNP service.
|
|
||||||
*/
|
|
||||||
private AndroidUpnpService mUpnpService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connection Cling to UPNP service.
|
|
||||||
*/
|
|
||||||
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
|
||||||
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
mUpnpService = (AndroidUpnpService) service;
|
|
||||||
Log.i(TAG, "Starting device search");
|
|
||||||
mUpnpService.getRegistry().addListener(mServerAdapter);
|
|
||||||
mUpnpService.getControlPoint().search();
|
|
||||||
mServerAdapter.add(
|
|
||||||
mUpnpService.getControlPoint().getRegistry().getDevices());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
mUpnpService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes ListView adapters, launches Cling UPNP service.
|
* Initializes ListView adapters, launches Cling UPNP service.
|
||||||
|
@ -134,24 +103,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
||||||
|
|
||||||
mServerAdapter = new DeviceArrayAdapter(
|
mServerAdapter = new DeviceArrayAdapter(
|
||||||
getActivity(), DeviceArrayAdapter.SERVER);
|
getActivity(), DeviceArrayAdapter.SERVER);
|
||||||
setListAdapter(mServerAdapter);
|
setListAdapter(mServerAdapter);
|
||||||
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
getActivity().getApplicationContext().bindService(
|
activity.getUpnpPlayer().getDeviceListener().addCallback(mServerAdapter);
|
||||||
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
|
|
||||||
mUpnpServiceConnection,
|
|
||||||
Context.BIND_AUTO_CREATE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes Cling UPNP service.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
if (mUpnpService != null)
|
|
||||||
mUpnpService.getRegistry().removeListener(mServerAdapter);
|
|
||||||
getActivity().getApplicationContext().unbindService(mUpnpServiceConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -175,7 +129,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
||||||
playlist.add((Item) mFileAdapter.getItem(i));
|
playlist.add((Item) mFileAdapter.getItem(i));
|
||||||
}
|
}
|
||||||
MainActivity activity = (MainActivity) getActivity();
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
activity.play(playlist, position);
|
activity.switchToRendererTab();
|
||||||
|
activity.getUpnpPlayer().getPlayService()
|
||||||
|
.setPlaylist(playlist, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,11 +147,15 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the current directory on the ListView.
|
* Displays the current directory on the ListView.
|
||||||
|
*
|
||||||
|
* @param restoreListState True if we are going back up the directory tree,
|
||||||
|
* which means we restore scroll position etc.
|
||||||
*/
|
*/
|
||||||
private void getFiles(final boolean restoreListState) {
|
private void getFiles(final boolean restoreListState) {
|
||||||
Service<?, ?> service = mCurrentServer.findService(
|
Service<?, ?> service = mCurrentServer.findService(
|
||||||
new ServiceType("schemas-upnp-org", "ContentDirectory"));
|
new ServiceType("schemas-upnp-org", "ContentDirectory"));
|
||||||
mUpnpService.getControlPoint().execute(new Browse(service,
|
MainActivity activity = (MainActivity) getActivity();
|
||||||
|
activity.getUpnpPlayer().execute(new Browse(service,
|
||||||
mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
|
mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
|
|
|
@ -73,6 +73,12 @@ import com.github.nutomic.controldlna.LoadImageTask;
|
||||||
import com.github.nutomic.controldlna.R;
|
import com.github.nutomic.controldlna.R;
|
||||||
import com.github.nutomic.controldlna.gui.MainActivity;
|
import com.github.nutomic.controldlna.gui.MainActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background service that handles media playback to a single UPNP media renderer.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class PlayService extends Service {
|
public class PlayService extends Service {
|
||||||
|
|
||||||
private static final String TAG = "PlayService";
|
private static final String TAG = "PlayService";
|
||||||
|
@ -82,7 +88,7 @@ public class PlayService extends Service {
|
||||||
private final PlayServiceBinder mBinder = new PlayServiceBinder(this);
|
private final PlayServiceBinder mBinder = new PlayServiceBinder(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The DLNA media renderer device that is currently active.
|
* The DLNA media renderer device that is currently active for playback.
|
||||||
*/
|
*/
|
||||||
private Device<?, ?, ?> mRenderer;
|
private Device<?, ?, ?> mRenderer;
|
||||||
|
|
||||||
|
@ -115,7 +121,7 @@ public class PlayService extends Service {
|
||||||
private AndroidUpnpService mUpnpService;
|
private AndroidUpnpService mUpnpService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection Cling to UPNP service.
|
* Cling connection to UPNP service.
|
||||||
*/
|
*/
|
||||||
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
||||||
|
|
||||||
|
@ -346,6 +352,10 @@ public class PlayService extends Service {
|
||||||
playTrack(mCurrentTrack);
|
playTrack(mCurrentTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Device<?, ?, ?> getRenderer() {
|
||||||
|
return mRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a new playlist and starts playing.
|
* Sets a new playlist and starts playing.
|
||||||
*
|
*
|
||||||
|
|
|
@ -29,6 +29,12 @@ package com.github.nutomic.controldlna.service;
|
||||||
|
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides connection to PlayService.
|
||||||
|
*
|
||||||
|
* @author Felix Ableitner
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class PlayServiceBinder extends Binder {
|
public class PlayServiceBinder extends Binder {
|
||||||
|
|
||||||
PlayService mService;
|
PlayService mService;
|
||||||
|
|
Reference in a new issue