屏幕共享美颜更新
This commit is contained in:
parent
e09271a60e
commit
6a1f3db5eb
|
|
@ -33,3 +33,6 @@ google-services.json
|
||||||
# Android Profiling
|
# Android Profiling
|
||||||
*.hprof
|
*.hprof
|
||||||
|
|
||||||
|
#sdk files
|
||||||
|
SellyCloudSDK/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ plugins {
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
}
|
}
|
||||||
|
|
||||||
def usePublishedSdk = (findProperty("usePublishedSdk")?.toString()?.toBoolean() ?: false)
|
|
||||||
def sdkGroupId = rootProject.findProperty("sellySdkGroupId") ?: "com.sellycloud"
|
def sdkGroupId = rootProject.findProperty("sellySdkGroupId") ?: "com.sellycloud"
|
||||||
def sdkArtifactId = rootProject.findProperty("sellySdkArtifactId") ?: "sellycloudsdk"
|
def sdkArtifactId = rootProject.findProperty("sellySdkArtifactId") ?: "sellycloudsdk"
|
||||||
def sdkVersion = rootProject.findProperty("sellySdkVersion") ?: "1.0.0"
|
def sdkVersion = rootProject.findProperty("sellySdkVersion") ?: "1.0.0"
|
||||||
|
def hasLocalSdk = rootProject.file("SellyCloudSDK").exists()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.demo.SellyCloudSDK'
|
namespace 'com.demo.SellyCloudSDK'
|
||||||
|
|
@ -63,13 +63,30 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// SellyCloudSDK 需要的依赖(需要手动添加)
|
if (hasLocalSdk) {
|
||||||
implementation 'com.google.code.gson:gson:2.10.1'
|
implementation project(':SellyCloudSDK')
|
||||||
implementation 'com.github.pedroSG94.RootEncoder:library:2.6.6'
|
} else {
|
||||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
implementation files(
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
"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"],
|
||||||
|
exclude: [
|
||||||
|
"${sdkArtifactId}-${sdkVersion}.aar",
|
||||||
|
"ijkplayer-cmake-release.aar",
|
||||||
|
"Kiwi.aar",
|
||||||
|
"libwebrtc.aar"
|
||||||
|
]
|
||||||
|
)
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0-alpha03'
|
implementation 'androidx.appcompat:appcompat:1.7.0-alpha03'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha13'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha13'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
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.lifecycle:lifecycle-livedata-ktx:2.8.4'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -11,6 +11,7 @@
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<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_CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
|
<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.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
|
@ -55,7 +56,7 @@
|
||||||
<service
|
<service
|
||||||
android:name=".interactive.InteractiveForegroundService"
|
android:name=".interactive.InteractiveForegroundService"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="camera|microphone" />
|
android:foregroundServiceType="camera|microphone|mediaProjection" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,7 @@ import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.app.ServiceCompat
|
||||||
import com.demo.SellyCloudSDK.R
|
import com.demo.SellyCloudSDK.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,7 +19,27 @@ import com.demo.SellyCloudSDK.R
|
||||||
class InteractiveForegroundService : Service() {
|
class InteractiveForegroundService : Service() {
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
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
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,9 +82,11 @@ class InteractiveForegroundService : Service() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val CHANNEL_ID = "interactive_call_foreground"
|
private const val CHANNEL_ID = "interactive_call_foreground"
|
||||||
private const val NOTIFICATION_ID = 0x101
|
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)
|
val intent = Intent(context, InteractiveForegroundService::class.java)
|
||||||
|
intent.putExtra(EXTRA_MEDIA_PROJECTION, includeMediaProjection)
|
||||||
ContextCompat.startForegroundService(context, intent)
|
ContextCompat.startForegroundService(context, intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
package com.demo.SellyCloudSDK.interactive
|
package com.demo.SellyCloudSDK.interactive
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.media.projection.MediaProjectionManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
@ -63,6 +66,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
private var pendingJoinRequest: JoinRequest? = null
|
private var pendingJoinRequest: JoinRequest? = null
|
||||||
private var currentCallId: String? = null
|
private var currentCallId: String? = null
|
||||||
@Volatile private var selfUserId: String? = null
|
@Volatile private var selfUserId: String? = null
|
||||||
|
private var isScreenSharing: Boolean = false
|
||||||
|
|
||||||
private val permissionLauncher = registerForActivityResult(
|
private val permissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
|
@ -78,6 +82,17 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
if (!granted) setJoinButtonEnabled(true)
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityInteractiveLiveBinding.inflate(layoutInflater)
|
binding = ActivityInteractiveLiveBinding.inflate(layoutInflater)
|
||||||
|
|
@ -151,7 +166,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
setEventHandler(rtcEventHandler)
|
setEventHandler(rtcEventHandler)
|
||||||
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
|
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
|
||||||
// setVideoEncoderConfiguration(InteractiveVideoEncoderConfig()) 使用默认值
|
// setVideoEncoderConfiguration(InteractiveVideoEncoderConfig()) 使用默认值
|
||||||
setVideoEncoderConfiguration(InteractiveVideoEncoderConfig(640, 480 , fps = 20, minBitrateKbps = 150, maxBitrateKbps = 350))
|
setVideoEncoderConfiguration(InteractiveVideoEncoderConfig(640, 480 , fps = 20, minBitrateKbps = 150, maxBitrateKbps = 850))
|
||||||
setDefaultAudioRoutetoSpeakerphone(true)
|
setDefaultAudioRoutetoSpeakerphone(true)
|
||||||
setCaptureVideoFrameInterceptor { frame ->
|
setCaptureVideoFrameInterceptor { frame ->
|
||||||
if (!beautyEnabled) return@setCaptureVideoFrameInterceptor frame
|
if (!beautyEnabled) return@setCaptureVideoFrameInterceptor frame
|
||||||
|
|
@ -277,6 +292,10 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
val tip = "onStreamStateChanged[$peerId] state=$state code=$code ${message ?: ""}"
|
val tip = "onStreamStateChanged[$peerId] state=$state code=$code ${message ?: ""}"
|
||||||
Log.d(TAG, tip)
|
Log.d(TAG, tip)
|
||||||
Toast.makeText(this@InteractiveLiveActivity, tip, Toast.LENGTH_SHORT).show()
|
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()
|
updateControlButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -390,6 +427,14 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.ctrl_camera_on)
|
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() {
|
private fun applyLocalPreviewVisibility() {
|
||||||
|
|
@ -402,6 +447,36 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
updateLocalStatsLabel()
|
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() {
|
private fun attemptJoin() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
val callId = binding.etCallId.text.toString().trim()
|
val callId = binding.etCallId.text.toString().trim()
|
||||||
|
|
@ -643,6 +718,7 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
||||||
callDurationSeconds = 0
|
callDurationSeconds = 0
|
||||||
lastMessage = null
|
lastMessage = null
|
||||||
binding.tvMessageLog.text = getString(R.string.message_none)
|
binding.tvMessageLog.text = getString(R.string.message_none)
|
||||||
|
isScreenSharing = false
|
||||||
updateControlButtons()
|
updateControlButtons()
|
||||||
updateLocalStatsLabel()
|
updateLocalStatsLabel()
|
||||||
updateCallInfo()
|
updateCallInfo()
|
||||||
|
|
|
||||||
|
|
@ -222,6 +222,13 @@
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/ctrl_mic_off" />
|
android:text="@string/ctrl_mic_off" />
|
||||||
</LinearLayout>
|
</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>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@
|
||||||
<string name="ctrl_mic_on">开启麦克风</string>
|
<string name="ctrl_mic_on">开启麦克风</string>
|
||||||
<string name="ctrl_camera_off">关闭摄像头</string>
|
<string name="ctrl_camera_off">关闭摄像头</string>
|
||||||
<string name="ctrl_camera_on">开启摄像头</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="message_hint">发送频道广播消息</string>
|
||||||
<string name="send_message">发送</string>
|
<string name="send_message">发送</string>
|
||||||
<string name="ctrl_beauty_on">美颜开启</string>
|
<string name="ctrl_beauty_on">美颜开启</string>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ pluginManagement {
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def sdkDir = file('SellyCloudSDK')
|
||||||
|
def hasLocalSdk = sdkDir.exists()
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||||
repositories {
|
repositories {
|
||||||
|
|
@ -12,11 +16,19 @@ dependencyResolutionManagement {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
// Local AARs for SellyCloudSDK
|
// Local AARs for the demo or the SDK when present
|
||||||
flatDir { dirs file('SellyCloudSDK/libs') }
|
flatDir {
|
||||||
|
dirs file('example/libs')
|
||||||
|
if (hasLocalSdk) {
|
||||||
|
dirs sdkDir.toPath().resolve('libs').toFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "SellyCLoudSDKExample"
|
rootProject.name = "SellyCLoudSDKExample"
|
||||||
include ':example'
|
include ':example'
|
||||||
include ':SellyCloudSDK'
|
// Use the SDK module only when it exists locally; otherwise rely on the published/prebuilt AAR.
|
||||||
|
if (hasLocalSdk) {
|
||||||
|
include ':SellyCloudSDK'
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue