Merge branch 'dev' into 黑色主题

This commit is contained in:
lyswhut
2021-04-14 08:35:00 +08:00
149 changed files with 9948 additions and 8547 deletions
+234 -48
View File
@@ -6,7 +6,6 @@
core-view#view
core-player#player
core-icons
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
material-version-modal(v-show="version.showModal")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
#container(v-else :class="theme")
@@ -16,7 +15,6 @@
core-view#view
core-player#player
core-icons
material-xm-verify-modal(v-show="globalObj.xm.isShowVerify" :show="globalObj.xm.isShowVerify" :bg-close="false" @close="handleXMVerifyModalClose")
material-version-modal(v-show="version.showModal")
material-pact-modal(v-show="!setting.isAgreePact || globalObj.isShowPact")
</template>
@@ -27,8 +25,9 @@ import { mapMutations, mapGetters, mapActions } from 'vuex'
import { rendererOn, rendererSend, rendererInvoke, NAMES } from '../common/ipc'
import { isLinux } from '../common/utils'
import music from './utils/music'
import { throttle, openUrl, compareVer, getPlayList } from './utils'
import { throttle, openUrl, compareVer, getPlayList, parseUrlParams, saveSetting } from './utils'
import { base as eventBaseName } from './event/names'
import apiSourceInfo from './utils/music/api-source-info'
window.ELECTRON_DISABLE_SECURITY_WARNINGS = process.env.ELECTRON_DISABLE_SECURITY_WARNINGS
dnscache({
@@ -37,6 +36,20 @@ dnscache({
cachesize: 1000,
})
const listThrottle = (fn, delay = 100) => {
let timer = null
let _data = {}
return function(data) {
Object.assign(_data, data)
if (timer) return
timer = setTimeout(() => {
timer = null
fn.call(this, _data)
_data = {}
}, delay)
}
}
export default {
data() {
return {
@@ -44,12 +57,15 @@ export default {
isDT: false,
isLinux,
globalObj: {
apiSource: 'test',
apiSource: null,
proxy: {},
isShowPact: false,
qualityList: {},
xm: {
isShowVerify: false,
userApi: {
list: [],
status: false,
message: 'initing',
apis: {},
},
},
updateTimeout: null,
@@ -70,24 +86,12 @@ export default {
}),
},
created() {
this.saveDefaultList = throttle(n => {
this.saveMyList = listThrottle(data => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'defaultList',
data: n,
type: 'myList',
data,
})
}, 500)
this.saveLoveList = throttle(n => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'loveList',
data: n,
})
}, 500)
this.saveUserList = throttle(n => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'userList',
data: n,
})
}, 500)
}, 300)
this.saveDownloadList = throttle(n => {
rendererSend(NAMES.mainWindow.save_playlist, {
type: 'downloadList',
@@ -109,25 +113,25 @@ export default {
watch: {
setting: {
handler(n, o) {
rendererSend(NAMES.mainWindow.set_app_setting, n)
saveSetting(n)
},
deep: true,
},
defaultList: {
handler(n) {
this.saveDefaultList(n)
this.saveMyList({ defaultList: n })
},
deep: true,
},
loveList: {
handler(n) {
this.saveLoveList(n)
this.saveMyList({ loveList: n })
},
deep: true,
},
userList: {
handler(n) {
this.saveUserList(n)
this.saveMyList({ userList: n })
},
deep: true,
},
@@ -140,14 +144,40 @@ export default {
searchHistoryList(n) {
this.saveSearchHistoryList(n)
},
'globalObj.apiSource'(n) {
this.globalObj.qualityList = music.supportQuality[n]
'globalObj.apiSource'(n, o) {
if (/^user_api/.test(n)) {
this.globalObj.qualityList = {}
this.globalObj.userApi.status = false
this.globalObj.userApi.message = 'initing'
} else {
this.globalObj.qualityList = music.supportQuality[n]
}
if (o === null) return
rendererInvoke(NAMES.mainWindow.set_user_api, n).catch(err => {
console.log(err)
let api = apiSourceInfo.find(api => !api.disabled)
if (api) this.globalObj.apiSource = api.id
})
if (n != this.setting.apiSource) {
this.setSetting(Object.assign({}, this.setting, {
apiSource: n,
}))
}
},
'globalObj.proxy.enable'(n, o) {
if (n != this.setting.network.proxy.enable) {
this.setSetting({
...this.setting,
network: {
...this.setting.network,
proxy: {
...this.setting.network.proxy,
enable: n,
},
},
})
}
},
'windowSizeActive.fontSize'(n) {
document.documentElement.style.fontSize = n
},
@@ -177,10 +207,12 @@ export default {
...mapMutations('player', {
setPlayList: 'setList',
}),
...mapActions('songList', ['getListDetailAll']),
init() {
document.documentElement.style.fontSize = this.windowSizeActive.fontSize
rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit)
const asyncTask = []
asyncTask.push(rendererInvoke(NAMES.mainWindow.get_env_params).then(this.handleEnvParamsInit))
document.body.addEventListener('click', this.handleBodyClick, true)
rendererOn(NAMES.mainWindow.update_available, (e, info) => {
@@ -228,7 +260,7 @@ export default {
})
rendererOn(NAMES.mainWindow.set_config, (event, config) => {
// console.log(config)
console.log(config)
// this.setDesktopLyricConfig(config)
// console.log('set_config', JSON.stringify(this.setting) === JSON.stringify(config))
this.setSetting(Object.assign({}, this.setting, config))
@@ -244,14 +276,23 @@ export default {
}, 60 * 30 * 1000)
this.listenEvent()
this.initData()
asyncTask.push(this.initData())
asyncTask.push(this.initUserApi())
this.globalObj.apiSource = this.setting.apiSource
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
if (/^user_api/.test(this.setting.apiSource)) {
rendererInvoke(NAMES.mainWindow.set_user_api, this.setting.apiSource)
} else {
this.globalObj.qualityList = music.supportQuality[this.setting.apiSource]
}
this.globalObj.proxy = Object.assign({}, this.setting.network.proxy)
window.globalObj = this.globalObj
// 初始化音乐sdk
music.init()
asyncTask.push(music.init())
Promise.all(asyncTask).then(() => {
this.handleInitEnvParamSearch()
this.handleInitEnvParamPlay()
})
},
enableIgnoreMouseEvents() {
if (this.isDT) return
@@ -265,12 +306,14 @@ export default {
},
initData() { // 初始化数据
this.initLocalList() // 初始化播放列表
return Promise.all([
this.initMyList(), // 初始化播放列表
this.initSearchHistoryList(), // 初始化搜索历史列表
])
// this.initDownloadList() // 初始化下载列表
this.initSearchHistoryList() // 初始化搜索历史列表
},
initLocalList() {
getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
initMyList() {
return getPlayList().then(({ defaultList, loveList, userList, downloadList }) => {
if (!defaultList) defaultList = this.defaultList
if (!loveList) loveList = this.loveList
if (userList) {
@@ -284,7 +327,7 @@ export default {
l.sourceListId = id
if (!needSave) needSave = true
})
if (needSave) this.saveUserList(userList)
if (needSave) this.saveMyList({ userList })
} else {
userList = this.userList
}
@@ -326,7 +369,7 @@ export default {
if (info.listId) {
const list = window.allList[info.listId]
// console.log(list)
if (!list) return
if (!list || !list.list[info.index]) return
info.list = list.list
}
@@ -340,6 +383,72 @@ export default {
})
})
},
initUserApi() {
return Promise.all([
rendererOn(NAMES.mainWindow.user_api_status, (event, { status, message, apiInfo }) => {
// console.log(apiInfo)
this.globalObj.userApi.status = status
this.globalObj.userApi.message = message
if (status) {
if (apiInfo.id === this.setting.apiSource) {
let apis = {}
let qualitys = {}
for (const [source, { actions, type, qualitys: sourceQualitys }] of Object.entries(apiInfo.sources)) {
if (type != 'music') continue
apis[source] = {}
for (const action of actions) {
switch (action) {
case 'musicUrl':
apis[source].getMusicUrl = (songInfo, type) => {
const requestKey = `request__${Math.random().toString().substring(2)}`
return {
canceleFn() {
rendererInvoke(NAMES.mainWindow.request_user_api_cancel, requestKey)
},
promise: rendererInvoke(NAMES.mainWindow.request_user_api, {
requestKey,
data: {
source: source,
action: 'musicUrl',
info: {
type,
musicInfo: songInfo,
},
},
}).then(res => {
// console.log(res)
if (!/^https?:/.test(res.data.url)) return Promise.reject(new Error('Get url failed'))
return { type, url: res.data.url }
}).catch(err => {
console.log(err.message)
return Promise.reject(err)
}),
}
}
break
default:
break
}
}
qualitys[source] = sourceQualitys
}
this.globalObj.qualityList = qualitys
this.globalObj.userApi.apis = apis
}
}
}),
rendererInvoke(NAMES.mainWindow.get_user_api_list).then(res => {
// console.log(res)
if (![...apiSourceInfo.map(s => s.id), ...res.map(s => s.id)].includes(this.setting.apiSource)) {
console.warn('reset api')
let api = apiSourceInfo.find(api => !api.disabled)
if (api) this.globalObj.apiSource = api.id
}
this.globalObj.userApi.list = res
}),
])
},
showUpdateModal() {
(this.version.newVersion && this.version.newVersion.history
? Promise.resolve(this.version.newVersion)
@@ -389,18 +498,91 @@ export default {
document.body.addEventListener('mouseenter', this.dieableIgnoreMouseEvents)
document.body.addEventListener('mouseleave', this.enableIgnoreMouseEvents)
}
this.handleInitEnvParamSearch()
this.handleInitEnvParamPlay()
},
// 处理启动参数 search
handleInitEnvParamSearch() {
if (this.envParams.search == null) return
this.$router.push({
path: 'search',
query: {
text: this.envParams.search,
},
})
},
// 处理启动参数 play
handleInitEnvParamPlay() {
if (this.envParams.play == null || typeof this.envParams.play != 'string') return
// -play="source=kw&link=链接、ID"
// -play="source=myList&name=名字"
// -play="source=myList&name=名字&index=位置"
const params = parseUrlParams(this.envParams.play)
if (params.type != 'songList') return
this.handlePlaySongList(params)
},
handlePlaySongList(params) {
switch (params.source) {
case 'myList':
if (params.name != null) {
let targetList
const lists = Object.values(window.allList)
for (const list of lists) {
if (list.name === params.name) {
targetList = list
break
}
}
if (!targetList) return
if (this.envParams.search != null) {
this.$router.push({
path: 'search',
query: {
text: this.envParams.search,
},
})
this.setPlayList({
list: {
list: targetList.list,
id: targetList.id,
},
index: this.getListPlayIndex(targetList.list, params.index),
})
}
break
case 'kw':
case 'kg':
case 'tx':
case 'mg':
case 'wy':
this.playSongListDetail(params.source, params.link, params.index)
break
}
},
handleXMVerifyModalClose() {
music.xm.closeVerifyModal()
async playSongListDetail(source, link, playIndex) {
if (link == null) return
let list
try {
list = await this.getListDetailAll({ source, id: decodeURIComponent(link) })
} catch (err) {
console.log(err)
}
this.setPlayList({
list: {
list,
id: null,
},
index: this.getListPlayIndex(list, playIndex),
})
},
getListPlayIndex(list, index) {
if (index == null) {
index = 1
} else {
index = parseInt(index)
if (Number.isNaN(index)) {
index = 1
} else {
if (index < 1) index = 1
else if (index > list.length) index = list.length
}
}
return index - 1
},
listenEvent() {
window.eventHub.$on('key_escape_down', this.handle_key_esc_down)
@@ -462,6 +644,10 @@ body {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
#view { // 偏移5px距离解决非透明模式下右侧滚动条无法拖动的问题
margin-right: 5Px;
}
}
#container {
+35
View File
@@ -871,6 +871,41 @@
transform: scale(1);
}
}
@keyframes hinge {
0% {
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
20%,
60% {
-webkit-transform: rotate3d(0, 0, 1, 80deg);
transform: rotate3d(0, 0, 1, 80deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
40%,
80% {
-webkit-transform: rotate3d(0, 0, 1, 60deg);
transform: rotate3d(0, 0, 1, 60deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
opacity: 1;
}
to {
-webkit-transform: translate3d(0, 700px, 0);
transform: translate3d(0, 700px, 0);
opacity: 0;
}
}
.flipInX {
backface-visibility: visible !important;
+130 -192
View File
@@ -34,7 +34,7 @@ div(:class="$style.player")
div(:class="$style.titleBtn" @click='addMusicTo' :tips="$t('core.player.add_music_to')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='80%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-add-2')
//- div(:class="$style.playBtn" @click='handleNext' tips="音量")
//- div(:class="$style.playBtn" @click='playNext' tips="音量")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 291.063 291.064' space='preserve')
use(xlink:href='#icon-sound')
@@ -49,7 +49,7 @@ div(:class="$style.player")
span(style="margin: 0 5px;") /
span {{maxPlayTimeStr}}
div(:class="$style.right")
div(:class="$style.playBtn" @click='handlePrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
div(:class="$style.playBtn" @click='playPrev' :tips="$t('core.player.prev')" style="transform: rotate(180deg);")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
div(:class="$style.playBtn" :tips="isPlay ? $t('core.player.pause') : $t('core.player.play')" @click='togglePlay')
@@ -57,7 +57,7 @@ div(:class="$style.player")
use(xlink:href='#icon-pause')
svg(v-else 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')
div(:class="$style.playBtn" @click='handleNext' :tips="$t('core.player.next')")
div(:class="$style.playBtn" @click='playNext' :tips="$t('core.player.next')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 220.847 220.847' space='preserve')
use(xlink:href='#icon-nextMusic')
//- transition(enter-active-class="animated lightSpeedIn"
@@ -85,14 +85,12 @@ div(:class="$style.player")
</template>
<script>
import Lyric from 'lrc-file-parser'
import Lyric from '@renderer/utils/lyric-font-player'
import { rendererSend, rendererOn, NAMES } from '../../../common/ipc'
import { formatPlayTime2, getRandom, checkPath, setTitle, clipboardWriteText, debounce, throttle, assertApiSupport } from '../../utils'
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { requestMsg } from '../../utils/message'
import { isMac } from '../../../common/utils'
import { player as eventPlayerNames } from '../../../common/hotKey'
import musicSdk from '@renderer/utils/music'
import path from 'path'
let audio
@@ -107,7 +105,6 @@ const playNextModes = [
export default {
data() {
return {
show: true,
volume: 0,
nowPlayTime: 0,
maxPlayTime: 0,
@@ -123,7 +120,6 @@ export default {
singer: '',
album: '',
},
targetSong: null,
pregessWidth: 0,
lyric: {
lines: [],
@@ -133,7 +129,6 @@ export default {
delayNextTimeout: null,
restorePlayTime: 0,
retryNum: 0,
isMac,
volumeEvent: {
isMsDown: false,
msDownX: 0,
@@ -149,10 +144,19 @@ export default {
},
computed: {
...mapGetters(['setting']),
...mapGetters('player', ['list', 'playIndex', 'changePlay', 'listId', 'isShowPlayerDetail', 'playedList']),
...mapGetters('player', ['list', 'changePlay', 'playMusicInfo', 'isShowPlayerDetail', 'playInfo', 'playedList']),
// pic() {
// return this.musicInfo.img ? this.musicInfo.img : ''
// },
listId() { // 当前播放歌曲的列表ID
return this.playInfo.listId
},
playIndex() { // 当前播放歌曲所在列表的 播放列表的播放位置
return this.playInfo.playIndex
},
targetSong() {
return this.playInfo.musicInfo
},
title() {
return this.musicInfo.name
? this.setting.download.fileName.replace('歌名', this.musicInfo.name).replace('歌手', this.musicInfo.singer)
@@ -211,8 +215,9 @@ export default {
singer: this.musicInfo.singer,
name: this.musicInfo.name,
album: this.musicInfo.album,
lyric: this.musicInfo.lrc,
tlyric: this.musicInfo.tlrc,
lrc: this.musicInfo.lrc,
tlrc: this.musicInfo.tlrc,
lxlrc: this.musicInfo.lxlrc,
isPlay: this.isPlay,
line: this.lyric.line,
played_time: audio.currentTime * 1000,
@@ -247,38 +252,19 @@ export default {
if (!n) return
this.resetChangePlay()
if (window.restorePlayInfo) {
let musicInfo = this.targetSong = this.list[window.restorePlayInfo.index]
this.musicInfo.songmid = musicInfo.songmid
this.musicInfo.singer = musicInfo.singer
this.musicInfo.name = musicInfo.name
this.musicInfo.album = musicInfo.albumName
this.setImg(musicInfo)
this.setLrc(musicInfo)
this.nowPlayTime = this.restorePlayTime = window.restorePlayInfo.time
this.maxPlayTime = window.restorePlayInfo.maxTime || 0
this.handleUpdateWinLyricInfo('music_info', {
songmid: this.musicInfo.songmid,
singer: this.musicInfo.singer,
name: this.musicInfo.name,
album: this.musicInfo.album,
})
this.$nextTick(() => {
this.sendProgressEvent(this.progress, 'paused')
})
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(musicInfo)
this.handleRestorePlay(window.restorePlayInfo)
window.restorePlayInfo = null
return
}
// console.log('changePlay')
this.handleRemoveMusic()
if (this.playIndex < 0) return
this.stopPlay()
if (!this.playInfo.musicInfo) return
this.play()
},
'setting.player.togglePlayMethod'(n) {
audio.loop = n === 'singleLoop'
if (this.playedList.length) this.clearPlayedList()
if (n == 'random' && this.playIndex > -1) this.setPlayedList(this.list[this.playIndex])
if (n == 'random') this.setPlayedList(this.playMusicInfo)
},
'setting.player.isMute'(n) {
audio.muted = n
@@ -286,7 +272,10 @@ export default {
'setting.player.mediaDeviceId'(n) {
this.setMediaDevice()
},
'setting.player.isShowLyricTransition'() {
'setting.player.isShowLyricTranslation'() {
this.setLyric()
},
'setting.player.isPlayLxlrc'() {
this.setLyric()
},
async list(n, o) {
@@ -297,13 +286,13 @@ export default {
if (index < 0) {
// console.log(this.playIndex)
if (n.length) {
this.fixPlayIndex(this.playIndex - 1)
this.handleNext()
this.setPlayIndex(this.playInfo.listPlayIndex - 1)
this.playNext()
} else {
this.setPlayIndex(-1)
this.setPlayMusicInfo(null)
}
} else {
this.fixPlayIndex(index)
this.setPlayIndex(index)
}
// console.log(this.playIndex)
}
@@ -317,26 +306,38 @@ export default {
},
nowPlayTime(n, o) {
if (Math.abs(n - o) > 2) this.isActiveTransition = true
this.savePlayInfo({
time: n,
maxTime: this.maxPlayTime,
listId: this.listId,
list: this.listId == null ? this.list : null,
index: this.playIndex,
})
if (this.setting.player.isSavePlayTime && !this.playInfo.isTempPlay) {
this.savePlayInfo({
time: n,
maxTime: this.maxPlayTime,
listId: this.listId,
list: this.listId == null ? this.list : null,
index: this.playIndex,
})
}
},
maxPlayTime(maxPlayTime) {
if (!this.playInfo.isTempPlay) {
this.savePlayInfo({
time: this.nowPlayTime,
maxTime: maxPlayTime,
listId: this.listId,
list: this.listId == null ? this.list : null,
index: this.playIndex,
})
}
},
},
methods: {
...mapActions('player', ['getUrl', 'getPic', 'getLrc']),
...mapActions('player', ['getUrl', 'getPic', 'getLrc', 'playPrev', 'playNext']),
...mapActions('list', ['getOtherSource']),
...mapMutations('player', [
'setPlayMusicInfo',
'setPlayIndex',
'fixPlayIndex',
'resetChangePlay',
'visiblePlayerDetail',
'clearPlayedList',
'setPlayedList',
'removePlayedList',
'setList',
]),
...mapMutations(['setVolume', 'setPlayNextMode', 'setVisibleDesktopLyric', 'setLockDesktopLyric']),
...mapMutations('list', ['updateMusicInfo']),
@@ -345,8 +346,8 @@ export default {
let eventHub = window.eventHub
let name = action == 'on' ? '$on' : '$off'
eventHub[name](eventPlayerNames.toggle_play.action, this.togglePlay)
eventHub[name](eventPlayerNames.next.action, this.handleNext)
eventHub[name](eventPlayerNames.prev.action, this.handlePrev)
eventHub[name](eventPlayerNames.next.action, this.playNext)
eventHub[name](eventPlayerNames.prev.action, this.playPrev)
eventHub[name](eventPlayerNames.volume_up.action, this.handleSetVolumeUp)
eventHub[name](eventPlayerNames.volume_down.action, this.handleSetVolumeDown)
eventHub[name](eventPlayerNames.volume_mute.action, this.handleSetVolumeMute)
@@ -377,18 +378,19 @@ export default {
console.log('播放完毕')
this.stopPlay()
this.status = this.statusText = this.$t('core.player.end')
this.handleNext()
this.playNext()
})
audio.addEventListener('error', () => {
// console.log('code', audio.error)
if (!this.musicInfo.songmid) return
console.log('出错')
this.stopPlay()
this.clearLoadingTimeout()
if (this.listId != 'download' && audio.error.code !== 1 && this.retryNum < 2) { // 若音频URL无效则尝试刷新2次URL
// console.log(this.retryNum)
if (!this.restorePlayTime) this.restorePlayTime = audio.currentTime // 记录出错的播放时间
this.retryNum++
this.setUrl(this.list[this.playIndex], true)
this.setUrl(this.targetSong, true)
this.status = this.statusText = this.$t('core.player.refresh_url')
return
}
@@ -406,7 +408,9 @@ export default {
audio.currentTime = this.restorePlayTime
this.restorePlayTime = 0
}
if (!this.targetSong.interval && this.listId != 'download') this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) } })
if (!this.targetSong.interval && this.listId != 'download') {
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { interval: formatPlayTime2(this.maxPlayTime) }, musicInfo: this.targetSong })
}
})
audio.addEventListener('loadstart', () => {
console.log('loadstart')
@@ -452,6 +456,10 @@ export default {
})
window.lrc = new Lyric({
lineClassName: 'lrc-content',
fontClassName: 'font',
shadowContent: false,
activeLineClassName: 'active',
onPlay: (line, text) => {
this.lyric.text = text
this.lyric.line = line
@@ -463,16 +471,17 @@ export default {
this.lyric.lines = lines
this.lyric.line = 0
},
offset: 80,
// offset: 80,
})
this.handleRegisterEvent('on')
},
async play() {
console.log('play', this.playIndex)
this.clearDelayNextTimeout()
let targetSong = this.targetSong = this.list[this.playIndex]
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(targetSong)
const targetSong = this.targetSong
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
this.retryNum = 0
this.restorePlayTime = 0
@@ -480,7 +489,7 @@ export default {
const filePath = path.join(this.setting.download.savePath, targetSong.fileName)
// console.log(filePath)
if (!await checkPath(filePath) || !targetSong.isComplate || /\.ape$/.test(filePath)) {
return this.list.length == 1 ? null : this.handleNext()
return this.list.length == 1 ? null : this.playNext()
}
this.musicInfo.songmid = targetSong.musicInfo.songmid
this.musicInfo.singer = targetSong.musicInfo.singer
@@ -491,7 +500,7 @@ export default {
this.setImg(targetSong.musicInfo)
this.setLrc(targetSong.musicInfo)
} else {
if (!this.assertApiSupport(targetSong.source)) return this.handleNext()
// if (!this.assertApiSupport(targetSong.source)) return this.playNext()
this.musicInfo.songmid = targetSong.songmid
this.musicInfo.singer = targetSong.singer
this.musicInfo.name = targetSong.name
@@ -506,6 +515,15 @@ export default {
name: this.musicInfo.name,
album: this.musicInfo.album,
})
if (!this.playInfo.isTempPlay) {
this.savePlayInfo({
time: this.nowPlayTime,
maxTime: this.maxPlayTime,
listId: this.listId,
list: this.listId == null ? this.list : null,
index: this.playIndex,
})
}
},
clearDelayNextTimeout() {
// console.log(this.delayNextTimeout)
@@ -518,120 +536,10 @@ export default {
this.clearDelayNextTimeout()
this.delayNextTimeout = setTimeout(() => {
this.delayNextTimeout = null
this.handleNext()
this.playNext()
}, 5000)
},
async filterList() {
// if (this.list.listName === null) return
let list
let playedList = [...this.playedList]
if (this.listId == 'download') {
list = []
for (const item of this.list) {
const filePath = path.join(this.setting.download.savePath, item.fileName)
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
let index = playedList.indexOf(item)
if (index > -1) {
playedList.splice(index, 1)
continue
}
list.push(item)
}
} else {
list = this.list.filter(s => {
let index = playedList.indexOf(s)
if (index > -1) {
playedList.splice(index, 1)
return false
}
return this.assertApiSupport(s.source)
})
}
if (!list.length && this.playedList.length) {
this.clearPlayedList()
return this.filterList()
}
return list
},
async handlePrev() {
// console.log(playIndex)
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
let index = this.playedList.indexOf(this.targetSong)
index -= 1
while (true) {
if (index > -1) {
let listIndex = this.list.indexOf(this.playedList[index])
if (listIndex < 0) {
this.removePlayedList(index)
continue
}
this.setPlayIndex(listIndex)
return
}
break
}
}
let list = await this.filterList()
if (!list.length) return this.setPlayIndex(-1)
let playIndex = list.indexOf(this.list[this.playIndex])
let index
switch (this.setting.player.togglePlayMethod) {
case 'random':
index = this.hanldeListRandom(list, playIndex)
break
case 'listLoop':
case 'list':
index = playIndex === 0 ? list.length - 1 : playIndex - 1
break
default:
return
}
if (index < 0) return
index = this.list.indexOf(list[index])
this.setPlayIndex(index)
},
async handleNext() {
// if (this.list.listName === null) return
// eslint-disable-next-line no-debugger
if (this.setting.player.togglePlayMethod == 'random' && this.playedList.length) {
let index = this.playedList.indexOf(this.targetSong)
index += 1
while (true) {
if (index < this.playedList.length) {
let listIndex = this.list.indexOf(this.playedList[index])
if (listIndex < 0) {
this.removePlayedList(index)
continue
}
this.setPlayIndex(listIndex)
return
}
break
}
}
let list = await this.filterList()
if (!list.length) return this.setPlayIndex(-1)
let playIndex = list.indexOf(this.list[this.playIndex])
// console.log(playIndex)
let index
switch (this.setting.player.togglePlayMethod) {
case 'listLoop':
index = playIndex === list.length - 1 ? 0 : playIndex + 1
break
case 'random':
index = this.hanldeListRandom(list, playIndex)
break
case 'list':
index = playIndex === list.length - 1 ? -1 : playIndex + 1
break
default:
return
}
if (index < 0) return
index = this.list.indexOf(list[index])
this.setPlayIndex(index)
},
hanldeListRandom(list, index) {
return getRandom(0, list.length)
},
@@ -673,7 +581,7 @@ export default {
togglePlay() {
if (!audio.src) {
if (this.restorePlayTime != null) {
if (!this.assertApiSupport(this.targetSong.source)) return this.handleNext()
// if (!this.assertApiSupport(this.targetSong.source)) return this.playNext()
this.setUrl(this.targetSong)
}
return
@@ -699,11 +607,12 @@ export default {
if (!retryedSource.includes(targetSong.source)) retryedSource.push(targetSong.source)
let type = this.getPlayType(this.setting.player.highQuality, targetSong)
this.musicInfo.url = targetSong.typeUrl[type]
// this.musicInfo.url = await getMusicUrl(targetSong, type)
this.status = this.statusText = this.$t('core.player.geting_url')
return this.getUrl({ musicInfo: targetSong, originMusic, type, isRefresh }).then(() => {
audio.src = this.musicInfo.url = targetSong.typeUrl[type]
return this.getUrl({ musicInfo: targetSong, originMusic, type, isRefresh }).then(url => {
if ((targetSong !== this.targetSong && originMusic !== this.targetSong) || this.isPlay) return
audio.src = this.musicInfo.url = url
}).catch(err => {
// console.log('err', err.message)
if (err.message == requestMsg.cancelRequest) return
@@ -712,14 +621,11 @@ export default {
this.status = this.statusText = 'Try toggle source...'
return (originMusic.otherSource && originMusic.otherSource.length ? Promise.resolve(originMusic.otherSource) : musicSdk.findMusic(originMusic)).then(res => {
this.updateMusicInfo({ id: this.listId, index: this.playIndex, data: { otherSource: res } })
return res
}).then(otherSource => {
return this.getOtherSource(originMusic).then(otherSource => {
console.log('find otherSource', otherSource)
if (otherSource.length) {
for (const item of otherSource) {
if (retryedSource.includes(item.source)) continue
if (retryedSource.includes(item.source) || !this.assertApiSupport(item.source)) continue
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
return this.setUrl(item, isRefresh, false, retryedSource, originMusic)
}
@@ -740,13 +646,14 @@ export default {
}
},
setLrc(targetSong) {
this.getLrc(targetSong).then(() => {
this.musicInfo.lrc = targetSong.lrc
this.musicInfo.tlrc = targetSong.tlrc
this.getLrc(targetSong).then(({ lyric, tlyric, lxlyric }) => {
this.musicInfo.lrc = lyric
this.musicInfo.tlrc = tlyric
this.musicInfo.lxlrc = lxlyric
}).catch(() => {
this.status = this.statusText = this.$t('core.player.lyric_error')
}).finally(() => {
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc })
this.handleUpdateWinLyricInfo('lyric', { lrc: this.musicInfo.lrc, tlrc: this.musicInfo.tlrc, lxlrc: this.musicInfo.lxlrc })
this.setLyric()
})
},
@@ -760,6 +667,7 @@ export default {
this.musicInfo.songmid = null
this.musicInfo.lrc = null
this.musicInfo.tlrc = null
this.musicInfo.lxlrc = null
this.musicInfo.url = null
this.nowPlayTime = 0
this.maxPlayTime = 0
@@ -824,13 +732,13 @@ export default {
this.setProgressWidth()
},
handleToMusicLocation() {
if (!this.listId || this.listId == 'download') return
if (!this.listId || this.listId == '__temp__' || this.listId == 'download') return
if (this.playIndex == -1) return
this.$router.push({
path: 'list',
query: {
id: this.listId,
scrollIndex: this.playIndex,
scrollIndex: this.playInfo.playIndex,
},
})
},
@@ -845,7 +753,7 @@ export default {
startLoadingTimeout() {
// console.log('start load timeout')
this.loadingTimeout = setTimeout(() => {
this.handleNext()
this.playNext()
}, 20000)
},
clearLoadingTimeout() {
@@ -864,7 +772,7 @@ export default {
if (skipTime > this.maxPlayTime) skipTime = (this.maxPlayTime - audio.currentTime) / 2
if (skipTime - this.mediaBuffer.playTime < 1 || this.maxPlayTime - skipTime < 1) {
this.mediaBuffer.playTime = 0
this.handleNext()
this.playNext()
return
}
this.startBuffering()
@@ -924,13 +832,13 @@ export default {
handlePlayDetailAction({ type, data }) {
switch (type) {
case 'prev':
this.handlePrev()
this.playPrev()
break
case 'togglePlay':
this.togglePlay()
break
case 'next':
this.handleNext()
this.playNext()
break
case 'progress':
this.setProgress(data)
@@ -950,7 +858,15 @@ export default {
})
},
setLyric() {
window.lrc.setLyric((this.setting.player.isShowLyricTransition && this.musicInfo.tlrc ? (this.musicInfo.tlrc + '\n') : '') + (this.musicInfo.lrc || ''))
window.lrc.setLyric(
this.setting.player.isPlayLxlrc && this.musicInfo.lxlrc ? this.musicInfo.lxlrc : this.musicInfo.lrc,
this.setting.player.isShowLyricTranslation && this.musicInfo.tlrc ? this.musicInfo.tlrc : '',
// (
// this.setting.player.isShowLyricTranslation && this.musicInfo.tlrc
// ? (this.musicInfo.tlrc + '\n')
// : ''
// ) + (this.musicInfo.lrc || ''),
)
if (this.isPlay && (this.musicInfo.url || this.listId == 'download')) {
window.lrc.play(audio.currentTime * 1000)
this.handleUpdateWinLyricInfo('play', audio.currentTime * 1000)
@@ -971,6 +887,28 @@ export default {
if (!this.musicInfo.songmid) return
this.isShowAddMusicTo = true
},
handleRestorePlay(restorePlayInfo) {
let musicInfo = this.list[restorePlayInfo.index]
this.musicInfo.songmid = musicInfo.songmid
this.musicInfo.singer = musicInfo.singer
this.musicInfo.name = musicInfo.name
this.musicInfo.album = musicInfo.albumName
this.setImg(musicInfo)
this.setLrc(musicInfo)
this.nowPlayTime = this.restorePlayTime = restorePlayInfo.time
this.maxPlayTime = restorePlayInfo.maxTime || 0
this.handleUpdateWinLyricInfo('music_info', {
songmid: this.musicInfo.songmid,
singer: this.musicInfo.singer,
name: this.musicInfo.name,
album: this.musicInfo.album,
})
this.$nextTick(() => {
this.sendProgressEvent(this.progress, 'paused')
})
if (this.setting.player.togglePlayMethod == 'random') this.setPlayedList(this.playMusicInfo)
},
},
}
</script>
+91 -12
View File
@@ -29,7 +29,8 @@
div(:class="$style.right")
div(:class="[$style.lyric, lyricEvent.isMsDown ? $style.draging : null]" @wheel="handleWheel" @mousedown="handleLyricMouseDown" ref="dom_lyric")
div(:class="$style.lyricSpace")
p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
div(:class="[$style.lyricText]" ref="dom_lyric_text")
//- p(v-for="(info, index) in lyricLines" :key="index" :class="lyric.line == index ? $style.lrcActive : null") {{info.text}}
div(:class="$style.lyricSpace")
material-music-comment(:class="$style.comment" :titleFormat="this.setting.download.fileName" :musicInfo="musicInfo" v-model="isShowComment")
@@ -158,11 +159,14 @@ export default {
handler(n, o) {
this.isSetedLines = true
if (o) {
this.$refs.dom_lyric_text.textContent = ''
this.setLyric(n)
this._lyricLines = n
if (n.length) {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
} else {
@@ -174,7 +178,7 @@ export default {
if (this.lyricLines === this._lyricLines && this._lyricLines.length) return
this.lyricLines = this._lyricLines
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
}, 50)
@@ -182,7 +186,7 @@ export default {
} else {
this.lyricLines = n
this.$nextTick(() => {
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('p')
this.dom_lines = this.$refs.dom_lyric.querySelectorAll('.lrc-content')
this.handleScrollLrc()
})
}
@@ -235,6 +239,8 @@ export default {
document.addEventListener('mousemove', this.handleMouseMsMove)
document.addEventListener('mouseup', this.handleMouseMsUp)
window.addEventListener('resize', this.handleResize)
// console.log('object', this.$refs.dom_lyric_text)
this.setLyric(this.lyricLines)
},
beforeDestroy() {
this.clearLyricScrollTimeout()
@@ -250,6 +256,13 @@ export default {
...mapMutations('player', [
'visiblePlayerDetail',
]),
setLyric(lines) {
const dom_lines = document.createDocumentFragment()
for (const line of lines) {
dom_lines.appendChild(line.dom_line)
}
this.$refs.dom_lyric_text.appendChild(dom_lines)
},
handleResize() {
this.setProgressWidth()
},
@@ -540,6 +553,7 @@ export default {
&:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
content: ' ';
@@ -565,16 +579,62 @@ export default {
overflow: hidden;
font-size: 16px;
cursor: grab;
color: @color-theme_2-font;
&.draging {
cursor: grabbing;
}
p {
padding: 8px 0;
line-height: 1.2;
overflow-wrap: break-word;
transition: @transition-theme !important;
transition-property: color, font-size;
:global {
.lrc-content {
line-height: 1.2;
margin: 16px 0;
overflow-wrap: break-word;
.translation {
transition: @transition-theme !important;
transition-property: font-size, color;
font-size: .9em;
margin-top: 5px;
}
.line {
transition-property: font-size, color !important;
background: none !important;
-webkit-text-fill-color: unset;
// -webkit-text-fill-color: none !important;
}
&.active {
.line {
color: @color-theme;
}
.translation {
font-size: 1em;
color: @color-theme;
}
span {
// color: @color-theme;
font-size: 1.2em;
}
}
span {
transition: @transition-theme !important;
transition-property: font-size !important;
font-size: 1em;
background-repeat: no-repeat;
background-color: rgba(77, 77, 77, 0.9);
background-image: -webkit-linear-gradient(top, @color-theme, @color-theme);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-size: 0 100%;
}
}
}
// p {
// padding: 8px 0;
// line-height: 1.2;
// overflow-wrap: break-word;
// transition: @transition-theme !important;
// transition-property: color, font-size;
// }
}
.lyric-space {
height: 70%;
@@ -736,6 +796,7 @@ each(@themes, {
.container {
border-left-color: ~'@{color-@{value}-theme}';
background-color: ~'@{color-@{value}-theme_2-background_1}';
// color: ~'@{color-@{value}-theme_2-font}';
}
.right {
&:before {
@@ -769,9 +830,27 @@ each(@themes, {
box-shadow: 0 0 4px ~'@{color-@{value}-theme-hover}';
// border-color: ~'@{color-@{value}-theme-hover}';
}
.lrc-active {
color: ~'@{color-@{value}-theme}';
.lyric {
:global {
.lrc-content {
&.active {
.translation {
color: ~'@{color-@{value}-theme}';
}
.line {
color: ~'@{color-@{value}-theme}';
}
}
span {
// background-color: ~'@{color-@{value}-theme_2-font}';
background-image: -webkit-linear-gradient(top, ~'@{color-@{value}-theme}', ~'@{color-@{value}-theme}');
}
}
}
}
// .lrc-active {
// color: ~'@{color-@{value}-theme}';
// }
.footerLeftControlBtns {
color: ~'@{color-@{value}-theme_2-font}';
}
@@ -1,319 +0,0 @@
<template lang="pug">
div(:class="$style.lists")
h2(:class="$style.listsTitle") {{$t('core.aside.my_list')}}
ul.scroll(:class="$style.listsContent")
li(:class="$style.listsItem" v-for="item in lists" :key="item.id") {{item.name}}
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="!list.length" :class="$style.noitem")
p(v-html="noItem")
material-flow-btn(:show="isShowEditBtn && assertApiSupport(source)" :remove-btn="false" @btn-click="handleFlowBtnClick")
</template>
<script>
import { mapGetters } from 'vuex'
import { scrollTo, clipboardWriteText, assertApiSupport } from '../../utils'
export default {
name: 'MaterialSongList',
model: {
prop: 'selectdData',
event: 'input',
},
props: {
list: {
type: Array,
default() {
return []
},
},
page: {
type: Number,
required: true,
},
limit: {
type: Number,
required: true,
},
total: {
type: Number,
required: true,
},
selectdData: {
type: Array,
required: true,
},
source: {
type: String,
},
noItem: {
type: String,
default: '列表加载中...',
},
},
computed: {
...mapGetters(['setting']),
},
watch: {
selectdList(n) {
const len = n.length
if (len) {
this.isShowEditBtn = true
} else {
this.isShowEditBtn = false
}
},
selectdData(n) {
const len = n.length
if (len) {
this.isShowEditBtn = true
this.selectdList = [...n]
} else {
this.isShowEditBtn = false
this.removeAllSelect()
}
},
list(n) {
this.removeAllSelect()
if (!this.list.length) return
this.$nextTick(() => scrollTo(this.$refs.dom_scrollContent, 0))
},
},
data() {
return {
clickTime: 0,
clickIndex: -1,
isShowEditBtn: false,
selectdList: [],
keyEvent: {
isShiftDown: false,
isModDown: false,
isADown: false,
aDownTimeout: null,
},
lastSelectIndex: 0,
}
},
created() {
this.listenEvent()
},
beforeDestroy() {
this.unlistenEvent()
},
methods: {
listenEvent() {
window.eventHub.$on('key_shift_down', this.handle_key_shift_down)
window.eventHub.$on('key_shift_up', this.handle_key_shift_up)
window.eventHub.$on('key_mod_down', this.handle_key_mod_down)
window.eventHub.$on('key_mod_up', this.handle_key_mod_up)
window.eventHub.$on('key_mod+a_down', this.handle_key_mod_a_down)
window.eventHub.$on('key_mod+a_up', this.handle_key_mod_a_up)
},
unlistenEvent() {
window.eventHub.$off('key_shift_down', this.handle_key_shift_down)
window.eventHub.$off('key_shift_up', this.handle_key_shift_up)
window.eventHub.$off('key_mod_down', this.handle_key_mod_down)
window.eventHub.$off('key_mod_up', this.handle_key_mod_up)
window.eventHub.$off('key_mod+a_down', this.handle_key_mod_a_down)
window.eventHub.$off('key_mod+a_up', this.handle_key_mod_a_up)
},
handle_key_shift_down() {
if (!this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = true
},
handle_key_shift_up() {
if (this.keyEvent.isShiftDown) this.keyEvent.isShiftDown = false
},
handle_key_mod_down() {
if (!this.keyEvent.isModDown) this.keyEvent.isModDown = true
},
handle_key_mod_up() {
if (this.keyEvent.isModDown) this.keyEvent.isModDown = false
},
handle_key_mod_a_down() {
if (!this.keyEvent.isADown) {
this.keyEvent.isModDown = false
this.keyEvent.isADown = true
this.handleSelectAllData()
if (this.keyEvent.aDownTimeout) clearTimeout(this.keyEvent.aDownTimeout)
this.keyEvent.aDownTimeout = setTimeout(() => {
this.keyEvent.aDownTimeout = null
this.keyEvent.isADown = false
}, 500)
}
},
handle_key_mod_a_up() {
if (this.keyEvent.isADown) {
if (this.keyEvent.aDownTimeout) {
clearTimeout(this.keyEvent.aDownTimeout)
this.keyEvent.aDownTimeout = null
}
this.keyEvent.isADown = false
}
},
handleDoubleClick(event, index) {
if (event.target.classList.contains('select')) return
this.handleSelectData(event, index)
if (
window.performance.now() - this.clickTime > 400 ||
this.clickIndex !== index
) {
this.clickTime = window.performance.now()
this.clickIndex = index
return
}
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
this.clickTime = 0
this.clickIndex = -1
},
handleSelectData(event, clickIndex) {
if (this.keyEvent.isShiftDown) {
if (this.selectdList.length) {
let lastSelectIndex = this.lastSelectIndex
this.removeAllSelect()
if (lastSelectIndex != clickIndex) {
let isNeedReverse = false
if (clickIndex < lastSelectIndex) {
let temp = lastSelectIndex
lastSelectIndex = clickIndex
clickIndex = temp
isNeedReverse = true
}
this.selectdList = this.list.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) this.selectdList.reverse()
let nodes = this.$refs.dom_tbody.childNodes
do {
nodes[lastSelectIndex].classList.add('active')
lastSelectIndex++
} while (lastSelectIndex <= clickIndex)
}
} else {
event.currentTarget.classList.add('active')
this.selectdList.push(this.list[clickIndex])
this.lastSelectIndex = clickIndex
}
} else if (this.keyEvent.isModDown) {
this.lastSelectIndex = clickIndex
let item = this.list[clickIndex]
let index = this.selectdList.indexOf(item)
if (index < 0) {
this.selectdList.push(item)
event.currentTarget.classList.add('active')
} else {
this.selectdList.splice(index, 1)
event.currentTarget.classList.remove('active')
}
} else if (this.selectdList.length) {
this.removeAllSelect()
} else return
this.$emit('input', [...this.selectdList])
},
removeAllSelect() {
this.selectdList = []
let dom_tbody = this.$refs.dom_tbody
if (!dom_tbody) return
let nodes = dom_tbody.querySelectorAll('.active')
for (const node of nodes) {
if (node.parentNode == dom_tbody) node.classList.remove('active')
}
},
handleListBtnClick(info) {
this.emitEvent('listBtnClick', info)
},
handleSelectAllData() {
this.removeAllSelect()
this.selectdList = [...this.list]
let nodes = this.$refs.dom_tbody.childNodes
for (const node of nodes) {
node.classList.add('active')
}
this.$emit('input', [...this.selectdList])
},
handleTogglePage(page) {
this.emitEvent('togglePage', page)
},
handleFlowBtnClick(action) {
this.emitEvent('flowBtnClick', action)
},
emitEvent(action, data) {
this.$emit('action', { action, data })
},
handleChangeSelect() {
this.$emit('input', [...this.selectdList])
},
handleContextMenu(event) {
if (!event.target.classList.contains('select')) return
let classList = this.$refs.dom_scrollContent.classList
classList.add(this.$style.copying)
window.requestAnimationFrame(() => {
let str = window.getSelection().toString()
classList.remove(this.$style.copying)
str = str.trim()
if (!str.length) return
clipboardWriteText(str)
})
},
assertApiSupport(source) {
return assertApiSupport(source)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.lists {
flex: none;
width: 15%;
display: flex;
flex-flow: column nowrap;
}
.title {
font-size: 12px;
line-height: 28px;
padding: 5px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
flex: none;
}
.list {
flex: auto;
min-width: 0;
}
.item {
}
.noitem {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
p {
font-size: 24px;
color: @color-theme_2-font-label;
}
}
each(@themes, {
:global(#container.@{value}) {
.tbody {
td {
&:first-child {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
.noitem {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
}
})
</style>
+12 -1
View File
@@ -1,5 +1,5 @@
<template lang="pug">
button(:class="[$style.btn, min ? $style.min : null]" :disabled="disabled" @click="$emit('click', $event)")
button(:class="[$style.btn, min ? $style.min : null, outline ? $style.outline : null]" :disabled="disabled" @click="$emit('click', $event)")
slot
</template>
@@ -9,6 +9,10 @@ export default {
min: {
type: Boolean,
},
outline: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
@@ -36,6 +40,10 @@ export default {
opacity: .4;
}
&.outline {
background-color: transparent;
}
&:hover {
background-color: @color-btn-hover;
}
@@ -54,6 +62,9 @@ each(@themes, {
.btn {
color: ~'@{color-@{value}-btn}';
background-color: ~'@{color-@{value}-btn-background}';
&.outline {
background-color: transparent;
}
&:hover {
background-color: ~'@{color-@{value}-btn-hover}';
}
@@ -6,7 +6,7 @@ material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
span(:class="$style.name") {{this.musicInfo && `${musicInfo.name}`}}
| &nbsp;{{$t('material.list_add_modal.title_last')}}
div.scroll(:class="$style.btnContent")
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
material-btn(:class="$style.btn" :tips="$t('material.list_add_modal.btn_title', { name: item.name })" :key="item.id" :disabled="item.isExist" @click="handleClick(index)" v-for="(item, index) in lists") {{item.name}}
material-btn(:class="[$style.btn, $style.newList, isEditing ? $style.editing : null]" @click="handleEditing($event)" :tips="$t('view.list.lists_new_list_btn')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 42 42' space='preserve')
use(xlink:href='#icon-addTo')
@@ -57,11 +57,12 @@ export default {
computed: {
...mapGetters('list', ['defaultList', 'loveList', 'userList']),
lists() {
if (!this.musicInfo) return []
return [
this.defaultList,
this.loveList,
...this.userList,
].filter(l => l.id != this.excludeListId.includes(l.id))
].filter(l => !this.excludeListId.includes(l.id)).map(l => ({ ...l, isExist: l.list.some(s => s.songmid == this.musicInfo.songmid) }))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
@@ -62,7 +62,7 @@ export default {
this.defaultList,
this.loveList,
...this.userList,
].filter(l => l.id != this.excludeListId.includes(l.id))
].filter(l => !this.excludeListId.includes(l.id))
},
spaceNum() {
return this.lists.length < 2 ? 0 : (3 - this.lists.length % 3 - 1)
+1 -1
View File
@@ -138,7 +138,7 @@ export default {
// will-change: transform;
li {
cursor: pointer;
min-width: 90px;
min-width: 96px;
line-height: 34px;
// color: @color-btn;
padding: 0 10px;
+2 -2
View File
@@ -74,9 +74,9 @@ export default {
'slideOutLeft',
'slideOutRight',
'slideOutUp',
'hinge',
// 'hinge',
],
inClass: 'animated flipInX',
inClass: 'animated jackInTheBox',
outClass: 'animated flipOutX',
unwatchFn: null,
}
@@ -50,7 +50,7 @@ export default {
singer: '',
},
page: 1,
total: 10,
total: 0,
maxPage: 1,
limit: 20,
isHotLoading: true,
@@ -80,7 +80,7 @@ export default {
watch: {
'setting.isAgreePact'(n) {
if (n) return
this.time = 10
this.time = 5
this.startTimeout()
},
},
+10 -5
View File
@@ -31,8 +31,6 @@ div(:class="$style.songList")
td(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :class="$style.btns"
:remove-btn="false" @btn-click="handleListBtnClick"
:listAdd-btn="assertApiSupport(item.source)"
:play-btn="assertApiSupport(item.source)"
:download-btn="assertApiSupport(item.source)")
//- 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)') 试听
@@ -114,6 +112,11 @@ export default {
action: 'download',
disabled: !this.listMenu.itemMenuControl.download,
},
{
name: this.$t('material.song_list.list_play_later'),
action: 'playLater',
disabled: !this.listMenu.itemMenuControl.playLater,
},
{
name: this.$t('material.song_list.list_search'),
action: 'search',
@@ -173,6 +176,7 @@ export default {
itemMenuControl: {
play: true,
addTo: true,
playLater: true,
download: true,
search: true,
sourceDetail: true,
@@ -237,7 +241,7 @@ export default {
this.clickIndex = index
return
}
this.emitEvent(this.assertApiSupport(this.source) ? 'testPlay' : 'search', index)
this.emitEvent('testPlay', index)
this.clickTime = 0
this.clickIndex = -1
},
@@ -334,8 +338,9 @@ export default {
},
handleListItemRigthClick(event, index) {
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.download =
// this.listMenu.itemMenuControl.play =
// this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.download =
this.assertApiSupport(this.list[index].source)
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
if (dom_selected) dom_selected.classList.remove('selected')
@@ -0,0 +1,240 @@
<template lang="pug">
material-modal(:show="visible" bg-close @close="handleClose")
main(:class="$style.main")
h2 {{$t('material.user_api_modal.title')}}
ul.scroll(v-if="apiList.length" :class="$style.content")
li(:class="[$style.listItem, setting.apiSource == api.id ? $style.active : null]" v-for="(api, index) in apiList" :key="api.id")
div(:class="$style.listLeft")
h3 {{api.name}}
p {{api.description}}
material-btn(:class="$style.listBtn" outline :tips="$t('material.user_api_modal.btn_remove')" @click.stop="handleRemove(index)")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' viewBox='0 0 212.982 212.982' space='preserve' v-once)
use(xlink:href='#icon-delete')
div(v-else :class="$style.content")
div(:class="$style.noitem") {{$t('material.user_api_modal.noitem')}}
div(:class="$style.note")
p(:class="[$style.ruleLink]")
| {{$t('material.user_api_modal.readme')}}
span.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%BA%90%E8%84%9A%E6%9C%AC%E7%BC%96%E5%86%99%E8%AF%B4%E6%98%8E')" tips="https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md") FAQ.md
p {{$t('material.user_api_modal.note')}}
div(:class="$style.footer")
material-btn(:class="$style.footerBtn" @click="handleImport") {{$t('material.user_api_modal.btn_import')}}
//- material-btn(:class="$style.footerBtn" @click="handleExport") {{$t('material.user_api_modal.btn_export')}}
</template>
<script>
import { mapGetters } from 'vuex'
import { rendererInvoke, NAMES } from '@common/ipc'
import { promises as fsPromises } from 'fs'
import {
selectDir,
openUrl,
} from '../../utils'
import apiSourceInfo from '../../utils/music/api-source-info'
export default {
props: {
visible: {
type: Boolean,
default: false,
},
},
model: {
prop: 'visible',
event: 'toggle',
},
data() {
return {
globalObj: window.globalObj,
}
},
computed: {
...mapGetters(['setting']),
apiList() {
return this.globalObj.userApi.list
},
},
methods: {
handleImport() {
selectDir({
title: this.$t('material.user_api_modal.import_file'),
properties: ['openFile'],
filters: [
{ name: 'LX API File', extensions: ['js'] },
{ name: 'All Files', extensions: ['*'] },
],
}).then(result => {
if (result.canceled) return
return fsPromises.readFile(result.filePaths[0]).then(data => {
return rendererInvoke(NAMES.mainWindow.import_user_api, data.toString()).then(({ apiList }) => {
window.globalObj.userApi.list = apiList
})
})
})
},
handleExport() {
},
async handleRemove(index) {
const api = this.apiList[index]
if (!api) return
if (this.setting.apiSource == api.id) {
let backApi = apiSourceInfo.find(api => !api.disabled)
if (backApi) window.globalObj.apiSource = backApi.id
}
window.globalObj.userApi.list = await rendererInvoke(NAMES.mainWindow.remove_user_api, [api.id])
},
handleClose() {
this.$emit('toggle', false)
},
handleOpenUrl(url) {
openUrl(url)
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
padding: 15px;
max-width: 400px;
min-width: 300px;
display: flex;
flex-flow: column nowrap;
justify-content: center;
min-height: 0;
// max-height: 100%;
// overflow: hidden;
h2 {
font-size: 16px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
.name {
color: @color-theme;
}
.content {
flex: auto;
min-height: 100px;
max-height: 100%;
margin-top: 15px;
}
.listItem {
display: flex;
flex-flow: row nowrap;
align-items: center;
transition: background-color 0.2s ease;
padding: 10px;
border-radius: @radius-border;
&:hover {
background-color: @color-theme_2-hover;
}
&.active {
background-color: @color-theme_2-active;
}
h3 {
font-size: 15px;
color: @color-theme_2-font;
word-break: break-all;
}
p {
margin-top: 5px;
font-size: 14px;
color: @color-theme_2-font-label;
word-break: break-all;
}
}
.noitem {
height: 100px;
font-size: 18px;
color: @color-theme_2-font-label;
display: flex;
justify-content: center;
align-items: center;
}
.listLeft {
flex: auto;
min-width: 0;
display: flex;
flex-flow: column nowrap;
justify-content: center;
}
.listBtn {
flex: none;
height: 30px;
width: 30px;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
svg {
width: 60%;
}
}
.note {
margin-top: 15px;
font-size: 12px;
line-height: 1.25;
color: @color-theme_2-font;
p {
+ p {
margin-top: 5px;
}
}
}
.footer {
margin-top: 15px;
display: flex;
flex-flow: row nowrap;
}
.footerBtn {
flex: auto;
height: 36px;
line-height: 36px;
padding: 0 10px !important;
width: 150px;
.mixin-ellipsis-1;
+ .footerBtn {
margin-left: 15px;
}
}
.ruleLink {
.mixin-ellipsis-1;
}
each(@themes, {
:global(#container.@{value}) {
.main {
h2 {
color: ~'@{color-@{value}-theme_2-font}';
}
}
.listItem {
&:hover {
background-color: ~'@{color-@{value}-theme_2-hover}';
}
&.active {
background-color: ~'@{color-@{value}-theme_2-active}';
}
h3 {
color: ~'@{color-@{value}-theme_2-font}';
}
p {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
.noitem {
color: ~'@{color-@{value}-theme_2-font-label}';
}
}
})
</style>
@@ -3,17 +3,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
main(:class="$style.main" v-if="version.isDownloaded")
h2 🚀程序更新🚀
div.scroll(:class="$style.info")
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 版本变化:
p(:class="$style.desc" v-html="version.newVersion.desc")
pre(:class="$style.desc" v-text="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
pre(v-text="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
p 新版本已下载完毕,
@@ -27,17 +27,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
main(:class="$style.main" v-else-if="version.isError && !version.isUnknow && version.newVersion.version != version.version")
h2 ❌ 版本更新出错 ❌
div.scroll(:class="$style.info")
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 版本变化:
p(:class="$style.desc" v-html="version.newVersion.desc")
pre(:class="$style.desc" v-text="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
pre(v-text="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
@@ -46,7 +46,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
| 你可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| &nbsp;或&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )&nbsp;下载新版本,
@@ -58,12 +58,12 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
main(:class="$style.main" v-else-if="version.isDownloading && version.isTimeOut && !version.isUnknow")
h2 ❗️ 新版本下载超时 ❗️
div(:class="$style.desc")
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),建议手动更新版本!
p 你当前所在网络访问GitHub较慢,导致新版本下载超时(已经下了半个钟了😳),你仍可选择继续等,但墙裂建议手动更新版本!
p
| 你可以去
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| 或
material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )下载新版本,
@@ -75,7 +75,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
main(:class="$style.main" v-else-if="version.isUnknow")
h2 ❓ 获取最新版本信息失败 ❓
div.scroll(:class="$style.info")
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 当前版本:{{version.version}}
div(:class="$style.desc")
@@ -84,7 +84,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
| 检查方法:打开
material-btn(min @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| 或
material-btn(min @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
material-btn(min @click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )查看它们的
@@ -94,17 +94,17 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
main(:class="$style.main" v-else)
h2 🌟发现新版本🌟
div.scroll(:class="$style.info")
div.scroll.select(:class="$style.info")
div(:class="$style.current")
h3 最新版本:{{version.newVersion.version}}
h3 当前版本:{{version.version}}
h3 版本变化:
p(:class="$style.desc" v-html="version.newVersion.desc")
pre(:class="$style.desc" v-text="version.newVersion.desc")
div(:class="[$style.history, $style.desc]" v-if="history.length")
h3 历史版本:
div(:class="$style.item" v-for="ver in history")
h4 v{{ver.version}}
p(v-html="ver.desc")
pre(v-text="ver.desc")
div(:class="$style.footer")
div(:class="$style.desc")
@@ -117,7 +117,7 @@ material-modal(:show="version.showModal" @close="handleClose" v-if="version.newV
| 手动更新可以去&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/releases')" tips="点击打开") 软件发布页
| &nbsp;或&nbsp;
strong.hover.underline(@click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')" tips="点击打开") 网盘
strong.hover.underline(@click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')" tips="点击打开") 网盘
| (密码:
strong.hover(@click="handleCopy('glqw')" tips="点击复制") glqw
| )&nbsp;下载,
@@ -146,7 +146,7 @@ export default {
progress() {
return this.version.downloadProgress
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
: '初始化中...'
: '处理更新中...'
},
isIgnored() {
return this.setting.ignoreVersion == this.version.newVersion.version
@@ -207,6 +207,11 @@ export default {
font-size: 14px;
line-height: 1.3;
}
pre {
white-space: pre-wrap;
text-align: justify;
margin-top: 10px;
}
}
.info {
@@ -1,50 +0,0 @@
<template lang="pug">
material-modal(:show="show" :bg-close="bgClose" @close="handleClose")
main.ignore-to-rem(:class="$style.main")
h2 {{$t('material.xm_verify_modal.title')}}
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
bgClose: {
type: Boolean,
default: true,
},
},
methods: {
handleClose() {
this.$emit('close')
},
},
}
</script>
<style lang="less" module>
@import '../../assets/styles/layout.less';
.main {
background: #fff !important;
&:global(.ignore-to-rem) {
padding: 15px;
width: 360px;
height: 330px;
h2 {
font-size: 16px;
}
}
h2 {
font-size: 13px;
color: @color-theme_2-font;
line-height: 1.3;
text-align: center;
}
}
</style>
@@ -1,5 +1,6 @@
{
"list_play": "Play",
"list_play_later": "Play later",
"list_add_to": "Add to ...",
"list_download": "Download",
"list_search": "Search",
@@ -0,0 +1,10 @@
{
"title": "Custom Source Management",
"readme": "Source writing instructions: ",
"note": "Tip: Although we have isolated the script's running environment as much as possible, importing scripts containing malicious behaviors may still affect your system. Please import them carefully.",
"btn_remove": "Remove",
"btn_import": "Import",
"btn_export": "Export",
"import_file": "Select music API script file",
"noitem": "There is nothing here...😲"
}
@@ -1,5 +1,6 @@
{
"menu_play": "Play",
"menu_play_later": "Play later",
"menu_start": "Start task",
"menu_pause": "Pause Task",
"menu_file": "Locate File",
+1
View File
@@ -7,6 +7,7 @@
"lists_sync": "Sync",
"lists_remove": "Remove",
"list_play": "Play",
"list_play_later": "Play later",
"list_copy_name": "Copy name",
"list_add_to": "Add to ...",
"list_move_to": "Move to ...",
+2
View File
@@ -1,5 +1,6 @@
{
"list_play": "Play",
"list_play_later": "Play later",
"list_add_to": "Add to ...",
"list_download": "Download",
"list_source_detail": "Song Page",
@@ -10,6 +11,7 @@
"time": "Length",
"lossless": "SQ",
"high_quality": "HQ",
"loding_list": "Loading...",
"no_item": "Search what I want to 😉",
"hot_search": "Top Searches",
"history_search": "History Searches",
+19 -28
View File
@@ -2,7 +2,6 @@
"basic": "General",
"basic_theme": "Theme",
"basic_show_animation": "Show switching animation",
"basic_animation_title": "Animation effect of the pop-up layer",
"basic_animation": "Random pop-up animation",
"basic_source_title": "Choose a music source",
"basic_source_test": "Test API (Available for most software features)",
@@ -12,6 +11,10 @@
"basic_sourcename_real": "Original",
"basic_sourcename_alias": "Aliases",
"basic_sourcename": "Source name",
"basic_source_status_success": "Initialization successful",
"basic_source_status_initing": "Initializing",
"basic_source_status_failed": "Initialization failed",
"basic_source_user_api_btn": "Custom Source Management",
"basic_window_size_title": "Set the window size",
"basic_window_size": "Window size",
"basic_window_size_smaller": "Smaller",
@@ -21,8 +24,7 @@
"basic_window_size_larger": "Larger",
"basic_window_size_oversized": "Oversized",
"basic_window_size_huge": "Huge",
"basic_to_tray_title": "Minimize it to the system tray without closing the software when closing",
"basic_to_tray": "Minimize to system tray when closing",
"basic_to_tray": "Do not exit the software when closing the software and minimize it to the system tray",
"basic_lang_title": "The language displayed in the software",
"basic_lang": "Language",
"basic_control_btn_position": "Control Button Position",
@@ -30,21 +32,14 @@
"basic_control_btn_position_right": "Right",
"play": "Play",
"play_toggle_title": "If none selected, it stopped when the music playing is done.",
"play_toggle": "Playback mode",
"play_toggle_list_loop": "Playlist repeat",
"play_toggle_random": "Playlist shuffle",
"play_toggle_list": "Play in order",
"play_toggle_single_loop": "Single repeat",
"play_save_play_time": "Remember playback progress",
"play_lyric_transition": "Show lyrics translation",
"play_quality_title": "The 320k quality is preferred for playing",
"play_quality": "Prefer High Quality 320k",
"play_task_bar_title": "Show playing progress on the taskbar",
"play_task_bar": "Taskbar play progress bar",
"play_lyric_lxlrc": "Use Karaoke-style lyrics playback (if supported)",
"play_quality": "Play 320K quality songs first (if supported)",
"play_task_bar": "Show playing progress on the taskbar",
"play_mediaDevice_title": "Select a media device for audio output",
"play_mediaDevice": "Audio output",
"play_mediaDevice_remove_stop_play": "Whether to pause playback when the audio output device is changed",
"play_mediaDevice_remove_stop_play_title": "Whether to pause the song when the current sound output device is changed",
"play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed",
"desktop_lyric": "Desktop Lyric Settings",
"desktop_lyric_enable": "Display lyrics",
@@ -53,18 +48,13 @@
"desktop_lyric_lock_screen": "It is not allowed to drag the lyrics window out of the main screen",
"search": "Search",
"search_hot_title": "Select whether to show popular searches",
"search_hot": "Top Searches",
"search_history_title": "Select whether to show search history",
"search_history": "Search history",
"search_focus_search_box_title": "Whether the search box is automatically focused on startup",
"search_focus_search_box": "Whether the search box is focused on startup",
"search_focus_search_box": "Automatically focus the search box on startup",
"list": "List",
"list_source_title": "Select whether to show music source",
"list_source": "Select whether to show music source (for Your Library only)",
"list_scroll_title": "Select whether to remember the playlist scrollbar position",
"list_scroll": "Remember playlist scrolling position (for Your library only)",
"list_source": "Show song source (only valid for my music category)",
"list_scroll": "Remember the position of the scroll bar of the playlist (only valid for my music classification)",
"download": "Download",
"download_enable": "Whether to enable download function",
@@ -136,10 +126,11 @@
"other_tray_theme": "Tray Icon Style",
"other_tray_theme_native": "Solid Color",
"other_tray_theme_origin": "Primary Color",
"other_cache": "Cache size (Not recommended since resources such as pictures after the cache is cleaned need re-downloading. The software will dynamically manage the cache size based on disk space)",
"other_cache_label": "Cache size used: ",
"other_cache_label_title": "Currently used cache size",
"other_cache_clear_btn": "Clear cache",
"other_resource_cache": "Resource cache management (pictures, audios and other caches, pictures and other resources will need to be downloaded again after cleaning up, it is not recommended to clean up, the software will dynamically manage the cache size according to the disk space)",
"other_resource_cache_label": "The software has used cache size: ",
"other_resource_cache_clear_btn": "Clear resource cache",
"other_play_list_cache": "List cache management (links to songs that have been cached in my list, alternative sources for playback, after cleaning up, you need to re-acquire them when you play and download songs, and do not clean up if there are no issues related to song playback)",
"other_play_list_cache_clear_btn": "Clear list cache information",
"update": "Update",
"update_latest_label": "Latest version: ",
@@ -150,7 +141,7 @@
"update_latest": "The software is up-to-date, enjoy yourself!🥂",
"update_open_version_modal_btn": "Open the update window🚀",
"update_checking": "Checking for updates...",
"update_init": "Initializing update...",
"update_init": "Processing update...",
"about": "About lx-music-desktop",
@@ -8,5 +8,6 @@
"tip_2": "If you encounter a link to a playlist that cannot be opened, welcome feedback",
"tip_3": "Kugou source does not support opening with playlist ID, but supports Kugou code opening",
"play_all": "Play",
"play_later": "Play later",
"add_all": "Collect"
}
@@ -1,5 +1,6 @@
{
"list_play": "播放",
"list_play_later": "稍后播放",
"list_add_to": "添加到...",
"list_download": "下载",
"list_source_detail": "歌曲详情页",
@@ -0,0 +1,10 @@
{
"title": "自定义源管理",
"readme": "源编写说明:",
"note": "提示:虽然我们已经尽可能地隔离了脚本的运行环境,但导入包含恶意行为的脚本仍可能会影响你的系统,请谨慎导入。",
"btn_remove": "移除",
"btn_import": "导入",
"btn_export": "导出",
"import_file": "选择音乐API脚本文件",
"noitem": "这里竟然是空的 😲"
}
+1 -1
View File
@@ -33,6 +33,6 @@
"source_alias_all": "聚合大会",
"load_list_file_error_title": "播放列表数据加载错误",
"load_list_file_error_title": "播放列表数据加载错误(建议到GitHub或加群反馈)",
"load_list_file_error_detail": "我们已经帮你把旧的列表文件备份到{path}\n它以 JSON 格式存储,你可以尝试手动修复并恢复它\n\n错误详情:{detail}"
}
@@ -1,5 +1,6 @@
{
"menu_play": "播放",
"menu_play_later": "稍后播放",
"menu_start": "开始任务",
"menu_pause": "暂停任务",
"menu_file": "定位文件",
+1
View File
@@ -7,6 +7,7 @@
"lists_sync": "同步",
"lists_remove": "删除",
"list_play": "播放",
"list_play_later": "稍后播放",
"list_copy_name": "复制歌曲名",
"list_source_detail": "歌曲详情页",
"list_add_to": "添加到...",
+2
View File
@@ -1,5 +1,6 @@
{
"list_play": "播放",
"list_play_later": "稍后播放",
"list_add_to": "添加到...",
"list_download": "下载",
"list_source_detail": "歌曲详情页",
@@ -10,6 +11,7 @@
"time": "时长",
"lossless": "无损",
"high_quality": "高品质",
"loding_list": "加载中...",
"no_item": "搜我所想~~😉",
"hot_search": "热门搜索",
"history_search": "历史搜索",
+21 -30
View File
@@ -1,7 +1,6 @@
{
"basic": "基本设置",
"basic_theme": "主题颜色",
"basic_animation_title": "弹出层的动画效果",
"basic_animation": "弹出层随机动画",
"basic_show_animation": "显示切换动画",
"basic_source_title": "选择音乐来源",
@@ -12,6 +11,10 @@
"basic_sourcename_real": "原名",
"basic_sourcename_alias": "别名",
"basic_sourcename": "音源名字",
"basic_source_status_success": "初始化成功",
"basic_source_status_initing": "初始化中",
"basic_source_status_failed": "初始化失败",
"basic_source_user_api_btn": "自定义源管理",
"basic_window_size_title": "设置软件窗口尺寸",
"basic_window_size": "窗口尺寸",
"basic_window_size_smaller": "较小",
@@ -21,8 +24,7 @@
"basic_window_size_larger": "较大",
"basic_window_size_oversized": "超大",
"basic_window_size_huge": "巨大",
"basic_to_tray_title": "关闭时不退出软件将其最小化到系统托盘",
"basic_to_tray": "关闭时最小化到系统托盘",
"basic_to_tray": "关闭软件时不退出软件将其最小化到系统托盘",
"basic_lang_title": "软件显示的语言",
"basic_lang": "语言",
"basic_control_btn_position": "控制按钮位置",
@@ -30,21 +32,14 @@
"basic_control_btn_position_right": "右边",
"play": "播放设置",
"play_toggle_title": "都不选时播放完当前歌曲就停止播放",
"play_toggle": "歌曲切换方式",
"play_toggle_list_loop": "列表循环",
"play_toggle_random": "列表随机",
"play_toggle_list": "顺序播放",
"play_toggle_single_loop": "单曲循环",
"play_save_play_time": "记住播放进度",
"play_lyric_transition": "显示歌词翻译",
"play_quality_title": "启用时将优先播放320K品质的歌曲",
"play_quality": "优先播放高品质音乐",
"play_task_bar_title": "在任务栏上显示当前歌曲播放进度",
"play_task_bar": "任务栏播放进度条",
"play_lyric_lxlrc": "使用卡拉OK式歌词播放(如果支持)",
"play_quality": "优先播放320K品质的歌曲(如果支持)",
"play_task_bar": "在任务栏上显示当前歌曲播放进度",
"play_mediaDevice_title": "选择声音输出的媒体设备",
"play_mediaDevice": "音频输出",
"play_mediaDevice_remove_stop_play": "音频输出设备被改变时是否暂停播放",
"play_mediaDevice_remove_stop_play_title": "当前的声音输出设备被改变时是否暂停播放歌曲",
"play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲",
"desktop_lyric": "桌面歌词设置",
"desktop_lyric_enable": "显示歌词",
@@ -53,18 +48,13 @@
"desktop_lyric_lock_screen": "不允许歌词窗口拖出主屏幕之外",
"search": "搜索设置",
"search_hot_title": "是否显示热门搜索",
"search_hot": "热门搜索",
"search_history_title": "是否显示历史搜索记录",
"search_history": "搜索历史",
"search_focus_search_box_title": "启动时是否自动聚焦搜索框",
"search_focus_search_box": "启动时是否聚焦搜索框",
"search_hot": "显示热门搜索",
"search_history": "显示历史搜索记录",
"search_focus_search_box": "启动时自动聚焦搜索框",
"list": "列表设置",
"list_source_title": "是否显示歌曲源",
"list_source": "是否显示歌曲源(仅对我的音乐分类有效)",
"list_scroll_title": "是否记住播放列表滚动条位置",
"list_scroll": "记住列表滚动位置(仅对我的音乐分类有效)",
"list_source": "显示歌曲源(仅对我的音乐分类有效)",
"list_scroll": "记住播放列表滚动条位置(仅对我的音乐分类有效)",
"download": "下载设置",
"download_enable": "是否启用下载功能",
@@ -136,10 +126,11 @@
"other_tray_theme": "托盘图标样式",
"other_tray_theme_native": "纯色",
"other_tray_theme_origin": "原色",
"other_cache": "缓存大小(清理缓存后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
"other_cache_label": "软件已使用缓存大小:",
"other_cache_label_title": "当前已用缓存",
"other_cache_clear_btn": "清理缓存",
"other_resource_cache": "资源缓存管理(图片、音频等缓存,清理后图片等资源将需要重新下载,不建议清理,软件会根据磁盘空间动态管理缓存大小)",
"other_resource_cache_label": "软件已使用缓存大小:",
"other_resource_cache_clear_btn": "清理资源缓存",
"other_play_list_cache": "列表缓存管理(我的列表中已缓存的歌曲链接、播放代替源,清理后播放、下载歌曲时需要重新获取,没有歌曲播放相关的问题不要清理)",
"other_play_list_cache_clear_btn": "清理列表缓存信息",
"update": "软件更新",
"update_latest_label": "最新版本:",
@@ -150,7 +141,7 @@
"update_latest": "软件已是最新,尽情地体验吧~🥂",
"update_open_version_modal_btn": "打开更新窗口 🚀",
"update_checking": "检查更新中...",
"update_init": "更新初始化中...",
"update_init": "处理更新中...",
"about": "关于洛雪音乐",
@@ -8,5 +8,6 @@
"tip_2": "若遇到无法打开的歌单链接,欢迎反馈",
"tip_3": "酷狗源不支持用歌单ID打开,但支持酷狗码打开",
"play_all": "播放",
"play_later": "稍后播放",
"add_all": "收藏"
}
@@ -1,5 +1,6 @@
{
"list_play": "播放",
"list_play_later": "稍後播放",
"list_add_to": "添加到...",
"list_download": "下載",
"list_search": "搜索",
@@ -0,0 +1,10 @@
{
"title": "自定義源管理",
"readme": "源編寫說明:",
"note": "提示:雖然我們已經盡可能地隔離了腳本的運行環境,但導入包含惡意行為的腳本仍可能會影響你的系統,請謹慎導入。",
"btn_remove": "移除",
"btn_import": "導入",
"btn_export": "導出",
"import_file": "選擇音樂API腳本文件",
"noitem": "這裡竟然是空的 😲"
}
@@ -1,5 +1,6 @@
{
"menu_play": "播放",
"menu_play_later": "稍後播放",
"menu_start": "開始任務",
"menu_pause": "暫停任務",
"menu_file": "定位文件",
+1
View File
@@ -7,6 +7,7 @@
"lists_sync": "同步",
"lists_remove": "刪除",
"list_play": "播放",
"list_play_later": "稍後播放",
"list_copy_name": "複製歌曲名",
"list_add_to": "添加到...",
"list_move_to": "移動到...",
+2
View File
@@ -1,5 +1,6 @@
{
"list_play": "播放",
"list_play_later": "稍後播放",
"list_add_to": "添加到...",
"list_download": "下載",
"list_source_detail": "歌曲詳情頁",
@@ -10,6 +11,7 @@
"time": "時長",
"lossless": "無損",
"high_quality": "高品質",
"loding_list": "加載中...",
"no_item": "搜我所想~~😉",
"hot_search": "熱門搜索",
"history_search": "歷史搜索",
+36 -31
View File
@@ -1,7 +1,6 @@
{
"basic": "基本設置",
"basic_theme": "主題顏色",
"basic_animation_title": "彈出層的動畫效果",
"basic_animation": "彈出層隨機動畫",
"basic_show_animation": "顯示切換動畫",
"basic_source_title": "選擇音樂來源",
@@ -12,6 +11,10 @@
"basic_sourcename_real": "原名",
"basic_sourcename_alias": "別名",
"basic_sourcename": "音源名字",
"basic_source_status_success": "初始化成功",
"basic_source_status_initing": "初始化中",
"basic_source_status_failed": "初始化失敗",
"basic_source_user_api_btn": "自定義源管理",
"basic_window_size_title": "設置軟件窗口尺寸",
"basic_window_size": "窗口尺寸",
"basic_window_size_smaller": "較小",
@@ -21,46 +24,38 @@
"basic_window_size_larger": "較大",
"basic_window_size_oversized": "超大",
"basic_window_size_huge": "巨大",
"basic_to_tray_title": "關閉時不退出軟件將其最小化到系統托盤",
"basic_to_tray": "關閉時最小化到系統托盤",
"basic_to_tray": "關閉軟件時不退出軟件將其最小化到系統托盤",
"basic_lang_title": "軟件顯示的語言",
"basic_lang": "語言",
"basic_control_btn_position": "控制按鈕位置",
"basic_control_btn_position_left": "左邊",
"basic_control_btn_position_right": "右邊",
"play": "播放設置",
"play_toggle_title": "都不選時播放完當前歌曲就停止播放",
"play_toggle": "歌曲切換方式",
"play_toggle_list_loop": "列表循環",
"play_toggle_random": "列表隨機",
"play_toggle_list": "順序播放",
"play_toggle_single_loop": "單曲循環",
"play_save_play_time": "記住播放進度",
"play_lyric_transition": "顯示歌詞翻譯",
"play_quality_title": "啟用時將優先播放320K品質的歌曲",
"play_quality": "優先播放高品質音樂",
"play_task_bar_title": "在任務欄上顯示當前歌曲播放進度",
"play_task_bar": "任務欄播放進度條",
"play_lyric_lxlrc": "使用卡拉OK式歌詞播放(如果支持)",
"play_quality": "優先播放320K品質的歌曲(如果支持)",
"play_task_bar": "在任務欄上顯示當前歌曲播放進度",
"play_mediaDevice_title": "選擇聲音輸出的媒體設備",
"play_mediaDevice": "音頻輸出",
"play_mediaDevice_remove_stop_play": "音頻輸出設備被改變時是否暫停播放",
"play_mediaDevice_remove_stop_play_title": "當前的聲音輸出設備被改變時是否暫停播放歌曲",
"play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲",
"desktop_lyric": "桌面歌詞設置",
"desktop_lyric_enable": "顯示歌詞",
"desktop_lyric_lock": "鎖定歌詞",
"desktop_lyric_always_on_top": "使歌詞總是在其他窗口之上",
"desktop_lyric_lock_screen": "不允許歌詞窗口拖出主屏幕之外",
"search": "搜索設置",
"search_hot_title": "是否顯示熱門搜索",
"search_hot": "熱門搜索",
"search_history_title": "是否顯示歷史搜索記錄",
"search_history": "搜索歷史",
"search_focus_search_box_title": "啟動時是否自動聚焦搜索框",
"search_focus_search_box": "啟動時是否聚焦搜索框",
"search_hot": "顯示熱門搜索",
"search_history": "顯示歷史搜索記錄",
"search_focus_search_box": "啟動時自動聚焦搜索框",
"list": "列表設置",
"list_source_title": "是否顯示歌曲源",
"list_source": "是否顯示歌曲源(僅對我的音樂分類有效)",
"list_scroll_title": "是否記住播放列表滾動條位置",
"list_scroll": "記住列表滾動位置(僅對我的音樂分類有效)",
"list_source": "顯示歌曲源(僅對我的音樂分類有效)",
"list_scroll": "記住播放列表滾動條位置(僅對我的音樂分類有效)",
"download": "下載設置",
"download_enable": "是否啟用下載功能",
"download_path_title": "下載歌曲保存的路徑",
@@ -79,6 +74,7 @@
"download_name2": "歌手 - 歌名",
"download_name3": "歌名",
"download_select_save_path": "選擇歌曲保存路徑",
"hot_key": "快捷鍵設置",
"hot_key_local_title": "軟件內快捷鍵",
"hot_key_global_title": "全局快捷鍵",
@@ -98,15 +94,18 @@
"hot_key_desktop_lyric_toggle_visible": "開/關桌面歌詞",
"hot_key_desktop_lyric_toggle_lock": "桌面歌詞鎖定切換",
"hot_key_desktop_lyric_toggle_always_top": "桌面歌詞置頂切換",
"network": "網絡設置",
"network_proxy_title": "HTTP代理設置(亂設置軟件將無法聯網)",
"network_proxy_host": "主機",
"network_proxy_port": "端口",
"network_proxy_username": "用戶名",
"network_proxy_password": "密碼",
"odc": "強迫症設置",
"odc_clear_search_input": "離開搜索界面時清空搜索框",
"odc_clear_search_list": "離開搜索界面時清空搜索列表",
"backup": "備份與恢復",
"backup_part": "部分數據(列表數據包括試聽列表、收藏列表、用戶自定義列表,設置數據不包括快捷鍵設置)",
"backup_part_import_list": "導入列表",
@@ -121,15 +120,18 @@
"backup_part_import_setting_desc": "選擇配置文件",
"backup_part_export_setting_desc": "選擇設置保存位置",
"backup_part_import_list_desc": "選擇列表文件",
"backup_part_export_list_desc": "選擇列表保存位置",
"backup_part_export_list_desc": "選擇歌單保存位置",
"other": "其他",
"other_tray_theme": "托盤圖標樣式",
"other_tray_theme_native": "純色",
"other_tray_theme_origin": "原色",
"other_cache": "緩存大小(清理緩存後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
"other_cache_label": "軟件已使用緩存大小:",
"other_cache_label_title": "當前已用緩存",
"other_cache_clear_btn": "清理緩存",
"other_resource_cache": "資源緩存管理(圖片、音頻等緩存,清理後圖片等資源將需要重新下載,不建議清理,軟件會根據磁盤空間動態管理緩存大小)",
"other_resource_cache_label": "軟件已使用緩存大小:",
"other_resource_cache_clear_btn": "清理資源緩存",
"other_play_list_cache": "列表緩存管理(我的列表中已緩存的歌曲鏈接、播放代替源,清理後播放、下載歌曲時需要重新獲取,沒有歌曲播放相關的問題不要清理)",
"other_play_list_cache_clear_btn": "清理列表緩存信息",
"update": "軟件更新",
"update_latest_label": "最新版本:",
"update_unknown": "未知",
@@ -139,8 +141,11 @@
"update_latest": "軟件已是最新,盡情地體驗吧~🥂",
"update_open_version_modal_btn": "打開更新窗口 🚀",
"update_checking": "檢查更新中...",
"update_init": "更新初始化中...",
"update_init": "處理更新中...",
"about": "關於洛雪音樂",
"is_enable": "是否啟用",
"is_show": "是否顯示",
"click_open": "點擊打開",
@@ -8,5 +8,6 @@
"tip_2": "若遇到無法打開的歌單鏈接,歡迎反饋",
"tip_3": "酷狗源不支持用歌單ID打開,但支持酷狗碼打開",
"play_all": "播放",
"play_later": "稍後播放",
"add_all": "收藏"
}
+17 -2
View File
@@ -19,16 +19,31 @@ export default {
},
getVersionInfo2(state, retryNum = 0) {
return new Promise((resolve, reject) => {
httpGet('https://cdn.stsky.cn/lx-music/desktop/version.json', {
httpGet('https://gitee.com/lyswhut/lx-music-desktop-versions/raw/master/version.json', {
timeout: 20000,
}, (err, resp, body) => {
if (!err && !body.version) err = new Error(JSON.stringify(body))
if (err) {
return ++retryNum > 3
? reject(err)
? this.dispatch('getVersionInfo3').then(resolve).catch(reject)
: this.dispatch('getVersionInfo2', retryNum).then(resolve).catch(reject)
}
resolve(body)
})
})
},
getVersionInfo3(state, retryNum = 0) {
return new Promise((resolve, reject) => {
httpGet('https://cdn.stsky.cn/lx-music/desktop/version.json', {
timeout: 20000,
}, (err, resp, body) => {
if (err) {
return ++retryNum > 3
? reject(err)
: this.dispatch('getVersionInfo3', retryNum).then(resolve).catch(reject)
}
resolve(body)
})
})
},
}
+36 -21
View File
@@ -3,7 +3,7 @@ import fs from 'fs'
import path from 'path'
import music from '../../utils/music'
import { getMusicType } from '../../utils/music/utils'
import { setMeta, saveLrc } from '../../utils'
import { setMeta, saveLrc, getLyric, setLyric, getMusicUrl, setMusicUrl } from '../../utils'
// state
const state = {
@@ -147,15 +147,20 @@ const pauseTasks = async(store, list, runs = []) => {
await pauseTasks(store, list, runs)
}
const getUrl = (downloadInfo, isRefresh) => {
const url = downloadInfo.musicInfo.typeUrl[downloadInfo.type]
const getUrl = async(downloadInfo, isRefresh) => {
const cachedUrl = await getMusicUrl(downloadInfo.musicInfo, downloadInfo.type)
if (!downloadInfo.musicInfo._types[downloadInfo.type]) {
// 兼容旧版酷我源搜索列表过滤128k音质的bug
if (!(downloadInfo.musicInfo.source == 'kw' && downloadInfo.type == '128k')) return Promise.reject(new Error('该歌曲没有可下载的音频'))
if (!(downloadInfo.musicInfo.source == 'kw' && downloadInfo.type == '128k')) throw new Error('该歌曲没有可下载的音频')
// return Promise.reject(new Error('该歌曲没有可下载的音频'))
}
return url && !isRefresh ? Promise.resolve({ url }) : music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
return cachedUrl && !isRefresh
? cachedUrl
: music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise.then(({ url }) => {
setMusicUrl(downloadInfo.musicInfo, downloadInfo.type, url)
return url
})
}
// 修复 1.1.x版本 酷狗源歌词格式
@@ -179,12 +184,17 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => {
})
: Promise.resolve(),
isEmbedLyric
? downloadInfo.musicInfo.lrc
? Promise.resolve({ lyric: downloadInfo.musicInfo.lrc, tlyric: downloadInfo.musicInfo.tlrc || '' })
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.catch(err => {
console.log(err)
return null
})
? getLyric(downloadInfo.musicInfo).then(lrcInfo => {
return lrcInfo.lyric
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.then(({ lyric, tlyric, lxlyric }) => {
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
}).catch(err => {
console.log(err)
return null
})
})
: Promise.resolve(),
]
Promise.all(tasks).then(([imgUrl, lyrics = {}]) => {
@@ -205,9 +215,14 @@ const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => {
* @param {*} filePath
*/
const downloadLyric = (downloadInfo, filePath) => {
const promise = downloadInfo.musicInfo.lrc
? Promise.resolve({ lyric: downloadInfo.musicInfo.lrc, tlyric: downloadInfo.musicInfo.tlrc || '' })
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
const promise = getLyric(downloadInfo.musicInfo).then(lrcInfo => {
return lrcInfo.lyric
? Promise.resolve({ lyric: lrcInfo.lyric, tlyric: lrcInfo.tlyric || '' })
: music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.then(({ lyric, tlyric, lxlyric }) => {
setLyric(downloadInfo.musicInfo, { lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
})
})
promise.then(lrcs => {
if (lrcs.lyric) {
lrcs.lyric = fixKgLyric(lrcs.lyric)
@@ -218,12 +233,12 @@ const downloadLyric = (downloadInfo, filePath) => {
const refreshUrl = function(commit, downloadInfo) {
commit('setStatusText', { downloadInfo, text: '链接失效,正在刷新链接' })
getUrl(downloadInfo, true).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
getUrl(downloadInfo, true).then(url => {
commit('updateUrl', { downloadInfo, url })
commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
const dl = dls[downloadInfo.key]
if (!dl) return
dl.refreshUrl(result.url)
dl.refreshUrl(url)
dl.start().catch(err => {
commit('onError', { downloadInfo, errorMsg: err.message })
commit('setStatusText', { downloadInfo, text: err.message })
@@ -379,10 +394,10 @@ const actions = {
commit('setStatusText', { downloadInfo, text: '获取URL中...' })
let p = options.url
? Promise.resolve()
: getUrl(downloadInfo).then(result => {
commit('updateUrl', { downloadInfo, url: result.url })
if (!result.url) return Promise.reject(new Error('获取URL失败'))
options.url = result.url
: getUrl(downloadInfo).then(url => {
commit('updateUrl', { downloadInfo, url })
if (!url) return Promise.reject(new Error('获取URL失败'))
options.url = url
})
p.then(() => {
tryNum[downloadInfo.key] = 0
+33 -3
View File
@@ -1,3 +1,6 @@
import musicSdk from '../../utils/music'
import { clearLyric, clearMusicUrl } from '../../utils'
let allList = {}
window.allList = allList
@@ -48,7 +51,12 @@ const getters = {
// actions
const actions = {
getOtherSource({ state, commit }, musicInfo) {
return (musicInfo.otherSource && musicInfo.otherSource.length ? Promise.resolve(musicInfo.otherSource) : musicSdk.findMusic(musicInfo)).then(otherSource => {
commit('setOtherSource', { musicInfo, otherSource })
return otherSource
})
},
}
// mitations
@@ -142,9 +150,9 @@ const mutations = {
if (!targetList) return
targetList.list.splice(0, targetList.list.length)
},
updateMusicInfo(state, { id, index, data }) {
updateMusicInfo(state, { id, index, data, musicInfo = {} }) {
let targetList = allList[id]
if (!targetList) return
if (!targetList) return Object.assign(musicInfo, data)
Object.assign(targetList.list[index], data)
},
createUserList(state, { name, id = `userlist_${Date.now()}`, list = [], source, sourceListId }) {
@@ -191,6 +199,28 @@ const mutations = {
targetList.list.splice(sortNum - 1, 0, ...musicInfos)
},
clearCache() {
const lists = Object.values(allList)
for (const { list } of lists) {
for (const item of list) {
if (item.otherSource) item.otherSource = null
if (item.typeUrl['128k']) delete item.typeUrl['128k']
if (item.typeUrl['320k']) delete item.typeUrl['320k']
if (item.typeUrl.flac) delete item.typeUrl.flac
if (item.typeUrl.wav) delete item.typeUrl.wav
// v1.8.2以前的Lyric
if (item.lxlrc) delete item.lxlrc
if (item.lrc) delete item.lrc
if (item.tlrc) delete item.tlrc
}
}
clearMusicUrl()
clearLyric()
},
setOtherSource(state, { musicInfo, otherSource }) {
musicInfo.otherSource = otherSource
},
}
export default {
+324 -38
View File
@@ -1,4 +1,13 @@
import path from 'path'
import music from '../../utils/music'
import {
getRandom,
checkPath,
getLyric as getStoreLyric,
setLyric,
setMusicUrl,
getMusicUrl,
} from '../../utils'
// state
const state = {
@@ -10,107 +19,360 @@ const state = {
changePlay: false,
isShowPlayerDetail: false,
playedList: [],
playMusicInfo: null,
tempPlayList: [],
}
let urlRequest
let picRequest
let lrcRequest
// let picRequest
// let lrcRequest
const filterList = async({ playedList, listInfo, savePath, commit }) => {
// if (this.list.listName === null) return
let list
let canPlayList = []
const filteredPlayedList = playedList.filter(({ listId, isTempPlay }) => listInfo.id === listId && !isTempPlay).map(({ musicInfo }) => musicInfo)
if (listInfo.id == 'download') {
list = []
for (const item of listInfo.list) {
const filePath = path.join(savePath, item.fileName)
if (!await checkPath(filePath) || !item.isComplate || /\.ape$/.test(filePath)) continue
canPlayList.push(item)
// 排除已播放音乐
let index = filteredPlayedList.indexOf(item)
if (index > -1) {
filteredPlayedList.splice(index, 1)
continue
}
list.push(item)
}
} else {
list = listInfo.list.filter(s => {
// if (!assertApiSupport(s.source)) return false
canPlayList.push(s)
let index = filteredPlayedList.indexOf(s)
if (index > -1) {
filteredPlayedList.splice(index, 1)
return false
}
return true
})
}
if (!list.length && playedList.length) {
commit('clearPlayedList')
return canPlayList
}
return list
}
const getPic = function(musicInfo, retryedSource = [], originMusic) {
// console.log(musicInfo.source)
return music[musicInfo.source].getPic(musicInfo).promise.catch(err => {
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
return this.dispatch('list/getOtherSource', musicInfo).then(otherSource => {
if (!originMusic) originMusic = musicInfo
console.log('find otherSource', otherSource)
if (otherSource.length) {
for (const item of otherSource) {
if (retryedSource.includes(item.source)) continue
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
return getPic.call(this, item, retryedSource, originMusic)
}
}
return Promise.reject(err)
})
})
}
const getLyric = function(musicInfo, retryedSource = [], originMusic) {
return music[musicInfo.source].getLyric(musicInfo).promise.catch(err => {
if (!retryedSource.includes(musicInfo.source)) retryedSource.push(musicInfo.source)
return this.dispatch('list/getOtherSource', musicInfo).then(otherSource => {
if (!originMusic) originMusic = musicInfo
console.log('find otherSource', otherSource)
if (otherSource.length) {
for (const item of otherSource) {
if (retryedSource.includes(item.source)) continue
console.log('try toggle to: ', item.source, item.name, item.singer, item.interval)
return getLyric.call(this, item, retryedSource, originMusic)
}
}
return Promise.reject(err)
})
})
}
// getters
const getters = {
list: state => state.listInfo.list,
listId: state => state.listInfo.id,
changePlay: satte => satte.changePlay,
playIndex: state => state.playIndex,
playInfo(state) {
if (state.playMusicInfo == null) return { listId: null, playIndex: -1, playListId: null, listPlayIndex: -1, isPlayList: false, musicInfo: null }
const playListId = state.listInfo.id
let listId = state.playMusicInfo.listId
const isTempPlay = !!state.playMusicInfo.isTempPlay
const isPlayList = listId === playListId
let playIndex = -1
let listPlayIndex = state.playIndex
if (listId != '__temp__') {
if (isPlayList) {
playIndex = state.listInfo.list.indexOf(state.playMusicInfo.musicInfo)
if (!isTempPlay) listPlayIndex = playIndex
} else {
let list = window.allList[listId]
if (list) playIndex = list.list.indexOf(state.playMusicInfo.musicInfo)
}
}
// console.log({
// listId,
// playIndex,
// playListId,
// listPlayIndex,
// isPlayList,
// isTempPlay,
// musicInfo: state.playMusicInfo.musicInfo,
// })
return {
listId,
playIndex,
playListId,
listPlayIndex,
isPlayList,
isTempPlay,
musicInfo: state.playMusicInfo.musicInfo,
}
},
isShowPlayerDetail: state => state.isShowPlayerDetail,
playMusicInfo: state => state.playMusicInfo,
playedList: state => state.playedList,
tempPlayList: state => state.tempPlayList,
}
// actions
const actions = {
getUrl({ commit, state }, { musicInfo, originMusic, type, isRefresh }) {
async getUrl({ commit, state }, { musicInfo, originMusic, type, isRefresh }) {
if (!musicInfo._types[type]) {
// 兼容旧版酷我源搜索列表过滤128k音质的bug
if (!(musicInfo.source == 'kw' && type == '128k')) return Promise.reject(new Error('该歌曲没有可播放的音频'))
if (!(musicInfo.source == 'kw' && type == '128k')) throw new Error('该歌曲没有可播放的音频')
// return Promise.reject(new Error('该歌曲没有可播放的音频'))
}
if (urlRequest && urlRequest.cancelHttp) urlRequest.cancelHttp()
if (musicInfo.typeUrl[type] && !isRefresh) return Promise.resolve()
const cachedUrl = await getMusicUrl(musicInfo, type)
if (cachedUrl && !isRefresh) return cachedUrl
urlRequest = music[musicInfo.source].getMusicUrl(musicInfo, type)
return urlRequest.promise.then(result => {
if (originMusic) commit('setUrl', { musicInfo: originMusic, url: result.url, type })
commit('setUrl', { musicInfo, url: result.url, type })
return urlRequest.promise.then(({ url }) => {
if (originMusic) commit('setUrl', { musicInfo: originMusic, url, type })
commit('setUrl', { musicInfo, url, type })
urlRequest = null
return url
}).catch(err => {
urlRequest = null
return Promise.reject(err)
})
},
getPic({ commit, state }, musicInfo) {
if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
picRequest = music[musicInfo.source].getPic(musicInfo)
return picRequest.promise.then(url => {
picRequest = null
// if (picRequest && picRequest.cancelHttp) picRequest.cancelHttp()
// picRequest = music[musicInfo.source].getPic(musicInfo)
return getPic.call(this, musicInfo).then(url => {
// picRequest = null
commit('getPic', { musicInfo, url })
}).catch(err => {
picRequest = null
// picRequest = null
return Promise.reject(err)
})
},
getLrc({ commit, state }, musicInfo) {
if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
if (musicInfo.lrc && musicInfo.tlrc != null) {
if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc })
}
return Promise.resolve()
async getLrc({ commit, state }, musicInfo) {
const lrcInfo = await getStoreLyric(musicInfo)
// if (lrcRequest && lrcRequest.cancelHttp) lrcRequest.cancelHttp()
if (lrcInfo.lyric && lrcInfo.tlyric != null) {
// if (musicInfo.lrc.startsWith('\ufeff[id:$00000000]')) {
// let str = musicInfo.lrc.replace('\ufeff[id:$00000000]\n', '')
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
// } else if (musicInfo.lrc.startsWith('[id:$00000000]')) {
// let str = musicInfo.lrc.replace('[id:$00000000]\n', '')
// commit('setLrc', { musicInfo, lyric: str, tlyric: musicInfo.tlrc, lxlyric: musicInfo.tlrc })
// }
if ((lrcInfo.lxlyric == null && musicInfo.source != 'kg') || lrcInfo.lxlyric != null) return lrcInfo
}
lrcRequest = music[musicInfo.source].getLyric(musicInfo)
return lrcRequest.promise.then(({ lyric, tlyric }) => {
lrcRequest = null
commit('setLrc', { musicInfo, lyric, tlyric })
// lrcRequest = music[musicInfo.source].getLyric(musicInfo)
return getLyric.call(this, musicInfo).then(({ lyric, tlyric, lxlyric }) => {
// lrcRequest = null
commit('setLrc', { musicInfo, lyric, tlyric, lxlyric })
return { lyric, tlyric, lxlyric }
}).catch(err => {
lrcRequest = null
// lrcRequest = null
return Promise.reject(err)
})
},
async playPrev({ state, rootState, commit, getters }) {
const currentListId = state.listInfo.id
const currentList = state.listInfo.list
if (state.playedList.length) {
// 从已播放列表移除播放列表已删除的歌曲
let index
for (index = state.playedList.indexOf(state.playMusicInfo) - 1; index > -1; index--) {
const playMusicInfo = state.playedList[index]
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
commit('removePlayedList', index)
continue
}
break
}
if (index > -1) {
commit('setPlayMusicInfo', state.playedList[index])
return
}
}
let filteredList = await filterList({
listInfo: state.listInfo,
playedList: state.playedList,
savePath: rootState.setting.download.savePath,
commit,
})
if (!filteredList.length) return commit('setPlayMusicInfo', null)
const playInfo = getters.playInfo
let currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
if (currentIndex == -1) currentIndex = 0
let nextIndex = currentIndex
if (!playInfo.isTempPlay) {
switch (rootState.setting.player.togglePlayMethod) {
case 'random':
nextIndex = getRandom(0, filteredList.length)
break
case 'listLoop':
case 'list':
nextIndex = currentIndex === 0 ? filteredList.length - 1 : currentIndex - 1
break
case 'singleLoop':
break
default:
nextIndex = -1
return
}
if (nextIndex < 0) return
}
commit('setPlayMusicInfo', {
musicInfo: filteredList[nextIndex],
listId: currentListId,
})
},
async playNext({ state, rootState, commit, getters }) {
if (state.tempPlayList.length) {
const playMusicInfo = state.tempPlayList[0]
commit('removeTempPlayList', 0)
commit('setPlayMusicInfo', playMusicInfo)
return
}
const currentListId = state.listInfo.id
const currentList = state.listInfo.list
if (state.playedList.length) {
// 从已播放列表移除播放列表已删除的歌曲
let index
for (index = state.playedList.indexOf(state.playMusicInfo) + 1; index < state.playedList.length; index++) {
const playMusicInfo = state.playedList[index]
if (playMusicInfo.listId == currentListId && !currentList.includes(playMusicInfo.musicInfo)) {
commit('removePlayedList', index)
continue
}
break
}
if (index < state.playedList.length) {
commit('setPlayMusicInfo', state.playedList[index])
return
}
}
let filteredList = await filterList({
listInfo: state.listInfo,
playedList: state.playedList,
savePath: rootState.setting.download.savePath,
commit,
})
if (!filteredList.length) return commit('setPlayMusicInfo', null)
const playInfo = getters.playInfo
const currentIndex = filteredList.indexOf(currentList[playInfo.listPlayIndex])
let nextIndex = currentIndex
switch (rootState.setting.player.togglePlayMethod) {
case 'listLoop':
nextIndex = currentIndex === filteredList.length - 1 ? 0 : currentIndex + 1
break
case 'random':
nextIndex = getRandom(0, filteredList.length)
break
case 'list':
nextIndex = currentIndex === filteredList.length - 1 ? -1 : currentIndex + 1
break
case 'singleLoop':
break
default:
nextIndex = -1
return
}
if (nextIndex < 0) return
commit('setPlayMusicInfo', {
musicInfo: filteredList[nextIndex],
listId: currentListId,
})
},
}
// mitations
const mutations = {
setUrl(state, datas) {
datas.musicInfo.typeUrl = Object.assign({}, datas.musicInfo.typeUrl, { [datas.type]: datas.url })
setUrl(state, { musicInfo, type, url }) {
setMusicUrl(musicInfo, type, url)
},
getPic(state, datas) {
datas.musicInfo.img = datas.url
},
setLrc(state, datas) {
datas.musicInfo.lrc = datas.lyric
datas.musicInfo.tlrc = datas.tlyric
// datas.musicInfo.lrc = datas.lyric
// datas.musicInfo.tlrc = datas.tlyric
// datas.musicInfo.lxlrc = datas.lxlyric
setLyric(datas.musicInfo, {
lyric: datas.lyric,
tlyric: datas.tlyric,
lxlyric: datas.lxlyric,
})
},
setList(state, { list, index }) {
state.playMusicInfo = {
musicInfo: list.list[index],
listId: list.id,
}
state.listInfo = list
state.playIndex = index
state.changePlay = true
// console.log(state.playMusicInfo)
if (state.playedList.length) this.commit('player/clearPlayedList')
if (state.tempPlayList.length) this.commit('player/clearTempPlayeList')
},
setPlayIndex(state, index) {
state.playIndex = index
state.changePlay = true
// console.log(state.changePlay)
},
fixPlayIndex(state, index) {
state.playIndex = index
setChangePlay(state) {
state.changePlay = true
},
resetChangePlay(state) {
state.changePlay = false
},
setPlayedList(state, item) {
// console.log(item)
if (state.playedList.includes(item)) return
state.playedList.push(item)
},
@@ -118,11 +380,35 @@ const mutations = {
state.playedList.splice(index, 1)
},
clearPlayedList(state) {
state.playedList = []
state.playedList.splice(0, state.playedList.length)
},
visiblePlayerDetail(state, visible) {
state.isShowPlayerDetail = visible
},
setTempPlayList(state, list) {
state.tempPlayList.push(...list.map(({ musicInfo, listId }) => ({ musicInfo, listId, isTempPlay: true })))
if (!state.playMusicInfo) this.commit('player/playNext')
},
removeTempPlayList(state, index) {
state.tempPlayList.splice(index, 1)
},
clearTempPlayeList(state) {
state.tempPlayList.splice(0, state.tempPlayList.length)
},
setPlayMusicInfo(state, playMusicInfo) {
let playIndex = state.playIndex
if (playMusicInfo == null) {
playIndex = -1
} else {
let listId = playMusicInfo.listId
if (listId != '__temp__' && listId === state.listInfo.id) playIndex = state.listInfo.list.indexOf(playMusicInfo.musicInfo)
}
state.playMusicInfo = playMusicInfo
state.playIndex = playIndex
state.changePlay = true
},
}
export default {
+2 -2
View File
@@ -127,7 +127,7 @@ const actions = {
}
}))
}
Promise.all(task).then(results => commit('setLists', { results, page }))
return Promise.all(task).then(results => commit('setLists', { results, page }))
} else {
return music[rootState.setting.search.searchSource].musicSearch.search(text, page, limit).catch(error => {
console.log(error)
@@ -167,7 +167,7 @@ const mutations = {
list.push(...source.list)
pages.push(source.allPage)
total += source.total
limit += source.limit
// limit = Math.max(source.limit, limit)
}
state.allPage = Math.max(...pages)
state.total = total
+28 -2
View File
@@ -232,6 +232,7 @@ export const objectDeepMerge = (target, source, mergedObj) => {
* @param {*} url
*/
export const openUrl = url => {
if (!/^https?:\/\//.test(url)) return
shell.openExternal(url)
}
@@ -370,7 +371,7 @@ export const clearCache = () => rendererInvoke(NAMES.mainWindow.clear_cache)
export const setWindowSize = (width, height) => rendererSend(NAMES.mainWindow.set_window_size, { width, height })
export const getProxyInfo = () => window.globalObj.proxy.enable
export const getProxyInfo = () => window.globalObj.proxy.enable && window.globalObj.proxy.host
? `http://${window.globalObj.proxy.username}:${window.globalObj.proxy.password}@${window.globalObj.proxy.host}:${window.globalObj.proxy.port};`
: undefined
@@ -378,7 +379,7 @@ export const getProxyInfo = () => window.globalObj.proxy.enable
export const assertApiSupport = source => window.globalObj.qualityList[source] != undefined
export const getSetting = () => rendererInvoke(NAMES.mainWindow.get_setting)
export const saveSetting = () => rendererInvoke(NAMES.mainWindow.set_app_setting)
export const saveSetting = setting => rendererInvoke(NAMES.mainWindow.set_app_setting, setting)
export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).catch(error => {
rendererInvoke(NAMES.mainWindow.get_data_path).then(dataPath => {
@@ -395,3 +396,28 @@ export const getPlayList = () => rendererInvoke(NAMES.mainWindow.get_playlist).c
return rendererInvoke(NAMES.mainWindow.get_playlist, true)
})
// 解析URL参数为对象
export const parseUrlParams = str => {
const params = {}
if (typeof str !== 'string') return params
const paramsArr = str.split('&')
for (const param of paramsArr) {
let [key, value] = param.split('=')
params[key] = value
}
return params
}
export const getLyric = musicInfo => rendererInvoke(NAMES.mainWindow.get_lyric, `${musicInfo.source}_${musicInfo.songmid}`)
export const setLyric = (musicInfo, { lyric, tlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric, {
id: `${musicInfo.source}_${musicInfo.songmid}`,
lyrics: { lyric, tlyric, lxlyric },
})
export const clearLyric = () => rendererSend(NAMES.mainWindow.clear_lyric)
export const getMusicUrl = (musicInfo, type) => rendererInvoke(NAMES.mainWindow.get_music_url, `${musicInfo.source}_${musicInfo.songmid}_${type}`)
export const setMusicUrl = (musicInfo, type, url) => rendererSend(NAMES.mainWindow.save_music_url, {
id: `${musicInfo.source}_${musicInfo.songmid}_${type}`,
url,
})
export const clearMusicUrl = () => rendererSend(NAMES.mainWindow.clear_music_url)
@@ -0,0 +1,291 @@
const { getNow, TimeoutTools } = require('./utils')
// const fontFormateRxp = /(?=<\d+,\d+>).*?/g
const fontSplitRxp = /(?=<\d+,\d+>).*?/g
const timeRxp = /<(\d+),(\d+)>/
// Create animation
const createAnimation = (dom, duration) => new window.Animation(new window.KeyframeEffect(dom, [
{ backgroundSize: '0 100%' },
{ backgroundSize: '100% 100%' },
], {
duration,
easing: 'linear',
},
), document.timeline)
// https://jsfiddle.net/ceqpnbky/
// https://jsfiddle.net/ceqpnbky/1/
module.exports = class FontPlayer {
constructor({ lyric = '', translationLyric = '', lineClassName = '', fontClassName = '', translationClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
this.lyric = lyric
this.translationLyric = translationLyric
this.lineClassName = lineClassName
this.fontClassName = fontClassName
this.translationClassName = translationClassName
this.lineModeClassName = lineModeClassName
this.shadowContent = shadowContent
this.shadowClassName = shadowClassName
this.isPlay = false
this.curFontNum = 0
this.maxFontNum = 0
this._performanceTime = 0
this._performanceOffsetTime = 0
this.fontContent = null
this.timeoutTools = new TimeoutTools()
this.waitPlayTimeout = new TimeoutTools()
this._init()
}
_init() {
if (this.lyric == null) this.lyric = ''
this.isLineMode = false
this.lineContent = document.createElement('div')
if (this.lineClassName) this.lineContent.classList.add(this.lineClassName)
this.fontContent = document.createElement('div')
this.fontContent.style = 'position:relative;display:inline-block;'
if (this.fontClassName) this.fontContent.classList.add(this.fontClassName)
if (this.shadowContent) {
this.fontShadowContent = document.createElement('div')
this.fontShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
this.fontShadowContent.className = this.shadowClassName
this.fontContent.appendChild(this.fontShadowContent)
}
this.lineContent.appendChild(this.fontContent)
if (this.translationLyric) {
this.translationContent = document.createElement('div')
this.translationContent.style = 'position:relative;display:inline-block;'
this.translationContent.className = this.translationClassName
this.translationContent.textContent = this.translationLyric
this.lineContent.appendChild(document.createElement('br'))
this.lineContent.appendChild(this.translationContent)
if (this.shadowContent) {
this.translationShadowContent = document.createElement('div')
this.translationShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
this.translationShadowContent.className = this.shadowClassName
this.translationShadowContent.textContent = this.translationLyric
this.translationContent.appendChild(this.translationShadowContent)
}
}
this._parseLyric()
}
_parseLyric() {
const fonts = this.lyric.split(fontSplitRxp)
// console.log(fonts)
this.maxFontNum = fonts.length - 1
this.fonts = []
let text
for (const font of fonts) {
text = font.replace(timeRxp, '')
if (RegExp.$2 == '') return this._handleLineParse()
const time = parseInt(RegExp.$2)
const dom = document.createElement('span')
let shadowDom
dom.textContent = text
const animation = createAnimation(dom, time)
this.fontContent.appendChild(dom)
if (this.shadowContent) {
shadowDom = document.createElement('span')
shadowDom.textContent = text
this.fontShadowContent.appendChild(shadowDom)
}
// dom.style = shadowDom.style = this.fontStyle
// dom.className = shadowDom.className = this.fontClassName
this.fonts.push({
text,
startTime: parseInt(RegExp.$1),
time,
dom,
shadowDom,
animation,
})
}
// console.log(this.fonts)
}
_handleLineParse() {
this.isLineMode = true
const dom = document.createElement('span')
let shadowDom
dom.classList.add(this.lineModeClassName)
dom.textContent = this.lyric
if (this.shadowContent) {
shadowDom = document.createElement('span')
shadowDom.textContent = this.lyric
this.fontShadowContent.appendChild(shadowDom)
}
this.fontContent.appendChild(dom)
this.fonts.push({
text: this.lyric,
dom,
shadowDom,
})
}
_currentTime() {
return getNow() - this._performanceTime + this._performanceOffsetTime
}
_findcurFontNum(curTime) {
const length = this.fonts.length
for (let index = 0; index < length; index++) if (curTime <= this.fonts[index].startTime) return index === 0 ? 0 : index - 1
return length - 1
}
_handlePlayMaxFontNum() {
let curFont = this.fonts[this.curFontNum]
// console.log(curFont.text)
const currentTime = this._currentTime()
const driftTime = currentTime - curFont.startTime
if (currentTime > curFont.startTime + curFont.time) {
this._handlePlayFont(curFont, driftTime, true)
this.pause()
} else {
this._handlePlayFont(curFont, driftTime)
this.isPlay = false
}
}
_handlePlayFont(font, currentTime, toFinishe) {
switch (font.animation.playState) {
case 'finished':
break
case 'idle':
font.dom.style.backgroundSize = '100% 100%'
if (!toFinishe) font.animation.play()
break
default:
if (toFinishe) {
font.animation.cancel()
} else {
font.animation.currentTime = currentTime
font.animation.play()
}
break
}
}
_handlePlayLine(isPlayed) {
this.isPlay = false
this.fonts[0].dom.style.backgroundSize = isPlayed ? '100% 100%' : '100% 0'
}
_handlePauseFont(font) {
if (font.animation.playState == 'running') font.animation.pause()
}
_refresh() {
this.curFontNum++
// console.log('curFontNum time', this.fonts[this.curFontNum].time)
if (this.curFontNum === this.maxFontNum) return this._handlePlayMaxFontNum()
let curFont = this.fonts[this.curFontNum]
let nextFont = this.fonts[this.curFontNum + 1]
// console.log(curFont, nextFont, this.curFontNum, this.maxFontNum)
const currentTime = this._currentTime()
// console.log(curFont.text)
const driftTime = currentTime - curFont.startTime
// console.log(currentTime, driftTime)
if (driftTime >= 0 || this.curFontNum == 0) {
this.delay = nextFont.startTime - curFont.startTime - driftTime
if (this.delay > 0) {
this._handlePlayFont(curFont, driftTime)
this.timeoutTools.start(() => {
this._refresh()
}, this.delay)
return
}
} else if (this.curFontNum == 0) {
this.curFontNum--
this.waitPlayTimeout.start(() => {
this._refresh()
}, -driftTime)
return
}
this.curFontNum = this._findcurFontNum(currentTime)
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
this.curFontNum--
this._refresh()
}
play(curTime = 0) {
// console.log('play', curTime)
if (!this.fonts.length) return
this.pause()
if (this.isLineMode) return this._handlePlayLine(true)
this.isPlay = true
this._performanceTime = getNow() - curTime
this._performanceOffsetTime = 0
if (this._performanceTime < 0) {
this._performanceOffsetTime = -this._performanceTime
this._performanceTime = 0
}
this.curFontNum = this._findcurFontNum(curTime)
for (let i = this.curFontNum; i > -1; i--) {
this._handlePlayFont(this.fonts[i], 0, true)
}
for (let i = this.curFontNum, len = this.fonts.length; i < len; i++) {
let font = this.fonts[i]
font.animation.cancel()
font.dom.style.backgroundSize = '0 100%'
}
this.curFontNum--
this._refresh()
}
pause() {
if (!this.isPlay) return
this.isPlay = false
this.timeoutTools.clear()
this.waitPlayTimeout.clear()
this._handlePauseFont(this.fonts[this.curFontNum])
if (this.curFontNum === this.maxLine) return
const curFontNum = this._findcurFontNum(this._currentTime())
if (this.curFontNum === curFontNum) return
for (let i = 0; i < this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
}
finish() {
this.pause()
if (this.isLineMode) return this._handlePlayLine(true)
for (const font of this.fonts) {
font.animation.cancel()
font.dom.style.backgroundSize = '100% 100%'
}
this.curFontNum = this.maxFontNum
}
reset() {
this.pause()
if (this.isLineMode) return this._handlePlayLine(false)
for (const font of this.fonts) {
font.animation.cancel()
font.dom.style.backgroundSize = '0 100%'
}
this.curFontNum = 0
}
}
@@ -0,0 +1,167 @@
const LinePlayer = require('./line-player')
const FontPlayer = require('./font-player')
const fontTimeExp = /<(\d+),(\d+)>/g
module.exports = class Lyric {
constructor({
lyric = '',
translationLyric = '',
offset = 150,
lineClassName = '',
fontClassName = 'font',
translationClassName = 'translation',
activeLineClassName = 'active',
lineModeClassName = 'line',
shadowClassName = '',
shadowContent = false,
onPlay = function() { },
onSetLyric = function() { },
}) {
this.lyric = lyric
this.translationLyric = translationLyric
this.offset = offset
this.onPlay = onPlay
this.onSetLyric = onSetLyric
this.lineClassName = lineClassName
this.fontClassName = fontClassName
this.translationClassName = translationClassName
this.activeLineClassName = activeLineClassName
this.lineModeClassName = lineModeClassName
this.shadowClassName = shadowClassName
this.shadowContent = shadowContent
this.playingLineNum = -1
this.isLineMode = false
}
_init() {
this.playingLineNum = -1
this.isLineMode = false
if (this.linePlayer) {
this.linePlayer.setLyric(this.lyric, this.translationLyric)
} else {
this.linePlayer = new LinePlayer({
lyric: this.lyric,
translationLyric: this.translationLyric,
offset: this.offset,
onPlay: this._handleLinePlayerOnPlay,
onSetLyric: this._handleLinePlayerOnSetLyric,
})
}
}
_handleLinePlayerOnPlay = (num, text, curTime) => {
if (this.isLineMode) {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum; i > num - 1; i--) {
const font = this._lineFonts[i]
font.reset()
font.lineContent.classList.remove(this.activeLineClassName)
}
} else if (num > this.playingLineNum + 1) {
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
const font = this._lineFonts[i]
font.reset()
font.lineContent.classList.remove(this.activeLineClassName)
}
} else if (this.playingLineNum > -1) {
const font = this._lineFonts[this.playingLineNum]
font.reset()
font.lineContent.classList.remove(this.activeLineClassName)
}
} else {
if (num < this.playingLineNum + 1) {
for (let i = this.playingLineNum; i > num - 1; i--) {
const font = this._lineFonts[i]
font.lineContent.classList.remove(this.activeLineClassName)
font.reset()
}
} else if (num > this.playingLineNum + 1) {
for (let i = Math.max(this.playingLineNum, 0); i < num; i++) {
const font = this._lineFonts[i]
font.lineContent.classList.remove(this.activeLineClassName)
font.finish()
}
} else if (this.playingLineNum > -1) {
const font = this._lineFonts[this.playingLineNum]
font.lineContent.classList.remove(this.activeLineClassName)
}
}
this.playingLineNum = num
const font = this._lineFonts[num]
font.lineContent.classList.add(this.activeLineClassName)
font.play(curTime - this._lines[num].time)
this.onPlay(num, this._lines[num].text)
}
_handleLinePlayerOnSetLyric = lyricLines => {
// console.log(lyricLines)
// this._lines = lyricsLines
this.isLineMode = lyricLines.length && !/^<\d+,\d+>/.test(lyricLines[0].text)
this._lineFonts = []
if (this.isLineMode) {
this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({
lyric: line.text,
translationLyric: line.translation,
lineClassName: this.lineClassName,
fontClassName: this.fontClassName,
translationClassName: this.translationClassName,
lineModeClassName: this.lineModeClassName,
shadowClassName: this.shadowClassName,
shadowContent: this.shadowContent,
})
this._lineFonts.push(fontPlayer)
return {
text: line.text,
time: line.time,
dom_line: fontPlayer.lineContent,
}
})
} else {
this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({
lyric: line.text,
translationLyric: line.translation,
lineClassName: this.lineClassName,
fontClassName: this.fontClassName,
translationClassName: this.translationClassName,
shadowClassName: this.shadowClassName,
shadowContent: this.shadowContent,
})
this._lineFonts.push(fontPlayer)
return {
text: line.text.replace(fontTimeExp, ''),
time: line.time,
translation: line.translation,
dom_line: fontPlayer.lineContent,
}
})
}
this.onSetLyric(this._lines)
}
play(curTime) {
if (!this.linePlayer) return
this.linePlayer.play(curTime)
}
pause() {
if (!this.linePlayer) return
this.linePlayer.pause()
if (this.playingLineNum > -1) this._lineFonts[this.playingLineNum].pause()
}
setLyric(lyric, translationLyric) {
this.lyric = lyric
this.translationLyric = translationLyric
this._init()
}
}
@@ -0,0 +1,177 @@
const { getNow, TimeoutTools } = require('./utils')
const timeExp = /^\[([\d:.]*)\]{1}/g
const tagRegMap = {
title: 'ti',
artist: 'ar',
album: 'al',
offset: 'offset',
by: 'by',
}
const timeoutTools = new TimeoutTools()
module.exports = class LinePlayer {
constructor({ lyric = '', translationLyric = '', offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
this.lyric = lyric
this.translationLyric = translationLyric
this.tags = {}
this.lines = null
this.translationLines = null
this.onPlay = onPlay
this.onSetLyric = onSetLyric
this.isPlay = false
this.curLineNum = 0
this.maxLine = 0
this.offset = offset
this.isOffseted = false
this._performanceTime = 0
this._performanceOffsetTime = 0
this._init()
}
_init() {
if (this.lyric == null) this.lyric = ''
if (this.translationLyric == null) this.translationLyric = ''
this._initTag()
this._initLines()
this.onSetLyric(this.lines)
}
_initTag() {
for (let tag in tagRegMap) {
const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i'))
this.tags[tag] = (matches && matches[1]) || ''
}
}
_initLines() {
this.lines = []
this.translationLines = []
const lines = this.lyric.split('\n')
const linesMap = {}
// const translationLines = this.translationLyric.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()
let result = timeExp.exec(line)
if (result) {
const text = line.replace(timeExp, '').trim()
if (text) {
const timeStr = RegExp.$1
const timeArr = timeStr.split(':')
if (timeArr.length < 3) timeArr.unshift(0)
if (timeArr[2].indexOf('.') > -1) {
timeArr.push(...timeArr[2].split('.'))
timeArr.splice(2, 1)
}
linesMap[timeStr] = {
time: parseInt(timeArr[0]) * 60 * 60 * 1000 + parseInt(timeArr[1]) * 60 * 1000 + parseInt(timeArr[2]) * 1000 + parseInt(timeArr[3] || 0),
text,
}
}
}
}
const translationLines = this.translationLyric.split('\n')
for (let i = 0; i < translationLines.length; i++) {
const line = translationLines[i].trim()
let result = timeExp.exec(line)
if (result) {
const text = line.replace(timeExp, '').trim()
if (text) {
const timeStr = RegExp.$1
const targetLine = linesMap[timeStr]
if (targetLine) targetLine.translation = text
}
}
}
this.lines = Object.values(linesMap)
this.lines.sort((a, b) => {
return a.time - b.time
})
this.maxLine = this.lines.length - 1
}
_currentTime() {
return getNow() - this._performanceTime + this._performanceOffsetTime
}
_findCurLineNum(curTime) {
const length = this.lines.length
for (let index = 0; index < length; index++) if (curTime <= this.lines[index].time) return index === 0 ? 0 : index - 1
return length - 1
}
_handleMaxLine() {
this.onPlay(this.curLineNum, this.lines[this.curLineNum].text, this._currentTime())
this.pause()
}
_refresh() {
this.curLineNum++
// console.log('curLineNum time', this.lines[this.curLineNum].time)
let curLine = this.lines[this.curLineNum]
let nextLine = this.lines[this.curLineNum + 1]
const currentTime = this._currentTime()
const driftTime = currentTime - curLine.time
if (driftTime >= 0 || this.curLineNum === 0) {
if (this.curLineNum === this.maxLine) return this._handleMaxLine()
this.delay = nextLine.time - curLine.time - driftTime
if (this.delay > 0) {
if (!this.isOffseted && this.delay >= this.offset) {
this._performanceOffsetTime += this.offset
this.delay -= this.offset
this.isOffseted = true
}
timeoutTools.start(() => {
this._refresh()
}, this.delay)
this.onPlay(this.curLineNum, curLine.text, currentTime)
return
}
}
this.curLineNum = this._findCurLineNum(currentTime) - 1
this._refresh()
}
play(curTime = 0) {
if (!this.lines.length) return
this.pause()
this.isPlay = true
this._performanceOffsetTime = 0
this._performanceTime = getNow() - curTime
if (this._performanceTime < 0) {
this._performanceOffsetTime = -this._performanceTime
this._performanceTime = 0
}
this.curLineNum = this._findCurLineNum(curTime) - 1
this._refresh()
}
pause() {
if (!this.isPlay) return
this.isPlay = false
this.isOffseted = false
timeoutTools.clear()
if (this.curLineNum === this.maxLine) return
const currentTime = this._currentTime()
const curLineNum = this._findCurLineNum(currentTime)
if (this.curLineNum !== curLineNum) {
this.curLineNum = curLineNum
this.onPlay(curLineNum, this.lines[curLineNum].text, currentTime)
}
}
setLyric(lyric, translationLyric) {
// console.log(translationLyric)
if (this.isPlay) this.pause()
this.lyric = lyric
this.translationLyric = translationLyric
this._init()
}
}
@@ -0,0 +1,50 @@
const getNow = exports.getNow = typeof performance == 'object' && window.performance.now ? window.performance.now.bind(window.performance) : Date.now.bind(Date)
exports.TimeoutTools = class TimeoutTools {
constructor() {
this.invokeTime = 0
this.animationFrameId = null
this.timeoutId = null
this.callback = null
this.thresholdTime = 200
}
run() {
this.animationFrameId = window.requestAnimationFrame(() => {
this.animationFrameId = null
let diff = this.invokeTime - getNow()
// console.log('diff', diff)
if (diff > 0) {
if (diff < this.thresholdTime) return this.run()
return this.timeoutId = setTimeout(() => {
this.timeoutId = null
this.run()
}, diff - this.thresholdTime)
}
// console.log('diff', diff)
this.callback(diff)
})
}
start(callback = () => {}, timeout = 0) {
// console.log(timeout)
this.callback = callback
this.invokeTime = getNow() + timeout
this.run()
}
clear() {
if (this.animationFrameId) {
window.cancelAnimationFrame(this.animationFrameId)
this.animationFrameId = null
}
if (this.timeoutId) {
window.clearTimeout(this.timeoutId)
this.timeoutId = null
}
}
}
@@ -11,7 +11,6 @@ module.exports = [
tx: ['128k'],
wy: ['128k'],
mg: ['128k'],
xm: ['128k'],
// bd: ['128k'],
},
},
+1
View File
@@ -13,6 +13,7 @@ for (const api of apiSourceInfo) {
const getAPI = source => apiList[`${window.globalObj.apiSource}_api_${source}`]
const apis = source => {
if (/^user_api/.test(window.globalObj.apiSource)) return window.globalObj.userApi.apis[source]
let api = getAPI(source)
if (api) return api
throw new Error('Api is not found')
+6 -6
View File
@@ -11,9 +11,9 @@ export default {
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
musicSearch(str, page, limit) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.6&method=baidu.ting.search.merge&format=json&query=${encodeURIComponent(str)}&page_no=${page}&page_size=${this.limit}&type=0&data_source=0&use_cluster=1`)
searchRequest = httpFetch(`http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.6&method=baidu.ting.search.merge&format=json&query=${encodeURIComponent(str)}&page_no=${page}&page_size=${limit}&type=0&data_source=0&use_cluster=1`)
return searchRequest.promise.then(({ body }) => body)
},
handleResult(rawData) {
@@ -66,9 +66,9 @@ export default {
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
if (limit == null) limit = this.limit
return this.musicSearch(str, page).then(result => {
return this.musicSearch(str, page, limit).then(result => {
if (!result || result.error_code !== 22000) return this.search(str, page, { limit }, retryNum)
let list = this.handleResult(result.result.song_info.song_list)
@@ -76,12 +76,12 @@ export default {
this.total = result.result.song_info.total
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
limit: limit,
total: this.total,
source: 'bd',
})
+54 -33
View File
@@ -50,66 +50,87 @@ const sources = {
export default {
...sources,
init() {
const tasks = []
for (let source of sources.sources) {
let sm = sources[source.id]
sm && sm.init && sm.init()
sm && sm.init && tasks.push(sm.init())
}
return Promise.all(tasks)
},
supportQuality,
async findMusic(musicInfo) {
const tasks = []
const sortSingle = singer => singer.includes('、') ? singer.split('、').sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0)).join('、') : singer
const sortMusic = (arr, callback) => {
const tempResult = []
for (let i = arr.length - 1; i > -1; i--) {
const item = arr[i]
if (callback(item)) {
delete item.sortedSinger
tempResult.push(item)
arr.splice(i, 1)
}
}
tempResult.reverse()
return tempResult
}
const trimStr = str => typeof str == 'string' ? str.trim() : str
const sortedSinger = String(sortSingle(musicInfo.singer)).toLowerCase()
const musicName = trimStr(musicInfo.name)
const lowerCaseName = String(musicName).toLowerCase()
const lowerCaseAlbumName = String(musicInfo.albumName).toLowerCase()
for (const source of sources.sources) {
if (!sources[source.id].musicSearch || source.id === musicInfo.source || source.id === 'xm') continue
tasks.push(sources[source.id].musicSearch.search(`${musicInfo.name} ${musicInfo.singer || ''} ${musicInfo.albumName || ''}`.trim(), 1, { limit: 5 }).then(res => {
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${musicInfo.singer || ''}`.trim(), 1, { limit: 10 }).then(res => {
for (const item of res.list) {
item.sortedSinger = String(sortSingle(item.singer)).toLowerCase()
item.name = trimStr(item.name)
item.lowerCaseName = String(item.name).toLowerCase()
item.lowerCaseAlbumName = String(item.albumName).toLowerCase()
// console.log(lowerCaseName, item.lowerCaseName)
if (
(
item.singer === musicInfo.singer &&
(item.name === musicInfo.name || item.interval === musicInfo.interval)
item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName
) ||
(
item.interval === musicInfo.interval && item.name === musicInfo.name &&
(item.singer.includes(musicInfo.singer) || musicInfo.singer.includes(item.singer))
item.interval === musicInfo.interval && item.lowerCaseName === lowerCaseName &&
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
) ||
(
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
item.interval === musicInfo.interval
)
) {
return item
}
}
for (const item of res.list) {
item.sortedSinger = String(sortSingle(item.singer)).toLowerCase()
item.name = trimStr(item.name)
item.lowerCaseName = String(item.name).toLowerCase()
item.lowerCaseAlbumName = String(item.albumName).toLowerCase()
// console.log(lowerCaseName, item.lowerCaseName)
if (
item.sortedSinger === sortedSinger && item.interval === musicInfo.interval
) {
return item
}
}
return null
}).catch(_ => null))
}
const result = (await Promise.all(tasks)).filter(s => s)
const newResult = []
if (result.length) {
for (let i = result.length - 1; i > -1; i--) {
const item = result[i]
if (item.singer === musicInfo.singer && item.name === musicInfo.name && item.interval === musicInfo.interval) {
newResult.push(item)
result.splice(i, 1)
}
}
for (let i = result.length - 1; i > -1; i--) {
const item = result[i]
if (item.singer === musicInfo.singer && item.interval === musicInfo.interval) {
newResult.push(item)
result.splice(i, 1)
}
}
for (let i = result.length - 1; i > -1; i--) {
const item = result[i]
if (item.name === musicInfo.name && item.singer === musicInfo.singer && item.albumName === musicInfo.albumName) {
newResult.push(item)
result.splice(i, 1)
}
}
for (let i = result.length - 1; i > -1; i--) {
const item = result[i]
if (item.singer === musicInfo.singer && item.name === musicInfo.name) {
newResult.push(item)
result.splice(i, 1)
}
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName && item.interval === musicInfo.interval))
newResult.push(...sortMusic(result, item => item.lowerCaseName === lowerCaseName && item.sortedSinger === sortedSinger && item.lowerCaseAlbumName === lowerCaseAlbumName))
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName))
newResult.push(...sortMusic(result, item => item.sortedSinger === sortedSinger && item.interval === musicInfo.interval))
for (const item of result) {
delete item.sortedSinger
delete item.lowerCaseName
}
newResult.push(...result)
}
+5 -1
View File
@@ -31,9 +31,13 @@ export default {
if (statusCode != 200 || body.err_code !== 0) throw new Error('获取热门评论失败')
return { source: 'kg', comments: this.filterComment(body.weightList || []) }
},
async getReplyComment({ songmid }, replyId, page = 1, limit = 100) {
async getReplyComment({ songmid, audioId }, replyId, page = 1, limit = 100) {
if (this._requestObj2) this._requestObj2.cancelHttp()
songmid = songmid.length == 32 // 修复歌曲ID存储变更导致图片获取失败的问题
? audioId.split('_')[0]
: songmid
const _requestObj2 = httpFetch(`http://comment.service.kugou.com/index.php?r=commentsv2/getReplyWithLike&code=fc4be23b4e972707f36b8a828a93ba8a&p=${page}&pagesize=${limit}&ver=1.01&clientver=8373&kugouid=687373022&appid=1001&childrenid=${songmid}&tid=${replyId}`, {
headers: {
'User-Agent': 'Android712-AndroidPhone-8983-18-0-COMMENT-wifi',
+1 -1
View File
@@ -26,7 +26,7 @@ const kg = {
return pic.getPic(songInfo)
},
getMusicDetailPageUrl(songInfo) {
return `https://www.kugou.com/song/#hash=${songInfo.hash}`
return `https://www.kugou.com/song/#hash=${songInfo.hash}&album_id=${songInfo.albumId}`
},
// getPic(songInfo) {
// return apis('kg').getPic(songInfo)
+11 -5
View File
@@ -2,10 +2,13 @@ import { httpFetch } from '../../request'
import { decodeLyric } from './util'
import { decodeName } from '../..'
const headExp = /^.*\[id:\$\w+\]\n/
const parseLyric = str => {
str = str.replace(/(?:<\d+,\d+,\d+>|\r)/g, '')
if (str.startsWith('\ufeff[id:$00000000]')) str = str.replace('\ufeff[id:$00000000]\n', '')
str = str.replace(/\r/g, '')
if (headExp.test(str)) str = str.replace(headExp, '')
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
let lyric
let tlyric
if (trans) {
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
@@ -18,7 +21,7 @@ const parseLyric = str => {
}
}
let i = 0
let lyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
let result = str.match(/\[((\d+),\d+)\].*/)
let time = parseInt(result[2])
let ms = time % 1000
@@ -31,11 +34,14 @@ const parseLyric = str => {
return str.replace(result[1], time)
})
tlyric = tlyric ? tlyric.join('\n') : ''
lyric = decodeName(lyric)
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
lxlyric = decodeName(lxlyric)
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
tlyric = decodeName(tlyric)
return {
lyric,
tlyric,
lxlyric,
}
}
@@ -119,7 +125,7 @@ export default {
let requestObj = this.searchLyric(songInfo.name, songInfo.hash, songInfo._interval || this.getIntv(songInfo.interval))
requestObj.promise = requestObj.promise.then(result => {
if (!result) return { lyric: '', tlyric: '' }
if (!result) return { lyric: null, tlyric: null, lxlyric: null }
let requestObj2 = this.getLyricDownload(result.id, result.accessKey)
+60 -50
View File
@@ -11,69 +11,79 @@ export default {
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
musicSearch(str, page, limit) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
return searchRequest.promise.then(({ body }) => body)
},
filterData(rawData) {
const types = []
const _types = {}
if (rawData.filesize !== 0) {
let size = sizeFormate(rawData.filesize)
types.push({ type: '128k', size, hash: rawData.hash })
_types['128k'] = {
size,
hash: rawData.hash,
}
}
if (rawData['320filesize'] !== 0) {
let size = sizeFormate(rawData['320filesize'])
types.push({ type: '320k', size, hash: rawData['320hash'] })
_types['320k'] = {
size,
hash: rawData['320hash'],
}
}
if (rawData.sqfilesize !== 0) {
let size = sizeFormate(rawData.sqfilesize)
types.push({ type: 'flac', size, hash: rawData.sqhash })
_types.flac = {
size,
hash: rawData.sqhash,
}
}
return {
singer: decodeName(rawData.singername),
name: decodeName(rawData.songname),
albumName: decodeName(rawData.album_name),
albumId: rawData.album_id,
songmid: rawData.audio_id,
source: 'kg',
interval: formatPlayTime(rawData.duration),
_interval: rawData.duration,
img: null,
lrc: null,
otherSource: null,
hash: rawData.hash,
types,
_types,
typeUrl: {},
}
},
handleResult(rawData) {
// console.log(rawData)
let ids = new Set()
const list = []
rawData.forEach(item => {
if (ids.has(item.audio_id)) return
ids.add(item.audio_id)
const types = []
const _types = {}
if (item.filesize !== 0) {
let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash })
_types['128k'] = {
size,
hash: item.hash,
}
const key = item.audio_id + item.hash
if (ids.has(key)) return
ids.add(key)
list.push(this.filterData(item))
for (const childItem of item.group) {
const key = item.audio_id + item.hash
if (ids.has(key)) return
ids.add(key)
list.push(this.filterData(childItem))
}
if (item['320filesize'] !== 0) {
let size = sizeFormate(item['320filesize'])
types.push({ type: '320k', size, hash: item['320hash'] })
_types['320k'] = {
size,
hash: item['320hash'],
}
}
if (item.sqfilesize !== 0) {
let size = sizeFormate(item.sqfilesize)
types.push({ type: 'flac', size, hash: item.sqhash })
_types.flac = {
size,
hash: item.sqhash,
}
}
list.push({
singer: decodeName(item.singername),
name: decodeName(item.songname),
albumName: decodeName(item.album_name),
albumId: item.album_id,
songmid: item.audio_id,
source: 'kg',
interval: formatPlayTime(item.duration),
_interval: item.duration,
img: null,
lrc: null,
otherSource: null,
hash: item.hash,
types,
_types,
typeUrl: {},
})
})
return list
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
return this.musicSearch(str, page, limit).then(result => {
if (!result || result.errcode !== 0) return this.search(str, page, { limit }, retryNum)
let list = this.handleResult(result.data.info)
@@ -81,12 +91,12 @@ export default {
this.total = result.data.total
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
limit,
total: this.total,
source: 'kg',
})
+7 -2
View File
@@ -20,7 +20,10 @@ export default {
relate: 1,
resource: [
{
album_audio_id: songInfo.songmid,
album_audio_id:
songInfo.songmid.length == 32 // 修复歌曲ID存储变更导致图片获取失败的问题
? songInfo.audioId.split('_')[0]
: songInfo.songmid,
album_id: songInfo.albumId,
hash: songInfo.hash,
id: 0,
@@ -37,7 +40,9 @@ export default {
requestObj.promise = requestObj.promise.then(({ body }) => {
if (body.error_code !== 0) return Promise.reject('图片获取失败')
let info = body.data[0].info
return info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
const img = info.imgsize ? info.image.replace('{size}', info.imgsize[0]) : info.image
if (!img) return Promise.reject('Pic get failed')
return img
})
return requestObj
},
+1 -1
View File
@@ -106,7 +106,7 @@ const kw = {
},
init() {
getToken()
return getToken()
},
}
+6 -6
View File
@@ -16,14 +16,14 @@ export default {
page: 0,
allPage: 1,
// cancelFn: null,
musicSearch(str, page) {
musicSearch(str, page, limit) {
if (this._musicSearchRequestObj != null) {
cancelHttp(this._musicSearchRequestObj)
this._musicSearchPromiseCancelFn(new Error('取消http请求'))
}
return new Promise((resolve, reject) => {
this._musicSearchPromiseCancelFn = reject
this._musicSearchRequestObj = httpGet(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${this.limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`, (err, resp, body) => {
this._musicSearchRequestObj = httpGet(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`, (err, resp, body) => {
this._musicSearchRequestObj = null
this._musicSearchPromiseCancelFn = null
if (err) {
@@ -125,9 +125,9 @@ export default {
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (retryNum > 2) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
return this.musicSearch(str, page, limit).then(result => {
// console.log(result)
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, { limit }, ++retryNum)
let list = this.handleResult(result.abslist)
@@ -136,13 +136,13 @@ export default {
this.total = parseInt(result.TOTAL)
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return Promise.resolve({
list,
allPage: this.allPage,
total: this.total,
limit: this.limit,
limit,
source: 'kw',
})
})
+4 -4
View File
@@ -16,7 +16,7 @@ export default {
if (!info) info = list.find(s => s.songId == songmid)
return info ? info.songId : null
},
async getComment(musicInfo, page = 1, limit = 20) {
async getComment(musicInfo, page = 1, limit = 10) {
if (this._requestObj) this._requestObj.cancelHttp()
if (!musicInfo.songId) {
let id = await this.getSongId(musicInfo)
@@ -35,7 +35,7 @@ export default {
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取评论失败')
return { source: 'mg', comments: this.filterComment(body.data.items), total: body.data.itemTotal, page, limit, maxPage: Math.ceil(body.data.itemTotal / limit) || 1 }
},
async getHotComment(musicInfo, page = 1, limit = 100) {
async getHotComment(musicInfo, page = 1, limit = 5) {
if (this._requestObj2) this._requestObj2.cancelHttp()
if (!musicInfo.songId) {
@@ -55,7 +55,7 @@ export default {
if (statusCode != 200 || body.returnCode !== '000000') throw new Error('获取热门评论失败')
return { source: 'mg', comments: this.filterComment(body.data.items) }
},
async getReplyComment(musicInfo, replyId, page = 1, limit = 100) {
async getReplyComment(musicInfo, replyId, page = 1, limit = 10) {
if (this._requestObj2) this._requestObj2.cancelHttp()
const _requestObj2 = httpFetch(`https://music.migu.cn/v3/api/comment/listCommentsById?commentId=${replyId}&pageSize=${limit}&pageNo=${page}`, {
@@ -75,7 +75,7 @@ export default {
time: item.createTime,
timeStr: dateFormat2(new Date(item.createTime).getTime()),
userName: item.author.name,
avatar: item.author.avatar,
avatar: item.author.avatar && item.author.avatar.startsWith('//') ? `http:${item.author.avatar}` : item.author.avatar,
userId: item.author.id,
likedCount: item.praiseCount,
replyNum: item.replyTotal,
+7 -7
View File
@@ -11,9 +11,9 @@ export default {
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
musicSearch(str, page, limit) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch(`http://jadeite.migu.cn:7090/music_search/v2/search/searchAll?sid=4f87090d01c84984a11976b828e2b02c18946be88a6b4c47bcdc92fbd40762db&isCorrect=1&isCopyright=1&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A1%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A0%2C%22lyricSong%22%3A0%7D&pageSize=${this.limit}&text=${encodeURIComponent(str)}&pageNo=${page}&sort=0`, {
searchRequest = httpFetch(`http://jadeite.migu.cn:7090/music_search/v2/search/searchAll?sid=4f87090d01c84984a11976b828e2b02c18946be88a6b4c47bcdc92fbd40762db&isCorrect=1&isCopyright=1&searchSwitch=%7B%22song%22%3A1%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A1%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A0%2C%22lyricSong%22%3A0%7D&pageSize=${limit}&text=${encodeURIComponent(str)}&pageNo=${page}&sort=0`, {
headers: {
sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
timestamp: 1578225871982,
@@ -25,7 +25,7 @@ export default {
'User-Agent': 'okhttp/3.9.1',
},
})
// searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${this.limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`)
// searchRequest = httpFetch(`https://app.c.nf.migu.cn/MIGUM2.0/v1.0/content/search_all.do?isCopyright=1&isCorrect=1&pageNo=${page}&pageSize=${limit}&searchSwitch={%22song%22:1,%22album%22:0,%22singer%22:0,%22tagSong%22:0,%22mvSong%22:0,%22songlist%22:0,%22bestShow%22:0}&sort=0&text=${encodeURIComponent(str)}`)
return searchRequest.promise.then(({ body }) => body)
},
getSinger(singers) {
@@ -99,9 +99,9 @@ export default {
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
return this.musicSearch(str, page, limit).then(result => {
// console.log(result)
if (!result || result.code !== '000000') return Promise.reject(new Error(result ? result.info : '搜索失败'))
const songResultData = result.songResultData || { resultList: [], totalCount: 0 }
@@ -111,12 +111,12 @@ export default {
this.total = parseInt(songResultData.totalCount)
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
limit,
total: this.total,
source: 'mg',
})
+53 -13
View File
@@ -5,9 +5,11 @@ export default {
_requestObj_tags: null,
_requestObj_list: null,
_requestObj_listDetail: null,
_requestObj_listDetailInfo: null,
limit_list: 10,
limit_song: 10000,
successCode: '000000',
cachedDetailInfo: {},
sortList: [
{
name: '推荐',
@@ -48,10 +50,12 @@ export default {
return `https://app.c.nf.migu.cn/MIGUM2.0/v1.0/user/queryMusicListSongs.do?musicListId=${id}&pageNo=${page}&pageSize=${this.limit_song}`
},
defaultHeaders: {
language: 'Chinese',
ua: 'Android_migu',
mode: 'android',
version: '6.8.5',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1',
Referer: 'https://m.music.migu.cn/',
// language: 'Chinese',
// ua: 'Android_migu',
// mode: 'android',
// version: '6.8.5',
},
/**
@@ -64,11 +68,14 @@ export default {
return num
},
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
getListDetailList(id, page, tryNum = 0) {
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
// https://h5.nf.migu.cn/app/v4/p/share/playlist/index.html?id=184187437&channel=0146921
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
if (/playlist\/index\.html\?/.test(id)) {
id = id.replace(/.*(?:\?|&)id=(\d+)(?:&.*|$)/, '$1')
} else if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
this._requestObj_listDetail = httpFetch(this.getSongListDetailUrl(id, page), { headers: this.defaultHeaders })
return this._requestObj_listDetail.promise.then(({ body }) => {
@@ -81,16 +88,47 @@ export default {
limit: this.limit_song,
total: body.totalCount,
source: 'mg',
// info: {
// // name: body.result.info.list_title,
// // img: body.result.info.list_pic,
// // desc: body.result.info.list_desc,
// // author: body.result.info.userinfo.username,
// // play_count: this.formatPlayCount(body.result.listen_num),
// },
}
})
},
getListDetailInfo(id, tryNum = 0) {
if (this._requestObj_listDetailInfo) this._requestObj_listDetailInfo.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if (this.cachedDetailInfo[id]) return Promise.resolve(this.cachedDetailInfo[id])
this._requestObj_listDetailInfo = httpFetch(`https://c.musicapp.migu.cn/MIGUM3.0/resource/playlist/v2.0?playlistId=${id}`, {
headers: this.defaultHeaders,
})
return this._requestObj_listDetailInfo.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getListDetail(id, ++tryNum)
// console.log(JSON.stringify(body))
// console.log(body)
const cachedDetailInfo = this.cachedDetailInfo[id] = {
name: body.data.title,
img: body.data.imgItem.img,
desc: body.data.summary,
author: body.data.ownerName,
play_count: this.formatPlayCount(body.data.opNumItem.playNum),
}
return cachedDetailInfo
})
},
getListDetail(id, page) { // 获取歌曲列表内的音乐
// https://h5.nf.migu.cn/app/v4/p/share/playlist/index.html?id=184187437&channel=0146921
if (/playlist\/index\.html\?/.test(id)) {
id = id.replace(/.*(?:\?|&)id=(\d+)(?:&.*|$)/, '$1')
} else if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
return Promise.all([
this.getListDetailList(id, page),
this.getListDetailInfo(id),
]).then(([listData, info]) => {
listData.info = info
return listData
})
},
filterListDetail(rawList) {
// console.log(rawList)
let ids = new Set()
@@ -155,6 +193,7 @@ export default {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(this.getSongListUrl(sortId, tagId, page), {
headers: this.defaultHeaders,
// headers: {
// sign: 'c3b7ae985e2206e97f1b2de8f88691e2',
// timestamp: 1578225871982,
@@ -186,6 +225,7 @@ export default {
// })
// })
return this._requestObj_list.promise.then(({ body }) => {
// console.log(body)
if (body.retCode !== '100000' || body.retMsg.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {
list: this.filterList(body.retMsg.playlist),
+7 -7
View File
@@ -12,13 +12,13 @@ export default {
page: 0,
allPage: 1,
successCode: 0,
musicSearch(str, page, retryNum = 0) {
musicSearch(str, page, limit, retryNum = 0) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
if (retryNum > 5) return Promise.reject(new Error('搜索失败'))
searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${this.limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`)
searchRequest = httpFetch(`https://c.y.qq.com/soso/fcgi-bin/client_search_cp?ct=24&qqmusic_ver=1298&new_json=1&remoteplace=sizer.yqq.song_next&searchid=49252838123499591&t=0&aggr=1&cr=1&catZhida=1&lossless=0&flag_qc=0&p=${page}&n=${limit}&w=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`)
// searchRequest = httpFetch(`http://ioscdn.kugou.com/api/v3/search/song?keyword=${encodeURIComponent(str)}&page=${page}&pagesize=${this.limit}&showtype=10&plat=2&version=7910&tag=1&correct=1&privilege=1&sver=5`)
return searchRequest.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.musicSearch(str, page, ++retryNum)
if (body.code !== this.successCode) return this.musicSearch(str, page, limit, ++retryNum)
return body.data
})
},
@@ -86,19 +86,19 @@ export default {
})
},
search(str, page = 1, { limit } = {}) {
if (limit != null) this.limit = limit
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(({ song }) => {
return this.musicSearch(str, page, limit).then(({ song }) => {
let list = this.handleResult(song.list)
this.total = song.totalnum
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
limit,
total: this.total,
source: 'tx',
})
+3 -1
View File
@@ -20,7 +20,9 @@ const wy = {
return getLyric(songInfo.songmid)
},
getPic(songInfo) {
return getMusicInfo(songInfo.songmid).then(info => info.al.picUrl)
const requestObj = getMusicInfo(songInfo.songmid)
requestObj.promise = requestObj.promise.then(info => info.al.picUrl)
return requestObj
},
getMusicDetailPageUrl(songInfo) {
return `https://music.163.com/#/song?id=${songInfo.songmid}`
+34
View File
@@ -1,5 +1,38 @@
import { httpFetch } from '../../request'
import { linuxapi } from './utils/crypto'
// import { decodeName } from '../..'
// const parseLyric = (str, lrc) => {
// if (!str) return ''
// str = str.replace(/\r/g, '')
// let lxlyric = str.replace(/\[((\d+),\d+)\].*/g, str => {
// let result = str.match(/\[((\d+),\d+)\].*/)
// let time = parseInt(result[2])
// let ms = time % 1000
// time /= 1000
// let m = parseInt(time / 60).toString().padStart(2, '0')
// time %= 60
// let s = parseInt(time).toString().padStart(2, '0')
// time = `${m}:${s}.${ms}`
// str = str.replace(result[1], time)
// let startTime = 0
// str = str.replace(/\(0,1\) /g, ' ').replace(/\(\d+,\d+\)/g, time => {
// const [start, end] = time.replace(/^\((\d+,\d+)\)$/, '$1').split(',')
// time = `<${parseInt(startTime + parseInt(start))},${end}>`
// startTime = parseInt(startTime + parseInt(end))
// return time
// })
// return str
// })
// lxlyric = decodeName(lxlyric)
// return lxlyric.trim()
// }
export default songmid => {
const requestObj = httpFetch('https://music.163.com/api/linux/forward', {
@@ -21,6 +54,7 @@ export default songmid => {
return {
lyric: body.lrc.lyric,
tlyric: body.tlyric.lyric,
// lxlyric: parseLyric(body.klyric.lyric),
}
})
return requestObj
+7 -7
View File
@@ -9,7 +9,7 @@ export default {
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
musicSearch(str, page, limit) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = httpFetch('https://music.163.com/weapi/search/get', {
method: 'post',
@@ -20,8 +20,8 @@ export default {
form: weapi({
s: str,
type: 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit: this.limit,
offset: this.limit * (page - 1),
limit,
offset: limit * (page - 1),
}),
})
return searchRequest.promise.then(({ body }) =>
@@ -29,13 +29,13 @@ export default {
? musicDetailApi.getList(body.result.songs.map(s => s.id)).then(({ list }) => {
this.total = body.result.songCount || 0
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
this.allPage = Math.ceil(this.total / limit)
return {
code: 200,
data: {
list,
allPage: this.allPage,
limit: this.limit,
limit,
total: this.total,
source: 'wy',
},
@@ -103,8 +103,8 @@ export default {
}, */
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
return this.musicSearch(str, page).then(result => {
if (limit == null) limit = this.limit
return this.musicSearch(str, page, limit).then(result => {
// console.log(result)
if (!result || result.code !== 200) return this.search(str, page, { limit }, retryNum)
// let list = this.handleResult(result.result.songs || [])
+57
View File
@@ -0,0 +1,57 @@
// import { apis } from '../api-source'
// import leaderboard from './leaderboard'
// import songList from './songList'
// import musicSearch from './musicSearch'
// import pic from './pic'
// import lyric from './lyric'
// import hotSearch from './hotSearch'
// import comment from './comment'
// import musicInfo from './musicInfo'
// import { closeVerifyModal } from './util'
const xm = {
// songList,
// musicSearch,
// leaderboard,
// hotSearch,
// closeVerifyModal,
comment: {
getComment() {
return Promise.reject(new Error('fail'))
},
getHotComment() {
return Promise.reject(new Error('fail'))
},
},
getMusicUrl(songInfo, type) {
return {
promise: Promise.reject(new Error('fail')),
}
// return apis('xm').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return {
promise: Promise.reject(new Error('fail')),
}
// return lyric.getLyric(songInfo)
},
getPic(songInfo) {
return {
promise: Promise.reject(new Error('fail')),
}
// return pic.getPic(songInfo)
},
// getMusicDetailPageUrl(songInfo) {
// if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
// musicInfo.getMusicInfo(songInfo).then(({ data }) => {
// songInfo.songStringId = data.songStringId
// })
// return `https://www.xiami.com/song/${songInfo.songmid}`
// },
// init() {
// getToken()
// },
}
export default xm
-20
View File
@@ -1,20 +0,0 @@
import { httpFetch } from '../../request'
import { requestMsg } from '../../message'
import { headers, timeout } from '../options'
const api_test = {
getMusicUrl(songInfo, type) {
const requestObj = httpFetch(`http://ts.tempmusic.tk/url/xm/${songInfo.songmid}/${type}`, {
method: 'get',
timeout,
headers,
family: 4,
})
requestObj.promise = requestObj.promise.then(({ body }) => {
return body.code === 0 ? Promise.resolve({ type, url: body.data }) : Promise.reject(new Error(requestMsg.fail))
})
return requestObj
},
}
export default api_test
-47
View File
@@ -1,47 +0,0 @@
import { xmRequest } from './util'
import { dateFormat2 } from '../../'
export default {
_requestObj: null,
_requestObj2: null,
async getComment({ songmid }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp()
const _requestObj = xmRequest('/api/comment/getCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
const { body, statusCode } = await _requestObj.promise
// console.log(body)
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取评论失败')
return { source: 'xm', comments: this.filterComment(body.result.data.commentList), total: body.result.data.pagingVO.count, page, limit, maxPage: Math.ceil(body.result.data.pagingVO.count / limit) || 1 }
},
async getHotComment({ songmid }, page = 1, limit = 100) {
if (this._requestObj2) this._requestObj2.cancelHttp()
if (!songmid) throw new Error('获取失败')
const _requestObj2 = xmRequest('/api/comment/getHotCommentList', { objectId: songmid, objectType: 'song', pagingVO: { page, pageSize: limit } })
const { body, statusCode } = await _requestObj2.promise
// console.log(body)
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取热门评论失败')
return { source: 'xm', comments: this.filterComment(body.result.data.hotList) }
},
filterComment(rawList) {
return rawList.map(item => ({
id: item.commentId,
text: item.message.split('\n').filter(t => !!t),
time: item.gmtCreate,
timeStr: dateFormat2(item.gmtCreate),
userName: item.nickName,
avatar: item.avatar,
userId: item.userId,
likedCount: item.likes,
reply: item.replyData ? item.replyData.map(c => ({
id: c.commentId,
text: c.message.split('\n').filter(t => !!t),
time: c.gmtCreate,
timeStr: dateFormat2(c.gmtCreate),
userName: c.nickName,
avatar: c.avatar,
userId: c.userId,
likedCount: c.likes,
})) : [],
}))
},
}
-19
View File
@@ -1,19 +0,0 @@
import { xmRequest } from './util'
export default {
_requestObj: null,
async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num'))
const _requestObj = xmRequest('/api/search/getHotSearchWords')
const { body, statusCode } = await _requestObj.promise
// console.log(body)
if (statusCode != 200 || body.code !== 'SUCCESS') return this.getList(++retryNum)
// // console.log(body, statusCode)
return { source: 'xm', list: this.filterList(body.result.data.hotWords) }
},
filterList(rawList) {
return rawList.map(item => item.word)
},
}
-42
View File
@@ -1,42 +0,0 @@
import { apis } from '../api-source'
import leaderboard from './leaderboard'
import songList from './songList'
import musicSearch from './musicSearch'
// import pic from './pic'
import lyric from './lyric'
import hotSearch from './hotSearch'
import comment from './comment'
import musicInfo from './musicInfo'
import { closeVerifyModal } from './util'
const xm = {
songList,
musicSearch,
leaderboard,
hotSearch,
closeVerifyModal,
comment,
getMusicUrl(songInfo, type) {
return apis('xm').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return lyric.getLyric(songInfo)
},
getPic(songInfo) {
return Promise.reject(new Error('fail'))
// return pic.getPic(songInfo)
},
getMusicDetailPageUrl(songInfo) {
if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
musicInfo.getMusicInfo(songInfo).then(({ data }) => {
songInfo.songStringId = data.songStringId
})
return `https://www.xiami.com/song/${songInfo.songmid}`
},
// init() {
// getToken()
// },
}
export default xm
-211
View File
@@ -1,211 +0,0 @@
import { xmRequest } from './util'
import { formatPlayTime, sizeFormate } from '../../index'
// import jshtmlencode from 'js-htmlencode'
let boardList = [{ id: 'xm__102', name: '新歌榜', bangid: '102' }, { id: 'xm__103', name: '热歌榜', bangid: '103' }, { id: 'xm__104', name: '原创榜', bangid: '104' }, { id: 'xm__306', name: 'K歌榜', bangid: '306' }, { id: 'xm__332', name: '抖音热歌榜', bangid: '332' }, { id: 'xm__305', name: '歌单收录榜', bangid: '305' }, { id: 'xm__327', name: '趴间热歌榜', bangid: '327' }, { id: 'xm__324', name: '影视原声榜', bangid: '324' }, { id: 'xm__204', name: '美国Billboard单曲榜', bangid: '204' }, { id: 'xm__206', name: '韩国MNET音乐排行榜', bangid: '206' }, { id: 'xm__201', name: 'Hito 中文排行榜', bangid: '201' }, { id: 'xm__203', name: '英国UK单曲榜', bangid: '203' }, { id: 'xm__205', name: 'oricon公信单曲榜', bangid: '205' }, { id: 'xm__328', name: '美国iTunes榜', bangid: '328' }, { id: 'xm__329', name: 'Beatport电音榜', bangid: '329' }, { id: 'xm__330', name: '香港商业电台榜', bangid: '330' }]
export default {
limit: 200,
list: [
{
id: 'xmrgb',
name: '热歌榜',
bangid: '103',
},
{
id: 'xmxgb',
name: '新歌榜',
bangid: '102',
},
{
id: 'xmrcb',
name: '原创榜',
bangid: '104',
},
{
id: 'xmdyb',
name: '抖音榜',
bangid: '332',
},
{
id: 'xmkgb',
name: 'K歌榜',
bangid: '306',
},
{
id: 'xmfxb',
name: '分享榜',
bangid: '307',
},
{
id: 'xmrdtlb',
name: '讨论榜',
bangid: '331',
},
{
id: 'xmgdslb',
name: '歌单榜',
bangid: '305',
},
{
id: 'xmpjrgb',
name: '趴间榜',
bangid: '327',
},
{
id: 'xmysysb',
name: '影视榜',
bangid: '324',
},
],
requestBoardsObj: null,
requestObj: null,
getBoardsData() {
if (this.requestBoardsObj) this.requestBoardsObj.cancelHttp()
this.requestBoardsObj = xmRequest('/api/billboard/getBillboards')
return this.requestBoardsObj.promise
},
getData(id) {
if (this.requestObj) this.requestObj.cancelHttp()
this.requestObj = xmRequest('/api/billboard/getBillboardDetail', { billboardId: id })
return this.requestObj.promise
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.artistName)
})
return arr.join('、')
},
filterData(rawList) {
// console.log(rawList)
let ids = new Set()
const list = []
rawList.forEach(songData => {
if (!songData) return
if (ids.has(songData.songId)) return
ids.add(songData.songId)
const types = []
const _types = {}
let size = null
for (const item of songData.purviewRoleVOs) {
if (!item.filesize) continue
size = sizeFormate(item.filesize)
switch (item.quality) {
case 's':
types.push({ type: 'wav', size })
_types.wav = {
size,
}
break
case 'h':
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
break
case 'l':
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
break
}
}
types.reverse()
list.push({
singer: this.getSinger(songData.singerVOs),
name: songData.songName,
albumName: songData.albumName,
albumId: songData.albumId,
source: 'xm',
interval: formatPlayTime(parseInt(songData.length / 1000)),
songmid: songData.songId,
img: songData.albumLogo || songData.albumLogoS,
songStringId: songData.songStringId,
lrc: null,
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
otherSource: null,
types,
_types,
typeUrl: {},
})
})
return list
},
filterBoardsData(rawList) {
// console.log(rawList)
let list = []
if (rawList.xiamiBillboards) {
for (const board of rawList.xiamiBillboards) {
if (board.itemType != 1) continue
list.push({
id: 'xm__' + board.billboardId,
name: board.name,
bangid: String(board.billboardId),
})
}
}
if (rawList.spBillboards) {
for (const board of rawList.spBillboards) {
if (board.itemType != 1) continue
list.push({
id: 'xm__' + board.billboardId,
name: board.name,
bangid: String(board.billboardId),
})
}
}
if (rawList.globalBillboards) {
for (const board of rawList.globalBillboards) {
if (board.itemType != 1) continue
list.push({
id: 'xm__' + board.billboardId,
name: board.name,
bangid: String(board.billboardId),
})
}
}
return list
},
async getBoards(retryNum = 0) {
// if (++retryNum > 3) return Promise.reject(new Error('try max num'))
// let response
// try {
// response = await this.getBoardsData()
// } catch (error) {
// return this.getBoards(retryNum)
// }
// if (response.statusCode !== 200 || response.body.code !== 'SUCCESS') return this.getBoards(retryNum)
// const list = this.filterBoardsData(response.body.result.data)
// this.list = list
// return {
// list,
// source: 'xm',
// }
this.list = boardList
return {
list: boardList,
source: 'xm',
}
},
getList(bangid, page, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
return this.getData(bangid).then(({ statusCode, body }) => {
if (statusCode !== 200 || body.code !== 'SUCCESS') return this.getList(bangid, page, retryNum)
// console.log(body)
const list = this.filterData(body.result.data.billboard.songs)
return {
total: parseInt(body.result.data.billboard.attributeMap.item_size),
list,
limit: this.limit,
page,
source: 'xm',
}
})
},
}
-107
View File
@@ -1,107 +0,0 @@
import { httpGet, httpFetch } from '../../request'
import { xmRequest } from './util'
const parseLyric = str => {
str = str.replace(/(?:<\d+>|\r)/g, '')
let tlyric = []
let lyric = str.replace(/\[[\d:.]+\].*?\n\[x-trans\].*/g, s => {
// console.log(s)
let [lrc, tlrc] = s.split('\n')
tlrc = tlrc.replace('[x-trans]', lrc.replace(/^(\[[\d:.]+\]).*$/, '$1'))
tlyric.push(tlrc)
return lrc
})
tlyric = tlyric.join('\n')
return {
lyric,
tlyric,
}
}
export default {
failTime: 0,
expireTime: 60 * 1000 * 1000,
getLyricFile_1(url, retryNum = 0) {
if (retryNum > 5) return Promise.reject('歌词获取失败')
let requestObj = httpFetch(url)
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => {
if (statusCode !== 200) {
let tryRequestObj = this.getLyric(url, ++retryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise
}
return url.endsWith('.xtrc') ? parseLyric(body) : {
lyric: body,
tlyric: '',
}
})
return requestObj
},
getLyricFile_2(url, retryNum = 0) {
if (retryNum > 5) return Promise.reject('歌词获取失败')
return new Promise((resolve, reject) => {
httpGet(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
referer: 'https://www.xiami.com',
},
}, function(err, resp, body) {
if (err || resp.statusCode !== 200) return this.getLyricFile(url, ++retryNum).then(resolve).catch(reject)
return resolve(url.endsWith('.xtrc') ? parseLyric(body) : {
lyric: body,
tlyric: '',
})
})
})
},
getLyricUrl_1(songInfo, retryNum = 0) {
if (retryNum > 2) return Promise.reject('歌词获取失败')
let requestObj = xmRequest('/api/lyric/getSongLyrics', { songId: songInfo.songmid })
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
if (statusCode !== 200) {
let tryRequestObj = this.getLyricUrl_1(songInfo, ++retryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise
}
if (body.code !== 'SUCCESS') {
this.failTime = Date.now()
let tryRequestObj = this.getLyricUrl_2(songInfo)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise
}
if (!body.result.data.lyrics.length) return Promise.reject(new Error('未找到歌词'))
let lrc = body.result.data.lyrics.find(lyric => /\.(trc|lrc)$/.test(lyric.lyricUrl))
return lrc
? lrc.lyricUrl.endsWith('.trc')
? parseLyric(lrc.content)
: { lyric: lrc.content, tlyric: '' }
: Promise.reject(new Error('未找到歌词'))
})
return requestObj
},
getLyricUrl_2(songInfo, retryNum = 0) {
if (retryNum > 2) return Promise.reject('歌词获取失败')
// https://github.com/listen1/listen1_chrome_extension/blob/2587e627d23a85e490628acc0b3c9b534bc8323d/js/provider/xiami.js#L149
let requestObj = httpFetch(`https://emumo.xiami.com/song/playlist/id/${songInfo.songmid}/object_name/default/object_id/0/cat/json`, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
referer: 'https://www.xiami.com',
},
})
requestObj.promise = requestObj.promise.then(({ statusCode, body }) => {
if (statusCode !== 200 || !body.status) {
let tryRequestObj = this.getLyricUrl_2(songInfo, ++retryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise
}
let url = body.data.trackList[0].lyric_url
if (!url) return Promise.reject(new Error('未找到歌词'))
return this.getLyricFile_2(/^http:/.test(url) ? url : ('http:' + url))
})
return requestObj
},
getLyric(songInfo) {
if (songInfo.lrcUrl && /\.(xtrc|lrc)$/.test(songInfo.lrcUrl)) return this.getLyricFile_1(songInfo.lrcUrl)
return Date.now() - this.failTime > this.expireTime ? this.getLyricUrl_1(songInfo) : this.getLyricUrl_2(songInfo)
},
}
-14
View File
@@ -1,14 +0,0 @@
import { xmRequest } from './util'
export default {
_requestObj: null,
async getMusicInfo({ songmid }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp()
const _requestObj = xmRequest('/api/song/initialize', { songId: songmid })
const { body, statusCode } = await _requestObj.promise
// console.log(body)
if (statusCode != 200 || body.code !== 'SUCCESS') throw new Error('获取歌曲信息失败')
return { source: 'xm', data: body.result.data.songDetail }
},
}
-115
View File
@@ -1,115 +0,0 @@
// import '../../polyfill/array.find'
// import jshtmlencode from 'js-htmlencode'
import { xmRequest } from './util'
import { formatPlayTime, sizeFormate } from '../../index'
// import { debug } from '../../utils/env'
// import { formatSinger } from './util'
// "cdcb72dc3eba41cb5bc4267f09183119_xmMain_/api/list/collect_{"pagingVO":{"page":1,"pageSize":60},"dataType":"system"}"
let searchRequest
export default {
limit: 30,
total: 0,
page: 0,
allPage: 1,
musicSearch(str, page) {
if (searchRequest && searchRequest.cancelHttp) searchRequest.cancelHttp()
searchRequest = xmRequest('/api/search/searchSongs', {
key: str,
pagingVO: {
page: page,
pageSize: this.limit,
},
})
return searchRequest.promise.then(({ body }) => body)
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.artistName)
})
return arr.join('、')
},
handleResult(rawData) {
// console.log(rawData)
let ids = new Set()
const list = []
rawData.forEach(songData => {
if (!songData) return
if (ids.has(songData.songId)) return
ids.add(songData.songId)
const types = []
const _types = {}
let size = null
for (const item of songData.purviewRoleVOs) {
if (!item.filesize) continue
size = sizeFormate(item.filesize)
switch (item.quality) {
case 's':
types.push({ type: 'wav', size })
_types.wav = {
size,
}
break
case 'h':
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
break
case 'l':
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
break
}
}
types.reverse()
list.push({
singer: this.getSinger(songData.singerVOs),
name: songData.songName,
albumName: songData.albumName,
albumId: songData.albumId,
source: 'xm',
interval: formatPlayTime(parseInt(songData.length / 1000)),
songmid: songData.songId,
img: songData.albumLogo || songData.albumLogoS,
songStringId: songData.songStringId,
lrc: null,
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
otherSource: null,
types,
_types,
typeUrl: {},
})
})
return list
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit != null) this.limit = limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page).then(result => {
if (!result) return this.search(str, page, { limit }, retryNum)
if (result.code !== 'SUCCESS') return this.search(str, page, { limit }, retryNum)
// const songResultData = result.data || { songs: [], total: 0 }
let list = this.handleResult(result.result.data.songs)
if (list == null) return this.search(str, page, { limit }, retryNum)
this.total = parseInt(result.result.data.pagingVO.count)
this.page = page
this.allPage = Math.ceil(this.total / this.limit)
return Promise.resolve({
list,
allPage: this.allPage,
limit: this.limit,
total: this.total,
source: 'xm',
})
}).catch(err => err.message.includes('canceled verify') ? Promise.reject(err) : this.search(str, page, { limit }, retryNum))
},
}
-221
View File
@@ -1,221 +0,0 @@
import { xmRequest } from './util'
import { sizeFormate, formatPlayTime } from '../../index'
export default {
_requestObj_tags: null,
_requestObj_list: null,
_requestObj_listDetail: null,
limit_list: 36,
limit_song: 100000,
successCode: 'SUCCESS',
sortList: [
{
name: '推荐',
id: 'system',
},
{
name: '精选',
id: 'recommend',
},
{
name: '最热',
id: 'hot',
},
{
name: '最新',
id: 'new',
},
],
regExps: {
// https://www.xiami.com/collect/1138092824?action=play
listDetailLink: /^.+\/collect\/(\d+)(?:\s\(.*|\?.*|&.*$|#.*$|$)/,
},
tagsUrl: '/api/collect/getRecommendTags',
songListUrl: '/api/list/collect',
songListDetailUrl: '/api/collect/initialize',
getSongListData(sortId, tagId, page) {
if (tagId == null) {
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId }
}
switch (sortId) {
case 'system':
case 'recommend':
sortId = 'hot'
}
return { pagingVO: { page, pageSize: this.limit_list }, dataType: sortId, key: tagId }
},
/**
* 格式化播放数量
* @param {*} num
*/
formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万'
return num
},
getSinger(singers) {
let arr = []
singers.forEach(singer => {
arr.push(singer.artistName)
})
return arr.join('、')
},
getListDetail(id, page, tryNum = 0) { // 获取歌曲列表内的音乐
if (this._requestObj_listDetail) this._requestObj_listDetail.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
if ((/[?&:/]/.test(id))) id = id.replace(this.regExps.listDetailLink, '$1')
this._requestObj_listDetail = xmRequest('/api/collect/getCollectStaticUrl', { listId: id })
return this._requestObj_listDetail.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getListDetail(id, page, ++tryNum)
this._requestObj_listDetail = xmRequest(body.result.data.data.data.url)
return this._requestObj_listDetail.promise.then(({ body }) => {
if (!body.status) return this.getListDetail(id, page, ++tryNum)
// console.log(JSON.stringify(body))
return {
list: this.filterListDetail(body.resultObj.songs),
page,
limit: this.limit_song,
total: body.resultObj.songCount,
source: 'xm',
info: {
name: body.resultObj.collectName,
img: body.resultObj.collectLogo,
desc: body.resultObj.description,
author: body.resultObj.userName,
play_count: this.formatPlayCount(body.resultObj.playCount),
},
}
})
})
},
filterListDetail(rawList) {
// console.log(rawList)
let ids = new Set()
const list = []
rawList.forEach(songData => {
if (!songData) return
if (ids.has(songData.songId)) return
ids.add(songData.songId)
const types = []
const _types = {}
let size = null
for (const item of songData.purviewRoleVOs) {
if (!item.filesize) continue
size = sizeFormate(item.filesize)
switch (item.quality) {
case 's':
types.push({ type: 'wav', size })
_types.wav = {
size,
}
break
case 'h':
types.push({ type: '320k', size })
_types['320k'] = {
size,
}
break
case 'l':
types.push({ type: '128k', size })
_types['128k'] = {
size,
}
break
}
}
types.reverse()
list.push({
singer: this.getSinger(songData.singerVOs),
name: songData.songName,
albumName: songData.albumName,
albumId: songData.albumId,
source: 'xm',
interval: formatPlayTime(parseInt(songData.length / 1000)),
songmid: songData.songId,
songStringId: songData.songStringId,
img: songData.albumLogo || songData.albumLogoS,
lrc: null,
lrcUrl: songData.lyricInfo && songData.lyricInfo.lyricFile,
otherSource: null,
types,
_types,
typeUrl: {},
})
})
return list
},
// 获取列表数据
getList(sortId, tagId, page, tryNum = 0) {
if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = xmRequest(this.songListUrl, this.getSongListData(sortId, tagId, page))
return this._requestObj_list.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return {
list: this.filterList(body.result.data.collects),
total: body.result.data.pagingVO.count,
page,
limit: body.result.data.pagingVO.pageSize,
source: 'xm',
}
})
},
filterList(rawData) {
return rawData.map(item => ({
play_count: this.formatPlayCount(item.playCount),
id: item.listId,
author: item.userName,
name: item.collectName,
time: null,
img: item.collectLogo,
grade: null,
desc: null,
source: 'xm',
}))
},
// 获取标签
getTag(tryNum = 0) {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = xmRequest(this.tagsUrl, { recommend: 1 })
return this._requestObj_tags.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body.result.data.recommendTags)
})
},
filterTagInfo(rawList) {
return {
hotTag: rawList[0].items.map(item => ({
id: item.name,
name: item.name,
source: 'xm',
})),
tags: rawList.slice(1).map(item => ({
name: item.title,
list: item.items.map(tag => ({
parent_id: item.title,
parent_name: item.title,
id: tag.name,
name: tag.name,
source: 'xm',
})),
})),
source: 'xm',
}
},
getTags() {
return this.getTag()
},
}
// getList
// getTags
// getListDetail
-122
View File
@@ -1,122 +0,0 @@
import { httpGet, httpFetch } from '../../request'
import { toMD5 } from '../../index'
// import crateIsg from './isg'
import { rendererInvoke, NAMES } from '../../../../common/ipc'
if (!window.xm_token) {
let data = window.localStorage.getItem('xm_token')
window.xm_token = data ? JSON.parse(data) : {
cookies: {},
cookie: null,
token: null,
isGetingToken: false,
}
window.xm_token.isGetingToken = false
}
export const formatSinger = rawData => rawData.replace(/&/g, '、')
const matchToken = headers => {
let cookies = {}
let token
for (const item of headers['set-cookie']) {
const [key, value] = item.substring(0, item.indexOf(';')).split('=')
cookies[key] = value
if (key == 'xm_sg_tk') token = value.substring(0, value.indexOf('_'))
}
// console.log(cookies)
return { token, cookies }
}
const wait = time => new Promise(resolve => setTimeout(() => resolve(), time))
const createToken = (token, path, params) => toMD5(`${token}_xmMain_${path}_${params}`)
const handleSaveToken = ({ token, cookies }) => {
Object.assign(window.xm_token.cookies, cookies)
// window.xm_token.cookies.isg = crateIsg()
window.xm_token.cookie = Object.keys(window.xm_token.cookies).map(k => `${k}=${window.xm_token.cookies[k]};`).join(' ')
if (token) window.xm_token.token = token
window.localStorage.setItem('xm_token', JSON.stringify(window.xm_token))
}
export const getToken = (path, params) => new Promise((resolve, reject) => {
if (window.xm_token.isGetingToken) return wait(1000).then(() => getToken(path, params).then(data => resolve(data)))
if (window.xm_token.token) return resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
window.xm_token.isGetingToken = true
httpGet('https://www.xiami.com/', (err, resp) => {
window.xm_token.isGetingToken = false
if (err) return reject(err)
if (resp.statusCode != 200) return reject(new Error('获取失败'))
handleSaveToken(matchToken(resp.headers))
resolve({ token: createToken(window.xm_token.token, path, params), cookie: window.xm_token.cookie })
})
})
const baseUrl = 'https://www.xiami.com'
export const xmRequest = (path, params = '') => {
let query = params
if (params != '') {
params = JSON.stringify(params)
query = '&_q=' + encodeURIComponent(params)
}
let requestObj = {
isInited: false,
isCancelled: false,
cancelHttp() {
if (!this.isInited) this.isCancelled = true
this.requestObj.cancelHttp()
},
}
requestObj.promise = getToken(path, params).then(data => {
// console.log(data)
if (requestObj.isCancelled) return Promise.reject('取消请求')
let url = path
if (!/^http/.test(path)) url = baseUrl + path
let s = `_s=${data.token}${query}`
url += (url.includes('?') ? '&' : '?') + s
requestObj.requestObj = httpFetch(url, {
headers: {
Referer: 'https://www.xiami.com/',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
cookie: data.cookie,
},
})
return requestObj.requestObj.promise.then(resp => {
// console.log(resp.body)
if (resp.statusCode != 200) {
// console.log(resp.headers)
window.xm_token.token = null
return Promise.reject(new Error('获取失败'))
}
if (resp.body.code !== 'SUCCESS' && resp.body.rgv587_flag == 'sm') {
window.globalObj.xm.isShowVerify = true
return wait(300).then(() => {
return rendererInvoke(NAMES.mainWindow.handle_xm_verify_open, 'https:' + resp.body.url).then(x5sec => {
handleSaveToken({ cookies: { x5sec } })
// console.log(x5sec)
window.globalObj.xm.isShowVerify = false
return Promise.reject(new Error('获取成功'))
}).catch(err => {
window.globalObj.xm.isShowVerify = false
return Promise.reject(err)
})
})
}
if (resp.headers['set-cookie']) handleSaveToken(matchToken(resp.headers))
return Promise.resolve(resp)
})
})
return requestObj
}
export const closeVerifyModal = async() => {
if (!window.globalObj.xm.isShowVerify) return
await rendererInvoke(NAMES.mainWindow.handle_xm_verify_close)
window.globalObj.xm.isShowVerify = false
}
+46 -29
View File
@@ -45,7 +45,7 @@ export default {
return {
clickTime: window.performance.now(),
clickIndex: -1,
selectdData: [],
selectedData: [],
isShowDownloadMultiple: false,
tabId: 'all',
keyEvent: {
@@ -59,6 +59,7 @@ export default {
play: true,
start: true,
pause: true,
playLater: true,
file: true,
search: true,
remove: true,
@@ -74,13 +75,13 @@ export default {
computed: {
...mapGetters(['setting']),
...mapGetters('download', ['list', 'downloadStatus']),
...mapGetters('player', ['listId', 'playIndex']),
...mapGetters('player', ['playInfo']),
isPlayList() {
return this.listId == 'download'
return this.playInfo.listId == 'download'
},
playListIndex() {
if (this.listId != 'download' || !this.list.length) return
let info = this.list[this.playIndex]
if (this.playInfo.listId != 'download' || !this.list.length) return
let info = this.list[this.playInfo.playIndex]
if (!info) return -1
let key = info.key
return this.showList.findIndex(i => i.key == key)
@@ -140,6 +141,11 @@ export default {
action: 'pause',
hide: !this.listMenu.itemMenuControl.pause,
},
{
name: this.$t('view.download.menu_play_later'),
action: 'playLater',
hide: !this.listMenu.itemMenuControl.playLater,
},
{
name: this.$t('view.download.menu_file'),
action: 'file',
@@ -229,7 +235,7 @@ export default {
},
handleSelectData(event, clickIndex) {
if (this.keyEvent.isShiftDown) {
if (this.selectdData.length) {
if (this.selectedData.length) {
let lastSelectIndex = this.lastSelectIndex
this.removeAllSelect()
if (lastSelectIndex != clickIndex) {
@@ -240,8 +246,8 @@ export default {
clickIndex = temp
isNeedReverse = true
}
this.selectdData = this.showList.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) this.selectdData.reverse()
this.selectedData = this.showList.slice(lastSelectIndex, clickIndex + 1)
if (isNeedReverse) this.selectedData.reverse()
let nodes = this.$refs.dom_tbody.childNodes
do {
nodes[lastSelectIndex].classList.add('active')
@@ -250,24 +256,24 @@ export default {
}
} else {
event.currentTarget.classList.add('active')
this.selectdData.push(this.showList[clickIndex])
this.selectedData.push(this.showList[clickIndex])
this.lastSelectIndex = clickIndex
}
} else if (this.keyEvent.isModDown) {
this.lastSelectIndex = clickIndex
let item = this.showList[clickIndex]
let index = this.selectdData.indexOf(item)
let index = this.selectedData.indexOf(item)
if (index < 0) {
this.selectdData.push(item)
this.selectedData.push(item)
event.currentTarget.classList.add('active')
} else {
this.selectdData.splice(index, 1)
this.selectedData.splice(index, 1)
event.currentTarget.classList.remove('active')
}
} else if (this.selectdData.length) this.removeAllSelect()
} else if (this.selectedData.length) this.removeAllSelect()
},
removeAllSelect() {
this.selectdData = []
this.selectedData = []
let dom_tbody = this.$refs.dom_tbody
if (!dom_tbody) return
let nodes = dom_tbody.querySelectorAll('.active')
@@ -306,6 +312,14 @@ export default {
case 'remove':
this.removeTask(item)
break
case 'playLater':
if (this.selectedData.length) {
this.setTempPlayList(this.selectedData.map(s => ({ listId: 'download', musicInfo: s })))
this.removeAllSelect()
} else {
this.setTempPlayList([{ listId: 'download', musicInfo: item }])
}
break
case 'file':
this.handleOpenFolder(item.filePath)
break
@@ -316,7 +330,7 @@ export default {
},
handleSelectAllData() {
this.removeAllSelect()
this.selectdData = [...this.showList]
this.selectedData = [...this.showList]
let nodes = this.$refs.dom_tbody.childNodes
for (const node of nodes) {
@@ -324,19 +338,19 @@ export default {
}
},
// async handleFlowBtnClick(action) {
// let selectdData = [...this.selectdData]
// let selectedData = [...this.selectedData]
// this.removeAllSelect()
// await this.$nextTick()
// switch (action) {
// case 'start':
// this.startTasks(selectdData)
// this.startTasks(selectedData)
// break
// case 'pause':
// this.pauseTasks(selectdData)
// this.pauseTasks(selectedData)
// break
// case 'remove':
// this.removeTasks(selectdData)
// this.removeTasks(selectedData)
// break
// }
// },
@@ -353,7 +367,7 @@ export default {
})
},
handleTabChange() {
this.selectdData = []
this.selectedData = []
},
handleListItemRigthClick(event, index) {
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.showList[index].musicInfo.source].getMusicDetailPageUrl
@@ -368,16 +382,19 @@ export default {
let item = this.showList[index]
if (item.isComplate) {
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.file = true
this.listMenu.itemMenuControl.start =
this.listMenu.itemMenuControl.pause = false
} else if (item.status === this.downloadStatus.ERROR || item.status === this.downloadStatus.PAUSE) {
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.pause =
this.listMenu.itemMenuControl.file = false
this.listMenu.itemMenuControl.start = true
} else {
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.start =
this.listMenu.itemMenuControl.file = false
this.listMenu.itemMenuControl.pause = true
@@ -407,10 +424,10 @@ export default {
if (item) this.handlePlay(item)
break
case 'start':
if (this.selectdData.length) {
let selectdData = [...this.selectdData]
if (this.selectedData.length) {
let selectedData = [...this.selectedData]
this.removeAllSelect()
this.startTasks(selectdData)
this.startTasks(selectedData)
} else {
item = this.showList[index]
if (item) this.startTask(item)
@@ -420,10 +437,10 @@ export default {
}
break
case 'pause':
if (this.selectdData.length) {
let selectdData = [...this.selectdData]
if (this.selectedData.length) {
let selectedData = [...this.selectedData]
this.removeAllSelect()
this.pauseTasks(selectdData)
this.pauseTasks(selectedData)
} else {
item = this.showList[index]
if (item) this.pauseTask(item)
@@ -443,10 +460,10 @@ export default {
if (item) this.handleSearch(item.musicInfo)
break
case 'remove':
if (this.selectdData.length) {
let selectdData = [...this.selectdData]
if (this.selectedData.length) {
let selectedData = [...this.selectedData]
this.removeAllSelect()
this.removeTasks(selectdData)
this.removeTasks(selectedData)
} else {
item = this.showList[index]
if (item) this.removeTask(item)
+9 -1
View File
@@ -77,7 +77,7 @@ export default {
...mapActions('leaderboard', ['getBoardsList', 'getList']),
...mapActions('download', ['createDownload', 'createDownloadMultiple']),
...mapMutations('list', ['listAdd', 'listAddMultiple']),
...mapMutations('player', ['setList']),
...mapMutations('player', ['setList', 'setTempPlayList']),
handleListBtnClick(info) {
switch (info.action) {
case 'download':
@@ -121,6 +121,14 @@ export default {
}
this.testPlay(info.index)
break
case 'playLater':
if (this.selectedData.length) {
this.setTempPlayList(this.selectedData.map(s => ({ listId: '__temp__', musicInfo: s })))
this.resetSelect()
} else {
this.setTempPlayList([{ listId: '__temp__', musicInfo: this.list[info.index] }])
}
break
case 'search':
this.handleSearch(info.index)
break
+27 -11
View File
@@ -37,7 +37,7 @@
table
tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody")
tr(v-for='(item, index) in list' :key='item.songmid' :id="'mid_' + item.songmid" @contextmenu="handleListItemRigthClick($event, index)"
@click="handleDoubleClick($event, index)" :class="[isPlayList && playIndex === index ? $style.active : '', assertApiSupport(item.source) ? null : $style.disabled]")
@click="handleDoubleClick($event, index)" :class="[isPlayList && playInfo.playIndex === index ? $style.active : '', assertApiSupport(item.source) ? null : $style.disabled]")
td.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
td.break
span.select {{item.name}}
@@ -54,7 +54,7 @@
td(style="width: 9%;")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
td(style="width: 15%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" @btn-click="handleListBtnClick")
material-list-buttons(:index="index" @btn-click="handleListBtnClick" :download-btn="assertApiSupport(item.source)")
//- 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-secondary(type='button' @click.stop='handleRemove(index)') 删除
@@ -122,6 +122,7 @@ export default {
isShowItemMenu: false,
itemMenuControl: {
play: true,
playLater: true,
copyName: true,
addTo: true,
moveTo: true,
@@ -145,12 +146,12 @@ export default {
computed: {
...mapGetters(['userInfo', 'setting']),
...mapGetters('list', ['isInitedList', 'defaultList', 'loveList', 'userList']),
...mapGetters('player', {
playerListId: 'listId',
playIndex: 'playIndex',
}),
...mapGetters('player', ['playInfo']),
playerListId() {
return this.playInfo.listId
},
isPlayList() {
return this.playerListId == this.listId
return this.playInfo.listId == this.listId
},
list() {
return this.listData.list
@@ -223,6 +224,11 @@ export default {
action: 'download',
disabled: !this.listMenu.itemMenuControl.download,
},
{
name: this.$t('view.list.list_play_later'),
action: 'playLater',
disabled: !this.listMenu.itemMenuControl.playLater,
},
{
name: this.$t('view.list.list_add_to'),
action: 'addTo',
@@ -355,6 +361,7 @@ export default {
...mapActions('download', ['createDownload', 'createDownloadMultiple']),
...mapMutations('player', {
setPlayList: 'setList',
setTempPlayList: 'setTempPlayList',
}),
listenEvent() {
window.eventHub.$on('key_shift_down', this.handle_key_shift_down)
@@ -562,7 +569,7 @@ export default {
}
},
testPlay(index) {
if (!this.assertApiSupport(this.list[index].source)) return
// if (!this.assertApiSupport(this.list[index].source)) return
this.setPlayList({ list: this.listData, index })
},
handleRemove(index) {
@@ -572,7 +579,7 @@ export default {
switch (info.action) {
case 'download': {
const minfo = this.list[info.index]
if (!this.assertApiSupport(minfo.source)) return
// if (!this.assertApiSupport(minfo.source)) return
this.musicInfo = minfo
this.$nextTick(() => {
this.isShowDownload = true
@@ -725,8 +732,9 @@ export default {
},
handleListItemRigthClick(event, index) {
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.list[index].source].getMusicDetailPageUrl
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.download =
// this.listMenu.itemMenuControl.play =
// this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.download =
this.assertApiSupport(this.list[index].source)
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
if (dom_selected) dom_selected.classList.remove('selected')
@@ -789,6 +797,14 @@ export default {
case 'play':
this.testPlay(index)
break
case 'playLater':
if (this.selectdListDetailData.length) {
this.setTempPlayList(this.selectdListDetailData.map(s => ({ listId: this.listId, musicInfo: s })))
this.removeAllSelectListDetail()
} else {
this.setTempPlayList([{ listId: this.listId, musicInfo: this.list[index] }])
}
break
case 'copyName':
minfo = this.list[index]
clipboardWriteText(this.setting.download.fileName.replace('歌名', minfo.name).replace('歌手', minfo.singer))
+107 -54
View File
@@ -3,60 +3,65 @@
//- transition
div(:class="$style.header")
material-tab(:class="$style.tab" :list="sources" align="left" item-key="id" item-name="name" v-model="searchSourceId")
div(v-if="listInfo.list.length" :class="$style.list")
div(:class="$style.thead")
table
thead
tr
th.nobreak.center(style="width: 5%;") #
th.nobreak {{$t('view.search.name')}}
th.nobreak(style="width: 22%;") {{$t('view.search.singer')}}
th.nobreak(style="width: 22%;") {{$t('view.search.album')}}
th.nobreak(style="width: 8%;") {{$t('view.search.time')}}
th.nobreak(style="width: 13%;") {{$t('view.search.action')}}
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody")
tr(v-for='(item, index) in listInfo.list' :key='item.songmid' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)")
td.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
td.break
span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
span(:class="[$style.labelSource, $style.noSelect]" v-if="searchSourceId == 'all'") {{item.source}}
td.break(style="width: 22%;")
span.select {{item.singer}}
td.break(style="width: 22%;")
span.select {{item.albumName}}
td(style="width: 8%;")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
td(style="width: 13%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :remove-btn="false" :class="$style.listBtn"
:play-btn="assertApiSupport(item.source)"
:download-btn="assertApiSupport(item.source)"
@btn-click="handleListBtnClick")
div(:class="$style.pagination")
material-pagination(:max-page="listInfo.allPage" :limit="listInfo.limit" :page="page" @btn-click="handleTogglePage")
div(v-else :class="$style.noitem")
div.scroll(:class="$style.noitemListContainer" v-if="setting.search.isShowHotSearch || setting.search.isShowHistorySearch")
dl(:class="[$style.noitemList, $style.noitemHotSearchList]" v-if="setting.search.isShowHotSearch")
dt(:class="$style.noitemListTitle") {{$t('view.search.hot_search')}}
dd(:class="$style.noitemListItem" @click="handleNoitemSearch(item)" v-for="item in hotSearchList") {{item}}
dl(:class="$style.noitemList" v-if="setting.search.isShowHistorySearch && historyList.length")
dt(:class="$style.noitemListTitle")
span {{$t('view.search.history_search')}}
span(:class="$style.historyClearBtn" @click="clearHistory" :tips="$t('view.search.history_clear')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-eraser')
dd(:class="$style.noitemListItem" v-for="(item, index) in historyList" @contextmenu="removeHistory(index)" :key="index + item" @click="handleNoitemSearch(item)" :tips="$t('view.search.history_remove')") {{item}}
div(v-else :class="$style.noitem_list")
p {{$t('view.search.no_item')}}
div(:class="$style.main")
div(:class="$style.list" v-show="isLoading || listInfo.list.length")
div(:class="$style.thead")
table
thead
tr
th.nobreak.center(style="width: 5%;") #
th.nobreak {{$t('view.search.name')}}
th.nobreak(style="width: 22%;") {{$t('view.search.singer')}}
th.nobreak(style="width: 22%;") {{$t('view.search.album')}}
th.nobreak(style="width: 8%;") {{$t('view.search.time')}}
th.nobreak(style="width: 13%;") {{$t('view.search.action')}}
div.scroll(:class="$style.tbody" ref="dom_scrollContent")
table
tbody(@contextmenu.capture="handleContextMenu" ref="dom_tbody")
tr(v-for='(item, index) in listInfo.list' :key='item.songmid' @contextmenu="handleListItemRigthClick($event, index)" @click="handleDoubleClick($event, index)")
td.nobreak.center(style="width: 5%; padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
td.break
span.select {{item.name}}
span.badge.badge-theme-success(:class="[$style.labelQuality, $style.noSelect]" v-if="item._types.ape || item._types.flac || item._types.wav") {{$t('material.song_list.lossless')}}
span.badge.badge-theme-info(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item._types['320k']") {{$t('material.song_list.high_quality')}}
span(:class="[$style.labelSource, $style.noSelect]" v-if="searchSourceId == 'all'") {{item.source}}
td.break(style="width: 22%;")
span.select {{item.singer}}
td.break(style="width: 22%;")
span.select {{item.albumName}}
td(style="width: 8%;")
span(:class="[$style.time, $style.noSelect]") {{item.interval || '--/--'}}
td(style="width: 13%; padding-left: 0; padding-right: 0;")
material-list-buttons(:index="index" :remove-btn="false" :class="$style.listBtn"
:play-btn="assertApiSupport(item.source)"
:download-btn="assertApiSupport(item.source)"
@btn-click="handleListBtnClick")
div(:class="$style.pagination")
material-pagination(:max-page="listInfo.allPage" :limit="listInfo.limit" :page="page" @btn-click="handleTogglePage")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated fadeOut")
div(v-show="isLoading" :class="$style.loading")
p {{$t('view.search.loding_list')}}
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="!isLoading && !listInfo.list.length" :class="$style.noitem")
div.scroll(:class="$style.noitemListContainer" v-if="setting.search.isShowHotSearch || setting.search.isShowHistorySearch")
dl(:class="[$style.noitemList, $style.noitemHotSearchList]" v-if="setting.search.isShowHotSearch")
dt(:class="$style.noitemListTitle") {{$t('view.search.hot_search')}}
dd(:class="$style.noitemListItem" @click="handleNoitemSearch(item)" v-for="item in hotSearchList") {{item}}
dl(:class="$style.noitemList" v-if="setting.search.isShowHistorySearch && historyList.length")
dt(:class="$style.noitemListTitle")
span {{$t('view.search.history_search')}}
span(:class="$style.historyClearBtn" @click="clearHistory" :tips="$t('view.search.history_clear')")
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 512 512' space='preserve')
use(xlink:href='#icon-eraser')
dd(:class="$style.noitemListItem" v-for="(item, index) in historyList" @contextmenu="removeHistory(index)" :key="index + item" @click="handleNoitemSearch(item)" :tips="$t('view.search.history_remove')") {{item}}
div(v-else :class="$style.noitem_list")
p {{$t('view.search.no_item')}}
material-menu(:menus="listItemMenu" :location="listMenu.menuLocation" item-name="name" :isShow="listMenu.isShowItemMenu" @menu-click="handleListItemMenuClick")
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectedData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
//- material-flow-btn(:show="isShowEditBtn && (searchSourceId == 'all' || assertApiSupport(searchSourceId))" :remove-btn="false" @btn-click="handleFlowBtnClick")
material-list-add-modal(:show="isShowListAdd" :musicInfo="musicInfo" @close="handleListAddModalClose")
material-list-add-multiple-modal(:show="isShowListAddMultiple" :musicList="selectedData" @close="handleListAddMultipleModalClose")
material-menu(:menus="listItemMenu" :location="listMenu.menuLocation" item-name="name" :isShow="listMenu.isShowItemMenu" @menu-click="handleListItemMenuClick")
</template>
<script>
@@ -88,6 +93,7 @@ export default {
isShowItemMenu: false,
itemMenuControl: {
play: true,
playLater: true,
addTo: true,
download: true,
sourceDetail: true,
@@ -97,6 +103,7 @@ export default {
y: 0,
},
},
isLoading: false,
}
},
beforeRouteUpdate(to, from, next) {
@@ -148,6 +155,7 @@ export default {
},
searchSourceId(n) {
if (n === this.setting.search.searchSource) return
if (this.text !== '') this.isLoading = true
this.$nextTick(() => {
this.page = 1
this.handleSearch(this.text, this.page)
@@ -174,6 +182,11 @@ export default {
action: 'download',
disabled: !this.listMenu.itemMenuControl.download,
},
{
name: this.$t('view.search.list_play_later'),
action: 'playLater',
disabled: !this.listMenu.itemMenuControl.playLater,
},
{
name: this.$t('view.search.list_add_to'),
action: 'addTo',
@@ -199,7 +212,7 @@ export default {
...mapActions('download', ['createDownload', 'createDownloadMultiple']),
...mapMutations('search', ['clearList', 'setPage', 'removeHistory', 'clearHistory']),
...mapMutations('list', ['listAdd', 'listAddMultiple']),
...mapMutations('player', ['setList']),
...mapMutations('player', ['setList', 'setTempPlayList']),
...mapActions('hotSearch', {
getHotSearch: 'getList',
}),
@@ -238,12 +251,14 @@ export default {
},
handleSearch(text, page) {
if (text === '') return this.clearList()
this.isLoading = true
this.search({ text, page, limit: this.listInfo.limit }).then(data => {
this.page = page
this.$nextTick(() => {
scrollTo(this.$refs.dom_scrollContent, 0)
})
}).finally(() => {
this.isLoading = false
})
},
handleDoubleClick(event, index) {
@@ -259,7 +274,7 @@ export default {
this.clickIndex = index
return
}
this.testPlay(index)
if (this.assertApiSupport(this.listInfo.list[index].source)) this.testPlay(index)
this.clickTime = 0
this.clickIndex = -1
},
@@ -423,6 +438,7 @@ export default {
handleListItemRigthClick(event, index) {
this.listMenu.itemMenuControl.sourceDetail = !!musicSdk[this.listInfo.list[index].source].getMusicDetailPageUrl
this.listMenu.itemMenuControl.play =
this.listMenu.itemMenuControl.playLater =
this.listMenu.itemMenuControl.download =
this.assertApiSupport(this.listInfo.list[index].source)
let dom_selected = this.$refs.dom_tbody.querySelector('tr.selected')
@@ -456,6 +472,14 @@ export default {
}
this.testPlay(index)
break
case 'playLater':
if (this.selectedData.length) {
this.setTempPlayList(this.selectedData.map(s => ({ listId: '__temp__', musicInfo: s })))
this.resetSelect()
} else {
this.setTempPlayList([{ listId: '__temp__', musicInfo: this.listInfo.list[index] }])
}
break
case 'addTo':
if (this.selectedData.length) {
this.$nextTick(() => {
@@ -504,6 +528,12 @@ export default {
.header {
flex: none;
}
.main {
flex: auto;
min-height: 0;
position: relative;
display: flex;
}
.list {
// position: relative;
height: 100%;
@@ -560,10 +590,30 @@ export default {
// left: 50%;
// transform: translateX(-50%);
}
.loading {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
background-color: @color-theme_2;
p {
font-size: 24px;
color: @color-theme_2-font-label;
}
}
.noitem {
flex: auto;
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
display: flex;
flex-flow: column nowrap;
justify-content: center;
@@ -635,6 +685,9 @@ each(@themes, {
.labelSource {
color: ~'@{color-@{value}-theme}';
}
.loading {
background-color: ~'@{color-@{value}-theme_2}';
}
.noitem {
p {
color: ~'@{color-@{value}-theme_2-font-label}';
+367 -308
View File
@@ -1,286 +1,270 @@
<template lang="pug">
//- div(:class="$style.main")
div.scroll(:class="$style.toc")
ul(:class="$style.tocList")
li(:class="$style.tocListItem" v-for="h2 in toc" :key="h2.id")
h2(:class="$style.tocH2" :tips="h2.title")
a(:href="'#' + h2.id") {{h2.title}}
ul(:class="$style.tocList" v-if="h2.children.length")
li(:class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id")
h3(:class="$style.tocH3" :tips="h3.title")
a(:href="'#' + h3.id") {{h3.title}}
div.scroll(:class="$style.setting" ref="dom_setting")
dl(ref="dom_setting_list")
dt#basic {{$t('view.setting.basic')}}
dd
h3#basic_theme {{$t('view.setting.basic_theme')}}
div
ul(:class="$style.theme")
li(v-for="theme in themes.list" :key="theme.id" :tips="$t('store.state.theme_' + theme.class)" @click="current_setting.themeId = theme.id" :class="[theme.class, themes.active == theme.id ? $style.active : '']")
span
label {{$t('store.state.theme_' + theme.class)}}
div(:class="$style.main")
//- div.scroll(:class="$style.toc")
//- ul(:class="$style.tocList")
//- li(:class="$style.tocListItem" v-for="h2 in toc.list" :key="h2.id")
//- h2(:class="[$style.tocH2, toc.activeId == h2.id ? $style.active : null]" :tips="h2.title")
//- a(:href="'#' + h2.id" @click="toc.activeId = h2.id") {{h2.title}}
//- ul(:class="$style.tocList" v-if="h2.children.length")
//- li(:class="$style.tocSubListItem" v-for="h3 in h2.children" :key="h3.id")
//- h3(:class="[$style.tocH3, toc.activeId == h3.id ? $style.active : null]" :tips="h3.title")
//- a(:href="'#' + h3.id" @click="toc.activeId = h3.id") {{h3.title}}
div.scroll(:class="$style.setting" ref="dom_setting")
dl(ref="dom_setting_list")
dt#basic {{$t('view.setting.basic')}}
dd
h3#basic_theme {{$t('view.setting.basic_theme')}}
div
ul(:class="$style.theme")
li(v-for="theme in themes.list" :key="theme.id" :tips="$t('store.state.theme_' + theme.class)" @click="current_setting.themeId = theme.id" :class="[theme.class, themes.active == theme.id ? $style.active : '']")
span
label {{$t('store.state.theme_' + theme.class)}}
dd
h3#basic_show_animation {{$t('view.setting.basic_show_animation')}}
div
material-checkbox(id="setting_show_animate" v-model="current_setting.isShowAnimation" :label="$t('view.setting.is_show')")
dd
div(:class="[$style.gapTop, $style.top]")
material-checkbox(id="setting_show_animate" v-model="current_setting.isShowAnimation" :label="$t('view.setting.basic_show_animation')")
div(:class="$style.gapTop")
material-checkbox(id="setting_animate" v-model="current_setting.randomAnimate" :label="$t('view.setting.basic_animation')")
div(:class="$style.gapTop")
material-checkbox(id="setting_to_tray" v-model="current_setting.tray.isShow" @change="handleTrayShowChange" :label="$t('view.setting.basic_to_tray')")
dd(:tips="$t('view.setting.basic_animation_title')")
h3#basic_animation {{$t('view.setting.basic_animation')}}
div
material-checkbox(id="setting_animate" v-model="current_setting.randomAnimate" :label="$t('view.setting.is_enable')")
dd(:tips="$t('view.setting.basic_source_title')")
h3#basic_source {{$t('view.setting.basic_source')}}
div
div(v-for="item in apiSources" :key="item.id" :class="$style.gapTop")
material-checkbox(:id="`setting_api_source_${item.id}`" name="setting_api_source" @change="handleAPISourceChange(item.id)"
need v-model="current_setting.apiSource" :disabled="item.disabled" :value="item.id" :label="item.label")
dd(:tips="$t('view.setting.basic_source_title')")
h3#basic_source {{$t('view.setting.basic_source')}}
div
div(v-for="item in apiSources" :key="item.id" :class="$style.gapTop")
material-checkbox(:id="`setting_api_source_${item.id}`" name="setting_api_source" @change="handleAPISourceChange(item.id)"
need v-model="current_setting.apiSource" :disabled="item.disabled" :value="item.id" :label="item.label")
p(:class="$style.gapTop")
material-btn(:class="$style.btn" min @click="isShowUserApiModal = true") {{$t('view.setting.basic_source_user_api_btn')}}
dd(:tips="$t('view.setting.basic_to_tray_title')")
h3#basic_to_tray {{$t('view.setting.basic_to_tray')}}
div
material-checkbox(id="setting_to_tray" v-model="current_setting.tray.isShow" @change="handleTrayShowChange" :label="$t('view.setting.is_enable')")
dd(:tips="$t('view.setting.basic_window_size_title')")
h3#basic_window_size {{$t('view.setting.basic_window_size')}}
div
material-checkbox(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size" @change="handleWindowSizeChange" :class="$style.gapLeft"
need v-model="current_setting.windowSizeId" :value="item.id" :label="$t('view.setting.basic_window_size_' + item.name)" :key="item.id")
dd(:tips="$t('view.setting.basic_window_size_title')")
h3#basic_window_size {{$t('view.setting.basic_window_size')}}
div
material-checkbox(v-for="(item, index) in windowSizeList" :id="`setting_window_size_${item.id}`" name="setting_window_size" @change="handleWindowSizeChange" :class="$style.gapLeft"
need v-model="current_setting.windowSizeId" :value="item.id" :label="$t('view.setting.basic_window_size_' + item.name)" :key="item.id")
dd(:tips="$t('view.setting.basic_lang_title')")
h3#basic_lang {{$t('view.setting.basic_lang')}}
div
material-checkbox(v-for="item in languageList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
@change="handleLangChange(item.locale)" :class="$style.gapLeft"
need v-model="current_setting.langId" :value="item.locale" :label="item.name")
dd(:tips="$t('view.setting.basic_lang_title')")
h3#basic_lang {{$t('view.setting.basic_lang')}}
div
material-checkbox(v-for="item in languageList" :key="item.locale" :id="`setting_lang_${item.locale}`" name="setting_lang"
@change="handleLangChange(item.locale)" :class="$style.gapLeft"
need v-model="current_setting.langId" :value="item.locale" :label="item.name")
dd(:tips="$t('view.setting.basic_sourcename_title')")
h3#basic_sourcename {{$t('view.setting.basic_sourcename')}}
div
material-checkbox(v-for="item in sourceNameTypes" :key="item.id" :class="$style.gapLeft" :id="`setting_abasic_sourcename_${item.id}`"
name="setting_basic_sourcename" need v-model="current_setting.sourceNameType" :value="item.id" :label="item.label")
dd(:tips="$t('view.setting.basic_sourcename_title')")
h3#basic_sourcename {{$t('view.setting.basic_sourcename')}}
div
material-checkbox(v-for="item in sourceNameTypes" :key="item.id" :class="$style.gapLeft" :id="`setting_abasic_sourcename_${item.id}`"
name="setting_basic_sourcename" need v-model="current_setting.sourceNameType" :value="item.id" :label="item.label")
dd
h3#basic_control_btn_position {{$t('view.setting.basic_control_btn_position')}}
div
material-checkbox(v-for="item in controlBtnPositionList" :key="item.id" :class="$style.gapLeft" :id="`setting_basic_control_btn_position_${item.id}`"
name="setting_basic_control_btn_position" need v-model="current_setting.controlBtnPosition" :value="item.id" :label="item.name")
dd
h3#basic_control_btn_position {{$t('view.setting.basic_control_btn_position')}}
div
material-checkbox(v-for="item in controlBtnPositionList" :key="item.id" :class="$style.gapLeft" :id="`setting_basic_control_btn_position_${item.id}`"
name="setting_basic_control_btn_position" need v-model="current_setting.controlBtnPosition" :value="item.id" :label="item.name")
dt#play {{$t('view.setting.play')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_player_save_play_time" v-model="current_setting.player.isSavePlayTime" :label="$t('view.setting.play_save_play_time')")
div(:class="$style.gapTop")
material-checkbox(id="setting_player_lyric_transition" v-model="current_setting.player.isShowLyricTranslation" :label="$t('view.setting.play_lyric_transition')")
div(:class="$style.gapTop")
material-checkbox(id="setting_player_lyric_play_lxlrc" v-model="current_setting.player.isPlayLxlrc" :label="$t('view.setting.play_lyric_lxlrc')")
div(:class="$style.gapTop")
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality" :label="$t('view.setting.play_quality')")
div(:class="$style.gapTop")
material-checkbox(id="setting_player_showTaskProgess" v-model="current_setting.player.isShowTaskProgess" :label="$t('view.setting.play_task_bar')")
div(:class="$style.gapTop")
material-checkbox(id="setting_player_isMediaDeviceRemovedStopPlay" v-model="current_setting.player.isMediaDeviceRemovedStopPlay" :label="$t('view.setting.play_mediaDevice_remove_stop_play')")
dd(:tips="$t('view.setting.play_mediaDevice_title')")
h3#play_mediaDevice {{$t('view.setting.play_mediaDevice')}}
div
material-selection(:list="mediaDevices" :class="$style.gapLeft" v-model="current_setting.player.mediaDeviceId" item-key="deviceId" item-name="label")
dt#desktop_lyric {{$t('view.setting.desktop_lyric')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_enable" v-model="current_setting.desktopLyric.enable" :label="$t('view.setting.desktop_lyric_enable')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_lock" v-model="current_setting.desktopLyric.isLock" :label="$t('view.setting.desktop_lyric_lock')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="current_setting.desktopLyric.isAlwaysOnTop" :label="$t('view.setting.desktop_lyric_always_on_top')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_lockScreen" v-model="current_setting.desktopLyric.isLockScreen" :label="$t('view.setting.desktop_lyric_lock_screen')")
dt#play {{$t('view.setting.play')}}
dd(:tips="$t('view.setting.play_toggle_title')")
h3#play_toggle {{$t('view.setting.play_toggle')}}
div
material-checkbox(:id="`setting_player_togglePlay_${item.value}`" :class="$style.gapLeft" :value="item.value" :key="item.value"
v-model="current_setting.player.togglePlayMethod" v-for="item in togglePlayMethods" :label="item.name")
dd
h3#play_lyric_transition {{$t('view.setting.play_lyric_transition')}}
div
material-checkbox(id="setting_player_lyric_transition" v-model="current_setting.player.isShowLyricTransition" :label="$t('view.setting.is_show')")
dd(:tips="$t('view.setting.play_quality_title')")
h3#play_quality {{$t('view.setting.play_quality')}}
div
material-checkbox(id="setting_player_highQuality" v-model="current_setting.player.highQuality" :label="$t('view.setting.is_enable')")
dd(:tips="$t('view.setting.play_task_bar_title')")
h3#play_task_bar {{$t('view.setting.play_task_bar')}}
div
material-checkbox(id="setting_player_showTaskProgess" v-model="current_setting.player.isShowTaskProgess" :label="$t('view.setting.is_enable')")
dd(:tips="$t('view.setting.play_mediaDevice_remove_stop_play_title')")
h3#play_mediaDevice_remove_stop_play {{$t('view.setting.play_mediaDevice_remove_stop_play')}}
div
material-checkbox(id="setting_player_isMediaDeviceRemovedStopPlay" v-model="current_setting.player.isMediaDeviceRemovedStopPlay" :label="$t('view.setting.is_enable')")
dd(:tips="$t('view.setting.play_mediaDevice_title')")
h3#play_mediaDevice {{$t('view.setting.play_mediaDevice')}}
div
material-selection(:list="mediaDevices" :class="$style.gapLeft" v-model="current_setting.player.mediaDeviceId" item-key="deviceId" item-name="label")
dt#desktop_lyric {{$t('view.setting.desktop_lyric')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_enable" v-model="current_setting.desktopLyric.enable" :label="$t('view.setting.desktop_lyric_enable')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_lock" v-model="current_setting.desktopLyric.isLock" :label="$t('view.setting.desktop_lyric_lock')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_alwaysOnTop" v-model="current_setting.desktopLyric.isAlwaysOnTop" :label="$t('view.setting.desktop_lyric_always_on_top')")
div(:class="$style.gapTop")
material-checkbox(id="setting_desktop_lyric_lockScreen" v-model="current_setting.desktopLyric.isLockScreen" :label="$t('view.setting.desktop_lyric_lock_screen')")
dt#search {{$t('view.setting.search')}}
dd(:tips="$t('view.setting.search_hot_title')")
h3#search_hot {{$t('view.setting.search_hot')}}
div
material-checkbox(id="setting_search_showHot_enable" v-model="current_setting.search.isShowHotSearch" :label="$t('view.setting.is_show')")
dd(:tips="$t('view.setting.search_history_title')")
h3#search_history {{$t('view.setting.search_history')}}
div
material-checkbox(id="setting_search_showHistory_enable" v-model="current_setting.search.isShowHistorySearch" :label="$t('view.setting.is_show')")
dd(:tips="$t('view.setting.search_focus_search_box_title')")
h3#search_focus_search_box {{$t('view.setting.search_focus_search_box')}}
div
material-checkbox(id="setting_search_focusSearchBox_enable" v-model="current_setting.search.isFocusSearchBox" :label="$t('view.setting.is_enable')")
dt#search {{$t('view.setting.search')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_search_showHot_enable" v-model="current_setting.search.isShowHotSearch" :label="$t('view.setting.search_hot')")
div(:class="$style.gapTop")
material-checkbox(id="setting_search_showHistory_enable" v-model="current_setting.search.isShowHistorySearch" :label="$t('view.setting.search_history')")
div(:class="$style.gapTop")
material-checkbox(id="setting_search_focusSearchBox_enable" v-model="current_setting.search.isFocusSearchBox" :label="$t('view.setting.search_focus_search_box')")
dt#list {{$t('view.setting.list')}}
dd(:tips="$t('view.setting.list_source_title')")
h3#list_source {{$t('view.setting.list_source')}}
div
material-checkbox(id="setting_list_showSource_enable" v-model="current_setting.list.isShowSource" :label="$t('view.setting.is_show')")
dd(:tips="$t('view.setting.list_scroll_title')")
h3#list_scroll {{$t('view.setting.list_scroll')}}
div
material-checkbox(id="setting_list_scroll_enable" v-model="current_setting.list.isSaveScrollLocation" :label="$t('view.setting.is_enable')")
//- dd(:tips="播放列表是否显示专辑栏")
h3 专辑栏
div
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName" label="是否显示专辑栏")
dt#download {{$t('view.setting.download')}}
dd
material-checkbox(id="setting_download_enable" v-model="current_setting.download.enable" :label="$t('view.setting.download_enable')")
dd(:tips="$t('view.setting.download_path_title')")
h3#download_path {{$t('view.setting.download_path')}}
div
p
| {{$t('view.setting.download_path_label')}}
span.auto-hidden.hover(:tips="$t('view.setting.download_path_open_label')" :class="$style.savePath" @click="handleOpenDir(current_setting.download.savePath)") {{current_setting.download.savePath}}
p
material-btn(:class="$style.btn" min @click="handleChangeSavePath") {{$t('view.setting.download_path_change_btn')}}
dd(:tips="$t('view.setting.download_name_title')")
h3#download_name {{$t('view.setting.download_name')}}
div
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need
v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name")
dd
h3#download_data_embed {{$t('view.setting.download_data_embed')}}
div(:class="$style.gapTop")
material-checkbox(id="setting_download_isEmbedPic" v-model="current_setting.download.isEmbedPic" :label="$t('view.setting.download_embed_pic')")
div(:class="$style.gapTop")
material-checkbox(id="setting_download_isEmbedLyric" v-model="current_setting.download.isEmbedLyric" :label="$t('view.setting.download_embed_lyric')")
dd(:tips="$t('view.setting.download_lyric_title')")
h3#download_lyric {{$t('view.setting.download_lyric')}}
div
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" :label="$t('view.setting.is_enable')")
dt#list {{$t('view.setting.list')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_list_showSource_enable" v-model="current_setting.list.isShowSource" :label="$t('view.setting.list_source')")
div(:class="$style.gapTop")
material-checkbox(id="setting_list_scroll_enable" v-model="current_setting.list.isSaveScrollLocation" :label="$t('view.setting.list_scroll')")
//- dd(:tips="播放列表是否显示专辑栏")
h3 专辑栏
div
material-checkbox(id="setting_list_showalbum" v-model="current_setting.list.isShowAlbumName" label="是否显示专辑栏")
dt#download {{$t('view.setting.download')}}
dd
material-checkbox(id="setting_download_enable" v-model="current_setting.download.enable" :label="$t('view.setting.download_enable')")
dd(:tips="$t('view.setting.download_path_title')")
h3#download_path {{$t('view.setting.download_path')}}
div
p
| {{$t('view.setting.download_path_label')}}
span.auto-hidden.hover(:tips="$t('view.setting.download_path_open_label')" :class="$style.savePath" @click="handleOpenDir(current_setting.download.savePath)") {{current_setting.download.savePath}}
p
material-btn(:class="$style.btn" min @click="handleChangeSavePath") {{$t('view.setting.download_path_change_btn')}}
dd(:tips="$t('view.setting.download_name_title')")
h3#download_name {{$t('view.setting.download_name')}}
div
material-checkbox(:id="`setting_download_musicName_${item.value}`" :class="$style.gapLeft" name="setting_download_musicName" :value="item.value" :key="item.value" need
v-model="current_setting.download.fileName" v-for="item in musicNames" :label="item.name")
dd
h3#download_data_embed {{$t('view.setting.download_data_embed')}}
div(:class="$style.gapTop")
material-checkbox(id="setting_download_isEmbedPic" v-model="current_setting.download.isEmbedPic" :label="$t('view.setting.download_embed_pic')")
div(:class="$style.gapTop")
material-checkbox(id="setting_download_isEmbedLyric" v-model="current_setting.download.isEmbedLyric" :label="$t('view.setting.download_embed_lyric')")
dd(:tips="$t('view.setting.download_lyric_title')")
h3#download_lyric {{$t('view.setting.download_lyric')}}
div
material-checkbox(id="setting_download_isDownloadLrc" v-model="current_setting.download.isDownloadLrc" :label="$t('view.setting.is_enable')")
dt#hot_key {{$t('view.setting.hot_key')}}
dd
h3#hot_key_local_title {{$t('view.setting.hot_key_local_title')}}
div
material-checkbox(id="setting_download_hotKeyLocal" v-model="current_hot_key.local.enable" :label="$t('view.setting.is_enable')" @change="handleHotKeySaveConfig")
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.local.enable ? 1 : .6 }")
div(:class="$style.hotKeyItem" v-for="item in hotKeys.local")
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
material-input.key-bind(:class="$style.hotKeyItemInput" readonly @keyup.prevent :placeholder="$t('view.setting.hot_key_unset_input')"
:value="hotKeyConfig.local[item.name] && formatHotKeyName(hotKeyConfig.local[item.name].key)"
@focus="handleHotKeyFocus($event, item, 'local')"
@blur="handleHotKeyBlur($event, item, 'local')")
dt#hot_key {{$t('view.setting.hot_key')}}
dd
h3#hot_key_local_title {{$t('view.setting.hot_key_local_title')}}
div
material-checkbox(id="setting_download_hotKeyLocal" v-model="current_hot_key.local.enable" :label="$t('view.setting.is_enable')" @change="handleHotKeySaveConfig")
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.local.enable ? 1 : .6 }")
div(:class="$style.hotKeyItem" v-for="item in hotKeys.local")
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
material-input.key-bind(:class="$style.hotKeyItemInput" readonly @keyup.prevent :placeholder="$t('view.setting.hot_key_unset_input')"
:value="hotKeyConfig.local[item.name] && formatHotKeyName(hotKeyConfig.local[item.name].key)"
@focus="handleHotKeyFocus($event, item, 'local')"
@blur="handleHotKeyBlur($event, item, 'local')")
h3#hot_key_global_title {{$t('view.setting.hot_key_global_title')}}
div
material-checkbox(id="setting_download_hotKeyGlobal" v-model="current_hot_key.global.enable" :label="$t('view.setting.is_enable')" @change="handleEnableHotKey")
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.global.enable ? 1 : .6 }")
div(:class="$style.hotKeyItem" v-for="item in hotKeys.global")
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
material-input.key-bind(:class="[$style.hotKeyItemInput, hotKeyConfig.global[item.name] && hotKeyStatus[hotKeyConfig.global[item.name].key] && hotKeyStatus[hotKeyConfig.global[item.name].key].status === false ? $style.hotKeyFailed : null]"
:value="hotKeyConfig.global[item.name] && formatHotKeyName(hotKeyConfig.global[item.name].key)" @input.prevent readonly :placeholder="$t('view.setting.hot_key_unset_input')"
@focus="handleHotKeyFocus($event, item, 'global')"
@blur="handleHotKeyBlur($event, item, 'global')")
h3#hot_key_global_title {{$t('view.setting.hot_key_global_title')}}
div
material-checkbox(id="setting_download_hotKeyGlobal" v-model="current_hot_key.global.enable" :label="$t('view.setting.is_enable')" @change="handleEnableHotKey")
div(:class="$style.hotKeyContainer" :style="{ opacity: current_hot_key.global.enable ? 1 : .6 }")
div(:class="$style.hotKeyItem" v-for="item in hotKeys.global")
h4(:class="$style.hotKeyItemTitle") {{$t('view.setting.hot_key_' + item.name)}}
material-input.key-bind(:class="[$style.hotKeyItemInput, hotKeyConfig.global[item.name] && hotKeyStatus[hotKeyConfig.global[item.name].key] && hotKeyStatus[hotKeyConfig.global[item.name].key].status === false ? $style.hotKeyFailed : null]"
:value="hotKeyConfig.global[item.name] && formatHotKeyName(hotKeyConfig.global[item.name].key)" @input.prevent readonly :placeholder="$t('view.setting.hot_key_unset_input')"
@focus="handleHotKeyFocus($event, item, 'global')"
@blur="handleHotKeyBlur($event, item, 'global')")
dt#network {{$t('view.setting.network')}}
dd
h3#network_proxy_title {{$t('view.setting.network_proxy_title')}}
div
p
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" :label="$t('view.setting.is_enable')")
p
material-input(:class="$style.gapLeft" v-model.trim="current_setting.network.proxy.host" @change="handleProxyChange('host')" :placeholder="$t('view.setting.network_proxy_host')")
material-input(:class="$style.gapLeft" v-model.trim="current_setting.network.proxy.port" @change="handleProxyChange('port')" :placeholder="$t('view.setting.network_proxy_port')")
p
material-input(:class="$style.gapLeft" v-model.trim="current_setting.network.proxy.username" @change="handleProxyChange('username')" :placeholder="$t('view.setting.network_proxy_username')")
material-input(:class="$style.gapLeft" v-model.trim="current_setting.network.proxy.password" @change="handleProxyChange('password')" type="password" :placeholder="$t('view.setting.network_proxy_password')")
dt#odc {{$t('view.setting.odc')}}
dd
div(:class="$style.gapTop")
material-checkbox(id="setting_odc_isAutoClearSearchInput" v-model="current_setting.odc.isAutoClearSearchInput" :label="$t('view.setting.odc_clear_search_input')")
div(:class="$style.gapTop")
material-checkbox(id="setting_odc_isAutoClearSearchList" v-model="current_setting.odc.isAutoClearSearchList" :label="$t('view.setting.odc_clear_search_list')")
dt#backup {{$t('view.setting.backup')}}
dd
h3#backup_part {{$t('view.setting.backup_part')}}
div
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportPlayList") {{$t('view.setting.backup_part_import_list')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportPlayList") {{$t('view.setting.backup_part_export_list')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportSetting") {{$t('view.setting.backup_part_import_setting')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportSetting") {{$t('view.setting.backup_part_export_setting')}}
dd
h3#backup_all {{$t('view.setting.backup_all')}}
div
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportAllData") {{$t('view.setting.backup_all_import')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") {{$t('view.setting.backup_all_export')}}
dt#other {{$t('view.setting.other')}}
dd
h3#other_tray_theme {{$t('view.setting.other_tray_theme')}}
div
material-checkbox(:id="'setting_tray_theme_' + item.id" v-model="current_setting.tray.themeId" name="setting_tray_theme" need :class="$style.gapLeft"
:label="$t('view.setting.other_tray_theme_' + item.name)" :key="item.id" :value="item.id" v-for="item in trayThemeList")
dd
h3#other_resource_cache {{$t('view.setting.other_resource_cache')}}
div
p
| {{$t('view.setting.other_resource_cache_label')}}
span.auto-hidden {{cacheSize}}
p
material-btn(:class="$style.btn" min :disabled="isDisabledResourceCacheClear" @click="clearResourceCache") {{$t('view.setting.other_resource_cache_clear_btn')}}
dd
h3#other_play_list_cache {{$t('view.setting.other_play_list_cache')}}
div
material-btn(:class="$style.btn" min :disabled="isDisabledListCacheClear" @click="clearListCache") {{$t('view.setting.other_play_list_cache_clear_btn')}}
dt#update {{$t('view.setting.update')}}
dd
p.small
| {{$t('view.setting.update_latest_label')}}{{version.newVersion ? version.newVersion.version : $t('view.setting.update_unknown')}}
p.small {{$t('view.setting.update_current_label')}}{{version.version}}
p.small(v-if="this.version.downloadProgress" style="line-height: 1.5;")
| {{$t('view.setting.update_downloading')}}
br
| {{$t('view.setting.update_progress')}}{{downloadProgress}}
p(v-if="version.newVersion")
span(v-if="version.isLatestVer") {{$t('view.setting.update_latest')}}
material-btn(v-else :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") {{$t('view.setting.update_open_version_modal_btn')}}
p.small(v-else) {{$t('view.setting.update_checking')}}
dt#about {{$t('view.setting.about')}}
dd
p.small
| 本软件完全免费,代码已开源,开源地址:
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
p.small
| 最新版网盘下载地址(网盘内有Windows、MAC版):
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://www.lanzous.com/b0bf2cfa/')") 网盘地址
| &nbsp;&nbsp;密码:
span.hover(:tips="$t('view.setting.click_copy')" @click="clipboardWriteText('glqw')") glqw
p.small
| 软件的常见问题可转至:
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
p.small
strong 仔细 仔细 仔细
| 地阅读常见问题后,
p.small
| 仍有问题可加企鹅群&nbsp;
span.hover(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
| &nbsp;反馈
strong (为免满人,无事勿加,入群先看群公告)
| ,或到 GitHub 提交&nbsp;
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
dt#network {{$t('view.setting.network')}}
dd
h3#network_proxy_title {{$t('view.setting.network_proxy_title')}}
div
p
material-checkbox(id="setting_network_proxy_enable" v-model="current_setting.network.proxy.enable" @change="handleProxyChange('enable')" :label="$t('view.setting.is_enable')")
p
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.host" @change="handleProxyChange('host')" :placeholder="$t('view.setting.network_proxy_host')")
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.port" @change="handleProxyChange('port')" :placeholder="$t('view.setting.network_proxy_port')")
p
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.username" @change="handleProxyChange('username')" :placeholder="$t('view.setting.network_proxy_username')")
material-input(:class="$style.gapLeft" v-model="current_setting.network.proxy.password" @change="handleProxyChange('password')" type="password" :placeholder="$t('view.setting.network_proxy_password')")
dt#odc {{$t('view.setting.odc')}}
dd
h3#odc_clear_search_input {{$t('view.setting.odc_clear_search_input')}}
div
material-checkbox(id="setting_odc_isAutoClearSearchInput" v-model="current_setting.odc.isAutoClearSearchInput" :label="$t('view.setting.is_enable')")
dd
h3#odc_clear_search_list {{$t('view.setting.odc_clear_search_list')}}
div
material-checkbox(id="setting_odc_isAutoClearSearchList" v-model="current_setting.odc.isAutoClearSearchList" :label="$t('view.setting.is_enable')")
dt#backup {{$t('view.setting.backup')}}
dd
h3#backup_part {{$t('view.setting.backup_part')}}
div
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportPlayList") {{$t('view.setting.backup_part_import_list')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportPlayList") {{$t('view.setting.backup_part_export_list')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportSetting") {{$t('view.setting.backup_part_import_setting')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportSetting") {{$t('view.setting.backup_part_export_setting')}}
dd
h3#backup_all {{$t('view.setting.backup_all')}}
div
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleImportAllData") {{$t('view.setting.backup_all_import')}}
material-btn(:class="[$style.btn, $style.gapLeft]" min @click="handleExportAllData") {{$t('view.setting.backup_all_export')}}
dt#other {{$t('view.setting.other')}}
dd
h3#other_tray_theme {{$t('view.setting.other_tray_theme')}}
div
material-checkbox(:id="'setting_tray_theme_' + item.id" v-model="current_setting.tray.themeId" name="setting_tray_theme" need :class="$style.gapLeft"
:label="$t('view.setting.other_tray_theme_' + item.name)" :key="item.id" :value="item.id" v-for="item in trayThemeList")
dd
h3#other_cache {{$t('view.setting.other_cache')}}
div
p
| {{$t('view.setting.other_cache_label')}}
span.auto-hidden(:tips="$t('view.setting.other_cache_label_title')") {{cacheSize}}
p
material-btn(:class="$style.btn" min @click="clearCache") {{$t('view.setting.other_cache_clear_btn')}}
dt#update {{$t('view.setting.update')}}
dd
p.small
| {{$t('view.setting.update_latest_label')}}{{version.newVersion ? version.newVersion.version : $t('view.setting.update_unknown')}}
p.small {{$t('view.setting.update_current_label')}}{{version.version}}
p.small(v-if="this.version.downloadProgress" style="line-height: 1.5;")
| {{$t('view.setting.update_downloading')}}
br
| {{$t('view.setting.update_progress')}}{{downloadProgress}}
p(v-if="version.newVersion")
span(v-if="version.isLatestVer") {{$t('view.setting.update_latest')}}
material-btn(v-else :class="[$style.btn, $style.gapLeft]" min @click="showUpdateModal") {{$t('view.setting.update_open_version_modal_btn')}}
p.small(v-else) {{$t('view.setting.update_checking')}}
dt#about {{$t('view.setting.about')}}
dd
p.small
| 本软件完全免费,代码已开源,开源地址:
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#readme')") https://github.com/lyswhut/lx-music-desktop
p.small
| 最新版网盘下载地址(网盘内有Windows、MAC版):
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://www.lanzoux.com/b0bf2cfa/')") 网盘地址
| &nbsp;&nbsp;密码:
span.hover(:tips="$t('view.setting.click_copy')" @click="clipboardWriteText('glqw')") glqw
p.small
| 软件的常见问题可转至:
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/blob/master/FAQ.md')") 常见问题
p.small
strong 仔细 仔细 仔细
| 地阅读常见问题后,
p.small
| 仍有问题可加企鹅群&nbsp;
span.hover(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://jq.qq.com/?_wv=1027&k=51ECeq2')") 830125506
| &nbsp;反馈
strong (为免满人,无事勿加,入群先看群公告)
| ,或到 GitHub 提交&nbsp;
span.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop/issues')") issue
p.small 感谢以前捐赠过的人❤️,现在软件不再接受捐赠,建议把你们的爱心用来支持正版音乐,
p.small 由于软件开发的初衷仅是为了对新技术的学习与研究,因此软件直至停止维护都将会一直保持纯净。
br
p.small 感谢以前捐赠过的人❤️,现在软件不再接受捐赠,建议把你们的爱心用来支持正版音乐,
p.small 由于软件开发的初衷仅是为了对新技术的学习与研究,因此软件直至停止维护都将会一直保持纯净。
p.small
| 你已签署本软件的&nbsp;
material-btn(min @click="handleShowPact") 许可协议
| ,协议的在线版本在&nbsp;
strong.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
| &nbsp;。
br
p.small
| 你已签署本软件的&nbsp;
material-btn(min @click="handleShowPact") 许可协议
| ,协议的在线版本在&nbsp;
strong.hover.underline(:tips="$t('view.setting.click_open')" @click="handleOpenUrl('https://github.com/lyswhut/lx-music-desktop#%E9%A1%B9%E7%9B%AE%E5%8D%8F%E8%AE%AE')") 这里
| &nbsp;。
br
p
small By:
| 落雪无痕
p
small By:
| 落雪无痕
material-user-api-modal(v-model="isShowUserApiModal")
</template>
<script>
@@ -295,6 +279,8 @@ import {
clearCache,
sizeFormate,
setWindowSize,
getSetting,
saveSetting,
} from '../utils'
import { rendererSend, rendererInvoke, NAMES } from '@common/ipc'
import { mergeSetting, isMac } from '../../common/utils'
@@ -305,6 +291,7 @@ import { base as eventBaseName } from '../event/names'
import * as hotKeys from '../../common/hotKey'
import { mainWindow as eventsNameMainWindow, winLyric as eventsNameWinLyric } from '../../main/events/_name'
import { gzip, gunzip } from 'zlib'
import music from '../utils/music'
let hotKeyTargetInput
let newHotKey
@@ -322,32 +309,41 @@ export default {
? `${this.version.downloadProgress.percent.toFixed(2)}% - ${sizeFormate(this.version.downloadProgress.transferred)}/${sizeFormate(this.version.downloadProgress.total)} - ${sizeFormate(this.version.downloadProgress.bytesPerSecond)}/s`
: this.$t('view.setting.update_init')
},
togglePlayMethods() {
return [
{
name: this.$t('view.setting.play_toggle_list_loop'),
value: 'listLoop',
},
{
name: this.$t('view.setting.play_toggle_random'),
value: 'random',
},
{
name: this.$t('view.setting.play_toggle_list'),
value: 'list',
},
{
name: this.$t('view.setting.play_toggle_single_loop'),
value: 'singleLoop',
},
]
},
// togglePlayMethods() {
// return [
// {
// name: this.$t('view.setting.play_toggle_list_loop'),
// value: 'listLoop',
// },
// {
// name: this.$t('view.setting.play_toggle_random'),
// value: 'random',
// },
// {
// name: this.$t('view.setting.play_toggle_list'),
// value: 'list',
// },
// {
// name: this.$t('view.setting.play_toggle_single_loop'),
// value: 'singleLoop',
// },
// ]
// },
apiSources() {
return apiSourceInfo.map(api => ({
id: api.id,
label: this.$t('view.setting.basic_source_' + api.id) || api.name,
disabled: api.disabled,
}))
return [
...apiSourceInfo.map(api => ({
id: api.id,
label: this.$t('view.setting.basic_source_' + api.id) || api.name,
disabled: api.disabled,
})),
...window.globalObj.userApi.list.map(api => ({
id: api.id,
label: `${api.name}${api.description ? `(${api.description})` : ''}${api.id == this.setting.apiSource ? `[${this.getApiStatus()}]` : ''}`,
status: api.status,
message: api.message,
disabled: false,
})),
]
},
sourceNameTypes() {
return [
@@ -592,7 +588,13 @@ export default {
},
isEditHotKey: false,
toc: [],
isShowUserApiModal: false,
toc: {
list: [],
activeId: '',
},
isDisabledResourceCacheClear: false,
isDisabledListCacheClear: false,
}
},
watch: {
@@ -613,6 +615,9 @@ export default {
'setting.player.isMute'(n) {
this.current_setting.player.isMute = n
},
'setting.apiSource'(n) {
this.current_setting.apiSource = n
},
'setting.desktopLyric.enable'(n) {
this.current_setting.desktopLyric.enable = n
},
@@ -622,6 +627,9 @@ export default {
'setting.player.togglePlayMethod'(n) {
this.current_setting.player.togglePlayMethod = n
},
// 'setting.player.isPlayLxlrc'(n) {
// this.current_setting.player.isPlayLxlrc = n
// },
'current_setting.player.isShowTaskProgess'(n) {
if (n) return
this.$nextTick(() => {
@@ -644,10 +652,15 @@ export default {
window.eventHub.$off(eventBaseName.set_config, this.handleUpdateSetting)
window.eventHub.$off(eventBaseName.key_down, this.handleKeyDown)
window.eventHub.$off(eventBaseName.set_hot_key_config, this.handleUpdateHotKeyConfig)
if (this.current_setting.network.proxy.enable && !this.current_setting.network.proxy.host) window.globalObj.proxy.enable = false
},
methods: {
...mapMutations(['setSetting', 'setSettingVersion', 'setVersionModalVisible']),
...mapMutations('list', ['setList']),
...mapMutations('list', {
setList: 'setList',
clearMyListCache: 'clearCache',
}),
...mapMutations(['setMediaDeviceId']),
init() {
this.current_setting = JSON.parse(JSON.stringify(this.setting))
@@ -684,7 +697,7 @@ export default {
// }
// }
// console.log(toc)
// this.toc = toc
// this.toc.list = toc
// },
// handleListScroll(event) {
// // console.log(event.target.scrollTop)
@@ -740,15 +753,22 @@ export default {
if (list.location == null) list.location = 0
this.setList(list)
}
await this.refreshSetting(this.setting, this.settingVersion)
},
exportPlayList(path) {
const data = {
async exportPlayList(path) {
const data = JSON.parse(JSON.stringify({
type: 'playList',
data: [
this.defaultList,
this.loveList,
...this.userList,
],
}))
for await (const list of data.data) {
for await (const item of list.list) {
if (item.otherSource) delete item.otherSource
}
}
this.handleSaveFile(path, JSON.stringify(data))
},
@@ -760,9 +780,6 @@ export default {
return
}
if (allData.type !== 'allData') return
const { version: settingVersion, setting } = mergeSetting(allData.setting)
setting.isAgreePact = false
this.refreshSetting(setting, settingVersion)
// 兼容0.6.2及以前版本的列表数据
if (allData.defaultList) return this.setList({ id: 'default', list: allData.defaultList.list, name: '试听列表', location: 0 })
@@ -771,9 +788,14 @@ export default {
if (list.location == null) list.location = 0
this.setList(list)
}
const { version: settingVersion, setting } = mergeSetting(allData.setting)
setting.isAgreePact = false
await this.refreshSetting(setting, settingVersion)
},
async exportAllData(path) {
let allData = {
let allData = JSON.parse(JSON.stringify({
type: 'allData',
setting: Object.assign({ version: this.settingVersion }, this.setting),
playList: [
@@ -781,6 +803,11 @@ export default {
this.loveList,
...this.userList,
],
}))
for await (const list of allData.playList) {
for await (const item of list.list) {
if (item.otherSource) delete item.otherSource
}
}
this.handleSaveFile(path, JSON.stringify(allData))
},
@@ -872,19 +899,34 @@ export default {
this.cacheSize = sizeFormate(size)
})
},
clearCache() {
clearResourceCache() {
this.isDisabledResourceCacheClear = true
clearCache().then(() => {
this.getCacheSize()
this.isDisabledResourceCacheClear = false
})
},
clearListCache() {
this.isDisabledListCacheClear = true
this.clearMyListCache()
this.isDisabledListCacheClear = false
},
handleWindowSizeChange(index) {
let info = index == null ? this.windowSizeList[2] : this.windowSizeList[index]
setWindowSize(info.width, info.height)
},
refreshSetting(setting, version) {
async refreshSetting(newSetting, newVersion) {
await saveSetting(newSetting)
const { setting, version } = await getSetting()
this.setSetting(setting)
this.setSettingVersion(version)
if (setting.windowSizeId != null) this.handleWindowSizeChange(null, setting.windowSizeId)
window.globalObj.apiSource = setting.apiSource
if (/^user_api/.test(setting.apiSource)) {
rendererInvoke(NAMES.mainWindow.set_user_api, setting.apiSource)
} else {
window.globalObj.qualityList = music.supportQuality[setting.apiSource]
}
for (let key of Object.keys(setting.network.proxy)) {
window.globalObj.proxy[key] = setting.network.proxy[key]
}
@@ -1083,6 +1125,14 @@ export default {
})
})
},
getApiStatus() {
let status
if (window.globalObj.userApi.status) status = this.$t('view.setting.basic_source_status_success')
else if (window.globalObj.userApi.message == 'initing') status = this.$t('view.setting.basic_source_status_initing')
else status = `${this.$t('view.setting.basic_source_status_failed')} - ${window.globalObj.userApi.message}`
return status
},
},
}
</script>
@@ -1090,12 +1140,12 @@ export default {
<style lang="less" module>
@import '../assets/styles/layout.less';
// .main {
// display: flex;
// flex-flow: row nowrap;
// height: 100%;
// border-top: 1px solid rgba(0, 0, 0, 0.12);
// }
.main {
display: flex;
flex-flow: row nowrap;
height: 100%;
// border-top: 1px solid rgba(0, 0, 0, 0.12);
}
// .toc {
// flex: 0 0 15%;
@@ -1113,6 +1163,11 @@ export default {
// a {
// color: @color-theme;
// }
// &.active {
// a {
// color: @color-theme;
// }
// }
// }
// .tocH3 {
// font-size: 13px;
@@ -1194,6 +1249,10 @@ export default {
}
}
.gap-top {
&.top {
margin-top: 25px;
}
+ .gap-top {
margin-top: 10px;
}
+15 -5
View File
@@ -23,7 +23,7 @@
material-select(:class="$style.select" :list="sourceInfo.sources" item-key="id" item-name="name" v-model="source")
div(:class="$style.songListContent")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(:class="$style.songListContent" v-show="listData.list.length")
div(:class="$style.songListContent")
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div.scroll(:class="$style.songList" v-if="sortId !== 'importSongList'" ref="dom_scrollContent")
ul
@@ -49,7 +49,7 @@
li {{$t('view.song_list.tip_2')}}
li {{$t('view.song_list.tip_3')}}
transition(enter-active-class="animated-fast fadeIn" leave-active-class="animated-fast fadeOut")
div(v-show="!listData.list.length" :class="$style.noitem")
div(v-show="!listData.list.length && sortId !== 'importSongList'" :class="$style.noitem")
p {{$t('view.song_list.loding_list')}}
material-download-modal(:show="isShowDownload" :musicInfo="musicInfo" @select="handleAddDownload" @close="isShowDownload = false")
material-download-multiple-modal(:show="isShowDownloadMultiple" :list="selectedData" @select="handleAddDownloadMultiple" @close="isShowDownloadMultiple = false")
@@ -183,6 +183,7 @@ export default {
...mapMutations('list', ['listAdd', 'listAddMultiple', 'createUserList']),
...mapMutations('player', {
setPlayList: 'setList',
setTempPlayList: 'setTempPlayList',
}),
listenEvent() {
window.eventHub.$on('key_backspace_down', this.handle_key_backspace_down)
@@ -238,11 +239,19 @@ export default {
break
case 'play':
if (this.selectedData.length) {
this.listAddMultiple({ id: 'default', list: this.filterList(this.selectedData) })
this.listAddMultiple({ id: 'default', list: [...this.selectedData] })
this.resetSelect()
}
this.testPlay(info.index)
break
case 'playLater':
if (this.selectedData.length) {
this.setTempPlayList(this.selectedData.map(s => ({ listId: '__temp__', musicInfo: s })))
this.resetSelect()
} else {
this.setTempPlayList([{ listId: '__temp__', musicInfo: this.listDetail.list[info.index] }])
}
break
case 'search':
this.handleSearch(info.index)
break
@@ -410,10 +419,11 @@ export default {
})
},
async addSongListDetail() {
if (!this.listDetail.info.name) return
// console.log(this.listDetail.info)
// if (!this.listDetail.info.name) return
const list = await this.fetchList()
this.createUserList({
name: this.listDetail.info.name,
name: this.listDetail.info.name || `${this.listDetail.source}-list`,
id: `${this.listDetail.source}__${this.listDetail.id}`,
list,
source: this.listDetail.source,