lkmusic项目改进版本之WebAudio版本支持音乐可视化 已更新至github 欢迎下载

亲们如果觉得好请fork我的github

lkmusic 效果预览图(2016-4-26日已经改进)

更新:

优化界面效果

修复播放完成后不会自动切换处于暂停状态的bug

已经上传至github(敬请关注)

GitHub 项目仓库地址(欢迎访问):

https://github.com/laikedou/LMusic.git

目录结构:

index.html

<!DOCTYPE html>
<!--对离线存储进行支持-->
<html lang="zh-cmn-Hans"  >
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="renderer" content="webkit">
    <meta name="version" content="1.0.0">
    <link rel="stylesheet" href="http://data.smohan.net/static/css/demo.css">
    <link rel="stylesheet" href="static/css/smusic.css"/>
    <title>HTML5音乐播放器LKMusic</title>
</head>
<body>
<div class="grid-music-container f-usn">
    <div class="m-music-play-wrap">
        <div class="u-cover"></div>
        <div class="m-now-info">
            <h1 class="u-music-title"><strong>标题</strong><small>歌手</small></h1>
            <div class="m-now-controls">
                <div class="u-control u-process">
                    <span class="buffer-process"></span>
                    <span class="current-process"></span>
                </div>
                <div class="u-control u-time">00:00/00:00</div>
                <div class="u-control u-volume">
                    <div class="volume-process" data-volume="0.50">
                        <span class="volume-current"></span>
                        <span class="volume-bar"></span>
                        <span class="volume-event"></span>
                    </div>
                    <a class="volume-control"></a>
                </div>
            </div>
            <div class="m-play-controls">
                <a class="u-play-btn prev" title="上一曲"></a>
                <a class="u-play-btn ctrl-play play" title="暂停"></a>
                <a class="u-play-btn next" title="下一曲"></a>
                <a class="u-play-btn mode mode-list current" title="列表循环"></a>
                <a class="u-play-btn mode mode-random" title="随机播放"></a>
                <a class="u-play-btn mode mode-single" title="单曲循环"></a>
            </div>
        </div>
    </div>
    <div class="f-cb">&nbsp;</div>
    <div class="m-music-list-wrap"></div>
    <div class="m-music-lyric-wrap">
        <div class="inner">
            <ul class="js-music-lyric-content">
                <li class="eof">暂无歌词...</li>
            </ul>
        </div>
    </div>
    <div class="music-container-blur-bg"></div>
</div>

<script src="js/musicList.js"></script>
<script src="js/wavesurfer.js"></script>
<!--引入工具库-->
<script src="js/util.js"></script>
<!--引入webaudio封装js-->
<script src="js/webaudio.js"></script>
<!--引入音频可视化js-->
<script src="js/lkmusic_visualizer.js"></script>
<script>
whenReady(function(){
     new LMusic({
        musicList : musicList,
        autoPlay  : true,  //是否自动播放
        defaultMode : 2,   //默认播放模式,随机
        offlineMode:true,
        callback   : function (obj) {  //返回当前播放歌曲信息

        }
    });
});
</script>
</body>
</html>

smusic.css

@charset "utf-8";
/**
 * SMusic
 * Author:Smohan
 * Version:2.0.0
 * url: http://www.smohan.net/lab/smusic.html
 * 使用请保留以上信息
 */
/*reset*/
html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0;-webkit-tap-highlight-color:rgba(0,0,0,0);word-wrap: break-word;font-size: inherit;line-height: inherit;overflow: visible;}
header,footer,section,article,aside,nav,address,figure,figcaption,menu,details{display:block;}
.f-cb{height: 0;}
.f-cb:after{display:block;content:" ";height:0;visibility:hidden;clear: both;}
.f-ib{display:inline-block;}
.f-din{display:inline;}
.f-dn{display:none;}
.f-db{display:block;}
.f-fl{float:left;}
.f-fr{float:right;}
.f-fwn{font-weight:normal;}
.f-fwb{font-weight:bold;}
.f-tal{text-align:left;}
.f-tac{text-align:center;}
.f-tar{text-align:right;}
.f-oh{overflow: hidden;zoom: 1;clear: both;}
.f-tdn{text-decoration: none!important;}
.f-vam,.f-vama *{vertical-align:middle;}
.f-wsn{word-wrap:normal;white-space:nowrap;}
.f-pre{overflow:hidden;text-align:left;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;}
.f-wwb{white-space:normal;word-wrap:break-word;word-break:break-all;}
.f-ti{overflow:hidden;text-indent:-30000px;}
.f-lhn{line-height:normal;}
.f-toe{overflow:hidden;word-wrap:normal!important;white-space:nowrap;text-overflow:ellipsis;}
.f-usn{-webkit-user-select:none;user-select:none;}
.f-bsb{-webkit-box-sizing:border-box;box-sizing:border-box;}
.f-cp{cursor: pointer}

/*滚动条美化*/
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-button:vertical{display:none}
::-webkit-scrollbar-track:vertical{background-color:transparent;}
::-webkit-scrollbar-track-piece{background-color:transparent;}
::-webkit-scrollbar-thumb:vertical{background-color:rgba(255,102,0,0.5);border-radius:6px}
::-webkit-scrollbar-thumb:vertical:hover,
::-webkit-scrollbar-thumb:vertical:active {background-color: #2e86ef}

html,body{
    width: 100%;
    height: 100%;
    overflow: hidden;
    font-family: "Microsoft Yahei","微软雅黑","Helvetica Neue","Hiragino Sans GB",Helvetica,Tahoma,sans-serif;
}

/*SMusic*/
.grid-music-container{
    width: 625px;
    height: 276px;
    padding: 20px;
    position: relative;
    margin: 80px auto;
    font-family: "Microsoft Yahei","微软雅黑","Helvetica Neue","Hiragino Sans GB",Helvetica,Tahoma,sans-serif;
}
.grid-music-container .music-container-spectrum{
    width:100%;
    height:100%;
    position: absolute;
    left:0;
    top:0;
    z-index: 2;
    -webkit-filter: blur(3px);
}
.grid-music-container .music-container-blur-bg{
    width:100%;
    height:100%;
    position: absolute;
    left:0;
    top:0;
    z-index: 2;
        background: url(../images/music_bg.jpg);
    background-size: 100% 100%;
    -webkit-filter: blur(5px);
}
.grid-music-container .m-music-play-wrap{
    height: 150px;
    position: relative;
    padding-left: 140px;
    z-index: 3;
}
.grid-music-container .u-cover{
    width: 121px;
    height: 121px;
    overflow: hidden;
    background: url(../images/music_icons_white.png) 0 0 no-repeat;
    position: absolute;
    top: 0;
    left: 0;
}
.grid-music-container .u-cover img{
    display: block;
    width: 100%;
    height: auto;
    max-height: 100%;
    border-radius: 50%;
}
.grid-music-container .u-cover.play{
    -webkit-animation: Circle 10s linear infinite 0s forwards;
    animation: Circle 10s linear infinite 0s forwards;
}
.grid-music-container .u-cover.paused{
    -webkit-animation-play-state: paused;
    animation-play-state: paused;
}
@-webkit-keyframes Circle {
    from{
        -webkit-transform: rotate(0deg);
    }
    to{
        -webkit-transform: rotate(360deg);
    }
}
@keyframes Circle {
    from{
        transform: rotate(0deg);
    }
    to{
        transform: rotate(360deg);
    }
}

.grid-music-container .m-now-info{
    height: 100%;
}
.grid-music-container .m-now-info h1{
    font-weight: normal;
}
.grid-music-container .m-now-info h1 strong{
    font-size: 18px;
    color: #fff;
    font-weight: normal;
}
.grid-music-container .m-now-info h1 small{
    margin-left: 20px;
    font-size: 14px;
    color: #fff;
}
.grid-music-container .m-now-controls{
    padding-top: 30px;
    position: relative;
}
.grid-music-container .m-now-controls .u-control{
    display: inline-block;
    vertical-align:middle;
    font-size: 0;
    overflow: hidden;
}
.grid-music-container .m-now-controls .u-process{
    width: 220px;
    height: 4px;
    position: relative;
    background-color: #cecfd4;
    cursor: pointer;
}
.grid-music-container .m-now-controls .u-process .buffer-process,.grid-music-container .m-now-controls .u-process .current-process{
    display: block;
    width: 0;
    height: 4px;
    position: absolute;
    top:0;
    left: 0;
    background-color: #FF6600;
    z-index: 1;
}
.grid-music-container .m-now-controls .u-process .buffer-process{
    z-index: 0;
    background-color: #c1c2c0;
}
.grid-music-container .m-now-controls .u-time{
    margin-left: 10px;
    font-size: 12px;
    color: #fff;
}
.grid-music-container .m-now-controls .u-volume{
    overflow: visible;
    text-align: center;
    position: relative;
    margin-left: 20px;
}
.grid-music-container .u-volume .volume-process{
    width: 3px;
    height: 50px;
    background: #cecfd4;
    position: absolute;
    top: -54px;
    left: 6px;
    cursor: pointer;
    visibility: hidden; /*设置不可见性,最好不要使用display:none,不然高度很难获取*/
}
.grid-music-container .u-volume .volume-process.show{
    visibility: visible;
}
.grid-music-container .u-volume .volume-process .volume-current,
.grid-music-container .u-volume .volume-process .volume-event{
    display: inline-block;
    width: 3px;
    height: 50%;
    background-color: #FF6600;
    position: absolute;
    left: 0;
    bottom:0;
    -webkit-transition: height .2s linear;
    transition: height .2s linear;
}
.grid-music-container .u-volume .volume-process .volume-event{
    width: 21px;
    left: -10px;
    background: none;
    height: 100%;
    z-index: 1;
}
.grid-music-container .u-volume .volume-process .volume-bar{
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 100%;
    background-color: #fff;
    border: 1px solid #a8a9a7;
    position: absolute;
    left: -3px;
    bottom: 50%;
    -webkit-transition: bottom .2s linear;
    transition: bottom .2s linear;
   /* z-index: 2;*/
   /* opacity: 0;*/
}
.grid-music-container .u-volume .volume-process .volume-bar:hover,
.grid-music-container .u-volume .volume-process .volume-bar:active{
    background-color: #f1f2f0;
}
.grid-music-container .u-volume .volume-control{
    display: inline-block;
    width: 18px;
    height: 18px;
    background: url(../images/music_icons_white.png) -140px -80px no-repeat;
    cursor: pointer;
}
.grid-music-container .u-volume .volume-control:hover{
    background-position: -158px -80px;
}
.grid-music-container .u-volume .volume-control.muted{
    background-position: -140px -98px;
}
.grid-music-container .u-volume .volume-control.muted:hover{
    background-position: -158px -98px;
}
.grid-music-container .m-music-list-wrap{
    margin-left: 135px;
    margin-right: 25px;
    height: 120px;
    border: 1px solid rgba(255,255,255,.7);
    overflow-x: hidden;
    overflow-y: auto;
    position: relative;
    z-index: 3;
}
.grid-music-container .m-music-list-wrap ul{}
.grid-music-container .m-music-list-wrap li{
    display: block;
    line-height: 30px;
    padding: 0 10px;
    cursor: pointer;
    color: #fff;
    font-size: 14px;
}
.grid-music-container .m-music-list-wrap li strong{
    font-size: 16px;
    font-weight: normal;
}
.grid-music-container .m-music-list-wrap li:hover,.grid-music-container .m-music-list-wrap li.current{
    background-color: rgba(255,255,255,.7);
    color: #FF6600;
}
.grid-music-container .m-music-list-wrap li.current{
    background-color: transparent;
}
.grid-music-container .m-play-controls{
    margin-top: 10px;
}
.grid-music-container .m-play-controls a{
    display: inline-block;
    vertical-align: middle;
}
.grid-music-container .m-play-controls .u-play-btn{
    display: inline-block;
    width: 30px;
    height: 30px;
    margin-right: 15px;
    cursor: pointer;
}
.u-play-btn{
    background: url(../images/music_icons_white.png) -220px 0 no-repeat;
}
.u-play-btn.prev,.u-play-btn.next{
    background-position: -220px 0;
}
.u-play-btn.prev:hover,.u-play-btn.next:hover{
    background-position: -220px -36px;
}
.u-play-btn.prev{
    -webkit-transform: rotate(-180deg);
    transform: rotate(-180deg);
}
.u-play-btn.play{
    background-position: -250px 0;
}
.u-play-btn.play:hover{
    background-position: -250px -36px;
}
.u-play-btn.paused{
    background-position: -280px 0;
}
.u-play-btn.paused:hover{
    background-position: -280px -36px;
}
.u-play-btn.mode{
    width: 20px!important;
    height: 18px!important;
    margin-right: 10px!important;
}
.u-play-btn.mode-list{
    background-position: -181px -98px;
    margin-left: 65px;
}
.u-play-btn.mode-list.current{
    background-position: -221px -98px;
}
.u-play-btn.mode-random{
    background-position: -201px -80px;
}
.u-play-btn.mode-random.current{
    background-position: -241px -80px;
}
.u-play-btn.mode-single{
    background-position: -181px -80px;
}
.u-play-btn.mode-single.current{
    background-position: -221px -80px;
}
/*歌词*/
.grid-music-container .m-music-lyric-wrap{
    width: 250px;
    height: 100%;
    position: absolute;
    right: -250px;
    top : 0;
    border-left: 1px solid #fff;
    overflow: hidden;
    z-index: 1;
    box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
}
.grid-music-container .m-music-lyric-wrap .inner-bg{
    width:100%;
    height:100%;
    background-color: rgba(0,0,0,.7);
    -webkit-filter: blur(2px);
    position: absolute;
    left:0;
    top:0;
    z-index: 2;
}
.grid-music-container .m-music-lyric-wrap .inner{
    position: absolute;
    top:20px;
    left: 20px;
    right: 20px;
    bottom: 20px;
    overflow-x: hidden;
    overflow-y: auto;
    z-index: 3;
}
.grid-music-container .m-music-lyric-wrap .inner ul{
    position: absolute;
    width: 100%;
    top:0;
    left: 0;
    overflow: hidden;
    -webkit-transition: all .3s;
    transition: all .3s;
}
.grid-music-container .m-music-lyric-wrap ul li{
    display: block;
    line-height: 30px;
    height: 30px;
    overflow: hidden;
    text-align: center;
    font-size: 13px;
    color: #fff;
}
.grid-music-container .m-music-lyric-wrap ul li.current{
    color:#FF6600;
    font-size: 14px;
    font-weight: bold;
}
.grid-music-container .m-music-lyric-wrap ul li.eof{
    height: 100%;
    line-height: 250px;
    font-size: 20px;
    color: #999;
}

musiclist.js

/**
 * Created by Smohan on 2015/5/15.
 */

var musicList = [
    {
        title : ‘王铮亮-老朋友(电视剧《妈妈的花样年华》插曲)‘,
        singer : ‘王铮亮‘,
        cover  : ‘static/images/music_cover.jpg‘,
        src    : ‘static/laopengyou.mp3‘,
        lyric  : "static/laopengyou.lrc"
    },
     {
        title : ‘李克勤-月半小夜曲‘,
        singer : ‘李克勤‘,
        cover  : ‘static/images/music_cover.jpg‘,
        src    : ‘static/yuebanxiaoyequ.mp3‘,
        lyric  : "static/yuebanxiaoyequ.lrc"
    }
     ,
     {
        title : ‘彭佳慧-喜欢两个人‘,
        singer : ‘彭佳慧‘,
        cover  : ‘static/images/music_cover.jpg‘,
        src    : ‘static/xihuanlianggeren.mp3‘,
        lyric  : "static/xihuanlianggeren.lrc"
    }
];

wavesurfer.js


‘use strict‘;

var WaveSurfer = {

};

var LkMusic = {

};

util.js

/* Common utilities */
WaveSurfer.util = {
    extend: function (dest) {
        var sources = Array.prototype.slice.call(arguments, 1);
        sources.forEach(function (source) {
            Object.keys(source).forEach(function (key) {
                dest[key] = source[key];
            });
        });
        return dest;
    },

    min: function(values) {
        var min = +Infinity;
        for (var i in values) {
            if (values[i] < min) {
                min = values[i];
            }
        }

        return min;
    },

    max: function(values) {
        var max = -Infinity;
        for (var i in values) {
            if (values[i] > max) {
                max = values[i];
            }
        }

        return max;
    },

    getId: function () {
        return ‘wavesurfer_‘ + Math.random().toString(32).substring(2);
    },

    ajax: function (options) {
        var ajax = Object.create(WaveSurfer.Observer);
        var xhr = new XMLHttpRequest();
        var fired100 = false;

        xhr.open(options.method || ‘GET‘, options.url, true);
        xhr.responseType = options.responseType || ‘json‘;

        xhr.addEventListener(‘progress‘, function (e) {
            ajax.fireEvent(‘progress‘, e);
            if (e.lengthComputable && e.loaded == e.total) {
                fired100 = true;
            }
        });

        xhr.addEventListener(‘load‘, function (e) {
            if (!fired100) {
                ajax.fireEvent(‘progress‘, e);
            }
            ajax.fireEvent(‘load‘, e);

            if (200 == xhr.status || 206 == xhr.status) {
                ajax.fireEvent(‘success‘, xhr.response, e);
            } else {
                ajax.fireEvent(‘error‘, e);
            }
        });

        xhr.addEventListener(‘error‘, function (e) {
            ajax.fireEvent(‘error‘, e);
        });

        xhr.send();
        ajax.xhr = xhr;
        return ajax;
    }
};

/* Observer */
WaveSurfer.Observer = {
    /**
     * Attach a handler function for an event.
     */
    on: function (event, fn) {
        if (!this.handlers) { this.handlers = {}; }

        var handlers = this.handlers[event];
        if (!handlers) {
            handlers = this.handlers[event] = [];
        }
        handlers.push(fn);

        // Return an event descriptor
        return {
            name: event,
            callback: fn,
            un: this.un.bind(this, event, fn)
        };
    },

    /**
     * Remove an event handler.
     */
    un: function (event, fn) {
        if (!this.handlers) { return; }

        var handlers = this.handlers[event];
        if (handlers) {
            if (fn) {
                for (var i = handlers.length - 1; i >= 0; i--) {
                    if (handlers[i] == fn) {
                        handlers.splice(i, 1);
                    }
                }
            } else {
                handlers.length = 0;
            }
        }
    },

    /**
     * Remove all event handlers.
     */
    unAll: function () {
        this.handlers = null;
    },

    /**
     * Attach a handler to an event. The handler is executed at most once per
     * event type.
     */
    once: function (event, handler) {
        var my = this;
        var fn = function () {
            handler.apply(this, arguments);
            setTimeout(function () {
                my.un(event, fn);
            }, 0);
        };
        return this.on(event, fn);
    },

    fireEvent: function (event) {
        if (!this.handlers) { return; }
        var handlers = this.handlers[event];
        var args = Array.prototype.slice.call(arguments, 1);
        handlers && handlers.forEach(function (fn) {
            fn.apply(null, args);
        });
    }
};

/* Make the main WaveSurfer object an observer */
WaveSurfer.util.extend(WaveSurfer, WaveSurfer.Observer);

webaudio.js

‘use strict‘;

LkMusic.WebAudio = {
    scriptBufferSize: 256,
    PLAYING_STATE: 0,
    PAUSED_STATE: 1,
    FINISHED_STATE: 2,

    supportsWebAudio: function () {
        return !!(window.AudioContext || window.webkitAudioContext);
    },

    getAudioContext: function () {
        if (!LkMusic.WebAudio.audioContext) {
            LkMusic.WebAudio.audioContext = new (
                window.AudioContext || window.webkitAudioContext
            );
        }
        return LkMusic.WebAudio.audioContext;
    },

    getOfflineAudioContext: function (sampleRate) {
        if (!LkMusic.WebAudio.offlineAudioContext) {
            LkMusic.WebAudio.offlineAudioContext = new (
                window.OfflineAudioContext || window.webkitOfflineAudioContext
            )(1, 2, sampleRate);
        }
        return LkMusic.WebAudio.offlineAudioContext;
    },

    init: function (params) {
        this.params = params;
        this.ac = params.audioContext || this.getAudioContext();

        this.lastPlay = this.ac.currentTime;
        this.startPosition = 0;
        this.scheduledPause = null;

        this.states = [
            Object.create(LkMusic.WebAudio.state.playing),
            Object.create(LkMusic.WebAudio.state.paused),
            Object.create(LkMusic.WebAudio.state.finished)
        ];

        this.createVolumeNode();
        this.createScriptNode();
        this.createAnalyserNode();

        this.setState(this.PAUSED_STATE);
        this.setPlaybackRate(this.params.audioRate);
    },

    disconnectFilters: function () {
        if (this.filters) {
            this.filters.forEach(function (filter) {
                filter && filter.disconnect();
            });
            this.filters = null;
            // Reconnect direct path
            this.analyser.connect(this.splitter);
        }
    },

    setState: function (state) {
        if (this.state !== this.states[state]) {
            this.state = this.states[state];
            this.state.init.call(this);
        }
    },

    // Unpacked filters
    setFilter: function () {
        this.setFilters([].slice.call(arguments));
    },

    /**
     * @param {Array} filters Packed ilters array
     */
    setFilters: function (filters) {
        // Remove existing filters
        this.disconnectFilters();

        // Insert filters if filter array not empty
        if (filters && filters.length) {
            this.filters = filters;

            // Disconnect direct path before inserting filters
            this.analyser.disconnect();

            // Connect each filter in turn
            filters.reduce(function (prev, curr) {
                prev.connect(curr);
                return curr;
            }, this.analyser).connect(this.splitter);
        }

    },

    createScriptNode: function () {
        if (this.ac.createScriptProcessor) {
            this.scriptNode = this.ac.createScriptProcessor(this.scriptBufferSize);
        } else {
            this.scriptNode = this.ac.createJavaScriptNode(this.scriptBufferSize);
        }

        this.scriptNode.connect(this.ac.destination);
    },

    addOnAudioProcess: function () {
        var my = this;

        this.scriptNode.onaudioprocess = function () {
            var time = my.getCurrentTime();

            if (time >= my.getDuration()) {
                my.setState(my.FINISHED_STATE);
                my.fireEvent(‘pause‘);
            } else if (time >= my.scheduledPause) {
                my.setState(my.PAUSED_STATE);
                my.fireEvent(‘pause‘);
            } else if (my.state === my.states[my.PLAYING_STATE]) {
                my.fireEvent(‘audioprocess‘, time);
            }
        };
    },

    removeOnAudioProcess: function () {
        this.scriptNode.onaudioprocess = null;
    },

    createChannelNodes: function () {
        var channels = this.buffer.numberOfChannels;

        this.splitter = this.ac.createChannelSplitter(channels);
        this.merger = this.ac.createChannelMerger(channels);

        this.setChannel(this.params.channel);

        this.analyser.disconnect();
        this.analyser.connect(this.splitter);

        this.merger.connect(this.gainNode);
    },

    setChannel: function (channel) {
        var channels = this.buffer.numberOfChannels;

        this.splitter.disconnect();

        for (var c = 0; c < channels; c++) {
            this.splitter.connect(this.merger, channel === -1 ? c : channel, c);
        }
    },

    createAnalyserNode: function () {
        this.analyser = this.ac.createAnalyser();
        this.analyser.connect(this.gainNode);
    },

    /**
     * Create the gain node needed to control the playback volume.
     */
    createVolumeNode: function () {
        // Create gain node using the AudioContext
        if (this.ac.createGain) {
            this.gainNode = this.ac.createGain();
        } else {
            this.gainNode = this.ac.createGainNode();
        }
        // Add the gain node to the graph
        this.gainNode.connect(this.ac.destination);
    },

    /**
     * Set the gain to a new value.
     *
     * @param {Number} newGain The new gain, a floating point value
     * between 0 and 1. 0 being no gain and 1 being maximum gain.
     */
    setVolume: function (newGain) {
        this.gainNode.gain.value = newGain;
    },

    /**
     * Get the current gain.
     *
     * @returns {Number} The current gain, a floating point value
     * between 0 and 1. 0 being no gain and 1 being maximum gain.
     */
    getVolume: function () {
        return this.gainNode.gain.value;
    },

    decodeArrayBuffer: function (arraybuffer, callback, errback) {
        if (!this.offlineAc) {
            this.offlineAc = this.getOfflineAudioContext(this.ac ? this.ac.sampleRate : 44100);
        }
        this.offlineAc.decodeAudioData(arraybuffer, (function (data) {
            callback(data);
        }).bind(this), errback);
    },

    /**
     * Compute the max and min value of the waveform when broken into
     * <length> subranges.
     * @param {Number} How many subranges to break the waveform into.
     * @returns {Array} Array of 2*<length> peaks or array of arrays
     * of peaks consisting of (max, min) values for each subrange.
     */
    getPeaks: function (length) {
        var sampleSize = this.buffer.length / length;
        var sampleStep = ~~(sampleSize / 10) || 1;
        var channels = this.buffer.numberOfChannels;
        var splitPeaks = [];
        var mergedPeaks = [];

        for (var c = 0; c < channels; c++) {
            var peaks = splitPeaks[c] = [];
            var chan = this.buffer.getChannelData(c);

            for (var i = 0; i < length; i++) {
                var start = ~~(i * sampleSize);
                var end = ~~(start + sampleSize);
                var min = 0;
                var max = 0;

                for (var j = start; j < end; j += sampleStep) {
                    var value = chan[j];

                    if (value > max) {
                        max = value;
                    }

                    if (value < min) {
                        min = value;
                    }
                }

                peaks[2 * i] = max;
                peaks[2 * i + 1] = min;

                if (c == 0 || max > mergedPeaks[2 * i]) {
                    mergedPeaks[2 * i] = max;
                }

                if (c == 0 || min < mergedPeaks[2 * i + 1]) {
                    mergedPeaks[2 * i + 1] = min;
                }
            }
        }

        return (this.params.splitChannels || this.params.channel > -1) ? splitPeaks : mergedPeaks;
    },

    getPlayedPercents: function () {
        return this.state.getPlayedPercents.call(this);
    },

    disconnectSource: function () {
        if (this.source) {
            this.source.disconnect();
        }
    },

    destroy: function () {
        if (!this.isPaused()) {
            this.pause();
        }
        this.unAll();
        this.buffer = null;
        this.disconnectFilters();
        this.disconnectSource();
        this.gainNode.disconnect();
        this.scriptNode.disconnect();
        this.merger.disconnect();
        this.splitter.disconnect();
        this.analyser.disconnect();
    },

    load: function (buffer) {
        this.startPosition = 0;
        this.lastPlay = this.ac.currentTime;
        this.buffer = buffer;
        this.createSource();
        this.createChannelNodes();
    },

    createSource: function () {
        this.disconnectSource();
        this.source = this.ac.createBufferSource();

        //adjust for old browsers.
        this.source.start = this.source.start || this.source.noteGrainOn;
        this.source.stop = this.source.stop || this.source.noteOff;

        this.source.playbackRate.value = this.playbackRate;
        this.source.buffer = this.buffer;
        this.source.connect(this.analyser);
    },

    isPaused: function () {
        return this.state !== this.states[this.PLAYING_STATE];
    },

    getDuration: function () {
        if (!this.buffer) {
            return 0;
        }
        return this.buffer.duration;
    },

    seekTo: function (start, end) {
        this.scheduledPause = null;

        if (start == null) {
            start = this.getCurrentTime();
            if (start >= this.getDuration()) {
                start = 0;
            }
        }
        if (end == null) {
            end = this.getDuration();
        }

        this.startPosition = start;
        this.lastPlay = this.ac.currentTime;

        if (this.state === this.states[this.FINISHED_STATE]) {
            this.setState(this.PAUSED_STATE);
        }

        return { start: start, end: end };
    },

    getPlayedTime: function () {
        return (this.ac.currentTime - this.lastPlay) * this.playbackRate;
    },

    /**
     * Plays the loaded audio region.
     *
     * @param {Number} start Start offset in seconds,
     * relative to the beginning of a clip.
     * @param {Number} end When to stop
     * relative to the beginning of a clip.
     */
    play: function (start, end) {
        // need to re-create source on each playback
        this.createSource();

        var adjustedTime = this.seekTo(start, end);

        start = adjustedTime.start;
        end = adjustedTime.end;

        this.scheduledPause = end;

        this.source.start(0, start, end - start);

        this.setState(this.PLAYING_STATE);

        this.fireEvent(‘play‘);
    },

    /**
     * Pauses the loaded audio.
     */
    pause: function () {
        this.scheduledPause = null;

        this.startPosition += this.getPlayedTime();
        this.source && this.source.stop(0);

        this.setState(this.PAUSED_STATE);

        this.fireEvent(‘pause‘);
    },

    /**
    *   Returns the current time in seconds relative to the audioclip‘s duration.
    */
    getCurrentTime: function () {
        return this.state.getCurrentTime.call(this);
    },

    /**
     * Set the audio source playback rate.
     */
    setPlaybackRate: function (value) {
        value = value || 1;
        if (this.isPaused()) {
            this.playbackRate = value;
        } else {
            this.pause();
            this.playbackRate = value;
            this.play();
        }
    }
};

LkMusic.WebAudio.state = {};

LkMusic.WebAudio.state.playing = {
    init: function () {
        this.addOnAudioProcess();
    },
    getPlayedPercents: function () {
        var duration = this.getDuration();
        return (this.getCurrentTime() / duration) || 0;
    },
    getCurrentTime: function () {
        return this.startPosition + this.getPlayedTime();
    }
};

LkMusic.WebAudio.state.paused = {
    init: function () {
        this.removeOnAudioProcess();
    },
    getPlayedPercents: function () {
        var duration = this.getDuration();
        return (this.getCurrentTime() / duration) || 0;
    },
    getCurrentTime: function () {
        return this.startPosition;
    }
};

LkMusic.WebAudio.state.finished = {
    init: function () {
        this.removeOnAudioProcess();
        this.fireEvent(‘finish‘);
    },
    getPlayedPercents: function () {
        return 1;
    },
    getCurrentTime: function () {
        return this.getDuration();
    }
};

WaveSurfer.util.extend(LkMusic.WebAudio, WaveSurfer.Observer);

lkmusic_visualizer.js

//注意此版本是支持音乐可视化的版本如果不需要音乐可视化的话那么可以使用lkmusic.js这个没有使用到AudioContext这个音频接口版本的js
//此版本完全使用audiocontext来进行的
//自用工具库
var whenReady = (function(){
    var ready = false;
    var funcs = [];//存储函数的数组
    function handler(e){
        if(ready) return;
        if(e.type === ‘readystatechange‘ && document.readyState !== ‘commplete‘){
            return;
        }
        for(var i=0;i<funcs.length;i++){
              funcs[i].call(document);
        }
        //进行标记
        ready  = true;
        funcs = null;//置空
    }
    if(document.addEventListener){
         document.addEventListener(‘DOMContentLoaded‘, handler,false);
         document.addEventListener(‘readystatechange‘, handler,false);
         window.addEventListener(‘load‘,handler,false);
    }else{
        //兼容IE等不支持addEventListener方法的浏览器
        document.attachEvent(‘onreadystatechange‘,handler);
        window.attachEvent(‘onload‘,handler);
    }
    return function isReady(f){

        if(ready){
             f.call(document);
        }else{
            funcs.push(f);
        }
    }
}());
//基于smusic开源代码进行修改和加强
(function(win,lkMusic,WaveSurfer){
  //此音乐库采用全新的html5 api来对样式的增删改查进行操作 ie版本需要10.0以上 使用此音乐类库请特别注意低版本ie 已不再我的兼容列表里面
  //至于为什么使用classList这个新的api 请参见张兴旭的一篇文章 http://www.zhangxinxu.com/wordpress/2013/07/domtokenlist-html5-dom-classlist-%E7%B1%BB%E5%90%8D/
  //修正一下 实现了操作元素样式的操作类classList默认使用原生支持的api 否则使用模拟的api来进行实现
  function classList(e){
    if(e.classList){
        //这里需要对classList的原生api进行一下加强
        /*
        DOMTokenList.prototype.adds = function(tokens){
            tokens.split(‘ ‘).forEach(function(token){
                 this.add(token);
            });
            return this;
        };*/
        return e.classList;
    }//如果原生支持classList这个api那么直接返回
    else return new CSSClassList(e);//否则就伪造一个
  }
  //CSSClassList 是一个模拟DOMTokenList的javascript类
  function CSSClassList(e){
    this.e = e;
  }
  //如果e.className 包含类名c则返回true;否则返回false
  CSSClassList.prototype.contains=function(c){
      if(c.length ===0 || c.indexOf(‘ ‘)!==-1){
        throw new Error("invalid class name: ‘"+c+"‘");
      }
      //首先是常规检查
      var classes = this.e.className;
      if(!classes) return false;//e不含类名
      if(classes === c) return true;//e有一个完全匹配的类名
      //否则,把c自身看成一个单词,利用正则表达式搜索
      //\b在正则表达式里面代表单词的边界
      return classes.search(‘\\b‘+c+‘\\b‘) !== -1;
  };
  //如果c不存在,将c添加到e.className中
  CSSClassList.prototype.add = function(c){
    if(this.contains(c)) return;//如果存在就直接返回 不需要再新增一个
    var classes = this.e.className;
    if(classes && classes[classes.length-1] !== ‘ ‘){
        c = ‘ ‘+c;//如果没有空格需要加上一个空格
    }
    this.e.className +=c;
  };
  //将在e.className中出现的c全部删除
  CSSClassList.prototype.remove =function(c){
    if(!this.contains(c))return;//如果类里面本来就没有就直接返回
    //将所有作为单词的c和多余的尾随空格全部删除
    var pattern = new RegExp(‘\\b‘+c+‘\\b\\s*‘,‘g‘);
    this.e.className = this.e.className.replace(pattern,‘‘);
  };
  //如果c不存在,将c添加到e.className中,并且返回true
  //如果c存在,那么将在e.className中出现的所有c都删除,并返回false
  CSSClassList.prototype.toggle=function(c){
     if(this.contains(c)){//如果e.className 包含c
        this.remove(c); //删除它
        return false;
     }else{
        //否则的话
        this.add(c);//否则就添加c
        return true;
     }
  };
  //返回 e.className本身
  CSSClassList.prototype.toString = function(){
    return this.e.className;
  };
  //返回e.className 中的类名 是一个数组
  CSSClassList.prototype.toArray = function(){
    return this.e.className.match(/\b\w+\b/g) || [];
  }
  //禁用右键菜单函数
  function forbidenRightMenu(){
    document.oncontextmenu=function(){
         return false;
    }
    document.onmousedown=function(){

    }
  }
  //实现链式调用
  /**
   * 实现基于jquery的链式调用
   * @AuthorHTL
   * @DateTime  2016-04-08T14:46:18+0800
   * @param     {[type]}                 query [description]
   * @return    {[type]}                       [description]
   */
  function $(query){
     return document.querySelector(query);
  }
  //实现基本拓展
  /**
   * 实现继承
   * @AuthorHTL
   * @DateTime  2016-04-08T14:46:43+0800
   * @param     {[type]}                 o [description]
   * @param     {[type]}                 p [description]
   * @return    {[type]}                   [description]
   */
  function extend(o,p){
    for(prop in p){
      if(p.hasOwnProperty(prop)){
        o[prop] = p[prop];
      }
    }
    return o;
  }
  //实现计算时间
  /**
   * 对时间进行格式化
   * @AuthorHTL
   * @DateTime  2016-04-08T14:45:31+0800
   * @param     {[type]}                 time [description]
   * @return    {[type]}                      [description]
   */
  function calctime(time){
        var hours,minutes,seconds,timer=‘‘;
        hours = String(parseInt(time / 3600));
        minutes = String(parseInt((time % 3600)/60));
        seconds = String(parseInt((time % 60)));
        if(hours !== ‘0‘){
            if(hours.length === 1) hours =‘0‘+hours;
            timer+=(hours+‘:‘);
        }
        if(minutes.length === 1 ){
            minutes =‘0‘+minutes;
        }
        timer+=(minutes+":");
        if(seconds.length === 1){
             seconds = ‘0‘+seconds;
        }
        timer+=seconds;
        return timer;
  }
  //var cover =classList($(‘.u-cover‘)).remove(‘u-cover‘); //测试代码
  //实现兼容各个浏览器的XMLHttpRequest
  if(win.XMLHttpRequest === undefined){
     win.XMLHttpRequest = function(){
        try{
             return new ActiveXObject(‘Msxml2.XMLHTTP.6.0‘);
        }catch(e1){
             try{
                return new ActiveXObject(‘Msxml2.XMLHTTP.3.0‘);
             }catch(e2){
                //否则就出错了
                throw new Error(‘XMLHttpRequset is not supported!‘);
             }
        }
     };
  }
  //实现简单的ajax操作
  /**
   * 简单的ajax封装
   * @AuthorHTL
   * @DateTime  2016-04-08T14:45:10+0800
   * @param     {[type]}                 url      [description]
   * @param     {[type]}                 type     [description]
   * @param     {[type]}                 isasync  [description]
   * @param     {Function}               callback [description]
   * @return    {[type]}                          [description]
   */
  function ajax(url,type,isasync,callback){
      if(!url) return false;
      if(!type) type = ‘GET‘;
      if(!isasync) isasync = false;
      if(!callback) callback = function(result){
              console.log(result);
      }
      var xhr = new XMLHttpRequest();
      xhr.open(type,url,isasync);//这里要注意是否异步,如果是true那就是异步调用不会卡死浏览器
      xhr.onreadystatechange = function(){
             if(xhr.readyState === 4 && xhr.status=== 200){
                 var type = xhr.getResponseHeader(‘Content-Type‘);
                 if(type){
                    //检查类型
                   if(type.indexOf(‘xml‘) !== -1 && xhr.responseXML) callback(xhr.responseXML);//返回xml
                   else if(type === ‘application/json‘)
                   callback(JSON.parse(xhr.responseText));  //如果是json那么就对json数据进行编码操作
                   else
                   //直接返回字符串响应
                   callback(xhr.responseText);
                 }else{
                   callback(xhr.responseText);
                 }

             }
      };
      xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");//让服务器判断是ajax请求还是属于普通请求
      xhr.send(null);//发送请求
  }
  //ajax(‘http://localhost:808/smusicmyversion/index.html‘); 测试代码
  var bufferTimer = null;

  /**
   * 构造函数
   * @AuthorHTL
   * @DateTime  2016-04-08T14:44:48+0800
   * @param     {[type]}                 config [description]
   */
  function LMusic(config){
       //配置参数
       this.config = extend(this.config,config);
       this.musicList = this.config.musicList ||[];
       this.musicLength = this.musicList.length;
       if(!this.musicLength){
          this.musicDom.listWrap.innerHTML = ‘<p>暂无播放记录</p>‘;
       }
       this.audioDom = null;
       this.audiocontext =null;//创建audiotext
       this.audiobuffer = null;
       this.gainnode = null;
       this.buffersource = null;
       this.curTime = 0;
       this.Ajax = null;
       this.tmpEvents = [];
       this.init();//初始化播放器相关dom以及事件
  }
  LMusic.prototype ={
    config:{
      musicList:[],//播放列表
      defaultVolume:0.5,//默认音量大小 0-1.0之间
      defaultIndex:0,//播放列表索引
      autoPlay:false,//是否默认自动播放
      defaultMode:1,//播放模式 随机播放 按顺序播放 还是循环播放
      bufferTime:1000,//缓冲的计时器时间
      offlineMode:false, //是否支持离线缓存
      showrightmenu:false,
      gradientleft:‘#0f0‘,
      gradientmiddle:‘#ff0‘,
      gradientright:‘#f00‘,
      gradienttop:‘#ff0‘,
      callback:function(obj){
      }
    },
    prepareAudioApi:function(){
          //统一前缀
          window.requestAnimationFrame = window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.msRequestAnimationFrame;

          //处理当浏览器不支持AudioContext的情况,这样浏览器就不会报错了
          if(LkMusic.WebAudio.supportsWebAudio()){
             //如果支持webaudio那么就是下面代码
             LkMusic.WebAudio.init(
                {
                    audioContext:new (window.AudioContext || window.webkitAudioContext||window.mozAudioContext||window.msAudioContext)
                }
             );
          }else{
            console.log("对不起您的浏览器暂时不支持AudioContext!无法进行音乐可视化显示!");
          }
    },
    /**
     * 根据列表数组创建列表(以后可以使用List数据结构)
     * @AuthorHTL
     * @DateTime  2016-04-08T15:31:33+0800
     * @return    {[type]}                 [description]
     */
    createListDom:function(){
      var i =0,ul=‘<ul>‘;

      for (var i = 0; i < this.musicLength; i++) {
        ul += ‘<li><strong>‘+this.musicList[i][‘title‘]+‘</strong> -- <small>‘+this.musicList[i][‘singer‘]+‘</small></li>‘;
      }
      ul += ‘</ul>‘;
      this.musicDom.listWrap.innerHTML = ul;
    },
    changeVolume:function(percent){
        LkMusic.WebAudio.setVolume(percent);
    },
    /**
     * 重置播放器
     * @AuthorHTL
     * @DateTime  2016-04-15T11:21:32+0800
     * @param     {[type]}                 idx [description]
     * @return    {[type]}                     [description]
     */
    resetPlayer:function(idx){

         (idx >= (this.musicLength-1)) && (idx = this.musicLength-1);
         (idx <=0) && (idx =0);
         var _this = this;
         this.currentMusic = idx;
         var nowMusic = this.musicList[idx];
         this.loadBuffer(nowMusic.src);
         clearInterval(bufferTimer);
         this.musicDom.bufferProcess.style.width = 0+‘px‘;
         this.musicDom.curProcess.style.width = 0 + ‘px‘;

         this.musicDom.cover.innerHTML = ‘<img src="‘+nowMusic.cover+‘" title="‘+nowMusic.title + ‘ -- ‘+ nowMusic.singer + ‘">‘;
         this.musicDom.title.innerHTML = ‘<strong>‘+nowMusic.title+‘</strong><small>‘+nowMusic.singer+‘</small>‘;
         this.musicDom["lyricWrap"].innerHTML = ‘<li class="eof">正在加载歌词...</li>‘;
         this.musicDom["lyricWrap"].style.marginTop = 0 + "px";
         this.musicDom["lyricWrap"].scrollTop = 0;
         this.getLyric(idx);
         //设置播放列表选中
         var playlist = document.querySelectorAll(‘.m-music-list-wrap li‘), i = 0;
         for (var i = 0; i < this.musicLength; i++) {
           if (i === idx) {
               var cl = new classList(playlist[i]);
               cl.add(‘current‘);
           }else{
               var cl = new classList(playlist[i]);
               cl.remove(‘current‘);
           }
         }

         var _callback = nowMusic;
         _callback.index = idx;
         typeof this.config.callback === ‘function‘ && this.config.callback(_callback);
    },
    /**
     * 控制音量播放
     * @AuthorHTL
     * @DateTime  2016-04-15T11:31:58+0800
     * @param     {[type]}                 volume [description]
     */
    setVolume:function(volume){
        _this = this;
        var v = this.musicDom.volume,h=v.volumeEventer.offsetHeight||50;
        (volume >=1) && (volume = 1);
        (volume <=0) && (volume = 0);
        var currentHeight = h*volume;//获取当前的音量高度
        v.volumeCurrent.style.height = currentHeight +‘px‘;
        v.volumeCtrlBar.style.bottom = currentHeight +‘px‘;
        v.volumeProcess.setAttribute(‘data-volume‘, volume);
        if(volume === 0){
             var cl = new classList(v.volumeControl);
             cl.add(‘muted‘);
             LkMusic.WebAudio.setVolume(0);
        }else{
             var cl = new classList(v.volumeControl);
             cl.remove(‘muted‘);
             LkMusic.WebAudio.setVolume(volume);
        }
    },
    clearTmpEvents: function () {
        this.tmpEvents.forEach(function (e) { e.un(); });
    },
    cancelAjax: function () {
        if (this.Ajax) {
            this.Ajax.xhr.abort();
            this.Ajax = null;
        }
    },
    empty: function () {
        if (!LkMusic.WebAudio.isPaused()) {
             this.pause();
             LkMusic.WebAudio.seekTo(0);
             LkMusic.WebAudio.disconnectSource();
        }
        this.cancelAjax();
        this.clearTmpEvents();
    },
    loadBuffer: function (url) {
        this.empty();
        // load via XHR and render all at once
        return this.getArrayBuffer(url, this.loadArrayBuffer.bind(this));
    },
     /**
     * Internal method.
     */
    loadArrayBuffer: function (arraybuffer) {
        LkMusic.WebAudio.decodeArrayBuffer(arraybuffer, function (data) {
            this.loadDecodedBuffer(data);

        }.bind(this));
    },
    /**
     * Directly load an externally decoded AudioBuffer.
     */
    loadDecodedBuffer: function (buffer) {
        LkMusic.WebAudio.load(buffer);
        this.fireEvent(‘ready‘,this);
    },

    getArrayBuffer: function (url, callback) {
        var _this = this;
        var ajax = WaveSurfer.util.ajax({
            url: url,
            responseType: ‘arraybuffer‘
        });
        this.Ajax = ajax;

        this.tmpEvents.push(
            ajax.on(‘progress‘, function (e) {
                _this.onProgress(e);

            }),
            ajax.on(‘success‘, function (data, e) {
                callback(data);
                _this.Ajax = null;
            }),
            ajax.on(‘error‘, function (e) {
                _this.fireEvent(‘error‘, ‘XHR error: ‘ + e.target.statusText);
                _this.Ajax = null;
            })
        );

        return ajax;
    },
    onProgress: function (e) {
        if (e.lengthComputable) {
            var percentComplete = e.loaded / e.total;
        } else {
            // Approximate progress with an asymptotic
            // function, and assume downloads in the 1-3 MB range.
            percentComplete = e.loaded / (e.loaded + 1000000);
        }
        this.fireEvent(‘loading‘, Math.round(percentComplete * 100), this);
    },
    showProgress:function(percent,target){
        target.setBuffer(percent,target.musicDom.bufferProcess);
    },
    drawSpectrum: function(analyser) {
        var that = this,
            canvas = document.querySelector(‘.music-container-spectrum‘),
            cwidth = canvas.width,
            cheight = canvas.height - 2,
            meterWidth = 10, //width of the meters in the spectrum
            gap = 2, //gap between meters
            capHeight = 2,
            capStyle = this.config.gradienttop,
            meterNum = 800 / (10 + 2), //count of the meters
            capYPositionArray = []; ////store the vertical position of hte caps for the preivous frame
        ctx = canvas.getContext(‘2d‘),
        gradient = ctx.createLinearGradient(0, 0, 0, 500);
        gradient.addColorStop(1, this.config.gradientleft);
        gradient.addColorStop(0.5, this.config.gradientmiddle);
        gradient.addColorStop(0, this.config.gradientright);
        var drawMeter = function() {
            var array = new Uint8Array(analyser.frequencyBinCount);
            analyser.getByteFrequencyData(array);
            if (that.status === 0) {
                //fix when some sounds end the value still not back to zero
                for (var i = array.length - 1; i >= 0; i--) {
                    array[i] = 0;
                };
                allCapsReachBottom = true;
                for (var i = capYPositionArray.length - 1; i >= 0; i--) {
                    allCapsReachBottom = allCapsReachBottom && (capYPositionArray[i] === 0);
                };
                if (allCapsReachBottom) {
                    cancelAnimationFrame(that.animationId); //since the sound is top and animation finished, stop the requestAnimation to prevent potential memory leak,THIS IS VERY IMPORTANT!
                    return;
                };
            };
            var step = Math.round(array.length / meterNum); //sample limited data from the total array
            ctx.clearRect(0, 0, cwidth, cheight);
            for (var i = 0; i < meterNum; i++) {
                var value = array[i * step];
                if (capYPositionArray.length < Math.round(meterNum)) {
                    capYPositionArray.push(value);
                };
                ctx.fillStyle = capStyle;
                //draw the cap, with transition effect
                if (value < capYPositionArray[i]) {
                    ctx.fillRect(i * 12, cheight - (--capYPositionArray[i]), meterWidth, capHeight);
                } else {
                    ctx.fillRect(i * 12, cheight - value, meterWidth, capHeight);
                    capYPositionArray[i] = value;
                };
                ctx.fillStyle = gradient; //set the filllStyle to gradient for a better look
                ctx.fillRect(i * 12 /*meterWidth+gap*/ , cheight - value + capHeight, meterWidth, cheight); //the meter
            }
            that.animationId = requestAnimationFrame(drawMeter);
        }
        this.animationId = requestAnimationFrame(drawMeter);
    },
    hideProgress:function(target){
        if(target.config.autoPlay){
            target.play();
         }else{
            target.pause();
         }

         LkMusic.WebAudio.setVolume(target.config.defaultVolume);

         target.action();
         //开始进行音频频谱的绘制
         target.drawSpectrum(LkMusic.WebAudio.analyser);
    },
    destroy:function(event){

    },
    /**
     * 初始化播放
     * @AuthorHTL
     * @DateTime  2016-04-15T13:54:55+0800
     * @return    {[type]}                 [description]
     */
    initPlay:function(){
            var idx = this.config.defaultIndex;

            if(this.config.defaultMode === 2){
              //随机播放
              idx = this.getRandomIndex();
            }

            var _this = this;

            this.on(‘loading‘, this.showProgress);

            this.on(‘ready‘, this.hideProgress);

            this.on(‘destroy‘, this.destroy);

            this.on(‘error‘, this.hideProgress);

            LkMusic.WebAudio.on(‘finish‘, function () {
                _this.playByMode(‘ended‘);
            });

            LkMusic.WebAudio.on(‘play‘, function () {

            });

            LkMusic.WebAudio.on(‘pause‘, function () {

            });

            LkMusic.WebAudio.on(‘audioprocess‘, function (time) {

            });

            this.resetPlayer(idx);

    },
    /**
     * 播放音乐
     * @AuthorHTL
     * @DateTime  2016-04-15T14:09:29+0800
     * @return    {[type]}                 [description]
     */
    play:function(start,end){
        var ctrl = this.musicDom.button.ctrl;
        LkMusic.WebAudio.play(start,end);
        var ctrllist = new classList(ctrl);
        ctrllist.remove(‘paused‘);
        ctrllist.add(‘play‘);
        ctrl.setAttribute(‘title‘, ‘暂停‘);
        var coverlist = new classList(this.musicDom.cover);
        coverlist.remove(‘paused‘);
        coverlist.add(‘play‘);
    },
    /**
     * 暂停音乐
     * @AuthorHTL
     * @DateTime  2016-04-15T14:09:38+0800
     * @return    {[type]}                 [description]
     */
    pause:function(){
        var ctrl = this.musicDom.button.ctrl;
        LkMusic.WebAudio.pause();
        var ctrllist = new classList(ctrl);
        ctrllist.remove(‘play‘);
        ctrllist.add(‘paused‘);
        ctrl.setAttribute(‘title‘, ‘播放‘);
        var coverlist = new classList(this.musicDom.cover);
        coverlist.remove(‘play‘);
        coverlist.add(‘paused‘);
    },
    /**
     * 获取随机索引
     * @AuthorHTL
     * @DateTime  2016-04-15T13:59:03+0800
     * @return    {[type]}                 [description]
     */
    getRandomIndex:function(){
        var idx = this.currentMusic,len = this.musicLength,i=0,temp=[];
        //将不是当前播放的推入到新的temp数组之中
        for (var i = 0; i < len; i++) {
            if(i !== idx){
              temp.push(i);
            }
       }
       var random = parseInt(Math.random() * temp.length);
       return temp[random];
    },
    /**
     * 根据播放模式来进行播放
     * @AuthorHTL
     * @DateTime  2016-04-15T14:41:01+0800
     * @param     {[type]}                 type [description]
     * @return    {[type]}                      [description]
     */
    playByMode:function(type){
      var mode = this.playMode,
          idx = this.currentMusic,
          len = this.musicLength,
          index = idx;
          switch (mode) {
            case 1:
              if(type === ‘prev‘){
                  index = ((idx<=len-1)&&(idx>0))?(idx-1):(len-1);
              }else if(type === ‘next‘ || type ===‘ended‘){
                  index = (idx >= len-1) ? 0 : (idx-1);//这种是三元运算符
              }
              break;
            case 2:
              index = this.getRandomIndex();//随机获取index
              break;
            case 3:
              if(type === ‘prev‘){
                  index = ((idx<=len-1)&&(idx>0))?(idx-1):(len-1);
              }else if(type === ‘next‘){
                  index = (idx >= len-1) ? 0 : (idx-1);//这种是三元运算符
              }else{
                index = idx;
              }
              break;
            default:
              //empty code here
              break;
          }
          this.resetPlayer(index);
    },
    initRightPanel:function(options){
        this.musicDom.music.addEventListener(‘‘,function(event){
            event = event || window.event;

        });

    },
    /**
     * 一些操作
     * @AuthorHTL
     * @DateTime  2016-04-15T14:53:39+0800
     * @return    {[type]}                 [description]
     */
    action:function(){
         //这里为什么要用_this 这个要明白this在不同地方的指向不一样
         var _this = this,v = this.musicDom.volume,btn=this.musicDom.button;
         var enterFrame = function(){

            var currenttime = calctime(LkMusic.WebAudio.getCurrentTime()),
                    totaltime = calctime(LkMusic.WebAudio.getDuration());
                var currentProcess = (LkMusic.WebAudio.getCurrentTime()/LkMusic.WebAudio.getDuration()) *(_this.musicDom.bufferProcess.parentNode.offsetWidth);
                _this.musicDom.time.innerHTML = ‘‘+currenttime+‘/‘+totaltime;
                _this.musicDom.curProcess.style.width = currentProcess +"px";
                //歌词进度
                var curTime = parseInt(LkMusic.WebAudio.getCurrentTime()*1e3);//这里不能跟着smusic那样写不然会出很大的错误
                var lyrics = _this.musicDom[‘lyricWrap‘].querySelectorAll(‘.u-lyric‘),
                    sizes = lyrics.length,
                    i =0;
                if(sizes >1){
                   for(;i<sizes;i++){
                     var lyl = lyrics[i];
                     var cl = new classList(lyl);
                     if(lyl){
                         var _time = parseFloat(lyl.getAttribute(‘data-time‘));
                         if(curTime >= _time){
                             var top = (i-1) *30;//30是每个LI的高度
                             _this.musicDom[‘lyricWrap‘].style.marginTop = -top +‘px‘;
                             //移除之前的current
                             for(var j =0;j<sizes;j++){
                                  var cl1 = new classList(lyrics[j]);
                                lyrics[j] && cl1.remove(‘current‘);
                             }
                             cl.add(‘current‘);
                         }
                     }
                   }
                }
            requestAnimationFrame(enterFrame);
         };
         requestAnimationFrame(enterFrame);
         //音量控制器 静音和回复播放
         v.volumeControl.addEventListener(‘click‘,function(event){
          event = event || window.event;
          event.stopPropagation();//阻止冒泡
          var cl = new classList(v.volumeProcess);
          if(cl.contains(‘show‘)){
              cl.toggle(‘muted‘);
              cl.contains(‘muted‘) ?(_this.changeVolume(0)) :(_this.changeVolume(_this.config.defaultVolume));
          }else{
            cl.add(‘show‘);
          }
         },false);
         //当点击文档空白区域的时候隐藏音量调节器
         document.addEventListener(‘click‘, function(event){
          event = event || window.event;
          event.stopPropagation();
          var target = event.target || event.srcElement;
          var cl = new classList(v.volumeProcess);
          if((target.parentNode !== v.volumeProcess) && target.parentNode !==$(‘.grid-music-container .u-volume‘)){
             cl.remove(‘show‘);
          }
         },false);
         //绑定音量调节器
         v.volumeEventer.addEventListener(‘click‘,function(event){
            event = event || window.event;
            event.stopPropagation();
            var h = this.offsetHeight,
            y = event.offsetY,
            volume = (h-y)/h;
            _this.setVolume(volume);
         },false);
         //绑定简单的播放列表以后我们使用数据结构的list来进行实现
         var playlist = document.querySelectorAll(‘.m-music-list-wrap li‘),i=0;

         for(;i<_this.musicLength;i++){
          !(function(i){
             playlist[i].addEventListener(‘click‘,function(){
                _this.resetPlayer(i);
             },false);
          }(i)
          );

         }

         //绑定播放按钮
         btn.ctrl.addEventListener(‘click‘,function(){
            var cl = new classList(btn.ctrl);
            if(cl.contains(‘play‘)){
               _this.pause();
            }else{
              _this.play();
            }

         },false);
         //绑定下一曲
         btn.prev.addEventListener(‘click‘,function(){
          _this.playByMode(‘prev‘);
         },false);
         //绑定下一曲
         btn.next.addEventListener(‘click‘,function(){
           _this.playByMode(‘next‘);
         },false);
         //这里我们还要根据外部配置或者默认配置的播放模式进行
         switch (_this.playMode){

            case 1:
                      var cl = new classList(btn.listCircular);
                    cl.add(‘current‘);
                    var cl1 = new classList(btn.singleCircular);
                    cl1.remove(‘current‘);
                    var cl2 = new classList(btn.randomPlay);
                    cl2.remove(‘current‘);
                break;

            case 2:
                  var cl = new classList(btn.randomPlay);
                    cl.add(‘current‘);
                    var cl1 = new classList(btn.listCircular);
                    cl1.remove(‘current‘);
                    var cl2 = new classList(btn.singleCircular);
                    cl2.remove(‘current‘);
                break;

            case 3:
                  var cl = new classList(btn.singleCircular);
                    cl.add(‘current‘);
                    var cl1 = new classList(btn.listCircular);
                    cl1.remove(‘current‘);
                    var cl2 = new classList(btn.randomPlay);
                    cl2.remove(‘current‘);
                break;

            default:
              //empty code here
                break;
         }
         //绑定列表循环
         btn.listCircular.addEventListener(‘click‘,function(){

            var cl = new classList(this);
            cl.add(‘current‘);
            var cl1 = new classList(btn.singleCircular);
            cl1.remove(‘current‘);
            var cl2 = new classList(btn.randomPlay);
            cl2.remove(‘current‘);
            _this.playMode = 1;

         },false);
         //绑定随机播放按钮
         btn.randomPlay.addEventListener(‘click‘,function(){
              var cl = new classList(this);
                    cl.add(‘current‘);
                    var cl1 = new classList(btn.listCircular);
                    cl1.remove(‘current‘);
                    var cl2 = new classList(btn.singleCircular);
                    cl2.remove(‘current‘);
                    _this.playMode = 3;

         },false);
         //绑定单曲循环
         btn.singleCircular.addEventListener(‘click‘,function(){

                var cl = new classList(this);
                    cl.add(‘current‘);
                    var cl1 = new classList(btn.listCircular);
                    cl1.remove(‘current‘);
                    var cl2 = new classList(btn.randomPlay);
                    cl2.remove(‘current‘);
                    _this.playMode = 3;
            });
         //拖动进度条
         var $progress = this.musicDom[‘curProcess‘].parentNode;
         $progress.addEventListener(‘click‘,function(event){
          event = event || window.event;
          var left = this.getBoundingClientRect().left,width = this.offsetWidth;
          var progressX = Math.min(width,Math.abs(event.clientX - left));//防止超出范围
          //这里要特别注意不能讲currentTime 写成了currenttime
          if(LkMusic.WebAudio.getCurrentTime() && LkMusic.WebAudio.getDuration()){
            var start = parseInt((progressX/width) *(LkMusic.WebAudio.getDuration()));
            LkMusic.WebAudio.play(start);
          }
         });

    },
    /**
     * 缓冲加载
     * @AuthorHTL
     * @DateTime  2016-04-08T15:41:55+0800
     * @param     {[type]}                 audio     [description]
     * @param     {[type]}                 bufferDom [description]
     */
    setBuffer:function(percent,bufferDom){
              var w = bufferDom.parentNode.offsetWidth;
              var bufferWidth = percent/100*w;
              bufferDom.style.width = bufferWidth+‘px‘;
              if(percent===100){
                   bufferDom.style.width = w +‘px‘;
              }
    },
    /**
     * 获取歌词
     * @AuthorHTL
     * @DateTime  2016-04-14T16:48:12+0800
     * @param     {[type]}                 index [description]
     * @return    {[type]}                       [description]
     */
    getLyric:function(index){
          var _this = this;
          if(this.lyricCache[index]){
                this.renderLyric(this.lyricCache[index]);
            }else{
                var url = this.musicList[index]["lyric"];
                if(url){
                    ajax(url,‘GET‘,true,function(lrc){

                        var lyric = _this.parseLyric(lrc);

                        _this.lyricCache[index] = lyric ? lyric : null;
                        _this.renderLyric(lyric);
                    })
                }else{

                    this.lyricCache[index] = null;
                    this.renderLyric(null);
                }
            }
    },
    /**
     * 渲染歌词
     * @AuthorHTL
     * @DateTime  2016-04-14T16:48:23+0800
     * @param     {[type]}                 lyric [description]
     * @return    {[type]}                       [description]
     */
    renderLyric:function(lyric){

             var dom = this.musicDom["lyricWrap"], tpl = "";
              if(lyric){
                  for(var k in lyric){
                      var txt = lyric[k] ? lyric[k] :‘--- lkmusic ---‘;
                      tpl += ‘<li class="u-lyric f-toe" data-time="‘+k+‘">‘+txt+‘</li>‘;
                  }
                  tpl && (tpl += ‘<li class="u-lyric">www.laijiadayuan.com</li>‘);
              }else{
                tpl = ‘<li class="eof">暂无歌词...</li>‘;
              }
              dom.style.marginTop = 0 + "px";
              dom.screenTop = 0;
              dom.innerHTML = tpl;

    },
    /**
     * 解析歌词
     * @AuthorHTL
     * @DateTime  2016-04-14T15:29:02+0800
     * @param     {[type]}                 lrc [description]
     * @return    {[type]}                     [description]
     */
     /*
      [00:00.91]小苹果
      [00:01.75]作词:王太利 作曲:王太利
      [00:02.47]演唱:筷子兄弟
     */
    parseLyric:function (lrc){
        var lyrics = lrc.split("[");

        var lrcObj = {};
        for(var i=0;i<lyrics.length;i++){
            var lyric = decodeURIComponent(lyrics[i]);
            var timeReg = /\d*:\d*((\.|\:)\d*)*\]/g;
            var timeRegExpArr = lyric.match(timeReg);

            if(!timeRegExpArr)continue;

            var clause = lyric.replace(timeReg,‘‘);

            for(var k = 0,h = timeRegExpArr.length;k < h;k++) {
                var t = timeRegExpArr[k];
                var min = Number(String(t.match(/\d*/i)).slice(1)),
                    sec = Number(String(t.match(/\:\d*/i)).slice(1));
                var time = (min * 60 + sec)*1e3;//必须转换成毫秒才行
                lrcObj[time] = clause;
            }
    }
    return lrcObj;
    },
    /**
     * 初始化播放器
     * @AuthorHTL
     * @DateTime  2016-04-14T15:29:02+0800
     * @return   null                  [description]
     */
    init:function(){
            //缓存DOM结构
            this.musicDom = {
                music : $(‘.grid-music-container‘),
                cover : $(‘.grid-music-container .u-cover‘),
                title : $(‘.grid-music-container .u-music-title‘),
                curProcess : $(‘.grid-music-container .current-process‘),
                bufferProcess : $(‘.grid-music-container .buffer-process‘),
                time : $(‘.grid-music-container .u-time‘),
                listWrap : $(‘.grid-music-container .m-music-list-wrap‘),
                lyricWrap : $(‘.grid-music-container .js-music-lyric-content‘), //歌词区域
                volume   : {
                    volumeProcess : $(‘.grid-music-container .volume-process‘),
                    volumeCurrent : $(‘.grid-music-container .volume-current‘),
                    volumeCtrlBar : $(‘.grid-music-container .volume-bar‘),
                    volumeEventer : $(‘.grid-music-container .volume-event‘),  //主要作用于绑定事件,扩大了音量的触发范围
                    volumeControl : $(‘.grid-music-container .volume-control‘)
                },
                button  : {
                    ctrl : $(‘.grid-music-container .ctrl-play‘),
                    prev : $(‘.grid-music-container .prev‘),
                    next : $(‘.grid-music-container .next‘),
                    listCircular : $(‘.grid-music-container .mode-list‘), //列表循环
                    randomPlay   : $(‘.grid-music-container .mode-random‘), //随机循环
                    singleCircular : $(‘.grid-music-container .mode-single‘) //单曲循环
                }
            };
            this.currentMusic = this.config.defaultIndex ||0;
            this.playMode = this.config.defaultMode||1;
            this.lyricCache = {};
            this.audioDom = document.createElement(‘audio‘);
            this.createListDom();
            //准备api
            this.prepareAudioApi();
            this.initPlay();

            if(!this.config.showrightmenu){
                forbidenRightMenu();
            }
            //支持离线缓存 (可以配置lkmusic.appcache)
            if(this.config.offlineMode){
                 document.documentElement.setAttribute(‘manifest‘,‘lkmusic.appcache‘);
            }//此功能暂时不起作用 因为必须把这个manifest先写在html中
            //创建支持音乐频谱的cavas背景
            var canvas = document.createElement(‘canvas‘);
            canvas.className = ‘music-container-spectrum‘;
            this.musicDom.music.appendChild(canvas);

    }
  };
  //这里是关键代码用来拓展观察者模式来着
  Object.keys(WaveSurfer.Observer).forEach(function (key) {
                LMusic.prototype[key] = WaveSurfer.Observer[key];
  });
  win = win || window;
  win.LMusic = function(options){
     return new LMusic(options);
  };
})(window,LkMusic,WaveSurfer);
时间: 2024-08-25 17:47:38

lkmusic项目改进版本之WebAudio版本支持音乐可视化 已更新至github 欢迎下载的相关文章

lkmusic项目改进版本之WebAudio版本支持音乐可视化(目前还有问题)后续发布到github

index.html <!DOCTYPE html> <!--对离线存储进行支持--> <html lang="zh-cmn-Hans" <!--manifest="lkmusic.appcache"--> > <head> <meta charset="UTF-8"> <meta name="viewport" content="widt

如何为自己的博客文章自动添加移动版本(目前仅支持博客园)

前言 从2005年开始撰写第一篇技术博客,我也算是国内最早的一批技术博客作者之一了.其中中断过一段时间,但2008年重新启用之后,这个习惯一直保留到现在,目前已经累积的文章数量已经达到1226篇.这些文章绝大部分都是原创的,转载的有特别注明,文章内容大多是自己实际工作中遇到的问题和解决方案,或者我为企业做顾问服务.培训服务时解决的问题,我尤其喜欢在解决问题后做一点总结,并整理成一篇博客文章分享出来,一来是对思路的重新梳理,同时也希望也许日后能对其他人也有所帮助. 这些年随着移动互联网逐渐占据主流

DIOCP开源项目-DIOCP3的重生和稳定版本发布

DIOCP3的重生 从开始写DIOCP到现在已经有一年多的时间了,最近两个月以来一直有个想法做个 30 * 24 稳定的企业服务端架构,让程序员专注于逻辑实现就好.虽然DIOCP到现在通讯层已经很稳定了,但是要做如果做这种架构,发现还有诸多不便.于是,有了重写DIOCP的想法. 关于开源服务器的选用: 前段时间大部分代码已经编写完成,于是需要给diocp3安个家,google显然不行了,老是被墙.然后准备选用http://sourceforge.net/,发现我的qq email老是收不到验证邮

Android官方技术文档翻译——迁移 Gradle 项目到1.0.0 版本

本文译自Android官方技术文档<Migrating Gradle Projects to version 1.0.0>,原文地址:http://tools.android.com/tech-docs/new-build-system/migrating-to-1-0-0. 本篇文档介绍的是低版本的Gradle项目怎么升级到1.0.0版本. 翻译不易,转载请注明CSDN博客上的出处: http://blog.csdn.net/maosidiaoxian/article/details/427

jquery 1.9版本后不在支持browser 方法的解决方案

今天对jquery 进行升级,导致项目出错,原来在1.9版本之后 jquery 不支持browser 方法了.  官方建议的又不好用,所以我所jquery 原来的代码摘除来,又扩展回去. //解决jquery 1.9版本之后不支持 browser 这里进行了扩展 var a, b; $.uaMatch = function (a) { a = a.toLowerCase(); var b = /(chrome)[ \/]([\w.]+)/.exec(a) || /(webkit)[ \/]([\

Android 开源项目android-open-project工具库解析之(二) 高版本向低版本兼容,多媒体相关,事件总线(订阅者模式),传感器,安全,插件化,文件

六.Android 高版本向低版本兼容 ActionBarSherlock 为Android所有版本提供统一的ActionBar,解决4.0以下ActionBar的适配问题 项目地址:https://github.com/JakeWharton/ActionBarSherlock Demo地址:https://play.google.com/store/apps/details?id=com.actionbarsherlock.sample.demos APP示例:太多了..现在连google都

WebGL 支持检测与已支持浏览器版本汇总

太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 是否我的浏览器支持 WebGL http://caniuse.com 在页面搜索 webgl,找到  WebGL - 3D Canvas grap

Database Control 在Oracle DB 11.2版本之后被放弃支持

参考自: Database Control To Be Desupported in DB Releases after 11.2 (Doc ID 1484775.1) 适用于: Oracle Server - Enterprise Edition Information in this document applies to any platform. 细节: Oracle宣布如下:Database Control (也称作DBControl or Database Console)将会在 O

win2008 iis7/iis7.5下最简单最强安装多版本PHP支持环境,以及解决主机宝php版本过低问题 支持不同网站不同php版本

利用PHP Manager,windows 2008 R2 IIS7.5安装多版本PHP环境 个人对在windows 2008 R2上,在iis环境中配置安装php环境实践中的注意点: 1.如需要在服务器上配置mysql的话,在配置php环境前先安装mysql.(根据自己的需求,下载相应的mysql版本) mysql安装配置步骤请查看:http://www.jb51.net/article/39188.htm sqlserver 2008安装:http://www.jb51.net/articl