From 80e39555ca3b6ed115af4c7810082edabd85318f Mon Sep 17 00:00:00 2001 From: lyswhut Date: Wed, 8 Jan 2025 10:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=20LX=20Music=20=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E4=B8=BA=E2=80=9C=E9=9F=B3=E4=B9=90=E5=BA=94=E7=94=A8=E2=80=9D?= =?UTF-8?q?=E5=88=86=E7=B1=BB=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=9C=A8=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E5=A4=96=E4=BD=BF=E7=94=A8LX=20Music=E6=89=93?= =?UTF-8?q?=E5=BC=80=E6=AD=8C=E6=9B=B2=E6=96=87=E4=BB=B6=E3=80=81`js`?= =?UTF-8?q?=E3=80=81`json`=E3=80=81`lxmc`=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 54 +++++++++++++++- publish/changeLog.md | 2 + src/core/init/deeplink/fileAction.ts | 36 +++++++++++ src/core/init/deeplink/index.ts | 61 ++++++++++++++++--- src/lang/en-us.json | 11 ++-- src/lang/zh-cn.json | 10 +-- src/lang/zh-tw.json | 2 + .../Home/Views/Mylist/MyList/listAction.ts | 4 +- src/store/player/action.ts | 2 +- src/types/player.d.ts | 2 +- src/utils/fs.ts | 2 + src/utils/localMediaMetadata.ts | 8 ++- src/utils/music.ts | 4 +- 13 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 src/core/init/deeplink/fileAction.ts diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d9ddae6..f203997 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -31,12 +31,64 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/publish/changeLog.md b/publish/changeLog.md index a99a3a7..9d59bf1 100644 --- a/publish/changeLog.md +++ b/publish/changeLog.md @@ -6,6 +6,8 @@ - 新增蓝牙歌词支持,可以通过「设置 → 播放设置 → 显示蓝牙歌词」启用(#615) - 新增繁体中文语言(#659, @3gf8jv4dv) +- 将 LX Music 设置为“音乐应用”分类,允许将 LX Music 设置为系统默认音乐播放器 +- 添加在程序外使用LX Music打开歌曲文件、`js`、`json`、`lxmc`文件的支持 ### 优化 diff --git a/src/core/init/deeplink/fileAction.ts b/src/core/init/deeplink/fileAction.ts new file mode 100644 index 0000000..c8db09d --- /dev/null +++ b/src/core/init/deeplink/fileAction.ts @@ -0,0 +1,36 @@ +import { readMetadata } from '@/utils/localMediaMetadata' +import { handleImportList } from '@/screens/Home/Views/Setting/settings/Backup/actions' +import { handleImportLocalFile } from '@/screens/Home/Views/Setting/settings/Basic/UserApiEditModal/action' +import { type FileType } from '@/utils/fs' +import { confirmDialog } from '@/utils/tools' +import playerState from '@/store/player/state' +import { addTempPlayList } from '@/core/player/tempPlayList' +import { LIST_IDS } from '@/config/constant' +import { playNext } from '@/core/player/player' +import { buildLocalMusicInfo, buildLocalMusicInfoByFilePath } from '@/screens/Home/Views/Mylist/MyList/listAction' + + +export const handleFileLXMCAction = async(file: FileType) => { + if (!(await confirmDialog({ + message: global.i18n.t('deep_link_file_lxmc_confirm_tip', { name: file.name }), + }))) return + + handleImportList(file.path) +} + +export const handleFileMusicAction = async(file: FileType) => { + const info = await readMetadata(file.path) + const isPlaying = !!playerState.playMusicInfo.musicInfo + const musicInfo = info ? buildLocalMusicInfo(file.path, info) : buildLocalMusicInfoByFilePath(file) + console.log(musicInfo) + addTempPlayList([{ listId: LIST_IDS.PLAY_LATER, musicInfo, isTop: true }]) + if (isPlaying) void playNext() +} + +export const handleFileJSAction = async(file: FileType) => { + if (!(await confirmDialog({ + message: global.i18n.t('deep_link_file_js_confirm_tip', { name: file.name }), + }))) return + + handleImportLocalFile(file.path) +} diff --git a/src/core/init/deeplink/index.ts b/src/core/init/deeplink/index.ts index cb4ebc7..e7ce1e0 100644 --- a/src/core/init/deeplink/index.ts +++ b/src/core/init/deeplink/index.ts @@ -3,6 +3,8 @@ import { errorDialog } from './utils' import { handleMusicAction } from './musicAction' import { handlePlayerAction, type PlayerAction } from './playerAction' import { handleSonglistAction } from './songlistAction' +import { extname, stat } from '@/utils/fs' +import { handleFileMusicAction, handleFileJSAction, handleFileLXMCAction } from './fileAction' const handleLinkAction = async(link: string) => { @@ -38,16 +40,61 @@ const handleLinkAction = async(link: string) => { // default: throw new Error('Unknown type: ' + type) } } -const runLinkAction = async(link: string) => { - if (!link.startsWith('lxmusic://')) return - try { - await handleLinkAction(link) - } catch (err: any) { - errorDialog(err.message) - // focusWindow() + +const handleFileAction = async(link: string) => { + const file = await stat(link) + // console.log(file) + switch (extname(file.name)) { + case 'json': + case 'lxmc': + await handleFileLXMCAction(file) + break + case 'js': + await handleFileJSAction(file) + break + case 'ogg': + case 'flac': + case 'wav': + case 'mp3': + await handleFileMusicAction(file) + break + default: + if (!file.mimeType?.startsWith('audio/')) throw new Error('Unknown file type') + await handleFileMusicAction(file) + break } } +// const handleHttpAction = async(link: string) => { +// } + + +const runLinkAction = async(link: string) => { + if (link.startsWith('lxmusic://')) { + try { + await handleLinkAction(link) + } catch (err: any) { + errorDialog(err.message) + // focusWindow() + } + } else if (link.startsWith('file://') || link.startsWith('content://')) { + try { + await handleFileAction(link) + } catch (err: any) { + errorDialog(err.message) + // focusWindow() + } + } + // else if (/^https?:\/\//.test(link)) { + // try { + // await handleHttpAction(link) + // } catch (err: any) { + // errorDialog(err.message) + // // focusWindow() + // } + // } +} + export const initDeeplink = async() => { Linking.addEventListener('url', ({ url }) => { void runLinkAction(url) diff --git a/src/lang/en-us.json b/src/lang/en-us.json index 9b0403d..c9386b6 100644 --- a/src/lang/en-us.json +++ b/src/lang/en-us.json @@ -37,6 +37,8 @@ "date_format_minute": "{num} minutes ago", "date_format_second": "{num} seconds ago", "deep_link__handle_error_tip": "Call failed: {message}", + "deep_link_file_js_confirm_tip": "Are you sure you want to import this ({name}) custom source file?", + "deep_link_file_lxmc_confirm_tip": "Are you sure you want to import this ({name}) list file?", "delete": "Remove", "dialog_cancel": "No", "dialog_confirm": "OK", @@ -49,8 +51,6 @@ "ignoring_battery_optimization_check_tip": "LX Music is \"Restricted\" or \"Optimized\" in \"App battery usage\", which may cause LX Music to be prevented by the system when playing music in the background. Do you need to set LX Music to \"Unrestricted\"?", "ignoring_battery_optimization_check_title": "Background Running Permission Reminder", "input_error": "Don't type indiscriminately 😡", - "list_name_default": "Default", - "list_name_love": "Loved", "list_add_btn_title": "Add the song(s) to \"{name}\"", "list_add_tip_exists": "This song already exists in the list, don't click me again~😡", "list_add_title_first_add": "Add", @@ -84,6 +84,9 @@ "list_multi_add_title_first_add": "Add the selected", "list_multi_add_title_first_move": "Move the selected", "list_multi_add_title_last": "songs to ...", + "list_name_default": "Default", + "list_name_love": "Loved", + "list_name_temp": "Temp List", "list_remove": "Remove", "list_remove_music_multi_tip": "Do you really want to remove the selected {num} songs?", "list_remove_tip": "Do you really want to remove \"{name}\"?", @@ -96,7 +99,6 @@ "list_select_local_file_desc": "Choose local song folder", "list_select_local_file_empty_tip": "No songs found in current folder", "list_select_local_file_result_failed_tip": "Found {total} song(s), successfully added {success} song(s), failed to add {failed} song(s). View the error log for details.", - "list_select_local_file_result_tip": "Found {Total} song(s), all added!", "list_select_local_file_temp_add_tip": "Found {total} matching files, quickly added to the current list, will now start the file metadata reading process. Please do not exit the app!", "list_select_range": "Range", @@ -115,7 +117,6 @@ "list_sort_modal_by_up": "Ascending", "list_sync": "Update", "list_sync_confirm_tip": "This will replace the songs in \"{name}\" with the songs in the online list, are you sure you want to update?", - "list_name_temp": "Temp List", "list_update_error": "Failed to update \"{name}\"", "list_update_success": "Successfully updated \"{name}\"", "list_updating": "Updating", @@ -303,11 +304,11 @@ "setting_list_click_action": "Automatically switch to current list when clicking a song in the list (Only valid for \"Playlists\" and \"Charts\" page)", "setting_list_show interval": "Show song length", "setting_list_show_album_name": "Show song album name", - "setting_lyric_desktop_permission_tip": "To use this feature, you need to grant LX Music the permission to display hover windows in the system permission settings.\n\nDo you go to the relevant page to grant this permission?", "setting_lyric_desktop": "Desktop Lyric", "setting_lyric_desktop_enable": "Show lyric window", "setting_lyric_desktop_lock": "Lock lyric window", "setting_lyric_desktop_maxlineNum": "Maximum Number of Lines", + "setting_lyric_desktop_permission_tip": "To use this feature, you need to grant LX Music the permission to display hover windows in the system permission settings.\n\nDo you go to the relevant page to grant this permission?", "setting_lyric_desktop_single_line": "Do not wrap lyrics", "setting_lyric_desktop_text_opacity": "Lyric Font Transparency", "setting_lyric_desktop_text_size": "Lyric Font Size", diff --git a/src/lang/zh-cn.json b/src/lang/zh-cn.json index 225dd6d..53e1354 100644 --- a/src/lang/zh-cn.json +++ b/src/lang/zh-cn.json @@ -37,6 +37,8 @@ "date_format_minute": "{num} 分钟前", "date_format_second": "{num} 秒前", "deep_link__handle_error_tip": "调用失败:{message}", + "deep_link_file_js_confirm_tip": "确认要导入这个({name})自定义源文件吗?", + "deep_link_file_lxmc_confirm_tip": "确认要导入这个({name})列表文件吗?", "delete": "移除", "dialog_cancel": "我不", "dialog_confirm": "好的", @@ -49,8 +51,6 @@ "ignoring_battery_optimization_check_tip": "LX Music 没有在「忽略电池优化」的白名单中,这可能会导致在后台播放音乐时被系统暂停。是否将 LX Music 加入该白名单中?", "ignoring_battery_optimization_check_title": "后台运行权限设置提醒", "input_error": "不要乱输好吧😡", - "list_name_default": "试听列表", - "list_name_love": "我的收藏", "list_add_btn_title": "把该歌曲添加到「{name}」", "list_add_tip_exists": "列表已经存在这首歌啦,不要再点我啦~😡", "list_add_title_first_add": "添加", @@ -84,6 +84,9 @@ "list_multi_add_title_first_add": "添加已选的", "list_multi_add_title_first_move": "移动已选的", "list_multi_add_title_last": "首歌曲到...", + "list_name_default": "试听列表", + "list_name_love": "我的收藏", + "list_name_temp": "临时列表", "list_remove": "移除", "list_remove_music_multi_tip": "你真的想要移除所选的 {num} 首歌曲吗?", "list_remove_tip": "你真的想要移除「{name}」吗?", @@ -114,7 +117,6 @@ "list_sort_modal_by_up": "升序", "list_sync": "更新", "list_sync_confirm_tip": "这将会把「{name}」内的歌曲替换成在线列表的歌曲,你确认要更新吗?", - "list_name_temp": "临时列表", "list_update_error": "「{name}」更新失败", "list_update_success": "「{name}」更新成功", "list_updating": "更新中", @@ -302,11 +304,11 @@ "setting_list_click_action": "点击列表里的歌曲时自动切换到当前列表播放(仅对歌单、排行榜有效)", "setting_list_show interval": "显示歌曲时长", "setting_list_show_album_name": "显示歌曲专辑名", - "setting_lyric_desktop_permission_tip": "桌面歌词功能需要在系统权限设置中授予 LX Music 显示悬浮窗口的权限才能使用,是否去相关界面授予该权限?", "setting_lyric_desktop": "桌面歌词", "setting_lyric_desktop_enable": "显示歌词", "setting_lyric_desktop_lock": "锁定歌词", "setting_lyric_desktop_maxlineNum": "最大行数", + "setting_lyric_desktop_permission_tip": "桌面歌词功能需要在系统权限设置中授予 LX Music 显示悬浮窗口的权限才能使用,是否去相关界面授予该权限?", "setting_lyric_desktop_single_line": "使用单行歌词", "setting_lyric_desktop_text_opacity": "歌词字体透明度", "setting_lyric_desktop_text_size": "歌词字体大小", diff --git a/src/lang/zh-tw.json b/src/lang/zh-tw.json index 1aad54e..e986b24 100644 --- a/src/lang/zh-tw.json +++ b/src/lang/zh-tw.json @@ -37,6 +37,8 @@ "date_format_minute": "{num} 分鐘前", "date_format_second": "{num} 秒前", "deep_link__handle_error_tip": "呼叫失敗:{message}", + "deep_link_file_js_confirm_tip": "確認要匯入這個({name})自訂原始檔嗎?", + "deep_link_file_lxmc_confirm_tip": "確認要匯入這個({name})清單檔案嗎?", "delete": "移除", "dialog_cancel": "我不", "dialog_confirm": "好的", diff --git a/src/screens/Home/Views/Mylist/MyList/listAction.ts b/src/screens/Home/Views/Mylist/MyList/listAction.ts index d2297f3..e66d0c8 100644 --- a/src/screens/Home/Views/Mylist/MyList/listAction.ts +++ b/src/screens/Home/Views/Mylist/MyList/listAction.ts @@ -92,7 +92,7 @@ export const handleSync = (listInfo: LX.List.UserListInfo) => { }) } -const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal => { +export const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal => { const index = file.name.lastIndexOf('.') return { id: file.path, @@ -109,7 +109,7 @@ const buildLocalMusicInfoByFilePath = (file: FileType): LX.Music.MusicInfoLocal }, } } -const buildLocalMusicInfo = (filePath: string, metadata: MusicMetadataFull): LX.Music.MusicInfoLocal => { +export const buildLocalMusicInfo = (filePath: string, metadata: MusicMetadataFull): LX.Music.MusicInfoLocal => { return { id: filePath, name: metadata.name, diff --git a/src/store/player/action.ts b/src/store/player/action.ts index 6788c9d..5054101 100644 --- a/src/store/player/action.ts +++ b/src/store/player/action.ts @@ -81,7 +81,7 @@ export default { global.state_event.playPlayedListChanged({ ...state.playedList }) }, addTempPlayList(list: LX.Player.TempPlayListItem[]) { - const topList: Array<{ listId: string, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = [] + const topList: Array<{ listId: string | null, musicInfo: LX.Music.MusicInfo | LX.Download.ListItem }> = [] const bottomList = list.filter(({ isTop, ...musicInfo }) => { if (isTop) { topList.push(musicInfo) diff --git a/src/types/player.d.ts b/src/types/player.d.ts index 74f2245..d09a4a6 100644 --- a/src/types/player.d.ts +++ b/src/types/player.d.ts @@ -57,7 +57,7 @@ declare global { /** * 播放列表id */ - listId: string + listId: string | null /** * 歌曲信息 */ diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 161a1ee..c776712 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -15,6 +15,8 @@ export type { // export const externalDirectoryPath = RNFS.ExternalDirectoryPath +export const extname = (name: string) => name.lastIndexOf('.') > 0 ? name.substring(name.lastIndexOf('.') + 1) : '' + export const temporaryDirectoryPath = Dirs.CacheDir export const externalStorageDirectoryPath = Dirs.SDCardDir export const privateStorageDirectoryPath = Dirs.DocumentDir diff --git a/src/utils/localMediaMetadata.ts b/src/utils/localMediaMetadata.ts index 98e5f69..5f67f31 100644 --- a/src/utils/localMediaMetadata.ts +++ b/src/utils/localMediaMetadata.ts @@ -1,4 +1,4 @@ -import { temporaryDirectoryPath, readDir, unlink } from '@/utils/fs' +import { temporaryDirectoryPath, readDir, unlink, extname } from '@/utils/fs' import { readPic as _readPic } from 'react-native-local-media-metadata' export { type MusicMetadata, @@ -15,7 +15,11 @@ const picCachePath = temporaryDirectoryPath + '/local-media-metadata' export const scanAudioFiles = async(dirPath: string) => { const files = await readDir(dirPath) - return files.filter(file => file.mimeType?.startsWith('audio/')).map(file => file) + return files.filter(file => { + if (file.mimeType?.startsWith('audio/')) return true + if (extname(file?.name ?? '') === 'ogg') return true + return false + }).map(file => file) } const clearPicCache = async() => { diff --git a/src/utils/music.ts b/src/utils/music.ts index 9f42f28..a8d0adb 100644 --- a/src/utils/music.ts +++ b/src/utils/music.ts @@ -2,5 +2,7 @@ import { existsFile } from './fs' export const getLocalFilePath = async(musicInfo: LX.Music.MusicInfoLocal): Promise => { - return (await existsFile(musicInfo.meta.filePath)) ? musicInfo.meta.filePath : '' + if (await existsFile(musicInfo.meta.filePath)) return musicInfo.meta.filePath + // 直接从应用外 intent 调用打开的文件,ogg等类型无法判断文件是否存在,但这类文件路径为纯数字 + return /\/\d+$/.test(musicInfo.meta.filePath) ? musicInfo.meta.filePath : '' }