diff --git a/package.json b/package.json index 5fe2570..40ecd67 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "name": "yutent", "email": "yutent.io@gmail.com" }, - "homepage": "https://yutent.me", + "homepage": "https://yutent.top", "license": "MIT", "dependencies": { "crypto.js": "^2.0.2", diff --git a/src/css/app.css b/src/css/app.css index 34522e4..984355c 100644 --- a/src/css/app.css +++ b/src/css/app.css @@ -1 +1 @@ -.app{position:relative;display:flex;flex-direction:column;width:100%;height:100%}.app .album-cover{overflow:hidden;position:absolute;z-index:-1;left:0;top:0;width:100%;height:394px}.app .album-cover img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;-webkit-filter:blur(35px) opacity(0.8);filter:blur(35px) opacity(0.8)}.app .title-bar{position:relative;display:flex;align-items:center;z-index:9;height:26px;background:rgba(32,32,32,0.5)}.app .title-bar .btn-box{display:inline-flex;width:auto;height:12px;padding:0 8px}.app .title-bar .btn-box .item{display:inline-flex;width:12px;height:12px;margin:0 3px;background:url(/images/btn-grey.svg) no-repeat;background-size:cover}.app .title-bar .btn-box.focus .quit{background-image:url(/images/btn-close.svg)}.app .title-bar .btn-box.focus .min{background-image:url(/images/btn-mini.svg)}.app .title-bar .btn-box:hover .quit{background-image:url(/images/btn-close_a.svg)}.app .title-bar .btn-box:hover .min{background-image:url(/images/btn-mini_a.svg)}.app .main-body{overflow:hidden;flex:1;display:flex;justify-content:space-between;background:rgba(32,32,32,0.5)}.app .main-body .aside{width:180px;height:100%;padding:0 16px;line-height:2}.app .main-body .aside fieldset{border:0;color:#ebebeb;font-size:12px}.app .main-body .aside fieldset legend{font-size:18px;background:linear-gradient(to bottom, #58ffdf 50%, #459888);-webkit-background-clip:text;background-clip:text;color:transparent}.app .main-body .aside fieldset .item{padding-left:12px;line-height:1.75}.app .main-body .aside fieldset button{width:42px;height:16px;margin-left:12px;font-size:10px;border:0;border-radius:9px;background:#64b5f6;color:#fff}.app .main-body .song-box{width:618px}.app .main-body .song-box .preview{position:relative;display:flex;align-items:center;width:100%;height:99px;padding-bottom:16px;border-bottom:1px solid rgba(200,200,200,0.1)}.app .main-body .song-box .preview .album{width:80px;height:80px}.app .main-body .song-box .preview .album img{width:100%;height:100%}.app .main-body .song-box .preview .info{display:flex;flex-direction:column;justify-content:center;width:320px;margin-left:32px}.app .main-body .song-box .preview .info strong{height:36px;font-size:18px;font-weight:normal}.app .main-body .song-box .preview .info cite{font-size:12px;font-style:normal}.app .main-body .song-box .preview .info p{font-size:12px;color:#bdbdbd}.app .main-body .song-box .preview .total{position:absolute;right:32px;bottom:16px;font-size:16px;font-weight:bold;font-family:Raleway}.app .main-body .song-box .scroll-box{width:100%;height:269px;padding:16px 6px;border-top:1px solid rgba(32,32,32,0.1)}.app .main-body .song-box .list{height:237px;font-size:12px}.app .main-body .song-box .list .item{display:flex;align-items:center;height:26px;padding:0 12px;border-radius:13px}.app .main-body .song-box .list .item .idx{position:relative;width:64px;padding-left:16px;font-size:12px;font-family:Raleway}.app .main-body .song-box .list .item .name{flex:1}.app .main-body .song-box .list .item .artist{width:128px;margin-left:12px}.app .main-body .song-box .list .item .duration{width:42px;margin-left:12px}.app .main-body .song-box .list .item.on{color:#feac23;font-size:14px}.app .main-body .song-box .list .item.on .idx::before{position:absolute;left:0;top:3px;font-size:10px;content:'▶ '}.app .main-body .song-box .list .item.active,.app .main-body .song-box .list .item:hover{color:#58ffdf;background:rgba(29,77,68,0.15)}.app .play-bar{height:66px;width:100%;background:rgba(24,24,24,0.3);color:#fff}.app .play-bar .stat-bar{display:flex;align-items:center;justify-content:space-between;height:20px;color:#ebebeb}.app .play-bar .stat-bar .time{width:42px;margin:0 6px;text-align:center;font-size:12px}.app .play-bar .stat-bar .progress{flex:1;display:flex;align-items:flex-start;height:3px;background:#b2cfe3}.app .play-bar .stat-bar .thumb{height:3px;background:#58ffdf}.app .play-bar .ctrl-box{display:flex;align-items:center;height:42px}.app .play-bar .ctrl-box .holder{flex:1}.app .play-bar .ctrl-box .info{display:flex;flex-direction:column;justify-content:center;width:320px;height:42px;padding-left:12px;line-height:1.25}.app .play-bar .ctrl-box .info strong{font-size:14px;font-weight:500}.app .play-bar .ctrl-box .info cite{font-size:12px;font-style:normal;color:#aeaeae}.app .play-bar .ctrl-box .play-btn{display:flex;align-items:center;justify-content:space-between;width:120px;height:42px;margin-left:32px}.app .play-bar .ctrl-box .play-btn .item{width:22px;height:22px;background-repeat:no-repeat;background-size:cover;transition:background 0.1s ease-in-out;cursor:pointer}.app .play-bar .ctrl-box .play-btn .item.prev{background-image:url(/images/ctrl/prev.png)}.app .play-bar .ctrl-box .play-btn .item.prev:hover,.app .play-bar .ctrl-box .play-btn .item.prev:active{background-image:url(/images/ctrl/prev_a.png)}.app .play-bar .ctrl-box .play-btn .item.on,.app .play-bar .ctrl-box .play-btn .item.off{width:42px;height:42px}.app .play-bar .ctrl-box .play-btn .item.on{background-image:url(/images/ctrl/play.png);-webkit-animation:round 2s linear infinite;animation:round 2s linear infinite}.app .play-bar .ctrl-box .play-btn .item.on:hover,.app .play-bar .ctrl-box .play-btn .item.on:active{background-image:url(/images/ctrl/play_a.png)}.app .play-bar .ctrl-box .play-btn .item.off{background-image:url(/images/ctrl/pause.png)}.app .play-bar .ctrl-box .play-btn .item.off:hover,.app .play-bar .ctrl-box .play-btn .item.off:active{background-image:url(/images/ctrl/pause_a.png)}.app .play-bar .ctrl-box .play-btn .item.next{background-image:url(/images/ctrl/next.png)}.app .play-bar .ctrl-box .play-btn .item.next:hover,.app .play-bar .ctrl-box .play-btn .item.next:active{background-image:url(/images/ctrl/next_a.png)}.app .play-bar .ctrl-box .play-action{display:flex;align-items:center;justify-content:space-between;width:64px;height:22px;margin-right:16px}.app .play-bar .ctrl-box .play-action .item{width:22px;height:22px;background-repeat:no-repeat;background-size:cover;transition:background 0.1s ease-in-out;cursor:pointer}.app .play-bar .ctrl-box .play-action .item.volume{background-image:url(/images/ctrl/volume.png)}.app .play-bar .ctrl-box .play-action .item.volume:hover,.app .play-bar .ctrl-box .play-action .item.volume:active{background-image:url(/images/ctrl/volume_a.png)}.app .play-bar .ctrl-box .play-action .item.mute{background-image:url(/images/ctrl/mute.png)}.app .play-bar .ctrl-box .play-action .item.mute:hover,.app .play-bar .ctrl-box .play-action .item.mute:active{background-image:url(/images/ctrl/mute_a.png)}.app .play-bar .ctrl-box .play-action .item.single{background-image:url(/images/ctrl/single.png)}.app .play-bar .ctrl-box .play-action .item.single:hover,.app .play-bar .ctrl-box .play-action .item.single:active{background-image:url(/images/ctrl/single_a.png)}.app .play-bar .ctrl-box .play-action .item.all{background-image:url(/images/ctrl/all.png)}.app .play-bar .ctrl-box .play-action .item.all:hover,.app .play-bar .ctrl-box .play-action .item.all:active{background-image:url(/images/ctrl/all_a.png)}.app .play-bar .ctrl-box .play-action .item.rand{background-image:url(/images/ctrl/rand.png)}.app .play-bar .ctrl-box .play-action .item.rand:hover,.app .play-bar .ctrl-box .play-action .item.rand:active{background-image:url(/images/ctrl/rand_a.png)}@-webkit-keyframes round{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes round{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}} +.app{position:relative;display:flex;flex-direction:column;width:100%;height:100%}.app .album-cover{overflow:hidden;position:absolute;z-index:-1;left:0;top:0;width:100%;height:394px}.app .album-cover img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;-webkit-filter:blur(35px) opacity(0.8);filter:blur(35px) opacity(0.8)}.app .title-bar{position:relative;display:flex;align-items:center;z-index:9;height:26px;background:rgba(32,32,32,0.5)}.app .main-body{overflow:hidden;flex:1;display:flex;justify-content:space-between;background:rgba(32,32,32,0.5)}.app .main-body .aside{width:180px;height:100%;padding:0 16px;line-height:2}.app .main-body .aside fieldset{border:0;color:#ebebeb;font-size:12px}.app .main-body .aside fieldset legend{font-size:18px;background:linear-gradient(to bottom, #58ffdf 50%, #459888);-webkit-background-clip:text;background-clip:text;color:transparent}.app .main-body .aside fieldset .item{padding-left:12px;line-height:1.75}.app .main-body .aside fieldset button{width:42px;height:16px;margin-left:12px;font-size:10px;border:0;border-radius:9px;background:#64b5f6;color:#fff}.app .main-body .song-box{width:618px}.app .main-body .song-box .preview{position:relative;display:flex;align-items:center;width:100%;height:99px;padding-bottom:16px;border-bottom:1px solid rgba(200,200,200,0.1)}.app .main-body .song-box .preview .album{width:80px;height:80px}.app .main-body .song-box .preview .album img{width:100%;height:100%}.app .main-body .song-box .preview .info{display:flex;flex-direction:column;justify-content:center;width:320px;margin-left:32px}.app .main-body .song-box .preview .info strong{height:36px;font-size:18px;font-weight:normal}.app .main-body .song-box .preview .info cite{font-size:12px;font-style:normal}.app .main-body .song-box .preview .info p{font-size:12px;color:#bdbdbd}.app .main-body .song-box .preview .total{position:absolute;right:32px;bottom:16px;font-size:16px;font-weight:bold;font-family:Raleway}.app .main-body .song-box .scroll-box{width:100%;height:269px;padding:16px 6px;border-top:1px solid rgba(32,32,32,0.1)}.app .main-body .song-box .list{height:237px;font-size:12px}.app .main-body .song-box .list .item{display:flex;align-items:center;height:26px;padding:0 12px;border-radius:13px}.app .main-body .song-box .list .item .idx{position:relative;width:64px;padding-left:16px;font-size:12px;font-family:Raleway}.app .main-body .song-box .list .item .name{flex:1}.app .main-body .song-box .list .item .artist{width:128px;margin-left:12px}.app .main-body .song-box .list .item .duration{width:42px;margin-left:12px}.app .main-body .song-box .list .item.on{color:#feac23;font-size:14px}.app .main-body .song-box .list .item.on .idx::before{position:absolute;left:0;top:3px;font-size:10px;content:'▶ '}.app .main-body .song-box .list .item.active,.app .main-body .song-box .list .item:hover{color:#58ffdf;background:rgba(29,77,68,0.15)}.app .play-bar{height:66px;width:100%;background:rgba(24,24,24,0.3);color:#fff}.app .play-bar .stat-bar{display:flex;align-items:center;justify-content:space-between;height:20px;color:#ebebeb}.app .play-bar .stat-bar .time{width:42px;margin:0 6px;text-align:center;font-size:12px}.app .play-bar .stat-bar .progress{position:relative;flex:1;display:flex;align-items:flex-start;height:3px;background:#b2cfe3}.app .play-bar .stat-bar .progress input{position:absolute;left:0;top:0;display:block;width:100%;height:3px;opacity:0}.app .play-bar .stat-bar .thumb{height:3px;background:#58ffdf}.app .play-bar .ctrl-box{display:flex;align-items:center;height:42px}.app .play-bar .ctrl-box .holder{flex:1}.app .play-bar .ctrl-box .info{display:flex;flex-direction:column;justify-content:center;width:320px;height:42px;padding-left:12px;line-height:1.25}.app .play-bar .ctrl-box .info strong{font-size:14px;font-weight:500}.app .play-bar .ctrl-box .info cite{font-size:12px;font-style:normal;color:#aeaeae}.app .play-bar .ctrl-box .play-btn{display:flex;align-items:center;justify-content:space-between;width:120px;height:42px;margin-left:32px}.app .play-bar .ctrl-box .play-btn .item{width:22px;height:22px;background-repeat:no-repeat;background-size:cover;transition:background 0.1s ease-in-out;cursor:pointer}.app .play-bar .ctrl-box .play-btn .item.prev{background-image:url(/images/ctrl/prev.png)}.app .play-bar .ctrl-box .play-btn .item.prev:hover,.app .play-bar .ctrl-box .play-btn .item.prev:active{background-image:url(/images/ctrl/prev_a.png)}.app .play-bar .ctrl-box .play-btn .item.on,.app .play-bar .ctrl-box .play-btn .item.off{width:42px;height:42px}.app .play-bar .ctrl-box .play-btn .item.on{background-image:url(/images/ctrl/play.png);-webkit-animation:round 2s linear infinite;animation:round 2s linear infinite}.app .play-bar .ctrl-box .play-btn .item.on:hover,.app .play-bar .ctrl-box .play-btn .item.on:active{background-image:url(/images/ctrl/play_a.png)}.app .play-bar .ctrl-box .play-btn .item.off{background-image:url(/images/ctrl/pause.png)}.app .play-bar .ctrl-box .play-btn .item.off:hover,.app .play-bar .ctrl-box .play-btn .item.off:active{background-image:url(/images/ctrl/pause_a.png)}.app .play-bar .ctrl-box .play-btn .item.next{background-image:url(/images/ctrl/next.png)}.app .play-bar .ctrl-box .play-btn .item.next:hover,.app .play-bar .ctrl-box .play-btn .item.next:active{background-image:url(/images/ctrl/next_a.png)}.app .play-bar .ctrl-box .play-action{display:flex;align-items:center;justify-content:space-between;width:64px;height:22px;margin-right:16px}.app .play-bar .ctrl-box .play-action .item{width:22px;height:22px;background-repeat:no-repeat;background-size:cover;transition:background 0.1s ease-in-out;cursor:pointer}.app .play-bar .ctrl-box .play-action .item.volume,.app .play-bar .ctrl-box .play-action .item.mute{position:relative}.app .play-bar .ctrl-box .play-action .item.volume a,.app .play-bar .ctrl-box .play-action .item.mute a{display:block;width:22px;height:22px;background-repeat:no-repeat;background-size:cover;transition:background 0.1s ease-in-out}.app .play-bar .ctrl-box .play-action .item.volume .range,.app .play-bar .ctrl-box .play-action .item.mute .range{visibility:hidden;position:absolute;right:-5px;bottom:20px;width:32px;height:128px;padding:14px;border-radius:3px;background:rgba(128,128,128,0.8)}.app .play-bar .ctrl-box .play-action .item.volume .range input,.app .play-bar .ctrl-box .play-action .item.mute .range input{display:block;width:4px;height:100px;border-radius:3px;-webkit-appearance:slider-vertical;-moz-appearance:slider-vertical;appearance:slider-vertical}.app .play-bar .ctrl-box .play-action .item.volume:hover .range,.app .play-bar .ctrl-box .play-action .item.mute:hover .range{visibility:visible}.app .play-bar .ctrl-box .play-action .item.volume a{background-image:url(/images/ctrl/volume.png)}.app .play-bar .ctrl-box .play-action .item.volume a:hover,.app .play-bar .ctrl-box .play-action .item.volume a:active{background-image:url(/images/ctrl/volume_a.png)}.app .play-bar .ctrl-box .play-action .item.mute a{background-image:url(/images/ctrl/mute.png)}.app .play-bar .ctrl-box .play-action .item.mute a:hover,.app .play-bar .ctrl-box .play-action .item.mute a:active{background-image:url(/images/ctrl/mute_a.png)}.app .play-bar .ctrl-box .play-action .item.single{background-image:url(/images/ctrl/single.png)}.app .play-bar .ctrl-box .play-action .item.single:hover,.app .play-bar .ctrl-box .play-action .item.single:active{background-image:url(/images/ctrl/single_a.png)}.app .play-bar .ctrl-box .play-action .item.all{background-image:url(/images/ctrl/all.png)}.app .play-bar .ctrl-box .play-action .item.all:hover,.app .play-bar .ctrl-box .play-action .item.all:active{background-image:url(/images/ctrl/all_a.png)}.app .play-bar .ctrl-box .play-action .item.rand{background-image:url(/images/ctrl/rand.png)}.app .play-bar .ctrl-box .play-action .item.rand:hover,.app .play-bar .ctrl-box .play-action .item.rand:active{background-image:url(/images/ctrl/rand_a.png)}@-webkit-keyframes round{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes round{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}} diff --git a/src/css/app.scss b/src/css/app.scss index fce432c..9d864d3 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -39,41 +39,6 @@ z-index: 9; height: 26px; background: rgba(32, 32, 32, 0.5); - - .btn-box { - display: inline-flex; - width: auto; - height: 12px; - padding: 0 8px; - - .item { - display: inline-flex; - width: 12px; - height: 12px; - margin: 0 3px; - background: url(/images/btn-grey.svg) no-repeat; - background-size: cover; - } - - &.focus { - .quit { - background-image: url(/images/btn-close.svg); - } - .min { - background-image: url(/images/btn-mini.svg); - } - // .max {background-image:url(/images/btn-maxi.svg);} - } - &:hover { - .quit { - background-image: url(/images/btn-close_a.svg); - } - .min { - background-image: url(/images/btn-mini_a.svg); - } - // .max {background-image:url(/images/btn-maxi_a.svg);} - } - } } .main-body { @@ -255,11 +220,22 @@ } .progress { + position: relative; flex: 1; display: flex; align-items: flex-start; height: 3px; background: #b2cfe3; + + input { + position: absolute; + left: 0; + top: 0; + display: block; + width: 100%; + height: 3px; + opacity: 0; + } } .thumb { @@ -373,7 +349,46 @@ transition: background 0.1s ease-in-out; cursor: pointer; - &.volume { + &.volume, + &.mute { + position: relative; + + a { + display: block; + width: 22px; + height: 22px; + background-repeat: no-repeat; + background-size: cover; + transition: background 0.1s ease-in-out; + } + .range { + visibility: hidden; + position: absolute; + right: -5px; + bottom: 20px; + width: 32px; + height: 128px; + padding: 14px; + border-radius: 3px; + background: rgba(128, 128, 128, 0.8); + + input { + display: block; + width: 4px; + height: 100px; + border-radius: 3px; + appearance: slider-vertical; + } + } + + &:hover { + .range { + visibility: visible; + } + } + } + + &.volume a { background-image: url(/images/ctrl/volume.png); &:hover, &:active { @@ -381,7 +396,7 @@ } } - &.mute { + &.mute a { background-image: url(/images/ctrl/mute.png); &:hover, &:active { diff --git a/src/images/btn-close.svg b/src/images/btn-close.svg deleted file mode 100644 index 74a7d39..0000000 --- a/src/images/btn-close.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - btn_close_hover - - - - - diff --git a/src/images/btn-close_a.svg b/src/images/btn-close_a.svg deleted file mode 100644 index 1bccab6..0000000 --- a/src/images/btn-close_a.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - btn_close_hover - - - - - - - diff --git a/src/images/btn-grey.svg b/src/images/btn-grey.svg deleted file mode 100644 index 740eaba..0000000 --- a/src/images/btn-grey.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - btn_grey - - - - - diff --git a/src/images/btn-maxi.svg b/src/images/btn-maxi.svg deleted file mode 100644 index 90c9a08..0000000 --- a/src/images/btn-maxi.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - btn_max_hover - - - - - diff --git a/src/images/btn-maxi_a.svg b/src/images/btn-maxi_a.svg deleted file mode 100644 index 7a30247..0000000 --- a/src/images/btn-maxi_a.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - btn_max_hover - - - - - - diff --git a/src/images/btn-mini.svg b/src/images/btn-mini.svg deleted file mode 100644 index 85777a9..0000000 --- a/src/images/btn-mini.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - btn_min_hover - - - - - diff --git a/src/images/btn-mini_a.svg b/src/images/btn-mini_a.svg deleted file mode 100644 index c97f6fd..0000000 --- a/src/images/btn-mini_a.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - btn_min_hover - - - - - - diff --git a/src/index.html b/src/index.html index ef665f0..99a3b64 100644 --- a/src/index.html +++ b/src/index.html @@ -16,11 +16,7 @@
-
- - - -
+
@@ -57,8 +53,8 @@
@@ -75,7 +71,8 @@
- + +
@@ -87,22 +84,30 @@
- - - + + + +
- - - + + +
+ +
+ +
diff --git a/src/js/app.js b/src/js/app.js index 441251c..5dd4540 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -15,6 +15,8 @@ import Player from '/js/lib/audio/index.js' const id3 = require('music-metadata') +const MODE_DICT = { 1: 'all', 2: 'single', 3: 'random' } + var kb = new Keyboard() var player = new Player() @@ -26,9 +28,10 @@ Anot({ $id: 'app', state: { defaultCover: '/images/disk.png', - isplaying: true, - playmode: 1, + isplaying: false, + playmode: +Anot.ls('app_mode') || 1, mute: false, + volume: +Anot.ls('app_volume') || 50, preview: { name: '', album: '', @@ -42,24 +45,13 @@ Anot({ time: 0, duration: 0 }, - curr: 2, + progress: 0, + curr: -1, list: [] }, async mounted() { - var list = app.dispatch('scan-dir', { path: '/Volumes/extends/music' }) - - this.list = list - - for (let it of this.list) { - let { album, artist, title, duration } = await this.getID3(it.path) - - it.name = title || it.name - it.artist = artist - it.album = album - it.duration = duration - } - - player.load(list.map(it => `sonist://${it.path}`)) + var list = app.dispatch('get-all-songs') + // var list = app.dispatch('scan-dir', { dir: '/Volumes/extends/music' }) kb.on(['left'], ev => { var time = this.song.time - 5 @@ -75,17 +67,121 @@ Anot({ } this.song.time = time }) + kb.on(['down'], ev => { + var vol = +this.volume - 5 + if (vol < 0) { + vol = 0 + } + this.volume = vol + }) + kb.on(['up'], ev => { + var vol = +this.volume + 5 + if (vol > 100) { + vol = 100 + } + this.volume = vol + }) + + // for (let it of list) { + // let { album, artist, title, duration } = await this.getID3(it.file_path) + + // it.name = title || it.name + // it.artist = artist + // it.album = album + // it.duration = duration + // app.dispatch('add-song', { + // name: it.name, + // artist, + // album, + // duration, + // file_path: it.path + // }) + // } + this.list = list + + player.volume = this.volume / 100 + player.load(list.map(it => `sonist://${it.file_path}`)) + + player.on('play', time => { + this.song.time = time + }) + player.on('stop', _ => { + var idx = this.curr + var repeat = false + switch (this.playmode) { + case 1: // all + idx++ + if (idx >= this.list.length) { + idx = 0 + } + break + case 2: // single + repeat = true + break + case 3: // random + idx = ~~(Math.random() * this.list.length) + break + } + + this.playSong(this.list[idx], idx, repeat) + }) + }, + watch: { + volume(v) { + Anot.ls('app_volume', v) + player.volume = v / 100 + }, + playmode(v) { + Anot.ls('app_mode', v) + }, + progress(v) { + var t = +(this.song.duration * (v / 712)).toFixed(2) + player.seek(t) + } }, methods: { - load() { - // window.player = new Howl({ - // src: [`sonist://${this.list[0].path}`] - // }) - // window.player = this.__PLAYER__ - }, - play() { - this.isplaying = !this.isplaying + play(act) { + var idx = this.curr + var repeat = false + + switch (act) { + case 0: + if (idx > -1) { + player.play(-1) + } else { + this.playSong(0) + } + this.isplaying = !this.isplaying + break + + default: + switch (this.playmode) { + case 1: // all + idx += act + if (idx < 0) { + idx = this.list.length - 1 + } + if (idx >= this.list.length) { + idx = 0 + } + break + case 2: // single + if (idx === -1) { + idx = ~~(Math.random() * this.list.length) + } + repeat = true + break + case 3: // random + idx = ~~(Math.random() * this.list.length) + break + } + + this.playSong(idx, repeat) + + break + } }, + switchMode() { var n = this.playmode + 1 if (n > 3) { @@ -105,18 +201,30 @@ Anot({ return { album, artist, title, duration: ~~duration } }) }, - prviewSong(it, i) { + previewSong(it) { var { album, artist, name, cover } = it Object.assign(this.preview, { album, artist, name, cover }) }, - playSong(it, i) { + playSong(i, repeat) { // + var it = this.list[i] + + if (this.curr === i) { + if (repeat) { + this.song.time = 0 + player.play(-1, repeat) + } + return + } this.curr = i this.song.name = it.name this.song.artist = it.artist this.song.duration = it.duration - this.song.src = `file://${it.path}` + this.song.src = `file://${it.file_path}` this.song.time = 0 + this.isplaying = true + this.previewSong(it) + player.play(i) } } }) diff --git a/src/js/lib/audio/index.js b/src/js/lib/audio/index.js index 881a73c..3e16346 100644 --- a/src/js/lib/audio/index.js +++ b/src/js/lib/audio/index.js @@ -4,8 +4,12 @@ * @date 2020/11/19 17:32:19 */ +import $ from '../utils.js' import fetch from '../fetch/index.js' +const { EventEmitter } = require('events') +const util = require('util') + function hide(target, key, value) { Object.defineProperty(target, key, { value, @@ -19,16 +23,39 @@ export default class Player { constructor() { hide(this, '__LIST__', []) hide(this, '__AC__', new AudioContext()) - hide(this, '__AUDIO__', new Audio()) hide(this, 'props', { curr: '', stat: 'ready', volume: 0.5, - mode: 'all', // 循环模式, all, single, rand - time: 0, duration: 0 }) hide(this, 'track', null) + hide(this, 'gain', null) + + this.__main__() + } + + __main__() { + hide(this, '__AUDIO__', new Audio()) + + this.__playFn = $.bind(this.__AUDIO__, 'timeupdate', _ => { + this.emit('play', this.__AUDIO__.currentTime) + }) + this.__stopFn = $.bind(this.__AUDIO__, 'ended', _ => { + this.props.stat = 'paused' + this.emit('stop') + }) + } + + __destroy__() { + $.unbind(this.__AUDIO__, 'timeupdate', this.__playFn) + $.unbind(this.__AUDIO__, 'ended', this.__stopFn) + + this.__AUDIO__.pause() + this.__AUDIO__.currentTime = 0 + + delete this.__playFn + delete this.__stopFn } load(list) { @@ -37,11 +64,16 @@ export default class Player { } async _getTrack(file) { + this.__main__() this.__AUDIO__.src = URL.createObjectURL( await fetch(file).then(r => r.blob()) ) - console.log(this.__AUDIO__) - return this.__AC__.createMediaElementSource(this.__AUDIO__) + + this.gain = this.__AC__.createGain() + this.gain.gain.value = this.volume + + this.track = this.__AC__.createMediaElementSource(this.__AUDIO__) + this.track.connect(this.gain).connect(this.__AC__.destination) } get volume() { @@ -49,7 +81,7 @@ export default class Player { } set volume(val) { - val = +val || 0.5 + val = +val if (val < 0) { val = 0 } @@ -57,31 +89,31 @@ export default class Player { val = 1 } this.props.volume = val - } - - get mode() { - return this.props.mode - } - - set mode(val) { - this.props.mode = val + if (this.gain) { + this.gain.gain.value = val + } } get time() { - return this.props.time + return this.__AUDIO__.currentTime } get stat() { return this.props.stat } - async play(id) { - var url, gain - - if (id === undefined) { + /** + * id: 歌曲序号 + * force: 强制重新播放 + */ + play(id, force = false) { + if (id === -1) { if (this.track) { + if (force) { + this.seek(0) + this.props.stat = 'paused' + } if (this.stat === 'playing') { - this.props.time = this.track.context.currentTime this.__AUDIO__.pause() this.props.stat = 'paused' } else if (this.stat === 'paused') { @@ -90,21 +122,17 @@ export default class Player { } } } else { - url = this.__LIST__[id] + var url = this.__LIST__[id] if (!url || this.props.curr === url) { return } - gain = this.__AC__.createGain() - if (this.track) { this.stop() } - gain.gain.value = this.volume - - this.track = await this._getTrack(url) - this.track.connect(gain).connect(this.__AC__.destination) + this.props.curr = url + this._getTrack(url) this.__AUDIO__.play() this.props.stat = 'playing' @@ -112,15 +140,19 @@ export default class Player { } seek(time) { - this.props.time = time + if (this.track) { + this.__AUDIO__.currentTime = time + } } stop() { if (this.track) { - this.__AUDIO__.pause() this.track = null - this.props.time = 0 + this.gain = null + this.__destroy__() this.props.stat = 'stoped' } } } + +util.inherits(Player, EventEmitter) diff --git a/src/js/lib/socket.js b/src/js/lib/socket.js index e6dd883..d2327fe 100644 --- a/src/js/lib/socket.js +++ b/src/js/lib/socket.js @@ -7,7 +7,7 @@ const { ipcRenderer } = require('electron') export default { - dispatch(type = '', params = {}) { - return ipcRenderer.sendSync('app', Object.assign(params, { type })) + dispatch(type = '', data = {}) { + return ipcRenderer.sendSync('app', { data, type }) } } diff --git a/src/main.js b/src/main.js index 3e27817..314863e 100644 --- a/src/main.js +++ b/src/main.js @@ -7,12 +7,12 @@ const { app, session, protocol, globalShortcut } = require('electron') const path = require('path') const fs = require('iofs') -// const {exec} = require('child_process') require('./tools/init.js') + +const { createMainWindow, createMiniWindow } = require('./tools/windows.js') const { createAppTray, createLrcTray } = require('./tools/tray.js') const createMenu = require('./tools/menu.js') -const { createMainWindow, createMiniWindow } = require('./tools/windows.js') const MIME_TYPES = { '.js': 'text/javascript', @@ -47,18 +47,17 @@ protocol.registerSchemesAsPrivileged([ // 初始化应用 app.once('ready', () => { // 注册协议 - protocol.registerBufferProtocol('app', (req, cb) => { - let file = decodeURIComponent(req.url.replace(/^app:\/\/local\//, '')) - let ext = path.extname(req.url) - let buff = fs.cat(path.resolve(__dirname, file)) + protocol.registerBufferProtocol('app', function(req, cb) { + var file = decodeURIComponent(req.url.replace(/^app:\/\/local\//, '')) + var ext = path.extname(req.url) + var buff = fs.cat(path.resolve(__dirname, file)) cb({ data: buff, mimeType: MIME_TYPES[ext] }) }) - protocol.registerBufferProtocol('sonist', (req, cb) => { - let file = decodeURIComponent(req.url.replace(/^sonist:[\/]+/, '/')) - let ext = path.extname(req.url) - let buff = fs.cat(file) - cb({ data: buff, mimeType: MIME_TYPES[ext] || MIME_TYPES.all }) + protocol.registerBufferProtocol('sonist', function(req, cb) { + var file = decodeURIComponent(req.url.replace(/^sonist:[\/]+/, '/')) + var ext = path.extname(req.url) + cb({ data: fs.cat(file), mimeType: MIME_TYPES[ext] || MIME_TYPES.all }) }) // 修改app的UA session.defaultSession.setUserAgent( @@ -74,7 +73,7 @@ app.once('ready', () => { // mac专属事件,点击dock栏图标,可激活窗口 app.on('activate', _ => { if (win) { - win.webContents.send('dock-click') + win.restore() } }) }) diff --git a/src/tools/db/init.js b/src/tools/db/init.js index f1b5517..e5b2853 100644 --- a/src/tools/db/init.js +++ b/src/tools/db/init.js @@ -4,6 +4,7 @@ * @date 2020/07/14 18:17:59 */ +// 歌单 const TABLE_PLAYLIST = ` CREATE TABLE IF NOT EXISTS "playlist" ( "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, @@ -11,15 +12,16 @@ CREATE TABLE IF NOT EXISTS "playlist" ( ) ` +// aid: 歌手ID const TABLE_SONGS = ` CREATE TABLE IF NOT EXISTS "songs" ( "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, - "pid" integer NOT NULL, - "aid" integer NOT NULL, "name" char(128) NOT NULL, + "artist" char(128) NOT NULL, "album" char(128) NOT NULL, + "duration" integer NOT NULL, "cover" char(256) NOT NULL, - "path" char(256) NOT NULL, + "file_path" char(256) NOT NULL, "lrc" text NOT NULL ) ` @@ -28,15 +30,7 @@ const TABLE_RELATIONS = ` CREATE TABLE IF NOT EXISTS "relations" ( "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "sid" integer NOT NULL, - "pid" integer NOT NULL, -) -` - -const TABLE_ARTISTS = ` -CREATE TABLE IF NOT EXISTS "artists" ( - "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, - "name" integer NOT NULL, - "avatar" char(256) NOT NULL + "pid" integer NOT NULL ) ` @@ -50,5 +44,4 @@ module.exports = function(db) { db.query(TABLE_PLAYLIST).catch(error) db.query(TABLE_SONGS).catch(error) db.query(TABLE_RELATIONS).catch(error) - db.query(TABLE_ARTISTS).catch(error) } diff --git a/src/tools/init.js b/src/tools/init.js index 997151a..821647e 100644 --- a/src/tools/init.js +++ b/src/tools/init.js @@ -74,11 +74,63 @@ ipcMain.on('app', (ev, conn) => { fs.echo(JSON.stringify(conn.data, null, 2), INIT_FILE) break + case 'add-song': + var { + name, + artist = '', + album = '', + duration, + cover = '', + file_path, + lrc = '' + } = conn.data + db.query( + 'INSERT INTO `songs` (name, artist, album, duration, cover, file_path, lrc) VALUES ($name, $artist, $album, $duration, $cover, $file_path, $lrc)', + { + $name: name, + $artist: artist, + $album: album, + $duration: duration, + $cover: cover, + $file_path: file_path, + $lrc: lrc + } + ) + ev.returnValue = true + break + + case 'get-all-songs': + db.getAll('SELECT id, name, duration, artist, file_path FROM songs') + .then(res => { + ev.returnValue = res + }) + .catch(err => { + ev.returnValue = err + }) + break + + case 'get-songs': + db.getAll( + 'SELECT id, name, duration, artist, file_path ' + + 'FROM songs ' + + 'WHERE id IN ' + + '(SELECT sid FROM relations WHERE pid = $pid)', + { $pid: conn.pid } + ) + .then(res => { + ev.returnValue = res + }) + .catch(err => { + ev.returnValue = err + }) + break + // 扫描目录 case 'scan-dir': - if (fs.isdir(conn.path)) { + var { dir } = conn.data + if (fs.isdir(dir)) { let list = fs - .ls(conn.path, true) + .ls(dir, true) .filter(it => { if (fs.isdir(it)) { return false @@ -97,7 +149,13 @@ ipcMain.on('app', (ev, conn) => { end: 256, encoding: 'base64' }) - return { name, path: it, artist: '', album: '', duration: '00:00' } + return { + name, + file_path: it, + artist: '', + album: '', + duration: '00:00' + } }) ev.returnValue = list } else { diff --git a/src/tools/menu.js b/src/tools/menu.js index 5e17c0b..8b67f2c 100644 --- a/src/tools/menu.js +++ b/src/tools/menu.js @@ -15,7 +15,13 @@ module.exports = function(win) { submenu: [ { role: 'about', label: '关于 Sonist' }, { type: 'separator' }, - { role: 'quit', label: '退出' } + { + label: '退出 Sonist', + accelerator: 'Command+Q', + click(a, b, ev) { + win.destroy() + } + } ] }, { diff --git a/src/tools/windows.js b/src/tools/windows.js index 676029e..1c7bfc5 100644 --- a/src/tools/windows.js +++ b/src/tools/windows.js @@ -16,6 +16,7 @@ exports.createMainWindow = function(icon) { width: 820, height: 460, frame: false, + titleBarStyle: 'hiddenInset', resizable: false, maximizable: false, icon, @@ -40,6 +41,11 @@ exports.createMainWindow = function(icon) { win.openDevTools() }) + win.on('close', ev => { + ev.preventDefault() + win.hide() + }) + return win }