音乐播放器

项目效果

在这里插入图片描述

音乐播放器的简述

“背景图片”

图片 首先运用canvas的方式 将图片通过drawimage的方法画到canvas上,然后运用canvas的一个方法 getimagedata 获取图片每一个像素点的 rgba的像素 就是
获取data里面rgba的数据 在网上查到高斯模糊的算法 就是好像对data里的rgba的数据进行数学方式的处理 处理之后将data输出 canvas的 putImageData()
将图像数据放回画布 重新放进canvas里 然后利用canvas的todataurl方法转化成base的方式 就是这个编码的格式 来当作他背景图片的src

“移动端的兼容”

html文件里用了meta标签

<meta name="viewport" content="width=device-width initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>

背景图片考虑到不同手机的分辨率 不定宽 用 "background-size:cover; "尽可能把容器覆盖上

“页面的结构”

就是分成了两个模块,采用模块的方式写的,页面主要分为两个大的部分 一个是音频管理对象 用来管理播放器本身的一些功能就比如 播放 暂停 上一首 下一首点赞
另一个就是控制对象 controlmanager 控制对象主要是针对用户来控制播放器的一些功能和方法

“获取信息”

我们将歌曲的信息,歌曲的图片,歌曲的播放时长,作者,专辑信息之类的利用json的形式封装到后台 进行模拟后台的数据
用了ajax的方式进行获取 利用回调函数来处理以及渲染页面

音频管理对象(audioManager.js)

首先我们定义一个构造函数,在构造函数里面定义一些方法以及事件,方法:
音频播放,音频暂停,加载音频资源,修改音频当前播放时间;
渲染当前歌曲总时长,当前时长以及进度条的位置,进度条停止和开始,
歌单的渲染,歌单的隐藏和显示,
渲染当前歌曲信息,背景图片,播放按钮样式
事件:ended,当当前音频播放结束触发ended事件,触发点击下一首事件

音频控制播放器

绑定了一些点击事件以及拖拽事件,
点击事件:点击上一首后下一首按钮时,修改当前歌曲索引值,加载歌单资源,修改当前页面样式;
点击播放/暂停按钮,触发音频播放/暂停,渲染播放按钮样式;
点击显示歌单按钮,渲染歌单,点击关闭,将歌单隐藏;
点击歌单中某一首歌曲的时候,重新渲染歌单,加载歌曲资源,歌曲播放
拖拽事件:
在body上面绑定touchstart,touchmove,touchend事件,在touchstart事件触发的时候,进度条的动画停止,记录停止的时间;
touchmove,计算拖拽的百分比 更新我们的当前时间和进度条;
touchend,修改音频当前播放时间继续播放,开启进度条动画

进度条

由两个div实现,通过定位,将后一个div放在前一个div上,最开始上面的div设置为translate(-100%),然后根据当前时长,歌曲总时长,计算百分比,修改translate的值,实现进度条动画,动画效果是通过requestAnimitionFrame实现的,判断进度条的百分比是否达到1,没有则继续渲染,有则触发点击下一首事件

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>Document</title>
    <link rel="stylesheet" href="../css/index.css">
</head>
<body>
    <div class="wrapper">
        <div class="song-img">
            <div class="img-wrapper">
                <img src="" alt="">
            </div>
        </div>
        <div class="song-info">
            
        </div>
        <div class="pro">
            <div class="cur-time">00:00</div>
            <div class="pro-wrapper">
                <div class="pro-bottom"></div>
                <div class="pro-top">
                    <div class="slider-point"></div>
                </div>
            </div>
            <div class="all-time">04:10</div>

        </div>
        <div class="control">
            <div class="btn-wrapper like-btn"></div>
            <div class="btn-wrapper prev-btn"></div>
            <div class="btn-wrapper play-btn"></div>
            <div class="btn-wrapper next-btn"></div>
            <div class="btn-wrapper list-btn"></div>
        </div>
    </div>
    <script src = "../js/zepto.min.js"></script>
    <script src = "../js/gaussBlur.js"></script>
    <script src = "../js/render.js"></script>
    <script src = "../js/controlManager.js"></script>
    <script src = "../js/audioManager.js"></script>
    <script src = "../js/processor.js"></script>
    <script src = "../js/playlist.js"></script>
    <script src = "../js/index.js"></script>
