JLBA202505130001_关于吉林银行新建AI智能培训系统的需求_修改安卓工程的权限列表,适配安卓13+
@@ -1,2 +1,2 @@
|
||||
#Sun Mar 08 15:36:39 CST 2026
|
||||
gradle.version=8.11
|
||||
#Thu Mar 19 16:52:20 CST 2026
|
||||
gradle.version=8.11.1
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
.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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minSdkVersion": "35"
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deploymentTarget": "12"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
import DCloudUTSFoundation
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"minSdkVersion": "22"
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"deploymentTarget": "13"
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 460 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -1 +1,2 @@
|
||||
cn.com.jlbank.ai
|
||||
cn.com.jlbank.ai
|
||||
|
||||
|
||||