| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- /**
- * useVideoManager —— 单活视频管理器
- * ---------------------------------------------------------------------------
- * 移动端 / 平板的 H.264 硬件解码器实例有限(1080p 约 4~8 路,4K 只有 1~2 路)。
- * 同一时刻只允许一个 <video> 持有 src,释放其他所有(pause + removeAttribute + load)。
- *
- * 职责:只管"释放"(归还解码器)和"注册/注销",不管 src 的挂载。
- * src 的设置由调用方(VideoPlayer / useMSE)自己决定。
- * ---------------------------------------------------------------------------
- */
- interface VMMeta {
- duration: number
- currentTime: number
- played: boolean
- }
- function getMeta(el: HTMLVideoElement): VMMeta {
- if (!(el as any)._vmMeta) {
- (el as any)._vmMeta = { duration: 0, currentTime: 0, played: false }
- }
- return (el as any)._vmMeta as VMMeta
- }
- // 单例
- class VideoManager {
- current: HTMLVideoElement | null = null
- private _els: HTMLVideoElement[] = []
- /** 注册一个 video 元素 */
- register(el: HTMLVideoElement) {
- if (!el || (el as any)._vmRegistered) return
- ;(el as any)._vmRegistered = true
- const m = getMeta(el)
- el.addEventListener('loadedmetadata', () => {
- if (isFinite(el.duration) && el.duration > 0) m.duration = el.duration
- })
- el.addEventListener('durationchange', () => {
- if (isFinite(el.duration) && el.duration > 0) m.duration = el.duration
- })
- el.addEventListener('timeupdate', () => {
- if (el.currentTime > 0) m.currentTime = el.currentTime
- })
- el.addEventListener('play', () => {
- m.played = true
- })
- this._els.push(el)
- }
- /**
- * 释放除 keepEl 之外的所有 video:pause → removeAttribute('src') → load
- * 必须用 load(),只删 src 不调 load 解码管线不会释放
- */
- releaseAllOthers(keepEl: HTMLVideoElement) {
- this._els.forEach(other => {
- if (other !== keepEl && other.src) {
- this.release(other)
- }
- })
- this.current = keepEl
- }
- /** 释放单个元素:归还解码器 + 释放内存 */
- release(el: HTMLVideoElement) {
- if (!el) return
- const m = getMeta(el)
- if (el.currentTime > 0) m.currentTime = el.currentTime
- try {
- el.pause()
- }
- catch {}
- el.removeAttribute('src')
- try {
- el.load()
- }
- catch {}
- // 关键:手动派发 pause 事件
- // 某些浏览器(尤其 iOS Safari)在 pause → remove src → load 连续调用时,
- // 原生 @pause 事件可能被"吞掉",导致外部状态(Vue 的 paused ref)没同步。
- // 这里用 dispatchEvent 兜底,确保所有监听方都能收到。
- try {
- el.dispatchEvent(new Event('pause'))
- }
- catch {}
- if (this.current === el) this.current = null
- }
- /** 释放全部 */
- releaseAll() {
- this._els.forEach(el => {
- if (el.src) this.release(el)
- })
- this.current = null
- }
- /** 取缓存的时长/进度,释放后依然可用 */
- getMeta(el: HTMLVideoElement) {
- const m = getMeta(el)
- return {
- duration: m.duration || (isFinite(el.duration) ? el.duration : 0),
- currentTime: m.currentTime || el.currentTime || 0,
- played: m.played,
- }
- }
- unregister(el: HTMLVideoElement) {
- this.release(el)
- this._els = this._els.filter(e => e !== el)
- if (this.current === el) this.current = null
- }
- }
- const _manager = new VideoManager()
- export function useVideoManager() {
- return _manager
- }
- export { VideoManager }
- export default _manager
|