Currently playing music track displayed under Options-Sound (#5357)

This commit is contained in:
SomeTroglodyte 2021-09-30 05:35:20 +02:00 committed by GitHub
parent 9fe19d0d52
commit 03f374b058
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 12 deletions

View File

@ -474,6 +474,7 @@ Turns between autosaves =
Sound effects volume = Sound effects volume =
Music volume = Music volume =
Pause between tracks = Pause between tracks =
Currently playing: [title] =
Download music = Download music =
Downloading... = Downloading... =
Could not download music! = Could not download music! =

View File

@ -78,13 +78,39 @@ class MusicController {
/** Keeps paths of recently played track to reduce repetition */ /** Keeps paths of recently played track to reduce repetition */
private val musicHistory = ArrayDeque<String>(musicHistorySize) private val musicHistory = ArrayDeque<String>(musicHistorySize)
/** One potential listener gets notified when track changes */
private var onTrackChangeListener: ((String)->Unit)? = null
//endregion //endregion
//region Pure functions //region Pure functions
/** @return the path of the playing track or null if none playing */ /** @return the path of the playing track or null if none playing */
fun currentlyPlaying() = if (state != ControllerState.Playing && state != ControllerState.PlaySingle) null private fun currentlyPlaying(): String = if (state != ControllerState.Playing && state != ControllerState.PlaySingle) ""
else musicHistory.peekLast() else musicHistory.peekLast()
/** Registers a callback that will be called with the new track name every time it changes.
* The track name will be prettified ("Modname: Track" instead of "mods/Modname/music/Track.ogg").
*
* Will be called on a background thread, so please decouple UI access on the receiving side.
*/
fun onChange(listener: ((String)->Unit)?) {
onTrackChangeListener = listener
fireOnChange()
}
private fun fireOnChange() {
val fileName = currentlyPlaying()
if (fileName.isEmpty()) {
onTrackChangeListener?.invoke(fileName)
return
}
val fileNameParts = fileName.split('/')
val modName = if (fileNameParts.size > 1 && fileNameParts[0] == "mods") fileNameParts[1] else ""
var trackName = fileNameParts[if (fileNameParts.size > 3 && fileNameParts[2] == "music") 3 else 1]
for (extension in fileExtensions)
trackName = trackName.removeSuffix(".$extension")
onTrackChangeListener?.invoke(modName + (if (modName.isEmpty()) "" else ": ") + trackName)
}
/** /**
* Determines whether any music tracks are available for the options menu * Determines whether any music tracks are available for the options menu
*/ */
@ -112,12 +138,15 @@ class MusicController {
// no music to play - begin silence or shut down // no music to play - begin silence or shut down
ticksOfSilence = 0 ticksOfSilence = 0
state = if (state == ControllerState.PlaySingle) ControllerState.Shutdown else ControllerState.Silence state = if (state == ControllerState.PlaySingle) ControllerState.Shutdown else ControllerState.Silence
fireOnChange()
} else if (next!!.state.canPlay) { } else if (next!!.state.canPlay) {
// Next track - if top slot empty and a next exists, move it to top and start // Next track - if top slot empty and a next exists, move it to top and start
current = next current = next
next = null next = null
if (!current!!.play()) if (!current!!.play())
state = ControllerState.Shutdown state = ControllerState.Shutdown
else
fireOnChange()
} // else wait for the thread of next.load() to finish } // else wait for the thread of next.load() to finish
} else if (!current!!.isPlaying()) { } else if (!current!!.isPlaying()) {
// normal end of track // normal end of track
@ -133,10 +162,13 @@ class MusicController {
ticksOfSilence = 0 ticksOfSilence = 0
chooseTrack() chooseTrack()
} }
ControllerState.Shutdown, ControllerState.Idle -> { ControllerState.Shutdown -> {
state = ControllerState.Idle // Fade out first, when all queue entries are idle set up for real shutdown
shutdown() if (current?.shutdownTick() != false && next?.shutdownTick() != false)
state = ControllerState.Idle
} }
ControllerState.Idle ->
shutdown() // stops timer so this will not repeat
ControllerState.Pause -> ControllerState.Pause ->
current?.timerTick() current?.timerTick()
} }
@ -311,8 +343,10 @@ class MusicController {
} }
/** Forceful shutdown of music playback and timers - see [gracefulShutdown] */ /** Forceful shutdown of music playback and timers - see [gracefulShutdown] */
fun shutdown() { private fun shutdown() {
state = ControllerState.Idle state = ControllerState.Idle
fireOnChange()
onTrackChangeListener = null
if (musicTimer != null) { if (musicTimer != null) {
musicTimer!!.cancel() musicTimer!!.cancel()
musicTimer = null musicTimer = null

View File

@ -99,14 +99,17 @@ class MusicTrackController(private var volume: Float) {
} }
private fun fadeOutStep() { private fun fadeOutStep() {
// fade-out: linearly ramp fadeVolume to 0.0, then act according to Status (Playing->Silence/Pause/Shutdown) // fade-out: linearly ramp fadeVolume to 0.0, then act according to Status (Playing->Silence/Pause/Shutdown)
// This needs to guard against the music backend breaking mid-fade away during game shutdown
fadeVolume -= fadeStep fadeVolume -= fadeStep
if (fadeVolume >= 0.001f && music != null && music!!.isPlaying) { try {
music!!.volume = volume * fadeVolume if (fadeVolume >= 0.001f && music != null && music!!.isPlaying) {
return music!!.volume = volume * fadeVolume
} return
fadeVolume = 0f }
music!!.volume = 0f fadeVolume = 0f
music!!.pause() music!!.volume = 0f
music!!.pause()
} catch (_: Throwable) {}
state = State.Idle state = State.Idle
} }
@ -121,6 +124,19 @@ class MusicTrackController(private var volume: Float) {
state = fade state = fade
} }
/** Graceful shutdown tick event - fade out then report Idle
* @return `true` shutdown can proceed, `false` still fading out
*/
fun shutdownTick(): Boolean {
if (!state.canPlay) state = State.Idle
if (state == State.Idle) return true
if (state != State.FadeOut) {
state = State.FadeOut
fadeStep = MusicController.defaultFadingStep
}
return timerTick() == State.Idle
}
/** @return [Music.isPlaying] (Gdx music stream is playing) unless [state] says it won't make sense */ /** @return [Music.isPlaying] (Gdx music stream is playing) unless [state] says it won't make sense */
fun isPlaying() = state.canPlay && music?.isPlaying == true fun isPlaying() = state.canPlay && music?.isPlaying == true

View File

@ -84,6 +84,7 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
} }
addCloseButton { addCloseButton {
previousScreen.game.musicController.onChange(null)
previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait) previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait)
if (previousScreen is WorldScreen) if (previousScreen is WorldScreen)
previousScreen.enableNextTurnButtonAfterOptions() previousScreen.enableNextTurnButtonAfterOptions()
@ -210,6 +211,7 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
if (previousScreen.game.musicController.isMusicAvailable()) { if (previousScreen.game.musicController.isMusicAvailable()) {
addMusicVolumeSlider() addMusicVolumeSlider()
addMusicPauseSlider() addMusicPauseSlider()
addMusicCurrentlyPlaying()
} else { } else {
addDownloadMusic() addDownloadMusic()
} }
@ -456,6 +458,17 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
add(pauseLengthSlider).pad(5f).row() add(pauseLengthSlider).pad(5f).row()
} }
private fun Table.addMusicCurrentlyPlaying() {
val label = WrappableLabel("", this.width - 10f, Color(-0x2f5001), 16)
label.wrap = true
add(label).padTop(20f).colspan(2).fillX().row()
previousScreen.game.musicController.onChange {
Gdx.app.postRunnable {
label.setText("Currently playing: [$it]".tr())
}
}
}
private fun Table.addDownloadMusic() { private fun Table.addDownloadMusic() {
val downloadMusicButton = "Download music".toTextButton() val downloadMusicButton = "Download music".toTextButton()
add(downloadMusicButton).colspan(2).row() add(downloadMusicButton).colspan(2).row()