mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
More visualizer
This commit is contained in:
parent
74f935e1f6
commit
8053f5397f
5 changed files with 69 additions and 63 deletions
|
|
@ -1,7 +1,6 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import android.media.audiofx.Visualizer
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
|
|
@ -44,8 +43,6 @@ import timber.log.Timber
|
|||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Singleton
|
||||
|
|
@ -57,12 +54,10 @@ class MusicService
|
|||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
) : Visualizer.OnDataCaptureListener {
|
||||
) {
|
||||
private val _state = MutableStateFlow(MusicServiceState.EMPTY)
|
||||
val state: StateFlow<MusicServiceState> = _state
|
||||
|
||||
val viz = MutableStateFlow<IntArray>(IntArray(0))
|
||||
|
||||
val player: Player by lazy {
|
||||
ExoPlayer
|
||||
.Builder(context)
|
||||
|
|
@ -76,12 +71,6 @@ class MusicService
|
|||
}
|
||||
}
|
||||
|
||||
private val visualizer by lazy {
|
||||
Visualizer(player.audioSessionId).apply {
|
||||
captureSize = Visualizer.getCaptureSizeRange()[1]
|
||||
}
|
||||
}
|
||||
|
||||
private val mutex = Mutex()
|
||||
var mediaSession: MediaSession? = null
|
||||
private set
|
||||
|
|
@ -92,15 +81,6 @@ class MusicService
|
|||
if (mediaSession == null) {
|
||||
Timber.i("Starting music MediaSession")
|
||||
mediaSession = MediaSession.Builder(context, player).build()
|
||||
onMain {
|
||||
visualizer.setDataCaptureListener(
|
||||
this@MusicService,
|
||||
Visualizer.getMaxCaptureRate() / 2,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
visualizer.enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +102,6 @@ class MusicService
|
|||
player.stop()
|
||||
player.setMediaItems(emptyList())
|
||||
}
|
||||
visualizer.enabled = false
|
||||
_state.update {
|
||||
MusicServiceState.EMPTY
|
||||
}
|
||||
|
|
@ -308,37 +287,6 @@ class MusicService
|
|||
_state.update { it.copy(loadingState = LoadingState.Success) }
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onFftDataCapture(
|
||||
visualizer: Visualizer,
|
||||
fft: ByteArray,
|
||||
samplingRate: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onWaveFormDataCapture(
|
||||
visualizer: Visualizer,
|
||||
waveform: ByteArray,
|
||||
samplingRate: Int,
|
||||
) {
|
||||
val resolution = 64
|
||||
val processed = IntArray(resolution)
|
||||
val captureSize =
|
||||
Visualizer.getCaptureSizeRange()[1]
|
||||
val groupSize = captureSize / resolution
|
||||
// val groupSize = captureSize / resolution.toFloat()
|
||||
for (i in 0 until resolution) {
|
||||
processed[i] =
|
||||
waveform
|
||||
.map { abs(it.toInt()) }
|
||||
// .map { it.toInt() }
|
||||
.subList(i * groupSize, min((i + 1) * groupSize, waveform.size))
|
||||
.average()
|
||||
.toInt()
|
||||
// processed[i] = abs(waveform[ceil(i * finder).toInt()].toInt())
|
||||
}
|
||||
viz.update { processed }
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
import com.github.damontecres.wholphin.services.MediaReportService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SuggestionService
|
||||
|
|
@ -62,12 +63,14 @@ class RecommendedMusicViewModel
|
|||
favoriteWatchManager: FavoriteWatchManager,
|
||||
mediaReportService: MediaReportService,
|
||||
backdropService: BackdropService,
|
||||
mediaManagementService: MediaManagementService,
|
||||
) : RecommendedViewModel(
|
||||
context,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
backdropService,
|
||||
mediaManagementService,
|
||||
) {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ fun BoxScope.BarVisualizer(
|
|||
|
||||
data.forEachIndexed { index, data ->
|
||||
val height by animateDpAsState(
|
||||
targetValue = size.height * data / 128f / 1.5f,
|
||||
targetValue = size.height * data / 128f,
|
||||
animationSpec = tween(easing = LinearEasing),
|
||||
)
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -166,12 +167,19 @@ fun NowPlayingPage(
|
|||
.fillMaxSize()
|
||||
.weight(1f),
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize(.7f)) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(.75f),
|
||||
) {
|
||||
AsyncImage(
|
||||
contentDescription = null,
|
||||
model = current?.imageUrl,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
BarVisualizer(
|
||||
data = viz,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.music
|
||||
|
||||
import android.content.Context
|
||||
import android.media.audiofx.Visualizer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
|
|
@ -35,6 +33,8 @@ import org.jellyfin.sdk.model.api.LyricDto
|
|||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
|
@ -45,18 +45,22 @@ class NowPlayingViewModel
|
|||
private val api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
val navigationManager: NavigationManager,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val backdropService: BackdropService,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
private val musicService: MusicService,
|
||||
val userPreferencesService: UserPreferencesService,
|
||||
) : ViewModel(),
|
||||
Visualizer.OnDataCaptureListener,
|
||||
Player.Listener {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(): NowPlayingViewModel
|
||||
}
|
||||
|
||||
private val visualizer by lazy {
|
||||
Visualizer(player.audioSessionId).apply {
|
||||
captureSize = Visualizer.getCaptureSizeRange()[1]
|
||||
}
|
||||
}
|
||||
|
||||
val controllerViewState =
|
||||
ControllerViewState(
|
||||
AppPreference.Companion.ControllerTimeout.defaultValue,
|
||||
|
|
@ -66,7 +70,7 @@ class NowPlayingViewModel
|
|||
val state = MutableStateFlow(NowPlayingState(musicService.state.value))
|
||||
val player get() = musicService.player
|
||||
|
||||
val viz = musicService.viz
|
||||
val viz = MutableStateFlow<IntArray>(IntArray(0))
|
||||
|
||||
private val lyricCache =
|
||||
InMemoryKache<UUID, LyricDto>(20) {
|
||||
|
|
@ -75,7 +79,18 @@ class NowPlayingViewModel
|
|||
|
||||
init {
|
||||
player.addListener(this)
|
||||
addCloseable { player.removeListener(this) }
|
||||
visualizer.setDataCaptureListener(
|
||||
this,
|
||||
Visualizer.getMaxCaptureRate() / 2,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
visualizer.enabled = true
|
||||
|
||||
addCloseable {
|
||||
player.removeListener(this)
|
||||
visualizer.release()
|
||||
}
|
||||
viewModelScope.launchDefault {
|
||||
musicService.state.collectLatest { musicServiceState ->
|
||||
state.update { it.copy(musicServiceState = musicServiceState) }
|
||||
|
|
@ -192,4 +207,36 @@ class NowPlayingViewModel
|
|||
navigationManager.goBack()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFftDataCapture(
|
||||
visualizer: Visualizer,
|
||||
fft: ByteArray,
|
||||
samplingRate: Int,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onWaveFormDataCapture(
|
||||
visualizer: Visualizer,
|
||||
waveform: ByteArray,
|
||||
samplingRate: Int,
|
||||
) {
|
||||
val resolution = 64
|
||||
val processed = IntArray(resolution)
|
||||
val captureSize =
|
||||
Visualizer.getCaptureSizeRange()[1]
|
||||
// val groupSize = captureSize / resolution
|
||||
val groupSize = captureSize / resolution.toFloat()
|
||||
for (i in 0 until resolution) {
|
||||
// processed[i] =
|
||||
// waveform
|
||||
// .map { abs(it.toInt()) }
|
||||
// .map { it.toInt() }
|
||||
// .subList(i * groupSize, min((i + 1) * groupSize, waveform.size))
|
||||
// .average()
|
||||
// .toInt()
|
||||
val v = abs(waveform[ceil(i * groupSize).toInt()].toInt())
|
||||
processed[i] = v * v / (128)
|
||||
}
|
||||
viz.update { processed }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue