JLBA202505130001_关于吉林银行新建AI智能培训系统的需求_修改安卓工程的权限列表,适配安卓13+
This commit is contained in:
@@ -1,38 +1,38 @@
|
||||
{
|
||||
"version": "1",
|
||||
"prompt": "template",
|
||||
"title": "个人信息保护提示",
|
||||
"message": "欢迎使用吉AI学!<br/> 请你务必审慎阅读、充分理解<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>各条款,帮助您了解我们为您提供的服务、我们如何处理个人信息以及您享有的权利。我们会严格按照相关法律法规要求,采取各种安全措施来保护您的个人信息。<br/> 如果你同意,请点击下面按钮开始接受我们的服务。<br/>1.为了保障软件的安全运行和账户安全,我们会申请手机您的设备信息、IP地址、WLAN MAC地址。<br/>2.上传或拍摄图片,需要使用您的媒体影音、图片、视频、音频、相机、等权限。<br/>3.为了实现AI问答、课程学习等APP内功能,我们需要使用您的麦克风权限。",
|
||||
"buttonAccept": "同意并接受",
|
||||
"buttonRefuse": "暂不同意",
|
||||
"hrefLoader": "system",
|
||||
"backToExit":"true",
|
||||
"second": {
|
||||
"title": "确认提示",
|
||||
"message": " 进入应用前,你需先同意<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>,否则将退出应用。",
|
||||
"buttonAccept": "同意并继续",
|
||||
"buttonRefuse": "退出应用"
|
||||
},
|
||||
"disagreeMode":{
|
||||
"support": false,
|
||||
"loadNativePlugins": false,
|
||||
"visitorEntry": false,
|
||||
"showAlways": false
|
||||
},
|
||||
"styles": {
|
||||
"backgroundColor": "#fff",
|
||||
"borderRadius":"5px",
|
||||
"title": {
|
||||
"color": "#000"
|
||||
"version" : "1",
|
||||
"prompt" : "template",
|
||||
"title" : "个人信息保护提示",
|
||||
"message" : "欢迎使用吉AI学!<br/> 请你务必审慎阅读、充分理解<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>各条款,帮助您了解我们为您提供的服务、我们如何处理个人信息以及您享有的权利。我们会严格按照相关法律法规要求,采取各种安全措施来保护您的个人信息。<br/> 如果你同意,请点击下面按钮开始接受我们的服务。<br/>1.为了保障软件的安全运行和账户安全,我们会申请手机您的设备信息、IP地址、WLAN MAC地址。<br/>2.上传或拍摄图片,需要使用您的媒体影音、图片、视频、音频、相机、等权限。<br/>3.为了实现AI问答、课程学习等APP内功能,我们需要使用您的麦克风权限。",
|
||||
"buttonAccept" : "同意并接受",
|
||||
"buttonRefuse" : "暂不同意",
|
||||
"hrefLoader" : "system",
|
||||
"backToExit" : "true",
|
||||
"second" : {
|
||||
"title" : "确认提示",
|
||||
"message" : " 进入应用前,你需先同意<a href=\"https://aits.jlbank.com.cn:7001/traapp/dfAgtInfo/queryValidDfAgtHtml\">《用户隐私协议》</a>,否则将退出应用。",
|
||||
"buttonAccept" : "同意并继续",
|
||||
"buttonRefuse" : "退出应用"
|
||||
},
|
||||
"buttonAccept": {
|
||||
"color": "#F91B59"
|
||||
"disagreeMode" : {
|
||||
"support" : false,
|
||||
"loadNativePlugins" : false,
|
||||
"visitorEntry" : false,
|
||||
"showAlways" : false
|
||||
},
|
||||
"buttonRefuse": {
|
||||
"color": "#333"
|
||||
},
|
||||
"buttonVisitor": {
|
||||
"color": "#00ffff"
|
||||
"styles" : {
|
||||
"backgroundColor" : "#fff",
|
||||
"borderRadius" : "5px",
|
||||
"title" : {
|
||||
"color" : "#000"
|
||||
},
|
||||
"buttonAccept" : {
|
||||
"color" : "#F91B59"
|
||||
},
|
||||
"buttonRefuse" : {
|
||||
"color" : "#333"
|
||||
},
|
||||
"buttonVisitor" : {
|
||||
"color" : "#00ffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,10 +5,10 @@
|
||||
"iPad"
|
||||
],
|
||||
"id": "__UNI__EDE0E52",
|
||||
"name": "tra-app",
|
||||
"name": "吉AI学",
|
||||
"version": {
|
||||
"name": "0.0.8",
|
||||
"code": 8
|
||||
"name": "0.2.1",
|
||||
"code": 277
|
||||
},
|
||||
"description": "",
|
||||
"developer": {
|
||||
@@ -73,6 +73,38 @@
|
||||
},
|
||||
"useOriginalMsgbox": true
|
||||
},
|
||||
"icons": {
|
||||
"android": {
|
||||
"hdpi": "unpackage/res/icons/72x72.png",
|
||||
"xhdpi": "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi": "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi": "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios": {
|
||||
"appstore": "unpackage/res/icons/1024x1024.png",
|
||||
"ipad": {
|
||||
"app": "unpackage/res/icons/76x76.png",
|
||||
"app@2x": "unpackage/res/icons/152x152.png",
|
||||
"notification": "unpackage/res/icons/20x20.png",
|
||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x": "unpackage/res/icons/167x167.png",
|
||||
"settings": "unpackage/res/icons/29x29.png",
|
||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||
"spotlight": "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x": "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone": {
|
||||
"app@2x": "unpackage/res/icons/120x120.png",
|
||||
"app@3x": "unpackage/res/icons/180x180.png",
|
||||
"notification@2x": "unpackage/res/icons/40x40.png",
|
||||
"notification@3x": "unpackage/res/icons/60x60.png",
|
||||
"settings@2x": "unpackage/res/icons/58x58.png",
|
||||
"settings@3x": "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x": "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x": "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"google": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
@@ -105,10 +137,9 @@
|
||||
"NSMicrophoneUsageDescription": "此应用需要访问您的麦克风来录制音频"
|
||||
},
|
||||
"capabilities": {
|
||||
"entitlements": {
|
||||
"com.apple.developer.avfoundation.multitasking-camera-access": true
|
||||
}
|
||||
}
|
||||
"entitlements": {}
|
||||
},
|
||||
"idfa": false
|
||||
},
|
||||
"plugins": {
|
||||
"audio": {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
.uni-popup[data-v-4d2f82f0]{position:fixed;z-index:99}.uni-popup.top[data-v-4d2f82f0],.uni-popup.left[data-v-4d2f82f0],.uni-popup.right[data-v-4d2f82f0]{top:0}.uni-popup .uni-popup__wrapper[data-v-4d2f82f0]{display:block;position:relative}.uni-popup .uni-popup__wrapper.left[data-v-4d2f82f0],.uni-popup .uni-popup__wrapper.right[data-v-4d2f82f0]{padding-top:0;flex:1}.fixforpc-z-index[data-v-4d2f82f0]{z-index:999}.fixforpc-top[data-v-4d2f82f0]{top:0}.overlay-box[data-v-ab866bd0]{position:fixed;left:0;top:0;width:100vw;height:100vh;z-index:999;background-color:rgba(0,0,0,.65)}.overlay-box uni-movable-view[data-v-ab866bd0]{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.overlay-box uni-movable-area[data-v-ab866bd0]{height:100%;width:100%;position:fixed;overflow:hidden}.overlay-box uni-movable-view uni-image[data-v-ab866bd0]{width:100%}.back_div[data-v-41596618]{position:absolute;top:calc(var(--status-bar-height));left:.625rem;padding:.3125rem .3125rem .3125rem .625rem}.back_div .back-icon[data-v-41596618]{position:relative;width:1.5625rem;height:1.5625rem;cursor:pointer}.back_div .back-icon[data-v-41596618]:before,.back_div .back-icon[data-v-41596618]:after{content:"";position:absolute;transition:all .3s ease}.back_div .back-icon[data-v-41596618]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #eaeaea;border-left:.125rem solid #eaeaea;transform:rotate(-45deg)}.title_div[data-v-41596618]{width:60vw;text-shadow:.3125rem 0 .9375rem #3c3c3c}.title_div .img[data-v-41596618]{width:100%}.tip_div[data-v-41596618]{text-shadow:0 0 .625rem #ceceea}.tip_div .tip_msg[data-v-41596618]{font-family:PingFangSC;font-weight:400;font-size:4vw;color:#fff;line-height:1.25rem;text-align:center;font-style:normal}.mascot_div[data-v-41596618]{width:100%;height:100%;display:flex;align-items:center;position:relative}.mascot_div .swiper[data-v-41596618]{white-space:nowrap;height:120vw;flex:1}.mascot_div .swiper-item-div[data-v-41596618]{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;height:120vw}.mascot_div .swiper-item-div .img[data-v-41596618]{width:80vw;height:100%}.mascot_div .doct_icon[data-v-41596618]{padding:0%;position:absolute;width:13vw;display:flex;align-items:center;flex-direction:column}.mascot_div .doct_icon .doct_icon_div[data-v-41596618]{width:100%;display:flex;align-items:center;justify-content:center;padding:1.25rem 0}.mascot_div .doct_icon.left[data-v-41596618]{top:40%;left:2%;z-index:10}.mascot_div .doct_icon.right[data-v-41596618]{top:40%;right:2%}.mascot_div .doct_icon .img[data-v-41596618]{width:40%}.button_div[data-v-41596618]{margin-top:.625rem;margin-bottom:3.125rem;width:80%;height:16vw;position:relative}.button_div .img[data-v-41596618]{position:absolute;width:100%;height:100%}.bg_img[data-v-41596618]{position:absolute;top:-2%;left:-1%;width:102%;height:104%;z-index:-1}.bg_img .img[data-v-41596618]{width:100%;height:100%}.main_div[data-v-41596618]{display:flex;flex-direction:column;position:relative;align-items:center;overflow:hidden}
|
||||
.uni-popup[data-v-7f3fc735]{position:fixed;z-index:99}.uni-popup.top[data-v-7f3fc735],.uni-popup.left[data-v-7f3fc735],.uni-popup.right[data-v-7f3fc735]{top:0}.uni-popup .uni-popup__wrapper[data-v-7f3fc735]{display:block;position:relative}.uni-popup .uni-popup__wrapper.left[data-v-7f3fc735],.uni-popup .uni-popup__wrapper.right[data-v-7f3fc735]{padding-top:0;flex:1}.fixforpc-z-index[data-v-7f3fc735]{z-index:999}.fixforpc-top[data-v-7f3fc735]{top:0}.overlay-box[data-v-ab866bd0]{position:fixed;left:0;top:0;width:100vw;height:100vh;z-index:999;background-color:rgba(0,0,0,.65)}.overlay-box uni-movable-view[data-v-ab866bd0]{display:flex;align-items:center;justify-content:center;width:100%;height:100%}.overlay-box uni-movable-area[data-v-ab866bd0]{height:100%;width:100%;position:fixed;overflow:hidden}.overlay-box uni-movable-view uni-image[data-v-ab866bd0]{width:100%}.back_div[data-v-41596618]{position:absolute;top:calc(var(--status-bar-height));left:.625rem;padding:.3125rem .3125rem .3125rem .625rem}.back_div .back-icon[data-v-41596618]{position:relative;width:1.5625rem;height:1.5625rem;cursor:pointer}.back_div .back-icon[data-v-41596618]:before,.back_div .back-icon[data-v-41596618]:after{content:"";position:absolute;transition:all .3s ease}.back_div .back-icon[data-v-41596618]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #eaeaea;border-left:.125rem solid #eaeaea;transform:rotate(-45deg)}.title_div[data-v-41596618]{width:60vw;text-shadow:.3125rem 0 .9375rem #3c3c3c}.title_div .img[data-v-41596618]{width:100%}.tip_div[data-v-41596618]{text-shadow:0 0 .625rem #ceceea}.tip_div .tip_msg[data-v-41596618]{font-family:PingFangSC;font-weight:400;font-size:4vw;color:#fff;line-height:1.25rem;text-align:center;font-style:normal}.mascot_div[data-v-41596618]{width:100%;height:100%;display:flex;align-items:center;position:relative}.mascot_div .swiper[data-v-41596618]{white-space:nowrap;height:120vw;flex:1}.mascot_div .swiper-item-div[data-v-41596618]{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;height:120vw}.mascot_div .swiper-item-div .img[data-v-41596618]{width:80vw;height:100%}.mascot_div .doct_icon[data-v-41596618]{padding:0%;position:absolute;width:13vw;display:flex;align-items:center;flex-direction:column}.mascot_div .doct_icon .doct_icon_div[data-v-41596618]{width:100%;display:flex;align-items:center;justify-content:center;padding:1.25rem 0}.mascot_div .doct_icon.left[data-v-41596618]{top:40%;left:2%;z-index:10}.mascot_div .doct_icon.right[data-v-41596618]{top:40%;right:2%}.mascot_div .doct_icon .img[data-v-41596618]{width:40%}.button_div[data-v-41596618]{margin-top:.625rem;margin-bottom:3.125rem;width:80%;height:16vw;position:relative}.button_div .img[data-v-41596618]{position:absolute;width:100%;height:100%}.bg_img[data-v-41596618]{position:absolute;top:-2%;left:-1%;width:102%;height:104%;z-index:-1}.bg_img .img[data-v-41596618]{width:100%;height:100%}.main_div[data-v-41596618]{display:flex;flex-direction:column;position:relative;align-items:center;overflow:hidden}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
.nav-bar[data-v-6f106210]{width:100%;height:var(--status-bar-height);position:fixed}.nav-bar-seat[data-v-6f106210]{width:100%;height:var(--status-bar-height)}.nav-bar-div[data-v-6f106210]{z-index:1900;width:100%;position:relative}uni-view[data-v-843a5998],uni-scroll-view[data-v-843a5998],uni-swiper-item[data-v-843a5998]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}@font-face{font-family:uvicon-iconfont;src:url(../../assets/uvicons.04d281cc.ttf) format("truetype")}.uv-icon[data-v-843a5998]{display:flex;align-items:center}.uv-icon--left[data-v-843a5998]{flex-direction:row-reverse;align-items:center}.uv-icon--right[data-v-843a5998]{flex-direction:row;align-items:center}.uv-icon--top[data-v-843a5998]{flex-direction:column-reverse;justify-content:center}.uv-icon--bottom[data-v-843a5998]{flex-direction:column;justify-content:center}.uv-icon__icon[data-v-843a5998]{font-family:uvicon-iconfont;position:relative;display:flex;flex-direction:row;align-items:center}.uv-icon__icon--primary[data-v-843a5998]{color:#06f}.uv-icon__icon--success[data-v-843a5998]{color:#5ac725}.uv-icon__icon--error[data-v-843a5998]{color:#f56c6c}.uv-icon__icon--warning[data-v-843a5998]{color:#f9ae3d}.uv-icon__icon--info[data-v-843a5998]{color:#909399}.uv-icon__img[data-v-843a5998]{height:auto;will-change:transform}.uv-icon__label[data-v-843a5998]{line-height:1}.uv-border[data-v-1b8a7a55]{border-width:.5px!important;border-color:#dadbde!important;border-style:solid}.uv-border-bottom[data-v-1b8a7a55]{border-bottom-width:.5px!important;border-color:#dadbde!important;border-bottom-style:solid}uni-view[data-v-1b8a7a55],uni-scroll-view[data-v-1b8a7a55],uni-swiper-item[data-v-1b8a7a55]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-input[data-v-1b8a7a55]{display:flex;flex-direction:row;align-items:center;justify-content:space-between;flex:1}.uv-input--radius[data-v-1b8a7a55],.uv-input--square[data-v-1b8a7a55]{border-radius:4px}.uv-input--no-radius[data-v-1b8a7a55]{border-radius:0}.uv-input--circle[data-v-1b8a7a55]{border-radius:100px}.uv-input__content[data-v-1b8a7a55]{flex:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.uv-input__content__field-wrapper[data-v-1b8a7a55]{position:relative;display:flex;flex-direction:row;margin:0;flex:1}.uv-input__content__field-wrapper__field[data-v-1b8a7a55]{line-height:26px;text-align:left;color:#303133;height:24px;font-size:15px;flex:1}.uv-input__content__clear[data-v-1b8a7a55]{width:20px;height:20px;border-radius:100px;background-color:#c6c7cb;display:flex;flex-direction:row;align-items:center;justify-content:center;transform:scale(.82);margin-left:15px}.uv-input__content__subfix-icon[data-v-1b8a7a55]{margin-left:4px}.uv-input__content__prefix-icon[data-v-1b8a7a55]{margin-right:4px}.no_search[data-v-3ea77890]{width:100%;display:flex;flex-direction:column;align-items:center;margin-top:3.125rem}.no_search .img[data-v-3ea77890]{width:7.1875rem}.no_search .text[data-v-3ea77890]{color:#999;font-size:.9375rem}.history_div[data-v-3ea77890]{width:100%}.history_item[data-v-3ea77890]{padding:.25rem 0;color:#777}.title_div[data-v-3ea77890]{display:flex;justify-content:space-between;align-items:center;width:100%;height:2.1875rem;margin:.3125rem 0}.title_div .text[data-v-3ea77890]{font-size:1rem;font-weight:700}.title_div .delete_img[data-v-3ea77890]{display:flex;align-items:center;justify-content:center;padding:.625rem 0 .625rem 1rem}.title_div .delete_img .img[data-v-3ea77890]{height:.75rem}.search_content[data-v-3ea77890]{background-color:#fff;margin-top:-.1875rem;border-radius:.375rem .375rem 0 0;display:flex;flex-direction:column;align-items:center;padding:0 .9375rem;min-height:6.25rem;transition:max-height .3s ease}.search_content .bg_img[data-v-3ea77890]{width:84%;margin:.625rem auto}.fixed_search_div[data-v-3ea77890]{padding:calc(.3125rem + var(--status-bar-height)) .46875rem .75rem .3125rem;background:linear-gradient(90deg,#f1f7ff,#d8edff,#c3e6ff);display:flex;align-items:center}.fixed_search_div .back_div[data-v-3ea77890]{padding:.3125rem .3125rem .3125rem .625rem}.fixed_search_div .back_div .back-icon[data-v-3ea77890]{position:relative;width:1.25rem;height:1.25rem;cursor:pointer}.fixed_search_div .back_div .back-icon[data-v-3ea77890]:before,.fixed_search_div .back_div .back-icon[data-v-3ea77890]:after{content:"";position:absolute;transition:all .3s ease}.fixed_search_div .back_div .back-icon[data-v-3ea77890]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #2b2c2e;border-left:.125rem solid #2b2c2e;transform:rotate(-45deg)}.main_div[data-v-3ea77890]{display:flex;flex-direction:column}
|
||||
.nav-bar[data-v-4b9b9f99]{width:100%;height:var(--status-bar-height);position:fixed}.nav-bar-seat[data-v-4b9b9f99]{width:100%;height:var(--status-bar-height)}.nav-bar-div[data-v-4b9b9f99]{z-index:1900;width:100%;position:relative}uni-view[data-v-843a5998],uni-scroll-view[data-v-843a5998],uni-swiper-item[data-v-843a5998]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}@font-face{font-family:uvicon-iconfont;src:url(../../assets/uvicons.04d281cc.ttf) format("truetype")}.uv-icon[data-v-843a5998]{display:flex;align-items:center}.uv-icon--left[data-v-843a5998]{flex-direction:row-reverse;align-items:center}.uv-icon--right[data-v-843a5998]{flex-direction:row;align-items:center}.uv-icon--top[data-v-843a5998]{flex-direction:column-reverse;justify-content:center}.uv-icon--bottom[data-v-843a5998]{flex-direction:column;justify-content:center}.uv-icon__icon[data-v-843a5998]{font-family:uvicon-iconfont;position:relative;display:flex;flex-direction:row;align-items:center}.uv-icon__icon--primary[data-v-843a5998]{color:#06f}.uv-icon__icon--success[data-v-843a5998]{color:#5ac725}.uv-icon__icon--error[data-v-843a5998]{color:#f56c6c}.uv-icon__icon--warning[data-v-843a5998]{color:#f9ae3d}.uv-icon__icon--info[data-v-843a5998]{color:#909399}.uv-icon__img[data-v-843a5998]{height:auto;will-change:transform}.uv-icon__label[data-v-843a5998]{line-height:1}.uv-border[data-v-1b8a7a55]{border-width:.5px!important;border-color:#dadbde!important;border-style:solid}.uv-border-bottom[data-v-1b8a7a55]{border-bottom-width:.5px!important;border-color:#dadbde!important;border-bottom-style:solid}uni-view[data-v-1b8a7a55],uni-scroll-view[data-v-1b8a7a55],uni-swiper-item[data-v-1b8a7a55]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-input[data-v-1b8a7a55]{display:flex;flex-direction:row;align-items:center;justify-content:space-between;flex:1}.uv-input--radius[data-v-1b8a7a55],.uv-input--square[data-v-1b8a7a55]{border-radius:4px}.uv-input--no-radius[data-v-1b8a7a55]{border-radius:0}.uv-input--circle[data-v-1b8a7a55]{border-radius:100px}.uv-input__content[data-v-1b8a7a55]{flex:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.uv-input__content__field-wrapper[data-v-1b8a7a55]{position:relative;display:flex;flex-direction:row;margin:0;flex:1}.uv-input__content__field-wrapper__field[data-v-1b8a7a55]{line-height:26px;text-align:left;color:#303133;height:24px;font-size:15px;flex:1}.uv-input__content__clear[data-v-1b8a7a55]{width:20px;height:20px;border-radius:100px;background-color:#c6c7cb;display:flex;flex-direction:row;align-items:center;justify-content:center;transform:scale(.82);margin-left:15px}.uv-input__content__subfix-icon[data-v-1b8a7a55]{margin-left:4px}.uv-input__content__prefix-icon[data-v-1b8a7a55]{margin-right:4px}.no_search[data-v-3ea77890]{width:100%;display:flex;flex-direction:column;align-items:center;margin-top:3.125rem}.no_search .img[data-v-3ea77890]{width:7.1875rem}.no_search .text[data-v-3ea77890]{color:#999;font-size:.9375rem}.history_div[data-v-3ea77890]{width:100%}.history_item[data-v-3ea77890]{padding:.25rem 0;color:#777}.title_div[data-v-3ea77890]{display:flex;justify-content:space-between;align-items:center;width:100%;height:2.1875rem;margin:.3125rem 0}.title_div .text[data-v-3ea77890]{font-size:1rem;font-weight:700}.title_div .delete_img[data-v-3ea77890]{display:flex;align-items:center;justify-content:center;padding:.625rem 0 .625rem 1rem}.title_div .delete_img .img[data-v-3ea77890]{height:.75rem}.search_content[data-v-3ea77890]{background-color:#fff;margin-top:-.1875rem;border-radius:.375rem .375rem 0 0;display:flex;flex-direction:column;align-items:center;padding:0 .9375rem;min-height:6.25rem;transition:max-height .3s ease}.search_content .bg_img[data-v-3ea77890]{width:84%;margin:.625rem auto}.fixed_search_div[data-v-3ea77890]{padding:calc(.3125rem + var(--status-bar-height)) .46875rem .75rem .3125rem;background:linear-gradient(90deg,#f1f7ff,#d8edff,#c3e6ff);display:flex;align-items:center}.fixed_search_div .back_div[data-v-3ea77890]{padding:.3125rem .3125rem .3125rem .625rem}.fixed_search_div .back_div .back-icon[data-v-3ea77890]{position:relative;width:1.25rem;height:1.25rem;cursor:pointer}.fixed_search_div .back_div .back-icon[data-v-3ea77890]:before,.fixed_search_div .back_div .back-icon[data-v-3ea77890]:after{content:"";position:absolute;transition:all .3s ease}.fixed_search_div .back_div .back-icon[data-v-3ea77890]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #2b2c2e;border-left:.125rem solid #2b2c2e;transform:rotate(-45deg)}.main_div[data-v-3ea77890]{display:flex;flex-direction:column}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
.nav-bar[data-v-6f106210]{width:100%;height:var(--status-bar-height);position:fixed}.nav-bar-seat[data-v-6f106210]{width:100%;height:var(--status-bar-height)}.nav-bar-div[data-v-6f106210]{z-index:1900;width:100%;position:relative}uni-view[data-v-843a5998],uni-scroll-view[data-v-843a5998],uni-swiper-item[data-v-843a5998]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}@font-face{font-family:uvicon-iconfont;src:url(../../assets/uvicons.04d281cc.ttf) format("truetype")}.uv-icon[data-v-843a5998]{display:flex;align-items:center}.uv-icon--left[data-v-843a5998]{flex-direction:row-reverse;align-items:center}.uv-icon--right[data-v-843a5998]{flex-direction:row;align-items:center}.uv-icon--top[data-v-843a5998]{flex-direction:column-reverse;justify-content:center}.uv-icon--bottom[data-v-843a5998]{flex-direction:column;justify-content:center}.uv-icon__icon[data-v-843a5998]{font-family:uvicon-iconfont;position:relative;display:flex;flex-direction:row;align-items:center}.uv-icon__icon--primary[data-v-843a5998]{color:#06f}.uv-icon__icon--success[data-v-843a5998]{color:#5ac725}.uv-icon__icon--error[data-v-843a5998]{color:#f56c6c}.uv-icon__icon--warning[data-v-843a5998]{color:#f9ae3d}.uv-icon__icon--info[data-v-843a5998]{color:#909399}.uv-icon__img[data-v-843a5998]{height:auto;will-change:transform}.uv-icon__label[data-v-843a5998]{line-height:1}.uv-line[data-v-37b2eb6f]{vertical-align:middle}uni-view[data-v-97e65963],uni-scroll-view[data-v-97e65963],uni-swiper-item[data-v-97e65963]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-cell__body[data-v-97e65963]{display:flex;flex-direction:row;box-sizing:border-box;padding:10px 15px;font-size:15px;color:#303133}.uv-cell__body__content[data-v-97e65963]{display:flex;flex-direction:row;align-items:center;flex:1}.uv-cell__body--large[data-v-97e65963]{padding-top:13px;padding-bottom:13px}.uv-cell__left-icon-wrap[data-v-97e65963],.uv-cell__right-icon-wrap[data-v-97e65963]{display:flex;flex-direction:row;align-items:center;font-size:16px}.uv-cell__left-icon-wrap[data-v-97e65963]{margin-right:4px}.uv-cell__right-icon-wrap[data-v-97e65963]{margin-left:4px;transition:transform .3s}.uv-cell__right-icon-wrap--up[data-v-97e65963]{transform:rotate(-90deg)}.uv-cell__right-icon-wrap--down[data-v-97e65963]{transform:rotate(90deg)}.uv-cell__title[data-v-97e65963]{flex:1}.uv-cell__title-text[data-v-97e65963]{font-size:15px;line-height:22px;color:#303133}.uv-cell__title-text--large[data-v-97e65963]{font-size:16px}.uv-cell__label[data-v-97e65963]{margin-top:5px;font-size:12px;color:#909193;line-height:18px}.uv-cell__label--large[data-v-97e65963]{font-size:14px}.uv-cell__value[data-v-97e65963]{text-align:right;font-size:14px;line-height:24px;color:#606266}.uv-cell__value--large[data-v-97e65963]{font-size:15px}.uv-cell--clickable[data-v-97e65963]{background-color:#f3f4f6}.uv-cell--disabled[data-v-97e65963]{color:#c8c9cc;cursor:not-allowed}.uv-cell--center[data-v-97e65963]{align-items:center}uni-view[data-v-c010df49],uni-scroll-view[data-v-c010df49],uni-swiper-item[data-v-c010df49]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-cell-group[data-v-c010df49]{flex:1}.uv-cell-group__title[data-v-c010df49]{padding:16px 16px 8px}.uv-cell-group__title__text[data-v-c010df49]{font-size:15px;line-height:16px;color:#303133}.uv-cell-group__wrapper[data-v-c010df49]{position:relative}.detail-title[data-v-2bc510e5]{margin:.9375rem 1.5625rem 0 2.03125rem;font-size:1rem;font-weight:600;z-index:101;overflow:hidden}.detail-body[data-v-2bc510e5]{margin:.9375rem 1.5625rem 0;background:#fff;border-radius:.46875rem;z-index:101;overflow:hidden}.detail-body[data-v-2bc510e5] .uv-line{width:calc(100% - 1.5rem)!important;margin:0 .75rem!important}.detail-body[data-v-2bc510e5] .uv-cell__body{padding:12px 15px}.detail-body[data-v-2bc510e5] .uv-cell__title{flex:none!important}.detail-main[data-v-2bc510e5]{background-color:#f6f8ff}.title[data-v-2bc510e5]{color:#000;font-size:1.0625rem;height:1.0625rem;font-weight:600;line-height:1.0625rem}.fixed_search_div[data-v-2bc510e5]{padding:calc(.3125rem + var(--status-bar-height)) .46875rem .75rem .9375rem;display:flex;align-items:center;background-color:#f6f7f8}.fixed_search_div .back_div[data-v-2bc510e5]{margin-right:.3125rem}.fixed_search_div .back_div .back-icon[data-v-2bc510e5]{position:relative;width:1.25rem;height:1.25rem;cursor:pointer;display:flex;align-items:center}.fixed_search_div .back_div .back-icon[data-v-2bc510e5]:before,.fixed_search_div .back_div .back-icon[data-v-2bc510e5]:after{content:"";position:absolute;transition:all .3s ease}.fixed_search_div .back_div .back-icon[data-v-2bc510e5]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #2b2c2e;border-left:.125rem solid #2b2c2e;transform:rotate(-45deg)}
|
||||
.nav-bar[data-v-4b9b9f99]{width:100%;height:var(--status-bar-height);position:fixed}.nav-bar-seat[data-v-4b9b9f99]{width:100%;height:var(--status-bar-height)}.nav-bar-div[data-v-4b9b9f99]{z-index:1900;width:100%;position:relative}uni-view[data-v-843a5998],uni-scroll-view[data-v-843a5998],uni-swiper-item[data-v-843a5998]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}@font-face{font-family:uvicon-iconfont;src:url(../../assets/uvicons.04d281cc.ttf) format("truetype")}.uv-icon[data-v-843a5998]{display:flex;align-items:center}.uv-icon--left[data-v-843a5998]{flex-direction:row-reverse;align-items:center}.uv-icon--right[data-v-843a5998]{flex-direction:row;align-items:center}.uv-icon--top[data-v-843a5998]{flex-direction:column-reverse;justify-content:center}.uv-icon--bottom[data-v-843a5998]{flex-direction:column;justify-content:center}.uv-icon__icon[data-v-843a5998]{font-family:uvicon-iconfont;position:relative;display:flex;flex-direction:row;align-items:center}.uv-icon__icon--primary[data-v-843a5998]{color:#06f}.uv-icon__icon--success[data-v-843a5998]{color:#5ac725}.uv-icon__icon--error[data-v-843a5998]{color:#f56c6c}.uv-icon__icon--warning[data-v-843a5998]{color:#f9ae3d}.uv-icon__icon--info[data-v-843a5998]{color:#909399}.uv-icon__img[data-v-843a5998]{height:auto;will-change:transform}.uv-icon__label[data-v-843a5998]{line-height:1}.uv-line[data-v-37b2eb6f]{vertical-align:middle}uni-view[data-v-97e65963],uni-scroll-view[data-v-97e65963],uni-swiper-item[data-v-97e65963]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-cell__body[data-v-97e65963]{display:flex;flex-direction:row;box-sizing:border-box;padding:10px 15px;font-size:15px;color:#303133}.uv-cell__body__content[data-v-97e65963]{display:flex;flex-direction:row;align-items:center;flex:1}.uv-cell__body--large[data-v-97e65963]{padding-top:13px;padding-bottom:13px}.uv-cell__left-icon-wrap[data-v-97e65963],.uv-cell__right-icon-wrap[data-v-97e65963]{display:flex;flex-direction:row;align-items:center;font-size:16px}.uv-cell__left-icon-wrap[data-v-97e65963]{margin-right:4px}.uv-cell__right-icon-wrap[data-v-97e65963]{margin-left:4px;transition:transform .3s}.uv-cell__right-icon-wrap--up[data-v-97e65963]{transform:rotate(-90deg)}.uv-cell__right-icon-wrap--down[data-v-97e65963]{transform:rotate(90deg)}.uv-cell__title[data-v-97e65963]{flex:1}.uv-cell__title-text[data-v-97e65963]{font-size:15px;line-height:22px;color:#303133}.uv-cell__title-text--large[data-v-97e65963]{font-size:16px}.uv-cell__label[data-v-97e65963]{margin-top:5px;font-size:12px;color:#909193;line-height:18px}.uv-cell__label--large[data-v-97e65963]{font-size:14px}.uv-cell__value[data-v-97e65963]{text-align:right;font-size:14px;line-height:24px;color:#606266}.uv-cell__value--large[data-v-97e65963]{font-size:15px}.uv-cell--clickable[data-v-97e65963]{background-color:#f3f4f6}.uv-cell--disabled[data-v-97e65963]{color:#c8c9cc;cursor:not-allowed}.uv-cell--center[data-v-97e65963]{align-items:center}uni-view[data-v-c010df49],uni-scroll-view[data-v-c010df49],uni-swiper-item[data-v-c010df49]{display:flex;flex-direction:column;flex-shrink:0;flex-grow:0;flex-basis:auto;align-items:stretch;align-content:flex-start}.uv-cell-group[data-v-c010df49]{flex:1}.uv-cell-group__title[data-v-c010df49]{padding:16px 16px 8px}.uv-cell-group__title__text[data-v-c010df49]{font-size:15px;line-height:16px;color:#303133}.uv-cell-group__wrapper[data-v-c010df49]{position:relative}.detail-title[data-v-97b73069]{margin:.9375rem 1.5625rem 0 2.03125rem;font-size:1rem;font-weight:600;z-index:101;overflow:hidden}.detail-body[data-v-97b73069]{margin:.9375rem 1.5625rem 0;background:#fff;border-radius:.46875rem;z-index:101;overflow:hidden}.detail-body[data-v-97b73069] .uv-line{width:calc(100% - 1.5rem)!important;margin:0 .75rem!important}.detail-body[data-v-97b73069] .uv-cell__body{padding:12px 15px}.detail-body[data-v-97b73069] .uv-cell__title{flex:none!important}.detail-main[data-v-97b73069]{background-color:#f6f8ff}.title[data-v-97b73069]{color:#000;font-size:1.0625rem;height:1.0625rem;font-weight:600;line-height:1.0625rem}.fixed_search_div[data-v-97b73069]{padding:calc(.3125rem + var(--status-bar-height)) .46875rem .75rem .9375rem;display:flex;align-items:center;background-color:#f6f7f8}.fixed_search_div .back_div[data-v-97b73069]{margin-right:.3125rem}.fixed_search_div .back_div .back-icon[data-v-97b73069]{position:relative;width:1.25rem;height:1.25rem;cursor:pointer;display:flex;align-items:center}.fixed_search_div .back_div .back-icon[data-v-97b73069]:before,.fixed_search_div .back_div .back-icon[data-v-97b73069]:after{content:"";position:absolute;transition:all .3s ease}.fixed_search_div .back_div .back-icon[data-v-97b73069]:after{top:25%;left:25%;width:40%;height:40%;border-top:.125rem solid #2b2c2e;border-left:.125rem solid #2b2c2e;transform:rotate(-45deg)}
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="30px" height="30px" viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:2.4306;stroke-miterlimit:10;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M17.1,24.2h-12c-0.2,0-0.3-0.2-0.3-0.3v-9.3c0-0.2,0.2-0.3,0.3-0.3h12c0.2,0,0.3,0.2,0.3,0.3v9.3
|
||||
C17.5,24.1,17.3,24.2,17.1,24.2z"/>
|
||||
<path class="st0" d="M16.6,5.4c4.8,0,8.7,3.9,8.7,8.7"/>
|
||||
<polyline class="st0" points="19.3,10.1 14.9,5.6 19.3,1.2 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 791 B |
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minSdkVersion": "35"
|
||||
}
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
package uts.sdk.modules.tyCamera
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.ImageFormat
|
||||
import android.hardware.camera2.CameraCaptureSession
|
||||
import android.hardware.camera2.CameraCharacteristics
|
||||
import android.hardware.camera2.CameraDevice
|
||||
import android.hardware.camera2.CameraManager
|
||||
import android.hardware.camera2.CaptureRequest
|
||||
import android.media.Image
|
||||
import android.media.ImageReader
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.view.Surface
|
||||
import io.dcloud.uniapp.*
|
||||
import io.dcloud.uniapp.extapi.*
|
||||
import io.dcloud.uts.*
|
||||
import io.dcloud.uts.Map
|
||||
import io.dcloud.uts.Set
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import io.dcloud.uts.UTSAndroid
|
||||
open class MyApiOptions (
|
||||
@JsonNotNull
|
||||
open var paramA: Boolean = false,
|
||||
open var success: ((res: String) -> Unit)? = null,
|
||||
open var fail: ((res: MyApiFail) -> Unit)? = null,
|
||||
open var complete: ((res: Any) -> Unit)? = null,
|
||||
) : UTSObject()
|
||||
typealias MyApiErrorCode = Number
|
||||
interface MyApiFail : IUniError {
|
||||
override var errCode: MyApiErrorCode
|
||||
}
|
||||
typealias CameraFacingType = String
|
||||
open class CameraHelper {
|
||||
private var context: Context
|
||||
private var cameraManager: CameraManager
|
||||
private var currentCameraId: String? = null
|
||||
private var totalRotation: Number = 0
|
||||
constructor(context: Context){
|
||||
this.context = context
|
||||
this.cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||
if (this.cameraManager == null) {
|
||||
throw UTSError("CameraHelper: 获取CameraManager失败,请检查摄像头权限或设备支持性")
|
||||
}
|
||||
}
|
||||
public open fun getCameraIdByFacing(facing: CameraFacingType): String {
|
||||
try {
|
||||
val cameraIds = this.cameraManager.getCameraIdList()
|
||||
if (cameraIds.size === 0) {
|
||||
console.error("CameraHelper: 无可用摄像头ID")
|
||||
throw UTSError()
|
||||
}
|
||||
var targetCameraId: String? = null
|
||||
for(id in resolveUTSValueIterator(cameraIds)){
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(id)
|
||||
val lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
if (lensFacing !== null && lensFacing === (if (facing === "BACK") {
|
||||
CameraCharacteristics.LENS_FACING_BACK
|
||||
} else {
|
||||
CameraCharacteristics.LENS_FACING_FRONT
|
||||
}
|
||||
)) {
|
||||
targetCameraId = id
|
||||
break
|
||||
}
|
||||
}
|
||||
if (targetCameraId === null) {
|
||||
throw UTSError()
|
||||
}
|
||||
console.log("CameraHelper: \u6210\u529F\u83B7\u53D6" + (if (facing === "BACK") {
|
||||
"后置"
|
||||
} else {
|
||||
"前置"
|
||||
}
|
||||
) + "\u6444\u50CF\u5934ID\uFF1A" + targetCameraId)
|
||||
return targetCameraId
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("CameraHelper: \u83B7\u53D6" + (if (facing === "BACK") {
|
||||
"后置"
|
||||
} else {
|
||||
"前置"
|
||||
}
|
||||
) + "\u6444\u50CF\u5934ID\u5F02\u5E38\uFF1A", e.message)
|
||||
throw UTSError()
|
||||
}
|
||||
}
|
||||
public open fun openCameraByFacing(facing: CameraFacingType, callback: CameraDevice.StateCallback, handler: Handler? = null): Unit {
|
||||
var cameraId: String
|
||||
try {
|
||||
cameraId = this.getCameraIdByFacing(facing)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
throw UTSError()
|
||||
}
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(cameraId)
|
||||
val streamConfigurationMap = characteristics.get(android.hardware.camera2.CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
|
||||
if (streamConfigurationMap !== null) {
|
||||
val supportedSizes = streamConfigurationMap.getOutputSizes(ImageFormat.YUV_420_888)
|
||||
for(size in resolveUTSValueIterator(supportedSizes)){
|
||||
console.log(object : UTSJSONObject() {
|
||||
var width = size.getWidth()
|
||||
var height = size.getHeight()
|
||||
})
|
||||
}
|
||||
}
|
||||
this.cameraManager.openCamera(cameraId, callback, handler)
|
||||
}
|
||||
public open fun closeCamera(): Unit {}
|
||||
public open fun getCurrentCameraId(): String? {
|
||||
return this.currentCameraId
|
||||
}
|
||||
public open fun getTotalRotation(): Number {
|
||||
return this.totalRotation
|
||||
}
|
||||
private fun getBackCameraId(cameraIds: UTSArray<String>): String? {
|
||||
for(id in resolveUTSValueIterator(cameraIds)){
|
||||
val characteristics = this.cameraManager.getCameraCharacteristics(id)
|
||||
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
|
||||
if (facing !== null && facing === CameraCharacteristics.LENS_FACING_BACK) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
private fun calculateCameraRotation(cameraId: String): Unit {}
|
||||
public open fun release(): Unit {
|
||||
this.currentCameraId = null
|
||||
this.totalRotation = 0
|
||||
console.log("CameraHelper: 摄像头辅助类资源已释放")
|
||||
}
|
||||
}
|
||||
val width: Int = 720
|
||||
val height: Int = 480
|
||||
var backgroundHandler: Handler? = null
|
||||
var handlerThread: HandlerThread? = null
|
||||
var imageReader: ImageReader? = null
|
||||
var captureSession: CameraCaptureSession? = null
|
||||
var cameraDevice: CameraDevice? = null
|
||||
val runBlock1 = run {
|
||||
console.log("1111", android.os.Build.VERSION.SDK_INT)
|
||||
}
|
||||
fun yuvToJpegBase64(image: android.media.Image): String {
|
||||
val width = image.getWidth()
|
||||
val height = image.getHeight()
|
||||
if (image.getFormat() !== android.graphics.ImageFormat.YUV_420_888) {
|
||||
console.error("仅支持YUV_420_888格式")
|
||||
return ""
|
||||
}
|
||||
val planes = image.getPlanes()
|
||||
val yPlane = planes[0]
|
||||
val uPlane = planes[1]
|
||||
val vPlane = planes[2]
|
||||
val yRowStride = yPlane.getRowStride()
|
||||
val yPixelStride = yPlane.getPixelStride()
|
||||
val uRowStride = uPlane.getRowStride()
|
||||
val uPixelStride = uPlane.getPixelStride()
|
||||
val vRowStride = vPlane.getRowStride()
|
||||
val vPixelStride = vPlane.getPixelStride()
|
||||
val yBuffer = yPlane.getBuffer()
|
||||
val uBuffer = uPlane.getBuffer()
|
||||
val vBuffer = vPlane.getBuffer()
|
||||
val yByteArray = ByteArray(yBuffer.remaining())
|
||||
val uByteArray = ByteArray(uBuffer.remaining())
|
||||
val vByteArray = ByteArray(vBuffer.remaining())
|
||||
yBuffer.get(yByteArray)
|
||||
uBuffer.get(uByteArray)
|
||||
vBuffer.get(vByteArray)
|
||||
val nv21 = ByteArray(width * height * 3 / 2)
|
||||
var nv21Index: Int = 0
|
||||
run {
|
||||
var i: Int = 0
|
||||
while(i < height){
|
||||
run {
|
||||
var j: Int = 0
|
||||
while(j < width){
|
||||
nv21[nv21Index++] = yByteArray[i * yRowStride + j * yPixelStride]
|
||||
j++
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
val halfHeight = Math.floor(height / 2)
|
||||
val halfWidth = Math.floor(width / 2)
|
||||
run {
|
||||
var i: Int = 0
|
||||
while(i < halfHeight){
|
||||
run {
|
||||
var j: Int = 0
|
||||
while(j < halfWidth){
|
||||
val uIndex: Int = i * uRowStride + j * uPixelStride
|
||||
val vIndex: Int = i * vRowStride + j * vPixelStride
|
||||
nv21[nv21Index++] = vByteArray[vIndex]
|
||||
nv21[nv21Index++] = uByteArray[uIndex]
|
||||
j++
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
try {
|
||||
val yuvImage = android.graphics.YuvImage(nv21, android.graphics.ImageFormat.NV21, width, height, null)
|
||||
val outputStream = java.io.ByteArrayOutputStream()
|
||||
val compressSuccess = yuvImage.compressToJpeg(android.graphics.Rect(0, 0, width, height), 80, outputStream)
|
||||
if (!compressSuccess) {
|
||||
console.error("YUV压缩为JPEG失败")
|
||||
outputStream.close()
|
||||
return ""
|
||||
}
|
||||
val jpegBytes = outputStream.toByteArray()
|
||||
val srcBitmap: Bitmap = android.graphics.BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
||||
val matrix = android.graphics.Matrix()
|
||||
matrix.postRotate((-90 as Number).toFloat())
|
||||
val outputStream2 = java.io.ByteArrayOutputStream()
|
||||
val rotatedBitmap = android.graphics.Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight(), matrix, true)
|
||||
rotatedBitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, outputStream2)
|
||||
val BitmapBytes = outputStream2.toByteArray()
|
||||
val base64Raw = android.util.Base64.encodeToString(BitmapBytes, android.util.Base64.DEFAULT)
|
||||
val base64Jpeg = "data:image/jpeg;base64," + base64Raw
|
||||
outputStream.close()
|
||||
return base64Jpeg
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("YUV转JPEG/Base64失败:", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
fun startCameraPreview(options: MyApiOptions): Unit {
|
||||
val context = UTSAndroid.getAppContext()
|
||||
val activity = UTSAndroid.getUniActivity()
|
||||
if (context == null || activity == null) {
|
||||
console.error("错误:上下文或Activity为空")
|
||||
return
|
||||
}
|
||||
handlerThread = HandlerThread("CameraBackground", -8)
|
||||
handlerThread!!!!.start()
|
||||
backgroundHandler = Handler(handlerThread!!!!.getLooper())
|
||||
val cameraHelper = CameraHelper(context)
|
||||
val totalRotation = cameraHelper.getTotalRotation()
|
||||
try {
|
||||
console.log("width, height", width, height)
|
||||
imageReader = ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, 8)
|
||||
val localImageReader = imageReader
|
||||
if (localImageReader == null) {
|
||||
console.error("ImageReader 创建失败,无法设置监听器")
|
||||
return
|
||||
}
|
||||
val imageAvailableListener = fun(reader: android.media.ImageReader): Unit {
|
||||
val imageProxy: Image? = reader.acquireLatestImage()
|
||||
if (imageProxy === null) {
|
||||
return
|
||||
}
|
||||
options.success?.invoke(yuvToJpegBase64(imageProxy))
|
||||
imageProxy.close()
|
||||
return
|
||||
}
|
||||
localImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler)
|
||||
open class CustomCameraStateCallback : android.hardware.camera2.CameraDevice.StateCallback {
|
||||
public constructor() : super() {}
|
||||
override fun onOpened(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机打开成功")
|
||||
cameraDevice = device
|
||||
val localCameraDevice = device
|
||||
val surface = imageReader!!!!.getSurface()
|
||||
val surfaces = utsArrayOf(
|
||||
surface
|
||||
)
|
||||
open class CustomCaptureSessionCallback : android.hardware.camera2.CameraCaptureSession.StateCallback {
|
||||
private var localCameraDevice: android.hardware.camera2.CameraDevice
|
||||
private var surface: android.view.Surface
|
||||
public constructor(cameraDevice: android.hardware.camera2.CameraDevice, surface: android.view.Surface) : super() {
|
||||
this.localCameraDevice = cameraDevice
|
||||
this.surface = surface
|
||||
}
|
||||
override fun onConfigured(session: android.hardware.camera2.CameraCaptureSession): Unit {
|
||||
console.log("采集会话配置成功,启动持续帧采集")
|
||||
captureSession = session
|
||||
val requestBuilder = this.localCameraDevice.createCaptureRequest(android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW)
|
||||
requestBuilder.addTarget(this.surface)
|
||||
requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270)
|
||||
session.setRepeatingRequest(requestBuilder.build(), null, backgroundHandler)
|
||||
}
|
||||
override fun onConfigureFailed(session: android.hardware.camera2.CameraCaptureSession): Unit {
|
||||
console.error("采集会话配置失败")
|
||||
captureSession = null
|
||||
}
|
||||
}
|
||||
val sessionCallback = CustomCaptureSessionCallback(localCameraDevice, surface) as CameraCaptureSession.StateCallback
|
||||
localCameraDevice.createCaptureSession(surfaces, sessionCallback, backgroundHandler)
|
||||
}
|
||||
override fun onDisconnected(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机连接断开")
|
||||
this.releaseCameraResources()
|
||||
}
|
||||
override fun onError(device: android.hardware.camera2.CameraDevice, p1: Int): Unit {
|
||||
console.error("\u76F8\u673A\u6253\u5F00\u5931\u8D25")
|
||||
this.releaseCameraResources()
|
||||
}
|
||||
override fun onClosed(device: android.hardware.camera2.CameraDevice): Unit {
|
||||
console.log("相机已关闭")
|
||||
super.onClosed(device)
|
||||
}
|
||||
private fun releaseCameraResources(): Unit {
|
||||
if (cameraDevice != null) {
|
||||
cameraDevice!!!!.close()
|
||||
cameraDevice = null
|
||||
console.log("相机资源已释放")
|
||||
}
|
||||
}
|
||||
}
|
||||
val cameraStateCallback = CustomCameraStateCallback()
|
||||
cameraHelper.openCameraByFacing("FRONT", cameraStateCallback, backgroundHandler)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("启动摄像头失败:", e)
|
||||
}
|
||||
}
|
||||
fun stopCameraPreview(): Unit {
|
||||
try {
|
||||
console.log("开始释放摄像头资源")
|
||||
if (captureSession != null) {
|
||||
try {
|
||||
captureSession!!!!.stopRepeating()
|
||||
captureSession!!!!.abortCaptures()
|
||||
captureSession!!!!.close()
|
||||
console.log("采集会话已释放")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("释放采集会话时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
captureSession = null
|
||||
}
|
||||
}
|
||||
if (cameraDevice != null) {
|
||||
try {
|
||||
cameraDevice!!!!.close()
|
||||
console.log("相机设备已关闭")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("关闭相机设备时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
cameraDevice = null
|
||||
}
|
||||
}
|
||||
if (imageReader != null) {
|
||||
try {
|
||||
imageReader!!!!.close()
|
||||
console.log("ImageReader已释放")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("释放ImageReader时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
imageReader = null
|
||||
}
|
||||
}
|
||||
if (handlerThread != null) {
|
||||
try {
|
||||
if (backgroundHandler != null) {
|
||||
console.log("先移除所有待处理的消息")
|
||||
backgroundHandler!!!!.removeCallbacksAndMessages(null)
|
||||
backgroundHandler = null
|
||||
}
|
||||
handlerThread!!!!.quitSafely()
|
||||
handlerThread!!!!.join()
|
||||
console.log("后台线程已停止")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("停止后台线程时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
handlerThread = null
|
||||
}
|
||||
}
|
||||
console.log("摄像头资源释放完成")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.error("释放摄像头资源时发生全局异常:", e)
|
||||
}
|
||||
}
|
||||
open class MyApiOptionsJSONObject : UTSJSONObject() {
|
||||
open var paramA: Boolean = false
|
||||
open var success: UTSCallback? = null
|
||||
open var fail: UTSCallback? = null
|
||||
open var complete: UTSCallback? = null
|
||||
}
|
||||
fun startCameraPreviewByJs(options: MyApiOptionsJSONObject): Unit {
|
||||
return startCameraPreview(MyApiOptions(paramA = options.paramA, success = fun(res: String): Unit {
|
||||
options.success?.invoke(res)
|
||||
}
|
||||
, fail = fun(res: MyApiFail): Unit {
|
||||
options.fail?.invoke(res)
|
||||
}
|
||||
, complete = fun(res: Any): Unit {
|
||||
options.complete?.invoke(res)
|
||||
}
|
||||
))
|
||||
}
|
||||
fun stopCameraPreviewByJs(): Unit {
|
||||
return stopCameraPreview()
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deploymentTarget": "12"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
import DCloudUTSFoundation
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minSdkVersion": "22"
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
@file:Suppress("UNCHECKED_CAST", "USELESS_CAST", "INAPPLICABLE_JVM_NAME", "UNUSED_ANONYMOUS_PARAMETER", "NAME_SHADOWING", "UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
package uts.sdk.modules.tyRecording
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioRecord
|
||||
import android.media.MediaRecorder
|
||||
import io.dcloud.uniapp.*
|
||||
import io.dcloud.uniapp.extapi.*
|
||||
import io.dcloud.uts.*
|
||||
import io.dcloud.uts.Map
|
||||
import io.dcloud.uts.Set
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
open class AudioStartRes (
|
||||
@JsonNotNull
|
||||
open var recordId: String,
|
||||
open var sampleRate: Number? = null,
|
||||
@JsonNotNull
|
||||
open var startTime: Number,
|
||||
) : UTSObject()
|
||||
open class AudioFileRes (
|
||||
@JsonNotNull
|
||||
open var filePath: String,
|
||||
@JsonNotNull
|
||||
open var fileSize: Number,
|
||||
@JsonNotNull
|
||||
open var duration: Number,
|
||||
@JsonNotNull
|
||||
open var format: String,
|
||||
@JsonNotNull
|
||||
open var endTime: Number,
|
||||
) : UTSObject()
|
||||
typealias AudioErrorCode = Number
|
||||
interface AudioErrorRes : IUniError {
|
||||
override var errCode: Number
|
||||
}
|
||||
open class StartAudioRecordOptions (
|
||||
open var autoStopDuration: Number? = null,
|
||||
open var onFrame: ((frameData: Any) -> Unit)? = null,
|
||||
open var onVolume: ((volume: Number) -> Unit)? = null,
|
||||
open var onStart: ((res: AudioStartRes) -> Unit)? = null,
|
||||
open var onError: ((res: AudioErrorRes) -> Unit)? = null,
|
||||
open var onStop: (() -> Unit)? = null,
|
||||
open var onFile: ((res: AudioFileRes) -> Unit)? = null,
|
||||
) : UTSObject()
|
||||
val UniErrorSubject = "ty-recording"
|
||||
val AudioErrors = Map<AudioErrorCode, String>(utsArrayOf(
|
||||
utsArrayOf(
|
||||
9010001,
|
||||
"麦克风权限未授予,请前往设置开启1"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010002,
|
||||
"麦克风权限被永久拒绝,请前往设置开启"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010003,
|
||||
"获取麦克风权限状态失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010010,
|
||||
"音频录制实例初始化失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010011,
|
||||
"音频录制实例未初始化,无法执行操作"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010012,
|
||||
"系统资源不足,无法创建音频录制实例"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010020,
|
||||
"音频采集读取数据失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010021,
|
||||
"音频采集被中断(麦克风被其他应用抢占)"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010022,
|
||||
"音频采集缓冲区无有效数据"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010023,
|
||||
"启动音频采集失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010030,
|
||||
"采集线程创建失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010031,
|
||||
"采集线程启动失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010032,
|
||||
"采集线程中断失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010033,
|
||||
"采集线程运行崩溃"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010040,
|
||||
"停止音频采集失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010041,
|
||||
"音频录制资源释放失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010042,
|
||||
"音频资源重复释放"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010043,
|
||||
"音频资源泄漏(未正常释放)"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010050,
|
||||
"当前设备不支持该采样率"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010051,
|
||||
"声道配置非法或不被支持"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010052,
|
||||
"音频格式非法或不被支持"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010053,
|
||||
"采集缓冲区大小低于系统最小值"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010054,
|
||||
"采集缓冲区大小配置非法"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010060,
|
||||
"麦克风被其他应用占用"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010061,
|
||||
"音频硬件故障"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010062,
|
||||
"系统资源不足,无法创建音频实例"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010063,
|
||||
"系统音频服务异常或未启动"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010070,
|
||||
"重复启动音频录制"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010071,
|
||||
"未在录制状态,无法执行停止/暂停操作"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010072,
|
||||
"音频数据缓冲区拷贝失败"
|
||||
),
|
||||
utsArrayOf(
|
||||
9010080,
|
||||
"预留错误码"
|
||||
)
|
||||
))
|
||||
open class StartAudioRecordFailImpl : UniError, AudioErrorRes {
|
||||
constructor(errCode: Number) : super() {
|
||||
this.errSubject = UniErrorSubject
|
||||
this.errCode = errCode
|
||||
this.errMsg = AudioErrors.get(errCode as AudioErrorCode) ?: ""
|
||||
}
|
||||
}
|
||||
val isRecording = AtomicBoolean(false)
|
||||
val sampleRate: Int = 16000
|
||||
val channelConfig = AudioFormat.CHANNEL_IN_MONO
|
||||
val audioFormat = AudioFormat.ENCODING_PCM_16BIT
|
||||
val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)
|
||||
val bufferSize = minBufferSize * 2
|
||||
var audioRecord: AudioRecord? = null
|
||||
var recordThread: Thread? = null
|
||||
var recordStartTime: Number? = null
|
||||
fun startAudioRecord(options: StartAudioRecordOptions): Unit {
|
||||
if (isRecording.get()) {
|
||||
return
|
||||
}
|
||||
val _audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig, audioFormat, bufferSize)
|
||||
if (_audioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
|
||||
val err = StartAudioRecordFailImpl(9010001)
|
||||
options.onError?.invoke(err)
|
||||
return
|
||||
}
|
||||
audioRecord = _audioRecord
|
||||
isRecording.set(true)
|
||||
console.log("开始采集")
|
||||
val _recordThread = Thread(fun(){
|
||||
_audioRecord.startRecording()
|
||||
val recordId = "" + Date().getTime() + "_" + Math.floor(Math.random() * 10000)
|
||||
recordStartTime = Date().getTime()
|
||||
options.onStart?.invoke(AudioStartRes(startTime = recordStartTime as Number, sampleRate = sampleRate as Number, recordId = recordId))
|
||||
console.log("AudioRecord已启动采集")
|
||||
val buffer = ByteArray(bufferSize)
|
||||
while(isRecording.get()){
|
||||
val readSize = _audioRecord.read(buffer, 0, bufferSize)
|
||||
if (readSize > 0) {
|
||||
val data = ByteArray(readSize)
|
||||
java.lang.System.arraycopy(buffer, 0, data, 0, readSize)
|
||||
options.onFrame?.invoke(data)
|
||||
}
|
||||
}
|
||||
if (_audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
console.log("停止采集xxxxxxxxx")
|
||||
_audioRecord.stop()
|
||||
options.onStop?.invoke()
|
||||
}
|
||||
}
|
||||
)
|
||||
_recordThread.start()
|
||||
recordThread = _recordThread
|
||||
}
|
||||
fun stopAudioRecord(): Unit {
|
||||
console.log("停止麦克风采集", isRecording.get())
|
||||
if (!isRecording.get()) {
|
||||
return
|
||||
}
|
||||
isRecording.set(false)
|
||||
val _audioRecord = audioRecord
|
||||
if (_audioRecord != null) {
|
||||
try {
|
||||
if (_audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
_audioRecord.stop()
|
||||
}
|
||||
_audioRecord.release()
|
||||
}
|
||||
catch (e: Throwable) {}
|
||||
}
|
||||
audioRecord = null
|
||||
console.log("等待线程结束")
|
||||
val _recordThread = recordThread
|
||||
if (_recordThread != null) {
|
||||
try {
|
||||
_recordThread.join(1000)
|
||||
console.log("后台线程已停止")
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
console.warn("停止后台线程时出现异常:", e)
|
||||
}
|
||||
finally{
|
||||
recordThread = null
|
||||
}
|
||||
}
|
||||
}
|
||||
fun preRequestRecordPermission(): Unit {}
|
||||
open class StartAudioRecordOptionsJSONObject : UTSJSONObject() {
|
||||
open var autoStopDuration: Number? = null
|
||||
open var onFrame: UTSCallback? = null
|
||||
open var onVolume: UTSCallback? = null
|
||||
open var onStart: UTSCallback? = null
|
||||
open var onError: UTSCallback? = null
|
||||
open var onStop: UTSCallback? = null
|
||||
open var onFile: UTSCallback? = null
|
||||
}
|
||||
fun startAudioRecordByJs(options: StartAudioRecordOptionsJSONObject): Unit {
|
||||
return startAudioRecord(StartAudioRecordOptions(autoStopDuration = options.autoStopDuration, onFrame = fun(frameData: Any): Unit {
|
||||
options.onFrame?.invoke(frameData)
|
||||
}
|
||||
, onVolume = fun(volume: Number): Unit {
|
||||
options.onVolume?.invoke(volume)
|
||||
}
|
||||
, onStart = fun(res: AudioStartRes): Unit {
|
||||
options.onStart?.invoke(res)
|
||||
}
|
||||
, onError = fun(res: AudioErrorRes): Unit {
|
||||
options.onError?.invoke(res)
|
||||
}
|
||||
, onStop = fun(): Unit {
|
||||
options.onStop?.invoke()
|
||||
}
|
||||
, onFile = fun(res: AudioFileRes): Unit {
|
||||
options.onFile?.invoke(res)
|
||||
}
|
||||
))
|
||||
}
|
||||
fun stopAudioRecordByJs(): Unit {
|
||||
return stopAudioRecord()
|
||||
}
|
||||
fun preRequestRecordPermissionByJs(): Unit {
|
||||
return preRequestRecordPermission()
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deploymentTarget": "13"
|
||||
}
|
||||
+478
@@ -0,0 +1,478 @@
|
||||
import AVFoundation
|
||||
import DCloudUTSFoundation
|
||||
import Foundation
|
||||
import Swift
|
||||
@objc(UTSSDKModulesTyRecordingAudioStartRes)
|
||||
@objcMembers
|
||||
public class AudioStartRes : NSObject, UTSObject {
|
||||
public var recordId: String!
|
||||
public var sampleRate: NSNumber?
|
||||
public var startTime: NSNumber!
|
||||
public subscript(_ key: String) -> Any? {
|
||||
get {
|
||||
return utsSubscriptGetValue(key)
|
||||
}
|
||||
set {
|
||||
switch(key){
|
||||
case "recordId":
|
||||
self.recordId = try! utsSubscriptCheckValue(newValue)
|
||||
case "sampleRate":
|
||||
self.sampleRate = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "startTime":
|
||||
self.startTime = try! utsSubscriptCheckValue(newValue)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
public init(_ obj: UTSJSONObject) {
|
||||
self.recordId = obj["recordId"] as! String
|
||||
self.sampleRate = obj["sampleRate"] as! NSNumber?
|
||||
self.startTime = obj["startTime"] as! NSNumber
|
||||
}
|
||||
}
|
||||
@objc(UTSSDKModulesTyRecordingAudioFileRes)
|
||||
@objcMembers
|
||||
public class AudioFileRes : NSObject, UTSObject {
|
||||
public var filePath: String!
|
||||
public var fileSize: NSNumber!
|
||||
public var duration: NSNumber!
|
||||
public var format: String!
|
||||
public var endTime: NSNumber!
|
||||
public subscript(_ key: String) -> Any? {
|
||||
get {
|
||||
return utsSubscriptGetValue(key)
|
||||
}
|
||||
set {
|
||||
switch(key){
|
||||
case "filePath":
|
||||
self.filePath = try! utsSubscriptCheckValue(newValue)
|
||||
case "fileSize":
|
||||
self.fileSize = try! utsSubscriptCheckValue(newValue)
|
||||
case "duration":
|
||||
self.duration = try! utsSubscriptCheckValue(newValue)
|
||||
case "format":
|
||||
self.format = try! utsSubscriptCheckValue(newValue)
|
||||
case "endTime":
|
||||
self.endTime = try! utsSubscriptCheckValue(newValue)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
public init(_ obj: UTSJSONObject) {
|
||||
self.filePath = obj["filePath"] as! String
|
||||
self.fileSize = obj["fileSize"] as! NSNumber
|
||||
self.duration = obj["duration"] as! NSNumber
|
||||
self.format = obj["format"] as! String
|
||||
self.endTime = obj["endTime"] as! NSNumber
|
||||
}
|
||||
}
|
||||
public typealias AudioErrorCode = NSNumber
|
||||
public protocol AudioErrorRes : IUniError {
|
||||
var errCode: NSNumber { get set }
|
||||
}
|
||||
@objc(UTSSDKModulesTyRecordingStartAudioRecordOptions)
|
||||
@objcMembers
|
||||
public class StartAudioRecordOptions : NSObject, UTSObject {
|
||||
public var autoStopDuration: NSNumber?
|
||||
public var onFrame: ((_ frameData: Any) -> Void)?
|
||||
public var onVolume: ((_ volume: NSNumber) -> Void)?
|
||||
public var onStart: ((_ res: AudioStartRes) -> Void)?
|
||||
public var onError: ((_ res: AudioErrorRes) -> Void)?
|
||||
public var onStop: (() -> Void)?
|
||||
public var onFile: ((_ res: AudioFileRes) -> Void)?
|
||||
public subscript(_ key: String) -> Any? {
|
||||
get {
|
||||
return utsSubscriptGetValue(key)
|
||||
}
|
||||
set {
|
||||
switch(key){
|
||||
case "autoStopDuration":
|
||||
self.autoStopDuration = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onFrame":
|
||||
self.onFrame = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onVolume":
|
||||
self.onVolume = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onStart":
|
||||
self.onStart = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onError":
|
||||
self.onError = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onStop":
|
||||
self.onStop = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
case "onFile":
|
||||
self.onFile = try! utsSubscriptCheckValueIfPresent(newValue)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
public override init() {
|
||||
super.init()
|
||||
}
|
||||
public init(_ obj: UTSJSONObject) {
|
||||
self.autoStopDuration = obj["autoStopDuration"] as! NSNumber?
|
||||
self.onFrame = obj["onFrame"] as! ((_ frameData: Any) -> Void)?
|
||||
self.onVolume = obj["onVolume"] as! ((_ volume: NSNumber) -> Void)?
|
||||
self.onStart = obj["onStart"] as! ((_ res: AudioStartRes) -> Void)?
|
||||
self.onError = obj["onError"] as! ((_ res: AudioErrorRes) -> Void)?
|
||||
self.onStop = obj["onStop"] as! (() -> Void)?
|
||||
self.onFile = obj["onFile"] as! ((_ res: AudioFileRes) -> Void)?
|
||||
}
|
||||
}
|
||||
public var UniErrorSubject = "ty-recording"
|
||||
public var AudioErrors = Map<AudioErrorCode, String>([
|
||||
[
|
||||
9010001,
|
||||
"麦克风权限未授予,请前往设置开启1"
|
||||
],
|
||||
[
|
||||
9010002,
|
||||
"麦克风权限被永久拒绝,请前往设置开启"
|
||||
],
|
||||
[
|
||||
9010003,
|
||||
"获取麦克风权限状态失败"
|
||||
],
|
||||
[
|
||||
9010010,
|
||||
"音频录制实例初始化失败"
|
||||
],
|
||||
[
|
||||
9010011,
|
||||
"音频录制实例未初始化,无法执行操作"
|
||||
],
|
||||
[
|
||||
9010012,
|
||||
"系统资源不足,无法创建音频录制实例"
|
||||
],
|
||||
[
|
||||
9010020,
|
||||
"音频采集读取数据失败"
|
||||
],
|
||||
[
|
||||
9010021,
|
||||
"音频采集被中断(麦克风被其他应用抢占)"
|
||||
],
|
||||
[
|
||||
9010022,
|
||||
"音频采集缓冲区无有效数据"
|
||||
],
|
||||
[
|
||||
9010023,
|
||||
"启动音频采集失败"
|
||||
],
|
||||
[
|
||||
9010030,
|
||||
"采集线程创建失败"
|
||||
],
|
||||
[
|
||||
9010031,
|
||||
"采集线程启动失败"
|
||||
],
|
||||
[
|
||||
9010032,
|
||||
"采集线程中断失败"
|
||||
],
|
||||
[
|
||||
9010033,
|
||||
"采集线程运行崩溃"
|
||||
],
|
||||
[
|
||||
9010040,
|
||||
"停止音频采集失败"
|
||||
],
|
||||
[
|
||||
9010041,
|
||||
"音频录制资源释放失败"
|
||||
],
|
||||
[
|
||||
9010042,
|
||||
"音频资源重复释放"
|
||||
],
|
||||
[
|
||||
9010043,
|
||||
"音频资源泄漏(未正常释放)"
|
||||
],
|
||||
[
|
||||
9010050,
|
||||
"当前设备不支持该采样率"
|
||||
],
|
||||
[
|
||||
9010051,
|
||||
"声道配置非法或不被支持"
|
||||
],
|
||||
[
|
||||
9010052,
|
||||
"音频格式非法或不被支持"
|
||||
],
|
||||
[
|
||||
9010053,
|
||||
"采集缓冲区大小低于系统最小值"
|
||||
],
|
||||
[
|
||||
9010054,
|
||||
"采集缓冲区大小配置非法"
|
||||
],
|
||||
[
|
||||
9010060,
|
||||
"麦克风被其他应用占用"
|
||||
],
|
||||
[
|
||||
9010061,
|
||||
"音频硬件故障"
|
||||
],
|
||||
[
|
||||
9010062,
|
||||
"系统资源不足,无法创建音频实例"
|
||||
],
|
||||
[
|
||||
9010063,
|
||||
"系统音频服务异常或未启动"
|
||||
],
|
||||
[
|
||||
9010070,
|
||||
"重复启动音频录制"
|
||||
],
|
||||
[
|
||||
9010071,
|
||||
"未在录制状态,无法执行停止/暂停操作"
|
||||
],
|
||||
[
|
||||
9010072,
|
||||
"音频数据缓冲区拷贝失败"
|
||||
],
|
||||
[
|
||||
9010080,
|
||||
"预留错误码"
|
||||
]
|
||||
])
|
||||
@objc(UTSSDKModulesTyRecordingStartAudioRecordFailImpl)
|
||||
@objcMembers
|
||||
public class StartAudioRecordFailImpl : UniError, AudioErrorRes {
|
||||
public init(_ errCode: NSNumber){
|
||||
super.init()
|
||||
self.errSubject = UniErrorSubject
|
||||
self.errCode = errCode
|
||||
self.errMsg = AudioErrors.get(errCode as! AudioErrorCode) ?? ""
|
||||
}
|
||||
}
|
||||
public var isRecording = false
|
||||
public var audioEngine: AVAudioEngine? = nil
|
||||
public var inputNode: AVAudioInputNode? = nil
|
||||
public var targetSampleRate: Double = 16000.0
|
||||
public var channels: NSNumber = 1
|
||||
public var recordStartTime: NSNumber? = nil
|
||||
public func preRequestRecordPermission(_ callback: ((_ granted: Bool) -> Void)?) {
|
||||
var audioSession = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
try audioSession.setCategory(AVAudioSession.Category.record)
|
||||
try audioSession.setActive(true)
|
||||
audioSession.requestRecordPermission({
|
||||
(granted: Bool) -> Void in
|
||||
console.log("预申请麦克风权限结果:", granted)
|
||||
do {
|
||||
try audioSession.setActive(false)
|
||||
}
|
||||
catch let e {
|
||||
var e = UTSError(e)
|
||||
console.warn("预申请后停用音频会话失败:", e)
|
||||
}
|
||||
callback?(granted)
|
||||
})
|
||||
}
|
||||
catch let error {
|
||||
var error = UTSError(error)
|
||||
console.error("预申请麦克风权限失败:", error)
|
||||
callback?(false)
|
||||
}
|
||||
}
|
||||
public func startAudioRecord(_ options: StartAudioRecordOptions) {
|
||||
console.log("避免重复启动", isRecording)
|
||||
if (isRecording) {
|
||||
return
|
||||
}
|
||||
do {
|
||||
var audioSession = AVAudioSession.sharedInstance()
|
||||
try audioSession.setCategory(AVAudioSession.Category.record)
|
||||
try audioSession.setActive(true)
|
||||
isRecording = true
|
||||
audioSession.requestRecordPermission({
|
||||
(granted: Bool) -> Void in
|
||||
if (!granted) {
|
||||
do {
|
||||
try audioSession.setActive(false, options: AVAudioSession.SetActiveOptions.notifyOthersOnDeactivation)
|
||||
} catch let e {}
|
||||
isRecording = false
|
||||
var err = StartAudioRecordFailImpl(9010002)
|
||||
options.onError?(err)
|
||||
} else {
|
||||
do {
|
||||
audioEngine = AVAudioEngine.init()
|
||||
inputNode = audioEngine!.inputNode
|
||||
var unwrappedInputNode = inputNode!
|
||||
var inputFormat = unwrappedInputNode.inputFormat(forBus: 0)
|
||||
console.log("硬件原生采样率:", inputFormat.sampleRate)
|
||||
recordStartTime = Date.init().getTime()
|
||||
var recordId = """
|
||||
\(Date.init().getTime())_\(Math.floor(Math.random() * 10000))
|
||||
"""
|
||||
options.onStart?(AudioStartRes(UTSJSONObject([
|
||||
"recordId": recordId,
|
||||
"sampleRate": inputFormat.sampleRate,
|
||||
"startTime": recordStartTime
|
||||
])))
|
||||
var targetFormat = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: targetSampleRate, channels: inputFormat.channelCount, interleaved: false)
|
||||
unwrappedInputNode.installTap(onBus: 0, bufferSize: 2048, format: inputFormat, block: {
|
||||
(buffer: AVAudioPCMBuffer, time: Any) -> Void in
|
||||
var inputFrameLen = Double(buffer.frameLength)
|
||||
var sampleRateRatio = targetSampleRate / inputFormat.sampleRate
|
||||
var outputFrameCounta = inputFrameLen * sampleRateRatio
|
||||
var outputFrameCount = AVAudioFrameCount(outputFrameCounta)
|
||||
var outputBufferOpt = AVAudioPCMBuffer.init(pcmFormat: targetFormat!, frameCapacity: outputFrameCount)
|
||||
outputBufferOpt!.frameLength = outputFrameCount
|
||||
var inputBlock: AVAudioConverterInputBlock = {
|
||||
(_ inNumPackets, _ outStatus) -> AVAudioBuffer in
|
||||
outStatus.pointee = AVAudioConverterInputStatus.haveData
|
||||
return buffer
|
||||
}
|
||||
var convertError: NSError? = nil
|
||||
var converter = AVAudioConverter.init(from: inputFormat, to: targetFormat!)
|
||||
var convertStatus = converter!.convert(to: outputBufferOpt!, error: &convertError, withInputFrom: inputBlock)
|
||||
var int16Ptr = outputBufferOpt!.int16ChannelData
|
||||
var dataPtr = int16Ptr![0]
|
||||
var _recordedPCMData: [UInt8] = []
|
||||
do {
|
||||
var i: Int = 0
|
||||
while(i < outputFrameCount){
|
||||
var int16Value = dataPtr[i]
|
||||
var byte1 = UInt8(int16Value & 0xFF)
|
||||
var byte2 = UInt8((int16Value >> 8) & 0xFF)
|
||||
_recordedPCMData.push(byte1)
|
||||
_recordedPCMData.push(byte2)
|
||||
i++
|
||||
}
|
||||
}
|
||||
options.onFrame?(_recordedPCMData)
|
||||
})
|
||||
try audioEngine!.start()
|
||||
if (audioEngine!.isRunning) {
|
||||
console.log("引擎确实处于运行状态")
|
||||
isRecording = true
|
||||
} else {
|
||||
console.error("调用 start() 但引擎未运行")
|
||||
}
|
||||
isRecording = true
|
||||
}
|
||||
catch let error {
|
||||
var error = UTSError(error)
|
||||
isRecording = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
catch let error {
|
||||
var error = UTSError(error)
|
||||
console.log("报错了", error)
|
||||
var err = StartAudioRecordFailImpl(9010002)
|
||||
options.onError?(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
public func stopAudioRecord() {
|
||||
console.log("停止音频采集")
|
||||
if (audioEngine == nil) {
|
||||
isRecording = false
|
||||
return
|
||||
}
|
||||
var _audioEngine = audioEngine!
|
||||
if (_audioEngine.isRunning) {
|
||||
_audioEngine.stop()
|
||||
}
|
||||
if (inputNode == nil) {
|
||||
isRecording = false
|
||||
return
|
||||
}
|
||||
var _inputNode = inputNode!
|
||||
_inputNode.removeTap(onBus: 0)
|
||||
console.log("移除音频回调")
|
||||
var audioSession = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
try audioSession.setActive(false)
|
||||
}
|
||||
catch let e {
|
||||
var e = UTSError(e)
|
||||
console.log(e)
|
||||
}
|
||||
inputNode = nil
|
||||
audioEngine = nil
|
||||
isRecording = false
|
||||
}
|
||||
@objc(UTSSDKModulesTyRecordingStartAudioRecordOptionsJSONObject)
|
||||
@objcMembers
|
||||
public class StartAudioRecordOptionsJSONObject : NSObject {
|
||||
public var autoStopDuration: NSNumber?
|
||||
public var onFrame: UTSCallback?
|
||||
public var onVolume: UTSCallback?
|
||||
public var onStart: UTSCallback?
|
||||
public var onError: UTSCallback?
|
||||
public var onStop: UTSCallback?
|
||||
public var onFile: UTSCallback?
|
||||
}
|
||||
public func preRequestRecordPermissionByJs(_ callback: UTSCallback?) {
|
||||
return preRequestRecordPermission({
|
||||
(granted: Bool) -> Void in
|
||||
callback?(granted)
|
||||
})
|
||||
}
|
||||
public func startAudioRecordByJs(_ options: StartAudioRecordOptionsJSONObject) {
|
||||
return startAudioRecord(StartAudioRecordOptions(UTSJSONObject([
|
||||
"autoStopDuration": options.autoStopDuration,
|
||||
"onFrame": {
|
||||
(frameData: Any) -> Void in
|
||||
options.onFrame?(frameData)
|
||||
},
|
||||
"onVolume": {
|
||||
(volume: NSNumber) -> Void in
|
||||
options.onVolume?(volume)
|
||||
},
|
||||
"onStart": {
|
||||
(res: AudioStartRes) -> Void in
|
||||
options.onStart?(res)
|
||||
},
|
||||
"onError": {
|
||||
(res: AudioErrorRes) -> Void in
|
||||
options.onError?(res)
|
||||
},
|
||||
"onStop": {
|
||||
() -> Void in
|
||||
options.onStop?()
|
||||
},
|
||||
"onFile": {
|
||||
(res: AudioFileRes) -> Void in
|
||||
options.onFile?(res)
|
||||
}
|
||||
])))
|
||||
}
|
||||
public func stopAudioRecordByJs() {
|
||||
return stopAudioRecord()
|
||||
}
|
||||
@objc(UTSSDKModulesTyRecordingIndexSwift)
|
||||
@objcMembers
|
||||
public class UTSSDKModulesTyRecordingIndexSwift : NSObject {
|
||||
public static func s_preRequestRecordPermissionByJs(_ callback: UTSCallback?) {
|
||||
return preRequestRecordPermissionByJs(callback)
|
||||
}
|
||||
public static func s_startAudioRecordByJs(_ options: StartAudioRecordOptionsJSONObject) {
|
||||
return startAudioRecordByJs(options)
|
||||
}
|
||||
public static func s_stopAudioRecordByJs() {
|
||||
return stopAudioRecordByJs()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user