fix(im): 清理 RTC 媒体元素卸载时的 srcObject

- 对齐 Vue3 管理后台 63dfc5e 的 RTC 媒体元素处理
- useMediaStreamElement 改为 callback ref 闭包保存当前元素
- 组件卸载或 ref 置空时清理旧 video/audio 元素的 srcObject,避免流关闭后画面残留
- 同步适配 web-antd、web-ele、web-antdv-next

验证:
- pnpm -F @vben/web-antd run typecheck
- pnpm -F @vben/web-ele run typecheck
- web-antdv-next 仍为既有 55 个类型错误,无 RTC 新增错误
migration
YunaiV 2026-06-21 07:55:13 -07:00
parent 953e7c1502
commit 0a76bed471
3 changed files with 66 additions and 27 deletions

View File

@ -1,4 +1,4 @@
import { ref, type VNodeRef, watch } from 'vue'
import { type VNodeRef, watch } from 'vue'
/**
* MediaStream `<video>` / `<audio>` srcObject
@ -7,21 +7,34 @@ import { ref, type VNodeRef, watch } from 'vue'
export function useMediaStreamElement<T extends HTMLMediaElement>(
streamSource: () => MediaStream | null | undefined
): VNodeRef {
const elRef = ref<T>()
const syncStream = (stream = streamSource()) => {
if (elRef.value) {
elRef.value.srcObject = stream || null
let el: T | null = null
let currentStream: MediaStream | null | undefined
const syncStream = () => {
if (el) {
el.srcObject = currentStream || null
}
}
watch(
streamSource,
(stream) => {
syncStream(stream)
currentStream = stream
syncStream()
},
{ flush: 'post', immediate: true }
)
return (el) => {
elRef.value = el instanceof HTMLMediaElement ? (el as T) : undefined
syncStream()
return (value) => {
if (value instanceof HTMLMediaElement) {
el = value as T
syncStream()
return
}
if (el) {
el.srcObject = null
}
el = null
}
}

View File

@ -1,4 +1,4 @@
import { ref, type VNodeRef, watch } from 'vue'
import { type VNodeRef, watch } from 'vue'
/**
* MediaStream `<video>` / `<audio>` srcObject
@ -7,21 +7,34 @@ import { ref, type VNodeRef, watch } from 'vue'
export function useMediaStreamElement<T extends HTMLMediaElement>(
streamSource: () => MediaStream | null | undefined
): VNodeRef {
const elRef = ref<T>()
const syncStream = (stream = streamSource()) => {
if (elRef.value) {
elRef.value.srcObject = stream || null
let el: T | null = null
let currentStream: MediaStream | null | undefined
const syncStream = () => {
if (el) {
el.srcObject = currentStream || null
}
}
watch(
streamSource,
(stream) => {
syncStream(stream)
currentStream = stream
syncStream()
},
{ flush: 'post', immediate: true }
)
return (el) => {
elRef.value = el instanceof HTMLMediaElement ? (el as T) : undefined
syncStream()
return (value) => {
if (value instanceof HTMLMediaElement) {
el = value as T
syncStream()
return
}
if (el) {
el.srcObject = null
}
el = null
}
}

View File

@ -1,4 +1,4 @@
import { ref, type VNodeRef, watch } from 'vue'
import { type VNodeRef, watch } from 'vue'
/**
* MediaStream `<video>` / `<audio>` srcObject
@ -7,21 +7,34 @@ import { ref, type VNodeRef, watch } from 'vue'
export function useMediaStreamElement<T extends HTMLMediaElement>(
streamSource: () => MediaStream | null | undefined
): VNodeRef {
const elRef = ref<T>()
const syncStream = (stream = streamSource()) => {
if (elRef.value) {
elRef.value.srcObject = stream || null
let el: T | null = null
let currentStream: MediaStream | null | undefined
const syncStream = () => {
if (el) {
el.srcObject = currentStream || null
}
}
watch(
streamSource,
(stream) => {
syncStream(stream)
currentStream = stream
syncStream()
},
{ flush: 'post', immediate: true }
)
return (el) => {
elRef.value = el instanceof HTMLMediaElement ? (el as T) : undefined
syncStream()
return (value) => {
if (value instanceof HTMLMediaElement) {
el = value as T
syncStream()
return
}
if (el) {
el.srcObject = null
}
el = null
}
}