Add console logs.
This commit is contained in:
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@@ -4,6 +4,14 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<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>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.ui.graphics)
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
|
implementation(libs.timber)
|
||||||
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
@@ -56,4 +58,4 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
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.PermissionSection
|
||||||
import dev.adriankuta.visualizer.components.WaveformView
|
import dev.adriankuta.visualizer.components.WaveformView
|
||||||
import dev.adriankuta.visualizer.ui.theme.VisualizerTheme
|
import dev.adriankuta.visualizer.ui.theme.VisualizerTheme
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point activity that hosts the Compose UI for the audio visualizer demo.
|
* 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() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Initialize Timber for logging
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
VisualizerTheme {
|
VisualizerTheme {
|
||||||
@@ -103,8 +108,8 @@ private fun VisualizerScreen(modifier: Modifier = Modifier) {
|
|||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (!permissionGranted) return@LifecycleEventObserver
|
if (!permissionGranted) return@LifecycleEventObserver
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_START -> runCatching { controller.start() }
|
Lifecycle.Event.ON_CREATE -> runCatching { controller.start() }
|
||||||
Lifecycle.Event.ON_STOP -> runCatching { controller.stop() }
|
Lifecycle.Event.ON_DESTROY -> runCatching { controller.stop() }
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,4 +184,4 @@ fun VisualizerPreview() {
|
|||||||
VisualizerTheme {
|
VisualizerTheme {
|
||||||
VisualizerScreen()
|
VisualizerScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package dev.adriankuta.visualizer
|
package dev.adriankuta.visualizer
|
||||||
|
|
||||||
import android.media.audiofx.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).
|
* 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 val onFft: (ByteArray) -> Unit,
|
||||||
) {
|
) {
|
||||||
private var visualizer: Visualizer? = null
|
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].
|
* Starts capturing audio data via [Visualizer].
|
||||||
@@ -23,6 +29,7 @@ class VisualizerController(
|
|||||||
fun start() {
|
fun start() {
|
||||||
if (visualizer != null) return
|
if (visualizer != null) return
|
||||||
try {
|
try {
|
||||||
|
Timber.d("Starting audio visualizer listening (audioSessionId: $audioSessionId)")
|
||||||
val v = Visualizer(audioSessionId)
|
val v = Visualizer(audioSessionId)
|
||||||
// Use the maximum supported capture size for better resolution
|
// Use the maximum supported capture size for better resolution
|
||||||
val captureSize = Visualizer.getCaptureSizeRange().let { it[1] }
|
val captureSize = Visualizer.getCaptureSizeRange().let { it[1] }
|
||||||
@@ -34,7 +41,10 @@ class VisualizerController(
|
|||||||
waveform: ByteArray?,
|
waveform: ByteArray?,
|
||||||
samplingRate: Int
|
samplingRate: Int
|
||||||
) {
|
) {
|
||||||
if (waveform != null) onWaveform(waveform)
|
if (waveform != null) {
|
||||||
|
latestWaveform = waveform.clone()
|
||||||
|
onWaveform(waveform)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFftDataCapture(
|
override fun onFftDataCapture(
|
||||||
@@ -42,13 +52,22 @@ class VisualizerController(
|
|||||||
fft: ByteArray?,
|
fft: ByteArray?,
|
||||||
samplingRate: Int
|
samplingRate: Int
|
||||||
) {
|
) {
|
||||||
if (fft != null) onFft(fft)
|
if (fft != null) {
|
||||||
|
latestFft = fft.clone()
|
||||||
|
onFft(fft)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, rate, true, true)
|
}, rate, true, true)
|
||||||
v.enabled = true
|
v.enabled = true
|
||||||
visualizer = v
|
visualizer = v
|
||||||
|
|
||||||
|
// Start coroutine-based logging for raw data samples every 1 second
|
||||||
|
startDataLogging()
|
||||||
|
|
||||||
|
Timber.i("Audio visualizer listening started successfully")
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
// If Visualizer cannot be initialized (e.g. permission or device restriction), ensure cleanup
|
// If Visualizer cannot be initialized (e.g. permission or device restriction), ensure cleanup
|
||||||
|
Timber.e(e, "Failed to start audio visualizer listening")
|
||||||
stop()
|
stop()
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
@@ -60,8 +79,45 @@ class VisualizerController(
|
|||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stop() {
|
fun stop() {
|
||||||
|
Timber.d("Stopping audio visualizer listening")
|
||||||
|
|
||||||
|
// Stop the logging coroutine
|
||||||
|
loggingJob?.cancel()
|
||||||
|
loggingJob = null
|
||||||
|
|
||||||
visualizer?.enabled = false
|
visualizer?.enabled = false
|
||||||
visualizer?.release()
|
visualizer?.release()
|
||||||
visualizer = null
|
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"
|
lifecycleRuntimeKtx = "2.9.3"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2025.08.01"
|
composeBom = "2025.08.01"
|
||||||
|
timber = "5.0.1"
|
||||||
|
coroutines = "1.8.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
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-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user