屏幕共享美颜更新

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

3
.gitignore vendored
View File

@ -33,3 +33,6 @@ google-services.json
# Android Profiling
*.hprof
#sdk files
SellyCloudSDK/

View File

@ -3,10 +3,10 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
def usePublishedSdk = (findProperty("usePublishedSdk")?.toString()?.toBoolean() ?: false)
def sdkGroupId = rootProject.findProperty("sellySdkGroupId") ?: "com.sellycloud"
def sdkArtifactId = rootProject.findProperty("sellySdkArtifactId") ?: "sellycloudsdk"
def sdkVersion = rootProject.findProperty("sellySdkVersion") ?: "1.0.0"
def hasLocalSdk = rootProject.file("SellyCloudSDK").exists()
android {
namespace 'com.demo.SellyCloudSDK'
@ -63,13 +63,30 @@ android {
}
dependencies {
// SellyCloudSDK
if (hasLocalSdk) {
implementation project(':SellyCloudSDK')
} else {
implementation files(
"libs/${sdkArtifactId}-${sdkVersion}.aar",
"libs/ijkplayer-cmake-release.aar",
"libs/Kiwi.aar",
"libs/libwebrtc.aar"
)
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.pedroSG94.RootEncoder:library:2.6.6'
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
}
implementation fileTree(
dir: "libs",
include: ["*.jar", "*.aar"],
exclude: [
"${sdkArtifactId}-${sdkVersion}.aar",
"ijkplayer-cmake-release.aar",
"Kiwi.aar",
"libwebrtc.aar"
]
)
implementation 'androidx.appcompat:appcompat:1.7.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha13'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
@ -81,6 +98,4 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.4'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
}

Binary file not shown.

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 {
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>

View File

@ -5,6 +5,10 @@ pluginManagement {
gradlePluginPortal()
}
}
def sdkDir = file('SellyCloudSDK')
def hasLocalSdk = sdkDir.exists()
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
@ -12,11 +16,19 @@ dependencyResolutionManagement {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
// Local AARs for SellyCloudSDK
flatDir { dirs file('SellyCloudSDK/libs') }
// Local AARs for the demo or the SDK when present
flatDir {
dirs file('example/libs')
if (hasLocalSdk) {
dirs sdkDir.toPath().resolve('libs').toFile()
}
}
}
}
rootProject.name = "SellyCLoudSDKExample"
include ':example'
// Use the SDK module only when it exists locally; otherwise rely on the published/prebuilt AAR.
if (hasLocalSdk) {
include ':SellyCloudSDK'
}