Commit c2355191 authored by martin hou's avatar martin hou

feat: 录音试听测试

parent 2589667c
...@@ -5,6 +5,13 @@ export type DeviceInfo = { ...@@ -5,6 +5,13 @@ export type DeviceInfo = {
versionCode: string; // 固件版本号的字符串表示,如1.2.3 versionCode: string; // 固件版本号的字符串表示,如1.2.3
}; };
// 电池电量信息
export type BatteryStatus = {
status: 'idle' | 'charging' | 'full'; // 电池状态,idle:闲置,charging:充电中,full:满电
battery: number; // 电量百分比,整型,0~100
voltage: number; // 电池电压,单位:微伏
}
// 常规指令的应答结构,表示指令的结果是成功或是失败 // 常规指令的应答结构,表示指令的结果是成功或是失败
export type ReturnStruct = { export type ReturnStruct = {
common: { result: 'failed' | 'success' }; common: { result: 'failed' | 'success' };
...@@ -168,21 +175,27 @@ declare class Jensen { ...@@ -168,21 +175,27 @@ declare class Jensen {
// 注意:文件传输进度和数据并不是同时到达的,在文件传输到100%时才会开始调用on回调进行转移数据 // 注意:文件传输进度和数据并不是同时到达的,在文件传输到100%时才会开始调用on回调进行转移数据
getFile: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) => void; getFile: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) => void;
// 持续传输文件,filename为文件名称,length为文件长度,ondata为数据回调,seconds为超时时长
transferFile: (filename: string, length: number, ondata: (msg: Uint8Array | 'fail') => void, seconds?: number) => Promise<ReturnStruct['common']>;
// 获取指定长度的录音文件内容,length参数可以小于或等于文件的原始长度,其它参数与getFile()方法一致 // 获取指定长度的录音文件内容,length参数可以小于或等于文件的原始长度,其它参数与getFile()方法一致
getFilePart: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) => void; getFilePart: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void, onprogress?: (size: number) => void) => void;
// 暂不使用 // 暂不使用
getFileBlock: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void) => Promise<ReturnStruct['common']>; getFileBlock: (fileName: string, length: number, on?: (msg: Uint8Array | 'fail') => void) => Promise<ReturnStruct['common']>;
// 录音文件随机读取,从指定位置起读取指定数量个字节
readFile: (fname: string, offset: number, length: number) => Promise<Uint8Array>;
// 传输文件
transferFile: (filename: string, length: number, ondata: (msg: Uint8Array | 'fail') => void, seconds?: number) => Promise<ReturnStruct['common']>;
// 请求固件更新 // 请求固件更新
// #versionNumber 新固件的版本号 // #versionNumber 新固件的版本号
// #length 新固件的内容长度 // #length 新固件的内容长度
// #time 超时时长 // #time 超时时长
requestFirmwareUpgrade: (versionNumber: number, length: number, time?: number) => Promise<{ result: 'accepted' | 'fail' }>; requestFirmwareUpgrade: (versionNumber: number, length: number, time?: number) => Promise<{ result: 'accepted' | 'fail' }>;
// 获取电池电量信息
getBatteryStatus: (time?:number) => Promise<BatteryStatus>;
// 发送新固件内容到设备端 // 发送新固件内容到设备端
// #data 为固件数据内容 // #data 为固件数据内容
// #seconds 为超时时长 // #seconds 为超时时长
...@@ -277,9 +290,6 @@ declare class Jensen { ...@@ -277,9 +290,6 @@ declare class Jensen {
// 获取实时音频流数据 // 获取实时音频流数据
getRealtime: (frames: number) => Promise<{ data: Uint8Array; rest: number }>; getRealtime: (frames: number) => Promise<{ data: Uint8Array; rest: number }>;
// 录音文件随机读取,从指定位置起读取指定数量个字节
readFile: (fname: string, offset: number, length: number) => Promise<Uint8Array>;
// 请求设备提示音更新 // 请求设备提示音更新
requestToneUpdate: (signature: string, size: number, seconds?: number) => Promise<ToneUpdateResponse>; requestToneUpdate: (signature: string, size: number, seconds?: number) => Promise<ToneUpdateResponse>;
...@@ -291,6 +301,7 @@ declare class Jensen { ...@@ -291,6 +301,7 @@ declare class Jensen {
// 更新设备UAC固件 // 更新设备UAC固件
updateUAC: (data: Uint8Array, seconds?: number) => Promise<ReturnStruct['common']>; updateUAC: (data: Uint8Array, seconds?: number) => Promise<ReturnStruct['common']>;
} }
export = Jensen; export = Jensen;
...@@ -31,36 +31,41 @@ async function initFFmpeg() { ...@@ -31,36 +31,41 @@ async function initFFmpeg() {
} }
// 解码mp3数据为pcm数据 // 解码mp3数据为pcm数据
async function decode_mp3(data) { async function decode_mp3(req) {
try { try {
const ffmpeg = await initFFmpeg(); const ffmpeg = await initFFmpeg();
// 检查FFmpeg是否已加载 // 检查FFmpeg是否已加载
if (!ffmpeg || !ffmpeg.loaded) { if (!ffmpeg || !ffmpeg.loaded) {
throw new Error('FFmpeg not properly loaded'); // 如果没有加载完成,就一直等待
while (!ffmpeg || !ffmpeg.loaded) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// throw new Error('FFmpeg not properly loaded');
} }
console.log('FFmpeg is ready, starting MP3 decode...'); console.log('FFmpeg is ready, starting MP3 decode...');
// 将MP3数据写入FFmpeg // 将MP3数据写入FFmpeg
await ffmpeg.writeFile('input.mp3', data); let fname = req.name;
await ffmpeg.writeFile(fname + '.mp3', req.data);
// 使用FFmpeg解码MP3为PCM s16le格式 // 使用FFmpeg解码MP3为PCM s16le格式
await ffmpeg.exec([ await ffmpeg.exec([
'-i', 'input.mp3', '-i', fname + '.mp3',
'-f', 's16le', '-f', 's16le',
'-acodec', 'pcm_s16le', '-acodec', 'pcm_s16le',
'-ar', '16000', '-ar', '16000',
'-ac', '1', '-ac', '1',
'output.pcm' fname + '-output.pcm'
]); ]);
// 读取解码后的PCM数据 // 读取解码后的PCM数据
const pcmData = await ffmpeg.readFile('output.pcm'); const pcmData = await ffmpeg.readFile('output.pcm');
// 清理临时文件 // 清理临时文件
await ffmpeg.deleteFile('input.mp3'); await ffmpeg.deleteFile(fname + '.mp3');
await ffmpeg.deleteFile('output.pcm'); await ffmpeg.deleteFile(fname + '-output.pcm');
// 将Uint8Array转换为Uint16Array (s16le格式) // 将Uint8Array转换为Uint16Array (s16le格式)
const uint8Array = new Uint8Array(pcmData); const uint8Array = new Uint8Array(pcmData);
......
...@@ -98,7 +98,9 @@ export function Home() { ...@@ -98,7 +98,9 @@ export function Home() {
} }
const getTime = async () => { const getTime = async () => {
let time = await jensen.getTime(); console.log(jensen);
jensen.dump();
let time = await jensen.getTime(5);
alert(JSON.stringify(time)); alert(JSON.stringify(time));
} }
...@@ -223,11 +225,17 @@ export function Home() { ...@@ -223,11 +225,17 @@ export function Home() {
// 先创建audio-worker,给到后面使用 // 先创建audio-worker,给到后面使用
const audioWorker = new Worker(new URL('./audio-worker.js', import.meta.url), { type: 'module' }); const audioWorker = new Worker(new URL('./audio-worker.js', import.meta.url), { type: 'module' });
const waveformWorker = new Worker(new URL('./waveform-worker.js', import.meta.url), { type: 'module' });
waveformWorker.onmessage = (e) => {
console.log('waveformWorker', e);
}
audioWorker.onmessage = (e) => { audioWorker.onmessage = (e) => {
// console.log(e); // console.log(e);
if (e.data.waveformData) if (e.data.waveformData)
{ {
console.log(e.data.waveformData); console.log('waveform', e.data.waveformData);
// 把e.data.waveformData追加到waveformData的末尾 // 把e.data.waveformData追加到waveformData的末尾
// let waveform = []; // let waveform = [];
/* /*
...@@ -292,6 +300,7 @@ export function Home() { ...@@ -292,6 +300,7 @@ export function Home() {
const prepareAudio = () => { const prepareAudio = () => {
const mediaSource = new MediaSource(); const mediaSource = new MediaSource();
const audioElement = document.createElement('audio'); const audioElement = document.createElement('audio');
audioElement.autoplay = true;
audioElement.src = URL.createObjectURL(mediaSource); audioElement.src = URL.createObjectURL(mediaSource);
document.body.appendChild(audioElement); document.body.appendChild(audioElement);
...@@ -299,34 +308,34 @@ export function Home() { ...@@ -299,34 +308,34 @@ export function Home() {
// Create source buffer for MP3 // Create source buffer for MP3
let sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg'); let sourceBuffer = mediaSource.addSourceBuffer('audio/mpeg');
setSourceBuffer(sourceBuffer); setSourceBuffer(sourceBuffer);
// sourceBuffer.mode = 'sequence';
alert('sourceopen'); alert('sourceopen');
// Feed MP3 data to source buffer // Feed MP3 data to source buffer
sourceBuffer.addEventListener('updateend', () => { sourceBuffer.addEventListener('updateend', () => {
if (!sourceBuffer.updating && mediaSource.readyState === 'open') { // dequeue();
mediaSource.endOfStream();
audioElement.play();
}
if (mp3.length > 0)
{
sourceBuffer.appendBuffer(mp3.shift());
}
}); });
}); });
} }
const enqueue = (clip: Uint8Array) => { const dequeue = () => {
if (!sourceBuffer) return; if (sourceBuffer?.updating == false && mp3.length > 0 && sourceBuffer.buffered.length < 100)
if (!sourceBuffer.updating && mp3.length === 0) { {
let clip = mp3.shift();
sourceBuffer.appendBuffer(clip); sourceBuffer.appendBuffer(clip);
} else { console.log('appendBuffer', clip.length);
mp3.push(clip); }
else
{
setTimeout(() => dequeue(), 100);
} }
} }
const mp3data:any[] = [];
const test = async () => { const test = async () => {
let idx = prompt('请输入文件序号', '0'); let idx = prompt('请输入文件序号', '8');
if (idx === null || idx === undefined) return; if (idx === null || idx === undefined) return;
let file = files[parseInt(idx)]; let file = files[parseInt(idx)];
if (file === null || file === undefined) return alert('文件不存在'); if (file === null || file === undefined) return alert('文件不存在');
...@@ -334,10 +343,72 @@ export function Home() { ...@@ -334,10 +343,72 @@ export function Home() {
jensen.transferFile(file.name, file.length, (data : Uint8Array | 'fail') => { jensen.transferFile(file.name, file.length, (data : Uint8Array | 'fail') => {
if (data instanceof Uint8Array) if (data instanceof Uint8Array)
{ {
enqueue(data); if (sourceBuffer?.updating == false)
{
sourceBuffer.appendBuffer(data);
console.log('directly appendBuffer', data.length);
}
else
{
console.log('enqueue', data.length);
mp3.push(data);
dequeue();
}
mp3data.push(data);
let totalLength = 0;
for (let i = 0; i < mp3data.length; i++)
{
totalLength += mp3data[i].length;
}
if (totalLength == file.length)
{
let audioData = new Uint8Array(totalLength);
let offset = 0;
for (let i = 0; i < mp3data.length; i++)
{
audioData.set(mp3data[i], offset);
offset += mp3data[i].length;
}
decodeAudioData(audioData);
}
}
});
} }
const decodeAudioData = (audioData: Uint8Array) => {
// 将audioData分成10块,分别发送给audioWorker进行处理
let clips = 1;
let blockSize = Math.ceil(audioData.length / clips);
let audioContext = new AudioContext();
for (let i = 0; i < clips; i++)
{
let block = audioData.buffer.slice(i * blockSize, (i + 1) * blockSize);
let name = 'block-' + i;
// audioWorker.postMessage({ data: block, name: name });
audioContext.decodeAudioData(block).then((buffer) => {
// console.log('decodeAudioData' + i, buffer);
waveformWorker.postMessage({ id: name, pcmdata: buffer });
}).catch((err) => {
console.log('decodeAudioData' + i, err);
}); });
} }
}
const transmit = () => {
let idx = prompt('请输入文件序号', '10');
if (idx === null || idx === undefined) return;
let file = files[parseInt(idx)];
if (file === null || file === undefined) return alert('文件不存在');
jensen.getFile(file.name, file.length, (data : Uint8Array | 'fail') => {}, (b:number) => {console.log('progress', b)});
setTimeout(() => {
jensen.stopReceive();
console.log('stop receive.......');
setTimeout(() => {
jensen.resumeReceive();
console.log('resume receive.......');
}, 1500);
}, 5000);
}
return ( return (
<> <>
...@@ -346,6 +417,7 @@ export function Home() { ...@@ -346,6 +417,7 @@ export function Home() {
<button onClick={listFiles}>文件列表</button> <button onClick={listFiles}>文件列表</button>
<button onClick={prepareAudio}>准备音频</button> <button onClick={prepareAudio}>准备音频</button>
<button onClick={test}>传输与播放</button> <button onClick={test}>传输与播放</button>
<button onClick={transmit}>传输中断测试</button>
<button onClick={getFilePart}>获取文件</button> <button onClick={getFilePart}>获取文件</button>
<button onClick={writeSN}>SN写号</button> <button onClick={writeSN}>SN写号</button>
<button onClick={getTime}>获取时间</button> <button onClick={getTime}>获取时间</button>
......
import { Logger as internalLogger, formatTime, shortcutKeys, sliceTime } from './utils'; import { Logger as internalLogger, formatTime, shortcutKeys, sliceTime } from './utils';
import md5 from 'js-md5';
const INVAILD = 0x00; const INVAILD = 0x00;
const QUERY_DEVICE_INFO = 0x01; const QUERY_DEVICE_INFO = 0x01;
...@@ -26,7 +27,7 @@ const TONE_UPDATE = 0x17; ...@@ -26,7 +27,7 @@ const TONE_UPDATE = 0x17;
const REQUEST_UAC_UPDATE = 0x18; const REQUEST_UAC_UPDATE = 0x18;
const UAC_UPDATE = 0x19; const UAC_UPDATE = 0x19;
const FACTORY_RESET = 0xf00b; const FACTORY_RESET = 0xf00b;
const GET_BATTERY_STATUS = 0x1004;
const REALTIME_READ_SETTING = 0x20; const REALTIME_READ_SETTING = 0x20;
const REALTIME_CONTROL = 0x21; const REALTIME_CONTROL = 0x21;
const REALTIME_TRANSFER = 0x22; const REALTIME_TRANSFER = 0x22;
...@@ -50,20 +51,20 @@ const COMMAND_NAMES = { ...@@ -50,20 +51,20 @@ const COMMAND_NAMES = {
[DELETE_FILE]: 'delete-file', [DELETE_FILE]: 'delete-file',
[REQUEST_FIRMWARE_UPGRADE]: 'request-firmware-upgrade', [REQUEST_FIRMWARE_UPGRADE]: 'request-firmware-upgrade',
[FIRMWARE_UPLOAD]: 'firmware-upload', [FIRMWARE_UPLOAD]: 'firmware-upload',
[READ_CARD_INFO]: 'read-card-info', [READ_CARD_INFO]: 'read card info',
[FORMAT_CARD]: 'format-card', [FORMAT_CARD]: 'format card',
[GET_RECORDING_FILE]: 'get-recording-file', [GET_RECORDING_FILE]: 'get recording file',
[RESTORE_FACTORY_SETTINGS]: 'restore-factory-settings', [RESTORE_FACTORY_SETTINGS]: 'restore factory settings',
[SCHEDULE_INFO]: 'calendar', [SCHEDULE_INFO]: 'send meeting schedule info',
[DEVICE_MSG_TEST]: 'test', [DEVICE_MSG_TEST]: 'device msg test',
[BNC_DEMO_TEST]: 'bnc-demo', [BNC_DEMO_TEST]: 'bnc demo test',
[GET_SETTINGS]: 'get-settings', [GET_SETTINGS]: 'get-settings',
[SET_SETTINGS]: 'set-settings', [SET_SETTINGS]: 'set-settings',
[GET_FILE_BLOCK]: 'get-file-block', [GET_FILE_BLOCK]: 'get file block',
[FACTORY_RESET]: 'factory-reset', [FACTORY_RESET]: 'factory reset',
[TEST_SN_WRITE]: 'test-sn-write', [TEST_SN_WRITE]: 'test sn write',
[RECORD_TEST_START]: 'record-test-start', [RECORD_TEST_START]: 'record test start',
[RECORD_TEST_END]: 'record-test-end', [RECORD_TEST_END]: 'record test end',
[BLUETOOTH_SCAN]: 'bluetooth-scan', [BLUETOOTH_SCAN]: 'bluetooth-scan',
[BLUETOOTH_CMD]: 'bluetooth-cmd', [BLUETOOTH_CMD]: 'bluetooth-cmd',
[BLUETOOTH_STATUS]: 'bluetooth-status' [BLUETOOTH_STATUS]: 'bluetooth-status'
...@@ -86,9 +87,11 @@ function Jensen(log, conn) { ...@@ -86,9 +87,11 @@ function Jensen(log, conn) {
let totalBytes = 0; let totalBytes = 0;
let handlers = {}; let handlers = {};
let pid = 0; let pid = 0;
let recvable = true;
let self = this; let self = this;
this.data = {}; this.data = {};
this.sqidx = new Date().getTime();
// 消息 // 消息
this.decodeTimeout = 0; this.decodeTimeout = 0;
this.timewait = 1; this.timewait = 1;
...@@ -122,6 +125,20 @@ function Jensen(log, conn) { ...@@ -122,6 +125,20 @@ function Jensen(log, conn) {
self.versionNumber = null; self.versionNumber = null;
}; };
function determineModel(productId) {
if (productId == 0xb00c) return 'hidock-h1';
if (productId == 0xb00d) return 'hidock-h1e';
if (productId == 0xb00e) return 'hidock-p1';
if (productId == 0xb00f) return 'hidock-p1:mini';
if (productId == 0x0100) return 'hidock-h1';
if (productId == 0x0101) return 'hidock-h1e';
if (productId == 0x0102) return 'hidock-h1';
if (productId == 0x0103) return 'hidock-h1e';
if (productId == 0x2040) return 'hidock-p1';
if (productId == 0x2041) return 'hidock-p1:mini';
return 'unknown';
}
const setup = async function (disableOnConnect) { const setup = async function (disableOnConnect) {
if (ready) return; if (ready) return;
self.versionCode = null; self.versionCode = null;
...@@ -132,10 +149,7 @@ function Jensen(log, conn) { ...@@ -132,10 +149,7 @@ function Jensen(log, conn) {
await device.claimInterface(0); await device.claimInterface(0);
await device.selectAlternateInterface(0, 0); await device.selectAlternateInterface(0, 0);
pid = device.productId; pid = device.productId;
self.model = device.productId == 0xb00c ? 'hidock-h1' self.model = determineModel(device.productId);
: device.productId == 0xb00d ? 'hidock-h1e'
: device.productId == 0xb00e ? 'hidock-p1'
: device.productId == 0xb00f ? 'hidock-p1:mini' : 'unknown';
Logger.info('jensen', 'connect', 'device pid: ' + device.productId); Logger.info('jensen', 'connect', 'device pid: ' + device.productId);
} catch (e) { } catch (e) {
Logger.error('jensen', 'setup', String(e)); Logger.error('jensen', 'setup', String(e));
...@@ -152,26 +166,27 @@ function Jensen(log, conn) { ...@@ -152,26 +166,27 @@ function Jensen(log, conn) {
} }
}; };
this.initialize = async function() { this.initialize = async function () {
if (conn) if (conn) {
{
if (!conn.opened) conn.open(); if (!conn.opened) conn.open();
await setup(); await setup();
} }
return true; return true;
} }
this.sequence = function () {
return this.sqidx++;
}
this.reconnect = async function () { this.reconnect = async function () {
try try {
{
await device.close(); await device.close();
await device.open(); await device.open();
ready = false; ready = false;
await setup(); await setup();
return true; return true;
} }
catch(ex) catch (ex) {
{
Logger.error('jensen', 'reconnect', ex); Logger.error('jensen', 'reconnect', ex);
} }
} }
...@@ -182,7 +197,7 @@ function Jensen(log, conn) { ...@@ -182,7 +197,7 @@ function Jensen(log, conn) {
if (r) return; if (r) return;
let conn = await navigator.usb.requestDevice({ let conn = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x10d6 }] filters: [{ vendorId: 0x10d6 }, { vendorId: 0x3887 }]
}); });
await conn.open(); await conn.open();
// self.model = conn.productId == 45069 ? 'hidock-h1e' : 'hidock-h1'; // self.model = conn.productId == 45069 ? 'hidock-h1e' : 'hidock-h1';
...@@ -232,6 +247,19 @@ function Jensen(log, conn) { ...@@ -232,6 +247,19 @@ function Jensen(log, conn) {
setup(); setup();
} }
this.stopReceive = function () {
recvable = false;
}
this.resumeReceive = async function () {
recvable = true;
if (current) trigger(null, current.replace(/^cmd-(\d+)-(\d+)$/gi, '$1'));
blocks.length = 0;
totalBytes = 0;
// 不发送一下,是不会回应数据的
await this.getTime(5);
}
this.isConnected = function () { this.isConnected = function () {
return device != null; return device != null;
}; };
...@@ -240,7 +268,7 @@ function Jensen(log, conn) { ...@@ -240,7 +268,7 @@ function Jensen(log, conn) {
Logger.info('jensen', 'disconnect', 'disconnect'); Logger.info('jensen', 'disconnect', 'disconnect');
try { try {
await device?.close(); await device?.close();
} catch (e) {} } catch (e) { }
}; };
this.send = function (cmd, seconds, onprogress) { this.send = function (cmd, seconds, onprogress) {
...@@ -322,11 +350,16 @@ function Jensen(log, conn) { ...@@ -322,11 +350,16 @@ function Jensen(log, conn) {
}); });
}; };
this._trigger = function (resp, msgid) {
trigger(resp, msgid);
}
const trigger = function (resp, msgid) { const trigger = function (resp, msgid) {
if (current == null) return; if (current == null) return;
Logger.debug('jensen', 'trigger', pid + ':trigger - ' + current.substring(0, current.lastIndexOf('-')) + ' <---> cmd-' + (msgid || 'err')); Logger.debug('jensen', 'trigger', pid + ':trigger - ' + current.substring(0, current.lastIndexOf('-')) + ' <---> cmd-' + (msgid || 'err'));
if (current.substring(0, current.lastIndexOf('-')) != 'cmd-' + msgid) { if (current.substring(0, current.lastIndexOf('-')) != 'cmd-' + msgid) {
// current = null; // current = null;
Logger.debug('jensen', 'trigger', 'msgid mismatched');
return; return;
} }
if (current in actions == false) { if (current in actions == false) {
...@@ -345,6 +378,7 @@ function Jensen(log, conn) { ...@@ -345,6 +378,7 @@ function Jensen(log, conn) {
let p = actions[tag]; let p = actions[tag];
p.resolve(null); p.resolve(null);
delete actions[tag]; delete actions[tag];
current = null;
}; };
this.dump = function () { this.dump = function () {
...@@ -355,7 +389,7 @@ function Jensen(log, conn) { ...@@ -355,7 +389,7 @@ function Jensen(log, conn) {
}; };
const tryReceive = function () { const tryReceive = function () {
if (device) if (device && recvable)
// console.log('trace', device); // console.log('trace', device);
device.transferIn(2, RECV_BUFF_SIZE).then((r) => { device.transferIn(2, RECV_BUFF_SIZE).then((r) => {
Logger.save?.('jensen', 'tryReceive', r?.data); Logger.save?.('jensen', 'tryReceive', r?.data);
...@@ -378,9 +412,13 @@ function Jensen(log, conn) { ...@@ -378,9 +412,13 @@ function Jensen(log, conn) {
blocks.push(result.data); blocks.push(result.data);
tryReceive(); tryReceive();
if (self.decodeTimeout) clearTimeout(self.decodeTimeout); if (self.decodeTimeout) clearTimeout(self.decodeTimeout);
self.decodeTimeout = setTimeout(function () { tryDecode(); }, self.timewait); self.decodeTimeout = setTimeout(function () {
tryDecode();
}, self.timewait);
if (self.onreceive) { if (self.onreceive) {
try { self.onreceive(totalBytes); } catch (e) {} try {
self.onreceive(totalBytes);
} catch (e) { }
} }
}; };
...@@ -451,6 +489,7 @@ function Jensen(log, conn) { ...@@ -451,6 +489,7 @@ function Jensen(log, conn) {
if (!handler) handler = Jensen.handlers[msgid]; if (!handler) handler = Jensen.handlers[msgid];
handler(null, self); handler(null, self);
} catch (e) { } catch (e) {
console.error(e);
trigger(e); trigger(e);
Logger.error('jensen', 'decode', 'decode error: ' + String(e)); Logger.error('jensen', 'decode', 'decode error: ' + String(e));
} }
...@@ -667,14 +706,12 @@ Jensen.prototype.listFiles = async function () { ...@@ -667,14 +706,12 @@ Jensen.prototype.listFiles = async function () {
this[tag] = []; this[tag] = [];
this.registerHandler(QUERY_FILE_LIST, (msg, jensen) => { this.registerHandler(QUERY_FILE_LIST, (msg, jensen) => {
console.log('tag', tag); console.log('tag', tag);
if (msg.body.length == 0) if (msg.body.length == 0) {
{
console.log('remove tag1: ' + tag); console.log('remove tag1: ' + tag);
jensen[tag] = null; jensen[tag] = null;
return []; return [];
} }
if (jensen[tag] == null) if (jensen[tag] == null) {
{
console.trace('no tag for: ' + tag); console.trace('no tag for: ' + tag);
return 'x'; return 'x';
} }
...@@ -693,7 +730,7 @@ Jensen.prototype.listFiles = async function () { ...@@ -693,7 +730,7 @@ Jensen.prototype.listFiles = async function () {
let fnpad = function (v) { let fnpad = function (v) {
return v > 9 ? v : '0' + v; return v > 9 ? v : '0' + v;
}; };
for (let i = start; i < data.length; ) { for (let i = start; i < data.length;) {
let len = 0; let len = 0;
let fname = []; let fname = [];
...@@ -728,24 +765,19 @@ Jensen.prototype.listFiles = async function () { ...@@ -728,24 +765,19 @@ Jensen.prototype.listFiles = async function () {
ftime = new Date(ftime); ftime = new Date(ftime);
duration = (flen / 32) * 4; duration = (flen / 32) * 4;
} }
else else {
{
ftime = null; ftime = null;
} }
if (ver == 1) if (ver == 1) {
{
duration = duration * 2; duration = duration * 2;
} }
else if (ver == 2) else if (ver == 2) {
{
duration = (flen - 44) / 48 / 2; duration = (flen - 44) / 48 / 2;
} }
else if (ver == 3) else if (ver == 3) {
{
duration = (flen - 44) / 48 / 2 / 2; duration = (flen - 44) / 48 / 2 / 2;
} }
else if (ver == 5) else if (ver == 5) {
{
duration = flen / 12; duration = flen / 12;
} }
let createDate = ''; let createDate = '';
...@@ -790,8 +822,7 @@ Jensen.prototype.listFiles = async function () { ...@@ -790,8 +822,7 @@ Jensen.prototype.listFiles = async function () {
// 如果已经等待过长的时间了,那就直接返回好了 // 如果已经等待过长的时间了,那就直接返回好了
let kv = tag + '-timer'; let kv = tag + '-timer';
let now = new Date().getTime(); let now = new Date().getTime();
if (kv in jensen) if (kv in jensen) {
{
window.clearTimeout(jensen[kv]); window.clearTimeout(jensen[kv]);
} }
...@@ -830,27 +861,6 @@ Jensen.prototype.readFile = async function (filename, offset, length, seconds) { ...@@ -830,27 +861,6 @@ Jensen.prototype.readFile = async function (filename, offset, length, seconds) {
return this.send(new Command(TRANSFER_FILE_PARTIAL).body(data), seconds); return this.send(new Command(TRANSFER_FILE_PARTIAL).body(data), seconds);
} }
Jensen.prototype.transferFile = async function (filename, length, ondata, seconds) {
let fname = [];
for (let i = 0; i < filename.length; i++) fname.push(filename.charCodeAt(i));
let flen = 0;
this.registerHandler(TRANSFER_FILE, (msg) => {
if (msg != null) {
flen += msg.body.length || msg.body.byteLength;
ondata(msg.body);
Logger.info('jensen', 'transfer-file', `${length} ${flen}`);
if (flen >= length) {
Logger.info('jensen', 'transfer-file', 'file transfer finish.');
return 'OK';
}
} else {
Logger.info('jensen', 'transfer-file', 'file transfer fail.');
ondata('fail');
}
});
return this.send(new Command(TRANSFER_FILE).body(fname), seconds);
}
Jensen.prototype.setTime = async function (time, seconds) { Jensen.prototype.setTime = async function (time, seconds) {
let str = let str =
time.getFullYear() + time.getFullYear() +
...@@ -1006,17 +1016,14 @@ Jensen.prototype.getFile = async function (filename, length, ondata, onprogress) ...@@ -1006,17 +1016,14 @@ Jensen.prototype.getFile = async function (filename, length, ondata, onprogress)
}; };
// this.onreceive = onprogress; // this.onreceive = onprogress;
this._progress_report = {}; this._progress_report = {};
this.onreceive = function(recvBytes) this.onreceive = function (recvBytes) {
{
let percent = Math.floor(recvBytes / length * 100); let percent = Math.floor(recvBytes / length * 100);
let k = 't' + percent; let k = 't' + percent;
if (percent % 5 == 0 && percent > 0 && percent < 100) if (percent % 5 == 0 && percent > 0 && percent < 100) {
{
this._progress_report[k] && onprogress?.(recvBytes); this._progress_report[k] && onprogress?.(recvBytes);
this._progress_report[k] = true; this._progress_report[k] = true;
} }
if (percent == 100) if (percent == 100) {
{
onprogress?.(recvBytes); onprogress?.(recvBytes);
} }
} }
...@@ -1129,7 +1136,10 @@ Jensen.prototype.getFileBlock = async function (filename, length, ondata) { ...@@ -1129,7 +1136,10 @@ Jensen.prototype.getFileBlock = async function (filename, length, ondata) {
return this.send(new Command(GET_FILE_BLOCK).body(data)); return this.send(new Command(GET_FILE_BLOCK).body(data));
}; };
Jensen.prototype.getBatteryStatus = function (seconds) {
if (this.model != 'hidock-p1') return null;
return this.send(new Command(GET_BATTERY_STATUS), seconds);
}
Jensen.prototype.writeSerialNumber = async function (sn) { Jensen.prototype.writeSerialNumber = async function (sn) {
let data = []; let data = [];
for (let i = 0; i < sn.length; i++) { for (let i = 0; i < sn.length; i++) {
...@@ -1169,7 +1179,27 @@ Jensen.prototype.sendScheduleInfo = function (infos) { ...@@ -1169,7 +1179,27 @@ Jensen.prototype.sendScheduleInfo = function (infos) {
return this.send(new Command(SCHEDULE_INFO).body(bodys)); return this.send(new Command(SCHEDULE_INFO).body(bodys));
} }
}; };
Jensen.prototype.transferFile = async function (filename, length, ondata, seconds) {
let fname = [];
for (let i = 0; i < filename.length; i++) fname.push(filename.charCodeAt(i));
let flen = 0;
this.registerHandler(TRANSFER_FILE, (msg) => {
if (msg != null) {
flen += msg.body.length || msg.body.byteLength;
ondata(msg.body);
// Logger.info('jensen', 'transfer-file', `${length} ${flen}`);
if (flen >= length) {
// Logger.info('jensen', 'transfer-file', 'file transfer finish.');
ondata(null);
return 'OK';
}
} else {
Logger.info('jensen', 'transfer-file', 'file transfer fail.');
ondata('fail');
}
});
return this.send(new Command(TRANSFER_FILE).body(fname), seconds);
}
Jensen.prototype.getRealtimeSettings = async function () { Jensen.prototype.getRealtimeSettings = async function () {
return this.send(new Command(REALTIME_READ_SETTING)); return this.send(new Command(REALTIME_READ_SETTING));
}; };
...@@ -1189,10 +1219,9 @@ Jensen.prototype.getRealtime = async function (frames) { ...@@ -1189,10 +1219,9 @@ Jensen.prototype.getRealtime = async function (frames) {
let d = (frames >> 0) & 0xff; let d = (frames >> 0) & 0xff;
return this.send(new Command(REALTIME_TRANSFER).body([a, b, c, d])); return this.send(new Command(REALTIME_TRANSFER).body([a, b, c, d]));
}; };
Jensen.prototype.requestToneUpdate = async function(signature, size, seconds) { Jensen.prototype.requestToneUpdate = async function (signature, size, seconds) {
let data = []; let data = [];
for (let i = 0; i < signature.length; i+=2) for (let i = 0; i < signature.length; i += 2) {
{
let b = signature.substring(i, i + 2); let b = signature.substring(i, i + 2);
data.push(parseInt(b, 16)); data.push(parseInt(b, 16));
} }
...@@ -1202,13 +1231,12 @@ Jensen.prototype.requestToneUpdate = async function(signature, size, seconds) { ...@@ -1202,13 +1231,12 @@ Jensen.prototype.requestToneUpdate = async function(signature, size, seconds) {
data.push((size >> 0) & 0xff); data.push((size >> 0) & 0xff);
return this.send(new Command(REQUEST_TONE_UPDATE).body(data), seconds); return this.send(new Command(REQUEST_TONE_UPDATE).body(data), seconds);
} }
Jensen.prototype.updateTone = async function(toneFile, seconds) { Jensen.prototype.updateTone = async function (toneFile, seconds) {
return this.send(new Command(TONE_UPDATE).body(toneFile), seconds); return this.send(new Command(TONE_UPDATE).body(toneFile), seconds);
} }
Jensen.prototype.requestUACUpdate = async function(signature, size, seconds) { Jensen.prototype.requestUACUpdate = async function (signature, size, seconds) {
let data = []; let data = [];
for (let i = 0; i < signature.length; i+=2) for (let i = 0; i < signature.length; i += 2) {
{
let b = signature.substring(i, i + 2); let b = signature.substring(i, i + 2);
data.push(parseInt(b, 16)); data.push(parseInt(b, 16));
} }
...@@ -1218,7 +1246,7 @@ Jensen.prototype.requestUACUpdate = async function(signature, size, seconds) { ...@@ -1218,7 +1246,7 @@ Jensen.prototype.requestUACUpdate = async function(signature, size, seconds) {
data.push((size >> 0) & 0xff); data.push((size >> 0) & 0xff);
return this.send(new Command(REQUEST_UAC_UPDATE).body(data), seconds); return this.send(new Command(REQUEST_UAC_UPDATE).body(data), seconds);
} }
Jensen.prototype.updateUAC = async function(uacFile, seconds) { Jensen.prototype.updateUAC = async function (uacFile, seconds) {
return this.send(new Command(UAC_UPDATE).body(uacFile), seconds); return this.send(new Command(UAC_UPDATE).body(uacFile), seconds);
} }
Jensen.registerHandler(REALTIME_CONTROL, commonMessageParser); Jensen.registerHandler(REALTIME_CONTROL, commonMessageParser);
...@@ -1226,6 +1254,16 @@ Jensen.registerHandler(REALTIME_READ_SETTING, (msg) => { ...@@ -1226,6 +1254,16 @@ Jensen.registerHandler(REALTIME_READ_SETTING, (msg) => {
// console.log(msg); // console.log(msg);
return msg; return msg;
}); });
Jensen.registerHandler(GET_BATTERY_STATUS, (msg) => {
let status = msg.body[0] & 0xff;
let percent = msg.body[1] & 0xff;
let voltage = ((msg.body[2] & 0xff) << 24) | ((msg.body[3] & 0xff) << 16) | ((msg.body[4] & 0xff) << 8) | (msg.body[5] & 0xff);
return {
status: status == 0 ? 'idle' : (status == 1 ? 'charging' : 'full'),
battery: percent,
voltage: voltage
};
})
Jensen.registerHandler(REALTIME_TRANSFER, (msg) => { Jensen.registerHandler(REALTIME_TRANSFER, (msg) => {
let a = msg.body[0] & 0xff; let a = msg.body[0] & 0xff;
let b = msg.body[1] & 0xff; let b = msg.body[1] & 0xff;
...@@ -1336,8 +1374,7 @@ Jensen.registerHandler(READ_CARD_INFO, (msg) => { ...@@ -1336,8 +1374,7 @@ Jensen.registerHandler(READ_CARD_INFO, (msg) => {
}); });
Jensen.registerHandler(TRANSFER_FILE_PARTIAL, (msg) => { Jensen.registerHandler(TRANSFER_FILE_PARTIAL, (msg) => {
let buf = new Uint8Array(msg.body.length); let buf = new Uint8Array(msg.body.length);
for (let i = 0; i < msg.body.length; i++) for (let i = 0; i < msg.body.length; i++) {
{
buf[i] = msg.body[i] & 0xff; buf[i] = msg.body[i] & 0xff;
} }
return buf; return buf;
...@@ -1385,6 +1422,7 @@ Jensen.registerHandler(GET_RECORDING_FILE, (msg) => { ...@@ -1385,6 +1422,7 @@ Jensen.registerHandler(GET_RECORDING_FILE, (msg) => {
Jensen.registerHandler(BLUETOOTH_SCAN, (msg) => { Jensen.registerHandler(BLUETOOTH_SCAN, (msg) => {
// console.log('bluetooth-scan', msg); // console.log('bluetooth-scan', msg);
if (msg.body.length == 0) return [];
let nums = ((msg.body[0] & 0xff) << 8) | (msg.body[1] & 0xff); let nums = ((msg.body[0] & 0xff) << 8) | (msg.body[1] & 0xff);
let devices = []; let devices = [];
let decoder = new TextDecoder('UTF-8'); let decoder = new TextDecoder('UTF-8');
...@@ -1449,7 +1487,7 @@ Jensen.registerHandler(REQUEST_TONE_UPDATE, (msg) => { ...@@ -1449,7 +1487,7 @@ Jensen.registerHandler(REQUEST_TONE_UPDATE, (msg) => {
else if (rst == 0x03) txt = 'card-full'; else if (rst == 0x03) txt = 'card-full';
else if (rst == 0x04) txt = 'card-error'; else if (rst == 0x04) txt = 'card-error';
else txt = String(rst); else txt = String(rst);
return { code : rst, result : txt }; return { code: rst, result: txt };
}); });
Jensen.registerHandler(TONE_UPDATE, commonMessageParser); Jensen.registerHandler(TONE_UPDATE, commonMessageParser);
Jensen.registerHandler(REQUEST_UAC_UPDATE, (msg) => { Jensen.registerHandler(REQUEST_UAC_UPDATE, (msg) => {
...@@ -1460,7 +1498,7 @@ Jensen.registerHandler(REQUEST_UAC_UPDATE, (msg) => { ...@@ -1460,7 +1498,7 @@ Jensen.registerHandler(REQUEST_UAC_UPDATE, (msg) => {
else if (rst == 0x03) txt = 'card-full'; else if (rst == 0x03) txt = 'card-full';
else if (rst == 0x04) txt = 'card-error'; else if (rst == 0x04) txt = 'card-error';
else txt = String(rst); else txt = String(rst);
return { code : rst, result : txt }; return { code: rst, result: txt };
}); });
Jensen.registerHandler(UAC_UPDATE, commonMessageParser); Jensen.registerHandler(UAC_UPDATE, commonMessageParser);
export { Jensen }; export { Jensen };
\ No newline at end of file
/**
* 模拟 Recorder.getRecordAnalyseData 的功能
* @param {Int16Array} pcmData PCM 原始数据,s16le 格式
* @returns {Uint8Array} 长度固定 1024,范围 0–255 的波形数据
*/
function getRecordAnalyseData(pcmData) {
const BUF_LEN = 800;
const out = new Uint8Array(BUF_LEN);
const pcm = [];
for (let i = 0; i < pcmData.length; i+=2) {
let h = pcmData[i] & 0xff;
let l = pcmData[i + 1] & 0xff;
let p = (l << 8) | h;
// 如果p是负数,则取反
if (p & 0x8000) p = (p & 0x7fff) - 0x8000;
pcm.push(p);
}
const len = pcm.length;
if (len === 0) return out.fill(128); // 空数据,填充中值
const step = len / BUF_LEN;
let rst = [];
for (let i = 0; i < BUF_LEN; i++) {
const start = Math.floor(i * step);
const end = Math.min(Math.floor((i + 1) * step), len);
// 计算这一段的平均值(保持正负号,显示真实波形)
let sum = 0;
for (let j = start; j < end; j++)
{
sum += Math.abs(pcm[j]);
}
const avg = Math.floor(sum / (end - start));
rst.push(avg);
// 将平均值从 [-32768, 32767] 映射到 [0, 255]
// 128 是中心线(静音),0是最负值,255是最正值
const normalized = avg / 32767; // 归一化到 [0, 1]
out[i] = Math.max(0, Math.min(127, Math.round(normalized * 127)));
}
return out;
}
self.addEventListener('message', async function(e) {
try {
const task = e.data;
// pcmdata的值为浮点类型的0~1,需要转换为s16le格式
const pcmData = new Int16Array(task.pcmdata.length);
for (let i = 0; i < task.pcmdata.length; i++)
{
pcmData[i] = Math.round(task.pcmdata[i] * 32767);
}
// 生成波形分析数据
const waveformData = getRecordAnalyseData(pcmData);
// 返回结果给主线程
self.postMessage({
id: task.id,
success: true,
waveform: waveformData,
});
} catch (error) {
console.error('Worker error:', error);
// 返回错误信息给主线程
self.postMessage({
id: task.id,
success: false,
error: error.message
});
}
});
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment