原文见:语雀
有个声音很好听的小帅哥问我说,如果当前浏览器打开一个标签页,页面播放着音乐,然后相同的链接又在另外一个页面打开,该如何将之前的页面音频停止播放。
有小帅哥问问题,我当然要回答啦(其实是我不得不答)🤣
我一想,这是跨窗口通信,用postMessage,不过我忘了这个api具体怎么使用了。小帅哥提醒说,postMessage通信时,无法获取到原先标签页的window,所以无法改变原先的window内容。
最后,我想到的解决方案是localStorage存储播放状态,并将状态发送给后端,然后websockert保持前后端通信,在打开新标签页的时候,原先标签页监听到websocket的消息,暂停音乐播放。
虽然知道这样占用带宽,不是好的解决方案,但是一时也想不到什么好的方案。
我觉得这属于同源跨窗口通信类型的技术问题。
有空就在网上搜搜搜了起来,并结合自己以前的知识整理,发现有以下方案可以实现
● websocket
● 监听localStorage的storage事件(推荐)
● Broadcast channel (兼容性不佳)
websocket
不仅能跨窗口,还能跨浏览器,兼容性最佳,到那时需要消耗浏览器资源。
localStorage的storage事件
storage事件会在数据更新时触发,并且会在所有可访问到存储对象的window对象上触发,导致当前数据改变的window对象除外。
// a标签页,原先标签页
window.onstorage = event => { // 同window.addEventListener('storage', () => {})
if (event.key != 'changeVedioStatus') return;
console.log('音频在另一页面打开了')
}
// b标签,新打开的标签页
localStorage.setItem('changeVedioStatus', Date.now());
回想了一些,我想到了localStorage状态,但是却因为不记得storage事件,所以没有想到了好的解决方案。其实在小帅哥的提醒下,我应该从localStorage的角度出发考虑问题,可以试着想“是否有事件能实时监听到storage的改变”
Broadcast channel API
这个api的功能更全,但是浏览器支持情况不好,有些库基于localStorage来polyfill该API
// a标签页
const bc = new BroadcastChannel('changeVedioStatus'); // 连接到广播频道
bc.postMessage(Date.now()); // 一定要postMessage过去,不然不会监听到onmessage,就不能触发onmessage里的事件
bc.onmessage = (ev) => {
if (ev.currentTarget != 'changeVedioStatus') return;
alert(event.data); // 我打开啦
console.log(ev)
}
写到这里,我突然发现,浏览器刷新时会触发bc.onmessage事件,如果是切换标签页,并不会切换,实际上,我觉得如果切换标签页,也应该让当前页面播放,原先页面暂停播放。就想到了切换标签页的document.addEventListener(‘visibilitychange’, () => {})
代码主要逻辑如下,大致实现了标签切换,播放状态自动切换
const broadcast = () => {
const bc = new BroadcastChannel('changeVedioStatus'); // 连接到广播频道
const audio = audioRef.current;
document.addEventListener('visibilitychange', () => {
// a标签页发送消息
bc.postMessage(Date.now());
// b标签页接收到消息,关闭音频
bc.onmessage = (ev) => {
if (document.visibilityState !== 'hidden') return;
// b标签页
if (ev?.currentTarget?.name !== 'changeVedioStatus') return;
audio?.pause();
console.log(`%c 我关闭了:::`, 'background-color: red;font-size:14px;');
};
// a标签页播放
if (document.visibilityState === 'visible') {
audio?.play();
console.log(`%c 我打开了:::`, 'background-color: pink;font-size:14px;');
}
});
return () => {
bc.close();
};
};
useEffect(() => {
broadcast();
}, []);
// 音频
<audio controls ref={audioRef}>
<source src="horse.ogg" type="audio/ogg" />
<source src="http://img0.xinmin.cn/2021/11/25/20211125150754275040.mp3" type="audio/mpeg" />
您的浏览器不支持 audio 元素。
</audio>
postMessage(不可行)
因为曾想到postMessage,所以我自己又尝试了一下是否可以实现,发现这个api,同源的origin是一样的,所以无法区别两个窗口,也就是a标签页用了window.postMessage,想发消息给b标签页,但是因为origin是相同的,根本判断不出来是是想要发给b的,只要在b中执行就行了。因为代码是同一个页面,所以a发出的消息,a自己也会执行,
// a想发消息给b
window.postMessage('changeVedioStatus', '*');
window.addEventListener('message', (event) => {
// 无法判断event里的消息,a是否需要执行,所以a、b标签都会执行这个方法
if (document.visibilityState !== 'hidden') return;
if (event.data !== 'changeVedioStatus') return;
audio?.pause(); // 本想让b停止播放,结果a自己也停止播放了
});
想到当时小帅哥说,网易云音乐其实在localStorage出现之前,就已经实现了这项技术,所以我觉得,应该还是会有其他方案的。
经过我坚持不懈的努力,发现…
=> 20220325-aSuncat:发现暂时还木有搜到其他的~~~🤣