mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Play theme song for movies (#149)
Closes #133 Fixes #142 Theme songs will play for both TV shows and movies, if available. Also, if there are multiple theme songs, one will be picked randomly instead of always playing the first. Additionally, the music now waits to start until the UI has loaded.
This commit is contained in:
parent
d658ec16f3
commit
08577b8a31
6 changed files with 121 additions and 71 deletions
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
|
@ -109,6 +110,15 @@ fun MovieDetails(
|
||||||
-> LoadingPage()
|
-> LoadingPage()
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
item?.let { movie ->
|
item?.let { movie ->
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
viewModel.maybePlayThemeSong(
|
||||||
|
destination.itemId,
|
||||||
|
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
||||||
|
)
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
MovieDetailsContent(
|
MovieDetailsContent(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
movie = movie,
|
movie = movie,
|
||||||
|
|
@ -118,10 +128,10 @@ fun MovieDetails(
|
||||||
trailers = trailers,
|
trailers = trailers,
|
||||||
similar = similar,
|
similar = similar,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
viewModel.navigationManager.navigateTo(it.destination())
|
viewModel.navigateTo(it.destination())
|
||||||
},
|
},
|
||||||
onClickPerson = {
|
onClickPerson = {
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigateTo(
|
||||||
Destination.MediaItem(
|
Destination.MediaItem(
|
||||||
it.id,
|
it.id,
|
||||||
BaseItemKind.PERSON,
|
BaseItemKind.PERSON,
|
||||||
|
|
@ -129,7 +139,7 @@ fun MovieDetails(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
playOnClick = {
|
playOnClick = {
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigateTo(
|
||||||
Destination.Playback(
|
Destination.Playback(
|
||||||
movie.id,
|
movie.id,
|
||||||
it.inWholeMilliseconds,
|
it.inWholeMilliseconds,
|
||||||
|
|
@ -157,7 +167,7 @@ fun MovieDetails(
|
||||||
favorite = movie.data.userData?.isFavorite ?: false,
|
favorite = movie.data.userData?.isFavorite ?: false,
|
||||||
series = null,
|
series = null,
|
||||||
sourceId = chosenStreams?.sourceId,
|
sourceId = chosenStreams?.sourceId,
|
||||||
navigateTo = viewModel.navigationManager::navigateTo,
|
navigateTo = viewModel::navigateTo,
|
||||||
onClickWatch = viewModel::setWatched,
|
onClickWatch = viewModel::setWatched,
|
||||||
onClickFavorite = viewModel::setFavorite,
|
onClickFavorite = viewModel::setFavorite,
|
||||||
onChooseVersion = {
|
onChooseVersion = {
|
||||||
|
|
@ -203,7 +213,7 @@ fun MovieDetails(
|
||||||
trailerOnClick = { trailer ->
|
trailerOnClick = { trailer ->
|
||||||
when (trailer) {
|
when (trailer) {
|
||||||
is LocalTrailer ->
|
is LocalTrailer ->
|
||||||
viewModel.navigationManager.navigateTo(
|
viewModel.navigateTo(
|
||||||
Destination.Playback(
|
Destination.Playback(
|
||||||
itemId = trailer.baseItem.id,
|
itemId = trailer.baseItem.id,
|
||||||
item = trailer.baseItem,
|
item = trailer.baseItem,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,18 @@ import com.github.damontecres.wholphin.data.model.LocalTrailer
|
||||||
import com.github.damontecres.wholphin.data.model.Person
|
import com.github.damontecres.wholphin.data.model.Person
|
||||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||||
import com.github.damontecres.wholphin.data.model.Trailer
|
import com.github.damontecres.wholphin.data.model.Trailer
|
||||||
|
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.detail.LoadingItemViewModel
|
import com.github.damontecres.wholphin.ui.detail.LoadingItemViewModel
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
@ -40,9 +43,10 @@ class MovieViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
private val navigationManager: NavigationManager,
|
||||||
val serverRepository: ServerRepository,
|
val serverRepository: ServerRepository,
|
||||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||||
|
private val themeSongPlayer: ThemeSongPlayer,
|
||||||
) : LoadingItemViewModel(api) {
|
) : LoadingItemViewModel(api) {
|
||||||
private lateinit var itemId: UUID
|
private lateinit var itemId: UUID
|
||||||
|
|
||||||
|
|
@ -181,4 +185,25 @@ class MovieViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun maybePlayThemeSong(
|
||||||
|
seriesId: UUID,
|
||||||
|
playThemeSongs: ThemeSongVolume,
|
||||||
|
) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
themeSongPlayer.playThemeFor(seriesId, playThemeSongs)
|
||||||
|
addCloseable {
|
||||||
|
themeSongPlayer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
themeSongPlayer.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateTo(destination: Destination) {
|
||||||
|
release()
|
||||||
|
navigationManager.navigateTo(destination)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,15 +83,6 @@ fun SeriesDetails(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: SeriesViewModel = hiltViewModel(),
|
viewModel: SeriesViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
LifecycleStartEffect(destination.itemId) {
|
|
||||||
viewModel.maybePlayThemeSong(
|
|
||||||
destination.itemId,
|
|
||||||
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
|
||||||
)
|
|
||||||
onStopOrDispose {
|
|
||||||
viewModel.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.init(preferences, destination.itemId, destination.item, null)
|
viewModel.init(preferences, destination.itemId, destination.item, null)
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +104,16 @@ fun SeriesDetails(
|
||||||
-> LoadingPage()
|
-> LoadingPage()
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
item?.let { item ->
|
item?.let { item ->
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
viewModel.maybePlayThemeSong(
|
||||||
|
destination.itemId,
|
||||||
|
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
||||||
|
)
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val played = item.data.userData?.played ?: false
|
val played = item.data.userData?.played ?: false
|
||||||
SeriesDetailsContent(
|
SeriesDetailsContent(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
|
|
|
||||||
|
|
@ -72,15 +72,6 @@ fun SeriesOverview(
|
||||||
viewModel: SeriesViewModel = hiltViewModel(),
|
viewModel: SeriesViewModel = hiltViewModel(),
|
||||||
initialSeasonEpisode: SeasonEpisodeIds? = null,
|
initialSeasonEpisode: SeasonEpisodeIds? = null,
|
||||||
) {
|
) {
|
||||||
LifecycleStartEffect(destination.itemId) {
|
|
||||||
viewModel.maybePlayThemeSong(
|
|
||||||
destination.itemId,
|
|
||||||
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
|
||||||
)
|
|
||||||
onStopOrDispose {
|
|
||||||
viewModel.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val firstItemFocusRequester = remember { FocusRequester() }
|
val firstItemFocusRequester = remember { FocusRequester() }
|
||||||
val episodeRowFocusRequester = remember { FocusRequester() }
|
val episodeRowFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
|
@ -168,6 +159,15 @@ fun SeriesOverview(
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
series?.let { series ->
|
series?.let { series ->
|
||||||
LaunchedEffect(Unit) { episodeRowFocusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { episodeRowFocusRequester.tryRequestFocus() }
|
||||||
|
LifecycleStartEffect(destination.itemId) {
|
||||||
|
viewModel.maybePlayThemeSong(
|
||||||
|
destination.itemId,
|
||||||
|
preferences.appPreferences.interfacePreferences.playThemeSongs,
|
||||||
|
)
|
||||||
|
onStopOrDispose {
|
||||||
|
viewModel.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun buildMoreForEpisode(
|
fun buildMoreForEpisode(
|
||||||
ep: BaseItem,
|
ep: BaseItem,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,8 @@ package com.github.damontecres.wholphin.ui.detail.series
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
|
|
@ -30,7 +28,6 @@ import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||||
import com.github.damontecres.wholphin.util.profile.Codec
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -41,7 +38,6 @@ import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.universalAudioApi
|
|
||||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
import org.jellyfin.sdk.model.api.ItemFields
|
||||||
|
|
@ -141,37 +137,17 @@ class SeriesViewModel
|
||||||
/**
|
/**
|
||||||
* If the series has a theme song & app settings allow, play it
|
* If the series has a theme song & app settings allow, play it
|
||||||
*/
|
*/
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
fun maybePlayThemeSong(
|
fun maybePlayThemeSong(
|
||||||
seriesId: UUID,
|
seriesId: UUID,
|
||||||
playThemeSongs: ThemeSongVolume,
|
playThemeSongs: ThemeSongVolume,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
themeSongPlayer.playThemeFor(seriesId, playThemeSongs)
|
||||||
themeSongs.items.firstOrNull()?.let { theme ->
|
|
||||||
theme.mediaSources?.firstOrNull()?.let { source ->
|
|
||||||
val url =
|
|
||||||
api.universalAudioApi.getUniversalAudioStreamUrl(
|
|
||||||
theme.id,
|
|
||||||
container =
|
|
||||||
listOf(
|
|
||||||
Codec.Audio.OPUS,
|
|
||||||
Codec.Audio.MP3,
|
|
||||||
Codec.Audio.AAC,
|
|
||||||
Codec.Audio.FLAC,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
Timber.v("Found theme song for series $seriesId")
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
themeSongPlayer.play(playThemeSongs, url)
|
|
||||||
addCloseable {
|
addCloseable {
|
||||||
themeSongPlayer.stop()
|
themeSongPlayer.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
themeSongPlayer.stop()
|
themeSongPlayer.stop()
|
||||||
|
|
|
||||||
|
|
@ -10,31 +10,77 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
import com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
||||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||||
|
import com.github.damontecres.wholphin.util.profile.Codec
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.libraryApi
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.universalAudioApi
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple service to play theme song music
|
* Simple service to play theme song music
|
||||||
*/
|
*/
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
@Singleton
|
@Singleton
|
||||||
class ThemeSongPlayer
|
class ThemeSongPlayer
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
@param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient,
|
@param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient,
|
||||||
|
private val api: ApiClient,
|
||||||
) {
|
) {
|
||||||
private var player: Player? = null
|
private val player: Player by lazy {
|
||||||
|
ExoPlayer
|
||||||
|
.Builder(context)
|
||||||
|
.setMediaSourceFactory(
|
||||||
|
DefaultMediaSourceFactory(
|
||||||
|
OkHttpDataSource.Factory(authOkHttpClient),
|
||||||
|
),
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
suspend fun playThemeFor(
|
||||||
fun play(
|
itemId: UUID,
|
||||||
volume: ThemeSongVolume,
|
volume: ThemeSongVolume,
|
||||||
|
): Boolean =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (volume == ThemeSongVolume.DISABLED || volume == ThemeSongVolume.UNRECOGNIZED) {
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
val themeSongs by api.libraryApi.getThemeSongs(itemId)
|
||||||
|
return@withContext themeSongs.items.randomOrNull()?.let { theme ->
|
||||||
|
val url =
|
||||||
|
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||||
|
theme.id,
|
||||||
|
container =
|
||||||
|
listOf(
|
||||||
|
Codec.Audio.OPUS,
|
||||||
|
Codec.Audio.MP3,
|
||||||
|
Codec.Audio.AAC,
|
||||||
|
Codec.Audio.FLAC,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Timber.v("Found theme song for $itemId")
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
play(volume, url)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun play(
|
||||||
|
volumeLevel: ThemeSongVolume,
|
||||||
url: String,
|
url: String,
|
||||||
) {
|
) {
|
||||||
stop()
|
stop()
|
||||||
val volumeLevel =
|
val volumeLevel =
|
||||||
when (volume) {
|
when (volumeLevel) {
|
||||||
ThemeSongVolume.UNRECOGNIZED,
|
ThemeSongVolume.UNRECOGNIZED,
|
||||||
ThemeSongVolume.DISABLED,
|
ThemeSongVolume.DISABLED,
|
||||||
-> return
|
-> return
|
||||||
|
|
@ -45,24 +91,16 @@ class ThemeSongPlayer
|
||||||
ThemeSongVolume.HIGH -> .5f
|
ThemeSongVolume.HIGH -> .5f
|
||||||
ThemeSongVolume.HIGHEST -> 75f
|
ThemeSongVolume.HIGHEST -> 75f
|
||||||
}
|
}
|
||||||
val player =
|
player.apply {
|
||||||
ExoPlayer
|
volume = volumeLevel
|
||||||
.Builder(context)
|
setMediaItem(MediaItem.fromUri(url))
|
||||||
.setMediaSourceFactory(
|
prepare()
|
||||||
DefaultMediaSourceFactory(
|
play()
|
||||||
OkHttpDataSource.Factory(authOkHttpClient),
|
|
||||||
),
|
|
||||||
).build()
|
|
||||||
.apply {
|
|
||||||
this.volume = volumeLevel
|
|
||||||
playWhenReady = true
|
|
||||||
}
|
}
|
||||||
player.setMediaItem(MediaItem.fromUri(url))
|
|
||||||
player.prepare()
|
|
||||||
this.player = player
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
player?.release()
|
Timber.v("Stopping theme song")
|
||||||
|
player.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue