feat: scrcpy following with auto fullscreen
This commit is contained in:
@@ -33,4 +33,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
MainScreen()
|
MainScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
StreamActivity.dismissActivePictureInPicture()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.github.miuzarte.scrcpyforandroid
|
package io.github.miuzarte.scrcpyforandroid
|
||||||
|
|
||||||
|
import android.R.drawable
|
||||||
import android.app.PictureInPictureUiState
|
import android.app.PictureInPictureUiState
|
||||||
import android.app.RemoteAction
|
import android.app.RemoteAction
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -8,16 +9,18 @@ import android.graphics.drawable.Icon
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.core.app.PictureInPictureParamsCompat
|
import androidx.core.app.PictureInPictureParamsCompat.Builder
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.pip.BasicPictureInPicture
|
import androidx.core.pip.BasicPictureInPicture
|
||||||
import io.github.miuzarte.scrcpyforandroid.pages.StreamScreen
|
import io.github.miuzarte.scrcpyforandroid.pages.StreamScreen
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.PictureInPictureActionReceiver
|
import io.github.miuzarte.scrcpyforandroid.services.PictureInPictureActionReceiver
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class StreamActivity : ComponentActivity() {
|
class StreamActivity : ComponentActivity() {
|
||||||
private val basicPip = BasicPictureInPicture(this)
|
private val basicPip = BasicPictureInPicture(this)
|
||||||
|
|
||||||
private val pipActionReceiver = PictureInPictureActionReceiver()
|
private val pipActionReceiver = PictureInPictureActionReceiver()
|
||||||
private var isPipActionReceiverRegistered = false
|
private var isPipActionReceiverRegistered = false
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ class StreamActivity : ComponentActivity() {
|
|||||||
|
|
||||||
val pipStopAction: RemoteAction by lazy {
|
val pipStopAction: RemoteAction by lazy {
|
||||||
RemoteAction(
|
RemoteAction(
|
||||||
Icon.createWithResource(this, android.R.drawable.ic_menu_close_clear_cancel),
|
Icon.createWithResource(this, drawable.ic_menu_close_clear_cancel),
|
||||||
"停止投屏",
|
"停止投屏",
|
||||||
"停止投屏",
|
"停止投屏",
|
||||||
PictureInPictureActionReceiver.createPendingIntent(this),
|
PictureInPictureActionReceiver.createPendingIntent(this),
|
||||||
@@ -39,6 +42,7 @@ class StreamActivity : ComponentActivity() {
|
|||||||
// 都会重建 activity
|
// 都会重建 activity
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
currentActivityRef = WeakReference(this)
|
||||||
|
|
||||||
registerPipActionReceiver()
|
registerPipActionReceiver()
|
||||||
|
|
||||||
@@ -76,11 +80,13 @@ class StreamActivity : ComponentActivity() {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configurePip(params: PictureInPictureParamsCompat) {
|
fun configurePip(block: Builder.() -> Builder) =
|
||||||
basicPip.setPictureInPictureParams(params)
|
basicPip.setPictureInPictureParams(Builder().block().build())
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
currentActivityRef?.get()
|
||||||
|
?.takeIf { it === this }
|
||||||
|
?.let { currentActivityRef = null }
|
||||||
unregisterPipActionReceiver()
|
unregisterPipActionReceiver()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
@@ -138,8 +144,16 @@ class StreamActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private var currentActivityRef: WeakReference<StreamActivity>? = null
|
||||||
|
|
||||||
fun createIntent(context: Context): Intent {
|
fun createIntent(context: Context): Intent {
|
||||||
return Intent(context, StreamActivity::class.java)
|
return Intent(context, StreamActivity::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissActivePictureInPicture() {
|
||||||
|
currentActivityRef?.get()
|
||||||
|
?.takeIf { it.isInPictureInPictureMode }
|
||||||
|
?.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
port: Int? = null,
|
port: Int? = null,
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
startScrcpyOnConnect: Boolean? = null,
|
startScrcpyOnConnect: Boolean? = null,
|
||||||
|
openFullscreenOnStart: Boolean? = null,
|
||||||
newPort: Int? = null,
|
newPort: Int? = null,
|
||||||
updateNameOnlyWhenEmpty: Boolean = false,
|
updateNameOnlyWhenEmpty: Boolean = false,
|
||||||
): DeviceShortcuts {
|
): DeviceShortcuts {
|
||||||
@@ -59,6 +60,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
val finalHost = if (updateById) host ?: old.host else old.host
|
val finalHost = if (updateById) host ?: old.host else old.host
|
||||||
val finalPort = if (updateById) port ?: old.port else newPort ?: old.port
|
val finalPort = if (updateById) port ?: old.port else newPort ?: old.port
|
||||||
val finalStartScrcpyOnConnect = startScrcpyOnConnect ?: old.startScrcpyOnConnect
|
val finalStartScrcpyOnConnect = startScrcpyOnConnect ?: old.startScrcpyOnConnect
|
||||||
|
val finalOpenFullscreenOnStart = openFullscreenOnStart ?: old.openFullscreenOnStart
|
||||||
|
|
||||||
// 若无任何变化,返回原实例
|
// 若无任何变化,返回原实例
|
||||||
if (
|
if (
|
||||||
@@ -66,6 +68,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
&& finalHost == old.host
|
&& finalHost == old.host
|
||||||
&& finalPort == old.port
|
&& finalPort == old.port
|
||||||
&& finalStartScrcpyOnConnect == old.startScrcpyOnConnect
|
&& finalStartScrcpyOnConnect == old.startScrcpyOnConnect
|
||||||
|
&& finalOpenFullscreenOnStart == old.openFullscreenOnStart
|
||||||
)
|
)
|
||||||
return this
|
return this
|
||||||
|
|
||||||
@@ -75,6 +78,7 @@ class DeviceShortcuts(val devices: List<DeviceShortcut>) : List<DeviceShortcut>
|
|||||||
host = finalHost,
|
host = finalHost,
|
||||||
port = finalPort,
|
port = finalPort,
|
||||||
startScrcpyOnConnect = finalStartScrcpyOnConnect,
|
startScrcpyOnConnect = finalStartScrcpyOnConnect,
|
||||||
|
openFullscreenOnStart = finalOpenFullscreenOnStart,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return DeviceShortcuts(
|
return DeviceShortcuts(
|
||||||
@@ -128,13 +132,18 @@ data class DeviceShortcut(
|
|||||||
val host: String,
|
val host: String,
|
||||||
val port: Int = Defaults.ADB_PORT,
|
val port: Int = Defaults.ADB_PORT,
|
||||||
val startScrcpyOnConnect: Boolean = false,
|
val startScrcpyOnConnect: Boolean = false,
|
||||||
|
val openFullscreenOnStart: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val id: String get() = "$host:$port"
|
val id: String get() = "$host:$port"
|
||||||
|
|
||||||
fun marshalToString(
|
fun marshalToString(
|
||||||
separator: String = DEFAULT_SEPARATOR,
|
separator: String = DEFAULT_SEPARATOR,
|
||||||
): String = listOf(
|
): String = listOf(
|
||||||
name.trim(), host.trim(), port.toString(), if (startScrcpyOnConnect) "1" else "0"
|
name.trim(),
|
||||||
|
host.trim(),
|
||||||
|
port.toString(),
|
||||||
|
if (startScrcpyOnConnect) "1" else "0",
|
||||||
|
if (openFullscreenOnStart) "1" else "0",
|
||||||
).joinToString(
|
).joinToString(
|
||||||
separator = separator
|
separator = separator
|
||||||
)
|
)
|
||||||
@@ -147,16 +156,19 @@ data class DeviceShortcut(
|
|||||||
): DeviceShortcut? {
|
): DeviceShortcut? {
|
||||||
val parts = s.split(separator)
|
val parts = s.split(separator)
|
||||||
return when (parts.size) {
|
return when (parts.size) {
|
||||||
3, 4 -> {
|
3, 4, 5 -> {
|
||||||
val name = parts[0].trim()
|
val name = parts[0].trim()
|
||||||
val host = parts[1].trim()
|
val host = parts[1].trim()
|
||||||
val port = parts[2].trim().toIntOrNull() ?: Defaults.ADB_PORT
|
val port = parts[2].trim().toIntOrNull() ?: Defaults.ADB_PORT
|
||||||
val startScrcpyOnConnect = parts.getOrNull(3)?.trim() == "1"
|
val startScrcpyOnConnect = parts.getOrNull(3)?.trim() == "1"
|
||||||
|
val openFullscreenOnStart =
|
||||||
|
startScrcpyOnConnect && parts.getOrNull(4)?.trim() == "1"
|
||||||
if (host.isNotBlank()) DeviceShortcut(
|
if (host.isNotBlank()) DeviceShortcut(
|
||||||
name = name,
|
name = name,
|
||||||
host = host,
|
host = host,
|
||||||
port = port,
|
port = port,
|
||||||
startScrcpyOnConnect = startScrcpyOnConnect,
|
startScrcpyOnConnect = startScrcpyOnConnect,
|
||||||
|
openFullscreenOnStart = openFullscreenOnStart,
|
||||||
)
|
)
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -37,7 +38,6 @@ import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
|||||||
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
|
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
|
||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.AppRuntime
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||||
@@ -232,10 +232,9 @@ fun DeviceTabPage(
|
|||||||
var adbConnected by rememberSaveable { mutableStateOf(false) }
|
var adbConnected by rememberSaveable { mutableStateOf(false) }
|
||||||
var isQuickConnected by rememberSaveable { mutableStateOf(false) }
|
var isQuickConnected by rememberSaveable { mutableStateOf(false) }
|
||||||
var currentTargetHost by rememberSaveable { mutableStateOf("") }
|
var currentTargetHost by rememberSaveable { mutableStateOf("") }
|
||||||
var currentTargetPort by rememberSaveable { mutableStateOf(Defaults.ADB_PORT) }
|
var currentTargetPort by rememberSaveable { mutableIntStateOf(Defaults.ADB_PORT) }
|
||||||
var connectedDeviceLabel by rememberSaveable { mutableStateOf("未连接") }
|
var connectedDeviceLabel by rememberSaveable { mutableStateOf("未连接") }
|
||||||
val sessionInfo by scrcpy.currentSessionState.collectAsState()
|
val sessionInfo by scrcpy.currentSessionState.collectAsState()
|
||||||
var previewControlsVisible by rememberSaveable { mutableStateOf(false) }
|
|
||||||
var editingDeviceId by rememberSaveable { mutableStateOf<String?>(null) }
|
var editingDeviceId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
var activeDeviceActionId by rememberSaveable { mutableStateOf<String?>(null) }
|
var activeDeviceActionId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
var adbConnecting by rememberSaveable { mutableStateOf(false) }
|
var adbConnecting by rememberSaveable { mutableStateOf(false) }
|
||||||
@@ -277,9 +276,6 @@ fun DeviceTabPage(
|
|||||||
qdBundle = qdBundle.copy(quickDevicesList = serialized)
|
qdBundle = qdBundle.copy(quickDevicesList = serialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val editingDevice = remember(savedShortcuts, editingDeviceId) {
|
|
||||||
editingDeviceId?.let(savedShortcuts::get)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect the current ADB connection and stop any running scrcpy session.
|
* Disconnect the current ADB connection and stop any running scrcpy session.
|
||||||
@@ -400,10 +396,7 @@ fun DeviceTabPage(
|
|||||||
suspend fun keepAliveCheck(host: String, port: Int): Boolean {
|
suspend fun keepAliveCheck(host: String, port: Int): Boolean {
|
||||||
return withTimeout(ADB_KEEPALIVE_TIMEOUT_MS) {
|
return withTimeout(ADB_KEEPALIVE_TIMEOUT_MS) {
|
||||||
val connected = NativeAdbService.isConnected()
|
val connected = NativeAdbService.isConnected()
|
||||||
if (!connected) {
|
return@withTimeout connected
|
||||||
return@withTimeout false
|
|
||||||
}
|
|
||||||
return@withTimeout true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,10 +529,15 @@ fun DeviceTabPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun startScrcpySession() {
|
suspend fun startScrcpySession(openFullscreen: Boolean = false) {
|
||||||
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
||||||
val session = scrcpy.start(options)
|
val session = scrcpy.start(options)
|
||||||
pendingScrollToPreview = true
|
pendingScrollToPreview = true
|
||||||
|
if (openFullscreen) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
context.startActivity(StreamActivity.createIntent(context))
|
||||||
|
}
|
||||||
|
}
|
||||||
if (options.disableScreensaver) {
|
if (options.disableScreensaver) {
|
||||||
setKeepScreenOn(true)
|
setKeepScreenOn(true)
|
||||||
}
|
}
|
||||||
@@ -571,7 +569,11 @@ fun DeviceTabPage(
|
|||||||
listState.animateScrollToItem(PREVIEW_CARD_ITEM_INDEX)
|
listState.animateScrollToItem(PREVIEW_CARD_ITEM_INDEX)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun handleAdbConnected(host: String, port: Int) {
|
suspend fun handleAdbConnected(
|
||||||
|
host: String, port: Int,
|
||||||
|
autoStartScrcpy: Boolean = false,
|
||||||
|
autoEnterFullScreen: Boolean = false,
|
||||||
|
) {
|
||||||
currentTargetHost = host
|
currentTargetHost = host
|
||||||
currentTargetPort = port
|
currentTargetPort = port
|
||||||
|
|
||||||
@@ -603,15 +605,11 @@ fun DeviceTabPage(
|
|||||||
)
|
)
|
||||||
snackbar.show("ADB 已连接")
|
snackbar.show("ADB 已连接")
|
||||||
|
|
||||||
if (
|
if (autoStartScrcpy && sessionInfo == null) runBusy("启动 scrcpy") {
|
||||||
savedShortcuts.get(host, port)?.startScrcpyOnConnect == true &&
|
startScrcpySession(openFullscreen = autoStartScrcpy && autoEnterFullScreen)
|
||||||
sessionInfo == null
|
|
||||||
) {
|
|
||||||
runBusy("启动 scrcpy") {
|
|
||||||
startScrcpySession()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(
|
LaunchedEffect(
|
||||||
adbConnected,
|
adbConnected,
|
||||||
asBundle.adbAutoReconnectPairedDevice,
|
asBundle.adbAutoReconnectPairedDevice,
|
||||||
@@ -751,8 +749,9 @@ fun DeviceTabPage(
|
|||||||
adbConnecting && activeDeviceActionId == device.id
|
adbConnecting && activeDeviceActionId == device.id
|
||||||
},
|
},
|
||||||
editingDeviceId = editingDeviceId,
|
editingDeviceId = editingDeviceId,
|
||||||
onClick = {
|
onClick = { device ->
|
||||||
snackbar.show("长按可编辑")
|
if (editingDeviceId != device.id)
|
||||||
|
snackbar.show("长按可编辑")
|
||||||
},
|
},
|
||||||
onLongClick = { device ->
|
onLongClick = { device ->
|
||||||
val connected = adbConnected
|
val connected = adbConnected
|
||||||
@@ -768,11 +767,14 @@ fun DeviceTabPage(
|
|||||||
},
|
},
|
||||||
onAction = { device ->
|
onAction = { device ->
|
||||||
haptics.contextClick()
|
haptics.contextClick()
|
||||||
|
if (editingDeviceId == device.id) editingDeviceId = null
|
||||||
|
|
||||||
val host = device.host
|
val host = device.host
|
||||||
val port = device.port
|
val port = device.port
|
||||||
val connected = adbConnected
|
val connected = adbConnected
|
||||||
&& currentTarget?.host == host
|
&& currentTarget?.host == host
|
||||||
&& currentTarget.port == port
|
&& currentTarget.port == port
|
||||||
|
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
runAdbConnect(
|
runAdbConnect(
|
||||||
"连接 ADB",
|
"连接 ADB",
|
||||||
@@ -787,7 +789,12 @@ fun DeviceTabPage(
|
|||||||
savedShortcuts = savedShortcuts.update(
|
savedShortcuts = savedShortcuts.update(
|
||||||
host = host, port = port,
|
host = host, port = port,
|
||||||
)
|
)
|
||||||
handleAdbConnected(host, port)
|
handleAdbConnected(
|
||||||
|
host = host, port = port,
|
||||||
|
autoStartScrcpy = device.startScrcpyOnConnect,
|
||||||
|
autoEnterFullScreen = device.startScrcpyOnConnect
|
||||||
|
&& device.openFullscreenOnStart,
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
statusLine = "ADB 连接失败"
|
statusLine = "ADB 连接失败"
|
||||||
logEvent("ADB 连接失败: $e", Log.ERROR)
|
logEvent("ADB 连接失败: $e", Log.ERROR)
|
||||||
@@ -817,6 +824,7 @@ fun DeviceTabPage(
|
|||||||
host = updated.host,
|
host = updated.host,
|
||||||
port = updated.port,
|
port = updated.port,
|
||||||
startScrcpyOnConnect = updated.startScrcpyOnConnect,
|
startScrcpyOnConnect = updated.startScrcpyOnConnect,
|
||||||
|
openFullscreenOnStart = updated.openFullscreenOnStart,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onEditorDelete = { device ->
|
onEditorDelete = { device ->
|
||||||
@@ -962,10 +970,6 @@ fun DeviceTabPage(
|
|||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
sessionInfo = sessionInfo,
|
sessionInfo = sessionInfo,
|
||||||
previewHeightDp = asBundle.devicePreviewCardHeightDp.coerceAtLeast(120),
|
previewHeightDp = asBundle.devicePreviewCardHeightDp.coerceAtLeast(120),
|
||||||
controlsVisible = previewControlsVisible,
|
|
||||||
onTapped = {
|
|
||||||
previewControlsVisible = !previewControlsVisible
|
|
||||||
},
|
|
||||||
onOpenFullscreen = {
|
onOpenFullscreen = {
|
||||||
context.startActivity(StreamActivity.createIntent(context))
|
context.startActivity(StreamActivity.createIntent(context))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -955,7 +955,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// TODO: 在 OverlaySpinnerPreference / OverlayDropdownPreference 支持展开状态回调后, 在展开时触发获取
|
// TODO: 等 MIUIX 发版, 在 OverlaySpinnerPreference / OverlayDropdownPreference 支持展开状态回调后, 在展开时触发获取
|
||||||
OverlaySpinnerPreference(
|
OverlaySpinnerPreference(
|
||||||
title = "视频编码器",
|
title = "视频编码器",
|
||||||
summary = "--video-encoder",
|
summary = "--video-encoder",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.app.PictureInPictureParamsCompat
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.StreamActivity
|
import io.github.miuzarte.scrcpyforandroid.StreamActivity
|
||||||
import io.github.miuzarte.scrcpyforandroid.constants.ThemeModes
|
import io.github.miuzarte.scrcpyforandroid.constants.ThemeModes
|
||||||
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
import io.github.miuzarte.scrcpyforandroid.haptics.LocalAppHaptics
|
||||||
@@ -72,15 +71,13 @@ fun StreamScreen(activity: StreamActivity) {
|
|||||||
lastPipAspectRatio = ratio
|
lastPipAspectRatio = ratio
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.configurePip(
|
activity.configurePip {
|
||||||
PictureInPictureParamsCompat.Builder()
|
setEnabled(true)
|
||||||
.setEnabled(true)
|
setAspectRatio(pipAspectRatio)
|
||||||
.setAspectRatio(pipAspectRatio)
|
setSourceRectHint(pipSourceRectHint)
|
||||||
.setSourceRectHint(pipSourceRectHint)
|
setSeamlessResizeEnabled(true)
|
||||||
.setSeamlessResizeEnabled(true)
|
setCloseAction(activity.pipStopAction)
|
||||||
.setCloseAction(activity.pipStopAction)
|
}
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val themeMode =
|
val themeMode =
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
|
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
@@ -250,14 +250,17 @@ internal fun PreviewCard(
|
|||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
sessionInfo: Scrcpy.Session.SessionInfo?,
|
sessionInfo: Scrcpy.Session.SessionInfo?,
|
||||||
previewHeightDp: Int,
|
previewHeightDp: Int,
|
||||||
controlsVisible: Boolean,
|
|
||||||
onTapped: () -> Unit,
|
|
||||||
onOpenFullscreen: () -> Unit,
|
onOpenFullscreen: () -> Unit,
|
||||||
autoBringIntoView: Boolean = false,
|
autoBringIntoView: Boolean = false,
|
||||||
onAutoBringIntoViewConsumed: () -> Unit = {},
|
onAutoBringIntoViewConsumed: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val haptics = rememberAppHaptics()
|
val haptics = rememberAppHaptics()
|
||||||
val alpha by animateFloatAsState(if (controlsVisible) 1f else 0f, label = "preview-controls")
|
|
||||||
|
var previewControlsVisible by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val alpha by animateFloatAsState(
|
||||||
|
if (previewControlsVisible) 1f else 0f,
|
||||||
|
label = "preview-controls"
|
||||||
|
)
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
|
|
||||||
@@ -268,6 +271,9 @@ internal fun PreviewCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(lifecycleOwner) {
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||||
|
VideoOutputTargetState.set(VideoOutputTarget.PREVIEW)
|
||||||
|
}
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_START -> VideoOutputTargetState.set(VideoOutputTarget.PREVIEW)
|
Lifecycle.Event.ON_START -> VideoOutputTargetState.set(VideoOutputTarget.PREVIEW)
|
||||||
@@ -287,12 +293,20 @@ internal fun PreviewCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Card(modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester).then(modifier)) {
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.bringIntoViewRequester(bringIntoViewRequester)
|
||||||
|
.then(modifier)
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(previewHeightDp.coerceAtLeast(120).dp)
|
.height(previewHeightDp.coerceAtLeast(120).dp)
|
||||||
.pointerInput(sessionInfo) { detectTapGestures(onTap = { onTapped() }) },
|
.pointerInput(sessionInfo) {
|
||||||
|
detectTapGestures(onTap = {
|
||||||
|
previewControlsVisible = !previewControlsVisible
|
||||||
|
})
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
|
||||||
val sessionAspect =
|
val sessionAspect =
|
||||||
@@ -801,6 +815,17 @@ fun ScrcpyVideoSurface(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(lifecycleOwner, session, currentSurface) {
|
DisposableEffect(lifecycleOwner, session, currentSurface) {
|
||||||
|
val surface = currentSurface
|
||||||
|
if (
|
||||||
|
lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) &&
|
||||||
|
session != null &&
|
||||||
|
surface != null &&
|
||||||
|
surface.isValid
|
||||||
|
) {
|
||||||
|
scope.launch {
|
||||||
|
NativeCoreFacade.attachVideoSurface(surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_START) {
|
if (event == Lifecycle.Event.ON_START) {
|
||||||
val surface = currentSurface
|
val surface = currentSurface
|
||||||
@@ -916,6 +941,8 @@ internal fun DeviceTile(
|
|||||||
host = trimmedHost,
|
host = trimmedHost,
|
||||||
port = currentDraft.port,
|
port = currentDraft.port,
|
||||||
startScrcpyOnConnect = currentDraft.startScrcpyOnConnect,
|
startScrcpyOnConnect = currentDraft.startScrcpyOnConnect,
|
||||||
|
openFullscreenOnStart = currentDraft.startScrcpyOnConnect
|
||||||
|
&& currentDraft.openFullscreenOnStart,
|
||||||
)
|
)
|
||||||
if (updated != device) {
|
if (updated != device) {
|
||||||
onEditorSave(updated)
|
onEditorSave(updated)
|
||||||
@@ -1048,6 +1075,18 @@ internal fun DeviceTile(
|
|||||||
draft = currentDraft.copy(startScrcpyOnConnect = it)
|
draft = currentDraft.copy(startScrcpyOnConnect = it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
AnimatedVisibility(currentDraft.startScrcpyOnConnect) {
|
||||||
|
CheckboxPreference(
|
||||||
|
title = "直接进入全屏",
|
||||||
|
checkboxLocation = CheckboxLocation.End,
|
||||||
|
checked = currentDraft.startScrcpyOnConnect
|
||||||
|
&& currentDraft.openFullscreenOnStart,
|
||||||
|
enabled = currentDraft.startScrcpyOnConnect,
|
||||||
|
onCheckedChange = {
|
||||||
|
draft = currentDraft.copy(openFullscreenOnStart = it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
Reference in New Issue
Block a user