屏幕共享美颜更新

This commit is contained in:
2025-12-04 15:31:05 +08:00
parent e09271a60e
commit 6a1f3db5eb
11 changed files with 157 additions and 18 deletions

View File

@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
@@ -55,7 +56,7 @@
<service
android:name=".interactive.InteractiveForegroundService"
android:exported="false"
android:foregroundServiceType="camera|microphone" />
android:foregroundServiceType="camera|microphone|mediaProjection" />
</application>

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.app.ServiceCompat
import com.demo.SellyCloudSDK.R
/**
@@ -18,7 +19,27 @@ import com.demo.SellyCloudSDK.R
class InteractiveForegroundService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
val includeMediaProjection = intent?.getBooleanExtra(EXTRA_MEDIA_PROJECTION, false) == true
val serviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val base = android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
if (includeMediaProjection) {
base or android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
} else {
base
}
} else 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
buildNotification(),
serviceType
)
} else {
startForeground(NOTIFICATION_ID, buildNotification())
}
return START_STICKY
}
@@ -61,9 +82,11 @@ class InteractiveForegroundService : Service() {
companion object {
private const val CHANNEL_ID = "interactive_call_foreground"
private const val NOTIFICATION_ID = 0x101
private const val EXTRA_MEDIA_PROJECTION = "extra_media_projection"
fun start(context: Context) {
fun start(context: Context, includeMediaProjection: Boolean = false) {
val intent = Intent(context, InteractiveForegroundService::class.java)
intent.putExtra(EXTRA_MEDIA_PROJECTION, includeMediaProjection)
ContextCompat.startForegroundService(context, intent)
}

View File

@@ -1,11 +1,14 @@
package com.demo.SellyCloudSDK.interactive
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.media.projection.MediaProjectionManager
import android.os.Bundle
import android.view.inputmethod.InputMethodManager
import android.util.Log
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
@@ -63,6 +66,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
private var pendingJoinRequest: JoinRequest? = null
private var currentCallId: String? = null
@Volatile private var selfUserId: String? = null
private var isScreenSharing: Boolean = false
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
@@ -78,6 +82,17 @@ class InteractiveLiveActivity : AppCompatActivity() {
if (!granted) setJoinButtonEnabled(true)
}
private val screenShareLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
startScreenShareInternal(result.resultCode, result.data!!)
} else {
Toast.makeText(this, "已取消屏幕共享授权", Toast.LENGTH_SHORT).show()
updateControlButtons()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityInteractiveLiveBinding.inflate(layoutInflater)
@@ -151,7 +166,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
setEventHandler(rtcEventHandler)
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
// setVideoEncoderConfiguration(InteractiveVideoEncoderConfig()) 使用默认值
setVideoEncoderConfiguration(InteractiveVideoEncoderConfig(640, 480 , fps = 20, minBitrateKbps = 150, maxBitrateKbps = 350))
setVideoEncoderConfiguration(InteractiveVideoEncoderConfig(640, 480 , fps = 20, minBitrateKbps = 150, maxBitrateKbps = 850))
setDefaultAudioRoutetoSpeakerphone(true)
setCaptureVideoFrameInterceptor { frame ->
if (!beautyEnabled) return@setCaptureVideoFrameInterceptor frame
@@ -277,6 +292,10 @@ class InteractiveLiveActivity : AppCompatActivity() {
val tip = "onStreamStateChanged[$peerId] state=$state code=$code ${message ?: ""}"
Log.d(TAG, tip)
Toast.makeText(this@InteractiveLiveActivity, tip, Toast.LENGTH_SHORT).show()
if (peerId == currentUserId && message?.contains("screen_share_stopped") == true) {
isScreenSharing = false
updateControlButtons()
}
}
}
}
@@ -360,6 +379,24 @@ class InteractiveLiveActivity : AppCompatActivity() {
}
}
}
binding.btnScreenShare.setOnClickListener {
if (currentCallId == null) {
Toast.makeText(this, "请先加入频道", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (isScreenSharing) {
stopScreenShareInternal(true)
} else {
// Android 14+ 屏幕捕获需要先开启包含 mediaProjection 类型的前台服务
InteractiveForegroundService.start(this, includeMediaProjection = true)
val mgr = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as? MediaProjectionManager
if (mgr == null) {
Toast.makeText(this, "当前设备不支持屏幕捕获", Toast.LENGTH_SHORT).show()
} else {
screenShareLauncher.launch(mgr.createScreenCaptureIntent())
}
}
}
updateControlButtons()
}
@@ -390,6 +427,14 @@ class InteractiveLiveActivity : AppCompatActivity() {
} else {
getString(R.string.ctrl_camera_on)
}
binding.btnScreenShare.text = if (isScreenSharing) {
getString(R.string.stop_screen_share)
} else {
getString(R.string.start_screen_share)
}
binding.btnSwitchCamera.isEnabled = !isScreenSharing
binding.btnToggleBeauty.isEnabled = !isScreenSharing
}
private fun applyLocalPreviewVisibility() {
@@ -402,6 +447,36 @@ class InteractiveLiveActivity : AppCompatActivity() {
updateLocalStatsLabel()
}
private fun startScreenShareInternal(resultCode: Int, data: Intent) {
// Android 14+ 需开启包含 mediaProjection 类型的前台服务
InteractiveForegroundService.start(this, includeMediaProjection = true)
val started = rtcEngine?.startScreenShare(
resultCode,
data,
width = 720,
height = 1280,
fps = 15
) == true
if (started) {
isScreenSharing = true
} else {
Toast.makeText(this, "屏幕共享启动失败", Toast.LENGTH_LONG).show()
}
updateControlButtons()
}
private fun stopScreenShareInternal(showToast: Boolean = false) {
val stopped = rtcEngine?.stopScreenShare() == true
if (stopped) {
isScreenSharing = false
ensureBeautySessionReady()
fuFrameInterceptor?.setEnabled(beautyEnabled)
} else if (showToast) {
Toast.makeText(this, "停止屏幕共享失败", Toast.LENGTH_SHORT).show()
}
updateControlButtons()
}
private fun attemptJoin() {
hideKeyboard()
val callId = binding.etCallId.text.toString().trim()
@@ -643,6 +718,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
callDurationSeconds = 0
lastMessage = null
binding.tvMessageLog.text = getString(R.string.message_none)
isScreenSharing = false
updateControlButtons()
updateLocalStatsLabel()
updateCallInfo()

View File

@@ -222,6 +222,13 @@
android:layout_weight="1"
android:text="@string/ctrl_mic_off" />
</LinearLayout>
<Button
android:id="@+id/btn_screen_share"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/start_screen_share" />
</LinearLayout>
<LinearLayout

View File

@@ -39,6 +39,8 @@
<string name="ctrl_mic_on">开启麦克风</string>
<string name="ctrl_camera_off">关闭摄像头</string>
<string name="ctrl_camera_on">开启摄像头</string>
<string name="start_screen_share">开始屏幕共享</string>
<string name="stop_screen_share">停止屏幕共享</string>
<string name="message_hint">发送频道广播消息</string>
<string name="send_message">发送</string>
<string name="ctrl_beauty_on">美颜开启</string>