diff --git a/pages/main/lockDetail.vue b/pages/main/lockDetail.vue index bd4ee91..3cc70b9 100644 --- a/pages/main/lockDetail.vue +++ b/pages/main/lockDetail.vue @@ -288,6 +288,10 @@ import { deleteKeyRequest } from '@/api/key' import { transportType } from '@/constant/transportType' + // #ifdef APP-PLUS + import { requestPermission } from '@/uni_modules/xhj-record' + // #endif + const $bluetooth = useBluetoothStore() const $basic = useBasicStore() const $lock = useLockStore() @@ -309,7 +313,18 @@ await getServeTime() }) - const jumpToPlayer = () => { + const jumpToPlayer = async () => { + // #ifdef APP-PLUS + // 检查权限 + const result = await requestPermission() + if (result.code !== 0) { + uni.showToast({ + title: '请在设置中打开应用的麦克风权限', + icon: 'none' + }) + return + } + // #endif $basic.routeJump({ name: 'p2pPlayer' }) } diff --git a/pages/p2p/p2pPlayer.vue b/pages/p2p/p2pPlayer.vue index b38b8f2..12c34a6 100644 --- a/pages/p2p/p2pPlayer.vue +++ b/pages/p2p/p2pPlayer.vue @@ -189,12 +189,12 @@ // #ifdef APP-PLUS import { startService, - getLiveUrl, stopService, runSendService, stopSendService, dataSend } from '@/uni_modules/xhj-tencent-xp2p' + import { initAudio, onStartRecord, stopRecord, releaseRecord } from '@/uni_modules/xhj-record' // #endif const $bluetooth = useBluetoothStore() @@ -229,9 +229,8 @@ onMounted(async () => { // #ifdef APP-PLUS + initAudio() isApp.value = true - // 初始化录音管理器 - initRecorder() // #endif // #ifdef MP-WEIXIN @@ -271,22 +270,13 @@ xp2pInfo: deviceInfo.value.xp2pInfo }) if (result.code === 0) { - setTimeout(async () => { - const urlResult = await getLiveUrl({ - id: `${deviceInfo.value.productId}/${deviceInfo.value.deviceName}` - }) - if (urlResult.code === 0) { - url.value = urlResult.data.url - runSendService( - `${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`, - 'channel=0', - false - ) - handlePlaySuccess() - } else { - $basic.backAndToast(message) - } - }, 200) + url.value = result.data.url + runSendService( + `${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`, + 'channel=0', + false + ) + handlePlaySuccess() } else { $basic.backAndToast(message) } @@ -299,10 +289,7 @@ onUnload(() => { // #ifdef APP-PLUS // 停止录音 - if (isVoice.value && recorderManager.value) { - stopRecording() - } - + releaseRecord() stopSendService(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`) stopService({ id: `${deviceInfo.value.productId}/${deviceInfo.value.deviceName}` @@ -480,104 +467,6 @@ return false } - // #ifdef APP-PLUS - const recorderManager = ref(null) - const recordingFilePath = ref('') - // #endif - - const convertAudioFileToUint8Array = filePath => { - // #ifdef APP-PLUS - try { - plus.io.resolveLocalFileSystemURL(filePath, entry => { - entry.file( - file => { - console.log('文件内容', file) - if (file.size === 0) return - - const fileReader = new plus.io.FileReader() - - fileReader.onloadend = async evt => { - try { - console.log(evt) - const base64 = evt.target.result.split(',')[1] - - const binaryString = atob(base64) - const byteArray = [] - - for (let i = 0; i < binaryString.length; i++) { - byteArray[i] = binaryString.charCodeAt(i) - } - - const id = `${deviceInfo.value.productId}/${deviceInfo.value.deviceName}` - - await dataSend(id, byteArray) - } catch (error) { - console.error('音频数据转换失败:', error) - } - } - - fileReader.onerror = () => { - console.error('音频文件读取失败') - } - - fileReader.readAsDataURL(file) - }, - error => console.error('获取文件对象失败:', error) - ) - }) - } catch (error) { - console.error('音频文件处理异常:', error) - } - // #endif - } - - const initRecorder = () => { - // #ifdef APP-PLUS - recorderManager.value = uni.getRecorderManager() - - recorderManager.value.onStart(() => { - console.log('录音开始') - }) - - recorderManager.value.onError(error => { - console.error('录音出错:', error) - }) - - recorderManager.value.onStop(res => { - console.log('录音结束:', res) - recordingFilePath.value = res.tempFilePath - - convertAudioFileToUint8Array(res.tempFilePath) - }) - // #endif - } - - // 开始录音 - const startRecording = () => { - // #ifdef APP-PLUS - if (!recorderManager.value) { - initRecorder() - } - - recorderManager.value.start({ - duration: 60000, - sampleRate: 16000, - format: 'aac' - }) - console.log('开始录音') - // #endif - } - - // 停止录音 - const stopRecording = () => { - // #ifdef APP-PLUS - if (recorderManager.value) { - recorderManager.value.stop() - console.log('停止录音') - } - // #endif - } - const toggleVoice = () => { // #ifdef MP-WEIXIN if (isVoice.value) { @@ -600,17 +489,19 @@ // #ifdef APP-PLUS if (isVoice.value) { isVoice.value = false - - stopRecording() + stopRecord() } else { uni.vibrateLong() isVoice.value = true - // 初始化并开始录音 - startRecording() + onStartRecord(callback) } // #endif } + const callback = audioData => { + dataSend(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`, audioData) + } + const handlePlaySuccess = () => { isVideoLoaded.value = true } diff --git a/uni_modules/xhj-record/package.json b/uni_modules/xhj-record/package.json new file mode 100644 index 0000000..4b6c479 --- /dev/null +++ b/uni_modules/xhj-record/package.json @@ -0,0 +1,83 @@ +{ + "id": "xhj-record", + "displayName": "xhj-record", + "version": "1.0.0", + "description": "xhj-record", + "keywords": [ + "xhj-record" +], + "repository": "", + "engines": { + "HBuilderX": "^3.6.8" + }, + "dcloudext": { + "type": "uts", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "", + "data": "", + "permissions": "" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "u", + "aliyun": "u", + "alipay": "u" + }, + "client": { + "Vue": { + "vue2": "u", + "vue3": "u" + }, + "App": { + "app-android": "u", + "app-ios": "u", + "app-harmony": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/xhj-record/utssdk/app-android/AndroidManifest.xml b/uni_modules/xhj-record/utssdk/app-android/AndroidManifest.xml new file mode 100644 index 0000000..211df66 --- /dev/null +++ b/uni_modules/xhj-record/utssdk/app-android/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/uni_modules/xhj-record/utssdk/app-android/config.json b/uni_modules/xhj-record/utssdk/app-android/config.json new file mode 100644 index 0000000..bf95925 --- /dev/null +++ b/uni_modules/xhj-record/utssdk/app-android/config.json @@ -0,0 +1,3 @@ +{ + "minSdkVersion": "21" +} \ No newline at end of file diff --git a/uni_modules/xhj-record/utssdk/app-android/index.uts b/uni_modules/xhj-record/utssdk/app-android/index.uts new file mode 100644 index 0000000..6735ea4 --- /dev/null +++ b/uni_modules/xhj-record/utssdk/app-android/index.uts @@ -0,0 +1,209 @@ +/* eslint-disable */ +// @ts-nocheck +// @ts-ignore-start +import 'android.media.AudioRecord' +import 'android.media.MediaRecorder' +import 'android.media.AudioFormat' +import 'android.media.MediaSyncEvent' +import 'java.lang.Thread' +import { UTSAndroid } from 'io.dcloud.uts' +import { Result } from '../interface.uts' + +let recorder: AudioRecord | null = null +let recorderState: boolean = false +let bufferSizeInBytes: Int = 0 + +export const requestPermission = async function (): Promise { + return new Promise(resolve => { + try { + let permissionNeed = ['android.permission.RECORD_AUDIO'] + + UTSAndroid.requestSystemPermission( + UTSAndroid.getUniActivity()!, + permissionNeed, + function (allRight: boolean, _: string[]) { + if (allRight) { + resolve({ + code: 0, + data: {}, + message: '成功' + }) + } else { + resolve({ + code: -1, + data: {}, + message: '失败' + }) + } + }, + function (_: boolean, _: string[]) { + resolve({ + code: -1, + data: {}, + message: '失败' + }) + } + ) + } catch (error) { + resolve({ + code: -1, + data: {}, + message: error.toString() + }) + } + }) +} + +export const initAudio = async function (): Promise { + try { + const audioSource = MediaRecorder.AudioSource.MIC + const sampleRateInHz = 44100 + const channelConfig = AudioFormat.CHANNEL_IN_MONO + const audioFormat = AudioFormat.ENCODING_PCM_16BIT + + bufferSizeInBytes = AudioRecord.getMinBufferSize( + sampleRateInHz.toInt(), + channelConfig, + audioFormat + ) + + console.log('bufferSizeInBytes', bufferSizeInBytes) + + recorder = new AudioRecord( + audioSource, + sampleRateInHz.toInt(), + channelConfig, + audioFormat, + bufferSizeInBytes + ) + + const currentRecorder = recorder + if (currentRecorder !== null) { + console.log('初始化录音结果:', currentRecorder.getState()) + if (currentRecorder.getState() == AudioRecord.STATE_INITIALIZED) { + return { + code: 0, + data: {}, + message: '成功' + } + } + } + return { + code: -1, + data: {}, + message: '初始化录音失败' + } + } catch (error) { + console.log('初始化录音失败', error) + return { + code: -1, + data: {}, + message: error.toString() + } + } +} + +export async function onStartRecord(callback: (data: Array) => void): Promise { + try { + const callbackFunction = () => { + const currentRecorder = recorder + if (currentRecorder !== null) { + while (recorderState) { + let audioData = new ByteArray(bufferSizeInBytes) + const result: Int = currentRecorder.read(audioData, 0, bufferSizeInBytes) + if (result > 0) { + callback(Array.fromNative(audioData)) + console.log('录音按帧返回数据', Array.fromNative(audioData)) + } + Thread.sleep(10) + } + } + return + } + + const currentRecorder = recorder + if (currentRecorder !== null) { + currentRecorder.startRecording() + console.log('开始录音') + recorderState = true + + callbackFunction() + + return { + code: 0, + data: {}, + message: '成功' + } + } + return { + code: -1, + data: {}, + message: '开始录音失败' + } + } catch (error) { + console.log('开始录音失败', error) + return { + code: -1, + data: {}, + message: error.toString() + } + } +} + +export const stopRecord = async function (): Promise { + try { + const currentRecorder = recorder + if (currentRecorder !== null) { + currentRecorder.stop() + recorderState = false + console.log('停止录音') + return { + code: 0, + data: {}, + message: '成功' + } + } + return { + code: -1, + data: {}, + message: '停止录音失败' + } + } catch (error) { + console.log('停止录音失败', error) + recorderState = false + return { + code: -1, + data: {}, + message: error.toString() + } + } +} + +export const releaseRecord = async function (): Promise { + try { + const currentRecorder = recorder + if (currentRecorder !== null) { + currentRecorder.release() + console.log('释放录音') + return { + code: 0, + data: {}, + message: '成功' + } + } + return { + code: -1, + data: {}, + message: '释放录音失败' + } + } catch (error) { + console.log('释放录音失败', error) + return { + code: -1, + data: {}, + message: error.toString() + } + } +} + +// @ts-ignore-end diff --git a/uni_modules/xhj-record/utssdk/interface.uts b/uni_modules/xhj-record/utssdk/interface.uts new file mode 100644 index 0000000..0dab6cd --- /dev/null +++ b/uni_modules/xhj-record/utssdk/interface.uts @@ -0,0 +1,5 @@ +export type Result = { + code: number + data: object + message: string +} diff --git a/uni_modules/xhj-tencent-xp2p/utssdk/app-android/index.uts b/uni_modules/xhj-tencent-xp2p/utssdk/app-android/index.uts index f37c1e1..60e6bc9 100644 --- a/uni_modules/xhj-tencent-xp2p/utssdk/app-android/index.uts +++ b/uni_modules/xhj-tencent-xp2p/utssdk/app-android/index.uts @@ -1,7 +1,69 @@ +/* eslint-disable */ +// @ts-nocheck +// @ts-ignore-start + import { UTSAndroid } from 'io.dcloud.uts' -import { XP2PAppConfig, XP2P } from 'com.tencent.xnet' +import { XP2PAppConfig, XP2P, XP2PCallback } from 'com.tencent.xnet' import { Result, InitParams, IdParams } from '../interface.uts' +class XP2PCallbackImpl extends XP2PCallback { + private eventNotifyCallback?: (id: string | null, msg: string | null, event: Int) => void + + constructor(eventNotifyCallback?: (id: string | null, msg: string | null, event: Int) => void) { + super() + this.eventNotifyCallback = eventNotifyCallback + } + + override fail(msg: string | null, errorCode: Int): void { + console.log('XP2P callback fail:', msg, errorCode) + } + + override commandRequest(id: string | null, msg: string | null): void { + console.log('XP2P callback commandRequest:', id, msg) + } + + override xp2pEventNotify(id: string | null, msg: string | null, event: Int): void { + console.log('XP2P callback xp2pEventNotify:', id, msg, event) + this.eventNotifyCallback?.invoke(id, msg, event) + } + + override avDataRecvHandle(id: string | null, data: ByteArray | null, len: Int): void { + console.log('XP2P callback avDataRecvHandle:', id, len) + } + + override avDataCloseHandle(id: string | null, msg: string | null, errorCode: Int): void { + console.log('XP2P callback avDataCloseHandle:', id, msg, errorCode) + } + + override onDeviceMsgArrived(id: string | null, data: ByteArray | null, len: Int): string { + console.log('XP2P callback onDeviceMsgArrived:', id, len) + return '' + } +} + +async function getLiveUrlAsync( + id: string, + resolve: (result: Result) => void, + reject: (result: Result) => void +): Promise { + try { + const liveUrl = await XP2P.delegateHttpFlv(id) + resolve({ + code: 0, + data: { + url: liveUrl + }, + message: '成功' + }) + } catch (error) { + reject({ + code: -1, + data: {}, + message: error.toString() + }) + } +} + export const startService = async function (params: InitParams): Promise { try { const context = UTSAndroid.getAppContext() @@ -19,31 +81,17 @@ export const startService = async function (params: InitParams): Promise xp2pConfig ) - return { - code: 0, - data: {}, - message: '成功' - } - } catch (error) { - return { - code: -1, - data: {}, - message: error.toString() - } - } -} + return new Promise((resolve, reject) => { + const callback: XP2PCallback = new XP2PCallbackImpl( + (id: string | null, msg: string | null, event: Int): void => { + if (event == 1004) { + getLiveUrlAsync(id!, resolve, reject) + } + } + ) -export const getLiveUrl = async function (params: IdParams): Promise { - try { - const liveUrl = await XP2P.delegateHttpFlv(params.id) - - return { - code: 0, - data: { - url: liveUrl - }, - message: '成功' - } + XP2P.setCallback(callback) + }) } catch (error) { return { code: -1, @@ -70,13 +118,6 @@ export const stopService = async function (params: IdParams): Promise { } } -// export const setCallback = function (callback: XP2PCallback) { -// try { -// XP2P.setCallback(callback) -// } catch (error) { -// console.log(2, error) -// } -// } export const runSendService = async function ( id: string, cmd: string, @@ -119,31 +160,15 @@ export const stopSendService = async function (id: string): Promise { export const dataSend = async function (id: string, data: Array): Promise { try { - let byteTest = new ByteArray(data.length.toInt()) - // byteTest.set(0, (0xff).toByte()) - // byteTest.set(1, (0xf9).toByte()) - - // let profile = 2 - // let freqIdx = 8 - // let chanCfg = 1 - // let packetLen = data.length + 7 - - // byteTest.set(2, (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2)).toByte()) - // byteTest.set(3, (((chanCfg & 3) << 6) + (packetLen >> 11)).toByte()) - // byteTest.set(4, ((packetLen & 0x7ff) >> 3).toByte()) - // byteTest.set(5, (((packetLen & 7) << 5) + 0x1f).toByte()) - // byteTest.set(6, (0xfc).toByte()) + let byteArray = new ByteArray(data.length.toInt()) for (let i = 0; i < data.length; i++) { - byteTest.set(i.toInt(), data[i].toByte()) + byteArray.set(i.toInt(), data[i].toByte()) } - console.log(byteTest) - console.log(1, id, byteTest[0], byteTest[1], data.length.toInt()) + const result = await XP2P.dataSend(id, byteArray, data.length.toInt()) - const result = await XP2P.dataSend(id, byteTest, data.length.toInt()) - - console.log('发送数据', result) + console.log('发送数据结果', result, byteArray) return { code: 0, @@ -158,3 +183,5 @@ export const dataSend = async function (id: string, data: Array): Promis } } } + +// @ts-ignore-end