mirror of
https://github.com/Michatec/Radio.git
synced 2026-01-30 23:17:21 +00:00
748 lines
29 KiB
Kotlin
748 lines
29 KiB
Kotlin
/*
|
|
* CollectionHelper.kt
|
|
* Implements the CollectionHelper object
|
|
* A CollectionHelper provides helper methods for the collection of stations
|
|
*
|
|
* This file is part of
|
|
* TRANSISTOR - Radio App for Android
|
|
*
|
|
* Copyright (c) 2015-22 - Y20K.org
|
|
* Licensed under the MIT-License
|
|
* http://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
|
|
package com.michatec.radio.helpers
|
|
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.net.Uri
|
|
import android.os.Handler
|
|
import android.os.Looper
|
|
import android.util.Log
|
|
import android.widget.Toast
|
|
import androidx.core.net.toFile
|
|
import androidx.core.net.toUri
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|
import androidx.media3.common.MediaItem
|
|
import androidx.media3.common.MediaMetadata
|
|
import com.michatec.radio.Keys
|
|
import com.michatec.radio.R
|
|
import com.michatec.radio.core.Collection
|
|
import com.michatec.radio.core.Station
|
|
import com.michatec.radio.search.DirectInputCheck
|
|
import kotlinx.coroutines.*
|
|
import kotlinx.coroutines.Dispatchers.IO
|
|
import java.io.File
|
|
import java.net.URL
|
|
import java.util.*
|
|
|
|
|
|
/*
|
|
* CollectionHelper object
|
|
*/
|
|
object CollectionHelper {
|
|
|
|
/* Define log tag */
|
|
private val TAG: String = CollectionHelper::class.java.simpleName
|
|
|
|
/* Checks if station is already in collection */
|
|
private fun isNewStation(collection: Collection, station: Station): Boolean {
|
|
collection.stations.forEach {
|
|
if (it.getStreamUri() == station.getStreamUri()) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
|
|
/* Checks if station is already in collection */
|
|
fun isNewStation(collection: Collection, remoteStationLocation: String): Boolean {
|
|
collection.stations.forEach {
|
|
if (it.remoteStationLocation == remoteStationLocation) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
|
|
/* Checks if a newer collection of radio stations is available on storage */
|
|
fun isNewerCollectionAvailable(date: Date): Boolean {
|
|
var newerCollectionAvailable = false
|
|
val modificationDate: Date = PreferencesHelper.loadCollectionModificationDate()
|
|
if (modificationDate.after(date) || date == Keys.DEFAULT_DATE) {
|
|
newerCollectionAvailable = true
|
|
}
|
|
return newerCollectionAvailable
|
|
}
|
|
|
|
|
|
/* Creates station from previously downloaded playlist file */
|
|
fun createStationFromPlaylistFile(
|
|
context: Context,
|
|
localFileUri: Uri,
|
|
remoteFileLocation: String
|
|
): Station {
|
|
// read station playlist
|
|
val station: Station =
|
|
FileHelper.readStationPlaylist(context.contentResolver.openInputStream(localFileUri))
|
|
if (station.name.isEmpty()) {
|
|
// construct name from file name - strips file extension
|
|
station.name = FileHelper.getFileName(context, localFileUri).substringBeforeLast(".")
|
|
}
|
|
station.remoteStationLocation = remoteFileLocation
|
|
station.remoteImageLocation = getFaviconAddress(remoteFileLocation)
|
|
station.modificationDate = GregorianCalendar.getInstance().time
|
|
return station
|
|
}
|
|
|
|
|
|
/* Updates radio station in collection */
|
|
fun updateStation(context: Context, collection: Collection, station: Station): Collection {
|
|
var updatedCollection: Collection = collection
|
|
|
|
// CASE: Update station retrieved from radio browser
|
|
if (station.radioBrowserStationUuid.isNotEmpty()) {
|
|
updatedCollection.stations.forEach {
|
|
if (it.radioBrowserStationUuid == station.radioBrowserStationUuid) {
|
|
// update station in collection with values from new station
|
|
it.streamUris[it.stream] = station.getStreamUri()
|
|
it.streamContent = station.streamContent
|
|
it.remoteImageLocation = station.remoteImageLocation
|
|
it.remoteStationLocation = station.remoteStationLocation
|
|
it.homepage = station.homepage
|
|
// update name - if not changed previously by user
|
|
if (!it.nameManuallySet) it.name = station.name
|
|
// re-download station image - if new URL and not changed previously by user
|
|
DownloadHelper.updateStationImage(context, it)
|
|
}
|
|
}
|
|
// sort and save collection
|
|
updatedCollection = sortCollection(updatedCollection)
|
|
saveCollection(context, updatedCollection, false)
|
|
}
|
|
|
|
// CASE: Update station retrieved via playlist
|
|
else if (station.remoteStationLocation.isNotEmpty()) {
|
|
updatedCollection.stations.forEach {
|
|
if (it.remoteStationLocation == station.remoteStationLocation) {
|
|
// update stream uri, mime type and station image url
|
|
it.streamUris[it.stream] = station.getStreamUri()
|
|
it.streamContent = station.streamContent
|
|
it.remoteImageLocation = station.remoteImageLocation
|
|
// update name - if not changed previously by user
|
|
if (!it.nameManuallySet) it.name = station.name
|
|
// re-download station image - if not changed previously by user
|
|
if (!it.imageManuallySet) DownloadHelper.updateStationImage(context, it)
|
|
}
|
|
}
|
|
// sort and save collection
|
|
updatedCollection = sortCollection(updatedCollection)
|
|
saveCollection(context, updatedCollection, false)
|
|
}
|
|
|
|
return updatedCollection
|
|
}
|
|
|
|
|
|
/* Adds new radio station to collection */
|
|
fun addStation(context: Context, collection: Collection, newStation: Station): Collection {
|
|
// check validity
|
|
if (!newStation.isValid()) {
|
|
Toast.makeText(context, R.string.toastmessage_station_not_valid, Toast.LENGTH_LONG)
|
|
.show()
|
|
return collection
|
|
}
|
|
// duplicate check
|
|
else if (!isNewStation(collection, newStation)) {
|
|
// update station
|
|
Handler(Looper.getMainLooper()).post {
|
|
Toast.makeText(context, R.string.toastmessage_station_duplicate, Toast.LENGTH_LONG)
|
|
.show()
|
|
}
|
|
return collection
|
|
}
|
|
// all clear -> add station
|
|
else {
|
|
var updatedCollection: Collection = collection
|
|
val updatedStationList: MutableList<Station> = collection.stations.toMutableList()
|
|
// add station
|
|
updatedStationList.add(newStation)
|
|
updatedCollection.stations = updatedStationList
|
|
// sort and save collection
|
|
updatedCollection = sortCollection(updatedCollection)
|
|
saveCollection(context, updatedCollection, false)
|
|
// download station image
|
|
DownloadHelper.updateStationImage(context, newStation)
|
|
// return updated collection
|
|
return updatedCollection
|
|
}
|
|
}
|
|
|
|
|
|
/* Sets station image - determines station by remote image file location */
|
|
fun setStationImageWithRemoteLocation(
|
|
context: Context,
|
|
collection: Collection,
|
|
tempImageFileUri: String,
|
|
remoteFileLocation: String,
|
|
imageManuallySet: Boolean = false
|
|
): Collection {
|
|
collection.stations.forEach { station ->
|
|
// compare image location protocol-agnostic (= without http / https)
|
|
if (station.remoteImageLocation.substringAfter(":") == remoteFileLocation.substringAfter(
|
|
":"
|
|
)
|
|
) {
|
|
station.smallImage = FileHelper.saveStationImage(
|
|
context,
|
|
station.uuid,
|
|
tempImageFileUri.toUri(),
|
|
Keys.SIZE_STATION_IMAGE_CARD,
|
|
Keys.STATION_IMAGE_FILE
|
|
).toString()
|
|
station.image = FileHelper.saveStationImage(
|
|
context,
|
|
station.uuid,
|
|
tempImageFileUri.toUri(),
|
|
Keys.SIZE_STATION_IMAGE_MAXIMUM,
|
|
Keys.STATION_IMAGE_FILE
|
|
).toString()
|
|
station.imageColor = ImageHelper.getMainColor(context, tempImageFileUri.toUri())
|
|
station.imageManuallySet = imageManuallySet
|
|
}
|
|
}
|
|
// save and return collection
|
|
saveCollection(context, collection)
|
|
return collection
|
|
}
|
|
|
|
|
|
/* Sets station image - determines station by remote image file location */
|
|
fun setStationImageWithStationUuid(
|
|
context: Context,
|
|
collection: Collection,
|
|
tempImageFileUri: Uri,
|
|
stationUuid: String,
|
|
imageManuallySet: Boolean = false
|
|
): Collection {
|
|
collection.stations.forEach { station ->
|
|
// find station by uuid
|
|
if (station.uuid == stationUuid) {
|
|
station.smallImage = FileHelper.saveStationImage(
|
|
context,
|
|
station.uuid,
|
|
tempImageFileUri,
|
|
Keys.SIZE_STATION_IMAGE_CARD,
|
|
Keys.STATION_IMAGE_FILE
|
|
).toString()
|
|
station.image = FileHelper.saveStationImage(
|
|
context,
|
|
station.uuid,
|
|
tempImageFileUri,
|
|
Keys.SIZE_STATION_IMAGE_MAXIMUM,
|
|
Keys.STATION_IMAGE_FILE
|
|
).toString()
|
|
station.imageColor = ImageHelper.getMainColor(context, tempImageFileUri)
|
|
station.imageManuallySet = imageManuallySet
|
|
}
|
|
}
|
|
// save and return collection
|
|
saveCollection(context, collection)
|
|
return collection
|
|
}
|
|
|
|
|
|
/* Clears an image folder for a given station */
|
|
fun clearImagesFolder(context: Context, station: Station) {
|
|
// clear image folder
|
|
val imagesFolder = File(
|
|
context.getExternalFilesDir(""),
|
|
FileHelper.determineDestinationFolderPath(Keys.FILE_TYPE_IMAGE, station.uuid)
|
|
)
|
|
FileHelper.clearFolder(imagesFolder, 0)
|
|
}
|
|
|
|
|
|
/* Deletes Images of a given station */
|
|
fun deleteStationImages(context: Context, station: Station) {
|
|
val imagesFolder = File(
|
|
context.getExternalFilesDir(""),
|
|
FileHelper.determineDestinationFolderPath(Keys.FILE_TYPE_IMAGE, station.uuid)
|
|
)
|
|
FileHelper.clearFolder(imagesFolder, 0, true)
|
|
}
|
|
|
|
|
|
/* Get station from collection for given UUID */
|
|
fun getStation(collection: Collection, stationUuid: String): Station {
|
|
collection.stations.forEach { station ->
|
|
if (station.uuid == stationUuid) {
|
|
return station
|
|
}
|
|
}
|
|
// fallback: return first station
|
|
return if (collection.stations.isNotEmpty()) {
|
|
collection.stations.first()
|
|
} else {
|
|
Station()
|
|
}
|
|
}
|
|
|
|
|
|
/* Get the position from collection for given UUID */
|
|
fun getStationPosition(collection: Collection, stationUuid: String): Int {
|
|
collection.stations.forEachIndexed { stationId, station ->
|
|
if (station.uuid == stationUuid) {
|
|
return stationId
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
|
|
/* Get the position from collection for given radioBrowserStationUuid */
|
|
fun getStationPositionFromRadioBrowserStationUuid(
|
|
collection: Collection,
|
|
radioBrowserStationUuid: String
|
|
): Int {
|
|
collection.stations.forEachIndexed { stationId, station ->
|
|
if (station.radioBrowserStationUuid == radioBrowserStationUuid) {
|
|
return stationId
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
|
|
/* Returns the children stations under under root (simple media library structure: root > stations) */
|
|
fun getChildren(context: Context, collection: Collection): List<MediaItem> {
|
|
val mediaItems: MutableList<MediaItem> = mutableListOf()
|
|
collection.stations.forEach { station ->
|
|
mediaItems.add(buildMediaItem(context, station))
|
|
}
|
|
return mediaItems
|
|
}
|
|
|
|
|
|
/* Returns media item for given station id */
|
|
fun getItem(context: Context, collection: Collection, stationUuid: String): MediaItem {
|
|
return buildMediaItem(context, getStation(collection, stationUuid))
|
|
}
|
|
|
|
|
|
/* Returns media item for last played station */
|
|
fun getRecent(context: Context, collection: Collection): MediaItem {
|
|
return buildMediaItem(context, getStation(collection, PreferencesHelper.loadLastPlayedStationUuid()))
|
|
}
|
|
|
|
|
|
/* Returns the library root item */
|
|
fun getRootItem(): MediaItem {
|
|
val metadata: MediaMetadata = MediaMetadata.Builder()
|
|
.setTitle("Root Folder")
|
|
.setIsPlayable(false)
|
|
.setIsBrowsable(true)
|
|
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
|
|
.build()
|
|
return MediaItem.Builder()
|
|
.setMediaId("[rootID]")
|
|
.setMediaMetadata(metadata)
|
|
.build()
|
|
}
|
|
|
|
|
|
/* Saves the playback state of a given station */
|
|
fun savePlaybackState(
|
|
context: Context,
|
|
collection: Collection,
|
|
stationUuid: String,
|
|
isPlaying: Boolean
|
|
): Collection {
|
|
collection.stations.forEach {
|
|
// reset playback state everywhere
|
|
it.isPlaying = false
|
|
// set given playback state at this station
|
|
if (it.uuid == stationUuid) {
|
|
it.isPlaying = isPlaying
|
|
}
|
|
}
|
|
// save collection and store modification date
|
|
collection.modificationDate = saveCollection(context, collection)
|
|
return collection
|
|
}
|
|
|
|
|
|
/* Saves collection of radio stations */
|
|
fun saveCollection(context: Context, collection: Collection, async: Boolean = true): Date {
|
|
Log.v(
|
|
TAG,
|
|
"Saving collection of radio stations to storage. Async = ${async}. Size = ${collection.stations.size}"
|
|
)
|
|
// get modification date
|
|
val date: Date = Calendar.getInstance().time
|
|
collection.modificationDate = date
|
|
// save collection to storage
|
|
when (async) {
|
|
true -> {
|
|
CoroutineScope(IO).launch {
|
|
// save collection on background thread
|
|
FileHelper.saveCollectionSuspended(context, collection, date)
|
|
// broadcast collection update
|
|
sendCollectionBroadcast(context, date)
|
|
}
|
|
}
|
|
false -> {
|
|
// save collection
|
|
FileHelper.saveCollection(context, collection, date)
|
|
// broadcast collection update
|
|
sendCollectionBroadcast(context, date)
|
|
}
|
|
}
|
|
// return modification date
|
|
return date
|
|
}
|
|
|
|
|
|
/* Creates station from playlist URLs and stream address URLs */
|
|
suspend fun createStationsFromUrl(query: String, lastCheckedAddress: String = String()): List<Station> {
|
|
val stationList: MutableList<Station> = mutableListOf()
|
|
val contentType: String = NetworkHelper.detectContentType(query).type.lowercase(Locale.getDefault())
|
|
val directInputCheck: DirectInputCheck? = null
|
|
|
|
// CASE: M3U playlist detected
|
|
if (Keys.MIME_TYPES_M3U.contains(contentType)) {
|
|
val lines: List<String> = NetworkHelper.downloadPlaylist(query)
|
|
stationList.addAll(readM3uPlaylistContent(lines))
|
|
}
|
|
// CASE: PLS playlist detected
|
|
else if (Keys.MIME_TYPES_PLS.contains(contentType)) {
|
|
val lines: List<String> = NetworkHelper.downloadPlaylist(query)
|
|
stationList.addAll(readPlsPlaylistContent(lines))
|
|
}
|
|
// CASE: stream address detected
|
|
else if (Keys.MIME_TYPES_MPEG.contains(contentType) or
|
|
Keys.MIME_TYPES_OGG.contains(contentType) or
|
|
Keys.MIME_TYPES_AAC.contains(contentType) or
|
|
Keys.MIME_TYPES_HLS.contains(contentType)) {
|
|
// process Icecast stream and extract metadata
|
|
directInputCheck?.processIcecastStream(query, stationList)
|
|
// create station and add to collection
|
|
val station = Station(name = query, streamUris = mutableListOf(query), streamContent = contentType, modificationDate = GregorianCalendar.getInstance().time)
|
|
if (lastCheckedAddress != query) {
|
|
stationList.add(station)
|
|
}
|
|
}
|
|
return stationList
|
|
}
|
|
|
|
|
|
/* Creates station from URI pointing to a local file */
|
|
fun createStationListFromContentUri(context: Context, contentUri: Uri): List<Station> {
|
|
val stationList: MutableList<Station> = mutableListOf()
|
|
val fileType: String = FileHelper.getContentType(context, contentUri)
|
|
// CASE: M3U playlist detected
|
|
if (Keys.MIME_TYPES_M3U.contains(fileType)) {
|
|
val playlist = FileHelper.readTextFileFromContentUri(context, contentUri)
|
|
stationList.addAll(readM3uPlaylistContent(playlist))
|
|
}
|
|
// CASE: PLS playlist detected
|
|
else if (Keys.MIME_TYPES_PLS.contains(fileType)) {
|
|
val playlist = FileHelper.readTextFileFromContentUri(context, contentUri)
|
|
stationList.addAll(readPlsPlaylistContent(playlist))
|
|
}
|
|
return stationList
|
|
}
|
|
|
|
|
|
/* Reads a m3u playlist and returns a list of stations */
|
|
private fun readM3uPlaylistContent(playlist: List<String>): List<Station> {
|
|
val stations: MutableList<Station> = mutableListOf()
|
|
var name = String()
|
|
var streamUri: String
|
|
var contentType: String
|
|
|
|
playlist.forEach { line ->
|
|
// get name of station
|
|
if (line.startsWith("#EXTINF:")) {
|
|
name = line.substringAfter(",").trim()
|
|
}
|
|
// get stream uri and check mime type
|
|
else if (line.isNotBlank() && !line.startsWith("#")) {
|
|
streamUri = line.trim()
|
|
// use the stream address as the name if no name is specified
|
|
if (name.isEmpty()) {
|
|
name = streamUri
|
|
}
|
|
contentType = NetworkHelper.detectContentType(streamUri).type.lowercase(Locale.getDefault())
|
|
// store station in list if mime type is supported
|
|
if (contentType != Keys.MIME_TYPE_UNSUPPORTED) {
|
|
val station = Station(name = name, streamUris = mutableListOf(streamUri), streamContent = contentType, modificationDate = GregorianCalendar.getInstance().time)
|
|
stations.add(station)
|
|
}
|
|
// reset name for the next station - useful if playlist does not provide name(s)
|
|
name = String()
|
|
}
|
|
}
|
|
return stations
|
|
}
|
|
|
|
|
|
/* Reads a pls playlist and returns a list of stations */
|
|
private fun readPlsPlaylistContent(playlist: List<String>): List<Station> {
|
|
val stations: MutableList<Station> = mutableListOf()
|
|
var name = String()
|
|
var streamUri: String
|
|
var contentType: String
|
|
|
|
playlist.forEachIndexed { index, line ->
|
|
// get stream uri and check mime type
|
|
if (line.startsWith("File")) {
|
|
streamUri = line.substringAfter("=").trim()
|
|
contentType = NetworkHelper.detectContentType(streamUri).type.lowercase(Locale.getDefault())
|
|
if (contentType != Keys.MIME_TYPE_UNSUPPORTED) {
|
|
// look for the matching station name
|
|
val number: String = line.substring(4 /* File */, line.indexOf("="))
|
|
val lineBeforeIndex: Int = index - 1
|
|
val lineAfterIndex: Int = index + 1
|
|
// first: check the line before
|
|
if (lineBeforeIndex >= 0) {
|
|
val lineBefore: String = playlist[lineBeforeIndex]
|
|
if (lineBefore.startsWith("Title$number")) {
|
|
name = lineBefore.substringAfter("=").trim()
|
|
}
|
|
}
|
|
// then: check the line after
|
|
if (name.isEmpty() && lineAfterIndex < playlist.size) {
|
|
val lineAfter: String = playlist[lineAfterIndex]
|
|
if (lineAfter.startsWith("Title$number")) {
|
|
name = lineAfter.substringAfter("=").trim()
|
|
}
|
|
}
|
|
// fallback: use stream uri as name
|
|
if (name.isEmpty()) {
|
|
name = streamUri
|
|
}
|
|
// add station
|
|
val station = Station(name = name, streamUris = mutableListOf(streamUri), streamContent = contentType, modificationDate = GregorianCalendar.getInstance().time)
|
|
stations.add(station)
|
|
}
|
|
}
|
|
}
|
|
return stations
|
|
}
|
|
|
|
|
|
/* Export collection of stations as M3U */
|
|
fun exportCollectionM3u(context: Context, collection: Collection) {
|
|
Log.v(TAG, "Exporting collection of stations as M3U")
|
|
// export collection as M3U - launch = fire & forget (no return value from save collection)
|
|
if (collection.stations.isNotEmpty()) {
|
|
CoroutineScope(IO).launch {
|
|
FileHelper.backupCollectionAsM3uSuspended(
|
|
context,
|
|
collection
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Create M3U string from collection of stations */
|
|
fun createM3uString(collection: Collection): String {
|
|
val m3uString = StringBuilder()
|
|
/* Extended M3U Format
|
|
#EXTM3U
|
|
#EXTINF:-1,My Cool Stream
|
|
http://www.site.com:8000/listen.pls
|
|
*/
|
|
|
|
// add opening tag
|
|
m3uString.append("#EXTM3U")
|
|
m3uString.append("\n")
|
|
|
|
// add name and stream address
|
|
collection.stations.forEach { station ->
|
|
m3uString.append("\n")
|
|
m3uString.append("#EXTINF:-1,")
|
|
m3uString.append(station.name)
|
|
m3uString.append("\n")
|
|
m3uString.append(station.getStreamUri())
|
|
m3uString.append("\n")
|
|
}
|
|
|
|
return m3uString.toString()
|
|
}
|
|
|
|
|
|
/* Export collection of stations as PLS */
|
|
fun exportCollectionPls(context: Context, collection: Collection) {
|
|
Log.v(TAG, "Exporting collection of stations as PLS")
|
|
// export collection as PLS - launch = fire & forget (no return value from save collection)
|
|
if (collection.stations.isNotEmpty()) {
|
|
CoroutineScope(IO).launch {
|
|
FileHelper.backupCollectionAsPlsSuspended(
|
|
context,
|
|
collection
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Create PLS string from collection of stations */
|
|
fun createPlsString(collection: Collection): String {
|
|
/* Extended PLS Format
|
|
[playlist]
|
|
|
|
Title1=My Cool Stream
|
|
File1=http://www.site.com:8000/listen.pls
|
|
Length1=-1
|
|
|
|
NumberOfEntries=1
|
|
Version=2
|
|
*/
|
|
|
|
val plsString = StringBuilder()
|
|
var counter = 1
|
|
|
|
// add opening tag
|
|
plsString.append("[playlist]")
|
|
plsString.append("\n")
|
|
|
|
// add name and stream address
|
|
collection.stations.forEach { station ->
|
|
plsString.append("\n")
|
|
plsString.append("Title$counter=")
|
|
plsString.append(station.name)
|
|
plsString.append("\n")
|
|
plsString.append("File$counter=")
|
|
plsString.append(station.getStreamUri())
|
|
plsString.append("\n")
|
|
plsString.append("Length$counter=-1")
|
|
plsString.append("\n")
|
|
counter++
|
|
}
|
|
|
|
// add ending tag
|
|
plsString.append("\n")
|
|
plsString.append("NumberOfEntries=${collection.stations.size}")
|
|
plsString.append("\n")
|
|
plsString.append("Version=2")
|
|
|
|
return plsString.toString()
|
|
}
|
|
|
|
|
|
/* Sends a broadcast containing the collection as parcel */
|
|
fun sendCollectionBroadcast(context: Context, modificationDate: Date) {
|
|
Log.v(TAG, "Broadcasting that collection has changed.")
|
|
val collectionChangedIntent = Intent()
|
|
collectionChangedIntent.action = Keys.ACTION_COLLECTION_CHANGED
|
|
collectionChangedIntent.putExtra(
|
|
Keys.EXTRA_COLLECTION_MODIFICATION_DATE,
|
|
modificationDate.time
|
|
)
|
|
LocalBroadcastManager.getInstance(context).sendBroadcast(collectionChangedIntent)
|
|
}
|
|
|
|
|
|
// /* Creates MediaMetadata for a single station - used in media session*/
|
|
// fun buildStationMediaMetadata(context: Context, station: Station, metadata: String): MediaMetadataCompat {
|
|
// return MediaMetadataCompat.Builder().apply {
|
|
// putString(MediaMetadataCompat.METADATA_KEY_ARTIST, station.name)
|
|
// putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadata)
|
|
// putString(MediaMetadataCompat.METADATA_KEY_ALBUM, context.getString(R.string.app_name))
|
|
// putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, station.getStreamUri())
|
|
// putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
|
|
// //putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, station.image)
|
|
// }.build()
|
|
// }
|
|
//
|
|
//
|
|
// /* Creates MediaItem for a station - used by collection provider */
|
|
// fun buildStationMediaMetaItem(context: Context, station: Station): MediaBrowserCompat.MediaItem {
|
|
// val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
|
|
// mediaDescriptionBuilder.setMediaId(station.uuid)
|
|
// mediaDescriptionBuilder.setTitle(station.name)
|
|
// mediaDescriptionBuilder.setIconBitmap(ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
|
|
// // mediaDescriptionBuilder.setIconUri(station.image.toUri())
|
|
// return MediaBrowserCompat.MediaItem(mediaDescriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
|
// }
|
|
//
|
|
//
|
|
// /* Creates description for a station - used in MediaSessionConnector */
|
|
// fun buildStationMediaDescription(context: Context, station: Station, metadata: String): MediaDescriptionCompat {
|
|
// val coverBitmap: Bitmap = ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN)
|
|
// val extras: Bundle = Bundle()
|
|
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, coverBitmap)
|
|
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, coverBitmap)
|
|
// return MediaDescriptionCompat.Builder().apply {
|
|
// setMediaId(station.uuid)
|
|
// setIconBitmap(coverBitmap)
|
|
// setIconUri(station.image.toUri())
|
|
// setTitle(metadata)
|
|
// setSubtitle(station.name)
|
|
// setExtras(extras)
|
|
// }.build()
|
|
// }
|
|
|
|
|
|
/* Creates a MediaItem with MediaMetadata for a single radio station - used to prepare player */
|
|
fun buildMediaItem(context: Context, station: Station): MediaItem {
|
|
// todo implement HLS MediaItems
|
|
// put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460
|
|
val requestMetadata = MediaItem.RequestMetadata.Builder().apply {
|
|
setMediaUri(station.getStreamUri().toUri())
|
|
}.build()
|
|
// build MediaMetadata
|
|
val mediaMetadata = MediaMetadata.Builder().apply {
|
|
setArtist(station.name)
|
|
//setTitle(station.name)
|
|
/* check for "file://" prevents a crash when an old backup was restored */
|
|
if (station.image.isNotEmpty() && station.image.startsWith("file://")) {
|
|
//setArtworkUri(station.image.toUri())
|
|
setArtworkData(station.image.toUri().toFile().readBytes(), MediaMetadata.PICTURE_TYPE_FRONT_COVER)
|
|
} else {
|
|
//setArtworkUri(Uri.parse(Keys.LOCATION_RESOURCES + R.raw.ic_default_station_image))
|
|
setArtworkData(ImageHelper.getStationImageAsByteArray(context), MediaMetadata.PICTURE_TYPE_FRONT_COVER)
|
|
}
|
|
setIsBrowsable(false)
|
|
setIsPlayable(true)
|
|
}.build()
|
|
// build MediaItem and return it
|
|
return MediaItem.Builder().apply {
|
|
setMediaId(station.uuid)
|
|
setRequestMetadata(requestMetadata)
|
|
setMediaMetadata(mediaMetadata)
|
|
//setMimeType(station.getMediaType())
|
|
setUri(station.getStreamUri().toUri())
|
|
}.build()
|
|
}
|
|
|
|
|
|
/* Sorts radio stations */
|
|
fun sortCollection(collection: Collection): Collection {
|
|
val favoriteStations = collection.stations.filter { it.starred }
|
|
val otherStations = collection.stations.filter { !it.starred }
|
|
collection.stations = (favoriteStations + otherStations) as MutableList<Station>
|
|
return collection
|
|
}
|
|
|
|
|
|
/* Get favicon address */
|
|
private fun getFaviconAddress(urlString: String): String {
|
|
var faviconAddress = String()
|
|
try {
|
|
var host: String = URL(urlString).host
|
|
if (!host.startsWith("www")) {
|
|
val index = host.indexOf(".")
|
|
host = "www" + host.substring(index)
|
|
}
|
|
faviconAddress = "http://$host/favicon.ico"
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "Unable to get base URL from $urlString.\n$e ")
|
|
}
|
|
return faviconAddress
|
|
}
|
|
|
|
}
|