Added Media Server browsing.
This commit is contained in:
parent
0896c65afe
commit
eceb3f8025
8 changed files with 369 additions and 11 deletions
|
@ -3,6 +3,11 @@
|
||||||
package="com.github.nutomic.controldlna"
|
package="com.github.nutomic.controldlna"
|
||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="1.0" >
|
android:versionName="1.0" >
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="8"
|
android:minSdkVersion="8"
|
||||||
|
@ -27,6 +32,8 @@
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
</application>
|
<service android:name="org.teleal.cling.android.AndroidUpnpServiceImpl"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
BIN
libs/cling-core-1.0.5.jar
Normal file
BIN
libs/cling-core-1.0.5.jar
Normal file
Binary file not shown.
BIN
libs/cling-support-1.0.5.jar
Normal file
BIN
libs/cling-support-1.0.5.jar
Normal file
Binary file not shown.
BIN
libs/teleal-common-1.0.13.jar
Normal file
BIN
libs/teleal-common-1.0.13.jar
Normal file
Binary file not shown.
30
src/com/github/nutomic/controldlna/FileArrayAdapter.java
Normal file
30
src/com/github/nutomic/controldlna/FileArrayAdapter.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
import org.teleal.cling.support.model.DIDLObject;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class FileArrayAdapter extends ArrayAdapter<DIDLObject> {
|
||||||
|
|
||||||
|
public FileArrayAdapter(Context context) {
|
||||||
|
super(context, android.R.layout.simple_list_item_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||||
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||||
|
}
|
||||||
|
TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
|
||||||
|
tv.setText(getItem(position).getTitle());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,12 +27,14 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
* intensive, it may be best to switch to a
|
* intensive, it may be best to switch to a
|
||||||
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
|
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
|
||||||
*/
|
*/
|
||||||
SectionsPagerAdapter mSectionsPagerAdapter;
|
private SectionsPagerAdapter mSectionsPagerAdapter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link ViewPager} that will host the section contents.
|
* The {@link ViewPager} that will host the section contents.
|
||||||
*/
|
*/
|
||||||
ViewPager mViewPager;
|
private ViewPager mViewPager;
|
||||||
|
|
||||||
|
private ServerFragment mServerFragment = new ServerFragment();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -105,14 +107,18 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
// getItem is called to instantiate the fragment for the given page.
|
switch (position) {
|
||||||
// Return a DummySectionFragment (defined as a static inner class
|
case 0:
|
||||||
// below) with the page number as its lone argument.
|
Fragment fragment = new DummySectionFragment();
|
||||||
Fragment fragment = new DummySectionFragment();
|
Bundle args = new Bundle();
|
||||||
Bundle args = new Bundle();
|
args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
|
||||||
args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
|
fragment.setArguments(args);
|
||||||
fragment.setArguments(args);
|
return fragment;
|
||||||
return fragment;
|
case 1:
|
||||||
|
return mServerFragment;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -160,5 +166,15 @@ public class MainActivity extends SherlockFragmentActivity implements
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forwards to ServerFragment if it is active.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if ((getSupportActionBar().getSelectedTab().getPosition() == 1) &&
|
||||||
|
!mServerFragment.onBackPressed())
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
30
src/com/github/nutomic/controldlna/ServerArrayAdapter.java
Normal file
30
src/com/github/nutomic/controldlna/ServerArrayAdapter.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
import org.teleal.cling.model.meta.Device;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
public class ServerArrayAdapter extends ArrayAdapter<Device<?, ?, ?>> {
|
||||||
|
|
||||||
|
public ServerArrayAdapter(Context context) {
|
||||||
|
super(context, android.R.layout.simple_list_item_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||||
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
|
||||||
|
}
|
||||||
|
TextView tv = (TextView) convertView.findViewById(android.R.id.text1);
|
||||||
|
tv.setText(getItem(position).getDisplayString());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
275
src/com/github/nutomic/controldlna/ServerFragment.java
Normal file
275
src/com/github/nutomic/controldlna/ServerFragment.java
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
package com.github.nutomic.controldlna;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 org.teleal.cling.registry.Registry;
|
||||||
|
import org.teleal.cling.registry.RegistryListener;
|
||||||
|
import org.teleal.cling.support.contentdirectory.callback.Browse;
|
||||||
|
import org.teleal.cling.support.model.BrowseFlag;
|
||||||
|
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.support.v4.app.ListFragment;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ListView;
|
||||||
|
|
||||||
|
public class ServerFragment extends ListFragment {
|
||||||
|
|
||||||
|
private String TAG = "ServerFragment";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ListView adapter for showing a list of DLNA media servers.
|
||||||
|
*/
|
||||||
|
private ServerArrayAdapter serverAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the media server of which folders are currently shown.
|
||||||
|
* Null if media servers are shown.
|
||||||
|
*/
|
||||||
|
private Device<?, ?, ?> currentServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ListView adapter for showing a list of files/folders.
|
||||||
|
*/
|
||||||
|
private FileArrayAdapter fileAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds path to current directory on top, paths for higher directories
|
||||||
|
* behind that.
|
||||||
|
*/
|
||||||
|
private Stack<String> currentPath = new Stack<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cling UPNP service.
|
||||||
|
*/
|
||||||
|
private AndroidUpnpService upnpService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection Cling to UPNP service.
|
||||||
|
*/
|
||||||
|
private ServiceConnection serviceConnection= new ServiceConnection() {
|
||||||
|
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
upnpService = (AndroidUpnpService) service;
|
||||||
|
Log.i(TAG, "Starting device search");
|
||||||
|
upnpService.getRegistry().addListener(registryListener);
|
||||||
|
upnpService.getControlPoint().search();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
upnpService = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives updates when devices are added or removed.
|
||||||
|
*/
|
||||||
|
private RegistryListener registryListener = new RegistryListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceUpdated(Registry registry, RemoteDevice device) {
|
||||||
|
if (device == currentServer)
|
||||||
|
getFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceRemoved(Registry registry, RemoteDevice device) {
|
||||||
|
remove(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryStarted(Registry registry,
|
||||||
|
RemoteDevice device) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceDiscoveryFailed(Registry registry,
|
||||||
|
RemoteDevice device, Exception exception) {
|
||||||
|
Log.w(TAG, "Device discovery failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remoteDeviceAdded(Registry registry, RemoteDevice device) {
|
||||||
|
add(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceRemoved(Registry registry, LocalDevice device) {
|
||||||
|
remove(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localDeviceAdded(Registry registry, LocalDevice device) {
|
||||||
|
add(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeShutdown(Registry registry) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterShutdown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a device to the ListView.
|
||||||
|
*/
|
||||||
|
private void add(final Device<?, ?, ?> device) {
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (device.getType().getType().equals("MediaServer"))
|
||||||
|
serverAdapter.add(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a device from the ListView.
|
||||||
|
*/
|
||||||
|
private void remove(final Device<?, ?, ?> device) {
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
serverAdapter.remove(device);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes ListView adapters, launches Cling UPNP service.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
serverAdapter = new ServerArrayAdapter(getActivity());
|
||||||
|
fileAdapter = new FileArrayAdapter(getActivity());
|
||||||
|
|
||||||
|
setListAdapter(serverAdapter);
|
||||||
|
|
||||||
|
getActivity().getApplicationContext().bindService(
|
||||||
|
new Intent(getActivity(), AndroidUpnpServiceImpl.class),
|
||||||
|
serviceConnection,
|
||||||
|
Context.BIND_AUTO_CREATE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes Cling UPNP service.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
if (upnpService != null)
|
||||||
|
upnpService.getRegistry().removeListener(registryListener);
|
||||||
|
getActivity().getApplicationContext().unbindService(serviceConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters directory browsing mode or enters a deeper level directory.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||||
|
if (getListAdapter() == serverAdapter) {
|
||||||
|
setListAdapter(fileAdapter);
|
||||||
|
currentServer = serverAdapter.getItem(position);
|
||||||
|
// Root directory.
|
||||||
|
getFiles("0");
|
||||||
|
}
|
||||||
|
else if (getListAdapter() == fileAdapter) {
|
||||||
|
if (fileAdapter.getItem(position) instanceof Container) {
|
||||||
|
getFiles(((Container) fileAdapter.getItem(position)).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a new directory and displays it.
|
||||||
|
*/
|
||||||
|
private void getFiles(String directory) {
|
||||||
|
currentPath.push(directory);
|
||||||
|
getFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the current directory on the ListView.
|
||||||
|
*/
|
||||||
|
private void getFiles() {
|
||||||
|
Service<?, ?> service = currentServer.findService(
|
||||||
|
new ServiceType("schemas-upnp-org", "ContentDirectory"));
|
||||||
|
upnpService.getControlPoint().execute(new Browse(service,
|
||||||
|
currentPath.peek(), BrowseFlag.DIRECT_CHILDREN) {
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void received(ActionInvocation actionInvocation,
|
||||||
|
final DIDLContent didl) {
|
||||||
|
getActivity().runOnUiThread(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
fileAdapter.clear();
|
||||||
|
for (Container c : didl.getContainers())
|
||||||
|
fileAdapter.add(c);
|
||||||
|
for (Item i : didl.getItems())
|
||||||
|
fileAdapter.add(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateStatus(Status status) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Override
|
||||||
|
public void failure(ActionInvocation actionInvocation,
|
||||||
|
UpnpResponse operation, String defaultMessage) {
|
||||||
|
Log.w(TAG, "Failed to load directory contents: " +
|
||||||
|
defaultMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles back button press to traverse directories (while in directory
|
||||||
|
* browsing mode).
|
||||||
|
*
|
||||||
|
* @return True if button press was handled.
|
||||||
|
*/
|
||||||
|
public boolean onBackPressed() {
|
||||||
|
if (getListAdapter() == serverAdapter)
|
||||||
|
return false;
|
||||||
|
currentPath.pop();
|
||||||
|
if (currentPath.empty()) {
|
||||||
|
setListAdapter(serverAdapter);
|
||||||
|
currentServer = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getFiles();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in a new issue