This commit is contained in:
2026-01-14 11:46:52 +08:00
parent dca2a0b1e8
commit 2c01b1da9d
12 changed files with 796 additions and 49 deletions

View File

@@ -0,0 +1,401 @@
# Selly Live SDK 推拉流接入文档Android
> 统一 SDK 名称:**SellyCloudSDK**
> 本文档适用于 Android 客户端,面向对外集成方与内部使用。
---
## 1. 概述
Selly Live SDK 提供完整的音视频直播能力,支持 **推流(直播发布)****拉流(直播播放)** 两大核心场景,适用于泛直播、互动直播、实时音视频等业务。
### 主要能力
- 支持 **RTMP / RTC** 推流与播放模式
- 高性能音视频采集与编码
- 灵活的视频参数配置(分辨率 / 帧率 / 码率)
- 推流状态与统计回调
- 拉流播放状态与错误回调
- 支持视频帧处理(美颜 / 滤镜 / 水印)
- 基于 **Token 的安全鉴权机制**
---
## 2. 系统要求
- Android 8.0+Demo `minSdk` 为 26
- 需真机运行(音视频采集限制)
- 需要摄像头 / 麦克风权限
---
## 3. SDK 集成
### 3.1 项目结构参考
- `example/`Android Demo 工程
- 推流示例:`example/src/main/java/com/demo/SellyCloudSDK/live/LivePushActivity.kt`
- 拉流示例:`example/src/main/java/com/demo/SellyCloudSDK/live/LivePlayActivity.kt`
- `example/libs/`:本地 AAR 依赖存放目录
### 3.2 Gradle 依赖方式(仅 AAR
```gradle
dependencies {
implementation files("libs/sellycloudsdk-1.0.0.aar")
}
```
> 若接入美颜等能力,请按业务侧 SDK 要求额外引入第三方美颜库Demo 使用 FaceUnity AAR
### 3.3 权限配置AndroidManifest.xml
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
> 如需截图保存且兼容 Android 9 及以下,可额外申请 `WRITE_EXTERNAL_STORAGE`。
---
## 4. Token 鉴权机制(重点)
### 4.1 Token 注入方式
| 场景 | 设置位置 |
| ---- | ---- |
| 推流 | `SellyLiveVideoPusher.token` |
| 拉流 | `SellyLiveVideoPlayer.token` |
说明:
- Token **不拼接到 URL**
- Token **不绑定 streamId**
- SDK 内部在建立连接时自动携带当前 Token
- 直接使用 RTMP 地址推/拉流不需要 Token可不设置
### 4.2 Token 设置时机(强约束)
#### 推流
必须在以下接口调用 **之前** 设置:
- `startLiveWithStreamId(...)`
#### 拉流
必须在以下接口调用 **之前** 设置:
- `prepareToPlay()`
- `play()`
> ⚠️ 在连接建立后修改 Token不会影响当前连接。
### 4.3 Token 刷新机制说明
- SDK **不提供自动刷新**
- 业务层可在任意时刻 **重新设置 token 属性**
推荐流程:
1. 业务侧向服务端获取新 Token
2. 调用 `pusher.token = newToken` / `player.token = newToken`
3. 停止并重新开始推流 / 拉流流程
---
## 5. 推流接入详解
### 5.1 创建推流实例
```kotlin
val pusher = SellyLiveVideoPusher.initWithLiveMode(
context = this,
liveMode = SellyLiveMode.RTMP
)
pusher.delegate = object : SellyLiveVideoPusherDelegate {
override fun liveStatusDidChanged(status: SellyLiveStatus) {}
override fun onStatisticsUpdate(stats: SellyLivePusherStats) {}
override fun onError(error: SellyLiveError) {}
}
```
### 5.2 视频参数配置与预览
```kotlin
val config = SellyLiveVideoConfiguration.defaultConfiguration().apply {
videoSize = SellyLiveVideoResolution.RES_1280x720
videoFrameRate = 25
videoBitRate = 1000 * 1000
videoMinBitRate = 500 * 1000
outputImageOrientation = SellyLiveOrientation.PORTRAIT
}
pusher.attachPreview(previewContainer)
pusher.startRunning(
cameraPosition = SellyLiveCameraPosition.FRONT,
videoConfig = config,
audioConfig = null
)
```
### 5.3 设置推流 Token使用 streamId 时)
```kotlin
pusher.token = pushToken
```
### 5.4 开始/停止推流
```kotlin
pusher.startLiveWithStreamId(streamId)
```
```kotlin
pusher.stopLive()
```
#### 直接使用 RTMP 地址推流Demo 未覆盖)
```kotlin
val rtmpUrl = "rtmp://your.host/live/streamKey"
pusher.startLiveWithUrl(rtmpUrl)
```
> 直接使用 RTMP 地址推流不需要 Token可不设置。
#### 停止推流(带回调)
```kotlin
pusher.stopLive { error ->
if (error != null) {
// 处理停止失败
}
}
```
### 5.5 常用控制接口
- `setMuted(true/false)`:静音
- `switchCameraPosition(...)`:切换摄像头
- `switchCamera()`:前后摄像头切换(自动切换)
- `startCamera()` / `stopCamera()`:控制摄像头
- `startMicrophone()` / `stopMicrophone()`:控制麦克风
- `setCameraEnabled(true/false)`:关闭/开启摄像头
- `setStreamOrientation(...)`:切换推流方向
- `setVideoConfiguration(...)` + `changeResolution(...)`:动态调整分辨率
- `setBeautyEngine(...)` + `setBeautyEnabled(...)`:接入美颜
- `setBeautyLevel(level)`:设置美颜强度
- `setBitmapAsVideoSource(...)` / `restoreCameraVideoSource()`:背景图推流
### 5.6 生命周期建议
在宿主 Activity 中对齐生命周期:
- `onResume()``pusher.onResume()`
- `onPause()``pusher.onPause()`
- `onDestroy()``pusher.release()`
### 5.7 状态与统计回调
**状态枚举:**
- `Idle`
- `Connecting`
- `Publishing`
- `Reconnecting`
- `Stopped`
- `Failed`
**统计字段:**
- fps
- videoBitrateKbps / audioBitrateKbps
- rttMs
- cpu 使用率Demo 通过 `CpuUsage` 读取)
### 5.8 推流 API 速览(含 Demo 未覆盖)
初始化与预览:
- `initWithLiveMode(context, liveMode)`:创建推流实例
- `setPreviewView(view)`:设置预览 View
- `attachPreview(container)`:将预览 View 添加到容器
- `getPreviewView()`:获取当前预览 View
采集与推流:
- `startRunning(cameraPosition, videoConfig, audioConfig)`:开始采集预览
- `setVideoConfiguration(config)`:更新视频参数
- `startLiveWithStreamId(streamId)`:使用 streamId 推流
- `startLiveWithUrl(url)`:使用完整 URL 推流
- `stopLive()` / `stopLive(callback)`:停止推流
设备与音视频控制:
- `switchCameraPosition(position)` / `switchCamera()`:切换摄像头
- `startCamera()` / `stopCamera()`:启动 / 停止摄像头
- `startMicrophone()` / `stopMicrophone()`:启动 / 停止麦克风
- `setMuted(true/false)`:静音推流音频
- `setCameraEnabled(true/false)`:开启 / 关闭摄像头
美颜与画面:
- `setBeautyEngine(engine)`:设置美颜引擎
- `setBeautyEnabled(true/false)`:启用 / 关闭美颜
- `setBeautyLevel(level)`:设置美颜强度
- `setStreamOrientation(orientation)`:设置推流方向
- `changeResolution(width, height)`:动态调整分辨率
- `setBitmapAsVideoSource(bitmap)` / `restoreCameraVideoSource()`:背景图推流
生命周期:
- `onPause()` / `onResume()` / `release()`:与 Activity 生命周期对齐
---
## 6. 拉流接入详解
### 6.1 创建播放器
```kotlin
val player = SellyLiveVideoPlayer.initWithStreamId(
context = this,
streamId = streamId,
liveMode = SellyLiveMode.RTC
)
// 或直接使用完整 URL
// val player = SellyLiveVideoPlayer.initWithUrl(this, playUrl)
```
若需要指定 `vhost` / `appName`
```kotlin
val player = SellyLiveVideoPlayer.initWithStreamId(
context = this,
streamId = streamId,
liveMode = SellyLiveMode.RTMP,
vhost = "your-vhost",
appName = "live"
)
```
### 6.2 设置拉流 Token使用 streamId 时)
```kotlin
player.token = playToken
```
> 直接使用 RTMP 地址拉流不需要 Token可不设置。
### 6.3 播放流程
```kotlin
player.attachRenderView(renderContainer)
player.prepareToPlay()
player.play()
```
控制接口:
- `pause()`
- `stop()`
- `play()`
- `setMuted(true/false)`
- `release()`
补充接口Demo 未覆盖):
- `setRenderView(view)`:手动指定渲染 View
- `seekBy(deltaMs)`:播放进度跳转(仅在流支持快进/回放时有效)
### 6.4 播放回调
```kotlin
player.delegate = object : SellyLiveVideoPlayerDelegate {
override fun playbackStateChanged(state: SellyPlayerState) {}
override fun onFirstVideoFrameRendered() {}
override fun onFirstAudioFrameRendered() {}
override fun onLatencyChasingUpdate(update: SellyLatencyChasingUpdate) {}
override fun onLatencyChasingReloadRequired(latencyMs: Long) {}
override fun onError(error: SellyLiveError) {}
}
```
状态枚举:
- `Idle`
- `Connecting`
- `Playing`
- `Paused`
- `Stopped`
- `Reconnecting`
- `Failed`
### 6.5 播放 API 速览(含 Demo 未覆盖)
创建与渲染:
- `initWithStreamId(context, streamId, liveMode, vhost, appName)`:使用 streamId 创建播放器
- `initWithUrl(context, url)`:使用完整 URL 创建播放器
- `attachRenderView(container)` / `setRenderView(view)`:设置渲染 View
- `getRenderView()`:获取当前渲染 View
播放控制:
- `prepareToPlay()` / `play()` / `pause()` / `stop()`:播放流程控制
- `seekBy(deltaMs)`:播放进度跳转(流支持回放时有效)
- `isPlaying()`:查询播放状态
- `setMuted(true/false)`:静音播放
统计与释放:
- `setStatsListener { snapshot -> }`:播放统计回调
- `release()`:释放播放器资源
---
## 7. 错误处理与重试建议
### Token 错误
1. 停止当前推 / 拉
2. 获取新 Token
3. 重新设置 Token
4. 重新开始推流 / 拉流流程
### 网络错误
- 监听 `onStatisticsUpdate` 或播放器状态
- 弱网时适当降低分辨率 / 码率
- 必要时重启连接
---
## 8. 最佳实践
- 推流前先完成采集预览
- Token 即将过期前提前刷新
- 使用统计回调做质量监控
- 拉流失败避免无限重试
---
## 9. 常见问题FAQ
### Q1Token 可以拼接到 URL 吗?
**A** 不可以。
SDK 不解析 URL 中的鉴权信息,所有鉴权均通过 `token` 属性完成。
### Q2运行中修改 Token 是否生效?
**A**
运行中修改 Token **不会影响当前已建立的连接**
**下次重连或重新启动推流 / 拉流时会使用新的 Token**
### Q3播放器出现黑屏怎么办
**A** 可按以下步骤排查:
- 检查播放地址是否正确
- 确认当前网络连接正常
- 查看播放器回调中的错误信息
- 确认视频流格式是否被 SDK 支持

