feat: ios添加录音相关功能

This commit is contained in:
fanpeng 2025-07-04 18:53:21 +08:00
parent 026212c5b5
commit ea2000af96
9 changed files with 541 additions and 140 deletions

View File

@ -1,114 +1,114 @@
{
"name": "星星锁Lite",
"appid": "__UNI__933D519",
"description": "",
"versionName": "1.3.2",
"versionCode": "39",
"mp-weixin": {
"appid": "wx9829a39e65550757",
"setting": {
"urlCheck": true,
"minified": true
},
"permission": {
"scope.bluetooth": {
"desc": "蓝牙将用于控制和管理您的智能门锁"
}
},
"usingComponents": true,
"lazyCodeLoading": "requiredComponents",
"optimization": {
"subPackages": true
},
"plugins": {
"wmpf-voip": {
"version": "latest",
"provider": "wxf830863afde621eb",
"genericsImplementation": {
"call-page-plugin": {
"custombox": "pages/main/customBox"
}
}
}
}
},
"vueVersion": "3",
"app-plus": {
"distribute": {
"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"
"name" : "星星锁Lite",
"appid" : "__UNI__933D519",
"description" : "",
"versionName" : "1.3.2",
"versionCode" : "39",
"mp-weixin" : {
"appid" : "wx9829a39e65550757",
"setting" : {
"urlCheck" : true,
"minified" : true
},
"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"
}
"permission" : {
"scope.bluetooth" : {
"desc" : "蓝牙将用于控制和管理您的智能门锁"
}
},
"usingComponents" : true,
"lazyCodeLoading" : "requiredComponents",
"optimization" : {
"subPackages" : true
},
"plugins" : {
"wmpf-voip" : {
"version" : "latest",
"provider" : "wxf830863afde621eb",
"genericsImplementation" : {
"call-page-plugin" : {
"custombox" : "pages/main/customBox"
}
}
}
}
},
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>"
],
"targetSdkVersion": 34,
"abiFilters": ["armeabi-v7a", "arm64-v8a"]
},
"ios": {
"dSYMs": false
}
},
"modules": {
"Bluetooth": {},
"VideoPlayer": {},
"Camera": {},
"Record": {}
},
"splashscreen": {
"waiting": false
"vueVersion" : "3",
"app-plus" : {
"distribute" : {
"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"
}
}
},
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\" />",
"<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>"
],
"targetSdkVersion" : 34,
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
},
"ios" : {
"dSYMs" : false
}
},
"modules" : {
"Bluetooth" : {},
"VideoPlayer" : {},
"Camera" : {},
"Record" : {}
},
"splashscreen" : {
"waiting" : false
}
}
}
}

View File

@ -314,7 +314,7 @@
})
const jumpToPlayer = async () => {
// #ifdef APP-ANDROID
// #ifdef APP-PLUS
//
const result = await requestPermission()
if (result.code !== 0) {

View File

@ -193,7 +193,7 @@
stopServiceFunction,
runSendServiceFunction,
stopSendServiceFunction,
dataSend
dataSendFunction
} from '@/uni_modules/xhj-tencent-xp2p'
import { initAudio, onStartRecord, stopRecord, releaseRecord } from '@/uni_modules/xhj-record'
// #endif
@ -224,6 +224,7 @@
const isMute = ref(false)
const isVideoLoaded = ref(false)
const videoKey = ref(Date.now())
const cleanupCalled = ref(false)
const advancedOptions = ref([
{
@ -249,6 +250,34 @@
return data
})
const cleanupResources = () => {
if (cleanupCalled.value) return
cleanupCalled.value = true
// #ifdef APP-PLUS
//
url.value = null
isVideoLoaded.value = false
if (isVoice.value) {
stopRecord()
}
releaseRecord()
if (deviceInfo.value) {
stopSendServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
stopServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
}
// #endif
// #ifdef MP-WEIXIN
const page = getCurrentPages().pop()
if (page) {
const player = page.selectComponent('#playerRef')
if (player && player.stopAll) {
player.stopAll()
}
}
// #endif
}
onMounted(async () => {
// #ifdef APP-PLUS
initAudio()
@ -307,7 +336,7 @@
runSendServiceFunction(
`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`,
'channel=0',
true
false
)
}, 0)
} else {
@ -320,19 +349,7 @@
})
onUnload(() => {
// #ifdef APP-PLUS
//
releaseRecord()
stopSendServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
stopServiceFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`)
// #endif
// #ifdef MP-WEIXIN
const page = getCurrentPages().pop()
const player = page.selectComponent('#playerRef')
if (player.stopAll) {
player.stopAll()
}
// #endif
cleanupResources()
})
const showQualitySelector = () => {
@ -384,13 +401,7 @@
}
const handleHangUp = () => {
// #ifdef MP-WEIXIN
const page = getCurrentPages().pop()
const player = page.selectComponent('#playerRef')
if (player.stopAll) {
player.stopAll()
}
// #endif
cleanupResources()
uni.navigateBack()
}
@ -535,7 +546,7 @@
}
const callback = audioData => {
dataSend(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`, audioData)
dataSendFunction(`${deviceInfo.value.productId}/${deviceInfo.value.deviceName}`, audioData)
}
const handlePlaySuccess = () => {

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风进行录音</string>
</dict>
</plist>

View File

@ -0,0 +1,244 @@
import Foundation
import AVFoundation
@objc
public class RecordPermission: NSObject {
@objc
public static func requestRecordPermission(_ completion: @escaping (Bool) -> Void) {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
completion(granted)
}
}
}
@objc
public class AudioRecorderManager: NSObject {
private var audioEngine: AVAudioEngine?
private var audioConverter: AVAudioConverter?
private var aacBuffer: AVAudioCompressedBuffer?
private let lock = NSLock()
private var _isRecording = false
var isRecording: Bool {
get {
lock.lock()
defer { lock.unlock() }
return _isRecording
}
set {
lock.lock()
_isRecording = newValue
lock.unlock()
}
}
@objc
public static let shared = AudioRecorderManager()
private override init() {}
@objc
public func initAudio(_ completion: @escaping (Bool, String) -> Void) {
completion(true, "Module initialized")
}
@objc
public func startRecord(_ completion: @escaping (Data?, Bool, String) -> Void) {
if self.isRecording {
completion(nil, false, "Recording is already in progress.")
return
}
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker)
try session.setPreferredSampleRate(16000.0)
try session.setPreferredInputNumberOfChannels(1)
try session.setActive(true)
} catch {
completion(nil, false, "Failed to set up audio session: \(error.localizedDescription)")
return
}
audioEngine = AVAudioEngine()
guard let audioEngine = audioEngine else {
completion(nil, false, "Failed to create audio engine")
return
}
let inputNode = audioEngine.inputNode
let inputFormat = inputNode.outputFormat(forBus: 0)
var outputFormatDescription = AudioStreamBasicDescription(
mSampleRate: 16000.0,
mFormatID: kAudioFormatMPEG4AAC,
mFormatFlags: 2,
mBytesPerPacket: 0,
mFramesPerPacket: 1024,
mBytesPerFrame: 0,
mChannelsPerFrame: 1,
mBitsPerChannel: 0,
mReserved: 0
)
guard let outputFormat = AVAudioFormat(streamDescription: &outputFormatDescription) else {
completion(nil, false, "Failed to create output audio format")
return
}
guard let converter = AVAudioConverter(from: inputFormat, to: outputFormat) else {
completion(nil, false, "Failed to create audio converter")
return
}
self.audioConverter = converter
self.aacBuffer = AVAudioCompressedBuffer(
format: outputFormat,
packetCapacity: 1,
maximumPacketSize: converter.maximumOutputPacketSize
)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: inputFormat) { [weak self] (pcmBuffer, when) in
guard let self = self, self.isRecording else { return }
self.convert(pcmBuffer: pcmBuffer, completion: completion)
}
do {
audioEngine.prepare()
try audioEngine.start()
self.isRecording = true
completion(nil, true, "Recording started")
} catch {
self.isRecording = false
completion(nil, false, "Failed to start audio engine: \(error.localizedDescription)")
}
}
private func convert(pcmBuffer: AVAudioPCMBuffer, completion: @escaping (Data?, Bool, String) -> Void) {
guard let converter = self.audioConverter, let outputBuffer = self.aacBuffer else { return }
outputBuffer.byteLength = 0
outputBuffer.packetCount = 0
var error: NSError?
var pcmBufferWasProvided = false
let status = converter.convert(to: outputBuffer, error: &error) { _, outStatus in
if pcmBufferWasProvided {
outStatus.pointee = .noDataNow
return nil
}
outStatus.pointee = .haveData
pcmBufferWasProvided = true
return pcmBuffer
}
guard status != .error, error == nil else {
print("AAC conversion error: \(error?.localizedDescription ?? "unknown")")
return
}
if outputBuffer.byteLength == 0 {
return
}
let aacData = Data(bytes: outputBuffer.data, count: Int(outputBuffer.byteLength))
guard let adtsHeader = self.adtsHeader(for: aacData.count) else {
print("Failed to create ADTS header")
return
}
var fullPacket = Data()
fullPacket.append(Data(adtsHeader))
fullPacket.append(aacData)
completion(fullPacket, true, "")
}
private func adtsHeader(for aacFrameSize: Int) -> [UInt8]? {
guard let outputFormat = self.audioConverter?.outputFormat else { return nil }
let adtsLength = aacFrameSize + 7
let sampleRate = outputFormat.sampleRate
let channels = outputFormat.channelCount
let sampleRateIndices: [Double: Int] = [
96000: 0, 88200: 1, 64000: 2, 48000: 3, 44100: 4, 32000: 5,
24000: 6, 22050: 7, 16000: 8, 12000: 9, 11025: 10, 8000: 11, 7350: 12
]
guard let freqIndex = sampleRateIndices[sampleRate] else {
print("Unsupported sample rate for ADTS header: \(sampleRate)")
return nil
}
let profile = 2 // AAC-LC
let channelCfg = channels
var adtsHeader = [UInt8](repeating: 0, count: 7)
adtsHeader[0] = 0xFF
adtsHeader[1] = 0xF9
adtsHeader[2] = UInt8(((profile - 1) << 6) | (freqIndex << 2) | (Int(channelCfg) >> 2))
adtsHeader[3] = UInt8((Int(channelCfg) & 3) << 6 | (adtsLength >> 11))
adtsHeader[4] = UInt8((adtsLength & 0x7FF) >> 3)
adtsHeader[5] = UInt8(((adtsLength & 7) << 5) | 0x1F)
adtsHeader[6] = 0xFC
return adtsHeader
}
@objc
public func stopRecord(_ completion: @escaping (Bool, String, String) -> Void) {
guard self.isRecording else {
completion(false, "Recording is not in progress.", "")
return
}
self.isRecording = false
audioEngine?.stop()
audioEngine?.inputNode.removeTap(onBus: 0)
audioEngine = nil
audioConverter = nil
aacBuffer = nil
do {
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
} catch {
print("Failed to deactivate audio session: \(error)")
}
completion(true, "Recording stopped", "")
}
@objc
public func releaseRecord(_ completion: @escaping (Bool, String) -> Void) {
if self.isRecording {
self.isRecording = false
audioEngine?.stop()
audioEngine?.inputNode.removeTap(onBus: 0)
}
audioEngine = nil
audioConverter = nil
aacBuffer = nil
do {
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
} catch {
print("Failed to deactivate audio session on release: \(error)")
}
completion(true, "Record released")
}
}
@objc
public class UTSConversionHelper: NSObject {
@objc
public static func dataToNSArray(_ data: Data) -> NSArray {
let byteArray = [UInt8](data)
let nsArray = NSMutableArray()
for byte in byteArray {
nsArray.add(NSNumber(value: byte))
}
return nsArray
}
}

