zoukankan      html  css  js  c++  java
  • 利用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{ 100%; height:100%; background-color: #000; margin:0; overflow: hidden;}
    .wrap{ position: absolute; left:50%; top:50%; 132px; height: 132px; -webkit-transform: translate(-50%,-50%); transform: translate(-50%,-50%);  }
    .hand{ position: absolute; left:0;  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%;  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; 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;  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;  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;  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; 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; 250px; height: 2px; margin: 30px auto 0; }
    .volume-bar .vol-muted{ position:absolute; left:0; top:-6px;  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; 50%; height: 100%; background-color:#ed553b; }
    .volume-bar .vol-max{ position:absolute; right:0; top:-8.5px;  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播放的歌曲。

    这个功能主要是我自己使用,可能有些细节没有进行处理,大家可以在该基础上进行改造,还可以做一些多屏互动的效果。

  • 相关阅读:
    流式布局思想 js函数的几种简写方式 面向对象 js vue引入bootstrap和jQuery环境
    04--CBV源码分析 Django的settings源码分析 模板层
    C#使用委托和事件来重写串口的接收数据方法DataReceived方法完成数据的接收处理
    自定义控件的封装、常用的鼠标事件的重载、定时器的使用、event事件以及事件过滤器、文件操作以及文本流和数据流的使用
    QT的优点、项目文件目录、main函数、QpushButton、对象树、Qt中坐标系、Qt中信号和槽、自定义信号和槽、信号和槽的拓展、Qt4版本中的信号和槽的缺点、Lambda表达式
    Stylet框架显示自定义界面的步骤
    MahApps.Metro使用
    async和await的使用
    第一个WPF程序(串口调试)
    结构、类、属性:以及面向对象的各种特性继承、封装、多态
  • 原文地址:https://www.cnblogs.com/libin-1/p/6279571.html
Copyright © 2011-2022 走看看