mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-18 18:17:43 +03:00
Migrate AutoMediaBrowser
This commit is contained in:
parent
2f7f47783a
commit
dd65a12b53
@ -21,6 +21,7 @@ multidex = "2.0.1"
|
|||||||
room = "2.4.0"
|
room = "2.4.0"
|
||||||
kotlin = "1.6.10"
|
kotlin = "1.6.10"
|
||||||
kotlinxCoroutines = "1.6.0-native-mt"
|
kotlinxCoroutines = "1.6.0-native-mt"
|
||||||
|
kotlinxGuava = "1.6.0"
|
||||||
viewModelKtx = "2.3.0"
|
viewModelKtx = "2.3.0"
|
||||||
|
|
||||||
retrofit = "2.9.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" }
|
kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
|
||||||
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
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" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
gsonConverter = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
gsonConverter = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||||
jacksonConverter = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofit" }
|
jacksonConverter = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofit" }
|
||||||
|
@ -112,6 +112,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation libs.kotlinStdlib
|
implementation libs.kotlinStdlib
|
||||||
implementation libs.kotlinxCoroutines
|
implementation libs.kotlinxCoroutines
|
||||||
|
implementation libs.kotlinxGuava
|
||||||
implementation libs.koinAndroid
|
implementation libs.koinAndroid
|
||||||
implementation libs.okhttpLogging
|
implementation libs.okhttpLogging
|
||||||
implementation libs.fastScroll
|
implementation libs.fastScroll
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -30,7 +30,7 @@ class CachedDataSource(
|
|||||||
) : BaseDataSource(false) {
|
) : BaseDataSource(false) {
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
var upstreamDataSourceFactory: DataSource.Factory
|
private var upstreamDataSourceFactory: DataSource.Factory
|
||||||
) : DataSource.Factory {
|
) : DataSource.Factory {
|
||||||
|
|
||||||
private var eventListener: EventListener? = null
|
private var eventListener: EventListener? = null
|
||||||
@ -112,16 +112,16 @@ class CachedDataSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||||
if (cachePath != null) {
|
return if (cachePath != null) {
|
||||||
try {
|
try {
|
||||||
return readInternal(buffer, offset, length)
|
readInternal(buffer, offset, length)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
throw HttpDataSource.HttpDataSourceException.createForIOException(
|
throw HttpDataSource.HttpDataSourceException.createForIOException(
|
||||||
e, Util.castNonNull(dataSpec), HttpDataSource.HttpDataSourceException.TYPE_READ
|
e, Util.castNonNull(dataSpec), HttpDataSource.HttpDataSourceException.TYPE_READ
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return upstreamDataSource.read(buffer, offset, length)
|
upstreamDataSource.read(buffer, offset, length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,20 +64,6 @@ class LegacyPlaylistManager : KoinComponent {
|
|||||||
currentPlaying = mediaItemCache[item?.mediaMetadata?.mediaUri.toString()]
|
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
|
@Synchronized
|
||||||
fun clearPlaylist() {
|
fun clearPlaylist() {
|
||||||
_playlist.clear()
|
_playlist.clear()
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -50,7 +50,6 @@ internal class MediaNotificationProvider(context: Context) :
|
|||||||
context,
|
context,
|
||||||
NOTIFICATION_CHANNEL_ID
|
NOTIFICATION_CHANNEL_ID
|
||||||
)
|
)
|
||||||
// TODO(b/193193926): Filter actions depending on the player's available commands.
|
|
||||||
// Skip to previous action.
|
// Skip to previous action.
|
||||||
builder.addAction(
|
builder.addAction(
|
||||||
actionFactory.createMediaAction(
|
actionFactory.createMediaAction(
|
||||||
|
@ -18,8 +18,6 @@ package org.moire.ultrasonic.playback
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.C.CONTENT_TYPE_MUSIC
|
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.DefaultRenderersFactory
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.session.LibraryResult
|
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
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.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import org.moire.ultrasonic.activity.NavigationActivity
|
import org.moire.ultrasonic.activity.NavigationActivity
|
||||||
@ -48,94 +41,7 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
|||||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||||
private lateinit var dataSourceFactory: DataSource.Factory
|
private lateinit var dataSourceFactory: DataSource.Factory
|
||||||
|
|
||||||
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
private lateinit var librarySessionCallback: MediaLibrarySession.MediaLibrarySessionCallback
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
|
* For some reason the LocalConfiguration of MediaItem are stripped somewhere in ExoPlayer,
|
||||||
@ -148,11 +54,9 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
|||||||
mediaItem: MediaItem
|
mediaItem: MediaItem
|
||||||
): MediaItem {
|
): MediaItem {
|
||||||
// Again, set the Uri, so that it will get a LocalConfiguration
|
// Again, set the Uri, so that it will get a LocalConfiguration
|
||||||
val item = mediaItem.buildUpon()
|
return mediaItem.buildUpon()
|
||||||
.setUri(mediaItem.mediaMetadata.mediaUri)
|
.setUri(mediaItem.mediaMetadata.mediaUri)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return item
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,9 +106,10 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
|
|||||||
// Enable audio offload
|
// Enable audio offload
|
||||||
player.experimentalSetOffloadSchedulingEnabled(true)
|
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)
|
mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||||
.setMediaItemFiller(CustomMediaItemFiller())
|
.setMediaItemFiller(CustomMediaItemFiller())
|
||||||
.setSessionActivity(getPendingIntentForContent())
|
.setSessionActivity(getPendingIntentForContent())
|
||||||
|
@ -64,7 +64,7 @@ class Downloader(
|
|||||||
|
|
||||||
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
|
private val rxBusSubscription: CompositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
var downloadChecker = object : Runnable {
|
private var downloadChecker = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
try {
|
try {
|
||||||
Timber.w("Checking Downloads")
|
Timber.w("Checking Downloads")
|
||||||
@ -399,11 +399,11 @@ class Downloader(
|
|||||||
val fileLength = Storage.getFromPath(downloadFile.partialFile)?.length ?: 0
|
val fileLength = Storage.getFromPath(downloadFile.partialFile)?.length ?: 0
|
||||||
|
|
||||||
needsDownloading = (
|
needsDownloading = (
|
||||||
downloadFile.desiredBitRate == 0 ||
|
downloadFile.desiredBitRate == 0 ||
|
||||||
duration == null ||
|
duration == null ||
|
||||||
duration == 0 ||
|
duration == 0 ||
|
||||||
fileLength == 0L
|
fileLength == 0L
|
||||||
)
|
)
|
||||||
|
|
||||||
if (needsDownloading) {
|
if (needsDownloading) {
|
||||||
// Attempt partial HTTP GET, appending to the file if it exists.
|
// Attempt partial HTTP GET, appending to the file if it exists.
|
||||||
|
@ -8,6 +8,7 @@ package org.moire.ultrasonic.service
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@ -145,7 +146,7 @@ class JukeboxMediaPlayer(private val downloader: Downloader) {
|
|||||||
private fun disableJukeboxOnError(x: Throwable, resourceId: Int) {
|
private fun disableJukeboxOnError(x: Throwable, resourceId: Int) {
|
||||||
Timber.w(x.toString())
|
Timber.w(x.toString())
|
||||||
val context = applicationContext()
|
val context = applicationContext()
|
||||||
Handler().post { toast(context, resourceId, false) }
|
Handler(Looper.getMainLooper()).post { toast(context, resourceId, false) }
|
||||||
mediaPlayerControllerLazy.value.isJukeboxEnabled = false
|
mediaPlayerControllerLazy.value.isJukeboxEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,12 +66,11 @@ class MediaPlayerLifecycleSupport : KoinComponent {
|
|||||||
|
|
||||||
if (!created) return
|
if (!created) return
|
||||||
|
|
||||||
// TODO
|
playbackStateSerializer.serializeNow(
|
||||||
// playbackStateSerializer.serializeNow(
|
mediaPlayerController.playList,
|
||||||
// downloader.getPlaylist(),
|
mediaPlayerController.currentMediaItemIndex,
|
||||||
// downloader.currentPlayingIndex,
|
mediaPlayerController.playerPosition
|
||||||
// mediaPlayerController.playerPosition
|
)
|
||||||
// )
|
|
||||||
|
|
||||||
mediaPlayerController.clear(false)
|
mediaPlayerController.clear(false)
|
||||||
mediaButtonEventSubscription?.dispose()
|
mediaButtonEventSubscription?.dispose()
|
||||||
@ -110,10 +109,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
|
|||||||
|
|
||||||
val autoStart =
|
val autoStart =
|
||||||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
|
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
|
||||||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
|
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY ||
|
||||||
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
|
keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
|
||||||
keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
|
keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS ||
|
||||||
keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
|
keyCode == KeyEvent.KEYCODE_MEDIA_NEXT
|
||||||
|
|
||||||
// We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start
|
// We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start
|
||||||
onCreate(autoStart) {
|
onCreate(autoStart) {
|
||||||
@ -150,10 +149,10 @@ class MediaPlayerLifecycleSupport : KoinComponent {
|
|||||||
return
|
return
|
||||||
|
|
||||||
val autoStart = action == Constants.CMD_PLAY ||
|
val autoStart = action == Constants.CMD_PLAY ||
|
||||||
action == Constants.CMD_RESUME_OR_PLAY ||
|
action == Constants.CMD_RESUME_OR_PLAY ||
|
||||||
action == Constants.CMD_TOGGLEPAUSE ||
|
action == Constants.CMD_TOGGLEPAUSE ||
|
||||||
action == Constants.CMD_PREVIOUS ||
|
action == Constants.CMD_PREVIOUS ||
|
||||||
action == Constants.CMD_NEXT
|
action == Constants.CMD_NEXT
|
||||||
|
|
||||||
// We can receive intents when everything is stopped, so we need to start
|
// We can receive intents when everything is stopped, so we need to start
|
||||||
onCreate(autoStart) {
|
onCreate(autoStart) {
|
||||||
|
@ -53,7 +53,7 @@ class PlaybackStateSerializer : KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun serializeNow(
|
fun serializeNow(
|
||||||
songs: Iterable<DownloadFile>,
|
songs: Iterable<DownloadFile>,
|
||||||
currentPlayingIndex: Int,
|
currentPlayingIndex: Int,
|
||||||
currentPlayingPosition: Int
|
currentPlayingPosition: Int
|
||||||
|
@ -9,10 +9,8 @@ package org.moire.ultrasonic.util
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
@ -28,18 +26,20 @@ import android.net.Uri
|
|||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.net.wifi.WifiManager.WifiLock
|
import android.net.wifi.WifiManager.WifiLock
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
|
||||||
import android.support.v4.media.MediaDescriptionCompat
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.AnyRes
|
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.Closeable
|
||||||
import java.io.UnsupportedEncodingException
|
import java.io.UnsupportedEncodingException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
@ -49,15 +49,6 @@ import java.util.concurrent.TimeUnit
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
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 LINE_LENGTH = 60
|
||||||
private const val DEGRADE_PRECISION_AFTER = 10
|
private const val DEGRADE_PRECISION_AFTER = 10
|
||||||
@ -77,11 +68,6 @@ object Util {
|
|||||||
private var MEGA_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
|
private var MEGA_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
|
||||||
private var KILO_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
|
private var KILO_BYTE_LOCALIZED_FORMAT: DecimalFormat? = null
|
||||||
private var 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()
|
// Used by hexEncode()
|
||||||
private val HEX_DIGITS =
|
private val HEX_DIGITS =
|
||||||
@ -448,150 +434,6 @@ object Util {
|
|||||||
return musicDirectory
|
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
|
@JvmStatic
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
fun getNotificationImageSize(context: Context): Int {
|
fun getNotificationImageSize(context: Context): Int {
|
||||||
@ -667,7 +509,7 @@ object Util {
|
|||||||
val hours = TimeUnit.MILLISECONDS.toHours(millis)
|
val hours = TimeUnit.MILLISECONDS.toHours(millis)
|
||||||
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(hours)
|
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(hours)
|
||||||
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) -
|
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) -
|
||||||
TimeUnit.MINUTES.toSeconds(hours * MINUTES_IN_HOUR + minutes)
|
TimeUnit.MINUTES.toSeconds(hours * MINUTES_IN_HOUR + minutes)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
hours >= DEGRADE_PRECISION_AFTER -> {
|
hours >= DEGRADE_PRECISION_AFTER -> {
|
||||||
@ -761,9 +603,9 @@ object Util {
|
|||||||
fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri {
|
fun getUriToDrawable(context: Context, @AnyRes drawableId: Int): Uri {
|
||||||
return Uri.parse(
|
return Uri.parse(
|
||||||
ContentResolver.SCHEME_ANDROID_RESOURCE +
|
ContentResolver.SCHEME_ANDROID_RESOURCE +
|
||||||
"://" + context.resources.getResourcePackageName(drawableId) +
|
"://" + context.resources.getResourcePackageName(drawableId) +
|
||||||
'/' + context.resources.getResourceTypeName(drawableId) +
|
'/' + context.resources.getResourceTypeName(drawableId) +
|
||||||
'/' + context.resources.getResourceEntryName(drawableId)
|
'/' + context.resources.getResourceEntryName(drawableId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -776,39 +618,6 @@ object Util {
|
|||||||
var fileFormat: String?,
|
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")
|
@Suppress("ComplexMethod", "LongMethod")
|
||||||
fun readableEntryDescription(song: Track): ReadableEntryDescription {
|
fun readableEntryDescription(song: Track): ReadableEntryDescription {
|
||||||
val artist = StringBuilder(LINE_LENGTH)
|
val artist = StringBuilder(LINE_LENGTH)
|
||||||
@ -834,8 +643,8 @@ object Util {
|
|||||||
|
|
||||||
if (artistName != null) {
|
if (artistName != null) {
|
||||||
if (Settings.shouldDisplayBitrateWithArtist && (
|
if (Settings.shouldDisplayBitrateWithArtist && (
|
||||||
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
!bitRate.isNullOrBlank() || !fileFormat.isNullOrBlank()
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
artist.append(artistName).append(" (").append(
|
artist.append(artistName).append(" (").append(
|
||||||
String.format(
|
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 {
|
fun getConnectivityManager(): ConnectivityManager {
|
||||||
val context = appContext()
|
val context = appContext()
|
||||||
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
Loading…
x
Reference in New Issue
Block a user