diff --git a/publish/changeLog.md b/publish/changeLog.md index a1408a96..bc4ee3f8 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -2,6 +2,7 @@ - 新增“双击列表里的歌曲时自动切换到当前列表播放”设置,此功能仅对歌单、排行榜有效,默认关闭 - 新增打开收藏的在线列表的对应平台详情页功能,可以在我的列表-列表右键菜单中使用 +- 新增定时暂停播放功能,由于此功能大多数人可能不常用,所以将其放在设置-基本设置中 ### 优化 diff --git a/src/common/defaultSetting.js b/src/common/defaultSetting.js index 965c9686..f7ab6228 100644 --- a/src/common/defaultSetting.js +++ b/src/common/defaultSetting.js @@ -2,7 +2,7 @@ const path = require('path') const os = require('os') const defaultSetting = { - version: '1.0.49', + version: '1.0.50', player: { togglePlayMethod: 'listLoop', highQuality: false, @@ -16,6 +16,8 @@ const defaultSetting = { isPlayLxlrc: true, isSavePlayTime: false, audioVisualization: false, + waitPlayEndStop: true, + waitPlayEndStopTime: 0, }, desktopLyric: { enable: false, diff --git a/src/common/ipcNames.js b/src/common/ipcNames.js index 7586e083..2293a819 100644 --- a/src/common/ipcNames.js +++ b/src/common/ipcNames.js @@ -11,6 +11,9 @@ const names = { clear_env_params_deeplink: 'clear_env_params_deeplink', wait: 'wait', wait_cancel: 'wait_cancel', + interval: 'interval', + interval_callback: 'interval_callback', + interval_cancel: 'interval_cancel', open_dev_tools: 'open_dev_tools', set_music_meta: 'set_music_meta', diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 875eb1db..e45e2e78 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -143,6 +143,12 @@ "pagination__next": "Next page", "pagination__page": "Page {num}", "pagination__prev": "Previous page", + "play_timeout": "Timed pause", + "play_timeout_close": "Close", + "play_timeout_confirm": "Confirm", + "play_timeout_end": "Wait for the song to finish before pausing", + "play_timeout_unit": "minute", + "play_timeout_update": "Update timing", "player__add_music_to": "Add the current song to...", "player__buffering": "Buffering...", "player__desktop_lyric_lock": "Right click to lock lyrics", @@ -322,6 +328,7 @@ "setting__play_quality": "Play 320K quality songs first (if supported)", "setting__play_save_play_time": "Remember playback progress", "setting__play_task_bar": "Show playing progress on the taskbar", + "setting__play_timeout": "Timed pause", "setting__search": "Search", "setting__search_focus_search_box": "Automatically focus the search box on startup", "setting__search_history": "Search history", diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 0e3b4abd..1599efa0 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -143,6 +143,14 @@ "pagination__next": "下一页", "pagination__page": "第 {num} 页", "pagination__prev": "上一页", + "play_timeout": "定时暂停", + "play_timeout_close": "关闭", + "play_timeout_confirm": "确认", + "play_timeout_end": "等待歌曲播放完毕再暂停", + "play_timeout_stop": "取消定时", + "play_timeout_tip": "{time} 后暂停播放", + "play_timeout_unit": "分钟", + "play_timeout_update": "更新定时", "player__add_music_to": "添加当前歌曲到...", "player__buffering": "缓冲中...", "player__desktop_lyric_lock": "右击锁定歌词", @@ -322,6 +330,7 @@ "setting__play_quality": "优先播放320K品质的歌曲(如果支持)", "setting__play_save_play_time": "记住播放进度", "setting__play_task_bar": "在任务栏上显示当前歌曲播放进度", + "setting__play_timeout": "定时暂停", "setting__search": "搜索设置", "setting__search_focus_search_box": "启动时自动聚焦搜索框", "setting__search_history": "显示历史搜索记录", diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 4aedd277..fad40b87 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -143,6 +143,12 @@ "pagination__next": "下一頁", "pagination__page": "第 {num} 頁", "pagination__prev": "上一頁", + "play_timeout": "定時暫停", + "play_timeout_close": "關閉", + "play_timeout_confirm": "確認", + "play_timeout_end": "等待歌曲播放完畢再暫停", + "play_timeout_unit": "分鐘", + "play_timeout_update": "更新定時", "player__add_music_to": "添加當前歌曲到...", "player__album": "專輯名:", "player__buffering": "緩衝中...", @@ -322,6 +328,7 @@ "setting__play_quality": "優先播放320K品質的歌曲(如果支持)", "setting__play_save_play_time": "記住播放進度", "setting__play_task_bar": "在任務欄上顯示當前歌曲播放進度", + "setting__play_timeout": "定時暫停", "setting__search": "搜索設置", "setting__search_focus_search_box": "啟動時自動聚焦搜索框", "setting__search_history": "顯示歷史搜索記錄", diff --git a/src/main/rendererEvents/wait.js b/src/main/rendererEvents/wait.js index a6d99368..ca8d842d 100644 --- a/src/main/rendererEvents/wait.js +++ b/src/main/rendererEvents/wait.js @@ -1,4 +1,4 @@ -const { mainOn, mainHandle, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') +const { mainOn, mainHandle, mainSend, NAMES: { mainWindow: ipcMainWindowNames } } = require('@common/ipc') const timeoutMap = new Map() @@ -23,3 +23,24 @@ mainOn(ipcMainWindowNames.wait_cancel, (event, id) => { clearTimeout(timeout.timeout) timeout.reject('cancelled') }) + +mainOn(ipcMainWindowNames.interval, (event, { time, id }) => { + if (timeoutMap.has(id)) return + const timeout = setInterval(() => { + if (global.modules.mainWindow) mainSend(global.modules.mainWindow, ipcMainWindowNames.interval_callback, id) + }, time) + + timeoutMap.set(id, { + timeout, + type: 'interval', + time, + }) +}) + +mainOn(ipcMainWindowNames.interval_cancel, (event, id) => { + if (!timeoutMap.has(id)) return + const timeout = timeoutMap.get(id) + timeoutMap.delete(id) + if (timeout.type != 'interval') return + clearInterval(timeout.timeout) +}) diff --git a/src/renderer/components/base/Input.vue b/src/renderer/components/base/Input.vue index ec3a5a22..9ae89be4 100644 --- a/src/renderer/components/base/Input.vue +++ b/src/renderer/components/base/Input.vue @@ -24,7 +24,7 @@ export default { default: false, }, modelValue: { - type: String, + type: [String, Number], default: '', }, type: { diff --git a/src/renderer/core/useApp/usePlayer/index.js b/src/renderer/core/useApp/usePlayer/index.js index 72c59941..479df21c 100644 --- a/src/renderer/core/useApp/usePlayer/index.js +++ b/src/renderer/core/useApp/usePlayer/index.js @@ -4,6 +4,7 @@ import { import useMediaDevice from './useMediaDevice' import usePlayerEvent from './usePlayerEvent' import usePlayer from './usePlayer' +import { init as initPlayTimeoutStop } from '@renderer/utils/timeoutStop' export default ({ setting }) => { createAudio() @@ -11,5 +12,7 @@ export default ({ setting }) => { usePlayerEvent() useMediaDevice({ setting }) // 初始化音频驱动输出设置 usePlayer({ setting }) + + initPlayTimeoutStop() } diff --git a/src/renderer/core/useApp/usePlayer/usePlayEvent.js b/src/renderer/core/useApp/usePlayer/usePlayEvent.js index be0ae5f1..76f9836c 100644 --- a/src/renderer/core/useApp/usePlayer/usePlayEvent.js +++ b/src/renderer/core/useApp/usePlayer/usePlayEvent.js @@ -49,6 +49,7 @@ export default ({ } const handleLoadstart = () => { + if (global.isPlayedStop) return startLoadingTimeout() setAllStatus(t('player__loading')) } @@ -77,6 +78,7 @@ export default ({ const handleError = errCode => { if (!musicInfo.songmid) return clearLoadingTimeout() + if (global.isPlayedStop) return if (playMusicInfo.listId != 'download' && errCode !== 1 && retryNum < 2) { // 若音频URL无效则尝试刷新2次URL // console.log(this.retryNum) retryNum++ @@ -96,6 +98,11 @@ export default ({ clearLoadingTimeout() } + const handlePlayedStop = () => { + clearDelayNextTimeout() + clearLoadingTimeout() + } + window.eventHub.on(eventPlayerNames.player_loadstart, handleLoadstart) window.eventHub.on(eventPlayerNames.player_loadeddata, handleLoadeddata) @@ -105,6 +112,7 @@ export default ({ window.eventHub.on(eventPlayerNames.player_emptied, handleEmpied) window.eventHub.on(eventPlayerNames.error, handleError) window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo) + window.eventHub.on(eventPlayerNames.playedStop, handlePlayedStop) onBeforeUnmount(() => { window.eventHub.off(eventPlayerNames.player_loadstart, handleLoadstart) @@ -115,5 +123,6 @@ export default ({ window.eventHub.off(eventPlayerNames.player_emptied, handleEmpied) window.eventHub.off(eventPlayerNames.error, handleError) window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo) + window.eventHub.off(eventPlayerNames.playedStop, handlePlayedStop) }) } diff --git a/src/renderer/core/useApp/usePlayer/usePlayer.js b/src/renderer/core/useApp/usePlayer/usePlayer.js index f0a64c9b..8c436c2c 100644 --- a/src/renderer/core/useApp/usePlayer/usePlayer.js +++ b/src/renderer/core/useApp/usePlayer/usePlayer.js @@ -103,11 +103,13 @@ export default ({ setting }) => { setAllStatus('Try toggle source...') }, }).then(url => { + if (global.isPlayedStop) return if (targetSong !== musicInfoItem.value || isPlay.value || type != getPlayType(setting.value.player.highQuality, musicInfoItem.value)) return setMusicInfo({ url }) setResource(url) }).catch(err => { // console.log('err', err.message) + if (global.isPlayedStop) return if (targetSong !== musicInfoItem.value || isPlay.value) return if (err.message == requestMsg.cancelRequest) return if (!isRetryed) return setUrl(targetSong, isRefresh, true) @@ -198,6 +200,7 @@ export default ({ setting }) => { const setPauseStatus = () => { setPlay(false) setTitle() + if (global.isPlayedStop) handlePause() } const setStopStatus = () => { setPlay(false) @@ -288,11 +291,16 @@ export default ({ setting }) => { const handleEnded = () => { setAllStatus(t('player__end')) + + if (global.isPlayedStop) return playNext() } - // 播放、暂停播放切换 - const handleTogglePlay = async() => { + const handlePause = () => { + setPlayerPause() + } + + const handlePlay = async() => { if (playMusicInfo.musicInfo == null) return if (isPlayerEmpty()) { if (playMusicInfo.listId == 'download') { @@ -313,13 +321,24 @@ export default ({ setting }) => { } return } + setPlayerPlay() + } + + // 播放、暂停播放切换 + const handleTogglePlay = () => { + if (global.isPlayedStop) global.isPlayedStop = false if (isPlay.value) { - setPlayerPause() + handlePause() } else { - setPlayerPlay() + handlePlay() } } + const handlePlayedStop = () => { + clearDelayNextTimeout() + clearLoadTimeout() + } + watch(() => setting.value.player.togglePlayMethod, newValue => { setLoopPlay(newValue === 'singleLoop') if (playedList.length) clearPlayedList() @@ -339,13 +358,16 @@ export default ({ setting }) => { window.eventHub.on(eventPlayerNames.stop, setStopStatus) window.eventHub.on(eventPlayerNames.playMusic, playMusic) + window.eventHub.on(eventPlayerNames.setPlay, handlePlay) + window.eventHub.on(eventPlayerNames.setPause, handlePause) + window.eventHub.on(eventPlayerNames.setStop, handelStop) window.eventHub.on(eventPlayerNames.setTogglePlay, handleTogglePlay) window.eventHub.on(eventPlayerNames.setPlayPrev, playPrev) window.eventHub.on(eventPlayerNames.setPlayNext, playNext) window.eventHub.on(eventPlayerNames.setPlayInfo, handleSetPlayInfo) - window.eventHub.on(eventPlayerNames.setStop, handelStop) window.eventHub.on(eventPlayerNames.player_ended, handleEnded) + window.eventHub.on(eventPlayerNames.playedStop, handlePlayedStop) onBeforeUnmount(() => { @@ -360,11 +382,14 @@ export default ({ setting }) => { window.eventHub.off(eventPlayerNames.playMusic, playMusic) window.eventHub.off(eventPlayerNames.setTogglePlay, handleTogglePlay) + window.eventHub.off(eventPlayerNames.setPlay, handlePlay) + window.eventHub.off(eventPlayerNames.setPause, handlePause) + window.eventHub.off(eventPlayerNames.setStop, handelStop) window.eventHub.off(eventPlayerNames.setPlayPrev, playPrev) window.eventHub.off(eventPlayerNames.setPlayNext, playNext) window.eventHub.off(eventPlayerNames.setPlayInfo, handleSetPlayInfo) - window.eventHub.off(eventPlayerNames.setStop, handelStop) window.eventHub.off(eventPlayerNames.player_ended, handleEnded) + window.eventHub.off(eventPlayerNames.playedStop, handlePlayedStop) }) } diff --git a/src/renderer/core/useApp/usePlayer/useWatchList.js b/src/renderer/core/useApp/usePlayer/useWatchList.js index 84ba6708..284fbbd5 100644 --- a/src/renderer/core/useApp/usePlayer/useWatchList.js +++ b/src/renderer/core/useApp/usePlayer/useWatchList.js @@ -15,7 +15,7 @@ export default ({ playNext }) => { const { playIndex } = updatePlayIndex() if (playIndex < 0 && !playMusicInfo.isTempPlay) { // 歌曲被移除 - if (getList(playMusicInfo.listId).length) { + if (getList(playMusicInfo.listId).length && !global.isPlayedStop) { playNext() } else { window.eventHub.emit(eventPlayerNames.setStop) diff --git a/src/renderer/event/names.js b/src/renderer/event/names.js index 2216018d..651f4d47 100644 --- a/src/renderer/event/names.js +++ b/src/renderer/event/names.js @@ -15,6 +15,7 @@ const names = { }, player: { setTogglePlay: 'setTogglePlay', // 播放/暂停切换 + setPlay: 'setPlay', // 播放 setPause: 'setPause', // 暂停 setStop: 'setStop', // 停止 setPlayPrev: 'setPlayPrev', // 上一曲 @@ -31,6 +32,7 @@ const names = { updateLyric: 'updateLyric', activeTransition: 'activeTransition', // 激活进度条动画事件 + playedStop: 'playedStop', // 定时停止事件 // 播放器事件 play: 'play', diff --git a/src/renderer/store/modules/player.js b/src/renderer/store/modules/player.js index a6e42dca..7e54afc3 100644 --- a/src/renderer/store/modules/player.js +++ b/src/renderer/store/modules/player.js @@ -32,6 +32,7 @@ const state = { } const playMusic = () => { + if (global.isPlayedStop) global.isPlayedStop = false window.eventHub.emit(eventPlayerNames.playMusic) } diff --git a/src/renderer/utils/timeoutStop.js b/src/renderer/utils/timeoutStop.js new file mode 100644 index 00000000..c06d0313 --- /dev/null +++ b/src/renderer/utils/timeoutStop.js @@ -0,0 +1,93 @@ +import { ref, computed } from '@renderer/utils/vueTools' +import { rendererSend, rendererOn, NAMES } from '@common/ipc' +import { isPlay } from '@renderer/core/share/player' +import store from '@renderer/store' +import { player as eventPlayerNames } from '@renderer/event/names' + +global.isPlayedStop = false + +const time = ref(-1) + + +const timeoutTools = { + inited: false, + isRunning: false, + timeout: null, + time: -1, + id: 'play__stop__timeout', + exit() { + const setting = store.getters.setting + global.isPlayedStop = true + if (!setting.player.waitPlayEndStop && isPlay.value) { + window.eventHub.emit(eventPlayerNames.setPause) + } + }, + clearTimeout() { + rendererSend(NAMES.mainWindow.interval_cancel, this.id) + if (!this.isRunning) return + this.time = -1 + time.value = -1 + this.isRunning = false + }, + start(_time) { + this.clearTimeout() + this.time = _time + time.value = _time + this.isRunning = true + rendererSend(NAMES.mainWindow.interval, { + time: 1000, + id: this.id, + }) + }, + init() { + if (this.inited) return + this.clearTimeout() + rendererOn(NAMES.mainWindow.interval_callback, (event, id) => { + if (id !== this.id) return + + if (this.time > 0) { + this.time-- + time.value-- + } else { + this.clearTimeout() + this.exit() + } + }) + this.inited = true + }, +} + +export const init = () => { + timeoutTools.init() +} + +export const startTimeoutStop = time => { + if (global.isPlayedStop) global.isPlayedStop = false + timeoutTools.start(time) +} +export const stopTimeoutStop = () => { + if (global.isPlayedStop) global.isPlayedStop = false + timeoutTools.clearTimeout() +} + +const formatTime = time => { + // let d = parseInt(time / 86400) + // d = d ? d.toString() + ':' : '' + // time = time % 86400 + let h = parseInt(time / 3600) + h = h ? h.toString() + ':' : '' + time = time % 3600 + const m = parseInt(time / 60).toString().padStart(2, '0') + const s = parseInt(time % 60).toString().padStart(2, '0') + return `${h}${m}:${s}` +} +export const useTimeout = () => { + const timeLabel = computed(() => { + return time.value > 0 ? formatTime(time.value) : '' + }) + + return { + time, + timeLabel, + } +} diff --git a/src/renderer/views/setting/components/PlayTimeoutModal.vue b/src/renderer/views/setting/components/PlayTimeoutModal.vue new file mode 100644 index 00000000..0d0b43b4 --- /dev/null +++ b/src/renderer/views/setting/components/PlayTimeoutModal.vue @@ -0,0 +1,175 @@ + + + + + + diff --git a/src/renderer/views/setting/components/SettingBasic.vue b/src/renderer/views/setting/components/SettingBasic.vue index d5724a3f..f530b98b 100644 --- a/src/renderer/views/setting/components/SettingBasic.vue +++ b/src/renderer/views/setting/components/SettingBasic.vue @@ -9,12 +9,15 @@ dd label {{$t('theme_' + theme.className)}} dd - .gap-top.top - base-checkbox(id="setting_show_animate" v-model="currentStting.isShowAnimation" :label="$t('setting__basic_show_animation')") - .gap-top - base-checkbox(id="setting_animate" v-model="currentStting.randomAnimate" :label="$t('setting__basic_animation')") - .gap-top - base-checkbox(id="setting_to_tray" v-model="currentStting.tray.isShow" :label="$t('setting__basic_to_tray')") + div + .gap-top.top + base-checkbox(id="setting_show_animate" v-model="currentStting.isShowAnimation" :label="$t('setting__basic_show_animation')") + .gap-top + base-checkbox(id="setting_animate" v-model="currentStting.randomAnimate" :label="$t('setting__basic_animation')") + .gap-top + base-checkbox(id="setting_to_tray" v-model="currentStting.tray.isShow" :label="$t('setting__basic_to_tray')") + p.gap-top + base-btn.btn(min @click="isShowPlayTimeoutModal = true") {{$t('setting__play_timeout')}} {{ timeLabel ? ` (${timeLabel})` : '' }} dd(:tips="$t('setting__basic_source_title')") h3#basic_source {{$t('setting__basic_source')}} @@ -48,6 +51,7 @@ dd div base-checkbox.gap-left(v-for="item in controlBtnPositionList" :key="item.id" :id="`setting_basic_control_btn_position_${item.id}`" name="setting_basic_control_btn_position" need v-model="currentStting.controlBtnPosition" :value="item.id" :label="item.name") +play-timeout-modal(v-model="isShowPlayTimeoutModal") user-api-modal(v-model="isShowUserApiModal") @@ -58,12 +62,15 @@ import { langList } from '@/lang' import { currentStting } from '../setting' import { setWindowSize } from '@renderer/utils' import apiSourceInfo from '@renderer/utils/music/api-source-info' +import { useTimeout } from '@renderer/utils/timeoutStop' +import PlayTimeoutModal from './PlayTimeoutModal' import UserApiModal from './UserApiModal' export default { name: 'SettingBasic', components: { + PlayTimeoutModal, UserApiModal, }, setup() { @@ -81,6 +88,9 @@ export default { apiSource.value = visible }) + const isShowPlayTimeoutModal = ref(false) + const { timeLabel } = useTimeout() + const isShowUserApiModal = ref(false) const getApiStatus = () => { let status @@ -138,6 +148,8 @@ export default { return { currentStting, themes, + isShowPlayTimeoutModal, + timeLabel, apiSources, isShowUserApiModal, windowSizeList, diff --git a/src/renderer/views/setting/setting.js b/src/renderer/views/setting/setting.js index 8b007922..8b753255 100644 --- a/src/renderer/views/setting/setting.js +++ b/src/renderer/views/setting/setting.js @@ -8,6 +8,8 @@ export const currentStting = ref({ volume: 1, mediaDeviceId: 'default', isMediaDeviceRemovedStopPlay: false, + waitPlayEndStop: true, + waitPlayEndStopTime: 0, }, desktopLyric: { enable: false,