music: re-add search browsing
This commit is contained in:
parent
b1e871c6e1
commit
924e3d1801
4 changed files with 79 additions and 35 deletions
|
@ -105,7 +105,7 @@ class AuxioService : MediaBrowserServiceCompat(), ForegroundListener, MusicServi
|
||||||
extras: Bundle?,
|
extras: Bundle?,
|
||||||
result: Result<MutableList<MediaItem>>
|
result: Result<MutableList<MediaItem>>
|
||||||
) {
|
) {
|
||||||
super.onSearch(query, extras, result)
|
musicFragment.search(query, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
|
|
@ -307,21 +307,6 @@ constructor(
|
||||||
// results
|
// results
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private fun List<MediaItem>.paginate(page: Int, pageSize: Int): List<MediaItem>? {
|
|
||||||
if (page == Int.MAX_VALUE) {
|
|
||||||
// I think if someone requests this page it more or less implies that I should
|
|
||||||
// return all of the pages.
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
val start = page * pageSize
|
|
||||||
val end = min((page + 1) * pageSize, size) // Tolerate partial page queries
|
|
||||||
if (pageSize == 0 || start !in indices) {
|
|
||||||
// These pages are probably invalid. Hopefully this won't backfire.
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return subList(start, end).toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
// TODO: Rely on detail item gen logic?
|
// TODO: Rely on detail item gen logic?
|
||||||
val ARTIST_ALBUMS_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)
|
val ARTIST_ALBUMS_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)
|
||||||
|
|
|
@ -15,47 +15,45 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.service
|
package org.oxycblt.auxio.music.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.PowerManager
|
|
||||||
import androidx.media.MediaBrowserServiceCompat.BrowserRoot
|
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
|
||||||
import androidx.media.utils.MediaConstants
|
|
||||||
import android.support.v4.media.MediaBrowserCompat.MediaItem
|
import android.support.v4.media.MediaBrowserCompat.MediaItem
|
||||||
import coil.ImageLoader
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
import androidx.media.MediaBrowserServiceCompat.BrowserRoot
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import kotlinx.coroutines.launch
|
||||||
import org.oxycblt.auxio.ForegroundListener
|
import org.oxycblt.auxio.ForegroundListener
|
||||||
import org.oxycblt.auxio.ForegroundServiceNotification
|
import org.oxycblt.auxio.ForegroundServiceNotification
|
||||||
import org.oxycblt.auxio.music.IndexingState
|
import org.oxycblt.auxio.music.IndexingState
|
||||||
import org.oxycblt.auxio.music.MusicRepository
|
import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.music.MusicSettings
|
import org.oxycblt.auxio.music.MusicSettings
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.search.SearchEngine
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MusicServiceFragment
|
class MusicServiceFragment
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val indexer: Indexer,
|
private val indexer: Indexer,
|
||||||
private val browser: MusicBrowser,
|
private val musicBrowser: MusicBrowser,
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
private val musicSettings: MusicSettings,
|
private val musicSettings: MusicSettings,
|
||||||
|
private val searchEngine: SearchEngine,
|
||||||
private val contentObserver: SystemContentObserver,
|
private val contentObserver: SystemContentObserver,
|
||||||
) : MusicBrowser.Invalidator, MusicSettings.Listener {
|
) : MusicBrowser.Invalidator, MusicSettings.Listener {
|
||||||
private val indexingNotification = IndexingNotification(context)
|
private val indexingNotification = IndexingNotification(context)
|
||||||
private val observingNotification = ObservingNotification(context)
|
private val observingNotification = ObservingNotification(context)
|
||||||
private var invalidator: Invalidator? = null
|
private var invalidator: Invalidator? = null
|
||||||
private var foregroundListener: ForegroundListener? = null
|
private var foregroundListener: ForegroundListener? = null
|
||||||
|
private val dispatchJob = Job()
|
||||||
|
private val dispatchScope = CoroutineScope(dispatchJob + Dispatchers.Default)
|
||||||
|
|
||||||
interface Invalidator {
|
interface Invalidator {
|
||||||
fun invalidateMusic(mediaId: String)
|
fun invalidateMusic(mediaId: String)
|
||||||
|
@ -64,7 +62,7 @@ constructor(
|
||||||
fun attach(foregroundListener: ForegroundListener, invalidator: Invalidator) {
|
fun attach(foregroundListener: ForegroundListener, invalidator: Invalidator) {
|
||||||
this.invalidator = invalidator
|
this.invalidator = invalidator
|
||||||
indexer.attach(foregroundListener)
|
indexer.attach(foregroundListener)
|
||||||
browser.attach(this)
|
musicBrowser.attach(this)
|
||||||
contentObserver.attach()
|
contentObserver.attach()
|
||||||
musicSettings.registerListener(this)
|
musicSettings.registerListener(this)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +70,8 @@ constructor(
|
||||||
fun release() {
|
fun release() {
|
||||||
musicSettings.unregisterListener(this)
|
musicSettings.unregisterListener(this)
|
||||||
contentObserver.release()
|
contentObserver.release()
|
||||||
browser.release()
|
dispatchJob.cancel()
|
||||||
|
musicBrowser.release()
|
||||||
indexer.release()
|
indexer.release()
|
||||||
invalidator = null
|
invalidator = null
|
||||||
}
|
}
|
||||||
|
@ -127,10 +126,53 @@ constructor(
|
||||||
fun getRoot() = BrowserRoot(Category.ROOT.id, null)
|
fun getRoot() = BrowserRoot(Category.ROOT.id, null)
|
||||||
|
|
||||||
fun getItem(mediaId: String, result: MediaBrowserServiceCompat.Result<MediaItem>) =
|
fun getItem(mediaId: String, result: MediaBrowserServiceCompat.Result<MediaItem>) =
|
||||||
result.dispatch { browser.getItem(mediaId) }
|
result.dispatch { musicBrowser.getItem(mediaId) }
|
||||||
|
|
||||||
fun getChildren(mediaId: String, result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>) =
|
fun getChildren(
|
||||||
result.dispatch { browser.getChildren(mediaId)?.toMutableList() }
|
mediaId: String,
|
||||||
|
result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>
|
||||||
|
) =
|
||||||
|
result.dispatch { musicBrowser.getChildren(mediaId)?.toMutableList() }
|
||||||
|
|
||||||
|
fun search(query: String, result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>) =
|
||||||
|
result.dispatchAsync {
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
return@dispatchAsync mutableListOf()
|
||||||
|
}
|
||||||
|
val deviceLibrary =
|
||||||
|
musicRepository.deviceLibrary ?: return@dispatchAsync mutableListOf()
|
||||||
|
val userLibrary = musicRepository.userLibrary ?: return@dispatchAsync mutableListOf()
|
||||||
|
val items =
|
||||||
|
SearchEngine.Items(
|
||||||
|
deviceLibrary.songs,
|
||||||
|
deviceLibrary.albums,
|
||||||
|
deviceLibrary.artists,
|
||||||
|
deviceLibrary.genres,
|
||||||
|
userLibrary.playlists
|
||||||
|
)
|
||||||
|
searchEngine.search(items, query).concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun SearchEngine.Items.concat(): MutableList<MediaItem> {
|
||||||
|
val music = mutableListOf<MediaItem>()
|
||||||
|
if (songs != null) {
|
||||||
|
music.addAll(songs.map { it.toMediaItem(context, null) })
|
||||||
|
}
|
||||||
|
if (albums != null) {
|
||||||
|
music.addAll(albums.map { it.toMediaItem(context) })
|
||||||
|
}
|
||||||
|
if (artists != null) {
|
||||||
|
music.addAll(artists.map { it.toMediaItem(context) })
|
||||||
|
}
|
||||||
|
if (genres != null) {
|
||||||
|
music.addAll(genres.map { it.toMediaItem(context) })
|
||||||
|
}
|
||||||
|
if (playlists != null) {
|
||||||
|
music.addAll(playlists.map { it.toMediaItem(context) })
|
||||||
|
}
|
||||||
|
return music
|
||||||
|
}
|
||||||
|
|
||||||
private fun <T> MediaBrowserServiceCompat.Result<T>.dispatch(body: () -> T?) {
|
private fun <T> MediaBrowserServiceCompat.Result<T>.dispatch(body: () -> T?) {
|
||||||
try {
|
try {
|
||||||
|
@ -144,4 +186,19 @@ constructor(
|
||||||
sendResult(null)
|
sendResult(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> MediaBrowserServiceCompat.Result<T>.dispatchAsync(body: suspend () -> T?) {
|
||||||
|
dispatchScope.launch {
|
||||||
|
try {
|
||||||
|
val result = body()
|
||||||
|
if (result == null) {
|
||||||
|
logW("Result is null")
|
||||||
|
}
|
||||||
|
sendResult(result)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logD("Error while dispatching: $e")
|
||||||
|
sendResult(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,9 @@ interface SearchEngine {
|
||||||
val artists: Collection<Artist>? = null,
|
val artists: Collection<Artist>? = null,
|
||||||
val genres: Collection<Genre>? = null,
|
val genres: Collection<Genre>? = null,
|
||||||
val playlists: Collection<Playlist>? = null
|
val playlists: Collection<Playlist>? = null
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) :
|
class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) :
|
||||||
|
|
Loading…
Reference in a new issue