diff --git a/build-config/renderer/webpack.config.dev.js b/build-config/renderer/webpack.config.dev.js
index 2bce67f3..5fd16fd7 100644
--- a/build-config/renderer/webpack.config.dev.js
+++ b/build-config/renderer/webpack.config.dev.js
@@ -10,7 +10,7 @@ const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
- devtool: '#cheap-module-eval-source-map',
+ devtool: 'eval-source-map',
module: {
rules: [
{
@@ -46,7 +46,7 @@ module.exports = merge(baseConfig, {
NODE_ENV: '"development"',
ELECTRON_DISABLE_SECURITY_WARNINGS: 'true',
},
- '__static': `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
+ __static: `"${path.join(__dirname, '../../src/static').replace(/\\/g, '\\\\')}"`,
}),
],
performance: {
diff --git a/build-config/web/webpack.config.dev.js b/build-config/web/webpack.config.dev.js
index 3aa6420e..3f0d180d 100644
--- a/build-config/web/webpack.config.dev.js
+++ b/build-config/web/webpack.config.dev.js
@@ -9,7 +9,7 @@ const { mergeCSSLoaderDev } = require('../utils')
module.exports = merge(baseConfig, {
mode: 'development',
- devtool: '#cheap-module-eval-source-map',
+ devtool: 'eval-source-map',
output: {
filename: '[name].js',
path: path.join(__dirname, '../../dist/web'),
diff --git a/package-lock.json b/package-lock.json
index 189b0d23..dc796222 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "lx-music-desktop",
- "version": "0.1.0",
+ "version": "0.1.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/publish/changeLog.md b/publish/changeLog.md
index e69de29b..da95ebf8 100644
--- a/publish/changeLog.md
+++ b/publish/changeLog.md
@@ -0,0 +1,10 @@
+### 新增
+
+- 新增搜索列表批量试听与下载功能
+- 新增排行榜列表批量试听与下载功能
+- 新增试听列表批量移除与下载功能
+- 新增下载列表批量开始、暂停与移除功能
+
+### 优化
+
+- 优化歌曲切换机制
diff --git a/publish/index.js b/publish/index.js
index aa2e9e65..15da4437 100644
--- a/publish/index.js
+++ b/publish/index.js
@@ -16,7 +16,7 @@ const run = async() => {
try {
console.log(chalk.blue('Clearing assets...'))
await clearAssets()
- console.log(chalk.green('Assets clear complated...'))
+ console.log(chalk.green('Assets clear completed...'))
// console.log(chalk.blue('Compileing assets...'))
// await compileAssets()
diff --git a/src/renderer/App.vue b/src/renderer/App.vue
index 66d50448..79f4566b 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/App.vue
@@ -38,6 +38,7 @@ export default {
...mapGetters('list', ['defaultList']),
...mapGetters('download', {
downloadList: 'list',
+ downloadStatus: 'downloadStatus',
}),
},
mounted() {
@@ -119,6 +120,12 @@ export default {
initDownloadList() {
let downloadList = this.electronStore.get('download.list')
if (downloadList) {
+ downloadList.forEach(item => {
+ if (item.status == this.downloadStatus.RUN || item.status == this.downloadStatus.WAITING) {
+ item.status = this.downloadStatus.PAUSE
+ item.statusText = '暂停下载'
+ }
+ })
this.updateDownloadList(downloadList)
}
},
diff --git a/src/renderer/components/core/Player.vue b/src/renderer/components/core/Player.vue
index f085e9bc..57b4778c 100644
--- a/src/renderer/components/core/Player.vue
+++ b/src/renderer/components/core/Player.vue
@@ -252,36 +252,43 @@ export default {
},
handleNext() {
// if (this.list.listName === null) return
- const list = this.isAPITemp ? this.list.filter(s => s.source == 'kw') : this.list
- if (!list.length) return
+ let list
+ if (this.listId == 'download') {
+ list = this.list.filter(s => !(!checkPath(s.filePath) || !s.isComplate || /\.ape$/.test(s.filePath)))
+ } else if (this.isAPITemp) {
+ list = this.list.filter(s => s.source == 'kw')
+ } else {
+ list = this.list
+ }
+ if (!list.length) return this.setPlayIndex(-1)
+ let playIndex = this.list === list ? this.playIndex : list.indexOf(this.list[this.playIndex])
+
let index
switch (this.setting.player.togglePlayMethod) {
case 'listLoop':
- index = this.hanldeListLoop()
+ index = this.hanldeListLoop(list, playIndex)
break
case 'random':
- index = this.hanldeListRandom()
+ index = this.hanldeListRandom(list, playIndex)
break
case 'list':
- index = this.hanldeListNext()
+ index = this.hanldeListNext(list, playIndex)
break
default:
return
}
if (index < 0) return
+ if (this.list !== list) index = this.list.indexOf(list[index])
this.setPlayIndex(index)
},
- hanldeListLoop(index = this.playIndex) {
- index = index === this.list.length - 1 ? 0 : index + 1
- return this.isAPITemp && this.list[index].source != 'kw' ? this.hanldeListLoop(index) : index
+ hanldeListLoop(list, index) {
+ return index === list.length - 1 ? 0 : index + 1
},
- hanldeListNext(index = this.playIndex) {
- index = index === this.list.length - 1 ? -1 : index + 1
- return this.isAPITemp && this.list[index].source != 'kw' ? this.hanldeListNext(index) : index
+ hanldeListNext(list, index) {
+ return index === list.length - 1 ? -1 : index + 1
},
- hanldeListRandom(index = this.playIndex) {
- index = getRandom(0, this.list.length)
- return this.isAPITemp && this.list[index].source != 'kw' ? this.hanldeListRandom(index) : index
+ hanldeListRandom(list, index) {
+ return getRandom(0, list.length)
},
startPlay() {
this.isPlay = true
@@ -333,6 +340,9 @@ export default {
this.audio.src = this.musicInfo.url
}).catch(err => {
this.status = err.message
+ setTimeout(() => {
+ this.handleNext()
+ }, 2000)
})
},
setImg(targetSong) {
diff --git a/src/renderer/components/material/Checkbox.vue b/src/renderer/components/material/Checkbox.vue
index e7fb58db..abba8097 100644
--- a/src/renderer/components/material/Checkbox.vue
+++ b/src/renderer/components/material/Checkbox.vue
@@ -10,7 +10,7 @@ div(:class="$style.checkbox")
div(v-else)
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
use(xlink:href='#icon-check-true')
- span(v-if="label != null") {{label}}
+ span(v-if="label != null" v-html="label")
+
+
+
diff --git a/src/renderer/components/material/FlowBtn.vue b/src/renderer/components/material/FlowBtn.vue
new file mode 100644
index 00000000..ccf8deca
--- /dev/null
+++ b/src/renderer/components/material/FlowBtn.vue
@@ -0,0 +1,122 @@
+
+transition(enter-active-class="animated-fast zoomIn" leave-active-class="animated zoomOut")
+ div(:class="$style.btns" v-show="show")
+ button(type="button" v-if="playBtn" title="播放" @click.stop="handleClick('play')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 287.386 287.386' space='preserve')
+ use(xlink:href='#icon-testPlay')
+ button(type="button" v-if="addBtn" title="添加" @click.stop="handleClick('add')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
+ use(xlink:href='#icon-addTo')
+ button(type="button" v-if="downloadBtn" title="下载" @click.stop="handleClick('download')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 475.078 475.077' space='preserve')
+ use(xlink:href='#icon-download')
+ button(type="button" v-if="startBtn" title="开始" @click.stop="handleClick('start')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
+ use(xlink:href='#icon-play')
+ button(type="button" v-if="pauseBtn" title="暂停" @click.stop="handleClick('pause')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
+ use(xlink:href='#icon-pause')
+ button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
+ use(xlink:href='#icon-delete')
+
+
+
+
+
+
+
diff --git a/src/renderer/components/material/ListButtons.vue b/src/renderer/components/material/ListButtons.vue
index 6a5eea0d..9126fcf6 100644
--- a/src/renderer/components/material/ListButtons.vue
+++ b/src/renderer/components/material/ListButtons.vue
@@ -9,6 +9,12 @@ div(:class="$style.btns")
button(type="button" title="添加" v-if="userInfo" @click.stop="handleClick('add')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
+ button(type="button" v-if="startBtn" title="开始" @click.stop="handleClick('start')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 170 170' space='preserve')
+ use(xlink:href='#icon-play')
+ button(type="button" v-if="pauseBtn" title="暂停" @click.stop="handleClick('pause')")
+ svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 277.338 277.338' space='preserve')
+ use(xlink:href='#icon-pause')
button(type="button" v-if="removeBtn" title="移除" @click.stop="handleClick('remove')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 212.982 212.982' space='preserve')
use(xlink:href='#icon-delete')
@@ -27,6 +33,14 @@ export default {
type: Number,
required: true,
},
+ startBtn: {
+ type: Boolean,
+ default: false,
+ },
+ pauseBtn: {
+ type: Boolean,
+ default: false,
+ },
removeBtn: {
type: Boolean,
default: true,
diff --git a/src/renderer/store/modules/download.js b/src/renderer/store/modules/download.js
index c1b45efa..b20070d5 100644
--- a/src/renderer/store/modules/download.js
+++ b/src/renderer/store/modules/download.js
@@ -2,18 +2,30 @@ import download from '../../utils/download'
import fs from 'fs'
import path from 'path'
import music from '../../utils/music'
+import { getMusicType } from '../../utils/music/utils'
// state
const state = {
list: [],
+ waitingList: [],
+ downloadStatus: {
+ RUN: 'run',
+ WAITING: 'waiting',
+ PAUSE: 'pause',
+ ERROR: 'error',
+ COMPLETED: 'completed',
+ },
}
+
const dls = {}
+const tryNum = {}
// getters
const getters = {
list: state => state.list || [],
dls: () => dls || {},
+ downloadStatus: state => state.downloadStatus,
}
const checkPath = path => {
@@ -40,19 +52,37 @@ const getExt = type => {
const checkList = (list, musicInfo, type) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && s.type === type)
+const getStartTask = (list, downloadStatus, maxDownloadNum) => {
+ let downloadCount = 0
+ const waitList = list.filter(item => item.status == downloadStatus.WAITING ? true : (item.status === downloadStatus.RUN && ++downloadCount && false))
+ console.log(downloadCount, waitList)
+ return downloadCount < maxDownloadNum && waitList.length > 0 && waitList.shift()
+}
+
+const addTask = (list, type, store) => {
+ window.requestAnimationFrame(() => {
+ let item = list.shift()
+ store.dispatch('download/createDownload', {
+ musicInfo: item,
+ type: getMusicType(item, type),
+ })
+ if (list.length) addTask(list, type, store)
+ })
+}
+
const refreshUrl = downloadInfo => {
return music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
}
// actions
const actions = {
- createDownload({ state, rootState }, { musicInfo, type }) {
+ createDownload({ state, rootState, commit }, { musicInfo, type }) {
if (checkList(state.list, musicInfo, type)) return
let ext = getExt(type)
const downloadInfo = {
isComplate: false,
- isDownloading: false,
- statusText: '任务初始化中',
+ status: state.downloadStatus.WAITING,
+ statusText: '待下载',
url: null,
fileName: `${rootState.setting.download.fileName
.replace('歌名', musicInfo.name)
@@ -68,19 +98,33 @@ const actions = {
key: `${musicInfo.songmid}${ext}`,
}
downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
+ commit('addTask', downloadInfo)
if (dls[downloadInfo.key]) {
dls[downloadInfo.key].stop().finally(() => {
- this.dispatch('download/addTask', downloadInfo)
+ delete dls[downloadInfo.key]
+ this.dispatch('download/startTask', downloadInfo)
})
} else {
// console.log(downloadInfo)
- this.dispatch('download/addTask', downloadInfo)
+ this.dispatch('download/startTask', downloadInfo)
}
},
- addTask({ commit, rootState }, downloadInfo) {
- commit('addTask', downloadInfo)
+ createDownloadMultiple({ state, rootState }, { list, type }) {
+ addTask([...list], type, this)
+ },
+ startTask({ commit, state, rootState }, downloadInfo) {
+ // 检查是否可以开始任务
+ if (downloadInfo && downloadInfo != state.downloadStatus.WAITING) commit('setStatus', { downloadInfo, status: state.downloadStatus.WAITING })
+ let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
+ if (!result) return downloadInfo && commit('setStatus', { downloadInfo, status: state.downloadStatus.WAITING })
+ if (!downloadInfo) downloadInfo = result
+
+ // 开始任务
+ commit('setStatusText', { downloadInfo, text: '任务初始化中' })
+ commit('onDownload', downloadInfo)
let msg = checkPath(rootState.setting.download.savePath)
if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
+ const _this = this
const options = {
url: downloadInfo.url,
path: rootState.setting.download.savePath,
@@ -89,37 +133,46 @@ const actions = {
override: true,
onEnd() {
commit('onEnd', downloadInfo)
+ _this.dispatch('download/startTask')
console.log('on complate')
},
onError(err) {
- console.log(err)
- if (err.message.includes('Response status was')) {
- const code = err.message.replace(/Response status was (\d+)$/, '$1')
- switch (code) {
- case '401':
- case '403':
- case '410':
- commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
- refreshUrl(downloadInfo).then(result => {
- commit('updateUrl', { downloadInfo, url: result.url })
- commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
- dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
- dls[downloadInfo.key].__initProtocol(result.url)
- dls[downloadInfo.key].resume()
- }).catch(err => {
- console.log(err)
- })
- return
-
- default:
- break
- }
- }
+ console.log(err.message)
commit('onError', downloadInfo)
+ // console.log(tryNum[downloadInfo.key])
+ if (++tryNum[downloadInfo.key] > 5) return
+ let code
+ if (err.message.includes('Response status was')) {
+ code = err.message.replace(/Response status was (\d+)$/, '$1')
+ } if (err.code === 'ETIMEDOUT') {
+ code = 'ETIMEDOUT'
+ } else {
+ console.log('Download failed, Attempting Retry')
+ dls[downloadInfo.key].resume()
+ commit('setStatusText', { downloadInfo, text: '正在重试' })
+ return
+ }
+ switch (code) {
+ case '401':
+ case '403':
+ case '410':
+ case 'ETIMEDOUT':
+ commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
+ refreshUrl(downloadInfo).then(result => {
+ commit('updateUrl', { downloadInfo, url: result.url })
+ commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
+ dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
+ dls[downloadInfo.key].__initProtocol(result.url)
+ dls[downloadInfo.key].resume()
+ }).catch(err => {
+ console.log(err)
+ _this.dispatch('download/startTask')
+ })
+ }
},
- onStateChanged(state) {
- console.log(state)
- },
+ // onStateChanged(state) {
+ // console.log(state)
+ // },
onDownload() {
commit('onDownload', downloadInfo)
console.log('on download')
@@ -130,6 +183,7 @@ const actions = {
},
onPause() {
commit('pauseTask', downloadInfo)
+ _this.dispatch('download/startTask')
},
onResume() {
commit('resumeTask', downloadInfo)
@@ -138,80 +192,52 @@ const actions = {
let p = options.url ? Promise.resolve() : refreshUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
options.url = result.url
+ }).catch(err => {
+ commit('onError', downloadInfo)
+ commit('setStatusText', { downloadInfo, text: err.message })
+ return Promise.reject(err)
})
p.then(() => {
+ tryNum[downloadInfo.key] = 0
dls[downloadInfo.key] = download(options)
})
+ },
+ startTaskMultiple({ state, rootState }, list) {
+
},
removeTask({ commit, state }, index) {
let info = state.list[index]
- if (state.list[index].isDownloading) {
- dls[info.key].stop().finally(() => {
- delete dls[info.key]
- })
+ if (state.list[index].status == state.downloadStatus.RUN) {
+ if (dls[info.key]) {
+ dls[info.key].stop().finally(() => {
+ delete dls[info.key]
+ })
+ }
}
commit('removeTask', index)
if (dls[info.key]) delete dls[info.key]
+ this.dispatch('download/startTask')
},
- resumeTask({ commit, rootState }, downloadInfo) {
- let msg = checkPath(rootState.setting.download.savePath)
- if (msg) return commit('setStatusText', '检查下载目录出错: ' + msg)
- const options = {
- url: downloadInfo.url,
- path: rootState.setting.download.savePath,
- fileName: downloadInfo.fileName,
- method: 'get',
- override: true,
- onEnd() {
- commit('onEnd', downloadInfo)
- console.log('on complate')
- },
- onError(err) {
- commit('onError', downloadInfo)
- commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
- refreshUrl(downloadInfo).then(result => {
- commit('updateUrl', { downloadInfo, url: result.url })
- commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
- dls[downloadInfo.key].url = dls[downloadInfo.key].requestURL = result.url
- dls[downloadInfo.key].__initProtocol(result.url)
- dls[downloadInfo.key].resume()
- }).catch(err => {
- console.log(err)
- })
- console.log(err)
- },
- onStateChanged(state) {
- console.log(state)
- },
- onDownload() {
- commit('onDownload', downloadInfo)
- console.log('on download')
- },
- onProgress(status) {
- commit('onProgress', { downloadInfo, status })
- console.log(status)
- },
- onPause() {
- commit('pauseTask', downloadInfo)
- },
- onResume() {
- commit('resumeTask', downloadInfo)
- },
- }
-
- let p = options.url ? Promise.resolve() : refreshUrl(downloadInfo).then(result => {
- commit('updateUrl', { downloadInfo, url: result.url })
- options.url = result.url
- })
-
- if (fs.existsSync(downloadInfo.filePath)) {
- options.resumeInfo = {
- totalFileSize: downloadInfo.progress.total,
+ removeTaskMultiple({ commit, rootState, state }, list) {
+ list.forEach(item => {
+ let index = state.list.indexOf(item)
+ if (index < 0) return
+ // this.dispatch('download/removeTask', index)
+ if (state.list[index].status == state.downloadStatus.RUN) {
+ if (dls[item.key]) {
+ dls[item.key].stop().finally(() => {
+ delete dls[item.key]
+ })
+ }
}
- }
- p.then(() => {
- dls[downloadInfo.key] = download(options)
+ commit('removeTask', index)
+ if (dls[item.key]) delete dls[item.key]
})
+ let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
+ while (result) {
+ this.dispatch('download/startTask', result)
+ result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
+ }
},
}
@@ -224,29 +250,57 @@ const mutations = {
state.list.splice(index, 1)
},
pauseTask(state, downloadInfo) {
- downloadInfo.isDownloading = false
+ downloadInfo.status = state.downloadStatus.PAUSE
+ downloadInfo.statusText = '暂停下载'
},
resumeTask(state, downloadInfo) {
- downloadInfo.statusText = '恢复下载'
+ downloadInfo.statusText = '开始下载'
},
- setStatusText(state, { downloadInfo, index, text }) {
+ setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
if (downloadInfo) {
downloadInfo.statusText = text
} else {
state.list[index].statusText = text
}
},
+ setStatus(state, { downloadInfo, index, status }) { // 设置状态及状态文本
+ let text
+ switch (status) {
+ case state.downloadStatus.RUN:
+ text = '正在下载'
+ break
+ case state.downloadStatus.WAITING:
+ text = '等待下载'
+ break
+ case state.downloadStatus.PAUSE:
+ text = '暂停下载'
+ break
+ case state.downloadStatus.ERROR:
+ text = '任务出错'
+ break
+ case state.downloadStatus.COMPLETED:
+ text = '下载完成'
+ break
+ }
+ if (downloadInfo) {
+ downloadInfo.statusText = text
+ downloadInfo.status = status
+ } else {
+ state.list[index].statusText = text
+ state.list[index].status = status
+ }
+ },
onEnd(state, downloadInfo) {
downloadInfo.isComplate = true
- downloadInfo.isDownloading = false
+ downloadInfo.status = state.downloadStatus.COMPLETED
downloadInfo.statusText = '下载完成'
},
onError(state, downloadInfo) {
- downloadInfo.isDownloading = false
+ downloadInfo.status = state.downloadStatus.ERROR
downloadInfo.statusText = '任务出错'
},
onDownload(state, downloadInfo) {
- downloadInfo.isDownloading = true
+ downloadInfo.status = state.downloadStatus.RUN
downloadInfo.statusText = '正在下载'
},
onProgress(state, { downloadInfo, status }) {
diff --git a/src/renderer/store/modules/list.js b/src/renderer/store/modules/list.js
index bf261251..1ec74d02 100644
--- a/src/renderer/store/modules/list.js
+++ b/src/renderer/store/modules/list.js
@@ -30,9 +30,22 @@ const mutations = {
if (state.defaultList.list.some(s => s.songmid === musicInfo.songmid)) return
state.defaultList.list.push(musicInfo)
},
+ defaultListAddMultiple(state, list) {
+ list.forEach(musicInfo => {
+ if (state.defaultList.list.some(s => s.songmid === musicInfo.songmid)) return
+ state.defaultList.list.push(musicInfo)
+ })
+ },
defaultListRemove(state, index) {
state.defaultList.list.splice(index, 1)
},
+ defaultListRemoveMultiple(state, list) {
+ list.forEach(musicInfo => {
+ let index = state.defaultList.list.indexOf(musicInfo)
+ if (index < 0) return
+ state.defaultList.list.splice(index, 1)
+ })
+ },
defaultListClear(state) {
state.defaultList.list.length = 0
},
diff --git a/src/renderer/utils/download/index.js b/src/renderer/utils/download/index.js
index eb77c2c1..40dd7b42 100644
--- a/src/renderer/utils/download/index.js
+++ b/src/renderer/utils/download/index.js
@@ -45,9 +45,8 @@ export default ({
onEnd()
debugDownload && console.log('Download Completed')
}).on('error', err => {
+ if (err.message === 'socket hang up') return
onError(err)
- dl.resume()
- console.log('Download failed, Attempting Retry')
debugDownload && console.error('Something happend', err)
}).on('stateChanged', state => {
onStateChanged(state)
diff --git a/src/renderer/utils/index.js b/src/renderer/utils/index.js
index 25699eb7..0cb34a3c 100644
--- a/src/renderer/utils/index.js
+++ b/src/renderer/utils/index.js
@@ -156,8 +156,12 @@ export const isChildren = (parent, children) => {
return children.parentNode ? children.parentNode === parent ? true : isChildren(parent, children.parentNode) : false
}
+/**
+ * 升级设置
+ * @param {*} setting
+ */
export const updateSetting = setting => {
- const defaultVersion = '1.0.2'
+ const defaultVersion = '1.0.3'
const defaultSetting = {
version: defaultVersion,
player: {
@@ -170,6 +174,7 @@ export const updateSetting = setting => {
download: {
savePath: path.join(os.homedir(), 'Desktop'),
fileName: '歌名 - 歌手',
+ maxDownloadNum: 3,
},
leaderboard: {
source: 'kw',
@@ -203,3 +208,4 @@ export const updateSetting = setting => {
export const openUrl = url => {
shell.openExternal(url)
}
+
diff --git a/src/renderer/utils/message.js b/src/renderer/utils/message.js
new file mode 100644
index 00000000..2977f075
--- /dev/null
+++ b/src/renderer/utils/message.js
@@ -0,0 +1,6 @@
+export const requestMsg = {
+ fail: '请求异常😮,可以多试几次,若还是不行就换一首吧。。。',
+ unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
+ notConnectNetwork: '无法连接网络',
+ cancelRequest: '取消http请求',
+}
diff --git a/src/renderer/utils/music/kw/api-messoer.js b/src/renderer/utils/music/kw/api-messoer.js
index 9a4749e4..599c14ab 100644
--- a/src/renderer/utils/music/kw/api-messoer.js
+++ b/src/renderer/utils/music/kw/api-messoer.js
@@ -1,4 +1,5 @@
import { httpFatch } from '../../request'
+import { requestMsg } from '../../message'
const api_messoer = {
getMusicUrl(songInfo, type) {
@@ -7,7 +8,7 @@ const api_messoer = {
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
- return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
+ return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
@@ -17,7 +18,7 @@ const api_messoer = {
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
- return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(body.msg))
+ return body.code === 200 ? Promise.resolve(body.data) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
diff --git a/src/renderer/utils/music/kw/api-temp.js b/src/renderer/utils/music/kw/api-temp.js
index d7091ae0..0c8436f2 100644
--- a/src/renderer/utils/music/kw/api-temp.js
+++ b/src/renderer/utils/music/kw/api-temp.js
@@ -7,11 +7,6 @@ const api_temp = {
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
- }).catch(err => {
- console.log(err)
- if (err.message === 'socket hang up') return Promise.reject(new Error('接口挂了'))
- if (err.code === 'ENOTFOUND') return Promise.reject(new Error('无法连接网络'))
- return Promise.reject(err)
})
return requestObj
},
@@ -21,10 +16,6 @@ const api_temp = {
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve(body.data) : Promise.reject(new Error(body.msg))
- }).catch(err => {
- if (err.message === 'socket hang up') return Promise.reject(new Error('接口挂了'))
- if (err.code === 'ENOTFOUND') return Promise.reject(new Error('无法连接网络'))
- return Promise.reject(err)
})
return requestObj
},
diff --git a/src/renderer/utils/music/tx/api-messoer.js b/src/renderer/utils/music/tx/api-messoer.js
index 0b00f629..49ac2f04 100644
--- a/src/renderer/utils/music/tx/api-messoer.js
+++ b/src/renderer/utils/music/tx/api-messoer.js
@@ -1,4 +1,5 @@
import { httpFatch } from '../../request'
+import { requestMsg } from '../../message'
const api_messoer = {
getMusicUrl(songInfo, type) {
@@ -7,7 +8,7 @@ const api_messoer = {
timeout: 5000,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
- return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(body.msg))
+ return body.code === 200 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
diff --git a/src/renderer/utils/music/utils.js b/src/renderer/utils/music/utils.js
new file mode 100644
index 00000000..14f0e1c6
--- /dev/null
+++ b/src/renderer/utils/music/utils.js
@@ -0,0 +1,12 @@
+/**
+ * 获取音乐音质
+ * @param {*} info
+ * @param {*} type
+ */
+const types = ['flac', 'ape', '320k', '192k', '128k']
+export const getMusicType = (info, type) => {
+ const rangeType = types.slice(types.indexOf(type))
+ for (const type of rangeType) {
+ if (info._types[type]) return type
+ }
+}
diff --git a/src/renderer/utils/request.js b/src/renderer/utils/request.js
index 40214d00..f96b6383 100644
--- a/src/renderer/utils/request.js
+++ b/src/renderer/utils/request.js
@@ -1,6 +1,7 @@
import request from 'request'
// import progress from 'request-progress'
import { debugRequest } from './env'
+import { requestMsg } from './message'
// import fs from 'fs'
const headers = {
@@ -9,7 +10,7 @@ const headers = {
const fatchData = (url, method, options, callback) => {
// console.log(url, options)
- // console.log('---start---', url)
+ console.log('---start---', url)
return request(url, {
method,
headers: Object.assign({}, headers, options.headers || {}),
@@ -60,7 +61,7 @@ const buildHttpPromose = (url, options) => {
console.log('cancel')
if (!requestObj) return
cancelHttp(requestObj)
- cancelFn(new Error('取消http请求'))
+ cancelFn(new Error(requestMsg.cancelRequest))
requestObj = null
cancelFn = null
},
@@ -84,9 +85,9 @@ export const httpFatch = (url, options = { method: 'get' }) => {
}
if (err.message === 'socket hang up') {
window.globalObj.apiSource = 'temp'
- return Promise.reject(new Error('哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~'))
+ return Promise.reject(new Error(requestMsg.unachievable))
}
- if (err.code === 'ENOTFOUND') return Promise.reject(new Error('无法连接网络'))
+ if (err.code === 'ENOTFOUND') return Promise.reject(new Error(requestMsg.notConnectNetwork))
return Promise.reject(err)
})
return requestObj
diff --git a/src/renderer/views/Download.vue b/src/renderer/views/Download.vue
index f52bd6bd..0266b9cb 100644
--- a/src/renderer/views/Download.vue
+++ b/src/renderer/views/Download.vue
@@ -6,8 +6,11 @@ div(:class="$style.download")
table
thead
tr
- th.nobreak(style="width: 30%;") 歌曲名
- th.nobreak(style="width: 25%;") 进度
+ th.nobreak.center(style="width: 37px;")
+ material-checkbox(id="search_select_all" v-model="isSelectAll" @change="handleSelectAllData"
+ :indeterminate="isIndeterminate" :title="isSelectAll && !isIndeterminate ? '全不选' : '全选'")
+ th.nobreak(style="width: 28%;") 歌曲名
+ th.nobreak(style="width: 22%;") 进度
th.nobreak(style="width: 15%;") 状态
th.nobreak(style="width: 10%;") 品质
th.nobreak(style="width: 20%;") 操作
@@ -15,12 +18,17 @@ div(:class="$style.download")
table
tbody
tr(v-for='(item, index) in list' :key='item.key' @click="handleDoubleClick(index)" :class="isPlayList && playIndex === index ? $style.active : ''")
- td.break(style="width: 30%;") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
- td.break(style="width: 25%;") {{item.progress.progress}}%
+ td.nobreak.center(style="width: 37px;" @click.stop)
+ material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
+ td.break(style="width: 28%;") {{item.musicInfo.name}} - {{item.musicInfo.singer}}
+ td.break(style="width: 22%;") {{item.progress.progress}}%
td.break(style="width: 15%;") {{item.statusText}}
td.break(style="width: 10%;") {{item.type.toUpperCase()}}
td(style="width: 20%; padding-left: 0; padding-right: 0;")
- material-list-buttons(:index="index" :downloadBtn="false" @btn-click="handleBtnClick")
+ material-list-buttons(:index="index" :download-btn="false" :start-btn="!item.isComplate && item.status != downloadStatus.WAITING && (item.status != downloadStatus.RUN)"
+ :pause-btn="!item.isComplate && (item.status == downloadStatus.RUN || item.status == downloadStatus.WAITING)"
+ :play-btn="item.status == downloadStatus.COMPLETED" @btn-click="handleListBtnClick")
+ material-flow-btn(:show="isShowEditBtn" :play-btn="false" :download-btn="false" :add-btn="false" :start-btn="true" :pause-btn="true" @btn-click="handleFlowBtnClick")
div(:class="$style.noItem" v-else)
@@ -34,26 +42,51 @@ export default {
return {
clickTime: window.performance.now(),
clickIndex: -1,
+ selectdData: [],
+ isSelectAll: false,
+ isIndeterminate: false,
+ isShowEditBtn: false,
+ isShowDownloadMultiple: false,
}
},
computed: {
- ...mapGetters('download', ['list', 'dls']),
+ ...mapGetters('download', ['list', 'dls', 'downloadStatus']),
...mapGetters('player', ['listId', 'playIndex']),
isPlayList() {
return this.listId == 'download'
},
},
- methods: {
- ...mapActions('download', ['removeTask', 'resumeTask']),
- ...mapMutations('player', ['setList']),
- pauseTask(index) {
- let info = this.list[index]
- this.dls[info.key].pause()
+ watch: {
+ selectdData(n) {
+ const len = n.length
+ if (len) {
+ this.isSelectAll = true
+ this.isIndeterminate = len !== this.list.length
+ this.isShowEditBtn = true
+ } else {
+ this.isSelectAll = false
+ this.isShowEditBtn = false
+ }
},
- startTask(index) {
+ list() {
+ this.resetSelect()
+ },
+ },
+ methods: {
+ ...mapActions('download', ['removeTask', 'removeTaskMultiple', 'startTask']),
+ ...mapMutations('player', ['setList']),
+ ...mapMutations('download', ['pauseTask']),
+ handlePauseTask(index) {
let info = this.list[index]
let dl = this.dls[info.key]
- dl ? dl.resume() : this.resumeTask(info)
+ dl ? dl.pause() : this.pauseTask(info)
+ console.log('pause')
+ },
+ handleStartTask(index) {
+ console.log('start')
+ let info = this.list[index]
+ let dl = this.dls[info.key]
+ dl ? dl.resume() : this.startTask(info)
},
handleDoubleClick(index) {
if (
@@ -72,26 +105,71 @@ export default {
let info = this.list[index]
if (info.isComplate) {
this.handlePlay(index)
- } else if (info.isDownloading) {
- this.pauseTask(index)
+ } else if (info.status === this.downloadStatus.RUN) {
+ this.handlePauseTask(index)
} else {
- this.startTask(index)
+ this.handleStartTask(index)
}
},
handlePlay(index) {
if (!checkPath(this.list[index].filePath)) return
this.setList({ list: this.list, listId: 'download', index })
},
- handleBtnClick(info) {
+ handleListBtnClick(info) {
switch (info.action) {
case 'play':
this.handlePlay(info.index)
break
+ case 'start':
+ this.handleStartTask(info.index)
+ break
+ case 'pause':
+ this.handlePauseTask(info.index)
+ break
case 'remove':
this.removeTask(info.index)
break
}
},
+ handleSelectAllData(isSelect) {
+ this.selectdData = isSelect ? [...this.list] : []
+ },
+ resetSelect() {
+ this.isSelectAll = false
+ this.selectdData = []
+ },
+ handleFlowBtnClick(action) {
+ switch (action) {
+ case 'start':
+ this.selectdData.forEach(item => {
+ if (item.isComplate || item.status == this.downloadStatus.RUN) return
+ let index = this.list.indexOf(item)
+ if (index < 0) return
+ this.handleStartTask(index)
+ })
+ break
+ case 'pause':
+ let runs = []
+ this.selectdData.forEach(item => {
+ if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
+ if (item.status == this.downloadStatus.RUN) return runs.push(item)
+ let index = this.list.indexOf(item)
+ if (index < 0) return
+ this.handlePauseTask(index)
+ })
+ runs.forEach(item => {
+ if (item.isComplate || item.status == this.downloadStatus.PAUSE) return
+ let index = this.list.indexOf(item)
+ if (index < 0) return
+ this.handlePauseTask(index)
+ })
+ break
+ case 'remove':
+ this.removeTaskMultiple(this.selectdData)
+ break
+ }
+ this.resetSelect()
+ },
},
}
diff --git a/src/renderer/views/Leaderboard.vue b/src/renderer/views/Leaderboard.vue
index 91300978..6d0ebcd8 100644
--- a/src/renderer/views/Leaderboard.vue
+++ b/src/renderer/views/Leaderboard.vue
@@ -9,30 +9,37 @@
table
thead
tr
- th.nobreak(style="width: 27%;") 歌曲名
+ th.nobreak.center(style="width: 37px;")
+ material-checkbox(id="search_select_all" v-model="isSelectAll" @change="handleSelectAllData"
+ :indeterminate="isIndeterminate" :title="isSelectAll && !isIndeterminate ? '全不选' : '全选'")
+ th.nobreak(style="width: 25%;") 歌曲名
th.nobreak(style="width: 20%;") 歌手
- th.nobreak(style="width: 25%;") 专辑
+ th.nobreak(style="width: 22%;") 专辑
th.nobreak(style="width: 18%;") 操作
th.nobreak(style="width: 10%;") 时长
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody
tr(v-for='(item, index) in list' :key='item.songmid' @click="handleDoubleClick(index)")
- td.break(style="width: 27%;")
+ td.nobreak.center(style="width: 37px;" @click.stop)
+ material-checkbox(:id="index.toString()" v-model="selectdData" :value="item")
+ td.break(style="width: 25%;")
| {{item.name}}
//- span.badge.badge-info(v-if="item._types['320k']") 高品质
//- span.badge.badge-success(v-if="item._types.ape || item._types.flac") 无损
td.break(style="width: 20%;") {{item.singer}}
- td.break(style="width: 25%;") {{item.albumName}}
+ td.break(style="width: 22%;") {{item.albumName}}
td(style="width: 18%;")
- material-list-buttons(:index="index" :search-btn="true" :play-btn="item.source == 'kw' || (!isAPITemp && item.source == 'tx')" :download-btn="item.source == 'kw' || (!isAPITemp && item.source == 'tx')" :remove-btn="false" @btn-click="handleBtnClick")
+ material-list-buttons(:index="index" :search-btn="true" :play-btn="item.source == 'kw' || (!isAPITemp && item.source == 'tx')" :download-btn="item.source == 'kw' || (!isAPITemp && item.source == 'tx')" :remove-btn="false" @btn-click="handleListBtnClick")
//- button.btn-info(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k'] || item._types.flac" @click.stop='openDownloadModal(index)') 下载
//- button.btn-secondary(type='button' v-if="item._types['128k'] || item._types['192k'] || item._types['320k']" @click.stop='testPlay(index)') 试听
//- button.btn-success(type='button' v-if="(item._types['128k'] || item._types['192k'] || item._types['320k']) && userInfo" @click.stop='showListModal(index)') +
td(style="width: 10%;") {{item.interval}}
div(:class="$style.pagination")
material-pagination(:count="info.total" :limit="info.limit" :page="info.page" @btn-click="handleTogglePage")
- material-download-modal(:show="showDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="showDownload = false")
+ material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
+ material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectdData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
+ material-flow-btn(:show="isShowEditBtn" :remove-btn="false" @btn-click="handleFlowBtnClick")
diff --git a/src/renderer/views/Setting.vue b/src/renderer/views/Setting.vue
index 6280c9fa..ac5fdd1b 100644
--- a/src/renderer/views/Setting.vue
+++ b/src/renderer/views/Setting.vue
@@ -142,7 +142,7 @@ export default {
apiSources: [
{
id: 'messoer',
- label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)',
+ label: '由 messoer 提供的接口(推荐,软件的所有功能都可用)
注意:本接口10秒内请求数超过100次会封10小时的IP',
},
{
id: 'temp',