</body>
</html>

gulp.js

var gulp = require("gulp");
var imagemin = require("gulp-imagemin")           //图片压缩
var htmlclean = require("gulp-htmlclean");         //去掉html空格,进行压缩
var uglify = require("gulp-uglify");               //js压缩
var stripDebug = require("gulp-strip-debug");       //去掉调试语句
var concat = require("gulp-concat");               //没使用
var deporder = require("gulp-deporder");           //没使用
var less = require("gulp-less");                     //将less转成css
var postcss = require("gulp-postcss");              //cssnano,autoprefixer的依赖
var autoprefixer = require("autoprefixer");          //兼容css3属性,添加前缀
var cssnano = require("cssnano");                    //css代码压缩
var connect = require("gulp-connect");       



var folder = {
    src : "src/",
    dist : "dist/"
}

var devMode = process.env.NODE_ENV !== "production";

//流操作 task running
gulp.task("html",function(){
    var page =  gulp.src(folder.src + "html/index.html")
                    .pipe(connect.reload());
    if(!devMode){
        page.pipe(htmlclean());    
    }
    page.pipe(gulp.dest(folder.dist + "html/"))
})

gulp.task("images",function(){
    gulp.src(folder.src + "images/*")
        .pipe(imagemin())
        .pipe(gulp.dest(folder.dist+"images/"))
})
gulp.task("js",function(){
    var js = gulp.src(folder.src+"js/*")
            .pipe(connect.reload());
    if(!devMode){                         //开发模式不压缩,生产模式压缩
        js.pipe(uglify())                    //代码压缩
        .pipe(stripDebug())            //去掉调试语句
    }   
    js.pipe(gulp.dest(folder.dist+"js/"))
})
gulp.task("css",function(){
    var css = gulp.src(folder.src+"css/*")
                .pipe(connect.reload())
                .pipe(less());                  //将less转成css
    var options = [autoprefixer()];     //添加前缀,css3一些属性需要兼容,添加前缀
    if(!devMode){
        options.push(cssnano())            //css代码压缩
    }
        
    css.pipe(postcss(options))
    .pipe(gulp.dest(folder.dist + "css/"))
})
gulp.task("watch",function(){
    gulp.watch(folder.src + "html/*",["html"]);
    gulp.watch(folder.src + "images/*",["images"]);
    gulp.watch(folder.src + "js/*",["js"]);
    gulp.watch(folder.src + "css/*",["css"]);
})
gulp.task("server",function(){
    connect.server({
        port : "8081",
        livereload : true
    });
})

gulp.task("default",["html","images","js","css","watch","server"]);

index.js

获取数据,绑定点击事件,绑定touch事件

var $ = window.Zepto;
var root = window.player;
var $scope = $(document.body);   
var songList;             //存获取来的data
var controlmanager;        //创建的改变索引值的实例
var audio = new root.audioManager();   //创建一个音频实例
function bindClick(){
    $scope.on("play:change",function(event,index,flag){
        audio.setAudioSource(songList[index].audio);
        if(audio.status == "play"||flag){      //如果当前的状态是播放的话,加载完新的音频资源,播放
            audio.play();             
            root.processor.start();           //
        }
        root.processor.renderAllTime(songList[index].duration)   //渲染歌曲时间
        root.render(songList[index]);             //渲染页面
        root.processor.updata(0);                 //渲染curtime,以及进度条的位置
    })
    //移动端click有300ms延迟
    $scope.on("click",".prev-btn",function(){
        var index = controlmanager.prev();
        $scope.trigger("play:change",index);   //渲染整个页面
    })
    $scope.on("click",".next-btn",function(){
        var index = controlmanager.next();
        $scope.trigger("play:change",index);
    })
    $scope.on("click",".play-btn",function(){
        if(audio.status == "play"){
            audio.pause();
            root.processor.stop();
        }else{
            root.processor.start();
            audio.play();
        }
        $(this).toggleClass("playing");  //改变play-btn的样式
    })
    $scope.on("click",".list-btn",function(){
        root.playList.show(controlmanager);
    })
}
function bindTouch(){
    var $slidePoint = $scope.find(".slider-point");
    var offset = $scope.find(".pro-wrapper").offset();
    var left = offset.left;
    var width = offset.width;
    //绑定拖拽事件 开始拖拽 : 取消进度条渲染
    $slidePoint.on("touchstart",function(){ 
        root.processor.stop();
    }).on("touchmove",function(e){
        //计算拖拽的百分比 更新我们的当前时间和进度条
        var x = e.changedTouches[0].clientX;
        var percent = (x - left) / width;
        if(percent > 1 || percent < 0){
            percent = 0;
        }
        root.processor.updata(percent)
    }).on("touchend",function(e){
        //计算百分百 跳转播放 重新开始进度条渲染 
        var x = e.changedTouches[0].clientX;
        var percent = (x - left) / width;
        if(percent > 1 || percent < 0){
            percent = 0;
        }
        var curDuration = songList[controlmanager.index].duration;
        var curTime = curDuration * percent;
        audio.jumpToplay(curTime);
        root.processor.start(percent);  
        $scope.find(".play-btn").addClass("playing");
    })
}
function getData(url){           //请求数据,请求到数据之后,进行img渲染,imgInfo,背景图片,时间
    $.ajax({
        type : "GET",
        url : url,
        success : function(data){
            bindClick();
            bindTouch();
            root.playList.renderList(data);
            controlmanager = new root.controlManager(data.length);
            songList = data;
            $scope.trigger("play:change",0);
            
        },
        error : function(){
            console.log("error")
        }
    })
}

getData("../mock/data.json")

gaussBlur.js 高斯函数

渲染背景图皮

/* requires:
zepto.min.js
*/
(function ($, root) {
    'use strict';
    
    function gaussBlur(imgData) {
        var pixes = imgData.data;
        var width = imgData.width;
        var height = imgData.height;
        var gaussMatrix = [],
            gaussSum = 0,
            x, y,
            r, g, b, a,
            i, j, k, len;

        var radius = 10;
        var sigma = 5;

        a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
        b = -1 / (2 * sigma * sigma);
        //生成高斯矩阵
        for (i = 0, x = -radius; x <= radius; x++, i++) {
            g = a * Math.exp(b * x * x);
            gaussMatrix[i] = g;
            gaussSum += g;

        }
        //归一化, 保证高斯矩阵的值在[0,1]之间
        for (i = 0, len = gaussMatrix.length; i < len; i++) {
            gaussMatrix[i] /= gaussSum;
        }
        //x 方向一维高斯运算
        for (y = 0; y < height; y++) {
            for (x = 0; x < width; x++) {
                r = g = b = a = 0;
                gaussSum = 0;
                for (j = -radius; j <= radius; j++) {
                    k = x + j;
                    if (k >= 0 && k < width) {//确保 k 没超出 x 的范围
                        //r,g,b,a 四个一组
                        i = (y * width + k) * 4;
                        r += pixes[i] * gaussMatrix[j + radius];
                        g += pixes[i + 1] * gaussMatrix[j + radius];
                        b += pixes[i + 2] * gaussMatrix[j + radius];
                        // a += pixes[i + 3] * gaussMatrix[j];
                        gaussSum += gaussMatrix[j + radius];
                    }
                }
                i = (y * width + x) * 4;
                // 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
                // console.log(gaussSum)
                pixes[i] = r / gaussSum;
                pixes[i + 1] = g / gaussSum;
                pixes[i + 2] = b / gaussSum;
                // pixes[i + 3] = a ;
            }
        }
        //y 方向一维高斯运算
        for (x = 0; x < width; x++) {
            for (y = 0; y < height; y++) {
                r = g = b = a = 0;
                gaussSum = 0;
                for (j = -radius; j <= radius; j++) {
                    k = y + j;
                    if (k >= 0 && k < height) {//确保 k 没超出 y 的范围
                        i = (k * width + x) * 4;
                        r += pixes[i] * gaussMatrix[j + radius];
                        g += pixes[i + 1] * gaussMatrix[j + radius];
                        b += pixes[i + 2] * gaussMatrix[j + radius];
                        // a += pixes[i + 3] * gaussMatrix[j];
                        gaussSum += gaussMatrix[j + radius];
                    }
                }
                i = (y * width + x) * 4;
                pixes[i] = r / gaussSum;
                pixes[i + 1] = g / gaussSum;
                pixes[i + 2] = b / gaussSum;
            }
        }
        //end
        return imgData;
    }

    // 模糊图片
    function blurImg(img, ele) {

        var w = img.width,
            h = img.height,
            canvasW = 40,
            canvasH = 40;

        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');

        canvas.width = canvasW;
        canvas.height = canvasH;

        ctx.drawImage(img, 0, 0, w, h, 0, 0, canvasW, canvasH);

        var pixel = ctx.getImageData(0, 0, canvasH, canvasH);

        gaussBlur(pixel);

        ctx.putImageData(pixel, 0, 0);

        var imageData = canvas.toDataURL();

        ele.css('background-image', 'url(' + imageData + ')');
    }

    root.blurImg = blurImg;

})(window.Zepto, window.player || (window.player = {}));

