success
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"type" : "uni-app:app-ios"
|
"type" : "uni-app:app-ios"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"customPlaygroundType" : "local",
|
||||||
"playground" : "custom",
|
"playground" : "custom",
|
||||||
"type" : "uni-app:app-android"
|
"type" : "uni-app:app-android"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
TakePhotoOption,
|
TakePhotoOption,
|
||||||
TakePhotoSuccessCallbackResult,
|
TakePhotoSuccessCallbackResult,
|
||||||
GeneralCallbackResult
|
GeneralCallbackResult
|
||||||
} from '@/uni_modules/XF-cameraUTS'
|
} from '@/uni_modules/Skin-cameraUTS'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["pages/index/index.uvue"],"sourcesContent":["<template>\n\n\t<scroll-view class=\"page-scroll-view\">\n\n\t\t<view>\n\t\t\t<button @click=\"_takePhoto\">拍照</button>\n\t\t\t<xf-camera style=\"height:750rpx; background-color: red;\" @error=\"onError\" :flash=\"flash\"\n\t\t\t\t:position=\"device\"></xf-camera>\n\t\t\t<image v-if=\"imagePath\" mode=\"aspectFit\" :src=\"imagePath\"></image>\n\t\t</view>\n\n\t</scroll-view>\n\n</template>\n\n<script>\n\timport {\n\t\ttakePhoto,\n\t\tTakePhotoOption,\n\t\tTakePhotoSuccessCallbackResult,\n\t\tGeneralCallbackResult\n\t} from '@/uni_modules/Skin-cameraUTS'\n\t\n\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tyanse: \"red\",\n\t\t\t\twidthNum: 750,\n\t\t\t\theightNum: 200,\n\t\t\t\t\n\t\t\t\tflash: \"on\",\n\t\t\t\tdevice: \"front\",\n\t\t\t\timagePath: \"\",\n\t\t\t\tvideoPath: \"https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4\"\n\t\t\t}\n\t\t},\n\t\tonLoad() {\n\t\t\t// let permissionNeed = [\"android.permission.CAMERA\", \"android.permission.RECORD_AUDIO\",\n\t\t\t// \t\t\t\t\t\"android.permission.WRITE_EXTERNAL_STORAGE\"]\n\t\t\t// // 请求权限\n\t\t\t// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {\n\t\t\t// \tif (allRight) {\n\t\t\t// \t\t// 权限请求成功\n\t\t\t// \t} else {\n\t\t\t// \t\t//用户拒绝了部分权限\n\t\t\t// \t}\n\t\t\t// }, function (_ : boolean, _ : string[]) {\n\t\t\t// \t//用户拒绝了部分权限\n\t\t\t// })\n\t\t},\n\t\tmethods: {\n\t\t\tonError(res : any) {\n\t\t\t\tconsole.log(res)\n\t\t\t},\n\t\t\t_takePhoto() {\n\t\t\t\ttakePhoto({\n\t\t\t\t\tsuccess: (res : TakePhotoSuccessCallbackResult) => {\n\t\t\t\t\t\tconsole.log('takePhoto', res)\n\t\t\t\t\t\tthis.imagePath = res.tempImagePath;\n\t\t\t\t\t\t// _previewImage();\n\t\t\t\t\t},\n\t\t\t\t\tfail: (res : GeneralCallbackResult) => {\n\t\t\t\t\t\tconsole.log('takePhoto', res)\n\t\t\t\t\t}\n\t\t\t\t} as TakePhotoOption)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style>\n\t.video {\n\t\twidth: 100%;\n\t\theight: 200px;\n\t}\n</style>"],"names":[],"mappings":";;;;;;;;;;;;;;;AAgBC,OAKO,6BAA6B,CAJnC;OAIM,6BAA6B,CAHnC;OAGM,6BAA6B,CAFnC;OAEM,6BAA6B,CADnC;AAII;;eAaJ,sBAAM,CAaN;;;;;;eAhDD,IASc,eAAA,IATD,WAAM,qBAAkB;YAEpC,IAKO,QAAA,IAAA,EAAA;gBAJN,IAAuC,UAAA,IAA9B,aAAO,KAAA,UAAU,GAAE,MAAE,CAAA,EAAA;oBAAA;iBAAA;gBAC9B,IAAA,kBAAA,IACgC,EAAA,IADrB,WAA6C,IAA7C,IAAA,YAAA,UAAA,sBAAA,SAA+C,aAAO,KAAA,OAAO,EAAG,WAAO,KAAA,KAAK,EACrF,cAAU,KAAA,MAAM;;;;;2BACL,KAAA,SAAS,GAAtB;oBAAA,IAAkE,SAAA,gBAA1C,UAAK,aAAa,SAAK,KAAA,SAAS;;;;;;;;;aAmBvD;aACA;aACA;aAEA;aACA;aACA;aACA;;;mBAPA,WAAO,OACP,cAAU,GAAG,EACb,eAAW,GAAG,EAEd,WAAO,MACP,YAAQ,SACR,eAAW,IACX,eAAW;;aAkBZ;aAAA,eAAQ,KAAM,GAAG,EAAA;QAChB,QAAQ,GAAG,CAAC,KAAG;IAChB;aACA;aAAA,oBAAU;QACT,UASK,gBARJ,UAAS,IAAC,KAAM,+BAAiC;YAChD,QAAQ,GAAG,CAAC,aAAa,KAAG;YAC5B,IAAI,CAAC,SAAQ,GAAI,IAAI,aAAa;QAEnC;UACA,OAAM,IAAC,KAAM,sBAAwB;YACpC,QAAQ,GAAG,CAAC,aAAa,KAAG;QAC7B;;IAEF;;;;;;;;;;;;;;;;;;;;AAEF"}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"env": {
|
||||||
|
"compiler_version": "4.87"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"pages/index/index.kt": {
|
||||||
|
"class": "GenPagesIndexIndex",
|
||||||
|
"md5": "0ba1bdcb867acacda8a20ff033971ed372d8a0d5"
|
||||||
|
},
|
||||||
|
"index.kt": {
|
||||||
|
"class": "",
|
||||||
|
"md5": "3d228610d7ea1f50297ff2c98f1d707625ffc2ff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
package uni.UNI644401D
|
||||||
|
import io.dcloud.uniapp.*
|
||||||
|
import io.dcloud.uniapp.extapi.*
|
||||||
|
import io.dcloud.uniapp.framework.*
|
||||||
|
import io.dcloud.uniapp.runtime.*
|
||||||
|
import io.dcloud.uniapp.vue.*
|
||||||
|
import io.dcloud.uniapp.vue.shared.*
|
||||||
|
import io.dcloud.unicloud.*
|
||||||
|
import io.dcloud.uts.*
|
||||||
|
import io.dcloud.uts.Map
|
||||||
|
import io.dcloud.uts.Set
|
||||||
|
import io.dcloud.uts.UTSAndroid
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import io.dcloud.uniapp.extapi.exit as uni_exit
|
||||||
|
import io.dcloud.uniapp.extapi.showToast as uni_showToast
|
||||||
|
val runBlock1 = run {
|
||||||
|
__uniConfig.getAppStyles = fun(): Map<String, Map<String, Map<String, Any>>> {
|
||||||
|
return GenApp.styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var firstBackTime: Number = 0
|
||||||
|
open class GenApp : BaseApp {
|
||||||
|
constructor(__ins: ComponentInternalInstance) : super(__ins) {
|
||||||
|
onLaunch(fun(_: OnLaunchOptions) {
|
||||||
|
console.log("App Launch", " at App.uvue:7")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onAppShow(fun(_: OnShowOptions) {
|
||||||
|
console.log("App Show", " at App.uvue:10")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onAppHide(fun() {
|
||||||
|
console.log("App Hide", " at App.uvue:13")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onLastPageBackPress(fun() {
|
||||||
|
console.log("App LastPageBackPress", " at App.uvue:17")
|
||||||
|
if (firstBackTime == 0) {
|
||||||
|
uni_showToast(ShowToastOptions(title = "再按一次退出应用", position = "bottom"))
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
setTimeout(fun(){
|
||||||
|
firstBackTime = 0
|
||||||
|
}, 2000)
|
||||||
|
} else if (Date.now() - firstBackTime < 2000) {
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
uni_exit(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onExit(fun() {
|
||||||
|
console.log("App Exit", " at App.uvue:34")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
val styles: Map<String, Map<String, Map<String, Any>>> by lazy {
|
||||||
|
_nCS(_uA(
|
||||||
|
styles0
|
||||||
|
))
|
||||||
|
}
|
||||||
|
val styles0: Map<String, Map<String, Map<String, Any>>>
|
||||||
|
get() {
|
||||||
|
return _uM("uni-row" to _pS(_uM("flexDirection" to "row")), "uni-column" to _pS(_uM("flexDirection" to "column")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val GenAppClass = CreateVueAppComponent(GenApp::class.java, fun(): VueComponentOptions {
|
||||||
|
return VueComponentOptions(type = "app", name = "", inheritAttrs = true, inject = Map(), props = Map(), propsNeedCastKeys = _uA(), emits = Map(), components = Map(), styles = GenApp.styles)
|
||||||
|
}
|
||||||
|
, fun(instance): GenApp {
|
||||||
|
return GenApp(instance)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val GenPagesIndexIndexClass = CreateVueComponent(GenPagesIndexIndex::class.java, fun(): VueComponentOptions {
|
||||||
|
return VueComponentOptions(type = "page", name = "", inheritAttrs = GenPagesIndexIndex.inheritAttrs, inject = GenPagesIndexIndex.inject, props = GenPagesIndexIndex.props, propsNeedCastKeys = GenPagesIndexIndex.propsNeedCastKeys, emits = GenPagesIndexIndex.emits, components = GenPagesIndexIndex.components, styles = GenPagesIndexIndex.styles)
|
||||||
|
}
|
||||||
|
, fun(instance, renderer): GenPagesIndexIndex {
|
||||||
|
return GenPagesIndexIndex(instance, renderer)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fun createApp(): UTSJSONObject {
|
||||||
|
val app = createSSRApp(GenAppClass)
|
||||||
|
return _uO("app" to app)
|
||||||
|
}
|
||||||
|
fun main(app: IApp) {
|
||||||
|
definePageRoutes()
|
||||||
|
defineAppConfig()
|
||||||
|
(createApp()["app"] as VueApp).mount(app, GenUniApp())
|
||||||
|
}
|
||||||
|
open class UniAppConfig : io.dcloud.uniapp.appframe.AppConfig {
|
||||||
|
override var name: String = "SkinCamera"
|
||||||
|
override var appid: String = "__UNI__644401D"
|
||||||
|
override var versionName: String = "1.0.0"
|
||||||
|
override var versionCode: String = "100"
|
||||||
|
override var uniCompilerVersion: String = "4.87"
|
||||||
|
constructor() : super() {}
|
||||||
|
}
|
||||||
|
fun definePageRoutes() {
|
||||||
|
__uniRoutes.push(UniPageRoute(path = "pages/index/index", component = GenPagesIndexIndexClass, meta = UniPageMeta(isQuit = true), style = _uM("navigationBarTitleText" to "uni-app x")))
|
||||||
|
}
|
||||||
|
val __uniLaunchPage: Map<String, Any?> = _uM("url" to "pages/index/index", "style" to _uM("navigationBarTitleText" to "uni-app x"))
|
||||||
|
fun defineAppConfig() {
|
||||||
|
__uniConfig.entryPagePath = "/pages/index/index"
|
||||||
|
__uniConfig.globalStyle = _uM("navigationBarTextStyle" to "black", "navigationBarTitleText" to "uni-app x", "navigationBarBackgroundColor" to "#F8F8F8", "backgroundColor" to "#F8F8F8")
|
||||||
|
__uniConfig.getTabBarConfig = fun(): Map<String, Any>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig()
|
||||||
|
__uniConfig.conditionUrl = ""
|
||||||
|
__uniConfig.uniIdRouter = _uM()
|
||||||
|
__uniConfig.ready = true
|
||||||
|
}
|
||||||
|
open class GenUniApp : UniAppImpl() {
|
||||||
|
open val vm: GenApp?
|
||||||
|
get() {
|
||||||
|
return getAppVm() as GenApp?
|
||||||
|
}
|
||||||
|
open val `$vm`: GenApp?
|
||||||
|
get() {
|
||||||
|
return getAppVm() as GenApp?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun getApp(): GenUniApp {
|
||||||
|
return getUniApp() as GenUniApp
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
package uni.UNI644401D
|
||||||
|
import io.dcloud.uniapp.*
|
||||||
|
import io.dcloud.uniapp.extapi.*
|
||||||
|
import io.dcloud.uniapp.framework.*
|
||||||
|
import io.dcloud.uniapp.runtime.*
|
||||||
|
import io.dcloud.uniapp.vue.*
|
||||||
|
import io.dcloud.uniapp.vue.shared.*
|
||||||
|
import io.dcloud.unicloud.*
|
||||||
|
import io.dcloud.uts.*
|
||||||
|
import io.dcloud.uts.Map
|
||||||
|
import io.dcloud.uts.Set
|
||||||
|
import io.dcloud.uts.UTSAndroid
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.XfCameraComponent
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.takePhoto
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.TakePhotoOption
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.TakePhotoSuccessCallbackResult
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.GeneralCallbackResult
|
||||||
|
open class GenPagesIndexIndex : BasePage {
|
||||||
|
constructor(__ins: ComponentInternalInstance, __renderer: String?) : super(__ins, __renderer) {
|
||||||
|
onLoad(fun(_: OnLoadOptions) {}, __ins)
|
||||||
|
}
|
||||||
|
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
||||||
|
override fun `$render`(): Any? {
|
||||||
|
val _ctx = this
|
||||||
|
val _cache = this.`$`.renderCache
|
||||||
|
return _cE("scroll-view", _uM("class" to "page-scroll-view"), _uA(
|
||||||
|
_cE("view", null, _uA(
|
||||||
|
_cE("button", _uM("onClick" to _ctx._takePhoto), "拍照", 8, _uA(
|
||||||
|
"onClick"
|
||||||
|
)),
|
||||||
|
_cE(XfCameraComponent.name, _uM("style" to _nS(_uM("height" to "750rpx", "background-color" to "red")), "onError" to _ctx.onError, "flash" to _ctx.flash, "position" to _ctx.device), null, 44, _uA(
|
||||||
|
"onError",
|
||||||
|
"flash",
|
||||||
|
"position"
|
||||||
|
)),
|
||||||
|
if (isTrue(_ctx.imagePath)) {
|
||||||
|
_cE("image", _uM("key" to 0, "mode" to "aspectFit", "src" to _ctx.imagePath), null, 8, _uA(
|
||||||
|
"src"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
_cC("v-if", true)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
open var yanse: String by `$data`
|
||||||
|
open var widthNum: Number by `$data`
|
||||||
|
open var heightNum: Number by `$data`
|
||||||
|
open var flash: String by `$data`
|
||||||
|
open var device: String by `$data`
|
||||||
|
open var imagePath: String by `$data`
|
||||||
|
open var videoPath: String by `$data`
|
||||||
|
@Suppress("USELESS_CAST")
|
||||||
|
override fun data(): Map<String, Any?> {
|
||||||
|
return _uM("yanse" to "red", "widthNum" to 750, "heightNum" to 200, "flash" to "on", "device" to "front", "imagePath" to "", "videoPath" to "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4")
|
||||||
|
}
|
||||||
|
open var onError = ::gen_onError_fn
|
||||||
|
open fun gen_onError_fn(res: Any) {
|
||||||
|
console.log(res, " at pages/index/index.uvue:54")
|
||||||
|
}
|
||||||
|
open var _takePhoto = ::gen__takePhoto_fn
|
||||||
|
open fun gen__takePhoto_fn() {
|
||||||
|
takePhoto(TakePhotoOption(success = fun(res: TakePhotoSuccessCallbackResult){
|
||||||
|
console.log("takePhoto", res, " at pages/index/index.uvue:59")
|
||||||
|
this.imagePath = res.tempImagePath
|
||||||
|
}
|
||||||
|
, fail = fun(res: GeneralCallbackResult){
|
||||||
|
console.log("takePhoto", res, " at pages/index/index.uvue:64")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
val styles: Map<String, Map<String, Map<String, Any>>> by lazy {
|
||||||
|
_nCS(_uA(
|
||||||
|
styles0
|
||||||
|
), _uA(
|
||||||
|
GenApp.styles
|
||||||
|
))
|
||||||
|
}
|
||||||
|
val styles0: Map<String, Map<String, Map<String, Any>>>
|
||||||
|
get() {
|
||||||
|
return _uM("video" to _pS(_uM("width" to "100%", "height" to 200)))
|
||||||
|
}
|
||||||
|
var inheritAttrs = true
|
||||||
|
var inject: Map<String, Map<String, Any?>> = _uM()
|
||||||
|
var emits: Map<String, Any?> = _uM()
|
||||||
|
var props = _nP(_uM())
|
||||||
|
var propsNeedCastKeys: UTSArray<String> = _uA()
|
||||||
|
var components: Map<String, CreateVueComponent> = _uM()
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
|||||||
|
{"code":"import {} from \"vue\";\nexport default defineComponent({\n onLaunch() {\n uni.__log__('log', 'at App.uvue:7', 'App Launch');\n },\n onShow() {\n uni.__log__('log', 'at App.uvue:10', 'App Show');\n },\n onHide() {\n uni.__log__('log', 'at App.uvue:13', 'App Hide');\n },\n onExit() {\n uni.__log__('log', 'at App.uvue:34', 'App Exit');\n },\n});\n//# sourceMappingURL=/Users/feast/Documents/CameraProj/SkinCamera/App.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__log__"],"map":"{\"version\":3,\"file\":\"App.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"App.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AAIC,+BAAe;IACd,QAAQ;QACP,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,eAAe,EAAC,YAAY,CAAC,CAAA;IAChD,CAAC;IACD,MAAM;QACL,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC/C,CAAC;IACD,MAAM;QACL,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC/C,CAAC;IAmBD,MAAM;QACL,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,gBAAgB,EAAC,UAAU,CAAC,CAAA;IAC/C,CAAC;CACD,EAAA\"}"}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"code":"import { defineComponent } from \"vue\";\nimport { takePhoto } from '@/uni_modules/Skin-cameraUTS';\nexport default defineComponent({\n data() {\n return {\n yanse: \"red\",\n widthNum: 750,\n heightNum: 200,\n flash: \"on\",\n device: \"front\",\n imagePath: \"\",\n videoPath: \"https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4\"\n };\n },\n onLoad() {\n // let permissionNeed = [\"android.permission.CAMERA\", \"android.permission.RECORD_AUDIO\",\n // \t\t\t\t\t\"android.permission.WRITE_EXTERNAL_STORAGE\"]\n // // 请求权限\n // UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {\n // \tif (allRight) {\n // \t\t// 权限请求成功\n // \t} else {\n // \t\t//用户拒绝了部分权限\n // \t}\n // }, function (_ : boolean, _ : string[]) {\n // \t//用户拒绝了部分权限\n // })\n },\n methods: {\n onError(res = null) {\n uni.__log__('log', 'at pages/index/index.uvue:54', res);\n },\n _takePhoto() {\n takePhoto({\n success: (res = null) => {\n uni.__log__('log', 'at pages/index/index.uvue:59', 'takePhoto', res);\n this.imagePath = res.tempImagePath;\n // _previewImage();\n },\n fail: (res = null) => {\n uni.__log__('log', 'at pages/index/index.uvue:64', 'takePhoto', res);\n }\n });\n }\n }\n});\n//# sourceMappingURL=/Users/feast/Documents/CameraProj/SkinCamera/pages/index/index.uvue?vue&type=script&lang.uts.js.map","references":[],"uniExtApis":["uni.__log__"],"map":"{\"version\":3,\"file\":\"index.uvue?vue&type=script&lang.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"index.uvue?vue&type=script&lang.uts\"],\"names\":[],\"mappings\":\";AACC,OAAO,EACN,SAAS,EAIT,MAAM,8BAA8B,CAAA;AAGrC,+BAAe;IACd,IAAI;QACH,OAAO;YACN,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,GAAG;YACb,SAAS,EAAE,GAAG;YAEd,KAAK,EAAE,IAAI;YACX,MAAM,EAAE,OAAO;YACf,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,mEAAmE;SAC9E,CAAA;IACF,CAAC;IACD,MAAM;QACL,wFAAwF;QACxF,oDAAoD;QACpD,UAAU;QACV,iIAAiI;QACjI,mBAAmB;QACnB,cAAc;QACd,YAAY;QACZ,gBAAgB;QAChB,KAAK;QACL,4CAA4C;QAC5C,eAAe;QACf,KAAK;IACN,CAAC;IACD,OAAO,EAAE;QACR,OAAO,CAAC,UAAS;YAChB,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,8BAA8B,EAAC,GAAG,CAAC,CAAA;QACtD,CAAC;QACD,UAAU;YACT,SAAS,CAAC;gBACT,OAAO,EAAE,CAAC,UAAoC;oBAC7C,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,8BAA8B,EAAC,WAAW,EAAE,GAAG,CAAC,CAAA;oBAClE,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,aAAa,CAAC;oBACnC,mBAAmB;gBACpB,CAAC;gBACD,IAAI,EAAE,CAAC,UAA2B;oBACjC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAC,8BAA8B,EAAC,WAAW,EAAE,GAAG,CAAC,CAAA;gBACnE,CAAC;aACkB,CAAC,CAAA;QACtB,CAAC;KACD;CACD,EAAA\"}"}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"code":"import './manifest-json-uts';\nimport './pages-json-uts';\nconst __global__ = typeof globalThis === 'undefined' ? Function('return this')() : globalThis;\n__global__.__uniX = true;\nimport App from './App.uvue';\nimport { createSSRApp } from 'vue';\nexport function createApp() {\n const app = createSSRApp(App);\n return {\n app\n };\n}\ncreateApp().app.mount(\"#app\");\n//# sourceMappingURL=/Users/feast/Documents/CameraProj/SkinCamera/main.uts.js.map","references":["/Users/feast/Documents/CameraProj/SkinCamera/App.uvue.ts","/Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli-vite/node_modules/@vue/runtime-core/dist/runtime-core.d.ts"],"uniExtApis":[],"map":"{\"version\":3,\"file\":\"main.uts.js\",\"sourceRoot\":\"\",\"sources\":[\"main.uts\"],\"names\":[],\"mappings\":\"AACU,OAAO,qBAAqB,CAAA;AAC5B,OAAO,kBAAkB,CAAA;AACzB,MAAM,UAAU,GAAG,OAAO,UAAU,KAAK,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAA;AAC7F,UAAU,CAAC,MAAM,GAAG,IAAI,CAAA;AACxB,OAAO,GAAG,MAAM,YAAY,CAAA;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,KAAK,CAAA;AAClC,MAAM,UAAU,SAAS;IACxB,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC7B,OAAO;QACN,GAAG;KACH,CAAA;AACF,CAAC;AACS,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC\"}"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"app-service.js","sources":["uni_modules/Skin-cameraUTS?uts-proxy","pages/index/index.uvue","App.uvue"],"sourcesContent":["","<template>\n\n\t<scroll-view class=\"page-scroll-view\">\n\n\t\t<view>\n\t\t\t<button @click=\"_takePhoto\">拍照</button>\n\t\t\t<xf-camera style=\"height:750rpx; background-color: red;\" @error=\"onError\" :flash=\"flash\"\n\t\t\t\t:position=\"device\"></xf-camera>\n\t\t\t<image v-if=\"imagePath\" mode=\"aspectFit\" :src=\"imagePath\"></image>\n\t\t</view>\n\n\t</scroll-view>\n\n</template>\n\n<script lang=\"uts\">\n\timport {\n\t\ttakePhoto,\n\t\tTakePhotoOption,\n\t\tTakePhotoSuccessCallbackResult,\n\t\tGeneralCallbackResult\n\t} from '@/uni_modules/Skin-cameraUTS'\n\t\n\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tyanse: \"red\",\n\t\t\t\twidthNum: 750,\n\t\t\t\theightNum: 200,\n\t\t\t\t\n\t\t\t\tflash: \"on\",\n\t\t\t\tdevice: \"front\",\n\t\t\t\timagePath: \"\",\n\t\t\t\tvideoPath: \"https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4\"\n\t\t\t}\n\t\t},\n\t\tonLoad() {\n\t\t\t// let permissionNeed = [\"android.permission.CAMERA\", \"android.permission.RECORD_AUDIO\",\n\t\t\t// \t\t\t\t\t\"android.permission.WRITE_EXTERNAL_STORAGE\"]\n\t\t\t// // 请求权限\n\t\t\t// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {\n\t\t\t// \tif (allRight) {\n\t\t\t// \t\t// 权限请求成功\n\t\t\t// \t} else {\n\t\t\t// \t\t//用户拒绝了部分权限\n\t\t\t// \t}\n\t\t\t// }, function (_ : boolean, _ : string[]) {\n\t\t\t// \t//用户拒绝了部分权限\n\t\t\t// })\n\t\t},\n\t\tmethods: {\n\t\t\tonError(res : any) {\n\t\t\t\tuni.__log__('log','at pages/index/index.uvue:54',res)\n\t\t\t},\n\t\t\t_takePhoto() {\n\t\t\t\ttakePhoto({\n\t\t\t\t\tsuccess: (res : TakePhotoSuccessCallbackResult) => {\n\t\t\t\t\t\tuni.__log__('log','at pages/index/index.uvue:59','takePhoto', res)\n\t\t\t\t\t\tthis.imagePath = res.tempImagePath;\n\t\t\t\t\t\t// _previewImage();\n\t\t\t\t\t},\n\t\t\t\t\tfail: (res : GeneralCallbackResult) => {\n\t\t\t\t\t\tuni.__log__('log','at pages/index/index.uvue:64','takePhoto', res)\n\t\t\t\t\t}\n\t\t\t\t} as TakePhotoOption)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style>\n\t.video {\n\t\twidth: 100%;\n\t\theight: 200px;\n\t}\n</style>","<script lang=\"uts\">\r\n\r\n\r\n\r\n\texport default {\r\n\t\tonLaunch() {\r\n\t\t\tuni.__log__('log','at App.uvue:7','App Launch')\r\n\t\t},\r\n\t\tonShow() {\r\n\t\t\tuni.__log__('log','at App.uvue:10','App Show')\r\n\t\t},\r\n\t\tonHide() {\r\n\t\t\tuni.__log__('log','at App.uvue:13','App Hide')\r\n\t\t},\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t\tonExit() {\r\n\t\t\tuni.__log__('log','at App.uvue:34','App Exit')\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n\t.uni-row {\r\n\t\tflex-direction: row;\r\n\t}\r\n\r\n\t.uni-column {\r\n\t\tflex-direction: column;\r\n\t}\r\n</style>"],"names":["defineComponent"],"mappings":";;AACA,QAAM,EAAE,sBAAsB,mBAAmB,sBAAsB,oBAAoB,uBAAuB,iBAAgB,IAAK;AACvI,QAAM,OAAO;AACb,QAAM,aAAa;AACnB,QAAM,aAAa;AACnB,QAAM,SAAS;AACf,QAAM,iBAAiB;AACvB,QAAM,MAAoB,mCAAmB,MAAM,cAAc;AACjE,QAAM,MAAoB,sCAAsB,MAAM,cAAc;AAE7D,QAAM,oBAAoB,CAAE;AAG5B,QAAM,YAA0B,qCAAqB,OAAO,EAAE,YAAY,YAAY,QAAQ,MAAM,MAAM,SAAS,KAAK,OAAO,KAAK,MAAM,iBAAiB,WAAW,OAAO,QAAQ,CAAC,EAAC,QAAO,WAAU,QAAO,sDAAqD,CAAC,GAAG,QAAQ,GAAE,CAAC;;;;ACWzR,QAAA,cAAeA,oBAAA;AAAA,IACd,OAAI;AACH,aAAO;AAAA,QACN,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,QAEX,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,WAAW;AAAA;IAEZ;AAAA,IACD,SAAM;AAAA,IAaL;AAAA,IACD,SAAS;AAAA,MACR,QAAQ,MAAS,MAAA;AAChB,YAAI,QAAQ,OAAM,gCAA+B,GAAG;AAAA,MACpD;AAAA,MACD,aAAU;AACT,kBAAU;AAAA,UACT,SAAS,CAAC,MAAoC,SAAA;AAC7C,gBAAI,QAAQ,OAAM,gCAA+B,aAAa,GAAG;AACjE,iBAAK,YAAY,IAAI;AAAA,UAErB;AAAA,UACD,MAAM,CAAC,MAA2B,SAAA;AACjC,gBAAI,QAAQ,OAAM,gCAA+B,aAAa,GAAG;AAAA,UAClE;AAAA,QACmB,CAAA;AAAA,MACrB;AAAA,IACD;AAAA,GACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChEA,QAAA,YAAeA,oBAAA;AAAA,IACd,WAAQ;AACP,UAAI,QAAQ,OAAM,iBAAgB,YAAY;AAAA,IAC9C;AAAA,IACD,SAAM;AACL,UAAI,QAAQ,OAAM,kBAAiB,UAAU;AAAA,IAC7C;AAAA,IACD,SAAM;AACL,UAAI,QAAQ,OAAM,kBAAiB,UAAU;AAAA,IAC7C;AAAA,IAmBD,SAAM;AACL,UAAI,QAAQ,OAAM,kBAAiB,UAAU;AAAA,IAC7C;AAAA,GACF;;;;;;;;;;;;;","sourceRoot":"/Users/feast/Documents/CameraProj/SkinCamera"}
|
||||||
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
Vendored
+32
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"env": {
|
||||||
|
"compilerVersion": "4.87"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"utssdk/app-android/CameraManager.uts": {
|
||||||
|
"md5": "e36a0dff48ecd81fd4fd72e001266289"
|
||||||
|
},
|
||||||
|
"utssdk/app-android/camera.uts": {
|
||||||
|
"md5": "508c7d62fd4aeecebbae1cc07966f15a"
|
||||||
|
},
|
||||||
|
"utssdk/app-android/index.uts": {
|
||||||
|
"md5": "13664ac42171591a65f347ae45fe741b"
|
||||||
|
},
|
||||||
|
"utssdk/interface.uts": {
|
||||||
|
"md5": "ad42bbba6fb76c067b82ab17e0b7f2d2"
|
||||||
|
},
|
||||||
|
"package.json": {
|
||||||
|
"md5": "133398ad40a66c5dffe357524836867a"
|
||||||
|
},
|
||||||
|
"utssdk/app-android/AndroidManifest.xml": {
|
||||||
|
"md5": "7652ee876f4a20f80867fe01bf17a74f"
|
||||||
|
},
|
||||||
|
"utssdk/app-android/config.json": {
|
||||||
|
"md5": "5a79003477b0c0436ccfb8782f8b1be9"
|
||||||
|
},
|
||||||
|
"utssdk/app-android/index.vue": {
|
||||||
|
"md5": "4b27b0ce87deb69b8ab88a79f6e6ef70"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"duts":["uni-actionSheet","uni-theme","uni-getSystemInfo","uni-base64ToArrayBuffer","uni-arrayBufferToBase64","uni-crash","uni-storage","uni-dialogPage","uni-event","uni-exit","uni-form","uni-getAccessibilityInfo","uni-getAppAuthorizeSetting","uni-getAppBaseInfo","uni-getDeviceInfo","uni-getElementById","uni-getSystemSetting","uni-modal","uni-openAppAuthorizeSetting","uni-privacy","uni-progress","uni-prompt","uni-rpx2px","uni-rich-text"]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"dir":"/Volumes/Elements/srv_release/bdserver.dcloud.net.cn/service/storage/app/package/IOS/queue/682599b0-1e0f-11f1-9581-8972781ecb49/wgtRoot/__UNI__644401D/"}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"env": {
|
||||||
|
"compilerVersion": "4.87"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"utssdk/app-ios/CameraManager.uts": {
|
||||||
|
"md5": "e36a0dff48ecd81fd4fd72e001266289"
|
||||||
|
},
|
||||||
|
"utssdk/app-ios/camera.uts": {
|
||||||
|
"md5": "cafd4abbac9d57f5b49c0e2dd9131515"
|
||||||
|
},
|
||||||
|
"utssdk/app-ios/index.uts": {
|
||||||
|
"md5": "c93aa4536c5df6b2e32603f110bcef83"
|
||||||
|
},
|
||||||
|
"utssdk/interface.uts": {
|
||||||
|
"md5": "ad42bbba6fb76c067b82ab17e0b7f2d2"
|
||||||
|
},
|
||||||
|
"package.json": {
|
||||||
|
"md5": "133398ad40a66c5dffe357524836867a"
|
||||||
|
},
|
||||||
|
"utssdk/app-ios/config.json": {
|
||||||
|
"md5": "f5a5d7ae3ae1349af9816e625de9299f"
|
||||||
|
},
|
||||||
|
"utssdk/app-ios/index.vue": {
|
||||||
|
"md5": "7396593a50cbeff49fcd3c16e279ddf6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"duts":["uni-actionSheet","uni-theme","uni-getSystemInfo","uni-dialogPage","uni-event","uni-base64ToArrayBuffer","uni-arrayBufferToBase64","uni-crash","uni-storage","uni-exit","uni-form","uni-getAccessibilityInfo","uni-getAppAuthorizeSetting","uni-getAppBaseInfo","uni-getDeviceInfo","uni-getElementById","uni-getSystemSetting","uni-modal","uni-openAppAuthorizeSetting","uni-privacy","uni-progress","uni-prompt","uni-rpx2px","uni-rich-text"]}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
let firstBackTime = 0
|
||||||
|
|
||||||
|
const __sfc__ = defineApp({
|
||||||
|
onLaunch() {
|
||||||
|
console.log('App Launch')
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.log('App Show')
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
console.log('App Hide')
|
||||||
|
},
|
||||||
|
|
||||||
|
onLastPageBackPress() {
|
||||||
|
console.log('App LastPageBackPress')
|
||||||
|
if (firstBackTime == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '再按一次退出应用',
|
||||||
|
position: 'bottom',
|
||||||
|
})
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
setTimeout(() => {
|
||||||
|
firstBackTime = 0
|
||||||
|
}, 2000)
|
||||||
|
} else if (Date.now() - firstBackTime < 2000) {
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
uni.exit()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
console.log('App Exit')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default __sfc__
|
||||||
|
const GenAppStyles = [_uM([["uni-row", _pS(_uM([["flexDirection", "row"]]))], ["uni-column", _pS(_uM([["flexDirection", "column"]]))]])]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import App from './App.uvue'
|
||||||
|
|
||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function main(app: IApp) {
|
||||||
|
definePageRoutes();
|
||||||
|
defineAppConfig();
|
||||||
|
(createApp()['app'] as VueApp).mount(app, GenUniApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UniAppConfig extends io.dcloud.uniapp.appframe.AppConfig {
|
||||||
|
override name: string = "SkinCamera"
|
||||||
|
override appid: string = "__UNI__644401D"
|
||||||
|
override versionName: string = "1.0.0"
|
||||||
|
override versionCode: string = "100"
|
||||||
|
override uniCompilerVersion: string = "4.87"
|
||||||
|
|
||||||
|
constructor() { super() }
|
||||||
|
}
|
||||||
|
|
||||||
|
import GenPagesIndexIndexClass from './pages/index/index.uvue'
|
||||||
|
function definePageRoutes() {
|
||||||
|
__uniRoutes.push({ path: "pages/index/index", component: GenPagesIndexIndexClass, meta: { isQuit: true } as UniPageMeta, style: _uM([["navigationBarTitleText","uni-app x"]]) } as UniPageRoute)
|
||||||
|
}
|
||||||
|
const __uniTabBar: Map<string, any | null> | null = null
|
||||||
|
const __uniLaunchPage: Map<string, any | null> = _uM([["url","pages/index/index"],["style",_uM([["navigationBarTitleText","uni-app x"]])]])
|
||||||
|
function defineAppConfig(){
|
||||||
|
__uniConfig.entryPagePath = '/pages/index/index'
|
||||||
|
__uniConfig.globalStyle = _uM([["navigationBarTextStyle","black"],["navigationBarTitleText","uni-app x"],["navigationBarBackgroundColor","#F8F8F8"],["backgroundColor","#F8F8F8"]])
|
||||||
|
__uniConfig.getTabBarConfig = ():Map<string, any> | null => null
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig()
|
||||||
|
__uniConfig.conditionUrl = ''
|
||||||
|
__uniConfig.uniIdRouter = _uM()
|
||||||
|
|
||||||
|
__uniConfig.ready = true
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('vue');
|
||||||
|
|
||||||
|
uni;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
takePhoto,
|
||||||
|
TakePhotoOption,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
GeneralCallbackResult
|
||||||
|
} from '@/uni_modules/Skin-cameraUTS'
|
||||||
|
|
||||||
|
|
||||||
|
const __sfc__ = defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
yanse: "red",
|
||||||
|
widthNum: 750,
|
||||||
|
heightNum: 200,
|
||||||
|
|
||||||
|
flash: "on",
|
||||||
|
device: "front",
|
||||||
|
imagePath: "",
|
||||||
|
videoPath: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO",
|
||||||
|
// "android.permission.WRITE_EXTERNAL_STORAGE"]
|
||||||
|
// // 请求权限
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// // 权限请求成功
|
||||||
|
// } else {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, _ : string[]) {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onError(res : any) {
|
||||||
|
console.log(res)
|
||||||
|
},
|
||||||
|
_takePhoto() {
|
||||||
|
takePhoto({
|
||||||
|
success: (res : TakePhotoSuccessCallbackResult) => {
|
||||||
|
console.log('takePhoto', res)
|
||||||
|
this.imagePath = res.tempImagePath;
|
||||||
|
// _previewImage();
|
||||||
|
},
|
||||||
|
fail: (res : GeneralCallbackResult) => {
|
||||||
|
console.log('takePhoto', res)
|
||||||
|
}
|
||||||
|
} as TakePhotoOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default __sfc__
|
||||||
|
function GenPagesIndexIndexRender(this: InstanceType<typeof __sfc__>): any | null {
|
||||||
|
const _ctx = this
|
||||||
|
const _cache = this.$.renderCache
|
||||||
|
return _cE("scroll-view", _uM({ class: "page-scroll-view" }), [
|
||||||
|
_cE("view", null, [
|
||||||
|
_cE("button", _uM({ onClick: _ctx._takePhoto }), "拍照", 8 /* PROPS */, ["onClick"]),
|
||||||
|
_cE(XfCameraComponent.name, _uM({
|
||||||
|
style: _nS(_uM({"height":"750rpx","background-color":"red"})),
|
||||||
|
onError: _ctx.onError,
|
||||||
|
flash: _ctx.flash,
|
||||||
|
position: _ctx.device
|
||||||
|
}), null, 44 /* STYLE, PROPS, NEED_HYDRATION */, ["onError", "flash", "position"]),
|
||||||
|
isTrue(_ctx.imagePath)
|
||||||
|
? _cE("image", _uM({
|
||||||
|
key: 0,
|
||||||
|
mode: "aspectFit",
|
||||||
|
src: _ctx.imagePath
|
||||||
|
}), null, 8 /* PROPS */, ["src"])
|
||||||
|
: _cC("v-if", true)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const GenPagesIndexIndexStyles = [_uM([["video", _pS(_uM([["width", "100%"], ["height", 200]]))]])]
|
||||||
|
|
||||||
|
import { XfCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy'
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+474
@@ -0,0 +1,474 @@
|
|||||||
|
import ProcessCameraProvider from 'androidx.camera.lifecycle.ProcessCameraProvider'
|
||||||
|
import Preview from 'androidx.camera.core.Preview'
|
||||||
|
import MirrorMode from 'androidx.camera.core.MirrorMode'
|
||||||
|
import PreviewView from 'androidx.camera.view.PreviewView'
|
||||||
|
import Context from 'android.content.Context'
|
||||||
|
import FrameLayout from 'android.widget.FrameLayout'
|
||||||
|
import ViewGroup from 'android.view.ViewGroup'
|
||||||
|
import CameraSelector from 'androidx.camera.core.CameraSelector'
|
||||||
|
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
|
||||||
|
import UseCaseGroup from 'androidx.camera.core.UseCaseGroup'
|
||||||
|
import ContextCompat from 'androidx.core.content.ContextCompat'
|
||||||
|
|
||||||
|
import LifecycleOwner from 'androidx.lifecycle.LifecycleOwner'
|
||||||
|
import ImageCapture from 'androidx.camera.core.ImageCapture'
|
||||||
|
import Size from 'android.util.Size'
|
||||||
|
import ImageCaptureException from 'androidx.camera.core.ImageCaptureException'
|
||||||
|
import File from 'java.io.File'
|
||||||
|
|
||||||
|
import ImageProxy from 'androidx.camera.core.ImageProxy';
|
||||||
|
import CameraControl from 'androidx.camera.core.CameraControl';
|
||||||
|
|
||||||
|
import VideoCapture from 'androidx.camera.video.VideoCapture'
|
||||||
|
import Recorder from 'androidx.camera.video.Recorder'
|
||||||
|
import Recording from 'androidx.camera.video.Recording'
|
||||||
|
import FileOutputOptions from 'androidx.camera.video.FileOutputOptions'
|
||||||
|
import Quality from 'androidx.camera.video.Quality'
|
||||||
|
import QualitySelector from 'androidx.camera.video.QualitySelector'
|
||||||
|
import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent'
|
||||||
|
|
||||||
|
// import Bitmap from 'android.graphics.Bitmap';
|
||||||
|
// import FileOutputStream from 'java.io.FileOutputStream';
|
||||||
|
// import MediaMetadataRetriever from 'android.media.MediaMetadataRetriever';
|
||||||
|
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService'
|
||||||
|
import Executors from 'java.util.concurrent.Executors'
|
||||||
|
import ImageFormat from 'android.graphics.ImageFormat';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
SetZoomSuccessCallbackResult,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
StartRecordTimeoutCallbackResult,
|
||||||
|
StopRecordSuccessCallbackResult,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
|
||||||
|
CameraConfig,
|
||||||
|
FlashMode,
|
||||||
|
DevicePosition,
|
||||||
|
Resolution,
|
||||||
|
//FrameSize,
|
||||||
|
QualityType,
|
||||||
|
OnCameraFrameListenerOption
|
||||||
|
} from '../interface'
|
||||||
|
import Bitmap from "android.graphics.Bitmap";
|
||||||
|
import FileOutputStream from "java.io.FileOutputStream";
|
||||||
|
|
||||||
|
const FLASH_MODE_MAP = new Map<string, Int>([
|
||||||
|
['auto', ImageCapture.FLASH_MODE_AUTO],
|
||||||
|
['on', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['torch', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['off', ImageCapture.FLASH_MODE_OFF],
|
||||||
|
])
|
||||||
|
const DEVICE_POSITION_MAP = new Map<string, Int>([
|
||||||
|
['back', CameraSelector.LENS_FACING_BACK],
|
||||||
|
['front', CameraSelector.LENS_FACING_FRONT],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
const RESOLUTION_MAP = new Map<string, Quality>([
|
||||||
|
['low', Quality.LOWEST],
|
||||||
|
['medium', Quality.HD],
|
||||||
|
['high', Quality.HIGHEST],
|
||||||
|
])
|
||||||
|
|
||||||
|
const QUALITY_MAP = new Map<string, Int>([
|
||||||
|
["high", ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY],
|
||||||
|
["low", ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY],
|
||||||
|
])
|
||||||
|
|
||||||
|
const MIRROR_MODE = new Map<boolean, Int>([
|
||||||
|
[true, MirrorMode.MIRROR_MODE_ON],
|
||||||
|
[false, MirrorMode.MIRROR_MODE_OFF],
|
||||||
|
])
|
||||||
|
|
||||||
|
export class CameraView extends FrameLayout {
|
||||||
|
private previewView : PreviewView;
|
||||||
|
public cameraProvider : ProcessCameraProvider | null = null;
|
||||||
|
public cameraControl : CameraControl | null = null;
|
||||||
|
private imageCapture : ImageCapture | null = null
|
||||||
|
private videoCapture : VideoCapture<Recorder> | null = null
|
||||||
|
private recording : Recording | null = null
|
||||||
|
private comp : UTSComponent<CameraView>;
|
||||||
|
private flashMode : FlashMode = 'auto'
|
||||||
|
private devicePosition : DevicePosition = 'back'
|
||||||
|
private resolution : Resolution = 'medium'
|
||||||
|
// private frameSize : FrameSize = 'medium'
|
||||||
|
private quality : QualityType = 'normal'
|
||||||
|
private videoFile : File | null = null
|
||||||
|
maxZoom : Float = (1).toFloat()
|
||||||
|
minZoom : Float = (1).toFloat()
|
||||||
|
private initialized : boolean = false
|
||||||
|
private isListening : boolean = false
|
||||||
|
private videoSelfieMirror : boolean | null = null
|
||||||
|
private cameraContextStopRecordOption : CameraContextStopRecordOption | null = null
|
||||||
|
private onCameraFrameCallback : OnCameraFrameCallback | null = null
|
||||||
|
private cameraExecutor : ExecutorService | null = null
|
||||||
|
private onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null;
|
||||||
|
|
||||||
|
constructor(context : Context, comp : UTSComponent<CameraView>, config : CameraConfig = {} as CameraConfig) {
|
||||||
|
super(context);
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
|
||||||
|
this.previewView = new PreviewView(context);
|
||||||
|
let layoutParam = new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
this.addView(this.previewView, layoutParam);
|
||||||
|
this.setupCamera()
|
||||||
|
}
|
||||||
|
setupCamera() {
|
||||||
|
// this.checkSelfPermission().then(() => {
|
||||||
|
let cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!)
|
||||||
|
cameraProviderFuture.addListener(() => {
|
||||||
|
try {
|
||||||
|
this.cameraProvider = cameraProviderFuture.get()
|
||||||
|
this.bindCamera();
|
||||||
|
} catch (e) {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
this.comp.$emit('error', ret)
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!))
|
||||||
|
// }).catch((err) => {
|
||||||
|
// let ret : Map<string, any> = new Map();
|
||||||
|
// ret.set("errMsg", err ?? '权限不足')
|
||||||
|
// this.comp.$emit('error', ret)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
bindCamera() {
|
||||||
|
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? Quality.HD;
|
||||||
|
let captureMode = QUALITY_MAP.get(this.quality) ?? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
|
||||||
|
|
||||||
|
// 创建一个Preview对象,用于显示相机预览
|
||||||
|
let preview = new Preview.Builder()
|
||||||
|
.build();
|
||||||
|
preview.setSurfaceProvider(this.previewView.getSurfaceProvider());
|
||||||
|
|
||||||
|
// 创建一个ImageCapture对象,用于拍照
|
||||||
|
let imageCapture = new ImageCapture.Builder()
|
||||||
|
.setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!)
|
||||||
|
.setCaptureMode(captureMode)
|
||||||
|
.build();
|
||||||
|
// 选择要使用的相机(后置或前置)
|
||||||
|
let cameraSelector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recorder = Recorder.Builder()
|
||||||
|
.setQualitySelector(QualitySelector.from(targetResolution))
|
||||||
|
let mirrorMode = this.videoSelfieMirror == null ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MIRROR_MODE.get(this.videoSelfieMirror!);
|
||||||
|
let videoCapture = new VideoCapture.Builder(recorder.build())
|
||||||
|
.setMirrorMode(mirrorMode!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.videoCapture = videoCapture
|
||||||
|
|
||||||
|
// 将参数应用到 VideoCapture
|
||||||
|
this.imageCapture = imageCapture;
|
||||||
|
this.cameraProvider!.unbindAll();
|
||||||
|
let imageAnalysis = ImageAnalysis.Builder()
|
||||||
|
// .setTargetResolution(new Size(1280, 720))
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
let viewPort = this.previewView.getViewPort()!
|
||||||
|
let useCaseGroup = new UseCaseGroup.Builder()
|
||||||
|
.addUseCase(preview)
|
||||||
|
.addUseCase(imageAnalysis)
|
||||||
|
.addUseCase(imageCapture)
|
||||||
|
.addUseCase(videoCapture)
|
||||||
|
.setViewPort(viewPort)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
let _this = this
|
||||||
|
class Analyzer implements ImageAnalysis.Analyzer {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
override analyze(image : ImageProxy) {
|
||||||
|
if (_this.isListening) {
|
||||||
|
let imageFormat = image.getFormat();
|
||||||
|
// 获取图像的宽度和高度
|
||||||
|
let width = image.width
|
||||||
|
let height = image.height
|
||||||
|
|
||||||
|
if (imageFormat == ImageFormat.YUV_420_888) {
|
||||||
|
_this.onCameraFrameCallback?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
_this.onCameraFrameListenerOption?.success?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.close();
|
||||||
|
}
|
||||||
|
override getDefaultTargetResolution() : Size {
|
||||||
|
// 返回默认的目标分辨率
|
||||||
|
return Size(640, 480)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageAnalysis.setAnalyzer(this.cameraExecutor!, new Analyzer() as ImageAnalysis.Analyzer)
|
||||||
|
|
||||||
|
let camera = this.cameraProvider!.bindToLifecycle(
|
||||||
|
UTSAndroid.getUniActivity()! as LifecycleOwner,
|
||||||
|
cameraSelector,
|
||||||
|
useCaseGroup
|
||||||
|
// preview,
|
||||||
|
// imageCapture,
|
||||||
|
// videoCapture
|
||||||
|
);
|
||||||
|
this.cameraControl = camera.getCameraControl();
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true
|
||||||
|
let zoomState = camera.getCameraInfo().getZoomState().getValue();
|
||||||
|
this.maxZoom = zoomState?.getMaxZoomRatio() ?? (1).toFloat();
|
||||||
|
this.minZoom = zoomState?.getMinZoomRatio() ?? (1).toFloat();
|
||||||
|
let maxZoom = this.maxZoom
|
||||||
|
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", maxZoom)
|
||||||
|
this.comp.$emit('ready', ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
unbindCamera() {
|
||||||
|
this.cameraProvider?.unbindAll()
|
||||||
|
this.cameraExecutor?.shutdown()
|
||||||
|
}
|
||||||
|
// checkSelfPermission() : Promise<boolean> {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE"]
|
||||||
|
// return new Promise((resoleve, _) => {
|
||||||
|
// resoleve(true);
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, list : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// resoleve(true);
|
||||||
|
// } else {
|
||||||
|
// reject(list);
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, grantedList : string[]) {
|
||||||
|
// //grantedList
|
||||||
|
// reject(grantedList);
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
setConfig(config : CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
}
|
||||||
|
setFlash(mode : FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null) return
|
||||||
|
this.imageCapture!.setFlashMode(FLASH_MODE_MAP.get(mode)!)
|
||||||
|
}
|
||||||
|
switchCamera(position : DevicePosition) {
|
||||||
|
this.devicePosition = position
|
||||||
|
if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null) return
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
isResolutionSupported(context : Context, cameraSelector : CameraSelector, targetResolution : Size) : boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
let quality = option.quality ?? "high";
|
||||||
|
this.quality = quality;
|
||||||
|
if (quality == 'low') {
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
const tempFile = new File(privateDir);
|
||||||
|
if (!tempFile.exists()) {
|
||||||
|
tempFile.mkdirs()
|
||||||
|
}
|
||||||
|
// 设置文件名
|
||||||
|
const photoFile = new File(privateDir, Date.now() + '.png');
|
||||||
|
|
||||||
|
// 处理镜像
|
||||||
|
let metadata = new ImageCapture.Metadata();
|
||||||
|
metadata.setReversedHorizontal(option.selfieMirror ?? DEVICE_POSITION_MAP.get(this.devicePosition)! == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
|
||||||
|
const outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
|
||||||
|
class OnImageSavedCallback implements ImageCapture.OnImageSavedCallback {
|
||||||
|
override onImageSaved(outputFileResults : ImageCapture.OutputFileResults) {
|
||||||
|
option.success?.({
|
||||||
|
tempImagePath: photoFile.getAbsolutePath(),
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
override onError(err : ImageCaptureException) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: err.toString()
|
||||||
|
}
|
||||||
|
option.fail?.(result)
|
||||||
|
option.complete?.(result)
|
||||||
|
}
|
||||||
|
override onCaptureStarted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.imageCapture!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), new OnImageSavedCallback() as ImageCapture.OnImageSavedCallback)
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
if (this.cameraControl != null) {
|
||||||
|
try {
|
||||||
|
this.cameraControl!.setZoomRatio(option.zoom.toFloat())
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: e.toString()
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: '相机不存在'
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) {
|
||||||
|
this.videoSelfieMirror = option.selfieMirror
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
if (this.recording != null || this.videoCapture == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
// 设置文件名
|
||||||
|
this.videoFile = new File(privateDir, Date.now() + 'video.mp4');
|
||||||
|
let outputFileOptions = new FileOutputOptions.Builder(this.videoFile!).build();
|
||||||
|
|
||||||
|
|
||||||
|
this.recording = this.videoCapture!.output
|
||||||
|
.prepareRecording(UTSAndroid.getUniActivity()!, outputFileOptions)
|
||||||
|
.withAudioEnabled()
|
||||||
|
// .apply(()=>{
|
||||||
|
// })
|
||||||
|
.start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), (recordEvent : VideoRecordEvent) => {
|
||||||
|
if (recordEvent instanceof VideoRecordEvent.Start) {
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.recording?.stop()
|
||||||
|
}, option.timeout!)
|
||||||
|
}
|
||||||
|
} else if (recordEvent instanceof VideoRecordEvent.Finalize) {
|
||||||
|
this.recording?.close()
|
||||||
|
this.recording = null
|
||||||
|
let tempVideoPath = this.videoFile?.getAbsolutePath()!
|
||||||
|
//let tempThumbPath = extractThumbnail(tempVideoPath) ?? ''
|
||||||
|
this.cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult)
|
||||||
|
|
||||||
|
option.timeoutCallback?.({
|
||||||
|
tempVideoPath,
|
||||||
|
} as StartRecordTimeoutCallbackResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
if (this.recording == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.cameraContextStopRecordOption = option
|
||||||
|
this.recording?.stop()
|
||||||
|
}
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option ?: StopOption) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // 从视频文件中提取封面的函数
|
||||||
|
// function extractThumbnail(videoUri : string) : string | null {
|
||||||
|
// let retriever = new MediaMetadataRetriever()
|
||||||
|
// retriever.setDataSource(videoUri)
|
||||||
|
// // 提取视频的某一帧作为封面
|
||||||
|
// let thumbnail = retriever.frameAtTime
|
||||||
|
// // 保存封面到文件
|
||||||
|
// if (thumbnail != null) {
|
||||||
|
// let privateDir = UTSAndroid.getAppCachePath();
|
||||||
|
// const thumbnailFile = new File(privateDir, Date.now() + '_thumbnail.jpg');
|
||||||
|
// let outputStream = FileOutputStream(thumbnailFile)
|
||||||
|
// thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||||
|
// outputStream.close()
|
||||||
|
// return thumbnailFile.getAbsolutePath()
|
||||||
|
// }
|
||||||
|
// retriever.release()
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
Vendored
+252
@@ -0,0 +1,252 @@
|
|||||||
|
import { CameraView } from './camera'
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
// CameraFrameListener,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
// OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
TakePhoto,
|
||||||
|
SetZoom,
|
||||||
|
StartRecord,
|
||||||
|
StopRecord,
|
||||||
|
StartCameraFrame,
|
||||||
|
StopCameraFrame,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
SwitchOption,
|
||||||
|
SwitchCamera
|
||||||
|
} from '../interface'
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
|
||||||
|
|
||||||
|
class CameraFrameListener {
|
||||||
|
context : CameraContext
|
||||||
|
constructor(context : CameraContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
start() : void
|
||||||
|
start(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stop() : void
|
||||||
|
stop(option ?: StopOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener : CameraFrameListener | null = null
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
getCamera() : Promise<CameraView> {
|
||||||
|
let cameraPreview = CameraManager.getInstance().getCameraView()
|
||||||
|
if (cameraPreview != null) {
|
||||||
|
return Promise.resolve<CameraView>(cameraPreview);
|
||||||
|
}
|
||||||
|
let resolveFunc : ((res : CameraView) => void) | null = null
|
||||||
|
let rejectFunc : ((res : string) => void) | null = null
|
||||||
|
|
||||||
|
let maxAttempts : number = 5
|
||||||
|
let currentAttempt : number = 0
|
||||||
|
|
||||||
|
function attemptGetCamera() {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera !== null) {
|
||||||
|
resolveFunc?.(camera)
|
||||||
|
} else if (currentAttempt < maxAttempts) {
|
||||||
|
currentAttempt++
|
||||||
|
setTimeout(() => {
|
||||||
|
attemptGetCamera()
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
rejectFunc?.('Camera not found after multiple attempts')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise<CameraView>((resolve, reject) => {
|
||||||
|
resolveFunc = resolve
|
||||||
|
rejectFunc = reject
|
||||||
|
attemptGetCamera()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.takePhoto(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.setZoom(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.startRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.stopRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onCameraFrame() : CameraFrameListener
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrame(callback)
|
||||||
|
})
|
||||||
|
return this.mCameraFrameListener!
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrameListener(option)
|
||||||
|
}).catch(() => {
|
||||||
|
console.log("未找到相机")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option : CameraFrameListenerStartOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option : StopOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCamera(option : SwitchOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.switchCamera(option.position)
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context : CameraContext = new CameraContext();
|
||||||
|
|
||||||
|
export function createCameraContext() : CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export const takePhoto : TakePhoto = function (options : TakePhotoOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options : OnCameraFrameListenerOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopCameraFrame : StopCameraFrame = function (options : StopOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const switchCamera : SwitchCamera = function (options : SwitchOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
Vendored
+151
@@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import { CameraView } from './camera';
|
||||||
|
import { CameraConfig } from '../interface'
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件内部变量声明
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.switchCamera(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.setFlash(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [必须实现] 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* (Android需要明确知道View类型,需特殊校验)
|
||||||
|
*/
|
||||||
|
NVLoad() : CameraView {
|
||||||
|
let previewView = new CameraView(this.$androidContext!, this)
|
||||||
|
previewView.setTag("9527");
|
||||||
|
CameraManager.getInstance().setCameraView(previewView);
|
||||||
|
return previewView
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已创建
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View布局完成
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
this.$el?.setConfig({
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig)
|
||||||
|
this.$el?.switchCamera(this.position)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View将释放
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
this.$emit('stop', ret)
|
||||||
|
|
||||||
|
this.$el?.unbindCamera()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件销毁
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
unpackage/dist/build/.tsc/app-android/uni_modules/Skin-cameraUTS/utssdk/app-ios/CameraManager.uts.ts
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+469
@@ -0,0 +1,469 @@
|
|||||||
|
import { UIImage, UIView } from 'UIKit';
|
||||||
|
import { AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoPreviewLayer, AVMediaType, AVLayerVideoGravity, AVCaptureVideoDataOutput, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureOutput, AVCaptureConnection, AVCapturePhotoOutput, AVCapturePhotoSettings, AVCapturePhotoCaptureDelegate, AVCaptureMovieFileOutput, AVCaptureFileOutputRecordingDelegate, AVCaptureFileOutput } from "AVFoundation";
|
||||||
|
import { CMSampleBuffer, CMSampleBufferGetImageBuffer } from 'CoreMedia';
|
||||||
|
import { DispatchQueue } from 'Dispatch';
|
||||||
|
import { CVPixelBufferGetWidth, CVPixelBufferGetHeight } from "CoreVideo";
|
||||||
|
import { NSError, URL } from "Foundation";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
SetZoomSuccessCallbackResult,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
StartRecordTimeoutCallbackResult,
|
||||||
|
StopRecordSuccessCallbackResult,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
OnCameraFrameCallbackResult,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
|
||||||
|
CameraConfig,
|
||||||
|
FlashMode,
|
||||||
|
DevicePosition,
|
||||||
|
Resolution
|
||||||
|
} from '../interface'
|
||||||
|
import { AVCapturePhoto } from 'AVFoundation';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const RESOLUTION_MAP = new Map<string, AVCaptureSession.Preset>([
|
||||||
|
['low', AVCaptureSession.Preset.low],
|
||||||
|
['medium', AVCaptureSession.Preset.medium],
|
||||||
|
['high', AVCaptureSession.Preset.high],
|
||||||
|
])
|
||||||
|
|
||||||
|
const FLASH_MODE_MAP = new Map<string, AVCaptureDevice.TorchMode>([
|
||||||
|
['auto', AVCaptureDevice.TorchMode.auto],
|
||||||
|
['on', AVCaptureDevice.TorchMode.on],
|
||||||
|
['torch', AVCaptureDevice.TorchMode.on],
|
||||||
|
['off', AVCaptureDevice.TorchMode.off],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
let fileOutputRecordingDelegate : MyAVCaptureFileOutputRecordingDelegate | null = null;
|
||||||
|
let capturePhotoCaptureDelegate : MyAVCapturePhotoCaptureDelegate | null = null;
|
||||||
|
let onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null;
|
||||||
|
let cameraContextStopRecordOption : CameraContextStopRecordOption | null = null;
|
||||||
|
|
||||||
|
class MyAVCaptureFileOutputRecordingDelegate implements AVCaptureFileOutputRecordingDelegate {
|
||||||
|
private option : CameraContextStartRecordOption;
|
||||||
|
|
||||||
|
constructor(option : CameraContextStartRecordOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutput(output : AVCaptureFileOutput, @argumentLabel("didStartRecordingTo") fileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection]) : void {
|
||||||
|
this.option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutput(output : AVCaptureFileOutput, @argumentLabel("didFinishRecordingTo") outputFileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection], @argumentLabel("error") error ?: NSError) : void {
|
||||||
|
if (error == null) {
|
||||||
|
cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath: outputFileURL.path + "",
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult)
|
||||||
|
|
||||||
|
this.option.timeoutCallback?.({
|
||||||
|
tempVideoPath: outputFileURL.path + ""
|
||||||
|
} as StartRecordTimeoutCallbackResult)
|
||||||
|
} else {
|
||||||
|
cameraContextStopRecordOption?.fail?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
cameraContextStopRecordOption?.complete?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyAVCapturePhotoCaptureDelegate implements AVCapturePhotoCaptureDelegate {
|
||||||
|
private option : TakePhotoOption;
|
||||||
|
|
||||||
|
constructor(option : TakePhotoOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
captureOutput(output : AVCapturePhotoOutput, @argumentLabel("didFinishProcessingPhoto") photo : AVCapturePhoto, @argumentLabel("error") error : Error) : void {
|
||||||
|
let imageData = photo.fileDataRepresentation()
|
||||||
|
if (imageData == null) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".jpg";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
|
||||||
|
let quality = this.option.quality ?? "original"
|
||||||
|
if (quality == 'original') {
|
||||||
|
try {
|
||||||
|
UTSiOS.try(imageData!.write(to = url, options = NSData.WritingOptions.atomic))
|
||||||
|
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let image = UIImage(data = imageData!)
|
||||||
|
if (image != null) {
|
||||||
|
let _quality : number = 0.5;
|
||||||
|
if (quality == 'high') {
|
||||||
|
_quality = 1.0;
|
||||||
|
} else if (quality == 'normal') {
|
||||||
|
_quality = 0.8;
|
||||||
|
} else if (quality == 'low') {
|
||||||
|
_quality = 0.5;
|
||||||
|
}
|
||||||
|
let data = image!.jpegData(compressionQuality = _quality.toDouble())!
|
||||||
|
|
||||||
|
try {
|
||||||
|
UTSiOS.try(data.write(to = url, options = NSData.WritingOptions.atomic))
|
||||||
|
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CameraView implements AVCaptureVideoDataOutputSampleBufferDelegate {
|
||||||
|
|
||||||
|
private captureSession ?: AVCaptureSession;
|
||||||
|
private videoDeviceInput ?: AVCaptureDeviceInput;
|
||||||
|
private photoDeviceOutput ?: AVCapturePhotoOutput;
|
||||||
|
private movieDeviceOutput ?: AVCaptureMovieFileOutput;
|
||||||
|
private previewLayer ?: AVCaptureVideoPreviewLayer;
|
||||||
|
private previewView ?: UIView;
|
||||||
|
private comp ?: UTSComponent<UIView>;
|
||||||
|
private flashMode : FlashMode = 'auto'
|
||||||
|
private devicePosition : DevicePosition = 'back'
|
||||||
|
private resolution : Resolution = 'medium'
|
||||||
|
private isListening : boolean = false
|
||||||
|
private onCameraFrameCallback : OnCameraFrameCallback | null = null;
|
||||||
|
|
||||||
|
constructor(previewView : UIView, comp : UTSComponent<UIView>, config : CameraConfig = {} as CameraConfig) {
|
||||||
|
super.init();
|
||||||
|
this.previewView = previewView
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
this.setupCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCamera() {
|
||||||
|
this.captureSession = new AVCaptureSession();
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? AVCaptureSession.Preset.high;
|
||||||
|
this.captureSession!.sessionPreset = targetResolution;
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
try {
|
||||||
|
//相机
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device))
|
||||||
|
if (this.captureSession!.canAddInput(deviceInput)) {
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
}
|
||||||
|
//实时流输出
|
||||||
|
let output = new AVCaptureVideoDataOutput();
|
||||||
|
output.setSampleBufferDelegate(this, queue = DispatchQueue.main);
|
||||||
|
if (this.captureSession!.canAddOutput(output)) {
|
||||||
|
this.captureSession!.addOutput(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
let photoOutput = new AVCapturePhotoOutput();
|
||||||
|
// let photoSettings = new AVCapturePhotoSettings(format = [AVVideoCodecKey = AVVideoCodecType.jpeg]);
|
||||||
|
// photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler = null);
|
||||||
|
if (this.captureSession!.canAddOutput(photoOutput)) {
|
||||||
|
this.captureSession!.addOutput(photoOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
let movieOutput = new AVCaptureMovieFileOutput();
|
||||||
|
if (this.captureSession!.canAddOutput(movieOutput)) {
|
||||||
|
this.captureSession!.addOutput(movieOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.photoDeviceOutput = photoOutput;
|
||||||
|
this.movieDeviceOutput = movieOutput;
|
||||||
|
this.previewLayer = new AVCaptureVideoPreviewLayer(session = this.captureSession!)
|
||||||
|
this.previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill;
|
||||||
|
this.previewLayer!.frame = this.previewView!.layer.bounds;
|
||||||
|
this.previewView!.layer.addSublayer(this.previewLayer!)
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", device.maxAvailableVideoZoomFactor)
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.comp?.$emit("ready", detail);
|
||||||
|
} catch (e) {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.comp?.$emit("error", detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCameraDevice() : AVCaptureDevice {
|
||||||
|
let cameras = AVCaptureDevice.devices(for = AVMediaType.video)
|
||||||
|
let frontCamera : AVCaptureDevice | null = null;
|
||||||
|
let backCamera : AVCaptureDevice | null = null;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
while (index < cameras.length) {
|
||||||
|
if (index == cameras.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let camera = cameras[index.toInt()] as AVCaptureDevice;
|
||||||
|
if (camera.position == AVCaptureDevice.Position.back) {
|
||||||
|
backCamera = camera;
|
||||||
|
} else {
|
||||||
|
frontCamera = camera;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.devicePosition == 'back') {
|
||||||
|
return backCamera!;
|
||||||
|
} else {
|
||||||
|
return frontCamera!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
captureOutput(output : AVCaptureOutput, @argumentLabel("didOutput") sampleBuffer : CMSampleBuffer, @argumentLabel("from") connection : AVCaptureConnection) : void {
|
||||||
|
|
||||||
|
if (this.isListening) {
|
||||||
|
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
|
||||||
|
let width = CVPixelBufferGetWidth(imageBuffer!)
|
||||||
|
let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
|
||||||
|
const res : OnCameraFrameCallbackResult = {
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
};
|
||||||
|
this.onCameraFrameCallback?.(res)
|
||||||
|
onCameraFrameListenerOption?.success?.({
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
// this.onCameraFrameListenerOption?.success({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: imageBuffer!
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
|
||||||
|
// if (imageBuffer != null) {
|
||||||
|
// let ciImage = new CIImage(cvPixelBuffer = imageBuffer!)
|
||||||
|
// let temporaryContext = new CIContext(options = null)
|
||||||
|
// let width = CVPixelBufferGetWidth(imageBuffer!)
|
||||||
|
// let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
// let videoImage = temporaryContext.createCGImage(ciImage, from = new CGRect(x = 0, y = 0, width = width, height = height))
|
||||||
|
// let image = new UIImage(cgImage = videoImage!)
|
||||||
|
|
||||||
|
// this.onCameraFrameCallback?.({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: image
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindCamera() {
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unbindCamera() {
|
||||||
|
this.captureSession!.stopRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setConfig(config : CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash(mode : FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
let targetFlash = FLASH_MODE_MAP.get(this.flashMode) ?? AVCaptureDevice.TorchMode.auto;
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
if (device.hasTorch && device.isTorchAvailable) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.torchMode = targetFlash;
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switchCamera(position : DevicePosition) {
|
||||||
|
if (!this.captureSession!.isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.devicePosition = position;
|
||||||
|
try {
|
||||||
|
this.captureSession!.beginConfiguration();
|
||||||
|
this.captureSession!.removeInput(this.videoDeviceInput!);
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device))
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.captureSession!.commitConfiguration();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
capturePhotoCaptureDelegate = new MyAVCapturePhotoCaptureDelegate(option);
|
||||||
|
let settings = new AVCapturePhotoSettings()
|
||||||
|
this.photoDeviceOutput?.capturePhoto(with = settings, delegate = capturePhotoCaptureDelegate!);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
// if (device.isRampingVideoZoom) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.videoZoomFactor = option.zoom.toDouble();
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
// } else {
|
||||||
|
// const result : GeneralCallbackResult = {
|
||||||
|
// errMsg: '相机不支持缩放'
|
||||||
|
// }
|
||||||
|
// option.complete?.(result)
|
||||||
|
// option.fail?.(result)
|
||||||
|
// }
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutputRecordingDelegate = new MyAVCaptureFileOutputRecordingDelegate(option);
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".mp4";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
this.movieDeviceOutput?.startRecording(to = url, recordingDelegate = fileOutputRecordingDelegate!);
|
||||||
|
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}, (option.timeout!).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (!isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cameraContextStopRecordOption = option;
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option ?: StopOption) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+207
@@ -0,0 +1,207 @@
|
|||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
TakePhoto,
|
||||||
|
SetZoom,
|
||||||
|
StartRecord,
|
||||||
|
StopRecord,
|
||||||
|
StartCameraFrame,
|
||||||
|
StopCameraFrame,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
SwitchCamera,
|
||||||
|
SwitchOption
|
||||||
|
} from '../interface'
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
|
||||||
|
class CameraFrameListener {
|
||||||
|
context : CameraContext
|
||||||
|
constructor(context : CameraContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
start() : void
|
||||||
|
start(option ?: CameraFrameListenerStartOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
stop() : void
|
||||||
|
stop(option ?: StopOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener : CameraFrameListener | null = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.takePhoto(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera?.setZoom(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.startRecord(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.stopRecord(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrame() : CameraFrameListener
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
CameraManager.getInstance().getCameraView()?.onCameraFrame(callback)
|
||||||
|
return this.mCameraFrameListener!
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.onCameraFrameListener(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option : CameraFrameListenerStartOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option : StopOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCamera(option : SwitchOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.switchCamera(option.position)
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context : CameraContext = new CameraContext();
|
||||||
|
|
||||||
|
export function createCameraContext() : CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export const takePhoto : TakePhoto = function (options : TakePhotoOption) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options : OnCameraFrameListenerOption) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopCameraFrame : StopCameraFrame = function (options : StopOption) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const switchCamera : SwitchCamera = function (options : SwitchOption) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}
|
||||||
Vendored
+166
@@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import { UIView } from "UIKit"
|
||||||
|
import { CameraView } from "./camera";
|
||||||
|
import { CameraConfig } from '../interface'
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : String, _oldValue : String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.switchCamera(newValue)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : String, _oldValue : String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.setFlash(newValue)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* [必须实现]
|
||||||
|
*/
|
||||||
|
NVLoad() : UIView {
|
||||||
|
let previewView = new UIView()
|
||||||
|
previewView.tag = 9527
|
||||||
|
return previewView
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生View已创建
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View布局完成
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
let previewView = this.$el as UIView;
|
||||||
|
let cameraView = new CameraView(previewView, this, {
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
devicePosition: this.position,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig)
|
||||||
|
CameraManager.getInstance().setCameraView(cameraView);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View将释放
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.$emit("stop", detail);
|
||||||
|
|
||||||
|
CameraManager.getInstance().getCameraView()?.unbindCamera()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件销毁
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
export type GeneralCallbackResult = {
|
||||||
|
/** 错误信息 */
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
export type TakePhotoSuccessCallbackResult = {
|
||||||
|
/** 照片文件的临时路径 (本地路径),安卓是jpg图片格式,ios是png */
|
||||||
|
tempImagePath : string
|
||||||
|
quality : string
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type TakePhotoFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type TakePhotoSuccessCallback = (
|
||||||
|
result : TakePhotoSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type TakePhotoCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
|
||||||
|
export type TakePhotoOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: TakePhotoCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: TakePhotoFailCallback
|
||||||
|
/** 成像质量
|
||||||
|
*
|
||||||
|
* 可选值:
|
||||||
|
* - 'high': 高质量;
|
||||||
|
* - 'normal': 普通质量;
|
||||||
|
* - 'low': 低质量;
|
||||||
|
* - 'original': 原图; */
|
||||||
|
quality ?: 'high' | 'normal' | 'low' | 'original'
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror ?: boolean
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: TakePhotoSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type SetZoomSuccessCallbackResult = {
|
||||||
|
/** 实际设置的缩放级别。由于系统限制,某些机型可能无法设置成指定值,会改用最接近的可设值。 */
|
||||||
|
zoom : number
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SetZoomCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SetZoomFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextSetZoomSuccessCallback = (
|
||||||
|
result : SetZoomSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
|
||||||
|
export type CameraContextSetZoomOption = {
|
||||||
|
/** 缩放级别,范围[1, maxZoom]。zoom 可取小数,精确到小数后一位。maxZoom 可在 bindinitdone 返回值中获取。 */
|
||||||
|
zoom : number
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: SetZoomCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: SetZoomFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextSetZoomSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StartRecordTimeoutCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath : string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StartRecordCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StartRecordFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
export type StartRecordTimeoutCallback = (
|
||||||
|
result : StartRecordTimeoutCallbackResult
|
||||||
|
) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStartRecordSuccessCallback = (
|
||||||
|
res : GeneralCallbackResult
|
||||||
|
) => void
|
||||||
|
export type CameraContextStartRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StartRecordCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StartRecordFailCallback
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror ?: boolean
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextStartRecordSuccessCallback
|
||||||
|
/**
|
||||||
|
* 录制时长上限,单位为秒,最长不能超过 5 分钟 */
|
||||||
|
timeout ?: number
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
timeoutCallback ?: StartRecordTimeoutCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StopRecordSuccessCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath : string
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopRecordCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopRecordFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStopRecordSuccessCallback = (
|
||||||
|
result : StopRecordSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
export type CameraContextStopRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StopRecordCompleteCallback
|
||||||
|
/** 启动视频压缩,压缩效果同`chooseVideo` */
|
||||||
|
compressed ?: boolean
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StopRecordFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextStopRecordSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 回调函数 */
|
||||||
|
export type OnCameraFrameCallback = (result : OnCameraFrameCallbackResult) => void
|
||||||
|
export type OnCameraFrameCallbackResult = {
|
||||||
|
/** 图像像素点数据,一维数组,每四项表示一个像素点的 rgba */
|
||||||
|
data : any
|
||||||
|
/** 图像数据矩形的高度 */
|
||||||
|
height : number
|
||||||
|
/** 图像数据矩形的宽度 */
|
||||||
|
width : number
|
||||||
|
}
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StartSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
type StartFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
type StartCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type CameraFrameListenerStartOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StartCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StartFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: StartSuccessCallback
|
||||||
|
/** [Worker](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.html)
|
||||||
|
*
|
||||||
|
* 可选参数。如果需要在 iOS ExperimentalWorker 内监听摄像头帧数据,则需要传入对应 Worker 对象。详情 [Worker.getCameraFrameData](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.getCameraFrameData.html) */
|
||||||
|
// worker ?: Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StopSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type StopOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StopCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StopFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: StopSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashMode = 'auto' | 'on' | 'off' | 'torch'
|
||||||
|
export type DevicePosition = 'back' | 'front'
|
||||||
|
export type Resolution = 'low' | 'medium' | 'high'
|
||||||
|
export type FrameSize = 'medium' | 'small' | 'large'
|
||||||
|
export type QualityType = 'high' | 'normal' | 'low' | 'original'
|
||||||
|
|
||||||
|
|
||||||
|
export type CameraConfig = {
|
||||||
|
flash ?: FlashMode
|
||||||
|
devicePosition ?: DevicePosition
|
||||||
|
resolution ?: Resolution
|
||||||
|
frameSize ?: FrameSize
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SwitchCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SwitchFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type SwitchSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type SwitchOption = {
|
||||||
|
position : DevicePosition
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: SwitchCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: SwitchFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: SwitchSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export type OnCameraFrameListenerOption = {
|
||||||
|
success ?: OnCameraFrameCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TakePhoto = (options : TakePhotoOption) => void
|
||||||
|
export type SetZoom = (options : CameraContextSetZoomOption) => void
|
||||||
|
export type StartRecord = (options : CameraContextStartRecordOption) => void
|
||||||
|
export type StopRecord = (options : CameraContextStopRecordOption) => void
|
||||||
|
export type OnCameraFrameListener = (options : OnCameraFrameListenerOption) => void
|
||||||
|
export type StartCameraFrame = (options : CameraFrameListenerStartOption) => void
|
||||||
|
export type StopCameraFrame = (options : StopOption) => void
|
||||||
|
export type SwitchCamera = (options : SwitchOption) => void
|
||||||
unpackage/dist/build/.tsc/app-ios/uni_modules/Skin-cameraUTS/utssdk/app-android/CameraManager.uts.ts
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+474
@@ -0,0 +1,474 @@
|
|||||||
|
import ProcessCameraProvider from 'androidx.camera.lifecycle.ProcessCameraProvider'
|
||||||
|
import Preview from 'androidx.camera.core.Preview'
|
||||||
|
import MirrorMode from 'androidx.camera.core.MirrorMode'
|
||||||
|
import PreviewView from 'androidx.camera.view.PreviewView'
|
||||||
|
import Context from 'android.content.Context'
|
||||||
|
import FrameLayout from 'android.widget.FrameLayout'
|
||||||
|
import ViewGroup from 'android.view.ViewGroup'
|
||||||
|
import CameraSelector from 'androidx.camera.core.CameraSelector'
|
||||||
|
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
|
||||||
|
import UseCaseGroup from 'androidx.camera.core.UseCaseGroup'
|
||||||
|
import ContextCompat from 'androidx.core.content.ContextCompat'
|
||||||
|
|
||||||
|
import LifecycleOwner from 'androidx.lifecycle.LifecycleOwner'
|
||||||
|
import ImageCapture from 'androidx.camera.core.ImageCapture'
|
||||||
|
import Size from 'android.util.Size'
|
||||||
|
import ImageCaptureException from 'androidx.camera.core.ImageCaptureException'
|
||||||
|
import File from 'java.io.File'
|
||||||
|
|
||||||
|
import ImageProxy from 'androidx.camera.core.ImageProxy';
|
||||||
|
import CameraControl from 'androidx.camera.core.CameraControl';
|
||||||
|
|
||||||
|
import VideoCapture from 'androidx.camera.video.VideoCapture'
|
||||||
|
import Recorder from 'androidx.camera.video.Recorder'
|
||||||
|
import Recording from 'androidx.camera.video.Recording'
|
||||||
|
import FileOutputOptions from 'androidx.camera.video.FileOutputOptions'
|
||||||
|
import Quality from 'androidx.camera.video.Quality'
|
||||||
|
import QualitySelector from 'androidx.camera.video.QualitySelector'
|
||||||
|
import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent'
|
||||||
|
|
||||||
|
// import Bitmap from 'android.graphics.Bitmap';
|
||||||
|
// import FileOutputStream from 'java.io.FileOutputStream';
|
||||||
|
// import MediaMetadataRetriever from 'android.media.MediaMetadataRetriever';
|
||||||
|
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService'
|
||||||
|
import Executors from 'java.util.concurrent.Executors'
|
||||||
|
import ImageFormat from 'android.graphics.ImageFormat';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
SetZoomSuccessCallbackResult,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
StartRecordTimeoutCallbackResult,
|
||||||
|
StopRecordSuccessCallbackResult,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
|
||||||
|
CameraConfig,
|
||||||
|
FlashMode,
|
||||||
|
DevicePosition,
|
||||||
|
Resolution,
|
||||||
|
//FrameSize,
|
||||||
|
QualityType,
|
||||||
|
OnCameraFrameListenerOption
|
||||||
|
} from '../interface'
|
||||||
|
import Bitmap from "android.graphics.Bitmap";
|
||||||
|
import FileOutputStream from "java.io.FileOutputStream";
|
||||||
|
|
||||||
|
const FLASH_MODE_MAP = new Map<string, Int>([
|
||||||
|
['auto', ImageCapture.FLASH_MODE_AUTO],
|
||||||
|
['on', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['torch', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['off', ImageCapture.FLASH_MODE_OFF],
|
||||||
|
])
|
||||||
|
const DEVICE_POSITION_MAP = new Map<string, Int>([
|
||||||
|
['back', CameraSelector.LENS_FACING_BACK],
|
||||||
|
['front', CameraSelector.LENS_FACING_FRONT],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
const RESOLUTION_MAP = new Map<string, Quality>([
|
||||||
|
['low', Quality.LOWEST],
|
||||||
|
['medium', Quality.HD],
|
||||||
|
['high', Quality.HIGHEST],
|
||||||
|
])
|
||||||
|
|
||||||
|
const QUALITY_MAP = new Map<string, Int>([
|
||||||
|
["high", ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY],
|
||||||
|
["low", ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY],
|
||||||
|
])
|
||||||
|
|
||||||
|
const MIRROR_MODE = new Map<boolean, Int>([
|
||||||
|
[true, MirrorMode.MIRROR_MODE_ON],
|
||||||
|
[false, MirrorMode.MIRROR_MODE_OFF],
|
||||||
|
])
|
||||||
|
|
||||||
|
export class CameraView extends FrameLayout {
|
||||||
|
private previewView : PreviewView;
|
||||||
|
public cameraProvider : ProcessCameraProvider | null = null;
|
||||||
|
public cameraControl : CameraControl | null = null;
|
||||||
|
private imageCapture : ImageCapture | null = null
|
||||||
|
private videoCapture : VideoCapture<Recorder> | null = null
|
||||||
|
private recording : Recording | null = null
|
||||||
|
private comp : UTSComponent<CameraView>;
|
||||||
|
private flashMode : FlashMode = 'auto'
|
||||||
|
private devicePosition : DevicePosition = 'back'
|
||||||
|
private resolution : Resolution = 'medium'
|
||||||
|
// private frameSize : FrameSize = 'medium'
|
||||||
|
private quality : QualityType = 'normal'
|
||||||
|
private videoFile : File | null = null
|
||||||
|
maxZoom : Float = (1).toFloat()
|
||||||
|
minZoom : Float = (1).toFloat()
|
||||||
|
private initialized : boolean = false
|
||||||
|
private isListening : boolean = false
|
||||||
|
private videoSelfieMirror : boolean | null = null
|
||||||
|
private cameraContextStopRecordOption : CameraContextStopRecordOption | null = null
|
||||||
|
private onCameraFrameCallback : OnCameraFrameCallback | null = null
|
||||||
|
private cameraExecutor : ExecutorService | null = null
|
||||||
|
private onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null;
|
||||||
|
|
||||||
|
constructor(context : Context, comp : UTSComponent<CameraView>, config : CameraConfig = {} as CameraConfig) {
|
||||||
|
super(context);
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
|
||||||
|
this.previewView = new PreviewView(context);
|
||||||
|
let layoutParam = new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
this.addView(this.previewView, layoutParam);
|
||||||
|
this.setupCamera()
|
||||||
|
}
|
||||||
|
setupCamera() {
|
||||||
|
// this.checkSelfPermission().then(() => {
|
||||||
|
let cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!)
|
||||||
|
cameraProviderFuture.addListener(() => {
|
||||||
|
try {
|
||||||
|
this.cameraProvider = cameraProviderFuture.get()
|
||||||
|
this.bindCamera();
|
||||||
|
} catch (e) {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
this.comp.$emit('error', ret)
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!))
|
||||||
|
// }).catch((err) => {
|
||||||
|
// let ret : Map<string, any> = new Map();
|
||||||
|
// ret.set("errMsg", err ?? '权限不足')
|
||||||
|
// this.comp.$emit('error', ret)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
bindCamera() {
|
||||||
|
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? Quality.HD;
|
||||||
|
let captureMode = QUALITY_MAP.get(this.quality) ?? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
|
||||||
|
|
||||||
|
// 创建一个Preview对象,用于显示相机预览
|
||||||
|
let preview = new Preview.Builder()
|
||||||
|
.build();
|
||||||
|
preview.setSurfaceProvider(this.previewView.getSurfaceProvider());
|
||||||
|
|
||||||
|
// 创建一个ImageCapture对象,用于拍照
|
||||||
|
let imageCapture = new ImageCapture.Builder()
|
||||||
|
.setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!)
|
||||||
|
.setCaptureMode(captureMode)
|
||||||
|
.build();
|
||||||
|
// 选择要使用的相机(后置或前置)
|
||||||
|
let cameraSelector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recorder = Recorder.Builder()
|
||||||
|
.setQualitySelector(QualitySelector.from(targetResolution))
|
||||||
|
let mirrorMode = this.videoSelfieMirror == null ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MIRROR_MODE.get(this.videoSelfieMirror!);
|
||||||
|
let videoCapture = new VideoCapture.Builder(recorder.build())
|
||||||
|
.setMirrorMode(mirrorMode!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.videoCapture = videoCapture
|
||||||
|
|
||||||
|
// 将参数应用到 VideoCapture
|
||||||
|
this.imageCapture = imageCapture;
|
||||||
|
this.cameraProvider!.unbindAll();
|
||||||
|
let imageAnalysis = ImageAnalysis.Builder()
|
||||||
|
// .setTargetResolution(new Size(1280, 720))
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
let viewPort = this.previewView.getViewPort()!
|
||||||
|
let useCaseGroup = new UseCaseGroup.Builder()
|
||||||
|
.addUseCase(preview)
|
||||||
|
.addUseCase(imageAnalysis)
|
||||||
|
.addUseCase(imageCapture)
|
||||||
|
.addUseCase(videoCapture)
|
||||||
|
.setViewPort(viewPort)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
let _this = this
|
||||||
|
class Analyzer implements ImageAnalysis.Analyzer {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
override analyze(image : ImageProxy) {
|
||||||
|
if (_this.isListening) {
|
||||||
|
let imageFormat = image.getFormat();
|
||||||
|
// 获取图像的宽度和高度
|
||||||
|
let width = image.width
|
||||||
|
let height = image.height
|
||||||
|
|
||||||
|
if (imageFormat == ImageFormat.YUV_420_888) {
|
||||||
|
_this.onCameraFrameCallback?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
_this.onCameraFrameListenerOption?.success?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.close();
|
||||||
|
}
|
||||||
|
override getDefaultTargetResolution() : Size {
|
||||||
|
// 返回默认的目标分辨率
|
||||||
|
return Size(640, 480)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageAnalysis.setAnalyzer(this.cameraExecutor!, new Analyzer() as ImageAnalysis.Analyzer)
|
||||||
|
|
||||||
|
let camera = this.cameraProvider!.bindToLifecycle(
|
||||||
|
UTSAndroid.getUniActivity()! as LifecycleOwner,
|
||||||
|
cameraSelector,
|
||||||
|
useCaseGroup
|
||||||
|
// preview,
|
||||||
|
// imageCapture,
|
||||||
|
// videoCapture
|
||||||
|
);
|
||||||
|
this.cameraControl = camera.getCameraControl();
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true
|
||||||
|
let zoomState = camera.getCameraInfo().getZoomState().getValue();
|
||||||
|
this.maxZoom = zoomState?.getMaxZoomRatio() ?? (1).toFloat();
|
||||||
|
this.minZoom = zoomState?.getMinZoomRatio() ?? (1).toFloat();
|
||||||
|
let maxZoom = this.maxZoom
|
||||||
|
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", maxZoom)
|
||||||
|
this.comp.$emit('ready', ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
unbindCamera() {
|
||||||
|
this.cameraProvider?.unbindAll()
|
||||||
|
this.cameraExecutor?.shutdown()
|
||||||
|
}
|
||||||
|
// checkSelfPermission() : Promise<boolean> {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE"]
|
||||||
|
// return new Promise((resoleve, _) => {
|
||||||
|
// resoleve(true);
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, list : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// resoleve(true);
|
||||||
|
// } else {
|
||||||
|
// reject(list);
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, grantedList : string[]) {
|
||||||
|
// //grantedList
|
||||||
|
// reject(grantedList);
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
setConfig(config : CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
}
|
||||||
|
setFlash(mode : FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null) return
|
||||||
|
this.imageCapture!.setFlashMode(FLASH_MODE_MAP.get(mode)!)
|
||||||
|
}
|
||||||
|
switchCamera(position : DevicePosition) {
|
||||||
|
this.devicePosition = position
|
||||||
|
if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null) return
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
isResolutionSupported(context : Context, cameraSelector : CameraSelector, targetResolution : Size) : boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
let quality = option.quality ?? "high";
|
||||||
|
this.quality = quality;
|
||||||
|
if (quality == 'low') {
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
const tempFile = new File(privateDir);
|
||||||
|
if (!tempFile.exists()) {
|
||||||
|
tempFile.mkdirs()
|
||||||
|
}
|
||||||
|
// 设置文件名
|
||||||
|
const photoFile = new File(privateDir, Date.now() + '.png');
|
||||||
|
|
||||||
|
// 处理镜像
|
||||||
|
let metadata = new ImageCapture.Metadata();
|
||||||
|
metadata.setReversedHorizontal(option.selfieMirror ?? DEVICE_POSITION_MAP.get(this.devicePosition)! == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
|
||||||
|
const outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
|
||||||
|
class OnImageSavedCallback implements ImageCapture.OnImageSavedCallback {
|
||||||
|
override onImageSaved(outputFileResults : ImageCapture.OutputFileResults) {
|
||||||
|
option.success?.({
|
||||||
|
tempImagePath: photoFile.getAbsolutePath(),
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
override onError(err : ImageCaptureException) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: err.toString()
|
||||||
|
}
|
||||||
|
option.fail?.(result)
|
||||||
|
option.complete?.(result)
|
||||||
|
}
|
||||||
|
override onCaptureStarted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.imageCapture!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), new OnImageSavedCallback() as ImageCapture.OnImageSavedCallback)
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
if (this.cameraControl != null) {
|
||||||
|
try {
|
||||||
|
this.cameraControl!.setZoomRatio(option.zoom.toFloat())
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: e.toString()
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: '相机不存在'
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) {
|
||||||
|
this.videoSelfieMirror = option.selfieMirror
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
if (this.recording != null || this.videoCapture == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
// 设置文件名
|
||||||
|
this.videoFile = new File(privateDir, Date.now() + 'video.mp4');
|
||||||
|
let outputFileOptions = new FileOutputOptions.Builder(this.videoFile!).build();
|
||||||
|
|
||||||
|
|
||||||
|
this.recording = this.videoCapture!.output
|
||||||
|
.prepareRecording(UTSAndroid.getUniActivity()!, outputFileOptions)
|
||||||
|
.withAudioEnabled()
|
||||||
|
// .apply(()=>{
|
||||||
|
// })
|
||||||
|
.start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), (recordEvent : VideoRecordEvent) => {
|
||||||
|
if (recordEvent instanceof VideoRecordEvent.Start) {
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.recording?.stop()
|
||||||
|
}, option.timeout!)
|
||||||
|
}
|
||||||
|
} else if (recordEvent instanceof VideoRecordEvent.Finalize) {
|
||||||
|
this.recording?.close()
|
||||||
|
this.recording = null
|
||||||
|
let tempVideoPath = this.videoFile?.getAbsolutePath()!
|
||||||
|
//let tempThumbPath = extractThumbnail(tempVideoPath) ?? ''
|
||||||
|
this.cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult)
|
||||||
|
|
||||||
|
option.timeoutCallback?.({
|
||||||
|
tempVideoPath,
|
||||||
|
} as StartRecordTimeoutCallbackResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
if (this.recording == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.cameraContextStopRecordOption = option
|
||||||
|
this.recording?.stop()
|
||||||
|
}
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option ?: StopOption) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // 从视频文件中提取封面的函数
|
||||||
|
// function extractThumbnail(videoUri : string) : string | null {
|
||||||
|
// let retriever = new MediaMetadataRetriever()
|
||||||
|
// retriever.setDataSource(videoUri)
|
||||||
|
// // 提取视频的某一帧作为封面
|
||||||
|
// let thumbnail = retriever.frameAtTime
|
||||||
|
// // 保存封面到文件
|
||||||
|
// if (thumbnail != null) {
|
||||||
|
// let privateDir = UTSAndroid.getAppCachePath();
|
||||||
|
// const thumbnailFile = new File(privateDir, Date.now() + '_thumbnail.jpg');
|
||||||
|
// let outputStream = FileOutputStream(thumbnailFile)
|
||||||
|
// thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||||
|
// outputStream.close()
|
||||||
|
// return thumbnailFile.getAbsolutePath()
|
||||||
|
// }
|
||||||
|
// retriever.release()
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
Vendored
+252
@@ -0,0 +1,252 @@
|
|||||||
|
import { CameraView } from './camera'
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
// CameraFrameListener,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
// OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
TakePhoto,
|
||||||
|
SetZoom,
|
||||||
|
StartRecord,
|
||||||
|
StopRecord,
|
||||||
|
StartCameraFrame,
|
||||||
|
StopCameraFrame,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
SwitchOption,
|
||||||
|
SwitchCamera
|
||||||
|
} from '../interface'
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
|
||||||
|
|
||||||
|
class CameraFrameListener {
|
||||||
|
context : CameraContext
|
||||||
|
constructor(context : CameraContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
start() : void
|
||||||
|
start(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stop() : void
|
||||||
|
stop(option ?: StopOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener : CameraFrameListener | null = null
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
getCamera() : Promise<CameraView> {
|
||||||
|
let cameraPreview = CameraManager.getInstance().getCameraView()
|
||||||
|
if (cameraPreview != null) {
|
||||||
|
return Promise.resolve<CameraView>(cameraPreview);
|
||||||
|
}
|
||||||
|
let resolveFunc : ((res : CameraView) => void) | null = null
|
||||||
|
let rejectFunc : ((res : string) => void) | null = null
|
||||||
|
|
||||||
|
let maxAttempts : number = 5
|
||||||
|
let currentAttempt : number = 0
|
||||||
|
|
||||||
|
function attemptGetCamera() {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera !== null) {
|
||||||
|
resolveFunc?.(camera)
|
||||||
|
} else if (currentAttempt < maxAttempts) {
|
||||||
|
currentAttempt++
|
||||||
|
setTimeout(() => {
|
||||||
|
attemptGetCamera()
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
rejectFunc?.('Camera not found after multiple attempts')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise<CameraView>((resolve, reject) => {
|
||||||
|
resolveFunc = resolve
|
||||||
|
rejectFunc = reject
|
||||||
|
attemptGetCamera()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.takePhoto(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.setZoom(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.startRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.stopRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onCameraFrame() : CameraFrameListener
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrame(callback)
|
||||||
|
})
|
||||||
|
return this.mCameraFrameListener!
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrameListener(option)
|
||||||
|
}).catch(() => {
|
||||||
|
console.log("未找到相机")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option : CameraFrameListenerStartOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option : StopOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCamera(option : SwitchOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.switchCamera(option.position)
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context : CameraContext = new CameraContext();
|
||||||
|
|
||||||
|
export function createCameraContext() : CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export const takePhoto : TakePhoto = function (options : TakePhotoOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options : OnCameraFrameListenerOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopCameraFrame : StopCameraFrame = function (options : StopOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const switchCamera : SwitchCamera = function (options : SwitchOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
Vendored
+151
@@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import { CameraView } from './camera';
|
||||||
|
import { CameraConfig } from '../interface'
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件内部变量声明
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.switchCamera(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.setFlash(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [必须实现] 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* (Android需要明确知道View类型,需特殊校验)
|
||||||
|
*/
|
||||||
|
NVLoad() : CameraView {
|
||||||
|
let previewView = new CameraView(this.$androidContext!, this)
|
||||||
|
previewView.setTag("9527");
|
||||||
|
CameraManager.getInstance().setCameraView(previewView);
|
||||||
|
return previewView
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已创建
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View布局完成
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
this.$el?.setConfig({
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig)
|
||||||
|
this.$el?.switchCamera(this.position)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View将释放
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
this.$emit('stop', ret)
|
||||||
|
|
||||||
|
this.$el?.unbindCamera()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件销毁
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+469
@@ -0,0 +1,469 @@
|
|||||||
|
import { UIImage, UIView } from 'UIKit';
|
||||||
|
import { AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoPreviewLayer, AVMediaType, AVLayerVideoGravity, AVCaptureVideoDataOutput, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureOutput, AVCaptureConnection, AVCapturePhotoOutput, AVCapturePhotoSettings, AVCapturePhotoCaptureDelegate, AVCaptureMovieFileOutput, AVCaptureFileOutputRecordingDelegate, AVCaptureFileOutput } from "AVFoundation";
|
||||||
|
import { CMSampleBuffer, CMSampleBufferGetImageBuffer } from 'CoreMedia';
|
||||||
|
import { DispatchQueue } from 'Dispatch';
|
||||||
|
import { CVPixelBufferGetWidth, CVPixelBufferGetHeight } from "CoreVideo";
|
||||||
|
import { NSError, URL } from "Foundation";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
SetZoomSuccessCallbackResult,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
StartRecordTimeoutCallbackResult,
|
||||||
|
StopRecordSuccessCallbackResult,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
OnCameraFrameCallbackResult,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
|
||||||
|
CameraConfig,
|
||||||
|
FlashMode,
|
||||||
|
DevicePosition,
|
||||||
|
Resolution
|
||||||
|
} from '../interface'
|
||||||
|
import { AVCapturePhoto } from 'AVFoundation';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const RESOLUTION_MAP = new Map<string, AVCaptureSession.Preset>([
|
||||||
|
['low', AVCaptureSession.Preset.low],
|
||||||
|
['medium', AVCaptureSession.Preset.medium],
|
||||||
|
['high', AVCaptureSession.Preset.high],
|
||||||
|
])
|
||||||
|
|
||||||
|
const FLASH_MODE_MAP = new Map<string, AVCaptureDevice.TorchMode>([
|
||||||
|
['auto', AVCaptureDevice.TorchMode.auto],
|
||||||
|
['on', AVCaptureDevice.TorchMode.on],
|
||||||
|
['torch', AVCaptureDevice.TorchMode.on],
|
||||||
|
['off', AVCaptureDevice.TorchMode.off],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
let fileOutputRecordingDelegate : MyAVCaptureFileOutputRecordingDelegate | null = null;
|
||||||
|
let capturePhotoCaptureDelegate : MyAVCapturePhotoCaptureDelegate | null = null;
|
||||||
|
let onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null;
|
||||||
|
let cameraContextStopRecordOption : CameraContextStopRecordOption | null = null;
|
||||||
|
|
||||||
|
class MyAVCaptureFileOutputRecordingDelegate implements AVCaptureFileOutputRecordingDelegate {
|
||||||
|
private option : CameraContextStartRecordOption;
|
||||||
|
|
||||||
|
constructor(option : CameraContextStartRecordOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutput(output : AVCaptureFileOutput, @argumentLabel("didStartRecordingTo") fileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection]) : void {
|
||||||
|
this.option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutput(output : AVCaptureFileOutput, @argumentLabel("didFinishRecordingTo") outputFileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection], @argumentLabel("error") error ?: NSError) : void {
|
||||||
|
if (error == null) {
|
||||||
|
cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath: outputFileURL.path + "",
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult)
|
||||||
|
|
||||||
|
this.option.timeoutCallback?.({
|
||||||
|
tempVideoPath: outputFileURL.path + ""
|
||||||
|
} as StartRecordTimeoutCallbackResult)
|
||||||
|
} else {
|
||||||
|
cameraContextStopRecordOption?.fail?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
cameraContextStopRecordOption?.complete?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyAVCapturePhotoCaptureDelegate implements AVCapturePhotoCaptureDelegate {
|
||||||
|
private option : TakePhotoOption;
|
||||||
|
|
||||||
|
constructor(option : TakePhotoOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
|
||||||
|
captureOutput(output : AVCapturePhotoOutput, @argumentLabel("didFinishProcessingPhoto") photo : AVCapturePhoto, @argumentLabel("error") error : Error) : void {
|
||||||
|
let imageData = photo.fileDataRepresentation()
|
||||||
|
if (imageData == null) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".jpg";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
|
||||||
|
let quality = this.option.quality ?? "original"
|
||||||
|
if (quality == 'original') {
|
||||||
|
try {
|
||||||
|
UTSiOS.try(imageData!.write(to = url, options = NSData.WritingOptions.atomic))
|
||||||
|
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let image = UIImage(data = imageData!)
|
||||||
|
if (image != null) {
|
||||||
|
let _quality : number = 0.5;
|
||||||
|
if (quality == 'high') {
|
||||||
|
_quality = 1.0;
|
||||||
|
} else if (quality == 'normal') {
|
||||||
|
_quality = 0.8;
|
||||||
|
} else if (quality == 'low') {
|
||||||
|
_quality = 0.5;
|
||||||
|
}
|
||||||
|
let data = image!.jpegData(compressionQuality = _quality.toDouble())!
|
||||||
|
|
||||||
|
try {
|
||||||
|
UTSiOS.try(data.write(to = url, options = NSData.WritingOptions.atomic))
|
||||||
|
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
}
|
||||||
|
this.option.fail?.(result)
|
||||||
|
this.option.complete?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CameraView implements AVCaptureVideoDataOutputSampleBufferDelegate {
|
||||||
|
|
||||||
|
private captureSession ?: AVCaptureSession;
|
||||||
|
private videoDeviceInput ?: AVCaptureDeviceInput;
|
||||||
|
private photoDeviceOutput ?: AVCapturePhotoOutput;
|
||||||
|
private movieDeviceOutput ?: AVCaptureMovieFileOutput;
|
||||||
|
private previewLayer ?: AVCaptureVideoPreviewLayer;
|
||||||
|
private previewView ?: UIView;
|
||||||
|
private comp ?: UTSComponent<UIView>;
|
||||||
|
private flashMode : FlashMode = 'auto'
|
||||||
|
private devicePosition : DevicePosition = 'back'
|
||||||
|
private resolution : Resolution = 'medium'
|
||||||
|
private isListening : boolean = false
|
||||||
|
private onCameraFrameCallback : OnCameraFrameCallback | null = null;
|
||||||
|
|
||||||
|
constructor(previewView : UIView, comp : UTSComponent<UIView>, config : CameraConfig = {} as CameraConfig) {
|
||||||
|
super.init();
|
||||||
|
this.previewView = previewView
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
this.setupCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCamera() {
|
||||||
|
this.captureSession = new AVCaptureSession();
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? AVCaptureSession.Preset.high;
|
||||||
|
this.captureSession!.sessionPreset = targetResolution;
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
try {
|
||||||
|
//相机
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device))
|
||||||
|
if (this.captureSession!.canAddInput(deviceInput)) {
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
}
|
||||||
|
//实时流输出
|
||||||
|
let output = new AVCaptureVideoDataOutput();
|
||||||
|
output.setSampleBufferDelegate(this, queue = DispatchQueue.main);
|
||||||
|
if (this.captureSession!.canAddOutput(output)) {
|
||||||
|
this.captureSession!.addOutput(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
let photoOutput = new AVCapturePhotoOutput();
|
||||||
|
// let photoSettings = new AVCapturePhotoSettings(format = [AVVideoCodecKey = AVVideoCodecType.jpeg]);
|
||||||
|
// photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler = null);
|
||||||
|
if (this.captureSession!.canAddOutput(photoOutput)) {
|
||||||
|
this.captureSession!.addOutput(photoOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
let movieOutput = new AVCaptureMovieFileOutput();
|
||||||
|
if (this.captureSession!.canAddOutput(movieOutput)) {
|
||||||
|
this.captureSession!.addOutput(movieOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.photoDeviceOutput = photoOutput;
|
||||||
|
this.movieDeviceOutput = movieOutput;
|
||||||
|
this.previewLayer = new AVCaptureVideoPreviewLayer(session = this.captureSession!)
|
||||||
|
this.previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill;
|
||||||
|
this.previewLayer!.frame = this.previewView!.layer.bounds;
|
||||||
|
this.previewView!.layer.addSublayer(this.previewLayer!)
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", device.maxAvailableVideoZoomFactor)
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.comp?.$emit("ready", detail);
|
||||||
|
} catch (e) {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.comp?.$emit("error", detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCameraDevice() : AVCaptureDevice {
|
||||||
|
let cameras = AVCaptureDevice.devices(for = AVMediaType.video)
|
||||||
|
let frontCamera : AVCaptureDevice | null = null;
|
||||||
|
let backCamera : AVCaptureDevice | null = null;
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
while (index < cameras.length) {
|
||||||
|
if (index == cameras.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let camera = cameras[index.toInt()] as AVCaptureDevice;
|
||||||
|
if (camera.position == AVCaptureDevice.Position.back) {
|
||||||
|
backCamera = camera;
|
||||||
|
} else {
|
||||||
|
frontCamera = camera;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.devicePosition == 'back') {
|
||||||
|
return backCamera!;
|
||||||
|
} else {
|
||||||
|
return frontCamera!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
captureOutput(output : AVCaptureOutput, @argumentLabel("didOutput") sampleBuffer : CMSampleBuffer, @argumentLabel("from") connection : AVCaptureConnection) : void {
|
||||||
|
|
||||||
|
if (this.isListening) {
|
||||||
|
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)
|
||||||
|
let width = CVPixelBufferGetWidth(imageBuffer!)
|
||||||
|
let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
|
||||||
|
const res : OnCameraFrameCallbackResult = {
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
};
|
||||||
|
this.onCameraFrameCallback?.(res)
|
||||||
|
onCameraFrameListenerOption?.success?.({
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
// this.onCameraFrameListenerOption?.success({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: imageBuffer!
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
|
||||||
|
// if (imageBuffer != null) {
|
||||||
|
// let ciImage = new CIImage(cvPixelBuffer = imageBuffer!)
|
||||||
|
// let temporaryContext = new CIContext(options = null)
|
||||||
|
// let width = CVPixelBufferGetWidth(imageBuffer!)
|
||||||
|
// let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
// let videoImage = temporaryContext.createCGImage(ciImage, from = new CGRect(x = 0, y = 0, width = width, height = height))
|
||||||
|
// let image = new UIImage(cgImage = videoImage!)
|
||||||
|
|
||||||
|
// this.onCameraFrameCallback?.({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: image
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindCamera() {
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unbindCamera() {
|
||||||
|
this.captureSession!.stopRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setConfig(config : CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash(mode : FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
let targetFlash = FLASH_MODE_MAP.get(this.flashMode) ?? AVCaptureDevice.TorchMode.auto;
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
if (device.hasTorch && device.isTorchAvailable) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.torchMode = targetFlash;
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switchCamera(position : DevicePosition) {
|
||||||
|
if (!this.captureSession!.isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.devicePosition = position;
|
||||||
|
try {
|
||||||
|
this.captureSession!.beginConfiguration();
|
||||||
|
this.captureSession!.removeInput(this.videoDeviceInput!);
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device))
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.captureSession!.commitConfiguration();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
capturePhotoCaptureDelegate = new MyAVCapturePhotoCaptureDelegate(option);
|
||||||
|
let settings = new AVCapturePhotoSettings()
|
||||||
|
this.photoDeviceOutput?.capturePhoto(with = settings, delegate = capturePhotoCaptureDelegate!);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
// if (device.isRampingVideoZoom) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.videoZoomFactor = option.zoom.toDouble();
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
// } else {
|
||||||
|
// const result : GeneralCallbackResult = {
|
||||||
|
// errMsg: '相机不支持缩放'
|
||||||
|
// }
|
||||||
|
// option.complete?.(result)
|
||||||
|
// option.fail?.(result)
|
||||||
|
// }
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileOutputRecordingDelegate = new MyAVCaptureFileOutputRecordingDelegate(option);
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".mp4";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
this.movieDeviceOutput?.startRecording(to = url, recordingDelegate = fileOutputRecordingDelegate!);
|
||||||
|
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}, (option.timeout!).toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (!isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cameraContextStopRecordOption = option;
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option ?: StopOption) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
TakePhoto,
|
||||||
|
SetZoom,
|
||||||
|
StartRecord,
|
||||||
|
StopRecord,
|
||||||
|
StartCameraFrame,
|
||||||
|
StopCameraFrame,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
SwitchCamera,
|
||||||
|
SwitchOption
|
||||||
|
} from '../interface'
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
|
||||||
|
class CameraFrameListener {
|
||||||
|
context : CameraContext
|
||||||
|
constructor(context : CameraContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
start() : void
|
||||||
|
start(option ?: CameraFrameListenerStartOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
stop() : void
|
||||||
|
stop(option ?: StopOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener : CameraFrameListener | null = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.takePhoto(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera?.setZoom(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.startRecord(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.stopRecord(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrame() : CameraFrameListener
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
CameraManager.getInstance().getCameraView()?.onCameraFrame(callback)
|
||||||
|
return this.mCameraFrameListener!
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.onCameraFrameListener(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option : CameraFrameListenerStartOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option : StopOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCamera(option : SwitchOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.switchCamera(option.position)
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context : CameraContext = new CameraContext();
|
||||||
|
|
||||||
|
export function createCameraContext() : CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export const takePhoto : TakePhoto = function (options : TakePhotoOption) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options : OnCameraFrameListenerOption) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopCameraFrame : StopCameraFrame = function (options : StopOption) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const switchCamera : SwitchCamera = function (options : SwitchOption) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import { UIView } from "UIKit"
|
||||||
|
import { CameraView } from "./camera";
|
||||||
|
import { CameraConfig } from '../interface'
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : String, _oldValue : String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.switchCamera(newValue)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : String, _oldValue : String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.setFlash(newValue)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* [必须实现]
|
||||||
|
*/
|
||||||
|
NVLoad() : UIView {
|
||||||
|
let previewView = new UIView()
|
||||||
|
previewView.tag = 9527
|
||||||
|
return previewView
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原生View已创建
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View布局完成
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
let previewView = this.$el as UIView;
|
||||||
|
let cameraView = new CameraView(previewView, this, {
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
devicePosition: this.position,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig)
|
||||||
|
CameraManager.getInstance().setCameraView(cameraView);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View将释放
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
let detail : Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret)
|
||||||
|
this.$emit("stop", detail);
|
||||||
|
|
||||||
|
CameraManager.getInstance().getCameraView()?.unbindCamera()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件销毁
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
export type GeneralCallbackResult = {
|
||||||
|
/** 错误信息 */
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
export type TakePhotoSuccessCallbackResult = {
|
||||||
|
/** 照片文件的临时路径 (本地路径),安卓是jpg图片格式,ios是png */
|
||||||
|
tempImagePath : string
|
||||||
|
quality : string
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type TakePhotoFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type TakePhotoSuccessCallback = (
|
||||||
|
result : TakePhotoSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type TakePhotoCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
|
||||||
|
export type TakePhotoOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: TakePhotoCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: TakePhotoFailCallback
|
||||||
|
/** 成像质量
|
||||||
|
*
|
||||||
|
* 可选值:
|
||||||
|
* - 'high': 高质量;
|
||||||
|
* - 'normal': 普通质量;
|
||||||
|
* - 'low': 低质量;
|
||||||
|
* - 'original': 原图; */
|
||||||
|
quality ?: 'high' | 'normal' | 'low' | 'original'
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror ?: boolean
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: TakePhotoSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type SetZoomSuccessCallbackResult = {
|
||||||
|
/** 实际设置的缩放级别。由于系统限制,某些机型可能无法设置成指定值,会改用最接近的可设值。 */
|
||||||
|
zoom : number
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SetZoomCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SetZoomFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextSetZoomSuccessCallback = (
|
||||||
|
result : SetZoomSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
|
||||||
|
export type CameraContextSetZoomOption = {
|
||||||
|
/** 缩放级别,范围[1, maxZoom]。zoom 可取小数,精确到小数后一位。maxZoom 可在 bindinitdone 返回值中获取。 */
|
||||||
|
zoom : number
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: SetZoomCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: SetZoomFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextSetZoomSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StartRecordTimeoutCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath : string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StartRecordCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StartRecordFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
export type StartRecordTimeoutCallback = (
|
||||||
|
result : StartRecordTimeoutCallbackResult
|
||||||
|
) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStartRecordSuccessCallback = (
|
||||||
|
res : GeneralCallbackResult
|
||||||
|
) => void
|
||||||
|
export type CameraContextStartRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StartRecordCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StartRecordFailCallback
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror ?: boolean
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextStartRecordSuccessCallback
|
||||||
|
/**
|
||||||
|
* 录制时长上限,单位为秒,最长不能超过 5 分钟 */
|
||||||
|
timeout ?: number
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
timeoutCallback ?: StartRecordTimeoutCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StopRecordSuccessCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath : string
|
||||||
|
errMsg : string
|
||||||
|
}
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopRecordCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopRecordFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStopRecordSuccessCallback = (
|
||||||
|
result : StopRecordSuccessCallbackResult
|
||||||
|
) => void
|
||||||
|
export type CameraContextStopRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StopRecordCompleteCallback
|
||||||
|
/** 启动视频压缩,压缩效果同`chooseVideo` */
|
||||||
|
compressed ?: boolean
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StopRecordFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: CameraContextStopRecordSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 回调函数 */
|
||||||
|
export type OnCameraFrameCallback = (result : OnCameraFrameCallbackResult) => void
|
||||||
|
export type OnCameraFrameCallbackResult = {
|
||||||
|
/** 图像像素点数据,一维数组,每四项表示一个像素点的 rgba */
|
||||||
|
data : any
|
||||||
|
/** 图像数据矩形的高度 */
|
||||||
|
height : number
|
||||||
|
/** 图像数据矩形的宽度 */
|
||||||
|
width : number
|
||||||
|
}
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StartSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
type StartFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
type StartCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type CameraFrameListenerStartOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StartCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StartFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: StartSuccessCallback
|
||||||
|
/** [Worker](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.html)
|
||||||
|
*
|
||||||
|
* 可选参数。如果需要在 iOS ExperimentalWorker 内监听摄像头帧数据,则需要传入对应 Worker 对象。详情 [Worker.getCameraFrameData](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.getCameraFrameData.html) */
|
||||||
|
// worker ?: Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StopSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type StopOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: StopCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: StopFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: StopSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashMode = 'auto' | 'on' | 'off' | 'torch'
|
||||||
|
export type DevicePosition = 'back' | 'front'
|
||||||
|
export type Resolution = 'low' | 'medium' | 'high'
|
||||||
|
export type FrameSize = 'medium' | 'small' | 'large'
|
||||||
|
export type QualityType = 'high' | 'normal' | 'low' | 'original'
|
||||||
|
|
||||||
|
|
||||||
|
export type CameraConfig = {
|
||||||
|
flash ?: FlashMode
|
||||||
|
devicePosition ?: DevicePosition
|
||||||
|
resolution ?: Resolution
|
||||||
|
frameSize ?: FrameSize
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SwitchCompleteCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SwitchFailCallback = (res : GeneralCallbackResult) => void
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type SwitchSuccessCallback = (res : GeneralCallbackResult) => void
|
||||||
|
export type SwitchOption = {
|
||||||
|
position : DevicePosition
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete ?: SwitchCompleteCallback
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail ?: SwitchFailCallback
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success ?: SwitchSuccessCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export type OnCameraFrameListenerOption = {
|
||||||
|
success ?: OnCameraFrameCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TakePhoto = (options : TakePhotoOption) => void
|
||||||
|
export type SetZoom = (options : CameraContextSetZoomOption) => void
|
||||||
|
export type StartRecord = (options : CameraContextStartRecordOption) => void
|
||||||
|
export type StopRecord = (options : CameraContextStopRecordOption) => void
|
||||||
|
export type OnCameraFrameListener = (options : OnCameraFrameListenerOption) => void
|
||||||
|
export type StartCameraFrame = (options : CameraFrameListenerStartOption) => void
|
||||||
|
export type StopCameraFrame = (options : StopOption) => void
|
||||||
|
export type SwitchCamera = (options : SwitchOption) => void
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
let firstBackTime = 0;
|
||||||
|
const __sfc__ = defineApp({
|
||||||
|
onLaunch() {
|
||||||
|
console.log('App Launch');
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.log('App Show');
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
console.log('App Hide');
|
||||||
|
},
|
||||||
|
onLastPageBackPress() {
|
||||||
|
console.log('App LastPageBackPress');
|
||||||
|
if (firstBackTime == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '再按一次退出应用',
|
||||||
|
position: 'bottom',
|
||||||
|
});
|
||||||
|
firstBackTime = Date.now();
|
||||||
|
setTimeout(() => {
|
||||||
|
firstBackTime = 0;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
else if (Date.now() - firstBackTime < 2000) {
|
||||||
|
firstBackTime = Date.now();
|
||||||
|
uni.exit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExit() {
|
||||||
|
console.log('App Exit');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export default __sfc__;
|
||||||
|
const GenAppStyles = [_uM([["uni-row", _pS(_uM([["flexDirection", "row"]]))], ["uni-column", _pS(_uM([["flexDirection", "column"]]))]])];
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
import App from './App.uvue';
|
||||||
|
import { createSSRApp } from 'vue';
|
||||||
|
export function createApp(): UTSJSONObject {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function main(app: IApp) {
|
||||||
|
definePageRoutes();
|
||||||
|
defineAppConfig();
|
||||||
|
(createApp()['app'] as VueApp).mount(app, GenUniApp());
|
||||||
|
}
|
||||||
|
export class UniAppConfig extends io.dcloud.uniapp.appframe.AppConfig {
|
||||||
|
override name: string = "SkinCamera";
|
||||||
|
override appid: string = "__UNI__644401D";
|
||||||
|
override versionName: string = "1.0.0";
|
||||||
|
override versionCode: string = "100";
|
||||||
|
override uniCompilerVersion: string = "4.87";
|
||||||
|
constructor() { super(); }
|
||||||
|
}
|
||||||
|
import GenPagesIndexIndexClass from './pages/index/index.uvue';
|
||||||
|
function definePageRoutes() {
|
||||||
|
__uniRoutes.push({ path: "pages/index/index", component: GenPagesIndexIndexClass, meta: { isQuit: true } as UniPageMeta, style: _uM([["navigationBarTitleText", "uni-app x"]]) } as UniPageRoute);
|
||||||
|
}
|
||||||
|
const __uniTabBar: Map<string, any | null> | null = null;
|
||||||
|
const __uniLaunchPage: Map<string, any | null> = _uM([["url", "pages/index/index"], ["style", _uM([["navigationBarTitleText", "uni-app x"]])]]);
|
||||||
|
function defineAppConfig() {
|
||||||
|
__uniConfig.entryPagePath = '/pages/index/index';
|
||||||
|
__uniConfig.globalStyle = _uM([["navigationBarTextStyle", "black"], ["navigationBarTitleText", "uni-app x"], ["navigationBarBackgroundColor", "#F8F8F8"], ["backgroundColor", "#F8F8F8"]]);
|
||||||
|
__uniConfig.getTabBarConfig = (): Map<string, any> | null => null;
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig();
|
||||||
|
__uniConfig.conditionUrl = '';
|
||||||
|
__uniConfig.uniIdRouter = _uM();
|
||||||
|
__uniConfig.ready = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { takePhoto, TakePhotoOption, TakePhotoSuccessCallbackResult, GeneralCallbackResult } from '@/uni_modules/Skin-cameraUTS';
|
||||||
|
const __sfc__ = defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
yanse: "red",
|
||||||
|
widthNum: 750,
|
||||||
|
heightNum: 200,
|
||||||
|
flash: "on",
|
||||||
|
device: "front",
|
||||||
|
imagePath: "",
|
||||||
|
videoPath: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO",
|
||||||
|
// "android.permission.WRITE_EXTERNAL_STORAGE"]
|
||||||
|
// // 请求权限
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// // 权限请求成功
|
||||||
|
// } else {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, _ : string[]) {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onError(res: any) {
|
||||||
|
console.log(res);
|
||||||
|
},
|
||||||
|
_takePhoto() {
|
||||||
|
takePhoto({
|
||||||
|
success: (res: TakePhotoSuccessCallbackResult) => {
|
||||||
|
console.log('takePhoto', res);
|
||||||
|
this.imagePath = res.tempImagePath;
|
||||||
|
// _previewImage();
|
||||||
|
},
|
||||||
|
fail: (res: GeneralCallbackResult) => {
|
||||||
|
console.log('takePhoto', res);
|
||||||
|
}
|
||||||
|
} as TakePhotoOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export default __sfc__;
|
||||||
|
function GenPagesIndexIndexRender(this: InstanceType<typeof __sfc__>): any | null {
|
||||||
|
const _ctx = this;
|
||||||
|
const _cache = this.$.renderCache;
|
||||||
|
return _cE("scroll-view", _uM({ class: "page-scroll-view" }), [
|
||||||
|
_cE("view", null, [
|
||||||
|
_cE("button", _uM({ onClick: _ctx._takePhoto }), "拍照", 8 /* PROPS */, ["onClick"]),
|
||||||
|
_cE(XfCameraComponent.name, _uM({
|
||||||
|
style: _nS(_uM({ "height": "750rpx", "background-color": "red" })),
|
||||||
|
onError: _ctx.onError,
|
||||||
|
flash: _ctx.flash,
|
||||||
|
position: _ctx.device
|
||||||
|
}), null, 44 /* STYLE, PROPS, NEED_HYDRATION */, ["onError", "flash", "position"]),
|
||||||
|
isTrue(_ctx.imagePath)
|
||||||
|
? _cE("image", _uM({
|
||||||
|
key: 0,
|
||||||
|
mode: "aspectFit",
|
||||||
|
src: _ctx.imagePath
|
||||||
|
}), null, 8 /* PROPS */, ["src"])
|
||||||
|
: _cC("v-if", true)
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
const GenPagesIndexIndexStyles = [_uM([["video", _pS(_uM([["width", "100%"], ["height", 200]]))]])];
|
||||||
|
import { XfCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy';
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView: CameraView | null = null;
|
||||||
|
private static instance: CameraManager | null = null;
|
||||||
|
public static getInstance(): CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
public setCameraView(cameraView?: CameraView): void {
|
||||||
|
this.cameraView = cameraView!;
|
||||||
|
}
|
||||||
|
public getCameraView(): CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
public dispose(): void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+414
@@ -0,0 +1,414 @@
|
|||||||
|
import ProcessCameraProvider from 'androidx.camera.lifecycle.ProcessCameraProvider';
|
||||||
|
import Preview from 'androidx.camera.core.Preview';
|
||||||
|
import MirrorMode from 'androidx.camera.core.MirrorMode';
|
||||||
|
import PreviewView from 'androidx.camera.view.PreviewView';
|
||||||
|
import Context from 'android.content.Context';
|
||||||
|
import FrameLayout from 'android.widget.FrameLayout';
|
||||||
|
import ViewGroup from 'android.view.ViewGroup';
|
||||||
|
import CameraSelector from 'androidx.camera.core.CameraSelector';
|
||||||
|
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis';
|
||||||
|
import UseCaseGroup from 'androidx.camera.core.UseCaseGroup';
|
||||||
|
import ContextCompat from 'androidx.core.content.ContextCompat';
|
||||||
|
import LifecycleOwner from 'androidx.lifecycle.LifecycleOwner';
|
||||||
|
import ImageCapture from 'androidx.camera.core.ImageCapture';
|
||||||
|
import Size from 'android.util.Size';
|
||||||
|
import ImageCaptureException from 'androidx.camera.core.ImageCaptureException';
|
||||||
|
import File from 'java.io.File';
|
||||||
|
import ImageProxy from 'androidx.camera.core.ImageProxy';
|
||||||
|
import CameraControl from 'androidx.camera.core.CameraControl';
|
||||||
|
import VideoCapture from 'androidx.camera.video.VideoCapture';
|
||||||
|
import Recorder from 'androidx.camera.video.Recorder';
|
||||||
|
import Recording from 'androidx.camera.video.Recording';
|
||||||
|
import FileOutputOptions from 'androidx.camera.video.FileOutputOptions';
|
||||||
|
import Quality from 'androidx.camera.video.Quality';
|
||||||
|
import QualitySelector from 'androidx.camera.video.QualitySelector';
|
||||||
|
import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent';
|
||||||
|
// import Bitmap from 'android.graphics.Bitmap';
|
||||||
|
// import FileOutputStream from 'java.io.FileOutputStream';
|
||||||
|
// import MediaMetadataRetriever from 'android.media.MediaMetadataRetriever';
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService';
|
||||||
|
import Executors from 'java.util.concurrent.Executors';
|
||||||
|
import ImageFormat from 'android.graphics.ImageFormat';
|
||||||
|
import { TakePhotoOption, GeneralCallbackResult, TakePhotoSuccessCallbackResult, CameraContextSetZoomOption, SetZoomSuccessCallbackResult, CameraContextStopRecordOption, CameraContextStartRecordOption, StartRecordTimeoutCallbackResult, StopRecordSuccessCallbackResult, OnCameraFrameCallback, OnCameraFrameCallbackResult, CameraFrameListenerStartOption, StopOption, CameraConfig, FlashMode, DevicePosition, Resolution,
|
||||||
|
//FrameSize,
|
||||||
|
QualityType, OnCameraFrameListenerOption } from '../interface';
|
||||||
|
import Bitmap from "android.graphics.Bitmap";
|
||||||
|
import FileOutputStream from "java.io.FileOutputStream";
|
||||||
|
const FLASH_MODE_MAP = new Map<string, Int>([
|
||||||
|
['auto', ImageCapture.FLASH_MODE_AUTO],
|
||||||
|
['on', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['torch', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['off', ImageCapture.FLASH_MODE_OFF],
|
||||||
|
]);
|
||||||
|
const DEVICE_POSITION_MAP = new Map<string, Int>([
|
||||||
|
['back', CameraSelector.LENS_FACING_BACK],
|
||||||
|
['front', CameraSelector.LENS_FACING_FRONT],
|
||||||
|
]);
|
||||||
|
const RESOLUTION_MAP = new Map<string, Quality>([
|
||||||
|
['low', Quality.LOWEST],
|
||||||
|
['medium', Quality.HD],
|
||||||
|
['high', Quality.HIGHEST],
|
||||||
|
]);
|
||||||
|
const QUALITY_MAP = new Map<string, Int>([
|
||||||
|
["high", ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY],
|
||||||
|
["low", ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY],
|
||||||
|
]);
|
||||||
|
const MIRROR_MODE = new Map<boolean, Int>([
|
||||||
|
[true, MirrorMode.MIRROR_MODE_ON],
|
||||||
|
[false, MirrorMode.MIRROR_MODE_OFF],
|
||||||
|
]);
|
||||||
|
export class CameraView extends FrameLayout {
|
||||||
|
private previewView: PreviewView;
|
||||||
|
public cameraProvider: ProcessCameraProvider | null = null;
|
||||||
|
public cameraControl: CameraControl | null = null;
|
||||||
|
private imageCapture: ImageCapture | null = null;
|
||||||
|
private videoCapture: VideoCapture<Recorder> | null = null;
|
||||||
|
private recording: Recording | null = null;
|
||||||
|
private comp: UTSComponent<CameraView>;
|
||||||
|
private flashMode: FlashMode = 'auto';
|
||||||
|
private devicePosition: DevicePosition = 'back';
|
||||||
|
private resolution: Resolution = 'medium';
|
||||||
|
// private frameSize : FrameSize = 'medium'
|
||||||
|
private quality: QualityType = 'normal';
|
||||||
|
private videoFile: File | null = null;
|
||||||
|
maxZoom: Float = (1).toFloat();
|
||||||
|
minZoom: Float = (1).toFloat();
|
||||||
|
private initialized: boolean = false;
|
||||||
|
private isListening: boolean = false;
|
||||||
|
private videoSelfieMirror: boolean | null = null;
|
||||||
|
private cameraContextStopRecordOption: CameraContextStopRecordOption | null = null;
|
||||||
|
private onCameraFrameCallback: OnCameraFrameCallback | null = null;
|
||||||
|
private cameraExecutor: ExecutorService | null = null;
|
||||||
|
private onCameraFrameListenerOption: OnCameraFrameListenerOption | null = null;
|
||||||
|
constructor(context: Context, comp: UTSComponent<CameraView>, config: CameraConfig = {} as CameraConfig) {
|
||||||
|
super(context);
|
||||||
|
this.comp = comp;
|
||||||
|
this.flashMode = config.flash ?? 'auto';
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back';
|
||||||
|
this.resolution = config.resolution ?? 'medium';
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
this.previewView = new PreviewView(context);
|
||||||
|
let layoutParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
this.addView(this.previewView, layoutParam);
|
||||||
|
this.setupCamera();
|
||||||
|
}
|
||||||
|
setupCamera() {
|
||||||
|
// this.checkSelfPermission().then(() => {
|
||||||
|
let cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!);
|
||||||
|
cameraProviderFuture.addListener(() => {
|
||||||
|
try {
|
||||||
|
this.cameraProvider = cameraProviderFuture.get();
|
||||||
|
this.bindCamera();
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e));
|
||||||
|
this.comp.$emit('error', ret);
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!));
|
||||||
|
// }).catch((err) => {
|
||||||
|
// let ret : Map<string, any> = new Map();
|
||||||
|
// ret.set("errMsg", err ?? '权限不足')
|
||||||
|
// this.comp.$emit('error', ret)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
bindCamera() {
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? Quality.HD;
|
||||||
|
let captureMode = QUALITY_MAP.get(this.quality) ?? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
|
||||||
|
// 创建一个Preview对象,用于显示相机预览
|
||||||
|
let preview = new Preview.Builder()
|
||||||
|
.build();
|
||||||
|
preview.setSurfaceProvider(this.previewView.getSurfaceProvider());
|
||||||
|
// 创建一个ImageCapture对象,用于拍照
|
||||||
|
let imageCapture = new ImageCapture.Builder()
|
||||||
|
.setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!)
|
||||||
|
.setCaptureMode(captureMode)
|
||||||
|
.build();
|
||||||
|
// 选择要使用的相机(后置或前置)
|
||||||
|
let cameraSelector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!)
|
||||||
|
.build();
|
||||||
|
let recorder = Recorder.Builder()
|
||||||
|
.setQualitySelector(QualitySelector.from(targetResolution));
|
||||||
|
let mirrorMode = this.videoSelfieMirror == null ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MIRROR_MODE.get(this.videoSelfieMirror!);
|
||||||
|
let videoCapture = new VideoCapture.Builder(recorder.build())
|
||||||
|
.setMirrorMode(mirrorMode!)
|
||||||
|
.build();
|
||||||
|
this.videoCapture = videoCapture;
|
||||||
|
// 将参数应用到 VideoCapture
|
||||||
|
this.imageCapture = imageCapture;
|
||||||
|
this.cameraProvider!.unbindAll();
|
||||||
|
let imageAnalysis = ImageAnalysis.Builder()
|
||||||
|
// .setTargetResolution(new Size(1280, 720))
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build();
|
||||||
|
let viewPort = this.previewView.getViewPort()!;
|
||||||
|
let useCaseGroup = new UseCaseGroup.Builder()
|
||||||
|
.addUseCase(preview)
|
||||||
|
.addUseCase(imageAnalysis)
|
||||||
|
.addUseCase(imageCapture)
|
||||||
|
.addUseCase(videoCapture)
|
||||||
|
.setViewPort(viewPort)
|
||||||
|
.build();
|
||||||
|
this.cameraExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
let _this = this;
|
||||||
|
class Analyzer implements ImageAnalysis.Analyzer {
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
override analyze(image: ImageProxy) {
|
||||||
|
if (_this.isListening) {
|
||||||
|
let imageFormat = image.getFormat();
|
||||||
|
// 获取图像的宽度和高度
|
||||||
|
let width = image.width;
|
||||||
|
let height = image.height;
|
||||||
|
if (imageFormat == ImageFormat.YUV_420_888) {
|
||||||
|
_this.onCameraFrameCallback?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult);
|
||||||
|
_this.onCameraFrameListenerOption?.success?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.close();
|
||||||
|
}
|
||||||
|
override getDefaultTargetResolution(): Size {
|
||||||
|
// 返回默认的目标分辨率
|
||||||
|
return Size(640, 480);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageAnalysis.setAnalyzer(this.cameraExecutor!, new Analyzer() as ImageAnalysis.Analyzer);
|
||||||
|
let camera = this.cameraProvider!.bindToLifecycle(UTSAndroid.getUniActivity()! as LifecycleOwner, cameraSelector, useCaseGroup
|
||||||
|
// preview,
|
||||||
|
// imageCapture,
|
||||||
|
// videoCapture
|
||||||
|
);
|
||||||
|
this.cameraControl = camera.getCameraControl();
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true;
|
||||||
|
let zoomState = camera.getCameraInfo().getZoomState().getValue();
|
||||||
|
this.maxZoom = zoomState?.getMaxZoomRatio() ?? (1).toFloat();
|
||||||
|
this.minZoom = zoomState?.getMinZoomRatio() ?? (1).toFloat();
|
||||||
|
let maxZoom = this.maxZoom;
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", maxZoom);
|
||||||
|
this.comp.$emit('ready', ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unbindCamera() {
|
||||||
|
this.cameraProvider?.unbindAll();
|
||||||
|
this.cameraExecutor?.shutdown();
|
||||||
|
}
|
||||||
|
// checkSelfPermission() : Promise<boolean> {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE"]
|
||||||
|
// return new Promise((resoleve, _) => {
|
||||||
|
// resoleve(true);
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, list : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// resoleve(true);
|
||||||
|
// } else {
|
||||||
|
// reject(list);
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, grantedList : string[]) {
|
||||||
|
// //grantedList
|
||||||
|
// reject(grantedList);
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
setConfig(config: CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto';
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back';
|
||||||
|
this.resolution = config.resolution ?? 'medium';
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
}
|
||||||
|
setFlash(mode: FlashMode) {
|
||||||
|
this.flashMode = mode;
|
||||||
|
if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null)
|
||||||
|
return;
|
||||||
|
this.imageCapture!.setFlashMode(FLASH_MODE_MAP.get(mode)!);
|
||||||
|
}
|
||||||
|
switchCamera(position: DevicePosition) {
|
||||||
|
this.devicePosition = position;
|
||||||
|
if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null)
|
||||||
|
return;
|
||||||
|
this.unbindCamera();
|
||||||
|
this.bindCamera();
|
||||||
|
}
|
||||||
|
isResolutionSupported(context: Context, cameraSelector: CameraSelector, targetResolution: Size): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
takePhoto(option: TakePhotoOption) {
|
||||||
|
let quality = option.quality ?? "high";
|
||||||
|
this.quality = quality;
|
||||||
|
if (quality == 'low') {
|
||||||
|
this.unbindCamera();
|
||||||
|
this.bindCamera();
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
const tempFile = new File(privateDir);
|
||||||
|
if (!tempFile.exists()) {
|
||||||
|
tempFile.mkdirs();
|
||||||
|
}
|
||||||
|
// 设置文件名
|
||||||
|
const photoFile = new File(privateDir, Date.now() + '.png');
|
||||||
|
// 处理镜像
|
||||||
|
let metadata = new ImageCapture.Metadata();
|
||||||
|
metadata.setReversedHorizontal(option.selfieMirror ?? DEVICE_POSITION_MAP.get(this.devicePosition)! == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
const outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build();
|
||||||
|
class OnImageSavedCallback implements ImageCapture.OnImageSavedCallback {
|
||||||
|
override onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
option.success?.({
|
||||||
|
tempImagePath: photoFile.getAbsolutePath(),
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
override onError(err: ImageCaptureException) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: err.toString()
|
||||||
|
};
|
||||||
|
option.fail?.(result);
|
||||||
|
option.complete?.(result);
|
||||||
|
}
|
||||||
|
override onCaptureStarted() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.imageCapture!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), new OnImageSavedCallback() as ImageCapture.OnImageSavedCallback);
|
||||||
|
}
|
||||||
|
setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
if (this.cameraControl != null) {
|
||||||
|
try {
|
||||||
|
this.cameraControl!.setZoomRatio(option.zoom.toFloat());
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: e.toString()
|
||||||
|
};
|
||||||
|
option.complete?.(result);
|
||||||
|
option.fail?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: '相机不存在'
|
||||||
|
};
|
||||||
|
option.complete?.(result);
|
||||||
|
option.fail?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) {
|
||||||
|
this.videoSelfieMirror = option.selfieMirror;
|
||||||
|
this.unbindCamera();
|
||||||
|
this.bindCamera();
|
||||||
|
}
|
||||||
|
if (this.recording != null || this.videoCapture == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
// 设置文件名
|
||||||
|
this.videoFile = new File(privateDir, Date.now() + 'video.mp4');
|
||||||
|
let outputFileOptions = new FileOutputOptions.Builder(this.videoFile!).build();
|
||||||
|
this.recording = this.videoCapture!.output
|
||||||
|
.prepareRecording(UTSAndroid.getUniActivity()!, outputFileOptions)
|
||||||
|
.withAudioEnabled()
|
||||||
|
// .apply(()=>{
|
||||||
|
// })
|
||||||
|
.start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), (recordEvent: VideoRecordEvent) => {
|
||||||
|
if (recordEvent instanceof VideoRecordEvent.Start) {
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.recording?.stop();
|
||||||
|
}, option.timeout!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (recordEvent instanceof VideoRecordEvent.Finalize) {
|
||||||
|
this.recording?.close();
|
||||||
|
this.recording = null;
|
||||||
|
let tempVideoPath = this.videoFile?.getAbsolutePath()!;
|
||||||
|
//let tempThumbPath = extractThumbnail(tempVideoPath) ?? ''
|
||||||
|
this.cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult);
|
||||||
|
option.timeoutCallback?.({
|
||||||
|
tempVideoPath,
|
||||||
|
} as StartRecordTimeoutCallbackResult);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
if (this.recording == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.cameraContextStopRecordOption = option;
|
||||||
|
this.recording?.stop();
|
||||||
|
}
|
||||||
|
onCameraFrame(callback: OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback;
|
||||||
|
}
|
||||||
|
onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
this.onCameraFrameListenerOption = option;
|
||||||
|
}
|
||||||
|
cameraFrameOnStart(option?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true;
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option?: StopOption) {
|
||||||
|
this.isListening = false;
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// // 从视频文件中提取封面的函数
|
||||||
|
// function extractThumbnail(videoUri : string) : string | null {
|
||||||
|
// let retriever = new MediaMetadataRetriever()
|
||||||
|
// retriever.setDataSource(videoUri)
|
||||||
|
// // 提取视频的某一帧作为封面
|
||||||
|
// let thumbnail = retriever.frameAtTime
|
||||||
|
// // 保存封面到文件
|
||||||
|
// if (thumbnail != null) {
|
||||||
|
// let privateDir = UTSAndroid.getAppCachePath();
|
||||||
|
// const thumbnailFile = new File(privateDir, Date.now() + '_thumbnail.jpg');
|
||||||
|
// let outputStream = FileOutputStream(thumbnailFile)
|
||||||
|
// thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||||
|
// outputStream.close()
|
||||||
|
// return thumbnailFile.getAbsolutePath()
|
||||||
|
// }
|
||||||
|
// retriever.release()
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
Vendored
+214
@@ -0,0 +1,214 @@
|
|||||||
|
import { CameraView } from './camera';
|
||||||
|
import { TakePhotoOption, GeneralCallbackResult, CameraContextSetZoomOption, CameraContextStartRecordOption, CameraContextStopRecordOption,
|
||||||
|
// CameraFrameListener,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
// OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption, StopOption, TakePhoto, SetZoom, StartRecord, StopRecord, StartCameraFrame, StopCameraFrame, OnCameraFrameListenerOption, SwitchOption, SwitchCamera } from '../interface';
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
class CameraFrameListener {
|
||||||
|
context!: CameraContext;
|
||||||
|
constructor(context: CameraContext) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
start(): void;
|
||||||
|
start(option?: CameraFrameListenerStartOption) {
|
||||||
|
this.context.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop(): void;
|
||||||
|
stop(option?: StopOption) {
|
||||||
|
this.context.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener: CameraFrameListener | null = null;
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
getCamera(): Promise<CameraView> {
|
||||||
|
let cameraPreview = CameraManager.getInstance().getCameraView();
|
||||||
|
if (cameraPreview != null) {
|
||||||
|
return Promise.resolve<CameraView>(cameraPreview);
|
||||||
|
}
|
||||||
|
let resolveFunc: ((res: CameraView) => void) | null = null;
|
||||||
|
let rejectFunc: ((res: string) => void) | null = null;
|
||||||
|
let maxAttempts: number = 5;
|
||||||
|
let currentAttempt: number = 0;
|
||||||
|
function attemptGetCamera() {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera != null) {
|
||||||
|
resolveFunc?.(camera);
|
||||||
|
}
|
||||||
|
else if (currentAttempt < maxAttempts) {
|
||||||
|
currentAttempt++;
|
||||||
|
setTimeout(() => {
|
||||||
|
attemptGetCamera();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rejectFunc?.('Camera not found after multiple attempts');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise<CameraView>((resolve, reject) => {
|
||||||
|
resolveFunc = resolve;
|
||||||
|
rejectFunc = reject;
|
||||||
|
attemptGetCamera();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
takePhoto(option: TakePhotoOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.takePhoto(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.setZoom(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.startRecord(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.stopRecord(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onCameraFrame(): CameraFrameListener;
|
||||||
|
onCameraFrame(callback: OnCameraFrameCallback | null = null): CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this);
|
||||||
|
}
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.onCameraFrame(callback);
|
||||||
|
});
|
||||||
|
return this.mCameraFrameListener!;
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.onCameraFrameListener(option);
|
||||||
|
}).catch(() => {
|
||||||
|
console.log("未找到相机");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cameraFrameOnStart(option: CameraFrameListenerStartOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option: StopOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
switchCamera(option: SwitchOption) {
|
||||||
|
this.getCamera().then((camera: CameraView) => {
|
||||||
|
camera.switchCamera(option.position);
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let context: CameraContext = new CameraContext();
|
||||||
|
export function createCameraContext(): CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export const takePhoto: TakePhoto = function (options: TakePhotoOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.takePhoto(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
export const setZoom: SetZoom = function (options: CameraContextSetZoomOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.setZoom(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
export const startRecord: StartRecord = function (options: CameraContextStartRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.startRecord(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
export const stopRecord: StopRecord = function (options: CameraContextStopRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.stopRecord(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options: OnCameraFrameListenerOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.onCameraFrameListener(options);
|
||||||
|
}, null);
|
||||||
|
}
|
||||||
|
export const startCameraFrame: StartCameraFrame = function (options: CameraFrameListenerStartOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStart(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
export const stopCameraFrame: StopCameraFrame = function (options: StopOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStop(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
|
export const switchCamera: SwitchCamera = function (options: SwitchOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.switchCamera(options);
|
||||||
|
}, null);
|
||||||
|
};
|
||||||
Vendored
+140
@@ -0,0 +1,140 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
import { CameraView } from './camera';
|
||||||
|
import { CameraConfig } from '../interface';
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件内部变量声明
|
||||||
|
*/
|
||||||
|
data(): UTSJSONObject {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue: string, _: string) {
|
||||||
|
this.$el?.switchCamera(newValue);
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue: string, _: string) {
|
||||||
|
this.$el?.setFlash(newValue);
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [必须实现] 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* (Android需要明确知道View类型,需特殊校验)
|
||||||
|
*/
|
||||||
|
NVLoad(): CameraView {
|
||||||
|
let previewView = new CameraView(this.$androidContext!, this);
|
||||||
|
previewView.setTag("9527");
|
||||||
|
CameraManager.getInstance().setCameraView(previewView);
|
||||||
|
return previewView;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已创建
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View布局完成
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
this.$el?.setConfig({
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig);
|
||||||
|
this.$el?.switchCamera(this.position);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View将释放
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
this.$emit('stop', ret);
|
||||||
|
this.$el?.unbindCamera();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件销毁
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
export type GeneralCallbackResult = {
|
||||||
|
/** 错误信息 */
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
export type TakePhotoSuccessCallbackResult = {
|
||||||
|
/** 照片文件的临时路径 (本地路径),安卓是jpg图片格式,ios是png */
|
||||||
|
tempImagePath: string;
|
||||||
|
quality: string;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type TakePhotoFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type TakePhotoSuccessCallback = (result: TakePhotoSuccessCallbackResult) => void;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type TakePhotoCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type TakePhotoOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: TakePhotoCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: TakePhotoFailCallback;
|
||||||
|
/** 成像质量
|
||||||
|
*
|
||||||
|
* 可选值:
|
||||||
|
* - 'high': 高质量;
|
||||||
|
* - 'normal': 普通质量;
|
||||||
|
* - 'low': 低质量;
|
||||||
|
* - 'original': 原图; */
|
||||||
|
quality?: 'high' | 'normal' | 'low' | 'original';
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror?: boolean;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: TakePhotoSuccessCallback;
|
||||||
|
};
|
||||||
|
export type SetZoomSuccessCallbackResult = {
|
||||||
|
/** 实际设置的缩放级别。由于系统限制,某些机型可能无法设置成指定值,会改用最接近的可设值。 */
|
||||||
|
zoom: number;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SetZoomCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SetZoomFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextSetZoomSuccessCallback = (result: SetZoomSuccessCallbackResult) => void;
|
||||||
|
export type CameraContextSetZoomOption = {
|
||||||
|
/** 缩放级别,范围[1, maxZoom]。zoom 可取小数,精确到小数后一位。maxZoom 可在 bindinitdone 返回值中获取。 */
|
||||||
|
zoom: number;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: SetZoomCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: SetZoomFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextSetZoomSuccessCallback;
|
||||||
|
};
|
||||||
|
export type StartRecordTimeoutCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StartRecordCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StartRecordFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
export type StartRecordTimeoutCallback = (result: StartRecordTimeoutCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStartRecordSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type CameraContextStartRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StartRecordCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StartRecordFailCallback;
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror?: boolean;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextStartRecordSuccessCallback;
|
||||||
|
/**
|
||||||
|
* 录制时长上限,单位为秒,最长不能超过 5 分钟 */
|
||||||
|
timeout?: number;
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
timeoutCallback?: StartRecordTimeoutCallback;
|
||||||
|
};
|
||||||
|
export type StopRecordSuccessCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath: string;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopRecordCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopRecordFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStopRecordSuccessCallback = (result: StopRecordSuccessCallbackResult) => void;
|
||||||
|
export type CameraContextStopRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StopRecordCompleteCallback;
|
||||||
|
/** 启动视频压缩,压缩效果同`chooseVideo` */
|
||||||
|
compressed?: boolean;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StopRecordFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextStopRecordSuccessCallback;
|
||||||
|
};
|
||||||
|
/** 回调函数 */
|
||||||
|
export type OnCameraFrameCallback = (result: OnCameraFrameCallbackResult) => void;
|
||||||
|
export type OnCameraFrameCallbackResult = {
|
||||||
|
/** 图像像素点数据,一维数组,每四项表示一个像素点的 rgba */
|
||||||
|
data: any;
|
||||||
|
/** 图像数据矩形的高度 */
|
||||||
|
height: number;
|
||||||
|
/** 图像数据矩形的宽度 */
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StartSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
type StartFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
type StartCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type CameraFrameListenerStartOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StartCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StartFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: StartSuccessCallback;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StopSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type StopOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StopCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StopFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: StopSuccessCallback;
|
||||||
|
};
|
||||||
|
export type FlashMode = 'auto' | 'on' | 'off' | 'torch';
|
||||||
|
export type DevicePosition = 'back' | 'front';
|
||||||
|
export type Resolution = 'low' | 'medium' | 'high';
|
||||||
|
export type FrameSize = 'medium' | 'small' | 'large';
|
||||||
|
export type QualityType = 'high' | 'normal' | 'low' | 'original';
|
||||||
|
export type CameraConfig = {
|
||||||
|
flash?: FlashMode;
|
||||||
|
devicePosition?: DevicePosition;
|
||||||
|
resolution?: Resolution;
|
||||||
|
frameSize?: FrameSize;
|
||||||
|
};
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SwitchCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SwitchFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type SwitchSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type SwitchOption = {
|
||||||
|
position: DevicePosition;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: SwitchCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: SwitchFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: SwitchSuccessCallback;
|
||||||
|
};
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export type OnCameraFrameListenerOption = {
|
||||||
|
success?: OnCameraFrameCallback;
|
||||||
|
};
|
||||||
|
export type TakePhoto = (options: TakePhotoOption) => void;
|
||||||
|
export type SetZoom = (options: CameraContextSetZoomOption) => void;
|
||||||
|
export type StartRecord = (options: CameraContextStartRecordOption) => void;
|
||||||
|
export type StopRecord = (options: CameraContextStopRecordOption) => void;
|
||||||
|
export type OnCameraFrameListener = (options: OnCameraFrameListenerOption) => void;
|
||||||
|
export type StartCameraFrame = (options: CameraFrameListenerStartOption) => void;
|
||||||
|
export type StopCameraFrame = (options: StopOption) => void;
|
||||||
|
export type SwitchCamera = (options: SwitchOption) => void;
|
||||||
Vendored
+20
@@ -0,0 +1,20 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView: CameraView | null = null;
|
||||||
|
private static instance: CameraManager | null = null;
|
||||||
|
public static getInstance(): CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
public setCameraView(cameraView?: CameraView): void {
|
||||||
|
this.cameraView = cameraView!;
|
||||||
|
}
|
||||||
|
public getCameraView(): CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
public dispose(): void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+421
@@ -0,0 +1,421 @@
|
|||||||
|
import { UIImage, UIView } from 'UIKit';
|
||||||
|
import { AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoPreviewLayer, AVMediaType, AVLayerVideoGravity, AVCaptureVideoDataOutput, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureOutput, AVCaptureConnection, AVCapturePhotoOutput, AVCapturePhotoSettings, AVCapturePhotoCaptureDelegate, AVCaptureMovieFileOutput, AVCaptureFileOutputRecordingDelegate, AVCaptureFileOutput } from "AVFoundation";
|
||||||
|
import { CMSampleBuffer, CMSampleBufferGetImageBuffer } from 'CoreMedia';
|
||||||
|
import { DispatchQueue } from 'Dispatch';
|
||||||
|
import { CVPixelBufferGetWidth, CVPixelBufferGetHeight } from "CoreVideo";
|
||||||
|
import { NSError, URL } from "Foundation";
|
||||||
|
import { TakePhotoOption, GeneralCallbackResult, TakePhotoSuccessCallbackResult, CameraContextSetZoomOption, SetZoomSuccessCallbackResult, CameraContextStopRecordOption, CameraContextStartRecordOption, StartRecordTimeoutCallbackResult, StopRecordSuccessCallbackResult, OnCameraFrameCallback, OnCameraFrameCallbackResult, OnCameraFrameListenerOption, CameraFrameListenerStartOption, StopOption, CameraConfig, FlashMode, DevicePosition, Resolution } from '../interface';
|
||||||
|
import { AVCapturePhoto } from 'AVFoundation';
|
||||||
|
const RESOLUTION_MAP = new Map<string, AVCaptureSession.Preset>([
|
||||||
|
['low', AVCaptureSession.Preset.low],
|
||||||
|
['medium', AVCaptureSession.Preset.medium],
|
||||||
|
['high', AVCaptureSession.Preset.high],
|
||||||
|
]);
|
||||||
|
const FLASH_MODE_MAP = new Map<string, AVCaptureDevice.TorchMode>([
|
||||||
|
['auto', AVCaptureDevice.TorchMode.auto],
|
||||||
|
['on', AVCaptureDevice.TorchMode.on],
|
||||||
|
['torch', AVCaptureDevice.TorchMode.on],
|
||||||
|
['off', AVCaptureDevice.TorchMode.off],
|
||||||
|
]);
|
||||||
|
let fileOutputRecordingDelegate: MyAVCaptureFileOutputRecordingDelegate | null = null;
|
||||||
|
let capturePhotoCaptureDelegate: MyAVCapturePhotoCaptureDelegate | null = null;
|
||||||
|
let onCameraFrameListenerOption: OnCameraFrameListenerOption | null = null;
|
||||||
|
let cameraContextStopRecordOption: CameraContextStopRecordOption | null = null;
|
||||||
|
class MyAVCaptureFileOutputRecordingDelegate implements AVCaptureFileOutputRecordingDelegate {
|
||||||
|
private option: CameraContextStartRecordOption;
|
||||||
|
constructor(option: CameraContextStartRecordOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
fileOutput(output: AVCaptureFileOutput,
|
||||||
|
@argumentLabel("didStartRecordingTo")
|
||||||
|
fileURL: URL,
|
||||||
|
@argumentLabel("from")
|
||||||
|
connections: [
|
||||||
|
AVCaptureConnection
|
||||||
|
]): void {
|
||||||
|
this.option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
fileOutput(output: AVCaptureFileOutput,
|
||||||
|
@argumentLabel("didFinishRecordingTo")
|
||||||
|
outputFileURL: URL,
|
||||||
|
@argumentLabel("from")
|
||||||
|
connections: [
|
||||||
|
AVCaptureConnection
|
||||||
|
],
|
||||||
|
@argumentLabel("error")
|
||||||
|
error?: NSError): void {
|
||||||
|
if (error == null) {
|
||||||
|
cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath: outputFileURL.path + "",
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult);
|
||||||
|
this.option.timeoutCallback?.({
|
||||||
|
tempVideoPath: outputFileURL.path + ""
|
||||||
|
} as StartRecordTimeoutCallbackResult);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cameraContextStopRecordOption?.fail?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
cameraContextStopRecordOption?.complete?.({
|
||||||
|
errMsg: '录制失败'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class MyAVCapturePhotoCaptureDelegate implements AVCapturePhotoCaptureDelegate {
|
||||||
|
private option: TakePhotoOption;
|
||||||
|
constructor(option: TakePhotoOption) {
|
||||||
|
this.option = option;
|
||||||
|
}
|
||||||
|
captureOutput(output: AVCapturePhotoOutput,
|
||||||
|
@argumentLabel("didFinishProcessingPhoto")
|
||||||
|
photo: AVCapturePhoto,
|
||||||
|
@argumentLabel("error")
|
||||||
|
error: Error): void {
|
||||||
|
let imageData = photo.fileDataRepresentation();
|
||||||
|
if (imageData == null) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
};
|
||||||
|
this.option.fail?.(result);
|
||||||
|
this.option.complete?.(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".jpg";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
let quality = this.option.quality ?? "original";
|
||||||
|
if (quality == 'original') {
|
||||||
|
try {
|
||||||
|
UTSiOS.try(imageData!.write(to = url, options = NSData.WritingOptions.atomic));
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult);
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
};
|
||||||
|
this.option.fail?.(result);
|
||||||
|
this.option.complete?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let image = UIImage(data = imageData!);
|
||||||
|
if (image != null) {
|
||||||
|
let _quality: number = 0.5;
|
||||||
|
if (quality == 'high') {
|
||||||
|
_quality = 1.0;
|
||||||
|
}
|
||||||
|
else if (quality == 'normal') {
|
||||||
|
_quality = 0.8;
|
||||||
|
}
|
||||||
|
else if (quality == 'low') {
|
||||||
|
_quality = 0.5;
|
||||||
|
}
|
||||||
|
let data = image!.jpegData(compressionQuality = _quality.toDouble())!;
|
||||||
|
try {
|
||||||
|
UTSiOS.try(data.write(to = url, options = NSData.WritingOptions.atomic));
|
||||||
|
this.option.success?.({
|
||||||
|
tempImagePath: path,
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult);
|
||||||
|
this.option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
};
|
||||||
|
this.option.fail?.(result);
|
||||||
|
this.option.complete?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: "拍照失败"
|
||||||
|
};
|
||||||
|
this.option.fail?.(result);
|
||||||
|
this.option.complete?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class CameraView implements AVCaptureVideoDataOutputSampleBufferDelegate {
|
||||||
|
private captureSession?: AVCaptureSession;
|
||||||
|
private videoDeviceInput?: AVCaptureDeviceInput;
|
||||||
|
private photoDeviceOutput?: AVCapturePhotoOutput;
|
||||||
|
private movieDeviceOutput?: AVCaptureMovieFileOutput;
|
||||||
|
private previewLayer?: AVCaptureVideoPreviewLayer;
|
||||||
|
private previewView?: UIView;
|
||||||
|
private comp?: UTSComponent<UIView>;
|
||||||
|
private flashMode: FlashMode = 'auto';
|
||||||
|
private devicePosition: DevicePosition = 'back';
|
||||||
|
private resolution: Resolution = 'medium';
|
||||||
|
private isListening: boolean = false;
|
||||||
|
private onCameraFrameCallback: OnCameraFrameCallback | null = null;
|
||||||
|
constructor(previewView: UIView, comp: UTSComponent<UIView>, config: CameraConfig = {} as CameraConfig) {
|
||||||
|
super.init();
|
||||||
|
this.previewView = previewView;
|
||||||
|
this.comp = comp;
|
||||||
|
this.flashMode = config.flash ?? 'auto';
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back';
|
||||||
|
this.resolution = config.resolution ?? 'medium';
|
||||||
|
this.setupCamera();
|
||||||
|
}
|
||||||
|
setupCamera() {
|
||||||
|
this.captureSession = new AVCaptureSession();
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? AVCaptureSession.Preset.high;
|
||||||
|
this.captureSession!.sessionPreset = targetResolution;
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
try {
|
||||||
|
//相机
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device));
|
||||||
|
if (this.captureSession!.canAddInput(deviceInput)) {
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
}
|
||||||
|
//实时流输出
|
||||||
|
let output = new AVCaptureVideoDataOutput();
|
||||||
|
output.setSampleBufferDelegate(this, queue = DispatchQueue.main);
|
||||||
|
if (this.captureSession!.canAddOutput(output)) {
|
||||||
|
this.captureSession!.addOutput(output);
|
||||||
|
}
|
||||||
|
let photoOutput = new AVCapturePhotoOutput();
|
||||||
|
// let photoSettings = new AVCapturePhotoSettings(format = [AVVideoCodecKey = AVVideoCodecType.jpeg]);
|
||||||
|
// photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler = null);
|
||||||
|
if (this.captureSession!.canAddOutput(photoOutput)) {
|
||||||
|
this.captureSession!.addOutput(photoOutput);
|
||||||
|
}
|
||||||
|
let movieOutput = new AVCaptureMovieFileOutput();
|
||||||
|
if (this.captureSession!.canAddOutput(movieOutput)) {
|
||||||
|
this.captureSession!.addOutput(movieOutput);
|
||||||
|
}
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.photoDeviceOutput = photoOutput;
|
||||||
|
this.movieDeviceOutput = movieOutput;
|
||||||
|
this.previewLayer = new AVCaptureVideoPreviewLayer(session = this.captureSession!);
|
||||||
|
this.previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill;
|
||||||
|
this.previewLayer!.frame = this.previewView!.layer.bounds;
|
||||||
|
this.previewView!.layer.addSublayer(this.previewLayer!);
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", device.maxAvailableVideoZoomFactor);
|
||||||
|
let detail: Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret);
|
||||||
|
this.comp?.$emit("ready", detail);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e));
|
||||||
|
let detail: Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret);
|
||||||
|
this.comp?.$emit("error", detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCameraDevice(): AVCaptureDevice {
|
||||||
|
let cameras = AVCaptureDevice.devices(for = AVMediaType.video);
|
||||||
|
let frontCamera: AVCaptureDevice | null = null;
|
||||||
|
let backCamera: AVCaptureDevice | null = null;
|
||||||
|
let index = 0;
|
||||||
|
while (index < cameras.length) {
|
||||||
|
if (index == cameras.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let camera = cameras[index.toInt()] as AVCaptureDevice;
|
||||||
|
if (camera.position == AVCaptureDevice.Position.back) {
|
||||||
|
backCamera = camera;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frontCamera = camera;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
if (this.devicePosition == 'back') {
|
||||||
|
return backCamera!;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return frontCamera!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captureOutput(output: AVCaptureOutput,
|
||||||
|
@argumentLabel("didOutput")
|
||||||
|
sampleBuffer: CMSampleBuffer,
|
||||||
|
@argumentLabel("from")
|
||||||
|
connection: AVCaptureConnection): void {
|
||||||
|
if (this.isListening) {
|
||||||
|
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||||
|
let width = CVPixelBufferGetWidth(imageBuffer!);
|
||||||
|
let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
const res: OnCameraFrameCallbackResult = {
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
};
|
||||||
|
this.onCameraFrameCallback?.(res);
|
||||||
|
onCameraFrameListenerOption?.success?.({
|
||||||
|
width: width as Int,
|
||||||
|
height: height as Int,
|
||||||
|
data: imageBuffer!
|
||||||
|
} as OnCameraFrameCallbackResult);
|
||||||
|
// this.onCameraFrameListenerOption?.success({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: imageBuffer!
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
// if (imageBuffer != null) {
|
||||||
|
// let ciImage = new CIImage(cvPixelBuffer = imageBuffer!)
|
||||||
|
// let temporaryContext = new CIContext(options = null)
|
||||||
|
// let width = CVPixelBufferGetWidth(imageBuffer!)
|
||||||
|
// let height = CVPixelBufferGetHeight(imageBuffer!);
|
||||||
|
// let videoImage = temporaryContext.createCGImage(ciImage, from = new CGRect(x = 0, y = 0, width = width, height = height))
|
||||||
|
// let image = new UIImage(cgImage = videoImage!)
|
||||||
|
// this.onCameraFrameCallback?.({
|
||||||
|
// width: width as Int,
|
||||||
|
// height: height as Int,
|
||||||
|
// data: image
|
||||||
|
// } as OnCameraFrameCallbackResult)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bindCamera() {
|
||||||
|
this.captureSession!.startRunning();
|
||||||
|
}
|
||||||
|
unbindCamera() {
|
||||||
|
this.captureSession!.stopRunning();
|
||||||
|
}
|
||||||
|
setConfig(config: CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto';
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back';
|
||||||
|
this.resolution = config.resolution ?? 'medium';
|
||||||
|
}
|
||||||
|
setFlash(mode: FlashMode) {
|
||||||
|
this.flashMode = mode;
|
||||||
|
let targetFlash = FLASH_MODE_MAP.get(this.flashMode) ?? AVCaptureDevice.TorchMode.auto;
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
if (device.hasTorch && device.isTorchAvailable) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.torchMode = targetFlash;
|
||||||
|
device.unlockForConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switchCamera(position: DevicePosition) {
|
||||||
|
if (!this.captureSession!.isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.devicePosition = position;
|
||||||
|
try {
|
||||||
|
this.captureSession!.beginConfiguration();
|
||||||
|
this.captureSession!.removeInput(this.videoDeviceInput!);
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device));
|
||||||
|
this.captureSession!.addInput(deviceInput);
|
||||||
|
this.videoDeviceInput = deviceInput;
|
||||||
|
this.captureSession!.commitConfiguration();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
takePhoto(option: TakePhotoOption) {
|
||||||
|
capturePhotoCaptureDelegate = new MyAVCapturePhotoCaptureDelegate(option);
|
||||||
|
let settings = new AVCapturePhotoSettings();
|
||||||
|
this.photoDeviceOutput?.capturePhoto(with = settings, delegate = capturePhotoCaptureDelegate!);
|
||||||
|
}
|
||||||
|
setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
try {
|
||||||
|
let device = this.getCameraDevice();
|
||||||
|
// if (device.isRampingVideoZoom) {
|
||||||
|
UTSiOS.try(device.lockForConfiguration());
|
||||||
|
device.videoZoomFactor = option.zoom.toDouble();
|
||||||
|
device.unlockForConfiguration();
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
// } else {
|
||||||
|
// const result : GeneralCallbackResult = {
|
||||||
|
// errMsg: '相机不支持缩放'
|
||||||
|
// }
|
||||||
|
// option.complete?.(result)
|
||||||
|
// option.fail?.(result)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
const result: GeneralCallbackResult = {
|
||||||
|
errMsg: JSON.stringify(e)
|
||||||
|
};
|
||||||
|
option.complete?.(result);
|
||||||
|
option.fail?.(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileOutputRecordingDelegate = new MyAVCaptureFileOutputRecordingDelegate(option);
|
||||||
|
let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".mp4";
|
||||||
|
let url = new URL(fileURLWithPath = path);
|
||||||
|
this.movieDeviceOutput?.startRecording(to = url, recordingDelegate = fileOutputRecordingDelegate!);
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}, (option.timeout!).toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
let isRecording = this.movieDeviceOutput?.isRecording ?? false;
|
||||||
|
if (!isRecording) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cameraContextStopRecordOption = option;
|
||||||
|
this.movieDeviceOutput?.stopRecording();
|
||||||
|
}
|
||||||
|
onCameraFrame(callback: OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback;
|
||||||
|
}
|
||||||
|
onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
onCameraFrameListenerOption = option;
|
||||||
|
}
|
||||||
|
cameraFrameOnStart(option?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true;
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option?: StopOption) {
|
||||||
|
this.isListening = false;
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
import { TakePhotoOption, GeneralCallbackResult, CameraContextSetZoomOption, CameraContextStartRecordOption, CameraContextStopRecordOption, OnCameraFrameCallback, CameraFrameListenerStartOption, StopOption, TakePhoto, SetZoom, StartRecord, StopRecord, StartCameraFrame, StopCameraFrame, OnCameraFrameListenerOption, SwitchCamera, SwitchOption } from '../interface';
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
class CameraFrameListener {
|
||||||
|
context: CameraContext;
|
||||||
|
constructor(context: CameraContext) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
start(): void;
|
||||||
|
start(option?: CameraFrameListenerStartOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStart(option);
|
||||||
|
}
|
||||||
|
stop(): void;
|
||||||
|
stop(option?: StopOption) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.cameraFrameOnStop(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener: CameraFrameListener | null = null;
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
takePhoto(option: TakePhotoOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.takePhoto(option);
|
||||||
|
}
|
||||||
|
setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.setZoom(option);
|
||||||
|
}
|
||||||
|
startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.startRecord(option);
|
||||||
|
}
|
||||||
|
stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.stopRecord(option);
|
||||||
|
}
|
||||||
|
onCameraFrame(): CameraFrameListener;
|
||||||
|
onCameraFrame(callback: OnCameraFrameCallback | null = null): CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this);
|
||||||
|
}
|
||||||
|
CameraManager.getInstance().getCameraView()?.onCameraFrame(callback);
|
||||||
|
return this.mCameraFrameListener!;
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.onCameraFrameListener(option);
|
||||||
|
}
|
||||||
|
cameraFrameOnStart(option: CameraFrameListenerStartOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStart(option);
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option: StopOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.cameraFrameOnStop(option);
|
||||||
|
}
|
||||||
|
switchCamera(option: SwitchOption) {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView();
|
||||||
|
if (camera == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
camera?.switchCamera(option.position);
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let context: CameraContext = new CameraContext();
|
||||||
|
export function createCameraContext(): CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export const takePhoto: TakePhoto = function (options: TakePhotoOption) {
|
||||||
|
context.takePhoto(options);
|
||||||
|
};
|
||||||
|
export const setZoom: SetZoom = function (options: CameraContextSetZoomOption) {
|
||||||
|
context.setZoom(options);
|
||||||
|
};
|
||||||
|
export const startRecord: StartRecord = function (options: CameraContextStartRecordOption) {
|
||||||
|
context.startRecord(options);
|
||||||
|
};
|
||||||
|
export const stopRecord: StopRecord = function (options: CameraContextStopRecordOption) {
|
||||||
|
context.stopRecord(options);
|
||||||
|
};
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options: OnCameraFrameListenerOption) {
|
||||||
|
context.onCameraFrameListener(options);
|
||||||
|
}
|
||||||
|
export const startCameraFrame: StartCameraFrame = function (options: CameraFrameListenerStartOption) {
|
||||||
|
context.cameraFrameOnStart(options);
|
||||||
|
};
|
||||||
|
export const stopCameraFrame: StopCameraFrame = function (options: StopOption) {
|
||||||
|
context.cameraFrameOnStop(options);
|
||||||
|
};
|
||||||
|
export const switchCamera: SwitchCamera = function (options: SwitchOption) {
|
||||||
|
context.switchCamera(options);
|
||||||
|
};
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
import { UIView } from "UIKit";
|
||||||
|
import { CameraView } from "./camera";
|
||||||
|
import { CameraConfig } from '../interface';
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
data(): UTSJSONObject {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue: String, _oldValue: String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.switchCamera(newValue);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue: String, _oldValue: String) {
|
||||||
|
CameraManager.getInstance().getCameraView()?.setFlash(newValue);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
*/
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {},
|
||||||
|
/**
|
||||||
|
* 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* [必须实现]
|
||||||
|
*/
|
||||||
|
NVLoad(): UIView {
|
||||||
|
let previewView = new UIView();
|
||||||
|
previewView.tag = 9527;
|
||||||
|
return previewView;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View已创建
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View布局完成
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
let previewView = this.$el as UIView;
|
||||||
|
let cameraView = new CameraView(previewView, this, {
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
devicePosition: this.position,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig);
|
||||||
|
CameraManager.getInstance().setCameraView(cameraView);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View将释放
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret: Map<string, any> = new Map();
|
||||||
|
let detail: Map<string, any> = new Map();
|
||||||
|
detail.set("detail", ret);
|
||||||
|
this.$emit("stop", detail);
|
||||||
|
CameraManager.getInstance().getCameraView()?.unbindCamera();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件销毁
|
||||||
|
* [可选实现]
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
+182
@@ -0,0 +1,182 @@
|
|||||||
|
export type GeneralCallbackResult = {
|
||||||
|
/** 错误信息 */
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
export type TakePhotoSuccessCallbackResult = {
|
||||||
|
/** 照片文件的临时路径 (本地路径),安卓是jpg图片格式,ios是png */
|
||||||
|
tempImagePath: string;
|
||||||
|
quality: string;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type TakePhotoFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type TakePhotoSuccessCallback = (result: TakePhotoSuccessCallbackResult) => void;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type TakePhotoCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type TakePhotoOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: TakePhotoCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: TakePhotoFailCallback;
|
||||||
|
/** 成像质量
|
||||||
|
*
|
||||||
|
* 可选值:
|
||||||
|
* - 'high': 高质量;
|
||||||
|
* - 'normal': 普通质量;
|
||||||
|
* - 'low': 低质量;
|
||||||
|
* - 'original': 原图; */
|
||||||
|
quality?: 'high' | 'normal' | 'low' | 'original';
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror?: boolean;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: TakePhotoSuccessCallback;
|
||||||
|
};
|
||||||
|
export type SetZoomSuccessCallbackResult = {
|
||||||
|
/** 实际设置的缩放级别。由于系统限制,某些机型可能无法设置成指定值,会改用最接近的可设值。 */
|
||||||
|
zoom: number;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SetZoomCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SetZoomFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextSetZoomSuccessCallback = (result: SetZoomSuccessCallbackResult) => void;
|
||||||
|
export type CameraContextSetZoomOption = {
|
||||||
|
/** 缩放级别,范围[1, maxZoom]。zoom 可取小数,精确到小数后一位。maxZoom 可在 bindinitdone 返回值中获取。 */
|
||||||
|
zoom: number;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: SetZoomCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: SetZoomFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextSetZoomSuccessCallback;
|
||||||
|
};
|
||||||
|
export type StartRecordTimeoutCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StartRecordCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StartRecordFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
export type StartRecordTimeoutCallback = (result: StartRecordTimeoutCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStartRecordSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type CameraContextStartRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StartRecordCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StartRecordFailCallback;
|
||||||
|
/**
|
||||||
|
* 是否开启镜像 */
|
||||||
|
selfieMirror?: boolean;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextStartRecordSuccessCallback;
|
||||||
|
/**
|
||||||
|
* 录制时长上限,单位为秒,最长不能超过 5 分钟 */
|
||||||
|
timeout?: number;
|
||||||
|
/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */
|
||||||
|
timeoutCallback?: StartRecordTimeoutCallback;
|
||||||
|
};
|
||||||
|
export type StopRecordSuccessCallbackResult = {
|
||||||
|
/** 视频的文件的临时路径 (本地路径) */
|
||||||
|
tempVideoPath: string;
|
||||||
|
errMsg: string;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopRecordCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopRecordFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type CameraContextStopRecordSuccessCallback = (result: StopRecordSuccessCallbackResult) => void;
|
||||||
|
export type CameraContextStopRecordOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StopRecordCompleteCallback;
|
||||||
|
/** 启动视频压缩,压缩效果同`chooseVideo` */
|
||||||
|
compressed?: boolean;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StopRecordFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: CameraContextStopRecordSuccessCallback;
|
||||||
|
};
|
||||||
|
/** 回调函数 */
|
||||||
|
export type OnCameraFrameCallback = (result: OnCameraFrameCallbackResult) => void;
|
||||||
|
export type OnCameraFrameCallbackResult = {
|
||||||
|
/** 图像像素点数据,一维数组,每四项表示一个像素点的 rgba */
|
||||||
|
data: any;
|
||||||
|
/** 图像数据矩形的高度 */
|
||||||
|
height: number;
|
||||||
|
/** 图像数据矩形的宽度 */
|
||||||
|
width: number;
|
||||||
|
};
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StartSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
type StartFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
type StartCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type CameraFrameListenerStartOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StartCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StartFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: StartSuccessCallback;
|
||||||
|
};
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type StopCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type StopFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type StopSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type StopOption = {
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: StopCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: StopFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: StopSuccessCallback;
|
||||||
|
};
|
||||||
|
export type FlashMode = 'auto' | 'on' | 'off' | 'torch';
|
||||||
|
export type DevicePosition = 'back' | 'front';
|
||||||
|
export type Resolution = 'low' | 'medium' | 'high';
|
||||||
|
export type FrameSize = 'medium' | 'small' | 'large';
|
||||||
|
export type QualityType = 'high' | 'normal' | 'low' | 'original';
|
||||||
|
export type CameraConfig = {
|
||||||
|
flash?: FlashMode;
|
||||||
|
devicePosition?: DevicePosition;
|
||||||
|
resolution?: Resolution;
|
||||||
|
frameSize?: FrameSize;
|
||||||
|
};
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
export type SwitchCompleteCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
export type SwitchFailCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
export type SwitchSuccessCallback = (res: GeneralCallbackResult) => void;
|
||||||
|
export type SwitchOption = {
|
||||||
|
position: DevicePosition;
|
||||||
|
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||||
|
complete?: SwitchCompleteCallback;
|
||||||
|
/** 接口调用失败的回调函数 */
|
||||||
|
fail?: SwitchFailCallback;
|
||||||
|
/** 接口调用成功的回调函数 */
|
||||||
|
success?: SwitchSuccessCallback;
|
||||||
|
};
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export type OnCameraFrameListenerOption = {
|
||||||
|
success?: OnCameraFrameCallback;
|
||||||
|
};
|
||||||
|
export type TakePhoto = (options: TakePhotoOption) => void;
|
||||||
|
export type SetZoom = (options: CameraContextSetZoomOption) => void;
|
||||||
|
export type StartRecord = (options: CameraContextStartRecordOption) => void;
|
||||||
|
export type StopRecord = (options: CameraContextStopRecordOption) => void;
|
||||||
|
export type OnCameraFrameListener = (options: OnCameraFrameListenerOption) => void;
|
||||||
|
export type StartCameraFrame = (options: CameraFrameListenerStartOption) => void;
|
||||||
|
export type StopCameraFrame = (options: StopOption) => void;
|
||||||
|
export type SwitchCamera = (options: SwitchOption) => void;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
package uni.UNI644401D
|
||||||
|
import io.dcloud.uniapp.*
|
||||||
|
import io.dcloud.uniapp.extapi.*
|
||||||
|
import io.dcloud.uniapp.framework.*
|
||||||
|
import io.dcloud.uniapp.runtime.*
|
||||||
|
import io.dcloud.uniapp.vue.*
|
||||||
|
import io.dcloud.uniapp.vue.shared.*
|
||||||
|
import io.dcloud.uts.*
|
||||||
|
import io.dcloud.uts.Map
|
||||||
|
import io.dcloud.uts.Set
|
||||||
|
import io.dcloud.uts.UTSAndroid
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import io.dcloud.uniapp.extapi.exit as uni_exit
|
||||||
|
import io.dcloud.uniapp.extapi.showToast as uni_showToast
|
||||||
|
val runBlock1 = run {
|
||||||
|
__uniConfig.getAppStyles = fun(): Map<String, Map<String, Map<String, Any>>> {
|
||||||
|
return GenApp.styles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var firstBackTime: Number = 0
|
||||||
|
open class GenApp : BaseApp {
|
||||||
|
constructor(__ins: ComponentInternalInstance) : super(__ins) {
|
||||||
|
onLaunch(fun(_: OnLaunchOptions) {
|
||||||
|
console.log("App Launch")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onAppShow(fun(_: OnShowOptions) {
|
||||||
|
console.log("App Show")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onAppHide(fun() {
|
||||||
|
console.log("App Hide")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onLastPageBackPress(fun() {
|
||||||
|
console.log("App LastPageBackPress")
|
||||||
|
if (firstBackTime == 0) {
|
||||||
|
uni_showToast(ShowToastOptions(title = "再按一次退出应用", position = "bottom"))
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
setTimeout(fun(){
|
||||||
|
firstBackTime = 0
|
||||||
|
}, 2000)
|
||||||
|
} else if (Date.now() - firstBackTime < 2000) {
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
uni_exit(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
onExit(fun() {
|
||||||
|
console.log("App Exit")
|
||||||
|
}
|
||||||
|
, __ins)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
val styles: Map<String, Map<String, Map<String, Any>>> by lazy {
|
||||||
|
_nCS(_uA(
|
||||||
|
styles0
|
||||||
|
))
|
||||||
|
}
|
||||||
|
val styles0: Map<String, Map<String, Map<String, Any>>>
|
||||||
|
get() {
|
||||||
|
return _uM("uni-row" to _pS(_uM("flexDirection" to "row")), "uni-column" to _pS(_uM("flexDirection" to "column")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val GenAppClass = CreateVueAppComponent(GenApp::class.java, fun(): VueComponentOptions {
|
||||||
|
return VueComponentOptions(type = "app", name = "", inheritAttrs = true, inject = Map(), props = Map(), propsNeedCastKeys = _uA(), emits = Map(), components = Map(), styles = GenApp.styles)
|
||||||
|
}
|
||||||
|
, fun(instance): GenApp {
|
||||||
|
return GenApp(instance)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val GenPagesIndexIndexClass = CreateVueComponent(GenPagesIndexIndex::class.java, fun(): VueComponentOptions {
|
||||||
|
return VueComponentOptions(type = "page", name = "", inheritAttrs = GenPagesIndexIndex.inheritAttrs, inject = GenPagesIndexIndex.inject, props = GenPagesIndexIndex.props, propsNeedCastKeys = GenPagesIndexIndex.propsNeedCastKeys, emits = GenPagesIndexIndex.emits, components = GenPagesIndexIndex.components, styles = GenPagesIndexIndex.styles)
|
||||||
|
}
|
||||||
|
, fun(instance, renderer): GenPagesIndexIndex {
|
||||||
|
return GenPagesIndexIndex(instance, renderer)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fun createApp(): UTSJSONObject {
|
||||||
|
val app = createSSRApp(GenAppClass)
|
||||||
|
return _uO("app" to app)
|
||||||
|
}
|
||||||
|
fun main(app: IApp) {
|
||||||
|
definePageRoutes()
|
||||||
|
defineAppConfig()
|
||||||
|
(createApp()["app"] as VueApp).mount(app, GenUniApp())
|
||||||
|
}
|
||||||
|
open class UniAppConfig : io.dcloud.uniapp.appframe.AppConfig {
|
||||||
|
override var name: String = "SkinCamera"
|
||||||
|
override var appid: String = "__UNI__644401D"
|
||||||
|
override var versionName: String = "1.0.0"
|
||||||
|
override var versionCode: String = "100"
|
||||||
|
override var uniCompilerVersion: String = "4.87"
|
||||||
|
constructor() : super() {}
|
||||||
|
}
|
||||||
|
fun definePageRoutes() {
|
||||||
|
__uniRoutes.push(UniPageRoute(path = "pages/index/index", component = GenPagesIndexIndexClass, meta = UniPageMeta(isQuit = true), style = _uM("navigationBarTitleText" to "uni-app x")))
|
||||||
|
}
|
||||||
|
val __uniLaunchPage: Map<String, Any?> = _uM("url" to "pages/index/index", "style" to _uM("navigationBarTitleText" to "uni-app x"))
|
||||||
|
fun defineAppConfig() {
|
||||||
|
__uniConfig.entryPagePath = "/pages/index/index"
|
||||||
|
__uniConfig.globalStyle = _uM("navigationBarTextStyle" to "black", "navigationBarTitleText" to "uni-app x", "navigationBarBackgroundColor" to "#F8F8F8", "backgroundColor" to "#F8F8F8")
|
||||||
|
__uniConfig.getTabBarConfig = fun(): Map<String, Any>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig()
|
||||||
|
__uniConfig.conditionUrl = ""
|
||||||
|
__uniConfig.uniIdRouter = _uM()
|
||||||
|
__uniConfig.ready = true
|
||||||
|
}
|
||||||
|
open class GenUniApp : UniAppImpl() {
|
||||||
|
open val vm: GenApp?
|
||||||
|
get() {
|
||||||
|
return getAppVm() as GenApp?
|
||||||
|
}
|
||||||
|
open val `$vm`: GenApp?
|
||||||
|
get() {
|
||||||
|
return getAppVm() as GenApp?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun getApp(): GenUniApp {
|
||||||
|
return getUniApp() as GenUniApp
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
package uni.UNI644401D
|
||||||
|
import io.dcloud.uniapp.*
|
||||||
|
import io.dcloud.uniapp.extapi.*
|
||||||
|
import io.dcloud.uniapp.framework.*
|
||||||
|
import io.dcloud.uniapp.runtime.*
|
||||||
|
import io.dcloud.uniapp.vue.*
|
||||||
|
import io.dcloud.uniapp.vue.shared.*
|
||||||
|
import io.dcloud.uts.*
|
||||||
|
import io.dcloud.uts.Map
|
||||||
|
import io.dcloud.uts.Set
|
||||||
|
import io.dcloud.uts.UTSAndroid
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.XfCameraComponent
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.takePhoto
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.TakePhotoOption
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.TakePhotoSuccessCallbackResult
|
||||||
|
import uts.sdk.modules.SkinCameraUTS.GeneralCallbackResult
|
||||||
|
open class GenPagesIndexIndex : BasePage {
|
||||||
|
constructor(__ins: ComponentInternalInstance, __renderer: String?) : super(__ins, __renderer) {
|
||||||
|
onLoad(fun(_: OnLoadOptions) {}, __ins)
|
||||||
|
}
|
||||||
|
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
||||||
|
override fun `$render`(): Any? {
|
||||||
|
val _ctx = this
|
||||||
|
val _cache = this.`$`.renderCache
|
||||||
|
return _cE("scroll-view", _uM("class" to "page-scroll-view"), _uA(
|
||||||
|
_cE("view", null, _uA(
|
||||||
|
_cE("button", _uM("onClick" to _ctx._takePhoto), "拍照", 8, _uA(
|
||||||
|
"onClick"
|
||||||
|
)),
|
||||||
|
_cE(XfCameraComponent.name, _uM("style" to _nS(_uM("height" to "750rpx", "background-color" to "red")), "onError" to _ctx.onError, "flash" to _ctx.flash, "position" to _ctx.device), null, 44, _uA(
|
||||||
|
"onError",
|
||||||
|
"flash",
|
||||||
|
"position"
|
||||||
|
)),
|
||||||
|
if (isTrue(_ctx.imagePath)) {
|
||||||
|
_cE("image", _uM("key" to 0, "mode" to "aspectFit", "src" to _ctx.imagePath), null, 8, _uA(
|
||||||
|
"src"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
_cC("v-if", true)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
open var yanse: String by `$data`
|
||||||
|
open var widthNum: Number by `$data`
|
||||||
|
open var heightNum: Number by `$data`
|
||||||
|
open var flash: String by `$data`
|
||||||
|
open var device: String by `$data`
|
||||||
|
open var imagePath: String by `$data`
|
||||||
|
open var videoPath: String by `$data`
|
||||||
|
@Suppress("USELESS_CAST")
|
||||||
|
override fun data(): Map<String, Any?> {
|
||||||
|
return _uM("yanse" to "red", "widthNum" to 750, "heightNum" to 200, "flash" to "on", "device" to "front", "imagePath" to "", "videoPath" to "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4")
|
||||||
|
}
|
||||||
|
open var onError = ::gen_onError_fn
|
||||||
|
open fun gen_onError_fn(res: Any) {
|
||||||
|
console.log(res)
|
||||||
|
}
|
||||||
|
open var _takePhoto = ::gen__takePhoto_fn
|
||||||
|
open fun gen__takePhoto_fn() {
|
||||||
|
takePhoto(TakePhotoOption(success = fun(res: TakePhotoSuccessCallbackResult){
|
||||||
|
console.log("takePhoto", res)
|
||||||
|
this.imagePath = res.tempImagePath
|
||||||
|
}
|
||||||
|
, fail = fun(res: GeneralCallbackResult){
|
||||||
|
console.log("takePhoto", res)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
val styles: Map<String, Map<String, Map<String, Any>>> by lazy {
|
||||||
|
_nCS(_uA(
|
||||||
|
styles0
|
||||||
|
), _uA(
|
||||||
|
GenApp.styles
|
||||||
|
))
|
||||||
|
}
|
||||||
|
val styles0: Map<String, Map<String, Map<String, Any>>>
|
||||||
|
get() {
|
||||||
|
return _uM("video" to _pS(_uM("width" to "100%", "height" to 200)))
|
||||||
|
}
|
||||||
|
var inheritAttrs = true
|
||||||
|
var inject: Map<String, Map<String, Any?>> = _uM()
|
||||||
|
var emits: Map<String, Any?> = _uM()
|
||||||
|
var props = _nP(_uM())
|
||||||
|
var propsNeedCastKeys: UTSArray<String> = _uA()
|
||||||
|
var components: Map<String, CreateVueComponent> = _uM()
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"id": "__UNI__644401D",
|
||||||
|
"name": "SkinCamera",
|
||||||
|
"description": "",
|
||||||
|
"version": {
|
||||||
|
"name": "1.0.0",
|
||||||
|
"code": "100"
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"compilerVersion": "4.87"
|
||||||
|
},
|
||||||
|
"app-android": {
|
||||||
|
"distribute": {
|
||||||
|
"modules": {
|
||||||
|
"uni-exit": {},
|
||||||
|
"uni-prompt": {}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"hdpi": "",
|
||||||
|
"xhdpi": "",
|
||||||
|
"xxhdpi": "",
|
||||||
|
"xxxhdpi": ""
|
||||||
|
},
|
||||||
|
"splashScreens": {
|
||||||
|
"default": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="uts.sdk.modules.XFCameraUTS">
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.any" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.microphone" android:required="true" />
|
||||||
|
<application android:requestLegacyExternalStorage="true">
|
||||||
|
<meta-data android:name="ScopedStorage" android:value="true" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"minSdkVersion": "19",
|
||||||
|
"dependencies": [
|
||||||
|
"androidx.camera:camera-core:1.4.0-alpha04",
|
||||||
|
"androidx.camera:camera-camera2:1.4.0-alpha04",
|
||||||
|
"androidx.camera:camera-lifecycle:1.4.0-alpha04",
|
||||||
|
"androidx.camera:camera-video:1.4.0-alpha04",
|
||||||
|
"androidx.camera:camera-view:1.4.0-alpha04"
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "xf-camera",
|
||||||
|
"class": "uts.sdk.modules.SkinCameraUTS.XfCameraComponent"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+745
@@ -0,0 +1,745 @@
|
|||||||
|
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||||
|
package uts.sdk.modules.SkinCameraUTS
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
import android.util.Size
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.camera.core.CameraControl
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
|
import androidx.camera.core.MirrorMode
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.core.UseCaseGroup
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.video.FileOutputOptions
|
||||||
|
import androidx.camera.video.Quality
|
||||||
|
import androidx.camera.video.QualitySelector
|
||||||
|
import androidx.camera.video.Recorder
|
||||||
|
import androidx.camera.video.Recording
|
||||||
|
import androidx.camera.video.VideoCapture
|
||||||
|
import androidx.camera.video.VideoRecordEvent
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import io.dcloud.uniapp.*
|
||||||
|
import io.dcloud.uniapp.UniSDKEngine
|
||||||
|
import io.dcloud.uniapp.appframe.PageProxy
|
||||||
|
import io.dcloud.uniapp.dom.node.DomNode
|
||||||
|
import io.dcloud.uniapp.dom.node.PageNode
|
||||||
|
import io.dcloud.uniapp.extapi.*
|
||||||
|
import io.dcloud.uniapp.framework.*
|
||||||
|
import io.dcloud.uniapp.interfaces.INodeData
|
||||||
|
import io.dcloud.uniapp.runtime.*
|
||||||
|
import io.dcloud.uniapp.ui.component.IComponentData
|
||||||
|
import io.dcloud.uniapp.vue.*
|
||||||
|
import io.dcloud.uniapp.vue.shared.*
|
||||||
|
import io.dcloud.uts.*
|
||||||
|
import io.dcloud.uts.Map
|
||||||
|
import io.dcloud.uts.Set
|
||||||
|
import io.dcloud.uts.UTSAndroid
|
||||||
|
import io.dcloud.uts.component.*
|
||||||
|
import io.dcloud.uts.component.UTSComponent
|
||||||
|
import java.io.File
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
open class GeneralCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var errMsg: String,
|
||||||
|
) : UTSObject()
|
||||||
|
open class TakePhotoSuccessCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var tempImagePath: String,
|
||||||
|
@JsonNotNull
|
||||||
|
open var quality: String,
|
||||||
|
@JsonNotNull
|
||||||
|
open var errMsg: String,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias TakePhotoFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias TakePhotoSuccessCallback = (result: TakePhotoSuccessCallbackResult) -> Unit
|
||||||
|
typealias TakePhotoCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
open class TakePhotoOption (
|
||||||
|
open var complete: TakePhotoCompleteCallback? = null,
|
||||||
|
open var fail: TakePhotoFailCallback? = null,
|
||||||
|
open var quality: String? = null,
|
||||||
|
open var selfieMirror: Boolean? = null,
|
||||||
|
open var success: TakePhotoSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
open class SetZoomSuccessCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var zoom: Number,
|
||||||
|
@JsonNotNull
|
||||||
|
open var errMsg: String,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias SetZoomCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias SetZoomFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias CameraContextSetZoomSuccessCallback = (result: SetZoomSuccessCallbackResult) -> Unit
|
||||||
|
open class CameraContextSetZoomOption (
|
||||||
|
@JsonNotNull
|
||||||
|
open var zoom: Number,
|
||||||
|
open var complete: SetZoomCompleteCallback? = null,
|
||||||
|
open var fail: SetZoomFailCallback? = null,
|
||||||
|
open var success: CameraContextSetZoomSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
open class StartRecordTimeoutCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var tempVideoPath: String,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias StartRecordCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StartRecordFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StartRecordTimeoutCallback = (result: StartRecordTimeoutCallbackResult) -> Unit
|
||||||
|
typealias CameraContextStartRecordSuccessCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
open class CameraContextStartRecordOption (
|
||||||
|
open var complete: StartRecordCompleteCallback? = null,
|
||||||
|
open var fail: StartRecordFailCallback? = null,
|
||||||
|
open var selfieMirror: Boolean? = null,
|
||||||
|
open var success: CameraContextStartRecordSuccessCallback? = null,
|
||||||
|
open var timeout: Number? = null,
|
||||||
|
open var timeoutCallback: StartRecordTimeoutCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
open class StopRecordSuccessCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var tempVideoPath: String,
|
||||||
|
@JsonNotNull
|
||||||
|
open var errMsg: String,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias StopRecordCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StopRecordFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias CameraContextStopRecordSuccessCallback = (result: StopRecordSuccessCallbackResult) -> Unit
|
||||||
|
open class CameraContextStopRecordOption (
|
||||||
|
open var complete: StopRecordCompleteCallback? = null,
|
||||||
|
open var compressed: Boolean? = null,
|
||||||
|
open var fail: StopRecordFailCallback? = null,
|
||||||
|
open var success: CameraContextStopRecordSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias OnCameraFrameCallback = (result: OnCameraFrameCallbackResult) -> Unit
|
||||||
|
open class OnCameraFrameCallbackResult (
|
||||||
|
@JsonNotNull
|
||||||
|
open var data: Any,
|
||||||
|
@JsonNotNull
|
||||||
|
open var height: Number,
|
||||||
|
@JsonNotNull
|
||||||
|
open var width: Number,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias StartSuccessCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StartFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StartCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
open class CameraFrameListenerStartOption (
|
||||||
|
open var complete: StartCompleteCallback? = null,
|
||||||
|
open var fail: StartFailCallback? = null,
|
||||||
|
open var success: StartSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias StopCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StopFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias StopSuccessCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
open class StopOption (
|
||||||
|
open var complete: StopCompleteCallback? = null,
|
||||||
|
open var fail: StopFailCallback? = null,
|
||||||
|
open var success: StopSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias FlashMode = String
|
||||||
|
typealias DevicePosition = String
|
||||||
|
typealias Resolution = String
|
||||||
|
typealias FrameSize = String
|
||||||
|
typealias QualityType = String
|
||||||
|
open class CameraConfig (
|
||||||
|
open var flash: FlashMode? = null,
|
||||||
|
open var devicePosition: DevicePosition? = null,
|
||||||
|
open var resolution: Resolution? = null,
|
||||||
|
open var frameSize: FrameSize? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias SwitchCompleteCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias SwitchFailCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
typealias SwitchSuccessCallback = (res: GeneralCallbackResult) -> Unit
|
||||||
|
open class SwitchOption (
|
||||||
|
@JsonNotNull
|
||||||
|
open var position: DevicePosition,
|
||||||
|
open var complete: SwitchCompleteCallback? = null,
|
||||||
|
open var fail: SwitchFailCallback? = null,
|
||||||
|
open var success: SwitchSuccessCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
open class OnCameraFrameListenerOption (
|
||||||
|
open var success: OnCameraFrameCallback? = null,
|
||||||
|
) : UTSObject()
|
||||||
|
typealias TakePhoto = (options: TakePhotoOption) -> Unit
|
||||||
|
typealias SetZoom = (options: CameraContextSetZoomOption) -> Unit
|
||||||
|
typealias StartRecord = (options: CameraContextStartRecordOption) -> Unit
|
||||||
|
typealias StopRecord = (options: CameraContextStopRecordOption) -> Unit
|
||||||
|
typealias StartCameraFrame = (options: CameraFrameListenerStartOption) -> Unit
|
||||||
|
typealias StopCameraFrame = (options: StopOption) -> Unit
|
||||||
|
typealias SwitchCamera = (options: SwitchOption) -> Unit
|
||||||
|
val FLASH_MODE_MAP = Map<String, Int>(_uA(
|
||||||
|
_uA(
|
||||||
|
"auto",
|
||||||
|
ImageCapture.FLASH_MODE_AUTO
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"on",
|
||||||
|
ImageCapture.FLASH_MODE_ON
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"torch",
|
||||||
|
ImageCapture.FLASH_MODE_ON
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"off",
|
||||||
|
ImageCapture.FLASH_MODE_OFF
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val DEVICE_POSITION_MAP = Map<String, Int>(_uA(
|
||||||
|
_uA(
|
||||||
|
"back",
|
||||||
|
CameraSelector.LENS_FACING_BACK
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"front",
|
||||||
|
CameraSelector.LENS_FACING_FRONT
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val RESOLUTION_MAP = Map<String, Quality>(_uA(
|
||||||
|
_uA(
|
||||||
|
"low",
|
||||||
|
Quality.LOWEST
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"medium",
|
||||||
|
Quality.HD
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"high",
|
||||||
|
Quality.HIGHEST
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val QUALITY_MAP = Map<String, Int>(_uA(
|
||||||
|
_uA(
|
||||||
|
"high",
|
||||||
|
ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
"low",
|
||||||
|
ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
|
||||||
|
)
|
||||||
|
))
|
||||||
|
val MIRROR_MODE = Map<Boolean, Int>(_uA(
|
||||||
|
_uA(
|
||||||
|
true,
|
||||||
|
MirrorMode.MIRROR_MODE_ON
|
||||||
|
),
|
||||||
|
_uA(
|
||||||
|
false,
|
||||||
|
MirrorMode.MIRROR_MODE_OFF
|
||||||
|
)
|
||||||
|
))
|
||||||
|
open class CameraView : FrameLayout {
|
||||||
|
private var previewView: PreviewView
|
||||||
|
open var cameraProvider: ProcessCameraProvider? = null
|
||||||
|
open var cameraControl: CameraControl? = null
|
||||||
|
private var imageCapture: ImageCapture? = null
|
||||||
|
private var videoCapture: VideoCapture<Recorder>? = null
|
||||||
|
private var recording: Recording? = null
|
||||||
|
private var comp: UTSComponent<CameraView>
|
||||||
|
private var flashMode: FlashMode = "auto"
|
||||||
|
private var devicePosition: DevicePosition = "back"
|
||||||
|
private var resolution: Resolution = "medium"
|
||||||
|
private var quality: QualityType = "normal"
|
||||||
|
private var videoFile: File? = null
|
||||||
|
open var maxZoom: Float = 1.toFloat()
|
||||||
|
open var minZoom: Float = 1.toFloat()
|
||||||
|
private var initialized: Boolean = false
|
||||||
|
private var isListening: Boolean = false
|
||||||
|
private var videoSelfieMirror: Boolean? = null
|
||||||
|
private var cameraContextStopRecordOption: CameraContextStopRecordOption? = null
|
||||||
|
private var onCameraFrameCallback: OnCameraFrameCallback? = null
|
||||||
|
private var cameraExecutor: ExecutorService? = null
|
||||||
|
private var onCameraFrameListenerOption: OnCameraFrameListenerOption? = null
|
||||||
|
constructor(context: Context, comp: UTSComponent<CameraView>, config: CameraConfig = CameraConfig()) : super(context) {
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?: "auto"
|
||||||
|
this.devicePosition = config.devicePosition ?: "back"
|
||||||
|
this.resolution = config.resolution ?: "medium"
|
||||||
|
this.previewView = PreviewView(context)
|
||||||
|
var layoutParam = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
this.addView(this.previewView, layoutParam)
|
||||||
|
this.setupCamera()
|
||||||
|
}
|
||||||
|
open fun setupCamera() {
|
||||||
|
var cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!!)
|
||||||
|
cameraProviderFuture.addListener(fun(){
|
||||||
|
try {
|
||||||
|
this.cameraProvider = cameraProviderFuture.get()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
catch (e: Throwable) {
|
||||||
|
var ret: Map<String, Any> = Map()
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
this.comp.`$emit`("error", ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!!))
|
||||||
|
}
|
||||||
|
open fun bindCamera() {
|
||||||
|
var targetResolution = RESOLUTION_MAP.get(this.resolution) ?: Quality.HD
|
||||||
|
var captureMode = QUALITY_MAP.get(this.quality) ?: ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
|
||||||
|
var preview = Preview.Builder().build()
|
||||||
|
preview.setSurfaceProvider(this.previewView.getSurfaceProvider())
|
||||||
|
var imageCapture = ImageCapture.Builder().setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!!).setCaptureMode(captureMode).build()
|
||||||
|
var cameraSelector = CameraSelector.Builder().requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!!).build()
|
||||||
|
var recorder = Recorder.Builder().setQualitySelector(QualitySelector.from(targetResolution))
|
||||||
|
var mirrorMode = if (this.videoSelfieMirror == null) {
|
||||||
|
MirrorMode.MIRROR_MODE_ON_FRONT_ONLY
|
||||||
|
} else {
|
||||||
|
MIRROR_MODE.get(this.videoSelfieMirror!!)
|
||||||
|
}
|
||||||
|
var videoCapture = VideoCapture.Builder(recorder.build()).setMirrorMode(mirrorMode!!).build()
|
||||||
|
this.videoCapture = videoCapture
|
||||||
|
this.imageCapture = imageCapture
|
||||||
|
this.cameraProvider!!.unbindAll()
|
||||||
|
var imageAnalysis = ImageAnalysis.Builder().setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).build()
|
||||||
|
var viewPort = this.previewView.getViewPort()!!
|
||||||
|
var useCaseGroup = UseCaseGroup.Builder().addUseCase(preview).addUseCase(imageAnalysis).addUseCase(imageCapture).addUseCase(videoCapture).setViewPort(viewPort).build()
|
||||||
|
this.cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
var _this = this
|
||||||
|
open class Analyzer : ImageAnalysis.Analyzer {
|
||||||
|
constructor(){}
|
||||||
|
override fun analyze(image: ImageProxy) {
|
||||||
|
if (_this.isListening) {
|
||||||
|
var imageFormat = image.getFormat()
|
||||||
|
var width = image.width
|
||||||
|
var height = image.height
|
||||||
|
if (imageFormat == ImageFormat.YUV_420_888) {
|
||||||
|
_this.onCameraFrameCallback?.invoke(OnCameraFrameCallbackResult(width = width, height = height, data = image))
|
||||||
|
_this.onCameraFrameListenerOption?.success?.invoke(OnCameraFrameCallbackResult(width = width, height = height, data = image))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.close()
|
||||||
|
}
|
||||||
|
override fun getDefaultTargetResolution(): Size {
|
||||||
|
return Size(640, 480)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageAnalysis.setAnalyzer(this.cameraExecutor!!, Analyzer() as ImageAnalysis.Analyzer)
|
||||||
|
var camera = this.cameraProvider!!.bindToLifecycle(UTSAndroid.getUniActivity()!! as LifecycleOwner, cameraSelector, useCaseGroup)
|
||||||
|
this.cameraControl = camera.getCameraControl()
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true
|
||||||
|
var zoomState = camera.getCameraInfo().getZoomState().getValue()
|
||||||
|
this.maxZoom = zoomState?.getMaxZoomRatio() ?: 1.toFloat()
|
||||||
|
this.minZoom = zoomState?.getMinZoomRatio() ?: 1.toFloat()
|
||||||
|
var maxZoom = this.maxZoom
|
||||||
|
var ret: Map<String, Any> = Map()
|
||||||
|
ret.set("maxZoom", maxZoom)
|
||||||
|
this.comp.`$emit`("ready", ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open fun unbindCamera() {
|
||||||
|
this.cameraProvider?.unbindAll()
|
||||||
|
this.cameraExecutor?.shutdown()
|
||||||
|
}
|
||||||
|
open fun setConfig(config: CameraConfig = CameraConfig()) {
|
||||||
|
this.flashMode = config.flash ?: "auto"
|
||||||
|
this.devicePosition = config.devicePosition ?: "back"
|
||||||
|
this.resolution = config.resolution ?: "medium"
|
||||||
|
}
|
||||||
|
open fun setFlash(mode: FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.imageCapture!!.setFlashMode(FLASH_MODE_MAP.get(mode)!!)
|
||||||
|
}
|
||||||
|
open fun switchCamera(position: DevicePosition) {
|
||||||
|
this.devicePosition = position
|
||||||
|
if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
open fun isResolutionSupported(context: Context, cameraSelector: CameraSelector, targetResolution: Size): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
open fun takePhoto(option: TakePhotoOption) {
|
||||||
|
var quality = option.quality ?: "high"
|
||||||
|
this.quality = quality
|
||||||
|
if (quality == "low") {
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
var privateDir = UTSAndroid.getAppCachePath()!!
|
||||||
|
val tempFile = File(privateDir)
|
||||||
|
if (!tempFile.exists()) {
|
||||||
|
tempFile.mkdirs()
|
||||||
|
}
|
||||||
|
val photoFile = File(privateDir, Date.now() + ".png")
|
||||||
|
var metadata = ImageCapture.Metadata()
|
||||||
|
metadata.setReversedHorizontal(option.selfieMirror ?: DEVICE_POSITION_MAP.get(this.devicePosition)!! == CameraSelector.LENS_FACING_FRONT)
|
||||||
|
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
|
||||||
|
open class OnImageSavedCallback : ImageCapture.OnImageSavedCallback {
|
||||||
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
option.success?.invoke(TakePhotoSuccessCallbackResult(tempImagePath = photoFile.getAbsolutePath(), quality = quality, errMsg = "ok"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
}
|
||||||
|
override fun onError(err: ImageCaptureException) {
|
||||||
|
val result = GeneralCallbackResult(errMsg = err.toString())
|
||||||
|
option.fail?.invoke(result)
|
||||||
|
option.complete?.invoke(result)
|
||||||
|
}
|
||||||
|
override fun onCaptureStarted() {}
|
||||||
|
}
|
||||||
|
this.imageCapture!!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!!), OnImageSavedCallback() as ImageCapture.OnImageSavedCallback)
|
||||||
|
}
|
||||||
|
open fun setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
if (this.cameraControl != null) {
|
||||||
|
try {
|
||||||
|
this.cameraControl!!.setZoomRatio(option.zoom.toFloat())
|
||||||
|
option.success?.invoke(SetZoomSuccessCallbackResult(zoom = option.zoom, errMsg = "ok"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
val result = GeneralCallbackResult(errMsg = e.toString())
|
||||||
|
option.complete?.invoke(result)
|
||||||
|
option.fail?.invoke(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val result = GeneralCallbackResult(errMsg = "相机不存在")
|
||||||
|
option.complete?.invoke(result)
|
||||||
|
option.fail?.invoke(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open fun startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) {
|
||||||
|
this.videoSelfieMirror = option.selfieMirror
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
if (this.recording != null || this.videoCapture == null) {
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "已经在录制或相机不存在"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "已经在录制或相机不存在"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var privateDir = UTSAndroid.getAppCachePath()!!
|
||||||
|
this.videoFile = File(privateDir, Date.now() + "video.mp4")
|
||||||
|
var outputFileOptions = FileOutputOptions.Builder(this.videoFile!!).build()
|
||||||
|
this.recording = this.videoCapture!!.output.prepareRecording(UTSAndroid.getUniActivity()!!, outputFileOptions).withAudioEnabled().start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!!), fun(recordEvent: VideoRecordEvent){
|
||||||
|
if (recordEvent is VideoRecordEvent.Start) {
|
||||||
|
option.success?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
if (option.timeout != null && option.timeout!! > 0) {
|
||||||
|
setTimeout(fun(){
|
||||||
|
this.recording?.stop()
|
||||||
|
}
|
||||||
|
, option.timeout!!)
|
||||||
|
}
|
||||||
|
} else if (recordEvent is VideoRecordEvent.Finalize) {
|
||||||
|
this.recording?.close()
|
||||||
|
this.recording = null
|
||||||
|
var tempVideoPath = this.videoFile?.getAbsolutePath()!!
|
||||||
|
this.cameraContextStopRecordOption?.success?.invoke(StopRecordSuccessCallbackResult(tempVideoPath = tempVideoPath, errMsg = "ok"))
|
||||||
|
option.timeoutCallback?.invoke(StartRecordTimeoutCallbackResult(tempVideoPath = tempVideoPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
if (this.recording == null) {
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未开始录制"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未开始录制"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.cameraContextStopRecordOption = option
|
||||||
|
this.recording?.stop()
|
||||||
|
}
|
||||||
|
open fun onCameraFrame(callback: OnCameraFrameCallback? = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
open fun onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
this.onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
open fun cameraFrameOnStart(option: CameraFrameListenerStartOption?) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
}
|
||||||
|
open fun cameraFrameOnStop(option: StopOption?) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open class CameraManager {
|
||||||
|
private var cameraView: CameraView? = null
|
||||||
|
public open fun setCameraView(cameraView: CameraView?): Unit {
|
||||||
|
this.cameraView = cameraView!!
|
||||||
|
}
|
||||||
|
public open fun getCameraView(): CameraView? {
|
||||||
|
return this.cameraView
|
||||||
|
}
|
||||||
|
public open fun dispose(): Unit {
|
||||||
|
this.cameraView = null
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
private var instance: CameraManager? = null
|
||||||
|
public fun getInstance(): CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = CameraManager()
|
||||||
|
}
|
||||||
|
return CameraManager.instance!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open class XfCameraComponent : UTSComponent<CameraView> {
|
||||||
|
constructor(proxy: PageProxy, componentData: IComponentData) : super(proxy, componentData)
|
||||||
|
open var mode: String = "normal"
|
||||||
|
open var resolution: String = "medium"
|
||||||
|
open var position: String = "back"
|
||||||
|
open var flash: String = "auto"
|
||||||
|
open var frameSize: String = "medium"
|
||||||
|
override fun created() {}
|
||||||
|
override fun NVBeforeLoad() {}
|
||||||
|
override fun NVLoad(): CameraView {
|
||||||
|
var previewView = CameraView(this.`$androidContext`!!, this)
|
||||||
|
previewView.setTag("9527")
|
||||||
|
CameraManager.getInstance().setCameraView(previewView)
|
||||||
|
return previewView
|
||||||
|
}
|
||||||
|
override fun NVLoaded() {}
|
||||||
|
override fun NVLayouted() {
|
||||||
|
this.`$el`?.setConfig(CameraConfig(resolution = this.resolution, frameSize = this.frameSize, flash = this.flash))
|
||||||
|
this.`$el`?.switchCamera(this.position)
|
||||||
|
}
|
||||||
|
override fun NVBeforeUnload() {
|
||||||
|
var ret: Map<String, Any> = Map()
|
||||||
|
this.`$emit`("stop", ret)
|
||||||
|
this.`$el`?.unbindCamera()
|
||||||
|
}
|
||||||
|
override fun NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose()
|
||||||
|
}
|
||||||
|
override fun unmounted() {}
|
||||||
|
override fun `$init`() {
|
||||||
|
this.`$watch`<String>("position", fun(newValue, _){
|
||||||
|
this.`$el`?.switchCamera(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.`$watch`<String>("flash", fun(newValue, _){
|
||||||
|
this.`$el`?.setFlash(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
var name = "xf-camera"
|
||||||
|
init {
|
||||||
|
io.dcloud.uniapp.UniSDKEngine.registerUniComponent("xf-camera", XfCameraComponent::class.java, XfCameraElement::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open class XfCameraElement : DomNode {
|
||||||
|
constructor(data: INodeData, pageNode: PageNode) : super(data, pageNode)
|
||||||
|
}
|
||||||
|
open class CameraFrameListener {
|
||||||
|
open lateinit var context: CameraContext
|
||||||
|
constructor(context: CameraContext){
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
open fun start(): Unit {
|
||||||
|
this.start(null)
|
||||||
|
}
|
||||||
|
open fun start(option: CameraFrameListenerStartOption?) {
|
||||||
|
this.context.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun stop(): Unit {
|
||||||
|
this.stop(null)
|
||||||
|
}
|
||||||
|
open fun stop(option: StopOption?) {
|
||||||
|
this.context.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open class CameraContext {
|
||||||
|
private var mCameraFrameListener: CameraFrameListener? = null
|
||||||
|
constructor(){}
|
||||||
|
open fun getCamera(): UTSPromise<CameraView> {
|
||||||
|
var cameraPreview = CameraManager.getInstance().getCameraView()
|
||||||
|
if (cameraPreview != null) {
|
||||||
|
return UTSPromise.resolve<CameraView>(cameraPreview)
|
||||||
|
}
|
||||||
|
var resolveFunc: ((res: CameraView) -> Unit)? = null
|
||||||
|
var rejectFunc: ((res: String) -> Unit)? = null
|
||||||
|
var maxAttempts: Number = 5
|
||||||
|
var currentAttempt: Number = 0
|
||||||
|
fun attemptGetCamera() {
|
||||||
|
var camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera != null) {
|
||||||
|
resolveFunc?.invoke(camera)
|
||||||
|
} else if (currentAttempt < maxAttempts) {
|
||||||
|
currentAttempt++
|
||||||
|
setTimeout(fun(){
|
||||||
|
attemptGetCamera()
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
rejectFunc?.invoke("Camera not found after multiple attempts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UTSPromise<CameraView>(fun(resolve, reject){
|
||||||
|
resolveFunc = resolve
|
||||||
|
rejectFunc = reject
|
||||||
|
attemptGetCamera()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun takePhoto(option: TakePhotoOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.takePhoto(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun setZoom(option: CameraContextSetZoomOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.setZoom(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun startRecord(option: CameraContextStartRecordOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.startRecord(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun stopRecord(option: CameraContextStopRecordOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.stopRecord(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun onCameraFrame(): CameraFrameListener {
|
||||||
|
return this.onCameraFrame(null)
|
||||||
|
}
|
||||||
|
open fun onCameraFrame(callback: OnCameraFrameCallback? = null): CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.onCameraFrame(callback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return this.mCameraFrameListener!!
|
||||||
|
}
|
||||||
|
open fun onCameraFrameListener(option: OnCameraFrameListenerOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.onCameraFrameListener(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
console.log("未找到相机")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun cameraFrameOnStart(option: CameraFrameListenerStartOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun cameraFrameOnStop(option: StopOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
open fun switchCamera(option: SwitchOption) {
|
||||||
|
this.getCamera().then(fun(camera: CameraView){
|
||||||
|
camera.switchCamera(option.position)
|
||||||
|
option.success?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "ok"))
|
||||||
|
}
|
||||||
|
).`catch`(fun(){
|
||||||
|
option.fail?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
option.complete?.invoke(GeneralCallbackResult(errMsg = "未找到相机"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var context: CameraContext = CameraContext()
|
||||||
|
fun createCameraContext(): CameraContext {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
val takePhoto: TakePhoto = fun(options: TakePhotoOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val setZoom: SetZoom = fun(options: CameraContextSetZoomOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val startRecord: StartRecord = fun(options: CameraContextStartRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val stopRecord: StopRecord = fun(options: CameraContextStopRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
fun onCameraFrameListener(options: OnCameraFrameListenerOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val startCameraFrame: StartCameraFrame = fun(options: CameraFrameListenerStartOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val stopCameraFrame: StopCameraFrame = fun(options: StopOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
|
val switchCamera: SwitchCamera = fun(options: SwitchOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(fun(_) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}
|
||||||
|
, null)
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
const __uniConfig = {"pages":[],"globalStyle":{"navigationBarTextStyle":"black","navigationBarTitleText":"uni-app x","navigationBarBackgroundColor":"#F8F8F8","backgroundColor":"#F8F8F8"},"appname":"SkinCamera","compilerVersion":"4.87","entryPagePath":"pages/index/index","entryPageQuery":"","realEntryPagePath":"","themeConfig":{}};
|
||||||
|
__uniConfig.getTabBarConfig = () => {return undefined};
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig();
|
||||||
|
const __uniRoutes = [{"path":"pages/index/index","meta":{"isQuit":true,"isEntry":true,"navigationBarTitleText":"uni-app x"}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute)).concat(typeof __uniSystemRoutes !== 'undefined' ? __uniSystemRoutes : []);
|
||||||
|
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
(function(vue) {
|
||||||
|
"use strict";
|
||||||
|
const { registerUTSInterface, initUTSProxyClass, initUTSProxyFunction, initUTSPackageName, initUTSIndexClassName, initUTSClassName } = uni;
|
||||||
|
const name = "SkinCameraUTS";
|
||||||
|
const moduleName = "UTS实现自定义相机拍照和录像功能";
|
||||||
|
const moduleType = "";
|
||||||
|
const errMsg = "";
|
||||||
|
const is_uni_modules = true;
|
||||||
|
const pkg = /* @__PURE__ */ initUTSPackageName(name, is_uni_modules);
|
||||||
|
const cls = /* @__PURE__ */ initUTSIndexClassName(name, is_uni_modules);
|
||||||
|
const XfCameraComponent = {};
|
||||||
|
const takePhoto = /* @__PURE__ */ initUTSProxyFunction(false, { moduleName, moduleType, errMsg, main: true, package: pkg, class: cls, name: "takePhotoByJs", keepAlive: false, params: [{ "name": "options", "type": "UTSSDKModulesSkinCameraUTSTakePhotoOptionJSONObject" }], return: "" });
|
||||||
|
function resolveEasycom(component, easycom) {
|
||||||
|
return typeof component === "string" ? easycom : component;
|
||||||
|
}
|
||||||
|
const _sfc_main$1 = vue.defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
yanse: "red",
|
||||||
|
widthNum: 750,
|
||||||
|
heightNum: 200,
|
||||||
|
flash: "on",
|
||||||
|
device: "front",
|
||||||
|
imagePath: "",
|
||||||
|
videoPath: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onError(res = null) {
|
||||||
|
uni.__log__("log", "at pages/index/index.uvue:54", res);
|
||||||
|
},
|
||||||
|
_takePhoto() {
|
||||||
|
takePhoto({
|
||||||
|
success: (res = null) => {
|
||||||
|
uni.__log__("log", "at pages/index/index.uvue:59", "takePhoto", res);
|
||||||
|
this.imagePath = res.tempImagePath;
|
||||||
|
},
|
||||||
|
fail: (res = null) => {
|
||||||
|
uni.__log__("log", "at pages/index/index.uvue:64", "takePhoto", res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const _style_0$1 = { "video": { "": { "width": "100%", "height": 200 } } };
|
||||||
|
const _export_sfc = (sfc, props) => {
|
||||||
|
const target = sfc.__vccOpts || sfc;
|
||||||
|
for (const [key, val] of props) {
|
||||||
|
target[key] = val;
|
||||||
|
}
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
|
resolveEasycom(vue.resolveDynamicComponent("xf-camera"), XfCameraComponent);
|
||||||
|
return vue.openBlock(), vue.createElementBlock("scroll-view", { class: "page-scroll-view" }, [
|
||||||
|
vue.createElementVNode("view", null, [
|
||||||
|
vue.createElementVNode("button", {
|
||||||
|
onClick: _cache[0] || (_cache[0] = (...args) => $options._takePhoto && $options._takePhoto(...args))
|
||||||
|
}, "拍照"),
|
||||||
|
vue.createElementVNode("xf-camera", {
|
||||||
|
style: { "height": "750rpx", "background-color": "red" },
|
||||||
|
onError: _cache[1] || (_cache[1] = (...args) => $options.onError && $options.onError(...args)),
|
||||||
|
flash: $data.flash,
|
||||||
|
position: $data.device
|
||||||
|
}, null, 40, ["flash", "position"]),
|
||||||
|
$data.imagePath ? (vue.openBlock(), vue.createElementBlock("image", {
|
||||||
|
key: 0,
|
||||||
|
mode: "aspectFit",
|
||||||
|
src: $data.imagePath
|
||||||
|
}, null, 8, ["src"])) : vue.createCommentVNode("", true)
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["styles", [_style_0$1]]]);
|
||||||
|
__definePage("pages/index/index", PagesIndexIndex);
|
||||||
|
const _sfc_main = vue.defineComponent({
|
||||||
|
onLaunch() {
|
||||||
|
uni.__log__("log", "at App.uvue:7", "App Launch");
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
uni.__log__("log", "at App.uvue:10", "App Show");
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
uni.__log__("log", "at App.uvue:13", "App Hide");
|
||||||
|
},
|
||||||
|
onExit() {
|
||||||
|
uni.__log__("log", "at App.uvue:34", "App Exit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const _style_0 = { "uni-row": { "": { "flexDirection": "row" } }, "uni-column": { "": { "flexDirection": "column" } } };
|
||||||
|
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["styles", [_style_0]]]);
|
||||||
|
const __global__ = typeof globalThis === "undefined" ? Function("return this")() : globalThis;
|
||||||
|
__global__.__uniX = true;
|
||||||
|
function createApp() {
|
||||||
|
const app = vue.createSSRApp(App);
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
};
|
||||||
|
}
|
||||||
|
createApp().app.mount("#app");
|
||||||
|
})(Vue);
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"id": "__UNI__644401D",
|
||||||
|
"name": "SkinCamera",
|
||||||
|
"description": "",
|
||||||
|
"version": {
|
||||||
|
"name": "1.0.0",
|
||||||
|
"code": "100"
|
||||||
|
},
|
||||||
|
"uni-app-x": {
|
||||||
|
"compilerVersion": "4.87"
|
||||||
|
},
|
||||||
|
"app-ios": {
|
||||||
|
"distribute": {
|
||||||
|
"modules": {},
|
||||||
|
"icons": {},
|
||||||
|
"splashScreens": {},
|
||||||
|
"privacyDescription": {
|
||||||
|
"NSPhotoLibraryUsageDescription": "读取相册",
|
||||||
|
"NSPhotoLibraryAddUsageDescription": "写入相册",
|
||||||
|
"NSCameraUsageDescription": "要摄像头",
|
||||||
|
"NSMicrophoneUsageDescription": "要麦克风",
|
||||||
|
"NSLocationWhenInUseUsageDescription": "要访问",
|
||||||
|
"NSLocationAlwaysUsageDescription": "要访问",
|
||||||
|
"NSLocationAlwaysAndWhenInUseUsageDescription": "要访问"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"deploymentTarget": "11",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "xf-camera",
|
||||||
|
"class": "UTSSDKModulesSkinCameraUTSXfCameraComponent",
|
||||||
|
"delegateClass": "XfCameraComponentRegister"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+1407
File diff suppressed because it is too large
Load Diff
+39
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
let firstBackTime = 0
|
||||||
|
|
||||||
|
const __sfc__ = defineApp({
|
||||||
|
onLaunch() {
|
||||||
|
console.log('App Launch', " at App.uvue:7")
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
console.log('App Show', " at App.uvue:10")
|
||||||
|
},
|
||||||
|
onHide() {
|
||||||
|
console.log('App Hide', " at App.uvue:13")
|
||||||
|
},
|
||||||
|
|
||||||
|
onLastPageBackPress() {
|
||||||
|
console.log('App LastPageBackPress', " at App.uvue:17")
|
||||||
|
if (firstBackTime == 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '再按一次退出应用',
|
||||||
|
position: 'bottom',
|
||||||
|
})
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
setTimeout(() => {
|
||||||
|
firstBackTime = 0
|
||||||
|
}, 2000)
|
||||||
|
} else if (Date.now() - firstBackTime < 2000) {
|
||||||
|
firstBackTime = Date.now()
|
||||||
|
uni.exit()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
console.log('App Exit', " at App.uvue:34")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default __sfc__
|
||||||
|
const GenAppStyles = [_uM([["uni-row", _pS(_uM([["flexDirection", "row"]]))], ["uni-column", _pS(_uM([["flexDirection", "column"]]))]])]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["App.uvue"],"names":[],"mappings":";;CAEC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;;CAEpB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;EACd,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;GACV,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EACzB,CAAC;EACD,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;GACR,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;EACvB,CAAC;EACD,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;GACR,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;EACvB,CAAC;;EAED,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;GACrB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;GACnC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;IACvB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACb,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KACjB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;IACD,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;KAChB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;GACR,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;IAC7C,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;GACV;EACD,CAAC;;EAED,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;GACR,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;EACvB,CAAC;CACF","file":"App.uvue","sourceRoot":"","sourcesContent":["<script lang=\"uts\">\r\n\r\n\tlet firstBackTime = 0\r\n\r\n\texport default {\r\n\t\tonLaunch() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t},\r\n\r\n\t\tonLastPageBackPress() {\r\n\t\t\tconsole.log('App LastPageBackPress')\r\n\t\t\tif (firstBackTime == 0) {\r\n\t\t\t\tuni.showToast({\r\n\t\t\t\t\ttitle: '再按一次退出应用',\r\n\t\t\t\t\tposition: 'bottom',\r\n\t\t\t\t})\r\n\t\t\t\tfirstBackTime = Date.now()\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tfirstBackTime = 0\r\n\t\t\t\t}, 2000)\r\n\t\t\t} else if (Date.now() - firstBackTime < 2000) {\r\n\t\t\t\tfirstBackTime = Date.now()\r\n\t\t\t\tuni.exit()\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tonExit() {\r\n\t\t\tconsole.log('App Exit')\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n\t.uni-row {\r\n\t\tflex-direction: row;\r\n\t}\r\n\r\n\t.uni-column {\r\n\t\tflex-direction: column;\r\n\t}\r\n</style>"]}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import App from './App.uvue'
|
||||||
|
|
||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function main(app: IApp) {
|
||||||
|
definePageRoutes();
|
||||||
|
defineAppConfig();
|
||||||
|
(createApp()['app'] as VueApp).mount(app, GenUniApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UniAppConfig extends io.dcloud.uniapp.appframe.AppConfig {
|
||||||
|
override name: string = "SkinCamera"
|
||||||
|
override appid: string = "__UNI__644401D"
|
||||||
|
override versionName: string = "1.0.0"
|
||||||
|
override versionCode: string = "100"
|
||||||
|
override uniCompilerVersion: string = "4.87"
|
||||||
|
|
||||||
|
constructor() { super() }
|
||||||
|
}
|
||||||
|
|
||||||
|
import GenPagesIndexIndexClass from './pages/index/index.uvue'
|
||||||
|
function definePageRoutes() {
|
||||||
|
__uniRoutes.push({ path: "pages/index/index", component: GenPagesIndexIndexClass, meta: { isQuit: true } as UniPageMeta, style: _uM([["navigationBarTitleText","uni-app x"]]) } as UniPageRoute)
|
||||||
|
}
|
||||||
|
const __uniTabBar: Map<string, any | null> | null = null
|
||||||
|
const __uniLaunchPage: Map<string, any | null> = _uM([["url","pages/index/index"],["style",_uM([["navigationBarTitleText","uni-app x"]])]])
|
||||||
|
function defineAppConfig(){
|
||||||
|
__uniConfig.entryPagePath = '/pages/index/index'
|
||||||
|
__uniConfig.globalStyle = _uM([["navigationBarTextStyle","black"],["navigationBarTitleText","uni-app x"],["navigationBarBackgroundColor","#F8F8F8"],["backgroundColor","#F8F8F8"]])
|
||||||
|
__uniConfig.getTabBarConfig = ():Map<string, any> | null => null
|
||||||
|
__uniConfig.tabBar = __uniConfig.getTabBarConfig()
|
||||||
|
__uniConfig.conditionUrl = ''
|
||||||
|
__uniConfig.uniIdRouter = _uM()
|
||||||
|
|
||||||
|
__uniConfig.ready = true
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('vue');
|
||||||
|
|
||||||
|
uni;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
takePhoto,
|
||||||
|
TakePhotoOption,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
GeneralCallbackResult
|
||||||
|
} from '@/uni_modules/Skin-cameraUTS'
|
||||||
|
|
||||||
|
|
||||||
|
const __sfc__ = defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
yanse: "red",
|
||||||
|
widthNum: 750,
|
||||||
|
heightNum: 200,
|
||||||
|
|
||||||
|
flash: "on",
|
||||||
|
device: "front",
|
||||||
|
imagePath: "",
|
||||||
|
videoPath: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO",
|
||||||
|
// "android.permission.WRITE_EXTERNAL_STORAGE"]
|
||||||
|
// // 请求权限
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// // 权限请求成功
|
||||||
|
// } else {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, _ : string[]) {
|
||||||
|
// //用户拒绝了部分权限
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onError(res : any) {
|
||||||
|
console.log(res, " at pages/index/index.uvue:54")
|
||||||
|
},
|
||||||
|
_takePhoto() {
|
||||||
|
takePhoto({
|
||||||
|
success: (res : TakePhotoSuccessCallbackResult) => {
|
||||||
|
console.log('takePhoto', res, " at pages/index/index.uvue:59")
|
||||||
|
this.imagePath = res.tempImagePath;
|
||||||
|
// _previewImage();
|
||||||
|
},
|
||||||
|
fail: (res : GeneralCallbackResult) => {
|
||||||
|
console.log('takePhoto', res, " at pages/index/index.uvue:64")
|
||||||
|
}
|
||||||
|
} as TakePhotoOption)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default __sfc__
|
||||||
|
function GenPagesIndexIndexRender(this: InstanceType<typeof __sfc__>): any | null {
|
||||||
|
const _ctx = this
|
||||||
|
const _cache = this.$.renderCache
|
||||||
|
return _cE("scroll-view", _uM({ class: "page-scroll-view" }), [
|
||||||
|
_cE("view", null, [
|
||||||
|
_cE("button", _uM({ onClick: _ctx._takePhoto }), "拍照", 8 /* PROPS */, ["onClick"]),
|
||||||
|
_cE(XfCameraComponent.name, _uM({
|
||||||
|
style: _nS(_uM({"height":"750rpx","background-color":"red"})),
|
||||||
|
onError: _ctx.onError,
|
||||||
|
flash: _ctx.flash,
|
||||||
|
position: _ctx.device
|
||||||
|
}), null, 44 /* STYLE, PROPS, NEED_HYDRATION */, ["onError", "flash", "position"]),
|
||||||
|
isTrue(_ctx.imagePath)
|
||||||
|
? _cE("image", _uM({
|
||||||
|
key: 0,
|
||||||
|
mode: "aspectFit",
|
||||||
|
src: _ctx.imagePath
|
||||||
|
}), null, 8 /* PROPS */, ["src"])
|
||||||
|
: _cC("v-if", true)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const GenPagesIndexIndexStyles = [_uM([["video", _pS(_uM([["width", "100%"], ["height", 200]]))]])]
|
||||||
|
|
||||||
|
import { XfCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy'
|
||||||
File diff suppressed because one or more lines are too long
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+474
@@ -0,0 +1,474 @@
|
|||||||
|
import ProcessCameraProvider from 'androidx.camera.lifecycle.ProcessCameraProvider'
|
||||||
|
import Preview from 'androidx.camera.core.Preview'
|
||||||
|
import MirrorMode from 'androidx.camera.core.MirrorMode'
|
||||||
|
import PreviewView from 'androidx.camera.view.PreviewView'
|
||||||
|
import Context from 'android.content.Context'
|
||||||
|
import FrameLayout from 'android.widget.FrameLayout'
|
||||||
|
import ViewGroup from 'android.view.ViewGroup'
|
||||||
|
import CameraSelector from 'androidx.camera.core.CameraSelector'
|
||||||
|
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
|
||||||
|
import UseCaseGroup from 'androidx.camera.core.UseCaseGroup'
|
||||||
|
import ContextCompat from 'androidx.core.content.ContextCompat'
|
||||||
|
|
||||||
|
import LifecycleOwner from 'androidx.lifecycle.LifecycleOwner'
|
||||||
|
import ImageCapture from 'androidx.camera.core.ImageCapture'
|
||||||
|
import Size from 'android.util.Size'
|
||||||
|
import ImageCaptureException from 'androidx.camera.core.ImageCaptureException'
|
||||||
|
import File from 'java.io.File'
|
||||||
|
|
||||||
|
import ImageProxy from 'androidx.camera.core.ImageProxy';
|
||||||
|
import CameraControl from 'androidx.camera.core.CameraControl';
|
||||||
|
|
||||||
|
import VideoCapture from 'androidx.camera.video.VideoCapture'
|
||||||
|
import Recorder from 'androidx.camera.video.Recorder'
|
||||||
|
import Recording from 'androidx.camera.video.Recording'
|
||||||
|
import FileOutputOptions from 'androidx.camera.video.FileOutputOptions'
|
||||||
|
import Quality from 'androidx.camera.video.Quality'
|
||||||
|
import QualitySelector from 'androidx.camera.video.QualitySelector'
|
||||||
|
import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent'
|
||||||
|
|
||||||
|
// import Bitmap from 'android.graphics.Bitmap';
|
||||||
|
// import FileOutputStream from 'java.io.FileOutputStream';
|
||||||
|
// import MediaMetadataRetriever from 'android.media.MediaMetadataRetriever';
|
||||||
|
|
||||||
|
import ExecutorService from 'java.util.concurrent.ExecutorService'
|
||||||
|
import Executors from 'java.util.concurrent.Executors'
|
||||||
|
import ImageFormat from 'android.graphics.ImageFormat';
|
||||||
|
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
TakePhotoSuccessCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
SetZoomSuccessCallbackResult,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
StartRecordTimeoutCallbackResult,
|
||||||
|
StopRecordSuccessCallbackResult,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
|
||||||
|
CameraConfig,
|
||||||
|
FlashMode,
|
||||||
|
DevicePosition,
|
||||||
|
Resolution,
|
||||||
|
//FrameSize,
|
||||||
|
QualityType,
|
||||||
|
OnCameraFrameListenerOption
|
||||||
|
} from '../interface'
|
||||||
|
import Bitmap from "android.graphics.Bitmap";
|
||||||
|
import FileOutputStream from "java.io.FileOutputStream";
|
||||||
|
|
||||||
|
const FLASH_MODE_MAP = new Map<string, Int>([
|
||||||
|
['auto', ImageCapture.FLASH_MODE_AUTO],
|
||||||
|
['on', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['torch', ImageCapture.FLASH_MODE_ON],
|
||||||
|
['off', ImageCapture.FLASH_MODE_OFF],
|
||||||
|
])
|
||||||
|
const DEVICE_POSITION_MAP = new Map<string, Int>([
|
||||||
|
['back', CameraSelector.LENS_FACING_BACK],
|
||||||
|
['front', CameraSelector.LENS_FACING_FRONT],
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
const RESOLUTION_MAP = new Map<string, Quality>([
|
||||||
|
['low', Quality.LOWEST],
|
||||||
|
['medium', Quality.HD],
|
||||||
|
['high', Quality.HIGHEST],
|
||||||
|
])
|
||||||
|
|
||||||
|
const QUALITY_MAP = new Map<string, Int>([
|
||||||
|
["high", ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY],
|
||||||
|
["low", ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY],
|
||||||
|
])
|
||||||
|
|
||||||
|
const MIRROR_MODE = new Map<boolean, Int>([
|
||||||
|
[true, MirrorMode.MIRROR_MODE_ON],
|
||||||
|
[false, MirrorMode.MIRROR_MODE_OFF],
|
||||||
|
])
|
||||||
|
|
||||||
|
export class CameraView extends FrameLayout {
|
||||||
|
private previewView : PreviewView;
|
||||||
|
public cameraProvider : ProcessCameraProvider | null = null;
|
||||||
|
public cameraControl : CameraControl | null = null;
|
||||||
|
private imageCapture : ImageCapture | null = null
|
||||||
|
private videoCapture : VideoCapture<Recorder> | null = null
|
||||||
|
private recording : Recording | null = null
|
||||||
|
private comp : UTSComponent<CameraView>;
|
||||||
|
private flashMode : FlashMode = 'auto'
|
||||||
|
private devicePosition : DevicePosition = 'back'
|
||||||
|
private resolution : Resolution = 'medium'
|
||||||
|
// private frameSize : FrameSize = 'medium'
|
||||||
|
private quality : QualityType = 'normal'
|
||||||
|
private videoFile : File | null = null
|
||||||
|
maxZoom : Float = (1).toFloat()
|
||||||
|
minZoom : Float = (1).toFloat()
|
||||||
|
private initialized : boolean = false
|
||||||
|
private isListening : boolean = false
|
||||||
|
private videoSelfieMirror : boolean | null = null
|
||||||
|
private cameraContextStopRecordOption : CameraContextStopRecordOption | null = null
|
||||||
|
private onCameraFrameCallback : OnCameraFrameCallback | null = null
|
||||||
|
private cameraExecutor : ExecutorService | null = null
|
||||||
|
private onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null;
|
||||||
|
|
||||||
|
constructor(context : Context, comp : UTSComponent<CameraView>, config : CameraConfig = {} as CameraConfig) {
|
||||||
|
super(context);
|
||||||
|
this.comp = comp
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
|
||||||
|
this.previewView = new PreviewView(context);
|
||||||
|
let layoutParam = new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
this.addView(this.previewView, layoutParam);
|
||||||
|
this.setupCamera()
|
||||||
|
}
|
||||||
|
setupCamera() {
|
||||||
|
// this.checkSelfPermission().then(() => {
|
||||||
|
let cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!)
|
||||||
|
cameraProviderFuture.addListener(() => {
|
||||||
|
try {
|
||||||
|
this.cameraProvider = cameraProviderFuture.get()
|
||||||
|
this.bindCamera();
|
||||||
|
} catch (e) {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("errMsg", JSON.stringify(e))
|
||||||
|
this.comp.$emit('error', ret)
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!))
|
||||||
|
// }).catch((err) => {
|
||||||
|
// let ret : Map<string, any> = new Map();
|
||||||
|
// ret.set("errMsg", err ?? '权限不足')
|
||||||
|
// this.comp.$emit('error', ret)
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
bindCamera() {
|
||||||
|
|
||||||
|
let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? Quality.HD;
|
||||||
|
let captureMode = QUALITY_MAP.get(this.quality) ?? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
|
||||||
|
|
||||||
|
// 创建一个Preview对象,用于显示相机预览
|
||||||
|
let preview = new Preview.Builder()
|
||||||
|
.build();
|
||||||
|
preview.setSurfaceProvider(this.previewView.getSurfaceProvider());
|
||||||
|
|
||||||
|
// 创建一个ImageCapture对象,用于拍照
|
||||||
|
let imageCapture = new ImageCapture.Builder()
|
||||||
|
.setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!)
|
||||||
|
.setCaptureMode(captureMode)
|
||||||
|
.build();
|
||||||
|
// 选择要使用的相机(后置或前置)
|
||||||
|
let cameraSelector = new CameraSelector.Builder()
|
||||||
|
.requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let recorder = Recorder.Builder()
|
||||||
|
.setQualitySelector(QualitySelector.from(targetResolution))
|
||||||
|
let mirrorMode = this.videoSelfieMirror == null ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MIRROR_MODE.get(this.videoSelfieMirror!);
|
||||||
|
let videoCapture = new VideoCapture.Builder(recorder.build())
|
||||||
|
.setMirrorMode(mirrorMode!)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.videoCapture = videoCapture
|
||||||
|
|
||||||
|
// 将参数应用到 VideoCapture
|
||||||
|
this.imageCapture = imageCapture;
|
||||||
|
this.cameraProvider!.unbindAll();
|
||||||
|
let imageAnalysis = ImageAnalysis.Builder()
|
||||||
|
// .setTargetResolution(new Size(1280, 720))
|
||||||
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
let viewPort = this.previewView.getViewPort()!
|
||||||
|
let useCaseGroup = new UseCaseGroup.Builder()
|
||||||
|
.addUseCase(preview)
|
||||||
|
.addUseCase(imageAnalysis)
|
||||||
|
.addUseCase(imageCapture)
|
||||||
|
.addUseCase(videoCapture)
|
||||||
|
.setViewPort(viewPort)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
let _this = this
|
||||||
|
class Analyzer implements ImageAnalysis.Analyzer {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
override analyze(image : ImageProxy) {
|
||||||
|
if (_this.isListening) {
|
||||||
|
let imageFormat = image.getFormat();
|
||||||
|
// 获取图像的宽度和高度
|
||||||
|
let width = image.width
|
||||||
|
let height = image.height
|
||||||
|
|
||||||
|
if (imageFormat == ImageFormat.YUV_420_888) {
|
||||||
|
_this.onCameraFrameCallback?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
|
||||||
|
_this.onCameraFrameListenerOption?.success?.({
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data: image
|
||||||
|
} as OnCameraFrameCallbackResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image.close();
|
||||||
|
}
|
||||||
|
override getDefaultTargetResolution() : Size {
|
||||||
|
// 返回默认的目标分辨率
|
||||||
|
return Size(640, 480)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageAnalysis.setAnalyzer(this.cameraExecutor!, new Analyzer() as ImageAnalysis.Analyzer)
|
||||||
|
|
||||||
|
let camera = this.cameraProvider!.bindToLifecycle(
|
||||||
|
UTSAndroid.getUniActivity()! as LifecycleOwner,
|
||||||
|
cameraSelector,
|
||||||
|
useCaseGroup
|
||||||
|
// preview,
|
||||||
|
// imageCapture,
|
||||||
|
// videoCapture
|
||||||
|
);
|
||||||
|
this.cameraControl = camera.getCameraControl();
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.initialized = true
|
||||||
|
let zoomState = camera.getCameraInfo().getZoomState().getValue();
|
||||||
|
this.maxZoom = zoomState?.getMaxZoomRatio() ?? (1).toFloat();
|
||||||
|
this.minZoom = zoomState?.getMinZoomRatio() ?? (1).toFloat();
|
||||||
|
let maxZoom = this.maxZoom
|
||||||
|
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
ret.set("maxZoom", maxZoom)
|
||||||
|
this.comp.$emit('ready', ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
unbindCamera() {
|
||||||
|
this.cameraProvider?.unbindAll()
|
||||||
|
this.cameraExecutor?.shutdown()
|
||||||
|
}
|
||||||
|
// checkSelfPermission() : Promise<boolean> {
|
||||||
|
// let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE"]
|
||||||
|
// return new Promise((resoleve, _) => {
|
||||||
|
// resoleve(true);
|
||||||
|
// UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, list : string[]) {
|
||||||
|
// if (allRight) {
|
||||||
|
// resoleve(true);
|
||||||
|
// } else {
|
||||||
|
// reject(list);
|
||||||
|
// }
|
||||||
|
// }, function (_ : boolean, grantedList : string[]) {
|
||||||
|
// //grantedList
|
||||||
|
// reject(grantedList);
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
setConfig(config : CameraConfig = {} as CameraConfig) {
|
||||||
|
this.flashMode = config.flash ?? 'auto'
|
||||||
|
this.devicePosition = config.devicePosition ?? 'back'
|
||||||
|
this.resolution = config.resolution ?? 'medium'
|
||||||
|
// this.frameSize = config.frameSize ?? 'medium'
|
||||||
|
}
|
||||||
|
setFlash(mode : FlashMode) {
|
||||||
|
this.flashMode = mode
|
||||||
|
if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null) return
|
||||||
|
this.imageCapture!.setFlashMode(FLASH_MODE_MAP.get(mode)!)
|
||||||
|
}
|
||||||
|
switchCamera(position : DevicePosition) {
|
||||||
|
this.devicePosition = position
|
||||||
|
if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null) return
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
isResolutionSupported(context : Context, cameraSelector : CameraSelector, targetResolution : Size) : boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
let quality = option.quality ?? "high";
|
||||||
|
this.quality = quality;
|
||||||
|
if (quality == 'low') {
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
const tempFile = new File(privateDir);
|
||||||
|
if (!tempFile.exists()) {
|
||||||
|
tempFile.mkdirs()
|
||||||
|
}
|
||||||
|
// 设置文件名
|
||||||
|
const photoFile = new File(privateDir, Date.now() + '.png');
|
||||||
|
|
||||||
|
// 处理镜像
|
||||||
|
let metadata = new ImageCapture.Metadata();
|
||||||
|
metadata.setReversedHorizontal(option.selfieMirror ?? DEVICE_POSITION_MAP.get(this.devicePosition)! == CameraSelector.LENS_FACING_FRONT);
|
||||||
|
|
||||||
|
const outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
|
||||||
|
class OnImageSavedCallback implements ImageCapture.OnImageSavedCallback {
|
||||||
|
override onImageSaved(outputFileResults : ImageCapture.OutputFileResults) {
|
||||||
|
option.success?.({
|
||||||
|
tempImagePath: photoFile.getAbsolutePath(),
|
||||||
|
quality: quality,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as TakePhotoSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
override onError(err : ImageCaptureException) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: err.toString()
|
||||||
|
}
|
||||||
|
option.fail?.(result)
|
||||||
|
option.complete?.(result)
|
||||||
|
}
|
||||||
|
override onCaptureStarted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.imageCapture!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), new OnImageSavedCallback() as ImageCapture.OnImageSavedCallback)
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
if (this.cameraControl != null) {
|
||||||
|
try {
|
||||||
|
this.cameraControl!.setZoomRatio(option.zoom.toFloat())
|
||||||
|
option.success?.({
|
||||||
|
zoom: option.zoom,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as SetZoomSuccessCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
} catch (e) {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: e.toString()
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result : GeneralCallbackResult = {
|
||||||
|
errMsg: '相机不存在'
|
||||||
|
}
|
||||||
|
option.complete?.(result)
|
||||||
|
option.fail?.(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) {
|
||||||
|
this.videoSelfieMirror = option.selfieMirror
|
||||||
|
this.unbindCamera()
|
||||||
|
this.bindCamera()
|
||||||
|
}
|
||||||
|
if (this.recording != null || this.videoCapture == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '已经在录制或相机不存在'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 设置输出目录
|
||||||
|
let privateDir = UTSAndroid.getAppCachePath()!;
|
||||||
|
// 设置文件名
|
||||||
|
this.videoFile = new File(privateDir, Date.now() + 'video.mp4');
|
||||||
|
let outputFileOptions = new FileOutputOptions.Builder(this.videoFile!).build();
|
||||||
|
|
||||||
|
|
||||||
|
this.recording = this.videoCapture!.output
|
||||||
|
.prepareRecording(UTSAndroid.getUniActivity()!, outputFileOptions)
|
||||||
|
.withAudioEnabled()
|
||||||
|
// .apply(()=>{
|
||||||
|
// })
|
||||||
|
.start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), (recordEvent : VideoRecordEvent) => {
|
||||||
|
if (recordEvent instanceof VideoRecordEvent.Start) {
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
|
||||||
|
if (option.timeout != null && option.timeout! > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.recording?.stop()
|
||||||
|
}, option.timeout!)
|
||||||
|
}
|
||||||
|
} else if (recordEvent instanceof VideoRecordEvent.Finalize) {
|
||||||
|
this.recording?.close()
|
||||||
|
this.recording = null
|
||||||
|
let tempVideoPath = this.videoFile?.getAbsolutePath()!
|
||||||
|
//let tempThumbPath = extractThumbnail(tempVideoPath) ?? ''
|
||||||
|
this.cameraContextStopRecordOption?.success?.({
|
||||||
|
tempVideoPath,
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as StopRecordSuccessCallbackResult)
|
||||||
|
|
||||||
|
option.timeoutCallback?.({
|
||||||
|
tempVideoPath,
|
||||||
|
} as StartRecordTimeoutCallbackResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
if (this.recording == null) {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未开始录制'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.cameraContextStopRecordOption = option
|
||||||
|
this.recording?.stop()
|
||||||
|
}
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) {
|
||||||
|
this.onCameraFrameCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.onCameraFrameListenerOption = option
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.isListening = true
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
cameraFrameOnStop(option ?: StopOption) {
|
||||||
|
this.isListening = false
|
||||||
|
option?.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // 从视频文件中提取封面的函数
|
||||||
|
// function extractThumbnail(videoUri : string) : string | null {
|
||||||
|
// let retriever = new MediaMetadataRetriever()
|
||||||
|
// retriever.setDataSource(videoUri)
|
||||||
|
// // 提取视频的某一帧作为封面
|
||||||
|
// let thumbnail = retriever.frameAtTime
|
||||||
|
// // 保存封面到文件
|
||||||
|
// if (thumbnail != null) {
|
||||||
|
// let privateDir = UTSAndroid.getAppCachePath();
|
||||||
|
// const thumbnailFile = new File(privateDir, Date.now() + '_thumbnail.jpg');
|
||||||
|
// let outputStream = FileOutputStream(thumbnailFile)
|
||||||
|
// thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||||
|
// outputStream.close()
|
||||||
|
// return thumbnailFile.getAbsolutePath()
|
||||||
|
// }
|
||||||
|
// retriever.release()
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
Vendored
+252
@@ -0,0 +1,252 @@
|
|||||||
|
import { CameraView } from './camera'
|
||||||
|
import {
|
||||||
|
TakePhotoOption,
|
||||||
|
GeneralCallbackResult,
|
||||||
|
CameraContextSetZoomOption,
|
||||||
|
CameraContextStartRecordOption,
|
||||||
|
CameraContextStopRecordOption,
|
||||||
|
// CameraFrameListener,
|
||||||
|
OnCameraFrameCallback,
|
||||||
|
// OnCameraFrameCallbackResult,
|
||||||
|
CameraFrameListenerStartOption,
|
||||||
|
StopOption,
|
||||||
|
TakePhoto,
|
||||||
|
SetZoom,
|
||||||
|
StartRecord,
|
||||||
|
StopRecord,
|
||||||
|
StartCameraFrame,
|
||||||
|
StopCameraFrame,
|
||||||
|
OnCameraFrameListenerOption,
|
||||||
|
SwitchOption,
|
||||||
|
SwitchCamera
|
||||||
|
} from '../interface'
|
||||||
|
import { CameraManager } from "./CameraManager";
|
||||||
|
|
||||||
|
|
||||||
|
class CameraFrameListener {
|
||||||
|
context : CameraContext
|
||||||
|
constructor(context : CameraContext) {
|
||||||
|
this.context = context
|
||||||
|
}
|
||||||
|
start() : void
|
||||||
|
start(option ?: CameraFrameListenerStartOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stop() : void
|
||||||
|
stop(option ?: StopOption) {
|
||||||
|
this.context.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraContext {
|
||||||
|
private mCameraFrameListener : CameraFrameListener | null = null
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
getCamera() : Promise<CameraView> {
|
||||||
|
let cameraPreview = CameraManager.getInstance().getCameraView()
|
||||||
|
if (cameraPreview != null) {
|
||||||
|
return Promise.resolve<CameraView>(cameraPreview);
|
||||||
|
}
|
||||||
|
let resolveFunc : ((res : CameraView) => void) | null = null
|
||||||
|
let rejectFunc : ((res : string) => void) | null = null
|
||||||
|
|
||||||
|
let maxAttempts : number = 5
|
||||||
|
let currentAttempt : number = 0
|
||||||
|
|
||||||
|
function attemptGetCamera() {
|
||||||
|
let camera = CameraManager.getInstance().getCameraView()
|
||||||
|
if (camera !== null) {
|
||||||
|
resolveFunc?.(camera)
|
||||||
|
} else if (currentAttempt < maxAttempts) {
|
||||||
|
currentAttempt++
|
||||||
|
setTimeout(() => {
|
||||||
|
attemptGetCamera()
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
rejectFunc?.('Camera not found after multiple attempts')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise<CameraView>((resolve, reject) => {
|
||||||
|
resolveFunc = resolve
|
||||||
|
rejectFunc = reject
|
||||||
|
attemptGetCamera()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
takePhoto(option : TakePhotoOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.takePhoto(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
setZoom(option : CameraContextSetZoomOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.setZoom(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
startRecord(option : CameraContextStartRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.startRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
stopRecord(option : CameraContextStopRecordOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.stopRecord(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onCameraFrame() : CameraFrameListener
|
||||||
|
onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener {
|
||||||
|
if (this.mCameraFrameListener == null) {
|
||||||
|
this.mCameraFrameListener = new CameraFrameListener(this)
|
||||||
|
}
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrame(callback)
|
||||||
|
})
|
||||||
|
return this.mCameraFrameListener!
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
|
onCameraFrameListener(option : OnCameraFrameListenerOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.onCameraFrameListener(option)
|
||||||
|
}).catch(() => {
|
||||||
|
__f__('log','at uni_modules/Skin-cameraUTS/utssdk/app-android/index.uts:145',"未找到相机")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStart(option : CameraFrameListenerStartOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStart(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraFrameOnStop(option : StopOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.cameraFrameOnStop(option)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
switchCamera(option : SwitchOption) {
|
||||||
|
this.getCamera().then((camera : CameraView) => {
|
||||||
|
camera.switchCamera(option.position)
|
||||||
|
|
||||||
|
option.success?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: 'ok'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
}).catch(() => {
|
||||||
|
option.fail?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
option.complete?.({
|
||||||
|
errMsg: '未找到相机'
|
||||||
|
} as GeneralCallbackResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let context : CameraContext = new CameraContext();
|
||||||
|
|
||||||
|
export function createCameraContext() : CameraContext {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export const takePhoto : TakePhoto = function (options : TakePhotoOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.takePhoto(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.setZoom(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.startRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.stopRecord(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@UTSJS.keepAlive
|
||||||
|
export function onCameraFrameListener(options : OnCameraFrameListenerOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.onCameraFrameListener(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStart(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopCameraFrame : StopCameraFrame = function (options : StopOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.cameraFrameOnStop(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const switchCamera : SwitchCamera = function (options : SwitchOption) {
|
||||||
|
UTSAndroid.getDispatcher("main").async(function (_) {
|
||||||
|
context.switchCamera(options)
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
Vendored
+151
@@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import { CameraView } from './camera';
|
||||||
|
import { CameraConfig } from '../interface'
|
||||||
|
import { CameraManager } from './CameraManager';
|
||||||
|
|
||||||
|
//原生提供以下属性或方法的实现
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 组件名称,也就是开发者使用的标签
|
||||||
|
*/
|
||||||
|
name: "xf-camera",
|
||||||
|
/**
|
||||||
|
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
|
||||||
|
*/
|
||||||
|
emits: ['stop', 'error', 'ready'],
|
||||||
|
/**
|
||||||
|
* 属性声明,组件的使用者会传递这些属性值到组件
|
||||||
|
*/
|
||||||
|
props: {
|
||||||
|
"mode": {
|
||||||
|
type: String,
|
||||||
|
default: "normal"
|
||||||
|
},
|
||||||
|
"resolution": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // low | high
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
type: String,
|
||||||
|
default: "back" // front前置 |back 后置
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
type: String,
|
||||||
|
default: "auto" // auto, on, off, torch
|
||||||
|
},
|
||||||
|
"frameSize": {
|
||||||
|
type: String,
|
||||||
|
default: "medium" // small|large
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件内部变量声明
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 属性变化监听器实现
|
||||||
|
*/
|
||||||
|
watch: {
|
||||||
|
"position": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.switchCamera(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
},
|
||||||
|
"flash": {
|
||||||
|
/**
|
||||||
|
* 这里监听属性变化,并进行组件内部更新
|
||||||
|
*/
|
||||||
|
handler(newValue : string, _ : string) {
|
||||||
|
this.$el?.setFlash(newValue)
|
||||||
|
},
|
||||||
|
immediate: false // 创建时是否通过此方法更新属性,默认值为false
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
|
||||||
|
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
|
||||||
|
*/
|
||||||
|
expose: [],
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件被创建,组件第一个生命周期,
|
||||||
|
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 对应平台的view载体即将被创建,对应前端beforeMount
|
||||||
|
*/
|
||||||
|
NVBeforeLoad() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [必须实现] 创建原生View,必须定义返回值类型
|
||||||
|
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
|
||||||
|
* (Android需要明确知道View类型,需特殊校验)
|
||||||
|
*/
|
||||||
|
NVLoad() : CameraView {
|
||||||
|
let previewView = new CameraView(this.$androidContext!, this)
|
||||||
|
previewView.setTag("9527");
|
||||||
|
CameraManager.getInstance().setCameraView(previewView);
|
||||||
|
return previewView
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已创建
|
||||||
|
*/
|
||||||
|
NVLoaded() {
|
||||||
|
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View布局完成
|
||||||
|
*/
|
||||||
|
NVLayouted() {
|
||||||
|
this.$el?.setConfig({
|
||||||
|
resolution: this.resolution,
|
||||||
|
frameSize: this.frameSize,
|
||||||
|
flash: this.flash
|
||||||
|
} as CameraConfig)
|
||||||
|
this.$el?.switchCamera(this.position)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View将释放
|
||||||
|
*/
|
||||||
|
NVBeforeUnload() {
|
||||||
|
let ret : Map<string, any> = new Map();
|
||||||
|
this.$emit('stop', ret)
|
||||||
|
|
||||||
|
this.$el?.unbindCamera()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 原生View已释放,这里可以做释放View之后的操作
|
||||||
|
*/
|
||||||
|
NVUnloaded() {
|
||||||
|
CameraManager.getInstance().dispose();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* [可选实现] 组件销毁
|
||||||
|
*/
|
||||||
|
unmounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<view>
|
||||||
|
</view>
|
||||||
|
*/
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
import { CameraView } from "./camera";
|
||||||
|
|
||||||
|
export class CameraManager {
|
||||||
|
private cameraView : CameraView | null = null;
|
||||||
|
|
||||||
|
private static instance : CameraManager | null = null;
|
||||||
|
|
||||||
|
public static getInstance() : CameraManager {
|
||||||
|
if (CameraManager.instance == null) {
|
||||||
|
CameraManager.instance = new CameraManager();
|
||||||
|
}
|
||||||
|
return CameraManager.instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCameraView(cameraView ?: CameraView) : void {
|
||||||
|
this.cameraView = cameraView!
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCameraView() : CameraView | null {
|
||||||
|
return this.cameraView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose() : void {
|
||||||
|
this.cameraView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user