/*
This file is part of Subsonic.
Subsonic 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.
Subsonic 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 Subsonic. If not, see .
Copyright 2015 (C) Scott Jackson
*/
package github.daneren2005.dsub.service;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.util.Log;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import github.daneren2005.dsub.R;
import github.daneren2005.dsub.domain.Artist;
import github.daneren2005.dsub.domain.Indexes;
import github.daneren2005.dsub.domain.MusicDirectory;
import github.daneren2005.dsub.domain.MusicDirectory.Entry;
import github.daneren2005.dsub.domain.MusicFolder;
import github.daneren2005.dsub.domain.Playlist;
import github.daneren2005.dsub.domain.PodcastChannel;
import github.daneren2005.dsub.domain.PodcastEpisode;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.SilentServiceTask;
import github.daneren2005.dsub.util.Util;
import github.daneren2005.dsub.util.compat.RemoteControlClientLP;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class AutoMediaBrowserService extends MediaBrowserServiceCompat {
private static final String TAG = AutoMediaBrowserService.class.getSimpleName();
private static final String BROWSER_ROOT = "root";
private static final String BROWSER_ALBUM_LISTS = "albumLists";
private static final String BROWSER_LIBRARY = "library";
private static final String BROWSER_PLAYLISTS = "playlists";
private static final String BROWSER_PODCASTS = "podcasts";
private static final String BROWSER_BOOKMARKS = "bookmarks";
private static final String PLAYLIST_PREFIX = "pl-";
private static final String PODCAST_PREFIX = "po-";
private static final String ALBUM_TYPE_PREFIX = "ty-";
private static final String MUSIC_DIRECTORY_PREFIX = "md-";
private static final String MUSIC_FOLDER_PREFIX = "mf-";
private static final String MUSIC_DIRECTORY_CONTENTS_PREFIX = "mdc-";
private static final String ARTIST_CONTENTS_PREFIX = "art-";
private static final String ALBUM_CONTENTS_PREFIX = "alb-";
private DownloadService downloadService;
private Handler handler = new Handler();
@Override
public void onCreate() {
super.onCreate();
getDownloadService();
}
@Nullable
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
BrowserRoot root = new BrowserRoot(BROWSER_ROOT, null);
return root;
}
@Override
public void onLoadChildren(String parentId, Result> result) {
if(BROWSER_ROOT.equals(parentId)) {
getRootFolders(result);
} else if(BROWSER_ALBUM_LISTS.equals(parentId)) {
getAlbumLists(result);
} else if(parentId.startsWith(ALBUM_TYPE_PREFIX)) {
int id = Integer.valueOf(parentId.substring(ALBUM_TYPE_PREFIX.length()));
getAlbumList(result, id);
} else if(parentId.startsWith(MUSIC_DIRECTORY_PREFIX)) {
String id = parentId.substring(MUSIC_DIRECTORY_PREFIX.length());
getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_ID);
} else if(BROWSER_LIBRARY.equals(parentId)) {
if(Util.isTagBrowsing(downloadService)) {
getArtists(result);
} else {
getLibrary(result);
}
} else if(parentId.startsWith(MUSIC_FOLDER_PREFIX)) {
String id = parentId.substring(MUSIC_FOLDER_PREFIX.length());
getIndexes(result, id);
} else if(parentId.startsWith(MUSIC_DIRECTORY_CONTENTS_PREFIX)) {
String id = parentId.substring(MUSIC_DIRECTORY_CONTENTS_PREFIX.length());
getMusicDirectory(result, id);
} else if(parentId.startsWith(ARTIST_CONTENTS_PREFIX)) {
String id = parentId.substring(ARTIST_CONTENTS_PREFIX.length());
getArtist(result, id);
} else if(parentId.startsWith(ALBUM_CONTENTS_PREFIX)) {
String id = parentId.substring(ALBUM_CONTENTS_PREFIX.length());
getAlbum(result, id);
} else if(BROWSER_PLAYLISTS.equals(parentId)) {
getPlaylists(result);
} else if(parentId.startsWith(PLAYLIST_PREFIX)) {
String id = parentId.substring(PLAYLIST_PREFIX.length());
getPlayOptions(result, id, Constants.INTENT_EXTRA_NAME_PLAYLIST_ID);
} else if(BROWSER_PODCASTS.equals(parentId)) {
getPodcasts(result);
} else if(parentId.startsWith(PODCAST_PREFIX)) {
String id = parentId.substring(PODCAST_PREFIX.length());
getPodcastEpisodes(result, id);
} else if(BROWSER_BOOKMARKS.equals(parentId)) {
getBookmarks(result);
} else {
// No idea what it is, send empty result
result.sendResult(new ArrayList());
}
}
private void getRootFolders(Result> result) {
List mediaItems = new ArrayList<>();
MediaDescriptionCompat.Builder albumLists = new MediaDescriptionCompat.Builder();
albumLists.setTitle(downloadService.getString(R.string.main_albums_title))
.setMediaId(BROWSER_ALBUM_LISTS);
mediaItems.add(new MediaBrowserCompat.MediaItem(albumLists.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
MediaDescriptionCompat.Builder library = new MediaDescriptionCompat.Builder();
library.setTitle(downloadService.getString(R.string.button_bar_browse))
.setMediaId(BROWSER_LIBRARY);
mediaItems.add(new MediaBrowserCompat.MediaItem(library.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
MediaDescriptionCompat.Builder playlists = new MediaDescriptionCompat.Builder();
playlists.setTitle(downloadService.getString(R.string.button_bar_playlists))
.setMediaId(BROWSER_PLAYLISTS);
mediaItems.add(new MediaBrowserCompat.MediaItem(playlists.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_PODCASTS_ENABLED, true)) {
MediaDescriptionCompat.Builder podcasts = new MediaDescriptionCompat.Builder();
podcasts.setTitle(downloadService.getString(R.string.button_bar_podcasts))
.setMediaId(BROWSER_PODCASTS);
mediaItems.add(new MediaBrowserCompat.MediaItem(podcasts.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
if(Util.getPreferences(downloadService).getBoolean(Constants.PREFERENCES_KEY_BOOKMARKS_ENABLED, true)) {
MediaDescriptionCompat.Builder podcasts = new MediaDescriptionCompat.Builder();
podcasts.setTitle(downloadService.getString(R.string.button_bar_bookmarks))
.setMediaId(BROWSER_BOOKMARKS);
mediaItems.add(new MediaBrowserCompat.MediaItem(podcasts.build(), MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
private void getAlbumLists(Result> result) {
List albums = new ArrayList<>();
albums.add(R.string.main_albums_newest);
albums.add(R.string.main_albums_random);
if(!Util.isTagBrowsing(downloadService)) {
albums.add(R.string.main_albums_highest);
}
albums.add(R.string.main_albums_starred);
albums.add(R.string.main_albums_recent);
albums.add(R.string.main_albums_frequent);
List mediaItems = new ArrayList<>();
for(Integer id: albums) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(downloadService.getResources().getString(id))
.setMediaId(ALBUM_TYPE_PREFIX + id)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
private void getAlbumList(final Result> result, final int id) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
String albumListType;
switch(id) {
case R.string.main_albums_newest:
albumListType = "newest";
break;
case R.string.main_albums_random:
albumListType = "random";
break;
case R.string.main_albums_highest:
albumListType = "highest";
break;
case R.string.main_albums_starred:
albumListType = "starred";
break;
case R.string.main_albums_recent:
albumListType = "recent";
break;
case R.string.main_albums_frequent:
albumListType = "frequent";
break;
default:
albumListType = "newest";
}
return musicService.getAlbumList(albumListType, 20, 0, true, downloadService, null);
}
@Override
protected void done(MusicDirectory albumSet) {
List mediaItems = new ArrayList<>();
for(Entry album: albumSet.getChildren(true, false)) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(album.getAlbumDisplay())
.setSubtitle(album.getArtist())
.setMediaId(MUSIC_DIRECTORY_PREFIX + album.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getLibrary(final Result> result) {
new SilentServiceTask>(downloadService) {
@Override
protected List doInBackground(MusicService musicService) throws Throwable {
return musicService.getMusicFolders(false, downloadService, null);
}
@Override
protected void done(List folders) {
List mediaItems = new ArrayList<>();
for(MusicFolder folder: folders) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(folder.getName())
.setMediaId(MUSIC_FOLDER_PREFIX + folder.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getIndexes(final Result> result, final String musicFolderId) {
new SilentServiceTask(downloadService) {
@Override
protected Indexes doInBackground(MusicService musicService) throws Throwable {
return musicService.getIndexes(musicFolderId, false, downloadService, null);
}
@Override
protected void done(Indexes indexes) {
List mediaItems = new ArrayList<>();
// music directories
for(Artist artist : indexes.getArtists()) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(artist.getName())
.setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + artist.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
// music files
for(Entry entry: indexes.getEntries()) {
try {
entry.setBookmark(null); // don't resume from a bookmark in a browse listing
Bundle extras = new Bundle();
extras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, entry.toByteArray());
extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(entry.getId())
.setExtras(extras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch(IOException e) {
Log.e(TAG, "Failed to add entry", e);
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getMusicDirectory(final Result> result, final String musicDirectoryId) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
return musicService.getMusicDirectory(musicDirectoryId, "", false, downloadService, null);
}
@Override
protected void done(MusicDirectory directory) {
List mediaItems = new ArrayList<>();
addPlayOptions(mediaItems, musicDirectoryId, Constants.INTENT_EXTRA_NAME_ID);
for(Entry entry : directory.getChildren()) {
MediaDescriptionCompat description;
if (entry.isDirectory()) {
// browse deeper
description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(MUSIC_DIRECTORY_CONTENTS_PREFIX + entry.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
} else {
try {
// mark individual songs as directly playable
entry.setBookmark(null); // don't resume from a bookmark in a browse listing
Bundle extras = new Bundle();
extras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, entry.toByteArray());
extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(entry.getId())
.setExtras(extras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch (IOException e) {
Log.e(TAG, "Failed to add entry", e);
}
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getArtists(final Result> result) {
new SilentServiceTask(downloadService) {
@Override
protected Indexes doInBackground(MusicService musicService) throws Throwable {
return musicService.getIndexes(null, false, downloadService, null);
}
@Override
protected void done(Indexes indexes) {
List mediaItems = new ArrayList<>();
// music directories
for(Artist artist : indexes.getArtists()) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(artist.getName())
.setMediaId(ARTIST_CONTENTS_PREFIX + artist.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getAlbum(final Result> result, final String musicFolderId) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
return musicService.getAlbum(musicFolderId, "", false, downloadService, null);
}
@Override
protected void done(MusicDirectory songs) {
List mediaItems = new ArrayList<>();
addPlayOptions(mediaItems, musicFolderId, Constants.INTENT_EXTRA_NAME_ID);
// songs
for(Entry entry : songs.getSongs()) {
try {
Bundle extras = new Bundle();
extras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, entry.toByteArray());
extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(entry.getId())
.setExtras(extras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch(IOException e) {
Log.e(TAG, "Failed to add entry", e);
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getArtist(final Result> result, final String musicDirectoryId) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
return musicService.getArtist(musicDirectoryId, "", false, downloadService, null);
}
@Override
protected void done(MusicDirectory directory) {
List mediaItems = new ArrayList<>();
for(Entry entry : directory.getChildren()) {
MediaDescriptionCompat description;
if (entry.isDirectory()) {
// browse deeper
description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(ALBUM_CONTENTS_PREFIX + entry.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
} else {
try {
// mark individual songs as directly playable
Bundle extras = new Bundle();
extras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, entry.toByteArray());
extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setMediaId(entry.getId())
.setExtras(extras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch (IOException e) {
Log.e(TAG, "Failed to add entry", e);
}
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getPlaylists(final Result> result) {
new SilentServiceTask>(downloadService) {
@Override
protected List doInBackground(MusicService musicService) throws Throwable {
return musicService.getPlaylists(false, downloadService, null);
}
@Override
protected void done(List playlists) {
List mediaItems = new ArrayList<>();
for(Playlist playlist: playlists) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(playlist.getName())
.setMediaId(PLAYLIST_PREFIX + playlist.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getPodcasts(final Result> result) {
new SilentServiceTask>(downloadService) {
@Override
protected List doInBackground(MusicService musicService) throws Throwable {
return musicService.getPodcastChannels(false, downloadService, null);
}
@Override
protected void done(List podcasts) {
List mediaItems = new ArrayList<>();
for(PodcastChannel podcast: podcasts) {
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(podcast.getName())
.setMediaId(PODCAST_PREFIX + podcast.getId())
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE));
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getPodcastEpisodes(final Result> result, final String podcastId) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
return musicService.getPodcastEpisodes(false, podcastId, downloadService, null);
}
@Override
protected void done(MusicDirectory podcasts) {
List mediaItems = new ArrayList<>();
for(Entry entry: podcasts.getChildren(false, true)) {
try {
PodcastEpisode podcast = (PodcastEpisode) entry;
Bundle podcastExtras = new Bundle();
podcastExtras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, podcast.toByteArray());
podcastExtras.putString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, podcast.getId());
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(podcast.getTitle())
.setSubtitle(Util.formatDate(downloadService, podcast.getDate(), false))
.setMediaId(PODCAST_PREFIX + podcast.getId())
.setExtras(podcastExtras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch(IOException e) {
Log.e(TAG, "Failed to add podcast", e);
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void getBookmarks(final Result> result) {
new SilentServiceTask(downloadService) {
@Override
protected MusicDirectory doInBackground(MusicService musicService) throws Throwable {
return musicService.getBookmarks(false, downloadService, null);
}
@Override
protected void done(MusicDirectory bookmarkList) {
List mediaItems = new ArrayList<>();
for(Entry entry: bookmarkList.getChildren(false, true)) {
try {
Bundle extras = new Bundle();
extras.putByteArray(Constants.INTENT_EXTRA_ENTRY_BYTES, entry.toByteArray());
extras.putString(Constants.INTENT_EXTRA_NAME_CHILD_ID, entry.getId());
MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
.setTitle(entry.getTitle())
.setSubtitle(Util.formatDuration(entry.getBookmark().getPosition() / 1000))
.setMediaId(entry.getId())
.setExtras(extras)
.build();
mediaItems.add(new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
} catch(IOException e) {
Log.e(TAG, "Failed to add entry", e);
}
}
result.sendResult(mediaItems);
}
}.execute();
result.detach();
}
private void addPlayOptions(List mediaItems, String id, String idConstant) {
Bundle playAllExtras = new Bundle();
playAllExtras.putString(idConstant, id);
MediaDescriptionCompat.Builder playAll = new MediaDescriptionCompat.Builder();
playAll.setTitle(downloadService.getString(R.string.menu_play))
.setMediaId("play-" + id)
.setExtras(playAllExtras);
mediaItems.add(new MediaBrowserCompat.MediaItem(playAll.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
Bundle shuffleExtras = new Bundle();
shuffleExtras.putString(idConstant, id);
shuffleExtras.putBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, true);
MediaDescriptionCompat.Builder shuffle = new MediaDescriptionCompat.Builder();
shuffle.setTitle(downloadService.getString(R.string.menu_shuffle))
.setMediaId("shuffle-" + id)
.setExtras(shuffleExtras);
mediaItems.add(new MediaBrowserCompat.MediaItem(shuffle.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
Bundle playLastExtras = new Bundle();
playLastExtras.putString(idConstant, id);
playLastExtras.putBoolean(Constants.INTENT_EXTRA_PLAY_LAST, true);
MediaDescriptionCompat.Builder playLast = new MediaDescriptionCompat.Builder();
playLast.setTitle(downloadService.getString(R.string.menu_play_last))
.setMediaId("playLast-" + id)
.setExtras(playLastExtras);
mediaItems.add(new MediaBrowserCompat.MediaItem(playLast.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
}
private void getPlayOptions(Result> result, String id, String idConstant) {
List mediaItems = new ArrayList<>();
addPlayOptions(mediaItems, id, idConstant);
result.sendResult(mediaItems);
}
public void getDownloadService() {
if(DownloadService.getInstance() == null) {
DownloadService.startService(this);
}
waitForDownloadService();
}
public void waitForDownloadService() {
downloadService = DownloadService.getInstance();
if(downloadService == null) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
waitForDownloadService();
}
}, 100);
} else {
RemoteControlClientLP remoteControlClient = (RemoteControlClientLP) downloadService.getRemoteControlClient();
setSessionToken(remoteControlClient.getMediaSession().getSessionToken());
}
}
}