Created
August 22, 2018 00:23
-
-
Save kgilmer/b643d9d82206db034778b651b63d1411 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class JsonSource(context: Context, source: Uri) : AbstractMusicSource() { | |
private var catalog: List<MediaMetadataCompat> = emptyList() | |
init { | |
state = STATE_INITIALIZING | |
UpdateCatalogTask(Glide.with(context)) { mediaItems -> | |
catalog = mediaItems | |
state = STATE_INITIALIZED | |
}.execute(source) | |
} | |
override fun iterator(): Iterator<MediaMetadataCompat> = catalog.iterator() | |
} | |
/** | |
* Task to connect to remote URIs and download/process JSON files that correspond to | |
* [MediaMetadataCompat] objects. | |
*/ | |
private class UpdateCatalogTask(val glide: RequestManager, | |
val listener: (List<MediaMetadataCompat>) -> Unit) : | |
AsyncTask<Uri, Void, List<MediaMetadataCompat>>() { | |
override fun doInBackground(vararg params: Uri): List<MediaMetadataCompat> { | |
val gson = Gson() | |
val mediaItems = ArrayList<MediaMetadataCompat>() | |
params.forEach { catalogUri -> | |
val catalogConn = URL(catalogUri.toString()) | |
val reader = BufferedReader(InputStreamReader(catalogConn.openStream())) | |
val musicCat = gson.fromJson<JsonCatalog>(reader, JsonCatalog::class.java) | |
// Get the base URI to fix up relative references later. | |
val baseUri = catalogUri.toString().removeSuffix(catalogUri.lastPathSegment) | |
mediaItems += musicCat.music.map { song -> | |
// The JSON may have paths that are relative to the source of the JSON | |
// itself. We need to fix them up here to turn them into absolute paths. | |
if (!song.source.startsWith(catalogUri.scheme)) { | |
song.source = baseUri + song.source | |
} | |
if (!song.image.startsWith(catalogUri.scheme)) { | |
song.image = baseUri + song.image | |
} | |
// Block on downloading artwork. | |
val art = glide.applyDefaultRequestOptions(glideOptions) | |
.asBitmap() | |
.load(song.image) | |
.submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) | |
.get() | |
MediaMetadataCompat.Builder() | |
.from(song) | |
.apply { | |
albumArt = art | |
} | |
.build() | |
}.toList() | |
} | |
return mediaItems | |
} | |
override fun onPostExecute(mediaItems: List<MediaMetadataCompat>) { | |
super.onPostExecute(mediaItems) | |
listener(mediaItems) | |
} | |
} | |
/** | |
* Extension method for [MediaMetadataCompat.Builder] to set the fields from | |
* our JSON constructed object (to make the code a bit easier to see). | |
*/ | |
fun MediaMetadataCompat.Builder.from(jsonMusic: JsonMusic): MediaMetadataCompat.Builder { | |
// The duration from the JSON is given in seconds, but the rest of the code works in | |
// milliseconds. Here's where we convert to the proper units. | |
val durationMs = TimeUnit.SECONDS.toMillis(jsonMusic.duration) | |
id = jsonMusic.id | |
title = jsonMusic.title | |
artist = jsonMusic.artist | |
album = jsonMusic.album | |
duration = durationMs | |
genre = jsonMusic.genre | |
mediaUri = jsonMusic.source | |
albumArtUri = jsonMusic.image | |
trackNumber = jsonMusic.trackNumber | |
trackCount = jsonMusic.totalTrackCount | |
flag = MediaItem.FLAG_PLAYABLE | |
// To make things easier for *displaying* these, set the display properties as well. | |
displayTitle = jsonMusic.title | |
displaySubtitle = jsonMusic.artist | |
displayDescription = jsonMusic.album | |
displayIconUri = jsonMusic.image | |
// Add downloadStatus to force the creation of an "extras" bundle in the resulting | |
// MediaMetadataCompat object. This is needed to send accurate metadata to the | |
// media session during updates. | |
downloadStatus = STATUS_NOT_DOWNLOADED | |
// Allow it to be used in the typical builder style. | |
return this | |
} | |
/** | |
* Wrapper object for our JSON in order to be processed easily by GSON. | |
*/ | |
class JsonCatalog { | |
var music: List<JsonMusic> = ArrayList() | |
} | |
/** | |
* An individual piece of music included in our JSON catalog. | |
* The format from the server is as specified: | |
* ``` | |
* { "music" : [ | |
* { "title" : // Title of the piece of music | |
* "album" : // Album title of the piece of music | |
* "artist" : // Artist of the piece of music | |
* "genre" : // Primary genre of the music | |
* "source" : // Path to the music, which may be relative | |
* "image" : // Path to the art for the music, which may be relative | |
* "trackNumber" : // Track number | |
* "totalTrackCount" : // Track count | |
* "duration" : // Duration of the music in seconds | |
* "site" : // Source of the music, if applicable | |
* } | |
* ]} | |
* ``` | |
* | |
* `source` and `image` can be provided in either relative or | |
* absolute paths. For example: | |
* `` | |
* "source" : "https://www.example.com/music/ode_to_joy.mp3", | |
* "image" : "ode_to_joy.jpg" | |
* `` | |
* | |
* The `source` specifies the full URI to download the piece of music from, but | |
* `image` will be fetched relative to the path of the JSON file itself. This means | |
* that if the JSON was at "https://www.example.com/json/music.json" then the image would be found | |
* at "https://www.example.com/json/ode_to_joy.jpg". | |
*/ | |
class JsonMusic { | |
var id: String = "" | |
var title: String = "" | |
var album: String = "" | |
var artist: String = "" | |
var genre: String = "" | |
var source: String = "" | |
var image: String = "" | |
var trackNumber: Long = 0 | |
var totalTrackCount: Long = 0 | |
var duration: Long = -1 | |
var site: String = "" | |
} | |
private const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px | |
private val glideOptions = RequestOptions() | |
.fallback(R.drawable.default_art) | |
.diskCacheStrategy(DiskCacheStrategy.RESOURCE) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment