package de.baka.melo import android.app.Activity import android.content.Intent import android.media.audiofx.AudioEffect import android.media.audiofx.BassBoost import android.media.audiofx.Equalizer import android.media.audiofx.Virtualizer import android.provider.Settings import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel /** * Bruecke zu android.media.audiofx: Equalizer, Bass-Boost und Virtualizer * ("Immersives Audio") auf der Audio-Session des Players. * * Effekte haengen an einer Session-ID. just_audio meldet die ID erst, wenn * gespielt wird — bei jedem Wechsel ruft Dart erneut [attach] auf, deshalb * werden die alten Effekte vorher freigegeben. */ class AudioEffectsBridge(private val activity: Activity) { companion object { const val CHANNEL = "de.baka.melo/audio_effects" /** Prioritaet der Effekte gegenueber anderen Apps auf derselben Session. */ private const val PRIORITY = 0 } private var equalizer: Equalizer? = null private var bassBoost: BassBoost? = null private var virtualizer: Virtualizer? = null private var sessionId: Int = 0 private var enabled: Boolean = false fun handle(call: MethodCall, result: MethodChannel.Result) { try { when (call.method) { "attach" -> result.success(attach(call.argument("sessionId") ?: 0)) "setEnabled" -> { enabled = call.argument("enabled") ?: false equalizer?.enabled = enabled bassBoost?.enabled = enabled virtualizer?.enabled = enabled result.success(null) } "setBandLevel" -> { val band = (call.argument("band") ?: 0).toShort() val level = (call.argument("level") ?: 0).toShort() equalizer?.setBandLevel(band, level) result.success(null) } "usePreset" -> { val preset = (call.argument("preset") ?: 0).toShort() equalizer?.usePreset(preset) result.success(currentLevels()) } "setBassStrength" -> { val strength = (call.argument("strength") ?: 0).toShort() bassBoost?.let { if (it.strengthSupported) it.setStrength(strength) } result.success(null) } "setVirtualizerStrength" -> { val strength = (call.argument("strength") ?: 0).toShort() virtualizer?.let { if (it.strengthSupported) it.setStrength(strength) } result.success(null) } "openSystemPanel" -> result.success(openSystemPanel()) "release" -> { release() result.success(null) } else -> result.notImplemented() } } catch (e: Exception) { // Nicht jedes Geraet stellt jeden Effekt bereit; die App soll // deswegen nicht abstuerzen, sondern den Fehler anzeigen koennen. result.error("audio_effects", e.message, null) } } private fun attach(newSessionId: Int): Map { release() sessionId = newSessionId val eq = Equalizer(PRIORITY, sessionId) eq.enabled = enabled equalizer = eq bassBoost = runCatching { BassBoost(PRIORITY, sessionId).also { it.enabled = enabled } } .getOrNull() virtualizer = runCatching { Virtualizer(PRIORITY, sessionId).also { it.enabled = enabled } } .getOrNull() val bands = (0 until eq.numberOfBands).map { index -> mapOf( "index" to index, "centerFrequencyMilliHz" to eq.getCenterFreq(index.toShort()), "level" to eq.getBandLevel(index.toShort()).toInt() ) } val presets = (0 until eq.numberOfPresets).map { eq.getPresetName(it.toShort()) } return mapOf( "bands" to bands, "presets" to presets, "minLevel" to eq.bandLevelRange[0].toInt(), "maxLevel" to eq.bandLevelRange[1].toInt(), "bassSupported" to (bassBoost?.strengthSupported ?: false), "virtualizerSupported" to (virtualizer?.strengthSupported ?: false) ) } private fun currentLevels(): List { val eq = equalizer ?: return emptyList() return (0 until eq.numberOfBands).map { eq.getBandLevel(it.toShort()).toInt() } } /** * Oeffnet die Klangeffekt-Oberflaeche des Systems — auf POCO/Xiaomi ist * das die Dolby-Atmos-Seite unter Einstellungen -> Ton. Gibt false * zurueck, wenn kein System eine passende Anzeige unterstuetzt. * * MIUI/HyperOS registriert KEINE Activity fuer den Android-Standard-Intent * [AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL] — resolveActivity * liefert dort immer null, obwohl Dolby Atmos vorhanden ist ("kein * System-Klangeffekte" trotz vorhandenem Dolby Atmos auf dem POCO X7 Pro). * Der Xiaomi-Weg geht deshalb direkt ueber die System-Ton-Einstellungen, * in denen Dolby Atmos als Eintrag liegt. */ private fun openSystemPanel(): Boolean { val xiaomiTonEinstellungen = Intent() .setClassName("com.android.settings", "com.android.settings.Settings\$SoundSettingsActivity") if (tryStartActivity(xiaomiTonEinstellungen)) return true // Universeller Fallback: bringt auf jedem Android-Geraet die // System-eigene Ton-Seite — auf MIUI/HyperOS ebenfalls die Seite mit // dem Dolby-Atmos-Eintrag, falls obige Xiaomi-Activity mal umbenannt wird. if (tryStartActivity(Intent(Settings.ACTION_SOUND_SETTINGS))) return true // Letzter Versuch: der Android-Standard, funktioniert auf Geraeten, // die tatsaechlich eine Effekt-Anzeige dafuer registrieren. val standardIntent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL) .putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId) .putExtra(AudioEffect.EXTRA_PACKAGE_NAME, activity.packageName) .putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) return tryStartActivity(standardIntent) } private fun tryStartActivity(intent: Intent): Boolean { if (intent.resolveActivity(activity.packageManager) == null) return false activity.startActivity(intent) return true } fun release() { equalizer?.release() bassBoost?.release() virtualizer?.release() equalizer = null bassBoost = null virtualizer = null } }