视频会议

This commit is contained in:
duoaohui
2026-05-23 23:04:10 +08:00
parent ba7447ddb9
commit 981c12c5bb
3 changed files with 81 additions and 14 deletions

View File

@@ -467,26 +467,27 @@ const acquireVideoTrack = async () => {
}
const onMicToggle = async () => {
pushDebug(`mic点击 loading=${audioLoading.value} leaving=${leaving.value} on=${audioEnabled.value} producer=${meetingStore.localProducers?.audio || 'null'}`)
pushDebug(`mic点击 loading=${audioLoading.value} on=${audioEnabled.value} producer=${meetingStore.localProducers?.audio || 'null'}`)
if (audioLoading.value || leaving.value) {
pushDebug('mic 被 loading/leaving 拦住')
return
}
audioLoading.value = true
try {
if (audioEnabled.value) {
pushDebug('mic -> stopLocalAudio')
await meetingStore.stopLocalAudio()
pushDebug('mic stop 完成')
if (meetingStore.localProducers?.audio) {
// 已经有 producer只 mute/unmute不释放硬件关键WebView 释放后申请会被拒)
pushDebug(`mic -> setLocalAudioMuted(${audioEnabled.value})`)
await meetingStore.setLocalAudioMuted(audioEnabled.value)
pushDebug(`mic mute 切换完成 enabled=${meetingStore.localAudioEnabled}`)
} else {
// 没 producer兜底走完整流程自己 getUserMedia 拿 track用户手势作用域内
pushDebug('mic -> getUserMedia(audio)')
const track = await acquireAudioTrack()
pushDebug(`mic 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
pushDebug('mic -> startLocalAudio(track)')
await meetingStore.startLocalAudio(undefined, track)
pushDebug(`mic start 完成 enabled=${meetingStore.localAudioEnabled}`)
localMediaError.value = ''
}
localMediaError.value = ''
} catch (err) {
pushDebug(`mic 报错: ${err?.name || ''} ${err?.message || err}`)
localMediaError.value = `麦克风:${err?.message || err}`
@@ -496,26 +497,25 @@ const onMicToggle = async () => {
}
const onCamToggle = async () => {
pushDebug(`cam点击 loading=${videoLoading.value} leaving=${leaving.value} on=${videoEnabled.value} producer=${meetingStore.localProducers?.video || 'null'}`)
pushDebug(`cam点击 loading=${videoLoading.value} on=${videoEnabled.value} producer=${meetingStore.localProducers?.video || 'null'}`)
if (videoLoading.value || leaving.value) {
pushDebug('cam 被 loading/leaving 拦住')
return
}
videoLoading.value = true
try {
if (videoEnabled.value) {
pushDebug('cam -> stopLocalVideo')
await meetingStore.stopLocalVideo()
pushDebug('cam stop 完成')
if (meetingStore.localProducers?.video) {
pushDebug(`cam -> setLocalVideoMuted(${videoEnabled.value})`)
await meetingStore.setLocalVideoMuted(videoEnabled.value)
pushDebug(`cam mute 切换完成 enabled=${meetingStore.localVideoEnabled}`)
} else {
pushDebug('cam -> getUserMedia(video)')
const track = await acquireVideoTrack()
pushDebug(`cam 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
pushDebug('cam -> startLocalVideo(track)')
await meetingStore.startLocalVideo(undefined, track)
pushDebug(`cam start 完成 enabled=${meetingStore.localVideoEnabled}`)
localMediaError.value = ''
}
localMediaError.value = ''
} catch (err) {
pushDebug(`cam 报错: ${err?.name || ''} ${err?.message || err}`)
localMediaError.value = `摄像头:${err?.message || err}`

View File

@@ -1217,6 +1217,36 @@ export const useMeetingStore = defineStore('meeting', () => {
_broadcastSelfState({ video_enabled: true })
}
/**
* 静音/解除静音本地音频(不释放硬件)
* 用于"点击麦克风按钮关/开"场景:避免在 iOS Safari / 微信 WebView 上释放硬件后再申请被拒。
* 与 stopLocalAudio 区别:本方法只暂停 Producertrack 仍持有硬件,可幂等多次切换。
*/
const setLocalAudioMuted = async (muted) => {
if (!_engine || !localProducers.audio) return false
const ok = muted
? _engine.pauseProducer(localProducers.audio)
: _engine.resumeProducer(localProducers.audio)
if (ok) {
localAudioEnabled.value = !muted
_broadcastSelfState({ audio_enabled: !muted })
}
return ok
}
/** 静音/解除静音本地视频(不释放硬件),用法同 setLocalAudioMuted */
const setLocalVideoMuted = async (muted) => {
if (!_engine || !localProducers.video) return false
const ok = muted
? _engine.pauseProducer(localProducers.video)
: _engine.resumeProducer(localProducers.video)
if (ok) {
localVideoEnabled.value = !muted
_broadcastSelfState({ video_enabled: !muted })
}
return ok
}
/** 关闭本地音频 */
const stopLocalAudio = async () => {
if (!_engine || !localProducers.audio) return
@@ -1617,6 +1647,8 @@ export const useMeetingStore = defineStore('meeting', () => {
startLocalVideo,
stopLocalAudio,
stopLocalVideo,
setLocalAudioMuted,
setLocalVideoMuted,
startScreenShare,
stopScreenShare,
recording,

View File

@@ -405,6 +405,39 @@ export function createMediaEngine({ roomCode, userId, sendWithAck, logger = defa
}
}
/**
* 暂停指定 Producermediasoup-client 内部会把 track.enabled=false
* 远端 Consumer 收到的就是静音/黑帧,但本地 track 仍持有硬件不释放。
* 用于"关摄像头/麦克风"按钮:避免在 iOS Safari / 微信 WebView 上释放硬件后再申请被拒NotAllowedError
*/
const pauseProducer = (producerId) => {
const producer = producers.get(producerId)
if (!producer || producer.closed) return false
try {
if (!producer.paused) producer.pause()
// 双保险:有些 mediasoup-client 版本不会自动改 track.enabled
if (producer.track) producer.track.enabled = false
return true
} catch (e) {
logger('warn', '[MediaEngine] producer.pause 抛错', e)
return false
}
}
/** 恢复 Producer与 pauseProducer 对应 */
const resumeProducer = (producerId) => {
const producer = producers.get(producerId)
if (!producer || producer.closed) return false
try {
if (producer.paused) producer.resume()
if (producer.track) producer.track.enabled = true
return true
} catch (e) {
logger('warn', '[MediaEngine] producer.resume 抛错', e)
return false
}
}
/** 本地 Consumer 关闭(通常由 producer.new closed=true 广播触发,无需额外 WS */
const closeConsumer = (consumerId) => {
const consumer = consumers.get(consumerId)
@@ -445,6 +478,8 @@ export function createMediaEngine({ roomCode, userId, sendWithAck, logger = defa
consume,
resumeConsumer,
closeProducer,
pauseProducer,
resumeProducer,
closeConsumer,
close,
getDevice: () => device,