View File

@@ -0,0 +1,375 @@
# SellyRTC Android SDK 接入文档
本文档用于指导 **Android App 开发者** 快速接入 SellyRTC完成一对一或多人实时音视频通话能力。
SDK 核心以 `InteractiveRtcEngine` 为中心,通过 `InteractiveRtcEngineEventHandler` 回调通话状态、用户事件、音视频状态及异常。
---
## 目录
1. 准备工作
2. 快速开始
3. 基础通话流程
4. 常用功能
5. 屏幕分享
6. 视频帧前后处理
7. 事件回调EventHandler
8. 通话统计
9. Token 机制
10. 常见问题FAQ
---
## 准备工作
### 1. 环境要求
- Android 8.0+Demo `minSdk` 为 26
- 需真机运行(摄像头 / 麦克风)
### 2. 权限配置AndroidManifest.xml
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
---
## 快速开始
### 1. 创建引擎
```kotlin
val appId = getString(R.string.signaling_app_id)
val token = getString(R.string.signaling_token).takeIf { it.isNotBlank() }
val kiwiRsName = getString(R.string.signaling_kiwi_rsname).trim()
val rtcEngine = InteractiveRtcEngine.create(
InteractiveRtcEngineConfig(
context = applicationContext,
appId = appId,
defaultToken = token,
kiwiRsName = kiwiRsName
)
).apply {
setEventHandler(eventHandler)
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
setVideoEncoderConfiguration(
InteractiveVideoEncoderConfig(
width = 640,
height = 480,
fps = 20,
minBitrateKbps = 150,
maxBitrateKbps = 850
)
)
setDefaultAudioRoutetoSpeakerphone(true)
}
```
> `InteractiveRtcEngineConfig` 与默认 token 配置见 `example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt`。
### 2. 设置本地/远端画布
```kotlin
val localRenderer = SurfaceViewRenderer(this)
rtcEngine.setupLocalVideo(InteractiveVideoCanvas(localRenderer, userId))
```
```kotlin
val remoteRenderer = SurfaceViewRenderer(this)
rtcEngine.setupRemoteVideo(InteractiveVideoCanvas(remoteRenderer, remoteUserId))
```
### 3. 加入通话
```kotlin
val options = InteractiveChannelMediaOptions(callType = CallType.ONE_TO_ONE)
rtcEngine.joinChannel(
token = token,
channel = callId,
userId = userId,
options = options,
tokenSecret = tokenSecret,
tokenExpiresAtSec = tokenExpiresAtSec,
tokenTtlSeconds = tokenTtlSeconds
)
```
结束通话:
```kotlin
rtcEngine.leaveChannel()
```
销毁引擎:
```kotlin
InteractiveRtcEngine.destroy(rtcEngine)
```
### 4. 进阶配置Demo 未覆盖)
#### 4.1 InteractiveRtcEngineConfig 高级字段
```kotlin
val config = InteractiveRtcEngineConfig(
context = applicationContext,
appId = appId,
defaultCallType = CallType.ONE_TO_ONE,
defaultToken = token,
kiwiRsName = kiwiRsName,
signalingUrlPrefix = "https://",
signalingUrlSuffix = "/signaling"
)
```
#### 4.2 InteractiveChannelMediaOptions 订阅控制
```kotlin
val options = InteractiveChannelMediaOptions(
callType = CallType.GROUP,
autoSubscribeAudio = true,
autoSubscribeVideo = false
)
```
#### 4.3 InteractiveVideoEncoderConfig 更多参数
可选项(按需设置):
- `codecType`:编码类型
- `bitrateMode`:码率控制模式
- `minBitrateKbps` / `maxBitrateKbps`:码率范围
- `orientationMode`:画面方向策略
- `degradationPreference`:弱网降级策略
- `mirrorMode`:镜像策略
---
## 基础通话流程
1. 创建 `InteractiveRtcEngine`
2. 设置 `EventHandler`
3. 配置 `InteractiveVideoEncoderConfig`
4. 设置本地画布 `setupLocalVideo`
5. `joinChannel` 加入频道
6. `onUserJoined` 后设置远端画布
7. 通话中进行音视频控制
8. `leaveChannel` 并释放资源
---
## 常用功能
### 本地音视频控制
```kotlin
rtcEngine.enableLocalVideo(true)
rtcEngine.enableLocalAudio(false)
```
### 切换摄像头
```kotlin
rtcEngine.switchCamera()
```
### 音频输出(扬声器 / 听筒)
```kotlin
rtcEngine.setDefaultAudioRoutetoSpeakerphone(true)
```
### 发送自定义消息
```kotlin
rtcEngine.sendMessage("hello") { error ->
// error == null 表示成功
}
```
### 按用户静音远端音频/视频
```kotlin
rtcEngine.muteRemoteAudioStream(remoteUserId, true)
rtcEngine.muteRemoteVideoStream(remoteUserId, true)
```
### 全量静音远端音视频Demo 未覆盖)
```kotlin
rtcEngine.muteAllRemoteAudioStreams(true)
rtcEngine.muteAllRemoteVideoStreams(true)
```
### 清理远端画布Demo 未覆盖)
```kotlin
rtcEngine.clearRemoteVideo(remoteUserId)
```
### 向指定用户发送消息Demo 未覆盖)
```kotlin
rtcEngine.sendMessage("hello", targetUserId) { error ->
// error == null 表示成功
}
```
---
## 屏幕分享
```kotlin
val started = rtcEngine.startScreenShare(
resultCode = resultCode,
data = data,
width = 720,
height = 1280,
fps = 15
)
```
```kotlin
rtcEngine.stopScreenShare()
```
```kotlin
val isSharing = rtcEngine.isScreenSharing()
```
> Android 14+ 屏幕捕获需要前台服务Demo 使用 `InteractiveForegroundService` 处理)。
---
## 视频帧前后处理
```kotlin
rtcEngine.setCaptureVideoFrameInterceptor { frame ->
// 在此处理美颜/滤镜,返回新的 frame
frame
}
```
```kotlin
rtcEngine.setRenderVideoFrameInterceptor { frame, userId ->
// 远端渲染前处理,返回 true 表示继续渲染
true
}
```
> Demo 中的美颜示例见:
> `example/src/main/java/com/demo/SellyCloudSDK/beauty/FuVideoFrameInterceptor.kt`
---
## 事件回调EventHandler
```kotlin
val eventHandler = object : InteractiveRtcEngineEventHandler {
override fun onJoinChannelSuccess(channel: String, userId: String, code: Int) {}
override fun onLeaveChannel(durationSeconds: Int) {}
override fun onUserJoined(userId: String, code: Int) {}
override fun onUserLeave(userId: String, code: Int) {}
override fun onConnectionStateChanged(state: InteractiveConnectionState, reason: Int, userId: String?) {}
override fun onMessageReceived(message: String, userId: String?) {}
override fun onError(code: String, message: String) {}
override fun onTokenWillExpire(token: String?, expiresAt: Long) {}
override fun onTokenExpired(token: String?, expiresAt: Long) {}
override fun onLocalVideoStats(stats: InteractiveStreamStats) {}
override fun onRemoteVideoStats(stats: InteractiveStreamStats) {}
override fun onDuration(durationSeconds: Long) {}
override fun onRemoteVideoEnabled(enabled: Boolean, userId: String?) {}
override fun onRemoteAudioEnabled(enabled: Boolean, userId: String?) {}
override fun onStreamStateChanged(peerId: String, state: RemoteState, code: Int, message: String?) {}
}
```
---
## 通话统计
`InteractiveStreamStats` 常用字段:
- width / height
- fps
- videoBitrateKbps / audioBitrateKbps
- rttMs
- videoCodec / audioCodec
---
## Token 机制
### Token 来源
- 业务侧服务端生成 Token 并下发
- Demo 可在 `strings.xml` 配置 `signaling_token``signaling_secret` 进行本地生成
### Token 过期处理
- `onTokenWillExpire` / `onTokenExpired` 回调提示即将过期或已过期
- 建议获取新 Token 并 **重新 joinChannel**
Token 续期接口Demo 未覆盖):
```kotlin
rtcEngine.renewToken(newToken, expiresAtSec)
```
---
## 更多 API 速览(含 Demo 未覆盖)
引擎创建与销毁:
- `InteractiveRtcEngine.create(config)`:创建引擎
- `InteractiveRtcEngine.destroy(engine)` / `engine.destroy()`:释放引擎
通话控制:
- `setEventHandler(handler)`:设置事件回调
- `setClientRole(role)`:设置角色(主播/观众)
- `setVideoEncoderConfiguration(config)`:设置编码参数
- `setDefaultAudioRoutetoSpeakerphone(true/false)`:设置音频输出
- `joinChannel(...)` / `leaveChannel()`:加入 / 离开频道
本地与远端控制:
- `setupLocalVideo(canvas)` / `setupRemoteVideo(canvas)`:设置画布
- `clearRemoteVideo(userId)`:清理远端画面
- `enableLocalVideo(true/false)` / `enableLocalAudio(true/false)`:开关本地音视频
- `muteRemoteAudioStream(userId, true/false)` / `muteRemoteVideoStream(userId, true/false)`:按用户静音
- `muteAllRemoteAudioStreams(true/false)` / `muteAllRemoteVideoStreams(true/false)`:全量静音
- `switchCamera()`:切换摄像头
帧处理与屏幕共享:
- `setCaptureVideoFrameInterceptor(...)`:采集前帧处理
- `setRenderVideoFrameInterceptor(...)`:渲染前帧处理
- `startScreenShare(...)` / `stopScreenShare()` / `isScreenSharing()`:屏幕共享
消息与 Token
- `sendMessage(message, targetUserId, callback)`:发送消息(`targetUserId` 可为空)
- `renewToken(newToken, expiresAtSec)`:续期 Token
---
## 常见问题FAQ
### Q远端画面不显示
1. 是否设置了正确的 `remoteUserId`
2. 是否在 `onUserJoined` 后调用 `setupRemoteVideo`
3. 远端是否关闭了视频
### Q加入频道失败
1. 检查 `signaling_app_id` 是否正确
2. Token 是否为空或已过期
3. 网络是否受限
### Q屏幕分享失败
1. 是否已获取 `MediaProjection` 授权
2. Android 14+ 是否启动前台服务

