mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-02 11:58:27 +00:00
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:
parent
bbc6dda144
commit
a6d0f1d351
@ -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'
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user