Upgrade musikdroid dependencies and build tooling.

This commit is contained in:
casey langen 2021-04-17 19:23:08 -07:00
parent cdc9242cb3
commit 9ebf93b155
35 changed files with 172 additions and 210 deletions

View File

@ -12,14 +12,14 @@ apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.firebase.crashlytics'
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
applicationId "io.casey.musikcube.remote"
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 30
versionCode 109
versionName "0.94.0"
versionName "0.96.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -61,9 +61,9 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.google.firebase:firebase-analytics:17.6.0'
implementation 'com.google.firebase:firebase-core:17.5.1'
implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
implementation 'com.google.firebase:firebase-analytics:18.0.3'
implementation 'com.google.firebase:firebase-core:18.0.3'
implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
implementation(name:'android-taskrunner-0.5', ext:'aar')
implementation(name:'exoplayer-extension-flac-release-v2', ext:'aar')
@ -71,34 +71,34 @@ dependencies {
implementation 'org.slf4j:slf4j-android:1.7.21'
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-runtime:2.2.6"
kapt "androidx.room:room-compiler:2.2.6"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
implementation 'com.google.dagger:dagger:2.27'
kapt 'com.google.dagger:dagger-compiler:2.27'
implementation 'com.neovisionaries:nv-websocket-client:1.31'
implementation 'com.squareup.okhttp3:okhttp:3.12.8'
implementation 'com.squareup.okhttp3:okhttp:3.12.11'
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation "com.github.bumptech.glide:okhttp3-integration:4.10.0"
kapt 'com.github.bumptech.glide:compiler:4.10.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.16'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'com.google.android.exoplayer:exoplayer:2.11.4'
implementation 'com.google.android.exoplayer:extension-okhttp:2.11.4'
implementation 'com.google.android.exoplayer:exoplayer:2.13.3'
implementation 'com.google.android.exoplayer:extension-okhttp:2.13.3'
implementation 'com.simplecityapps:recyclerview-fastscroll:2.0.0'
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'androidx.media:media:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'com.google.android.material:material:1.4.0-alpha02'
implementation 'androidx.media:media:1.3.0'
testImplementation 'junit:junit:4.12'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

@ -1,26 +0,0 @@
package io.casey.musikcube.remote;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("io.casey.musikcube.remote", appContext.getPackageName());
}
}

View File

