需求:项目中做了一个音乐播放器,后面Boss说用户同时打开了多个页面播放音乐,这个时候每个页面都在播放音乐,能不能只让当前用户操作的页面播放音乐。
接到这个需求的时候,先去某云音乐看了一下,用的应该是WebSocket。给领导反馈一下后,领导说后端腾不出资源配合。那只能先排除这种方案了。
当时的思路是这样的:
具体是:给每一个打开的要播放的网页链接加一个唯一标识参数(uuid),点击播放的时候,获取该参数:uuid,并设置该uuid的值,作为localStorage
一个属性的key
,value
为‘play’
,并且把该uuid的值,存入一个uuidArray
数组里。那么当打开第二个页面并且播放的时候,重复上述动作,但是在localStorage
里将该页面的状态设为play
,其他页面设为pause
,每个页面监听storage
事件,如果本页面的状态值为play
,则可以播放,否则,停止播放。当关闭页面的时候,清除该localStorage
里存储的值,并且从uuidArray
数组里删除该项。
说的挺啰嗦,简言之,就是监听localStorage
的storage
事件,和页面的关闭事件,去做出不同的反应。
下面是代码,模拟该情况:
一个主页,表示页面会从这里跳往A、B、C三个页面;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>link-index</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button class="a">JUMP TO PAGE A</button>
<button class="b">JUMP TO PAGE B</button>
<button class="c">JUMP TO PAGE C</button>
<script>
$('.a').on('click', function () {
window.open('link-a.html?uuid=' + uuid(16))
})
$('.b').on('click', function () {
window.open('link-b.html?uuid=' + uuid(16))
})
$('.c').on('click', function () {
window.open('link-c.html?uuid=' + uuid(16))
})
function uuid(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [],
i;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
</script>
</body>
</html>
其中主页的文件名为:link-index.html
其他三个页面的文件名为:link-a.html
, link-b.html
, link-c.html
,内容为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!-- title名称可以相应的更换 -->
<title>link-a</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<div>
<button class="play">PLAY</button>
</div>
<script>
var key = getQueryVariable('uuid')
//设置uuids,以及key
function getUuids() {
localStorage.setItem(key, 'play')
var val = localStorage.getItem(key)
if (!localStorage.getItem('uuids')) { //如果不存在uuids,则构建数组,将该key push进去
var uuidArray = []
uuidArray.push(key)
localStorage.setItem('uuids', JSON.stringify(uuidArray))
} else {
//如果存在uuids数组,并且该key值不在数组内,则添加进uuids数组里,并重新设置localStorage里uuids的值
var uuidArray = localStorage.getItem('uuids');
var arr = JSON.parse(uuidArray);
!(arr.indexOf(key) > -1) && arr.push(key)
localStorage.setItem('uuids', JSON.stringify(arr))
console.log(localStorage.getItem('uuids'), 'uuids..');
}
}
//获取url链接上对应参数的值
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return '';
}
//设置各个页面的播放状态
function setStatus() {
let arr = JSON.parse(localStorage.getItem('uuids')), len = arr.length;
for(let i = 0; i < len; i ++) {
(arr[i] == key)? localStorage.setItem(arr[i], 'play') : localStorage.setItem(arr[i], 'pause')
}
}
//检测到本地存储发生变化,并且当前页面状态为pause的时候,停止播放音乐
//此时player为播放器对象,调用其pause()方法,可停止音乐播放
window.addEventListener('storage', function (e) {
let val = localStorage.getItem(getQueryVariable('uuid'))
(val == 'pause') && player.pause()
})
//点击事件发生,播放音乐的时候
$('.play').on('click', function(){
getUuids();
setStatus();
})
//当关闭页面的时候,从本地存储里移除对应项
window.onbeforeunload = function (event) {
var event = event || window.event;
var newArr = JSON.parse(uuidArray)
var newLocalstorageArray = newArr.filter(function(item, index){
return item != key
})
localStorage.setItem('uuids', JSON.stringify(newLocalstorageArray))
localStorage.removeItem(key)
}
</script>
</body>
</html>
注意点:上面项目,需要在服务器环境下启动页面,并且保证他们是同源的。
本文只提供处理问题的一种思路:具体知识点细节可以看下面了解。
什么是同源策略?同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。如果两个页面的协议,端口(如果有指定)和主机都相同,表示他们是同源的。
同源的问题,可以看这里:https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
关于
onstorage
事件:该事件不在导致数据变化的当前页面触发(如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 sessionStorage 或 localStorage 的数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件)
onstorage
事件,可以看这里:https://developer.mozilla.org/zh-CN/docs/Web/API/WindowEventHandlers/onstorage
关于onbeforeunload:提到onbeforeunload,不得不提到onunload,他们的区别,兼容性问题,可点击下面链接查看。
onbeforeunload事件:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/onbeforeunload
onunlaod事件: https://developer.mozilla.org/zh-CN/docs/Web/Events/unload
onbeforeunload在当前主流浏览器兼容良好,safari也支持。
想在localStorage里存储数组,怎么办?上面代码已经给出了。
至于其他方案,正在研究中,会找机会写出。
该方案存在的缺陷:
- 比如用户直接复制
link-a.html
的链接而打开新页面播放音乐,此时uuid
是重复的,按现有逻辑,此时两个页面会同时播放音乐。 - 上面逻辑在同一浏览器里不同标签页的情况下,是成立的。但是如果同时打开了两个浏览器,在每个浏览器都点播放的话,也会同时在这两个页面播放音乐。
还是觉得WebSocket这种方案好一些,但是目前项目中用的还是上述方案。
如果有更好的方案,而不用通过和后台交互,欢迎提供,再此先谢过!~