feat: 完成安卓端监控相关逻辑

This commit is contained in:
fanpeng 2025-06-24 18:15:27 +08:00
parent af68801d1b
commit 3a85d42c02
8 changed files with 419 additions and 178 deletions

View File

@ -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' })
}

View File

@ -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
}

View File

@ -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"
}
}
}
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="io.dcloud.nativeresouce">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application>
<!--meta-data-->
</application>
</manifest>

View File

@ -0,0 +1,3 @@
{
"minSdkVersion": "21"
}

View File

@ -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<Result> {
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<Result> {
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<Result> {
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<Result> {
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<Result> {
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

View File

@ -0,0 +1,5 @@
export type Result = {
code: number
data: object
message: string
}

View File

@ -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<void> {
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<Result> {
try {
const context = UTSAndroid.getAppContext()
@ -19,31 +81,17 @@ export const startService = async function (params: InitParams): Promise<Result>
xp2pConfig
)
return {
code: 0,
data: {},
message: '成功'
}
} catch (error) {
return {
code: -1,
data: {},
message: error.toString()
}
}
}
return new Promise<Result>((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<Result> {
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<Result> {
}
}
// 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<Result> {
export const dataSend = async function (id: string, data: Array<number>): Promise<Result> {
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<number>): Promis
}
}
}
// @ts-ignore-end