Addded a new setting to musikdroid that allows automatic playback

transfer from client to server when a headset is disconnected.
This commit is contained in:
casey langen 2018-02-19 14:40:24 -08:00
parent c87ea056dd
commit a1cc7c5422
12 changed files with 93 additions and 39 deletions

View File

@ -0,0 +1,47 @@
package io.casey.musikcube.remote.service.playback
import android.content.Context
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
sealed class Playback {
enum class SwitchMode { Transfer, Copy, Swap }
companion object {
fun transferPlayback(context: Context, mode: SwitchMode) {
transferPlayback(context, PlaybackMixin(), mode)
}
fun transferPlayback(context: Context, playback: PlaybackMixin, mode: SwitchMode) {
val isStreaming = playback.service is StreamingPlaybackService
if (mode == SwitchMode.Swap) {
if (isStreaming) {
playback.service.pause()
}
}
else {
playback.connectAll()
val streaming = PlaybackServiceFactory.streaming(context)
val remote = PlaybackServiceFactory.remote(context)
if (!isStreaming) {
streaming.playFrom(remote)
if (mode == SwitchMode.Transfer) {
remote.pause()
}
}
else {
remote.playFrom(streaming)
if (mode == SwitchMode.Transfer) {
streaming.pause()
}
}
}
playback.reload()
}
}
}

View File

@ -16,11 +16,11 @@ enum class PlaybackState constructor(private val rawValue: String) {
else if (Playing.rawValue == rawValue) {
return Playing
}
else if (Paused.rawValue == rawValue) {
else if (Paused.rawValue == rawValue || "prepared" == rawValue) {
return Paused
}
throw IllegalArgumentException("rawValue matches invalid")
throw IllegalArgumentException("rawValue '$rawValue' is unknown")
}
}
}

View File

@ -24,6 +24,7 @@ import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.injection.GlideApp
import io.casey.musikcube.remote.injection.GlideRequest
import io.casey.musikcube.remote.service.playback.Playback
import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory
import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService
@ -475,7 +476,21 @@ class SystemService : Service() {
private val headsetUnpluggedReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
playback?.pause()
playback?.let { pb ->
val switchOnDisconnect = prefs.getBoolean(
Prefs.Key.TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT,
Prefs.Default.TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT)
val isPlaying =
(pb.state == PlaybackState.Playing) ||
(pb.state == PlaybackState.Buffering)
pb.pause()
if (switchOnDisconnect && isPlaying) {
Playback.transferPlayback(this@SystemService, Playback.SwitchMode.Transfer)
}
}
}
}
}

View File

