diff --git a/src/com/github/nutomic/controldlna/ServerArrayAdapter.java b/src/com/github/nutomic/controldlna/DeviceArrayAdapter.java similarity index 88% rename from src/com/github/nutomic/controldlna/ServerArrayAdapter.java rename to src/com/github/nutomic/controldlna/DeviceArrayAdapter.java index 74eb558..d19b80f 100644 --- a/src/com/github/nutomic/controldlna/ServerArrayAdapter.java +++ b/src/com/github/nutomic/controldlna/DeviceArrayAdapter.java @@ -9,9 +9,9 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; -public class ServerArrayAdapter extends ArrayAdapter> { +public class DeviceArrayAdapter extends ArrayAdapter> { - public ServerArrayAdapter(Context context) { + public DeviceArrayAdapter(Context context) { super(context, android.R.layout.simple_list_item_1); } diff --git a/src/com/github/nutomic/controldlna/MainActivity.java b/src/com/github/nutomic/controldlna/MainActivity.java index 7e6eedb..b43232b 100644 --- a/src/com/github/nutomic/controldlna/MainActivity.java +++ b/src/com/github/nutomic/controldlna/MainActivity.java @@ -112,13 +112,30 @@ public class MainActivity extends SherlockFragmentActivity implements } /** - * Forwards to ServerFragment if it is active. + * 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. */ @Override public void onBackPressed() { - if ((getSupportActionBar().getSelectedTab().getPosition() == 1) && - !mServerFragment.onBackPressed()) + OnBackPressedListener currentFragment = (OnBackPressedListener) + mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem()); + if (!currentFragment.onBackPressed()) super.onBackPressed(); } + + public void play(String uri) { + getSupportActionBar().selectTab(getSupportActionBar().getTabAt(0)); + mRendererFragment.play(uri); + } } diff --git a/src/com/github/nutomic/controldlna/RendererFragment.java b/src/com/github/nutomic/controldlna/RendererFragment.java index 1a08166..40d1902 100644 --- a/src/com/github/nutomic/controldlna/RendererFragment.java +++ b/src/com/github/nutomic/controldlna/RendererFragment.java @@ -1,7 +1,258 @@ package com.github.nutomic.controldlna; -import android.support.v4.app.Fragment; +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.avtransport.callback.Play; +import org.teleal.cling.support.avtransport.callback.SetAVTransportURI; -public class RendererFragment extends Fragment { +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; +import android.widget.Toast; + +import com.github.nutomic.controldlna.MainActivity.OnBackPressedListener; + +/** + * Shows a list of media servers, allowing to select one for playback. + * + * @author Felix + * + */ +public class RendererFragment extends ListFragment implements OnBackPressedListener { + + private String TAG = "RendererFragment"; + + /** + * ListView adapter of media renderers. + */ + private DeviceArrayAdapter mRendererAdapter; + + /** + * The media renderer that is currently active. + */ + @SuppressWarnings("rawtypes") + private Device mCurrentRenderer; + + /** + * Stores uri that is to be played if no renderer is selected. + */ + private String mCachedPlayUri = ""; + + /** + * Cling UPNP service. + */ + private AndroidUpnpService mUpnpService; + + /** + * Connection Cling to UPNP service. + */ + private ServiceConnection mServiceConnection= new ServiceConnection() { + + public void onServiceConnected(ComponentName className, IBinder service) { + mUpnpService = (AndroidUpnpService) service; + Log.i(TAG, "Starting device search"); + mUpnpService.getRegistry().addListener(mRegistryListener); + mUpnpService.getControlPoint().search(); + } + + public void onServiceDisconnected(ComponentName className) { + mUpnpService = null; + } + }; + + /** + * Receives updates when devices are added or removed. + */ + private RegistryListener mRegistryListener = new RegistryListener() { + + @Override + public void remoteDeviceUpdated(Registry registry, RemoteDevice device) { + } + + @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" + exception.getMessage()); + } + + @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("MediaRenderer")) + mRendererAdapter.add(device); + } + }); + } + + /** + * Remove a device from the ListView. + */ + private void remove(final Device device) { + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + mRendererAdapter.remove(device); + } + }); + } + }; + + /** + * Initializes ListView adapters, launches Cling UPNP service. + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mRendererAdapter = new DeviceArrayAdapter(getActivity()); + + setListAdapter(mRendererAdapter); + + getActivity().getApplicationContext().bindService( + new Intent(getActivity(), AndroidUpnpServiceImpl.class), + mServiceConnection, + Context.BIND_AUTO_CREATE + ); + } + + /** + * Clears cached playback URI. + */ + @Override + public void onPause() { + super.onPause(); + mCachedPlayUri = ""; + } + + /** + * Closes Cling UPNP service. + */ + @Override + public void onDestroy() { + super.onDestroy(); + if (mUpnpService != null) + mUpnpService.getRegistry().removeListener(mRegistryListener); + getActivity().getApplicationContext().unbindService(mServiceConnection); + } + + /** + * Plays an URI to a media renderer. If none is selected, the URI is + * cached and played after selecting one. + */ + void play(String uri) { + Log.d(TAG, uri); + if (mCurrentRenderer != null) { + final Service service = mCurrentRenderer.findService( + new ServiceType("schemas-upnp-org", "AVTransport")); + mUpnpService.getControlPoint().execute(new SetAVTransportURI(service, + uri, "NO METADATA") { + @SuppressWarnings("rawtypes") + @Override + public void failure(ActionInvocation invocation, + UpnpResponse operation, String defaultMsg) { + Log.w(TAG, "Playback failed: " + defaultMsg); + } + + @SuppressWarnings("rawtypes") + @Override + public void success(ActionInvocation invocation) { + mUpnpService.getControlPoint().execute(new Play(service) { + @Override + public void failure(ActionInvocation invocation, + UpnpResponse operation, String defaultMsg) { + Log.w(TAG, "Playback failed: " + defaultMsg); + } + }); + } + }); + } + else { + Toast.makeText(getActivity(), "Please select a renderer.", + Toast.LENGTH_SHORT).show(); + mCachedPlayUri = uri; + } + } + + /** + * Selects a media renderer. + */ + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + if (mCurrentRenderer == null) { + mCurrentRenderer = mRendererAdapter.getItem(position); + setListAdapter(null); + if (!mCachedPlayUri.equals("")) { + play(mCachedPlayUri); + mCachedPlayUri = ""; + } + } + } + + /** + * Unselects current media renderer if one is selected. + */ + @Override + public boolean onBackPressed() { + if (mCurrentRenderer != null) { + setListAdapter(mRendererAdapter); + mCurrentRenderer = null; + return true; + } + return false; + } } diff --git a/src/com/github/nutomic/controldlna/ServerFragment.java b/src/com/github/nutomic/controldlna/ServerFragment.java index 9e7f912..5a7b5c3 100644 --- a/src/com/github/nutomic/controldlna/ServerFragment.java +++ b/src/com/github/nutomic/controldlna/ServerFragment.java @@ -30,51 +30,60 @@ import android.util.Log; import android.view.View; import android.widget.ListView; -public class ServerFragment extends ListFragment { +import com.github.nutomic.controldlna.MainActivity.OnBackPressedListener; + +/** + * Shows a list of media servers, upon selecting one, allows browsing theur + * directories. + * + * @author Felix + * + */ +public class ServerFragment extends ListFragment implements OnBackPressedListener { private String TAG = "ServerFragment"; /** * ListView adapter for showing a list of DLNA media servers. */ - private ServerArrayAdapter serverAdapter; + private DeviceArrayAdapter mServerAdapter; /** * Reference to the media server of which folders are currently shown. * Null if media servers are shown. */ - private Device currentServer; + private Device mCurrentServer; /** * ListView adapter for showing a list of files/folders. */ - private FileArrayAdapter fileAdapter; + private FileArrayAdapter mFileAdapter; /** * Holds path to current directory on top, paths for higher directories * behind that. */ - private Stack currentPath = new Stack(); + private Stack mCurrentPath = new Stack(); /** * Cling UPNP service. */ - private AndroidUpnpService upnpService; + private AndroidUpnpService mUpnpService; /** * Connection Cling to UPNP service. */ - private ServiceConnection serviceConnection= new ServiceConnection() { + private ServiceConnection mServiceConnection= new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { - upnpService = (AndroidUpnpService) service; + mUpnpService = (AndroidUpnpService) service; Log.i(TAG, "Starting device search"); - upnpService.getRegistry().addListener(registryListener); - upnpService.getControlPoint().search(); + mUpnpService.getRegistry().addListener(registryListener); + mUpnpService.getControlPoint().search(); } public void onServiceDisconnected(ComponentName className) { - upnpService = null; + mUpnpService = null; } }; @@ -85,7 +94,7 @@ public class ServerFragment extends ListFragment { @Override public void remoteDeviceUpdated(Registry registry, RemoteDevice device) { - if (device == currentServer) + if (device == mCurrentServer) getFiles(); } @@ -102,7 +111,7 @@ public class ServerFragment extends ListFragment { @Override public void remoteDeviceDiscoveryFailed(Registry registry, RemoteDevice device, Exception exception) { - Log.w(TAG, "Device discovery failed"); + Log.w(TAG, "Device discovery failed" + exception.getMessage()); } @Override @@ -137,7 +146,7 @@ public class ServerFragment extends ListFragment { @Override public void run() { if (device.getType().getType().equals("MediaServer")) - serverAdapter.add(device); + mServerAdapter.add(device); } }); } @@ -150,7 +159,7 @@ public class ServerFragment extends ListFragment { @Override public void run() { - serverAdapter.remove(device); + mServerAdapter.remove(device); } }); } @@ -162,14 +171,14 @@ public class ServerFragment extends ListFragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - serverAdapter = new ServerArrayAdapter(getActivity()); - fileAdapter = new FileArrayAdapter(getActivity()); + mServerAdapter = new DeviceArrayAdapter(getActivity()); + mFileAdapter = new FileArrayAdapter(getActivity()); - setListAdapter(serverAdapter); + setListAdapter(mServerAdapter); getActivity().getApplicationContext().bindService( new Intent(getActivity(), AndroidUpnpServiceImpl.class), - serviceConnection, + mServiceConnection, Context.BIND_AUTO_CREATE ); } @@ -180,9 +189,9 @@ public class ServerFragment extends ListFragment { @Override public void onDestroy() { super.onDestroy(); - if (upnpService != null) - upnpService.getRegistry().removeListener(registryListener); - getActivity().getApplicationContext().unbindService(serviceConnection); + if (mUpnpService != null) + mUpnpService.getRegistry().removeListener(registryListener); + getActivity().getApplicationContext().unbindService(mServiceConnection); } /** @@ -190,15 +199,20 @@ public class ServerFragment extends ListFragment { */ @Override public void onListItemClick(ListView l, View v, int position, long id) { - if (getListAdapter() == serverAdapter) { - setListAdapter(fileAdapter); - currentServer = serverAdapter.getItem(position); + if (getListAdapter() == mServerAdapter) { + setListAdapter(mFileAdapter); + mCurrentServer = mServerAdapter.getItem(position); // Root directory. getFiles("0"); } - else if (getListAdapter() == fileAdapter) { - if (fileAdapter.getItem(position) instanceof Container) { - getFiles(((Container) fileAdapter.getItem(position)).getId()); + else if (getListAdapter() == mFileAdapter) { + if (mFileAdapter.getItem(position) instanceof Container) { + getFiles(((Container) mFileAdapter.getItem(position)).getId()); + } + else { + MainActivity activity = (MainActivity) getActivity(); + activity.play(mFileAdapter.getItem(position) + .getFirstResource().getValue()); } } } @@ -207,7 +221,7 @@ public class ServerFragment extends ListFragment { * Opens a new directory and displays it. */ private void getFiles(String directory) { - currentPath.push(directory); + mCurrentPath.push(directory); getFiles(); } @@ -215,10 +229,10 @@ public class ServerFragment extends ListFragment { * Displays the current directory on the ListView. */ private void getFiles() { - Service service = currentServer.findService( + Service service = mCurrentServer.findService( new ServiceType("schemas-upnp-org", "ContentDirectory")); - upnpService.getControlPoint().execute(new Browse(service, - currentPath.peek(), BrowseFlag.DIRECT_CHILDREN) { + mUpnpService.getControlPoint().execute(new Browse(service, + mCurrentPath.peek(), BrowseFlag.DIRECT_CHILDREN) { @SuppressWarnings("rawtypes") @Override @@ -228,11 +242,11 @@ public class ServerFragment extends ListFragment { @Override public void run() { - fileAdapter.clear(); + mFileAdapter.clear(); for (Container c : didl.getContainers()) - fileAdapter.add(c); + mFileAdapter.add(c); for (Item i : didl.getItems()) - fileAdapter.add(i); + mFileAdapter.add(i); } }); } @@ -255,16 +269,14 @@ public class ServerFragment extends ListFragment { /** * 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) + if (getListAdapter() == mServerAdapter) return false; - currentPath.pop(); - if (currentPath.empty()) { - setListAdapter(serverAdapter); - currentServer = null; + mCurrentPath.pop(); + if (mCurrentPath.empty()) { + setListAdapter(mServerAdapter); + mCurrentServer = null; } else { getFiles();