Added Media Server browsing.

This commit is contained in:
Felix Ableitner 2013-06-01 17:47:48 +02:00
parent 0896c65afe
commit eceb3f8025
8 changed files with 369 additions and 11 deletions

View file

@ -4,6 +4,11 @@
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"
android:targetSdkVersion="17" /> android:targetSdkVersion="17" />
@ -27,6 +32,8 @@
</activity> </activity>
<service android:name="org.teleal.cling.android.AndroidUpnpServiceImpl"/>
</application> </application>
</manifest> </manifest>

BIN
libs/cling-core-1.0.5.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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;
}
}

View file

@ -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
@ -161,4 +167,14 @@ public class MainActivity extends SherlockFragmentActivity implements
} }
} }
/**
* Forwards to ServerFragment if it is active.
*/
@Override
public void onBackPressed() {
if ((getSupportActionBar().getSelectedTab().getPosition() == 1) &&
!mServerFragment.onBackPressed())
super.onBackPressed();
}
} }

View 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;
}
}

View 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;
}
}