提交100J代码
This commit is contained in:
@ -17,6 +17,98 @@ function tryGetUniFileSystemManager() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 从 readFile success 的 res.data 解析为 Uint8Array,失败返回 null */
|
||||
function _bytesFromReadFileResult(res) {
|
||||
try {
|
||||
const raw = res && res.data;
|
||||
if (raw instanceof ArrayBuffer) return new Uint8Array(raw);
|
||||
if (raw instanceof Uint8Array) return raw;
|
||||
if (raw && ArrayBuffer.isView(raw) && raw.buffer instanceof ArrayBuffer) {
|
||||
return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 为 readFile 准备多路径:App 上 FSM 常需绝对路径,_downloads/_doc 相对路径需 convert */
|
||||
function getLocalPathReadCandidates(path) {
|
||||
const s = String(path || '').trim();
|
||||
if (!s) return [];
|
||||
const out = [s];
|
||||
try {
|
||||
if (typeof plus !== 'undefined' && plus.io && typeof plus.io.convertLocalFileSystemURL === 'function') {
|
||||
if (/^_(?:downloads|doc|www)\//i.test(s)) {
|
||||
const c = plus.io.convertLocalFileSystemURL(s);
|
||||
if (c && typeof c === 'string' && c !== s) out.push(c);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return [...new Set(out)];
|
||||
}
|
||||
|
||||
/**
|
||||
* App-Plus 上仍尝试 uni.getFileSystemManager().readFile(部分机型对 _downloads/_doc 路径可用),
|
||||
* 带超时避免 success/fail 永不回调;与 tryGetUniFileSystemManager 不同:此处不因 plus.io 存在而跳过。
|
||||
*/
|
||||
function tryUniReadFileSinglePath(filePath, timeoutMs) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (!filePath || typeof uni === 'undefined' || typeof uni.getFileSystemManager !== 'function') {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const fsm = uni.getFileSystemManager();
|
||||
if (!fsm || typeof fsm.readFile !== 'function') {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
let finished = false;
|
||||
const t = setTimeout(() => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
resolve(null);
|
||||
}, timeoutMs);
|
||||
fsm.readFile({
|
||||
filePath: filePath,
|
||||
success: (res) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(t);
|
||||
const bytes = _bytesFromReadFileResult(res);
|
||||
resolve(bytes && bytes.length > 0 ? bytes : null);
|
||||
},
|
||||
fail: () => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(t);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function tryUniReadFileOnAppWithTimeout(path, timeoutMs) {
|
||||
const candidates = getLocalPathReadCandidates(path);
|
||||
if (!candidates.length) return Promise.resolve(null);
|
||||
const n = Math.min(candidates.length, 3);
|
||||
const per = Math.max(2800, Math.ceil(timeoutMs / n));
|
||||
|
||||
const tryIdx = (i) => {
|
||||
if (i >= candidates.length) return Promise.resolve(null);
|
||||
return tryUniReadFileSinglePath(candidates[i], per).then((bytes) => {
|
||||
if (bytes && bytes.length > 0) {
|
||||
if (i > 0) console.log('[100J-蓝牙] readFile 备用路径成功, idx=', i, 'pathHead=', String(candidates[i]).slice(0, 64));
|
||||
return bytes;
|
||||
}
|
||||
return tryIdx(i + 1);
|
||||
});
|
||||
};
|
||||
return tryIdx(0);
|
||||
}
|
||||
|
||||
// ================== 蓝牙协议封装类 ==================
|
||||
class HBY100JProtocol {
|
||||
constructor() {
|
||||
@ -222,12 +314,13 @@ class HBY100JProtocol {
|
||||
// 0x05 文件上传:分片传输,协议 FA 05 [fileType] [phase] [data...] FF
|
||||
// fileType: 1=语音 2=图片 3=动图 4=OTA
|
||||
// phase: 0=开始 1=数据 2=结束
|
||||
// 每包最大字节 蓝牙:CHUNK_SIZE=500
|
||||
// 每包最大负载见 uploadVoiceFileBle 内 CHUNK_SIZE(需与 MTU 匹配)
|
||||
// 支持 fileUrl(需网络下载) 或 localPath(无网络时本地文件)
|
||||
// 说明:下发的是已录制/已落盘的 MP3(等)二进制分片,经 GATT 写特征;非 A2DP/HFP 等「蓝牙录音实时流」
|
||||
// meta.voiceListId:若存在 uni 中 100J_voice_b64_* 缓存(与 HBY100 存 Storage 同理),优先用缓存字节下发
|
||||
uploadVoiceFileBle(fileUrlOrLocalPath, fileType = 1, onProgress, meta = null) {
|
||||
const CHUNK_SIZE = 500; // 每包有效数据,参考 6155 deviceDetail.vue
|
||||
// 协议 5.6:单包负载最大 500B,加 FA/05/FF 等约 507B;真机需已协商足够 MTU(见 BleHelper setBLEMTU 512)
|
||||
const CHUNK_SIZE = 500;
|
||||
const BLE_CACHE_SENTINEL = '__100J_BLE_CACHE__';
|
||||
return new Promise((resolve, reject) => {
|
||||
const srcStr = String(fileUrlOrLocalPath || '');
|
||||
@ -252,6 +345,7 @@ class HBY100JProtocol {
|
||||
this._sendVoiceChunks(cached, fileType, CHUNK_SIZE, onProgress).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
console.warn('[100J-蓝牙] 未命中 uni 缓存,将尝试读本地文件。key=', voiceBleCacheStorageKey(did, voiceListId), '若保存后立刻使用仍失败,请稍等再试或重新保存');
|
||||
}
|
||||
if (fileUrlOrLocalPath === BLE_CACHE_SENTINEL) {
|
||||
return reject(new Error('本地语音缓存不存在或已失效,请重新保存录音'));
|
||||
@ -304,41 +398,38 @@ class HBY100JProtocol {
|
||||
};
|
||||
const readFromPathPlus = (path, doSend, reject) => {
|
||||
const tryUniReadFile = (onFail) => {
|
||||
try {
|
||||
// 仅非 App(如微信小程序)走 uni FSM。App-Plus 上禁止此处调用 getFileSystemManager:
|
||||
// 否则会刷「not yet implemented」,且 readFile 的 success/fail 可能永不回调 → 永远进不了 plus.io 兜底,表现为「读不了本地文件」。
|
||||
const fsm = tryGetUniFileSystemManager();
|
||||
if (!fsm || typeof fsm.readFile !== 'function') {
|
||||
onFail();
|
||||
// 先走 uni readFile(含 App),带超时;旧逻辑在 plus 存在时完全跳过 FSM,导致只能卡 plus.io。
|
||||
tryUniReadFileOnAppWithTimeout(path, 5000).then((bytes) => {
|
||||
if (bytes && bytes.length > 0) {
|
||||
console.log('[100J-蓝牙] readFile 已读出本地语音,字节:', bytes.length);
|
||||
doSend(bytes);
|
||||
return;
|
||||
}
|
||||
fsm.readFile({
|
||||
filePath: path,
|
||||
success: (res) => {
|
||||
try {
|
||||
const raw = res && res.data;
|
||||
let bytes = null;
|
||||
if (raw instanceof ArrayBuffer) bytes = new Uint8Array(raw);
|
||||
else if (raw instanceof Uint8Array) bytes = raw;
|
||||
else if (raw && ArrayBuffer.isView(raw) && raw.buffer instanceof ArrayBuffer) {
|
||||
bytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
||||
}
|
||||
if (bytes && bytes.length > 0) {
|
||||
console.log('[100J-蓝牙] readFile 已读出本地语音,字节:', bytes.length);
|
||||
doSend(bytes);
|
||||
try {
|
||||
const fsm = tryGetUniFileSystemManager();
|
||||
if (!fsm || typeof fsm.readFile !== 'function') {
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
fsm.readFile({
|
||||
filePath: path,
|
||||
success: (res) => {
|
||||
const b = _bytesFromReadFileResult(res);
|
||||
if (b && b.length > 0) {
|
||||
console.log('[100J-蓝牙] readFile(非App) 已读出本地语音,字节:', b.length);
|
||||
doSend(b);
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
onFail();
|
||||
},
|
||||
fail: () => onFail()
|
||||
});
|
||||
} catch (e) {
|
||||
onFail();
|
||||
}
|
||||
onFail();
|
||||
},
|
||||
fail: () => onFail()
|
||||
});
|
||||
} catch (e) {
|
||||
onFail();
|
||||
}
|
||||
});
|
||||
};
|
||||
// _downloads/:与 Common.moveFileToDownloads 一致,文件在 PUBLIC_DOWNLOADS 根目录。
|
||||
// 优先 requestFileSystem+getFile(老版本注释:resolveLocalFileSystemURL 易卡住);resolve 仅作兜底。
|
||||
// _downloads/:resolve 与 requestFileSystem 并行竞速,避免单一路径在部分机型上长期无回调
|
||||
if (path && path.startsWith('_downloads/')) {
|
||||
const fileName = path.replace(/^_downloads\//, '');
|
||||
tryUniReadFile(() => {
|
||||
@ -346,7 +437,7 @@ class HBY100JProtocol {
|
||||
reject(new Error('当前环境不支持文件读取'));
|
||||
return;
|
||||
}
|
||||
console.log('[100J-蓝牙] _downloads 读取开始, fileName=', fileName);
|
||||
console.log('[100J-蓝牙] _downloads 并行 resolve+PUBLIC_DOWNLOADS, fileName=', fileName);
|
||||
let finished = false;
|
||||
const outerMs = 20000;
|
||||
const outerT = setTimeout(() => {
|
||||
@ -354,123 +445,13 @@ class HBY100JProtocol {
|
||||
finished = true;
|
||||
reject(new Error('读取下载目录语音超时,请重新保存录音后重试'));
|
||||
}, outerMs);
|
||||
const finishOk = (bytes) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(outerT);
|
||||
clearTimeout(reqFsGuardT);
|
||||
doSend(bytes);
|
||||
};
|
||||
const finishErr = (err) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(outerT);
|
||||
clearTimeout(reqFsGuardT);
|
||||
reject(err || new Error('读取文件失败'));
|
||||
};
|
||||
let fallbackStarted = false;
|
||||
const tryResolveUrl = () => {
|
||||
if (finished || fallbackStarted) return;
|
||||
fallbackStarted = true;
|
||||
clearTimeout(reqFsGuardT);
|
||||
console.log('[100J-蓝牙] _downloads 改用 resolveLocalFileSystemURL 兜底');
|
||||
let url = path;
|
||||
try {
|
||||
if (plus.io.convertLocalFileSystemURL) {
|
||||
const c = plus.io.convertLocalFileSystemURL(path);
|
||||
if (c) url = c;
|
||||
}
|
||||
} catch (e) {}
|
||||
if (typeof url === 'string' && url.startsWith('/') && !url.startsWith('file://')) {
|
||||
url = 'file://' + url;
|
||||
}
|
||||
plus.io.resolveLocalFileSystemURL(url, (entry) => {
|
||||
readFileEntry(entry, finishOk, () => {
|
||||
finishErr(new Error('_downloads 文件不存在或无法读取'));
|
||||
});
|
||||
}, () => finishErr(new Error('_downloads 路径解析失败')));
|
||||
};
|
||||
const reqFsGuardMs = 6000;
|
||||
const reqFsGuardT = setTimeout(() => {
|
||||
if (finished || fallbackStarted) return;
|
||||
console.log('[100J-蓝牙] _downloads requestFileSystem 超时,尝试 resolve 兜底');
|
||||
tryResolveUrl();
|
||||
}, reqFsGuardMs);
|
||||
plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, (fs) => {
|
||||
if (finished || fallbackStarted) return;
|
||||
fs.root.getFile(fileName, {}, (entry) => {
|
||||
if (finished || fallbackStarted) return;
|
||||
readFileEntry(entry, finishOk, (err) => {
|
||||
if (finished) return;
|
||||
clearTimeout(reqFsGuardT);
|
||||
console.log('[100J-蓝牙] getFile 后读失败,尝试 resolve', err && err.message);
|
||||
tryResolveUrl();
|
||||
});
|
||||
}, (err) => {
|
||||
if (finished) return;
|
||||
clearTimeout(reqFsGuardT);
|
||||
console.log('[100J-蓝牙] getFile 失败,尝试 resolve', err && err.message);
|
||||
tryResolveUrl();
|
||||
});
|
||||
}, (err) => {
|
||||
if (finished) return;
|
||||
clearTimeout(reqFsGuardT);
|
||||
console.log('[100J-蓝牙] requestFileSystem 失败,尝试 resolve', err && err.message);
|
||||
tryResolveUrl();
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// _doc/:与 _downloads 同理,App 只走 plus;并加 resolve 优先 + 超时,避免 requestFileSystem 链卡死
|
||||
if (path && path.startsWith('_doc/')) {
|
||||
const relPath = path.replace(/^_doc\//, '');
|
||||
const parts = relPath.split('/');
|
||||
const fileName = parts.pop();
|
||||
const dirs = parts;
|
||||
tryUniReadFile(() => {
|
||||
if (typeof plus === 'undefined' || !plus.io) {
|
||||
reject(new Error('当前环境不支持文件读取'));
|
||||
return;
|
||||
}
|
||||
let finished = false;
|
||||
const outerMs = 20000;
|
||||
const outerT = setTimeout(() => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
reject(new Error('读取本地语音超时(文档目录),请重新保存录音后重试'));
|
||||
}, outerMs);
|
||||
const finishOk = (bytes) => {
|
||||
const win = (bytes) => {
|
||||
if (finished) return;
|
||||
if (!bytes || !(bytes.length > 0)) return;
|
||||
finished = true;
|
||||
clearTimeout(outerT);
|
||||
doSend(bytes);
|
||||
};
|
||||
const finishErr = (err) => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(outerT);
|
||||
reject(err || new Error('读取文件失败'));
|
||||
};
|
||||
const tryWalkFs = () => {
|
||||
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
||||
let cur = fs.root;
|
||||
const next = (i) => {
|
||||
if (i >= dirs.length) {
|
||||
cur.getFile(fileName, { create: false }, (entry) => readFileEntry(entry, finishOk, finishErr), (err) => finishErr(err));
|
||||
return;
|
||||
}
|
||||
cur.getDirectory(dirs[i], { create: false }, (dir) => { cur = dir; next(i + 1); }, (err) => finishErr(err));
|
||||
};
|
||||
next(0);
|
||||
}, (err) => finishErr(err));
|
||||
};
|
||||
let resolvePhaseDone = false;
|
||||
const resolveT = setTimeout(() => {
|
||||
if (resolvePhaseDone) return;
|
||||
resolvePhaseDone = true;
|
||||
console.log('[100J-蓝牙] _doc resolve 超时,改用 PRIVATE_DOC 逐级目录');
|
||||
tryWalkFs();
|
||||
}, 6000);
|
||||
let url = path;
|
||||
try {
|
||||
if (plus.io.convertLocalFileSystemURL) {
|
||||
@ -482,19 +463,78 @@ class HBY100JProtocol {
|
||||
url = 'file://' + url;
|
||||
}
|
||||
plus.io.resolveLocalFileSystemURL(url, (entry) => {
|
||||
if (resolvePhaseDone) return;
|
||||
resolvePhaseDone = true;
|
||||
clearTimeout(resolveT);
|
||||
readFileEntry(entry, finishOk, (err) => {
|
||||
console.log('[100J-蓝牙] _doc resolve 后读失败,改用逐级目录', err && err.message);
|
||||
tryWalkFs();
|
||||
});
|
||||
}, () => {
|
||||
if (resolvePhaseDone) return;
|
||||
resolvePhaseDone = true;
|
||||
clearTimeout(resolveT);
|
||||
tryWalkFs();
|
||||
});
|
||||
if (finished) return;
|
||||
readFileEntry(entry, win, () => {});
|
||||
}, () => {});
|
||||
plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, (fs) => {
|
||||
if (finished) return;
|
||||
fs.root.getFile(fileName, {}, (entry) => {
|
||||
if (finished) return;
|
||||
readFileEntry(entry, win, () => {});
|
||||
}, () => {});
|
||||
}, () => {});
|
||||
});
|
||||
return;
|
||||
}
|
||||
// _doc/:PRIVATE_DOC 逐级与 resolve 并行,谁先读到有效字节谁胜出
|
||||
if (path && path.startsWith('_doc/')) {
|
||||
const relPath = path.replace(/^_doc\//, '');
|
||||
const parts = relPath.split('/');
|
||||
const fileName = parts.pop();
|
||||
const dirs = parts;
|
||||
tryUniReadFile(() => {
|
||||
if (typeof plus === 'undefined' || !plus.io) {
|
||||
reject(new Error('当前环境不支持文件读取'));
|
||||
return;
|
||||
}
|
||||
console.log('[100J-蓝牙] _doc 并行 resolve+PRIVATE_DOC, path=', path.slice(0, 96));
|
||||
let finished = false;
|
||||
const outerMs = 20000;
|
||||
const outerT = setTimeout(() => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
reject(new Error('读取本地语音超时(文档目录),请重新保存录音后重试'));
|
||||
}, outerMs);
|
||||
const win = (bytes) => {
|
||||
if (finished) return;
|
||||
if (!bytes || !(bytes.length > 0)) return;
|
||||
finished = true;
|
||||
clearTimeout(outerT);
|
||||
doSend(bytes);
|
||||
};
|
||||
let url = path;
|
||||
try {
|
||||
if (plus.io.convertLocalFileSystemURL) {
|
||||
const c = plus.io.convertLocalFileSystemURL(path);
|
||||
if (c) url = c;
|
||||
}
|
||||
} catch (e) {}
|
||||
if (typeof url === 'string' && url.startsWith('/') && !url.startsWith('file://')) {
|
||||
url = 'file://' + url;
|
||||
}
|
||||
plus.io.resolveLocalFileSystemURL(url, (entry) => {
|
||||
if (finished) return;
|
||||
readFileEntry(entry, win, () => {});
|
||||
}, () => {});
|
||||
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
||||
if (finished) return;
|
||||
let cur = fs.root;
|
||||
const next = (i) => {
|
||||
if (finished) return;
|
||||
if (i >= dirs.length) {
|
||||
cur.getFile(fileName, { create: false }, (entry) => {
|
||||
if (finished) return;
|
||||
readFileEntry(entry, win, () => {});
|
||||
}, () => {});
|
||||
return;
|
||||
}
|
||||
cur.getDirectory(dirs[i], { create: false }, (dir) => {
|
||||
cur = dir;
|
||||
next(i + 1);
|
||||
}, () => {});
|
||||
};
|
||||
next(0);
|
||||
}, () => {});
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -641,7 +681,7 @@ class HBY100JProtocol {
|
||||
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
// 开始包: FA 05 [fileType] [phase=0] [size 4B LE] FF
|
||||
const startData = [ft, 0, total & 0xFF, (total >> 8) & 0xFF, (total >> 16) & 0xFF, (total >> 24) & 0xFF];
|
||||
// 单包最大约 500+ 协议头尾,需 MTU>~520;BleHelper 里 setMtu 为异步且未与首写串联,易在未协商完就 write 导致长时间无回调→界面超时
|
||||
// 单包约 507B(500 负载),依赖 MTU;Android 上为整包 write
|
||||
const waitPromise = this.waitForFileResponse(2500);
|
||||
const prepMtuThenSend = (ble) => {
|
||||
const run = () => {
|
||||
@ -674,7 +714,9 @@ class HBY100JProtocol {
|
||||
try {
|
||||
if (typeof plus !== 'undefined' && plus.os && plus.os.name === 'Android' && ble.setMtu) {
|
||||
return ble.setMtu(this.bleDeviceId)
|
||||
.catch(() => {})
|
||||
.catch((e) => {
|
||||
console.warn('[100J-蓝牙] setBLEMTU 失败,大数据包可能无法一次写入,已用较小分片缓解:', e && (e.message || e));
|
||||
})
|
||||
.then(() => new Promise((r) => setTimeout(r, 350)))
|
||||
.then(run);
|
||||
}
|
||||
@ -804,86 +846,19 @@ export function remove100JVoiceBleCache(deviceId, voiceListId) {
|
||||
/** 保存录音/上传成功后:读落盘路径并写入缓存,点「使用」时可与 HBY100 一样不再依赖 plus 读文件 */
|
||||
export function cache100JVoiceFileForBle(deviceId, voiceListId, filePath) {
|
||||
return new Promise((resolve) => {
|
||||
const done = () => resolve();
|
||||
if (!deviceId || voiceListId == null || !filePath) {
|
||||
done();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const tryFs = () => {
|
||||
const fsm = tryGetUniFileSystemManager();
|
||||
if (!fsm || typeof fsm.readFile !== 'function') return false;
|
||||
fsm.readFile({
|
||||
filePath,
|
||||
success: (res) => {
|
||||
try {
|
||||
const raw = res && res.data;
|
||||
let bytes = null;
|
||||
if (raw instanceof ArrayBuffer) bytes = new Uint8Array(raw);
|
||||
else if (raw instanceof Uint8Array) bytes = raw;
|
||||
else if (raw && ArrayBuffer.isView(raw) && raw.buffer instanceof ArrayBuffer) {
|
||||
bytes = new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
||||
}
|
||||
if (bytes && bytes.length) put100JVoiceBleCache(deviceId, voiceListId, bytes);
|
||||
} catch (e) {}
|
||||
done();
|
||||
},
|
||||
fail: () => done()
|
||||
});
|
||||
return true;
|
||||
};
|
||||
if (tryFs()) return;
|
||||
if (typeof plus === 'undefined' || !plus.io) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
const readEntry = (entry, cb) => {
|
||||
entry.file((file) => {
|
||||
const reader = new plus.io.FileReader();
|
||||
reader.onloadend = (e) => {
|
||||
try {
|
||||
const buf = e.target.result;
|
||||
if (buf && buf.byteLength) put100JVoiceBleCache(deviceId, voiceListId, new Uint8Array(buf));
|
||||
} catch (err) {}
|
||||
cb();
|
||||
};
|
||||
reader.onerror = () => cb();
|
||||
reader.readAsArrayBuffer(file);
|
||||
}, () => cb());
|
||||
};
|
||||
if (filePath.startsWith('_downloads/')) {
|
||||
const name = filePath.replace(/^_downloads\//, '');
|
||||
plus.io.requestFileSystem(plus.io.PUBLIC_DOWNLOADS, (fs) => {
|
||||
fs.root.getFile(name, {}, (entry) => readEntry(entry, done), () => done());
|
||||
}, () => done());
|
||||
return;
|
||||
}
|
||||
if (filePath.startsWith('_doc/')) {
|
||||
const rel = filePath.replace(/^_doc\//, '');
|
||||
const parts = rel.split('/');
|
||||
const fileName = parts.pop();
|
||||
const dirs = parts;
|
||||
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, (fs) => {
|
||||
let cur = fs.root;
|
||||
const next = (i) => {
|
||||
if (i >= dirs.length) {
|
||||
cur.getFile(fileName, { create: false }, (entry) => readEntry(entry, done), () => done());
|
||||
return;
|
||||
}
|
||||
cur.getDirectory(dirs[i], { create: false }, (dir) => { cur = dir; next(i + 1); }, () => done());
|
||||
};
|
||||
next(0);
|
||||
}, () => done());
|
||||
return;
|
||||
}
|
||||
let resolvePath = filePath;
|
||||
try {
|
||||
if (plus.io.convertLocalFileSystemURL && !/^(?:_doc\/|_downloads\/|https?:)/i.test(filePath)) {
|
||||
const c = plus.io.convertLocalFileSystemURL(filePath);
|
||||
if (c) resolvePath = c;
|
||||
// 仅用 uni readFile + 超时;App 上不再走 tryGetUniFileSystemManager 跳过逻辑。
|
||||
// 旧版在此用 plus.requestFileSystem 且无总超时,部分机型永不回调 → Promise 挂死。
|
||||
tryUniReadFileOnAppWithTimeout(filePath, 4000).then((bytes) => {
|
||||
if (bytes && bytes.length) {
|
||||
put100JVoiceBleCache(deviceId, voiceListId, bytes);
|
||||
console.log('[100J] cache100JVoiceFileForBle 已写入 uni 缓存(readFile),字节:', bytes.length);
|
||||
}
|
||||
} catch (e) {}
|
||||
if (resolvePath.startsWith('/') && !resolvePath.startsWith('file://')) resolvePath = 'file://' + resolvePath;
|
||||
plus.io.resolveLocalFileSystemURL(resolvePath, (entry) => readEntry(entry, done), () => done());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1032,11 +1007,15 @@ function isHttpUrlString(s) {
|
||||
return !!(s && /^https?:\/\//i.test(String(s).trim()));
|
||||
}
|
||||
|
||||
/** 与后端约定:communicationMode 0=4G,1=蓝牙(云端记录本次「使用」语音的通讯方式) */
|
||||
export function deviceUpdateVoice(data) {
|
||||
const httpExec = () => request({
|
||||
const httpExec = (communicationMode) => request({
|
||||
url: `/app/hby100j/device/updateVoice`,
|
||||
method: 'post',
|
||||
data: { id: data.id }
|
||||
data: {
|
||||
id: data.id,
|
||||
communicationMode: communicationMode === 1 ? 1 : 0
|
||||
}
|
||||
});
|
||||
const lp = (data.localPath && String(data.localPath).trim()) || '';
|
||||
const fu = (data.fileUrl && String(data.fileUrl).trim()) || '';
|
||||
@ -1052,12 +1031,31 @@ export function deviceUpdateVoice(data) {
|
||||
const fileSource = hasLocalPath ? (effectiveLocal || BLE_CACHE_SENTINEL) : (remoteUrl || null);
|
||||
if (!fileSource) {
|
||||
console.log('[100J] 语音上传:无 fileUrl/localPath,仅 HTTP updateVoice(不会走蓝牙传文件)');
|
||||
return httpExec().then((res) => { if (res && typeof res === 'object') res._channel = '4g'; return res; });
|
||||
return httpExec(0).then((res) => { if (res && typeof res === 'object') res._channel = '4g'; return res; });
|
||||
}
|
||||
console.log('[100J] 语音上传:有文件源,尝试蓝牙下发文件', { isBleConnected: protocolInstance.isBleConnected, hasBleDeviceId: !!protocolInstance.bleDeviceId, local: !!hasLocalPath });
|
||||
const bleExec = () => protocolInstance.uploadVoiceFileBle(fileSource, 1, data.onProgress, { voiceListId: data.id });
|
||||
// 蓝牙传完文件后再调 updateVoice(communicationMode=1)。若仅 HTTP 失败而文件已下发,仍返回 _channel=ble,
|
||||
// 切勿让整链 reject 触发 execWithBleFirst 走 4G+MQTT,否则界面等不到进度会误报「音频进度同步超时」。
|
||||
const bleExec = () => protocolInstance.uploadVoiceFileBle(fileSource, 1, data.onProgress, { voiceListId: data.id })
|
||||
.then(() =>
|
||||
httpExec(1)
|
||||
.then((res) => {
|
||||
if (res && typeof res === 'object') res._channel = 'ble';
|
||||
return res;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn('[100J] 蓝牙已传完语音文件,updateVoice(communicationMode=1) 失败:', e && (e.message || e));
|
||||
return {
|
||||
code: 200,
|
||||
msg: '语音已通过蓝牙下发',
|
||||
_channel: 'ble',
|
||||
_updateVoiceAfterBleFailed: true
|
||||
};
|
||||
})
|
||||
);
|
||||
const http4g = () => httpExec(0);
|
||||
// 本地文件:禁止一切 4G 兜底(含蓝牙未开时),避免仅传 id 假成功
|
||||
return execWithBleFirst(bleExec, httpExec, '语音文件上传', data.onWaiting, { no4GFallback: hasLocalPath });
|
||||
return execWithBleFirst(bleExec, http4g, '语音文件上传', data.onWaiting, { no4GFallback: hasLocalPath });
|
||||
}
|
||||
// 100J信息
|
||||
export function deviceDetail(id) {
|
||||
|
||||
Reference in New Issue
Block a user