Added a spotlight feature that shows the user how to switch between

remote and streaming mode. Also updated GaplessHeaderService to require
less bandwidth, with more error checking.
This commit is contained in:
casey langen 2017-11-27 19:15:45 -08:00
parent bbc6dda144
commit a6d0f1d351
5 changed files with 62 additions and 12 deletions

View File

@ -92,6 +92,7 @@ dependencies {
implementation 'com.google.android.exoplayer:extension-okhttp:r2.5.4'
implementation 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.github.wooplr:Spotlight:1.2.3'
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'

View File

@ -22,7 +22,7 @@ import javax.inject.Inject
/**
* When MP3 files are transcoded on-demand, the required metadata for gapless playback isn't
* available yet. So, once we know the transocded files have been downloaded, we run this
* service to fetch and replace the first 32000 bytes of the file, which should ensure the
* service to fetch and replace the first few bytes of the file, which should ensure the
* required header is present, and subsequent plays are gapless.
*/
class GaplessHeaderService {
@ -78,11 +78,11 @@ class GaplessHeaderService {
var newState = -1
if (fn.exists()) {
/* the first 32000 bytes should be more than enough to snag the
/* the first few bytes should be more than enough to snag the
LAME header that contains gapless playback metadata. just rewrite
those bytes in the already-downloaded file */
val req = Request.Builder()
.addHeader("Range", "bytes=0-32000")
.addHeader("Range", "bytes=0-$HEADER_SIZE_BYTES")
.url(url)
.build()
@ -100,7 +100,7 @@ class GaplessHeaderService {
if (bytes?.isNotEmpty() == true) {
RandomAccessFile(fn, "rw").use {
it.seek(0)
it.write(bytes)
it.write(bytes, 0, Math.min(bytes.size, HEADER_SIZE_BYTES))
}
newState = GaplessTrack.UPDATED
}
@ -120,5 +120,6 @@ class GaplessHeaderService {
companion object {
val MESSAGE_PROCESS = 1
val HEADER_SIZE_BYTES = 6400
}
}

View File

@ -106,7 +106,6 @@ abstract class PlayerWrapper {
}
companion object {
private val TYPE = Type.ExoPlayer
private val DUCK_COEF = 0.2f /* volume = 20% when ducked */
private val DUCK_NONE = -1.0f

View File

@ -4,19 +4,18 @@ import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.*
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.SeekBar
import android.widget.TextView
import com.wooplr.spotlight.SpotlightView
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.RepeatMode
@ -101,6 +100,7 @@ class MainActivity : BaseActivity() {
scheduleUpdateTime(true)
runUpdateCheck()
initObservers()
registerLayoutListener()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -157,9 +157,15 @@ class MainActivity : BaseActivity() {
disposables.add(data.provider.observeState().subscribe(
{ states ->
when (states.first) {
IDataProvider.State.Connected -> rebindUi()
IDataProvider.State.Disconnected -> clearUi()
else -> { }
IDataProvider.State.Connected -> {
rebindUi()
checkShowSpotlight()
}
IDataProvider.State.Disconnected -> {
clearUi()
}
else -> {
}
}
}, { /* error */ }))
@ -351,6 +357,45 @@ class MainActivity : BaseActivity() {
}
}
private fun registerLayoutListener() {
window.decorView.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val toolbarButton = findViewById<View>(R.id.action_remote_toggle)
if (toolbarButton != null && data.provider.state == IDataProvider.State.Connected) {
checkShowSpotlight()
window.decorView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
private fun checkShowSpotlight() {
val toolbarButton = findViewById<View>(R.id.action_remote_toggle)
if (toolbarButton != null) {
SpotlightView.Builder(this@MainActivity)
.introAnimationDuration(400)
.enableRevealAnimation(true)
.performClick(true)
.fadeinTextDuration(400)
.headingTvColor(getColorCompat(R.color.color_accent))
.headingTvSize(24)
.headingTvText(getString(R.string.spotlight_playback_mode_title))
.subHeadingTvColor(Color.parseColor("#ffffff"))
.subHeadingTvSize(16)
.subHeadingTvText(getString(R.string.spotlight_playback_mode_message))
.maskColor(Color.parseColor("#dc000000"))
.target(toolbarButton)
.lineAnimDuration(400)
.lineAndArcColor(getColorCompat(R.color.color_primary))
.dismissOnTouch(true)
.dismissOnBackPress(true)
.enableDismissAfterShown(true)
.usageId(SPOTLIGHT_STREAMING_ID)
.show()
}
}
private fun clearUi() {
metadataView.clear()
rebindUi()
@ -523,6 +568,8 @@ class MainActivity : BaseActivity() {
}
companion object {
private val SPOTLIGHT_STREAMING_ID = "spotlight_streaming_mode"
private var REPEAT_TO_STRING_ID: MutableMap<RepeatMode, Int> = mutableMapOf(
RepeatMode.None to R.string.button_repeat_off,
RepeatMode.List to R.string.button_repeat_list,

View File

@ -139,4 +139,6 @@
<string name="playlist_not_created">could not create playlist \'%s\'</string>
<string name="playlist_deleted">playlist \'%s\' deleted</string>
<string name="playlist_not_deleted">could not delete playlist \'%s\'</string>
<string name="spotlight_playback_mode_title">playback mode</string>
<string name="spotlight_playback_mode_message">want to listen to music from your phone?\nclick here to switch between remote control and streaming modes.</string>
</resources>