controlManage.js

索引值改变

(function($,root){    //控制索引
    function controlManager(len){
        this.index = 0;
        this.len = len;
    }
    controlManager.prototype = {
        prev : function(){           
            return this.getIndex(-1);
        },
        next : function(){
            return this.getIndex(1);
        },
        getIndex : function(val){
            var index = this.index;
            var len = this.len;
            var curIndex = (index + val + len) % len;
            this.index = curIndex;
            return curIndex;
        }
    }
    root.controlManager = controlManager;
})(window.Zepto,window.player || (window.player = {}));

render.js 渲染页面

渲染img,bgImg,info,喜欢的样式

(function($,root){
    var $scope = $(document.body);
    console.log(root)
    //渲染当前这首歌的信息
    function renderInfo(info){
        var html = '<div class="song-name">'+info.song+'</div>'+
        '<div class="singer-name">'+info.singer+'</div>'+
        '<div class="album-name">'+info.album+'</div>';
        $scope.find(".song-info").html(html)
    }
    //渲染当前这首歌的图片
    function renderImg(src){
        var img = new Image();
        img.onload = function(){
            root.blurImg(img,$scope);    //bgimg 高斯模糊
            $scope.find(".song-img img").attr("src",src)
        }
        img.src = src;
    }
    function renderIsLike(isLike){
        if(isLike){                     //心的样式
            $scope.find(".like-btn").addClass("liking");
        }else{
            $scope.find(".like-btn").removeClass("liking");
            
        }
    }
    root.render = function(data){
        renderInfo(data);
        renderImg(data.image);
        renderIsLike(data.isLike)
    }
})(window.Zepto,window.player || (window.player = {}))

audioManage.js

音频播放,暂停,监听音频是否ended,如果结束,触发下一首按钮的点击事件,修改当前播放音乐的位置

(function($,root){
    var $scope = $(document.body);
    function audioManager(){
        this.audio = new Audio();
        this.status = "pause";
        // this.bindEvent();
    }  
    audioManager.prototype = {
        //绑定监听歌曲是否播放完成事件,如果播放完成,触发下一首的点击事件
        bindEvent:function(){
            $(this.audio).on("ended",function(){
                $scope.find(".next-btn").trigger("click");
            }) 
        },
        play : function(){
            this.audio.play();
            this.status = "play";
        },
        pause : function(){
            this.audio.pause();
            this.status = "pause";
        },
        setAudioSource : function(src){
            this.audio.src = src;
            this.audio.load();          //将音频加载进来
        },
        jumpToplay : function(time){
            this.audio.currentTime = time;   //将音频的currentTime修改,继续播放
            this.play();

        }   
    }
    root.audioManager = audioManager;
})(window.Zepto,window.player || (window.player ={}))

processor.js

渲染歌曲的时间,当前的播放时间,将时间转化成minute:second,更新进度条位置,进度条开始走,进度条结束

(function($,root){
    var $scope = $(document.body);    //进度条渲染,拖拽
    var curDuration;
    var frameId;
    var lastPercent = 0;
    var startTime;
    //把秒转换成分和秒
    function formatTime(duration){
        duration = Math.round(duration);  //取整
        var minute = Math.floor(duration / 60);       //向下取整,分钟
        var second = duration - minute * 60;          //秒数
        if(minute < 10){
            minute = "0" + minute;
        }
        if(second < 10){
            second = "0" + second;
        }
        return minute + ":" +second;
    }
    function renderAllTime(duration){   //渲染歌曲总时长
        lastPercent = 0;
        curDuration = duration;   //记录歌曲的时长
        var allTime = formatTime(duration);
        $scope.find(".all-time").html(allTime);
    }
    function updata(precent){               //渲染歌曲当前时长,以及进度条的位置
        var curTime = precent * curDuration;      //计算歌曲当前的时长
        curTime = formatTime(curTime);            //将当前事件装成minute:second
        $scope.find(".cur-time").html(curTime);    //渲染时间
        var percentage = (precent - 1) * 100 + "%";   //控制进度条的位置
        $scope.find(".pro-top").css({
            transform : "translateX("+percentage+")"
        })
    }
    function start(precentage){
        lastPercent = precentage === undefined ? lastPercent : precentage; 
        cancelAnimationFrame(frameId);          //清理定时器
        startTime = new Date().getTime();
        function frame(){
            var curTime = new Date().getTime();
            var precent = lastPercent + (curTime - startTime) / (curDuration * 1000);   //  
            if(precent < 1){
                frameId = requestAnimationFrame(frame);      //创建定时器
                updata(precent);
            }else{                                     //表示进度条到了最右面,歌曲播放结束,触发下一首的按钮的点击事件
                cancelAnimationFrame(frameId);
                $scope.find(".next-btn").trigger("click");
            }
           
        }
        frame()
    }
    function stop(){   //进度条停止,记录当前停止的位置
        var stopTime = new Date().getTime();
        lastPercent = lastPercent + (stopTime - startTime) / (curDuration * 1000);
        cancelAnimationFrame(frameId);
    }
    root.processor = {
        renderAllTime : renderAllTime,
        start : start,
        stop : stop,
        updata : updata
    }
})(window.Zepto,window.player || (window.player));

playlist.js

渲染播放列表,添加点击事件,渲染当前播放歌曲的样式

(function($,root){              //播放列表
    var $scope = $(document.body);
    var control;
    var $playList = $("<div class = 'play-list'>"+
        "<div class='play-header'>播放列表</div>" + 
        "<ul class = 'list-wrapper'></ul>" +
        "<div class='close-btn'>关闭</div>"+
    "</div>") 
    //渲染我们的播放列表dom
    function renderList(songList){
        var html = '';
        for(var i = 0;i < songList.length;i++){
            html += "<li><h3 >"+songList[i].song+"-<span>"+songList[i].singer+"</span></h3></li>"
        }
        $playList.find("ul").html(html);
        $scope.append($playList);   
        bindEvent();
    }  
    function show(controlmanager){
        control = controlmanager;
        $playList.addClass("show");   //将歌单展示出来
        signSong(control.index);     //将index的歌曲渲染不同的样式
    }
    function bindEvent(){
        $playList.on("click",".close-btn",function(){
            $playList.removeClass("show")
        })
        $playList.find("li").on("click",function(){
            var index = $(this).index();   //获取当前点击的歌曲的索引
            signSong(index);             //渲染样式
            control.index = index;       //改变索引
            $scope.trigger("play:change",[index,true]);
            $scope.find(".play-btn").addClass("playing");
            setTimeout(function(){
                $playList.removeClass("show")
            }, 200);
        })
    }
    function signSong(index){   //给当前点击的li添加sign样式,原来的删除
        $playList.find(".sign").removeClass("sign");   
        $playList.find("ul li").eq(index).addClass("sign");
    }
    root.playList = {
        renderList : renderList,
        show : show
    }
})(window.Zepto,window.player || (window.player = {}))