feat(audio,#2): MediaProjection + AudioPlaybackCapture preview on glasses; AudioPath /vm-audio to phone; debug test tone; debug audio pipe; FGS mediaProjection type; AudioOutputSection UI toggle
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
package pw.binom.rayneovm.glasses
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.projection.MediaProjectionManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Display
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -20,12 +24,41 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import pw.binom.rayneovm.glasses.audio.AudioBridge
|
||||
import pw.binom.rayneovm.glasses.protocol.VmGlassesController
|
||||
import pw.binom.rayneovm.glasses.ui.GlassesScreen
|
||||
import pw.binom.rayneovm.glasses.vm.GlassesScreenState
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
/** Последний обработанный tick запроса проекции (см. onCreate). */
|
||||
private var handledTick = 0L
|
||||
|
||||
/**
|
||||
* Пользовательский диалог MediaProjection. При выставленном appop `PROJECT_MEDIA = allow`
|
||||
* он не показывается — интент сразу возвращает RESULT_OK (нужно для задачи #2).
|
||||
*/
|
||||
private val projectionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val data = result.data
|
||||
if (result.resultCode == Activity.RESULT_OK && data != null) {
|
||||
val mp = getSystemService(MediaProjectionManager::class.java)
|
||||
.getMediaProjection(result.resultCode, data)
|
||||
if (mp != null) {
|
||||
Log.i(TAG, "MediaProjection получен")
|
||||
AudioBridge.onProjectionGranted(mp)
|
||||
} else {
|
||||
Log.w(TAG, "getMediaProjection вернул null")
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "MediaProjection не выдан: resultCode=${result.resultCode}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// HOME живёт на display 0. Если систему (или гостя на VirtualDisplay)
|
||||
@@ -38,6 +71,23 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
enableEdgeToEdge()
|
||||
ShizukuBootstrap.attach(this)
|
||||
// Запросы MediaProjection приходят из VmService через AudioBridge (задача #2).
|
||||
// Запускаем интент только когда Activity хотя бы STARTED, иначе лончер кинет.
|
||||
//
|
||||
// handledTick обязателен: repeatOnLifecycle перезапускает сбор при каждом выходе
|
||||
// Activity в STARTED (а после диалога проекции мы как раз возвращаемся в STARTED).
|
||||
// Без него один и тот же tick запускал бы запрос повторно, новая проекция гасила
|
||||
// предыдущую, и AudioRecord умирал с read -2.
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
AudioBridge.requestTick.collect { tick ->
|
||||
if (tick > handledTick) {
|
||||
handledTick = tick
|
||||
launchProjectionRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val controller = VmGlassesController.fromContext(this)
|
||||
|
||||
setContent {
|
||||
@@ -76,6 +126,17 @@ class MainActivity : ComponentActivity() {
|
||||
ShizukuBootstrap.onRequestPermissionResult(requestCode, grant)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchProjectionRequest() {
|
||||
runCatching {
|
||||
val mpm = getSystemService(MediaProjectionManager::class.java)
|
||||
projectionLauncher.launch(mpm.createScreenCaptureIntent())
|
||||
}.onFailure { Log.w(TAG, "Не удалось запустить запрос MediaProjection: ${it.message}") }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
Reference in New Issue
Block a user