View File

@ -0,0 +1,85 @@
/* eslint-disable */
// @ts-nocheck
// @ts-ignore-start
import { Result } from '../interface.uts'
function dataToByteArray(data: Data): Array<UInt8> {
const nsArray = UTSConversionHelper.dataToNSArray(data)
const buffer: Array<UInt8> = []
if (nsArray != null) {
for (const item of nsArray) {
const num = item as NSNumber
buffer.push(num.uint8Value)
}
}
return buffer
}
export const requestPermission = async function (): Promise<Result> {
return new Promise(resolve => {
RecordPermission.requestRecordPermission((granted: boolean) => {
if (granted) {
resolve({ code: 0, data: {}, message: '成功' })
} else {
resolve({ code: -1, data: {}, message: '失败' })
}
})
})
}
export const initAudio = async function (): Promise<Result> {
return new Promise(resolve => {
AudioRecorderManager.shared.initAudio((success: boolean, message: string) => {
if (success) {
resolve({ code: 0, data: {}, message: message })
} else {
resolve({ code: -1, data: {}, message: message })
}
})
})
}
export async function onStartRecord(callback: (data: Array<UInt8>) => void): Promise<Result> {
return new Promise(resolve => {
AudioRecorderManager.shared.startRecord(
(data: Data | null, success: boolean, message: string) => {
if (data != null) {
callback(dataToByteArray(data!))
} else if (success) {
resolve({ code: 0, data: {}, message: message })
} else {
resolve({ code: -1, data: {}, message: message })
}
}
)
})
}
export const stopRecord = async function (): Promise<Result> {
return new Promise(resolve => {
AudioRecorderManager.shared.stopRecord(
(success: boolean, message: string, filePath: string) => {
if (success) {
resolve({ code: 0, data: { filePath: filePath }, message: message })
} else {
resolve({ code: -1, data: {}, message: message })
}
}
)
})
}
export const releaseRecord = async function (): Promise<Result> {
return new Promise(resolve => {
AudioRecorderManager.shared.releaseRecord((success: boolean, message: string) => {
if (success) {
resolve({ code: 0, data: {}, message: message })
} else {
resolve({ code: -1, data: {}, message: message })
}
})
})
}
// @ts-ignore-end

