From a6d0f1d35106771df4aaa8a15305ced48020d2b3 Mon Sep 17 00:00:00 2001 From: casey langen Date: Mon, 27 Nov 2017 19:15:45 -0800 Subject: [PATCH] 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. --- src/musikdroid/app/build.gradle | 1 + .../service/gapless/GaplessHeaderService.kt | 9 +-- .../remote/service/playback/PlayerWrapper.kt | 1 - .../remote/ui/home/activity/MainActivity.kt | 61 ++++++++++++++++--- .../app/src/main/res/values/strings.xml | 2 + 5 files changed, 62 insertions(+), 12 deletions(-) diff --git a/src/musikdroid/app/build.gradle b/src/musikdroid/app/build.gradle index 1cd753676..7ec05666e 100644 --- a/src/musikdroid/app/build.gradle +++ b/src/musikdroid/app/build.gradle @@ -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' diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/gapless/GaplessHeaderService.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/gapless/GaplessHeaderService.kt index e91e7ccf8..70e4ca7f2 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/gapless/GaplessHeaderService.kt +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/gapless/GaplessHeaderService.kt @@ -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 } } \ No newline at end of file diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/playback/PlayerWrapper.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/playback/PlayerWrapper.kt index 1ddbc5f15..ccebdb5d4 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/playback/PlayerWrapper.kt +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/service/playback/PlayerWrapper.kt @@ -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 diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/home/activity/MainActivity.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/home/activity/MainActivity.kt index 0fec42132..e7ba60f6d 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/home/activity/MainActivity.kt +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/home/activity/MainActivity.kt @@ -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(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(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 = mutableMapOf( RepeatMode.None to R.string.button_repeat_off, RepeatMode.List to R.string.button_repeat_list, diff --git a/src/musikdroid/app/src/main/res/values/strings.xml b/src/musikdroid/app/src/main/res/values/strings.xml index 916aa8fc3..78ac66b5f 100644 --- a/src/musikdroid/app/src/main/res/values/strings.xml +++ b/src/musikdroid/app/src/main/res/values/strings.xml @@ -139,4 +139,6 @@ could not create playlist \'%s\' playlist \'%s\' deleted could not delete playlist \'%s\' + playback mode + want to listen to music from your phone?\nclick here to switch between remote control and streaming modes.