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.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
|
||||
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 com.github.nutomic.controldlna.RemoteImageView;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
@ -49,15 +42,17 @@ import android.view.ViewGroup;
|
|||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.github.nutomic.controldlna.DeviceListener.DeviceListenerCallback;
|
||||
|
||||
/**
|
||||
* Displays the devices that are inserted through the RegistryListener (either
|
||||
* of type RENDERER or SERVER).
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
||||
implements RegistryListener {
|
||||
implements DeviceListenerCallback {
|
||||
|
||||
private static final String TAG = "DeviceArrayAdapter";
|
||||
|
||||
|
@ -106,55 +101,11 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
|||
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.
|
||||
*/
|
||||
private void deviceAdded(final Device<?, ?, ?> device) {
|
||||
@Override
|
||||
public void deviceAdded(final Device<?, ?, ?> device) {
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
|
@ -168,7 +119,8 @@ public class DeviceArrayAdapter extends ArrayAdapter<Device<?, ?, ?>>
|
|||
/**
|
||||
* 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() {
|
||||
|
||||
@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.TextView;
|
||||
|
||||
/**
|
||||
* Allows displaying UPNP media server directory contents in a ListView.
|
||||
*
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
|
||||
|
||||
/**
|
||||
* Provides sorting of elements by track number.
|
||||
*/
|
||||
public FileArrayAdapter(Context context) {
|
||||
super(context, R.layout.list_item);
|
||||
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
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import android.util.Log;
|
|||
/**
|
||||
* Handles background task of loading a bitmap by URI.
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
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,31 +27,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
||||
package com.github.nutomic.controldlna.gui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.teleal.cling.support.model.item.Item;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.actionbarsherlock.app.ActionBar;
|
||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||
import com.github.nutomic.controldlna.R;
|
||||
import com.github.nutomic.controldlna.UpnpPlayer;
|
||||
|
||||
/**
|
||||
* Main activity, with tabs for media servers and media renderers.
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class MainActivity extends SherlockFragmentActivity implements
|
||||
ActionBar.TabListener {
|
||||
|
||||
/**
|
||||
* Manages all UPNP connections including playback.
|
||||
*/
|
||||
private UpnpPlayer mPlayer = new UpnpPlayer();
|
||||
|
||||
/**
|
||||
* Provides Fragments, holding all of them in memory.
|
||||
*/
|
||||
|
@ -102,6 +105,14 @@ public class MainActivity extends SherlockFragmentActivity implements
|
|||
actionBar.addTab(actionBar.newTab()
|
||||
.setText(R.string.title_renderer)
|
||||
.setTabListener(this));
|
||||
|
||||
mPlayer.open(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mPlayer.close(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -148,15 +159,12 @@ public class MainActivity extends SherlockFragmentActivity implements
|
|||
* Listener for the 'back' key.
|
||||
*/
|
||||
public interface OnBackPressedListener {
|
||||
|
||||
/**
|
||||
* Returns true if the press was consumed, false otherwise.
|
||||
*/
|
||||
public boolean onBackPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards back press to active Fragment.
|
||||
* Forwards back press to active Fragment (unless the fragment is
|
||||
* showing its root view).
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
|
@ -167,31 +175,37 @@ public class MainActivity extends SherlockFragmentActivity implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Utility function to call RendererFragment.play from ServerFragment.
|
||||
*/
|
||||
public void play(List<Item> playlist, int start) {
|
||||
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
|
||||
mRendererFragment.setPlaylist(playlist, start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends volume key events to RendererFragment (which sends them to
|
||||
* media renderer).
|
||||
* Changes volume on key press.
|
||||
*/
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
switch (event.getKeyCode()) {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the UPNP renderer tab in the activity.
|
||||
*/
|
||||
public void switchToRendererTab() {
|
||||
Log.d("tag", "called");
|
||||
getSupportActionBar().selectTab(getSupportActionBar().getTabAt(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ package com.github.nutomic.controldlna.gui;
|
|||
import java.util.List;
|
||||
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.model.action.ActionInvocation;
|
||||
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.Service;
|
||||
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.Seek;
|
||||
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.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.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
@ -76,19 +64,17 @@ import android.widget.ImageButton;
|
|||
import android.widget.ListView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.github.nutomic.controldlna.DeviceArrayAdapter;
|
||||
import com.github.nutomic.controldlna.FileArrayAdapter;
|
||||
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.service.PlayService;
|
||||
import com.github.nutomic.controldlna.service.PlayServiceBinder;
|
||||
|
||||
/**
|
||||
* Shows a list of media servers, allowing to select one for playback.
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class RendererFragment extends Fragment implements
|
||||
|
@ -118,43 +104,6 @@ public class RendererFragment extends Fragment implements
|
|||
|
||||
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
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
@ -186,29 +135,22 @@ public class RendererFragment extends Fragment implements
|
|||
mPlayPause.setImageResource(R.drawable.ic_media_play);
|
||||
getView().findViewById(R.id.previous).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),
|
||||
mPlayServiceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
);
|
||||
|
||||
getActivity().getApplicationContext().bindService(
|
||||
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
|
||||
mUpnpServiceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
);
|
||||
private UpnpPlayer getPlayer() {
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
return activity.getUpnpPlayer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls the renderer for the current play progress as long as
|
||||
* mPlaying is true.
|
||||
* playback is active.
|
||||
*/
|
||||
private void pollTimePosition() {
|
||||
final Service<?, ?> service = mCurrentRenderer.findService(
|
||||
new ServiceType("schemas-upnp-org", "AVTransport"));
|
||||
mUpnpService.getControlPoint().execute(
|
||||
Service<?, ?> service = getPlayer()
|
||||
.getService(mCurrentRenderer, "AVTransport");
|
||||
getPlayer().execute(
|
||||
new GetPositionInfo(service) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
@ -237,24 +179,13 @@ public class RendererFragment extends Fragment implements
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void setPlaylist(List<Item> playlist, int start) {
|
||||
mPlaylistAdapter.clear();
|
||||
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)
|
||||
mPlayService.getService().playTrack(position);
|
||||
getPlayer().getPlayService().playTrack(position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,9 +233,9 @@ public class RendererFragment extends Fragment implements
|
|||
mSubscriptionCallback.end();
|
||||
|
||||
mCurrentRenderer = renderer;
|
||||
mPlayService.getService().setRenderer(renderer);
|
||||
getPlayer().selectRenderer(renderer);
|
||||
mSubscriptionCallback = new SubscriptionCallback(
|
||||
getService("AVTransport"), 600) {
|
||||
getPlayer().getService("AVTransport"), 600) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
|
@ -374,10 +305,10 @@ public class RendererFragment extends Fragment implements
|
|||
Log.d(TAG, defaultMsg);
|
||||
}
|
||||
};
|
||||
mUpnpService.getControlPoint().execute(mSubscriptionCallback);
|
||||
getPlayer().execute(mSubscriptionCallback);
|
||||
}
|
||||
mPlaylistAdapter.clear();
|
||||
mPlaylistAdapter.add(mPlayService.getService().getPlaylist());
|
||||
mPlaylistAdapter.add(getPlayer().getPlayService().getPlaylist());
|
||||
mListView.setAdapter(mPlaylistAdapter);
|
||||
}
|
||||
|
||||
|
@ -388,7 +319,7 @@ public class RendererFragment extends Fragment implements
|
|||
if (mListView.getAdapter() == mRendererAdapter)
|
||||
return;
|
||||
disableTrackHighlight();
|
||||
mCurrentTrackView = mListView.getChildAt(mPlayService.getService()
|
||||
mCurrentTrackView = mListView.getChildAt(getPlayer().getPlayService()
|
||||
.getCurrentTrack()
|
||||
- mListView.getFirstVisiblePosition() + mListView.getHeaderViewsCount());
|
||||
if (mCurrentTrackView != null)
|
||||
|
@ -426,15 +357,15 @@ public class RendererFragment extends Fragment implements
|
|||
switch (v.getId()) {
|
||||
case R.id.playpause:
|
||||
if (mPlaying)
|
||||
mPlayService.getService().pause();
|
||||
getPlayer().getPlayService().pause();
|
||||
else
|
||||
mPlayService.getService().play();
|
||||
getPlayer().getPlayService().play();
|
||||
break;
|
||||
case R.id.previous:
|
||||
mPlayService.getService().playPrevious();
|
||||
getPlayer().getPlayService().playPrevious();
|
||||
break;
|
||||
case R.id.next:
|
||||
mPlayService.getService().playNext();
|
||||
getPlayer().getPlayService().playNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -446,17 +377,7 @@ public class RendererFragment extends Fragment implements
|
|||
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||
boolean fromUser) {
|
||||
if (fromUser) {
|
||||
mUpnpService.getControlPoint().execute(new Seek(
|
||||
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);
|
||||
}
|
||||
});
|
||||
getPlayer().seek(progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,50 +389,6 @@ public class RendererFragment extends Fragment implements
|
|||
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
|
||||
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
|
||||
enableTrackHighlight();
|
||||
|
|
|
@ -31,8 +31,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
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.message.UpnpResponse;
|
||||
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.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.IBinder;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
|
@ -61,10 +54,10 @@ import com.github.nutomic.controldlna.FileArrayAdapter;
|
|||
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.
|
||||
*
|
||||
* @author Felix
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class ServerFragment extends ListFragment implements OnBackPressedListener {
|
||||
|
@ -100,30 +93,6 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -135,23 +104,8 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
|||
mServerAdapter = new DeviceArrayAdapter(
|
||||
getActivity(), DeviceArrayAdapter.SERVER);
|
||||
setListAdapter(mServerAdapter);
|
||||
|
||||
getActivity().getApplicationContext().bindService(
|
||||
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);
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
activity.getUpnpPlayer().getDeviceListener().addCallback(mServerAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -175,7 +129,9 @@ public class ServerFragment extends ListFragment implements OnBackPressedListene
|
|||
playlist.add((Item) mFileAdapter.getItem(i));
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @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) {
|
||||
Service<?, ?> service = mCurrentServer.findService(
|
||||
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) {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
|
|
@ -73,6 +73,12 @@ import com.github.nutomic.controldlna.LoadImageTask;
|
|||
import com.github.nutomic.controldlna.R;
|
||||
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 {
|
||||
|
||||
private static final String TAG = "PlayService";
|
||||
|
@ -82,7 +88,7 @@ public class PlayService extends Service {
|
|||
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;
|
||||
|
||||
|
@ -115,7 +121,7 @@ public class PlayService extends Service {
|
|||
private AndroidUpnpService mUpnpService;
|
||||
|
||||
/**
|
||||
* Connection Cling to UPNP service.
|
||||
* Cling connection to UPNP service.
|
||||
*/
|
||||
private ServiceConnection mUpnpServiceConnection = new ServiceConnection() {
|
||||
|
||||
|
@ -346,6 +352,10 @@ public class PlayService extends Service {
|
|||
playTrack(mCurrentTrack);
|
||||
}
|
||||
|
||||
public Device<?, ?, ?> getRenderer() {
|
||||
return mRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new playlist and starts playing.
|
||||
*
|
||||
|
|
|
@ -29,6 +29,12 @@ package com.github.nutomic.controldlna.service;
|
|||
|
||||
import android.os.Binder;
|
||||
|
||||
/**
|
||||
* Provides connection to PlayService.
|
||||
*
|
||||
* @author Felix Ableitner
|
||||
*
|
||||
*/
|
||||
public class PlayServiceBinder extends Binder {
|
||||
|
||||
PlayService mService;
|
||||
|
|
Reference in a new issue