Java tutorial
/* * * Copyright (C) 2013 www.yaacc.de * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package de.yaacc.upnp.server.contentdirectory; import java.beans.PropertyChangeSupport; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.http.conn.util.InetAddressUtils; import org.fourthline.cling.binding.annotations.UpnpAction; import org.fourthline.cling.binding.annotations.UpnpInputArgument; import org.fourthline.cling.binding.annotations.UpnpOutputArgument; import org.fourthline.cling.binding.annotations.UpnpService; import org.fourthline.cling.binding.annotations.UpnpServiceId; import org.fourthline.cling.binding.annotations.UpnpServiceType; import org.fourthline.cling.binding.annotations.UpnpStateVariable; import org.fourthline.cling.binding.annotations.UpnpStateVariables; import org.fourthline.cling.model.types.ErrorCode; import org.fourthline.cling.model.types.UnsignedIntegerFourBytes; import org.fourthline.cling.model.types.csv.CSV; import org.fourthline.cling.model.types.csv.CSVString; import org.fourthline.cling.support.contentdirectory.ContentDirectoryErrorCode; import org.fourthline.cling.support.contentdirectory.ContentDirectoryException; import org.fourthline.cling.support.contentdirectory.DIDLParser; import org.fourthline.cling.support.model.BrowseFlag; import org.fourthline.cling.support.model.BrowseResult; import org.fourthline.cling.support.model.DIDLContent; import org.fourthline.cling.support.model.DIDLObject; import org.fourthline.cling.support.model.PersonWithRole; import org.fourthline.cling.support.model.Protocol; import org.fourthline.cling.support.model.ProtocolInfo; import org.fourthline.cling.support.model.Res; import org.fourthline.cling.support.model.SortCriterion; import org.fourthline.cling.support.model.container.Container; import org.fourthline.cling.support.model.container.MusicAlbum; import org.fourthline.cling.support.model.container.PhotoAlbum; import org.fourthline.cling.support.model.container.StorageFolder; import org.fourthline.cling.support.model.item.Item; import org.fourthline.cling.support.model.item.MusicTrack; import org.fourthline.cling.support.model.item.Photo; import org.seamless.util.MimeType; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; import de.yaacc.R; import de.yaacc.upnp.server.YaaccUpnpServerService; /** * a content directory which uses the content of the MediaStore in order to * provide it via upnp. * * @author Tobias Schoene (openbit) * */ @UpnpService(serviceId = @UpnpServiceId("ContentDirectory"), serviceType = @UpnpServiceType(value = "ContentDirectory", version = 1)) @UpnpStateVariables({ @UpnpStateVariable(name = "A_ARG_TYPE_ObjectID", sendEvents = false, datatype = "string"), @UpnpStateVariable(name = "A_ARG_TYPE_Result", sendEvents = false, datatype = "string"), @UpnpStateVariable(name = "A_ARG_TYPE_BrowseFlag", sendEvents = false, datatype = "string", allowedValuesEnum = BrowseFlag.class), @UpnpStateVariable(name = "A_ARG_TYPE_Filter", sendEvents = false, datatype = "string"), @UpnpStateVariable(name = "A_ARG_TYPE_SortCriteria", sendEvents = false, datatype = "string"), @UpnpStateVariable(name = "A_ARG_TYPE_Index", sendEvents = false, datatype = "ui4"), @UpnpStateVariable(name = "A_ARG_TYPE_Count", sendEvents = false, datatype = "ui4"), @UpnpStateVariable(name = "A_ARG_TYPE_UpdateID", sendEvents = false, datatype = "ui4"), @UpnpStateVariable(name = "A_ARG_TYPE_URI", sendEvents = false, datatype = "uri") }) public class YaaccContentDirectory { // test content only private Map<String, DIDLObject> content = new HashMap<String, DIDLObject>(); private Context context; private SharedPreferences preferences; public static final String CAPS_WILDCARD = "*"; @UpnpStateVariable(sendEvents = false) final private CSV<String> searchCapabilities; @UpnpStateVariable(sendEvents = false) final private CSV<String> sortCapabilities; @UpnpStateVariable(sendEvents = true, defaultValue = "0", eventMaximumRateMilliseconds = 200) private UnsignedIntegerFourBytes systemUpdateID = new UnsignedIntegerFourBytes(0); final private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public YaaccContentDirectory(Context context) { this.context = context; preferences = PreferenceManager.getDefaultSharedPreferences(context); if (isUsingTestContent()) { createTestContentDirectory(); } this.searchCapabilities = new CSVString(); this.searchCapabilities.addAll(searchCapabilities); this.sortCapabilities = new CSVString(); this.sortCapabilities.addAll(sortCapabilities); } private boolean isUsingTestContent() { return preferences.getBoolean(getContext().getString(R.string.settings_local_server_testcontent_chkbx), false); } public Context getContext() { return context; } /** * */ private void createTestContentDirectory() { StorageFolder rootContainer = new StorageFolder("0", "-1", "root", "yaacc", 2, 907000L); rootContainer.setClazz(new DIDLObject.Class("object.container")); rootContainer.setRestricted(true); addContent(rootContainer.getId(), rootContainer); List<MusicTrack> musicTracks = createMusicTracks("1"); MusicAlbum musicAlbum = new MusicAlbum("1", rootContainer, "Music", null, musicTracks.size(), musicTracks); musicAlbum.setClazz(new DIDLObject.Class("object.container")); musicAlbum.setRestricted(true); rootContainer.addContainer(musicAlbum); addContent(musicAlbum.getId(), musicAlbum); List<Photo> photos = createPhotos("2"); PhotoAlbum photoAlbum = new PhotoAlbum("2", rootContainer, "Photos", null, photos.size(), photos); photoAlbum.setClazz(new DIDLObject.Class("object.container")); photoAlbum.setRestricted(true); rootContainer.addContainer(photoAlbum); addContent(photoAlbum.getId(), photoAlbum); } private List<MusicTrack> createMusicTracks(String parentId) { String album = "Music"; // "Voice Mail"; String creator = null;// "Dr. Athur"; PersonWithRole artist = new PersonWithRole(creator, ""); MimeType mimeType = new MimeType("audio", "mpeg"); List<MusicTrack> result = new ArrayList<MusicTrack>(); Res res = new Res( new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456l, "00:02:33", 26752L, "http://api.jamendo.com/get2/stream/track/redirect/?id=310355&streamencoding=mp31"); res.setSampleFrequency(44100L); res.setNrAudioChannels(2L); MusicTrack musicTrack = new MusicTrack("101", parentId, "Bluey Shoey", creator, album, artist, res); musicTrack.setRestricted(true); addContent(musicTrack.getId(), musicTrack); result.add(musicTrack); musicTrack = new MusicTrack("102", parentId, "8-Bit", creator, album, artist, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456l, "00:02:01", 8192L, "http://api.jamendo.com/get2/stream/track/redirect/?id=310370&streamencoding=mp31")); musicTrack.setRestricted(true); addContent(musicTrack.getId(), musicTrack); result.add(musicTrack); musicTrack = new MusicTrack("103", parentId, "Spooky Number 3", creator, album, artist, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456l, "00:02:18", 8192L, "http://api.jamendo.com/get2/stream/track/redirect/?id=310371&streamencoding=mp31")); musicTrack.setRestricted(true); addContent(musicTrack.getId(), musicTrack); result.add(musicTrack); return result; } private List<Photo> createPhotos(String parentId) { String album = null; String creator = null; MimeType mimeType = new MimeType("image", "jpeg"); List<Photo> result = new ArrayList<Photo>(); String url = "http://kde-look.org/CONTENT/content-files/156304-DSC_0089-2-1600.jpg"; Photo photo = new Photo("201", parentId, url, creator, album, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456L, url)); photo.setRestricted(true); photo.setClazz(new DIDLObject.Class("object.item.imageItem")); addContent(photo.getId(), photo); result.add(photo); url = "http://kde-look.org/CONTENT/content-files/156246-DSC_0021-1600.jpg"; photo = new Photo("202", parentId, url, creator, album, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456L, url)); photo.setRestricted(true); photo.setClazz(new DIDLObject.Class("object.item.imageItem")); addContent(photo.getId(), photo); result.add(photo); url = "http://kde-look.org/CONTENT/content-files/156225-raining-bolt-1920x1200.JPG"; addContent(photo.getId(), photo); photo = new Photo("203", parentId, url, creator, album, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456L, url)); photo.setRestricted(true); photo.setClazz(new DIDLObject.Class("object.item.imageItem")); result.add(photo); url = "http://kde-look.org/CONTENT/content-files/156223-kungsleden1900x1200.JPG"; photo = new Photo("204", parentId, url, creator, album, new Res(new ProtocolInfo(Protocol.HTTP_GET, ProtocolInfo.WILDCARD, mimeType.toString(), ProtocolInfo.WILDCARD), 123456L, url)); photo.setRestricted(true); photo.setClazz(new DIDLObject.Class("object.item.imageItem")); addContent(photo.getId(), photo); result.add(photo); url = "http://kde-look.org/CONTENT/content-files/156218-DSC_0012-1600.jpg"; photo = new Photo("205", parentId, url, creator, album, new Res(mimeType, 123456L, url)); photo.setRestricted(true); photo.setClazz(new DIDLObject.Class("object.item.imageItem")); addContent(photo.getId(), photo); result.add(photo); return result; } // ******************************************************************* @UpnpAction(out = @UpnpOutputArgument(name = "SearchCaps")) public CSV<String> getSearchCapabilities() { return searchCapabilities; } @UpnpAction(out = @UpnpOutputArgument(name = "SortCaps")) public CSV<String> getSortCapabilities() { return sortCapabilities; } @UpnpAction(out = @UpnpOutputArgument(name = "Id")) synchronized public UnsignedIntegerFourBytes getSystemUpdateID() { return systemUpdateID; } public PropertyChangeSupport getPropertyChangeSupport() { return propertyChangeSupport; } /** * Call this method after making changes to your content directory. * <p> * This will notify clients that their view of the content directory is * potentially outdated and has to be refreshed. * </p> */ synchronized protected void changeSystemUpdateID() { Long oldUpdateID = getSystemUpdateID().getValue(); systemUpdateID.increment(true); getPropertyChangeSupport().firePropertyChange("SystemUpdateID", oldUpdateID, getSystemUpdateID().getValue()); } /** * add an object to the content of the directory * * @param id * of the object * @param content * the object */ private void addContent(String id, DIDLObject content) { this.content.put(id, content); } @UpnpAction(out = { @UpnpOutputArgument(name = "Result", stateVariable = "A_ARG_TYPE_Result", getterName = "getResult"), @UpnpOutputArgument(name = "NumberReturned", stateVariable = "A_ARG_TYPE_Count", getterName = "getCount"), @UpnpOutputArgument(name = "TotalMatches", stateVariable = "A_ARG_TYPE_Count", getterName = "getTotalMatches"), @UpnpOutputArgument(name = "UpdateID", stateVariable = "A_ARG_TYPE_UpdateID", getterName = "getContainerUpdateID") }) public BrowseResult browse(@UpnpInputArgument(name = "ObjectID", aliases = "ContainerID") String objectId, @UpnpInputArgument(name = "BrowseFlag") String browseFlag, @UpnpInputArgument(name = "Filter") String filter, @UpnpInputArgument(name = "StartingIndex", stateVariable = "A_ARG_TYPE_Index") UnsignedIntegerFourBytes firstResult, @UpnpInputArgument(name = "RequestedCount", stateVariable = "A_ARG_TYPE_Count") UnsignedIntegerFourBytes maxResults, @UpnpInputArgument(name = "SortCriteria") String orderBy) throws ContentDirectoryException { SortCriterion[] orderByCriteria; try { orderByCriteria = SortCriterion.valueOf(orderBy); } catch (Exception ex) { throw new ContentDirectoryException(ContentDirectoryErrorCode.UNSUPPORTED_SORT_CRITERIA, ex.toString()); } try { return browse(objectId, BrowseFlag.valueOrNullOf(browseFlag), filter, firstResult.getValue(), maxResults.getValue(), orderByCriteria); } catch (ContentDirectoryException ex) { throw ex; } catch (Exception ex) { throw new ContentDirectoryException(ErrorCode.ACTION_FAILED, ex.toString()); } } public BrowseResult browse(String objectID, BrowseFlag browseFlag, String filter, long firstResult, long maxResults, SortCriterion[] orderby) throws ContentDirectoryException { Log.d(getClass().getName(), "Browse: objectId: " + objectID + " browseFlag: " + browseFlag + " filter: " + filter + " firstResult: " + firstResult + " maxResults: " + maxResults + " orderby: " + orderby); int childCount = 0; DIDLObject didlObject = null; DIDLContent didl = new DIDLContent(); if (isUsingTestContent()) { didlObject = content.get(objectID); if (didlObject == null) { // object not found return root didlObject = content.get("0"); } if (browseFlag == BrowseFlag.METADATA) { didl.addObject(didlObject); childCount = 1; } else { if (didlObject instanceof Container) { Container container = (Container) didlObject; childCount = container.getChildCount(); for (Item item : container.getItems()) { didl.addItem(item); } for (Container cont : container.getContainers()) { didl.addContainer(cont); } } else { didl.addObject(didlObject); childCount = 1; } } } else { if (browseFlag == BrowseFlag.METADATA) { didlObject = findBrowserFor(objectID).browseMeta(this, objectID); didl.addObject(didlObject); childCount = 1; } else { childCount = 0; List<DIDLObject> children = findBrowserFor(objectID).browseChildren(this, objectID); for (DIDLObject child : children) { if (childCount >= maxResults) { break; } if (firstResult <= childCount) { didl.addObject(child); childCount++; } } } } BrowseResult result = null; try { // Generate output with nested items String didlXml = new DIDLParser().generate(didl, false); Log.d(getClass().getName(), "CDResponse: " + didlXml); result = new BrowseResult(didlXml, childCount, childCount); } catch (Exception e) { throw new ContentDirectoryException(ContentDirectoryErrorCode.CANNOT_PROCESS.getCode(), "Error while generating BrowseResult", e); } return result; } private ContentBrowser findBrowserFor(String objectID) { ContentBrowser result = null; if (objectID == null || objectID.equals("") || ContentDirectoryIDs.ROOT.getId().equals(objectID)) { result = new RootFolderBrowser(getContext()); } else if (ContentDirectoryIDs.IMAGES_FOLDER.getId().equals(objectID)) { result = new ImagesFolderBrowser(getContext()); } else if (ContentDirectoryIDs.VIDEOS_FOLDER.getId().equals(objectID)) { result = new VideosFolderBrowser(getContext()); } else if (ContentDirectoryIDs.MUSIC_FOLDER.getId().equals(objectID)) { result = new MusicFolderBrowser(getContext()); } else if (ContentDirectoryIDs.MUSIC_GENRES_FOLDER.getId().equals(objectID)) { result = new MusicGenresFolderBrowser(getContext()); } else if (ContentDirectoryIDs.MUSIC_ALBUMS_FOLDER.getId().equals(objectID)) { result = new MusicAlbumsFolderBrowser(getContext()); } else if (ContentDirectoryIDs.MUSIC_ARTISTS_FOLDER.getId().equals(objectID)) { result = new MusicArtistsFolderBrowser(getContext()); } else if (ContentDirectoryIDs.MUSIC_ALL_TITLES_FOLDER.getId().equals(objectID)) { result = new MusicAllTitlesFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_ALBUM_PREFIX.getId())) { result = new MusicAlbumFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_ARTIST_PREFIX.getId())) { result = new MusicArtistFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_GENRE_PREFIX.getId())) { result = new MusicGenreFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_ALL_TITLES_ITEM_PREFIX.getId())) { result = new MusicAllTitleItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_GENRE_ITEM_PREFIX.getId())) { result = new MusicGenreItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_ALBUM_ITEM_PREFIX.getId())) { result = new MusicAlbumItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.MUSIC_ARTIST_ITEM_PREFIX.getId())) { result = new MusicArtistItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.IMAGES_ALL_FOLDER.getId())) { result = new ImagesAllFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.IMAGES_BY_BUCKET_NAMES_FOLDER.getId())) { result = new ImagesByBucketNamesFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.IMAGE_ALL_PREFIX.getId())) { result = new ImageAllItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.IMAGES_BY_BUCKET_NAME_PREFIX.getId())) { result = new ImagesByBucketNameFolderBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.IMAGE_BY_BUCKET_PREFIX.getId())) { result = new ImageByBucketNameItemBrowser(getContext()); } else if (objectID.startsWith(ContentDirectoryIDs.VIDEO_PREFIX.getId())) { result = new VideoItemBrowser(getContext()); } return result; } /** * get the ip address of the device * * @return the address or null if anything went wrong * */ public String getIpAddress() { String hostAddress = null; try { for (Enumeration<NetworkInterface> networkInterfaces = NetworkInterface .getNetworkInterfaces(); networkInterfaces.hasMoreElements();) { NetworkInterface networkInterface = networkInterfaces.nextElement(); for (Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); inetAddresses .hasMoreElements();) { InetAddress inetAddress = inetAddresses.nextElement(); if (!inetAddress.isLoopbackAddress() && InetAddressUtils.isIPv4Address(inetAddress.getHostAddress())) { hostAddress = inetAddress.getHostAddress(); } } } } catch (SocketException se) { Log.d(YaaccUpnpServerService.class.getName(), "Error while retrieving network interfaces", se); } // maybe wifi is off we have to use the loopback device hostAddress = hostAddress == null ? "0.0.0.0" : hostAddress; return hostAddress; } public String formatDuration(String millisStr) { String res = ""; long duration = Long.valueOf(millisStr); long hours = TimeUnit.MILLISECONDS.toHours(duration) - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(duration)); long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(duration)); long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)); res = String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds); return res; // Date d = new Date(Long.parseLong(millis)); // SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); // return df.format(d); } }