mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 07:17:50 +07:00
Currently playing music track displayed under Options-Sound (#5357)
This commit is contained in:
@ -474,6 +474,7 @@ Turns between autosaves =
|
||||
Sound effects volume =
|
||||
Music volume =
|
||||
Pause between tracks =
|
||||
Currently playing: [title] =
|
||||
Download music =
|
||||
Downloading... =
|
||||
Could not download music! =
|
||||
|
@ -78,13 +78,39 @@ class MusicController {
|
||||
/** Keeps paths of recently played track to reduce repetition */
|
||||
private val musicHistory = ArrayDeque<String>(musicHistorySize)
|
||||
|
||||
/** One potential listener gets notified when track changes */
|
||||
private var onTrackChangeListener: ((String)->Unit)? = null
|
||||
|
||||
//endregion
|
||||
//region Pure functions
|
||||
|
||||
/** @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()
|
||||
|
||||
/** 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
|
||||
*/
|
||||
@ -112,12 +138,15 @@ class MusicController {
|
||||
// no music to play - begin silence or shut down
|
||||
ticksOfSilence = 0
|
||||
state = if (state == ControllerState.PlaySingle) ControllerState.Shutdown else ControllerState.Silence
|
||||
fireOnChange()
|
||||
} else if (next!!.state.canPlay) {
|
||||
// Next track - if top slot empty and a next exists, move it to top and start
|
||||
current = next
|
||||
next = null
|
||||
if (!current!!.play())
|
||||
state = ControllerState.Shutdown
|
||||
else
|
||||
fireOnChange()
|
||||
} // else wait for the thread of next.load() to finish
|
||||
} else if (!current!!.isPlaying()) {
|
||||
// normal end of track
|
||||
@ -133,10 +162,13 @@ class MusicController {
|
||||
ticksOfSilence = 0
|
||||
chooseTrack()
|
||||
}
|
||||
ControllerState.Shutdown, ControllerState.Idle -> {
|
||||
state = ControllerState.Idle
|
||||
shutdown()
|
||||
ControllerState.Shutdown -> {
|
||||
// Fade out first, when all queue entries are idle set up for real shutdown
|
||||
if (current?.shutdownTick() != false && next?.shutdownTick() != false)
|
||||
state = ControllerState.Idle
|
||||
}
|
||||
ControllerState.Idle ->
|
||||
shutdown() // stops timer so this will not repeat
|
||||
ControllerState.Pause ->
|
||||
current?.timerTick()
|
||||
}
|
||||
@ -311,8 +343,10 @@ class MusicController {
|
||||
}
|
||||
|
||||
/** Forceful shutdown of music playback and timers - see [gracefulShutdown] */
|
||||
fun shutdown() {
|
||||
private fun shutdown() {
|
||||
state = ControllerState.Idle
|
||||
fireOnChange()
|
||||
onTrackChangeListener = null
|
||||
if (musicTimer != null) {
|
||||
musicTimer!!.cancel()
|
||||
musicTimer = null
|
||||
|
@ -99,14 +99,17 @@ class MusicTrackController(private var volume: Float) {
|
||||
}
|
||||
private fun fadeOutStep() {
|
||||
// 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
|
||||
if (fadeVolume >= 0.001f && music != null && music!!.isPlaying) {
|
||||
music!!.volume = volume * fadeVolume
|
||||
return
|
||||
}
|
||||
fadeVolume = 0f
|
||||
music!!.volume = 0f
|
||||
music!!.pause()
|
||||
try {
|
||||
if (fadeVolume >= 0.001f && music != null && music!!.isPlaying) {
|
||||
music!!.volume = volume * fadeVolume
|
||||
return
|
||||
}
|
||||
fadeVolume = 0f
|
||||
music!!.volume = 0f
|
||||
music!!.pause()
|
||||
} catch (_: Throwable) {}
|
||||
state = State.Idle
|
||||
}
|
||||
|
||||
@ -121,6 +124,19 @@ class MusicTrackController(private var volume: Float) {
|
||||
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 */
|
||||
fun isPlaying() = state.canPlay && music?.isPlaying == true
|
||||
|
||||
|
@ -84,6 +84,7 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
|
||||
}
|
||||
|
||||
addCloseButton {
|
||||
previousScreen.game.musicController.onChange(null)
|
||||
previousScreen.game.limitOrientationsHelper?.allowPortrait(settings.allowAndroidPortrait)
|
||||
if (previousScreen is WorldScreen)
|
||||
previousScreen.enableNextTurnButtonAfterOptions()
|
||||
@ -210,6 +211,7 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
|
||||
if (previousScreen.game.musicController.isMusicAvailable()) {
|
||||
addMusicVolumeSlider()
|
||||
addMusicPauseSlider()
|
||||
addMusicCurrentlyPlaying()
|
||||
} else {
|
||||
addDownloadMusic()
|
||||
}
|
||||
@ -456,6 +458,17 @@ class OptionsPopup(val previousScreen: CameraStageBaseScreen) : Popup(previousSc
|
||||
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() {
|
||||
val downloadMusicButton = "Download music".toTextButton()
|
||||
add(downloadMusicButton).colspan(2).row()
|
||||
|
Reference in New Issue
Block a user