From edf82686c5da6ce4839dc028896ac746a147db14 Mon Sep 17 00:00:00 2001 From: Folltoshe Date: Tue, 27 Jun 2023 21:18:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=9F=B3=E8=B4=A8=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/constants.ts | 2 + src/common/defaultSetting.ts | 3 +- src/common/types/app_setting.d.ts | 9 ++- src/lang/en-us.json | 3 + src/lang/zh-cn.json | 3 + src/lang/zh-tw.json | 3 + src/renderer/core/music/online.ts | 4 +- src/renderer/core/music/utils.ts | 73 +++++++++++++++---- .../views/Setting/components/SettingPlay.vue | 35 ++++++++- 9 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/common/constants.ts b/src/common/constants.ts index 3975dfcb..1db8b597 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -77,6 +77,8 @@ export const DOWNLOAD_STATUS = { export const QUALITYS = ['flac24bit', 'flac', 'wav', 'ape', '320k', '192k', '128k'] as const +export const QUALITYS_SUPPORT = ['128k', '320k', 'flac', 'flac24bit'] as const + export const SYNC_CODE = { helloMsg: 'Hello~::^-^::~v3~', idPrefix: 'OjppZDo6', diff --git a/src/common/defaultSetting.ts b/src/common/defaultSetting.ts index 1a9f896b..0eccefb3 100644 --- a/src/common/defaultSetting.ts +++ b/src/common/defaultSetting.ts @@ -24,7 +24,8 @@ const defaultSetting: LX.AppSetting = { 'player.startupAutoPlay': false, 'player.togglePlayMethod': 'listLoop', - 'player.highQuality': false, + 'player.firstPlayQuality': '128k', + 'player.autoLowerQualityOnError': false, 'player.isShowTaskProgess': true, 'player.volume': 1, 'player.isMute': false, diff --git a/src/common/types/app_setting.d.ts b/src/common/types/app_setting.d.ts index 3af7f3c1..f4cd4f41 100644 --- a/src/common/types/app_setting.d.ts +++ b/src/common/types/app_setting.d.ts @@ -89,9 +89,14 @@ declare global { 'player.togglePlayMethod': 'listLoop' | 'random' | 'list' | 'singleLoop' | 'none' /** - * 是否优先播放320k音质 + * 优先播放音质 */ - 'player.highQuality': boolean + 'player.firstPlayQuality': LX.Quality + + /** + * 获取当前音质错误时自动降低音质 + */ + 'player.autoLowerQualityOnError': boolean /** * 是否显示任务栏进度条 diff --git a/src/lang/en-us.json b/src/lang/en-us.json index c1b9c95f..880d7afb 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -488,6 +488,9 @@ "setting__play_lyric_roma": "Show lyrics roman", "setting__play_lyric_s2t": "Convert the playing and downloading lyrics to Traditional Chinese", "setting__play_lyric_transition": "Show lyrics translation", + "setting__play_quality_auto_lower_quality_on_error": "Automatically lower the sound quality when getting the current sound quality error", + "setting__play_quality_first": "Play sound quality first", + "setting__play_quality_first_title": "Select priority playback sound quality", "setting__play_mediaDevice": "Audio output", "setting__play_mediaDevice_remove_stop_play": "Pause the song when the current sound output device is changed", "setting__play_mediaDevice_title": "Select a media device for audio output", diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index c898d6f0..c4540408 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -487,6 +487,9 @@ "setting__play_lyric_roma": "显示歌词罗马音(如果可用)", "setting__play_lyric_s2t": "将播放与下载的歌词转换为繁体中文", "setting__play_lyric_transition": "显示歌词翻译(如果可用)", + "setting__play_quality_auto_lower_quality_on_error": "获取当前音质错误时自动降低音质", + "setting__play_quality_first": "优先播放音质", + "setting__play_quality_first_title": "选择优先播放音质", "setting__play_mediaDevice": "音频输出", "setting__play_mediaDevice_remove_stop_play": "当前的声音输出设备被改变时暂停播放歌曲", "setting__play_mediaDevice_title": "选择声音输出的媒体设备", diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 353422b7..083731b8 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -488,6 +488,9 @@ "setting__play_lyric_roma": "顯示歌詞羅馬音(如果可用)", "setting__play_lyric_s2t": "將播放與下載的歌詞轉換為繁體中文", "setting__play_lyric_transition": "顯示歌詞翻譯(如果可用)", + "setting__play_quality_auto_lower_quality_on_error": "獲取當前音質錯誤時自動降低音質", + "setting__play_quality_first": "優先播放音質", + "setting__play_quality_first_title": "選擇優先播放音質", "setting__play_mediaDevice": "音頻輸出", "setting__play_mediaDevice_remove_stop_play": "當前的聲音輸出設備被改變時暫停播放歌曲", "setting__play_mediaDevice_title": "選擇聲音輸出的媒體設備", diff --git a/src/renderer/core/music/online.ts b/src/renderer/core/music/online.ts index 521ff1ac..7e287b27 100644 --- a/src/renderer/core/music/online.ts +++ b/src/renderer/core/music/online.ts @@ -51,11 +51,11 @@ export const getMusicUrl = async({ musicInfo, quality, isRefresh, allowToggleSou // // return Promise.reject(new Error('该歌曲没有可播放的音频')) // } - const targetQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo) + const targetQuality = quality ?? getPlayQuality(musicInfo) const cachedUrl = await getStoreMusicUrl(musicInfo, targetQuality) if (cachedUrl && !isRefresh) return cachedUrl - return await handleGetOnlineMusicUrl({ musicInfo, quality, onToggleSource, isRefresh, allowToggleSource }).then(({ url, quality: targetQuality, musicInfo: targetMusicInfo, isFromCache }) => { + return await handleGetOnlineMusicUrl({ musicInfo, quality: targetQuality, onToggleSource, isRefresh, allowToggleSource }).then(({ url, quality: targetQuality, musicInfo: targetMusicInfo, isFromCache }) => { if (targetMusicInfo.id != musicInfo.id && !isFromCache) void saveMusicUrl(targetMusicInfo, targetQuality, url) void saveMusicUrl(musicInfo, targetQuality, url) return url diff --git a/src/renderer/core/music/utils.ts b/src/renderer/core/music/utils.ts index c31ca89e..6763f573 100644 --- a/src/renderer/core/music/utils.ts +++ b/src/renderer/core/music/utils.ts @@ -10,6 +10,7 @@ import { import { appSetting } from '@renderer/store/setting' import { langS2T, toNewMusicInfo, toOldMusicInfo } from '@renderer/utils' import { requestMsg } from '@renderer/utils/message' +import { QUALITYS } from '@common/constants' const getOtherSourcePromises = new Map() @@ -138,16 +139,33 @@ export const getCachedLyricInfo = async(musicInfo: LX.Music.MusicInfo): Promise< return null } -export const getPlayQuality = (highQuality: boolean, musicInfo: LX.Music.MusicInfoOnline): LX.Quality => { - let type: LX.Quality = '128k' - let list = qualityList.value[musicInfo.source] - if (highQuality && musicInfo.meta._qualitys['320k'] && list && list.includes('320k')) type = '320k' - return type +const sliceQualityList = (startQuality: LX.Quality, skipNow?: boolean) => { + let startNum = QUALITYS.indexOf(startQuality) + if (skipNow) startNum = QUALITYS.indexOf(startQuality) + 1 + + const list = QUALITYS.slice(startNum) + return list } -export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggleSource, isRefresh, retryedSource = [] }: { +export const getPlayQuality = (musicInfo: LX.Music.MusicInfoOnline): LX.Quality => { + let list = qualityList.value[musicInfo.source] + let firstPlayQuality: LX.Quality = appSetting['player.firstPlayQuality'] + + if (!list) return firstPlayQuality + if (!list.includes(firstPlayQuality)) firstPlayQuality = list[list.length - 1] + + const rangeType = sliceQualityList(firstPlayQuality) + for (const quality of rangeType) { + if (musicInfo.meta._qualitys[quality]) return quality + } + + return '128k' +} + +export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggleSource, isRefresh, retryedSource = [], rawQuality = '128k' }: { musicInfos: LX.Music.MusicInfoOnline[] quality?: LX.Quality + rawQuality?: LX.Quality onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void isRefresh: boolean retryedSource?: LX.OnlineSource[] @@ -164,14 +182,23 @@ export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggl if (retryedSource.includes(musicInfo.source)) continue retryedSource.push(musicInfo.source) if (!assertApiSupport(musicInfo.source)) continue - itemQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo) + itemQuality = quality ?? getPlayQuality(musicInfo) if (!musicInfo.meta._qualitys[itemQuality]) continue console.log('try toggle to: ', musicInfo.source, musicInfo.name, musicInfo.singer, musicInfo.interval) onToggleSource(musicInfo) break } - if (!musicInfo || !itemQuality) throw new Error(window.i18n.t('toggle_source_failed')) + + if (!musicInfo || !itemQuality) { + const rangeType = sliceQualityList(rawQuality, true) + if (appSetting['player.autoLowerQualityOnError'] && rawQuality != '128k' && rangeType.length > 0) { + for (const type of rangeType) { + if (musicInfo.meta._qualitys[type]) return getOnlineOtherSourceMusicUrl({ musicInfos, quality: type, onToggleSource, isRefresh, retryedSource, rawQuality }) + } + } + throw new Error(window.i18n.t('toggle_source_failed')) + } const cachedUrl = await getStoreMusicUrl(musicInfo, itemQuality) if (cachedUrl && !isRefresh) return { url: cachedUrl, musicInfo, quality: itemQuality, isFromCache: true } @@ -190,16 +217,17 @@ export const getOnlineOtherSourceMusicUrl = async({ musicInfos, quality, onToggl }).catch((err: any) => { if (err.message == requestMsg.tooManyRequests) throw err console.log(err) - return getOnlineOtherSourceMusicUrl({ musicInfos, quality, onToggleSource, isRefresh, retryedSource }) + return getOnlineOtherSourceMusicUrl({ musicInfos, quality, onToggleSource, isRefresh, retryedSource, rawQuality }) }) } /** * 获取在线音乐URL */ -export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSource, isRefresh, allowToggleSource }: { +export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSource, isRefresh, allowToggleSource, rawQuality }: { musicInfo: LX.Music.MusicInfoOnline quality?: LX.Quality + rawQuality?: LX.Quality isRefresh: boolean allowToggleSource: boolean onToggleSource: (musicInfo?: LX.Music.MusicInfoOnline) => void @@ -209,8 +237,9 @@ export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSourc quality: LX.Quality isFromCache: boolean }> => { - // console.log(musicInfo.source) - const targetQuality = quality ?? getPlayQuality(appSetting['player.highQuality'], musicInfo) + const targetQuality = quality ?? getPlayQuality(musicInfo) + rawQuality = rawQuality ?? targetQuality + console.log(targetQuality, rawQuality) let reqPromise try { @@ -218,12 +247,29 @@ export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSourc } catch (err: any) { reqPromise = Promise.reject(err) } + return reqPromise.then(({ url, type }: { url: string, type: LX.Quality }) => { return { musicInfo, url, quality: type, isFromCache: false } }).catch(async(err: any) => { console.log(err) + + const rangeType = sliceQualityList(targetQuality, true) + if (targetQuality != '128k' && rangeType.length > 0 && appSetting['player.autoLowerQualityOnError']) { + for (const type of rangeType) { + if (musicInfo.meta._qualitys[type]) return handleGetOnlineMusicUrl({ + musicInfo, + quality: type, + onToggleSource, + isRefresh, + allowToggleSource, + rawQuality + }) + } + } + if (!allowToggleSource || err.message == requestMsg.tooManyRequests) throw err onToggleSource() + // eslint-disable-next-line @typescript-eslint/promise-function-async return await getOtherSource(musicInfo).then(otherSource => { console.log('find otherSource', otherSource) @@ -231,7 +277,8 @@ export const handleGetOnlineMusicUrl = async({ musicInfo, quality, onToggleSourc return getOnlineOtherSourceMusicUrl({ musicInfos: [...otherSource], onToggleSource, - quality, + quality: rawQuality ?? quality, + rawQuality: rawQuality ?? quality, isRefresh, retryedSource: [musicInfo.source], }) diff --git a/src/renderer/views/Setting/components/SettingPlay.vue b/src/renderer/views/Setting/components/SettingPlay.vue index f9e8bd46..7686fbee 100644 --- a/src/renderer/views/Setting/components/SettingPlay.vue +++ b/src/renderer/views/Setting/components/SettingPlay.vue @@ -15,12 +15,18 @@ dd base-checkbox(id="setting_player_lyric_s2t" :model-value="appSetting['player.isS2t']" :label="$t('setting__play_lyric_s2t')" @update:model-value="updateSetting({'player.isS2t': $event})") .gap-top base-checkbox(id="setting_player_lyric_play_lxlrc" :model-value="appSetting['player.isPlayLxlrc']" :label="$t('setting__play_lyric_lxlrc')" @update:model-value="updateSetting({'player.isPlayLxlrc': $event})") - .gap-top - base-checkbox(id="setting_player_highQuality" :model-value="appSetting['player.highQuality']" :label="$t('setting__play_quality')" @update:model-value="updateSetting({'player.highQuality': $event})") .gap-top base-checkbox(id="setting_player_showTaskProgess" :model-value="appSetting['player.isShowTaskProgess']" :label="$t('setting__play_task_bar')" @update:model-value="updateSetting({'player.isShowTaskProgess': $event})") .gap-top base-checkbox(id="setting_player_isMediaDeviceRemovedStopPlay" :model-value="appSetting['player.isMediaDeviceRemovedStopPlay']" :label="$t('setting__play_mediaDevice_remove_stop_play')" @update:model-value="updateSetting({'player.isMediaDeviceRemovedStopPlay': $event})") + .gap-top + base-checkbox(id="setting__play_quality_auto_lower_quality_on_error" :model-value="appSetting['player.autoLowerQualityOnError']" :label="$t('setting__play_quality_auto_lower_quality_on_error')" @update:model-value="updateSetting({'player.autoLowerQualityOnError': $event})") + +dd(:aria-label="$t('setting__play_quality_first_title')") + h3#play_firstPlayQuality {{ $t('setting__play_quality_first') }} + div + base-selection.gap-left(:list="playQualityList" item-key="qualityId" item-name="label" :model-value="appSetting['player.firstPlayQuality']" @update:model-value="updateSetting({'player.firstPlayQuality': $event})") + dd(:aria-label="$t('setting__play_mediaDevice_title')") h3#play_mediaDevice {{ $t('setting__play_mediaDevice') }} div @@ -33,7 +39,7 @@ import { hasInitedAdvancedAudioFeatures } from '@renderer/plugins/player' import { dialog } from '@renderer/plugins/Dialog' import { useI18n } from '@renderer/plugins/i18n' import { appSetting, updateSetting } from '@renderer/store/setting' - +import { QUALITYS_SUPPORT } from '@common/constants' export default { name: 'SettingPlay', @@ -83,12 +89,35 @@ export default { }) + const getQualityTypeName = (quality) => { + switch (quality) { + case 'flac24bit': + return t('download__lossless') + ' FLAC Hires' + case 'flac': + case 'ape': + case 'wav': + return t('download__lossless') + ' ' + quality.toUpperCase() + case '320k': + return t('download__high_quality') + ' ' + quality.toUpperCase() + case '192k': + case '128k': + return t('download__normal') + ' ' + quality.toUpperCase() + } + } + + const playQualityList = ref([]) + QUALITYS_SUPPORT.forEach(item => { + playQualityList.value.push({ qualityId: item, label: getQualityTypeName(item) }) + }) + + return { appSetting, updateSetting, mediaDevices, mediaDeviceId, handleMediaDeviceIdChnage, + playQualityList, } }, }