├── .DS_Store ├── .gitattributes ├── .gitignore ├── README.md ├── build ├── .DS_Store ├── smusic.min.css └── smusic.min.js ├── demo ├── data │ ├── chengdu.lrc │ ├── diamonds.lrc │ ├── fightSong.lrc │ └── oneCallAway.lrc ├── index.html └── songList.js ├── gulpfile.js ├── package.json └── src ├── js ├── mo.js └── smusic.js └── scss └── smusic.scss /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/smusic/b3f20839042cd2ac7e92ba2ff9e485feba4e76e8/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /dist 4 | /.sass-cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smusic 2 | 3 | 一款基于HTML5、Css3的列表式音乐播放器,包含列表,音量,进度,时间,歌词展示以及播放模式等功能,不依赖任何库 4 | 5 | 6 | > html5音乐列表播放器
Author:Smohan
Version:2.0.1
url: [https://smohan.net/lab/smusic](https://smohan.net/lab/smusic) 7 | 8 | 9 | ![smuic](https://img.smohan.net/article/9615e13fcf51eae18dc4d40afaa9e0ab.jpg 'smusic') 10 | 11 | 12 | ### [项目地址][1] 13 | 14 | ### [DEMO][2] 15 | 16 | 17 | ### 使用方式 18 | 19 | #### (c)npm install 20 | 21 | #### gulp compile 22 | 23 | #### gulp build 24 | 25 | #### 使用 26 | 27 | 在``中加入 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | 在 ``中 创建DOM(SMUSIC容器) 34 | 35 | ```html 36 | 37 | ... 38 |
39 | ... 40 | 41 | ``` 42 | 43 | 创建musicList文件或者数组,歌曲列表格式如下 44 | ```javascript 45 | var songList = [ 46 | { 47 | title : '成都', 48 | singer : '赵雷', 49 | audio : 'http://m2.music.126.net/4gwWNLUdEZuPCKGUWWu_rw==/18720284975304502.mp3', 50 | thumbnail : 'http://p1.music.126.net/34YW1QtKxJ_3YnX9ZzKhzw==/2946691234868155.jpg', 51 | lyric : './data/chengdu.lrc' 52 | } 53 | ] 54 | ``` 55 | 56 | 在``前加入JS 57 | ```html 58 | 59 | 60 | 66 | ``` 67 | 68 | > 歌词需要服务器环境支持, 可以启动[http-server](https://github.com/indexzero/http-server)创建一个简单的服务器环境 69 | 70 | 71 | 72 | ### Options 73 | 74 | ```javascript 75 | { 76 | //放置Smusic的DOM容器 77 | container: doc.body, 78 | //初始化播放索引 79 | playIndex: 0, 80 | //初始化播放模式 (1 : 列表循环 2 : 随机播放 3 : 单曲循环) 81 | playMode: 1, 82 | //初始化音量 (0 - 1之间) 83 | volume: .5, 84 | //自动播放 85 | autoPlay: true, 86 | //默认显示面板 87 | panel: 'list' //['list' 列表面板, 'lyric' 歌词面板] 88 | } 89 | ``` 90 | 91 | ### API 92 | 93 | ```javascript 94 | //初始化播放器 95 | init() 96 | 97 | /** 98 | * 获取当前播放的歌曲信息 99 | * @returns {*} 100 | */ 101 | getCurrentInfo() 102 | 103 | /** 104 | * 设置播放模式 105 | * @param mode (1, 2, 3) 106 | */ 107 | setMode(mode = 1) 108 | 109 | /** 110 | * 设置音量 111 | * @param volume ( 0 <= volume <= 1) 112 | */ 113 | setVolume(volume = .5) 114 | 115 | /** 116 | * 向列表中追加音乐 117 | * @param music 118 | * @param callback 119 | */ 120 | addSong(music = {}, callback = noop) 121 | 122 | //刷新播放列表 123 | refreshList() 124 | 125 | /** 126 | * 下一首 127 | * @param callback 128 | */ 129 | next(callback) 130 | 131 | /** 132 | * 上一首 133 | * @param callback 134 | */ 135 | prev(callback) 136 | 137 | /** 138 | * 播放 139 | * @param callback 140 | */ 141 | play(callback) 142 | 143 | /** 144 | * 暂停 145 | * @param callback 146 | */ 147 | pause(callback) 148 | ``` 149 | 150 | ### 更新记录 151 | 152 | 版本:```2.0.1``` 153 | 代码重构(es6,scss,gulp等),新增向列表追加歌曲(addSong)、上一曲(next)、下一曲(prev)等对外接口,优化拖拽、歌词等功能,优化界面 154 | 155 | 版本:```2.0.0``` 156 | 增加歌词展示功能 157 | 158 | 版本:```1.0.3``` 159 | 增加拖动进度条,调整播放进度功能 160 | 161 | 版本:```1.0.2``` 162 | 新增```defaultMode```属性,控制初始化播放模式,新增```callback```回调方法,用于获取当前播放媒体文件信息 163 | 164 | 版本:```1.0.1``` 165 | 增加了是否自动播放的配置项开关 ```autoPlay```,灵活配置播放器启动时是否自动播放 166 | 167 | 168 | [1]: https://smohan.net/lab/smusic 169 | [2]: https://s-mohan.github.io/demo/smusic/index.html 170 | -------------------------------------------------------------------------------- /build/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/S-mohan/smusic/b3f20839042cd2ac7e92ba2ff9e485feba4e76e8/build/.DS_Store -------------------------------------------------------------------------------- /build/smusic.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * smusic v2.1.0 3 | * smohan https://smohan.net 4 | * The MIT License (MIT) 5 | * https://smohan.net/lab/smusic 6 | * https://github.com/S-mohan/smusic 7 | */ 8 | @font-face{font-family:SMusic;font-weight:400;font-style:normal;font-variant:normal;src:url(data:application/font-woff;base64,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) format("woff")}.smusic-container,.smusic-container *{box-sizing:border-box}.smusic-container{font:14px/1.5 'Helvetica Neue',Helvetica,'PingFang SC','Hiragino Sans GB','WenQuanYi Micro Hei','Microsoft Yahei',Arial,sans-serif;position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;overflow:hidden;width:640px;height:360px;padding:20px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;word-wrap:break-word;color:#333;background:#fff;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.smusic-container audio{display:none!important}.smusic-ctrl--mode,.smusic-ctrl--play,.smusic-volume--toggle,[class*=' smusic-ico-'],[class^=smusic-ico-]{font-family:SMusic!important;font-weight:400;font-style:normal;font-variant:normal;line-height:1;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.smusic-ico-lyric:before{content:'\e90c'}.smusic-ico-list:before{content:'\e904'}.smusic-ico-prev:before{content:'\e909'}.smusic-ico-next:before{content:'\e908'}.smusic-container>.smusic-main{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.smusic-container>.smusic-aside{display:-webkit-box;display:-ms-flexbox;display:flex;height:40px;margin-top:10px;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.smusic-container>.smusic-aside>.smusic-ctrl{white-space:nowrap}.smusic-container>.smusic-aside>.smusic-ctrl.smusic-ctrl--left{width:70px;padding-right:10px}.smusic-container>.smusic-aside>.smusic-ctrl.smusic-ctrl--right{width:140px;text-align:right}.smusic-container>.smusic-aside>.smusic-progress{width:1px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.smusic-container>.smusic-aside{color:#333}.smusic-container>.smusic-aside .smusic-ctrl--list,.smusic-container>.smusic-aside .smusic-ctrl--lyric,.smusic-container>.smusic-aside .smusic-ctrl--mode,.smusic-container>.smusic-aside .smusic-ctrl--volume,.smusic-container>.smusic-aside .smusic-time{font-size:24px;display:inline-block;margin-left:10px;vertical-align:middle}.smusic-container>.smusic-aside .smusic-ctrl--volume{margin-left:0}.smusic-container>.smusic-aside .smusic-volume--toggle{position:relative;z-index:3;top:3px}.smusic-container>.smusic-aside .smusic-volume--toggle:before{content:'\e90b'}.smusic-container>.smusic-aside .smusic-volume--toggle.smusic-volume--mute:before{content:'\e90a'}.smusic-container>.smusic-aside .smusic-ctrl--mode.smusic-mode--loop{position:relative}.smusic-container>.smusic-aside .smusic-ctrl--mode.smusic-mode--loop:before{content:'\e901'}.smusic-container>.smusic-aside .smusic-ctrl--mode.smusic-mode--single:before{content:'\e907'}.smusic-container>.smusic-aside .smusic-ctrl--mode.smusic-mode--random:before{content:'\e906'}.smusic-container>.smusic-aside .smusic-ctrl--list,.smusic-container>.smusic-aside .smusic-ctrl--lyric{position:relative;top:3px}.smusic-container>.smusic-aside .smusic-ctrl--list.active,.smusic-container>.smusic-aside .smusic-ctrl--lyric.active{color:#e03a3a}.smusic-container>.smusic-aside a{display:inline-block;cursor:pointer}.smusic-container>.smusic-aside .smusic-time{font-size:12px;margin-left:5px}.smusic-container>.smusic-aside>.smusic-progress{position:relative;height:5px;cursor:pointer;background-color:#333}.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--buffer,.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--slider,.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--value{position:absolute;top:0;left:0;height:100%;-webkit-transition:width .1s linear;transition:width .1s linear}.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--buffer{background-color:#777}.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--value{width:16px;min-width:16px;background-color:#e03a3a}.smusic-container>.smusic-aside>.smusic-progress>.smusic-progress--value>.smusic-progress--slider{position:absolute;top:50%;right:-1px;width:16px;height:16px;margin-top:-8px;cursor:-webkit-grab;cursor:grab;border-radius:50%;background-color:#fff;box-shadow:1px 1px 1px rgba(0,0,0,.35)}.smusic-container .smusic-ctrl--volume{position:relative}.smusic-container .smusic-ctrl--volume>.smusic-volume--bar{position:absolute;top:-100px;left:12px;visibility:hidden;width:3px;height:100px;cursor:pointer;-webkit-transition:all .2s;transition:all .2s;opacity:0;background-color:#333}.smusic-container .smusic-ctrl--volume>.smusic-volume--bar:after{position:absolute;z-index:0;top:-10%;left:-14px;width:31px;height:140%;content:'';background-color:rgba(0,0,0,.15)}.smusic-container .smusic-ctrl--volume>.smusic-volume--bar>.smusic-volume--value{position:absolute;z-index:2;bottom:0;left:0;width:100%;height:4px;background-color:#e03a3a}.smusic-container .smusic-ctrl--volume>.smusic-volume--bar>.smusic-volume--slider{position:absolute;z-index:3;top:0;left:50%;width:10px;height:10px;margin-left:-5px;cursor:-webkit-grab;cursor:grab;border-radius:50%;background-color:#fff;box-shadow:1px 1px 1px rgba(0,0,0,.15)}.smusic-container .smusic-ctrl--volume:hover>.smusic-volume--bar{visibility:visible;opacity:1}.smusic-container>.smusic-main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.smusic-container>.smusic-main>.smusic-panel{width:45%;position:relative;text-align:center}.smusic-container>.smusic-main>.smusic-panel:last-of-type{overflow:hidden;margin-left:20px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.smusic-container>.smusic-main .smusic-music-info{line-height:30px;position:relative;overflow:hidden;height:30px;white-space:nowrap}.smusic-container>.smusic-main .smusic-music-info>.smusic-music-scroll{position:absolute;top:0;left:0;min-width:100%;height:100%;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);text-align:center}.smusic-container>.smusic-main .smusic-music-info .smusic-music--title{font-size:18px}.smusic-container>.smusic-main .smusic-music-info .smusic-music--singer{font-size:14px;margin-left:5px}.smusic-container>.smusic-main .smusic-music-thumbnail{width:170px;height:170px;margin:15px auto;background:url(https://s-mohan.github.io/demo/static/img/smusic.jpg) center no-repeat;background-size:cover}.smusic-container>.smusic-main .smusic-music-thumbnail>img{display:block;width:100%;height:100%;-webkit-transition:all .2s ease;transition:all .2s ease;border-radius:50%}.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--next,.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--play,.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--prev{display:inline-block;cursor:pointer;-webkit-transition:opacity .1s ease-in-out;transition:opacity .1s ease-in-out}.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--next:hover,.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--play:hover,.smusic-container>.smusic-main .smusic-music-ctrl>.smusic-ctrl--prev:hover{opacity:.9}.smusic-container>.smusic-main .smusic-ctrl--next,.smusic-container>.smusic-main .smusic-ctrl--prev{font-size:36px}.smusic-container>.smusic-main .smusic-ctrl--play{font-size:48px;width:48px;margin:0 10px;text-align:center;vertical-align:top;position:relative;top:-2px}.smusic-container>.smusic-main .smusic-ctrl--play.smusic-music-pause:before{content:'\e903'}.smusic-container>.smusic-main .smusic-ctrl--play.smusic-music-play:before{content:'\e902'}.smusic-container .smusic-panel--scroll{position:absolute;top:0;left:0;display:-webkit-box;display:-ms-flexbox;display:flex;width:200%;height:100%;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.smusic-container .smusic-panel--scroll .smusic-list--wrap,.smusic-container .smusic-panel--scroll .smusic-lyric--wrap{position:relative;overflow:hidden;width:50%;height:100%;-ms-flex-preferred-size:50%;flex-basis:50%}.smusic-container .smusic-panel--scroll.show-list{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.smusic-container .smusic-panel--scroll.show-list .smusic-lyric--wrap{opacity:0}.smusic-container .smusic-list--wrap{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.smusic-container .smusic-lyric--wrap:after,.smusic-container .smusic-lyric--wrap:before{position:absolute;z-index:3;left:0;width:100%;height:25%;content:''}.smusic-container .smusic-lyric--wrap:before{top:0;background-image:-webkit-linear-gradient(top,#fff,rgba(255,255,255,0));background-image:linear-gradient(to bottom,#fff,rgba(255,255,255,0))}.smusic-container .smusic-lyric--wrap:after{bottom:0;background-image:-webkit-linear-gradient(bottom,#fff,rgba(255,255,255,0));background-image:linear-gradient(to top,#fff,rgba(255,255,255,0))}.smusic-container .smusic-lyric--wrap li{font-size:14px;-webkit-transition:all .2s;transition:all .2s;color:#555}.smusic-container .smusic-lyric--wrap li.active{font-size:15px;color:#e03a3a}.smusic-container .smusic-lyric--wrap li a{text-decoration:underline;color:#777}.smusic-container .smusic-lyric--wrap li.empty{font-size:24px;margin-top:90px;margin-bottom:10px;color:#bbb}.smusic-container .smusic-list--wrap>.smusic-list--scroll,.smusic-container .smusic-lyric--wrap>.smusic-lyric--scroll{position:absolute;top:0;left:0;width:100%;margin:0;padding:0;list-style:none;-webkit-transition:-webkit-transform .2s;transition:-webkit-transform .2s;transition:transform .2s;transition:transform .2s,-webkit-transform .2s;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.smusic-container .smusic-list--wrap>.smusic-list--scroll>li,.smusic-container .smusic-lyric--wrap>.smusic-lyric--scroll>li{line-height:30px;display:block;overflow:hidden;height:30px;white-space:nowrap;text-overflow:ellipsis}.smusic-container .smusic-panel--scroll .smusic-list--wrap{overflow-y:auto}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;margin:3px 0;padding-left:20px;cursor:pointer;-webkit-transition:color .1s linear;transition:color .1s linear;text-align:left;color:#555;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li:hover{color:#333}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate{font-size:0;position:absolute;top:5px;left:0;visibility:hidden;width:20px;height:14px}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate>i{position:relative;display:inline-block;width:3px;height:100%;margin-right:2px}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate>i:after{position:absolute;bottom:0;width:100%;height:100%;content:'';background-color:#e03a3a}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate>i:nth-of-type(1):after{-webkit-animation:waves .6s linear 0s infinite alternate;animation:waves .6s linear 0s infinite alternate}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate>i:nth-of-type(2):after{-webkit-animation:waves .9s linear 0s infinite alternate;animation:waves .9s linear 0s infinite alternate}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-animate>i:nth-of-type(3):after{-webkit-animation:waves 1.1s linear 0s infinite alternate;animation:waves 1.1s linear 0s infinite alternate}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-singer,.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-title{width:1px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li>.song-singer{width:30%;margin-left:5%;text-align:right}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li.active{color:#e03a3a}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li.active>.song-animate{visibility:visible}.smusic-container .smusic-panel--scroll .smusic-list--wrap>.smusic-list--scroll>li.pause>.song-animate i:after{-webkit-animation-play-state:paused;animation-play-state:paused}@-webkit-keyframes waves{10%{height:20%}20%{height:60%}40%{height:40%}50%{height:100%}100%{height:50%}}@keyframes waves{10%{height:20%}20%{height:60%}40%{height:40%}50%{height:100%}100%{height:50%}} -------------------------------------------------------------------------------- /build/smusic.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * smusic v2.1.0 3 | * smohan https://smohan.net 4 | * The MIT License (MIT) 5 | * https://smohan.net/lab/smusic 6 | * https://github.com/S-mohan/smusic 7 | */ 8 | function _classCallCheck(s,t){if(!(s instanceof t))throw new TypeError("Cannot call a class as a function")}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(s){return typeof s}:function(s){return s&&"function"==typeof Symbol&&s.constructor===Symbol&&s!==Symbol.prototype?"symbol":typeof s};!function(){"use strict";var s=Array.prototype,t=function(s){return s?s.trim().replace(/\s+/,",").split(","):[]},i=function(s){return s.ownerDocument.defaultView.getComputedStyle(s,null)},e={contains:function(s,t){return s.classList?s.classList.contains(t):new RegExp("(^| )"+t+"( |$)","gi").test(s.className)},add:function(s,i){t(i).forEach(function(t){(t=t.trim())&&!e.contains(s,t)&&(s.classList?s.classList.add(t):(s.className+=" "+t,s.className=s.className.trim()))})},remove:function(s,i){t(i).forEach(function(t){(t=t.trim())&&e.contains(s,t)&&(s.classList?s.classList.remove(t):(s.className=s.className.replace(new RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," "),s.className=s.className.trim()))})},toggle:function(s,i){t(i).forEach(function(t){return s.classList?s.classList.toggle(t):e.contains(s,t)?e.remove(s,t):e.add(s,t)})}},n=function(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s=Object(s);for(var t=1,i=arguments.length;t0&&void 0!==arguments[0]?arguments[0]:"*",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:document;if("string"==typeof t){t=t.trim();var e=[];return a.id.test(t)?(e=document.getElementById(RegExp.$1),e=e?[e]:[]):e=a.className.test(t)?i.getElementsByClassName(RegExp.$1):a.tagName.test(t)?i.getElementsByTagName(t):i.querySelectorAll(t),s.slice.call(e)}return[]},o=function(){var s=Element.prototype,t=s.matches||s.matchesSelector||s.mozMatchesSelector||s.msMatchesSelector||s.oMatchesSelector||s.webkitMatchesSelector;return function(s,i){return t.call(s,i)}}(),r=function(s,t){if("object"===(void 0===s?"undefined":_typeof(s))&&"function"==typeof t)if(Array.isArray(s))for(var i=0,e=s.length;i0&&void 0!==arguments[0]?arguments[0]:{},t=s.url||window.location.href,i=s.done||null,e=s.fail||null,n=s.data,a=d(n),l=s.type&&/^(get|post)$/i.test(s.type)?s.type.toUpperCase():"GET",o=new XMLHttpRequest,r=s.headers||{};r.accept="application/json, text/javascript","POST"===l?r["Content-Type"]="application/x-www-form-urlencoded; charset=UTF-8":(r["Content-Type"]="application/json; charset=UTF-8",t=t.indexOf("?")>-1?t+a:t,a=void 0),o.open(l,t,!0);for(var c in r)o.setRequestHeader(c,r[c]);o.onload=function(){if(o.status>=200&&o.status<400){var s=o.responseText;try{s=JSON.parse(s)}catch(s){}i&&i.call(null,s,o)}else e&&e.call(o.status,"error",o)},o.send(a)},h={$:function(s,t){return l(s,t)[0]},$$:l,matchSelector:o,addClass:e.add,removeClass:e.remove,hasClass:e.contains,toggleClass:e.toggle,getStyles:i,assign:Object.assign||n,each:r,bind:c,unbind:u,trigger:m,http:p};window.MoUtils=h}(),function(){"use strict";var s=document,t=window,i=MoUtils,e="ontouchend"in document,n="2.1.0",a="https://smohan.net/lab/smusic",l="https://s-mohan.github.io/demo/static/img/smusic.jpg",o=[i.$,i.$$,i.bind,function(){}],r=o[0],c=o[1],u=o[2],m=o[3],d=e?"touchstart":"mousedown",p=e?"touchmove":"mousemove",h=e?"touchend":"mouseup",v=function(e,n){var a=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"horizontal",l=arguments.length>3&&void 0!==arguments[3]?arguments[3]:m,o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:m,c=[e,r(n,e)],v=c[0],f=c[1],y={width:v.offsetWidth,height:v.offsetHeight};u(f,"click",function(s){return s.stopPropagation()}),u(f,d,function(e){e=e||t.event,e.stopPropagation();var n=void 0,r=void 0;n={x:e.clientX||e.touches[0].clientX,y:e.clientY||e.touches[0].clientY},r={x:this.offsetLeft,y:this.offsetTop};var c="horizontal"==a?y.width:y.height,m=function(s){s=s||t.event,s.stopPropagation();var i=s.clientX||s.touches[0].clientX,e=s.clientY||s.touches[0].clientY,o=Math.min(c,Math.max(0,"horizontal"==a?r.x+(i-n.x):r.y+(e-n.y)));l&&"function"==typeof l&&l(o)};u(s,p,m),u(s,h,function(t){o&&"function"==typeof o&&o(),i.unbind(s,p,m)})})},f={container:s.body,playIndex:0,playMode:1,volume:.5,autoPlay:!0,panel:"list"},y=1,g=function(s){throw new Error("Smusic Error:"+s)},b=function(s){return t.console.log("Smusic Log:"+s)},j=function(s){var t=void 0,i=void 0,e=void 0,n="";return t=String(parseInt(s/3600,10)),i=String(parseInt(s%3600/60,10)),e=String(parseInt(s%60,10)),"0"!=t&&(1==t.length&&(t="0"+t),n+=t+":"),1==i.length&&(i="0"+i),n+=i+":",1==e.length&&(e="0"+e),n+=e},x=function(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"list",t="list"==s;return'\n
\n
\n
\n
\n Smusic\n smohan\n
\n
\n
\n '+a+'\n
\n \x3c!--smusic start: music play ctrl https://smohan.net/lab/smusic --\x3e\n
\n \n \n \n
\n \x3c!--smusic end: music ctrl https://smohan.net/lab/smusic --\x3e\n
\n
\n
\n
\n
    \n
    \n
    \n
      \n
      \n
      \n
      \n
      \n'},C={},M=/\[\d*:\d*((\.|:)\d*)*]/g,w=function(s){var t=s.replace(/(\[\d*:\d*((\.|:)\d*)*])/gm,"\n$1").trim().replace(/\\n/gm,"\n").split("\n"),i={},e=0,n=void 0,a=t.length;for(e;e0&&void 0!==arguments[0]?arguments[0]:{},i=t.container,e=y++,l=s.createElement("div");return l.id="smohan-smusic-"+e,l.className="smusic-container",l.setAttribute("data-smusic-version",n),l.setAttribute("data-smusic-homepage",a),l.setAttribute("data-smusic-id",e.toString()),l.innerHTML=x(t.panel||"list")+'',i.appendChild(l),{element:l,id:e}},I=function(){var s=this.smusic;this.dom={scroll:{title:r(".js-smusic-scroll--title",s),panel:r(".js-smusic-scroll--panel",s),lyric:r(".js-smusic-scroll--lyric",this.panel),list:r(".js-smusic-scroll--list",this.panel)},song:{title:r(".js-smusic-song--title",s),singer:r(".js-smusic-song--singer",s),thumbnail:r(".js-smusic-song--thumbnail",s)},btn:{prev:r(".js-smusic-btn--prev",s),play:r(".js-smusic-btn--play",s),next:r(".js-smusic-btn--next",s),volume:r(".js-smusic-btn--volume",s),mode:r(".js-smusic-btn--mode",s),lyric:r(".js-smusic-btn--lyric",s),list:r(".js-smusic-btn--list",s)},time:r(".js-smusic-time",s),progress:{bar:r(".js-smusic-progress",s),buffer:r(".js-smusic-progress--buffer",this.bar),value:r(".js-smusic-progress--value",this.bar),slider:r(".js-smusic-progress--slider",this.bar)},volume:{bar:r(".js-smusic-volume--bar",s),value:r(".js-smusic-volume--value",this.bar),slider:r(".js-smusic-volume--slider",this.bar)}}},E=function(){var s=this,t=s.dom.scroll.list,i=s.playList,e="";i.forEach(function(t,i){var n=i===s.playIndex?" active":"";e+='
    • \n\n'+t.title+'\n'+t.singer+"\n
    • "}),t.innerHTML=e},T=function(){var s=this,t=s.dom.scroll.lyric.parentNode.offsetHeight,i="",e=0;L(this.playList,this.playIndex,function(n){if("number"==typeof n){switch(n){case 0:i+='
    • 暂无歌词
    • ';break;case-1:i+='
    • 歌词解析失败
    • ';break;case-2:i+='
    • 歌词加载失败
    • '}s.dom.scroll.lyric.innerHTML=i}else{for(var l in n){var o=30*e,r=o>=t/2-30,c=n[l]||a;"string"!=typeof c&&(c=c.content),n[l]={index:e,content:c,top:o,scrollTop:r?o-(t/2-30):0,toScroll:r},e++,i+="
    • "+c+"
    • "}s.dom.scroll.lyric.innerHTML=i+'
    • '+a+"
    • "}})},k={},A=function(){var s=[this.dom,this.audio,this.smusicId],t=s[0],i=s[1],e=s[2];k[e]&&clearInterval(k[e]);var n=parseFloat(t.progress.bar.offsetWidth);if(!isNaN(i.duration)){var a=j(i.duration);t.time.textContent="00:00/"+a}k[e]=setInterval(function(){var s=i.buffered.length;if(s>0&&void 0!=i.buffered){var a=i.buffered.end(s-1)/i.duration*n;t.progress.buffer.style.width=a+"px",Math.abs(i.duration-i.buffered.end(s-1))<1&&(t.progress.buffer.style.width=n+"px",clearInterval(k[e]))}},1e3)},$=function(){var s=[this,this.dom,this.audio],t=s[0],e=s[1],n=s[2],a=Math.round(e.progress.bar.offsetWidth),l=Math.round(e.volume.bar.offsetHeight);u(t.smusic,"click",".js-smusic-panel--tab",function(s){if(s.stopPropagation(),i.hasClass(this,"active"))return!1;var t=this.getAttribute("data-panel");i.addClass(this,"active"),"list"===t?(i.removeClass(e.btn.lyric,"active"),i.addClass(e.scroll.panel,"show-list")):(i.removeClass(e.btn.list,"active"),i.removeClass(e.scroll.panel,"show-list"))}),u(n,"timeupdate",function(){if(!isNaN(n.duration)){var s=j(n.currentTime),l=j(n.duration),o=n.currentTime/n.duration*a;e.time.textContent=s+"/"+l;var r=Math.min(o,a);e.progress.value.style.width=r+"px";var u="_smusic_lyric_"+t.playIndex,m=C[u];if(!m)return;var d=Math.round(n.currentTime),p=m[d];if(!p||"number"==typeof p)return;var h=p.index,v=p.toScroll,f=c("li",e.scroll.lyric)[h];c("li.active",e.scroll.lyric).forEach(function(s){s&&i.removeClass(s,"active")}),f&&i.addClass(f,"active"),e.scroll.lyric.style.transform=v?"translate3d(0, -"+p.scrollTop+"px, 0)":"translate3d(0, 0, 0)"}}),u(e.btn.mode,"click",function(){var s=this.getAttribute("data-play-mode"),i=void 0;switch(Number(s)){case 1:i=2;break;case 2:i=3;break;case 3:i=1}t.setMode(i)}),u(e.btn.play,"click",function(){i.hasClass(this,"smusic-music-play")?t.pause():t.play()}),u(n,"ended",function(){return t.playByMode("ended")}),u(e.btn.prev,"click",function(){return t.prev()}),u(e.btn.next,"click",function(){return t.next()}),v(e.progress.bar,".js-smusic-progress--slider","horizontal",function(s){var t=Math.max(0,Math.min(s/a,a));n.currentTime&&n.duration&&(n.currentTime=Math.round(t*n.duration))}),u(e.progress.bar,"click",function(s){s.stopPropagation();var i=this.getBoundingClientRect(),e=Math.min(a,Math.abs(s.clientX-i.left))/a;n.currentTime&&n.duration&&(n.currentTime=Math.round(e*n.duration),t.play())}),v(e.volume.bar,".js-smusic-volume--slider","vertical",function(s){var i=Number((l-s)/l);t.setVolume(i)}),u(e.btn.volume,"click",function(s){n.muted?(i.removeClass(this,"smusic-volume--mute"),this.setAttribute("title","静音"),n.muted=!1):(i.addClass(this,"smusic-volume--mute"),this.setAttribute("title","取消静音"),n.muted=!0)}),u(e.scroll.list,"click",".js-smusic-song--item",function(s){var e=this.getAttribute("data-song-index");i.hasClass(this,"active")?t.play():H.call(t,e)})},H=function(s,t,e){var n=[this.dom,this.audio,this.playList.length],a=n[0],o=n[1],r=n[2];s>=r-1&&(s=r-1),s<=0&&(s=0),this.playIndex=s;var u=this.playList[this.playIndex];if(!u)return b("没有要播放的歌曲"),!1;var m=function(){return A.call(this)}.bind(this);o.removeEventListener("canplay",m,!1),k[this.smusicId]&&clearInterval(k[this.smusicId]),a.progress.buffer.style.width="0px",a.progress.value.style.width="0px",a.time.textContent="00:00/00:00",a.scroll.lyric.style.transform="translate3d(0, 0, 0)",a.scroll.lyric.innerHTML='
    • 正在加载歌词...
    • ',c(".js-smusic-song--item.active",a.scroll.list).forEach(function(s){s&&i.removeClass(s,"active, pause")});var d=c(".js-smusic-song--item",a.scroll.list)[s];d&&i.addClass(d,"active"),o.src=u.audio,o.load(),o.addEventListener("canplay",m,!1),e?this.config.autoPlay&&o.play():o.play();var p=u.title||"Smusic",h=u.singer||"singer",v=u.thumbnail||l;a.song.title.textContent=p,a.song.singer.textContent=h,a.song.thumbnail.src=v,a.scroll.title.setAttribute("title",h+" - "+p),T.call(this),t&&t.call(this,u)},_=function(){function s(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};_classCallCheck(this,s),Array.isArray(t)||g("播放列表必须是一个数组"),this.playList=t,this.audio=null,this.version=n,this.config=i.assign({},f,e);var a=parseInt(this.config.playIndex),l=parseInt(this.config.playMode),o=parseFloat(this.config.volume);a<0&&(a=0),a>this.playList.length-1&&(a=this.playList.length-1),(l<1||l>3)&&(l=1),(o<0||o>1)&&(o=.5),this.playIndex=a,this.playMode=l,this.volume=o}return s.prototype.playByMode=function(s,t,i){var e=[Number(this.playMode),this.playIndex,this.playList.length],n=e[0],a=e[1],l=e[2],o=a;if(s||2==n)switch(n){case 1:"prev"==s?o=a<=l-1&&a>0?a-1:l-1:"next"!=s&&"ended"!=s||(o=a>=l-1?0:a+1);break;case 2:o=S(a,l);break;case 3:o="prev"==s?a<=l-1&&a>0?a-1:l-1:"next"==s?a>=l-1?0:a+1:a}else;H.call(this,o,t,i)},s.prototype.addSong=function(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:m;return s&&s.audio?(this.playList.push({title:s.title||"歌曲名",singer:s.singer||"歌手名",audio:s.audio,thumbnail:s.thumbnail||l,lyric:""|s.lyric}),this.refreshList(),t&&t()):t&&t("添加失败,参数不是一个对象或者未设置audio属性"),this.playList},s.prototype.refreshList=function(){E.call(this)},s.prototype.next=function(s){this.playByMode("next",s)},s.prototype.prev=function(s){this.playByMode("next",s)},s.prototype.play=function(s){var t=this.dom.btn.play;i.removeClass(t,"smusic-music-pause"),i.addClass(t,"smusic-music-play"),t.setAttribute("title","暂停");var e=r(".js-smusic-song--item.active",this.dom.scroll.list);i.removeClass(e,"pause"),this.playList.length&&this.audio.play(),s&&s.call(this,this.playList[this.playIndex])},s.prototype.pause=function(s){var t=this.dom.btn.play;i.addClass(t,"smusic-music-pause"),i.removeClass(t,"smusic-music-play");var e=r(".js-smusic-song--item.active",this.dom.scroll.list);i.addClass(e,"pause"),t.setAttribute("title","播放"),this.playList.length&&this.audio.pause(),s&&s.call(this,this.playList[this.playIndex])},s.prototype.setVolume=function(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:.5;s=Number(s),s<0&&(s=0),s>1&&(s=1);var t=this.dom.volume.bar.offsetHeight,e=this.dom.volume.slider.offsetHeight,n=t*s,a=Math.min((1-s)*t,t-e);0===s?(i.addClass(this.dom.btn.volume,"smusic-volume--mute"),this.dom.btn.volume.setAttribute("title","取消静音"),this.audio.muted=!0):(i.removeClass(this.dom.btn.volume,"smusic-volume--mute"),this.audio.muted=!1,this.dom.btn.volume.setAttribute("title","静音")),this.dom.volume.value.style.height=n+"px",this.dom.volume.slider.style.top=a+"px",this.volume=s,this.audio.volume=s},s.prototype.setMode=function(){var s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1;s=Number(s);var t=void 0,i=void 0,e=this.dom.btn.mode;switch(s){case 1:default:t="列表循环",i="smusic-mode--loop";break;case 2:t="随机播放",i="smusic-mode--random";break;case 3:t="单曲循环",i="smusic-mode--single"}e.setAttribute("data-play-mode",s.toString()),e.setAttribute("title",t),e.className=e.className.replace(/smusic-mode--\w+/,i),this.playMode=s},s.prototype.init=function(){var s=[this.config,N(this.config)],t=s[0],e=s[1];this.smusic=e.element,this.smusicId=e.id,this.audio=this.smusic.getElementsByTagName("audio")[0],I.call(this),E.call(this),$.call(this),this.setVolume(this.volume),this.setMode(this.playMode),this.playList.length?this.playByMode(void 0,function(){t.autoPlay||i.trigger(this.dom.btn.play,"click")},!0):b("歌曲列表为空")},s.prototype.getCurrentInfo=function(){if(this.playList.length){var s=this.playList[this.playIndex];return s.index=this.playIndex,s.volume=this.volume,s.mode=this.playMode,s}return null},s.prototype.destroy=function(){},s}();t.SMusic=function(s,t){return new _(s,t)}}(); -------------------------------------------------------------------------------- /demo/data/chengdu.lrc: -------------------------------------------------------------------------------- 1 | [00:00.55]赵雷 - 成都 [00:01.35]词曲:赵雷 [00:02.15]编曲:赵雷、喜子 [00:18.77]让我掉下眼泪的 不止昨夜的酒 [00:26.66]让我依依不舍的 不止你的温柔 [00:34.47]余路还要走多久 你攥着我的手 [00:42.43]让我感到为难的 是挣扎的自由 [00:52.25]分别总是在九月 回忆是思念的愁 [01:00.18]深秋嫩绿的垂柳 亲吻着我额头 [01:08.04]在那座阴雨的小城里 我从未忘记你 [01:15.95]成都 带不走的 只有你 [01:24.00]和我在成都的街头走一走 [01:31.86]直到所有的灯都熄灭了也不停留 [01:39.84]你会挽着我的衣袖 我会把手揣进裤兜 [01:47.65]走到玉林路的尽头 坐在小酒馆的门口 [02:31.20]分别总是在九月 回忆是思念的愁 [02:39.08]深秋嫩绿的垂柳 亲吻着我额头 [02:46.96]在那座阴雨的小城里 我从未忘记你 [02:54.88]成都 带不走的 只有你 [03:03.01]和我在成都的街头走一走 [03:10.86]直到所有的灯都熄灭了也不停留 [03:18.81]你会挽着我的衣袖 我会把手揣进裤兜 [03:26.62]走到玉林路的尽头 坐在小酒馆的门口 [03:38.48]和我在成都的街头走一走 [03:46.31]直到所有的灯都熄灭了也不停留 [03:54.15]和我在成都的街头走一走 [04:02.14]直到所有的灯都熄灭了也不停留 [04:10.21]你会挽着我的衣袖 我会把手揣进裤兜 [04:17.76]走到玉林路的尽头 走过小酒馆的门口 [04:36.09]和我在成都的街头走一走 [04:43.83]直到所有的灯都熄灭了也不停留 -------------------------------------------------------------------------------- /demo/data/diamonds.lrc: -------------------------------------------------------------------------------- 1 | [00:01.32]Shine bright like a diamond [00:06.73]Shine bright like a diamond [00:11.83]Find light in the beautiful sea [00:13.98]I choose to be happy [00:16.68]You and I [00:18.18]You and I [00:19.68]We're like diamonds in the sky [00:22.28]You're a shooting star I see [00:24.38]A vision of ecstasy [00:26.88]When you hold me [00:28.78]I'm alive [00:30.08]We're like diamonds in the sky [00:32.38]I knew that we'd become one right away [00:39.24]Oh, right away [00:42.84]At first sight I left the energy of sun rays [00:49.64]I saw the life inside your eyes [00:52.09]So shine bright [00:54.59]Tonight you and I [01:00.12]We're beautiful like diamonds in the sky [01:04.67]Eye to eye [01:07.32]So alive [01:10.57]We're beautiful like diamonds in the sky [01:14.52]Shine bright like a diamond [01:17.02]Shine bright like a diamond [01:19.62]Shining bright like a diamond [01:21.03]We're beautiful like diamonds in the sky [01:24.88]Shine bright like a diamond [01:27.43]Shine bright like a diamond [01:30.13]Shining bright like a diamond [01:31.43]We're beautiful like diamonds in the sky [01:35.48]Palms rise to the universe [01:37.28]As we moonshine and molly [01:40.14]Feel the warmth [01:41.19]We'll never die [01:43.14]We're like diamonds in the sky [01:45.49]You're a shooting star I see [01:48.37]A vision of ecstasy [01:50.32]When you hold me [01:52.27]I'm alive [01:53.62]We're like diamonds in the sky [01:55.87]At first sight I felt the energy of sun rays [02:02.68]I saw the life inside your eyes [02:04.83]So shine bright [02:07.63]Tonight you and I [02:13.13]We're beautiful like diamonds in the sky [02:17.68]Eye to eye [02:20.13]So alive [02:23.53]We're beautiful like diamonds in the sky [02:27.58]Shine bright like a diamond [02:30.18]Shine bright like a diamond [02:32.73]Shining bright like a diamond [02:34.23]We're beautiful like diamonds in the sky [02:37.89]Shine bright like a diamond [02:40.55]Shine bright like a diamond [02:43.15]Shining bright like a diamond [02:44.45]We're beautiful like diamonds in the sky [02:48.31]Shine bright like a diamond [02:50.91]Shine bright like a diamond [02:53.61]Shine bright like a diamond [02:57.01]So shine bright [02:59.82]Tonight you and I [03:05.27]We're beautiful like diamonds in the sky [03:09.88]Eye to eye [03:12.48]So alive [03:15.78]We're beautiful like diamonds in the sky [03:19.69]Shine bright like a diamond [03:22.29]Shine bright like a diamond [03:24.89]Shine bright like a diamond [03:30.14]Shine bright like a diamond [03:32.69]Shine bright like a diamond [03:35.35]Shine bright like a diamond [03:37.30]Shine bright like a diamond -------------------------------------------------------------------------------- /demo/data/fightSong.lrc: -------------------------------------------------------------------------------- 1 | [00:05.42]Like a small boat on the ocean [00:10.83]Sending big waves into motion [00:16.09]Like how a single word [00:18.54]Can make a heart open [00:19.94]I might only have one match [00:24.35]But I can make an explosion [00:27.30]And all those things I didn't say [00:30.11]Wrecking balls inside my brain [00:33.06]I will scream them loud tonight [00:35.67]Can you hear my voice? [00:37.52]This time this is my fight song [00:40.67]Take back my life song [00:43.42]Prove I'm alright song [00:49.19]My powers turned on [00:51.49]Starting right now I'll be strong [00:54.54]I'll play my fight song [00:57.89]And I don't really care [00:59.44]If nobody else believes [01:03.10]Cause I've still got [01:04.90]A lot of fight left in me [01:09.17]Losing friends and I'm chasing sleep [01:11.77]Everybody's worried about me [01:14.68]In too deep they say I'm in too deep [01:19.73]And it's been two years [01:21.34]And I miss my home [01:22.64]But there's a fire burning in my bones [01:25.39]And I still believe yeah I still believe [01:30.21]And all those things I didn't say [01:32.86]Wrecking balls inside my brain [01:35.57]I will scream them loud tonight [01:38.17]Can you hear my voice? [01:40.15]This time this is my fight song [01:43.55]Take back my life song [01:46.41]Prove I'm alright song [01:52.02]My powers turned on [01:54.37]Starting right now I'll be strong [01:57.07]I'll play my fight song [02:00.47]And I don't really care [02:02.17]If nobody else believes [02:06.23]Cause I've still got [02:07.58]A lot of fight left in me [02:13.24]A lot of fight left In me [02:21.89]Like a small boat on the ocean [02:27.10]Sending big waves into motion [02:32.19]Like how a single word [02:34.89]Can make a heart open [02:38.09]I might only have one match [02:43.54]But I can make an explosion [02:46.64]This time this is my fight song [02:49.05]Take back my life song [02:51.70]Prove I'm alright song [02:57.26]My powers turned on [02:59.91]Starting right now I'll be strong [03:02.61]I'll play my fight song [03:05.92]And I don't really care [03:07.67]If nobody else believes [03:11.47]Cause I've still got [03:12.97]A lot of fight left in me [03:17.28]Now I've still got a lot of fight left in me -------------------------------------------------------------------------------- /demo/data/oneCallAway.lrc: -------------------------------------------------------------------------------- 1 | [00:19.40]I'm only one call away [00:24.15]I'll be there to save the day [00:29.11]Superman got nothing on me [00:34.61]I'm only one call away [00:41.08]Call me baby if you need a friend [00:46.14]I just wanna give you love [00:49.06]Come on come on come on [00:51.27]Reaching out to you [00:52.77]So take a chance [00:55.48]No matter where you go [00:58.13]Know you're not alone [00:59.76]I'm only one call away [01:04.62]I'll be there to save the day [01:09.47]Superman got nothing on me [01:14.86]I'm only one call away [01:21.66]Come along with me [01:23.14]And don't be scared [01:26.59]I just wanna set you free [01:29.49]Come on come on come on [01:31.73]You and me can make it up [01:34.43]Be wild [01:35.75]For now we can stay here [01:37.95]For a while [01:40.72]Cause you know [01:41.87]I just wanna see your smile [01:46.20]No matter where you go [01:48.45]You know you're not alone [01:50.21]I'm only one call away [01:54.96]I'll be there to save the day [01:59.99]Superman got nothing on me [02:05.44]I'm only one call away [02:10.50]and when you're weak I'll be strong [02:15.50]I'm gonna keep holding on [02:20.66]Now don't you won't be [02:22.81]won't be long [02:25.17]Darling [02:26.33]If you feel like hope is gone [02:28.78]Just run into my arms [02:30.75]I'm only one call away [02:35.40]I'll be there to save the day [02:40.45]Superman got nothing on me [02:45.88]I'm only one [02:48.37]I'm only one call away [02:55.67]I'll be there to save the day [03:00.67]Superman got nothing on me [03:05.97]I'm only one call away [03:11.12]I'm only one call away -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Smuisc - 水墨寒的博客 9 | 10 | 11 | 75 | 76 | 77 | 78 | 79 |

      Smusic

      80 |

      项目地址: https://smohan.net/lab/smusic

      81 |
      82 | 84 | 86 |
      87 |
      88 |
      89 |
      90 | 91 | 92 |
      93 |

      请打开控制台(F12)查看

      94 |
      95 | 96 | 97 | 98 | 99 | 100 | 101 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /demo/songList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by smohan on 2016/11/19. 3 | */ 4 | var songList = [ 5 | { 6 | title : '成都', 7 | singer : '赵雷', 8 | audio : 'http://m2.music.126.net/4gwWNLUdEZuPCKGUWWu_rw==/18720284975304502.mp3', 9 | thumbnail : 'http://p1.music.126.net/34YW1QtKxJ_3YnX9ZzKhzw==/2946691234868155.jpg', 10 | lyric : './data/chengdu.lrc' 11 | }, 12 | { 13 | title : 'Diamonds', 14 | singer : 'Rihanna', 15 | audio : 'http://m2.music.126.net/vmNy7zVcQuBIPzZh4zf4Bg==/1890060488175269.mp3', 16 | thumbnail : 'http://p1.music.126.net/vrJzlA1RIiwaPHlXzZy90g==/3263350514797284.jpg', 17 | lyric : './data/diamonds.lrc' 18 | }, 19 | { 20 | title : 'Fight Song', 21 | singer : 'Rachel Platten', 22 | audio : 'http://m2.music.126.net/OHIRY7VSN7iaHq6KScmgRA==/7836219371566858.mp3', 23 | thumbnail : 'http://p1.music.126.net/1pT1uMs8EzCCUpAw6XkuCw==/7890095441301282.jpg', 24 | lyric : './data/fightSong.lrc' 25 | }, 26 | { 27 | title : 'One Call Away', 28 | singer : 'Charlie Puth', 29 | audio : 'http://m2.music.126.net/tHLOQmayYY0SuM7pQikJ5Q==/3243559306098715.mp3', 30 | thumbnail : 'http://p1.music.126.net/VzNnyyZwxYPEFXMx621s9w==/2533274793679228.jpg', 31 | lyric : './data/oneCallAway.lrc' 32 | }, 33 | ]; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by smohan on 2016/11/15. 3 | */ 4 | "use strict"; 5 | const gulp = require('gulp'), 6 | sass = require('gulp-sass'), 7 | rename = require('gulp-rename'), 8 | clean = require('gulp-clean'), 9 | plumber = require('gulp-plumber'), 10 | cleanCss = require('gulp-clean-css'), 11 | autoprefixer = require('gulp-autoprefixer'), 12 | uglify = require('gulp-uglify'), 13 | order = require('gulp-order'), 14 | concat = require("gulp-concat"), 15 | header = require('gulp-header'), 16 | sourcemaps = require("gulp-sourcemaps"), 17 | babel = require("gulp-babel"); 18 | 19 | const pkg = require('./package.json'); 20 | 21 | const CONFIGS = { 22 | scss: { 23 | lineNumbers: true, 24 | //style : 'expanded', 25 | sourcemap: false 26 | }, 27 | css: { 28 | advanced: true, 29 | keepBreaks: false, 30 | mediaMerging: true, 31 | keepSpecialComments: '*' 32 | }, 33 | js: { 34 | mangle: true, 35 | compress: true, 36 | preserveComments: 'some' 37 | } 38 | }; 39 | 40 | gulp.task('clean', () => { 41 | return gulp.src(['dist/', 'build/']) 42 | .pipe(clean()); 43 | }); 44 | 45 | //编译scss文件 46 | gulp.task('compile:css', () => { 47 | 48 | return gulp.src('src/scss/**/*.scss') 49 | .pipe(sass().on('error', sass.logError)) 50 | .pipe(autoprefixer({ 51 | browsers: ['last 2 versions', 'Android >= 4.0'], 52 | cascade: true, 53 | remove: true 54 | })) 55 | .pipe(gulp.dest('dist/css')) 56 | }); 57 | 58 | //编译es6文件 59 | gulp.task('compile:js', () => { 60 | return gulp.src('src/js/**/*.js') 61 | .pipe(plumber()) 62 | .pipe(sourcemaps.init()) 63 | .pipe(babel({ 64 | presets: [ 65 | ['es2015', { 66 | modules: false, 67 | loose: true 68 | }] 69 | ] 70 | })) 71 | .pipe(sourcemaps.write(".")) 72 | .pipe(gulp.dest('dist/js')); 73 | }); 74 | 75 | //编辑 => dist 76 | gulp.task('compile', ['compile:js', 'compile:css']); 77 | 78 | //banner 79 | const Banner = [ 80 | '/*!', 81 | ' * <%= pkg.name %> v<%= pkg.version %>', 82 | ' * <%= pkg.author %>', 83 | ' * The <%= pkg.license %> License (<%= pkg.license %>)', 84 | ' * https://smohan.net/lab/smusic', 85 | ' * <%= pkg.homepage%>', 86 | ' */', 87 | '' 88 | ].join('\n'); 89 | 90 | //min css 91 | gulp.task('build:css', () => { 92 | return gulp.src('dist/css/smusic.css') 93 | .pipe(cleanCss(CONFIGS.css)) 94 | .pipe(rename('smusic.min.css')) 95 | .pipe(header(Banner, { 96 | pkg: pkg 97 | })) 98 | .pipe(gulp.dest('build/')); 99 | }); 100 | 101 | //concat && min js 102 | gulp.task('build:js', () => { 103 | return gulp.src('dist/js/**/*.js') 104 | .pipe(order([ 105 | 'mo.js', 106 | 'smusic.js', 107 | ])) 108 | .pipe(concat("smusic.min.js")) 109 | .pipe(uglify(CONFIGS.js)) 110 | .pipe(header(Banner, { 111 | pkg: pkg 112 | })) 113 | .pipe(gulp.dest('build/')); 114 | }); 115 | 116 | //min => build 117 | gulp.task('build', ['build:js', 'build:css']); 118 | 119 | 120 | 121 | gulp.task('watch', () => { 122 | gulp.watch('src/js/**/*.js', ['compile:js']); 123 | gulp.watch('src/scss/**/*.scss', ['compile:css']); 124 | }); 125 | 126 | gulp.task('default', ['watch']); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smusic", 3 | "version": "2.1.0", 4 | "description": "html5 music player", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/S-mohan/smusic.git" 12 | }, 13 | "keywords": [ 14 | "smusic", 15 | "html5", 16 | "audio" 17 | ], 18 | "author": "smohan https://smohan.net", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/S-mohan/smusic/issues" 22 | }, 23 | "homepage": "https://github.com/S-mohan/smusic", 24 | "dependencies": { 25 | "babel-preset-es2015": "^6.18.0", 26 | "gulp": "^3.9.1", 27 | "gulp-autoprefixer": "^3.1.1", 28 | "gulp-babel": "^6.1.2", 29 | "gulp-clean": "^0.3.2", 30 | "gulp-clean-css": "^2.0.13", 31 | "gulp-concat": "^2.6.1", 32 | "gulp-header": "^1.8.8", 33 | "gulp-make-css-url-version": "^0.0.13", 34 | "gulp-order": "^1.1.1", 35 | "gulp-plumber": "^1.1.0", 36 | "gulp-rename": "^1.2.2", 37 | "gulp-sass": "^3.1.0", 38 | "gulp-sourcemaps": "^2.2.0", 39 | "gulp-uglify": "^2.0.0" 40 | } 41 | } -------------------------------------------------------------------------------- /src/js/mo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * mo v0.0.1 3 | * author : smohan 4 | * The MIT License (MIT) 5 | * Copyright (c) 2016 https://smohan.net 6 | */ 7 | (() => { 8 | 9 | 'use strict'; 10 | 11 | const ArrayProto = Array.prototype 12 | 13 | /** 14 | * 过滤样式,返回样式数组 15 | * (classA classB ...) | (classA, classB ...) => [classA, classB] 16 | * @param className 17 | * @returns {Array} 18 | */ 19 | const filterClassName = className => !className ? [] : className.trim().replace(/\s+/, ',').split(',') 20 | 21 | /** 22 | * 获取元素计算后的样式 23 | * @param {HTMLElement} element 24 | * @return {Object} 25 | */ 26 | const getComputedStyles = element => element.ownerDocument.defaultView.getComputedStyle(element, null) 27 | 28 | 29 | /** 30 | * CLASSES 样式操作 31 | * @type {Object} 32 | */ 33 | const CLASSES = { 34 | contains(element, className) { 35 | if (element.classList) { 36 | return element.classList.contains(className) 37 | } else { 38 | return new RegExp('(^| )' + className + '( |$)', 'gi').test(element.className) 39 | } 40 | }, 41 | add(element, className) { 42 | filterClassName(className).forEach(name => { 43 | name = name.trim(); 44 | if (name && !CLASSES.contains(element, name)) { 45 | if (element.classList) { 46 | element.classList.add(name) 47 | } else { 48 | element.className += ' ' + name 49 | element.className = element.className.trim() 50 | } 51 | } 52 | }); 53 | }, 54 | remove(element, className) { 55 | filterClassName(className).forEach(name => { 56 | name = name.trim(); 57 | if (name && CLASSES.contains(element, name)) { 58 | if (element.classList) { 59 | element.classList.remove(name) 60 | } else { 61 | element.className = element.className.replace(new RegExp('(^|\\b)' + name.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 62 | element.className = element.className.trim() 63 | } 64 | } 65 | }); 66 | }, 67 | toggle(element, className) { 68 | filterClassName(className).forEach(name => { 69 | if (element.classList) { 70 | return element.classList.toggle(name) 71 | } else { 72 | return CLASSES.contains(element, name) ? 73 | CLASSES.remove(element, name) : 74 | CLASSES.add(element, name) 75 | } 76 | }); 77 | }, 78 | } 79 | 80 | /** 81 | * 拷贝源对象到目标对象 82 | * Polyfill https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign 83 | * @param target 84 | * @returns {{}} 85 | */ 86 | const assign = function (target = {}) { 87 | target = Object(target) 88 | for (let i = 1, len = arguments.length; i < len; i++) { 89 | let source = arguments[i] 90 | if (source != null) { 91 | for (let key in source) { 92 | if (Object.prototype.hasOwnProperty.call(source, key)) { 93 | target[key] = source[key] 94 | } 95 | } 96 | } 97 | } 98 | return target 99 | } 100 | 101 | const SelectorRegs = { 102 | id: /^#([\w-]+)$/, 103 | className: /^\.([\w-]+)$/, 104 | tagName: /^[\w-]+$/ 105 | } 106 | 107 | /** 108 | * 获取元素对象集合 109 | * @param selector 选择器 110 | * @param context 父级上下文 111 | * @returns {*} 112 | */ 113 | const qsa = (selector = '*', context = document) => { 114 | if (typeof selector === "string") { 115 | selector = selector.trim() 116 | let dom = []; 117 | if (SelectorRegs.id.test(selector)) { 118 | dom = document.getElementById(RegExp.$1) 119 | dom = dom ? [dom] : [] 120 | } else if (SelectorRegs.className.test(selector)) { 121 | dom = context.getElementsByClassName(RegExp.$1) 122 | } else if (SelectorRegs.tagName.test(selector)) { 123 | dom = context.getElementsByTagName(selector) 124 | } else { 125 | dom = context.querySelectorAll(selector) 126 | } 127 | return ArrayProto.slice.call(dom) 128 | } 129 | return [] 130 | } 131 | 132 | 133 | /** 134 | * matchSelector兼容模式 135 | * @param element 136 | * @param selector 137 | * @returns {*} 138 | */ 139 | const matchSelector = (() => { 140 | const prop = Element.prototype; 141 | const matchesSelector = 142 | prop.matches || 143 | prop.matchesSelector || 144 | prop.mozMatchesSelector || 145 | prop.msMatchesSelector || 146 | prop.oMatchesSelector || 147 | prop.webkitMatchesSelector 148 | return (element, selector) => matchesSelector.call(element, selector) 149 | })(); 150 | 151 | /** 152 | * 对象遍历 153 | * @param object 154 | * @param callback 155 | */ 156 | const each = (object, callback) => { 157 | if (typeof object === "object" && typeof callback === "function") { 158 | if (Array.isArray(object)) { 159 | for (let i = 0, len = object.length; i < len; i++) { 160 | if (callback.call(object[i], i, object[i]) === false) { 161 | break 162 | } 163 | } 164 | } else if ('length' in object && typeof object.length === "number") { //这地方不太严谨,谨慎使用 165 | for (let k in object) { 166 | if (callback.call(object[k], k, object[k]) === false) { 167 | break 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * 事件绑定,支持简单事件代理 176 | * @param element 177 | * @param eventType 178 | * @param selector 179 | * @param callback 180 | */ 181 | const bind = (element, eventType, selector, callback) => { 182 | let sel, handler; 183 | if (typeof selector === "function") { 184 | handler = selector 185 | } else if (typeof selector === "string" && typeof callback === "function") { 186 | sel = selector 187 | } else { 188 | return 189 | } 190 | if (sel) { //事件代理 191 | handler = function (e) { 192 | //todo, 多选择器支持 193 | const nodes = qsa(sel, element) 194 | let matched = false 195 | for (let i = 0, len = nodes.length; i < len; i++) { 196 | const node = nodes[i] 197 | if (node === e.target || node.contains(e.target)) { 198 | matched = node 199 | break 200 | } 201 | } 202 | if (matched) { 203 | callback.apply(matched, ArrayProto.slice.call(arguments)) 204 | } 205 | } 206 | } 207 | 208 | element.addEventListener(eventType, handler, false) 209 | } 210 | 211 | /** 212 | * 事件解绑 213 | * @param element 214 | * @param eventType 215 | * @param callback 216 | */ 217 | const unbind = (element, eventType, callback) => element.removeEventListener(eventType, callback, false) 218 | 219 | /** 220 | * 事件触发 221 | * @param element 222 | * @param eventName 223 | */ 224 | const trigger = (element, eventName) => { 225 | const event = document.createEvent('HTMLEvents') 226 | event.initEvent(eventName, true, false) 227 | element.dispatchEvent(event) 228 | } 229 | 230 | 231 | /** 232 | * 将字面量对象转为URL字符串 233 | * @param data 234 | * @returns {string} 235 | */ 236 | const paramStringify = data => { 237 | const arr = []; 238 | let key, value; 239 | for (key in data) { 240 | value = data[key] 241 | if (value) { 242 | arr.push(key + '=' + value.toString()) 243 | } 244 | } 245 | return arr.join('&') 246 | } 247 | 248 | /** 249 | * http请求 250 | * @param options 251 | */ 252 | const http = (options = {}) => { 253 | let url = options.url || window.location.href, 254 | done = options.done || null, 255 | fail = options.fail || null, 256 | data = options.data, 257 | dataString = paramStringify(data), 258 | method = options.type && /^(get|post)$/i.test(options.type) ? options.type.toUpperCase() : 'GET' 259 | const xhr = new XMLHttpRequest(), 260 | headers = options.headers || {} 261 | headers.accept = "application/json, text/javascript" 262 | if (method === 'POST') { 263 | headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' 264 | } else { 265 | headers['Content-Type'] = 'application/json; charset=UTF-8' 266 | url = url.indexOf('?') > -1 ? (url + dataString) : url 267 | dataString = undefined 268 | } 269 | xhr.open(method, url, true) 270 | for (let i in headers) { 271 | xhr.setRequestHeader(i, headers[i]) 272 | } 273 | xhr.onload = function () { 274 | if (xhr.status >= 200 && xhr.status < 400) { 275 | let response = xhr.responseText 276 | try { 277 | response = JSON.parse(response) 278 | } catch (e) { } 279 | done && done.call(null, response, xhr) 280 | } else { 281 | fail && fail.call(xhr.status, 'error', xhr) 282 | } 283 | } 284 | xhr.send(dataString) 285 | } 286 | 287 | const utils = { 288 | $: (selector, context) => qsa(selector, context)[0], 289 | $$: qsa, 290 | matchSelector, 291 | addClass: CLASSES.add, 292 | removeClass: CLASSES.remove, 293 | hasClass: CLASSES.contains, 294 | toggleClass: CLASSES.toggle, 295 | getStyles: getComputedStyles, 296 | assign: Object.assign || assign, 297 | each, 298 | bind, 299 | unbind, 300 | trigger, 301 | http 302 | } 303 | 304 | window.MoUtils = utils 305 | 306 | })(); -------------------------------------------------------------------------------- /src/js/smusic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * smusic v2.1.0 3 | * author : smohan 4 | * The MIT License (MIT) 5 | * Copyright (c) 2016 https://smohan.net. 6 | * https://smohan.net/lab/smusic 7 | * https://github.com/S-mohan/smusic 8 | */ 9 | 10 | (() => { 11 | "use strict"; 12 | const [doc, win, utils, isTouch] = [document, window, MoUtils, ("ontouchend" in document)]; 13 | const [version, homepage, thumbnailPlaceholder] = ['2.1.0', 'https://smohan.net/lab/smusic', 'https://s-mohan.github.io/demo/static/img/smusic.jpg']; 14 | const [$, $$, bind, noop] = [utils.$, utils.$$, utils.bind, function () { }]; 15 | 16 | const 17 | EVENT_START = isTouch ? 'touchstart' : 'mousedown', 18 | EVENT_MOVE = isTouch ? 'touchmove' : 'mousemove', 19 | EVENT_END = isTouch ? 'touchend' : 'mouseup'; 20 | 21 | /** 22 | * 模拟一个滑块方法 23 | * @param element 进度条容器 24 | * @param sliderSelector 滑块选择器 25 | * @param direction 滑动方向 horizontal | vertical 26 | * @param callback 移动时回调移动距离 27 | * @param end 结束滑动时回调 28 | */ 29 | const Range = (element, sliderSelector, direction = 'horizontal', callback = noop, end = noop) => { 30 | const [$bar, $slider] = [element, $(sliderSelector, element)]; 31 | const barSize = { 32 | width: $bar.offsetWidth, 33 | height: $bar.offsetHeight 34 | }; 35 | bind($slider, 'click', event => event.stopPropagation()); 36 | bind($slider, EVENT_START, function (event) { 37 | event = event || win.event; 38 | event.stopPropagation(); 39 | let start, offset; 40 | start = { 41 | x: event.clientX || event.touches[0].clientX, 42 | y: event.clientY || event.touches[0].clientY 43 | }; 44 | offset = { 45 | x: this.offsetLeft, 46 | y: this.offsetTop 47 | }; 48 | const MAX = (direction == "horizontal") ? barSize.width : barSize.height; 49 | const moveHandle = function (event) { 50 | event = event || win.event; 51 | event.stopPropagation(); 52 | let thisX = event.clientX || event.touches[0].clientX, 53 | thisY = event.clientY || event.touches[0].clientY; 54 | //x : left to right 55 | //y : bottom to top 56 | const range = Math.min(MAX, Math.max(0, ((direction == "horizontal") ? (offset.x + (thisX - start.x)) : (offset.y + (thisY - start.y))))); 57 | (callback && typeof callback == "function") && callback(range); 58 | }; 59 | bind(doc, EVENT_MOVE, moveHandle); 60 | bind(doc, EVENT_END, function (event) { 61 | (end && typeof end == "function") && end(); 62 | utils.unbind(doc, EVENT_MOVE, moveHandle); 63 | }); 64 | }); 65 | }; 66 | 67 | /** 68 | * 默认配置项 69 | * @type {{container: HTMLElement, playIndex: number, playMode: number, volume: number, autoPlay: boolean}} 70 | */ 71 | const defaultConfig = { 72 | //放置Smusic的DOM容器 73 | container: doc.body, 74 | //初始化播放索引 75 | playIndex: 0, 76 | //初始化播放模式 (1 : 列表循环 2 : 随机播放 3 : 单曲循环) 77 | playMode: 1, 78 | //初始化音量 (0 - 1之间) 79 | volume: .5, 80 | //自动播放 81 | autoPlay: true, 82 | //加载时默认显示面板,['list', 'lyric'] 83 | panel: 'list' 84 | }; 85 | 86 | let uid = 1; 87 | 88 | 89 | /** 90 | * 抛出异常 91 | * @param message 92 | */ 93 | const error = message => { 94 | throw new Error("Smusic Error:" + message); 95 | }; 96 | 97 | 98 | /** 99 | * 输出log 100 | * @param message 101 | */ 102 | const log = message => win.console.log("Smusic Log:" + message); 103 | 104 | 105 | /** 106 | * 格式化时间 107 | * @param time 108 | * @returns {string} 109 | */ 110 | const calcTime = time => { 111 | let hour, minute, second, times = ''; 112 | hour = String(parseInt(time / 3600, 10)); 113 | minute = String(parseInt((time % 3600) / 60, 10)); 114 | second = String(parseInt(time % 60, 10)); 115 | if (hour != '0') { 116 | if (hour.length == 1) hour = '0' + hour; 117 | times += (hour + ':'); 118 | } 119 | if (minute.length == 1) minute = '0' + minute; 120 | times += (minute + ':'); 121 | if (second.length == 1) second = '0' + second; 122 | times += second; 123 | return times; 124 | }; 125 | 126 | 127 | /** 128 | * [获取模板] 129 | * @param {String} panel [默认显示面板] 130 | * @return {String} 131 | */ 132 | const __getSmusicTpl = (panel = 'list') => { 133 | 134 | const isList = panel == 'list' 135 | const panelClass = isList ? 'show-list' : '' 136 | 137 | return ` 138 |
      139 |
      140 |
      141 |
      142 | Smusic 143 | smohan 144 |
      145 |
      146 |
      147 | ${homepage} 148 |
      149 | 150 |
      151 | 152 | 153 | 154 |
      155 | 156 |
      157 |
      158 |
      159 |
      160 |
        161 |
        162 |
        163 |
          164 |
          165 |
          166 |
          167 |
          168 | ` 189 | } 190 | 191 | 192 | 193 | //歌词缓存 194 | const _lyricCache = {}; 195 | const timeReg = /\[\d*:\d*((\.|:)\d*)*]/g; 196 | 197 | 198 | /** 199 | * 解析歌词 200 | * @param lyric 201 | * @returns {{}} 202 | * @private 203 | */ 204 | const _parseLyric = function (lyric) { 205 | // 将歌词通过换行符转换为数组, 206 | // 每一行格式如 [00:00.00] 作曲 : 赵雷 207 | const lyricRows = lyric 208 | .replace(/(\[\d*:\d*((\.|:)\d*)*])/gm, '\n$1').trim() // 草,酷狗的歌词竟然是一坨,没有换行符,这里要专门处理 209 | .replace(/\\n/gm, '\n') 210 | .split('\n'); 211 | const lyricData = {}; //时间为key, 歌词做value 212 | let i = 0, 213 | content, len = lyricRows.length; 214 | for (i; i < len; i++) { 215 | content = decodeURIComponent(lyricRows[i]); 216 | if (typeof content !== "string") break; 217 | const timeRegArr = content.match(timeReg); 218 | if (!timeRegArr) continue; 219 | for (let i = 0, len = timeRegArr.length; i < len; i++) { 220 | const t = timeRegArr[i]; 221 | const 222 | minute = Number(String(t.match(/\[\d*/i)).slice(1)), 223 | second = Number(String(t.match(/\:\d*/i)).slice(1)); 224 | const time = minute * 60 + second; 225 | lyricData[time] = content.replace(timeReg, ''); //内容区去掉时间 226 | } 227 | } 228 | return lyricData; 229 | }; 230 | 231 | /** 232 | * 加载歌词 233 | * @param playList 234 | * @param index 235 | * @param callback 236 | * @private 237 | */ 238 | const _getLyric = function (playList, index, callback) { 239 | const song = playList[index]; 240 | const lyricUrl = song['lyric']; 241 | const cacheName = '_smusic_lyric_' + index; 242 | if (!lyricUrl) { 243 | _lyricCache[cacheName] = 0; //eof 244 | } 245 | if (!_lyricCache[cacheName]) { 246 | utils.http({ 247 | type: 'GET', 248 | url: lyricUrl, 249 | done: function (response) { 250 | if (response) { 251 | response = _parseLyric(response); 252 | } else { 253 | response = -1; //empty 254 | } 255 | _lyricCache[cacheName] = response; 256 | callback(_lyricCache[cacheName]); 257 | }, 258 | fail: error => { 259 | _lyricCache[cacheName] = -2; //fail 260 | callback(_lyricCache[cacheName]); 261 | } 262 | }) 263 | } 264 | callback(_lyricCache[cacheName]); 265 | }; 266 | 267 | /** 268 | * 获取一个不包含当期索引在内的随机索引 269 | * @param playIndex 270 | * @param len 271 | * @returns {*} 272 | * @private 273 | */ 274 | const _getRandomIndex = function (playIndex, len) { 275 | let array = []; 276 | for (let i = 0; i < len; i++) { 277 | if (i != playIndex) { 278 | array.push(i); 279 | } 280 | } 281 | const random = parseInt(Math.random() * (len - 1)); 282 | const index = array[random]; 283 | array = null; 284 | return index; 285 | }; 286 | 287 | /** 288 | * 创建DOM 289 | * @param container 290 | * @returns {{element: Element, id: number}} 291 | * @private 292 | */ 293 | const _createDom = (options = {}) => { 294 | const container = options.container; 295 | 296 | let id = uid++; 297 | const smusic = doc.createElement('div'); 298 | smusic.id = 'smohan-smusic-' + id; 299 | smusic.className = 'smusic-container'; 300 | smusic.setAttribute('data-smusic-version', version); 301 | smusic.setAttribute('data-smusic-homepage', homepage); 302 | smusic.setAttribute('data-smusic-id', id.toString()); 303 | smusic.innerHTML = __getSmusicTpl(options.panel || 'list') + ''; 304 | container.appendChild(smusic); 305 | return { 306 | element: smusic, 307 | id: id 308 | }; 309 | }; 310 | 311 | 312 | /** 313 | * 缓存Dom 314 | * @returns {__domCache} 315 | * @private 316 | */ 317 | const __domCache = function () { 318 | const smusic = this.smusic; 319 | this.dom = { 320 | scroll: { 321 | title: $('.js-smusic-scroll--title', smusic), 322 | panel: $('.js-smusic-scroll--panel', smusic), 323 | lyric: $('.js-smusic-scroll--lyric', this.panel), 324 | list: $('.js-smusic-scroll--list', this.panel), 325 | }, 326 | song: { 327 | title: $('.js-smusic-song--title', smusic), 328 | singer: $('.js-smusic-song--singer', smusic), 329 | thumbnail: $('.js-smusic-song--thumbnail', smusic) 330 | }, 331 | btn: { 332 | prev: $('.js-smusic-btn--prev', smusic), 333 | play: $('.js-smusic-btn--play', smusic), 334 | next: $('.js-smusic-btn--next', smusic), 335 | volume: $('.js-smusic-btn--volume', smusic), 336 | mode: $('.js-smusic-btn--mode', smusic), 337 | lyric: $('.js-smusic-btn--lyric', smusic), 338 | list: $('.js-smusic-btn--list', smusic) 339 | }, 340 | time: $('.js-smusic-time', smusic), 341 | progress: { 342 | bar: $('.js-smusic-progress', smusic), 343 | buffer: $('.js-smusic-progress--buffer', this.bar), 344 | value: $('.js-smusic-progress--value', this.bar), 345 | slider: $('.js-smusic-progress--slider', this.bar) 346 | }, 347 | volume: { 348 | bar: $('.js-smusic-volume--bar', smusic), 349 | value: $('.js-smusic-volume--value', this.bar), 350 | slider: $('.js-smusic-volume--slider', this.bar) 351 | } 352 | }; 353 | }; 354 | 355 | 356 | /** 357 | * 渲染列表 358 | * @private 359 | */ 360 | const __renderList = function () { 361 | const self = this, 362 | list = self.dom.scroll.list, 363 | data = self.playList; 364 | let html = ''; 365 | data.forEach((item, index) => { 366 | const active = index === self.playIndex ? ' active' : ''; 367 | html += `
        • 368 | 369 | ${item.title} 370 | ${item.singer} 371 |
        • `; 372 | }); 373 | list.innerHTML = html; 374 | }; 375 | 376 | 377 | /** 378 | * 渲染歌词 379 | * @private 380 | */ 381 | const __renderLyric = function () { 382 | const self = this, 383 | rowHeight = 30; 384 | const lyricHeight = self.dom.scroll.lyric.parentNode.offsetHeight; 385 | let html = '', 386 | i = 0; 387 | _getLyric(this.playList, this.playIndex, function (lyric) { 388 | if (typeof lyric === "number") { 389 | switch (lyric) { 390 | case 0: 391 | html += `
        • 暂无歌词
        • `; 392 | break; 393 | case -1: 394 | html += `
        • 歌词解析失败
        • `; 395 | break; 396 | case -2: 397 | html += `
        • 歌词加载失败
        • `; 398 | break; 399 | } 400 | self.dom.scroll.lyric.innerHTML = html; 401 | } else { 402 | for (let time in lyric) { 403 | const top = rowHeight * i, 404 | toScroll = top >= (lyricHeight / 2 - rowHeight); 405 | let content = lyric[time] || homepage; 406 | if (typeof content !== "string") { 407 | content = content.content; 408 | } 409 | lyric[time] = { 410 | index: i, 411 | content: content, 412 | top: top, 413 | scrollTop: toScroll ? top - (lyricHeight / 2 - rowHeight) : 0, 414 | toScroll: toScroll 415 | }; 416 | i++; 417 | html += `
        • ${content}
        • `; 418 | } 419 | self.dom.scroll.lyric.innerHTML = html + `
        • ${homepage}
        • `; 420 | } 421 | }); 422 | }; 423 | 424 | 425 | //记录buffer的Interval 426 | const _bufferTimer = {}; 427 | 428 | /** 429 | * 设置播放缓冲 430 | * @private 431 | */ 432 | const __setBuffer = function () { 433 | const [DOM, AUDIO, smusicId] = [this.dom, this.audio, this.smusicId]; 434 | _bufferTimer[smusicId] && clearInterval(_bufferTimer[smusicId]); 435 | const progressWidth = parseFloat(DOM.progress.bar.offsetWidth); 436 | if (!isNaN(AUDIO.duration)) { 437 | const totalTime = calcTime(AUDIO.duration); 438 | DOM.time.textContent = `00:00/${totalTime}`; 439 | } 440 | _bufferTimer[smusicId] = setInterval(function () { 441 | const buffer = AUDIO.buffered.length; 442 | if (buffer > 0 && AUDIO.buffered != undefined) { 443 | const bufferWidth = (AUDIO.buffered.end(buffer - 1) / AUDIO.duration) * progressWidth; 444 | DOM.progress.buffer.style.width = bufferWidth + 'px'; 445 | if (Math.abs(AUDIO.duration - AUDIO.buffered.end(buffer - 1)) < 1) { 446 | DOM.progress.buffer.style.width = progressWidth + 'px'; 447 | clearInterval(_bufferTimer[smusicId]); 448 | } 449 | } 450 | }, 1e3); 451 | }; 452 | 453 | /** 454 | * 绑定事件 455 | * @private 456 | */ 457 | const __bindAction = function () { 458 | const [self, DOM, AUDIO] = [this, this.dom, this.audio]; 459 | //进度条宽度 460 | const progressWidth = Math.round(DOM.progress.bar.offsetWidth); 461 | const volumeHeight = Math.round(DOM.volume.bar.offsetHeight); 462 | 463 | //列表面板和歌词面板切换 464 | bind(self.smusic, 'click', '.js-smusic-panel--tab', function (event) { 465 | event.stopPropagation(); 466 | if (utils.hasClass(this, 'active')) return false; 467 | const tab = this.getAttribute('data-panel'); 468 | utils.addClass(this, 'active'); 469 | if (tab === 'list') { 470 | utils.removeClass(DOM.btn.lyric, 'active'); 471 | utils.addClass(DOM.scroll.panel, 'show-list'); 472 | } else { 473 | utils.removeClass(DOM.btn.list, 'active'); 474 | utils.removeClass(DOM.scroll.panel, 'show-list'); 475 | } 476 | }); 477 | 478 | //Audio timeupdate事件 479 | bind(AUDIO, 'timeupdate', function () { 480 | if (!isNaN(AUDIO.duration)) { 481 | const 482 | surplusTime = calcTime(AUDIO.currentTime), 483 | totalTime = calcTime(AUDIO.duration), 484 | currentProcess = (AUDIO.currentTime / AUDIO.duration) * progressWidth; 485 | //当前播放时间/总时间 = 播放百分比 486 | //播放百分比 * 进度条宽度 = 当前播放进度 487 | DOM.time.textContent = `${surplusTime}/${totalTime}`; 488 | const range = Math.min(currentProcess, progressWidth); 489 | DOM.progress.value.style.width = range + 'px'; 490 | //歌词 491 | const cacheName = '_smusic_lyric_' + self.playIndex; 492 | const lyricData = _lyricCache[cacheName]; 493 | if (!lyricData) return; 494 | const currentTime = Math.round(AUDIO.currentTime); 495 | const lyric = lyricData[currentTime]; 496 | if (!lyric || typeof lyric === "number") return; //当前时间点没有对应到歌词,结束并返回当前行歌词; 497 | const index = lyric['index'], 498 | toScroll = lyric['toScroll'], 499 | $lyric = $$('li', DOM.scroll.lyric)[index], 500 | $activeLyric = $$('li.active', DOM.scroll.lyric); 501 | $activeLyric.forEach(node => { 502 | node && utils.removeClass(node, 'active'); 503 | }); 504 | $lyric && utils.addClass($lyric, 'active'); 505 | DOM.scroll.lyric.style.transform = toScroll ? 'translate3d(0, -' + lyric.scrollTop + 'px, 0)' : 'translate3d(0, 0, 0)'; 506 | } 507 | }); 508 | 509 | //播放模式切换 510 | bind(DOM.btn.mode, 'click', function () { 511 | const mode = this.getAttribute('data-play-mode'); 512 | let _mode; 513 | switch (Number(mode)) { 514 | case 1: 515 | _mode = 2; 516 | break; 517 | case 2: 518 | _mode = 3; 519 | break; 520 | case 3: 521 | _mode = 1; 522 | break; 523 | } 524 | self.setMode(_mode); 525 | }); 526 | 527 | //播放暂停 528 | bind(DOM.btn.play, 'click', function () { 529 | const isPlay = utils.hasClass(this, 'smusic-music-play'); 530 | if (isPlay) { 531 | self.pause(); 532 | } else { 533 | self.play(); 534 | } 535 | }); 536 | 537 | //播放结束后调用ended事件,开始下一首 538 | bind(AUDIO, 'ended', () => self.playByMode('ended')); 539 | //上一首 540 | bind(DOM.btn.prev, 'click', () => self.prev()); 541 | 542 | //下一首 543 | bind(DOM.btn.next, 'click', () => self.next()); 544 | 545 | //拖动进度条 546 | Range(DOM.progress.bar, '.js-smusic-progress--slider', 'horizontal', range => { 547 | const progress = Math.max(0, Math.min(range / progressWidth, progressWidth)); 548 | if (AUDIO.currentTime && AUDIO.duration) { 549 | AUDIO.currentTime = Math.round(progress * AUDIO.duration); 550 | } 551 | }); 552 | 553 | //点击进度条 554 | bind(DOM.progress.bar, 'click', function (event) { 555 | event.stopPropagation(); 556 | const rect = this.getBoundingClientRect(); 557 | const progress = Math.min(progressWidth, Math.abs(event.clientX - rect.left)) / progressWidth; 558 | if (AUDIO.currentTime && AUDIO.duration) { 559 | AUDIO.currentTime = Math.round(progress * AUDIO.duration); 560 | self.play(); 561 | } 562 | }); 563 | 564 | //音量拖动 565 | Range(DOM.volume.bar, '.js-smusic-volume--slider', 'vertical', range => { 566 | const volume = Number((volumeHeight - range) / volumeHeight); 567 | self.setVolume(volume); 568 | }); 569 | 570 | //音量静音开关 571 | bind(DOM.btn.volume, 'click', function (event) { 572 | if (AUDIO.muted) { 573 | utils.removeClass(this, 'smusic-volume--mute'); 574 | this.setAttribute('title', '静音'); 575 | AUDIO.muted = false; 576 | } else { 577 | utils.addClass(this, 'smusic-volume--mute'); 578 | this.setAttribute('title', '取消静音'); 579 | AUDIO.muted = true; 580 | } 581 | }); 582 | 583 | //点击列表 584 | bind(DOM.scroll.list, 'click', '.js-smusic-song--item', function (event) { 585 | const index = this.getAttribute('data-song-index'); 586 | if (utils.hasClass(this, 'active')) { 587 | self.play(); 588 | } else { 589 | __playMusic.call(self, index); 590 | } 591 | }) 592 | }; 593 | 594 | /** 595 | * 播放歌曲 596 | * @param index 597 | * @param callback 598 | * @param isInit 599 | * @private 600 | */ 601 | const __playMusic = function (index, callback, isInit) { 602 | const [DOM, AUDIO, listLength] = [this.dom, this.audio, this.playList.length]; 603 | //index 调整 604 | (index >= (listLength - 1)) && (index = (listLength - 1)); 605 | (index <= 0) && (index = 0); 606 | this.playIndex = index; 607 | //当前播放歌曲信息 608 | const song = this.playList[this.playIndex]; 609 | if (!song) { 610 | log("没有要播放的歌曲"); 611 | return false; 612 | } 613 | const tempHandle = function () { 614 | return __setBuffer.call(this); 615 | }.bind(this); 616 | //在canplay事件监听前移除之前的监听 617 | AUDIO.removeEventListener('canplay', tempHandle, false); 618 | _bufferTimer[this.smusicId] && clearInterval(_bufferTimer[this.smusicId]); 619 | 620 | //刷新DOM 621 | DOM.progress.buffer.style.width = '0px'; 622 | DOM.progress.value.style.width = '0px'; 623 | DOM.time.textContent = '00:00/00:00'; 624 | DOM.scroll.lyric.style.transform = 'translate3d(0, 0, 0)'; 625 | DOM.scroll.lyric.innerHTML = `
        • 正在加载歌词...
        • `; 626 | 627 | $$('.js-smusic-song--item.active', DOM.scroll.list) 628 | .forEach(item => { 629 | item && utils.removeClass(item, 'active, pause'); 630 | }); 631 | const currentSong = $$('.js-smusic-song--item', DOM.scroll.list)[index]; 632 | currentSong && utils.addClass(currentSong, 'active'); 633 | 634 | AUDIO.src = song.audio; 635 | AUDIO.load(); 636 | AUDIO.addEventListener('canplay', tempHandle, false); 637 | if (isInit) { 638 | this.config.autoPlay && AUDIO.play(); 639 | } else { 640 | AUDIO.play(); 641 | } 642 | const [title, singer, thumbnail] = [song.title || 'Smusic', song.singer || 'singer', song.thumbnail || thumbnailPlaceholder]; 643 | DOM.song.title.textContent = title; 644 | DOM.song.singer.textContent = singer; 645 | DOM.song.thumbnail.src = thumbnail; 646 | DOM.scroll.title.setAttribute('title', `${singer} - ${title}`); 647 | //加载歌词 648 | __renderLyric.call(this); 649 | callback && callback.call(this, song); 650 | }; 651 | 652 | /** 653 | * smusic 654 | * playList 播放列表 655 | * options 配置 656 | */ 657 | class SmohanMusic { 658 | 659 | /** 660 | * 构造方法 661 | * @param playList 662 | * @param options 663 | */ 664 | constructor(playList = [], options = {}) { 665 | if (!Array.isArray(playList)) { 666 | error("播放列表必须是一个数组"); 667 | } 668 | this.playList = playList; 669 | this.audio = null; 670 | this.version = version; 671 | this.config = utils.assign({}, defaultConfig, options); 672 | let playIndex = parseInt(this.config.playIndex), 673 | playMode = parseInt(this.config.playMode), 674 | volume = parseFloat(this.config.volume); 675 | 676 | if (playIndex < 0) 677 | playIndex = 0; 678 | if (playIndex > this.playList.length - 1) 679 | playIndex = this.playList.length - 1; 680 | 681 | if (playMode < 1 || playMode > 3) 682 | playMode = 1; 683 | 684 | if (volume < 0 || volume > 1) 685 | volume = .5; 686 | 687 | this.playIndex = playIndex; 688 | this.playMode = playMode; 689 | this.volume = volume; 690 | }; 691 | 692 | /** 693 | * 通过模式播放 694 | * 由播放模式定义播放索引 695 | * prev,next,ended等方法都应该通过该接口调用 696 | * 返回一个合理的播放索引 697 | * @param type 698 | * @param callback 699 | * @param isInit 700 | */ 701 | playByMode(type, callback, isInit) { 702 | const [playMode, playIndex, songLength] = [Number(this.playMode), this.playIndex, this.playList.length]; 703 | let index = playIndex; 704 | //如果没有定义播放类型,以及播放模式非随机,则直接播放当前索引 705 | if (!type && playMode != 2) { 706 | //todo 不做任何事 707 | } else { 708 | switch (playMode) { 709 | case 1: 710 | if (type == 'prev') { 711 | index = ((playIndex <= songLength - 1) && (playIndex > 0)) ? (playIndex - 1) : (songLength - 1); 712 | } else if (type == 'next' || type == 'ended') { 713 | index = (playIndex >= songLength - 1) ? 0 : (playIndex + 1); 714 | } 715 | break; 716 | case 2: 717 | index = _getRandomIndex(playIndex, songLength); 718 | break; 719 | case 3: 720 | if (type == 'prev') { 721 | index = ((playIndex <= songLength - 1) && (playIndex > 0)) ? (playIndex - 1) : (songLength - 1); 722 | } else if (type == 'next') { 723 | index = (playIndex >= songLength - 1) ? 0 : (playIndex + 1); 724 | } else { 725 | index = playIndex; 726 | } 727 | break; 728 | } 729 | } 730 | __playMusic.call(this, index, callback, isInit); 731 | }; 732 | 733 | /** 734 | * 向列表中追加音乐 735 | * @param music 736 | * @param callback 737 | * @returns {Array|*} 738 | */ 739 | addSong(music = {}, callback = noop) { 740 | if (music && music.audio) { 741 | this.playList.push({ 742 | title: music.title || '歌曲名', 743 | singer: music.singer || '歌手名', 744 | audio: music.audio, 745 | thumbnail: music.thumbnail || thumbnailPlaceholder, 746 | lyric: music.lyric | '' 747 | }); 748 | this.refreshList(); 749 | callback && callback(); 750 | } else { 751 | callback && callback("添加失败,参数不是一个对象或者未设置audio属性"); 752 | } 753 | return this.playList; 754 | }; 755 | 756 | //刷新播放列表 757 | refreshList() { 758 | __renderList.call(this); 759 | }; 760 | 761 | /** 762 | * 下一首 763 | * @param callback 764 | */ 765 | next(callback) { 766 | this.playByMode('next', callback); 767 | }; 768 | 769 | /** 770 | * 上一首 771 | * @param callback 772 | */ 773 | prev(callback) { 774 | this.playByMode('next', callback); 775 | }; 776 | 777 | /** 778 | * 播放 779 | * @param callback 780 | */ 781 | play(callback) { 782 | const BTN = this.dom.btn.play; 783 | utils.removeClass(BTN, 'smusic-music-pause'); 784 | utils.addClass(BTN, 'smusic-music-play'); 785 | BTN.setAttribute('title', '暂停'); 786 | const $currentSong = $('.js-smusic-song--item.active', this.dom.scroll.list); 787 | utils.removeClass($currentSong, 'pause'); 788 | this.playList.length && this.audio.play(); 789 | callback && callback.call(this, this.playList[this.playIndex]); 790 | }; 791 | 792 | /** 793 | * 暂停 794 | * @param callback 795 | */ 796 | pause(callback) { 797 | const BTN = this.dom.btn.play; 798 | utils.addClass(BTN, 'smusic-music-pause'); 799 | utils.removeClass(BTN, 'smusic-music-play'); 800 | const $currentSong = $('.js-smusic-song--item.active', this.dom.scroll.list); 801 | utils.addClass($currentSong, 'pause'); 802 | BTN.setAttribute('title', '播放'); 803 | this.playList.length && this.audio.pause(); 804 | callback && callback.call(this, this.playList[this.playIndex]); 805 | }; 806 | 807 | /** 808 | * 设置音量 809 | * @param volume 810 | */ 811 | setVolume(volume = .5) { 812 | volume = Number(volume); 813 | volume < 0 && (volume = 0); 814 | volume > 1 && (volume = 1); 815 | const barHeight = this.dom.volume.bar.offsetHeight; 816 | const sliderHeight = this.dom.volume.slider.offsetHeight; 817 | const valueHeight = barHeight * volume; 818 | const sliderTop = Math.min(((1 - volume) * barHeight), barHeight - sliderHeight); 819 | if (volume === 0) { 820 | utils.addClass(this.dom.btn.volume, 'smusic-volume--mute'); 821 | this.dom.btn.volume.setAttribute('title', '取消静音'); 822 | this.audio.muted = true; 823 | } else { 824 | utils.removeClass(this.dom.btn.volume, 'smusic-volume--mute'); 825 | this.audio.muted = false; 826 | this.dom.btn.volume.setAttribute('title', '静音'); 827 | } 828 | this.dom.volume.value.style.height = valueHeight + 'px'; 829 | this.dom.volume.slider.style.top = sliderTop + 'px'; 830 | this.volume = volume; 831 | this.audio.volume = volume; 832 | }; 833 | 834 | /** 835 | * 设置播放模式 836 | * @param mode 837 | */ 838 | setMode(mode = 1) { 839 | mode = Number(mode); 840 | let title, className; 841 | const $Mode = this.dom.btn.mode; 842 | switch (mode) { 843 | case 1: 844 | default: 845 | title = '列表循环'; 846 | className = 'smusic-mode--loop'; 847 | break; 848 | case 2: 849 | title = '随机播放'; 850 | className = 'smusic-mode--random'; 851 | break; 852 | case 3: 853 | title = '单曲循环'; 854 | className = 'smusic-mode--single'; 855 | break; 856 | } 857 | $Mode.setAttribute('data-play-mode', mode.toString()); 858 | $Mode.setAttribute('title', title); 859 | $Mode.className = $Mode.className.replace(/smusic-mode--\w+/, className); 860 | this.playMode = mode; 861 | }; 862 | 863 | //初始化 864 | init() { 865 | const [config, create] = [this.config, _createDom(this.config)]; 866 | this.smusic = create.element; 867 | this.smusicId = create.id; 868 | this.audio = this.smusic.getElementsByTagName('audio')[0]; 869 | //缓存DOM 870 | __domCache.call(this); 871 | //渲染列表 872 | __renderList.call(this); 873 | //绑定事件 874 | __bindAction.call(this); 875 | //设置音量 876 | this.setVolume(this.volume); 877 | //设置播放模式 878 | this.setMode(this.playMode); 879 | 880 | if (this.playList.length) { 881 | this.playByMode(undefined, function () { 882 | if (!config.autoPlay) { 883 | utils.trigger(this.dom.btn.play, 'click'); 884 | } 885 | }, true); 886 | } else { 887 | log("歌曲列表为空"); 888 | } 889 | }; 890 | 891 | /** 892 | * 获取当前播放的歌曲信息 893 | * @returns {*} 894 | */ 895 | getCurrentInfo() { 896 | if (this.playList.length) { 897 | const song = this.playList[this.playIndex]; 898 | song.index = this.playIndex; 899 | song.volume = this.volume; 900 | song.mode = this.playMode; 901 | return song; 902 | } 903 | return null; 904 | } 905 | 906 | //析构 907 | destroy() { }; 908 | } 909 | 910 | /** 911 | * 作为全局对象返回 912 | * @param playList 913 | * @param config 914 | * @constructor 915 | */ 916 | win.SMusic = (playList, config) => new SmohanMusic(playList, config); 917 | })(); -------------------------------------------------------------------------------- /src/scss/smusic.scss: -------------------------------------------------------------------------------- 1 | $color-active: #e03a3a!default; 2 | 3 | @font-face { 4 | font-family: 'SMusic'; 5 | font-weight: normal; 6 | font-style: normal; 7 | font-variant: normal; 8 | src: url(data:application/font-woff;base64,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) format('woff'); 9 | } 10 | .smusic-container, .smusic-container * { 11 | box-sizing: border-box; 12 | } 13 | .smusic-container { 14 | font: 14px / 1.5 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'Microsoft Yahei', Arial, sans-serif; 15 | position: relative; 16 | display: flex; 17 | overflow: hidden; 18 | width: 640px; 19 | height: 360px; 20 | padding: 20px; 21 | user-select: none; 22 | word-wrap: break-word; 23 | color: #333; 24 | background: #fff; 25 | flex-flow: column; 26 | 27 | audio { 28 | display: none !important; 29 | } 30 | } 31 | //icons 32 | [class^='smusic-ico-'], [class*=' smusic-ico-'], .smusic-ctrl--play, .smusic-volume--toggle, .smusic-ctrl--mode { 33 | font-family: 'SMusic' !important; 34 | font-weight: normal; 35 | font-style: normal; 36 | font-variant: normal; 37 | line-height: 1; 38 | speak: none; 39 | -webkit-font-smoothing: antialiased; 40 | -moz-osx-font-smoothing: grayscale; 41 | } 42 | .smusic-ico-lyric:before { 43 | content: '\e90c'; 44 | } 45 | .smusic-ico-list:before { 46 | content: '\e904'; 47 | } 48 | .smusic-ico-prev:before { 49 | content: '\e909'; 50 | } 51 | .smusic-ico-next:before { 52 | content: '\e908'; 53 | } 54 | // container 55 | .smusic-container { 56 | > .smusic-main { 57 | flex: 1 1 auto; 58 | } 59 | > .smusic-aside { 60 | display: flex; 61 | height: 40px; 62 | margin-top: 10px; 63 | flex-flow: row nowrap; 64 | align-items: center; 65 | 66 | > .smusic-ctrl { 67 | white-space: nowrap; 68 | 69 | &.smusic-ctrl--left { 70 | width: 70px; 71 | padding-right: 10px; 72 | } 73 | &.smusic-ctrl--right { 74 | width: 140px; 75 | text-align: right; 76 | } 77 | } 78 | > .smusic-progress { 79 | width: 1px; 80 | flex: 1 1 auto; 81 | } 82 | } 83 | } 84 | // progress 85 | .smusic-container > .smusic-aside { 86 | color: #333; 87 | 88 | .smusic-ctrl--volume, .smusic-ctrl--mode, .smusic-ctrl--lyric, .smusic-ctrl--list, .smusic-time { 89 | font-size: 24px; 90 | display: inline-block; 91 | margin-left: 10px; 92 | vertical-align: middle; 93 | } 94 | .smusic-ctrl--volume { 95 | margin-left: 0; 96 | } 97 | .smusic-volume--toggle { 98 | position: relative; 99 | z-index: 3; 100 | top: 3px; 101 | &:before { 102 | content: '\e90b'; 103 | } 104 | &.smusic-volume--mute { 105 | &:before { 106 | content: '\e90a'; 107 | } 108 | } 109 | } 110 | .smusic-ctrl--mode { 111 | &.smusic-mode--loop { 112 | position: relative; 113 | &:before { 114 | content: '\e901'; 115 | } 116 | } 117 | &.smusic-mode--single { 118 | &:before { 119 | content: '\e907'; 120 | } 121 | } 122 | &.smusic-mode--random { 123 | &:before { 124 | content: '\e906'; 125 | } 126 | } 127 | } 128 | .smusic-ctrl--lyric, .smusic-ctrl--list { 129 | position: relative; 130 | top: 3px; 131 | &.active { 132 | color: $color-active; 133 | } 134 | } 135 | a { 136 | display: inline-block; 137 | cursor: pointer; 138 | } 139 | .smusic-time { 140 | font-size: 12px; 141 | margin-left: 5px; 142 | } 143 | > .smusic-progress { 144 | position: relative; 145 | height: 5px; 146 | cursor: pointer; 147 | background-color: #333; 148 | 149 | > .smusic-progress--value, > .smusic-progress--buffer, > .smusic-progress--slider { 150 | position: absolute; 151 | top: 0; 152 | left: 0; 153 | height: 100%; 154 | transition: width .1s linear; 155 | } 156 | > .smusic-progress--buffer { 157 | background-color: #777; 158 | } 159 | > .smusic-progress--value { 160 | width: 16px; 161 | min-width: 16px; 162 | background-color: $color-active; 163 | 164 | > .smusic-progress--slider { 165 | position: absolute; 166 | top: 50%; 167 | right: -1px; 168 | width: 16px; 169 | height: 16px; 170 | margin-top: -8px; 171 | cursor: -webkit-grab; 172 | cursor: grab; 173 | border-radius: 50%; 174 | background-color: #fff; 175 | box-shadow: 1px 1px 1px rgba(#000, .35); 176 | } 177 | } 178 | } 179 | } 180 | // volume 181 | .smusic-container .smusic-ctrl--volume { 182 | position: relative; 183 | 184 | > .smusic-volume--bar { 185 | position: absolute; 186 | top: -100px; 187 | left: 12px; 188 | visibility: hidden; 189 | width: 3px; 190 | height: 100px; 191 | cursor: pointer; 192 | transition: all .2s; 193 | opacity: 0; 194 | background-color: #333; 195 | 196 | &:after { 197 | position: absolute; 198 | z-index: 0; 199 | top: -10%; 200 | left: -14px; 201 | width: 31px; 202 | height: 140%; 203 | content: ''; 204 | background-color: rgba(#000, .15); 205 | } 206 | > .smusic-volume--value { 207 | position: absolute; 208 | z-index: 2; 209 | bottom: 0; 210 | left: 0; 211 | width: 100%; 212 | height: 4px; 213 | background-color: $color-active; 214 | } 215 | > .smusic-volume--slider { 216 | position: absolute; 217 | z-index: 3; 218 | top: 0; 219 | left: 50%; 220 | width: 10px; 221 | height: 10px; 222 | margin-left: -5px; 223 | cursor: -webkit-grab; 224 | cursor: grab; 225 | border-radius: 50%; 226 | background-color: #fff; 227 | box-shadow: 1px 1px 1px rgba(#000, .15); 228 | } 229 | } 230 | &:hover { 231 | > .smusic-volume--bar { 232 | visibility: visible; 233 | opacity: 1; 234 | } 235 | } 236 | } 237 | // main panel 238 | .smusic-container > .smusic-main { 239 | display: flex; 240 | flex-flow: row nowrap; 241 | 242 | > .smusic-panel { 243 | width: 45%; 244 | position: relative; 245 | text-align: center; 246 | 247 | &:last-of-type { 248 | overflow: hidden; 249 | margin-left: 20px; 250 | flex: 1 1 auto; 251 | } 252 | } 253 | //music info 254 | .smusic-music-info { 255 | line-height: 30px; 256 | position: relative; 257 | overflow: hidden; 258 | height: 30px; 259 | white-space: nowrap; 260 | 261 | > .smusic-music-scroll { 262 | position: absolute; 263 | top: 0; 264 | left: 0; 265 | min-width: 100%; 266 | height: 100%; 267 | transition: transform .2s; 268 | transform: translate3d(0, 0, 0); 269 | text-align: center; 270 | } 271 | .smusic-music--title { 272 | font-size: 18px; 273 | } 274 | .smusic-music--singer { 275 | font-size: 14px; 276 | margin-left: 5px; 277 | } 278 | } 279 | // thumbnail 280 | .smusic-music-thumbnail { 281 | width: 170px; 282 | height: 170px; 283 | margin: 15px auto; 284 | background: url(https://s-mohan.github.io/demo/static/img/smusic.jpg) center no-repeat; 285 | background-size: cover; 286 | > img { 287 | display: block; 288 | width: 100%; 289 | height: 100%; 290 | transition: all .2s ease; 291 | border-radius: 50%; 292 | } 293 | } 294 | .smusic-music-ctrl { 295 | > .smusic-ctrl--prev, > .smusic-ctrl--next, > .smusic-ctrl--play { 296 | display: inline-block; 297 | cursor: pointer; 298 | transition: opacity .1s ease-in-out; 299 | &:hover { 300 | opacity: .9; 301 | } 302 | } 303 | } 304 | .smusic-ctrl--prev, .smusic-ctrl--next { 305 | font-size: 36px; 306 | } 307 | .smusic-ctrl--play { 308 | font-size: 48px; 309 | width: 48px; 310 | margin: 0 10px; 311 | text-align: center; 312 | vertical-align: top; 313 | position: relative; 314 | top: -2px; 315 | &.smusic-music-pause { 316 | &:before { 317 | content: '\e903'; 318 | } 319 | } 320 | &.smusic-music-play { 321 | &:before { 322 | content: '\e902'; 323 | } 324 | } 325 | } 326 | } 327 | //lyric && list 328 | .smusic-container { 329 | .smusic-panel--scroll { 330 | position: absolute; 331 | top: 0; 332 | left: 0; 333 | display: flex; 334 | width: 200%; 335 | height: 100%; 336 | transition: transform .2s; 337 | transform: translate3d(0, 0, 0); 338 | flex-flow: row nowrap; 339 | 340 | .smusic-list--wrap, .smusic-lyric--wrap { 341 | position: relative; 342 | overflow: hidden; 343 | width: 50%; 344 | height: 100%; 345 | flex-basis: 50%; 346 | } 347 | &.show-list { 348 | transform: translate3d(-100%, 0, 0); 349 | 350 | .smusic-lyric--wrap { 351 | opacity: 0; 352 | } 353 | } 354 | } 355 | .smusic-list--wrap { 356 | transform: translate3d(100%, 0, 0); 357 | } 358 | } 359 | // lyric 360 | .smusic-container .smusic-lyric--wrap { 361 | &:before, &:after { 362 | position: absolute; 363 | z-index: 3; 364 | left: 0; 365 | width: 100%; 366 | height: 25%; 367 | content: ''; 368 | } 369 | &:before { 370 | top: 0; 371 | background-image: linear-gradient(to bottom, rgba(#fff, 1), rgba(#fff, 0)); 372 | } 373 | &:after { 374 | bottom: 0; 375 | background-image: linear-gradient(to top, rgba(#fff, 1), rgba(#fff, 0)); 376 | } 377 | li { 378 | font-size: 14px; 379 | transition: all .2s; 380 | color: #555; 381 | 382 | &.active { 383 | 384 | /* font-weight: bold;*/ 385 | font-size: 15px; 386 | color: $color-active; 387 | } 388 | a { 389 | text-decoration: underline; 390 | color: #777; 391 | } 392 | &.empty { 393 | font-size: 24px; 394 | margin-top: 90px; 395 | margin-bottom: 10px; 396 | color: #bbb; 397 | } 398 | } 399 | } 400 | .smusic-container { 401 | .smusic-lyric--wrap > .smusic-lyric--scroll, .smusic-list--wrap > .smusic-list--scroll { 402 | position: absolute; 403 | top: 0; 404 | left: 0; 405 | width: 100%; 406 | margin: 0; 407 | padding: 0; 408 | list-style: none; 409 | transition: transform .2s; 410 | transform: translate3d(0, 0, 0); 411 | 412 | > li { 413 | line-height: 30px; 414 | display: block; 415 | overflow: hidden; 416 | height: 30px; 417 | white-space: nowrap; 418 | text-overflow: ellipsis; 419 | } 420 | } 421 | } 422 | // list 423 | .smusic-container .smusic-panel--scroll .smusic-list--wrap { 424 | overflow-y: auto; 425 | 426 | > .smusic-list--scroll { 427 | > li { 428 | position: relative; 429 | display: flex; 430 | margin: 3px 0; 431 | padding-left: 20px; 432 | cursor: pointer; 433 | transition: color .1s linear; 434 | text-align: left; 435 | color: #555; 436 | flex-flow: row nowrap; 437 | 438 | &:hover { 439 | color: #333; 440 | } 441 | > .song-animate { 442 | font-size: 0; 443 | position: absolute; 444 | top: 5px; 445 | left: 0; 446 | visibility: hidden; 447 | width: 20px; 448 | height: 14px; 449 | 450 | > i { 451 | position: relative; 452 | display: inline-block; 453 | width: 3px; 454 | height: 100%; 455 | margin-right: 2px; 456 | 457 | &:after { 458 | position: absolute; 459 | bottom: 0; 460 | width: 100%; 461 | height: 100%; 462 | content: ''; 463 | background-color: $color-active; 464 | //background: radial-gradient(transparent 20%, $color-active 100%); 465 | } 466 | &:nth-of-type(1):after { 467 | animation: waves .6s linear 0s infinite alternate; 468 | } 469 | &:nth-of-type(2):after { 470 | animation: waves .9s linear 0s infinite alternate; 471 | } 472 | &:nth-of-type(3):after { 473 | animation: waves 1.1s linear 0s infinite alternate; 474 | } 475 | } 476 | } 477 | > .song-title, > .song-singer { 478 | overflow: hidden; 479 | white-space: nowrap; 480 | text-overflow: ellipsis; 481 | } 482 | > .song-title { 483 | width: 1px; 484 | flex: 1 1 auto; 485 | } 486 | > .song-singer { 487 | width: 30%; 488 | margin-left: 5%; 489 | text-align: right; 490 | } 491 | &.active { 492 | color: $color-active; 493 | 494 | > .song-animate { 495 | visibility: visible; 496 | } 497 | } 498 | &.pause { 499 | > .song-animate i:after { 500 | animation-play-state: paused; 501 | } 502 | } 503 | } 504 | } 505 | } 506 | @keyframes waves { 507 | 10% { 508 | height: 20%; 509 | } 510 | 20% { 511 | height: 60%; 512 | } 513 | 40% { 514 | height: 40%; 515 | } 516 | 50% { 517 | height: 100%; 518 | } 519 | 100% { 520 | height: 50%; 521 | } 522 | } --------------------------------------------------------------------------------