merge develop
This commit is contained in:
commit
4ff167e497
|
@ -1,7 +1,19 @@
|
|||
package org.moire.ultrasonic.domain
|
||||
|
||||
abstract class GenericEntry {
|
||||
// TODO Should be non-null!
|
||||
// TODO: Should be non-null!
|
||||
abstract val id: String?
|
||||
open val name: String? = null
|
||||
|
||||
// These are just a formality and will never be called,
|
||||
// because Kotlin data classes will have autogenerated equals() and hashCode() functions
|
||||
override operator fun equals(other: Any?): Boolean {
|
||||
return this === other
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id?.hashCode() ?: 0
|
||||
result = 31 * result + (name?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ exceptions:
|
|||
|
||||
empty-blocks:
|
||||
active: true
|
||||
EmptyFunctionBlock:
|
||||
active: true
|
||||
ignoreOverridden: true
|
||||
|
||||
complexity:
|
||||
active: true
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,5 @@
|
|||
package org.moire.ultrasonic.service;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.util.LRUCache;
|
||||
import org.moire.ultrasonic.util.ShufflePlayBuffer;
|
||||
|
@ -16,6 +14,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
|
||||
|
@ -342,7 +341,7 @@ public class Downloader
|
|||
Collections.shuffle(downloadList);
|
||||
if (localMediaPlayer.currentPlaying != null)
|
||||
{
|
||||
downloadList.remove(getCurrentPlayingIndex());
|
||||
downloadList.remove(localMediaPlayer.currentPlaying);
|
||||
downloadList.add(0, localMediaPlayer.currentPlaying);
|
||||
}
|
||||
revision++;
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2010 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.util;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
public abstract class SilentBackgroundTask<T> extends BackgroundTask<T>
|
||||
{
|
||||
|
||||
public SilentBackgroundTask(Activity activity)
|
||||
{
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute()
|
||||
{
|
||||
Thread thread = new Thread()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
final T result = doInBackground();
|
||||
|
||||
getHandler().post(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
done(result);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
catch (final Throwable t)
|
||||
{
|
||||
getHandler().post(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
error(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int messageId)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(String message)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -49,8 +49,9 @@ class AlbumListFragment : GenericListFragment<MusicDirectory.Entry, AlbumRowAdap
|
|||
if (args == null) throw IllegalArgumentException("Required arguments are missing")
|
||||
|
||||
val refresh = args.getBoolean(Constants.INTENT_EXTRA_NAME_REFRESH)
|
||||
val append = args.getBoolean(Constants.INTENT_EXTRA_NAME_APPEND)
|
||||
|
||||
return listModel.getAlbumList(refresh, refreshListView!!, args)
|
||||
return listModel.getAlbumList(refresh or append, refreshListView!!, args)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,8 @@ import org.moire.ultrasonic.util.Util
|
|||
|
||||
class AlbumListModel(application: Application) : GenericListModel(application) {
|
||||
|
||||
val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData()
|
||||
val albumList: MutableLiveData<List<MusicDirectory.Entry>> = MutableLiveData(listOf())
|
||||
var lastType: String? = null
|
||||
private var loadedUntil: Int = 0
|
||||
|
||||
fun getAlbumList(
|
||||
|
@ -21,8 +22,14 @@ class AlbumListModel(application: Application) : GenericListModel(application) {
|
|||
swipe: SwipeRefreshLayout?,
|
||||
args: Bundle
|
||||
): LiveData<List<MusicDirectory.Entry>> {
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
val albumListType = args.getString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE)!!
|
||||
|
||||
backgroundLoadFromServer(refresh, swipe, args)
|
||||
if (refresh || albumList.value!!.isEmpty() || albumListType != lastType) {
|
||||
lastType = albumListType
|
||||
backgroundLoadFromServer(refresh, swipe, args)
|
||||
}
|
||||
return albumList
|
||||
}
|
||||
|
||||
|
|
|
@ -30,13 +30,17 @@ import org.moire.ultrasonic.service.MusicService
|
|||
* Provides ViewModel which contains the list of available Artists
|
||||
*/
|
||||
class ArtistListModel(application: Application) : GenericListModel(application) {
|
||||
val artists: MutableLiveData<List<Artist>> = MutableLiveData()
|
||||
val artists: MutableLiveData<List<Artist>> = MutableLiveData(listOf())
|
||||
|
||||
/**
|
||||
* Retrieves all available Artists in a LiveData
|
||||
*/
|
||||
fun getItems(refresh: Boolean, swipe: SwipeRefreshLayout?): LiveData<List<Artist>> {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
// Don't reload the data if navigating back to the view that was active before.
|
||||
// This way, we keep the scroll position
|
||||
if (artists.value!!.isEmpty() || refresh) {
|
||||
backgroundLoadFromServer(refresh, swipe)
|
||||
}
|
||||
return artists
|
||||
}
|
||||
|
||||
|
|
|
@ -16,20 +16,24 @@ import android.widget.ImageView
|
|||
import android.widget.PopupMenu
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||
import org.moire.ultrasonic.domain.GenericEntry
|
||||
import org.moire.ultrasonic.domain.MusicFolder
|
||||
import org.moire.ultrasonic.view.SelectMusicFolderView
|
||||
|
||||
/*
|
||||
* An abstract Adapter, which can be extended to display a List of <T> in a RecyclerView
|
||||
*/
|
||||
abstract class GenericRowAdapter<T>(
|
||||
abstract class GenericRowAdapter<T : GenericEntry>(
|
||||
val onItemClick: (T) -> Unit,
|
||||
val onContextMenuClick: (MenuItem, T) -> Boolean,
|
||||
private val onMusicFolderUpdate: (String?) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
) : ListAdapter<T, RecyclerView.ViewHolder>(GenericDiffCallback()) {
|
||||
|
||||
open var itemList: List<T> = listOf()
|
||||
protected abstract val layout: Int
|
||||
protected abstract val contextMenuLayout: Int
|
||||
|
@ -40,11 +44,12 @@ abstract class GenericRowAdapter<T>(
|
|||
var selectedFolder: String? = null
|
||||
|
||||
/**
|
||||
* Sets the data to be displayed in the RecyclerView
|
||||
* Sets the data to be displayed in the RecyclerView,
|
||||
* using DiffUtil to efficiently calculate the minimum required changes..
|
||||
*/
|
||||
open fun setData(data: List<T>) {
|
||||
submitList(data)
|
||||
itemList = data
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,5 +141,17 @@ abstract class GenericRowAdapter<T>(
|
|||
companion object {
|
||||
internal const val TYPE_HEADER = 0
|
||||
internal const val TYPE_ITEM = 1
|
||||
|
||||
/**
|
||||
* Calculates the differences between data sets
|
||||
*/
|
||||
class GenericDiffCallback<T : GenericEntry> : DiffUtil.ItemCallback<T>() {
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -198,7 +198,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
|
|||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>) {
|
||||
override fun createPlaylist(id: String?, name: String?, entries: List<MusicDirectory.Entry>) {
|
||||
cachedPlaylists.clear()
|
||||
musicService.createPlaylist(id, name, entries)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Looper
|
|||
import android.os.PowerManager
|
||||
import android.os.PowerManager.PARTIAL_WAKE_LOCK
|
||||
import android.os.PowerManager.WakeLock
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import java.io.File
|
||||
import java.net.URLEncoder
|
||||
import java.util.Locale
|
||||
|
@ -29,7 +30,6 @@ import org.moire.ultrasonic.audiofx.EqualizerController
|
|||
import org.moire.ultrasonic.audiofx.VisualizerController
|
||||
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
|
||||
import org.moire.ultrasonic.domain.PlayerState
|
||||
import org.moire.ultrasonic.fragment.PlayerFragment
|
||||
import org.moire.ultrasonic.util.CancellableTask
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.StreamProxy
|
||||
|
@ -79,10 +79,12 @@ class LocalMediaPlayer(
|
|||
private var proxy: StreamProxy? = null
|
||||
private var bufferTask: CancellableTask? = null
|
||||
private var positionCache: PositionCache? = null
|
||||
private var secondaryProgress = -1
|
||||
|
||||
private val pm = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||
private val wakeLock: WakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, this.javaClass.name)
|
||||
|
||||
val secondaryProgress: MutableLiveData<Int> = MutableLiveData(0)
|
||||
|
||||
fun init() {
|
||||
Thread {
|
||||
Thread.currentThread().name = "MediaPlayerThread"
|
||||
|
@ -361,7 +363,6 @@ class LocalMediaPlayer(
|
|||
|
||||
downloadFile.updateModificationDate()
|
||||
mediaPlayer.setOnCompletionListener(null)
|
||||
secondaryProgress = -1 // Ensure seeking in non StreamProxy playback works
|
||||
|
||||
setPlayerState(PlayerState.IDLE)
|
||||
setAudioAttributes(mediaPlayer)
|
||||
|
@ -392,28 +393,28 @@ class LocalMediaPlayer(
|
|||
setPlayerState(PlayerState.PREPARING)
|
||||
|
||||
mediaPlayer.setOnBufferingUpdateListener { mp, percent ->
|
||||
val progressBar = PlayerFragment.getProgressBar()
|
||||
val song = downloadFile.song
|
||||
|
||||
if (percent == 100) {
|
||||
mp.setOnBufferingUpdateListener(null)
|
||||
}
|
||||
|
||||
secondaryProgress = (percent.toDouble() / 100.toDouble() * progressBar.max).toInt()
|
||||
|
||||
// The secondary progress is an indicator of how far the song is cached.
|
||||
if (song.transcodedContentType == null && Util.getMaxBitRate() == 0) {
|
||||
progressBar?.secondaryProgress = secondaryProgress
|
||||
val progress = (percent.toDouble() / 100.toDouble() * playerDuration).toInt()
|
||||
secondaryProgress.postValue(progress)
|
||||
}
|
||||
}
|
||||
|
||||
mediaPlayer.setOnPreparedListener {
|
||||
Timber.i("Media player prepared")
|
||||
setPlayerState(PlayerState.PREPARED)
|
||||
val progressBar = PlayerFragment.getProgressBar()
|
||||
if (progressBar != null && downloadFile.isWorkDone) {
|
||||
// Populate seek bar secondary progress if we have a complete file for consistency
|
||||
PlayerFragment.getProgressBar().secondaryProgress = 100 * progressBar.max
|
||||
|
||||
// Populate seek bar secondary progress if we have a complete file for consistency
|
||||
if (downloadFile.isWorkDone) {
|
||||
secondaryProgress.postValue(playerDuration)
|
||||
}
|
||||
|
||||
synchronized(this@LocalMediaPlayer) {
|
||||
if (position != 0) {
|
||||
Timber.i("Restarting player from position %d", position)
|
||||
|
|
|
@ -386,12 +386,6 @@ class MediaPlayerController(
|
|||
@get:Synchronized
|
||||
val playerDuration: Int
|
||||
get() {
|
||||
if (localMediaPlayer.currentPlaying != null) {
|
||||
val duration = localMediaPlayer.currentPlaying!!.song.duration
|
||||
if (duration != null) {
|
||||
return duration * 1000
|
||||
}
|
||||
}
|
||||
val mediaPlayerService = runningInstance ?: return 0
|
||||
return mediaPlayerService.playerDuration
|
||||
}
|
||||
|
@ -454,6 +448,19 @@ class MediaPlayerController(
|
|||
if (localMediaPlayer.currentPlaying == null) return
|
||||
val song = localMediaPlayer.currentPlaying!!.song
|
||||
|
||||
Thread {
|
||||
val musicService = getMusicService()
|
||||
try {
|
||||
if (song.starred) {
|
||||
musicService.unstar(song.id, null, null)
|
||||
} else {
|
||||
musicService.star(song.id, null, null)
|
||||
}
|
||||
} catch (all: Exception) {
|
||||
Timber.e(all)
|
||||
}
|
||||
}.start()
|
||||
|
||||
// Trigger an update
|
||||
localMediaPlayer.setCurrentPlaying(localMediaPlayer.currentPlaying)
|
||||
song.starred = !song.starred
|
||||
|
|
|
@ -73,7 +73,7 @@ interface MusicService {
|
|||
fun getPlaylists(refresh: Boolean): List<Playlist>
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>)
|
||||
fun createPlaylist(id: String?, name: String?, entries: List<MusicDirectory.Entry>)
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun deletePlaylist(id: String)
|
||||
|
|
|
@ -221,7 +221,7 @@ class OfflineMusicService : MusicService, KoinComponent {
|
|||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
@Throws(Exception::class)
|
||||
override fun createPlaylist(id: String, name: String, entries: List<MusicDirectory.Entry>) {
|
||||
override fun createPlaylist(id: String?, name: String?, entries: List<MusicDirectory.Entry>) {
|
||||
val playlistFile =
|
||||
FileUtil.getPlaylistFile(activeServerProvider.getActiveServer().name, name)
|
||||
val fw = FileWriter(playlistFile)
|
||||
|
|
|
@ -295,12 +295,20 @@ open class RESTMusicService(
|
|||
return response.body()!!.playlists.toDomainEntitiesList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Either ID or String is required.
|
||||
* ID is required when updating
|
||||
* String is required when creating
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun createPlaylist(
|
||||
id: String,
|
||||
name: String,
|
||||
id: String?,
|
||||
name: String?,
|
||||
entries: List<MusicDirectory.Entry>
|
||||
) {
|
||||
if (id == null && name == null)
|
||||
throw IllegalArgumentException("Either id or name is required.")
|
||||
|
||||
val pSongIds: MutableList<String> = ArrayList(entries.size)
|
||||
|
||||
for ((id1) in entries) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* SilentBackgroundTask.kt
|
||||
* Copyright (C) 2009-2021 Ultrasonic developers
|
||||
*
|
||||
* Distributed under terms of the GNU GPLv3 license.
|
||||
*/
|
||||
|
||||
package org.moire.ultrasonic.util
|
||||
|
||||
import android.app.Activity
|
||||
|
||||
/**
|
||||
* @author Sindre Mehus
|
||||
*/
|
||||
abstract class SilentBackgroundTask<T>(activity: Activity?) : BackgroundTask<T>(activity) {
|
||||
override fun execute() {
|
||||
val thread: Thread = object : Thread() {
|
||||
override fun run() {
|
||||
try {
|
||||
val result = doInBackground()
|
||||
handler.post { done(result) }
|
||||
} catch (all: Throwable) {
|
||||
handler.post { error(all) }
|
||||
}
|
||||
}
|
||||
}
|
||||
thread.start()
|
||||
}
|
||||
|
||||
override fun updateProgress(messageId: Int) {}
|
||||
override fun updateProgress(message: String) {}
|
||||
}
|
Loading…
Reference in New Issue