利用HTML5+Socket.io實現搖一搖控制PC端歌曲切換


我比較喜歡聽音樂,特別是周末的時候,電腦開着百度隨心聽fm,隨機播放歌曲,躺在床上享受。但碰到了一個煩人的事情,想切掉不喜歡的曲子,還得起床去操作電腦換歌。於是思考能不能用手機控制電腦切換歌曲,經過一段事件的思考,絕對采用html5+socket.io來實現這個功能。首先我把該功能的實現拆分為以下幾個步驟:

  1. 移動端前端頁面+腳本邏輯實現
  2. PC端前端頁面+腳本邏輯實現
  3. 后台邏輯實現
  4. 加入socket.io實現長連接
  5. 實現移動端控制PC端邏輯

下文中的代碼有不全的地方,大家可以查看我的Github項目源碼https://github.com/zhu495472831/shake-music

1、移動端頁面腳本的實現

html頁面編寫
仿造微信搖一搖的頁面,實現一個類似的界面,如下所示:
移動端界面
當我們搖手機的時候,會做一個動畫,中間的圖案一分為二,上半部向上運動然后回來,下半部亦同理,如下所示:
移動端界面
html代碼(shake.html):

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
	<title>搖一搖切歌</title>
	<link rel="stylesheet" href="shake.css">
</head>
<body>
	<div class="wrap" id="wrap">
		<div class="inner"></div>
		<div class="above-hand hand" id="up"></div>
		<div class="below-hand hand" id="bt"></div>
	</div>
	<div class="tip" id="tip">
		
	</div>
	<div style="display: none;">
		<audio id="shaking" src="new_silent.mp3"></audio>
		<audio id="found" src="new_silent.mp3"></audio>
	</div>
	<script type="text/javascript" src="socket.io.js"></script>
	<script src="shake.js"></script>
</body>
</html>

樣式表(shake.css):

html,body{ width:100%; height:100%; background-color: #000; margin:0; overflow: hidden;}
.wrap{ position: absolute; left:50%; top:50%; width:132px; height: 132px; -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%);  }
.hand{ position: absolute; left:0;  width:100%; height: 50%;background: url(shake.png) no-repeat #000; background-size: 132px 132px; }
.above-hand{ top:0; background-position: 0 0; }
.below-hand{ bottom:0; background-position: 0 -66px;  }
.inner{ position:absolute; top:50%; left:50%; width: 50px; height: 90px; background: url(inner.png) no-repeat 0 0;background-size: 50px 90px; -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%); }
.above-hand:after,.below-hand:before{ display: none; content:''; position:absolute; left:-100vw; width:200vw; height: 2px; background-color: #BABDC1;  }
.above-hand:after{ bottom:0;  }
.below-hand:before{ top:0;  }
.wrap.active .above-hand{  -webkit-animation: up 1.5s ease; animation: up 1s ease; }
.wrap.active .below-hand{ -webkit-animation: down 1.5s ease; animation: down 1s ease; }
.wrap.active .above-hand:after,.wrap.active .below-hand:before{ display: block; }
.tip{ position: absolute; bottom: 30px; left: 10px; color: #fff; font-family: '楷體'; text-align: center; right: 10px; height: 32px; line-height: 32px; background-color: rgba(255,255,255,.4); border-radius: 3px; } 
.tip.active{  -webkit-animation: jump 1.5s linear; animation: jump 1s linear; }
//動畫定義見github

腳本邏輯
接下來是移動端JS腳本邏輯的實現,搖一搖的實現需借助html5新增的devicemotion事件,獲取設備在位置和方向上的改變速度的相關信息,該事件的基本使用如下:

if(window.DeviceMotionEvent){
	window.addEventListener('devicemotion',handler,!1);
}else{
	alert('你的瀏覽器不支持搖一搖功能.');
}

devicemotion事件對象中有一個accelerationIncludingGravity屬性,該屬性包括:一個包含x、y 和z 屬性的對象,在考慮z 軸自然重力加速度的情況下,告訴你在每個方向上的加速度。該API的具體使用大家可以參考網上的資料,非常多,這里就不重復了。
搖一搖的具體邏輯如下:

function handler(e){
	var current = e.accelerationIncludingGravity;
	var currentTime;
	var timeDifference;
	var deltaX = 0;
	var deltaY = 0;
	var deltaZ = 0;
    //記錄上一次設備在x,y,z方向上的加速度
	if ((lastX === null) && (lastY === null) && (lastZ === null)) {
		lastX = current.x;
		lastY = current.y;
		lastZ = current.z;
		return;
	}
	//得到兩次移動各個方向上的加速度絕對差距
	deltaX = Math.abs(lastX - current.x);
	deltaY = Math.abs(lastY - current.y);
	deltaZ = Math.abs(lastZ - current.z);
    //當差距大於設定的閥值並且時間間隔大於指定閥值時,觸發搖一搖邏輯
	if (((deltaX > threshold) && (deltaY > threshold)) || ((deltaX > threshold) && (deltaZ > threshold)) || ((deltaY > threshold) && (deltaZ > threshold))) {
		currentTime = new Date();
		timeDifference = currentTime.getTime() - lastTime.getTime();
		if (timeDifference > timeout) {
			dealShake();
			lastTime = new Date();
		}
	}

	lastX = current.x;
	lastY = current.y;
	lastZ = current.z;
}

由於搖一搖需要播放搖一搖的聲音以及切換歌曲成功后的聲音,但由於手機大部分是禁止音頻的自動播放,必須需要用戶真實點擊才能播放音頻。這里沒有徹底的解決辦法,只是換了一個思路,利用用戶隨時觸摸屏幕的習慣,對document進行touchstart事件監聽。當用戶觸摸到屏幕時,先播放一個1S的無聲音頻,接着將touchstart事件移除,然后搖一搖的時候切換聲音源,播放搖一搖的聲音,這樣便可以達到類似的目的。代碼如下所示:

document.addEventListener('touchstart',autoplay,!1);
function autoplay(){
	shaking.play();
	found.play();
	document.removeEventListener('touchstart',autoplay);
}

2、PC端前端頁面腳本邏輯實現

html文檔
PC端界面也是仿的網上的一個html5音樂播放器的界面,效果如下所示:

HTML(shake_pc.html)布局代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>隨心聽</title>
	<meta name="referrer" content="never">
	<link rel="stylesheet" href="reset2.0.css">
	<link rel="stylesheet" href="shake_pc.css">
</head>
<body>
    <!-- 控制背景圖效果 -->
	<div class="bg" id="bg">
	</div>
	<div class="music-player">
	     <!-- 歌曲信息 -->
		<div class="info">
			<div class="song-name" id="songName"></div>
			<div class="author" id="author">By <span></span></div>
			 <!-- 播放進度 -->
			<div class="progress" id="progress"></div>
		</div>
		 <!-- 歌曲控制 -->
		<div class="controls">
			<div class="time" id="time">00:00</div>
			<div class="play-controls">
				<a href="javascript:;" class="prev btn" id="prev">
				</a><a href="javascript:;" class="play btn" id="play">
				</a><a href="javascript:;" class="next btn" id="next"></a>
			</div>
			<div class="volume-bar">
				<a href="javascript:;" class="vol-muted" id="muted">
				</a><a href="javascript:;" class="vol-slider" id="volSilder">
					<span class="vol-slider-inner" id="volInner"></span>
				</a><a href="javascript:;" class="vol-max" id="volMax"></a>
			</div>
		</div>
		 <!-- 歌曲播放 -->
		<audio id="audio" controls style="display: none;"></audio>
	</div>
	<script type="text/javascript" src="socket.io.js"></script>
	<script type="text/javascript" src="shake_pc.js"></script>
</body>
</html>

css樣式
樣式布局非常的簡單,沒什么好講的。CSS樣式代碼(shake_pc.css)如下:

body{ font-family: 'Open Sans', sans-serif;  overflow: hidden;  }
.bg{ position: absolute; left:0; right: 0;top:0; bottom: 0;margin:-30px; filter: blur(30px);  -webkit-filter: blur(30px); background: url(./imgaes/bg.jpg) no-repeat; background-size: cover;}
.music-player{  position: relative; width: 350px; height: 290px; margin: 150px auto; box-shadow: 0 0 60px rgba(0, 0, 0, 0.8); border-radius: 7px; background: #222; overflow: hidden; z-index: 0; }
.info{ position: relative; width: 100%; height: 80px; padding-top: 20px; color:#585858; text-align: center; }
.info .song-name{ height: 30px; font-size: 30px; font-weight: 300; }
.info .author{ margin-top: 14px; font-size: 14px; }
.progress{ position: absolute; left:0; bottom:0; width: 0; height: 3px; background-color: #ed553b; }
.controls{ height: 190px; background-color:rgba(152, 46, 75, 0.6); text-align: center; }
.controls .time{ font-size:48px; height: 84px; line-height: 84px; color:rgba(225, 225, 225, 0.4); }
.play-controls .btn{ display: inline-block; width:95px; height: 40px; vertical-align: top; }
.play-controls .btn.prev{ background:url(./imgaes/prev.png) no-repeat center center; }
.play-controls .btn.play{ background:url(./imgaes/play.png) no-repeat center center; }
.play-controls .btn.next{ background:url(./imgaes/next.png) no-repeat center center; }
.volume-bar{ position: relative; width:250px; height: 2px; margin: 30px auto 0; }
.volume-bar .vol-muted{ position:absolute; left:0; top:-6px; width: 10px; height:13px;background:url(./imgaes/muted.png) no-repeat center center;  }
.volume-bar .vol-slider{  position: absolute; left:14px; right:28px; top:0; height:100%; background-color:rgba(255,255,255,.3);  }
.volume-bar .vol-slider-inner{ display: block; width:50%; height: 100%; background-color:#ed553b; }
.volume-bar .vol-max{ position:absolute; right:0; top:-8.5px; width: 22px; height: 18px; background: url(./imgaes/max.png) no-repeat center center;}

腳本邏輯
文檔加載完成后,隨機獲取一首音樂,然后播放。點擊上一曲或下一曲都是隨機切換歌曲,以及可以對音量進行控制,有興趣的朋友還可以自行實現歌詞的同步播放。有關html5媒體原生API,大家可以參考HTML5的Video標簽的屬性,方法和事件匯總
部分代碼如下:

var mediaEvts = ['loadedmetadata','ended','timeupdate','error'];
//隨機獲取一首音樂
function getSong(){
	return new Promise(function(resolve,reject){
		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function() {
			if (xhr.readyState === 4) {
				if (xhr.status === 200) {
					resolve(xhr.responseText);
				}else{
					reject('發生錯誤');
				}
			}
		};
		xhr.open('get', 'http://api.jirengu.com/fm/getSong.php?channel=1', !0);
		xhr.send();
	});
}
function dealSong(responseText){
	var songObj = JSON.parse(responseText),
		song = songObj.song[0];
	updateUI(song);
	setMedia(song);
	return song;
}

function setMedia(song){
	var songSrc = song.url,
		lrc = song.lrc;
	player.src = songSrc;
	player.volume = 0.5;
	player.play();
}

function updateUI(song){
	var name = song.title,
		artist = song.artist,
		img = song.picture;
	songName.innerText = name;
	author.querySelector('span').innerText = artist;
	bg.style.backgroundImage = 'url('+img+')';
}
//初始化audio元素事件監聽
function initMediaEvents(player){
	mediaEvts.forEach(function(evt,idx,arr){
		var cb = evt+'CB';
		player.addEventListener(evt,window[cb],!1);
	});
}

3、后台實現

使用express+socket.io實現長連接,socket.io可以利用npm進行安裝。根據UA實現PC+移動端渲染不同的頁面,將移動端的發送的命令廣播給PC端,然后達到移動端控制PC的效果,代碼如下所示:

const http = require('http');
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.use(express.static('./'));

app.get('/',(req,res)=>{
	var userAgent = req.headers['user-agent'].toLowerCase();
	if(/(android|iphone|mobile)/.test(userAgent)){
		res.sendFile(__dirname+'/shake_m.html');
	}else{
		res.sendFile(__dirname+'/shake_pc.html');
	}
});

io.on('connection',function(socket){
	var usrname = '',
		sendData = {};
	console.log('a client connect...'+socket.id);
	socket.on('disconnect',function(){
		console.log(`設備${socket.id}斷開連接.`);
	});

	socket.on('message',function(data){
		var cmd = data.cmd;
		//next命令是由移動端發送,OK命令是由PC切歌成功后發送的命令
		if(cmd == 'next'){
			socket.broadcast.emit('next');
		}else if(cmd == 'ok'){
			socket.broadcast.emit('ok',data.data);
		}
	});
});
server.listen(3000,function(){
	console.log('start listening on 3000 port...');
});

4、移動端和PC端加上socket.io

首先在頁面中引入socket.io.js,然后連接socket服務器,接着監聽事件即可,如下所示:

//移動端socket邏輯
socket.on('connect',function(){
	console.log('websocket連接已建立...');
});

socket.on('ok',function(data){
	if(found.src!=host+'found.mp3'){
		found.src = 'found.mp3';
	}
	found.play();
	tip.innerText = '正在欣賞:'+data.artist+'--'+data.title;
	tip.classList.remove('active');
	tip.offsetWidth = tip.offsetWidth;
	tip.classList.add('active');
});
function dealShake(){
	if(isShaking) return;
	isShaking = !0;
	if(shaking.src!=host+'shaking.mp3'){
		shaking.src = 'shaking.mp3';
	}
	shaking.play();
	wrap.classList.add('active');
	setTimeout(function(){
		socket.emit('message',{cmd:'next',data:null});
	},1000);
	
}
//PC端socket邏輯
function initIOEvts(){
	socket.on('connect',function(){
		console.log('websocket連接已建立...');
	});

	socket.on('next',function(data){
		getSong().then(function(val){
			var song = dealSong(val);
			socket.emit('message',{cmd:'ok',data:song});
		},function(err){
			console.log(err);
		});
	});
}

當用戶搖動設備觸發搖一搖時,發送一個next命令的消息給服務端,然后服務端將該消息轉發給PC端,PC端接收到該消息后,執行歌曲切換操作,並反饋一個ok命令消息並攜帶歌曲消息給服務端,服務端再將該消息轉發回移動端,移動端播放切歌成功的聲音並顯示當前PC播放的歌曲。

這個功能主要是我自己使用,可能有些細節沒有進行處理,大家可以在該基礎上進行改造,還可以做一些多屏互動的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM