feat(glass): 添加相机预览和变焦功能支持

- 升级 com.rokid.security:glass3.open.sdk 从 2.1.6-E 到 2.1.7-E
- 在 GlassMediaServiceHelper 中新增相机共享和变焦相关方法
- 添加 Inspection 拍照相关的语音命令支持
- 实现相机预览功能,包括 TextureView 预览界面和回调处理
- 添加实时变焦功能,支持语音和手势操作
- 优化拍照流程,在成功拍照后自动停止预览
- 更新布局文件添加相机预览和缩放显示组件
这个提交包含在:
徐勤民 2026-04-21 17:39:40 +08:00
父节点 56da0f5899
当前提交 760547bdbf
共有 5 个文件被更改,包括 215 次插入4 次删除

查看文件

@ -84,7 +84,7 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0'
implementation "io.noties.markwon:core:4.6.2"
implementation ('com.rokid.security:glass3.open.sdk:2.1.6-E') {
implementation ('com.rokid.security:glass3.open.sdk:2.1.7-E') {
exclude group: "org.slf4j"
}
implementation 'com.rokid.security.sdk:online-speech:0.1.0'

查看文件

@ -1,6 +1,8 @@
package com.nova.brain.glass.helper
import android.view.Surface
import com.rokid.security.glass3.open.sdk.GlassSdk
import com.rokid.security.system.server.media.callback.ICameraSurfaceCallback
import com.rokid.security.system.server.media.callback.PhotoFileCallback
object GlassMediaServiceHelper {
@ -34,4 +36,35 @@ object GlassMediaServiceHelper {
service.javaClass.getMethod("removePhotoCallback", PhotoFileCallback::class.java)
.invoke(service, callback)
}
fun startCameraShare(surface: Surface, callback: ICameraSurfaceCallback) {
val service = service() ?: return
service.javaClass.getMethod(
"startCameraShare",
Surface::class.java,
ICameraSurfaceCallback::class.java
).invoke(service, surface, callback)
}
fun stopCameraShare(callback: ICameraSurfaceCallback) {
val service = service() ?: return
service.javaClass.getMethod("stopCameraShare", ICameraSurfaceCallback::class.java)
.invoke(service, callback)
}
fun getMaxZoomLevel(): Int {
val service = service() ?: return 0
return (service.javaClass.getMethod("getMaxZoomLevel").invoke(service) as? Int) ?: 0
}
fun getZoomLevel(): Int {
val service = service() ?: return 0
return (service.javaClass.getMethod("getZoomLevel").invoke(service) as? Int) ?: 0
}
fun zoomCamera(level: Int) {
val service = service() ?: return
service.javaClass.getMethod("zoomCamera", Int::class.javaPrimitiveType)
.invoke(service, level)
}
}

查看文件

@ -43,6 +43,16 @@ object OfflineCmdServiceHelper {
OfflineCmdBean("通过", "tong guo"),
OfflineCmdBean("同意", "tong yi")
)
private val CMDS_INSPECTION_CAPTURE = listOf(
OfflineCmdBean("开始", "kai shi"),
OfflineCmdBean("开始任务", "kai shi ren wu"),
OfflineCmdBean("拍照", "pai zhao"),
OfflineCmdBean("拍摄", "pai she"),
OfflineCmdBean("放大", "fang da"),
OfflineCmdBean("拉近", "la jin"),
OfflineCmdBean("缩小", "suo xiao"),
OfflineCmdBean("拉远", "la yuan")
)
private val CMDS_SPRAYING = listOf(
OfflineCmdBean("开始", "kai shi"),
OfflineCmdBean("开始任务", "kai shi ren wu")
@ -158,6 +168,8 @@ object OfflineCmdServiceHelper {
fun addListenerInspection() = registerBeans(CMDS_INSPECTION)
fun addListenerInspectionCapture() = registerBeans(CMDS_INSPECTION_CAPTURE)
fun addListenerSpraying() = registerBeans(CMDS_SPRAYING)
fun addListenerSprayingFinish() = registerBeans(CMDS_SPRAYING_FINISH)
@ -176,6 +188,8 @@ object OfflineCmdServiceHelper {
fun removeListenerInspection() = removeBeans(CMDS_INSPECTION)
fun removeListenerInspectionCapture() = removeBeans(CMDS_INSPECTION_CAPTURE)
fun removeListenerSpraying() = removeBeans(CMDS_SPRAYING)
fun removeListenerSprayingFinish() = removeBeans(CMDS_SPRAYING_FINISH)

查看文件

@ -3,6 +3,9 @@ package com.nova.brain.glass.ui
import android.content.Intent
import android.os.Environment
import android.view.WindowManager
import android.view.Surface
import android.view.TextureView
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.nova.brain.glass.R
import com.nova.brain.glass.databinding.ActivityInspectionBinding
@ -14,6 +17,7 @@ import com.nova.brain.glass.model.ItemItem
import com.nova.brain.glass.viewmodel.InspectionValidateState
import com.nova.brain.glass.viewmodel.InspectionVM
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
import com.rokid.security.system.server.media.callback.ICameraSurfaceCallback
import com.rokid.security.system.server.media.callback.PhotoFileCallback
import com.xuqm.base.adapter.BasePagedAdapter
import com.xuqm.base.adapter.CommonPagedAdapter
@ -33,22 +37,88 @@ class InspectionActivity :
private val glassTaskId: String by lazy { intent.getStringExtra("glassTaskId").orEmpty() }
private var pendingPhotoPath: String? = null
private var isPreviewRequested = false
private var isPreviewActive = false
private var currentZoomLevel = 1
private var maxZoomLevel = 1
private val listener = object : OfflineCmdListener {
override fun onOfflineCmd(cmd: String) {
runOnUiThread {
when (cmd) {
"退出", "返回", "退回" -> finish()
"开始", "开始任务", "拍照" -> startCapture()
"开始", "开始任务" -> startCapture()
"拍照", "拍摄" -> if (isPreviewActive) capturePhoto() else startCapture()
"放大", "拉近" -> adjustZoom(1)
"缩小", "拉远" -> adjustZoom(-1)
}
}
}
}
private val cameraSurfaceCallbackId = UUID.randomUUID().toString()
private val cameraSurfaceCallback = object : ICameraSurfaceCallback.Stub() {
override fun onCameraOpened(width: Int, height: Int) {
isPreviewActive = true
currentZoomLevel = GlassMediaServiceHelper.getZoomLevel().coerceAtLeast(1)
maxZoomLevel = GlassMediaServiceHelper.getMaxZoomLevel().coerceAtLeast(1)
runOnUiThread {
binding.cameraPreview.visibility = View.VISIBLE
binding.zoomText.visibility = View.VISIBLE
updateZoomText()
binding.hint.text = "预览中,单击预览或语音输入“拍照”完成拍摄"
}
}
override fun onCameraClosed() {
isPreviewActive = false
runOnUiThread {
binding.cameraPreview.visibility = View.GONE
binding.zoomText.visibility = View.GONE
}
}
override fun onError(code: Int, message: String?) {
isPreviewActive = false
runOnUiThread {
binding.cameraPreview.visibility = View.GONE
binding.zoomText.visibility = View.GONE
binding.hint.text = "相机预览失败,请重试"
(message ?: "相机预览失败").showMessage()
}
}
override fun getCallbackId(): String = cameraSurfaceCallbackId
}
private val previewTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: android.graphics.SurfaceTexture, width: Int, height: Int) {
if (isPreviewRequested) {
startCameraPreview()
}
}
override fun onSurfaceTextureSizeChanged(
surface: android.graphics.SurfaceTexture,
width: Int,
height: Int
) = Unit
override fun onSurfaceTextureDestroyed(surface: android.graphics.SurfaceTexture): Boolean = true
override fun onSurfaceTextureUpdated(surface: android.graphics.SurfaceTexture) = Unit
}
override fun initData() {
super.initData()
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
SprayingPhotoManager.clear()
binding.cameraPreview.surfaceTextureListener = previewTextureListener
binding.cameraPreview.setOnClickListener {
if (isPreviewActive) {
capturePhoto()
}
}
viewModel.taskInfo.observe(this) { item ->
if (item != null) {
@ -95,11 +165,68 @@ class InspectionActivity :
private fun startCapture() {
if (viewModel.validateState.value == InspectionValidateState.LOADING) return
binding.hint.text = "拍照中,请稍后..."
if (isPreviewActive) {
capturePhoto()
return
}
binding.hint.text = "相机预览启动中,请稍后..."
isPreviewRequested = true
SprayingPhotoManager.clear()
if (binding.cameraPreview.isAvailable) {
startCameraPreview()
} else {
binding.cameraPreview.visibility = View.VISIBLE
binding.zoomText.visibility = View.VISIBLE
}
}
private fun startCameraPreview() {
val surfaceTexture = binding.cameraPreview.surfaceTexture ?: return
val surface = Surface(surfaceTexture)
runCatching {
GlassMediaServiceHelper.startCameraShare(surface, cameraSurfaceCallback)
}.onFailure {
isPreviewRequested = false
binding.cameraPreview.visibility = View.GONE
binding.zoomText.visibility = View.GONE
binding.hint.text = "相机预览启动失败,请重试"
(it.message ?: "相机预览启动失败").showMessage()
}
}
private fun stopCameraPreview() {
isPreviewRequested = false
isPreviewActive = false
runCatching {
GlassMediaServiceHelper.stopCameraShare(cameraSurfaceCallback)
}
binding.cameraPreview.visibility = View.GONE
binding.zoomText.visibility = View.GONE
}
private fun capturePhoto() {
if (viewModel.validateState.value == InspectionValidateState.LOADING) return
binding.hint.text = "拍照中,请稍后..."
takePhoto()
}
private fun adjustZoom(delta: Int) {
if (!isPreviewActive) return
maxZoomLevel = GlassMediaServiceHelper.getMaxZoomLevel().coerceAtLeast(1)
currentZoomLevel = (GlassMediaServiceHelper.getZoomLevel() + delta).coerceIn(1, maxZoomLevel)
runCatching {
GlassMediaServiceHelper.zoomCamera(currentZoomLevel)
}.onSuccess {
updateZoomText()
}.onFailure {
(it.message ?: "缩放失败").showMessage()
}
}
private fun updateZoomText() {
binding.zoomText.text = "缩放 ${currentZoomLevel}x"
}
private fun takePhoto() {
val fileName = "inspection_${System.currentTimeMillis()}.png"
val file = File(
@ -121,6 +248,7 @@ class InspectionActivity :
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
if (path == null) {
runOnUiThread {
stopCameraPreview()
binding.hint.text = "单击或语音输入\"开始\",进入下一步"
}
"相机异常".showMessage()
@ -129,6 +257,7 @@ class InspectionActivity :
SprayingPhotoManager.addPhoto(path)
pendingPhotoPath = path
runOnUiThread {
stopCameraPreview()
viewModel.validateDocument(path)
}
}
@ -152,12 +281,15 @@ class InspectionActivity :
super.onResume()
GlassMediaServiceHelper.addPhotoCallback(mPhotoFileCallback)
OfflineCmdServiceHelper.addOnLineListener(listener)
OfflineCmdServiceHelper.addListenerInspectionCapture()
}
override fun onPause() {
super.onPause()
stopCameraPreview()
GlassMediaServiceHelper.removePhotoCallback(mPhotoFileCallback)
OfflineCmdServiceHelper.removeOnLineListener(listener)
OfflineCmdServiceHelper.removeListenerInspectionCapture()
}
override fun onDestroy() {

查看文件

@ -104,5 +104,37 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView" />
<TextureView
android:id="@+id/cameraPreview"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
android:background="@drawable/bg_task_title_selected"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/zoomText"
app:layout_constraintDimensionRatio="3:4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hint" />
<TextView
android:id="@+id/zoomText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:background="@drawable/bg_item"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="缩放 1x"
android:textColor="#ff40FF5E"
android:textSize="14sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
</layout>