View File

@ -158,7 +158,7 @@ export const stopSendServiceFunction = async function (id: string): Promise<Resu
}
}
export const dataSend = async function (id: string, data: Array<number>): Promise<Result> {
export const dataSendFunction = async function (id: string, data: Array<number>): Promise<Result> {
try {
let byteArray = new ByteArray(data.length.toInt())

View File

@ -7,7 +7,30 @@ public class P2PConversionHelper: NSObject {
guard let pointer = cString else {
return nil
}
// Use explicit UTF-8 encoding for safety, especially with URLs.
return String(cString: pointer, encoding: .utf8)
}
}
@objc
public class XP2PDataHelper: NSObject {
@objc
public static func createBytes(_ array: NSArray) -> UnsafeMutablePointer<UInt8>? {
let count = array.count
guard count > 0 else { return nil }
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: count)
for i in 0..<count {
if let number = array[i] as? NSNumber {
pointer[i] = number.uint8Value
} else {
pointer[i] = 0 // Default value
}
}
return pointer
}
@objc
public static func deallocateBytes(_ pointer: UnsafeMutablePointer<UInt8>?) {
pointer?.deallocate()
}
}

View File

@ -74,7 +74,7 @@ export const startServiceFunction = async function (
config.cross = true
// 设置回调
setUserCallbackToXp2p(avRecvHandle, msgHandle, deviceDataHandle)
setUserCallbackToXp2p(null, msgHandle, null)
const result = startService(deviceId, productId, deviceName, config)
@ -132,8 +132,8 @@ export const runSendServiceFunction = async function (
crypto: boolean
): Promise<Result> {
try {
// const result = await runSendService(id, cmd, crypto)
// console.log('开始发送服务', result)
const result = runSendService(id, cmd, crypto)
console.log('开始发送服务', result)
return {
code: 0,
data: {},
@ -150,9 +150,39 @@ export const runSendServiceFunction = async function (
export const stopSendServiceFunction = async function (id: string): Promise<Result> {
try {
// const nullPointer = P2PPointerHelper.createNullPointer()
// const result = await stopSendService(id, nullPointer)
// console.log('停止发送服务', result)
const result = stopSendService(id, null)
console.log('停止发送服务', result)
return {
code: 0,
data: {},
message: '成功'
}
} catch (error) {
return {
code: -1,
data: {},
message: error.toString()
}
}
}
export const dataSendFunction = async function (id: string, data: Array<number>): Promise<Result> {
const buffer = XP2PDataHelper.createBytes(data as NSArray)
if (buffer == null) {
return {
code: -1,
data: {},
message: '创建数据缓冲区失败'
}
}
try {
const result = dataSend(id, buffer, data.length)
// console.log('发送数据结果', result)
XP2PDataHelper.deallocateBytes(buffer)
return {
code: 0,
data: {},