feat(glass): 添加相机预览和变焦功能支持
- 升级 com.rokid.security:glass3.open.sdk 从 2.1.6-E 到 2.1.7-E - 在 GlassMediaServiceHelper 中新增相机共享和变焦相关方法 - 添加 Inspection 拍照相关的语音命令支持 - 实现相机预览功能,包括 TextureView 预览界面和回调处理 - 添加实时变焦功能,支持语音和手势操作 - 优化拍照流程,在成功拍照后自动停止预览 - 更新布局文件添加相机预览和缩放显示组件
这个提交包含在:
父节点
56da0f5899
当前提交
760547bdbf
@ -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>
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户