This repository has been archived on 2023-08-30. You can view files and clone it, but cannot push or open issues/pull-requests.
appcat
/
sonist
Archived
1
0
Fork 0

完成播放功能

2.x
宇天 2020-11-20 18:23:53 +08:00
parent b21ec91170
commit 641199aa1f
19 changed files with 364 additions and 291 deletions

View File

@ -11,7 +11,7 @@
"name": "yutent", "name": "yutent",
"email": "yutent.io@gmail.com" "email": "yutent.io@gmail.com"
}, },
"homepage": "https://yutent.me", "homepage": "https://yutent.top",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"crypto.js": "^2.0.2", "crypto.js": "^2.0.2",

File diff suppressed because one or more lines are too long

View File

@ -39,41 +39,6 @@
z-index: 9; z-index: 9;
height: 26px; height: 26px;
background: rgba(32, 32, 32, 0.5); 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 { .main-body {
@ -255,11 +220,22 @@
} }
.progress { .progress {
position: relative;
flex: 1; flex: 1;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
height: 3px; height: 3px;
background: #b2cfe3; background: #b2cfe3;
input {
position: absolute;
left: 0;
top: 0;
display: block;
width: 100%;
height: 3px;
opacity: 0;
}
} }
.thumb { .thumb {
@ -373,7 +349,46 @@
transition: background 0.1s ease-in-out; transition: background 0.1s ease-in-out;
cursor: pointer; 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); background-image: url(/images/ctrl/volume.png);
&:hover, &:hover,
&:active { &:active {
@ -381,7 +396,7 @@
} }
} }
&.mute { &.mute a {
background-image: url(/images/ctrl/mute.png); background-image: url(/images/ctrl/mute.png);
&:hover, &:hover,
&:active { &:active {

View File

@ -1,19 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #f55449;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
</style>
</defs>
<title>btn_close_hover</title>
<g id="red">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 594 B

View File

@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #f55449;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
.cls-3 {
opacity: 0.7;
}
</style>
</defs>
<title>btn_close_hover</title>
<g id="red">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
<rect class="cls-3" x="192" y="476" width="640" height="72" rx="36" ry="36" transform="translate(512 1236.08) rotate(-135)"/>
<rect class="cls-3" x="191" y="475" width="640" height="72" rx="36" ry="36" transform="translate(1233.66 511) rotate(135)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 899 B

View File

@ -1,19 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #dee1e3;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
</style>
</defs>
<title>btn_grey</title>
<g id="grey">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 588 B

View File

@ -1,19 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #39ea49;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
</style>
</defs>
<title>btn_max_hover</title>
<g id="green">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 594 B

View File

@ -1,24 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #39ea49;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
.cls-3 {
opacity: 0.7;
}
</style>
</defs>
<title>btn_max_hover</title>
<g id="green">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
<path class="cls-3" d="M395.93,267.8a31.32,31.32,0,0,1,22.86-9.48l319.33-2.55A31.25,31.25,0,0,1,770,287.64l-2.5,319.29A32,32,0,0,1,758,629.79a31.06,31.06,0,0,1-22.86,9.48,30.67,30.67,0,0,1-22.72-9.15L395.65,313.33c-6.22-6.13-9.24-13.67-9.15-22.72a31.28,31.28,0,0,1,9.43-22.82ZM629.84,758A31.32,31.32,0,0,1,607,767.45L287.64,770a31.25,31.25,0,0,1-31.87-31.87l2.5-319.29A32,32,0,0,1,267.75,396a31.06,31.06,0,0,1,22.86-9.48,30.67,30.67,0,0,1,22.72,9.15L630.12,712.44c6.22,6.13,9.24,13.67,9.15,22.72A31.28,31.28,0,0,1,629.84,758Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #fac536;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
</style>
</defs>
<title>btn_min_hover</title>
<g id="yellow">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 595 B

View File

@ -1,24 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<style>
.cls-1 {
fill: #fac536;
}
.cls-2 {
fill: #333;
opacity: 0.1;
}
.cls-3 {
opacity: 0.7;
}
</style>
</defs>
<title>btn_min_hover</title>
<g id="yellow">
<circle class="cls-1" cx="512" cy="512" r="512"/>
<path class="cls-2" d="M512,1024A512.13,512.13,0,0,1,312.7,40.24,512.13,512.13,0,0,1,711.3,983.76,508.81,508.81,0,0,1,512,1024Zm0-984A472.13,472.13,0,0,0,328.28,946.92,472.13,472.13,0,0,0,695.72,77.08,469,469,0,0,0,512,40Z"/>
<rect class="cls-3" x="192" y="476" width="640" height="72" rx="36" ry="36"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 723 B

View File

@ -16,11 +16,7 @@
</div> </div>
<div class="title-bar app-drag"> <div class="title-bar app-drag">
<div class="btn-box app-nodrag focus">
<a class="item quit"></a>
<a class="item min"></a>
<a class="item max"></a>
</div>
</div> </div>
<main class="main-body"> <main class="main-body">
@ -57,8 +53,8 @@
<wc-scroll class="list"> <wc-scroll class="list">
<section <section
class="item" class="item"
@click="prviewSong(it, i)" @click="previewSong(it, i)"
@dblclick="playSong(it, i)" @dblclick="playSong(i)"
:class="{on: curr === i}" :class="{on: curr === i}"
:for="i it in list"> :for="i it in list">
<span class="idx" :text="i + 1"></span> <span class="idx" :text="i + 1"></span>
@ -75,7 +71,8 @@
<section class="stat-bar"> <section class="stat-bar">
<span class="time" :text="song.time | time"></span> <span class="time" :text="song.time | time"></span>
<div class="progress"> <div class="progress">
<span class="thumb" :css="{width: ~~((100 * song.time) / song.duration) + '%'}"></span> <span class="thumb" :css="{width: ((100 * song.time) / song.duration).toFixed(1) + '%'}"></span>
<input type="range" max="712" min="0" step="1" :duplex="progress">
</div> </div>
<span class="time" :text="song.duration | time"></span> <span class="time" :text="song.duration | time"></span>
</section> </section>
@ -87,22 +84,30 @@
</div> </div>
<div class="play-btn"> <div class="play-btn">
<a class="item prev"></a> <a class="item prev" @click="play(-1)"></a>
<a class="item" :class="{on: isplaying, off: !isplaying}" @click="play"></a> <a class="item"
<a class="item next"></a> :class="{on: isplaying, off: !isplaying}"
@click="play(0)">
</a>
<a class="item next" @click="play(1)"></a>
</div> </div>
<div class="holder"></div> <div class="holder"></div>
<div class="play-action"> <div class="play-action">
<a class="item" :class="{ <item class="item" :class="{
all: playmode === 1, all: playmode === 1,
single: playmode === 2, single: playmode === 2,
rand: playmode === 3, rand: playmode === 3,
}" }"
@click="switchMode"> @click="switchMode">
</a> </item>
<a class="item" :class="{mute: mute, volume: !mute}" @click="toggleMute"></a> <item class="item" :class="{mute: mute || volume <= 0, volume: !mute && volume > 0}">
<div class="range">
<input type="range" max="100" min="0" step="1" :duplex="volume">
</div>
<a @click="toggleMute"></a>
</item>
</div> </div>
</section> </section>
</div> </div>

View File

@ -15,6 +15,8 @@ import Player from '/js/lib/audio/index.js'
const id3 = require('music-metadata') const id3 = require('music-metadata')
const MODE_DICT = { 1: 'all', 2: 'single', 3: 'random' }
var kb = new Keyboard() var kb = new Keyboard()
var player = new Player() var player = new Player()
@ -26,9 +28,10 @@ Anot({
$id: 'app', $id: 'app',
state: { state: {
defaultCover: '/images/disk.png', defaultCover: '/images/disk.png',
isplaying: true, isplaying: false,
playmode: 1, playmode: +Anot.ls('app_mode') || 1,
mute: false, mute: false,
volume: +Anot.ls('app_volume') || 50,
preview: { preview: {
name: '', name: '',
album: '', album: '',
@ -42,24 +45,13 @@ Anot({
time: 0, time: 0,
duration: 0 duration: 0
}, },
curr: 2, progress: 0,
curr: -1,
list: [] list: []
}, },
async mounted() { async mounted() {
var list = app.dispatch('scan-dir', { path: '/Volumes/extends/music' }) var list = app.dispatch('get-all-songs')
// var list = app.dispatch('scan-dir', { dir: '/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}`))
kb.on(['left'], ev => { kb.on(['left'], ev => {
var time = this.song.time - 5 var time = this.song.time - 5
@ -75,17 +67,121 @@ Anot({
} }
this.song.time = time 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: { methods: {
load() { play(act) {
// window.player = new Howl({ var idx = this.curr
// src: [`sonist://${this.list[0].path}`] var repeat = false
// })
// window.player = this.__PLAYER__ switch (act) {
}, case 0:
play() { if (idx > -1) {
player.play(-1)
} else {
this.playSong(0)
}
this.isplaying = !this.isplaying 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() { switchMode() {
var n = this.playmode + 1 var n = this.playmode + 1
if (n > 3) { if (n > 3) {
@ -105,18 +201,30 @@ Anot({
return { album, artist, title, duration: ~~duration } return { album, artist, title, duration: ~~duration }
}) })
}, },
prviewSong(it, i) { previewSong(it) {
var { album, artist, name, cover } = it var { album, artist, name, cover } = it
Object.assign(this.preview, { album, artist, name, cover }) 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.curr = i
this.song.name = it.name this.song.name = it.name
this.song.artist = it.artist this.song.artist = it.artist
this.song.duration = it.duration this.song.duration = it.duration
this.song.src = `file://${it.path}` this.song.src = `file://${it.file_path}`
this.song.time = 0 this.song.time = 0
this.isplaying = true
this.previewSong(it)
player.play(i)
} }
} }
}) })

View File

@ -4,8 +4,12 @@
* @date 2020/11/19 17:32:19 * @date 2020/11/19 17:32:19
*/ */
import $ from '../utils.js'
import fetch from '../fetch/index.js' import fetch from '../fetch/index.js'
const { EventEmitter } = require('events')
const util = require('util')
function hide(target, key, value) { function hide(target, key, value) {
Object.defineProperty(target, key, { Object.defineProperty(target, key, {
value, value,
@ -19,16 +23,39 @@ export default class Player {
constructor() { constructor() {
hide(this, '__LIST__', []) hide(this, '__LIST__', [])
hide(this, '__AC__', new AudioContext()) hide(this, '__AC__', new AudioContext())
hide(this, '__AUDIO__', new Audio())
hide(this, 'props', { hide(this, 'props', {
curr: '', curr: '',
stat: 'ready', stat: 'ready',
volume: 0.5, volume: 0.5,
mode: 'all', // 循环模式, all, single, rand
time: 0,
duration: 0 duration: 0
}) })
hide(this, 'track', null) 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) { load(list) {
@ -37,11 +64,16 @@ export default class Player {
} }
async _getTrack(file) { async _getTrack(file) {
this.__main__()
this.__AUDIO__.src = URL.createObjectURL( this.__AUDIO__.src = URL.createObjectURL(
await fetch(file).then(r => r.blob()) 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() { get volume() {
@ -49,7 +81,7 @@ export default class Player {
} }
set volume(val) { set volume(val) {
val = +val || 0.5 val = +val
if (val < 0) { if (val < 0) {
val = 0 val = 0
} }
@ -57,31 +89,31 @@ export default class Player {
val = 1 val = 1
} }
this.props.volume = val this.props.volume = val
if (this.gain) {
this.gain.gain.value = val
} }
get mode() {
return this.props.mode
}
set mode(val) {
this.props.mode = val
} }
get time() { get time() {
return this.props.time return this.__AUDIO__.currentTime
} }
get stat() { get stat() {
return this.props.stat return this.props.stat
} }
async play(id) { /**
var url, gain * id: 歌曲序号
* force: 强制重新播放
if (id === undefined) { */
play(id, force = false) {
if (id === -1) {
if (this.track) { if (this.track) {
if (force) {
this.seek(0)
this.props.stat = 'paused'
}
if (this.stat === 'playing') { if (this.stat === 'playing') {
this.props.time = this.track.context.currentTime
this.__AUDIO__.pause() this.__AUDIO__.pause()
this.props.stat = 'paused' this.props.stat = 'paused'
} else if (this.stat === 'paused') { } else if (this.stat === 'paused') {
@ -90,21 +122,17 @@ export default class Player {
} }
} }
} else { } else {
url = this.__LIST__[id] var url = this.__LIST__[id]
if (!url || this.props.curr === url) { if (!url || this.props.curr === url) {
return return
} }
gain = this.__AC__.createGain()
if (this.track) { if (this.track) {
this.stop() this.stop()
} }
gain.gain.value = this.volume this.props.curr = url
this._getTrack(url)
this.track = await this._getTrack(url)
this.track.connect(gain).connect(this.__AC__.destination)
this.__AUDIO__.play() this.__AUDIO__.play()
this.props.stat = 'playing' this.props.stat = 'playing'
@ -112,15 +140,19 @@ export default class Player {
} }
seek(time) { seek(time) {
this.props.time = time if (this.track) {
this.__AUDIO__.currentTime = time
}
} }
stop() { stop() {
if (this.track) { if (this.track) {
this.__AUDIO__.pause()
this.track = null this.track = null
this.props.time = 0 this.gain = null
this.__destroy__()
this.props.stat = 'stoped' this.props.stat = 'stoped'
} }
} }
} }
util.inherits(Player, EventEmitter)

View File

@ -7,7 +7,7 @@
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
export default { export default {
dispatch(type = '', params = {}) { dispatch(type = '', data = {}) {
return ipcRenderer.sendSync('app', Object.assign(params, { type })) return ipcRenderer.sendSync('app', { data, type })
} }
} }

View File

@ -7,12 +7,12 @@
const { app, session, protocol, globalShortcut } = require('electron') const { app, session, protocol, globalShortcut } = require('electron')
const path = require('path') const path = require('path')
const fs = require('iofs') const fs = require('iofs')
// const {exec} = require('child_process')
require('./tools/init.js') require('./tools/init.js')
const { createMainWindow, createMiniWindow } = require('./tools/windows.js')
const { createAppTray, createLrcTray } = require('./tools/tray.js') const { createAppTray, createLrcTray } = require('./tools/tray.js')
const createMenu = require('./tools/menu.js') const createMenu = require('./tools/menu.js')
const { createMainWindow, createMiniWindow } = require('./tools/windows.js')
const MIME_TYPES = { const MIME_TYPES = {
'.js': 'text/javascript', '.js': 'text/javascript',
@ -47,18 +47,17 @@ protocol.registerSchemesAsPrivileged([
// 初始化应用 // 初始化应用
app.once('ready', () => { app.once('ready', () => {
// 注册协议 // 注册协议
protocol.registerBufferProtocol('app', (req, cb) => { protocol.registerBufferProtocol('app', function(req, cb) {
let file = decodeURIComponent(req.url.replace(/^app:\/\/local\//, '')) var file = decodeURIComponent(req.url.replace(/^app:\/\/local\//, ''))
let ext = path.extname(req.url) var ext = path.extname(req.url)
let buff = fs.cat(path.resolve(__dirname, file)) var buff = fs.cat(path.resolve(__dirname, file))
cb({ data: buff, mimeType: MIME_TYPES[ext] }) cb({ data: buff, mimeType: MIME_TYPES[ext] })
}) })
protocol.registerBufferProtocol('sonist', (req, cb) => { protocol.registerBufferProtocol('sonist', function(req, cb) {
let file = decodeURIComponent(req.url.replace(/^sonist:[\/]+/, '/')) var file = decodeURIComponent(req.url.replace(/^sonist:[\/]+/, '/'))
let ext = path.extname(req.url) var ext = path.extname(req.url)
let buff = fs.cat(file) cb({ data: fs.cat(file), mimeType: MIME_TYPES[ext] || MIME_TYPES.all })
cb({ data: buff, mimeType: MIME_TYPES[ext] || MIME_TYPES.all })
}) })
// 修改app的UA // 修改app的UA
session.defaultSession.setUserAgent( session.defaultSession.setUserAgent(
@ -74,7 +73,7 @@ app.once('ready', () => {
// mac专属事件,点击dock栏图标,可激活窗口 // mac专属事件,点击dock栏图标,可激活窗口
app.on('activate', _ => { app.on('activate', _ => {
if (win) { if (win) {
win.webContents.send('dock-click') win.restore()
} }
}) })
}) })

View File

@ -4,6 +4,7 @@
* @date 2020/07/14 18:17:59 * @date 2020/07/14 18:17:59
*/ */
// 歌单
const TABLE_PLAYLIST = ` const TABLE_PLAYLIST = `
CREATE TABLE IF NOT EXISTS "playlist" ( CREATE TABLE IF NOT EXISTS "playlist" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -11,15 +12,16 @@ CREATE TABLE IF NOT EXISTS "playlist" (
) )
` `
// aid: 歌手ID
const TABLE_SONGS = ` const TABLE_SONGS = `
CREATE TABLE IF NOT EXISTS "songs" ( CREATE TABLE IF NOT EXISTS "songs" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"pid" integer NOT NULL,
"aid" integer NOT NULL,
"name" char(128) NOT NULL, "name" char(128) NOT NULL,
"artist" char(128) NOT NULL,
"album" char(128) NOT NULL, "album" char(128) NOT NULL,
"duration" integer NOT NULL,
"cover" char(256) NOT NULL, "cover" char(256) NOT NULL,
"path" char(256) NOT NULL, "file_path" char(256) NOT NULL,
"lrc" text NOT NULL "lrc" text NOT NULL
) )
` `
@ -28,15 +30,7 @@ const TABLE_RELATIONS = `
CREATE TABLE IF NOT EXISTS "relations" ( CREATE TABLE IF NOT EXISTS "relations" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"sid" integer NOT NULL, "sid" integer NOT NULL,
"pid" 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
) )
` `
@ -50,5 +44,4 @@ module.exports = function(db) {
db.query(TABLE_PLAYLIST).catch(error) db.query(TABLE_PLAYLIST).catch(error)
db.query(TABLE_SONGS).catch(error) db.query(TABLE_SONGS).catch(error)
db.query(TABLE_RELATIONS).catch(error) db.query(TABLE_RELATIONS).catch(error)
db.query(TABLE_ARTISTS).catch(error)
} }

View File

@ -74,11 +74,63 @@ ipcMain.on('app', (ev, conn) => {
fs.echo(JSON.stringify(conn.data, null, 2), INIT_FILE) fs.echo(JSON.stringify(conn.data, null, 2), INIT_FILE)
break 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': case 'scan-dir':
if (fs.isdir(conn.path)) { var { dir } = conn.data
if (fs.isdir(dir)) {
let list = fs let list = fs
.ls(conn.path, true) .ls(dir, true)
.filter(it => { .filter(it => {
if (fs.isdir(it)) { if (fs.isdir(it)) {
return false return false
@ -97,7 +149,13 @@ ipcMain.on('app', (ev, conn) => {
end: 256, end: 256,
encoding: 'base64' encoding: 'base64'
}) })
return { name, path: it, artist: '', album: '', duration: '00:00' } return {
name,
file_path: it,
artist: '',
album: '',
duration: '00:00'
}
}) })
ev.returnValue = list ev.returnValue = list
} else { } else {

View File

@ -15,7 +15,13 @@ module.exports = function(win) {
submenu: [ submenu: [
{ role: 'about', label: '关于 Sonist' }, { role: 'about', label: '关于 Sonist' },
{ type: 'separator' }, { type: 'separator' },
{ role: 'quit', label: '退出' } {
label: '退出 Sonist',
accelerator: 'Command+Q',
click(a, b, ev) {
win.destroy()
}
}
] ]
}, },
{ {

View File

@ -16,6 +16,7 @@ exports.createMainWindow = function(icon) {
width: 820, width: 820,
height: 460, height: 460,
frame: false, frame: false,
titleBarStyle: 'hiddenInset',
resizable: false, resizable: false,
maximizable: false, maximizable: false,
icon, icon,
@ -40,6 +41,11 @@ exports.createMainWindow = function(icon) {
win.openDevTools() win.openDevTools()
}) })
win.on('close', ev => {
ev.preventDefault()
win.hide()
})
return win return win
} }