feat(player): add radio recording and real waveform
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
|
||||
@@ -1,5 +1,154 @@
|
||||
package es.freetimelab.pluriwave
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.audiofx.Visualizer
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.ryanheise.audioservice.AudioServiceActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
|
||||
class MainActivity : AudioServiceActivity()
|
||||
class MainActivity : AudioServiceActivity() {
|
||||
private val visualizerChannel = "pluriwave/audio_visualizer"
|
||||
private val permissionRequestCode = 4821
|
||||
private var visualizer: Visualizer? = null
|
||||
private var pendingSink: EventChannel.EventSink? = null
|
||||
private var pendingArgs: Map<*, *>? = null
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
EventChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
visualizerChannel
|
||||
).setStreamHandler(object : EventChannel.StreamHandler {
|
||||
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||
pendingSink = events
|
||||
pendingArgs = arguments as? Map<*, *>
|
||||
startVisualizerWhenAllowed()
|
||||
}
|
||||
|
||||
override fun onCancel(arguments: Any?) {
|
||||
stopVisualizer()
|
||||
pendingSink = null
|
||||
pendingArgs = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun startVisualizerWhenAllowed() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
checkSelfPermission(Manifest.permission.RECORD_AUDIO)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissions(
|
||||
arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
permissionRequestCode
|
||||
)
|
||||
return
|
||||
}
|
||||
startVisualizer()
|
||||
}
|
||||
|
||||
private fun startVisualizer() {
|
||||
val sink = pendingSink ?: return
|
||||
val args = pendingArgs
|
||||
val sessionId = (args?.get("sessionId") as? Number)?.toInt() ?: 0
|
||||
val bands = ((args?.get("bands") as? Number)?.toInt() ?: 26).coerceIn(8, 96)
|
||||
|
||||
stopVisualizer()
|
||||
|
||||
try {
|
||||
val captureSize = Visualizer.getCaptureSizeRange()[1]
|
||||
visualizer = Visualizer(sessionId).apply {
|
||||
enabled = false
|
||||
setCaptureSize(captureSize)
|
||||
setDataCaptureListener(
|
||||
object : Visualizer.OnDataCaptureListener {
|
||||
override fun onWaveFormDataCapture(
|
||||
visualizer: Visualizer?,
|
||||
waveform: ByteArray?,
|
||||
samplingRate: Int
|
||||
) {
|
||||
val data = waveform ?: return
|
||||
val values = downsample(data, bands)
|
||||
mainHandler.post { sink.success(values) }
|
||||
}
|
||||
|
||||
override fun onFftDataCapture(
|
||||
visualizer: Visualizer?,
|
||||
fft: ByteArray?,
|
||||
samplingRate: Int
|
||||
) = Unit
|
||||
},
|
||||
Visualizer.getMaxCaptureRate() / 2,
|
||||
true,
|
||||
false
|
||||
)
|
||||
enabled = true
|
||||
}
|
||||
} catch (error: Throwable) {
|
||||
sink.error("VISUALIZER_UNAVAILABLE", error.message, null)
|
||||
stopVisualizer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downsample(data: ByteArray, bands: Int): List<Double> {
|
||||
if (data.isEmpty()) return emptyList()
|
||||
val bucket = maxOf(1, data.size / bands)
|
||||
val values = ArrayList<Double>(bands)
|
||||
|
||||
var index = 0
|
||||
while (index < data.size && values.size < bands) {
|
||||
var sum = 0.0
|
||||
var count = 0
|
||||
val end = minOf(index + bucket, data.size)
|
||||
for (i in index until end) {
|
||||
val centered = (data[i].toInt() and 0xFF) - 128
|
||||
sum += kotlin.math.abs(centered) / 128.0
|
||||
count++
|
||||
}
|
||||
values.add(if (count == 0) 0.0 else (sum / count).coerceIn(0.0, 1.0))
|
||||
index = end
|
||||
}
|
||||
|
||||
while (values.size < bands) values.add(0.0)
|
||||
return values
|
||||
}
|
||||
|
||||
private fun stopVisualizer() {
|
||||
try {
|
||||
visualizer?.enabled = false
|
||||
visualizer?.release()
|
||||
} catch (_: Throwable) {
|
||||
} finally {
|
||||
visualizer = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode != permissionRequestCode) return
|
||||
|
||||
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
||||
startVisualizer()
|
||||
} else {
|
||||
pendingSink?.error(
|
||||
"RECORD_AUDIO_DENIED",
|
||||
"Permiso de audio denegado para visualizar la onda real",
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
stopVisualizer()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user