This commit is contained in:
lyswhut
2022-10-29 11:36:35 +08:00
parent 66b965d9f7
commit e45cee6596
715 changed files with 57096 additions and 31771 deletions
+6 -5
View File
@@ -1,6 +1,6 @@
import Sortable, { AutoScroll } from 'sortablejs/modular/sortable.core.esm'
import { onMounted } from '@renderer/utils/vueTools'
import { base as eventBaseName } from '@renderer/event/names'
import { onMounted } from '@common/utils/vueTools'
import { clearDownKeys } from '@renderer/event'
Sortable.mount(new AutoScroll())
@@ -28,13 +28,14 @@ export default ({ dom_list, dragingItemClassName, filter, onUpdate, onStart = no
onUnchoose() {
onEnd()
// 处于拖动状态期间,键盘事件无法监听,拖动结束手动清理按下的键
window.eventHub.emit(eventBaseName.setClearDownKeys)
// window.app_event.emit(eventBaseName.setClearDownKeys)
clearDownKeys()
},
onStart(event) {
window.eventHub.emit(eventBaseName.dragStart)
window.app_event.dragStart()
},
onEnd(event) {
window.eventHub.emit(eventBaseName.dragEnd)
window.app_event.dragEnd()
},
})
})
@@ -1,8 +1,8 @@
import { useI18n } from '@renderer/utils/vueTools'
import { useI18n } from '@renderer/plugins/i18n'
import { dialog } from '@renderer/plugins/Dialog'
export default () => {
const { t } = useI18n()
const t = useI18n()
return (type) => {
@@ -19,6 +19,7 @@ export default () => {
message = t('list_import_tip__alldata')
break
case 'playListPart':
case 'playListPart_v2':
message = t('list_import_tip__playlist_part')
break
@@ -1,38 +0,0 @@
import { onMounted, onBeforeUnmount, ref } from '@renderer/utils/vueTools'
export default name => {
const keyDown = ref(false)
const down = `key_${name}_down`
const up = `key_${name}_up`
const handle_key_down = event => {
if (!keyDown.value) {
// console.log(event)
switch (event.event.target.tagName) {
case 'INPUT':
case 'SELECT':
case 'TEXTAREA':
return
default: if (event.event.target.isContentEditable) return
}
keyDown.value = true
}
}
const handle_key_up = () => {
if (keyDown.value) keyDown.value = false
}
onMounted(() => {
window.eventHub.on(down, handle_key_down)
window.eventHub.on(up, handle_key_up)
})
onBeforeUnmount(() => {
window.eventHub.off(down, handle_key_down)
window.eventHub.off(up, handle_key_up)
})
return keyDown
}
@@ -0,0 +1,38 @@
import { onMounted, onBeforeUnmount, ref } from '@common/utils/vueTools'
export default (name: string) => {
const keyDown = ref(false)
const down = `key_${name}_down`
const up = `key_${name}_up`
const handle_key_down = (event: LX.KeyDownEevent) => {
if (!keyDown.value) {
// console.log(event)
switch ((event.event?.target as HTMLElement).tagName) {
case 'INPUT':
case 'SELECT':
case 'TEXTAREA':
return
default: if ((event.event?.target as HTMLElement).isContentEditable) return
}
keyDown.value = true
}
}
const handle_key_up = () => {
if (keyDown.value) keyDown.value = false
}
onMounted(() => {
window.key_event.on(down, handle_key_down)
window.key_event.on(up, handle_key_up)
})
onBeforeUnmount(() => {
window.key_event.off(down, handle_key_down)
window.key_event.off(up, handle_key_up)
})
return keyDown
}
+44 -16
View File
@@ -1,8 +1,10 @@
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@renderer/utils/vueTools'
import { scrollTo, throttle, formatPlayTime2 } from '@renderer/utils'
import { player as eventPlayerNames } from '@renderer/event/names'
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from '@common/utils/vueTools'
import { throttle, formatPlayTime2 } from '@common/utils/common'
import { scrollTo } from '@common/utils/renderer'
import { play } from '@renderer/core/player/action'
// import { player as eventPlayerNames } from '@renderer/event/names'
export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
export default ({ isPlay, lyric, playProgress, isShowLyricProgressSetting, offset }) => {
const dom_lyric = ref(null)
const dom_lyric_text = ref(null)
const dom_skip_line = ref(null)
@@ -28,10 +30,8 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
if (time == -1) return
handleSkipMouseLeave()
isStopScroll.value = false
let offset = lyric.offset + lyric.tempOffset
if (offset) offset = offset / 1000
window.eventHub.emit(eventPlayerNames.setProgress, Math.max(time + offset, 0))
if (!isPlay.value) window.eventHub.emit(eventPlayerNames.setPlay)
window.app_event.setProgress(time)
if (!isPlay.value) play()
}
const handleSkipMouseEnter = () => {
isSkipMouseEnter = true
@@ -51,13 +51,15 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
if (dom_pre_line === dom) return
if (dom.tagName == 'SPAN') {
dom = dom.parentNode.parentNode
} else if (dom.classList.contains('font')) {
} else if (dom.classList.contains('line')) {
dom = dom.parentNode
}
if (dom.time == null) {
if (lyric.lines.length) {
time = dom.classList.contains('pre') ? 0 : lyric.lines[lyric.lines.length - 1].time ?? 0
if (time) time = time / 1000
time = Math.max(time - lyric.offset - lyric.tempOffset, 0)
time /= 1000
if (time > playProgress.maxPlayTime) time = playProgress.maxPlayTime
timeStr.value = formatPlayTime2(time)
} else {
time = -1
@@ -65,7 +67,9 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
}
} else {
time = dom.time
if (time) time = time / 1000
time = Math.max(time - lyric.offset - lyric.tempOffset, 0)
time /= 1000
if (time > playProgress.maxPlayTime) time = playProgress.maxPlayTime
timeStr.value = formatPlayTime2(time)
}
dom_pre_line = dom
@@ -100,31 +104,49 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
handleScrollLrc()
}, 3000)
}
const handleLyricMouseDown = event => {
const handleLyricDown = (y) => {
// console.log(event)
if (delayScrollTimeout) {
clearTimeout(delayScrollTimeout)
delayScrollTimeout = null
}
isMsDown.value = true
msDownY = event.clientY
msDownY = y
msDownScrollY = dom_lyric.value.scrollTop
}
const handleLyricMouseDown = event => {
handleLyricDown(event.clientY)
}
const handleLyricTouchStart = event => {
if (event.changedTouches.length) {
const touch = event.changedTouches[0]
handleLyricDown(touch.clientY)
}
}
const handleMouseMsUp = event => {
isMsDown.value = false
}
const handleMouseMsMove = event => {
const handleMove = (y) => {
if (isMsDown.value) {
if (!isStopScroll.value) isStopScroll.value = true
if (cancelScrollFn) {
cancelScrollFn()
cancelScrollFn = null
}
dom_lyric.value.scrollTop = msDownScrollY + msDownY - event.clientY
dom_lyric.value.scrollTop = msDownScrollY + msDownY - y
startLyricScrollTimeout()
setTime()
}
}
const handleMouseMsMove = event => {
handleMove(event.clientY)
}
const handleTouchMove = (e) => {
if (e.changedTouches.length) {
const touch = e.changedTouches[0]
handleMove(touch.clientY)
}
}
const handleWheel = (event) => {
console.log(event.deltaY)
@@ -146,7 +168,7 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
dom_lyric_text.value.textContent = ''
dom_lyric_text.value.appendChild(dom_line_content)
nextTick(() => {
dom_lines = dom_lyric.value.querySelectorAll('.lrc-content')
dom_lines = dom_lyric.value.querySelectorAll('.line-content')
handleScrollLrc()
})
}
@@ -189,6 +211,8 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
onMounted(() => {
document.addEventListener('mousemove', handleMouseMsMove)
document.addEventListener('mouseup', handleMouseMsUp)
document.addEventListener('touchmove', handleTouchMove)
document.addEventListener('touchend', handleMouseMsUp)
initLrc(lyric.lines, null)
nextTick(() => {
@@ -199,6 +223,8 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
onBeforeUnmount(() => {
document.removeEventListener('mousemove', handleMouseMsMove)
document.removeEventListener('mouseup', handleMouseMsUp)
document.removeEventListener('touchmove', handleTouchMove)
document.removeEventListener('touchend', handleMouseMsUp)
})
return {
@@ -209,9 +235,11 @@ export default ({ isPlay, lyric, isShowLyricProgressSetting }) => {
isMsDown,
timeStr,
handleLyricMouseDown,
handleLyricTouchStart,
handleWheel,
handleSkipPlay,
handleSkipMouseEnter,
handleSkipMouseLeave,
handleScrollLrc,
}
}
@@ -1,4 +1,7 @@
import { onMounted, onBeforeUnmount, watch, reactive, ref, nextTick } from '@renderer/utils/vueTools'
import { onMounted, onBeforeUnmount, watch, reactive, ref } from '@common/utils/vueTools'
import { isFullscreen } from '@renderer/store'
let rootOffset = window.dt ? 0 : 8
export default ({ visible, location, onHide }) => {
const transition1 = 'transform, opacity'
@@ -10,17 +13,20 @@ export default ({ visible, location, onHide }) => {
top: 0,
opacity: 0,
transitionProperty: 'transform, opacity',
transform: 'scale(0) translate(0,0)',
transform: 'scale(.8, .7) translate(0,0)',
pointerEvents: 'none',
})
const handleShow = () => {
show = true
menuStyles.opacity = 1
menuStyles.transform = `scaleY(1) translate(${handleGetOffsetXY(location.value.x, location.value.y)})`
menuStyles.transform = `scale(1) translate(${handleGetOffsetXY(location.value.x, location.value.y)})`
menuStyles.pointerEvents = 'auto'
}
const handleHide = () => {
menuStyles.opacity = 0
menuStyles.transform = 'scale(0) translate(0, 0)'
menuStyles.transform = 'scale(.8, .7) translate(0, 0)'
menuStyles.pointerEvents = 'none'
show = false
}
const handleGetOffsetXY = (left, top) => {
@@ -33,8 +39,8 @@ export default ({ visible, location, onHide }) => {
const offsetHeight = containerHeight - top - listHeight
let x = 0
let y = 0
if (containerWidth > listWidth && offsetWidth < 17) {
x = offsetWidth - 17
if (containerWidth > listWidth && offsetWidth < 12) {
x = offsetWidth - 12
}
if (containerHeight > listHeight && offsetHeight < 5) {
y = offsetHeight - 5
@@ -55,15 +61,19 @@ export default ({ visible, location, onHide }) => {
visible ? handleShow() : handleHide()
}, { immediate: true })
watch(isFullscreen, isFullscreen => {
rootOffset = window.dt || isFullscreen ? 0 : 8
}, { immediate: true })
watch(location, location => {
menuStyles.left = location.x + 2 + 'px'
menuStyles.top = location.y + 'px'
nextTick(() => {
if (show) {
if (menuStyles.transitionProperty != transition2) menuStyles.transitionProperty = transition2
menuStyles.transform = `scaleY(1) translate(${handleGetOffsetXY(location.x, location.y)})`
}
})
menuStyles.left = location.x - rootOffset + 2 + 'px'
menuStyles.top = location.y - rootOffset + 'px'
// nextTick(() => {
if (show) {
if (menuStyles.transitionProperty != transition2) menuStyles.transitionProperty = transition2
menuStyles.transform = `scale(1) translate(${handleGetOffsetXY(location.x, location.y)})`
}
// })
}, { deep: true })
onMounted(() => {
@@ -1,20 +1,21 @@
import { appSetting, setTogglePlayMode } from '@renderer/store/setting'
import {
computed,
useCommit,
} from '@renderer/utils/vueTools'
} from '@common/utils/vueTools'
import { useI18n } from '@renderer/plugins/i18n'
const playNextModes = [
'listLoop',
'random',
'list',
'singleLoop',
]
export default ({ setting, t }) => {
const setPlayNextMode = useCommit('setPlayNextMode')
'none',
] as const
export default () => {
const t = useI18n()
const nextTogglePlayName = computed(() => {
switch (setting.value.player.togglePlayMethod) {
switch (appSetting['player.togglePlayMethod']) {
case 'listLoop': return t('player__play_toggle_mode_list_loop')
case 'random': return t('player__play_toggle_mode_random')
case 'singleLoop': return t('player__play_toggle_mode_single_loop')
@@ -24,9 +25,9 @@ export default ({ setting, t }) => {
})
const toggleNextPlayMode = () => {
let index = playNextModes.indexOf(setting.value.player.togglePlayMethod)
if (++index >= playNextModes.length) index = -1
setPlayNextMode(playNextModes[index] || '')
let index = playNextModes.indexOf(appSetting['player.togglePlayMethod'])
if (++index >= playNextModes.length) index = 0
setTogglePlayMode(playNextModes[index] ?? playNextModes[0])
}
return {
@@ -1,6 +1,5 @@
import { ref, onBeforeUnmount, toRef } from '@renderer/utils/vueTools'
import { player as eventPlayerNames } from '@renderer/event/names'
import { playProgress } from '@renderer/core/share/playProgress'
import { ref, onBeforeUnmount, toRef } from '@common/utils/vueTools'
import { playProgress } from '@renderer/store/player/playProgress'
export default () => {
const isActiveTransition = ref(false)
@@ -15,10 +14,10 @@ export default () => {
isActiveTransition.value = true
}
window.eventHub.on(eventPlayerNames.activeTransition, handleActiveTransition)
window.app_event.on('activePlayProgressTransition', handleActiveTransition)
onBeforeUnmount(() => {
window.eventHub.off(eventPlayerNames.activeTransition, handleActiveTransition)
window.app_event.off('activePlayProgressTransition', handleActiveTransition)
})
return {
@@ -1,34 +0,0 @@
import { setListUpdateTime } from '@renderer/utils/data'
import { setFetchingListStatus } from '@renderer/core/share/list'
import { useAction, useCommit } from '@renderer/utils/vueTools'
export default () => {
const getBoardListAll = useAction('leaderboard', 'getListAll')
const getListDetailAll = useAction('songList', 'getListDetailAll')
const setList = useCommit('list', 'setList')
const fetchList = (id, source, sourceListId) => {
setFetchingListStatus(id, true)
let promise
if (/board__/.test(sourceListId)) {
const id = sourceListId.replace(/board__/, '')
promise = getBoardListAll({ id, isRefresh: true })
} else {
promise = getListDetailAll({ source, id: sourceListId, isRefresh: true })
}
return promise.finally(() => {
setFetchingListStatus(id, false)
})
}
return async targetListInfo => {
const list = await fetchList(targetListInfo.id, targetListInfo.source, targetListInfo.sourceListId)
// console.log(targetListInfo.list.length, list.length)
setList({
...targetListInfo,
list,
})
setListUpdateTime(targetListInfo.id, Date.now())
}
}
@@ -1,29 +1,29 @@
import {
computed,
useCommit,
} from '@renderer/utils/vueTools'
} from '@common/utils/vueTools'
import { useI18n } from '@renderer/plugins/i18n'
import { appSetting, setLockDesktopLyric, setVisibleDesktopLyric } from '@renderer/store/setting'
export default ({ setting, t }) => {
const setVisibleDesktopLyric = useCommit('setVisibleDesktopLyric')
const setLockDesktopLyric = useCommit('setLockDesktopLyric')
export default () => {
const t = useI18n()
const toggleDesktopLyricBtnTitle = computed(() => {
return `${
setting.value.desktopLyric.enable
appSetting['desktopLyric.enable']
? t('player__desktop_lyric_off')
: t('player__desktop_lyric_on')
}(${
setting.value.desktopLyric.isLock
appSetting['desktopLyric.isLock']
? t('player__desktop_lyric_unlock')
: t('player__desktop_lyric_lock')
})`
})
const toggleDesktopLyric = () => {
setVisibleDesktopLyric(!setting.value.desktopLyric.enable)
setVisibleDesktopLyric(!appSetting['desktopLyric.enable'])
}
const toggleLockDesktopLyric = () => {
setLockDesktopLyric(!setting.value.desktopLyric.isLock)
setLockDesktopLyric(!appSetting['desktopLyric.isLock'])
}
return {
-91
View File
@@ -1,91 +0,0 @@
import { rendererSend, rendererInvoke, NAMES } from '../../common/ipc'
import { throttle } from './index'
let listPosition = {}
let listPrevSelectId
let listUpdateInfo = {}
const saveListPosition = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listPosition',
data: listPosition,
})
}, 1000)
export const initListPosition = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listPosition').then(data => {
if (!data) data = {}
listPosition = data
})
}
export const getListPosition = id => listPosition[id] || 0
export const getListPositionAll = () => listPosition
export const setListPositionAll = positions => {
listPosition = positions
saveListPosition()
}
export const setListPosition = (id, position) => {
listPosition[id] = position || 0
saveListPosition()
}
export const removeListPosition = id => {
delete listPosition[id]
saveListPosition()
}
const saveListPrevSelectId = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listPrevSelectId',
data: listPrevSelectId,
})
}, 200)
export const initListPrevSelectId = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listPrevSelectId').then(id => {
listPrevSelectId = id
})
}
export const getListPrevSelectId = () => listPrevSelectId
export const setListPrevSelectId = id => {
listPrevSelectId = id
saveListPrevSelectId()
}
export const initListUpdateInfo = () => {
return rendererInvoke(NAMES.mainWindow.get_data, 'listUpdateInfo').then(data => {
if (!data) data = {}
// console.log(data)
listUpdateInfo = data
})
}
const saveListUpdateInfo = throttle(() => {
rendererSend(NAMES.mainWindow.save_data, {
path: 'listUpdateInfo',
data: listUpdateInfo,
})
}, 1000)
export const getListUpdateInfo = () => listUpdateInfo
export const setListUpdateInfo = info => {
listUpdateInfo = info
saveListUpdateInfo()
}
export const setListAutoUpdate = (id, enable) => {
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
targetInfo.isAutoUpdate = enable
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
export const setListUpdateTime = (id, time) => {
const targetInfo = listUpdateInfo[id] ?? { updateTime: '', isAutoUpdate: false }
targetInfo.updateTime = time
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
// export const setListUpdateInfo = (id, { updateTime, isAutoUpdate }) => {
// listUpdateInfo[id] = { updateTime, isAutoUpdate }
// saveListUpdateInfo()
// }
export const removeListUpdateInfo = id => {
delete listUpdateInfo[id]
saveListUpdateInfo()
}
+171
View File
@@ -0,0 +1,171 @@
/* eslint-disable @typescript-eslint/no-dynamic-delete */
import {
saveListPositionInfo as saveListPositionInfoFromData,
getListPositionInfo as getListPositionInfoFromData,
saveListPrevSelectId as saveListPrevSelectIdFromData,
getListPrevSelectId as getListPrevSelectIdFromData,
saveListUpdateInfo as saveListUpdateInfoFromData,
getListUpdateInfo as getListUpdateInfoFromData,
saveSearchSetting as saveSearchSettingFromData,
getSearchSetting as getSearchSettingFromData,
saveSongListSetting as saveSongListSettingFromData,
getSongListSetting as getSongListSettingFromData,
saveLeaderboardSetting as saveLeaderboardSettingFromData,
getLeaderboardSetting as getLeaderboardSettingFromData,
saveViewPrevState as saveViewPrevStateFromData,
} from '@renderer/utils/ipc'
import { throttle } from '@common/utils'
import { DEFAULT_SETTING, LIST_IDS } from '@common/constants'
import { dateFormat } from './index'
import { setUpdateTime } from '@renderer/store/list/action'
let listPosition: LX.List.ListPositionInfo
let listPrevSelectId: string
let listUpdateInfo: LX.List.ListUpdateInfo
let searchSetting: typeof DEFAULT_SETTING['search']
let songListSetting: typeof DEFAULT_SETTING['songList']
let leaderboardSetting: typeof DEFAULT_SETTING['leaderboard']
const saveListPositionThrottle = throttle(() => {
saveListPositionInfoFromData(listPosition)
}, 1000)
const saveSearchSettingThrottle = throttle(() => {
saveSearchSettingFromData(searchSetting)
}, 1000)
const saveSongListSettingThrottle = throttle(() => {
saveSongListSettingFromData(songListSetting)
}, 1000)
const saveLeaderboardSettingThrottle = throttle(() => {
saveLeaderboardSettingFromData(leaderboardSetting)
}, 1000)
const saveViewPrevStateThrottle = throttle((state) => {
saveViewPrevStateFromData(state)
}, 1000)
export const getListPosition = async(id: string): Promise<number> => {
if (listPosition == null) listPosition = await getListPositionInfoFromData() ?? {}
return listPosition[id] ?? 0
}
export const setListPosition = async(id: string, position?: number) => {
if (listPosition == null) await getListPosition(id)
listPosition[id] = position ?? 0
saveListPositionThrottle()
}
export const removeListPosition = (id: string) => {
delete listPosition[id]
saveListPositionThrottle()
}
export const overwriteListPosition = (ids: string[]) => {
const removedIds = []
for (const id of Object.keys(listPosition)) {
if (ids.includes(id)) continue
removedIds.push(id)
}
for (const id of removedIds) delete listPosition[id]
saveListPositionThrottle()
}
const saveListPrevSelectIdThrottle = throttle(() => {
saveListPrevSelectIdFromData(listPrevSelectId)
}, 200)
export const getListPrevSelectId = async() => {
if (listPrevSelectId == null) listPrevSelectId = await getListPrevSelectIdFromData() ?? LIST_IDS.DEFAULT
return listPrevSelectId ?? LIST_IDS.DEFAULT
}
export const saveListPrevSelectId = (id: string) => {
listPrevSelectId = id
saveListPrevSelectIdThrottle()
}
const saveListUpdateInfo = throttle(() => {
saveListUpdateInfoFromData(listUpdateInfo)
}, 1000)
export const getListUpdateInfo = async() => {
if (listUpdateInfo == null) {
listUpdateInfo = await getListUpdateInfoFromData() ?? {}
for (const [id, info] of Object.entries(listUpdateInfo)) {
setUpdateTime(id, info.updateTime ? dateFormat(info.updateTime) : '')
}
}
return listUpdateInfo
}
export const setListUpdateInfo = async(info: LX.List.ListUpdateInfo) => {
if (listUpdateInfo == null) await getListUpdateInfo()
listUpdateInfo = info
saveListUpdateInfo()
}
export const setListAutoUpdate = async(id: string, enable: boolean) => {
if (listUpdateInfo == null) await getListUpdateInfo()
const targetInfo = listUpdateInfo[id] ?? { updateTime: 0, isAutoUpdate: false }
targetInfo.isAutoUpdate = enable
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
export const setListUpdateTime = async(id: string, time: number) => {
if (listUpdateInfo == null) await getListUpdateInfo()
const targetInfo = listUpdateInfo[id] ?? { updateTime: 0, isAutoUpdate: false }
targetInfo.updateTime = time
listUpdateInfo[id] = targetInfo
saveListUpdateInfo()
}
// export const setListUpdateInfo = (id, { updateTime, isAutoUpdate }) => {
// listUpdateInfo[id] = { updateTime, isAutoUpdate }
// saveListUpdateInfo()
// }
export const removeListUpdateInfo = (id: string) => {
delete listUpdateInfo[id]
saveListUpdateInfo()
}
export const overwriteListUpdateInfo = (ids: string[]) => {
const removedIds = []
for (const id of Object.keys(listPosition)) {
if (ids.includes(id)) continue
removedIds.push(id)
}
for (const id of removedIds) delete listUpdateInfo[id]
saveListUpdateInfo()
}
export const getSearchSetting = async() => {
if (!searchSetting) searchSetting = await getSearchSettingFromData()
return { ...searchSetting }
}
export const setSearchSetting = async(setting: Partial<typeof DEFAULT_SETTING['search']>) => {
if (!searchSetting) await getSearchSetting()
let requiredSave = false
if (setting.source && searchSetting.source != setting.source) requiredSave = true
if (setting.type && searchSetting.type != setting.type) requiredSave = true
if (setting.temp_source && searchSetting.temp_source != setting.temp_source) requiredSave = true
if (!requiredSave) return
searchSetting = Object.assign(searchSetting, setting)
saveSearchSettingThrottle()
}
export const getSongListSetting = async() => {
if (!songListSetting) songListSetting = await getSongListSettingFromData()
return { ...songListSetting }
}
export const setSongListSetting = async(setting: Partial<typeof DEFAULT_SETTING['songList']>) => {
if (!songListSetting) await getSongListSetting()
songListSetting = Object.assign(songListSetting, setting)
saveSongListSettingThrottle()
}
export const getLeaderboardSetting = async() => {
if (!leaderboardSetting) leaderboardSetting = await getLeaderboardSettingFromData()
return { ...leaderboardSetting }
}
export const setLeaderboardSetting = async(setting: Partial<typeof DEFAULT_SETTING['leaderboard']>) => {
if (!leaderboardSetting) await getLeaderboardSetting()
leaderboardSetting = Object.assign(leaderboardSetting, setting)
saveLeaderboardSettingThrottle()
}
export const saveViewPrevState = (state: typeof DEFAULT_SETTING['viewPrevState']) => {
saveViewPrevStateThrottle(state)
}
-334
View File
@@ -1,334 +0,0 @@
import fs from 'fs'
import path from 'path'
import request from 'request'
import { EventEmitter } from 'events'
import { performance } from 'perf_hooks'
import { STATUS } from './util'
const defaultChunkInfo = {
path: null,
startByte: 0,
endByte: '',
}
const defaultRequestOptions = {
method: 'GET',
headers: {},
}
const defaultOptions = {
}
class Task extends EventEmitter {
/**
*
* @param {String} url download url
* @param {Object} chunkInfo
* @param {Object} options
*/
constructor(url, savePath, filename, options = {}) {
super()
this.resumeLastChunk = null
this.downloadUrl = url
this.chunkInfo = Object.assign({}, defaultChunkInfo, {
path: path.join(savePath, filename),
startByte: 0,
})
if (!this.chunkInfo.endByte) this.chunkInfo.endByte = ''
this.options = Object.assign({}, defaultOptions, options)
this.requestOptions = Object.assign({}, defaultRequestOptions, this.options.requestOptions || {})
if (!this.requestOptions.headers) this.requestOptions.headers = {}
this.progress = {
total: 0,
downloaded: 0,
speed: 0,
}
this.statsEstimate = {
time: 0,
bytes: 0,
prevBytes: 0,
}
this.status = STATUS.idle
}
__init() {
this.status = STATUS.init
const { path, startByte, endByte } = this.chunkInfo
this.progress.downloaded = 0
if (startByte != null) this.requestOptions.headers.range = `bytes=${startByte}-${endByte}`
return new Promise((resolve, reject) => {
if (!path) return resolve()
fs.stat(path, (errStat, stats) => {
if (errStat) {
if (errStat.code !== 'ENOENT') {
this.__handleError(errStat)
reject(errStat)
return
}
} else if (stats.size >= 10) {
fs.open(path, 'r', (errOpen, fd) => {
if (errOpen) {
this.__handleError(errOpen)
reject(errOpen)
return
}
fs.read(fd, Buffer.alloc(10), 0, 10, stats.size - 10, (errRead, bytesRead, buffer) => {
if (errRead) {
this.__handleError(errRead)
reject(errRead)
return
}
fs.close(fd, errClose => {
if (errClose) {
this.__handleError(errClose)
reject(errClose)
return
}
// resume download
// console.log(buffer)
this.resumeLastChunk = buffer
this.progress.downloaded = stats.size
this.requestOptions.headers.range = `bytes=${stats.size - 10}-${endByte || ''}`
resolve()
})
})
})
return
}
resolve()
})
})
}
__httpFetch(url, options) {
// console.log(options)
this.request = request(url, options)
.on('response', response => {
if (response.statusCode !== 200 && response.statusCode !== 206) {
if (response.statusCode == 416) {
fs.unlink(this.chunkInfo.path, async err => {
await this.__handleError(new Error(response.statusMessage))
this.chunkInfo.startByte = 0
this.resumeLastChunk = null
this.progress.downloaded = 0
if (err) this.__handleError(err)
})
return
}
this.status = STATUS.failed
this.emit('fail', response)
this.__closeRequest()
this.__closeWriteStream()
return
}
this.emit('response', response)
try {
this.__initDownload(response)
} catch (error) {
return this.__handleError(error)
}
this.status = STATUS.running
response
.on('data', this.__handleWriteData.bind(this))
.on('error', err => this.__handleError(err))
.on('end', () => {
if (response.complete) {
this.__handleComplete()
} else {
// this.__handleError(new Error('The connection was terminated while the message was still being sent'))
this.stop()
}
})
})
.on('error', err => this.__handleError(err))
.on('close', () => this.__closeWriteStream())
}
__initDownload(response) {
this.progress.total = parseInt(response.headers['content-length'] || 0)
let options = {}
let isResumable = this.options.forceResume || response.headers['accept-ranges'] !== 'none' || (typeof response.headers['accept-ranges'] == 'string' && parseInt(response.headers['accept-ranges'].replace(/^bytes=(\d+)/, '$1')) > 0)
if (isResumable) {
options.flags = 'a'
if (this.progress.downloaded) this.progress.total -= 10
} else {
if (this.chunkInfo.startByte > 0) return this.__handleError(new Error('The resource cannot be resumed download.'))
}
this.progress.total += this.progress.downloaded
this.statsEstimate.prevBytes = this.progress.downloaded
if (!this.chunkInfo.path) return this.__handleError(new Error('Chunk save Path is not set.'))
this.ws = fs.createWriteStream(this.chunkInfo.path, options)
this.ws.on('finish', () => this.__closeWriteStream())
this.ws.on('error', err => {
fs.unlink(this.chunkInfo.path, async unlinkErr => {
await this.__handleError(err)
this.chunkInfo.startByte = 0
this.resumeLastChunk = null
this.progress.downloaded = 0
if (unlinkErr && unlinkErr.code !== 'ENOENT') this.__handleError(unlinkErr)
})
})
}
__handleComplete() {
if (this.status == STATUS.error) return
this.__closeWriteStream().then(() => {
if (this.progress.downloaded == this.progress.total) {
this.status = STATUS.completed
this.emit('completed')
} else {
this.status = STATUS.stopped
this.emit('stop')
}
})
console.log('end')
}
__handleError(error) {
if (this.status == STATUS.error) return
this.status = STATUS.error
this.__closeRequest()
this.__closeWriteStream()
this.emit('error', error)
}
__closeWriteStream() {
return new Promise((resolve, reject) => {
if (!this.ws) return resolve()
console.log('close write stream')
this.ws.close(err => {
if (err) {
this.status = STATUS.error
this.emit('error', err)
reject(err)
return
}
this.ws = null
resolve()
})
})
}
__closeRequest() {
if (!this.request) return
console.log('close request')
this.request.abort()
this.request = null
}
__handleWriteData(chunk) {
if (this.resumeLastChunk) {
chunk = this.__handleDiffChunk(chunk)
if (!chunk) {
this.__handleStop().finally(() => {
this.__handleError(new Error('Resume failed, response chunk does not match.'))
})
return
}
}
// console.log('data', chunk)
if (this.status == STATUS.stopped || this.ws == null) return console.log('cancel write')
this.__calculateProgress(chunk.length)
this.ws.write(chunk, err => {
if (!err) return
console.log(err)
this.__handleError(err)
this.stop()
})
}
__handleDiffChunk(chunk) {
// console.log('diff', chunk)
let resumeLastChunkLen = this.resumeLastChunk.length
let chunkLen = chunk.length
let isOk
if (chunkLen >= resumeLastChunkLen) {
isOk = chunk.slice(0, resumeLastChunkLen).toString('hex') === this.resumeLastChunk.toString('hex')
if (!isOk) return null
this.resumeLastChunk = null
return chunk.slice(resumeLastChunkLen)
} else {
isOk = chunk.slice(0, chunkLen).toString('hex') === this.resumeLastChunk.slice(0, chunkLen).toString('hex')
if (!isOk) return null
this.resumeLastChunk = this.resumeLastChunk.slice(chunkLen)
return chunk.slice(chunkLen)
}
}
__handleStop() {
return new Promise((resolve, reject) => {
if (this.request) {
this.request.abort()
this.request = null
}
if (this.ws) {
this.ws.close(err => {
if (err) {
reject(err)
this.emit('error', err)
return
}
this.ws = null
resolve()
})
} else {
resolve()
}
})
}
__calculateProgress(receivedBytes) {
const currentTime = performance.now()
const elaspsedTime = currentTime - this.statsEstimate.time
const progress = this.progress
progress.downloaded += receivedBytes
progress.progress = progress.total ? (progress.downloaded / progress.total) * 100 : -1
// emit the progress every second or if finished
if (progress.downloaded === progress.total || elaspsedTime > 1000) {
this.statsEstimate.time = currentTime
this.statsEstimate.bytes = progress.downloaded - this.statsEstimate.prevBytes
this.statsEstimate.prevBytes = progress.downloaded
this.emit('progress', {
total: progress.total,
downloaded: progress.downloaded,
progress: progress.progress,
speed: this.statsEstimate.bytes,
})
}
}
async start() {
this.status = STATUS.running
await this.__init()
this.__httpFetch(this.downloadUrl, this.requestOptions)
this.emit('start')
}
async stop() {
if (this.status === STATUS.stopped) return
this.status = STATUS.stopped
await this.__handleStop()
this.emit('stop')
}
refreshUrl(url) {
this.downloadUrl = url
}
updateSaveInfo(filePath, fileName) {
this.chunkInfo.path = path.join(filePath, fileName)
}
}
export default Task
-83
View File
@@ -1,83 +0,0 @@
import Downloader from './Downloader'
// import { pauseResumeTimer } from './util'
import { sizeFormate, getProxyInfo } from '../index'
import { debugDownload } from '../env'
// these are the default options
// const options = {
// method: 'GET', // Request Method Verb
// // Custom HTTP Header ex: Authorization, User-Agent
// headers: {},
// fileName: '', // Custom filename when saved
// override: false, // if true it will override the file, otherwise will append '(number)' to the end of file
// forceResume: false, // If the server does not return the "accept-ranges" header, can be force if it does support it
// // httpRequestOptions: {}, // Override the http request options
// // httpsRequestOptions: {}, // Override the https request options, ex: to add SSL Certs
// }
export default ({
url,
path,
fileName,
method = 'get',
headers,
forceResume,
// resumeTime = 5000,
onCompleted = () => {},
onError = () => {},
onFail = () => {},
onStart = () => {},
onStop = () => {},
onProgress = () => {},
} = {}) => {
const dl = new Downloader(url, path, fileName, {
requestOptions: {
method,
headers,
proxy: getProxyInfo(),
},
forceResume,
})
dl.on('completed', () => {
onCompleted()
debugDownload && console.log('Download Completed')
}).on('error', err => {
if (err.message === 'socket hang up') return
onError(err)
debugDownload && console.error('Something happend', err)
}).on('start', () => {
onStart()
// pauseResumeTimer(dl, resumeTime)
}).on('progress', stats => {
const progress = stats.progress.toFixed(2)
const speed = sizeFormate(stats.speed)
onProgress({
progress,
speed,
downloaded: stats.downloaded,
total: stats.total,
})
if (debugDownload) {
const downloaded = sizeFormate(stats.downloaded)
const total = sizeFormate(stats.total)
console.log(`${speed}/s - ${progress}% [${downloaded}/${total}]`)
}
}).on('stop', () => {
onStop()
debugDownload && console.log('paused')
}).on('fail', resp => {
onFail(resp)
debugDownload && console.log('fail')
})
debugDownload && console.log('Downloading: ', url)
dl.start().catch(err => {
onError(err)
})
return dl
}
-11
View File
@@ -1,11 +0,0 @@
exports.STATUS = {
idle: 'IDLE',
init: 'INIT',
running: 'RUNNING',
paused: 'PAUSED',
stopped: 'STOPPED',
completed: 'COMPLETED',
error: 'ERROR',
failed: 'FAILED',
}
-613
View File
@@ -1,613 +0,0 @@
import fs from 'fs'
import path from 'path'
import { shell, clipboard } from 'electron'
import { httpOverHttp, httpsOverHttp } from 'tunnel'
import crypto from 'crypto'
import { rendererSend, rendererInvoke, NAMES } from '@common/ipc'
import { log } from '@common/utils'
import iconv from 'iconv-lite'
import { gzip, gunzip } from 'zlib'
import { proxy, qualityList } from '@renderer/core/share'
/**
* 获取两个数之间的随机整数,大于等于min,小于max
* @param {*} min
* @param {*} max
*/
export const getRandom = (min, max) => Math.floor(Math.random() * (max - min)) + min
export const sizeFormate = size => {
// https://gist.github.com/thomseddon/3511330
if (!size) return '0 B'
let units = ['B', 'KB', 'MB', 'GB', 'TB']
let number = Math.floor(Math.log(size) / Math.log(1024))
return `${(size / Math.pow(1024, Math.floor(number))).toFixed(2)} ${units[number]}`
}
/**
* 日期格式化
* @param {*} date 时间
* @param {String} format 时间格式,默认YYYY-MM-DD hh:mm:ss
*/
export const dateFormat = (date = new Date(), format = 'YYYY-MM-DD hh:mm:ss') => {
if (typeof date != 'object') date = new Date(date)
const munFix = (n) => n < 10 ? ('0' + n) : n
return format
.replace('YYYY', date.getFullYear())
.replace('MM', munFix(date.getMonth() + 1))
.replace('DD', munFix(date.getDate()))
.replace('hh', munFix(date.getHours()))
.replace('mm', munFix(date.getMinutes()))
.replace('ss', munFix(date.getSeconds()))
}
/**
* 时间格式化
*/
export const dateFormat2 = time => {
let differ = parseInt((Date.now() - time) / 1000)
if (differ < 60) {
return window.i18n.t('date_format_second', { num: differ })
} else if (differ < 3600) {
return window.i18n.t('date_format_minute', { num: parseInt(differ / 60) })
} else if (differ < 86400) {
return window.i18n.t('date_format_hour', { num: parseInt(differ / 3600) })
} else {
return dateFormat(time)
}
}
export const formatPlayTime = time => {
let m = parseInt(time / 60)
let s = parseInt(time % 60)
return m === 0 && s === 0 ? '--/--' : (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}
export const formatPlayTime2 = time => {
let m = parseInt(time / 60)
let s = parseInt(time % 60)
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}
export const b64DecodeUnicode = str => {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(window.atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
}
const encodeNames = {
'&nbsp;': ' ',
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&quot;': '"',
'&apos;': "'",
'&#039;': "'",
}
export const decodeName = (str = '') => str?.replace(/(?:&amp;|&lt;|&gt;|&quot;|&apos;|&#039;|&nbsp;)/gm, s => encodeNames[s]) || ''
const easeInOutQuad = (t, b, c, d) => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
const handleScrollY = (element, to, duration = 300, fn = () => {}) => {
if (!element) return fn()
const start = element.scrollTop || element.scrollY || 0
let cancel = false
if (to > start) {
let maxScrollTop = element.scrollHeight - element.clientHeight
if (to > maxScrollTop) to = maxScrollTop
} else if (to < start) {
if (to < 0) to = 0
} else return fn()
const change = to - start
const increment = 10
if (!change) return fn()
let currentTime = 0
let val
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(0, val)
} else {
element.scrollTop = val
}
if (currentTime < duration) {
if (cancel) return fn()
setTimeout(animateScroll, increment)
} else {
fn()
}
}
animateScroll()
return () => {
cancel = true
}
}
/**
* 设置滚动条位置
* @param {*} element 要设置滚动的容器 dom
* @param {*} to 滚动的目标位置
* @param {*} duration 滚动完成时间 ms
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollTo = (element, to, duration = 300, fn = () => {}, delay = 0) => {
let cancelFn
let timeout
if (delay) {
let scrollCancelFn
cancelFn = () => {
timeout == null ? scrollCancelFn && scrollCancelFn() : clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollY(element, to, duration, fn, delay)
}, delay)
} else {
cancelFn = handleScrollY(element, to, duration, fn, delay)
}
return cancelFn
}
const handleScrollX = (element, to, duration = 300, fn = () => {}) => {
if (!element) return fn()
const start = element.scrollLeft || element.scrollX || 0
let cancel = false
if (to > start) {
let maxScrollLeft = element.scrollWidth - element.clientWidth
if (to > maxScrollLeft) to = maxScrollLeft
} else if (to < start) {
if (to < 0) to = 0
} else return fn()
const change = to - start
const increment = 10
if (!change) return fn()
let currentTime = 0
let val
const animateScroll = () => {
currentTime += increment
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
if (element.scrollTo) {
element.scrollTo(val, 0)
} else {
element.scrollLeft = val
}
if (currentTime < duration) {
if (cancel) return fn()
setTimeout(animateScroll, increment)
} else {
fn()
}
}
animateScroll()
return () => {
cancel = true
}
}
/**
* 设置滚动条位置
* @param {*} element 要设置滚动的容器 dom
* @param {*} to 滚动的目标位置
* @param {*} duration 滚动完成时间 ms
* @param {*} fn 滚动完成后的回调
* @param {*} delay 延迟执行时间
*/
export const scrollXTo = (element, to, duration = 300, fn = () => {}, delay = 0) => {
let cancelFn
let timeout
if (delay) {
let scrollCancelFn
cancelFn = () => {
timeout == null ? scrollCancelFn && scrollCancelFn() : clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollX(element, to, duration, fn, delay)
}, delay)
} else {
cancelFn = handleScrollX(element, to, duration, fn, delay)
}
return cancelFn
}
/**
* 检查路径是否存在
* @param {*} path 路径
*/
export const checkPath = (path) => new Promise(resolve => {
fs.access(path, fs.constants.F_OK, err => {
if (err) return resolve(false)
resolve(true)
})
})
/**
* 选择路径
* @param {*} 选项
*/
export const selectDir = options => rendererInvoke(NAMES.mainWindow.select_dir, options)
/**
* 打开保存对话框
* @param {*} 选项
*/
export const openSaveDir = options => rendererInvoke(NAMES.mainWindow.show_save_dialog, options)
/**
* 在资源管理器中打开目录
* @param {*} dir
*/
export const openDirInExplorer = dir => {
shell.showItemInFolder(dir)
}
// https://stackoverflow.com/a/53387532
export const compareVer = (currentVer, targetVer) => {
// treat non-numerical characters as lower version
// replacing them with a negative number based on charcode of each character
const fix = s => `.${s.toLowerCase().charCodeAt(0) - 2147483647}.`
currentVer = ('' + currentVer).replace(/[^0-9.]/g, fix).split('.')
targetVer = ('' + targetVer).replace(/[^0-9.]/g, fix).split('.')
let c = Math.max(currentVer.length, targetVer.length)
for (let i = 0; i < c; i++) {
// convert to integer the most efficient way
currentVer[i] = ~~currentVer[i]
targetVer[i] = ~~targetVer[i]
if (currentVer[i] > targetVer[i]) return 1
else if (currentVer[i] < targetVer[i]) return -1
}
return 0
}
export const isObject = item => item && typeof item === 'object' && !Array.isArray(item)
/**
* 对象深度合并
* @param {} target 要合并源对象
* @param {} source 要合并目标对象
*/
export const objectDeepMerge = (target, source, mergedObj) => {
if (!mergedObj) {
mergedObj = new Set()
mergedObj.add(target)
}
let base = {}
Object.keys(source).forEach(item => {
if (isObject(source[item])) {
if (mergedObj.has(source[item])) return
if (!isObject(target[item])) target[item] = {}
mergedObj.add(source[item])
objectDeepMerge(target[item], source[item], mergedObj)
return
}
base[item] = source[item]
})
Object.assign(target, base)
}
/**
* 在浏览器打开URL
* @param {*} url
*/
export const openUrl = url => {
if (!/^https?:\/\//.test(url)) return
shell.openExternal(url)
}
/**
* 设置标题
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = (title = '洛雪音乐助手') => {
dom_title.innerText = title
}
/**
* 创建 MD5 hash
* @param {*} str
*/
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')
/**
* 复制文本到剪贴板
* @param {*} str
*/
export const clipboardWriteText = str => clipboard.writeText(str)
/**
* 从剪贴板读取文本
* @param {*} str
*/
export const clipboardReadText = str => clipboard.readText()
/**
* 设置音频 meta 信息
* @param {*} filePath
* @param {*} meta
*/
export const setMeta = (filePath, meta) => {
rendererSend(NAMES.mainWindow.set_music_meta, { filePath, meta })
}
/**
* 保存歌词文件
* @param {*} filePath
* @param {*} lrc
* @param {*} format
*/
export const saveLrc = (filePath, lrc, format) => {
switch (format) {
case 'gbk':
fs.writeFile(filePath, iconv.encode(lrc, 'gbk', { addBOM: true }), err => {
if (err) console.log(err)
})
break
case 'utf8':
default:
fs.writeFile(filePath, lrc, 'utf8', err => {
if (err) console.log(err)
})
break
}
}
/**
* 生成节流函数
* @param {*} fn
* @param {*} delay
*/
export const throttle = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) return
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
/**
* 生成防抖函数
* @param {*} fn
* @param {*} delay
*/
export const debounce = (fn, delay = 100) => {
let timer = null
let _args = null
return function(...args) {
_args = args
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
timer = null
fn.apply(this, _args)
}, delay)
}
}
const async_removeItem = (arr, num, callback) => window.requestAnimationFrame(() => {
let len = arr.length
if (len > num) {
arr.splice(0, num)
return async_removeItem(arr, num, callback)
} else {
arr.splice(0, len)
return callback()
}
})
const async_addItem = (arr, newArr, num, callback) => window.requestAnimationFrame(() => {
let len = newArr.length
if (len > num) {
arr.push(...newArr.splice(0, num))
return async_addItem(arr, newArr, num, callback)
} else {
arr.push(...newArr.splice(0, len))
return callback()
}
})
/**
* 异步设置数组
* @param {*} from 原数组
* @param {*} to 设置后的数组内容
* @param {*} num 每次设置的个数
*/
export const asyncSetArray = (from, to, num = 100) => new Promise(resolve => {
async_removeItem(from, num, () => {
async_addItem(from, Array.from(to), num, () => {
resolve()
})
})
})
/**
* 获取缓存大小
*/
export const getCacheSize = () => rendererInvoke(NAMES.mainWindow.get_cache_size)
/**
* 清除缓存
*/
export const clearCache = () => rendererInvoke(NAMES.mainWindow.clear_cache)
/**
* 设置窗口大小
* @param {*} width
* @param {*} height
*/
export const setWindowSize = (width, height) => rendererSend(NAMES.mainWindow.set_window_size, { width, height })
export const getProxyInfo = () => {
return proxy.enable && proxy.host
? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`
: proxy.envProxy
? `http://${proxy.envProxy.host}:${proxy.envProxy.port}`
: undefined
}
const httpsRxp = /^https:/
export const getRequestAgent = url => {
let options
if (proxy.enable && proxy.host) {
options = {
proxy: {
host: proxy.host,
port: proxy.port,
},
}
} else if (proxy.envProxy) {
options = {
proxy: {
host: proxy.envProxy.host,
port: proxy.envProxy.port,
},
}
}
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
}
export const assertApiSupport = source => qualityList.value[source] != undefined
export const getSetting = () => rendererInvoke(NAMES.mainWindow.get_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 => {
let filePath = path.join(dataPath, 'playList.json.bak')
rendererInvoke(NAMES.mainWindow.show_dialog, {
type: 'error',
message: window.i18n.t('load_list_file_error_title'),
detail: window.i18n.t('load_list_file_error_detail', {
path: filePath,
detail: error.message,
}),
}).then(() => openDirInExplorer(filePath))
})
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 getLyricRaw = musicInfo => rendererInvoke(NAMES.mainWindow.get_lyric_raw, `${musicInfo.source}_${musicInfo.songmid}`)
export const setLyric = (musicInfo, { lyric, tlyric, rlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric_raw, {
id: `${musicInfo.source}_${musicInfo.songmid}`,
lyrics: { lyric, tlyric, rlyric, lxlyric },
})
export const setLyricEdited = (musicInfo, { lyric, tlyric, rlyric, lxlyric }) => rendererSend(NAMES.mainWindow.save_lyric_edited, {
id: `${musicInfo.source}_${musicInfo.songmid}`,
lyrics: { lyric, tlyric, rlyric, lxlyric },
})
export const removeLyricEdited = musicInfo => rendererSend(NAMES.mainWindow.remove_lyric_edited, `${musicInfo.source}_${musicInfo.songmid}`)
export const clearLyric = () => rendererSend(NAMES.mainWindow.clear_lyric_raw)
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)
export const gzipData = str => {
return new Promise((resolve, reject) => {
gzip(str, (err, result) => {
if (err) return reject(err)
resolve(result)
})
})
}
export const gunzipData = buf => {
return new Promise((resolve, reject) => {
gunzip(buf, (err, result) => {
if (err) return reject(err)
resolve(result.toString())
})
})
}
export const saveLxConfigFile = async(path, data) => {
if (!path.endsWith('.lxmc')) path += '.lxmc'
fs.writeFile(path, await gzipData(JSON.stringify(data)), 'binary', err => {
console.log(err)
})
}
export const readLxConfigFile = async path => {
let isJSON = path.endsWith('.json')
let data = await fs.promises.readFile(path, isJSON ? 'utf8' : 'binary')
if (!data || isJSON) return data
data = await gunzipData(Buffer.from(data, 'binary'))
data = JSON.parse(data.toString('utf8'))
// 修复v1.14.0出现的导出数据被序列化两次的问题
if (typeof data != 'object') {
try {
data = JSON.parse(data)
} catch (err) {
return data
}
}
return data
}
export const saveStrToFile = (path, str) => new Promise((resolve, reject) => {
fs.writeFile(path, str, err => {
if (err) {
log.error(err)
reject(err)
return
}
resolve()
})
})
const fileNameRxp = /[\\/:*?#"<>|]/g
export const filterFileName = name => name.replace(fileNameRxp, '')
export const getFontSizeWithScreen = (screenWidth = window.innerWidth) => {
return screenWidth <= 1440
? 16
: screenWidth <= 1920
? 18
: screenWidth <= 2560
? 20
: screenWidth <= 2560 ? 20 : 22
}
export const deduplicationList = list => {
const ids = new Set()
return list.filter(s => {
if (ids.has(s.songmid)) return false
ids.add(s.songmid)
return true
})
}
+66
View File
@@ -0,0 +1,66 @@
import { dateFormat } from '@common/utils/common'
export * from '@common/utils/renderer'
export * from '@common/utils/nodejs'
export * from '@common/utils/common'
export * from '@common/utils/tools'
/**
* 时间格式化
*/
export const dateFormat2 = (time: number): string => {
let differ = Math.trunc((Date.now() - time) / 1000)
if (differ < 60) {
return window.i18n.t('date_format_second', { num: differ })
} else if (differ < 3600) {
return window.i18n.t('date_format_minute', { num: Math.trunc(differ / 60) })
} else if (differ < 86400) {
return window.i18n.t('date_format_hour', { num: Math.trunc(differ / 3600) })
} else {
return dateFormat(time)
}
}
/**
* 设置标题
*/
let dom_title = document.getElementsByTagName('title')[0]
export const setTitle = (title: string | null) => {
if (!title) title = '洛雪音乐助手'
dom_title.innerText = title
}
// export const getProxyInfo = () => {
// return proxy.enable && proxy.host
// ? `http://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`
// : proxy.envProxy
// ? `http://${proxy.envProxy.host}:${proxy.envProxy.port}`
// : undefined
// }
export const getFontSizeWithScreen = (screenWidth: number = window.innerWidth): number => {
return screenWidth <= 1440
? 16
: screenWidth <= 1920
? 18
: screenWidth <= 2560
? 20
: screenWidth <= 2560 ? 20 : 22
}
export const deduplicationList = <T extends LX.Music.MusicInfo>(list: T[]): T[] => {
const ids: Set<string | number> = new Set()
return list.filter(s => {
if (ids.has(s.id)) return false
ids.add(s.id)
return true
})
}
export const langS2T = async(str: string) => {
return await window.lx.worker.main.langS2t(Buffer.from(str).toString('base64')).then(b64 => Buffer.from(b64, 'base64').toString())
}
+734
View File
@@ -0,0 +1,734 @@
import { rendererSend, rendererInvoke, rendererOn, rendererOff } from '@common/rendererIpc'
import { HOTKEY_RENDERER_EVENT_NAME, WIN_MAIN_RENDERER_EVENT_NAME, CMMON_EVENT_NAME } from '@common/ipcNames'
import { ProgressInfo, UpdateDownloadedEvent, UpdateInfo } from 'electron-updater'
import { markRaw } from '@common/utils/vueTools'
import * as hotKeys from '@common/hotKey'
import { APP_EVENT_NAMES, DATA_KEYS, DEFAULT_SETTING } from '@common/constants'
type RemoveListener = () => void
export const getSetting = async() => {
return rendererInvoke<LX.AppSetting>(CMMON_EVENT_NAME.get_app_setting)
}
export const updateSetting = async(setting: Partial<LX.AppSetting>) => {
await rendererInvoke(CMMON_EVENT_NAME.set_app_setting, setting)
}
export const onSettingChanged = (listener: LX.IpcRendererEventListenerParams<Partial<LX.AppSetting>>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.on_config_change, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.on_config_change, listener)
}
}
export const sendInited = () => {
return rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.inited)
}
export const getOtherSource = async(id: string): Promise<LX.Music.MusicInfoOnline[]> => {
return rendererInvoke<string, LX.Music.MusicInfoOnline[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source, id)
}
export const saveOtherSource = async(id: string, sourceInfo: LX.Music.MusicInfoOnline[]) => {
await rendererInvoke<LX.Music.MusicInfoOtherSourceSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_other_source, {
id,
list: sourceInfo,
})
}
export const clearOtherSource = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_other_source)
}
export const getOtherSourceCount = async() => {
return await rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_other_source_count)
}
export const getHotKeyConfig = async() => {
return rendererInvoke<LX.HotKeyConfigAll>(WIN_MAIN_RENDERER_EVENT_NAME.get_hot_key)
}
export const setIgnoreMouseEvents = (ignore: boolean) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.set_ignore_mouse_events, ignore)
}
export const getEnvParams = async() => {
return rendererInvoke<LX.EnvParams>(CMMON_EVENT_NAME.get_env_params)
}
export const clearEnvParamsDeeplink = () => {
rendererSend(CMMON_EVENT_NAME.clear_env_params_deeplink)
}
export const onDeeplink = (listener: LX.IpcRendererEventListenerParams<string>): RemoveListener => {
rendererOn(CMMON_EVENT_NAME.deeplink, listener)
return () => {
rendererOff(CMMON_EVENT_NAME.deeplink, listener)
}
}
export const checkUpdate = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.update_check)
}
export const quitUpdate = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.quit_update)
}
export const onUpdateAvailable = (listener: LX.IpcRendererEventListenerParams<UpdateInfo>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.update_available, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.update_available, listener)
}
}
export const onUpdateError = (listener: LX.IpcRendererEventListenerParams<string>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.update_error, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.update_error, listener)
}
}
export const onUpdateProgress = (listener: LX.IpcRendererEventListenerParams<ProgressInfo>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.update_progress, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.update_progress, listener)
}
}
export const onUpdateDownloaded = (listener: LX.IpcRendererEventListenerParams<UpdateDownloadedEvent>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.update_downloaded, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.update_downloaded, listener)
}
}
export const onUpdateNotAvailable = (listener: LX.IpcRendererEventListenerParams<UpdateInfo>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.update_not_available, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.update_not_available, listener)
}
}
export const importUserApi = async(fileText: string) => {
return rendererInvoke<string, LX.UserApi.ImportUserApi>(WIN_MAIN_RENDERER_EVENT_NAME.import_user_api, fileText)
}
export const setUserApi = async(source: LX.UserApi.UserApiSetApiParams): Promise<void> => {
return rendererInvoke<LX.UserApi.UserApiSetApiParams>(WIN_MAIN_RENDERER_EVENT_NAME.set_user_api, source)
}
export const removeUserApi = async(ids: string[]) => {
return rendererInvoke<string[], LX.UserApi.UserApiInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.remove_user_api, ids)
}
export const onShowUserApiUpdateAlert = (listener: LX.IpcRendererEventListenerParams<LX.UserApi.UserApiUpdateInfo>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.user_api_show_update_alert, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.user_api_show_update_alert, listener)
}
}
export const setAllowShowUserApiUpdateAlert = async(id: string, enable: boolean): Promise<void> => {
return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.user_api_set_allow_update_alert, { id, enable })
}
export const onUserApiStatus = (listener: LX.IpcRendererEventListenerParams<LX.UserApi.UserApiStatus>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.user_api_status, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.user_api_status, listener)
}
}
export const getUserApiList = async() => {
return rendererInvoke<LX.UserApi.UserApiInfo[]>(WIN_MAIN_RENDERER_EVENT_NAME.get_user_api_list)
}
export const sendUserApiRequest = async({ requestKey, data }: LX.UserApi.UserApiRequestParams): Promise<any> => {
return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api, {
requestKey,
data,
})
}
export const userApiRequestCancel = (requestKey: LX.UserApi.UserApiRequestCancelParams) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.request_user_api_cancel, requestKey)
}
// export const setDesktopLyricInfo = (type, data, info) => {
// rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.set_lyric_info, {
// type,
// data,
// info,
// })
// }
// export const onGetDesktopLyricInfo = callback => {
// rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_info, callback)
// return () => {
// rendererOff(callback)
// }
// }
export const setTaskBarProgress = (progress: number, mode?: Electron.ProgressBarOptions['mode']) => {
rendererSend<LX.Player.ProgressBarOptions>(WIN_MAIN_RENDERER_EVENT_NAME.progress, {
progress: progress < 0 ? progress : Math.max(0.01, progress),
mode: mode ?? 'normal',
})
}
export const savePlayInfo = (playInfo: LX.Player.SavedPlayInfo) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.playInfo,
data: playInfo,
})
}
// 获取上次关闭时的当前歌曲播放信息
export const getPlayInfo = async() => {
return rendererInvoke<string, LX.Player.SavedPlayInfo | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.playInfo)
}
export const saveSearchHistoryList = (list: LX.List.SearchHistoryList) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.searchHistoryList,
data: list,
})
}
// 获取搜索历史列表
export const getSearchHistoryList = async() => {
return rendererInvoke<string, string[] | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.searchHistoryList)
}
export const saveListPositionInfo = (listPosition: LX.List.ListPositionInfo) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.listScrollPosition,
data: listPosition,
})
}
// 获取搜索历史列表
export const getListPositionInfo = async() => {
return rendererInvoke<string, LX.List.ListPositionInfo | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.listScrollPosition)
}
export const saveListPrevSelectId = (listPosition: string | null) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.listPrevSelectId,
data: listPosition,
})
}
// 获取上一次选中的列表id
export const getListPrevSelectId = async() => {
return rendererInvoke<string, string | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.listPrevSelectId)
}
export const saveListUpdateInfo = (listPosition: LX.List.ListUpdateInfo) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.listUpdateInfo,
data: listPosition,
})
}
// 获取列表更新记录
export const getListUpdateInfo = async() => {
return rendererInvoke<string, LX.List.ListUpdateInfo | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.listUpdateInfo)
}
export const saveIgnoreVersion = (version: string) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.ignoreVersion,
data: version,
})
}
// 获取忽略更新的版本号
export const getIgnoreVersion = async() => {
return rendererInvoke<string, string | null>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.ignoreVersion)
}
export const saveLeaderboardSetting = (source: typeof DEFAULT_SETTING['leaderboard']) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.leaderboardSetting,
data: source,
})
}
export const getLeaderboardSetting = async() => {
return await rendererInvoke<string, typeof DEFAULT_SETTING['leaderboard']>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.leaderboardSetting) ?? { ...DEFAULT_SETTING.leaderboard }
}
export const saveSongListSetting = (setting: typeof DEFAULT_SETTING['songList']) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.songListSetting,
data: setting,
})
}
export const getSongListSetting = async() => {
return await rendererInvoke<string, typeof DEFAULT_SETTING['songList']>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.songListSetting) ?? { ...DEFAULT_SETTING.songList }
}
export const saveSearchSetting = (setting: typeof DEFAULT_SETTING['search']) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.searchSetting,
data: setting,
})
}
export const getSearchSetting = async() => {
return await rendererInvoke<string, typeof DEFAULT_SETTING['search']>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.searchSetting) ?? { ...DEFAULT_SETTING.search }
}
export const saveViewPrevState = (state: typeof DEFAULT_SETTING['viewPrevState']) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.save_data, {
path: DATA_KEYS.viewPrevState,
data: state,
})
}
export const getViewPrevState = async() => {
return await rendererInvoke<string, typeof DEFAULT_SETTING['viewPrevState']>(WIN_MAIN_RENDERER_EVENT_NAME.get_data, DATA_KEYS.viewPrevState) ?? { ...DEFAULT_SETTING.viewPrevState }
}
export const getSystemFonts = async() => {
return rendererInvoke<string[]>(CMMON_EVENT_NAME.get_system_fonts).catch(() => {
return []
})
}
export const allHotKeys = markRaw({
local: [
{
name: hotKeys.HOTKEY_PLAYER.toggle_play.name,
action: hotKeys.HOTKEY_PLAYER.toggle_play.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.prev.name,
action: hotKeys.HOTKEY_PLAYER.prev.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.next.name,
action: hotKeys.HOTKEY_PLAYER.next.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_COMMON.focusSearchInput.name,
action: hotKeys.HOTKEY_COMMON.focusSearchInput.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_COMMON.min.name,
action: hotKeys.HOTKEY_COMMON.min.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_COMMON.close.name,
action: hotKeys.HOTKEY_COMMON.close.action,
type: APP_EVENT_NAMES.winMainName,
},
],
global: [
{
name: hotKeys.HOTKEY_COMMON.min_toggle.name,
action: hotKeys.HOTKEY_COMMON.min_toggle.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_COMMON.hide_toggle.name,
action: hotKeys.HOTKEY_COMMON.hide_toggle.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_COMMON.close.name,
action: hotKeys.HOTKEY_COMMON.close.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.toggle_play.name,
action: hotKeys.HOTKEY_PLAYER.toggle_play.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.prev.name,
action: hotKeys.HOTKEY_PLAYER.prev.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.next.name,
action: hotKeys.HOTKEY_PLAYER.next.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.volume_up.name,
action: hotKeys.HOTKEY_PLAYER.volume_up.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.volume_down.name,
action: hotKeys.HOTKEY_PLAYER.volume_down.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_PLAYER.volume_mute.name,
action: hotKeys.HOTKEY_PLAYER.volume_mute.action,
type: APP_EVENT_NAMES.winMainName,
},
{
name: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_visible.name,
action: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_visible.action,
type: APP_EVENT_NAMES.winLyricName,
},
{
name: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_lock.name,
action: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_lock.action,
type: APP_EVENT_NAMES.winLyricName,
},
{
name: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_always_top.name,
action: hotKeys.HOTKEY_DESKTOP_LYRIC.toggle_always_top.action,
type: APP_EVENT_NAMES.winLyricName,
},
],
})
export const hotKeySetEnable = async(enable: boolean) => {
return rendererInvoke(HOTKEY_RENDERER_EVENT_NAME.enable, enable)
}
export const hotKeySetConfig = async(config: LX.HotKeyActions) => {
return rendererInvoke(HOTKEY_RENDERER_EVENT_NAME.set_config, config)
}
export const hotKeyGetStatus = async() => {
return rendererInvoke<LX.HotKeyState>(HOTKEY_RENDERER_EVENT_NAME.status)
}
// 主进程操作播放器状态
export const onPlayerAction = (listener: LX.IpcRendererEventListenerParams<LX.Player.StatusButtonActions>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.player_action_on_button_click, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.player_action_on_button_click, listener)
}
}
// export const setTaskbarThumbnailClip = async(clip: Electron.Rectangle) => {
// await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.taskbar_set_thumbnail_clip, clip)
// }
// 播放器状态更新 通知主进程
export const setPlayerAction = (buttons: LX.TaskBarButtonFlags) => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.player_action_set_buttons, buttons)
}
/**
* On Theme Change
* @param listener LX.IpcRendererEventListenerParams<shouldUseDarkColors: boolean>
* @returns RemoveListener Fn
*/
export const onThemeChange = (listener: LX.IpcRendererEventListenerParams<LX.ThemeSetting>): RemoveListener => {
rendererOn(CMMON_EVENT_NAME.theme_change, listener)
return () => {
rendererOff(CMMON_EVENT_NAME.theme_change, listener)
}
}
/**
* 选择路径
*/
export const showSelectDialog = async(options: Electron.OpenDialogOptions) => {
return rendererInvoke<Electron.OpenDialogOptions, Electron.OpenDialogReturnValue>(WIN_MAIN_RENDERER_EVENT_NAME.show_select_dialog, options)
}
/**
* 打开保存对话框
*/
export const openSaveDir = async(options: Electron.SaveDialogOptions) => {
return rendererInvoke<Electron.SaveDialogOptions, Electron.SaveDialogReturnValue>(WIN_MAIN_RENDERER_EVENT_NAME.show_save_dialog, options)
}
/**
* 获取缓存大小
*/
export const getCacheSize = async() => {
return rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_cache_size)
}
/**
* 清除缓存
*/
export const clearCache = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_cache)
}
/**
* 设置窗口大小
* @param {*} width
* @param {*} height
*/
export const setWindowSize = (width: number, height: number) => {
const params: Partial<Electron.Rectangle> = {
width,
height,
}
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.set_window_size, params)
}
export const getPlayerLyric = async(musicInfo: LX.Music.MusicInfo) => {
return rendererInvoke<string, LX.Player.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_palyer_lyric, musicInfo.id)
}
export const getLyricRaw = async(musicInfo: LX.Music.MusicInfo): Promise<LX.Music.LyricInfo> => {
return rendererInvoke<string, LX.Music.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw, musicInfo.id)
}
export const clearLyricRaw = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_raw)
}
export const getLyricRawCount = async() => {
return await rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_raw_count)
}
export const getLyricEdited = async(musicInfo: LX.Music.MusicInfo): Promise<LX.Music.LyricInfo> => {
return rendererInvoke<string, LX.Music.LyricInfo>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited, musicInfo.id)
}
export const saveLyric = async(musicInfo: LX.Music.MusicInfo, lyricInfo: LX.Music.LyricInfo | LX.Player.LyricInfo) => {
// console.log(musicInfo)
if ('rawlrcInfo' in lyricInfo) {
const { rawlrcInfo, ...info } = lyricInfo
const tasks = [
rendererInvoke<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_raw, {
id: musicInfo.id,
lyrics: rawlrcInfo,
}),
]
if (info.lyric != rawlrcInfo.lyric) {
tasks.push(rendererInvoke<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_edited, {
id: musicInfo.id,
lyrics: info,
}))
}
console.log(tasks)
await Promise.all(tasks)
} else {
await rendererInvoke<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_raw, {
id: musicInfo.id,
lyrics: lyricInfo,
})
}
}
export const saveLyricEdited = async(musicInfo: LX.Music.MusicInfo, lyricInfo: LX.Music.LyricInfo) => {
await rendererInvoke<LX.Music.LyricInfoSave>(WIN_MAIN_RENDERER_EVENT_NAME.save_lyric_edited, {
id: musicInfo.id,
lyrics: lyricInfo,
})
}
export const removeLyricEdited = async(musicInfo: LX.Music.MusicInfo) => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.remove_lyric_edited, musicInfo.id)
}
export const clearLyric = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_raw)
}
export const clearLyricEdited = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_lyric_edited)
}
export const getLyricEditedCount = async() => {
return await rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_lyric_edited_count)
}
export const saveTheme = async(theme: LX.Theme) => {
return rendererInvoke<LX.Theme>(WIN_MAIN_RENDERER_EVENT_NAME.save_theme, theme)
}
export const removeTheme = async(id: string) => {
return rendererInvoke<string>(WIN_MAIN_RENDERER_EVENT_NAME.remove_theme, id)
}
export const getThemes = async() => {
return rendererInvoke<{ themes: LX.Theme[], userThemes: LX.Theme[], dataPath: string }>(WIN_MAIN_RENDERER_EVENT_NAME.get_themes)
}
/**
* 从缓存获取歌曲URL
* @param musicInfo 歌曲信息
* @param type URL音质
* @returns
*/
export const getMusicUrl = async(musicInfo: LX.Music.MusicInfo, type: LX.Quality): Promise<string> => {
return rendererInvoke<string, string>(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url, `${musicInfo.id}_${type}`)
}
/**
* 缓存歌曲URL
* @param musicInfo 歌曲信息
* @param type URL音质
* @param url 歌曲URL
*/
export const saveMusicUrl = async(musicInfo: LX.Music.MusicInfo, type: LX.Quality, url: string) => {
await rendererInvoke<LX.Music.MusicUrlInfo>(WIN_MAIN_RENDERER_EVENT_NAME.save_music_url, {
id: `${musicInfo.id}_${type}`,
url,
})
}
/**
* 清理所有缓存的歌曲URL
*/
export const clearMusicUrl = async() => {
await rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.clear_music_url)
}
export const getMusicUrlCount = async() => {
return await rendererInvoke<number>(WIN_MAIN_RENDERER_EVENT_NAME.get_music_url_count)
}
/**
* 退出应用
*/
export const quitApp = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.quit)
}
/**
* 关闭窗口
*/
export const closeWindow = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.close)
}
/**
* 最小化窗口
*/
export const minWindow = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.min)
}
/**
* 最大化窗口
*/
export const maxWindow = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.max)
}
/**
* 最小化、最大化窗口切换
*/
export const minMaxWindowToggle = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.min_toggle)
}
/**
* 显示、隐藏窗口切换
*/
export const showHideWindowToggle = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.hide_toggle)
}
/**
* 聚焦窗口
*/
export const focusWindow = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.focus)
}
/**
* 窗口获取焦点事件
* @param listener
* @returns
*/
export const onFocus = (listener: LX.IpcRendererEventListener): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.focus, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.focus, listener)
}
}
/**
* 快捷键触发事件
* @param listener
* @returns
*/
export const onKeyDown = (listener: LX.IpcRendererEventListenerParams<LX.HotKeyEvent>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.key_down, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.key_down, listener)
}
}
/**
* 快捷键设置更新事件
* @param listener
* @returns
*/
export const onUpdateHotkey = (listener: LX.IpcRendererEventListenerParams<LX.HotKeyConfigAll>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.set_hot_key_config, listener)
}
}
/**
* 设置全屏
* @param isFullscreen 是否全屏
* @returns
*/
export const setFullScreen = async(isFullscreen: boolean): Promise<boolean> => {
return rendererInvoke<boolean, boolean>(WIN_MAIN_RENDERER_EVENT_NAME.fullscreen, isFullscreen)
}
/**
* 打开开发者工具
* @returns
*/
export const openDevTools = () => {
rendererSend(WIN_MAIN_RENDERER_EVENT_NAME.open_dev_tools)
}
/**
* 接收同步事件
* @param listener
* @returns
*/
export const onSyncAction = (listener: LX.IpcRendererEventListenerParams<LX.Sync.SyncMainWindowActions>): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, listener)
}
}
/**
* 发送同步事件
* @param action
* @returns
*/
export const sendSyncAction = async(action: LX.Sync.SyncServiceActions) => {
return rendererInvoke<LX.Sync.SyncServiceActions>(WIN_MAIN_RENDERER_EVENT_NAME.sync_action, action)
}
// export const refreshSyncCode = async(): Promise<string> => {
// return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.sync_generate_code)
// }
// export const onSyncStatus = (listener: LX.IpcRendererEventListenerParams<LX.Sync.Status>): RemoveListener => {
// rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.sync_status, listener)
// return () => {
// rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.sync_status, listener)
// }
// }
/**
* 桌面歌词进程创建事件
* @param listener
* @returns
*/
export const onNewDesktopLyricProcess = (listener: LX.IpcRendererEventListener): RemoveListener => {
rendererOn(WIN_MAIN_RENDERER_EVENT_NAME.process_new_desktop_lyric_client, listener)
return () => {
rendererOff(WIN_MAIN_RENDERER_EVENT_NAME.process_new_desktop_lyric_client, listener)
}
}
export const downloadTasksGet = async() => {
return rendererInvoke<LX.Download.ListItem[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_get)
}
export const downloadTasksCreate = async(list: LX.Download.ListItem[], addMusicLocationType: LX.AddMusicLocationType) => {
return rendererInvoke<LX.Download.saveDownloadMusicInfo>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_add, {
list,
addMusicLocationType,
})
}
export const downloadTasksUpdate = async(list: LX.Download.ListItem[]) => {
return rendererInvoke<LX.Download.ListItem[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_update, list)
}
export const downloadTasksRemove = async(ids: string[]) => {
return rendererInvoke<string[]>(WIN_MAIN_RENDERER_EVENT_NAME.download_list_remove, ids)
}
export const downloadListClear = async() => {
return rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.download_list_clear)
}
@@ -1,8 +1,14 @@
import { isMac } from '../../common/utils'
import { isMac } from '@common/utils'
const downKeys = new Set()
const downKeys: Set<string> = new Set()
const handleEvent = (type, event, keys, isEditing) => {
export declare type KeyActionType = LX.KeyDownEevent['type']
export declare type Keys = LX.KeyDownEevent['keys']
export declare type Key = LX.KeyDownEevent['key']
export declare type EventKey = LX.KeyDownEevent['eventKey']
export declare type Event = LX.KeyDownEevent['event']
const handleEvent = (type: KeyActionType, event: LX.KeyEvent, keys: Keys, isEditing: boolean) => {
let eventKey = event.key
if (isMac) {
let index = keys.indexOf('meta')
@@ -27,8 +33,8 @@ const handleEvent = (type, event, keys, isEditing) => {
}
// 修饰键处理
const eventModifiers = event => {
let modifiers = []
const eventModifiers = (event: LX.KeyEvent): string[] => {
let modifiers: string[] = []
if (event.ctrlKey) modifiers.push('ctrl')
if (event.shiftKey) modifiers.push('shift')
if (event.altKey) modifiers.push('alt')
@@ -38,7 +44,7 @@ const eventModifiers = event => {
}
// 是否忽略事件(表单元素等默认忽略)
const assertStopCallback = element => {
const assertStopCallback = (element: HTMLElement) => {
// if the element has the class "keybind" then no need to stop
if (element.classList.contains('key-bind')) return false
@@ -49,11 +55,11 @@ const assertStopCallback = element => {
case 'TEXTAREA':
return true
default:
return element.isContentEditable
return !!element.isContentEditable
}
}
const handleKeyDown = event => {
const handleKeyDown = (event: LX.KeyEvent) => {
// if (assertStopCallback(event.target)) return
// event.preventDefault()
let keys = eventModifiers(event)
@@ -70,10 +76,10 @@ const handleKeyDown = event => {
keys.push((event.code.includes('Numpad') ? event.code.replace(/^Numpad(\w{1,3})\w*$/i, 'num$1') : event.key).toLowerCase())
break
}
handleEvent('down', event, keys, assertStopCallback(event.target))
handleEvent('down', event, keys, event.target ? assertStopCallback(event.target as HTMLElement) : false)
}
const handleKeyUp = event => {
const handleKeyUp = (event: LX.KeyEvent) => {
// if (assertStopCallback(event.target)) return
event.preventDefault()
let keys = eventModifiers(event)
@@ -88,12 +94,13 @@ const handleKeyUp = event => {
keys.push((event.code.includes('Numpad') ? event.code.replace(/^Numpad(\w{1,3})\w*$/i, 'num$1') : event.key).toLowerCase())
break
}
handleEvent('up', event, keys, assertStopCallback(event.target))
handleEvent('up', event, keys, event.target ? assertStopCallback(event.target as HTMLElement) : false)
}
let handleSendEvent
type HandleSendEvent = (key: Key, eventKey: EventKey, type: KeyActionType, event: Event, keys: Keys, isEditing: boolean) => void
let handleSendEvent: HandleSendEvent
const bindKey = (handle = () => {}) => {
const bindKey = (handle: HandleSendEvent = () => {}) => {
handleSendEvent = handle
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('keyup', handleKeyUp)
@@ -107,7 +114,7 @@ const unbindKey = () => {
const clearDownKeys = () => {
let keys = Array.from(downKeys)
for (let i = keys.length - 1; i > -1; i--) {
handleSendEvent(keys[i], keys[i], 'up', null, [keys[i]])
handleSendEvent(keys[i], keys[i], 'up', null, [keys[i]], false)
}
downKeys.clear()
}
@@ -1,302 +0,0 @@
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({ time = 0, lyric = '', extendedLyrics = '', lineClassName = '', fontClassName = '', extendedLrcClassName = '', lineModeClassName = '', shadowContent = false, shadowClassName = '' }) {
this.time = time
this.lyric = lyric
this.extendedLyrics = extendedLyrics
this.lineClassName = lineClassName
this.fontClassName = fontClassName
this.extendedLrcClassName = extendedLrcClassName
this.lineModeClassName = lineModeClassName
this.shadowContent = shadowContent
this.shadowClassName = shadowClassName
this.isPlay = false
this.curFontNum = 0
this.maxFontNum = 0
this._performanceTime = 0
this._startTime = 0
this.fontContent = null
this.timeoutTools = new TimeoutTools(80)
this.waitPlayTimeout = new TimeoutTools(80)
this._init()
}
_init() {
if (this.lyric == null) this.lyric = ''
this.isLineMode = false
this.lineContent = document.createElement('div')
this.lineContent.time = this.time
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)
for (const lrc of this.extendedLyrics) {
const extendedLrcContent = document.createElement('div')
extendedLrcContent.style = 'position:relative;display:inline-block;'
extendedLrcContent.className = this.extendedLrcClassName
extendedLrcContent.textContent = lrc
this.lineContent.appendChild(document.createElement('br'))
this.lineContent.appendChild(extendedLrcContent)
if (this.shadowContent) {
const extendedLrcShadowContent = document.createElement('div')
extendedLrcShadowContent.style = 'position:absolute;top:0;left:0;width:100%;z-index:-1;'
extendedLrcShadowContent.className = this.shadowClassName
extendedLrcShadowContent.textContent = lrc
extendedLrcContent.appendChild(extendedLrcShadowContent)
}
}
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._startTime
}
_findcurFontNum(curTime, startIndex = 0) {
const length = this.fonts.length
for (let index = startIndex; 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]
// 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) {
let nextFont = this.fonts[this.curFontNum + 1]
this.delay = nextFont.startTime - curFont.startTime - driftTime
if (this.delay > 0) {
if (this.isPlay) {
this.timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
}
this._handlePlayFont(curFont, driftTime)
return
} else {
let newCurLineNum = this._findcurFontNum(currentTime, this.curFontNum + 1)
if (newCurLineNum > this.curFontNum) this.curFontNum = newCurLineNum - 1
for (let i = 0; i <= this.curFontNum; i++) this._handlePlayFont(this.fonts[i], 0, true)
this._refresh()
return
}
} else if (this.curFontNum == 0) {
this.curFontNum--
if (this.isPlay) {
this.waitPlayTimeout.start(() => {
if (!this.isPlay) return
this._refresh()
}, -driftTime)
}
return
}
this.curFontNum = this._findcurFontNum(currentTime, this.curFontNum) - 1
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()
this._startTime = curTime
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
}
}
@@ -1,170 +0,0 @@
const LinePlayer = require('./line-player')
const FontPlayer = require('./font-player')
const fontTimeExp = /<(\d+),(\d+)>/g
module.exports = class Lyric {
constructor({
lyric = '',
extendedLyrics = [],
offset = 0,
lineClassName = '',
fontClassName = 'font',
extendedLrcClassName = 'extended',
activeLineClassName = 'active',
lineModeClassName = 'line',
shadowClassName = '',
shadowContent = false,
onPlay = function() { },
onSetLyric = function() { },
}) {
this.lyric = lyric
this.extendedLyrics = extendedLyrics
this.offset = offset
this.onPlay = onPlay
this.onSetLyric = onSetLyric
this.lineClassName = lineClassName
this.fontClassName = fontClassName
this.extendedLrcClassName = extendedLrcClassName
this.activeLineClassName = activeLineClassName
this.lineModeClassName = lineModeClassName
this.shadowClassName = shadowClassName
this.shadowContent = shadowContent
this.playingLineNum = -1
this.isLineMode = false
this.linePlayer = new LinePlayer({
offset: this.offset,
onPlay: this._handleLinePlayerOnPlay,
onSetLyric: this._handleLinePlayerOnSetLyric,
})
}
_init() {
this.playingLineNum = -1
this.isLineMode = false
this.linePlayer.setLyric(this.lyric, this.extendedLyrics)
}
_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) {
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) {
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, offset) => {
// 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({
time: line.time,
lyric: line.text,
extendedLyrics: line.extendedLyrics,
lineClassName: this.lineClassName,
fontClassName: this.fontClassName,
extendedLrcClassName: this.extendedLrcClassName,
lineModeClassName: this.lineModeClassName,
shadowClassName: this.shadowClassName,
shadowContent: this.shadowContent,
})
this._lineFonts.push(fontPlayer)
return {
text: line.text,
time: line.time,
extendedLyrics: line.extendedLyrics,
dom_line: fontPlayer.lineContent,
}
})
} else {
this._lines = lyricLines.map(line => {
const fontPlayer = new FontPlayer({
time: line.time,
lyric: line.text,
extendedLyrics: line.extendedLyrics,
lineClassName: this.lineClassName,
fontClassName: this.fontClassName,
extendedLrcClassName: this.extendedLrcClassName,
shadowClassName: this.shadowClassName,
shadowContent: this.shadowContent,
})
this._lineFonts.push(fontPlayer)
return {
text: line.text.replace(fontTimeExp, ''),
time: line.time,
extendedLyrics: line.extendedLyrics,
dom_line: fontPlayer.lineContent,
}
})
}
// 如果是逐行歌词,则添加 60ms 的偏移
let newOffset = this.isLineMode ? this.offset + 60 : this.offset
offset = offset - this.linePlayer.offset + newOffset
this.linePlayer.offset = newOffset
this.onSetLyric(this._lines, offset)
}
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, extendedLyrics) {
this.lyric = lyric
this.extendedLyrics = extendedLyrics
this._init()
}
}
@@ -1,184 +0,0 @@
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()
const parseExtendedLyric = (lrcLinesMap, extendedLyric) => {
const extendedLines = extendedLyric.split(/\r\n|\n|\r/)
for (let i = 0; i < extendedLines.length; i++) {
const line = extendedLines[i].trim()
let result = timeExp.exec(line)
if (result) {
const text = line.replace(timeExp, '').trim()
if (text) {
const timeStr = RegExp.$1.replace(/(\.\d\d)0$/, '$1')
const targetLine = lrcLinesMap[timeStr]
if (targetLine) targetLine.extendedLyrics.push(text)
}
}
}
}
module.exports = class LinePlayer {
constructor({ offset = 0, onPlay = function() { }, onSetLyric = function() { } } = {}) {
this.tags = {}
this.lines = null
this.onPlay = onPlay
this.onSetLyric = onSetLyric
this.isPlay = false
this.curLineNum = 0
this.maxLine = 0
this.offset = offset
this._performanceTime = 0
this._startTime = 0
}
_init() {
if (this.lyric == null) this.lyric = ''
if (this.extendedLyrics == null) this.extendedLyrics = []
this._initTag()
this._initLines()
this.onSetLyric(this.lines, this.tags.offset + this.offset)
}
_initTag() {
this.tags = {}
for (let tag in tagRegMap) {
const matches = this.lyric.match(new RegExp(`\\[${tagRegMap[tag]}:([^\\]]*)]`, 'i'))
this.tags[tag] = (matches && matches[1]) || ''
}
if (this.tags.offset) {
let offset = parseInt(this.tags.offset)
this.tags.offset = Number.isNaN(offset) ? 0 : offset
} else {
this.tags.offset = 0
}
}
_initLines() {
this.lines = []
const lines = this.lyric.split(/\r\n|\r|\n/)
const linesMap = {}
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.replace(/(\.\d\d)0$/, '$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,
extendedLyrics: [],
}
}
}
}
for (const lrc of this.extendedLyrics) parseExtendedLyric(linesMap, lrc)
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._startTime
}
_findCurLineNum(curTime, startIndex = 0) {
if (curTime <= 0) return 0
const length = this.lines.length
for (let index = startIndex; 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)
if (this.curLineNum >= this.maxLine) return this._handleMaxLine()
let curLine = this.lines[this.curLineNum]
const currentTime = this._currentTime()
const driftTime = currentTime - curLine.time
if (driftTime >= 0 || this.curLineNum === 0) {
let nextLine = this.lines[this.curLineNum + 1]
this.delay = nextLine.time - curLine.time - driftTime
if (this.delay > 0) {
if (this.isPlay) {
timeoutTools.start(() => {
if (!this.isPlay) return
this._refresh()
}, this.delay)
}
this.onPlay(this.curLineNum, curLine.text, currentTime)
return
} else {
let newCurLineNum = this._findCurLineNum(currentTime, this.curLineNum + 1)
if (newCurLineNum > this.curLineNum) this.curLineNum = newCurLineNum - 1
this._refresh()
return
}
}
this.curLineNum = this._findCurLineNum(currentTime, this.curLineNum) - 1
this._refresh()
}
play(curTime = 0) {
if (!this.lines.length) return
this.pause()
this.isPlay = true
this._performanceTime = getNow() - parseInt(this.tags.offset + this.offset)
this._startTime = curTime
this.curLineNum = this._findCurLineNum(this._currentTime()) - 1
this._refresh()
}
pause() {
if (!this.isPlay) return
this.isPlay = 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, extendedLyrics) {
// console.log(extendedLyrics)
if (this.isPlay) this.pause()
this.lyric = lyric
this.extendedLyrics = extendedLyrics
this._init()
}
}
@@ -1,50 +0,0 @@
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(thresholdTime = 200) {
this.invokeTime = 0
this.animationFrameId = null
this.timeoutId = null
this.callback = null
this.thresholdTime = thresholdTime
}
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
}
}
}
@@ -5,4 +5,4 @@ export const requestMsg = {
// unachievable: '哦No😱...接口无法访问了!已帮你切换到临时接口,重试下看能不能播放吧~',
notConnectNetwork: '无法连接到服务器',
cancelRequest: '取消http请求',
}
} as const
+191
View File
@@ -0,0 +1,191 @@
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
import { formatPlayTime } from '@common/utils/common'
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
(await checkPath(musicInfo.metadata.filePath) || await checkPath(joinPath(savePath, musicInfo.metadata.fileName)))
}
export const checkLocalFileAvailable = async(musicInfo: LX.Music.MusicInfoLocal): Promise<boolean> => {
return await checkPath(musicInfo.meta.filePath)
}
/**
* 检查音乐文件是否存在
* @param musicInfo
* @param savePath
*/
export const checkMusicFileAvailable = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, savePath: string): Promise<boolean> => {
if ('progress' in musicInfo) {
return await checkDownloadFileAvailable(musicInfo, savePath)
} else if (musicInfo.source == 'local') {
return await checkLocalFileAvailable(musicInfo)
} else return true
}
export const getDownloadFilePath = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<string> => {
if (musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName)) {
if (await checkPath(musicInfo.metadata.filePath)) return musicInfo.metadata.filePath
const path = joinPath(savePath, musicInfo.metadata.fileName)
if (await checkPath(path)) return path
}
return ''
}
export const getLocalFilePath = async(musicInfo: LX.Music.MusicInfoLocal): Promise<string> => {
return await checkPath(musicInfo.meta.filePath) ? musicInfo.meta.filePath : ''
}
/**
* 获取音乐文件路径
* @param musicInfo
* @param savePath
* @returns
*/
export const getMusicFilePath = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListItem, savePath: string): Promise<string> => {
if ('progress' in musicInfo) {
return await getDownloadFilePath(musicInfo, savePath)
} else if (musicInfo.source == 'local') {
return await getLocalFilePath(musicInfo)
}
return ''
}
/**
* 创建本地音乐信息对象
* @param path 文件路径
* @returns
*/
export const createLocalMusicInfo = async(path: string): Promise<LX.Music.MusicInfoLocal | null> => {
if (!await checkPath(path)) return null
const { parseFile } = await import('music-metadata')
let metadata
try {
metadata = await parseFile(path)
} catch (err) {
console.log(err)
return null
}
// console.log(metadata)
let ext = extname(path)
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
let name = (metadata.common.title || basename(path, ext)).trim()
let singer = metadata.common.artists?.length ? metadata.common.artists.map(a => a.trim()).join('、') : ''
let interval = metadata.format.duration ? formatPlayTime(metadata.format.duration) : ''
let albumName = metadata.common.album?.trim() ?? ''
return {
id: path,
name,
singer,
source: 'local',
interval,
meta: {
albumName,
filePath: path,
songId: path,
picUrl: '',
ext: ext.replace(/^\./, ''),
},
}
}
let prevFileInfo: {
path: string
promise: Promise<LX.MusicMetadataModule.IAudioMetadata | null>
} = {
path: '',
promise: Promise.resolve(null),
}
const getFileMetadata = async(path: string) => {
if (prevFileInfo.path == path) return prevFileInfo.promise
prevFileInfo.path = path
return prevFileInfo.promise = checkPath(path).then(async(isExist) => {
return isExist ? import('music-metadata').then(async({ parseFile }) => parseFile(path)) : null
})
}
/**
* 获取歌曲文件封面图片
* @param path 路径
*/
export const getLocalMusicFilePic = async(path: string) => {
const metadata = await getFileMetadata(path)
if (!metadata) return null
const { selectCover } = await import('music-metadata')
return selectCover(metadata.common.picture)
}
const timeExp = /^\[([\d:.]*)\]{1}/
/**
* 解析歌词文件,分离可能存在的翻译、罗马音歌词
* @param lrc 歌词内容
* @returns
*/
export const parseLyric = (lrc: string): LX.Music.LyricInfo => {
const lines = lrc.split(/\r\n|\r|\n/)
const lyrics: string[][] = []
const map = new Map<string, number>()
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()
let result = timeExp.exec(line)
if (result) {
const index = map.get(result[1]) ?? 0
if (!lyrics[index]) lyrics[index] = []
lyrics[index].push(line)
map.set(result[1], index + 1)
} else {
if (!lyrics[0]) lyrics[0] = []
lyrics[0].push(line)
}
}
const lyricInfo: LX.Music.LyricInfo = {
lyric: lyrics[0].join('\n'),
tlyric: '',
}
if (lyrics[1]) lyricInfo.tlyric = lyrics[1].join('\n')
if (lyrics[2]) lyricInfo.rlyric = lyrics[2].join('\n')
return lyricInfo
}
/**
* 获取歌曲文件歌词
* @param path 路径
*/
export const getLocalMusicFileLyric = async(path: string): Promise<string | null> => {
// 尝试读取同目录下的同名lrc文件
const lrcPath = path.replace(new RegExp('\\' + extname(path) + '$'), '.lrc')
const stats = await getFileStats(lrcPath)
// console.log(lrcPath, stats)
if (stats && stats.size < 1024 * 1024 * 10) {
const lrcBuf = await readFile(lrcPath)
const { detect } = await import('jschardet')
const { confidence, encoding } = detect(lrcBuf)
console.log('lrc file encoding', confidence, encoding)
if (confidence > 0.8) {
const iconv = await import('iconv-lite')
if (iconv.encodingExists(encoding)) {
const lrc = iconv.decode(lrcBuf, encoding)
if (lrc) return lrc
}
}
}
// 尝试读取文件内歌词
const metadata = await getFileMetadata(path)
if (!metadata) return null
if (metadata.common.lyrics?.length && metadata.common.lyrics[0].length > 10) {
return metadata.common.lyrics[0]
}
// console.log(metadata)
for (const info of Object.values(metadata.native)) {
const ust = info.find(i => i.id == 'USLT')
if (ust && ust.value.text.length > 10) return ust.value.text
}
return null
}
@@ -1,5 +1,5 @@
import apiSourceInfo from './api-source-info'
import { apiSource, userApi } from '@renderer/core/share'
import { apiSource, userApi } from '@renderer/store'
const apiList = {}
const supportQuality = {}
@@ -62,15 +62,15 @@ export default {
})
return list
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
return this.musicSearch(str, page, limit).then(result => {
if (!result || result.error_code !== 22000) return this.search(str, page, { limit }, retryNum)
if (!result || result.error_code !== 22000) return this.search(str, page, limit, retryNum)
let list = this.handleResult(result.result.song_info.song_list)
if (list == null) return this.search(str, page, { limit }, retryNum)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = result.result.song_info.total
this.page = page
@@ -1,9 +1,9 @@
import kw from './kw'
import kg from './kg'
import tx from './tx'
import wy from './wy'
import mg from './mg'
import bd from './bd'
import kw from './kw/index'
import kg from './kg/index'
import tx from './tx/index'
import wy from './wy/index'
import mg from './mg/index'
import bd from './bd/index'
import xm from './xm'
import { supportQuality } from './api-source'
@@ -59,9 +59,12 @@ export default {
},
supportQuality,
async findMusic(musicInfo) {
async findMusic({ name, singer, albumName, interval, source: s }) {
const tasks = []
const sortSingle = singer => singer.includes('、') ? singer.split('、').sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0)).join('、') : singer
const singersRxp = /、|&|;|;|\/|,|,|\|/
const sortSingle = singer => singersRxp.test(singer)
? singer.split(singersRxp).sort((a, b) => a.localeCompare(b)).join('、')
: singer
const sortMusic = (arr, callback) => {
const tempResult = []
for (let i = arr.length - 1; i > -1; i--) {
@@ -78,35 +81,36 @@ export default {
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()
const filterStr = str => typeof str == 'string' ? str.replace(/\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[/g, '') : str
const musicName = trimStr(name)
const sortedSinger = filterStr(String(sortSingle(singer)).toLowerCase())
const lowerCaseName = filterStr(String(musicName).toLowerCase())
const lowerCaseAlbumName = filterStr(String(albumName).toLowerCase())
const excludeSource = ['xm']
for (const source of sources.sources) {
if (!sources[source.id].musicSearch || source.id === musicInfo.source || excludeSource.includes(source.id)) continue
if (!sources[source.id].musicSearch || source.id == s || excludeSource.includes(source.id)) continue
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${musicInfo.singer || ''}`.trim(), 1, { limit: 25 }).then(res => {
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${singer || ''}`.trim(), 1, 25).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)
item.sortedSinger = filterStr(String(sortSingle(item.singer)).toLowerCase())
item.lowerCaseName = filterStr(String(item.name ?? '').toLowerCase())
item.lowerCaseAlbumName = filterStr(String(item.albumName ?? '').toLowerCase())
// console.log(lowerCaseName, item.lowerCaseName, item.source)
if (
(
item.sortedSinger === sortedSinger && item.lowerCaseName === lowerCaseName
item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName
) ||
(
item.interval === musicInfo.interval && item.lowerCaseName === lowerCaseName &&
(interval ? item.interval == interval : true) && item.lowerCaseName == lowerCaseName &&
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
) ||
(
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
item.interval === musicInfo.interval
item.lowerCaseName == lowerCaseName && (lowerCaseAlbumName ? item.lowerCaseAlbumName == lowerCaseAlbumName : true) &&
(interval ? item.interval == interval : true)
) ||
(
item.lowerCaseName === lowerCaseName && item.lowerCaseAlbumName === lowerCaseAlbumName &&
item.lowerCaseName == lowerCaseName && (lowerCaseAlbumName ? item.lowerCaseAlbumName == lowerCaseAlbumName : true) &&
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
)
) {
@@ -119,10 +123,10 @@ export default {
const result = (await Promise.all(tasks)).filter(s => s)
const newResult = []
if (result.length) {
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))
newResult.push(...sortMusic(result, item => item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName && item.interval == 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 == interval))
for (const item of result) {
delete item.sortedSinger
delete item.lowerCaseName
@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { decodeName, dateFormat2 } from '../../'
import { decodeName, dateFormat2 } from '../../index'
export default {
_requestObj: null,
@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { decodeName } from '../..'
import { decodeName } from '../../index'
export default {
_requestObj: null,
@@ -1,6 +1,6 @@
import { httpFetch } from '../../request'
import { decodeLyric } from './util'
import { decodeName } from '../..'
import { decodeName } from '../../index'
const headExp = /^.*\[id:\$\w+\]\n/
@@ -9,14 +9,19 @@ const parseLyric = str => {
if (headExp.test(str)) str = str.replace(headExp, '')
let trans = str.match(/\[language:([\w=\\/+]+)\]/)
let lyric
let rlyric
let tlyric
if (trans) {
str = str.replace(/\[language:[\w=\\/+]+\]\n/, '')
let json = JSON.parse(Buffer.from(trans[1], 'base64').toString())
for (const item of json.content) {
if (item.type == 1) {
tlyric = item.lyricContent
break
switch (item.type) {
case 0:
rlyric = item.lyricContent
break
case 1:
tlyric = item.lyricContent
break
}
}
}
@@ -30,17 +35,22 @@ const parseLyric = str => {
time %= 60
let s = parseInt(time).toString().padStart(2, '0')
time = `${m}:${s}.${ms}`
if (tlyric) tlyric[i] = `[${time}]${tlyric[i++][0]}`
if (rlyric) rlyric[i] = `[${time}]${rlyric[i]?.join('') ?? ''}`
if (tlyric) tlyric[i] = `[${time}]${tlyric[i]?.join('') ?? ''}`
i++
return str.replace(result[1], time)
})
rlyric = rlyric ? rlyric.join('\n') : ''
tlyric = tlyric ? tlyric.join('\n') : ''
lxlyric = lxlyric.replace(/<(\d+,\d+),\d+>/g, '<$1>')
lxlyric = decodeName(lxlyric)
lyric = lxlyric.replace(/<\d+,\d+>/g, '')
rlyric = decodeName(rlyric)
tlyric = decodeName(tlyric)
return {
lyric,
tlyric,
rlyric,
lxlyric,
}
}
@@ -77,15 +77,15 @@ export default {
})
return list
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page, limit).then(result => {
if (!result || result.errcode !== 0) return this.search(str, page, { limit }, retryNum)
if (!result || result.errcode !== 0) return this.search(str, page, limit, retryNum)
let list = this.handleResult(result.data.info)
if (list == null) return this.search(str, page, { limit }, retryNum)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = result.data.total
this.page = page
@@ -151,6 +151,7 @@ export default {
name: item.specialname,
time: item.publish_time || item.publishtime,
img: item.img || item.imgurl,
total: item.songcount,
grade: item.grade,
desc: item.intro,
source: 'kg',
@@ -723,6 +724,35 @@ export default {
}
return `https://www.kugou.com/yy/special/single/${id}.html`
},
search(text, page, limit = 20) {
// http://msearchretry.kugou.com/api/v3/search/special?version=9209&keyword=%E5%91%A8%E6%9D%B0%E4%BC%A6&pagesize=20&filter=0&page=1&sver=2&with_res_tag=0
// return httpFetch(`http://ioscdn.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&plat=2&version=7910&correct=1&sver=5`)
return httpFetch(`http://msearchretry.kugou.com/api/v3/search/special?keyword=${encodeURIComponent(text)}&page=${page}&pagesize=${limit}&showtype=10&filter=0&version=7910&sver=2`)
.promise.then(({ body }) => {
if (body.errcode != 0) throw new Error('filed')
// console.log(body.data.info)
return {
list: body.data.info.map(item => {
return {
play_count: this.formatPlayCount(item.playcount),
id: 'id_' + item.specialid,
author: item.nickname,
name: item.specialname,
time: item.publishtime,
img: item.imgurl,
grade: item.grade,
desc: item.intro,
total: item.songcount,
source: 'kg',
}
}),
limit,
total: body.data.total,
source: 'kg',
}
})
},
}
// getList
@@ -44,8 +44,8 @@ export default {
}
}
if (formats.includes('HIRFLAC')) {
types.push({ type: 'flac32bit', size: null })
_types.flac32bit = {
types.push({ type: 'flac24bit', size: null })
_types.flac24bit = {
size: null,
}
}
@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { dateFormat2 } from '../../'
import { dateFormat2 } from '../../index'
export default {
_requestObj: null,
@@ -116,8 +116,8 @@ export default {
}
}
if (formats.includes('HIRFLAC')) {
types.push({ type: 'flac32bit', size: null })
_types.flac32bit = {
types.push({ type: 'flac24bit', size: null })
_types.flac24bit = {
size: null,
}
}
@@ -47,8 +47,8 @@ export default {
if (info) {
switch (info[2]) {
case '4000':
types.push({ type: 'flac32bit', size: info[4] })
_types.flac32bit = {
types.push({ type: 'flac24bit', size: info[4] })
_types.flac24bit = {
size: info[4].toLocaleUpperCase(),
}
break
@@ -100,16 +100,16 @@ export default {
// console.log(result)
return result
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
search(str, page = 1, limit, retryNum = 0) {
if (retryNum > 2) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page, limit).then(({ body: result }) => {
// console.log(result)
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, { limit }, ++retryNum)
if (!result || (result.TOTAL !== '0' && result.SHOW === '0')) return this.search(str, page, limit, ++retryNum)
let list = this.handleResult(result.abslist)
if (list == null) return this.search(str, page, { limit })
if (list == null) return this.search(str, page, limit, ++retryNum)
this.total = parseInt(result.TOTAL)
this.page = page
@@ -1,6 +1,6 @@
import { httpFetch } from '../../request'
import { formatPlayTime, decodeName } from '../../index'
import { formatSinger } from './util'
import { formatSinger, objStr2JSON } from './util'
import album from './album'
export default {
@@ -108,7 +108,7 @@ export default {
source: 'kw',
}
} else if (!body.length) {
return this.getList(sortId, id, type, page, ++tryNum)
return this.getList(sortId, tagId, page, ++tryNum)
}
return {
list: this.filterList2(body),
@@ -252,8 +252,8 @@ export default {
if (info) {
switch (info[2]) {
case '4000':
types.push({ type: 'flac32bit', size: info[4] })
_types.flac32bit = {
types.push({ type: 'flac24bit', size: info[4] })
_types.flac24bit = {
size: info[4].toLocaleUpperCase(),
}
break
@@ -309,6 +309,32 @@ export default {
}
return `http://www.kuwo.cn/playlist_detail/${id}`
},
search(text, page, limit = 20) {
return httpFetch(`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`)
.promise.then(({ body }) => {
body = objStr2JSON(body)
// console.log(body)
return {
list: body.abslist.map(item => {
return {
play_count: this.formatPlayCount(item.playcnt),
id: item.playlistid,
author: decodeName(item.nickname),
name: decodeName(item.name),
total: item.songnum,
// time: item.publish_time,
img: item.pic,
desc: decodeName(item.intro),
source: 'kw',
}
}),
limit,
total: parseInt(body.TOTAL),
source: 'kw',
}
})
},
}
// getList
@@ -1,31 +1,38 @@
import { httpGet, httpFetch } from '../../request'
import { rendererInvoke, NAMES } from '@common/ipc'
import { WIN_MAIN_RENDERER_EVENT_NAME } from '@common/ipcNames'
import { rendererInvoke } from '@common/rendererIpc'
const kw_token = {
token: null,
isGetingToken: false,
}
const translationMap = {
"{'": '{"',
"'}\n": '"}',
"'}": '"}',
"':'": '":"',
"','": '","',
"':{'": '":{"',
"':['": '":["',
"'}],'": '"}],"',
"':[{'": '":[{"',
"'},'": '"},"',
"'},{'": '"},{"',
"':[],'": '":[],"',
"':{},'": '":{},"',
}
// const translationMap = {
// "{'": '{"',
// "'}\n": '"}',
// "'}": '"}',
// "':'": '":"',
// "','": '","',
// "':{'": '":{"',
// "':['": '":["',
// "'}],'": '"}],"',
// "':[{'": '":[{"',
// "'},'": '"},"',
// "'},{'": '"},{"',
// "':[],'": '":[],"',
// "':{},'": '":{},"',
// "'}]}": '"}]}',
// }
// export const objStr2JSON = str => {
// return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s]))
// }
export const objStr2JSON = str => {
return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},')/g, s => translationMap[s]))
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
}
export const formatSinger = rawData => rawData.replace(/&/g, '、')
export const matchToken = headers => {
@@ -54,7 +61,7 @@ export const getToken = (retryNum = 0) => new Promise((resolve, reject) => {
})
})
export const decodeLyric = base64Data => rendererInvoke(NAMES.mainWindow.handle_kw_decode_lyric, base64Data)
export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data)
export const tokenRequest = async(url, options = {}) => {
let token = kw_token.token
@@ -1,6 +1,6 @@
import { httpFetch } from '../../request'
import album from './album'
import { dateFormat2 } from '../../'
import { dateFormat2 } from '../../index'
export default {
_requestObj: null,
@@ -125,8 +125,8 @@ export default {
break
case 'ZQ':
size = sizeFormate(type.size ?? type.androidSize)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
break
@@ -69,8 +69,8 @@ export default {
break
case 'ZQ':
size = sizeFormate(type.size ?? type.androidSize)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
break
@@ -107,7 +107,7 @@ export default {
})
return list
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
@@ -117,7 +117,7 @@ export default {
const songResultData = result.songResultData || { result: [], totalCount: 0 }
let list = this.handleResult(songResultData.result)
if (list == null) return this.search(str, page, { limit }, retryNum)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = parseInt(songResultData.totalCount)
this.page = page
@@ -134,7 +134,7 @@ export default {
return Promise.reject(new Error('link get failed'))
},
getListDetail(id, page) { // 获取歌曲列表内的音乐
getListDetail(id, page, retryNum = 0) { // 获取歌曲列表内的音乐
// https://h5.nf.migu.cn/app/v4/p/share/playlist/index.html?id=184187437&channel=0146921
// http://c.migu.cn/00bTY6?ifrom=babddaadfde4ebeda289d671ab62f236
if (/playlist\/index\.html\?/.test(id)) {
@@ -147,8 +147,8 @@ export default {
}
return Promise.all([
this.getListDetailList(id, page),
this.getListDetailInfo(id),
this.getListDetailList(id, page, retryNum),
this.getListDetailInfo(id, retryNum),
]).then(([listData, info]) => {
listData.info = info
return listData
@@ -189,8 +189,8 @@ export default {
break
case 'ZQ':
size = sizeFormate(type.size ?? type.androidSize)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
break
@@ -361,6 +361,38 @@ export default {
}
return `https://music.migu.cn/v3/music/playlist/${id}`
},
search(text, page, limit = 20) {
// return httpFetch(`http://search.kuwo.cn/r.s?all=${encodeURIComponent(text)}&pn=${page - 1}&rn=${limit}&rformat=json&encoding=utf8&ver=mbox&vipver=MUSIC_8.7.7.0_BCS37&plat=pc&devid=28156413&ft=playlist&pay=0&needliveshow=0`)
return httpFetch(`http://pd.musicapp.migu.cn/MIGUM2.0/v1.0/content/search_all.do?ua=Android_migu&version=5.0.1&text=${encodeURIComponent(text)}&pageNo=${page}&pageSize=${limit}&searchSwitch=%7B%22song%22%3A0%2C%22album%22%3A0%2C%22singer%22%3A0%2C%22tagSong%22%3A0%2C%22mvSong%22%3A0%2C%22bestShow%22%3A1%2C%22songlist%22%3A1%2C%22lyricSong%22%3A0%7D`, {
headers: {
'User-Agent': 'okhttp/3.9.1',
osVersion: 'android 7.0',
},
// family: 6,
})
.promise.then(({ body }) => {
// console.log(body)
if (body.code != this.successCode) throw new Error('failed')
return {
list: body.songListResultData.result.map(item => {
return {
play_count: this.formatPlayCount(item.playcount),
id: item.id,
// author: item.createName,
name: item.name,
// time: item.createTime,
img: item.musicListPicUrl,
total: item.musicNum,
source: 'mg',
}
}),
limit,
total: parseInt(body.songListResultData.totalCount),
source: 'mg',
}
})
},
}
// getList
@@ -1,5 +1,5 @@
import { httpFetch } from '../../request'
import { dateFormat2 } from '../../'
import { dateFormat2 } from '../../index'
const emojis = {
e400846: '😘',
@@ -139,8 +139,8 @@ export default {
}
if (item.file.size_hires !== 0) {
let size = sizeFormate(item.file.size_hires)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
}
@@ -84,8 +84,8 @@ export default {
}
if (file.size_hires !== 0) {
let size = sizeFormate(file.size_hires)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
}
@@ -118,7 +118,7 @@ export default {
// console.log(list)
return list
},
search(str, page = 1, { limit } = {}) {
search(str, page = 1, limit) {
if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680
return this.musicSearch(str, page, limit).then(({ body, meta }) => {
@@ -262,8 +262,8 @@ export default {
}
if (item.file.size_hires !== 0) {
let size = sizeFormate(item.file.size_hires)
types.push({ type: 'flac32bit', size })
_types.flac32bit = {
types.push({ type: 'flac24bit', size })
_types.flac24bit = {
size,
}
}
@@ -299,6 +299,38 @@ export default {
return `https://y.qq.com/n/ryqq/playlist/${id}`
},
search(text, page, limit = 20) {
return httpFetch(`http://c.y.qq.com/soso/fcgi-bin/client_music_search_songlist?page_no=${page - 1}&num_per_page=${limit}&format=json&query=${encodeURIComponent(text)}&remoteplace=txt.yqq.playlist&inCharset=utf8&outCharset=utf-8`, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)',
Referer: 'http://y.qq.com/portal/search.html',
},
})
.promise.then(({ body }) => {
if (body.code != 0) throw new Error('filed')
// console.log(body.data.list)
return {
list: body.data.list.map(item => {
return {
play_count: this.formatPlayCount(item.listennum),
id: item.dissid,
author: item.creator.name,
name: item.dissname,
time: item.createtime,
img: item.imgurl,
// grade: item.favorcnt / 10,
total: item.song_count,
desc: item.introduction,
source: 'tx',
}
}),
limit,
total: body.data.sum,
source: 'tx',
}
})
},
}
// getList
@@ -1,25 +1,5 @@
import crypto from 'crypto'
import dns from 'dns'
import { qualityList } from '@renderer/core/share'
/**
* 获取音乐音质
* @param {*} info
* @param {*} type
*/
const types = ['flac32bit', 'flac', 'wav', 'ape', '320k', '192k', '128k']
export const getMusicType = (info, type) => {
let list = qualityList.value[info.source]
if (!list) return '128k'
if (!list.includes(type)) type = list[list.length - 1]
const rangeType = types.slice(types.indexOf(type))
for (const type of rangeType) {
if (info._types[type]) return type
}
return '128k'
}
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex')
@@ -1,6 +1,6 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
import { dateFormat2 } from '../../'
import { dateFormat2 } from '../../index'
let cursorTools = {
cache: {},
@@ -1,6 +1,6 @@
import { httpFetch } from '../../request'
import { weapi } from './utils/crypto'
import { formatPlayTime, sizeFormate } from '../..'
import { formatPlayTime, sizeFormate } from '../../index'
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
export default {
@@ -2,7 +2,7 @@
// import { weapi } from './utils/crypto'
import { sizeFormate, formatPlayTime } from '../../index'
// import musicDetailApi from './musicDetail'
import { eapiRequest } from './utils'
import { eapiRequest } from './utils/index'
export default {
limit: 30,
@@ -73,16 +73,16 @@ export default {
}
})
},
search(str, page = 1, { limit } = {}, retryNum = 0) {
search(str, page = 1, limit, retryNum = 0) {
if (++retryNum > 3) return Promise.reject(new Error('try max num'))
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)
if (!result || result.code !== 200) return this.search(str, page, limit, retryNum)
let list = this.handleResult(result.result.songs || [])
// console.log(list)
if (list == null) return this.search(str, page, { limit }, retryNum)
if (list == null) return this.search(str, page, limit, retryNum)
this.total = result.result.songCount || 0
this.page = page
@@ -7,6 +7,7 @@ import { weapi, linuxapi } from './utils/crypto'
import { httpFetch } from '../../request'
import { formatPlayTime, sizeFormate } from '../../index'
import musicDetailApi from './musicDetail'
import { eapiRequest } from './utils/index'
export default {
_requestObj_tags: null,
@@ -297,6 +298,26 @@ export default {
const { id } = await this.getListId(rawId)
return `https://music.163.com/#/playlist?id=${id}`
},
search(text, page, limit = 20) {
return eapiRequest('/api/cloudsearch/pc', {
s: text,
type: 1000, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit,
total: page == 1,
offset: limit * (page - 1),
})
.promise.then(({ body }) => {
if (body.code != this.successCode) throw new Error('filed')
// console.log(body)
return {
list: this.filterList(body.result.playlists),
limit,
total: body.result.playlistCount,
source: 'wy',
}
})
},
}
// getList
+91
View File
@@ -0,0 +1,91 @@
import { throttle } from '@common/utils/common'
import Pickr from '@simonwep/pickr'
import '@simonwep/pickr/dist/themes/classic.min.css'
export interface PickrTools {
pickr: Pickr | null
create: (dom: HTMLElement, color: string, swatches: string[] | null, change: (color: string) => void, reset: () => void) => PickrTools
destroy: () => void
setColor: (color: string) => void
}
export const pickrTools: PickrTools = {
pickr: null,
create(dom, color, swatches, change, reset) {
const pickrTools: PickrTools = Object.create(this)
pickrTools.pickr = Pickr.create({
el: dom,
default: color,
theme: 'classic', // or 'monolith', or 'nano'
defaultRepresentation: 'RGBA',
autoReposition: false,
closeWithKey: '',
appClass: 'color-picker',
comparison: false,
useAsButton: true,
swatches,
components: {
// Main components
preview: true,
opacity: true,
hue: true,
// Input / output Options
interaction: {
hex: true,
rgba: true,
input: true,
cancel: true,
// save: true,
},
},
i18n: {
// Strings visible in the UI
'ui:dialog': ' ',
'btn:toggle': window.i18n.t('theme_edit_modal__pick_color'),
'btn:swatch': ' ',
'btn:last-color': window.i18n.t('theme_edit_modal__pick_last_color'),
'btn:save': window.i18n.t('theme_edit_modal__pick_save'),
'btn:cancel': window.i18n.t('theme_edit_modal__pick_cancel'),
// Strings used for aria-labels
'aria:btn:save': ' ',
'aria:btn:cancel': ' ',
'aria:input': ' ',
'aria:palette': ' ',
'aria:hue': '',
'aria:opacity': ' ',
},
})
let swatchselectColor: any
const throttleChange = throttle((color: any, source: string) => {
if (source == 'swatch' && swatchselectColor !== color) return
change(color.toRGBA().toString())
})
pickrTools.pickr.on('swatchselect', (color: any) => {
swatchselectColor = color
}).on('change', throttleChange).on('cancel', () => {
change(color)
console.log('cancel')
}).on('cancel', () => {
reset()
})
return pickrTools
},
destroy() {
if (!this.pickr) return
this.pickr.destroyAndRemove()
this.pickr = null
},
setColor(color) {
this.pickr?.setColor(color)
},
}
+34 -11
View File
@@ -2,11 +2,34 @@ import needle from 'needle'
// import progress from 'request-progress'
import { debugRequest } from './env'
import { requestMsg } from './message'
import { bHh } from './music/options'
import { bHh } from './musicSdk/options'
import { deflateRaw } from 'zlib'
import { getRequestAgent } from './index'
import { proxy } from '@renderer/store'
import { httpOverHttp, httpsOverHttp } from 'tunnel'
// import fs from 'fs'
const httpsRxp = /^https:/
const getRequestAgent = url => {
let options
if (proxy.enable && proxy.host) {
options = {
proxy: {
host: proxy.host,
port: proxy.port,
},
}
} else if (proxy.envProxy) {
options = {
proxy: {
host: proxy.envProxy.host,
port: proxy.envProxy.port,
},
}
}
return options ? (httpsRxp.test(url) ? httpsOverHttp : httpOverHttp)(options) : undefined
}
const request = (url, options, callback) => {
let data
if (options.body) {
@@ -49,12 +72,20 @@ const defaultHeaders = {
const buildHttpPromose = (url, options) => {
let obj = {
isCancelled: false,
cancelHttp: () => {
if (!obj.requestObj) return obj.isCancelled = true
cancelHttp(obj.requestObj)
obj.requestObj = null
obj.promise = obj.cancelHttp = null
obj.cancelFn(new Error(requestMsg.cancelRequest))
obj.cancelFn = null
},
}
obj.promise = new Promise((resolve, reject) => {
obj.cancelFn = reject
debugRequest && console.log(`\n---send request------${url}------------`)
fetchData(url, options.method, options, (err, resp, body) => {
// options.isShowProgress && window.api.hideProgress()
// options.isShowProgress && window.api.hideProgress()
debugRequest && console.log(`\n---response------${url}------------`)
debugRequest && console.log(body)
obj.requestObj = null
@@ -66,14 +97,6 @@ const buildHttpPromose = (url, options) => {
if (obj.isCancelled) obj.cancelHttp()
})
})
obj.cancelHttp = () => {
if (!obj.requestObj) return obj.isCancelled = true
cancelHttp(obj.requestObj)
obj.requestObj = null
obj.promise = obj.cancelHttp = null
obj.cancelFn(new Error(requestMsg.cancelRequest))
obj.cancelFn = null
}
return obj
}
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2021 Shigma
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,10 @@
# simplify-chinese
Convert chinese characters between simplified form and tranditional form / 汉字简繁体转换工具。
```js
const { simplify, tranditionalize } = require('simplify-chinese')
console.log(simplify('窩窩頭')) // 窝窝头
console.log(tranditionalize('窝窝头')) // 窩窩頭
```
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More