feat(app): 添加摄像头配置和视频流设置功能
- 在 CameraOption 中新增 supportedResolutions 属性以支持分辨率选择 - 实现 StreamOrientation 和 StreamResolution 数据类用于视频方向和分辨率管理 - 重构 MainActivity 为多页面应用,添加设置页面支持摄像头、分辨率、方向配置 - 集成下拉菜单选择摄像头和视频分辨率功能 - 更新 PosefitStreamingService 支持视频分辨率和方向参数传递 - 移除 AndroidManifest 中 MainActivity 的屏幕方向锁定设置 - 添加详细的视频捕获帧日志记录和可用摄像头格式输出 - 优化视频流启动流程,支持多种分辨率和方向设置
This commit is contained in:
@@ -10,6 +10,8 @@ import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import com.kimgo.posefit.sender.CameraFacing
|
||||
import com.kimgo.posefit.sender.StreamOrientation
|
||||
import com.kimgo.posefit.sender.StreamResolution
|
||||
import com.kimgo.posefit.sender.WebRtcSenderClient
|
||||
import timber.log.Timber
|
||||
|
||||
@@ -29,23 +31,31 @@ class PosefitStreamingService : Service() {
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
ACTION_START -> {
|
||||
val signalingUrl = intent.getStringExtra(EXTRA_SIGNALING_URL) ?: DEFAULT_SIGNALING_URL
|
||||
val cameraFacing = intent.getStringExtra(EXTRA_CAMERA_FACING)
|
||||
?.let { runCatching { CameraFacing.valueOf(it) }.getOrNull() }
|
||||
?: CameraFacing.BACK
|
||||
val preferredCameraName = intent.getStringExtra(EXTRA_CAMERA_NAME)
|
||||
|
||||
startForegroundNotification(signalingUrl, cameraFacing, preferredCameraName)
|
||||
startStreaming(signalingUrl, cameraFacing, preferredCameraName)
|
||||
ACTION_START -> {
|
||||
val settings = StreamSettings(
|
||||
signalingUrl = intent.getStringExtra(EXTRA_SIGNALING_URL) ?: DEFAULT_SIGNALING_URL,
|
||||
cameraFacing = intent.getStringExtra(EXTRA_CAMERA_FACING)
|
||||
?.let { runCatching { CameraFacing.valueOf(it) }.getOrNull() }
|
||||
?: CameraFacing.BACK,
|
||||
preferredCameraName = intent.getStringExtra(EXTRA_CAMERA_NAME),
|
||||
resolution = StreamResolution.from(
|
||||
intent.getIntExtra(EXTRA_VIDEO_WIDTH, StreamResolution.DEFAULT.width),
|
||||
intent.getIntExtra(EXTRA_VIDEO_HEIGHT, StreamResolution.DEFAULT.height),
|
||||
),
|
||||
orientation = intent.getStringExtra(EXTRA_STREAM_ORIENTATION)
|
||||
?.let { runCatching { StreamOrientation.valueOf(it) }.getOrNull() }
|
||||
?: StreamOrientation.DEFAULT,
|
||||
)
|
||||
startForegroundNotification(settings)
|
||||
startStreaming(settings)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
else -> {
|
||||
val signalingUrl = getSavedSignalingUrl()
|
||||
val cameraFacing = getSavedCameraFacing()
|
||||
val preferredCameraName = getSavedCameraName()
|
||||
startForegroundNotification(signalingUrl, cameraFacing, preferredCameraName)
|
||||
startStreaming(signalingUrl, cameraFacing, preferredCameraName)
|
||||
val settings = getSavedSettings()
|
||||
startForegroundNotification(settings)
|
||||
startStreaming(settings)
|
||||
return START_STICKY
|
||||
}
|
||||
}
|
||||
@@ -58,19 +68,24 @@ class PosefitStreamingService : Service() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun startStreaming(signalingUrl: String, cameraFacing: CameraFacing, preferredCameraName: String?) {
|
||||
private fun startStreaming(settings: StreamSettings) {
|
||||
Timber.i(
|
||||
"Streaming service start: %s, cameraFacing=%s, preferredCameraName=%s, orientation=landscape",
|
||||
signalingUrl,
|
||||
cameraFacing,
|
||||
preferredCameraName,
|
||||
"Streaming service start: %s, cameraFacing=%s, preferredCameraName=%s, resolution=%s, orientation=%s",
|
||||
settings.signalingUrl,
|
||||
settings.cameraFacing,
|
||||
settings.preferredCameraName,
|
||||
settings.resolution.label,
|
||||
settings.orientation,
|
||||
)
|
||||
webRtcClient?.release()
|
||||
webRtcClient = WebRtcSenderClient(
|
||||
context = applicationContext,
|
||||
signalingUrl = signalingUrl,
|
||||
cameraFacing = cameraFacing,
|
||||
preferredCameraName = preferredCameraName,
|
||||
signalingUrl = settings.signalingUrl,
|
||||
cameraFacing = settings.cameraFacing,
|
||||
preferredCameraName = settings.preferredCameraName,
|
||||
videoWidth = settings.resolution.width,
|
||||
videoHeight = settings.resolution.height,
|
||||
streamOrientation = settings.orientation,
|
||||
).also { it.start() }
|
||||
isRunning = true
|
||||
}
|
||||
@@ -82,8 +97,8 @@ class PosefitStreamingService : Service() {
|
||||
isRunning = false
|
||||
}
|
||||
|
||||
private fun startForegroundNotification(signalingUrl: String, cameraFacing: CameraFacing, preferredCameraName: String?) {
|
||||
val notification = buildNotification(signalingUrl, cameraFacing, preferredCameraName)
|
||||
private fun startForegroundNotification(settings: StreamSettings) {
|
||||
val notification = buildNotification(settings)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA)
|
||||
} else {
|
||||
@@ -91,24 +106,27 @@ class PosefitStreamingService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNotification(signalingUrl: String, cameraFacing: CameraFacing, preferredCameraName: String?): Notification {
|
||||
val cameraText = when (cameraFacing) {
|
||||
private fun buildNotification(settings: StreamSettings): Notification {
|
||||
val cameraText = when (settings.cameraFacing) {
|
||||
CameraFacing.FRONT -> "前置摄像头"
|
||||
CameraFacing.BACK -> "后置摄像头"
|
||||
}
|
||||
val cameraModeText = preferredCameraName?.let { "$cameraText $it" } ?: "$cameraText 自动最广角"
|
||||
val cameraModeText = settings.preferredCameraName
|
||||
?.let { "$cameraText $it" }
|
||||
?: "$cameraText 自动最广角"
|
||||
val streamText = "${settings.resolution.label} ${settings.orientation.label}"
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
Notification.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("PoseFit 正在推流")
|
||||
.setContentText("$cameraModeText 横屏720p -> $signalingUrl")
|
||||
.setContentText("$cameraModeText $streamText -> ${settings.signalingUrl}")
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
} else {
|
||||
Notification.Builder(this)
|
||||
.setContentTitle("PoseFit 正在推流")
|
||||
.setContentText("$cameraModeText 横屏720p -> $signalingUrl")
|
||||
.setContentText("$cameraModeText $streamText -> ${settings.signalingUrl}")
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
@@ -123,28 +141,38 @@ class PosefitStreamingService : Service() {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"PoseFit 推流",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
NotificationManager.IMPORTANCE_LOW,
|
||||
)
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun getSavedSignalingUrl(): String {
|
||||
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
|
||||
return prefs.getString("signaling_url", DEFAULT_SIGNALING_URL) ?: DEFAULT_SIGNALING_URL
|
||||
private fun getSavedSettings(): StreamSettings {
|
||||
val prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val cameraFacingValue = prefs.getString(KEY_CAMERA_FACING, CameraFacing.BACK.name)
|
||||
val orientationValue = prefs.getString(KEY_STREAM_ORIENTATION, StreamOrientation.DEFAULT.name)
|
||||
|
||||
return StreamSettings(
|
||||
signalingUrl = prefs.getString(KEY_SIGNALING_URL, DEFAULT_SIGNALING_URL) ?: DEFAULT_SIGNALING_URL,
|
||||
cameraFacing = runCatching { CameraFacing.valueOf(cameraFacingValue ?: CameraFacing.BACK.name) }
|
||||
.getOrDefault(CameraFacing.BACK),
|
||||
preferredCameraName = prefs.getString(KEY_CAMERA_NAME, null)?.takeIf { it.isNotBlank() },
|
||||
resolution = StreamResolution.from(
|
||||
prefs.getInt(KEY_VIDEO_WIDTH, StreamResolution.DEFAULT.width),
|
||||
prefs.getInt(KEY_VIDEO_HEIGHT, StreamResolution.DEFAULT.height),
|
||||
),
|
||||
orientation = runCatching { StreamOrientation.valueOf(orientationValue ?: StreamOrientation.DEFAULT.name) }
|
||||
.getOrDefault(StreamOrientation.DEFAULT),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSavedCameraFacing(): CameraFacing {
|
||||
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
|
||||
val value = prefs.getString("camera_facing", CameraFacing.BACK.name)
|
||||
return runCatching { CameraFacing.valueOf(value ?: CameraFacing.BACK.name) }
|
||||
.getOrDefault(CameraFacing.BACK)
|
||||
}
|
||||
|
||||
private fun getSavedCameraName(): String? {
|
||||
val prefs = getSharedPreferences("posefit_prefs", Context.MODE_PRIVATE)
|
||||
return prefs.getString("camera_name", null)?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
private data class StreamSettings(
|
||||
val signalingUrl: String,
|
||||
val cameraFacing: CameraFacing,
|
||||
val preferredCameraName: String?,
|
||||
val resolution: StreamResolution,
|
||||
val orientation: StreamOrientation,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ID = "posefit_streaming"
|
||||
@@ -154,8 +182,19 @@ class PosefitStreamingService : Service() {
|
||||
private const val EXTRA_SIGNALING_URL = "extra_signaling_url"
|
||||
private const val EXTRA_CAMERA_FACING = "extra_camera_facing"
|
||||
private const val EXTRA_CAMERA_NAME = "extra_camera_name"
|
||||
private const val EXTRA_VIDEO_WIDTH = "extra_video_width"
|
||||
private const val EXTRA_VIDEO_HEIGHT = "extra_video_height"
|
||||
private const val EXTRA_STREAM_ORIENTATION = "extra_stream_orientation"
|
||||
private const val DEFAULT_SIGNALING_URL = "ws://192.168.2.6:8765"
|
||||
|
||||
const val PREFS_NAME = "posefit_prefs"
|
||||
const val KEY_SIGNALING_URL = "signaling_url"
|
||||
const val KEY_CAMERA_FACING = "camera_facing"
|
||||
const val KEY_CAMERA_NAME = "camera_name"
|
||||
const val KEY_VIDEO_WIDTH = "video_width"
|
||||
const val KEY_VIDEO_HEIGHT = "video_height"
|
||||
const val KEY_STREAM_ORIENTATION = "stream_orientation"
|
||||
|
||||
@Volatile
|
||||
var isRunning: Boolean = false
|
||||
private set
|
||||
@@ -164,13 +203,18 @@ class PosefitStreamingService : Service() {
|
||||
context: Context,
|
||||
signalingUrl: String,
|
||||
cameraFacing: CameraFacing,
|
||||
preferredCameraName: String?
|
||||
preferredCameraName: String?,
|
||||
resolution: StreamResolution,
|
||||
orientation: StreamOrientation,
|
||||
): Intent {
|
||||
return Intent(context, PosefitStreamingService::class.java).apply {
|
||||
action = ACTION_START
|
||||
putExtra(EXTRA_SIGNALING_URL, signalingUrl)
|
||||
putExtra(EXTRA_CAMERA_FACING, cameraFacing.name)
|
||||
putExtra(EXTRA_CAMERA_NAME, preferredCameraName)
|
||||
putExtra(EXTRA_VIDEO_WIDTH, resolution.width)
|
||||
putExtra(EXTRA_VIDEO_HEIGHT, resolution.height)
|
||||
putExtra(EXTRA_STREAM_ORIENTATION, orientation.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user