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 'com.google.android.material:material:1.3.0'
|
||||||
implementation "io.noties.markwon:core:4.6.2"
|
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"
|
exclude group: "org.slf4j"
|
||||||
}
|
}
|
||||||
implementation 'com.rokid.security.sdk:online-speech:0.1.0'
|
implementation 'com.rokid.security.sdk:online-speech:0.1.0'
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.nova.brain.glass.helper
|
package com.nova.brain.glass.helper
|
||||||
|
|
||||||
|
import android.view.Surface
|
||||||
import com.rokid.security.glass3.open.sdk.GlassSdk
|
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
|
import com.rokid.security.system.server.media.callback.PhotoFileCallback
|
||||||
|
|
||||||
object GlassMediaServiceHelper {
|
object GlassMediaServiceHelper {
|
||||||
@ -34,4 +36,35 @@ object GlassMediaServiceHelper {
|
|||||||
service.javaClass.getMethod("removePhotoCallback", PhotoFileCallback::class.java)
|
service.javaClass.getMethod("removePhotoCallback", PhotoFileCallback::class.java)
|
||||||
.invoke(service, callback)
|
.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 guo"),
|
||||||
OfflineCmdBean("同意", "tong yi")
|
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(
|
private val CMDS_SPRAYING = listOf(
|
||||||
OfflineCmdBean("开始", "kai shi"),
|
OfflineCmdBean("开始", "kai shi"),
|
||||||
OfflineCmdBean("开始任务", "kai shi ren wu")
|
OfflineCmdBean("开始任务", "kai shi ren wu")
|
||||||
@ -158,6 +168,8 @@ object OfflineCmdServiceHelper {
|
|||||||
|
|
||||||
fun addListenerInspection() = registerBeans(CMDS_INSPECTION)
|
fun addListenerInspection() = registerBeans(CMDS_INSPECTION)
|
||||||
|
|
||||||
|
fun addListenerInspectionCapture() = registerBeans(CMDS_INSPECTION_CAPTURE)
|
||||||
|
|
||||||
fun addListenerSpraying() = registerBeans(CMDS_SPRAYING)
|
fun addListenerSpraying() = registerBeans(CMDS_SPRAYING)
|
||||||
|
|
||||||
fun addListenerSprayingFinish() = registerBeans(CMDS_SPRAYING_FINISH)
|
fun addListenerSprayingFinish() = registerBeans(CMDS_SPRAYING_FINISH)
|
||||||
@ -176,6 +188,8 @@ object OfflineCmdServiceHelper {
|
|||||||
|
|
||||||
fun removeListenerInspection() = removeBeans(CMDS_INSPECTION)
|
fun removeListenerInspection() = removeBeans(CMDS_INSPECTION)
|
||||||
|
|
||||||
|
fun removeListenerInspectionCapture() = removeBeans(CMDS_INSPECTION_CAPTURE)
|
||||||
|
|
||||||
fun removeListenerSpraying() = removeBeans(CMDS_SPRAYING)
|
fun removeListenerSpraying() = removeBeans(CMDS_SPRAYING)
|
||||||
|
|
||||||
fun removeListenerSprayingFinish() = removeBeans(CMDS_SPRAYING_FINISH)
|
fun removeListenerSprayingFinish() = removeBeans(CMDS_SPRAYING_FINISH)
|
||||||
|
|||||||
@ -3,6 +3,9 @@ package com.nova.brain.glass.ui
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.view.Surface
|
||||||
|
import android.view.TextureView
|
||||||
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.nova.brain.glass.R
|
import com.nova.brain.glass.R
|
||||||
import com.nova.brain.glass.databinding.ActivityInspectionBinding
|
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.InspectionValidateState
|
||||||
import com.nova.brain.glass.viewmodel.InspectionVM
|
import com.nova.brain.glass.viewmodel.InspectionVM
|
||||||
import com.rokid.security.glass3.sdk.base.data.media.PhotoResolution
|
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.rokid.security.system.server.media.callback.PhotoFileCallback
|
||||||
import com.xuqm.base.adapter.BasePagedAdapter
|
import com.xuqm.base.adapter.BasePagedAdapter
|
||||||
import com.xuqm.base.adapter.CommonPagedAdapter
|
import com.xuqm.base.adapter.CommonPagedAdapter
|
||||||
@ -33,22 +37,88 @@ class InspectionActivity :
|
|||||||
|
|
||||||
private val glassTaskId: String by lazy { intent.getStringExtra("glassTaskId").orEmpty() }
|
private val glassTaskId: String by lazy { intent.getStringExtra("glassTaskId").orEmpty() }
|
||||||
private var pendingPhotoPath: String? = null
|
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 {
|
private val listener = object : OfflineCmdListener {
|
||||||
override fun onOfflineCmd(cmd: String) {
|
override fun onOfflineCmd(cmd: String) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
when (cmd) {
|
when (cmd) {
|
||||||
"退出", "返回", "退回" -> finish()
|
"退出", "返回", "退回" -> 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() {
|
override fun initData() {
|
||||||
super.initData()
|
super.initData()
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
SprayingPhotoManager.clear()
|
SprayingPhotoManager.clear()
|
||||||
|
binding.cameraPreview.surfaceTextureListener = previewTextureListener
|
||||||
|
binding.cameraPreview.setOnClickListener {
|
||||||
|
if (isPreviewActive) {
|
||||||
|
capturePhoto()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.taskInfo.observe(this) { item ->
|
viewModel.taskInfo.observe(this) { item ->
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
@ -95,11 +165,68 @@ class InspectionActivity :
|
|||||||
|
|
||||||
private fun startCapture() {
|
private fun startCapture() {
|
||||||
if (viewModel.validateState.value == InspectionValidateState.LOADING) return
|
if (viewModel.validateState.value == InspectionValidateState.LOADING) return
|
||||||
binding.hint.text = "拍照中,请稍后..."
|
if (isPreviewActive) {
|
||||||
|
capturePhoto()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.hint.text = "相机预览启动中,请稍后..."
|
||||||
|
isPreviewRequested = true
|
||||||
SprayingPhotoManager.clear()
|
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()
|
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() {
|
private fun takePhoto() {
|
||||||
val fileName = "inspection_${System.currentTimeMillis()}.png"
|
val fileName = "inspection_${System.currentTimeMillis()}.png"
|
||||||
val file = File(
|
val file = File(
|
||||||
@ -121,6 +248,7 @@ class InspectionActivity :
|
|||||||
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
|
override fun onTakePhotoV2(path: String?, width: Int, height: Int) {
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
stopCameraPreview()
|
||||||
binding.hint.text = "单击或语音输入\"开始\",进入下一步"
|
binding.hint.text = "单击或语音输入\"开始\",进入下一步"
|
||||||
}
|
}
|
||||||
"相机异常".showMessage()
|
"相机异常".showMessage()
|
||||||
@ -129,6 +257,7 @@ class InspectionActivity :
|
|||||||
SprayingPhotoManager.addPhoto(path)
|
SprayingPhotoManager.addPhoto(path)
|
||||||
pendingPhotoPath = path
|
pendingPhotoPath = path
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
|
stopCameraPreview()
|
||||||
viewModel.validateDocument(path)
|
viewModel.validateDocument(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,12 +281,15 @@ class InspectionActivity :
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
GlassMediaServiceHelper.addPhotoCallback(mPhotoFileCallback)
|
GlassMediaServiceHelper.addPhotoCallback(mPhotoFileCallback)
|
||||||
OfflineCmdServiceHelper.addOnLineListener(listener)
|
OfflineCmdServiceHelper.addOnLineListener(listener)
|
||||||
|
OfflineCmdServiceHelper.addListenerInspectionCapture()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
stopCameraPreview()
|
||||||
GlassMediaServiceHelper.removePhotoCallback(mPhotoFileCallback)
|
GlassMediaServiceHelper.removePhotoCallback(mPhotoFileCallback)
|
||||||
OfflineCmdServiceHelper.removeOnLineListener(listener)
|
OfflineCmdServiceHelper.removeOnLineListener(listener)
|
||||||
|
OfflineCmdServiceHelper.removeListenerInspectionCapture()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
@ -104,5 +104,37 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/baseRecyclerView" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</layout>
|
</layout>
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户