@ -605,7 +605,7 @@ class WebSocketService constructor(private val context: Context) {
private val AUTO_DISCONNECT_DELAY_MILLIS = 10000L
private val FLAG_AUTHENTICATION_FAILED = 0xbeef
private val WEBSOCKET_FLAG_POLICY_VIOLATION = 1008
private val MINIMUM_SUPPORTED_API_VERSION = 14
private val MINIMUM_SUPPORTED_API_VERSION = 15
private val MESSAGE_BASE = 0xcafedead.toInt()
private val MESSAGE_CONNECT_THREAD_FINISHED = MESSAGE_BASE + 0

View File

@ -14,6 +14,7 @@ import android.view.*
import android.widget.*
import com.wooplr.spotlight.SpotlightView
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.playback.Playback
import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory
import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.RepeatMode
@ -42,8 +43,6 @@ import io.casey.musikcube.remote.ui.shared.util.UpdateCheck
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
class MainActivity : BaseActivity() {
private enum class SwitchMode { Transfer, Copy, Swap }
private val handler = Handler()
private var updateCheck: UpdateCheck = UpdateCheck()
private var seekbarValue = -1
@ -148,8 +147,8 @@ class MainActivity : BaseActivity() {
popup.setOnMenuItemClickListener { it ->
when(it.itemId) {
R.id.menu_switch_seamless -> togglePlaybackService(SwitchMode.Transfer)
R.id.menu_switch_copy -> togglePlaybackService(SwitchMode.Copy)
R.id.menu_switch_seamless -> togglePlaybackService(Playback.SwitchMode.Transfer)
R.id.menu_switch_copy -> togglePlaybackService(Playback.SwitchMode.Copy)
else -> { }
}
true
@ -244,7 +243,7 @@ class MainActivity : BaseActivity() {
Prefs.Key.STREAMING_PLAYBACK,
Prefs.Default.STREAMING_PLAYBACK)
private fun togglePlaybackService(mode: SwitchMode = SwitchMode.Swap) {
private fun togglePlaybackService(mode: Playback.SwitchMode = Playback.SwitchMode.Swap) {
val isStreaming = isStreamingSelected
prefs.edit().putBoolean(Prefs.Key.STREAMING_PLAYBACK, !isStreaming)?.apply()
@ -255,32 +254,7 @@ class MainActivity : BaseActivity() {
showSnackbar(mainLayout, messageId)
if (mode == SwitchMode.Swap) {
if (isStreaming) {
playback.service.pause()
}
}
else {
playback.connectAll()
val streaming = PlaybackServiceFactory.streaming(this)
val remote = PlaybackServiceFactory.remote(this)
if (!isStreaming) {
streaming.playFrom(remote)
if (mode == SwitchMode.Transfer) {
remote.pause()
}
}
else {
remote.playFrom(streaming)
if (mode == SwitchMode.Transfer) {
streaming.pause()
}
}
}
playback.reload()
Playback.transferPlayback(this, playback, mode)
invalidateOptionsMenu()
rebindUi()

View File

@ -43,6 +43,7 @@ class SettingsActivity : BaseActivity() {
private lateinit var softwareVolume: CheckBox
private lateinit var sslCheckbox: CheckBox
private lateinit var certCheckbox: CheckBox
private lateinit var transferCheckbox: CheckBox
private lateinit var bitrateSpinner: Spinner
private lateinit var cacheSpinner: Spinner
private lateinit var prefs: SharedPreferences
@ -140,6 +141,10 @@ class SettingsActivity : BaseActivity() {
Keys.DISK_CACHE_SIZE_INDEX, Defaults.DISK_CACHE_SIZE_INDEX))
/* advanced */
transferCheckbox.isChecked = prefs.getBoolean(
Keys.TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT,
Defaults.TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT)
albumArtCheckbox.isChecked = prefs.getBoolean(
Keys.LASTFM_ENABLED, Defaults.LASTFM_ENABLED)
@ -196,6 +201,7 @@ class SettingsActivity : BaseActivity() {
this.cacheSpinner = findViewById(R.id.streaming_disk_cache_spinner)
this.sslCheckbox = findViewById(R.id.ssl_checkbox)
this.certCheckbox = findViewById(R.id.cert_validation)
this.transferCheckbox = findViewById(R.id.transfer_on_disconnect_checkbox)
}
private fun bindListeners() {
@ -262,6 +268,7 @@ class SettingsActivity : BaseActivity() {
.putBoolean(Keys.SOFTWARE_VOLUME, softwareVolume.isChecked)
.putBoolean(Keys.SSL_ENABLED, sslCheckbox.isChecked)
.putBoolean(Keys.CERT_VALIDATION_DISABLED, certCheckbox.isChecked)
.putBoolean(Keys.TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT, transferCheckbox.isChecked)
.putInt(Keys.TRANSCODER_BITRATE_INDEX, bitrateSpinner.selectedItemPosition)
.putInt(Keys.DISK_CACHE_SIZE_INDEX, cacheSpinner.selectedItemPosition)
.apply()

View File

@ -16,6 +16,7 @@ class Prefs {
const val TRANSCODER_BITRATE_INDEX = "transcoder_bitrate_index"
const val DISK_CACHE_SIZE_INDEX = "disk_cache_size_index"
const val UPDATE_DIALOG_SUPPRESSED_VERSION = "update_dialog_suppressed_version"
const val TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT = "transfer_to_server_on_headset_disconnect"
const val DEVICE_ID = "device_id"
}
}
@ -33,6 +34,7 @@ class Prefs {
const val SSL_ENABLED = false
const val CERT_VALIDATION_DISABLED = false
const val TRANSCODER_BITRATE_INDEX = 0
const val TRANSFER_TO_SERVER_ON_HEADSET_DISCONNECT = false
const val DISK_CACHE_SIZE_INDEX = 2
}
}

View File

@ -10,7 +10,7 @@ import io.casey.musikcube.remote.service.playback.IPlaybackService
import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory
import io.casey.musikcube.remote.ui.settings.constants.Prefs
class PlaybackMixin(var listener: (() -> Unit)? = null): MixinBase() {
class PlaybackMixin(var listener: (() -> Unit)? = { }): MixinBase() {
private lateinit var prefs: SharedPreferences
private val context = Application.instance!!

View File

@ -176,6 +176,14 @@
android:layout_marginBottom="8dp"
android:text="@string/settings_advanced"/>
<CheckBox
android:id="@+id/transfer_on_disconnect_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/theme_foreground"
android:layout_marginLeft="24dp"
android:text="@string/settings_transfer_to_server_on_disconnect"/>
<CheckBox
android:id="@+id/album_art_checkbox"
android:layout_width="match_parent"

View File

@ -120,6 +120,7 @@
<string name="settings_playback_engine_exo">ExoPlayer (stable)</string>
<string name="settings_playback_engine_exo_gapless">ExoPlayer Gapless (default)</string>
<string name="settings_playback_engine_mp">MediaPlayer (legacy)</string>
<string name="settings_transfer_to_server_on_disconnect">transfer playback to server on headset disconnect</string>
<string name="remote_settings_title">remote management</string>
<string name="remote_settings_output_driver">output driver</string>
<string name="remote_settings_output_device">output device</string>

View File

@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.2.10'
ext.kotlin_version = '1.2.21'
repositories {
jcenter()

View File

@ -216,7 +216,7 @@ namespace broadcast {
static auto PLAYBACK_STATE_TO_STRING = makeBimap<musik::core::sdk::PlaybackState, std::string>({
{ musik::core::sdk::PlaybackStopped, "stopped" },
{ musik::core::sdk::PlaybackPlaying, "playing" },
{ musik::core::sdk::PlaybackPrepared, "paused" },
{ musik::core::sdk::PlaybackPrepared, "prepared" },
{ musik::core::sdk::PlaybackPaused, "paused" }
});
@ -237,4 +237,4 @@ static auto TRANSPORT_TYPE_TO_STRING = makeBimap<musik::core::sdk::TransportType
{ musik::core::sdk::TransportType::Crossfade, "crossfade" },
});
static const int ApiVersion = 14;
static const int ApiVersion = 15;