Added the ability to modify the playback transport from the android

client.
This commit is contained in:
casey langen 2018-01-15 21:37:19 -08:00
parent 7bc5928e5a
commit 27c74b1d3b
12 changed files with 173 additions and 50 deletions

View File

@ -36,7 +36,9 @@ class Messages {
SetDefaultOutputDriver("set_default_output_driver"),
GetGainSettings("get_gain_settings"),
UpdateGainSettings("update_gain_settings"),
RunIndexer("run_indexer");
RunIndexer("run_indexer"),
GetTransportType("get_transport_type"),
SetTransportType("set_transport_type");
override fun toString(): String = rawValue
fun matches(name: String): Boolean = (rawValue == name)

View File

@ -52,10 +52,13 @@ interface IDataProvider {
fun setDefaultOutputDriver(driverName: String, deviceId: String = "default"): Observable<Boolean>
fun getGainSettings(): Observable<IGainSettings>
fun updateGainSettings(replayGainMode: IGainSettings.ReplayGainMode, preampGain: Float): Observable<Boolean>
fun updateGainSettings(replayGainMode: ReplayGainMode, preampGain: Float): Observable<Boolean>
fun reindexMetadata(): Observable<Boolean>
fun rebuildMetadata(): Observable<Boolean>
fun getTransportType(): Observable<TransportType>
fun setTransportType(type: TransportType): Observable<Boolean>
val state: State
}

View File

@ -1,22 +1,6 @@
package io.casey.musikcube.remote.service.websocket.model
interface IGainSettings {
enum class ReplayGainMode(val rawValue: String) {
Disabled("disabled"),
Album("album"),
Track("track");
companion object {
fun find(raw: String): ReplayGainMode {
values().forEach {
if (it.rawValue == raw) {
return it
}
}
return Disabled
}
}
}
val replayGainMode: ReplayGainMode
val preampGain: Float

View File

@ -0,0 +1,18 @@
package io.casey.musikcube.remote.service.websocket.model
enum class ReplayGainMode(val rawValue: String) {
Disabled("disabled"),
Album("album"),
Track("track");
companion object {
fun find(raw: String): ReplayGainMode {
values().forEach {
if (it.rawValue == raw) {
return it
}
}
return Disabled
}
}
}

View File

@ -0,0 +1,17 @@
package io.casey.musikcube.remote.service.websocket.model
enum class TransportType(val rawValue: String) {
Gapless("gapless"),
Crossfade("crossfade");
companion object {
fun find(raw: String): TransportType {
values().forEach {
if (it.rawValue == raw) {
return it
}
}
return Gapless
}
}
}

View File

@ -435,7 +435,7 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread())
}
override fun updateGainSettings(replayGainMode: IGainSettings.ReplayGainMode, preampGain: Float): Observable<Boolean> {
override fun updateGainSettings(replayGainMode: ReplayGainMode, preampGain: Float): Observable<Boolean> {
val message = SocketMessage.Builder
.request(Messages.Request.UpdateGainSettings)
.addOption(Messages.Key.REPLAYGAIN_MODE, replayGainMode.rawValue)
@ -455,6 +455,31 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
return runIndexer(Messages.Value.REBUILD)
}
override fun getTransportType(): Observable<TransportType> {
val message = SocketMessage.Builder
.request(Messages.Request.GetTransportType)
.build()
return service.observe(message, client)
.flatMap<TransportType> { socketMessage ->
Observable.just(TransportType.find(
socketMessage.getStringOption(Messages.Key.TYPE,
TransportType.Gapless.rawValue)))
}
.observeOn(AndroidSchedulers.mainThread())
}
override fun setTransportType(type: TransportType): Observable<Boolean> {
val message = SocketMessage.Builder
.request(Messages.Request.SetTransportType)
.addOption(Messages.Key.TYPE, type.rawValue)
.build()
return service.observe(message, client)
.flatMap<Boolean> { socketMessage -> isSuccessful(socketMessage) }
.observeOn(AndroidSchedulers.mainThread())
}
override fun observeState(): Observable<Pair<IDataProvider.State, IDataProvider.State>> =
connectionStatePublisher.observeOn(AndroidSchedulers.mainThread())

View File

@ -2,11 +2,12 @@ package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IGainSettings
import io.casey.musikcube.remote.service.websocket.model.ReplayGainMode
import org.json.JSONObject
class RemoteGainSettings(val json: JSONObject): IGainSettings {
override val replayGainMode: IGainSettings.ReplayGainMode
get() = IGainSettings.ReplayGainMode.find(json.optString(Messages.Key.REPLAYGAIN_MODE, "disabled"))
override val replayGainMode: ReplayGainMode
get() = ReplayGainMode.find(json.optString(Messages.Key.REPLAYGAIN_MODE, "disabled"))
override val preampGain: Float
get() = json.optDouble(Messages.Key.PREAMP_GAIN, 0.0).toFloat()
}

View File

@ -8,8 +8,9 @@ import android.widget.*
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.framework.ViewModel
import io.casey.musikcube.remote.service.websocket.model.IDevice
import io.casey.musikcube.remote.service.websocket.model.IGainSettings
import io.casey.musikcube.remote.service.websocket.model.IOutput
import io.casey.musikcube.remote.service.websocket.model.ReplayGainMode
import io.casey.musikcube.remote.service.websocket.model.TransportType
import io.casey.musikcube.remote.ui.settings.viewmodel.RemoteSettingsViewModel
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.extension.slideThisDown
@ -25,6 +26,7 @@ class RemoteSettingsActivity: BaseActivity() {
private lateinit var loadingOverlay: View
private lateinit var driverSpinner: Spinner
private lateinit var deviceSpinner: Spinner
private lateinit var transportSpinner: Spinner
private lateinit var replayGainSpinner: Spinner
private lateinit var preampSeekbar: SeekBar
private lateinit var preampTextView: TextView
@ -45,6 +47,7 @@ class RemoteSettingsActivity: BaseActivity() {
loadingOverlay = findViewById(R.id.loading_overlay)
driverSpinner = findViewById(R.id.output_driver_spinner)
deviceSpinner = findViewById(R.id.output_device_spinner)
transportSpinner = findViewById(R.id.transport_spinner)
replayGainSpinner = findViewById(R.id.replaygain_spinner)
preampSeekbar = findViewById(R.id.gain_seekbar)
preampTextView = findViewById(R.id.gain_textview)
@ -91,6 +94,7 @@ class RemoteSettingsActivity: BaseActivity() {
driverSpinner.onItemSelectedListener = driverChangeListener
deviceSpinner.adapter = DevicesAdapter(viewModel.devicesAt(viewModel.selectedDriverIndex))
deviceSpinner.setSelection(viewModel.selectedDeviceIndex)
transportSpinner.setSelection(TRANSPORT_TYPE_TO_INDEX[viewModel.transportType]!!)
replayGainSpinner.setSelection(REPLAYGAIN_MODE_TO_INDEX[viewModel.replayGainMode]!!)
initialized = true
}
@ -119,7 +123,9 @@ class RemoteSettingsActivity: BaseActivity() {
}
}
viewModel.save(replayGainMode, preampGain, driverName, deviceId)
val transport = indexToTransportType(transportSpinner.selectedItemPosition)
viewModel.save(replayGainMode, preampGain, transport, driverName, deviceId)
}
private fun initListeners() {
@ -145,10 +151,10 @@ class RemoteSettingsActivity: BaseActivity() {
/* replaygain / preamp */
val replayGainModes = ArrayAdapter.createFromResource(
this, R.array.replaygain_mode_array, android.R.layout.simple_spinner_dropdown_item)
this, R.array.replaygain_mode_array,
android.R.layout.simple_spinner_dropdown_item)
replayGainModes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
replayGainSpinner.adapter = replayGainModes
preampSeekbar.setOnSeekBarChangeListener(object:SeekBar.OnSeekBarChangeListener {
@ -166,6 +172,14 @@ class RemoteSettingsActivity: BaseActivity() {
})
preampSeekbar.progress = 2000
/* transport */
val transportModes = ArrayAdapter.createFromResource(
this, R.array.transport_type_array,
android.R.layout.simple_spinner_dropdown_item)
transportModes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
transportSpinner.adapter = transportModes
}
private fun initObservers() {
@ -238,17 +252,30 @@ class RemoteSettingsActivity: BaseActivity() {
companion object {
val REPLAYGAIN_MODE_TO_INDEX = mapOf(
IGainSettings.ReplayGainMode.Disabled to 0,
IGainSettings.ReplayGainMode.Track to 1,
IGainSettings.ReplayGainMode.Album to 2)
ReplayGainMode.Disabled to 0,
ReplayGainMode.Track to 1,
ReplayGainMode.Album to 2)
fun indexToReplayGain(index: Int): IGainSettings.ReplayGainMode {
val TRANSPORT_TYPE_TO_INDEX = mapOf(
TransportType.Gapless to 0,
TransportType.Crossfade to 1)
fun indexToReplayGain(index: Int): ReplayGainMode {
REPLAYGAIN_MODE_TO_INDEX.forEach {
if (it.value == index) {
return it.key
}
}
return IGainSettings.ReplayGainMode.Disabled
return ReplayGainMode.Disabled
}
fun indexToTransportType(index: Int): TransportType {
TRANSPORT_TYPE_TO_INDEX.forEach {
if (it.value == index) {
return it.key
}
}
return TransportType.Gapless
}
fun getStartIntent(context: Context):Intent =

View File

@ -7,6 +7,7 @@ import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function3
import io.reactivex.rxkotlin.subscribeBy
class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
@ -33,6 +34,11 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
}
}
var transportType: TransportType = TransportType.Gapless
private set(value) {
field = value
}
val driverName: String
get() {
outputs?.let {
@ -70,12 +76,12 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
return Math.max(0, deviceIndex)
}
val replayGainMode: IGainSettings.ReplayGainMode
val replayGainMode: ReplayGainMode
get() {
gain?.let {
return it.replayGainMode
}
return IGainSettings.ReplayGainMode.Disabled
return ReplayGainMode.Disabled
}
val preampGain: Float
@ -103,12 +109,19 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
return listOf()
}
private fun save(replayGainMode: IGainSettings.ReplayGainMode, preampGain: Float)
private fun save(replayGainMode: ReplayGainMode,
preampGain: Float,
transport: TransportType)
{
provider?.let {
val oldState = state
state = State.Saving
it.updateGainSettings(replayGainMode, preampGain)
val gainQuery = it.updateGainSettings(replayGainMode, preampGain)
val transportQuery = it.setTransportType(transport)
Observable.zip<Boolean, Boolean, Boolean>(
gainQuery,
transportQuery,
BiFunction { b1, b2 -> b1 && b2 })
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
@ -119,13 +132,14 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
}
}
fun save(replayGainMode: IGainSettings.ReplayGainMode,
fun save(replayGainMode: ReplayGainMode,
preampGain: Float,
transport: TransportType,
outputDriver: String,
outputDeviceId: String)
{
if (Strings.empty(outputDriver)) {
save(replayGainMode, preampGain)
save(replayGainMode, preampGain, transport)
}
else {
provider?.let {
@ -133,7 +147,12 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
state = State.Saving
val gainQuery = it.updateGainSettings(replayGainMode, preampGain)
val outputQuery = it.setDefaultOutputDriver(outputDriver, outputDeviceId)
Observable.zip<Boolean, Boolean, Boolean>(gainQuery, outputQuery, BiFunction { b1, b2 -> b1 && b2 })
val transportQuery = it.setTransportType(transport)
Observable.zip<Boolean, Boolean, Boolean, Boolean>(
gainQuery,
outputQuery,
transportQuery,
Function3 { b1, b2, b3 -> b1 && b2 && b3 })
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
@ -162,22 +181,21 @@ class RemoteSettingsViewModel: ViewModel<RemoteSettingsViewModel.State>() {
state = State.Loading
val gainQuery = it.getGainSettings()
val outputsQuery = it.listOutputDrivers()
Observable.zip<IGainSettings, IOutputs, Pair<IGainSettings, IOutputs>>(
val transportQuery = it.getTransportType()
Observable.zip<IGainSettings, IOutputs, TransportType, Boolean>(
gainQuery,
outputsQuery,
BiFunction { gainSettings, outputs ->
Pair(gainSettings, outputs)
transportQuery,
Function3 { gainSettings, outputs, transportType ->
this.gain = gainSettings
this.outputs = outputs
this.transportType = transportType
true
})
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {
gain = it.first
outputs = it.second
state = State.Ready
},
onError = {
state = State.Disconnected
})
onNext = { state = State.Ready },
onError = { state = State.Disconnected })
}
}

View File

@ -46,7 +46,24 @@
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="32dp"/>
android:layout_height="24dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dp"
android:text="@string/remote_settings_transport_type"/>
<Spinner
android:id="@+id/transport_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="24dp"/>
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="24dp"/>
<TextView
android:layout_width="wrap_content"
@ -97,7 +114,7 @@
<android.support.v4.widget.Space
android:layout_width="0dp"
android:layout_height="32dp"/>
android:layout_height="24dp"/>
<TextView
android:layout_width="wrap_content"
@ -138,6 +155,7 @@
android:id="@+id/loading_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:background="@color/theme_button_background_transparent">
<ProgressBar
android:layout_width="wrap_content"

View File

@ -127,6 +127,9 @@
<string name="remote_settings_metadata_actions">metadata library</string>
<string name="remote_settings_reindex_button">rescan</string>
<string name="remote_settings_rebuild_button">rebuild</string>
<string name="remote_settings_transport_type">playback transport</string>
<string name="transport_type_gapless">gapless</string>
<string name="transport_type_crossfade">crossfade</string>
<string name="replaygain_mode_disabled">disabled</string>
<string name="replaygain_mode_track">track</string>
<string name="replaygain_mode_album">album</string>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="transport_type_array">
<item>@string/transport_type_gapless</item>
<item>@string/transport_type_crossfade</item>
</string-array>
</resources>