在浏览器中对视频mp4与背景音乐mp3都同步自动播放画面和声音

在浏览器中对视频mp4与背景音乐mp3都同步自动播放画面和声音
文章目录需求分析1. 音乐的自动播放2. 视频的自动播放3. 开启无人交互自动播放【建议方法B】源码BgMuic.vueMediaSwiper.vue需求项目中需要在轮播图中播放视频时连声音一起播放并且网页自带的背景音乐也需要进行播放分析1. 音乐的自动播放动播放流程是这样的分为三个阶段加载阶段父组件传入 musicData(URL)→ watch 同步到 musicUrl→ 刷新→ 浏览器开始加载音频文件→ 加载完成触发 canplay自动播放尝试会被浏览器拦截canplay → onCanPlay()→ audio.play() 尝试播放→ 浏览器阻止NotAllowedError因为尚无用户交互→ catch 中启动 setInterval 每 1 秒轮询这里第一次 play() 必然失败浏览器禁止带声音的音频自动播放。用户交互触发真正播放用户点击/触摸页面任意位置→ onUserInteraction()→ userInteracted true→ playAudio() → audio.play() → 浏览器允许播放 → 声音出来→ clearInterval 停止轮询所以它并非真正意义上的自动播放而是 依赖用户第一次点击页面来绕过浏览器策略。由于页面本身有轮播图等内容用户几乎总会点击页面所以感觉上像是自动播放的。2. 视频的自动播放页面加载→ 视频 slide 出现尝试 play() → 被浏览器阻止 → 跳过→ 用户点击页面触发 BgMusic 的交互处理→ 浏览器解除媒体播放限制→ 下一个视频 slide → play() 成功 → 带声音播放如果用户始终不点击页面视频会播放失败Swiper直接跳到下一张图片。但实际使用中几乎总会有点击所以看起来是自动播放。3. 开启无人交互自动播放【建议方法B】开启这个后就会跳过手动点击才播放声音Windows 系统Edge 或 Chrome方法 A使用组策略推荐企业/受控部署下载策略模板ADMXEdgeMicrosoft Edge Policy TemplatesChromeChrome Enterprise Policy Templates安装模板将 ADMX 文件放到 C:\Windows\PolicyDefinitions将对应的 ADML 文件放到 C:\Windows\PolicyDefinitions\en-US打开组策略编辑器Win R → 输入 gpedit.msc → Enter找到策略位置Edge: 计算机配置 → 管理模板 → Microsoft Edge → 内容设置Chrome: 计算机配置 → 管理模板 → Google → Google Chrome → 内容设置配置策略找到 AutoplayAllowed 或 Allow autoplay of media设置为 Enabled 或 True可以选择 全部网站允许 或 只允许指定域名AutoplayAllowlist应用并重启浏览器方法 B注册表直接配置无需组策略打开注册表编辑器Win R → 输入 regedit → Enter定位注册表路径Edge:HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\EdgeChrome:HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome如果路径不存在可以手动创建。添加 DWORD 值名称AutoplayAllowed类型DWORD (32-bit)值1 表示允许有声自动播放重启浏览器网页视频即可在无人交互下播放声音。源码BgMuic.vueBgMusic :music-dataaudioUrls/template!-- 背景音乐组件视觉隐藏仅提供音频功能 --divstyledisplay: noneaudiorefaudioRef:srcmusicUrllooppreloadautocanplayonCanPlayerroronError//div/templatescript setuplangtsimport{ref, watch, onMounted, onBeforeUnmount,typePropType}fromvueconst propsdefineProps({// 由父组件传入的背景音乐地址 musicData:{type: String as PropTypestring|null, default: null}})const audioRefrefHTMLAudioElement|null(null)const musicUrlref()letplayAttemptTimer: ReturnTypetypeof setInterval|nullnullletuserInteractedfalse/** 父组件数据变化时自动同步 */ watch(()props.musicData,(val){if(typeof valstring){musicUrl.valueval}},{immediate:true})functionplayAudio(){const audioaudioRef.valueif(!audio||!musicUrl.value)returnaudio.play().catch((){})}functiononUserInteraction(){if(userInteracted)returnuserInteractedtrueplayAudio()if(playAttemptTimer){clearInterval(playAttemptTimer)playAttemptTimernull}}functiononCanPlay(){if(userInteracted){playAudio()return}const audioaudioRef.valueif(!audio)returnaudio.play().catch((){if(!playAttemptTimer){playAttemptTimersetInterval((){if(userInteracted){playAudio()if(playAttemptTimer){clearInterval(playAttemptTimer)playAttemptTimernull}}},1000)}})}functiononError(err: Event){console.warn([BgMusic] 音频加载失败:, err)}/** WebSocket 推送更新 */functionupdateMusic(url: string){if(typeof urlstring){musicUrl.valueurl}}onMounted((){document.addEventListener(click, onUserInteraction,{once:true})document.addEventListener(touchstart, onUserInteraction,{once:true})})onBeforeUnmount((){if(playAttemptTimer){clearInterval(playAttemptTimer)playAttemptTimernull}document.removeEventListener(click, onUserInteraction)document.removeEventListener(touchstart, onUserInteraction)})defineExpose({updateMusic})/scriptMediaSwiper.vue视频如果想要静音播放添加参数mutedMediaSwiper :media-datamiddleBannerUrlsclasscarousel/template!-- 顶部轮播区域 --divclasscarousel-wrapperdivclasscarousel-innerSwiperrefswiperRef:modulesmodules:looptrue:allow-touch-movefalse:autoplay{ delay: CAROUSEL_INTERVAL, disableOnInteraction: false, pauseOnMouseEnter: false }slide-changeonSlideChangeclasscarousel-swiperSwiperSlide v-for(item, index) in mediaList:keyindex!-- 视频 --video v-ifitem.type video:srcitem.url:refel setVideoRef(index, el)playsinlinepreloadautoclasscarousel-mediaplayingonVideoPlayingendedonVideoEnded/!-- 图片 --img v-else :srcitem.url:altitem.alt || 轮播图片classcarousel-media//SwiperSlide/Swiper/div/div/templatescript setuplangtsimport{ref, watch, onBeforeUnmount}fromvueimport{Swiper, SwiperSlide}fromswiper/vueimporttype{Swiper as SwiperClass}fromswiper/typesimport{Autoplay, Pagination}fromswiper/modules// ts-expect-error swiper ships CSS files withouttypedeclarationsimportswiper/css// ts-expect-error swiper ships CSS files withouttypedeclarationsimportswiper/css/paginationimport{CAROUSEL_INTERVAL}from../../../utils/constantsinterface MediaItem{type:video|imageurl: string alt?: string}const propsdefineProps({// 由父组件传入的轮播列表数据 mediaData:{type: Array as()(string|MediaItem)[]|null, default: null}})const swiperRefref{$el:{swiper: SwiperClass}}|null(null)const mediaListrefMediaItem[]([])const videoRefsrefRecordnumber, HTMLVideoElement|null({})letisVideoPlayingfalse// Autoplay 模块驱动图片轮播视频播放时暂停 autoplay const modules[Autoplay, Pagination]/** 将 URL 字符串标准化为{type, url}对象 */functionnormalizeItem(item: string|MediaItem): MediaItem{if(typeof itemstring){const pathitem.split(?)[0].split(#)[0]const isVideo/\.(mp4|webm|ogg|mov)$/i.test(path)return{type: isVideo ?video:image, url: item}}if(!item.type){const pathitem.url.split(?)[0].split(#)[0]const isVideo/\.(mp4|webm|ogg|mov)$/i.test(path)item.typeisVideo ?video:image}returnitem}/** 父组件数据变化时自动同步 */watch(()props.mediaData,(val){ if(Array.isArray(val)){mediaList.valueval.map(normalizeItem)}},{immediate:true})/** 获取 Swiper 实例 */functiongetSwiper(): SwiperClass|null{returnswiperRef.value?.$el?.swiper ?? null}/** 启停 autoplay */functionstopAutoplay(){getSwiper()?.autoplay?.stop()}functionstartAutoplay(){getSwiper()?.autoplay?.start()}/** 存储视频 DOM 引用 */functionsetVideoRef(index: number, el: any){if(el){videoRefs.value[index]el as HTMLVideoElement}}/** Slide 切换时的处理 */functiononSlideChange(swiper: SwiperClass){const currentIndexswiper.realIndex const itemmediaList.value[currentIndex]Object.values(videoRefs.value).forEach(video{if(video!video.paused)video.pause()})if(itemitem.typevideo){stopAutoplay()isVideoPlayingtrueconst videoElvideoRefs.value[currentIndex]if(videoEl){videoEl.currentTime0videoEl.play().catch((){ isVideoPlayingfalse startAutoplay()})} else { isVideoPlayingfalse startAutoplay()} } else { isVideoPlayingfalse startAutoplay()} } function onVideoPlaying(){//autoplay 已在 onSlideChange 中停止 } function onVideoEnded(){ isVideoPlayingfalse const swipergetSwiper()if(swiper){ swiper.slideNext()} startAutoplay()}/**外部WebSocket推送更新*/function updateMediaList(data:(string|MediaItem)[]){ if(Array.isArray(data)){mediaList.valuedata.map(normalizeItem)}}onBeforeUnmount((){stopAutoplay()})defineExpose({updateMediaList})/scriptstyle scoped.carousel-wrapper{width:100%;height: 600px;overflow: hidden;position: relative;display: flex;justify-content: center;align-items: center;}.carousel-inner{width: 956px;height: 543px;border-radius: 4px;overflow: hidden;}.carousel-swiper{width:100%;height:100%;}.carousel-media{width:100%;height:100%;object-fit: cover;display: block;}/style