同源跨窗口通信:网易云音乐不同标签页打开同一页面,暂停原先标签页音频播放

原文见:语雀

有个声音很好听的小帅哥问我说,如果当前浏览器打开一个标签页,页面播放着音乐,然后相同的链接又在另外一个页面打开,该如何将之前的页面音频停止播放。
有小帅哥问问题,我当然要回答啦(其实是我不得不答)🤣
我一想,这是跨窗口通信,用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:发现暂时还木有搜到其他的~~~🤣

    原文作者:aSuncat
    原文地址: https://blog.csdn.net/aSuncat/article/details/123746693
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