5 Commits
Author SHA1 Message Date
xsl 9a220464aa success 2026-03-13 13:28:03 +08:00
xsl d6b48a4b47 chang to skin 2026-03-13 11:33:24 +08:00
xsl 55c64a6db4 success 2026-03-12 21:13:07 +08:00
xsl f7982d45de save code 2026-03-12 20:25:20 +08:00
xsl ba06dec1e4 save code 2026-03-12 19:36:52 +08:00
151 changed files with 15586 additions and 42 deletions
+5
View File
@@ -9,6 +9,11 @@
"customPlaygroundType" : "local", "customPlaygroundType" : "local",
"playground" : "custom", "playground" : "custom",
"type" : "uni-app:app-ios" "type" : "uni-app:app-ios"
},
{
"customPlaygroundType" : "local",
"playground" : "custom",
"type" : "uni-app:app-android"
} }
] ]
} }
+10 -1
View File
@@ -58,7 +58,16 @@
"distribute": { "distribute": {
"modules": {}, "modules": {},
"icons": {}, "icons": {},
"splashScreens": {} "splashScreens": {},
"privacyDescription": {
"NSPhotoLibraryUsageDescription": "读取相册",
"NSPhotoLibraryAddUsageDescription": "写入相册",
"NSCameraUsageDescription": "要摄像头",
"NSMicrophoneUsageDescription": "要麦克风",
"NSLocationWhenInUseUsageDescription": "要访问",
"NSLocationAlwaysUsageDescription": "要访问",
"NSLocationAlwaysAndWhenInUseUsageDescription": "要访问"
}
} }
} }
} }
+75 -39
View File
@@ -1,41 +1,77 @@
<template> <template>
<view> <!-- #ifdef APP -->
<image class="logo" src="/static/logo.png"></image> <scroll-view class="page-scroll-view">
<text class="title">{{title}}</text> <!-- #endif -->
<xf-camera style="height:750rpx; background-color: red;" @error="onError" :flash="flash" <view>
:position="device"></xf-camera> <button @click="_takePhoto">拍照</button>
<image v-if="imagePath" mode="aspectFit" :src="imagePath"></image> <skin-camera style="height:750rpx; background-color: red;" @error="onError" :flash="flash"
</view> :position="device"></skin-camera>
</template> <image v-if="imagePath" mode="aspectFit" :src="imagePath"></image>
</view>
<script setup lang="uts"> <!-- #ifdef APP -->
const title = ref('Hello') </scroll-view>
// 相机闪光灯状态('off' | 'on' | 'auto' <!-- #endif -->
const flash = ref('off') </template>
<script>
import {
takePhoto,
TakePhotoOption,
TakePhotoSuccessCallbackResult,
GeneralCallbackResult
} from '@/uni_modules/Skin-cameraUTS'
// 相机朝向('front' | 'back'
const device = ref('back') export default {
data() {
// 拍摄的图片路径 return {
const imagePath = ref('') yanse: "red",
widthNum: 750,
// 相机错误处理 heightNum: 200,
const onError = (e : any) => {
console.log('相机错误:', e) flash: "on",
// 可在此添加用户提示,如 uni.showToast device: "front",
} imagePath: "",
</script> videoPath: "https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4"
}
<style> },
.logo { onLoad() {
height: 100px; // let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO",
width: 100px; // "android.permission.WRITE_EXTERNAL_STORAGE"]
margin: 100px auto 25px auto; // // 请求权限
} // UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, _ : string[]) {
// if (allRight) {
.title { // // 权限请求成功
font-size: 18px; // } else {
color: #8f8f94; // //用户拒绝了部分权限
text-align: center; // }
} // }, 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)
}
}
}
</script>
<style>
.video {
width: 100%;
height: 200px;
}
</style> </style>
@@ -13,7 +13,7 @@
/** /**
* 组件名称,也就是开发者使用的标签 * 组件名称,也就是开发者使用的标签
*/ */
name: "xf-camera", name: "skin-camera",
/** /**
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送 * 组件涉及的事件声明,只有声明过的事件,才能被正常发送
*/ */
@@ -17,7 +17,7 @@
/** /**
* 组件名称,也就是开发者使用的标签 * 组件名称,也就是开发者使用的标签
*/ */
name: "xf-camera", name: "skin-camera",
/** /**
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送 * 组件涉及的事件声明,只有声明过的事件,才能被正常发送
*/ */
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<skin-camera style=\"height:750rpx; background-color: red;\" @error=\"onError\" :flash=\"flash\"\n\t\t\t\t:position=\"device\"></skin-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\tlet 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\tUTSAndroid.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;YACL,IAAI,iBAAiB;gBAAC;gBAA6B;gBAC9C;aAA2C;YAEhD,WAAW,uBAAuB,CAAC,WAAW,cAAc,MAAK,gBAAgB,IAAU,UAAW,OAAO,EAAE,YAAI,MAAM,CAAE,EAAA;gBAC1H,IAAI,UAAU,EAId;YACD;cAAG,IAAU,GAAI,OAAO,EAAE,YAAI,MAAM,CAAE,EAAA,CAEtC;QACD;;;;;;;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,oBAAA,IACkC,EAAA,IADrB,WAA6C,IAA7C,IAAA,YAAA,UAAA,sBAAA,SAA+C,aAAO,KAAA,OAAO,EAAG,WAAO,KAAA,KAAK,EACvF,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
View File
@@ -0,0 +1,16 @@
{
"version": "1",
"env": {
"compiler_version": "4.87"
},
"files": {
"index.kt": {
"md5": "3d228610d7ea1f50297ff2c98f1d707625ffc2ff",
"class": ""
},
"pages/index/index.kt": {
"class": "GenPagesIndexIndex",
"md5": "12fc17b12c330ffd7c1f37c11fd8fd0ff8fb9d77"
}
}
}
+126
View File
@@ -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
}
+104
View File
@@ -0,0 +1,104 @@
@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.SkinCameraComponent
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) {
var permissionNeed = _uA(
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO",
"android.permission.WRITE_EXTERNAL_STORAGE"
)
UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!!, permissionNeed, fun(allRight: Boolean, _: UTSArray<String>) {
if (allRight) {}
}
, fun(_: Boolean, _: UTSArray<String>) {})
}
, __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(SkinCameraComponent.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
@@ -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\"}"}
@@ -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\"}"}
@@ -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\"}"}
+1
View File
@@ -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<skin-camera style=\"height:750rpx; background-color: red;\" @error=\"onError\" :flash=\"flash\"\n\t\t\t\t:position=\"device\"></skin-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,sBAAsB,CAAE;AAG9B,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
@@ -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": "b01794e3f083baf1b8aceebeab658367"
}
}
}
+1
View File
@@ -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"]}
+1
View File
@@ -0,0 +1 @@
{"dir":"/Volumes/Elements/srv_release/bdserver.dcloud.net.cn/service/storage/app/package/IOS/queue/4c5045b0-1e94-11f1-8555-e529708e47ad/wgtRoot/__UNI__644401D/"}
@@ -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": "d0eb186ea281ac8fa064d51ff53209b1"
}
}
}
+1
View File
@@ -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.
+39
View File
@@ -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"]]))]])]
+41
View File
@@ -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
}
+5
View File
@@ -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(SkinCameraComponent.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 { SkinCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy'
@@ -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;
}
}
@@ -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
// }
@@ -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)
}
@@ -0,0 +1,151 @@
//
//
//
//
//
import { CameraView } from './camera';
import { CameraConfig } from '../interface'
import { CameraManager } from './CameraManager';
//原生提供以下属性或方法的实现
export default {
/**
* 组件名称,也就是开发者使用的标签
*/
name: "skin-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>
*/
@@ -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;
}
}
@@ -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)
}
}
@@ -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)
}
@@ -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: "skin-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>
*/
@@ -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
@@ -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;
}
}
@@ -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
// }
@@ -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)
}
@@ -0,0 +1,151 @@
//
//
//
//
//
import { CameraView } from './camera';
import { CameraConfig } from '../interface'
import { CameraManager } from './CameraManager';
//原生提供以下属性或方法的实现
export default {
/**
* 组件名称,也就是开发者使用的标签
*/
name: "skin-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>
*/
@@ -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;
}
}
@@ -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)
}
}
@@ -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)
}
@@ -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: "skin-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>
*/
@@ -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
View File
@@ -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
View File
@@ -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,71 @@
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(SkinCameraComponent.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 { SkinCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy';
@@ -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;
}
}
@@ -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
// }
@@ -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);
};
@@ -0,0 +1,140 @@
//
//
//
//
//
import { CameraView } from './camera';
import { CameraConfig } from '../interface';
import { CameraManager } from './CameraManager';
//原生提供以下属性或方法的实现
export default {
/**
* 组件名称,也就是开发者使用的标签
*/
name: "skin-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>
*/
@@ -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,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;
}
}
@@ -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);
}
}
@@ -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);
};
@@ -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: "skin-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>
*/
@@ -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,103 @@
@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.SkinCameraComponent
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) {
var permissionNeed = _uA(
"android.permission.CAMERA",
"android.permission.RECORD_AUDIO",
"android.permission.WRITE_EXTERNAL_STORAGE"
)
UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!!, permissionNeed, fun(allRight: Boolean, _: UTSArray<String>) {
if (allRight) {}
}
, fun(_: Boolean, _: UTSArray<String>) {})
}
, __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(SkinCameraComponent.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
View File
@@ -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

@@ -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>
@@ -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": "skin-camera",
"class": "uts.sdk.modules.SkinCameraUTS.SkinCameraComponent"
}
]
}
@@ -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 SkinCameraComponent : 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 = "skin-camera"
init {
io.dcloud.uniapp.UniSDKEngine.registerUniComponent("skin-camera", SkinCameraComponent::class.java, SkinCameraElement::class.java)
}
}
}
open class SkinCameraElement : 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
View File
@@ -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
View File
@@ -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 SkinCameraComponent = {};
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("skin-camera"), SkinCameraComponent);
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("skin-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
View File
@@ -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": "要访问"
}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,10 @@
{
"deploymentTarget": "11",
"components": [
{
"name": "skin-camera",
"class": "UTSSDKModulesSkinCameraUTSSkinCameraComponent",
"delegateClass": "SkinCameraComponentRegister"
}
]
}
File diff suppressed because it is too large Load Diff
+39
View File
@@ -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"]]))]])]
+1
View File
@@ -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
View File
@@ -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
}
+5
View File
@@ -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(SkinCameraComponent.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 { SkinCameraComponent } from '@/uni_modules/Skin-cameraUTS?uts-proxy'
File diff suppressed because one or more lines are too long
@@ -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;
}
}
@@ -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
// }
@@ -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)
}

Some files were not shown because too many files have changed in this diff Show More