@ -57,7 +57,7 @@ abstract class ViewModel<T>(protected val runner: Runner? = null): Runner.TaskCa
}
open fun createSubject(): Subject<T> {
return PublishSubject.create<T>()
return PublishSubject.create()
}
override fun onTaskError(name: String?, id: Long, task: Task<*, *>?, error: Throwable?) {

View File

@ -29,7 +29,7 @@ class GlideModule : AppGlideModule() {
if (serverHost == requestHost) {
val userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD)!!
val encoded = Base64.encodeToString(userPass.toByteArray(), Base64.NO_WRAP)
request = req.newBuilder().addHeader("Authorization", "Basic " + encoded).build()
request = req.newBuilder().addHeader("Authorization", "Basic $encoded").build()
} else if (canInterceptArtwork(req)) {
request = interceptArtwork(req)
}

View File

@ -43,8 +43,8 @@ class GaplessHeaderService {
thread.start()
handler = object : Handler(thread.looper) {
override fun handleMessage(msg: Message?) {
if (msg?.what == MESSAGE_PROCESS) {
override fun handleMessage(msg: Message) {
if (msg.what == MESSAGE_PROCESS) {
db.dao().queryByState(GaplessTrack.DOWNLOADED).forEach { process(it) }
}
}

View File

@ -2,7 +2,6 @@ package io.casey.musikcube.remote.service.playback.impl.player
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import com.danikula.videocache.CacheListener
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
@ -13,7 +12,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.util.Util
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.service.playback.PlayerWrapper
@ -39,8 +38,12 @@ class GaplessExoPlayerWrapper : PlayerWrapper() {
init {
val userAgent = Util.getUserAgent(context, "musikdroid")
val httpFactory: DataSource.Factory = DefaultHttpDataSourceFactory(
userAgent, null, TIMEOUT, TIMEOUT, true)
val httpFactory: DataSource.Factory = DefaultHttpDataSource.Factory().apply {
setUserAgent(userAgent)
setConnectTimeoutMs(TIMEOUT)
setReadTimeoutMs(TIMEOUT)
setAllowCrossProtocolRedirects(true)
}
this.sourceFactory = DefaultDataSourceFactory(context, null, httpFactory)
@ -52,17 +55,18 @@ class GaplessExoPlayerWrapper : PlayerWrapper() {
if (!dead()) {
removeAllAndReset()
val proxyUri = streamProxy.getProxyUrl(uri)
this.metadata = metadata
this.originalUri = uri
this.proxyUri = streamProxy.getProxyUrl(uri)
this.proxyUri = proxyUri
this.initialOffsetMs = offsetMs
addCacheListener()
this.source = ProgressiveMediaSource
.Factory(sourceFactory)
.createMediaSource(Uri.parse(proxyUri))
.createMediaSource(MediaItem.fromUri(proxyUri))
addPlayer(this, this.source!!)
@ -76,14 +80,16 @@ class GaplessExoPlayerWrapper : PlayerWrapper() {
if (!dead()) {
removePending()
val proxyUri = streamProxy.getProxyUrl(uri)
this.metadata = metadata
this.originalUri = uri
this.proxyUri = streamProxy.getProxyUrl(uri)
this.proxyUri = proxyUri
this.prefetch = true
this.source = ProgressiveMediaSource
.Factory(sourceFactory)
.createMediaSource(Uri.parse(proxyUri))
.createMediaSource(MediaItem.fromUri(proxyUri))
addCacheListener()
addPlayer(this, source!!)
@ -115,7 +121,8 @@ class GaplessExoPlayerWrapper : PlayerWrapper() {
State.Error -> {
gaplessPlayer?.playWhenReady = lastPosition == -1L
source?.let {
gaplessPlayer?.prepare(it)
gaplessPlayer?.setMediaSource(it)
gaplessPlayer?.prepare()
state = State.Preparing
} ?: run {
state = State.Error
@ -362,7 +369,8 @@ class GaplessExoPlayerWrapper : PlayerWrapper() {
all.add(wrapper)
if (dcms.size == 1) {
gaplessPlayer?.prepare(dcms)
gaplessPlayer?.setMediaSource(dcms)
gaplessPlayer?.prepare()
}
}

View File

@ -1,6 +1,7 @@
package io.casey.musikcube.remote.service.playback.impl.remote
import android.os.Handler
import android.os.Looper
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.injection.DaggerServiceComponent
import io.casey.musikcube.remote.service.playback.IPlaybackService
@ -42,35 +43,35 @@ class RemotePlaybackService : IPlaybackService {
* and estimate.
*/
private class EstimatedPosition {
internal var lastTime = 0.0
internal var pauseTime = 0.0
internal var trackId: Long = -1
internal var queryTime: Long = 0
var lastTime = 0.0
var pauseTime = 0.0
var trackId: Long = -1
var queryTime: Long = 0
internal fun get(track: JSONObject?): Double {
fun get(track: JSONObject?): Double {
if (track != null && track.optLong(Metadata.Track.ID, -1L) == trackId && trackId != -1L) {
return if (pauseTime != 0.0) pauseTime else estimatedTime()
}
return 0.0
}
internal fun update(message: SocketMessage) {
fun update(message: SocketMessage) {
queryTime = System.nanoTime()
lastTime = message.getDoubleOption(Messages.Key.PLAYING_CURRENT_TIME, 0.0)
trackId = message.getLongOption(Messages.Key.ID, -1)
}
internal fun pause() {
fun pause() {
pauseTime = estimatedTime()
}
internal fun resume() {
fun resume() {
lastTime = pauseTime
queryTime = System.nanoTime()
pauseTime = 0.0
}
internal fun update(time: Double, id: Long) {
fun update(time: Double, id: Long) {
queryTime = System.nanoTime()
lastTime = time
trackId = id
@ -80,14 +81,14 @@ class RemotePlaybackService : IPlaybackService {
}
}
internal fun reset() {
fun reset() {
pauseTime = 0.0
lastTime = pauseTime
queryTime = System.nanoTime()
trackId = -1
}
internal fun estimatedTime(): Double {
fun estimatedTime(): Double {
val diff = System.nanoTime() - queryTime
val seconds = diff.toDouble() / NANOSECONDS_PER_SECOND
return lastTime + seconds
@ -97,52 +98,36 @@ class RemotePlaybackService : IPlaybackService {
@Inject lateinit var wss: WebSocketService
@Inject lateinit var metadataProxy: IMetadataProxy
private val handler = Handler()
private val handler = Handler(Looper.getMainLooper())
private val listeners = HashSet<() -> Unit>()
private val estimatedTime = EstimatedPosition()
override var state = PlaybackState.Stopped
private set(value) {
field = value
}
private set
override val currentTime: Double
get() = estimatedTime.get(track)
override var repeatMode: RepeatMode = RepeatMode.None
private set(value) {
field = value
}
private set
override var shuffled: Boolean = false
private set(value) {
field = value
}
private set
override var muted: Boolean = false
private set(value) {
field = value
}
private set
override var volume: Double = 0.0
private set(value) {
field = value
}
private set
override var queueCount: Int = 0
private set(value) {
field = value
}
private set
override var queuePosition: Int = 0
private set(value) {
field = value
}
private set
override var duration: Double = 0.0
private set(value) {
field = value
}
private set
private var track: JSONObject = JSONObject()
@ -401,7 +386,7 @@ class RemotePlaybackService : IPlaybackService {
}
}
override val queryContext: QueryContext?
override val queryContext: QueryContext
get() = QueryContext(Messages.Request.QueryPlayQueueTracks)
override val playlistQueryFactory: ITrackListQueryFactory = object : ITrackListQueryFactory {

View File

@ -1,5 +1,6 @@
package io.casey.musikcube.remote.service.playback.impl.streaming
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.database.ContentObserver
@ -28,6 +29,7 @@ import io.reactivex.rxkotlin.subscribeBy
import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import kotlin.math.roundToInt
class StreamingPlaybackService(context: Context) : IPlaybackService {
@Inject lateinit var metadataProxy: IMetadataProxy
@ -39,7 +41,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
private var lastSystemVolume: Int = 0
private var pausedByTransientLoss = false
private val random = Random()
private val handler = Handler()
private val handler = Handler(Looper.getMainLooper())
private val trackMetadataCache = object : LinkedHashMap<Int, ITrack>() {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, ITrack>): Boolean {
@ -48,13 +50,13 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
}
private class PlaybackContext {
internal var queueCount: Int = 0
internal var currentPlayer: PlayerWrapper? = null
internal var nextPlayer: PlayerWrapper? = null
internal var currentMetadata: ITrack? = null
internal var nextMetadata: ITrack? = null
internal var currentIndex = -1
internal var nextIndex = -1
var queueCount: Int = 0
var currentPlayer: PlayerWrapper? = null
var nextPlayer: PlayerWrapper? = null
var currentMetadata: ITrack? = null
var nextMetadata: ITrack? = null
var currentIndex = -1
var nextIndex = -1
fun stopPlaybackAndReset() {
reset(currentPlayer)
@ -190,10 +192,10 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
resetPlayContextAndQueryFactory()
snapshotQueryFactory = object: ITrackListQueryFactory {
override fun count(): Observable<Int>? =
override fun count(): Observable<Int> =
metadataProxy.getPlayQueueTracksCount(type)
override fun page(offset: Int, limit: Int): Observable<List<ITrack>>? =
override fun page(offset: Int, limit: Int): Observable<List<ITrack>> =
metadataProxy.getPlayQueueTracks(limit, offset, type)
override fun offline(): Boolean = false
@ -432,7 +434,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
PlayerWrapper.setVolume(current)
}
else {
val actual = Math.round(current * maxSystemVolume)
val actual = (current * maxSystemVolume).roundToInt()
lastSystemVolume = actual
audioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, actual, 0)
}
@ -704,6 +706,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
}
}
@SuppressLint("CheckResult")
private fun loadQueueAndPlay(newParams: QueryContext, startIndex: Int, offsetMs: Int = 0) {
state = PlaybackState.Buffering
@ -721,7 +724,6 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
val countMessage = playlistQueryFactory.count() ?: return
@Suppress("unused")
countMessage
.concatMap { count ->
getCurrentAndNextTrackMessages(playContext, count)
@ -767,6 +769,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
handler.postDelayed(pauseServiceSleepRunnable, PAUSED_SERVICE_SLEEP_DELAY_MS.toLong())
}
@SuppressLint("CheckResult")
private fun precacheTrackMetadata(start: Int, count: Int) {
val originalParams = queryContext
val query = playlistQueryFactory.page(start, count)
@ -779,7 +782,7 @@ class StreamingPlaybackService(context: Context) : IPlaybackService {
{ response ->
if (originalParams === this.queryContext) {
response.forEachIndexed { i, track ->
trackMetadataCache.put(start + i, track)
trackMetadataCache[start + i] = track
}
}
},

View File

@ -8,7 +8,7 @@ import org.json.JSONObject
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
class SocketMessage private constructor(val name: String, val id: String, val type: SocketMessage.Type, options: JSONObject?) {
class SocketMessage private constructor(val name: String, val id: String, val type: Type, options: JSONObject?) {
enum class Type constructor(val rawType: String) {
Request("request"),
Response("response"),

View File

@ -1,5 +1,3 @@
package io.casey.musikcube.remote.service.websocket.model
interface IArtist {
}
interface IArtist

View File

@ -8,9 +8,11 @@ import io.casey.musikcube.remote.service.websocket.model.ITrackListQueryFactory
import io.reactivex.Observable
import org.json.JSONObject
import javax.inject.Inject
import kotlin.math.min
class IdListTrackListQueryFactory(private val idList: List<String>): ITrackListQueryFactory {
@Inject protected lateinit var metadataProxy: IMetadataProxy
@Inject
protected lateinit var metadataProxy: IMetadataProxy
init {
DaggerDataComponent.builder()
@ -22,7 +24,7 @@ class IdListTrackListQueryFactory(private val idList: List<String>): ITrackListQ
override fun page(offset: Int, limit: Int): Observable<List<ITrack>>? {
val window = mutableSetOf<String>()
val max = Math.min(limit, idList.size)
val max = min(limit, idList.size)
for (i in 0 until max) {
window.add(idList[offset + i])
@ -30,7 +32,7 @@ class IdListTrackListQueryFactory(private val idList: List<String>): ITrackListQ
val missing = RemoteTrack(JSONObject())
return metadataProxy.getTracks(window)
.flatMap{ it ->
.flatMap{
val result = mutableListOf<ITrack>()
for (i in 0 until max) {
result.add(it[idList[offset + i]] ?: missing)

View File

@ -48,7 +48,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(message, client)
.observeOn(Schedulers.computation())
.flatMap<List<IAlbum>> { socketMessage -> toAlbumList(socketMessage) }
.flatMap { socketMessage -> toAlbumList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -61,7 +61,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(message, client)
.observeOn(Schedulers.computation())
.flatMap<Int> { socketMessage -> toCount(socketMessage) }
.flatMap { socketMessage -> toCount(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -77,7 +77,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(builder.build(), client)
.observeOn(Schedulers.computation())
.flatMap<List<ITrack>> { socketMessage -> toTrackList(socketMessage) }
.flatMap { socketMessage -> toTrackList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -108,7 +108,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(message, client)
.observeOn(Schedulers.computation())
.flatMap<List<String>> { socketMessage -> toStringList(socketMessage) }
.flatMap { socketMessage -> toStringList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -125,7 +125,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Int> { socketMessage -> toCount(socketMessage) }
.flatMap { socketMessage -> toCount(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -140,7 +140,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<List<String>> { socketMessage -> toStringList(socketMessage) }
.flatMap { socketMessage -> toStringList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -161,7 +161,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(builder.build(), client)
.observeOn(Schedulers.computation())
.flatMap<List<ITrack>> { socketMessage -> toTrackList(socketMessage) }
.flatMap { socketMessage -> toTrackList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -173,7 +173,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Int> { socketMessage -> toCount(socketMessage) }
.flatMap { socketMessage -> toCount(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -192,7 +192,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(builder.build(), client)
.observeOn(Schedulers.computation())
.flatMap<List<ITrack>> { socketMessage -> toTrackList(socketMessage) }
.flatMap { socketMessage -> toTrackList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -205,7 +205,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -216,7 +216,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
@Suppress("unused")
service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(onError = { })
}
@ -234,7 +234,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(builder.build(), client)
.observeOn(Schedulers.computation())
.flatMap<List<String>> { socketMessage -> toStringList(socketMessage) }
.flatMap { socketMessage -> toStringList(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -246,7 +246,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(message, client)
.observeOn(Schedulers.computation())
.flatMap<List<ICategoryValue>> { socketMessage ->
.flatMap { socketMessage ->
toCategoryList(socketMessage, Metadata.Category.PLAYLISTS)
}
.flatMap { values ->
@ -266,7 +266,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
return service.observe(message, client)
.observeOn(Schedulers.computation())
.flatMap<List<ICategoryValue>> { socketMessage -> toCategoryList(socketMessage, type) }
.flatMap { socketMessage -> toCategoryList(socketMessage, type) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -282,7 +282,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Long> { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.flatMap { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -305,7 +305,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Long> { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.flatMap { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -324,7 +324,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Long> { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.flatMap { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -343,7 +343,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Long> { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.flatMap { socketMessage -> extractId(socketMessage, Messages.Key.PLAYLIST_ID) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -356,7 +356,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -376,7 +376,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -395,7 +395,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -414,7 +414,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Int> { socketMessage -> toCount(socketMessage) }
.flatMap { socketMessage -> toCount(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -430,7 +430,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -441,7 +441,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -451,7 +451,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<IOutputs> { socketMessage -> toOutputs(socketMessage) }
.flatMap { socketMessage -> toOutputs(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -463,7 +463,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -487,7 +487,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -513,7 +513,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -531,7 +531,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<TransportType> { socketMessage ->
.flatMap { socketMessage ->
Observable.just(TransportType.find(
socketMessage.getStringOption(Messages.Key.TYPE,
TransportType.Gapless.rawValue)))
@ -546,7 +546,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
@ -580,7 +580,7 @@ class RemoteMetadataProxy(private val service: WebSocketService) : IMetadataProx
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.flatMap { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}

View File

@ -66,7 +66,7 @@ class BrowseFragmentAdapter(private val context: Context,
return fragment.pushTo(this.containerId)
}
override fun getPageTitle(position: Int): CharSequence? =
override fun getPageTitle(position: Int): CharSequence =
context.getString(when (position) {
0 -> R.string.button_artists
1 -> R.string.button_albums

View File

@ -212,7 +212,7 @@ class CategoryBrowseFragment: BaseFragment(), IFilterable, ITitleProvider, ITran
if (intent == null) {
throw IllegalArgumentException("invalid intent")
}
return create(intent.getBundleExtra(Category.Extra.FRAGMENT_ARGUMENTS))
return create(intent.getBundleExtra(Category.Extra.FRAGMENT_ARGUMENTS) ?: Bundle())
}
fun create(arguments: Bundle): CategoryBrowseFragment =

View File

@ -9,6 +9,7 @@ import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
@ -37,7 +38,7 @@ class TrackDownloadActivity: BaseActivity() {
private var outputFilename: String = ""
private lateinit var spinner: MaterialProgressBar
private lateinit var progress: MaterialProgressBar
private val handler = Handler()
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@ -6,7 +6,11 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.view.*
import android.os.Looper
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
@ -35,7 +39,7 @@ import io.casey.musikcube.remote.ui.shared.util.Duration
import io.casey.musikcube.remote.ui.shared.util.UpdateCheck
class MainActivity : BaseActivity() {
private val handler = Handler()
private val handler = Handler(Looper.getMainLooper())
private var updateCheck: UpdateCheck = UpdateCheck()
private var seekbarValue = -1
private var blink = 0

View File

@ -43,6 +43,7 @@ import io.casey.musikcube.remote.ui.shared.util.Size
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
import org.json.JSONArray
import javax.inject.Inject
import kotlin.math.roundToLong
import io.casey.musikcube.remote.ui.shared.util.AlbumArtLookup.getUrl as getAlbumArtUrl
class MainMetadataView : FrameLayout {
@ -123,7 +124,7 @@ class MainMetadataView : FrameLayout {
volumeWithArt.visibility = View.GONE
}
else {
val volume = getString(R.string.status_volume, Math.round(playback.volume * 100))
val volume = getString(R.string.status_volume, (playback.volume * 100).roundToLong())
this.volume.visibility = View.VISIBLE
this.volumeWithArt.visibility = View.VISIBLE
this.volume.text = volume

View File

@ -2,6 +2,7 @@ package io.casey.musikcube.remote.ui.settings.activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.TextView
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R
@ -15,7 +16,7 @@ class DiagnosticsActivity: BaseActivity() {
private lateinit var wakeRuntime: TextView
private lateinit var wakeAcquired: TextView
private lateinit var serviceState: TextView
private val handler = Handler()
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View File

@ -40,7 +40,7 @@ class RemoteEqActivity: BaseActivity() {
this.enabledCb = findViewById(R.id.enabled_checkbox)
initListeners()
viewModel = createViewModel()!!
viewModel = createViewModel()
updateFromViewModelState()
}
@ -51,7 +51,7 @@ class RemoteEqActivity: BaseActivity() {
viewModel.attach(data.provider)
}
override fun <T : ViewModel<*>> createViewModel(): T? {
override fun <T : ViewModel<*>> createViewModel(): T {
@Suppress("unchecked_cast")
return RemoteEqViewModel() as T
}

View File

@ -59,7 +59,7 @@ class RemoteSettingsActivity: BaseActivity() {
environmentTextView = findViewById(R.id.environment_information)
initListeners()
viewModel = getViewModel()!!
viewModel = getViewModel()
}
override fun onResume() {
@ -87,7 +87,7 @@ class RemoteSettingsActivity: BaseActivity() {
return super.onPrepareOptionsMenu(menu)
}
override fun <T : ViewModel<*>> createViewModel(): T? {
override fun <T : ViewModel<*>> createViewModel(): T {
@Suppress("unchecked_cast")
return RemoteSettingsViewModel(data.wss.environment) as T
}

View File

@ -318,7 +318,7 @@ class SettingsActivity : BaseActivity() {
}
else {
showSnackbar(
findViewById<View>(android.R.id.content),
findViewById(android.R.id.content),
R.string.snackbar_saved_connection_preset)
}
}

View File

@ -4,8 +4,6 @@ import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.websocket.model.*
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy
import kotlin.math.max
@ -98,7 +96,7 @@ class RemoteSettingsViewModel(private val environment: IEnvironment): BaseRemote
Observable.zip<Boolean, Boolean, Boolean>(
gainQuery,
transportQuery,
BiFunction { b1, b2 -> b1 && b2 })
{ b1, b2 -> b1 && b2 })
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { success ->
@ -129,7 +127,7 @@ class RemoteSettingsViewModel(private val environment: IEnvironment): BaseRemote
gainQuery,
outputQuery,
transportQuery,
Function3 { b1, b2, b3 -> b1 && b2 && b3 })
{ b1, b2, b3 -> b1 && b2 && b3 })
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { success ->
@ -156,12 +154,12 @@ class RemoteSettingsViewModel(private val environment: IEnvironment): BaseRemote
gainQuery,
outputsQuery,
transportQuery,
Function3 { gainSettings, outputs, transportType ->
this.gain = gainSettings
this.outputs = outputs
this.transportType = transportType
true
})
{ gainSettings, outputs, transportType ->
this.gain = gainSettings
this.outputs = outputs
this.transportType = transportType
true
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = { state = State.Ready },

View File

@ -161,7 +161,7 @@ abstract class BaseActivity : AppCompatActivity(), ViewModel.Provider, Runner.Ta
toolbar?.let { setSupportActionBar(it) }
}
protected val top: Fragment?
private val top: Fragment?
get() {
return when {
fm.backStackEntryCount == 0 ->
@ -172,7 +172,7 @@ abstract class BaseActivity : AppCompatActivity(), ViewModel.Provider, Runner.Ta
}
}
protected val fm: FragmentManager
private val fm: FragmentManager
get() = supportFragmentManager
protected open val transitionType = Transition.Horizontal
@ -181,7 +181,7 @@ abstract class BaseActivity : AppCompatActivity(), ViewModel.Provider, Runner.Ta
get() = intent?.extras ?: Bundle()
override fun <T: ViewModel<*>> createViewModel(): T? = null
protected fun <T: ViewModel<*>> getViewModel(): T? = mixin(ViewModelMixin::class.java)?.get<T>() as T
protected fun <T: ViewModel<*>> getViewModel(): T = mixin(ViewModelMixin::class.java)?.get() as T
protected fun <T: IMixin> mixin(mixin: T): T = mixins.add(mixin)
protected fun <T: IMixin> mixin(cls: Class<out T>): T? = mixins.get(cls)

View File

@ -7,6 +7,7 @@ import android.content.SharedPreferences
import android.content.res.Resources
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Base64
import android.util.Log
@ -104,7 +105,7 @@ fun AppCompatActivity.enableUpNavigation() {
}
fun AppCompatActivity.setTitleFromIntent(defaultTitle: String) {
val title = this.intent.getStringExtra(Shared.Extra.TITLE_OVERRIDE)
val title = this.intent.getStringExtra(Shared.Extra.TITLE_OVERRIDE) ?: ""
this.title = if (title.isNotEmpty()) title else defaultTitle
}
@ -347,7 +348,7 @@ fun showErrorSnackbar(view: View, stringId: Int, buttonText: String? = null, but
showErrorSnackbar(view, Application.instance.getString(stringId), buttonText, buttonCb)
fun AppCompatActivity.showErrorSnackbar(stringId: Int, buttonText: String? = null, buttonCb: ((View) -> Unit)? = null) =
showErrorSnackbar(this.findViewById<View>(android.R.id.content), stringId, buttonText, buttonCb)
showErrorSnackbar(this.findViewById(android.R.id.content), stringId, buttonText, buttonCb)
/*
*
@ -415,7 +416,7 @@ fun DialogFragment.showKeyboard() =
fun DialogFragment.hideKeyboard() {
val fragmentActivity = activity!! /* keep it in the closure so it doesn't get gc'd */
Handler().postDelayed({
Handler(Looper.getMainLooper()).postDelayed({
hideKeyboard(
fragmentActivity,
fragmentActivity.findViewById(android.R.id.content))

View File

@ -53,7 +53,7 @@ open class BaseDialogFragment: DialogFragment(), ViewModel.Provider {
}
override fun <T: ViewModel<*>> createViewModel(): T? = null
@Suppress("unused") protected fun <T: ViewModel<*>> getViewModel(): T? = mixin(ViewModelMixin::class.java)?.get<T>() as T
@Suppress("unused") protected fun <T: ViewModel<*>> getViewModel(): T = mixin(ViewModelMixin::class.java)?.get() as T
protected fun <T: IMixin> mixin(mixin: T): T = mixins.add(mixin)
protected fun <T: IMixin> mixin(cls: Class<out T>): T? = mixins.get(cls)

View File

@ -5,6 +5,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
@ -34,7 +35,7 @@ import io.reactivex.disposables.CompositeDisposable
open class BaseFragment: Fragment(), ViewModel.Provider, IBackHandler {
private val mixins = MixinSet()
protected val handler = Handler()
protected val handler = Handler(Looper.getMainLooper())
protected lateinit var prefs: SharedPreferences
protected val component: ViewComponent =
DaggerViewComponent.builder()
@ -172,7 +173,7 @@ open class BaseFragment: Fragment(), ViewModel.Provider, IBackHandler {
toolbar?.collapseActionViewIfExpanded() ?: false
override fun <T: ViewModel<*>> createViewModel(): T? = null
@Suppress protected fun <T: ViewModel<*>> getViewModel(): T? = mixin(ViewModelMixin::class.java)?.get<T>() as T
@Suppress protected fun <T: ViewModel<*>> getViewModel(): T = mixin(ViewModelMixin::class.java)?.get() as T
protected fun <T: IMixin> mixin(mixin: T): T = mixins.add(mixin)
protected fun <T: IMixin> mixin(cls: Class<out T>): T? = mixins.get(cls)

View File

@ -18,6 +18,7 @@ import io.casey.musikcube.remote.ui.shared.extension.topOfStack
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.view.InterceptTouchFrameLayout
import me.zhanghai.android.materialprogressbar.MaterialProgressBar
import kotlin.math.abs
class TransportFragment: BaseFragment() {
private lateinit var rootView: View
@ -34,7 +35,7 @@ class TransportFragment: BaseFragment() {
var modelChangedListener: ((TransportFragment) -> Unit)? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
{
this.rootView = inflater.inflate(R.layout.transport_fragment, container, false)
progress = this.rootView.findViewById(R.id.progress)
@ -240,8 +241,8 @@ class TransportFragment: BaseFragment() {
MotionEvent.ACTION_MOVE -> {
val x = ev.x.toInt()
val y = ev.y.toInt()
totalDx += Math.abs(lastX - x)
totalDy += Math.abs(lastY - y)
totalDx += abs(lastX - x)
totalDy += abs(lastY - y)
lastX = x
lastY = y
}

View File

@ -81,7 +81,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
if (pendingCode == request) {
if (result == Activity.RESULT_OK && data != null) {
val playlistId = data.getLongExtra(Category.Extra.ID, -1L)
val playlistName = data.getStringExtra(Category.Extra.NAME)
val playlistName = data.getStringExtra(Category.Extra.NAME) ?: ""
if (playlistId != -1L) {
completion?.invoke(playlistId, playlistName)
}
@ -90,7 +90,7 @@ class ItemContextMenuMixin(private val activity: AppCompatActivity,
completion = null
}
else if (result == Activity.RESULT_OK && request == REQUEST_EDIT_PLAYLIST && data != null) {
val playlistName = data.getStringExtra(EditPlaylistActivity.EXTRA_PLAYLIST_NAME)
val playlistName = data.getStringExtra(EditPlaylistActivity.EXTRA_PLAYLIST_NAME) ?: ""
val playlistId = data.getLongExtra(EditPlaylistActivity.EXTRA_PLAYLIST_ID, -1L)
showSnackbar(

View File

@ -7,12 +7,12 @@ import io.casey.musikcube.remote.framework.ViewModel
class ViewModelMixin(private val provider: ViewModel.Provider): MixinBase() {
private var viewModel: ViewModel<*>? = null
fun <T: ViewModel<*>> get(): T? {
fun <T: ViewModel<*>> get(): T {
if (viewModel == null) {
viewModel = provider.createViewModel()
}
@Suppress("unchecked_cast")
return viewModel as T?
return viewModel as T
}
override fun onCreate(bundle: Bundle) {

View File

@ -6,6 +6,7 @@ import io.casey.musikcube.remote.service.websocket.model.IMetadataProxy
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.websocket.model.ITrackListQueryFactory
import io.reactivex.rxkotlin.subscribeBy
import kotlin.math.max
class DefaultSlidingWindow(
private val recyclerView: FastScrollRecyclerView,
@ -41,7 +42,7 @@ class DefaultSlidingWindow(
loadedListener?.onReloaded(count)
},
onError = { _ ->
onError = {
Log.d("DefaultSlidingWindow", "message send failed, likely canceled")
})
@ -92,7 +93,7 @@ class DefaultSlidingWindow(
return /* already in flight */
}
val offset = Math.max(0, index - 10) /* snag a couple before */
val offset = max(0, index - 10) /* snag a couple before */
val limit = windowSize
val pageRequest = queryFactory.page(offset, limit)
@ -119,7 +120,7 @@ class DefaultSlidingWindow(
notifyAdapterChanged()
notifyMetadataLoaded(offset, i)
},
onError = { _ ->
onError = {
Log.d("DefaultSlidingWindow", "message send failed, likely canceled")
})
}

View File

@ -165,7 +165,7 @@ object AlbumArtLookup {
val images = mutableListOf<Pair<Size, String>>()
try {
val json = JSONObject(response.body()?.string())
val json = JSONObject(response.body()?.string() ?: "{}")
val imagesJson = json.getJSONObject("album").getJSONArray("image")
for (i in 0 until imagesJson.length()) {
val imageJson = imagesJson.getJSONObject(i)

View File

@ -120,7 +120,7 @@ class UpdateCheck {
}
private val USER_AGENT by lazy {
"musikdroid ${VERSION}"
"musikdroid $VERSION"
}
}
}

View File

@ -37,7 +37,7 @@ class EditPlaylistActivity: BaseActivity() {
playlistName = extras.getString(EXTRA_PLAYLIST_NAME, "-")
title = getString(R.string.playlist_edit_activity, playlistName)
setContentView(R.layout.edit_playlist_activity)
viewModel = getViewModel()!!
viewModel = getViewModel()
viewModel.attach(data.provider)
val recycler = findViewById<RecyclerView>(R.id.recycler_view)
val touchHelper = ItemTouchHelper(touchHelperCallback)
@ -80,7 +80,7 @@ class EditPlaylistActivity: BaseActivity() {
))
}
override fun <T: ViewModel<*>> createViewModel(): T? {
override fun <T: ViewModel<*>> createViewModel(): T {
@Suppress("unchecked_cast")
return EditPlaylistViewModel(extras.getLong(EXTRA_PLAYLIST_ID, -1L)) as T
}

View File

@ -1,17 +0,0 @@
package io.casey.musikcube.remote;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.72'
ext.kotlin_version = '1.4.10'
repositories {
google()
@ -7,10 +7,10 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2'
}
}