视频会议
This commit is contained in:
@@ -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}`
|
||||
|
||||
@@ -1217,6 +1217,36 @@ export const useMeetingStore = defineStore('meeting', () => {
|
||||
_broadcastSelfState({ video_enabled: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 静音/解除静音本地音频(不释放硬件)
|
||||
* 用于"点击麦克风按钮关/开"场景:避免在 iOS Safari / 微信 WebView 上释放硬件后再申请被拒。
|
||||
* 与 stopLocalAudio 区别:本方法只暂停 Producer,track 仍持有硬件,可幂等多次切换。
|
||||
*/
|
||||
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,
|
||||
|
||||
@@ -405,6 +405,39 @@ export function createMediaEngine({ roomCode, userId, sendWithAck, logger = defa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停指定 Producer:mediasoup-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,
|
||||
|
||||
Reference in New Issue
Block a user