Add console logs.
This commit is contained in:
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-09-18T12:21:21.436952600Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\DZCQIWG\.android\avd\Pixel_9_Pro.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
@@ -49,6 +49,8 @@ dependencies {
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
@@ -56,4 +58,4 @@ dependencies {
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import dev.adriankuta.visualizer.components.MetricsSection
|
||||
import dev.adriankuta.visualizer.components.PermissionSection
|
||||
import dev.adriankuta.visualizer.components.WaveformView
|
||||
import dev.adriankuta.visualizer.ui.theme.VisualizerTheme
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Entry point activity that hosts the Compose UI for the audio visualizer demo.
|
||||
@@ -51,6 +52,10 @@ import dev.adriankuta.visualizer.ui.theme.VisualizerTheme
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Initialize Timber for logging
|
||||
Timber.plant(Timber.DebugTree())
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
VisualizerTheme {
|
||||
@@ -103,8 +108,8 @@ private fun VisualizerScreen(modifier: Modifier = Modifier) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (!permissionGranted) return@LifecycleEventObserver
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_START -> runCatching { controller.start() }
|
||||
Lifecycle.Event.ON_STOP -> runCatching { controller.stop() }
|
||||
Lifecycle.Event.ON_CREATE -> runCatching { controller.start() }
|
||||
Lifecycle.Event.ON_DESTROY -> runCatching { controller.stop() }
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@@ -179,4 +184,4 @@ fun VisualizerPreview() {
|
||||
VisualizerTheme {
|
||||
VisualizerScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dev.adriankuta.visualizer
|
||||
|
||||
import android.media.audiofx.Visualizer
|
||||
import kotlinx.coroutines.*
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Simple wrapper over Android Visualizer to capture global output mix (audio session 0).
|
||||
@@ -11,6 +13,10 @@ class VisualizerController(
|
||||
private val onFft: (ByteArray) -> Unit,
|
||||
) {
|
||||
private var visualizer: Visualizer? = null
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private var loggingJob: Job? = null
|
||||
private var latestWaveform: ByteArray? = null
|
||||
private var latestFft: ByteArray? = null
|
||||
|
||||
/**
|
||||
* Starts capturing audio data via [Visualizer].
|
||||
@@ -23,6 +29,7 @@ class VisualizerController(
|
||||
fun start() {
|
||||
if (visualizer != null) return
|
||||
try {
|
||||
Timber.d("Starting audio visualizer listening (audioSessionId: $audioSessionId)")
|
||||
val v = Visualizer(audioSessionId)
|
||||
// Use the maximum supported capture size for better resolution
|
||||
val captureSize = Visualizer.getCaptureSizeRange().let { it[1] }
|
||||
@@ -34,7 +41,10 @@ class VisualizerController(
|
||||
waveform: ByteArray?,
|
||||
samplingRate: Int
|
||||
) {
|
||||
if (waveform != null) onWaveform(waveform)
|
||||
if (waveform != null) {
|
||||
latestWaveform = waveform.clone()
|
||||
onWaveform(waveform)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFftDataCapture(
|
||||
@@ -42,13 +52,22 @@ class VisualizerController(
|
||||
fft: ByteArray?,
|
||||
samplingRate: Int
|
||||
) {
|
||||
if (fft != null) onFft(fft)
|
||||
if (fft != null) {
|
||||
latestFft = fft.clone()
|
||||
onFft(fft)
|
||||
}
|
||||
}
|
||||
}, rate, true, true)
|
||||
v.enabled = true
|
||||
visualizer = v
|
||||
|
||||
// Start coroutine-based logging for raw data samples every 1 second
|
||||
startDataLogging()
|
||||
|
||||
Timber.i("Audio visualizer listening started successfully")
|
||||
} catch (e: Throwable) {
|
||||
// If Visualizer cannot be initialized (e.g. permission or device restriction), ensure cleanup
|
||||
Timber.e(e, "Failed to start audio visualizer listening")
|
||||
stop()
|
||||
throw e
|
||||
}
|
||||
@@ -60,8 +79,45 @@ class VisualizerController(
|
||||
*/
|
||||
@Synchronized
|
||||
fun stop() {
|
||||
Timber.d("Stopping audio visualizer listening")
|
||||
|
||||
// Stop the logging coroutine
|
||||
loggingJob?.cancel()
|
||||
loggingJob = null
|
||||
|
||||
visualizer?.enabled = false
|
||||
visualizer?.release()
|
||||
visualizer = null
|
||||
|
||||
// Clear latest data
|
||||
latestWaveform = null
|
||||
latestFft = null
|
||||
|
||||
Timber.i("Audio visualizer listening stopped")
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a coroutine that logs raw data samples every 1 second.
|
||||
*/
|
||||
private fun startDataLogging() {
|
||||
loggingJob?.cancel() // Cancel any existing logging job
|
||||
loggingJob = coroutineScope.launch {
|
||||
while (isActive) {
|
||||
delay(1000) // Wait for 1 second
|
||||
|
||||
val waveform = latestWaveform
|
||||
val fft = latestFft
|
||||
|
||||
if (waveform != null && fft != null) {
|
||||
// Log sample of raw data (first few bytes to avoid too much log spam)
|
||||
val waveformSample = waveform.take(8).joinToString(", ") { it.toString() }
|
||||
val fftSample = fft.take(8).joinToString(", ") { it.toString() }
|
||||
|
||||
Timber.d("Raw data sample - Waveform {size: ${waveform.size}, sample: [$waveformSample...]}, FFT {size: {${fft.size}}, sample: [$fftSample...]}")
|
||||
} else {
|
||||
Timber.d("Raw data sample - No data available yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ espressoCore = "3.7.0"
|
||||
lifecycleRuntimeKtx = "2.9.3"
|
||||
activityCompose = "1.10.1"
|
||||
composeBom = "2025.08.01"
|
||||
timber = "5.0.1"
|
||||
coroutines = "1.8.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@@ -24,9 +26,10 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
|
||||
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
|
||||
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user