View File

@@ -3,12 +3,9 @@ plugins {
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
} }
def sdkGroupId = rootProject.findProperty("sellySdkGroupId") ?: "com.sellycloud" def sdkAarPath = "libs/${findProperty("sellySdkArtifactId") ?: "sellycloudsdk"}-${findProperty("sellySdkVersion") ?: "1.0.0"}.aar"
def sdkArtifactId = rootProject.findProperty("sellySdkArtifactId") ?: "sellycloudsdk"
def sdkVersion = rootProject.findProperty("sellySdkVersion") ?: "1.0.0"
def hasLocalSdk = rootProject.file("SellyCloudSDK").exists()
def releaseStorePath = project.rootProject.file(findProperty("MY_STORE_FILE") ?: "release.keystore") def releaseStorePath = project.rootProject.file(findProperty("MY_STORE_FILE") ?: "release.keystore")
def hasReleaseKeystore = releaseStorePath != null && releaseStorePath.exists() def hasReleaseKeystore = releaseStorePath.exists()
android { android {
namespace 'com.demo.SellyCloudSDK' namespace 'com.demo.SellyCloudSDK'
@@ -67,34 +64,19 @@ android {
} }
dependencies { dependencies {
if (hasLocalSdk) {
implementation project(':SellyCloudSDK')
} else {
implementation files( implementation files(
"libs/${sdkArtifactId}-${sdkVersion}.aar", sdkAarPath,
"libs/ijkplayer-cmake-release.aar", "libs/fu_core_all_feature_release.aar",
"libs/Kiwi.aar", "libs/fu_model_all_feature_release.aar"
"libs/libwebrtc.aar"
)
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.github.pedroSG94.RootEncoder:library:2.6.6'
}
implementation fileTree(
dir: "libs",
include: ["*.jar", "*.aar"],
exclude: [
"${sdkArtifactId}-${sdkVersion}.aar",
"ijkplayer-cmake-release.aar",
"Kiwi.aar",
"libwebrtc.aar"
]
) )
implementation fileTree(dir: "libs", include: ["*.jar"])
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 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-ktx:1.13.1'
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation 'androidx.constraintlayout:constraintlayout:2.2.0-alpha13'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.activity:activity-ktx:1.9.2' implementation 'androidx.activity:activity-ktx:1.9.2'
@@ -102,7 +84,6 @@ 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'
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation "io.coil-kt:coil:2.6.0" implementation "io.coil-kt:coil:2.6.0"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -125,14 +125,6 @@ class LivePlayActivity : AppCompatActivity() {
} }
updatePreviewVisibility() updatePreviewVisibility()
logEvent("状态变更: ${formatState(state)}") logEvent("状态变更: ${formatState(state)}")
if (state == SellyPlayerState.Playing && firstVideoFrameElapsedMs == null) {
val startMs = playAttemptStartElapsedMs
firstVideoFrameElapsedMs = SystemClock.elapsedRealtime()
if (startMs != null) {
firstVideoFrameCostMs = firstVideoFrameElapsedMs!! - startMs
logEvent("首帧视频耗时=${firstVideoFrameCostMs}ms")
}
}
} }
} }
@@ -140,6 +132,11 @@ class LivePlayActivity : AppCompatActivity() {
runOnUiThread { runOnUiThread {
hasFirstVideoFrameRendered = true hasFirstVideoFrameRendered = true
updatePreviewVisibility() updatePreviewVisibility()
if (firstVideoFrameElapsedMs != null) return@runOnUiThread
val startMs = playAttemptStartElapsedMs ?: return@runOnUiThread
firstVideoFrameElapsedMs = SystemClock.elapsedRealtime()
firstVideoFrameCostMs = firstVideoFrameElapsedMs!! - startMs
logEvent("首帧视频耗时=${firstVideoFrameCostMs}ms")
} }
} }

View File

@@ -220,8 +220,10 @@ class LivePushActivity : AppCompatActivity() {
try { try {
val videoConfig = buildVideoConfig(settings) val videoConfig = buildVideoConfig(settings)
pusherClient.setVideoConfiguration(videoConfig)
pusherClient.attachPreview(binding.previewContainer) pusherClient.attachPreview(binding.previewContainer)
pusherClient.startRunning(currentFacing, videoConfig, null) pusherClient.startRunning(currentFacing, videoConfig, null)
applyStreamConfig(settings)
} catch (t: Throwable) { } catch (t: Throwable) {
Toast.makeText(this, "初始化预览失败: ${t.message}", Toast.LENGTH_LONG).show() Toast.makeText(this, "初始化预览失败: ${t.message}", Toast.LENGTH_LONG).show()
} }

View File

@@ -1,5 +1,6 @@
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
android.experimental.fusedLibrarySupport=true
# Increase Gradle daemon heap to avoid OOM during packaging large AAR/assets # Increase Gradle daemon heap to avoid OOM during packaging large AAR/assets
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dkotlin.daemon.jvm.options="-Xmx2g" org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dkotlin.daemon.jvm.options="-Xmx2g"

View File

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