Play sounds on each timer phase

This commit is contained in:
William Brawner 2020-07-04 19:41:45 -07:00
parent 46cb0e3c0a
commit 6ecc7e2a38
9 changed files with 67 additions and 25 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,26 +1,29 @@
package com.wbrawner.trainterval.activetimer package com.wbrawner.trainterval.activetimer
import android.media.AudioManager
import android.media.SoundPool
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.wbrawner.trainterval.Logger import com.wbrawner.trainterval.Logger
import com.wbrawner.trainterval.R import com.wbrawner.trainterval.R
import com.wbrawner.trainterval.model.IntervalTimerDao import com.wbrawner.trainterval.model.IntervalTimerDao
import com.wbrawner.trainterval.model.Phase
import kotlinx.android.synthetic.main.fragment_active_timer.* import kotlinx.android.synthetic.main.fragment_active_timer.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class ActiveTimerFragment : Fragment() { class ActiveTimerFragment : Fragment() {
private var coroutineScope: CoroutineScope? = null private var coroutineScope: CoroutineScope? = null
@ -28,11 +31,29 @@ class ActiveTimerFragment : Fragment() {
private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") }) private val logger: Logger by inject(parameters = { parametersOf("ActiveTimerStore") })
private val timerDao: IntervalTimerDao by inject() private val timerDao: IntervalTimerDao by inject()
private var timerId: Long = 0 private var timerId: Long = 0
private lateinit var soundPool: SoundPool
private val soundIds = mutableListOf<Int>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
timerId = requireArguments().getLong(EXTRA_TIMER_ID) timerId = requireArguments().getLong(EXTRA_TIMER_ID)
setHasOptionsMenu(true) setHasOptionsMenu(true)
soundPool = SoundPool.Builder()
.setMaxStreams(Phase.values().size)
.build()
activeTimerViewModel.viewModelScope.launch {
if (soundIds.isEmpty()) {
val assetManager = context?.assets ?: return@launch
withContext(Dispatchers.IO) {
Phase.values().forEachIndexed { index, phase ->
soundIds.add(
index,
soundPool.load(assetManager.openFd("audio/${phase.soundFile}"), 1)
)
}
}
}
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean = override fun onOptionsItemSelected(item: MenuItem): Boolean =
@ -94,6 +115,17 @@ class ActiveTimerFragment : Fragment() {
timeRemaining.text = state.timeRemaining timeRemaining.text = state.timeRemaining
timerSets.text = state.currentSet.toString() timerSets.text = state.currentSet.toString()
timerRounds.text = state.currentRound.toString() timerRounds.text = state.currentRound.toString()
state.soundId?.let {
playSound(soundIds[it])
}
}
private fun playSound(soundId: Int) {
val context = context ?: return
val audioManager = getSystemService(context, AudioManager::class.java) ?: return
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
val volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume
soundPool.play(soundId, volume, volume, 1, 0, 1f)
} }
override fun onDestroyView() { override fun onDestroyView() {

View file

@ -42,16 +42,7 @@ class ActiveTimerViewModel : ViewModel() {
currentRound = timer.cycles currentRound = timer.cycles
timeRemaining = timer.warmUpDuration timeRemaining = timer.warmUpDuration
currentPhase = Phase.WARM_UP currentPhase = Phase.WARM_UP
timerState.postValue( updateTimer()
TimerRunningState(
timer,
timeRemaining,
currentSet,
currentRound,
currentPhase,
timerJob != null
)
)
} }
} }
@ -220,6 +211,7 @@ sealed class IntervalTimerActiveState {
val timeRemaining: String, val timeRemaining: String,
val currentSet: Int, val currentSet: Int,
val currentRound: Int, val currentRound: Int,
val soundId: Int?,
@ColorRes val timerBackground: Int, @ColorRes val timerBackground: Int,
@DrawableRes val playPauseIcon: Int @DrawableRes val playPauseIcon: Int
) : IntervalTimerActiveState() { ) : IntervalTimerActiveState() {
@ -236,6 +228,9 @@ sealed class IntervalTimerActiveState {
currentSet = currentSet, currentSet = currentSet,
currentRound = currentRound, currentRound = currentRound,
timerBackground = phase.colorRes, timerBackground = phase.colorRes,
soundId = if (timerRunning && timeRemaining == timer.durationForPhase(phase))
phase.ordinal
else null,
playPauseIcon = if (timerRunning) R.drawable.ic_pause else R.drawable.ic_play_arrow playPauseIcon = if (timerRunning) R.drawable.ic_pause else R.drawable.ic_play_arrow
) )
} }

View file

@ -18,14 +18,25 @@ data class IntervalTimer(
val coolDownDuration: Long = TimeUnit.MINUTES.toMillis(5), val coolDownDuration: Long = TimeUnit.MINUTES.toMillis(5),
val sets: Int = 4, val sets: Int = 4,
val cycles: Int = 1 val cycles: Int = 1
) ) {
fun durationForPhase(phase: Phase) = when (phase) {
Phase.WARM_UP -> warmUpDuration
Phase.LOW_INTENSITY -> lowIntensityDuration
Phase.HIGH_INTENSITY -> highIntensityDuration
Phase.REST -> restDuration
Phase.COOL_DOWN -> coolDownDuration
}
}
enum class Phase(@ColorRes val colorRes: Int) { enum class Phase(
WARM_UP(R.color.colorSurface), @ColorRes val colorRes: Int,
LOW_INTENSITY(R.color.colorSurfaceLowIntensity), val soundFile: String
HIGH_INTENSITY(R.color.colorSurfaceHighIntensity), ) {
REST(R.color.colorSurfaceRest), WARM_UP(R.color.colorSurface, "warm.mp3"),
COOL_DOWN(R.color.colorSurfaceCoolDown), LOW_INTENSITY(R.color.colorSurfaceLowIntensity, "low.mp3"),
HIGH_INTENSITY(R.color.colorSurfaceHighIntensity, "high.mp3"),
REST(R.color.colorSurfaceRest, "rest.mp3"),
COOL_DOWN(R.color.colorSurfaceCoolDown, "cool.mp3"),
} }
@Dao @Dao

View file

@ -52,13 +52,15 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/label_set" android:text="@string/label_set"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" /> android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="@color/colorOnSurface" />
<TextView <TextView
android:id="@+id/timerSets" android:id="@+id/timerSets"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline3"
android:textColor="@color/colorOnSurface"
tools:text="5" /> tools:text="5" />
</LinearLayout> </LinearLayout>
@ -77,13 +79,15 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/label_round" android:text="@string/label_round"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" /> android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="@color/colorOnSurface" />
<TextView <TextView
android:id="@+id/timerRounds" android:id="@+id/timerRounds"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline3"
android:textColor="@color/colorOnSurface"
tools:text="4" /> tools:text="4" />
</LinearLayout> </LinearLayout>