Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
J
jensen
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Skye Yu
jensen
Commits
458a8c08
Commit
458a8c08
authored
Dec 02, 2025
by
martin hou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 实时音频传输与测试
parent
458fb496
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
160 additions
and
17 deletions
+160
-17
jensen.d.ts
jensen.d.ts
+3
-3
index.css
src/index.css
+14
-0
index.tsx
src/index.tsx
+129
-6
jensen.js
src/utils/jensen.js
+14
-8
No files found.
jensen.d.ts
View file @
458a8c08
...
@@ -297,16 +297,16 @@ declare class Jensen {
...
@@ -297,16 +297,16 @@ declare class Jensen {
getRealtimeSettings
:
()
=>
Promise
<
any
>
;
getRealtimeSettings
:
()
=>
Promise
<
any
>
;
// 开始实时音频流传输
// 开始实时音频流传输
startRealtime
:
()
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
startRealtime
:
(
frames
:
number
,
seconds
?:
number
)
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
// 暂停实时音频流传输
// 暂停实时音频流传输
pauseRealtime
:
()
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
pauseRealtime
:
()
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
// 停止/结束实时音频流传输
// 停止/结束实时音频流传输
stopRealtime
:
()
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
stopRealtime
:
(
seconds
?:
number
)
=>
Promise
<
ReturnStruct
[
'common'
]
>
;
// 获取实时音频流数据
// 获取实时音频流数据
getRealtime
:
(
frames
:
number
)
=>
Promise
<
{
data
:
Uint8Array
;
rest
:
number
}
>
;
getRealtime
:
(
frames
:
number
,
mute
:
boolean
,
seconds
?:
number
)
=>
Promise
<
{
data
:
Uint8Array
|
null
;
rest
:
number
,
muted
:
boolean
}
>
;
// 录音文件随机读取,从指定位置起读取指定数量个字节
// 录音文件随机读取,从指定位置起读取指定数量个字节
readFile
:
(
fname
:
string
,
offset
:
number
,
length
:
number
)
=>
Promise
<
Uint8Array
>
;
readFile
:
(
fname
:
string
,
offset
:
number
,
length
:
number
)
=>
Promise
<
Uint8Array
>
;
...
...
src/index.css
View file @
458a8c08
...
@@ -78,4 +78,18 @@ button:hover
...
@@ -78,4 +78,18 @@ button:hover
outline
:
none
;
outline
:
none
;
padding
:
5px
;
padding
:
5px
;
box-sizing
:
border-box
;
box-sizing
:
border-box
;
}
.bar
{
width
:
400px
;
height
:
30px
;
border
:
solid
1px
#ccc
;
overflow
:
hidden
;
}
.bar
div
{
background-color
:
#090
;
border
:
0px
;
height
:
100%
;
min-width
:
10px
;
}
}
\ No newline at end of file
src/index.tsx
View file @
458a8c08
import
{
useEffect
,
useState
}
from
'react'
;
import
{
useEffect
,
useState
,
useRef
}
from
'react'
;
import
Jensen
,
{
BluetoothDevice
}
from
'..'
;
import
Jensen
,
{
BluetoothDevice
}
from
'..'
;
import
'./index.css'
;
import
'./index.css'
;
import
{
Logger
}
from
'./Logger'
import
{
Logger
}
from
'./Logger'
...
@@ -13,6 +13,9 @@ const firmwareVersions = [
...
@@ -13,6 +13,9 @@ const firmwareVersions = [
{
model
:
'hidock-p1'
,
version
:
'1.2.25'
,
url
:
'https://jensen.test.hidock.com/firmwares/hidock-p1-1.2.25.bin'
,
remark
:
''
}
{
model
:
'hidock-p1'
,
version
:
'1.2.25'
,
url
:
'https://jensen.test.hidock.com/firmwares/hidock-p1-1.2.25.bin'
,
remark
:
''
}
];
];
const
sleep
=
(
ms
:
number
)
=>
{
return
new
Promise
(
resolve
=>
setTimeout
(
resolve
,
ms
));
}
export
function
Home
()
{
export
function
Home
()
{
const
[
files
,
setFiles
]
=
useState
<
Jensen
.
FileInfo
[]
>
([]);
const
[
files
,
setFiles
]
=
useState
<
Jensen
.
FileInfo
[]
>
([]);
...
@@ -21,6 +24,13 @@ export function Home() {
...
@@ -21,6 +24,13 @@ export function Home() {
const
[
firmwares
,
setFirmwares
]
=
useState
<
typeof
firmwareVersions
|
null
>
(
null
);
const
[
firmwares
,
setFirmwares
]
=
useState
<
typeof
firmwareVersions
|
null
>
(
null
);
const
[
logs
,
setLogs
]
=
useState
<
string
[]
>
([]);
const
[
logs
,
setLogs
]
=
useState
<
string
[]
>
([]);
const
[
sn
,
setSn
]
=
useState
<
string
|
null
>
(
null
);
const
[
sn
,
setSn
]
=
useState
<
string
|
null
>
(
null
);
const
[
liveStates
,
setLiveStates
]
=
useState
<
string
>
(
''
);
const
[
rmsLeft
,
setRmsLeft
]
=
useState
<
number
>
(
0
);
const
[
rmsRight
,
setRmsRight
]
=
useState
<
number
>
(
0
);
const
[
liveDelay
,
setLiveDelay
]
=
useState
<
number
>
(
100
);
const
liveTimeoutRef
=
useRef
<
number
|
null
>
(
null
);
const
liveIntervalRef
=
useRef
<
number
>
(
liveDelay
);
useEffect
(()
=>
{
liveIntervalRef
.
current
=
liveDelay
;
},
[
liveDelay
]);
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
mgr
.
onconnectionstatechanged
((
state
,
dinfo
)
=>
{
console
.
log
(
'onconnectionstatechanged'
,
state
,
dinfo
);
console
.
log
(
'onconnectionstatechanged'
,
state
,
dinfo
);
...
@@ -65,13 +75,13 @@ export function Home() {
...
@@ -65,13 +75,13 @@ export function Home() {
// 创建日志更新定时器
// 创建日志更新定时器
const
logUpdateInterval
=
setInterval
(()
=>
{
const
logUpdateInterval
=
setInterval
(()
=>
{
/
/ 从Logger.messages中获取最后500条记录
/
*
let last = Logger.messages.slice(-500).reverse();
let last = Logger.messages.slice(-500).reverse();
// 将last转换为字符串数组
let logStr: string[] = last.map((item) => {
let logStr: string[] = last.map((item) => {
return `[${item.level === 'error' ? 'x' : '*'}] [${new Date(item.time).toLocaleString()}] (${item.module} - ${item.procedure}) ${item.message}`;
return `[${item.level === 'error' ? 'x' : '*'}] [${new Date(item.time).toLocaleString()}] (${item.module} - ${item.procedure}) ${item.message}`;
});
});
setLogs(logStr);
setLogs(logStr);
*/
},
1000
);
},
1000
);
// 清理函数
// 清理函数
...
@@ -80,6 +90,14 @@ export function Home() {
...
@@ -80,6 +90,14 @@ export function Home() {
};
};
},
[]);
},
[]);
useEffect
(()
=>
{
return
()
=>
{
if
(
liveTimeoutRef
.
current
!==
null
)
{
window
.
clearTimeout
(
liveTimeoutRef
.
current
);
}
};
},
[]);
const
info
=
async
()
=>
{
const
info
=
async
()
=>
{
// alert(sn);
// alert(sn);
let
jensen
=
getJensen
();
let
jensen
=
getJensen
();
...
@@ -146,7 +164,7 @@ export function Home() {
...
@@ -146,7 +164,7 @@ export function Home() {
console
.
log
(
r
);
console
.
log
(
r
);
}
}
const
clsBtnConnect
=
{
const
clsBtnConnect
:
React
.
CSSProperties
=
{
height
:
'30px'
,
height
:
'30px'
,
padding
:
'0px 20px'
,
padding
:
'0px 20px'
,
position
:
'absolute'
,
position
:
'absolute'
,
...
@@ -155,7 +173,7 @@ export function Home() {
...
@@ -155,7 +173,7 @@ export function Home() {
cursor
:
'pointer'
cursor
:
'pointer'
}
}
const
clsBleDevice
=
{
const
clsBleDevice
:
React
.
CSSProperties
=
{
height
:
'40px'
,
height
:
'40px'
,
lineHeight
:
'40px'
,
lineHeight
:
'40px'
,
minWidth
:
'500px'
,
minWidth
:
'500px'
,
...
@@ -163,7 +181,7 @@ export function Home() {
...
@@ -163,7 +181,7 @@ export function Home() {
display
:
'inline-block'
display
:
'inline-block'
}
}
const
clsBleName
=
{
const
clsBleName
:
React
.
CSSProperties
=
{
fontSize
:
'16px'
,
fontSize
:
'16px'
,
fontFamily
:
'consolas'
fontFamily
:
'consolas'
}
}
...
@@ -443,6 +461,90 @@ export function Home() {
...
@@ -443,6 +461,90 @@ export function Home() {
Logger
.
info
(
'jensen'
,
'bluetooth'
,
'Clear Paired Devices: '
+
JSON
.
stringify
(
rst
));
Logger
.
info
(
'jensen'
,
'bluetooth'
,
'Clear Paired Devices: '
+
JSON
.
stringify
(
rst
));
}
}
const
rms
=
(
u8
:
Uint8Array
)
=>
{
let
sumL
=
0
;
let
sumR
=
0
;
const
frameCount
=
(
u8
.
length
-
8
)
/
4
;
for
(
let
i
=
8
;
i
<
u8
.
length
;
i
+=
4
)
{
const
a
=
u8
[
i
+
0
]
&
0xff
;
const
b
=
u8
[
i
+
1
]
&
0xff
;
const
c
=
u8
[
i
+
2
]
&
0xff
;
const
d
=
u8
[
i
+
3
]
&
0xff
;
let
l
=
(
b
<<
8
|
a
)
&
0xffff
;
let
r
=
(
d
<<
8
|
c
)
&
0xffff
;
if
(
l
&
0x8000
)
l
=
(
l
&
0x7fff
)
-
0x8000
;
if
(
r
&
0x8000
)
r
=
(
r
&
0x7fff
)
-
0x8000
;
// console.log('live, l', l, 'r', r);
const
nl
=
l
/
32768
;
const
nr
=
r
/
32768
;
sumL
+=
nl
*
nl
;
sumR
+=
nr
*
nr
;
}
// console.log('live, sumL', sumL, 'sumR', sumR);
return
[
Math
.
sqrt
(
sumL
/
frameCount
),
Math
.
sqrt
(
sumR
/
frameCount
)];
}
const
blocksRef
=
useRef
<
Uint8Array
[]
>
([]);
const
scheduleLiveTick
=
async
()
=>
{
let
jensen
=
getJensen
();
if
(
jensen
==
null
)
return
;
let
live
=
await
jensen
.
getRealtime
(
1
,
false
,
1
);
// 若无数据直接调度下一次
if
(
live
?.
data
==
null
)
return
;
setLiveStates
(
'live: '
+
live
.
data
.
length
+
' rest: '
+
live
.
rest
+
' muted: '
+
live
.
muted
);
// 仅计算,不保留引用;如需录音导出再使用 blocksRef.current.push(live.data.slice(8))
const
[
rms1
,
rms2
]
=
rms
(
live
.
data
);
// console.log('live, rms1', rms1, 'rms2', rms2);
setRmsLeft
(
Math
.
floor
(
rms1
*
4
*
400
));
setRmsRight
(
Math
.
floor
(
rms2
*
4
*
400
));
// 根据live.rest的值决定timeout的间隔时长,如果>1则为50,否则为100
let
timeout
=
live
.
rest
>
1
?
50
:
100
;
liveTimeoutRef
.
current
=
window
.
setTimeout
(
scheduleLiveTick
,
Math
.
max
(
0
,
timeout
|
0
));
}
const
startLive
=
async
()
=>
{
let
jensen
=
getJensen
();
if
(
jensen
==
null
)
return
;
let
rst
=
await
jensen
.
startRealtime
(
2
,
1
);
Logger
.
info
(
'jensen'
,
'live'
,
'Start Live: '
+
JSON
.
stringify
(
rst
));
if
(
liveTimeoutRef
.
current
!==
null
)
{
window
.
clearTimeout
(
liveTimeoutRef
.
current
);
}
// 立即触发一次,并在回调末尾调度下一次
scheduleLiveTick
();
}
const
stopLive
=
async
()
=>
{
let
jensen
=
getJensen
();
if
(
jensen
==
null
)
return
;
let
rst
=
await
jensen
.
stopRealtime
(
1
);
Logger
.
info
(
'jensen'
,
'live'
,
'Stop Live: '
+
JSON
.
stringify
(
rst
));
if
(
liveTimeoutRef
.
current
!==
null
)
{
window
.
clearTimeout
(
liveTimeoutRef
.
current
);
liveTimeoutRef
.
current
=
null
;
}
console
.
log
(
'live stopped'
);
// 将blocks拼接成一个Uint8Array,并下载到本地
const
totalLen
=
blocksRef
.
current
.
reduce
((
sum
,
b
)
=>
sum
+
b
.
length
,
0
);
const
data
=
new
Uint8Array
(
totalLen
);
let
offset
=
0
;
for
(
const
b
of
blocksRef
.
current
)
{
data
.
set
(
b
,
offset
);
offset
+=
b
.
length
;
}
// 清空缓存
/*
blocksRef.current = [];
let blob = new Blob([data], { type: 'audio/pcm' });
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'live.pcm';
a.click();
URL.revokeObjectURL(url);
*/
}
return
(
return
(
<>
<>
<
div
className=
"btn-container"
style=
{
{
display
:
'flex'
,
flexDirection
:
'row'
,
gap
:
'16px'
,
padding
:
'16px'
,
alignItems
:
'center'
,
flexWrap
:
'wrap'
}
}
>
<
div
className=
"btn-container"
style=
{
{
display
:
'flex'
,
flexDirection
:
'row'
,
gap
:
'16px'
,
padding
:
'16px'
,
alignItems
:
'center'
,
flexWrap
:
'wrap'
}
}
>
...
@@ -472,6 +574,18 @@ export function Home() {
...
@@ -472,6 +574,18 @@ export function Home() {
<
button
onClick=
{
turnPopupOn
}
>
Turn Popup On
</
button
>
<
button
onClick=
{
turnPopupOn
}
>
Turn Popup On
</
button
>
<
button
onClick=
{
turnPopupOff
}
>
Turn Popup Off
</
button
>
<
button
onClick=
{
turnPopupOff
}
>
Turn Popup Off
</
button
>
<
button
onClick=
{
getSettings
}
>
Get Settings
</
button
>
<
button
onClick=
{
getSettings
}
>
Get Settings
</
button
>
<
button
onClick=
{
startLive
}
>
Start Live
</
button
>
<
button
onClick=
{
stopLive
}
>
Stop Live
</
button
>
<
span
style=
{
{
marginLeft
:
'8px'
}
}
>
Interval(ms):
</
span
>
<
input
type=
"number"
value=
{
liveDelay
}
onChange=
{
(
e
)
=>
{
const
v
=
parseInt
(
e
.
target
.
value
||
'0'
);
setLiveDelay
(
isNaN
(
v
)
?
0
:
v
);
}
}
style=
{
{
width
:
'90px'
}
}
/>
</
div
>
</
div
>
<
div
className=
"result-container"
>
<
div
className=
"result-container"
>
<
div
className=
"list-container"
>
<
div
className=
"list-container"
>
...
@@ -489,6 +603,15 @@ export function Home() {
...
@@ -489,6 +603,15 @@ export function Home() {
</
div
>
</
div
>
)
:
(<></>)
)
:
(<></>)
}
}
{
liveStates
?
(<>
<
div
>
<
h3
>
{
liveStates
}
</
h3
>
<
div
className=
"bar"
><
div
style=
{
{
width
:
rmsLeft
+
'px'
}
}
></
div
></
div
>
<
div
className=
"bar"
><
div
style=
{
{
width
:
rmsRight
+
'px'
}
}
></
div
></
div
>
</
div
>
</>)
:
<></>
}
<
div
id=
"files"
style=
{
{
padding
:
'0px 0px 0px 30px'
,
marginBottom
:
'20px'
}
}
>
<
div
id=
"files"
style=
{
{
padding
:
'0px 0px 0px 30px'
,
marginBottom
:
'20px'
}
}
>
<
h3
>
Files:
</
h3
>
<
h3
>
Files:
</
h3
>
<
ol
style=
{
{
padding
:
'0px 0px 0px 30px'
,
'listStyle'
:
'none'
}
}
>
<
ol
style=
{
{
padding
:
'0px 0px 0px 30px'
,
'listStyle'
:
'none'
}
}
>
...
...
src/utils/jensen.js
View file @
458a8c08
...
@@ -1330,21 +1330,21 @@ Jensen.prototype.sendScheduleInfo = function (infos) {
...
@@ -1330,21 +1330,21 @@ Jensen.prototype.sendScheduleInfo = function (infos) {
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
));
};
};
Jensen
.
prototype
.
startRealtime
=
async
function
()
{
Jensen
.
prototype
.
startRealtime
=
async
function
(
channels
,
seconds
)
{
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
0
,
0x00
,
0x00
,
0x00
,
0x01
])
);
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
1
,
0x00
,
0x00
,
0x00
,
channels
&
0x03
]),
seconds
);
};
};
Jensen
.
prototype
.
pauseRealtime
=
async
function
()
{
Jensen
.
prototype
.
pauseRealtime
=
async
function
(
seconds
)
{
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
1
,
0x00
,
0x00
,
0x00
,
0x01
])
);
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
2
,
0x00
,
0x00
,
0x00
,
0x00
]),
seconds
);
};
};
Jensen
.
prototype
.
stopRealtime
=
async
function
()
{
Jensen
.
prototype
.
stopRealtime
=
async
function
(
seconds
)
{
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
2
,
0x00
,
0x00
,
0x00
,
0x01
])
);
return
this
.
send
(
new
Command
(
REALTIME_CONTROL
).
body
([
0x00
,
0x00
,
0x00
,
0x0
0
,
0x00
,
0x00
,
0x00
,
0x00
]),
seconds
);
};
};
Jensen
.
prototype
.
getRealtime
=
async
function
(
frames
)
{
Jensen
.
prototype
.
getRealtime
=
async
function
(
frames
,
mute
,
seconds
)
{
let
a
=
(
frames
>>
24
)
&
0xff
;
let
a
=
(
frames
>>
24
)
&
0xff
;
let
b
=
(
frames
>>
16
)
&
0xff
;
let
b
=
(
frames
>>
16
)
&
0xff
;
let
c
=
(
frames
>>
8
)
&
0xff
;
let
c
=
(
frames
>>
8
)
&
0xff
;
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
,
0x00
,
0x00
,
0x00
,
mute
?
0x01
:
0x00
]),
seconds
);
};
};
Jensen
.
prototype
.
requestToneUpdate
=
async
function
(
signature
,
size
,
seconds
)
{
Jensen
.
prototype
.
requestToneUpdate
=
async
function
(
signature
,
size
,
seconds
)
{
let
data
=
[];
let
data
=
[];
...
@@ -1398,8 +1398,14 @@ Jensen.registerHandler(REALTIME_TRANSFER, (msg) => {
...
@@ -1398,8 +1398,14 @@ Jensen.registerHandler(REALTIME_TRANSFER, (msg) => {
let
b
=
msg
.
body
[
1
]
&
0xff
;
let
b
=
msg
.
body
[
1
]
&
0xff
;
let
c
=
msg
.
body
[
2
]
&
0xff
;
let
c
=
msg
.
body
[
2
]
&
0xff
;
let
d
=
msg
.
body
[
3
]
&
0xff
;
let
d
=
msg
.
body
[
3
]
&
0xff
;
let
a1
=
msg
.
body
[
4
]
&
0xff
;
let
b1
=
msg
.
body
[
5
]
&
0xff
;
let
c1
=
msg
.
body
[
6
]
&
0xff
;
let
d1
=
msg
.
body
[
7
]
&
0xff
;
let
muted
=
(
a1
<<
24
|
b1
<<
16
|
c1
<<
8
|
d1
)
==
0x01
;
return
{
return
{
rest
:
(
a
<<
24
)
|
(
b
<<
16
)
|
(
c
<<
8
)
|
d
,
rest
:
(
a
<<
24
)
|
(
b
<<
16
)
|
(
c
<<
8
)
|
d
,
muted
:
muted
,
data
:
msg
.
body
data
:
msg
.
body
};
};
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment