Migrate AutoMediaBrowser

This commit is contained in:
tzugen 2022-04-04 21:18:07 +02:00
parent 2f7f47783a
commit dd65a12b53
No known key found for this signature in database
GPG Key ID: 61E9C34BC10EC930
13 changed files with 465 additions and 990 deletions

View File

@ -21,6 +21,7 @@ multidex = "2.0.1"
room = "2.4.0"
kotlin = "1.6.10"
kotlinxCoroutines = "1.6.0-native-mt"
kotlinxGuava = "1.6.0"
viewModelKtx = "2.3.0"
retrofit = "2.9.0"
@ -74,6 +75,7 @@ media3session = { module = "androidx.media3:media3-session", version.r
kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinxGuava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxGuava"}
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
gsonConverter = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
jacksonConverter = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofit" }

View File

@ -112,6 +112,7 @@ dependencies {
implementation libs.kotlinStdlib
implementation libs.kotlinxCoroutines
implementation libs.kotlinxGuava
implementation libs.koinAndroid
implementation libs.okhttpLogging
implementation libs.fastScroll

View File

@ -30,7 +30,7 @@ class CachedDataSource(
) : BaseDataSource(false) {
class Factory(
var upstreamDataSourceFactory: DataSource.Factory
private var upstreamDataSourceFactory: DataSource.Factory
) : DataSource.Factory {
private var eventListener: EventListener? = null
@ -112,16 +112,16 @@ class CachedDataSource(
}
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
if (cachePath != null) {
return if (cachePath != null) {
try {
return readInternal(buffer, offset, length)
readInternal(buffer, offset, length)
} catch (e: IOException) {
throw HttpDataSource.HttpDataSourceException.createForIOException(
e, Util.castNonNull(dataSpec), HttpDataSource.HttpDataSourceException.TYPE_READ
)
}
} else {
return upstreamDataSource.read(buffer, offset, length)
upstreamDataSource.read(buffer, offset, length)
}
}

View File

@ -64,20 +64,6 @@ class LegacyPlaylistManager : KoinComponent {
currentPlaying = mediaItemCache[item?.mediaMetadata?.mediaUri.toString()]
}
@Synchronized
fun clearIncomplete() {
val iterator = _playlist.iterator()
var changedPlaylist = false
while (iterator.hasNext()) {
val downloadFile = iterator.next()
if (!downloadFile.isCompleteFileAvailable) {
iterator.remove()
changedPlaylist = true
}
}
if (changedPlaylist) playlistUpdateRevision++
}
@Synchronized
fun clearPlaylist() {
_playlist.clear()

View File

@ -1,247 +0,0 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moire.ultrasonic.playback
import android.content.res.AssetManager
import android.net.Uri
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS
import com.google.common.collect.ImmutableList
import org.json.JSONObject
/**
* A sample media catalog that represents media items as a tree.
*
* It fetched the data from {@code catalog.json}. The root's children are folders containing media
* items from the same album/artist/genre.
*
* Each app should have their own way of representing the tree. MediaItemTree is used for
* demonstration purpose only.
*/
object MediaItemTree {
private var treeNodes: MutableMap<String, MediaItemNode> = mutableMapOf()
private var titleMap: MutableMap<String, MediaItemNode> = mutableMapOf()
private var isInitialized = false
private const val ROOT_ID = "[rootID]"
private const val ALBUM_ID = "[albumID]"
private const val GENRE_ID = "[genreID]"
private const val ARTIST_ID = "[artistID]"
private const val ALBUM_PREFIX = "[album]"
private const val GENRE_PREFIX = "[genre]"
private const val ARTIST_PREFIX = "[artist]"
private const val ITEM_PREFIX = "[item]"
private class MediaItemNode(val item: MediaItem) {
private val children: MutableList<MediaItem> = ArrayList()
fun addChild(childID: String) {
this.children.add(treeNodes[childID]!!.item)
}
fun getChildren(): List<MediaItem> {
return ImmutableList.copyOf(children)
}
}
private fun buildMediaItem(
title: String,
mediaId: String,
isPlayable: Boolean,
@MediaMetadata.FolderType folderType: Int,
album: String? = null,
artist: String? = null,
genre: String? = null,
sourceUri: Uri? = null,
imageUri: Uri? = null,
): MediaItem {
// TODO(b/194280027): add artwork
val metadata =
MediaMetadata.Builder()
.setAlbumTitle(album)
.setTitle(title)
.setArtist(artist)
.setGenre(genre)
.setFolderType(folderType)
.setIsPlayable(isPlayable)
.setArtworkUri(imageUri)
.build()
return MediaItem.Builder()
.setMediaId(mediaId)
.setMediaMetadata(metadata)
.setUri(sourceUri)
.build()
}
fun initialize(assets: AssetManager) {
if (isInitialized) return
isInitialized = true
// create root and folders for album/artist/genre.
treeNodes[ROOT_ID] =
MediaItemNode(
buildMediaItem(
title = "Root Folder",
mediaId = ROOT_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ALBUM_ID] =
MediaItemNode(
buildMediaItem(
title = "Album Folder",
mediaId = ALBUM_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ARTIST_ID] =
MediaItemNode(
buildMediaItem(
title = "Artist Folder",
mediaId = ARTIST_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[GENRE_ID] =
MediaItemNode(
buildMediaItem(
title = "Genre Folder",
mediaId = GENRE_ID,
isPlayable = false,
folderType = FOLDER_TYPE_MIXED
)
)
treeNodes[ROOT_ID]!!.addChild(ALBUM_ID)
treeNodes[ROOT_ID]!!.addChild(ARTIST_ID)
treeNodes[ROOT_ID]!!.addChild(GENRE_ID)
// Here, parse the json file in asset for media list.
// We use a file in asset for demo purpose
// val jsonObject = JSONObject(loadJSONFromAsset(assets))
// val mediaList = jsonObject.getJSONArray("media")
//
// // create subfolder with same artist, album, etc.
// for (i in 0 until mediaList.length()) {
// addNodeToTree(mediaList.getJSONObject(i))
// }
}
private fun addNodeToTree(mediaObject: JSONObject) {
val id = mediaObject.getString("id")
val album = mediaObject.getString("album")
val title = mediaObject.getString("title")
val artist = mediaObject.getString("artist")
val genre = mediaObject.getString("genre")
val sourceUri = Uri.parse(mediaObject.getString("source"))
val imageUri = Uri.parse(mediaObject.getString("image"))
// key of such items in tree
val idInTree = ITEM_PREFIX + id
val albumFolderIdInTree = ALBUM_PREFIX + album
val artistFolderIdInTree = ARTIST_PREFIX + artist
val genreFolderIdInTree = GENRE_PREFIX + genre
treeNodes[idInTree] =
MediaItemNode(
buildMediaItem(
title = title,
mediaId = idInTree,
isPlayable = true,
album = album,
artist = artist,
genre = genre,
sourceUri = sourceUri,
imageUri = imageUri,
folderType = FOLDER_TYPE_NONE
)
)
titleMap[title.lowercase()] = treeNodes[idInTree]!!
if (!treeNodes.containsKey(albumFolderIdInTree)) {
treeNodes[albumFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = album,
mediaId = albumFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[ALBUM_ID]!!.addChild(albumFolderIdInTree)
}
treeNodes[albumFolderIdInTree]!!.addChild(idInTree)
// add into artist folder
if (!treeNodes.containsKey(artistFolderIdInTree)) {
treeNodes[artistFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = artist,
mediaId = artistFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[ARTIST_ID]!!.addChild(artistFolderIdInTree)
}
treeNodes[artistFolderIdInTree]!!.addChild(idInTree)
// add into genre folder
if (!treeNodes.containsKey(genreFolderIdInTree)) {
treeNodes[genreFolderIdInTree] =
MediaItemNode(
buildMediaItem(
title = genre,
mediaId = genreFolderIdInTree,
isPlayable = true,
folderType = FOLDER_TYPE_PLAYLISTS
)
)
treeNodes[GENRE_ID]!!.addChild(genreFolderIdInTree)
}
treeNodes[genreFolderIdInTree]!!.addChild(idInTree)
}
fun getItem(id: String): MediaItem? {
return treeNodes[id]?.item
}
fun getRootItem(): MediaItem {
return treeNodes[ROOT_ID]!!.item
}
fun getChildren(id: String): List<MediaItem>? {
return treeNodes[id]?.getChildren()
}
fun getRandomItem(): MediaItem {
var curRoot = getRootItem()
while (curRoot.mediaMetadata.folderType != FOLDER_TYPE_NONE) {
val children = getChildren(curRoot.mediaId)!!
curRoot = children.random()
}
return curRoot
}
fun getItemFromTitle(title: String): MediaItem? {
return titleMap[title]?.item
}
}

View File

@ -50,7 +50,6 @@ internal class MediaNotificationProvider(context: Context) :
context,
NOTIFICATION_CHANNEL_ID
)
// TODO(b/193193926): Filter actions depending on the player's available commands.
// Skip to previous action.
builder.addAction(
actionFactory.createMediaAction(

View File

@ -18,8 +18,6 @@ package org.moire.ultrasonic.playback
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.C.CONTENT_TYPE_MUSIC
@ -29,13 +27,8 @@ import androidx.media3.datasource.DataSource
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionResult
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.moire.ultrasonic.activity.NavigationActivity
@ -48,94 +41,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var dataSourceFactory: DataSource.Factory
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
companion object {
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri"
}
private inner class CustomMediaLibrarySessionCallback :
MediaLibrarySession.MediaLibrarySessionCallback {
override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(
LibraryResult.ofItem(
MediaItemTree.getRootItem(),
params
)
)
}
override fun onGetItem(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: MediaSession.ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}
private fun setMediaItemFromSearchQuery(query: String) {
// Only accept query with pattern "play [Title]" or "[Title]"
// Where [Title]: must be exactly matched
// If no media with exact name found, play a random media instead
val mediaTitle =
if (query.startsWith("play ", ignoreCase = true)) {
query.drop(5)
} else {
query
}
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
player.setMediaItem(item)
}
override fun onSetMediaUri(
session: MediaSession,
controller: MediaSession.ControllerInfo,
uri: Uri,
extras: Bundle
): Int {
if (uri.toString().startsWith(SEARCH_QUERY_PREFIX) ||
uri.toString().startsWith(SEARCH_QUERY_PREFIX_COMPAT)
) {
val searchQuery =
uri.getQueryParameter("query")
?: return SessionResult.RESULT_ERROR_NOT_SUPPORTED
setMediaItemFromSearchQuery(searchQuery)
return SessionResult.RESULT_SUCCESS
} else {
return SessionResult.RESULT_ERROR_NOT_SUPPORTED
}
}
}
private lateinit var librarySessionCallback: MediaLibrarySession.MediaLibrarySessionCallback
/*
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
@ -148,11 +54,9 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
mediaItem: MediaItem
): MediaItem {
// Again, set the Uri, so that it will get a LocalConfiguration
val item = mediaItem.buildUpon()
return mediaItem.buildUpon()
.setUri(mediaItem.mediaMetadata.mediaUri)
.build()
return item
}
}
@ -202,9 +106,10 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
// Enable audio offload
player.experimentalSetOffloadSchedulingEnabled(true)
MediaItemTree.initialize(assets)
// Create browser interface
librarySessionCallback = AutoMediaBrowserCallback(player)
// THIS Will need to use the AutoCalls
// This will need to use the AutoCalls
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setMediaItemFiller(CustomMediaItemFiller())
.setSessionActivity(getPendingIntentForContent())

View File

@ -64,7 +64,7 @@ class Downloader(
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
var downloadChecker = object : Runnable {
private var downloadChecker = object : Runnable {
override fun run() {
try {
Timber.w("Checking Downloads")
@ -399,11 +399,11 @@ class Downloader(
val fileLength = Storage.getFromPath(downloadFile.partialFile)?.length ?: 0
needsDownloading = (
downloadFile.desiredBitRate == 0 ||
duration == null ||
duration == 0 ||
fileLength == 0L
)
downloadFile.desiredBitRate == 0 ||
duration == null ||
duration == 0 ||
fileLength == 0L
)
if (needsDownloading) {
// Attempt partial HTTP GET, appending to the file if it exists.

View File

@ -8,6 +8,7 @@ package org.moire.ultrasonic.service
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@ -145,7 +146,7 @@ class JukeboxMediaPlayer(private val downloader: Downloader) {
private fun disableJukeboxOnError(x: Throwable, resourceId: Int) {
Timber.w(x.toString())
val context = applicationContext()
Handler().post { toast(context, resourceId, false) }
Handler(Looper.getMainLooper()).post { toast(context, resourceId, false) }
mediaPlayerControllerLazy.value.isJukeboxEnabled = false
}

View File

@ -66,12 +66,11 @@ class MediaPlayerLifecycleSupport : KoinComponent {
if (!created) return
// TODO
// playbackStateSerializer.serializeNow(
// downloader.getPlaylist(),
// downloader.currentPlayingIndex,
// mediaPlayerController.playerPosition
// )
playbackStateSerializer.serializeNow(
mediaPlayerController.playList,
mediaPlayerController.currentMediaItemIndex,
mediaPlayerController.playerPosition
)
mediaPlayerController.clear(false)
mediaButtonEventSubscription?.dispose()
@ -110,10 +109,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
val autoStart =
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
// We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start
onCreate(autoStart) {
@ -150,10 +149,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
return
val autoStart = action == Constants.CMD_PLAY ||
action == Constants.CMD_RESUME_OR_PLAY ||
action == Constants.CMD_TOGGLEPAUSE ||
action == Constants.CMD_PREVIOUS ||
action == Constants.CMD_NEXT
action == Constants.CMD_RESUME_OR_PLAY ||
action == Constants.CMD_TOGGLEPAUSE ||
action == Constants.CMD_PREVIOUS ||
action == Constants.CMD_NEXT
// We can receive intents when everything is stopped, so we need to start
onCreate(autoStart) {

View File

@ -53,7 +53,7 @@ class PlaybackStateSerializer : KoinComponent {
}
}
private fun serializeNow(
fun serializeNow(
songs: Iterable<DownloadFile>,
currentPlayingIndex: Int,
currentPlayingPosition: Int

View File

@ -9,10 +9,8 @@ package org.moire.ultrasonic.util
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
@ -28,18 +26,20 @@ import android.net.Uri
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.WifiLock
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.support.v4.media.MediaDescriptionCompat
import android.text.TextUtils
import android.util.TypedValue
import android.view.Gravity
import android.view.KeyEvent
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.annotation.AnyRes
import androidx.media.utils.MediaConstants
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp.Companion.applicationContext
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.domain.Track
import timber.log.Timber
import java.io.Closeable
import java.io.UnsupportedEncodingException
import java.security.MessageDigest
@ -49,15 +49,6 @@ import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp.Companion.applicationContext
import org.moire.ultrasonic.domain.Bookmark
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.SearchResult
import org.moire.ultrasonic.domain.Track
import org.moire.ultrasonic.service.DownloadFile
import timber.log.Timber
private const val LINE_LENGTH = 60
private const val DEGRADE_PRECISION_AFTER = 10
@ -77,11 +68,6 @@ object Util {
private var MEGA_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
private var KILO_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
private var BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
private const val EVENT_META_CHANGED = "org.moire.ultrasonic.EVENT_META_CHANGED"
private const val EVENT_PLAYSTATE_CHANGED = "org.moire.ultrasonic.EVENT_PLAYSTATE_CHANGED"
private const val CM_AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged"
private const val CM_AVRCP_PLAYBACK_COMPLETE = "com.android.music.playbackcomplete"
private const val CM_AVRCP_METADATA_CHANGED = "com.android.music.metachanged"
// Used by hexEncode()
private val HEX_DIGITS =
@ -448,150 +434,6 @@ object Util {
return musicDirectory
}
/**
* Broadcasts the given song info as the new song being played.
*/
fun broadcastNewTrackInfo(context: Context, song: Track?) {
val intent = Intent(EVENT_META_CHANGED)
if (song != null) {
intent.putExtra("title", song.title)
intent.putExtra("artist", song.artist)
intent.putExtra("album", song.album)
val albumArtFile = FileUtil.getAlbumArtFile(song)
intent.putExtra("coverart", albumArtFile)
} else {
intent.putExtra("title", "")
intent.putExtra("artist", "")
intent.putExtra("album", "")
intent.putExtra("coverart", "")
}
context.sendBroadcast(intent)
}
fun broadcastA2dpMetaDataChange(
context: Context,
playerPosition: Int,
currentPlaying: DownloadFile?,
listSize: Int,
id: Int
) {
if (!Settings.shouldSendBluetoothNotifications) return
var song: Track? = null
val avrcpIntent = Intent(CM_AVRCP_METADATA_CHANGED)
if (currentPlaying != null) song = currentPlaying.track
fillIntent(avrcpIntent, song, playerPosition, id, listSize)
context.sendBroadcast(avrcpIntent)
}
@Suppress("LongParameterList")
fun broadcastA2dpPlayStatusChange(
context: Context,
state: PlayerState?,
newSong: Track?,
listSize: Int,
id: Int,
playerPosition: Int
) {
if (!Settings.shouldSendBluetoothNotifications) return
if (newSong != null) {
val avrcpIntent = Intent(
if (state == PlayerState.COMPLETED) CM_AVRCP_PLAYBACK_COMPLETE
else CM_AVRCP_PLAYSTATE_CHANGED
)
fillIntent(avrcpIntent, newSong, playerPosition, id, listSize)
if (state != PlayerState.COMPLETED) {
when (state) {
PlayerState.STARTED -> avrcpIntent.putExtra("playing", true)
PlayerState.STOPPED,
PlayerState.PAUSED -> avrcpIntent.putExtra("playing", false)
else -> return // No need to broadcast.
}
}
context.sendBroadcast(avrcpIntent)
}
}
private fun fillIntent(
intent: Intent,
song: Track?,
playerPosition: Int,
id: Int,
listSize: Int
) {
if (song == null) {
intent.putExtra("track", "")
intent.putExtra("track_name", "")
intent.putExtra("artist", "")
intent.putExtra("artist_name", "")
intent.putExtra("album", "")
intent.putExtra("album_name", "")
intent.putExtra("album_artist", "")
intent.putExtra("album_artist_name", "")
if (Settings.shouldSendBluetoothAlbumArt) {
intent.putExtra("coverart", null as Parcelable?)
intent.putExtra("cover", null as Parcelable?)
}
intent.putExtra("ListSize", 0.toLong())
intent.putExtra("id", 0.toLong())
intent.putExtra("duration", 0.toLong())
intent.putExtra("position", 0.toLong())
} else {
val title = song.title
val artist = song.artist
val album = song.album
val duration = song.duration
intent.putExtra("track", title)
intent.putExtra("track_name", title)
intent.putExtra("artist", artist)
intent.putExtra("artist_name", artist)
intent.putExtra("album", album)
intent.putExtra("album_name", album)
intent.putExtra("album_artist", artist)
intent.putExtra("album_artist_name", artist)
if (Settings.shouldSendBluetoothAlbumArt) {
val albumArtFile = FileUtil.getAlbumArtFile(song)
intent.putExtra("coverart", albumArtFile)
intent.putExtra("cover", albumArtFile)
}
intent.putExtra("position", playerPosition.toLong())
intent.putExtra("id", id.toLong())
intent.putExtra("ListSize", listSize.toLong())
if (duration != null) {
intent.putExtra("duration", duration.toLong())
}
}
}
/**
*
* Broadcasts the given player state as the one being set.
*/
fun broadcastPlaybackStatusChange(context: Context, state: PlayerState?) {
val intent = Intent(EVENT_PLAYSTATE_CHANGED)
when (state) {
PlayerState.STARTED -> intent.putExtra("state", "play")
PlayerState.STOPPED -> intent.putExtra("state", "stop")
PlayerState.PAUSED -> intent.putExtra("state", "pause")
PlayerState.COMPLETED -> intent.putExtra("state", "complete")
else -> return // No need to broadcast.
}
context.sendBroadcast(intent)
}
@JvmStatic
@Suppress("MagicNumber")
fun getNotificationImageSize(context: Context): Int {
@ -667,7 +509,7 @@ object Util {
val hours = TimeUnit.MILLISECONDS.toHours(millis)
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(hours)
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) -
TimeUnit.MINUTES.toSeconds(hours * MINUTES_IN_HOUR + minutes)
TimeUnit.MINUTES.toSeconds(hours * MINUTES_IN_HOUR + minutes)
return when {
hours >= DEGRADE_PRECISION_AFTER -> {
@ -761,9 +603,9 @@ object Util {
fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri {
return Uri.parse(
ContentResolver.SCHEME_ANDROID_RESOURCE +
"://" + context.resources.getResourcePackageName(drawableId) +
'/' + context.resources.getResourceTypeName(drawableId) +
'/' + context.resources.getResourceEntryName(drawableId)
"://" + context.resources.getResourcePackageName(drawableId) +
'/' + context.resources.getResourceTypeName(drawableId) +
'/' + context.resources.getResourceEntryName(drawableId)
)
}
@ -776,39 +618,6 @@ object Util {
var fileFormat: String?,
)
fun getMediaDescriptionForEntry(
song: Track,
mediaId: String? = null,
groupNameId: Int? = null
): MediaDescriptionCompat {
val descriptionBuilder = MediaDescriptionCompat.Builder()
val desc = readableEntryDescription(song)
val title: String
if (groupNameId != null)
descriptionBuilder.setExtras(
Bundle().apply {
putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
appContext().getString(groupNameId)
)
}
)
if (desc.trackNumber.isNotEmpty()) {
title = "${desc.trackNumber} - ${desc.title}"
} else {
title = desc.title
}
descriptionBuilder.setTitle(title)
descriptionBuilder.setSubtitle(desc.artist)
descriptionBuilder.setMediaId(mediaId)
return descriptionBuilder.build()
}
@Suppress("ComplexMethod", "LongMethod")
fun readableEntryDescription(song: Track): ReadableEntryDescription {
val artist = StringBuilder(LINE_LENGTH)
@ -834,8 +643,8 @@ object Util {
if (artistName != null) {
if (Settings.shouldDisplayBitrateWithArtist && (
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
)
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
)
) {
artist.append(artistName).append(" (").append(
String.format(
@ -880,18 +689,6 @@ object Util {
)
}
fun getPendingIntentForMediaAction(
context: Context,
keycode: Int,
requestCode: Int
): PendingIntent {
val intent = Intent(Constants.CMD_PROCESS_KEYCODE)
val flags = PendingIntent.FLAG_UPDATE_CURRENT
intent.setPackage(context.packageName)
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode))
return PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
fun getConnectivityManager(): ConnectivityManager {
val context = appContext()
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager