├── .gitignore ├── README.md ├── dist ├── assets │ ├── css │ │ ├── bootstrap-slider.min.css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.min.css │ │ └── my.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ ├── album.png │ │ ├── avater.jpg │ │ ├── cover.png │ │ ├── icon.ico │ │ └── icon.png │ └── js │ │ ├── bootstrap-slider.min.js │ │ ├── bootstrap.min.js │ │ ├── jquery.min.js │ │ ├── my.js │ │ └── sortable.js ├── index.html └── libs │ ├── Crypto.js │ ├── EntryModel.js │ ├── FileManager.js │ ├── NetEaseMusic.js │ ├── PlaylistModel.js │ ├── SongModel.js │ └── Utils.js ├── gulpfile.js ├── package.json ├── snapshot └── fm.png └── src ├── controller ├── account.js ├── category.js ├── lrc.js ├── nav.js ├── player.js ├── playlist.js ├── radio.js ├── settings.js └── tray.js ├── index.jade ├── model ├── Crypto.js ├── EntryModel.js ├── FileManager.js ├── NetEaseMusic.js ├── PlaylistModel.js ├── SongModel.js └── Utils.js ├── my.js ├── my.styl └── shell ├── icon.svg ├── install.sh ├── nwMusicBox.desktop └── 安装说明 /.gitignore: -------------------------------------------------------------------------------- 1 | music/ 2 | data/ 3 | .idea/ 4 | bin/ 5 | release/ 6 | node_modules/ 7 | *.exe 8 | *.py 9 | *Test* 10 | .directory 11 | build/ 12 | cache/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **注意** nw.js版本现已经不再维护,推荐使用[@issac2015](https://github.com/issac2015)的Electron移植版: [Cube in Electron](https://github.com/issac2015/Cube-In-Electron) 2 | 3 | # 网易音乐盒 4 | 使用nw.js构建的网易云音乐的跨平台第三方客户端。   5 | 6 | 7 | 此外: 8 | - JS版网易API可以参考:     [NetEaseMusic](https://github.com/stkevintan/Cube/blob/master/src/model/NetEaseMusic.js) 9 | - JS版的加密算法可以参考: [Crypto](https://github.com/stkevintan/Cube/blob/master/src/model/Crypto.js) 10 | 11 | ### Feature 12 | 1. 设定本地音乐文件夹,递归搜索本地音乐。 13 | 2. 搜索播放网络音乐 14 | 3. 自定义播放列表,可以同时加入网络音乐和本地音乐 15 | 4. 手机帐号或邮箱帐号登录 16 | 5. UI响应式布局与mini模式 17 | 6. 滚动歌词秀 18 | 7. 私人fm 19 | 8. 系统播放提示 20 | 9. 系统托盘 21 | 22 | 23 | ### Install 24 | 1. 下载安装[nw.js](https://github.com/nwjs/nw.js) 25 | 2. 拷贝chrome安装目录下的`libffmpegsumo.so`(windows下是`libffmpegsumo.dll`)至nw.js目录下 26 | 3. 下载并切换至项目:`git clone https://github.com/stkevintan/Cube.git && cd Cube/` 27 | 4. 安装模块: `npm i` 28 | 5. 运行: `/path/to/nw .` 29 | 30 | `libffmpegsumo`的版本一定要与nw.js版本对应,否则不支持MP3等常见格式。nw.js v0.12.0对应chrome 41.x + 31 | 32 | ### Update 33 | `cd /path/to/Cube/ && git pull` 34 | 35 | ### Screenshots 36 | ![FM](https://raw.githubusercontent.com/stkevintan/Cube/master/snapshot/fm.png) 37 | 38 | 39 | ### Troubleshooting 40 | 41 | #### Mini模式还能调窗口大小 42 | 这是nw.js的bug 43 | 44 | #### 没有声音或者'糟糕,文件或网络资源无法访问' 45 | 首先,可能是由于nw.js自带的音频解码器不支持mp3格式,参考`Manual Install`第二步解决。 46 | 也有可能是由于网络延时,网络音乐还未解析完全,这种情况等待一下即可。 47 | 48 | -------------------------------------------------------------------------------- /dist/assets/css/bootstrap-slider.min.css: -------------------------------------------------------------------------------- 1 | .slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px;margin-top:-5px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#0480be;margin-top:0}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;margin-left:-5px;left:50%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-left:-5px;margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#0480be;margin-left:0}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:-o-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:linear-gradient(to bottom,#149bdf 0,#0480be 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0);opacity:1} -------------------------------------------------------------------------------- /dist/assets/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /dist/assets/css/my.css: -------------------------------------------------------------------------------- 1 | html,body,#wrapper,#page,#page > .row,#sidebar,#entry,.list,.song-detail,.lyric{height:100%;min-height:100%}img{background:#fff}a{outline:none;color:#767676;text-decoration:none}.loading{-webkit-animation:loading 1500ms infinite linear}.navbar{box-shadow:0 0 8px rgba(0,0,0,0.21);}.navbar #user-profile{line-height:50px;padding-top:0;padding-bottom:0;}.navbar #user-profile img{height:30px;width:30px;margin-right:10px}.navbar #user-profile p{margin:0;display:inline-block}.navbar #menugo-0 span,.navbar #menugo-1 span{margin-right:10px}#page{padding-top:50px;padding-bottom:60px}#main{position:relative;}#main #sidebar{display:block;position:absolute;top:0;right:75%;width:25%;border-right:1px solid #eee;padding:0;overflow:hidden;background-color:#f8f8f8;}#main #sidebar #entry{padding:0 9px 30px 10px;overflow-y:auto;}#main #sidebar #entry::-webkit-scrollbar{width:2px}#main #sidebar #entry::-webkit-scrollbar-track{margin-bottom:30px}#main #sidebar #entry::-webkit-scrollbar-track-piece{background-color:#f8f8f8}#main #sidebar #entry::-webkit-scrollbar-thumb{background-color:#999;border-radius:4px;}#main #sidebar #entry::-webkit-scrollbar-thumb:hover{background-color:#767676}#main #sidebar #entry .plts-group{margin-top:20px;}#main #sidebar #entry .plts-group .plts-title{font-size:13px;color:#a5a3a3;padding-bottom:5px;padding-left:15px}#main #sidebar #entry .plts-group li{border-radius:3px;}#main #sidebar #entry .plts-group li a{position:relative;font-size:13px;line-height:13px;padding:8px 16px;border-radius:3px;}#main #sidebar #entry .plts-group li a .name{margin-right:62px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#main #sidebar #entry .plts-group li a .limark{position:absolute;right:5px;top:6px}#main #sidebar #entry .plts-group li a span{float:right;margin-left:5px;margin-right:5px}#main #sidebar #entry .plts-group li a span.badge{padding:2px 7px;background-color:#767676;color:#fff}#main #sidebar #entry .plts-group li a:hover,#main #sidebar #entry .plts-group li a:focus{color:#767676}#main #sidebar #entry .plts-group li.active > a,#main #sidebar #entry .plts-group li.active > a:focus,#main #sidebar #entry .plts-group li.active > a:hover{color:#fff;background-color:#428bca}#main #sidebar #entry .plts-group li.active > a span.badge{background-color:#fff;color:#428bca}#main #sidebar #plts-tools{position:absolute;bottom:0;width:100%;z-index:1000;padding-left:15px;padding-right:16px;color:#767676;text-align:right;line-height:30px;background-color:#eee;}#main #sidebar #plts-tools span{margin-left:5px;margin-right:5px}#main .list{overflow:auto;width:100%;padding-left:25%;}#main .list .twrap{padding:0 15px}#main .list table a{outline:none;text-decoration:none;}#main .list table a:hover,#main .list table a:focus{color:#d9534f}#main .list table td > a{margin-left:10px}#main .list table thead,#main .list table td:last-child{white-space:nowrap}#main .list table .dropdown-menu{right:0;left:inherit;}#main .list table .dropdown-menu a:hover,#main .list table .dropdown-menu a:focus{color:#333}.song-detail{position:absolute;z-index:1000;padding:80px 15px;left:0;bottom:0;overflow:auto;width:100%;background-color:#f4f4f4;transition:transform .2s;transform:scale(0,0);transform-origin:20% 100%;}.song-detail .detail{position:absolute;z-index:1;padding:0 10px;width:260px;margin-left:32px;}.song-detail .detail .cover{position:relative;}.song-detail .detail .cover:after,.song-detail .detail .cover:before{content:"";position:absolute;z-index:-1;bottom:15px;left:10px;width:50%;height:20%;box-shadow:0 15px 10px rgba(125,125,125,0.8);transform:rotate(-3deg)}.song-detail .detail .cover:after{right:10px;left:auto;transform:rotate(3deg)}.song-detail .detail .cover img{width:240px;height:240px}.song-detail .detail ul.info{margin-top:20px;padding-left:0;}.song-detail .detail ul.info li{list-style:none;line-height:25px;}.song-detail .detail ul.info li span.glyphicon{margin-right:10px}.song-detail .lyric{position:relative;min-height:350px;overflow:hidden;}.song-detail .lyric,.song-detail .lyric:before,.song-detail .lyric:after{width:100%;padding-left:360px;padding-right:100px}.song-detail .lyric:before,.song-detail .lyric:after{content:"";position:absolute;top:0;left:0;z-index:1;height:40px;background:linear-gradient(to bottom,#f4f4f4,transparent) content-box}.song-detail .lyric:after{bottom:0;top:auto;background:linear-gradient(to top,#f4f4f4,transparent) content-box}.song-detail .lyric ul{position:relative;line-height:20px;font-size:15px;text-align:center;padding-left:0;transition:all 1s ease-in-out;}.song-detail .lyric ul::-webkit-scrollbar{display:none}.song-detail .lyric ul li{list-style:none;min-height:20px;color:#808080;margin:8px auto;transition:all .5s ease-in-out;}.song-detail .lyric ul li.current{color:#000;font-size:18px}.song-detail .lrc-tools{position:absolute;right:70px;top:100px;border-radius:4px;box-shadow:rgba(159,159,159,0.5) 0 0 12px;}.song-detail .lrc-tools .btn{background-color:#fff;}.song-detail .lrc-tools .btn:active,.song-detail .lrc-tools .btn:hover{box-shadow:inset 0 0 5px rgba(0,0,0,0.125)}#settings{overflow:auto;}#settings .panel{margin:50px auto;width:80%}#footer{position:fixed;bottom:0;z-index:1500;width:100%;background-color:#262626;}#footer .mini-tool{display:none}#footer a{color:#fff;outline:none;text-decoration:none;margin-left:5px;margin-right:5px;}#footer a:hover{color:#bdbdbd}#footer .audio-control,#footer .audio-adjust{line-height:60px;font-size:25px;color:#fff;text-align:center;min-width:138px}#footer .audio-adjust a{margin-right:5px;}#footer .audio-adjust a#vol-wrapper{position:relative;overflow:visible;}#footer .audio-adjust a#vol-wrapper:hover #vol-panel{display:block}#footer .audio-adjust a #vol-panel{bottom:40px;top:inherit;left:-70px;width:150px;}#footer .audio-adjust a #vol-panel .layer{position:absolute;bottom:-11px;width:100%;height:11px}#footer .audio-adjust a #vol-panel .slider{width:100%}#footer .audio-adjust a #vol-panel .slider-track{border-radius:4px;background-color:#e7e7e7}#footer .audio-adjust a #vol-panel .slider-selection{display:none}#footer .audio-adjust a #vol-panel .slider-handle{height:12px;width:12px;margin-left:-5px;margin-top:-1px;border-radius:50%;background:#5e5e5e}#footer .audio-body{color:#fff;}#footer .audio-body .media-left,#footer .audio-body .media-body{display:block;height:60px}#footer .audio-body .media{overflow:visible}#footer .audio-body .media-heading{margin-top:12px;margin-bottom:5px;white-space:nowrap;display:inline-block;overflow:hidden;text-overflow:ellipsis;width:100%;padding-right:85px}#footer .audio-body .media-left{position:absolute;top:6px;z-index:2;}#footer .audio-body .media-left a{display:block;width:50px;cursor:pointer}#footer .audio-body .media-left img{width:50px;height:50px;border-width:0;padding:0;vertical-align:middle;}#footer .audio-body .media-left img:hover{-webkit-filter:brightness(.5)}#footer .audio-body .media-body{width:100%;padding-left:65px;position:relative;overflow:visible}#footer .audio-body .media-time{display:block;position:absolute;right:0;top:10px}#footer .audio-body #progress{width:100%;margin-top:-10px;}#footer .audio-body #progress .slider-track{background:none repeat scroll 0 0 #e8e8e8;border-radius:15px;width:100%}#footer .audio-body #progress .slider-selection{background:none repeat scroll 0 0 #09c;border-top-right-radius:0;border-bottom-right-radius:0}#footer .audio-body #progress .slider-handle{margin-left:-7px;margin-top:-2px;height:14px;width:14px;background:none repeat scroll 0 0 #fff;box-shadow:0 0 10px #333}@media (max-width:768px){.list{padding-left:0 !important}#open-side,#sidebar{display:none}}@media (max-width:560px){#page{display:none}#footer .mini-tool{display:block;float:left;color:#fff;margin-left:20px;line-height:60px;font-size:16px}#footer .audio-control{float:left}#footer .media{float:left;padding-left:15px;width:350px}#footer .audio-adjust{display:none}}@-moz-keyframes loading{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-webkit-keyframes loading{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-o-keyframes loading{from{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes loading{from{transform:rotate(0)}to{transform:rotate(360deg)}} -------------------------------------------------------------------------------- /dist/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /dist/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /dist/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /dist/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /dist/assets/img/album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/img/album.png -------------------------------------------------------------------------------- /dist/assets/img/avater.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/img/avater.jpg -------------------------------------------------------------------------------- /dist/assets/img/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/img/cover.png -------------------------------------------------------------------------------- /dist/assets/img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/img/icon.ico -------------------------------------------------------------------------------- /dist/assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/dist/assets/img/icon.png -------------------------------------------------------------------------------- /dist/assets/js/bootstrap-slider.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){if("function"==typeof define&&define.amd)define(["jquery"],b);else if("object"==typeof module&&module.exports){var c;try{c=require("jquery")}catch(d){c=null}module.exports=b(c)}else a.Slider=b(a.jQuery)}(this,function(a){var b;return function(a){"use strict";function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function c(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var f=Object.keys(this.defaultOptions),g=0;g0){for(g=0;g0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",g=0;g0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?this.options.range=!0:this.options.range&&(this.options.value=[this.options.value,this.options.max]),this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection&&(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),g=0;g0){for(var c,d,e,f=0,g=0;g0?this.options.ticks[g-1]:0,e=g>0?this.options.ticks_positions[g-1]:0,d=this.options.ticks[g],f=this.options.ticks_positions[g];break}if(g>0){var h=(a-e)/(f-e);b=c+h*(d-c)}}var i=this.options.min+Math.round(b/this.options.step)*this.options.step;return ithis.options.max?this.options.max:i},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};if(b=function(a,b){return c.call(this,a,b),this},b.prototype={_init:function(){},constructor:b,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,scale:"linear",focus:!1},over:!1,inDrag:!1,getValue:function(){return this.options.range?this.options.value:this.options.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this.options.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this.options.value[0]=e(this.options.value[0]),this.options.value[1]=e(this.options.value[1]),this.options.value[0]=Math.max(this.options.min,Math.min(this.options.max,this.options.value[0])),this.options.value[1]=Math.max(this.options.min,Math.min(this.options.max,this.options.value[1]))):(this.options.value=e(this.options.value),this.options.value=[Math.max(this.options.min,Math.min(this.options.max,this.options.value))],this._addClass(this.handle2,"hide"),this.options.value[1]="after"===this.options.selection?this.options.max:this.options.min),this.percentage=this.options.max>this.options.min?[this._toPercentage(this.options.value[0]),this._toPercentage(this.options.value[1]),100*this.options.step/(this.options.max-this.options.min)]:[0,0,100],this._layout();var f=this.options.range?this.options.value:this.options.value[0];return b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this._setDataVal(f),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this.options.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this.options.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this.options.enabled?this.disable():this.enable(),this},isEnabled:function(){return this.options.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),c.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._layout(),this},_removeSliderEventHandlers:function(){this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle1.removeEventListener("focus",this.showTooltip,!1),this.handle1.removeEventListener("blur",this.hideTooltip,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.handle2.removeEventListener("focus",this.handle2Keydown,!1),this.handle2.removeEventListener("blur",this.handle2Keydown,!1),this.sliderElem.removeEventListener("mouseenter",this.showTooltip,!1),this.sliderElem.removeEventListener("mouseleave",this.hideTooltip,!1),this.sliderElem.removeEventListener("touchstart",this.mousedown,!1),this.sliderElem.removeEventListener("mousedown",this.mousedown,!1)},_bindNonQueryEventHandler:function(a,b){void 0===this.eventToCallbackMap[a]&&(this.eventToCallbackMap[a]=[]),this.eventToCallbackMap[a].push(b)},_cleanUpEventCallbacksMap:function(){for(var a=Object.keys(this.eventToCallbackMap),b=0;b0){var b=Math.max.apply(Math,this.options.ticks),c=Math.min.apply(Math,this.options.ticks),d="vertical"===this.options.orientation?"height":"width",e="vertical"===this.options.orientation?"marginTop":"marginLeft",f=this.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var g=0;if(0===this.options.ticks_positions.length)this.tickLabelContainer.style[e]=-f/2+"px",g=this.tickLabelContainer.offsetHeight;else for(h=0;hg&&(g=this.tickLabelContainer.childNodes[h].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=g+"px")}for(var h=0;h=a[0]&&i<=a[1]&&this._addClass(this.ticks[h],"in-selection"):"after"===this.options.selection&&i>=a[0]?this._addClass(this.ticks[h],"in-selection"):"before"===this.options.selection&&i<=a[0]&&this._addClass(this.ticks[h],"in-selection"),this.tickLabels[h]&&(this.tickLabels[h].style[d]=f+"px",void 0!==this.options.ticks_positions[h]&&(this.tickLabels[h].style.position="absolute",this.tickLabels[h].style[this.stylePos]=this.options.ticks_positions[h]+"%",this.tickLabels[h].style[e]=-f/2+"px"))}}if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%",this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var j=this.tooltip_min.getBoundingClientRect(),k=this.tooltip_max.getBoundingClientRect();j.right>k.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}var l;if(this.options.range){l=this.options.formatter(this.options.value),this._setText(this.tooltipInner,l),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px"),"vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px");var m=this.options.formatter(this.options.value[0]);this._setText(this.tooltipInner_min,m);var n=this.options.formatter(this.options.value[1]);this._setText(this.tooltipInner_max,n),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-top",-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-left",-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-top",-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-left",-this.tooltip_max.offsetWidth/2+"px")}else l=this.options.formatter(this.options.value[0]),this._setText(this.tooltipInner,l),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px")},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this.options.enabled)return!1;this.offset=this._offset(this.sliderElem),this.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this.percentage[0]-b),d=Math.abs(this.percentage[1]-b);this.dragged=d>c?0:1}else this.dragged=0;this.percentage[this.dragged]=this.options.reversed?100-b:b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),this._pauseEvent(a),this.options.focus&&this._triggerFocusOnHandle(this.dragged),!0},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this.options.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this.options.value[a]+c*this.options.step;return this.options.range&&(f=[a?this.options.value[0]:f,a?f:this.options.value[1]]),this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._trigger("slideStop",f),this._setDataVal(f),this._layout(),this._pauseEvent(b),!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this.options.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this.percentage[this.dragged]=this.options.reversed?100-b:b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_adjustPercentageForRangeSliders:function(a){this.options.range&&(0===this.dragged&&this.percentage[1]a&&(this.percentage[1]=this.percentage[0],this.dragged=0))},_mouseup:function(){if(!this.options.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this.inDrag=!1,this.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._trigger("slideStop",a),this._setDataVal(a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this.percentage[0]&&(b[0]=this._toValue(this.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this.percentage[1]&&(b[1]=this._toValue(this.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d=0&&t1&&(this.plts=this.plts.sort(function(t,i){return t.timestamp-i.timestamp})),!this.loadQue.empty())){console.log("load task in queue");var i=this.loadQue.front();this.loadQue.pop(),utils.isArray(i)&&this.loadPlaylists.apply(this,i)}},this),Event.on("rfshBadge",function(){for(var t=this.$.lis().find(".badge"),i=0;i0&&s<0&&(i=Math.min(this.$.lyric[0].offsetHeight>>1,-s));var n=-this.$.ulDOM.offsetHeight+20;if(t<0&&s>n&&(i=Math.max(-this.$.lyric[0].offsetHeight>>1,n-s)),i){this.$.ulDOM.style.marginTop=s+i+"px";var a=this;player.playing&&(null!==this.delay.id&&clearTimeout(this.delay.id),this.delay.id=setTimeout(function(){a.delay.id=null,a.autoScroll()},this.delay.time))}},setDesc:function(t){this.$.pic.attr("src",t.pic||""),this.$.title.text(t.title),this.$.album.text(t.album),this.$.artist.text(t.artist)},setLrc:function(t){if(this.$.ulDOM.innerHTML="",this.$.ulDOM.style.marginTop=0,this.$.liDOM=[],this.lrcObj)if(this.lrcObj.noTime){this.appendline("****歌词无法滚动****");for(var i=0;i>2;this.$.ulDOM.style.marginTop=i>e?e-i+"px":0}else this.$.ulDOM.style.marginTop=0},seek:function(t){if(this.state&&this.lrcObj&&!this.lrcObj.noTime){var i=utils.binarySearch(this.lrcObj.lines,t,function(t){return t.time});i!=-1?this._index!==i&&(this._index>=0&&(this.$.liDOM[this._index].className=""),this.$.liDOM[i].className="current",this._index=i,this.autoScroll()):this.autoScroll()}},parse:function(){var t=/\[(\d{2,})\:(\d{2})(?:\.(\d{2,3}))?\]/g,i={title:"ti",artist:"ar",album:"al",offset:"offset",by:"by"},e=function(t){return t&&t.replace(/(^\s*|\s*$)/m,"")},s=function(i){return t.test(i)};return function(n){if(!utils.isString(n))return void console.log("invalid param");this.lrc=e(n);var a=n.split(/\n/);if(!s(this.lrc))return this.noTime=1,void(this.txt=a);this.tags={},this.lines=[];var r,l,o,h;for(var c in i)r=n.match(new RegExp("\\["+i[c]+":([^\\]]*)\\]","i")),this.tags[c]=r&&r[1]||"";t.lastIndex=0;for(var d=0,u=a.length;dthis.duration&&(t=this.duration),this.$.curTime.text(this.timeFormartter(t)),this.progress.slider("setValue",t)},setDuration:function(t){this.duration=t,this.progress.slider("setAttribute","max",t),this.$.totTime.text(this.timeFormartter(t))},setVolume:function(t){utils.isNumber(t)&&t>=0&&t<=1&&(this.volume.slider("setValue",t),this.audio.volume=t)},toggleVolMute:function(){var t=this.volume.slider("isEnabled");t?(this.audio.muted=!0,this.volume.slider("disable"),this.$.volIcon.attr("class","glyphicon glyphicon-volume-off")):(this.audio.muted=!1,this.volume.slider("enable"),this.$.volIcon.attr("class","glyphicon glyphicon-volume-up"))},setOrder:function(t){var i=this.orderList.length;utils.isNumber(t)&&(this.ID=(t-1+i)%i),this.ID=(this.ID+1)%i;var e=this.orderList[this.ID];this.$.order.attr("class","glyphicon glyphicon-"+e.value),this.$.order.attr("title",e.name)},listen:function(){var t=this;this.audio.onloadedmetadata=function(){t.setDuration(this.duration)},this.audio.onerror=function(){t.playing=!1;var i;switch(this.error.code){case 1:i="未选择歌曲";break;case 2:i="糟糕,网络貌似除了点问题";break;case 3:i="糟糕,缺少相应解码器";break;case 4:i="糟糕,文件或网络资源无法访问";break;default:i="未知错误,error code:"+this.error.code}t.stop(i)};var i=!1;this.audio.ontimeupdate=function(){i||t.setCurrentTime(this.currentTime),lrc.seek(this.currentTime)},this.audio.onended=function(){t.playing=!1,t.playNext()},this.audio.onpause=function(){t.playing=!1},this.audio.onplaying=function(){t.playing=!0},this.progress.slider("on","slideStart",function(){i=!0}),this.progress.slider("on","slideStop",function(){var e=t.progress.slider("getValue");t.audio.currentTime=e,t.setCurrentTime(e),i=!1}),this.volume.slider("on","slide",function(i){t.audio.volume=i}),this.volume.slider("on","change",function(i){i.oldValue!=i.newValue&&(t.audio.volume=i.newValue)})}};var Playlist=function(t,i){this.$={frame:$(t),tr:function(){return this.frame.find("tr")}},this.index=i,this.timestamp=category.plts[i].timestamp,this.songList=category.plts[i].songList,this.canDel=entry.getMode(category.plts[i].type,1),this.domCache=[],this.ID=-1,this.length=0,this.load(),this.listen()};Playlist.prototype={show:function(){var t=category.$.table;"none"==t.css("display")?(this.$.frame.show(),t.fadeIn()):this.$.frame.fadeIn()},hide:function(){this.$.frame.hide()},load:function(){this.$.frame.empty();for(var t=0;t=this.songList.length&&(this.songList.push(t),Event.emit("rfshBadge"))},removeItem:function(t){if(t<0||t>this.length)throw"index out of range";if(!this.canDel)throw"insufficient permission to remove";t==this.ID&&(this.ID=-1,player.stop()),this.ID>t&&this.ID--;var i=this.$.tr();i.eq(t).remove();var e=i.slice(t+1);e.each(function(){var t=$(this).children("td").first(),i=t.text();i&&t.text(Number(i)-1)}),this.songList.splice(t,1),this.length--,Event.emit("rfshBadge")},setState:function(t){if(t=t||0,t!=this.ID){if(t>=this.length)throw"index out of range";var i=this.$.tr(),e=[],s=[];t>=0&&(e=[i[t]],s=['']),this.ID>=0&&(e.push(i[this.ID]),s.push(this.ID+1)),$(e).toggleClass("active");for(var n=$(e).children("td:first-child"),a=0;a*",ghostClass:"sortable-ghost",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0};for(var e in d)!(e in b)&&(b[e]=d[e]);var g=b.group;g&&"object"==typeof g||(g=b.group={name:g}),["pull","put"].forEach(function(a){a in g||(g[a]=!0)}),b.groups=" "+g.name+(g.put.join?" "+g.put.join(" "):"")+" ";for(var h in this)"_"===h.charAt(0)&&(this[h]=c(this,this[h]));f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"dragover",this),f(a,"dragenter",this),Q.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){t&&t.state!==a&&(i(t,"display",a?"none":""),!a&&t.state&&u.insertBefore(t,r),t.state=a)}function c(a,b){var c=P.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(P.call(arguments)))}}function d(a,b,c){if(a){c=c||J,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function e(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,!1)}function g(a,b,c){a.removeEventListener(b,c,!1)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(G," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(G," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return J.defaultView&&J.defaultView.getComputedStyle?c=J.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function k(a){a.draggable=!1}function l(){M=!1}function m(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return b.clientY-(d.top+d.height)>5&&c}function n(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function o(a){for(var b=0;a&&(a=a.previousElementSibling);)"TEMPLATE"!==a.nodeName.toUpperCase()&&b++;return b}function p(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function q(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}var r,s,t,u,v,w,x,y,z,A,B,C,D,E,F={},G=/\s+/g,H="Sortable"+(new Date).getTime(),I=window,J=I.document,K=I.parseInt,L=!!("draggable"in J.createElement("div")),M=!1,N=function(a,b,c,d,e,f,g){var h=J.createEvent("Event"),i=(a||b[H]).options,j="on"+c.charAt(0).toUpperCase()+c.substr(1);h.initEvent(c,!0,!0),h.item=d||b,h.from=e||b,h.clone=t,h.oldIndex=f,h.newIndex=g,i[j]&&i[j].call(a,h),b.dispatchEvent(h)},O=Math.abs,P=[].slice,Q=[],R=p(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h=b.scrollSensitivity,i=b.scrollSpeed,j=a.clientX,k=a.clientY,l=window.innerWidth,m=window.innerHeight;if(x!==c&&(w=b.scroll,x=c,w===!0)){w=c;do if(w.offsetWidth=l-j)-(h>=j),g=(h>=m-k)-(h>=k),(f||g)&&(d=I)),(F.vx!==f||F.vy!==g||F.el!==d)&&(F.el=d,F.vx=f,F.vy=g,clearInterval(F.pid),d&&(F.pid=setInterval(function(){d===I?I.scrollTo(I.pageXOffset+f*i,I.pageYOffset+g*i):(g&&(d.scrollTop+=g*i),f&&(d.scrollLeft+=f*i))},24)))}},30);return a.prototype={constructor:a,_onTapStart:function(a){var b=this,c=this.el,e=this.options,f=a.type,g=a.touches&&a.touches[0],h=(g||a).target,i=h,j=e.filter;if(!("mousedown"===f&&0!==a.button||e.disabled)&&(h=d(h,e.draggable,c))){if(A=o(h),"function"==typeof j){if(j.call(this,a,h,this))return N(b,i,"filter",h,c,A),void a.preventDefault()}else if(j&&(j=j.split(",").some(function(a){return a=d(i,a.trim(),c),a?(N(b,a,"filter",h,c,A),!0):void 0})))return void a.preventDefault();(!e.handle||d(i,e.handle,c))&&this._prepareDragStart(a,g,h)}},_prepareDragStart:function(a,b,c){var d,e=this,g=e.el,h=e.options,i=g.ownerDocument;c&&!r&&c.parentNode===g&&(D=a,u=g,r=c,v=r.nextSibling,C=h.group,d=function(){e._disableDelayedDrag(),r.draggable=!0,h.ignore.split(",").forEach(function(a){j(r,a.trim(),k)}),e._triggerDragStart(b)},f(i,"mouseup",e._onDrop),f(i,"touchend",e._onDrop),f(i,"touchcancel",e._onDrop),h.delay?(f(i,"mousemove",e._disableDelayedDrag),f(i,"touchmove",e._disableDelayedDrag),e._dragStartTimer=setTimeout(d,h.delay)):d())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag)},_triggerDragStart:function(a){a?(D={target:r,clientX:a.clientX,clientY:a.clientY},this._onDragStart(D,"touch")):L?(f(r,"dragend",this),f(u,"dragstart",this._onDragStart)):this._onDragStart(D,!0);try{J.selection?J.selection.empty():window.getSelection().removeAllRanges()}catch(b){}},_dragStarted:function(){u&&r&&(h(r,this.options.ghostClass,!0),a.active=this,N(this,u,"start",r,u,A))},_emulateDragOver:function(){if(E){i(s,"display","none");var a=J.elementFromPoint(E.clientX,E.clientY),b=a,c=" "+this.options.group.name,d=Q.length;if(b)do{if(b[H]&&b[H].options.groups.indexOf(c)>-1){for(;d--;)Q[d]({clientX:E.clientX,clientY:E.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);i(s,"display","")}},_onTouchMove:function(a){if(D){var b=a.touches?a.touches[0]:a,c=b.clientX-D.clientX,d=b.clientY-D.clientY,e=a.touches?"translate3d("+c+"px,"+d+"px,0)":"translate("+c+"px,"+d+"px)";E=b,i(s,"webkitTransform",e),i(s,"mozTransform",e),i(s,"msTransform",e),i(s,"transform",e),a.preventDefault()}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;if(this._offUpEvents(),"clone"==C.pull&&(t=r.cloneNode(!0),i(t,"display","none"),u.insertBefore(t,r)),b){var e,g=r.getBoundingClientRect(),h=i(r);s=r.cloneNode(!0),i(s,"top",g.top-K(h.marginTop,10)),i(s,"left",g.left-K(h.marginLeft,10)),i(s,"width",g.width),i(s,"height",g.height),i(s,"opacity","0.8"),i(s,"position","fixed"),i(s,"zIndex","100000"),u.appendChild(s),e=s.getBoundingClientRect(),i(s,"width",2*g.width-e.width),i(s,"height",2*g.height-e.height),"touch"===b?(f(J,"touchmove",this._onTouchMove),f(J,"touchend",this._onDrop),f(J,"touchcancel",this._onDrop)):(f(J,"mousemove",this._onTouchMove),f(J,"mouseup",this._onDrop)),this._loopId=setInterval(this._emulateDragOver,150)}else c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,r)),f(J,"drop",this);setTimeout(this._dragStarted,0)},_onDragOver:function(a){var c,e,f,g=this.el,h=this.options,j=h.group,k=j.put,n=C===j,o=h.sort;if(void 0!==a.preventDefault&&(a.preventDefault(),!h.dragoverBubble&&a.stopPropagation()),C&&!h.disabled&&(n?o||(f=!u.contains(r)):C.pull&&k&&(C.name===j.name||k.indexOf&&~k.indexOf(C.name)))&&(void 0===a.rootEl||a.rootEl===this.el)){if(R(a,h,this.el),M)return;if(c=d(a.target,h.draggable,g),e=r.getBoundingClientRect(),f)return b(!0),void(t||v?u.insertBefore(r,t||v):o||u.appendChild(r));if(0===g.children.length||g.children[0]===s||g===a.target&&(c=m(g,a))){if(c){if(c.animated)return;q=c.getBoundingClientRect()}b(n),g.appendChild(r),this._animate(e,r),c&&this._animate(q,c)}else if(c&&!c.animated&&c!==r&&void 0!==c.parentNode[H]){y!==c&&(y=c,z=i(c));var p,q=c.getBoundingClientRect(),w=q.right-q.left,x=q.bottom-q.top,A=/left|right|inline/.test(z.cssFloat+z.display),B=c.offsetWidth>r.offsetWidth,D=c.offsetHeight>r.offsetHeight,E=(A?(a.clientX-q.left)/w:(a.clientY-q.top)/x)>.5,F=c.nextElementSibling;M=!0,setTimeout(l,30),b(n),p=A?c.previousElementSibling===r&&!B||E&&B:F!==r&&!D||E&&D,p&&!F?g.appendChild(r):c.parentNode.insertBefore(r,p?F:c),this._animate(e,r),this._animate(q,c)}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(J,"touchmove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(F.pid),clearTimeout(this.dragStartTimer),g(J,"drop",this),g(J,"mousemove",this._onTouchMove),g(c,"dragstart",this._onDragStart),this._offUpEvents(),b&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation(),s&&s.parentNode.removeChild(s),r&&(g(r,"dragend",this),k(r),h(r,this.options.ghostClass,!1),u!==r.parentNode?(B=o(r),N(null,r.parentNode,"sort",r,u,A,B),N(this,u,"sort",r,u,A,B),N(null,r.parentNode,"add",r,u,A,B),N(this,u,"remove",r,u,A,B)):(t&&t.parentNode.removeChild(t),r.nextSibling!==v&&(B=o(r),N(this,u,"update",r,u,A,B),N(this,u,"sort",r,u,A,B))),a.active&&N(this,u,"end",r,u,A,B)),u=r=s=v=t=w=x=D=E=y=z=C=a.active=null,this.save())},handleEvent:function(a){var b=a.type;"dragover"===b||"dragenter"===b?r&&(this._onDragOver(a),e(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],c=this.el.children,e=0,f=c.length,g=this.options;f>e;e++)a=c[e],d(a,g.draggable,this.el)&&b.push(a.getAttribute(g.dataIdAttr)||n(a));return b},sort:function(a){var b={},c=this.el;this.toArray().forEach(function(a,e){var f=c.children[e];d(f,this.options.draggable,c)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(c.removeChild(b[a]),c.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return d(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:void(c[a]=b)},destroy:function(){var a=this.el;a[H]=null,g(a,"mousedown",this._onTapStart),g(a,"touchstart",this._onTapStart),g(a,"dragover",this),g(a,"dragenter",this),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),Q.splice(Q.indexOf(this._onDragOver),1),this._onDrop(),this.el=a=null}},a.utils={on:f,off:g,css:i,find:j,bind:c,is:function(a,b){return!!d(a,b,a)},extend:q,throttle:p,closest:d,toggleClass:h,index:o},a.version="1.2.0",a.create=function(b,c){return new a(b,c)},a}); -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 网易音乐盒
专辑封面
  • 专辑:
  • 艺术家:
    -------------------------------------------------------------------------------- /dist/libs/Crypto.js: -------------------------------------------------------------------------------- 1 | function addPadding(e,r){var t=r.length;for(c=0;t>0&&"0"==r[c];c++)t--;for(var a=t-e.length,n="",c=0;c=0;c--)a+=e[c];var d=bigInt(new Buffer(a).toString("hex"),n),o=bigInt(r,n),f=bigInt(t,n),b=d.modPow(o,f);return addPadding(b.toString(n),t)}function createSecretKey(e){for(var r="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",t="",a=0;a=0&&(config.content.searchLimit=e,config.isChanged=1)},fm.getLocal=function(e){var n=this;process.nextTick(function(){var t=n.loadMusicDirSync();utils.isString(t)?e(t):e(null,pack([{timestamp:0,name:n.getMusicDir(),songList:t,type:"local"}]))})},fm.loadMusicDirSync=function(e){var e=e||this.getMusicDir(),n=[],t=[];try{t=fs.readdirSync(e)}catch(i){try{fs.mkdirSync(e)}catch(i){return{msg:"[loadMusicDir]failed to read or create dir, please try another one.",type:1}}}var c=this;t=t.map(function(n){return path.join(e,n)}).filter(function(e){var t=fs.statSync(e);if(t&&t.isDirectory())return n=n.concat(c.loadMusicDirSync(e)),!1;for(var i=path.extname(e),o=0;o>1;i(e[o])<=s?(n=o+1,t=o):r=o-1}return t},base.queue=function(){this._source=new Array,this._front=0},base.queue.prototype={size:function(){return this._source.length-this._front},empty:function(){return 0==this.size()},push:function(e){this._source.push(e)},pop:function(){if(!this.empty()){var e=this._source[this._front++];return this._front<<1>=this._source.length&&(this._source=this._source.slice(this._front),this._front=0),e}},front:function(){return this.size()?this._source[this._front]:void 0}};var isType=function(e){return function(s){return Object.prototype.toString.call(s)==="[object "+e+"]"}};base.isNumber=isType("Number"),base.isObject=isType("Object"),base.isFunction=isType("Function"),base.isString=isType("String"),base.isUndefined=isType("Undefined"),base.isBoolean=isType("Boolean"),base.isArray=isType("Array"),base.isNull=isType("Null"),base.isUndefinedorNull=function(e){return base.isNull(e)||base.isUndefined(e)},module.exports=base; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-23. 3 | */ 4 | var gulp = require('gulp'); 5 | var jade = require('gulp-jade'); 6 | var stylus = require('gulp-stylus'); 7 | var concat = require('gulp-concat'); 8 | var uglify = require('gulp-uglify'); 9 | var del = require('del'); 10 | var zip = require('gulp-zip'); 11 | var NwBuilder = require('nw-builder'); 12 | var info = require('./package'); 13 | gulp.task('html', function () { 14 | return gulp.src('./src/index.jade').pipe(jade({ 15 | locals: {} 16 | })).pipe(gulp.dest('./dist/')); 17 | }); 18 | 19 | gulp.task('css', function () { 20 | return gulp.src('./src/my.styl').pipe(stylus({ 21 | compress: true 22 | })).pipe(gulp.dest('./dist/assets/css/')); 23 | }); 24 | gulp.task('js', function () { 25 | return gulp.src(['./src/controller/*.js', './src/my.js']) 26 | .pipe(concat('my.js')) 27 | .pipe(uglify()) 28 | .pipe(gulp.dest('./dist/assets/js/')) 29 | }); 30 | 31 | gulp.task('clean', function (cb) { 32 | del(['./dist/libs/*'], cb); 33 | }); 34 | 35 | gulp.task('node', ['clean'], function () { 36 | //glob : https://github.com/isaacs/node-glob#glob-primer 37 | return gulp.src('./src/model/!(*Test*)') 38 | .pipe(uglify()) 39 | .pipe(gulp.dest('./dist/libs/')) 40 | }); 41 | gulp.task('default', ['html', 'css', 'js', 'node']); 42 | 43 | 44 | gulp.task('build', ['default'], function (cb) { 45 | //get no-dev node_modules 46 | var depends = Object.keys(info.dependencies).join(','); 47 | var nw = new NwBuilder({ 48 | files: './{package.json,dist/**,node_modules/{' + depends + '}/**}', // use the glob format 49 | platforms: ['linux64'], 50 | //buildType: 'versioned', 51 | version: '0.12.1' 52 | }); 53 | nw.on('log', console.log); 54 | nw.build(cb); 55 | }); 56 | 57 | gulp.task('release', ['build'], function () { 58 | return gulp.src(['./src/shell/*', './build/nwMusicBox/linux64/**']) 59 | .pipe(zip(info.version + 'linux64.zip')) 60 | .pipe(gulp.dest('./release/' + info.version)); 61 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nwMusicBox", 3 | "main": "dist/index.html", 4 | "version": "1.3.3", 5 | "window": { 6 | "title": "NetEaseMusic", 7 | "toolbar": true, 8 | "frame": true, 9 | "width": 1122, 10 | "height": 600, 11 | "icon": "dist/assets/img/icon.png" 12 | }, 13 | "platformOverrides": { 14 | "windows": { 15 | "window": { 16 | "title": "网易音乐盒 for Windows", 17 | "toolbar": false 18 | } 19 | }, 20 | "linux": { 21 | "window": { 22 | "title": "网易音乐盒 for Linux", 23 | "toolbar": false 24 | } 25 | }, 26 | "osx": { 27 | "window": { 28 | "title": "网易音乐盒 for OsX", 29 | "toolbar": false 30 | } 31 | } 32 | }, 33 | "dependencies": { 34 | "async": "^1.2.0", 35 | "big-integer": "^1.5.5", 36 | "home": "^0.1.8", 37 | "superagent": "^1.2.0" 38 | }, 39 | "devDependencies": { 40 | "del": "^1.2.0", 41 | "gulp": "^3.9.0", 42 | "gulp-concat": "^2.5.2", 43 | "gulp-jade": "^1.0.1", 44 | "gulp-stylus": "^2.0.5", 45 | "gulp-uglify": "^1.2.0", 46 | "gulp-zip": "^3.0.2", 47 | "nw-builder": "^2.0.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /snapshot/fm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stkevintan/Cube/f5745ff85cc4873c36a598d0ad942470475699d3/snapshot/fm.png -------------------------------------------------------------------------------- /src/controller/account.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description define the login action. 3 | * 4 | * @author Kevin Tan 5 | * 6 | * @constructor account.init 7 | */ 8 | var Account = function () { 9 | this.$ = { 10 | userProfile: $('#user-profile'), 11 | login: $('#login'), 12 | submit: $('#login').find('button.submit'), 13 | label: $('#login').find('label'), 14 | phone: $('#login').find('input[name="phone"]'), 15 | password: $('#login').find('input[name="password"]') 16 | } 17 | this.listen(this); 18 | } 19 | 20 | Account.prototype = { 21 | loadUser: function (uid) { 22 | //获得登录信息 23 | uid = uid || fm.getUserID(); 24 | var that = this; 25 | if (uid) { 26 | api.userProfile(uid, function (err, res) { 27 | if (err) { 28 | errorHandle(err); 29 | that.setUserProfile(); 30 | } else { 31 | that.setUserProfile(res); 32 | } 33 | }); 34 | } else { 35 | that.setUserProfile(); 36 | } 37 | }, 38 | unsign: function () { 39 | fm.setCookie(null); 40 | this.setUserProfile(); 41 | category.loadPlaylists({'net': true}); 42 | }, 43 | showlogin: function () { 44 | this.$.label.hide(); 45 | this.$.login.modal('show'); 46 | this.$.phone.focus(); 47 | }, 48 | loginErr: function (msg) { 49 | this.$.label.text(msg); 50 | this.$.label.show(); 51 | }, 52 | loginSuccess: function (profile) { 53 | this.$.label.text(''); 54 | this.$.login.modal('hide'); 55 | this.setUserProfile(profile); 56 | category.loadPlaylists({net: true}); 57 | }, 58 | /** 59 | */ 60 | setUserProfile: function (profile) { 61 | profile = profile || {nickname: '未登录', avatarUrl: ''}; 62 | nav.setMenu(profile.nickname, profile.avatarUrl); 63 | }, 64 | listen: function (that) { 65 | this.$.submit.click(function () { 66 | var $btn = $(this).button('loading'); 67 | var phone = that.$.phone.val(); 68 | var password = that.$.password.val(); 69 | api.login(phone, password, function (err, data) { 70 | if (err) { 71 | errorHandle(err); 72 | that.loginErr(err.msg); 73 | } else { 74 | that.loginSuccess(data); 75 | } 76 | $btn.button('reset'); 77 | }); 78 | 79 | }); 80 | this.$.phone.keydown(function (e) { 81 | if (e.which == 13) { 82 | that.$.password.focus(); 83 | } 84 | }); 85 | this.$.password.keydown(function (e) { 86 | if (e.which == 13) { 87 | that.$.submit.trigger('click'); 88 | } 89 | }) 90 | } 91 | } -------------------------------------------------------------------------------- /src/controller/category.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-8. 3 | * 4 | * @author Kevin Tan 5 | * 6 | * @description define the action of nav-sidebar and ptls-tools 7 | */ 8 | var loading = false; 9 | var loadSize = 0; 10 | function Category() { 11 | this.$ = { 12 | sidebar: $('#sidebar'), 13 | entry: $('#entry'), 14 | uls: function (key) { 15 | return key ? this.entry.find('#__' + key).find('ul') : 16 | this.entry.find('ul'); 17 | }, 18 | lis: function (key) { 19 | return key ? this.entry.find('#__' + key).find('li') : 20 | this.entry.find('li'); 21 | }, 22 | totSong: $("#totsong"), 23 | totlist: $("#totlist"), 24 | refresh: $('#refresh'), 25 | addlist: $('#addlist'), 26 | table: $('table') 27 | } 28 | this.isOpen = true; 29 | this.domCache = []; 30 | this.loadQue = new utils.queue();//队列 31 | this.listen(this); 32 | this.addEvents(); 33 | } 34 | 35 | Category.prototype = { 36 | loadPlaylists: function (options, isClean) { 37 | if (loading) { 38 | this.loadQue.push([options, isClean]); 39 | return; 40 | } 41 | if (isClean) { 42 | this.$plts = {}; 43 | this.plts = []; 44 | this.pLength = 0;//playlist's length 45 | 46 | this.$.entry.empty(); 47 | this.$.table.children('tbody').remove(); 48 | 49 | player.stop(); 50 | Event.once('setActive', function () { 51 | this.setActive(); 52 | }, this); 53 | } 54 | var schema = entry.schema; 55 | if (!options) { 56 | options = {}; 57 | for (var key in schema) { 58 | if (schema.hasOwnProperty(key)) { 59 | options[key] = true; 60 | } 61 | } 62 | } 63 | 64 | loadSize = 0; 65 | var that = this; 66 | for (var key in options) { 67 | if (options.hasOwnProperty(key) && options[key]) { 68 | loadSize++; 69 | this.add$EntryFrame(key, schema[key].name); 70 | } 71 | } 72 | if (loadSize) { 73 | //enter loading state 74 | this.$.refresh.addClass('loading'); 75 | loading = true; 76 | for (var key in options) { 77 | if (options.hasOwnProperty(key) && options[key]) { 78 | var loader = schema[key].loader; 79 | if (utils.isFunction(loader)) loader(callback); 80 | } 81 | } 82 | } 83 | function callback(err, plts) { 84 | if (err) errorHandle(err); 85 | else if (!utils.isArray(plts)) { 86 | errorHandle("the source doesn't return an Array!"); 87 | } else { 88 | var offset = that.plts.length; 89 | that.plts = that.plts.concat(plts); 90 | for (var i = 0; i < plts.length; i++) that.addItem(offset + i); 91 | } 92 | Event.emit('entryLoad'); 93 | } 94 | }, 95 | add$EntryFrame: function (key, name) { 96 | var old = this.$.entry.find('#__' + key); 97 | if (old.length) { 98 | var that = this; 99 | old.find('li').each(function () { 100 | that.removeItem($(this)); 101 | }); 102 | old.slideUp(600); 103 | return; 104 | } 105 | var div = createDOM('div', {class: 'plts-group', id: '__' + key, style: 'display:none'}); 106 | div.appendChild(createDOM('div', {class: 'plts-title'}, name)); 107 | div.appendChild(createDOM('ul', {class: 'nav nav-sidebar'})); 108 | Sortable.create(this.$.entry[0].appendChild(div).getElementsByTagName('ul')[0]); 109 | }, 110 | findPltIndexByTs: function (timestamp) { 111 | return utils.binarySearch(this.plts, timestamp, function (o) { 112 | return o.timestamp 113 | }); 114 | }, 115 | /** 116 | * @description add playlist 117 | * 118 | */ 119 | addItem: function (opt, instant) { 120 | var pltModel = opt; 121 | var index = opt; 122 | if (utils.isNumber(opt) && opt >= 0 && opt < this.plts.length) { 123 | pltModel = this.plts[opt]; 124 | } else {//plt don't have this model,push it in 125 | index = this.plts.length; 126 | this.plts.push(pltModel); 127 | entry.schema[pltModel.type].onadd(pltModel); 128 | } 129 | //change view (html) 130 | var ts = pltModel.timestamp; 131 | if (utils.isUndefined(ts)) { 132 | //没有时间戳 133 | ts = pltModel.timestamp = (new Date()).getTime(); 134 | } 135 | 136 | var li = createDOM('li', {'data-target': ts}); 137 | var a = createDOM('a', {title: pltModel.name, href: 'javascript:void(0)'}); 138 | var divName = createDOM('div', {class: 'name'}, pltModel.name); 139 | var divLimark = createDOM('div', {class: 'limark'}); 140 | var tbody = createDOM('tbody', {style: 'display:none', id: '_' + ts}); 141 | 142 | entry.getMode(pltModel.type, 1) && 143 | divLimark.appendChild(createDOM('span', {class: 'glyphicon glyphicon-trash'})); 144 | divLimark.appendChild(createDOM('span', {class: 'badge'}, pltModel.songList.length)); 145 | li.appendChild(a); 146 | a.appendChild(divName); 147 | a.appendChild(divLimark); 148 | this.domCache.push({type: pltModel.type, dom: li}); 149 | this.pLength++; 150 | this.$.table.append(tbody); 151 | this.$plts[ts] = new Playlist(tbody, index); 152 | if (instant) { 153 | this.addtoDOM(); 154 | this.setActive($(li)); 155 | } 156 | }, 157 | getActive: function () { 158 | return this.$.entry.find('li.active'); 159 | }, 160 | setActive: function (li) { 161 | li = li || this.$.lis().eq(0); 162 | if (li.length == 0) return; 163 | var curli = this.getActive(); 164 | if (curli == li)return; 165 | if (curli.length) { 166 | curli.removeClass('active'); 167 | this.$plts[curli.data('target')].hide(); 168 | } 169 | li.addClass('active'); 170 | this.$plts[li.data('target')].show(); 171 | }, 172 | /** 173 | * @description remove playlist by $li. 174 | * 175 | * @param {object} li - index of the playlist in category 176 | * 177 | * @throw index out of range 178 | */ 179 | removeItem: function (li) { 180 | var now$plt = this.$plts[li.data('target')]; 181 | var that = this; 182 | li.slideUp(600, function () { 183 | li.remove(); 184 | if (li.hasClass('active')) { 185 | that.setActive(); 186 | } 187 | now$plt.$.frame.remove(); 188 | }); 189 | if (now$plt == player.playlist) { 190 | player.stop(); 191 | } 192 | var index = this.findPltIndexByTs(now$plt.timestamp); 193 | if (index != -1) { 194 | entry.schema[this.plts[index].type].onremove(this.plts[index]); 195 | this.plts.splice(index, 1); 196 | } 197 | delete now$plt; 198 | this.pLength--; 199 | }, 200 | toggleOpen: function () { 201 | var list = $('.list'); 202 | var side = category.$.sidebar; 203 | if (this.isOpen) { 204 | list.animate({ 205 | 'padding-left': '0px' 206 | }, 600); 207 | side.animate({ 208 | right: '100%' 209 | }, 600) 210 | } else { 211 | side.animate({ 212 | right: '75%' 213 | }, 600); 214 | list.animate({ 215 | 'padding-left': '25%' 216 | }, 600); 217 | } 218 | this.isOpen ^= 1; 219 | 220 | }, 221 | listen: function (that) { 222 | //事件委托 223 | this.$.entry.on('click', 'li', function () { 224 | that.setActive($(this)); 225 | }); 226 | 227 | this.$.entry.on('click', 'li span.glyphicon-trash', function (e) { 228 | var it = $(this).closest('li'); 229 | that.removeItem(it); 230 | e.stopPropagation(); 231 | }); 232 | 233 | this.$.refresh.click(function () { 234 | //load entry 235 | that.loadPlaylists(null, true); 236 | }); 237 | 238 | var model = $('#inputListName'); 239 | var input = $('#inputListName input'); 240 | var submit = $('#submit-name'); 241 | this.$.addlist.click(function () { 242 | model.find('label').hide(); 243 | model.removeClass('has-error'); 244 | model.modal('show'); 245 | input.focus(); 246 | }); 247 | submit.click(function () { 248 | var val = input.val().trim(); 249 | var flag = true; 250 | if (val == '')flag = false; 251 | else that.$.lis().find('div.name').each(function () { 252 | if ($(this).text() == val)flag = false; 253 | }); 254 | if (flag) { 255 | model.modal('hide'); 256 | var plt = new PltM({name: val, type: 'user'}); 257 | that.addItem(plt, true); 258 | } else { 259 | model.find('label').fadeIn(); 260 | model.addClass('has-error'); 261 | } 262 | }); 263 | 264 | input.keydown(function (e) { 265 | if (e.which == 13) { 266 | e.preventDefault(); 267 | submit.trigger('click'); 268 | } 269 | }); 270 | }, 271 | addtoDOM: function () { 272 | if (this.domCache.length == 0)return; 273 | var tmp$ = {}; 274 | var that = this; 275 | this.domCache.forEach(function (o) { 276 | if (!tmp$[o.type]) { 277 | var $group = that.$.entry.find('#__' + o.type); 278 | $group.slideDown(600); 279 | tmp$[o.type] = $group.find('ul'); 280 | } 281 | o.dom.style.display = 'none'; 282 | tmp$[o.type][0].appendChild(o.dom); 283 | $(o.dom).slideDown(600); 284 | }); 285 | //flush dom cache 286 | this.domCache = []; 287 | Event.emit('setActive'); 288 | }, 289 | addEvents: function () { 290 | var count = 0; 291 | Event.on('entryLoad', function () { 292 | count++; 293 | this.addtoDOM(); 294 | if (count == loadSize) { 295 | //All playgroup load ready! flush domCache to dom! 296 | count = 0; 297 | loading = false; 298 | this.$.refresh.removeClass('loading'); 299 | //sort plts to fix some bugs 300 | if (this.plts.length > 1) 301 | this.plts = this.plts.sort(function (a, b) { 302 | return a.timestamp - b.timestamp; 303 | }); 304 | 305 | //load rest task in queue 306 | if (!this.loadQue.empty()) { 307 | console.log('load task in queue'); 308 | var task = this.loadQue.front(); 309 | this.loadQue.pop(); 310 | if (utils.isArray(task)) { 311 | this.loadPlaylists.apply(this, task); 312 | } 313 | } 314 | } 315 | }, this); 316 | Event.on('rfshBadge', function () { 317 | /** 318 | * @description refresh "id"th or all badge's number. 319 | * 320 | */ 321 | var badge = this.$.lis().find('.badge'); 322 | for (var i = 0; i < this.pLength; i++) { 323 | var bo = badge.eq(i); 324 | var ts = bo.closest('li').data('target'); 325 | badge.eq(i).text(this.$plts[ts].length); 326 | } 327 | }, this); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/controller/lrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-14. 3 | */ 4 | var Lrc = function () { 5 | this.$ = { 6 | panel: $('.song-detail'), 7 | lyric: $('.lyric'), 8 | ulDOM: $('.lyric ul')[0], 9 | pic: $('.song-detail img'), 10 | title: $('#info-title'), 11 | album: $('#info-album'), 12 | artist: $('#info-artist') 13 | } 14 | this.delay = { 15 | time: 3000, 16 | id: null 17 | } 18 | this.state = false; 19 | this.listen(); 20 | } 21 | Lrc.prototype = { 22 | toggle: function (flag) { 23 | if (flag === this.state)return; 24 | flag = flag || !this.state; 25 | if (flag) { 26 | this.$.panel.css('transform', 'none'); 27 | this.state = true; 28 | } else { 29 | //if current tab is radio,prevent this action 30 | if (nav.ID == 1)return; 31 | this.$.panel.css('transform', 'scale(0,0)'); 32 | this.state = false; 33 | } 34 | }, 35 | scroll: function (type) { 36 | var d = 0; 37 | var tmp = this.$.ulDOM.style.marginTop; 38 | var curTop = Number(tmp.substr(0, tmp.length - 2)); 39 | if (type > 0 && curTop < 0) { 40 | //scroll up 41 | d = Math.min(this.$.lyric[0].offsetHeight >> 1, -curTop); 42 | } 43 | var h = -this.$.ulDOM.offsetHeight + 20; 44 | if (type < 0 && curTop > h) { 45 | //scroll down 46 | d = Math.max(-this.$.lyric[0].offsetHeight >> 1, h - curTop); 47 | } 48 | if (d) { 49 | this.$.ulDOM.style.marginTop = curTop + d + 'px'; 50 | var that = this; 51 | if (player.playing) { 52 | if (this.delay.id !== null)clearTimeout(this.delay.id); 53 | this.delay.id = setTimeout(function () { 54 | that.delay.id = null; 55 | that.autoScroll(); 56 | }, this.delay.time); 57 | } 58 | 59 | } 60 | }, 61 | setDesc: function (opt) { 62 | this.$.pic.attr('src', opt.pic || ''); 63 | this.$.title.text(opt.title); 64 | this.$.album.text(opt.album); 65 | this.$.artist.text(opt.artist); 66 | }, 67 | setLrc: function (msg) { 68 | this.$.ulDOM.innerHTML = ''; 69 | this.$.ulDOM.style.marginTop = 0; 70 | this.$.liDOM = []; 71 | if (!this.lrcObj) { 72 | msg = msg || '****没有歌词****'; 73 | this.appendline(msg); 74 | } else if (this.lrcObj.noTime) { 75 | this.appendline('****歌词无法滚动****'); 76 | for (var i = 0; i < this.lrcObj.txt.length; i++) { 77 | this.appendline(this.lrcObj.txt[i]); 78 | } 79 | } else { 80 | 81 | 82 | for (var i = 0; i < this.lrcObj.lines.length; i++) { 83 | this.appendline(this.lrcObj.lines[i].txt); 84 | } 85 | } 86 | }, 87 | load: function (songM) { 88 | this.setDesc(songM); 89 | this.lrcObj = null; 90 | this._index = -1; 91 | if (!songM.id) { 92 | this.setLrc(); 93 | } else { 94 | var that = this; 95 | api.songLyric(songM.id, function (err, res) { 96 | if (!err) { 97 | that.lrcObj = new that.parse(res); 98 | } 99 | that.setLrc(); 100 | }); 101 | } 102 | }, 103 | appendline: function (txt) { 104 | var li = createDOM('li', null, txt); 105 | this.$.liDOM.push(li); 106 | this.$.ulDOM.appendChild(li); 107 | }, 108 | autoScroll: function (target) { 109 | if (this.delay.id !== null)return; 110 | if (this._index == -1 && !target) { 111 | this.$.ulDOM.style.marginTop = 0; 112 | } else { 113 | target = target || this.$.liDOM[this._index]; 114 | var top = target.offsetTop; 115 | var pos = this.$.lyric[0].offsetHeight >> 2; 116 | this.$.ulDOM.style.marginTop = top > pos ? pos - top + 'px' : 0; 117 | } 118 | }, 119 | seek: function (time) { 120 | if (this.state && this.lrcObj && !this.lrcObj.noTime) { 121 | var index = utils.binarySearch(this.lrcObj.lines, time, function (o) { 122 | return o.time; 123 | }); 124 | if (index != -1) { 125 | if (this._index !== index) { 126 | if (this._index >= 0) { 127 | this.$.liDOM[this._index].className = ''; 128 | } 129 | this.$.liDOM[index].className = 'current'; 130 | this._index = index; 131 | this.autoScroll(); 132 | } 133 | } else { 134 | this.autoScroll(); 135 | } 136 | } 137 | }, 138 | parse: (function () { 139 | var timeExp = /\[(\d{2,})\:(\d{2})(?:\.(\d{2,3}))?\]/g 140 | var tagsRegMap = { 141 | title: 'ti' 142 | , artist: 'ar' 143 | , album: 'al' 144 | , offset: 'offset' 145 | , by: 'by' 146 | }; 147 | var trim = function (lrc) { 148 | return lrc && lrc.replace(/(^\s*|\s*$)/m, '') 149 | } 150 | var isLrc = function (lrc) { 151 | return timeExp.test(lrc); 152 | } 153 | return function (lrc) { 154 | if (!utils.isString(lrc)) { 155 | console.log('invalid param'); 156 | return; 157 | } 158 | this.lrc = trim(lrc); 159 | var lines = lrc.split(/\n/); 160 | if (!isLrc(this.lrc)) { 161 | this.noTime = 1; 162 | this.txt = lines; 163 | return; 164 | } 165 | this.tags = {};//ID tags. 标题, 歌手, 专辑 166 | this.lines = [];//详细的歌词信息 167 | var res, line, time, _last; 168 | 169 | for (var tag in tagsRegMap) { 170 | res = lrc.match(new RegExp('\\[' + tagsRegMap[tag] + ':([^\\]]*)\\]', 'i')); 171 | this.tags[tag] = res && res[1] || ''; 172 | } 173 | timeExp.lastIndex = 0; 174 | for (var i = 0, l = lines.length; i < l; i++) { 175 | while (time = timeExp.exec(lines[i])) { 176 | _last = timeExp.lastIndex; 177 | line = trim(lines[i].replace(timeExp, '')); 178 | timeExp.lastIndex = _last; 179 | this.lines.push({ 180 | time: time[1] * 60 + 1 * time[2] + (time[3] || 0) / 1000 181 | , originLineNum: i 182 | , txt: line 183 | }); 184 | } 185 | } 186 | this.lines.sort(function (a, b) { 187 | return a.time - b.time; 188 | }); 189 | } 190 | })(), 191 | listen: function () { 192 | } 193 | } -------------------------------------------------------------------------------- /src/controller/nav.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-8. 3 | * @description define the action of the top navigation bar 4 | * 5 | * @author Kevin Tan 6 | * 7 | * @constructor nav.init 8 | * 9 | */ 10 | var tabName = ['#main', '#radio', '#settings']; 11 | var Nav = function () { 12 | this.ID = 0; 13 | this.$ = { 14 | tabBody: tabName.map(function (s) { 15 | return $(s); 16 | }), 17 | tabHead: tabName.map(function (s) { 18 | return $(s + '-nav'); 19 | }), 20 | search: $('#search'), 21 | UserImg: $('#user-profile img'), 22 | UserTxt: $('#user-profile p'), 23 | MenuItem0: $('#menugo-0'), 24 | MenuItem1: $('#menugo-1') 25 | }; 26 | this.WinMode = { 27 | isSimp: 0, 28 | width: null, 29 | height: null 30 | } 31 | this.listen(this); 32 | } 33 | Nav.prototype = { 34 | /** 35 | * @description switch to "id"th tab 36 | * 37 | * @param {number} id - the index of tab to switch 38 | */ 39 | setState: function (id) { 40 | id = id || 0; 41 | if (id == this.ID)return; 42 | var that = this; 43 | this.$.tabBody[this.ID].fadeOut(100, function () { 44 | that.$.tabBody[id].fadeIn(100); 45 | }); 46 | $([this.$.tabHead[this.ID], this.$.tabHead[id]]).toggleClass('active'); 47 | this.ID = id; 48 | if (id == 1) { 49 | radio.show(); 50 | } else { 51 | lrc.toggle(false); 52 | } 53 | }, 54 | /** 55 | * @description search keywords from UI,add result playlist to category 56 | * 57 | * @throw search api returns an error 58 | */ 59 | search: function () { 60 | var key = this.$.search.val(); 61 | api.search(key, function (err, results) { 62 | if (err) throw 'search api returns an error:' + err; 63 | var name = '"' + key + '"的搜索结果'; 64 | var songList = results; 65 | category.addItem(new PltM({ 66 | name: name, 67 | type: 'user', 68 | songList: songList 69 | }), true); 70 | }); 71 | }, 72 | close: function () { 73 | win.hide(); 74 | console.log('save the config changes...'); 75 | fm.SaveChanges(category.recKey, category.plts, function (err) { 76 | if (err)console.log('save failed', err); 77 | else console.log('saved'); 78 | win.close(true); 79 | }); 80 | }, 81 | minimize: function () { 82 | win.minimize(); 83 | }, 84 | maximize: function () { 85 | if (this.WinMode.isMaxi) { 86 | win.unmaximize(); 87 | } else { 88 | win.maximize(); 89 | } 90 | this.WinMode.isMaxi ^= 1; 91 | }, 92 | /** 93 | * toggle Window between normal size and mini size 94 | * this is a bug of nw.js. temporary solution. 95 | * @param {boolean} [flag=false] - if true,force to normal size,vice verse. 96 | */ 97 | toggleWindow: function (flag) { 98 | if (flag || this.WinMode.isSimp) { 99 | win.resizeTo(this.WinMode.width, this.WinMode.height); 100 | } else { 101 | win.unmaximize(); 102 | this.WinMode.width = win.width; 103 | this.WinMode.height = win.height; 104 | win.resizeTo(560, 60); 105 | } 106 | this.WinMode.isSimp ^= 1; 107 | }, 108 | /** 109 | * @description set menu display state 110 | * 111 | * @param type 0 - unsigned, 1 - signed 112 | */ 113 | setMenu: function (nickname, avatarUrl) { 114 | if (avatarUrl) { 115 | this.$.MenuItem0.hide(); 116 | this.$.MenuItem1.show(); 117 | } else { 118 | this.$.MenuItem1.hide(); 119 | this.$.MenuItem0.show(); 120 | } 121 | this.$.UserImg.attr('src', avatarUrl); 122 | this.$.UserTxt.text(nickname); 123 | }, 124 | /** 125 | * @description define the action after click a MenuItem 126 | * 127 | * @param index - the index of the MenuItem 128 | */ 129 | clickMenu: function (index) { 130 | if (index == 0) { 131 | account.showlogin(); 132 | } else { 133 | account.unsign(); 134 | } 135 | }, 136 | /** 137 | * @description attach handler to events 138 | * 139 | * @param {object} that - the reference of the outer object 140 | */ 141 | listen: function (that) { 142 | $(this.$.search).keydown(function (e) { 143 | if (e.which == 13) { 144 | e.preventDefault(); 145 | that.search(); 146 | } 147 | }); 148 | 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/controller/player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-10. 3 | */ 4 | /** 5 | * Created by kevin on 15-5-8. 6 | * @description define the action of control bar 7 | * 8 | * @author Kevin Tan 9 | * 10 | * @constructor controls.init 11 | */ 12 | 13 | var Player = function () { 14 | this.$ = { 15 | play: $('#play'), 16 | pause: $('#pause'), 17 | order: $('#order span'), 18 | backward: $('#backward'), 19 | volume: $('#volume'), 20 | volIcon: $('#vol-icon'), 21 | songPic: $('#song-pic'), 22 | totTime: $('#tot-time'), 23 | curTime: $('#cur-time'), 24 | title: $('h4.media-heading'), 25 | progress: $('.media-body input') 26 | } 27 | //初始化播放器 28 | this.audio = new Audio(); 29 | //初始化进度条 30 | this.progress = this.$.progress.slider({ 31 | id: 'progress', 32 | value: 0, 33 | min: 0, 34 | max: 0, 35 | step: 1, 36 | formatter: this.timeFormartter 37 | }); 38 | this.playling = false; 39 | this.volume = this.$.volume.slider({ 40 | value: 0.5, 41 | min: 0, 42 | max: 1, 43 | step: 0.01 44 | }); 45 | this.ID = 1; 46 | this.stop(); 47 | this.duration = -1; 48 | this.setOrder(this.ID); 49 | this.setVolume(0.5); 50 | this.listen(); 51 | } 52 | Player.prototype = { 53 | orderList: [{ 54 | name: '单曲循环', 55 | value: 'repeat' 56 | }, { 57 | name: '列表循环', 58 | value: 'refresh' 59 | }, { 60 | name: '顺序播放', 61 | value: 'align-justify' 62 | }, { 63 | name: '随机播放', 64 | value: 'random' 65 | }], 66 | play: function (songM) { 67 | if (this.playlist && this.playlist.ID != -1 || radio.state) { 68 | this.$.play.hide(); 69 | this.$.pause.show(); 70 | songM && this.setMetaData(songM); 71 | this.audio.play(); 72 | } 73 | }, 74 | playNext: function (type) { 75 | type = type || this.ID; 76 | if (this.playlist && this.playlist.ID != -1) { 77 | var nextSong = this.playlist.next(type); 78 | if (nextSong) this.play(nextSong); 79 | else this.stop(); 80 | } 81 | if (radio.state && type == 1) { 82 | radio.playNext(); 83 | } 84 | }, 85 | pause: function () { 86 | this.$.play.show(); 87 | this.$.pause.hide(); 88 | this.audio.pause(); 89 | }, 90 | stop: function (msg, noExit) { 91 | this.audio.pause(); 92 | if (noExit) { 93 | this.audio.currentTime = 0; 94 | this.setCurrentTime(0); 95 | } else { 96 | msg = msg || '未选择歌曲'; 97 | lrc.load({ 98 | title: msg, 99 | album: '未知', 100 | artist: '未知' 101 | }); 102 | if (this.playlist) 103 | this.playlist.setState(-1); 104 | this.playlist = null; 105 | this.$.play.show(); 106 | this.$.pause.hide(); 107 | this.setDuration(0); 108 | this.setHead(msg, ''); 109 | } 110 | }, 111 | /** 112 | * format 'val' (s) to 'mm:ss' 113 | * 114 | * @param {number} val - time (s) 115 | * 116 | * @return {string} 117 | */ 118 | timeFormartter: function (val) { 119 | var num = Math.ceil(val); 120 | var ss = num % 60; 121 | var mm = Math.floor(num / 60); 122 | var strs = (ss < 10 ? '0' : '') + ss; 123 | var strm = (mm < 10 ? '0' : '') + mm; 124 | return strm + ':' + strs; 125 | }, 126 | //UI 127 | setHead: function (title, pic) { 128 | this.$.songPic.attr('src', pic); 129 | this.$.title.text(title); 130 | } 131 | , 132 | setMetaData: function (songM) { 133 | //load lrc 134 | lrc.load(songM); 135 | this.setHead(songM.title, songM.pic); 136 | this.audio.src = songM.src; 137 | showNotify('现在播放:' + songM.title); 138 | }, 139 | //UI 140 | setCurrentTime: function (curTime) { 141 | if (curTime > this.duration) curTime = this.duration; 142 | this.$.curTime.text(this.timeFormartter(curTime)); 143 | this.progress.slider('setValue', curTime); 144 | }, 145 | //UI 146 | setDuration: function (duration) { 147 | this.duration = duration; 148 | this.progress.slider('setAttribute', 'max', duration); 149 | this.$.totTime.text(this.timeFormartter(duration)); 150 | }, 151 | setVolume: function (val) { 152 | if (utils.isNumber(val) && val >= 0 && val <= 1) { 153 | this.volume.slider('setValue', val); 154 | this.audio.volume = val; 155 | } 156 | }, 157 | toggleVolMute: function () { 158 | var state = this.volume.slider('isEnabled'); 159 | if (state) { 160 | //mute 161 | this.audio.muted = true; 162 | this.volume.slider('disable'); 163 | this.$.volIcon.attr('class', 'glyphicon glyphicon-volume-off'); 164 | } else { 165 | //unmute 166 | this.audio.muted = false; 167 | this.volume.slider('enable'); 168 | this.$.volIcon.attr('class', 'glyphicon glyphicon-volume-up'); 169 | } 170 | }, 171 | /** 172 | * set cur mode to the 'mode'th playMode:single-repeat->list-repeat->no-repeat->random 173 | * 174 | * @param {number} [mode] if no exists,set cur mode to the next mode; 175 | */ 176 | setOrder: function (mode) { 177 | var len = this.orderList.length; 178 | if (utils.isNumber(mode)) { 179 | this.ID = (mode - 1 + len) % len; 180 | } 181 | this.ID = (this.ID + 1) % len; 182 | var tag = this.orderList[this.ID]; 183 | this.$.order.attr('class', 'glyphicon glyphicon-' + tag.value); 184 | this.$.order.attr('title', tag.name); 185 | }, 186 | listen: function () { 187 | var that = this; 188 | this.audio.onloadedmetadata = function () { 189 | that.setDuration(this.duration); 190 | }; 191 | this.audio.onerror = function () { 192 | that.playing = false; 193 | var msg; 194 | switch (this.error.code) { 195 | case 1: 196 | msg = '未选择歌曲'; 197 | break; 198 | case 2: 199 | msg = '糟糕,网络貌似除了点问题'; 200 | break; 201 | case 3: 202 | msg = '糟糕,缺少相应解码器'; 203 | break; 204 | case 4: 205 | msg = '糟糕,文件或网络资源无法访问'; 206 | break; 207 | default: 208 | msg = '未知错误,error code:' + this.error.code; 209 | } 210 | that.stop(msg); 211 | }; 212 | var ondrag = false; 213 | this.audio.ontimeupdate = function () { 214 | if (!ondrag) that.setCurrentTime(this.currentTime); 215 | lrc.seek(this.currentTime); 216 | }; 217 | this.audio.onended = function () { 218 | that.playing = false; 219 | that.playNext(); 220 | }; 221 | this.audio.onpause = function () { 222 | that.playing = false; 223 | } 224 | this.audio.onplaying = function () { 225 | that.playing = true; 226 | } 227 | 228 | this.progress.slider('on', 'slideStart', function () { 229 | ondrag = true; 230 | }); 231 | this.progress.slider('on', 'slideStop', function () { 232 | var nowTime = that.progress.slider('getValue'); 233 | that.audio.currentTime = nowTime; 234 | that.setCurrentTime(nowTime); 235 | ondrag = false; 236 | }); 237 | this.volume.slider('on', 'slide', function (val) { 238 | that.audio.volume = val 239 | }); 240 | this.volume.slider('on', 'change', function (o) { 241 | if (o.oldValue != o.newValue) { 242 | that.audio.volume = o.newValue; 243 | } 244 | }); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/controller/playlist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-8. 3 | * @description define playlist's action 4 | * 5 | * @author Kevin Tan 6 | * 7 | * @constructor playlist.init 8 | */ 9 | var Playlist = function (frame, index) { 10 | this.$ = { 11 | frame: $(frame), 12 | tr: function () { 13 | return this.frame.find('tr'); 14 | } 15 | }; 16 | this.index = index; 17 | this.timestamp = category.plts[index].timestamp; 18 | this.songList = category.plts[index].songList; 19 | this.canDel = entry.getMode(category.plts[index].type, 1); 20 | this.domCache = []; 21 | this.ID = -1; 22 | this.length = 0; 23 | this.load(); 24 | this.listen(); 25 | } 26 | 27 | Playlist.prototype = { 28 | show: function () { 29 | var table = category.$.table; 30 | if (table.css('display') == 'none') { 31 | this.$.frame.show(); 32 | table.fadeIn(); 33 | } else { 34 | this.$.frame.fadeIn(); 35 | } 36 | }, 37 | hide: function () { 38 | this.$.frame.hide(); 39 | }, 40 | /** 41 | * @description load songs of the playlist 42 | */ 43 | load: function () { 44 | this.$.frame.empty(); 45 | for (var i = 0; i < this.songList.length; i++) { 46 | this.addItem(this.songList[i]); 47 | } 48 | this.addtoDOM(); 49 | }, 50 | /** 51 | * @description add song object 52 | * 53 | * @param {object} songModel - song object 54 | * @param {string} songModel.title - song's title 55 | * @param {string} [songModel.artist] - song's artist 56 | * @param {string} [songModel.album] - song's album 57 | * @param {boolean} [instant] - insert to dom instantly without cache 58 | */ 59 | addItem: function (songModel, instant) { 60 | var id = this.length; 61 | var tr = createDOM('tr'); 62 | tr.appendChild(createDOM('td', null, 1 + id)); 63 | tr.appendChild(createDOM('td', null, songModel.title)); 64 | tr.appendChild(createDOM('td', null, songModel.album)); 65 | tr.appendChild(createDOM('td', null, songModel.artist)); 66 | 67 | var td = createDOM('td', null); 68 | var span = createDOM('span', {class: 'dropdown'}); 69 | 70 | var aPlus = createDOM('a', {'data-toggle': 'dropdown', href: 'javascript:0'}); 71 | aPlus.appendChild(createDOM('span', {class: 'glyphicon glyphicon-plus'})); 72 | span.appendChild(aPlus); 73 | span.appendChild(createDOM('ul', {class: 'dropdown-menu', role: 'menu'})); 74 | 75 | td.appendChild(span); 76 | var aHeart = createDOM('a', {href: 'javascript:0'}); 77 | aHeart.appendChild(createDOM('span', {class: 'glyphicon glyphicon-heart'})); 78 | td.appendChild(aHeart); 79 | 80 | if (this.canDel) {//check permission 81 | var aTrash = createDOM('a', {href: 'javascript:0'}); 82 | aTrash.appendChild(createDOM('span', {class: 'glyphicon glyphicon-trash'})); 83 | td.appendChild(aTrash); 84 | } 85 | tr.appendChild(td); 86 | 87 | this.domCache.push(tr); 88 | if (instant)this.addtoDOM(); 89 | this.length++; 90 | if (id >= this.songList.length) { 91 | this.songList.push(songModel); 92 | //更新标记 93 | Event.emit('rfshBadge'); 94 | } 95 | }, 96 | /** 97 | * @description remove "id"th song from the playlist 98 | * 99 | * @param {number} id 100 | * 101 | * @throw index out of range 102 | * @throw local file cannot remove 103 | */ 104 | removeItem: function (id) { 105 | if (id < 0 || id > this.length)throw 'index out of range'; 106 | if (!this.canDel)throw 'insufficient permission to remove'; 107 | if (id == this.ID) { 108 | this.ID = -1; 109 | player.stop(); 110 | } 111 | if (this.ID > id) this.ID--; 112 | var Tr = this.$.tr(); 113 | Tr.eq(id).remove(); 114 | //将之后的歌曲编号-1 ,注意不要覆盖正在播放那一项 115 | var mark = Tr.slice(id + 1); 116 | mark.each(function () { 117 | var o = $(this).children('td').first(); 118 | var t = o.text(); 119 | if (t)o.text(Number(t) - 1); 120 | }); 121 | this.songList.splice(id, 1); 122 | this.length--; 123 | Event.emit('rfshBadge'); 124 | 125 | }, 126 | /** 127 | * @description switch current playing song to "id"th 128 | * 129 | * @param id 130 | * 131 | * @throw index out of range 132 | */ 133 | setState: function (id) { 134 | //list 135 | id = id || 0; 136 | if (id == this.ID)return; 137 | if (id >= this.length)throw 'index out of range'; 138 | var Tr = this.$.tr(); 139 | var Trl = []; 140 | var content = []; 141 | if (id >= 0) { 142 | Trl = [Tr[id]]; 143 | content = ['']; 144 | } 145 | if (this.ID >= 0) { 146 | Trl.push(Tr[this.ID]); 147 | content.push(this.ID + 1); 148 | } 149 | 150 | $(Trl).toggleClass('active'); 151 | var Tdl = $(Trl).children("td:first-child"); 152 | for (var i = 0; i < Tdl.length; i++) { 153 | $(Tdl[i]).html(content[i]); 154 | } 155 | this.ID = id; 156 | }, 157 | /** 158 | * @description get song's data next to play 159 | * 160 | * @param {number} type = 0 - return "id"th song's data 161 | * @param {number} type = 1 - return "id+1"th song's data (cycle) 162 | * @param {number} type = -1 - return "id-1"th song's data (cycle) 163 | * @param {number} type = 2 - return "id+1"th song's data (no cycle) 164 | * @param {number} type = 3 - return a random index song's data 165 | * @param {number} id 166 | * 167 | * @return {object|string} - song data or a msg of playlist state 168 | */ 169 | next: function (type, id) { 170 | switch (type) { 171 | case -1: 172 | id = (this.ID - 1 + this.length) % this.length; 173 | break; 174 | case 1: 175 | id = (this.ID + 1) % this.length; 176 | break; 177 | case 2: 178 | id = this.ID + 1; 179 | if (id == this.length) { 180 | return; 181 | } 182 | break; 183 | case 3: 184 | id = Math.round(Math.random() * this.length); 185 | break; 186 | default: 187 | if (!utils.isNumber(id))id = this.ID; 188 | } 189 | this.setState(id); 190 | return this.songList[id]; 191 | }, 192 | addtoDOM: function () { 193 | if (this.domCache.length == 0)return; 194 | //this.$.frame.append(this.domCache); 195 | var $frame = this.$.frame; 196 | this.domCache.forEach(function (o) { 197 | $frame.append(o); 198 | }); 199 | this.domCache = [];//flush 200 | }, 201 | create$DropDownMenus: function () { 202 | //形成下拉列表 203 | var menuDOM = []; 204 | for (var i = 0; i < category.plts.length; i++) { 205 | var cur = category.plts[i]; 206 | if (cur.timestamp != this.timestamp && entry.getMode(cur.type, 1)) { 207 | var li = createDOM('li', {role: "presentation", "data-target": cur.timestamp}); 208 | var a = createDOM('a', {role: "menuitem", tabindex: -1, href: "javascript:0"}, cur.name); 209 | li.appendChild(a); 210 | menuDOM.push(li); 211 | } 212 | } 213 | return menuDOM; 214 | }, 215 | listen: function () { 216 | var that = this; 217 | this.$.frame.on('dblclick', 'tr', function () { 218 | //去掉之前播放列表的播放状态 219 | var old = player.playlist; 220 | if (old && old != that)old.setState(-1); 221 | //close radio 222 | radio.close(); 223 | //获取要播放歌曲的数据 224 | var songModel = that.next(0, $(this).index()); 225 | player.playlist = that; 226 | player.play(songModel); 227 | }); 228 | this.$.frame.on('click', 'span.glyphicon-plus', function (e) { 229 | var menuDOM = that.create$DropDownMenus(); 230 | if (menuDOM.length == 0) e.stopPropagation(); 231 | else $(this).closest('tr').find('.dropdown-menu').html(menuDOM); 232 | }); 233 | this.$.frame.on('dblclick', 'span.glyphicon-plus', function (e) { 234 | //to avoid trigger 235 | e.stopPropagation(); 236 | }); 237 | this.$.frame.on('click', '.dropdown-menu li', function () { 238 | var index = $(this).closest('tr').index(); 239 | var ts = $(this).data('target'); 240 | category.$plts[ts].addItem(that.songList[index], true); 241 | }); 242 | this.$.frame.on('click', '.glyphicon-trash', function () { 243 | that.removeItem($(this).closest('tr').index());//不能用id 244 | }); 245 | } 246 | } -------------------------------------------------------------------------------- /src/controller/radio.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-25. 3 | */ 4 | var Radio = function () { 5 | this.state = false; 6 | this.loadState = null; 7 | this.loadQue = new utils.queue(); 8 | this.curplay = null; 9 | this.listen(); 10 | } 11 | Radio.prototype = { 12 | load: function () { 13 | if (this.loadState == 'loading')return; 14 | var that = this; 15 | api.radio(function (err, songList) { 16 | if (err)errorHandle(err); 17 | else { 18 | Event.emit('songloaded', songList); 19 | } 20 | that.loadState = 'loaded'; 21 | }); 22 | this.loadState = 'loading'; 23 | }, 24 | show: function () { 25 | if (this.state == false) { 26 | player.stop('loading...'); 27 | player.$.backward.hide(); 28 | player.$.order.hide(); 29 | this.state = true; 30 | this.play(); 31 | } 32 | lrc.toggle(true); 33 | }, 34 | close: function () { 35 | this.state = false; 36 | player.$.backward.show(); 37 | player.$.order.show(); 38 | }, 39 | play: function () { 40 | if (this.curplay) { 41 | player.play(this.curplay); 42 | } else { 43 | if (this.loadQue.empty()) { 44 | this.load(); 45 | } else { 46 | this.curplay = this.loadQue.pop(); 47 | player.play(this.curplay); 48 | } 49 | } 50 | }, 51 | playNext: function () { 52 | if (this.curplay == null)return; 53 | this.curplay = null; 54 | this.play(); 55 | }, 56 | listen: function () { 57 | Event.on('songloaded', function (songList) { 58 | for (var i = 0; i < songList.length; i++) { 59 | this.loadQue.push(songList[i]); 60 | } 61 | this.play(); 62 | }, this); 63 | } 64 | } -------------------------------------------------------------------------------- /src/controller/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-8. 3 | */ 4 | //设置页面行为 5 | var Settings = function () { 6 | this.$ = { 7 | musicDir: $('#music-dir'), 8 | dialog: $('#dialog'), 9 | btnOpen: $('button#openDialog'), 10 | searchLimit: $('#search-limit') 11 | }; 12 | this.$.searchLimit.val(fm.getSearchLimit()); 13 | this.$.musicDir.val(fm.getMusicDir()); 14 | this.listen(); 15 | }; 16 | Settings.prototype = { 17 | listen: function () { 18 | var that = this; 19 | this.$.btnOpen.click(function () { 20 | that.$.dialog.trigger('click'); 21 | }); 22 | 23 | this.$.dialog.change(function () { 24 | var newDir = $(this).val(); 25 | if (fm.setMusicDir(newDir)) { 26 | that.$.musicDir.val(newDir); 27 | //reload localdir 28 | category.loadPlaylists({'local': true}); 29 | } 30 | }); 31 | this.$.searchLimit.change(function () { 32 | var limit = $(this).val(); 33 | limit = limit.trim(); 34 | //判断limit是否是数字 35 | console.log('limit', limit); 36 | var regex = /^[1-9]\d*$/; 37 | if (regex.test(limit)) { 38 | $(this).parent().removeClass('has-error'); 39 | fm.setSearchLimit(Number(limit) || 0); 40 | } else { 41 | $(this).parent().addClass('has-error'); 42 | } 43 | }); 44 | } 45 | } -------------------------------------------------------------------------------- /src/controller/tray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-7-26. 3 | */ 4 | var Tray = (function () { 5 | // Get the current window 6 | var gui = require('nw.gui'); 7 | var win = gui.Window.get(); 8 | //icon source:http://www.easyicon.net/1185106-Cloud_Music_icon.html 9 | var tray = new gui.Tray({title: '网易音乐盒', icon: 'dist/assets/img/tray.png'}); 10 | var menuItems = { 11 | playorPause: new gui.MenuItem({ 12 | label: '播放/暂停', 13 | icon: 'dist/assets/img/play.png', 14 | click: function () { 15 | if (player.playing)player.pause(); 16 | else player.play(); 17 | } 18 | }), 19 | separator_1: new gui.MenuItem({ 20 | type: 'separator' 21 | }), 22 | previous: new gui.MenuItem({ 23 | label: '前一首', 24 | icon: 'dist/assets/img/previous.png', 25 | click: function () { 26 | player.playNext(-1); 27 | } 28 | }), 29 | next: new gui.MenuItem({ 30 | label: '后一首', 31 | icon: 'dist/assets/img/next.png', 32 | click: function () { 33 | player.playNext(1); 34 | } 35 | }), 36 | separator_2: new gui.MenuItem({ 37 | type: 'separator' 38 | }), 39 | exit: new gui.MenuItem({ 40 | label: '退出', 41 | icon: 'dist/assets/img/close.png', 42 | click: function () { 43 | win.hide(); 44 | tray.remove(); 45 | fm.saveChanges(); 46 | win.close(true); 47 | } 48 | }) 49 | } 50 | 51 | function ret() { 52 | tray.tooltip = '网易音乐盒'; 53 | var menu = new gui.Menu(); 54 | for (var key in menuItems) { 55 | menu.append(menuItems[key]); 56 | } 57 | tray.menu = menu; 58 | win.setMinimumSize(560, 60); 59 | this.listen(); 60 | } 61 | 62 | ret.prototype.listen = function () { 63 | win.on('close', function () { 64 | win.hide(); 65 | }); 66 | tray.on('click', function () { 67 | win.show(); 68 | win.restore(); 69 | }); 70 | } 71 | return ret; 72 | 73 | })(); -------------------------------------------------------------------------------- /src/index.jade: -------------------------------------------------------------------------------- 1 | //Created by kevin on 15-6-23. 2 | - var _pc={href:'javascript:void(0)'}; 3 | - var _hide={style:'display:none'}; 4 | - var _title='网易音乐盒'; 5 | mixin _style(name) 6 | link(href='assets/css/' + name + '.css',rel='stylesheet') 7 | mixin _script(name) 8 | script(src='assets/js/' + name + '.js') 9 | mixin _icon(name,attr) 10 | if attr 11 | span.glyphicon(class='glyphicon-' + name)&attributes(attr) 12 | else 13 | span.glyphicon(class='glyphicon-' + name) 14 | mixin _anchor(code,attr) 15 | if(attr) 16 | a(href="javascript:" + code)&attributes(attr) 17 | if block 18 | block 19 | else 20 | a(href="javascript:" + code) 21 | if block 22 | block 23 | doctype html 24 | html(lang="zh-CN") 25 | head 26 | title=_title 27 | meta(charset="UTF-8") 28 | meta(name="viewport",content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no") 29 | //load style 30 | -each name in ['bootstrap.min','bootstrap-slider.min','my'] 31 | +_style(name) 32 | 33 | //link(href='http://cdn.bootcss.com/bootstrap-material-design/0.3.0/css/material-fullpalette.min.css',rel='stylesheet') 34 | 35 | body 36 | #wrapper 37 | // navgation bar 38 | nav.navbar.navbar-fixed-top.navbar-default 39 | .container-fluid 40 | .navbar-header 41 | button.navbar-toggle.collapsed(data-toggle="collapse",data-target="ncollapse") 42 | span.sr-only 切换导航栏 43 | span.icon-bar 44 | span.icon-bar 45 | span.icon-bar 46 | a.navbar-brand&attributes(_pc)=_title 47 | 48 | .collapse.navbar-collapse#ncollapse 49 | ul.nav.navbar-nav 50 | li#main-nav.active: +_anchor('nav.setState(0)') 首页 51 | li#radio-nav: +_anchor('nav.setState(1)') 私人FM 52 | li#settings-nav: +_anchor('nav.setState(2)') 设置 53 | 54 | ul.nav.navbar-nav.navbar-right 55 | li.dropdown 56 | a#user-profile.dropdown-toggle(data-toggle="dropdown")&attributes(_pc) 57 | img.img-circle(src="",onError="this.src='assets/img/avater.jpg'") 58 | p 未登录 59 | span.caret 60 | ul.dropdown-menu(role="menu") 61 | li#menugo-0 62 | +_anchor('nav.clickMenu(0)') 63 | +_icon('log-in') 64 | | 登录 65 | li#menugo-1 66 | +_anchor('nav.clickMenu(1)') 67 | +_icon('log-out') 68 | | 注销 69 | 70 | li: +_anchor('nav.toggleWindow(false)'): +_icon('resize-small',{title: 'Mini模式'}) 71 | .navbar-right 72 | form.navbar-form.navbar-left 73 | .input-group 74 | input#search.form-control(type='text',placeholder='搜索音乐...') 75 | span.input-group-btn 76 | +_anchor('nav.search()',{class: 'btn btn-default'}): +_icon('search') 77 | //end of navgation bar 78 | #page.container-fluid 79 | #main.row 80 | #sidebar 81 | #entry 82 | #plts-tools 83 | +_icon('refresh',{title: '更新列表', id: 'refresh'}) 84 | +_icon('plus',{title: '添加自定义列表', id: 'addlist'}) 85 | .list: .twrap 86 | table.table.table-hover&attributes(_hide) 87 | thead: tr 88 | - each _item in ['#','标题','专辑','艺术家','操作'] 89 | th=_item 90 | tbody 91 | .song-detail 92 | .detail 93 | .cover: img(src='',alt='专辑封面',onError="this.src='assets/img/album.png'") 94 | ul.info 95 | li: h3#info-title 96 | li 97 | +_icon('cd') 98 | | 专辑: 99 | span#info-album 100 | li 101 | +_icon('user') 102 | | 艺术家: 103 | span#info-artist 104 | .lyric: ul 105 | .btn-group-vertical.lrc-tools(role='group') 106 | - var _icons=['minus','arrow-up','arrow-down']; 107 | - var _actions=['lrc.toggle(0)','lrc.scroll(1)','lrc.scroll(-1)'] 108 | - for(var i=0;i<3;i++) 109 | +_anchor(_actions[i],{class: 'btn'}): +_icon(_icons[i]) 110 | 111 | #settings.row&attributes(_hide) 112 | .panel.panel-default 113 | .panel-heading: h3.panel-title 基本设置 114 | .panel-body: form.form-horizontal 115 | .form-group 116 | label.col-sm-3.control-label(for='music-dir') 自定义音乐文件夹 117 | .col-sm-9: .input-group 118 | input#music-dir.form-control(disabled='disabled',type='text') 119 | span.input-group-btn 120 | button#openDialog.btn.btn-default(type="button") 选择... 121 | 122 | input#dialog(nwdirectory,type='file')&attributes(_hide) 123 | 124 | .panel.panel-default 125 | .panel-heading: h3.panel-title 云音乐设置 126 | .panel-body: form.form-horizontal 127 | .form-group 128 | label.col-sm-3.control-label(for='search-limit') 歌曲最大搜索数 129 | .col-sm-9: input#search-limit.form-control(type='number') 130 | 131 | .panel.panel-default: .panel-body 132 | h3 设计&&实现: Kevin 133 | p Email:stkevintan@foxmail.com 134 | #radio&attributes(_hide) 135 | 136 | #footer: .container-fluid: .row 137 | .col-sm-2.audio-control 138 | +_anchor('player.playNext(-1)',{id: 'backward'}): +_icon('step-backward') 139 | +_anchor('player.play()',{id: 'play'}): +_icon('play') 140 | +_anchor('player.pause()',{id: 'pause'}): +_icon('pause') 141 | +_anchor('player.playNext(1)',{id: 'forward'}): +_icon('step-forward') 142 | .col-sm-7.audio-body 143 | .media 144 | .media-left: +_anchor('lrc.toggle()') 145 | img#song-pic.img-thumbnail.media-object(src="",onError="this.src='assets/img/album.png'") 146 | .media-body 147 | h4.media-heading 未选择音乐 148 | p.media-time!='00.00/00.00' 149 | input 150 | .col-sm-3.audio-adjust 151 | +_anchor('category.toggleOpen()'): +_icon('list') 152 | a#vol-wrapper&attributes(_pc) 153 | #vol-panel.popover.top.in 154 | .layer 155 | .arrow 156 | .popover-content: input#volume 157 | +_icon('volume-up',{title: '音量', id: 'vol-icon', onclick: 'player.toggleVolMute()'}) 158 | +_anchor('player.setOrder()',{id: 'order'}): +_icon('repeat',{title: '列表循环'}) 159 | 160 | .mini-tool: +_anchor('nav.toggleWindow(true)'): +_icon('new-window',{title: '正常模式'}) 161 | 162 | // modal input 163 | #inputListName.modal.fade: .modal-dialog: .modal-content 164 | .modal-header 165 | button.close(type='button',data-dismiss='modal',aria-label='Close'): span(aria-hidden="true") × 166 | h4.modal-title 添加自定义播放列表 167 | .modal-body: form 168 | label.control-label&attributes(_hide) 糟糕,输入为空或者重名 169 | input.form-control(type='text',placeholder='输入自定义播放列表') 170 | .modal-footer 171 | button.btn.btn-default(type='button',data-dismiss='modal') 撤销 172 | button.btn.btn-primary#submit-name(type='button') 确定 173 | 174 | #login.modal.fade: .modal-dialog: .modal-content 175 | .modal-header 176 | button.close(type='button',data-dismiss='modal',aria-label='Close'): span(aria-hidden="true") × 177 | h4.modal-title 手机或邮箱登录 178 | .modal-body: form 179 | label.control-label&attributes(_hide) 糟糕,登录失败 180 | .form-group: input.form-control(name='phone',type='text',placeholder='手机号或者邮箱') 181 | .form-group: input.form-control(name='password',type='password',placeholder='密码') 182 | .modal-footer 183 | button.btn.btn-default(type='button',data-dismiss='modal') 撤销 184 | button.btn.btn-primary.submit(type='button',data-loading-text='加载中...') 确定 185 | 186 | 187 | //load script 188 | - each name in ['jquery.min','bootstrap.min','bootstrap-slider.min','sortable','my'] 189 | +_script(name) 190 | // script(src='http://cdn.bootcss.com/bootstrap-material-design/0.3.0/js/material.min.js') -------------------------------------------------------------------------------- /src/model/Crypto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-7-19. 3 | */ 4 | 5 | var crypto = require('crypto'); 6 | var bigInt = require('big-integer'); 7 | 8 | function addPadding(encText, modulus) { 9 | var ml = modulus.length; 10 | for (i = 0; ml > 0 && modulus[i] == '0'; i++)ml--; 11 | var num = ml - encText.length, prefix = ''; 12 | for (var i = 0; i < num; i++) { 13 | prefix += '0'; 14 | } 15 | return prefix + encText; 16 | } 17 | 18 | 19 | function aesEncrypt(text, secKey) { 20 | var cipher = crypto.createCipheriv('AES-128-CBC', secKey, '0102030405060708'); 21 | return cipher.update(text, 'utf-8', 'base64') + cipher.final('base64'); 22 | } 23 | 24 | /** 25 | * RSA Encryption algorithm. 26 | * @param text {string} - raw data to encrypt 27 | * @param exponent {string} - public exponent 28 | * @param modulus {string} - modulus 29 | * @returns {string} - encrypted data: reverseText^pubKey%modulus 30 | */ 31 | function rsaEncrypt(text, exponent, modulus) { 32 | var rText = '', radix = 16; 33 | for (var i = text.length - 1; i >= 0; i--) rText += text[i];//reverse text 34 | var biText = bigInt(new Buffer(rText).toString('hex'), radix), 35 | biEx = bigInt(exponent, radix), 36 | biMod = bigInt(modulus, radix), 37 | biRet = biText.modPow(biEx, biMod); 38 | return addPadding(biRet.toString(radix), modulus); 39 | } 40 | 41 | function createSecretKey(size) { 42 | var keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 43 | var key = ""; 44 | for (var i = 0; i < size; i++) { 45 | var pos = Math.random() * keys.length; 46 | pos = Math.floor(pos); 47 | key = key + keys.charAt(pos) 48 | } 49 | return key; 50 | } 51 | var modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'; 52 | var nonce = '0CoJUm6Qyw8W8jud'; 53 | var pubKey = '010001'; 54 | var Crypto = { 55 | MD5: function (text) { 56 | return crypto.createHash('md5').update(text).digest('hex'); 57 | }, 58 | aesRsaEncrypt: function (text) { 59 | var secKey = createSecretKey(16); 60 | return { 61 | params: aesEncrypt(aesEncrypt(text, nonce), secKey), 62 | encSecKey: rsaEncrypt(secKey, pubKey, modulus) 63 | } 64 | } 65 | }; 66 | module.exports = Crypto; 67 | 68 | -------------------------------------------------------------------------------- /src/model/EntryModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-12. 3 | */ 4 | function EntryModel(raw) { 5 | this.mode = raw.mode; 6 | this.name = raw.name; 7 | this.loader = raw.loader || function (callback) { 8 | callback(null, null); 9 | } 10 | this.onadd = raw.onadd || function () { 11 | } 12 | this.onload = raw.onload || function () { 13 | return true; 14 | } 15 | this.onremove = raw.onremove || function () { 16 | } 17 | } 18 | module.exports = EntryModel; -------------------------------------------------------------------------------- /src/model/FileManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-5. 3 | */ 4 | var fs = require('fs'); 5 | var utils = require('./Utils'); 6 | var PltM = require('./PlaylistModel'); 7 | var SongM = require('./SongModel'); 8 | var process = require('process'); 9 | var path = require('path'); 10 | var home = require('home'); 11 | 12 | function pack(arr) { 13 | if (!utils.isArray(arr))return []; 14 | arr = arr.map(function (o) { 15 | var n = new PltM(o); 16 | n.songList = n.songList.map(function (s) { 17 | return new SongM(s); 18 | }); 19 | return n; 20 | }); 21 | return arr; 22 | } 23 | 24 | const EXTS = ['.mp3', '.ogg', '.wav']; 25 | const DEFAULTMUSICDIR = (function () { 26 | var choices = ['音乐', 'Music', 'music'].map(function (e) { 27 | return home.resolve('~/' + e); 28 | }); 29 | for (var i = 0; i < choices.length; i++) { 30 | try { 31 | var stats = fs.statSync(choices[i]); 32 | } catch (e) { 33 | continue; 34 | } 35 | if (stats && stats.isDirectory()) return choices[i]; 36 | } 37 | return choices[0]; 38 | })(); 39 | const DEFAULTSEARCHLIMIT = 20; 40 | 41 | var config = { 42 | key: 'config', 43 | content: {}, 44 | isChanged: 0 45 | }; 46 | 47 | var scheme = { 48 | isChanged: 0, 49 | key: 'scheme', 50 | content: [], 51 | indexOf: function (pltm) { 52 | return utils.binarySearch(this.content, pltm.timestamp, function (o) { 53 | return o.timestamp; 54 | }); 55 | } 56 | } 57 | 58 | var fm = {}; 59 | config.content = JSON.parse(storage.getItem(config.key)) || {}; 60 | module.exports = fm; 61 | 62 | fm.saveChanges = function () { 63 | [config, scheme].forEach(function (o) { 64 | o.isChanged && storage.setItem(o.key, JSON.stringify(o.content)); 65 | }); 66 | } 67 | 68 | /*********MusicDir**********/ 69 | fm.setMusicDir = function (dir) { 70 | if (this.getMusicDir() !== dir) { 71 | config.content.musicDir = dir; 72 | config.isChanged = 1; 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | fm.getMusicDir = function () { 79 | return config.content.musicDir || DEFAULTMUSICDIR; 80 | } 81 | 82 | 83 | /*********SearchLimit**********/ 84 | fm.getSearchLimit = function () { 85 | return config.content.searchLimit || DEFAULTSEARCHLIMIT; 86 | } 87 | 88 | fm.setSearchLimit = function (limit) { 89 | if (typeof limit === 'number' && limit >= 0) { 90 | config.content.searchLimit = limit; 91 | config.isChanged = 1; 92 | } 93 | } 94 | 95 | 96 | /*********Local music file**********/ 97 | fm.getLocal = function (callback) { 98 | var that = this; 99 | process.nextTick(function () { 100 | var ret = that.loadMusicDirSync(); 101 | if (utils.isString(ret))callback(ret); 102 | else { 103 | callback(null, pack([{ 104 | timestamp: 0, 105 | name: that.getMusicDir(), 106 | songList: ret, 107 | type: 'local' 108 | }])); 109 | } 110 | }); 111 | } 112 | 113 | 114 | fm.loadMusicDirSync = function (musicDir) { 115 | var musicDir = musicDir || this.getMusicDir(); 116 | var ret = []; 117 | var files = []; 118 | try { 119 | files = fs.readdirSync(musicDir); 120 | } catch (e) { 121 | try { 122 | fs.mkdirSync(musicDir); 123 | } catch (e) { 124 | return {msg: '[loadMusicDir]failed to read or create dir, please try another one.', type: 1} 125 | } 126 | } 127 | var that = this; 128 | files = files.map(function (src) { 129 | return path.join(musicDir, src); 130 | }).filter(function (src) { 131 | var stat = fs.statSync(src); 132 | if (stat && stat.isDirectory()) { 133 | ret = ret.concat(that.loadMusicDirSync(src)); 134 | return false; 135 | } 136 | var ext = path.extname(src); 137 | for (var i = 0; i < EXTS.length; i++) { 138 | if (ext == EXTS[i])return true; 139 | } 140 | return false; 141 | }); 142 | for (var i = 0; i < files.length; i++) { 143 | var src = files[i]; 144 | var song = { 145 | "title": path.basename(src, src.substr(src.lastIndexOf('.'))), 146 | "artist": "", 147 | "album": "", 148 | "src": src 149 | }; 150 | ret.push(song); 151 | } 152 | return ret; 153 | } 154 | 155 | 156 | /*********Scheme inventory**********/ 157 | fm.loadScheme = function (callback) { 158 | process.nextTick(function () { 159 | scheme.content = JSON.parse(storage.getItem(scheme.key)) || []; 160 | scheme.isChanged = 1; 161 | callback(null, pack(scheme.content)); 162 | }) 163 | } 164 | 165 | fm.getScheme = function (callback) { 166 | if (scheme.isChanged == 0) { 167 | this.loadScheme(function (err, content) { 168 | if (err) callback(err) 169 | else { 170 | scheme.content = content; 171 | callback(null, content); 172 | } 173 | }); 174 | return; 175 | } 176 | callback(null, scheme.content); 177 | } 178 | 179 | fm.addScheme = function (plt) { 180 | if (!(plt instanceof PltM)) { 181 | console.log('AddScheme failed'); 182 | return; 183 | } 184 | scheme.isChanged = 1; 185 | scheme.content.push(plt); 186 | } 187 | 188 | fm.setScheme = function (plt) { 189 | if (!(plt instanceof PltM)) { 190 | console.log('SetScheme failed'); 191 | return; 192 | } 193 | scheme.isChanged = 1; 194 | var index = scheme.indexOf(plt); 195 | if (index != -1) { 196 | scheme.content[index] = plt; 197 | } 198 | } 199 | 200 | fm.delScheme = function (plt) { 201 | if (!(plt instanceof PltM)) { 202 | console.log('DelScheme failed'); 203 | return; 204 | } 205 | scheme.isChanged = 1; 206 | var index = scheme.indexOf(plt); 207 | console.log(index); 208 | if (index != -1) { 209 | scheme.content.splice(index, 1); 210 | } else { 211 | console.log('SetScheme failed'); 212 | } 213 | } 214 | 215 | fm.setCookie = function (cookie) { 216 | config.content.cookie = cookie; 217 | config.isChanged = 1; 218 | } 219 | 220 | fm.getCookie = function () { 221 | return config.content.cookie; 222 | } 223 | 224 | fm.getUserID = function () { 225 | if (!config.content.cookie)return null; 226 | var ret = /\d+/.exec(config.content.cookie[3]); 227 | return ret ? ret[0] : null; 228 | }; 229 | -------------------------------------------------------------------------------- /src/model/NetEaseMusic.js: -------------------------------------------------------------------------------- 1 | var request = require('superagent'); 2 | //var process = require('process'); 3 | var async = require('async'); 4 | var crypto = require('./Crypto'); 5 | var fm = require('./FileManager'); 6 | var PltM = require('./PlaylistModel'); 7 | var SongM = require('./SongModel'); 8 | var header = { 9 | 'Accept': '*/*', 10 | 'Accept-Encoding': 'gzip,deflate,sdch', 11 | 'Accept-Language': 'zh-CN,en-US;q=0.7,en;q=0.3', 12 | 'Connection': 'keep-alive', 13 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 14 | 'Host': 'music.163.com', 15 | 'Referer': 'http://music.163.com/', 16 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0' 17 | } 18 | 19 | var httpRequest = function (method, url, data, callback) { 20 | var ret; 21 | if (method == 'post') { 22 | ret = request.post(url).send(data); 23 | } else { 24 | ret = request.get(url).query(data); 25 | } 26 | var cookie = fm.getCookie(); 27 | if (cookie)ret.set('Cookie', cookie); 28 | ret.set(header).timeout(10000).end(callback); 29 | } 30 | module.exports = { 31 | login: function (username, password, callback) { 32 | var url, pattern = /^0\d{2,3}\d{7,8}$|^1[34578]\d{9}$/, 33 | body = { 34 | password: crypto.MD5(password), 35 | rememberLogin: 'true' 36 | }; 37 | if (pattern.test(username)) { 38 | //手机登录 39 | body.phone = username; 40 | url = 'http://music.163.com/weapi/login/cellphone/'; 41 | } else { 42 | //邮箱登录 43 | body.username = username; 44 | url = 'http://music.163.com/weapi/login/'; 45 | } 46 | 47 | var encBody = crypto.aesRsaEncrypt(JSON.stringify(body)); 48 | httpRequest('post', url, encBody, function (err, res) { 49 | if (err) { 50 | callback({msg: '[login]http error ' + err, type: 1}); 51 | return; 52 | } 53 | var data = JSON.parse(res.text); 54 | if (data.code != 200) { 55 | //登录失败 56 | callback({msg: "[login]username or password incorrect", type: 0}); 57 | } else { 58 | fm.setCookie(res.header['set-cookie']); 59 | callback(null, data.profile); 60 | } 61 | }); 62 | }, 63 | getNet: function (callback) { 64 | var that = this; 65 | this.userPlaylist(function (err, playlists) { 66 | if (err) { 67 | callback(err); 68 | return; 69 | } 70 | async.map(playlists, function (item, callback) { 71 | that.playlistDetail(item.id, function (err, songList) { 72 | if (err) { 73 | callback(err); 74 | return; 75 | } 76 | var pltm = new PltM({ 77 | name: item.name, 78 | type: 'net', 79 | songList: songList 80 | }); 81 | callback(null, pltm); 82 | }); 83 | }, function (err, results) { 84 | if (err) { 85 | callback(err); 86 | } else 87 | callback(null, results); 88 | }); 89 | }) 90 | }, 91 | userProfile: function (uid, callback) { 92 | uid = uid || fm.getUserID(); 93 | if (!uid) { 94 | callback({msg: '[userProfile]user not login', type: 0}); 95 | } else { 96 | var url = 'http://music.163.com/api/user/detail/' + uid; 97 | httpRequest('get', url, {'userId': uid}, function (err, res) { 98 | if (err) { 99 | callback({msg: '[userProfile]http error ' + err, type: 1}); 100 | } else if (res.body.code != 200) { 101 | callback({msg: '[userProfile]http code ' + res.body.code, type: 1}); 102 | } else { 103 | callback(null, res.body.profile); 104 | } 105 | }); 106 | } 107 | }, 108 | userPlaylist: function () { 109 | // [uid],[offset],[limit],callback 110 | var argv = [].slice.call(arguments); 111 | var callback = argv.pop(); 112 | var uid = fm.getUserID(); 113 | if (!uid) { 114 | callback({msg: '[userPlaylist]user do not login', type: 0}); 115 | return; 116 | } 117 | var uid = argv[0] || uid; 118 | 119 | var offset = argv[1] || 0; 120 | var limit = argv[2] || 100; 121 | var url = 'http://music.163.com/api/user/playlist/'; 122 | var data = { 123 | "offset": offset, 124 | "limit": limit, 125 | "uid": uid 126 | } 127 | httpRequest('get', url, data, function (err, res) { 128 | if (err) { 129 | callback({msg: '[userPlaylist]http timeout', type: 1}); 130 | return; 131 | } 132 | if (res.body.code != 200)callback({msg: '[userPlaylist]http code ' + data.code, type: 1}); 133 | else { 134 | callback(null, res.body.playlist); 135 | } 136 | }); 137 | }, 138 | playlistDetail: function (id, callback) { 139 | var url = 'http://music.163.com/api/playlist/detail'; 140 | var data = {"id": id} 141 | var that = this; 142 | httpRequest('get', url, data, function (err, res) { 143 | if (err)callback({msg: '[playlistDetail]http timeout', type: 1}); 144 | else { 145 | if (res.body.code != 200)callback({msg: '[playlistDetail]http code ' + data.code, type: 1}); 146 | else callback(null, that.transfer(res.body.result.tracks)); 147 | } 148 | }); 149 | }, 150 | // 搜索单曲(1),歌手(100),专辑(10),歌单(1000),用户(1002) *(type)* 151 | search: function () { 152 | //s, stype, offset, total, limit,callback; 153 | var argv = [].slice.call(arguments); 154 | var callback = argv.pop(); 155 | var s = argv[0]; 156 | var stype = argv[1] || 1; 157 | var offset = argv[2] || 0; 158 | var total = argv[3] || 'true'; 159 | var limit = argv[4] || fm.getSearchLimit(); 160 | var url = 'http://music.163.com/api/search/get/web'; 161 | var data = { 162 | 's': s, 163 | 'type': stype, 164 | 'offset': offset, 165 | 'total': total, 166 | 'limit': limit 167 | } 168 | var that = this; 169 | httpRequest('post', url, data, function (err, res) { 170 | if (err) { 171 | callback({msg: '[search]http error ' + err, type: 1}); 172 | return; 173 | } 174 | var doc = JSON.parse(res.text); 175 | if (doc.code != 200)callback({msg: '[search]http code ' + doc.code, type: 1}); 176 | else { 177 | var results = doc.result.songs; 178 | var data = that.transfer(results); 179 | callback(null, data); 180 | } 181 | }); 182 | }, 183 | transfer: function (results) { 184 | var songList = []; 185 | var idArray = []; 186 | var idMap = {}; 187 | for (var i = 0; i < results.length; i++) { 188 | var r = results[i]; 189 | idArray.push(r.id); 190 | idMap[r.id] = i; 191 | var o = {src: ''}; 192 | o.id = r.id; 193 | o.title = r.name; 194 | o.album = r.album.name; 195 | o.artist = r.artists.map(function (v) { 196 | return v.name; 197 | }).join(); 198 | songList.push(new SongM(o));//modefy here! 199 | } 200 | var that = this; 201 | process.nextTick(function () { 202 | // >100时分批查询 203 | var num = Math.ceil(idArray.length / 100); 204 | for (var k = 0; k < num; k++) { 205 | var idTmp = idArray.slice(k * 100, Math.min((k + 1) * 100, idArray.length)); 206 | that.songsDetail(idTmp, function (err, songs) { 207 | if (err) { 208 | throw(err.msg); 209 | return; 210 | } 211 | for (var i = 0; i < songs.length; i++) { 212 | var index = idMap[songs[i].id]; 213 | songList[index].src = songs[i].mp3Url; 214 | songList[index].pic = songs[i].album.picUrl; 215 | } 216 | }); 217 | } 218 | }); 219 | return songList; 220 | }, 221 | songsDetail: function (ids, callback) { 222 | var url = 'http://music.163.com/api/song/detail'; 223 | httpRequest('get', url, {ids: '[' + ids.join() + ']'}, function (err, res) { 224 | if (err) { 225 | callback({msg: '[songsDetail]http error ' + err, type: 1}); 226 | return; 227 | } 228 | var doc = JSON.parse(res.text); 229 | if (doc.code != 200)callback({msg: '[songsDetail]http code ' + doc.code, type: 1}); 230 | else callback(null, doc.songs); 231 | }); 232 | }, 233 | songDetail: function (id, callback) { 234 | var url = "http://music.163.com/api/song/detail"; 235 | httpRequest('get', url, {id: id, ids: '[' + id + ']'}, function (err, res) { 236 | if (err) { 237 | callback({msg: '[songDetail]http error ' + err, type: 1}); 238 | return; 239 | } 240 | 241 | var doc = JSON.parse(res.text); 242 | if (doc.code != 200)callback({msg: '[songDetail]http code ' + doc.code, type: 1}); 243 | else callback(null, doc); 244 | }); 245 | }, 246 | songLyric: function (id, callback) { 247 | var url = "http://music.163.com/api/song/lyric"; 248 | httpRequest('get', url, {os: 'android', id: id, lv: -1, tv: -1}, function (err, res) { 249 | if (err) { 250 | callback({msg: '[songLyric]http error ' + err, type: 1}); 251 | return; 252 | } 253 | var doc = JSON.parse(res.text); 254 | if (doc.lrc) { 255 | callback(null, doc.lrc.lyric); 256 | } 257 | else { 258 | callback({msg: '[songLyric]lrc do not exist', type: 0}); 259 | } 260 | }); 261 | }, 262 | radio: function (callback) { 263 | var url = 'http://music.163.com/api/radio/get'; 264 | httpRequest('get', url, null, function (err, res) { 265 | if (err) { 266 | callback({msg: '[radio]http error ' + err, type: 1}); 267 | return; 268 | } 269 | var doc = JSON.parse(res.text); 270 | if (doc.code != 200)callback({msg: '[radio]http code ' + doc.code, type: 1}); 271 | else callback(null, doc.data.map(function (o) { 272 | return new SongM({ 273 | id: o.id, 274 | src: o.mp3Url, 275 | pic: o.album.picUrl, 276 | artist: o.artists.map(function (v) { 277 | return v.name; 278 | }).join(), 279 | album: o.album.name, 280 | title: o.name 281 | }); 282 | })); 283 | }); 284 | } 285 | } 286 | 287 | -------------------------------------------------------------------------------- /src/model/PlaylistModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-6-4. 3 | */ 4 | var model = function (raw) { 5 | this.timestamp = raw.timestamp; 6 | this.name = raw.name; 7 | this.type = raw.type; 8 | this.songList = raw.songList||[]; 9 | } 10 | module.exports = model; -------------------------------------------------------------------------------- /src/model/SongModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-18. 3 | */ 4 | module.exports = function SongModel(raw) { 5 | this.id = raw.id; 6 | this.src = raw.src; 7 | this.pic = raw.pic || ''; 8 | this.artist = raw.artist || '未知'; 9 | this.album = raw.album || '未知'; 10 | this.title = raw.title || '未知'; 11 | } 12 | -------------------------------------------------------------------------------- /src/model/Utils.js: -------------------------------------------------------------------------------- 1 | var base = require('util'); 2 | 3 | base.binarySearch = function (array, value, compare) { 4 | var res = -1, l = 0, r = array.length - 1; 5 | if (!compare)compare = function (v) { 6 | return v 7 | } 8 | while (l <= r) { 9 | var mid = (l + r) >> 1; 10 | if (compare(array[mid]) <= value) { 11 | l = mid + 1; 12 | res = mid; 13 | } else { 14 | r = mid - 1; 15 | } 16 | } 17 | return res; 18 | } 19 | base.queue = function () { 20 | this._source = new Array(); 21 | this._front = 0; 22 | } 23 | base.queue.prototype = { 24 | size: function () { 25 | return this._source.length - this._front; 26 | }, 27 | empty: function () { 28 | return this.size() == 0; 29 | }, 30 | push: function (a) { 31 | this._source.push(a); 32 | }, 33 | pop: function () { 34 | if (this.empty())return undefined; 35 | var ret = this._source[this._front++]; 36 | if ((this._front << 1) >= this._source.length) { 37 | this._source = this._source.slice(this._front); 38 | this._front = 0; 39 | } 40 | return ret; 41 | }, 42 | front: function () { 43 | return this.size() ? this._source[this._front] : undefined; 44 | } 45 | } 46 | var isType = function (name) { 47 | return function (v) { 48 | return Object.prototype.toString.call(v) === '[object ' + name + ']'; 49 | } 50 | } 51 | base.isNumber = isType('Number'); 52 | base.isObject = isType('Object'); 53 | base.isFunction = isType('Function'); 54 | base.isString = isType('String'); 55 | base.isUndefined = isType('Undefined'); 56 | base.isBoolean = isType('Boolean'); 57 | base.isArray = isType('Array'); 58 | base.isNull = isType('Null'); 59 | base.isUndefinedorNull = function (v) { 60 | return base.isNull(v) || base.isUndefined(v); 61 | } 62 | 63 | module.exports = base; -------------------------------------------------------------------------------- /src/my.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by kevin on 15-5-4. 3 | */ 4 | //初始化 5 | // transfer Window.localStorage to node context 6 | global.storage = localStorage; 7 | var fm = require('./libs/FileManager'); 8 | var api = require('./libs/NetEaseMusic'); 9 | var utils = require('./libs/Utils'); 10 | var PltM = require('./libs/PlaylistModel'); 11 | var EntryM = require('./libs/EntryModel'); 12 | var Event = (function () { 13 | var w = $(window); 14 | return { 15 | on: function (event, handler, _this) { 16 | w.on(event, function () { 17 | handler.apply(_this, [].slice.call(arguments, 1)); 18 | }); 19 | }, 20 | once: function (event, handler, _this) { 21 | w.one(event, function () { 22 | handler.apply(_this, [].slice.call(arguments, 1)); 23 | }); 24 | }, 25 | emit: function (event) { 26 | w.triggerHandler(event, [].slice.call(arguments, 1)); 27 | } 28 | } 29 | })(); 30 | 31 | var errorHandle = function (err) { 32 | if (!err) return; 33 | if (err.type)showNotify(err.msg); 34 | console.log(err.msg ? err.msg : err); 35 | } 36 | 37 | var showNotify = function (msg) { 38 | new Notification('网易音乐盒', { 39 | body: msg 40 | }); 41 | } 42 | 43 | var createDOM = function (name, options, inner) { 44 | var dom = document.createElement(name); 45 | for (var key in options) { 46 | dom.setAttribute(key, options[key]); 47 | } 48 | if (!utils.isUndefinedorNull(inner)) 49 | dom.innerText = inner; 50 | return dom; 51 | } 52 | 53 | var entry = { 54 | schema: { 55 | local: new EntryM({ 56 | mode: 0, 57 | name: '本地', 58 | loader: function (callback) { 59 | fm.getLocal(callback); 60 | } 61 | }), 62 | user: new EntryM({ 63 | mode: 3, 64 | name: '用户', 65 | loader: function (callback) { 66 | fm.getScheme(callback); 67 | }, 68 | onadd: function (pltM) { 69 | fm.addScheme(pltM); 70 | }, 71 | onremove: function (pltM) { 72 | fm.delScheme(pltM); 73 | } 74 | }), 75 | net: new EntryM({ 76 | mode: 0, 77 | name: '云音乐', 78 | loader: function (callback) { 79 | api.getNet(callback); 80 | } 81 | }) 82 | }, 83 | //二进制表示权限,3->11,第0位表示是否保存,第1位表示是否允许修改 84 | getMode: function (type, w) { 85 | var mode = this.schema[type] ? this.schema[type].mode : 2; 86 | if (typeof w !== 'undefined') { 87 | return (1 << w) & mode; 88 | } else return mode; 89 | }, 90 | getPrefix: function (type) { 91 | return this.schema[type] ? this.schema[type].name : '未知'; 92 | } 93 | }; 94 | 95 | var nav = new Nav(); 96 | var account = new Account(); 97 | var radio = new Radio(); 98 | var lrc = new Lrc(); 99 | var player = new Player(); 100 | var tray = new Tray(); 101 | var settings = new Settings(); 102 | var category = new Category(); 103 | 104 | //加载用户信息 105 | account.loadUser(); 106 | 107 | //加载播放列表 108 | category.loadPlaylists(null, true); 109 | 110 | //table屏蔽选中 111 | $('table').on('selectstart', function (e) { 112 | e.preventDefault(); 113 | }); 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/my.styl: -------------------------------------------------------------------------------- 1 | html, body, #wrapper, #page, #page > .row, #sidebar, #entry, .list, .song-detail, .lyric 2 | height 100% 3 | min-height 100% 4 | 5 | img 6 | background #fff 7 | 8 | a 9 | outline none 10 | color #767676 11 | text-decoration none 12 | 13 | .loading 14 | -webkit-animation loading 1500ms infinite linear 15 | 16 | .navbar 17 | box-shadow 0 0 8px rgba(0, 0, 0, 0.21) 18 | #user-profile 19 | line-height 50px 20 | padding-top 0 21 | padding-bottom 0 22 | img 23 | height 30px 24 | width 30px 25 | margin-right 10px 26 | p 27 | margin 0 28 | display inline-block 29 | #menugo-0 span 30 | #menugo-1 span 31 | margin-right 10px 32 | 33 | #page 34 | padding-top 50px 35 | padding-bottom 60px 36 | 37 | #main 38 | position relative 39 | #sidebar 40 | display block 41 | position absolute 42 | top 0 43 | right 75% 44 | width 25% 45 | border-right 1px solid #eee 46 | padding 0 47 | overflow hidden 48 | background-color #f8f8f8 49 | #entry 50 | padding 0 9px 30px 10px 51 | overflow-y auto 52 | &::-webkit-scrollbar 53 | width: 2px 54 | &::-webkit-scrollbar-track 55 | margin-bottom: 30px 56 | &::-webkit-scrollbar-track-piece 57 | background-color #f8f8f8 58 | &::-webkit-scrollbar-thumb 59 | background-color #999 60 | border-radius 4px 61 | &:hover 62 | background-color #767676 63 | .plts-group 64 | margin-top 20px 65 | .plts-title 66 | font-size 13px 67 | color #A5A3A3 68 | padding-bottom 5px 69 | padding-left 15px 70 | li 71 | border-radius 3px 72 | a 73 | position: relative 74 | font-size 13px 75 | line-height 13px 76 | padding 8px 16px 77 | border-radius 3px 78 | .name 79 | margin-right 62px 80 | white-space nowrap 81 | overflow hidden 82 | text-overflow ellipsis 83 | .limark 84 | position absolute 85 | right 5px 86 | top 6px 87 | span 88 | float right 89 | margin-left 5px 90 | margin-right 5px 91 | span.badge 92 | padding 2px 7px 93 | background-color: #767676 94 | color: #fff 95 | &:hover, &:focus 96 | color #767676 97 | &.active > a, &.active > a:focus, &.active > a:hover 98 | color #fff 99 | background-color: #428bca 100 | &.active > a span.badge 101 | background-color #fff 102 | color #428bca 103 | 104 | #plts-tools 105 | position absolute 106 | bottom 0 107 | width 100% 108 | z-index 1000 109 | padding-left 15px 110 | padding-right 16px 111 | color #767676 112 | text-align right 113 | line-height 30px 114 | background-color #eee 115 | span 116 | margin-left 5px 117 | margin-right 5px 118 | 119 | .list 120 | overflow auto 121 | width 100% 122 | padding-left 25% 123 | .twrap 124 | padding 0 15px 125 | table 126 | a 127 | outline none 128 | text-decoration none 129 | &:hover, &:focus 130 | color #d9534f 131 | td > a 132 | margin-left 10px 133 | thead, td:last-child 134 | white-space nowrap 135 | .dropdown-menu 136 | right 0 137 | left inherit 138 | a:hover, a:focus 139 | color #333 140 | 141 | .song-detail 142 | position absolute 143 | z-index 1000 144 | padding 80px 15px 145 | left 0 146 | bottom 0 147 | overflow auto 148 | width 100% 149 | background-color #f4f4f4 150 | transition transform 0.2s 151 | transform scale(0, 0) 152 | transform-origin 20% 100% 153 | .detail 154 | position absolute 155 | z-index 1 156 | padding 0 10px 157 | width 260px 158 | margin-left 32px 159 | .cover 160 | position relative 161 | &:after 162 | &:before 163 | content "" 164 | position absolute 165 | z-index -1 166 | bottom 15px 167 | left 10px 168 | width 50% 169 | height 20% 170 | box-shadow 0 15px 10px rgba(125, 125, 125, 0.8) 171 | transform rotate(-3deg) 172 | &:after 173 | right 10px 174 | left auto 175 | transform rotate(3deg) 176 | img 177 | width 240px 178 | height 240px 179 | ul.info 180 | margin-top 20px 181 | padding-left 0 182 | li 183 | list-style none 184 | line-height 25px 185 | span.glyphicon 186 | margin-right 10px 187 | 188 | .lyric 189 | position relative 190 | min-height 350px 191 | overflow hidden 192 | &, &:before, &:after 193 | width 100% 194 | padding-left 360px 195 | padding-right 100px 196 | &:before, &:after 197 | content "" 198 | position absolute 199 | top 0 200 | left 0 201 | z-index 1 202 | height 40px 203 | background linear-gradient(to bottom, #f4f4f4, transparent) content-box 204 | &:after 205 | bottom 0 206 | top auto 207 | background linear-gradient(to top, #f4f4f4, transparent) content-box 208 | ul 209 | position relative 210 | line-height 20px 211 | font-size 15px 212 | text-align center 213 | padding-left 0 214 | transition all 1s ease-in-out 215 | &::-webkit-scrollbar 216 | display none 217 | li 218 | list-style none 219 | min-height 20px 220 | color #808080 221 | margin 8px auto 222 | transition all 0.5s ease-in-out 223 | &.current 224 | color #000 225 | font-size 18px 226 | 227 | .lrc-tools 228 | position absolute 229 | right 70px 230 | top 100px 231 | border-radius 4px 232 | box-shadow rgba(159, 159, 159, 0.5) 0 0 12px 233 | .btn 234 | background-color #fff 235 | &:active 236 | &:hover 237 | box-shadow inset 0 0 5px rgba(0, 0, 0, .125) 238 | 239 | #settings 240 | overflow auto 241 | .panel 242 | margin 50px auto 243 | width 80% 244 | 245 | #footer 246 | position fixed 247 | bottom 0 248 | z-index 1500 249 | width 100% 250 | background-color #262626 251 | .mini-tool 252 | display none 253 | a 254 | color #fff 255 | outline none 256 | text-decoration none 257 | margin-left 5px 258 | margin-right 5px 259 | &:hover 260 | color #bdbdbd 261 | .audio-control, .audio-adjust 262 | line-height 60px 263 | font-size 25px 264 | color #fff 265 | text-align center 266 | min-width 138px 267 | .audio-adjust a 268 | margin-right 5px 269 | &#vol-wrapper 270 | position relative 271 | overflow visible 272 | &:hover #vol-panel 273 | display block 274 | #vol-panel 275 | bottom 40px 276 | top: inherit 277 | left -70px 278 | width 150px 279 | .layer 280 | position absolute 281 | bottom -11px 282 | width 100% 283 | height 11px 284 | .slider 285 | width 100% 286 | .slider-track 287 | border-radius 4px 288 | background-color #e7e7e7 289 | .slider-selection 290 | display none 291 | .slider-handle 292 | height 12px 293 | width 12px 294 | margin-left -5px 295 | margin-top -1px 296 | border-radius 50% 297 | background #5e5e5e 298 | .audio-body 299 | color #fff 300 | .media-left 301 | .media-body 302 | display block 303 | height 60px 304 | .media 305 | overflow visible 306 | .media-heading 307 | margin-top 12px 308 | margin-bottom 5px 309 | white-space nowrap 310 | display inline-block 311 | overflow hidden 312 | text-overflow ellipsis 313 | width 100% 314 | padding-right 85px 315 | .media-left 316 | position absolute 317 | top 6px 318 | z-index 2 319 | a 320 | display block 321 | width 50px 322 | cursor pointer 323 | img 324 | width 50px 325 | height 50px 326 | border-width 0 327 | padding 0 328 | vertical-align middle 329 | &:hover 330 | -webkit-filter brightness(0.5) 331 | .media-body 332 | width 100% 333 | padding-left 65px 334 | position relative 335 | overflow visible 336 | .media-time 337 | display block 338 | position absolute 339 | right 0 340 | top 10px 341 | #progress 342 | width 100% 343 | margin-top -10px 344 | .slider-track 345 | background none repeat scroll 0 0 #E8E8E8 346 | border-radius 15px 347 | width 100% 348 | .slider-selection 349 | background none repeat scroll 0 0 #0099CC 350 | border-top-right-radius 0 351 | border-bottom-right-radius 0 352 | .slider-handle 353 | margin-left -7px 354 | margin-top -2px 355 | height 14px 356 | width 14px 357 | background none repeat scroll 0 0 #fff 358 | box-shadow 0 0 10px #333 359 | 360 | @keyframes loading { 361 | from { 362 | transform rotate(0deg) 363 | } 364 | to { 365 | transform rotate(360deg) 366 | } 367 | } 368 | 369 | @media (max-width: 768px) 370 | .list 371 | padding-left 0 !important 372 | 373 | #open-side, #sidebar 374 | display none 375 | 376 | @media (max-width 560px) 377 | #page 378 | display none 379 | 380 | #footer 381 | .mini-tool 382 | display block 383 | float left 384 | color #fff 385 | margin-left 20px 386 | line-height 60px 387 | font-size 16px 388 | .audio-control 389 | float left 390 | .media 391 | float left 392 | padding-left 15px 393 | width 350px 394 | .audio-adjust 395 | display none 396 | -------------------------------------------------------------------------------- /src/shell/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/shell/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | echo "start installation,Please do NOT run in root!" 3 | baseDir=$(cd "$(dirname "$0")"; pwd) 4 | fileName="nwMusicBox.desktop" 5 | filePath="$HOME/.local/share/applications/" 6 | [[ -d "$filePath" ]] || mkdir -p "$filePath" 7 | cp -f "$baseDir/$fileName" "$filePath" 8 | sed -i "s@__Exec@$baseDir/nwMusicBox@g; s@__Icon@$baseDir/icon.svg@g" "$filePath/$fileName" 9 | echo "installation completed!" -------------------------------------------------------------------------------- /src/shell/nwMusicBox.desktop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xdg-open 2 | [Desktop Entry] 3 | Type=Application 4 | Categories=Audio;Music;Player;AudioVideo;Network; 5 | Name=网易音乐盒 6 | Comment=nw_musicbox Launcher 7 | Exec=__Exec 8 | Icon=__Icon 9 | Terminal=false 10 | StartupNotify=true 11 | 12 | -------------------------------------------------------------------------------- /src/shell/安装说明: -------------------------------------------------------------------------------- 1 | 双击nwMusicBox即可打开程序。 2 | 如需建立桌面图标,在当前窗口打开终端,执行sh install.sh --------------------------------------------------------------------------------