Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-lab
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-lab
Commits
98b49f36
提交
98b49f36
authored
7月 13, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 移除水印功能,优化直播监控的文件命名逻辑
上级
464c32ed
隐藏空白字符变更
内嵌
并排
正在显示
2 个修改的文件
包含
5 行增加
和
95 行删除
+5
-95
useLive.ts
src/composables/useLive.ts
+3
-93
useLiveMonitor.ts
src/composables/useLiveMonitor.ts
+2
-2
没有找到文件。
src/composables/useLive.ts
浏览文件 @
98b49f36
...
...
@@ -21,79 +21,14 @@ export const readBlobAsBase64 = (blob: Blob): Promise<string> => {
interface
UseLiveProps
{
enabledUserMedia
?:
boolean
keepChunks
?:
boolean
watermark
?:
boolean
onStart
?:
()
=>
void
onRecord
?:
(
data
:
Blob
)
=>
void
onStop
?:
(
blob
:
Blob
)
=>
void
}
function
formatTimestamp
()
{
const
now
=
new
Date
()
const
pad
=
(
n
:
number
)
=>
String
(
n
).
padStart
(
2
,
'0'
)
return
`
${
now
.
getFullYear
()}
-
${
pad
(
now
.
getMonth
()
+
1
)}
-
${
pad
(
now
.
getDate
())}
${
pad
(
now
.
getHours
())}
:
${
pad
(
now
.
getMinutes
())}
:
${
pad
(
now
.
getSeconds
())}
`
}
/** 创建时间水印覆盖层:video → canvas → canvasStream */
function
createWatermark
(
cameraStream
:
MediaStream
)
{
const
video
=
document
.
createElement
(
'video'
)
video
.
muted
=
true
video
.
playsInline
=
true
video
.
srcObject
=
cameraStream
const
canvas
=
document
.
createElement
(
'canvas'
)
const
ctx
=
canvas
.
getContext
(
'2d'
)
!
let
animId
:
number
|
null
=
null
let
_captureStream
:
MediaStream
|
null
=
null
let
_ready
:
Promise
<
void
>
const
start
=
()
=>
{
_ready
=
video
.
play
().
then
(()
=>
{
canvas
.
width
=
video
.
videoWidth
||
640
canvas
.
height
=
video
.
videoHeight
||
480
_captureStream
=
canvas
.
captureStream
(
30
)
cameraStream
.
getAudioTracks
().
forEach
((
track
)
=>
_captureStream
?.
addTrack
(
track
))
const
draw
=
()
=>
{
ctx
.
drawImage
(
video
,
0
,
0
,
canvas
.
width
,
canvas
.
height
)
const
fontSize
=
Math
.
max
(
14
,
Math
.
floor
(
canvas
.
height
/
36
))
ctx
.
font
=
`bold
${
fontSize
}
px monospace`
ctx
.
textBaseline
=
'bottom'
const
text
=
formatTimestamp
()
const
textWidth
=
ctx
.
measureText
(
text
).
width
const
x
=
canvas
.
width
-
textWidth
-
12
const
y
=
canvas
.
height
-
12
ctx
.
fillStyle
=
'rgba(0, 0, 0, 0.4)'
ctx
.
fillRect
(
x
-
6
,
y
-
fontSize
-
4
,
textWidth
+
12
,
fontSize
+
8
)
ctx
.
fillStyle
=
'rgba(255, 255, 255, 0.9)'
ctx
.
fillText
(
text
,
x
,
y
)
animId
=
requestAnimationFrame
(
draw
)
}
if
(
video
.
readyState
>=
1
)
draw
()
else
video
.
addEventListener
(
'loadedmetadata'
,
draw
,
{
once
:
true
})
})
}
const
ready
=
()
=>
_ready
const
getStream
=
()
=>
_captureStream
const
stop
=
()
=>
{
if
(
animId
!==
null
)
cancelAnimationFrame
(
animId
)
if
(
_captureStream
)
{
_captureStream
.
getTracks
().
forEach
((
t
)
=>
t
.
stop
())
_captureStream
=
null
}
video
.
pause
()
video
.
srcObject
=
null
}
return
{
start
,
stop
,
getStream
,
ready
}
}
export
function
useLive
({
enabledUserMedia
=
true
,
keepChunks
=
true
,
watermark
=
false
,
onStart
,
onRecord
,
onStop
,
...
...
@@ -134,22 +69,11 @@ export function useLive({
let
mediaRecorder
:
MediaRecorder
|
null
=
null
let
timeUpdateInterval
:
number
|
null
=
null
// 水印
let
watermarkCtx
:
ReturnType
<
typeof
createWatermark
>
|
null
=
null
function
getRecordStream
():
MediaStream
|
null
{
if
(
watermark
&&
watermarkCtx
)
return
watermarkCtx
.
getStream
()
return
stream
.
value
??
null
}
// 初始化MediaRecorder
const
initializeMediaRecorder
=
async
()
=>
{
// 水印模式:等待 canvas 流就绪
if
(
watermark
&&
watermarkCtx
)
await
watermarkCtx
.
ready
()
const
recordStream
=
getRecordStream
()
if
(
!
recordStream
)
return
const
initializeMediaRecorder
=
()
=>
{
if
(
!
stream
.
value
)
return
const
mimeType
=
MediaRecorder
.
isTypeSupported
(
'video/mp4'
)
?
'video/mp4'
:
'video/webm'
mediaRecorder
=
new
MediaRecorder
(
recordStream
,
{
mimeType
})
mediaRecorder
=
new
MediaRecorder
(
stream
.
value
,
{
mimeType
})
mediaRecorder
.
ondataavailable
=
handleDataAvailable
mediaRecorder
.
onstart
=
handleStart
mediaRecorder
.
onstop
=
handleStop
...
...
@@ -216,20 +140,6 @@ export function useLive({
nextTick
(()
=>
initializeMediaRecorder
())
}
// 水印生命周期(sync 确保 restart 后 start 前水印已就绪)
watch
(
stream
,
(
newStream
)
=>
{
if
(
!
watermark
)
return
if
(
watermarkCtx
)
watermarkCtx
.
stop
()
if
(
newStream
)
{
watermarkCtx
=
createWatermark
(
newStream
)
watermarkCtx
.
start
()
}
},
{
flush
:
'sync'
})
onUnmounted
(()
=>
{
if
(
watermarkCtx
)
watermarkCtx
.
stop
()
})
return
{
stream
,
start
,
...
...
src/composables/useLiveMonitor.ts
浏览文件 @
98b49f36
...
...
@@ -50,7 +50,6 @@ export function useLiveMonitor({
const
{
startTime
,
start
,
stop
,
restart
,
stream
}
=
useLive
({
enabledUserMedia
:
autoStart
,
keepChunks
:
false
,
watermark
:
true
,
onStart
:
()
=>
{
uploadSession
=
{
result
:
null
,
...
...
@@ -60,7 +59,8 @@ export function useLiveMonitor({
onRecord
:
(
blob
)
=>
{
if
(
isOSSUpload
)
{
const
session
=
uploadSession
const
name
=
`date_
${
dateStr
}
/user_
${
ssoId
}
/
${
fileName
.
value
}
.mp4`
const
extension
=
blob
.
type
.
includes
(
'webm'
)
?
'webm'
:
'mp4'
const
name
=
`date_
${
dateStr
}
/user_
${
ssoId
}
/
${
fileName
.
value
}
.
${
extension
}
`
session
.
queue
=
session
.
queue
.
then
(
async
()
=>
{
session
.
result
=
await
appendUpload
(
blob
,
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论