新增 我的列表-换源播放 功能
|
@ -4,6 +4,7 @@
|
|||
- 新增设置-播放详情页设置-延迟歌词滚动设置(#1985)
|
||||
- 新增鼠标在音量按钮使用滚轮时可以调整音量大小的功能(#2000)
|
||||
- 新增设置-下载设置-同时下载任务数设置(#1498)
|
||||
- 新增 我的列表-换源播放 功能,换源后下次再播放该列表的该歌曲时将优先尝试播放所选源的歌曲,该功能允许你手动指定来源以解决自动换源失败或者换源不准确的问题
|
||||
|
||||
### 优化
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ declare namespace LX {
|
|||
songId: string | number // 歌曲ID,mg源为copyrightId,local为文件路径
|
||||
albumName: string // 歌曲专辑名称
|
||||
picUrl?: string | null // 歌曲图片链接
|
||||
toggleMusicInfo?: MusicInfoOnline | null
|
||||
}
|
||||
|
||||
interface MusicInfoMeta_online extends MusicInfoMetaBase {
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
"list__source_detail": "Song Page",
|
||||
"list__start": "Start Task",
|
||||
"list__sync": "Update",
|
||||
"list__toggle_source": "Change source",
|
||||
"list_add__btn_title": "Add the song(s) to {name}",
|
||||
"list_add__multiple_btn_title": "Add these song(s) to {name}",
|
||||
"list_add__multiple_title_add": "Add the selected {num} song(s) to ...",
|
||||
|
@ -193,6 +194,7 @@
|
|||
"music_sort__title": "Adjust the position of {name} to: ",
|
||||
"music_sort__title_multiple": "Adjust the position of the selected {num} songs to: ",
|
||||
"music_time": "Length",
|
||||
"music_toggle_clean": "Cancel source change",
|
||||
"my_list": "Your Library",
|
||||
"no_item": "Nothing's here...",
|
||||
"not_agree": "Not accept",
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
"list__source_detail": "歌曲详情页",
|
||||
"list__start": "开始任务",
|
||||
"list__sync": "更新",
|
||||
"list__toggle_source": "手动换源",
|
||||
"list_add__btn_title": "把该歌曲添加到 {name}",
|
||||
"list_add__multiple_btn_title": "把这些歌曲添加到 {name}",
|
||||
"list_add__multiple_title_add": "添加已选的 {num} 首歌曲到...",
|
||||
|
@ -193,6 +194,7 @@
|
|||
"music_sort__title": "将 {name} 的位置调整到:",
|
||||
"music_sort__title_multiple": "将已选的 {num} 首歌曲的位置调整到:",
|
||||
"music_time": "时长",
|
||||
"music_toggle_clean": "取消换源",
|
||||
"my_list": "我的列表",
|
||||
"no_item": "列表竟然是空的...",
|
||||
"not_agree": "不接受",
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
"list__source_detail": "歌曲詳情頁",
|
||||
"list__start": "開始任務",
|
||||
"list__sync": "更新",
|
||||
"list__toggle_source": "手動換源",
|
||||
"list_add__btn_title": "把該歌曲加到 {name}",
|
||||
"list_add__multiple_btn_title": "把這些歌曲加到 {name}",
|
||||
"list_add__multiple_title_add": "新增已選取的 {num} 首歌曲到...",
|
||||
|
@ -193,6 +194,7 @@
|
|||
"music_sort__title": "將 {name} 的位置調整到:",
|
||||
"music_sort__title_multiple": "將已選取的 {num} 首歌曲的位置調整至:",
|
||||
"music_time": "時長",
|
||||
"music_toggle_clean": "取消換源",
|
||||
"my_list": "我的列表",
|
||||
"no_item": "列表竟然是空的...",
|
||||
"not_agree": "不接受",
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 417 B |
|
@ -1,5 +1 @@
|
|||
<?xml version="1.0" ?><svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M3 5C3 3.89543 3.89543 3 5 3H15C16.1046 3 17 3.89543 17 5V15C17 16.1046 16.1046 17 15 17H5C3.89543 17 3 16.1046 3 15V5ZM5 4C4.44772 4 4 4.44772 4 5V15C4 15.5523 4.44772 16 5 16H15C15.5523 16 16 15.5523 16 15V5C16 4.44772 15.5523 4 15 4H5Z"
|
||||
fill="currentColor" />
|
||||
</svg>
|
||||
<svg fill="none" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M3 5a2 2 0 012-2h10a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5zm2-1a1 1 0 00-1 1v10a1 1 0 001 1h10a1 1 0 001-1V5a1 1 0 00-1-1H5z" fill="currentColor"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 407 B After Width: | Height: | Size: 244 B |
|
@ -1,3 +1 @@
|
|||
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M14,14H19V16H16V19H14V14M5,14H10V19H8V16H5V14M8,5H10V10H5V8H8V5M19,8V10H14V5H16V8H19Z" />
|
||||
</svg>
|
||||
<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M14 14h5v2h-3v3h-2v-5m-9 0h5v5H8v-3H5v-2m3-9h2v5H5V8h3V5m11 3v2h-5V5h2v3h3z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 187 B After Width: | Height: | Size: 170 B |
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,1C7,1 3,5 3,10V17A3,3 0 0,0 6,20H9V12H5V10A7,7 0 0,1 12,3A7,7 0 0,1 19,10V12H15V20H18A3,3 0 0,0 21,17V10C21,5 16.97,1 12,1Z" /></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M12 1c-5 0-9 4-9 9v7a3 3 0 003 3h3v-8H5v-2a7 7 0 017-7 7 7 0 017 7v2h-4v8h3a3 3 0 003-3v-7c0-5-4.03-9-9-9z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 422 B After Width: | Height: | Size: 188 B |
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z" /></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M11 18h2v-2h-2v2m1-16A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-14a4 4 0 00-4 4h2a2 2 0 012-2 2 2 0 012 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5a4 4 0 00-4-4z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 314 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11 7V9H13V7H11M14 17V15H13V11H10V13H11V15H10V17H14M22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12M20 12C20 7.58 16.42 4 12 4C7.58 4 4 7.58 4 12C4 16.42 7.58 20 12 20C16.42 20 20 16.42 20 12Z" /></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11 7v2h2V7h-2m3 10v-2h-1v-4h-3v2h1v2h-1v2h4m8-5c0 5.5-4.5 10-10 10S2 17.5 2 12 6.5 2 12 2s10 4.5 10 10m-2 0c0-4.42-3.58-8-8-8s-8 3.58-8 8 3.58 8 8 8 8-3.58 8-8z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 240 B |
|
@ -1 +1 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M21,3V15.5A3.5,3.5 0 0,1 17.5,19A3.5,3.5 0 0,1 14,15.5A3.5,3.5 0 0,1 17.5,12C18.04,12 18.55,12.12 19,12.34V6.47L9,8.6V17.5A3.5,3.5 0 0,1 5.5,21A3.5,3.5 0 0,1 2,17.5A3.5,3.5 0 0,1 5.5,14C6.04,14 6.55,14.12 7,14.34V6L21,3Z" /></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M21 3v12.5a3.5 3.5 0 01-3.5 3.5 3.5 3.5 0 01-3.5-3.5 3.5 3.5 0 013.5-3.5c.54 0 1.05.12 1.5.34V6.47L9 8.6v8.9A3.5 3.5 0 015.5 21 3.5 3.5 0 012 17.5 3.5 3.5 0 015.5 14c.54 0 1.05.12 1.5.34V6l14-3z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 515 B After Width: | Height: | Size: 276 B |
|
@ -1,4 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" width="208" height="208">
|
||||
<path d="M8 0C6.34375 0 5 1.34375 5 3L5 23C5 24.65625 6.34375 26 8 26L19 26C20.65625 26 22 24.65625 22 23L22 3C22 1.34375 20.65625 0 19 0 Z M 11.5 1L15.5 1C15.777344 1 16 1.222656 16 1.5C16 1.777344 15.777344 2 15.5 2L11.5 2C11.222656 2 11 1.777344 11 1.5C11 1.222656 11.222656 1 11.5 1 Z M 7.5 3L19.5 3C19.777344 3 20 3.222656 20 3.5L20 20.5C20 20.777344 19.777344 21 19.5 21L7.5 21C7.222656 21 7 20.777344 7 20.5L7 3.5C7 3.222656 7.222656 3 7.5 3 Z M 11.65625 6.71875C11.617188 6.746094 11.59375 6.828125 11.625 6.875L12.15625 7.625C11.320313 8.011719 10.730469 8.761719 10.65625 9.625L16.34375 9.625C16.269531 8.761719 15.679688 8.015625 14.84375 7.625L15.375 6.875C15.40625 6.828125 15.414063 6.746094 15.375 6.71875C15.335938 6.691406 15.25 6.703125 15.21875 6.75L14.6875 7.5625C14.324219 7.421875 13.921875 7.34375 13.5 7.34375C13.078125 7.34375 12.671875 7.421875 12.3125 7.5625L11.78125 6.75C11.75 6.703125 11.695313 6.691406 11.65625 6.71875 Z M 12.3125 8.1875C12.484375 8.1875 12.625 8.324219 12.625 8.5C12.625 8.675781 12.484375 8.84375 12.3125 8.84375C12.140625 8.84375 11.96875 8.675781 11.96875 8.5C11.96875 8.328125 12.140625 8.1875 12.3125 8.1875 Z M 14.75 8.1875C14.925781 8.1875 15.0625 8.324219 15.0625 8.5C15.0625 8.675781 14.925781 8.84375 14.75 8.84375C14.578125 8.84375 14.4375 8.675781 14.4375 8.5C14.4375 8.328125 14.574219 8.1875 14.75 8.1875 Z M 9.625 10C9.273438 10 9 10.296875 9 10.65625L9 13.1875C9 13.546875 9.273438 13.84375 9.625 13.84375C9.980469 13.84375 10.28125 13.546875 10.28125 13.1875L10.28125 10.65625C10.28125 10.296875 9.980469 10 9.625 10 Z M 17.34375 10C16.988281 10 16.71875 10.296875 16.71875 10.65625L16.71875 13.1875C16.71875 13.546875 16.988281 13.84375 17.34375 13.84375C17.699219 13.84375 18 13.546875 18 13.1875L18 10.65625C18 10.296875 17.699219 10 17.34375 10 Z M 10.65625 10.03125L10.65625 14.65625C10.65625 14.933594 10.882813 15.15625 11.15625 15.15625L11.71875 15.15625L11.71875 16.59375C11.71875 16.953125 12.019531 17.21875 12.375 17.21875C12.730469 17.21875 13 16.953125 13 16.59375L13 15.15625L14 15.15625L14 16.59375C14 16.953125 14.304688 17.21875 14.65625 17.21875C15.011719 17.21875 15.28125 16.953125 15.28125 16.59375L15.28125 15.15625L15.875 15.15625C16.152344 15.15625 16.375 14.933594 16.375 14.65625L16.375 10.03125 Z M 8.5 23L10.5 23C10.777344 23 11 23.222656 11 23.5C11 23.777344 10.777344 24 10.5 24L8.5 24C8.222656 24 8 23.777344 8 23.5C8 23.222656 8.222656 23 8.5 23 Z M 12.5 23L14.5 23C14.777344 23 15 23.222656 15 23.5L15 24.5C15 24.777344 14.777344 25 14.5 25L12.5 25C12.222656 25 12 24.777344 12 24.5L12 23.5C12 23.222656 12.222656 23 12.5 23 Z M 16.5 23L18.5 23C18.777344 23 19 23.222656 19 23.5C19 23.777344 18.777344 24 18.5 24L16.5 24C16.222656 24 16 23.777344 16 23.5C16 23.222656 16.222656 23 16.5 23Z" fill="#5B5B5B" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" width="208" height="208"><path d="M8 0a3 3 0 00-3 3v20a3 3 0 003 3h11a3 3 0 003-3V3a3 3 0 00-3-3zm3.5 1h4c.277 0 .5.223.5.5s-.223.5-.5.5h-4a.498.498 0 01-.5-.5c0-.277.223-.5.5-.5zm-4 2h12c.277 0 .5.223.5.5v17c0 .277-.223.5-.5.5h-12a.498.498 0 01-.5-.5v-17c0-.277.223-.5.5-.5zm4.156 3.719c-.039.027-.062.11-.031.156l.531.75c-.836.387-1.426 1.137-1.5 2h5.688c-.074-.863-.664-1.61-1.5-2l.531-.75c.031-.047.04-.129 0-.156-.04-.028-.125-.016-.156.031l-.531.813a3.29 3.29 0 00-1.188-.22c-.422 0-.828.079-1.188.22l-.53-.813c-.032-.047-.087-.059-.126-.031zm.656 1.468a.31.31 0 01.313.313c0 .176-.14.344-.313.344a.367.367 0 01-.343-.344c0-.172.172-.313.344-.313zm2.438 0c.176 0 .313.137.313.313s-.137.344-.313.344c-.172 0-.313-.168-.313-.344a.31.31 0 01.313-.313zM9.625 10c-.352 0-.625.297-.625.656v2.531c0 .36.273.657.625.657a.663.663 0 00.656-.656v-2.532c0-.36-.3-.656-.656-.656zm7.719 0c-.356 0-.625.297-.625.656v2.531c0 .36.27.657.625.657a.663.663 0 00.656-.656v-2.532c0-.36-.3-.656-.656-.656zm-6.688.031v4.625c0 .278.227.5.5.5h.563v1.438c0 .36.3.625.656.625a.61.61 0 00.625-.625v-1.438h1v1.438c0 .36.305.625.656.625a.61.61 0 00.625-.625v-1.438h.594c.277 0 .5-.222.5-.5v-4.625zM8.5 23h2c.277 0 .5.223.5.5s-.223.5-.5.5h-2a.498.498 0 01-.5-.5c0-.277.223-.5.5-.5zm4 0h2c.277 0 .5.223.5.5v1c0 .277-.223.5-.5.5h-2a.498.498 0 01-.5-.5v-1c0-.277.223-.5.5-.5zm4 0h2c.277 0 .5.223.5.5s-.223.5-.5.5h-2a.498.498 0 01-.5-.5c0-.277.223-.5.5-.5z" fill="#5B5B5B"/></svg>
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Play</title><path d="M112 111v290c0 17.44 17 28.52 31 20.16l247.9-148.37c12.12-7.25 12.12-26.33 0-33.58L143 90.84c-14-8.36-31 2.72-31 20.16z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path d="M112 111v290c0 17.44 17 28.52 31 20.16l247.9-148.37c12.12-7.25 12.12-26.33 0-33.58L143 90.84c-14-8.36-31 2.72-31 20.16z" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="32"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 299 B |
|
@ -1,4 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="144" height="144">
|
||||
<path fill="currentColor" d="M11.5 6C8.467 6 6 8.467 6 11.5L6 36.5C6 39.533 8.467 42 11.5 42L36.5 42C39.533 42 42 39.533 42 36.5L42 11.5C42 8.467 39.533 6 36.5 6L11.5 6 z M 11.5 9L36.5 9C37.878 9 39 10.122 39 11.5L39 36.5C39 37.878 37.878 39 36.5 39L11.5 39C10.122 39 9 37.878 9 36.5L9 11.5C9 10.122 10.122 9 11.5 9 z M 18.167969 13.074219L25.197266 24L18.167969 34.925781L24.644531 34.925781L31.753906 24L24.644531 13.074219L18.167969 13.074219 z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="144" height="144"><path fill="currentColor" d="M11.5 6A5.506 5.506 0 006 11.5v25c0 3.033 2.467 5.5 5.5 5.5h25c3.033 0 5.5-2.467 5.5-5.5v-25C42 8.467 39.533 6 36.5 6h-25zm0 3h25c1.378 0 2.5 1.122 2.5 2.5v25c0 1.378-1.122 2.5-2.5 2.5h-25A2.503 2.503 0 019 36.5v-25C9 10.122 10.122 9 11.5 9zm6.668 4.074L25.198 24l-7.03 10.926h6.477L31.754 24l-7.11-10.926h-6.476z"/></svg>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="96" height="96"><path d="M16.707 2.293l-1.414 1.414L17.586 6H17C10.937 6 6 10.937 6 17v1h2v-1c0-4.983 4.017-9 9-9h.586l-2.293 2.293 1.414 1.414L21.414 7l-4.707-4.707zM2 8v11c0 1.645 1.355 3 3 3h14c1.645 0 3-1.355 3-3v-2h-2v2c0 .565-.435 1-1 1H5c-.565 0-1-.435-1-1V8H2z" fill="currentColor" /></svg>
|
|
@ -1,5 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 13C6.14 13 4.59 14.28 4.14 16H2V18H4.14C4.59 19.72 6.14 21 8 21S11.41 19.72 11.86 18H22V16H11.86C11.41 14.28 9.86 13 8 13M8 19C6.9 19 6 18.1 6 17C6 15.9 6.9 15 8 15S10 15.9 10 17C10 18.1 9.1 19 8 19M19.86 6C19.41 4.28 17.86 3 16 3S12.59 4.28 12.14 6H2V8H12.14C12.59 9.72 14.14 11 16 11S19.41 9.72 19.86 8H22V6H19.86M16 9C14.9 9 14 8.1 14 7C14 5.9 14.9 5 16 5S18 5.9 18 7C18 8.1 17.1 9 16 9Z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M8 13c-1.86 0-3.41 1.28-3.86 3H2v2h2.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22v-2H11.86c-.45-1.72-2-3-3.86-3m0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2M19.86 6c-.45-1.72-2-3-3.86-3s-3.41 1.28-3.86 3H2v2h10.14c.45 1.72 2 3 3.86 3s3.41-1.28 3.86-3H22V6h-2.14M16 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 505 B After Width: | Height: | Size: 409 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Volume High</title><path d="M126 192H56a8 8 0 00-8 8v112a8 8 0 008 8h69.65a15.93 15.93 0 0110.14 3.54l91.47 74.89A8 8 0 00240 392V120a8 8 0 00-12.74-6.43l-91.47 74.89A15 15 0 01126 192zM320 320c9.74-19.38 16-40.84 16-64 0-23.48-6-44.42-16-64M368 368c19.48-33.92 32-64.06 32-112s-12-77.74-32-112M416 416c30-46 48-91.43 48-160s-18-113-48-160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path d="M126 192H56a8 8 0 00-8 8v112a8 8 0 008 8h69.65a15.93 15.93 0 0110.14 3.54l91.47 74.89A8 8 0 00240 392V120a8 8 0 00-12.74-6.43l-91.47 74.89A15 15 0 01126 192zm194 128c9.74-19.38 16-40.84 16-64 0-23.48-6-44.42-16-64m48 176c19.48-33.92 32-64.06 32-112s-12-77.74-32-112m48 272c30-46 48-91.43 48-160s-18-113-48-160" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 513 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Volume Low</title><path d="M189.65 192H120a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61zM384 320c9.74-19.41 16-40.81 16-64 0-23.51-6-44.4-16-64" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path d="M189.65 192H120a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61zM384 320c9.74-19.41 16-40.81 16-64 0-23.51-6-44.4-16-64" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 434 B After Width: | Height: | Size: 418 B |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Volume Medium</title><path d="M157.65 192H88a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61zM352 320c9.74-19.41 16-40.81 16-64 0-23.51-6-44.4-16-64M400 368c19.48-34 32-64 32-112s-12-77.7-32-112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path d="M157.65 192H88a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61zM352 320c9.74-19.41 16-40.81 16-64 0-23.51-6-44.4-16-64m48 176c19.48-34 32-64 32-112s-12-77.7-32-112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 462 B |
|
@ -1,9 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512">
|
||||
<title>Volume Mute</title>
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32"
|
||||
d="M416 432L64 80" />
|
||||
<path fill="currentColor"
|
||||
d="M224 136.92v33.8a4 4 0 001.17 2.82l24 24a4 4 0 006.83-2.82v-74.15a24.53 24.53 0 00-12.67-21.72 23.91 23.91 0 00-25.55 1.83 8.27 8.27 0 00-.66.51l-31.94 26.15a4 4 0 00-.29 5.92l17.05 17.06a4 4 0 005.37.26zM224 375.08l-78.07-63.92a32 32 0 00-20.28-7.16H64v-96h50.72a4 4 0 002.82-6.83l-24-24a4 4 0 00-2.82-1.17H56a24 24 0 00-24 24v112a24 24 0 0024 24h69.76l91.36 74.8a8.27 8.27 0 00.66.51 23.93 23.93 0 0025.85 1.69A24.49 24.49 0 00256 391.45v-50.17a4 4 0 00-1.17-2.82l-24-24a4 4 0 00-6.83 2.82zM125.82 336zM352 256c0-24.56-5.81-47.88-17.75-71.27a16 16 0 00-28.5 14.54C315.34 218.06 320 236.62 320 256q0 4-.31 8.13a8 8 0 002.32 6.25l19.66 19.67a4 4 0 006.75-2A146.89 146.89 0 00352 256zM416 256c0-51.19-13.08-83.89-34.18-120.06a16 16 0 00-27.64 16.12C373.07 184.44 384 211.83 384 256c0 23.83-3.29 42.88-9.37 60.65a8 8 0 001.9 8.26l16.77 16.76a4 4 0 006.52-1.27C410.09 315.88 416 289.91 416 256z" />
|
||||
<path fill="currentColor"
|
||||
d="M480 256c0-74.26-20.19-121.11-50.51-168.61a16 16 0 10-27 17.22C429.82 147.38 448 189.5 448 256c0 47.45-8.9 82.12-23.59 113a4 4 0 00.77 4.55L443 391.39a4 4 0 006.4-1C470.88 348.22 480 307 480 256z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M416 432L64 80"/><path fill="currentColor" d="M224 136.92v33.8a4 4 0 001.17 2.82l24 24a4 4 0 006.83-2.82v-74.15a24.53 24.53 0 00-12.67-21.72 23.91 23.91 0 00-25.55 1.83 8.27 8.27 0 00-.66.51l-31.94 26.15a4 4 0 00-.29 5.92l17.05 17.06a4 4 0 005.37.26zm0 238.16l-78.07-63.92a32 32 0 00-20.28-7.16H64v-96h50.72a4 4 0 002.82-6.83l-24-24a4 4 0 00-2.82-1.17H56a24 24 0 00-24 24v112a24 24 0 0024 24h69.76l91.36 74.8a8.27 8.27 0 00.66.51 23.93 23.93 0 0025.85 1.69A24.49 24.49 0 00256 391.45v-50.17a4 4 0 00-1.17-2.82l-24-24a4 4 0 00-6.83 2.82zM125.82 336zM352 256c0-24.56-5.81-47.88-17.75-71.27a16 16 0 00-28.5 14.54C315.34 218.06 320 236.62 320 256q0 4-.31 8.13a8 8 0 002.32 6.25l19.66 19.67a4 4 0 006.75-2A146.89 146.89 0 00352 256zm64 0c0-51.19-13.08-83.89-34.18-120.06a16 16 0 00-27.64 16.12C373.07 184.44 384 211.83 384 256c0 23.83-3.29 42.88-9.37 60.65a8 8 0 001.9 8.26l16.77 16.76a4 4 0 006.52-1.27C410.09 315.88 416 289.91 416 256z"/><path fill="currentColor" d="M480 256c0-74.26-20.19-121.11-50.51-168.61a16 16 0 10-27 17.22C429.82 147.38 448 189.5 448 256c0 47.45-8.9 82.12-23.59 113a4 4 0 00.77 4.55L443 391.39a4 4 0 006.4-1C470.88 348.22 480 307 480 256z"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><title>Volume Off</title><path d="M237.65 192H168a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="prefix__ionicon" viewBox="0 0 512 512"><path d="M237.65 192H168a8 8 0 00-8 8v112a8 8 0 008 8h69.65a16 16 0 0110.14 3.63l91.47 75a8 8 0 0012.74-6.46V119.83a8 8 0 00-12.74-6.44l-91.47 75a16 16 0 01-10.14 3.61z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 379 B After Width: | Height: | Size: 363 B |
|
@ -198,7 +198,7 @@ export default {
|
|||
// color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-button-font)-background;
|
||||
background-color: transparent;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
|
|
|
@ -8,17 +8,18 @@ import {
|
|||
} from './online'
|
||||
import { buildLyricInfo, getCachedLyricInfo } from './utils'
|
||||
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, allowToggleSource = true, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Download.ListItem
|
||||
isRefresh: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
allowToggleSource?: boolean
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
const path = await getDownloadFilePath(musicInfo, appSetting['download.savePath'])
|
||||
if (path) return path
|
||||
}
|
||||
|
||||
return getOnlineMusicUrl({ musicInfo: musicInfo.metadata.musicInfo, isRefresh, onToggleSource })
|
||||
return getOnlineMusicUrl({ musicInfo: musicInfo.metadata.musicInfo, isRefresh, onToggleSource, allowToggleSource })
|
||||
}
|
||||
|
||||
export const getPicUrl = async({ musicInfo, isRefresh, listId, onToggleSource = () => {} }: {
|
||||
|
|
|
@ -24,18 +24,20 @@ export const getMusicUrl = async({
|
|||
quality,
|
||||
isRefresh = false,
|
||||
onToggleSource,
|
||||
allowToggleSource,
|
||||
}: {
|
||||
musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
||||
isRefresh?: boolean
|
||||
quality?: LX.Quality
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
allowToggleSource?: boolean
|
||||
}): Promise<string> => {
|
||||
if ('progress' in musicInfo) {
|
||||
return getDownloadMusicUrl({ musicInfo, isRefresh, onToggleSource })
|
||||
return getDownloadMusicUrl({ musicInfo, isRefresh, onToggleSource, allowToggleSource })
|
||||
} else if (musicInfo.source == 'local') {
|
||||
return getLocalMusicUrl({ musicInfo, isRefresh, onToggleSource })
|
||||
return getLocalMusicUrl({ musicInfo, isRefresh, onToggleSource, allowToggleSource })
|
||||
} else {
|
||||
return getOnlineMusicUrl({ musicInfo, isRefresh, quality, onToggleSource })
|
||||
return getOnlineMusicUrl({ musicInfo, isRefresh, quality, onToggleSource, allowToggleSource })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,9 +66,10 @@ const getOtherSourceByLocal = async<T>(musicInfo: LX.Music.MusicInfoLocal, handl
|
|||
throw new Error('source not found')
|
||||
}
|
||||
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, onToggleSource = () => {} }: {
|
||||
export const getMusicUrl = async({ musicInfo, isRefresh, allowToggleSource = true, onToggleSource = () => {} }: {
|
||||
musicInfo: LX.Music.MusicInfoLocal
|
||||
isRefresh: boolean
|
||||
allowToggleSource?: boolean
|
||||
onToggleSource?: (musicInfo?: LX.Music.MusicInfoOnline) => void
|
||||
}): Promise<string> => {
|
||||
if (!isRefresh) {
|
||||
|
@ -83,6 +84,8 @@ export const getMusicUrl = async({ musicInfo, isRefresh, onToggleSource = () =>
|
|||
})
|
||||
} catch {}
|
||||
|
||||
if (!allowToggleSource) throw new Error('failed')
|
||||
|
||||
onToggleSource()
|
||||
return getOtherSourceByLocal(musicInfo, async(otherSource) => {
|
||||
return getOnlineOtherSourceMusicUrl({ musicInfos: [...otherSource], onToggleSource, isRefresh }).then(({ url, quality: targetQuality, musicInfo: targetMusicInfo, isFromCache }) => {
|
||||
|
|
|
@ -23,6 +23,10 @@ import { addDislikeInfo } from '@renderer/core/dislikeList'
|
|||
// import { checkMusicFileAvailable } from '@renderer/utils/music'
|
||||
|
||||
let gettingUrlId = ''
|
||||
const createGettingUrlId = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem) => {
|
||||
const tInfo = 'progress' in musicInfo ? musicInfo.metadata.musicInfo.meta.toggleMusicInfo : musicInfo.meta.toggleMusicInfo
|
||||
return `${musicInfo.id}_${tInfo?.id ?? ''}`
|
||||
}
|
||||
const createDelayNextTimeout = (delay: number) => {
|
||||
let timeout: NodeJS.Timeout | null
|
||||
const clearDelayNextTimeout = () => {
|
||||
|
@ -56,7 +60,7 @@ const { addDelayNextTimeout: addLoadTimeout, clearDelayNextTimeout: clearLoadTim
|
|||
*/
|
||||
const diffCurrentMusicInfo = (curMusicInfo: LX.Music.MusicInfo | LX.Download.ListItem): boolean => {
|
||||
// return curMusicInfo !== playMusicInfo.musicInfo || isPlay.value
|
||||
return gettingUrlId != curMusicInfo.id || curMusicInfo.id != playMusicInfo.musicInfo?.id || isPlay.value
|
||||
return gettingUrlId != createGettingUrlId(curMusicInfo) || curMusicInfo.id != playMusicInfo.musicInfo?.id || isPlay.value
|
||||
}
|
||||
|
||||
let cancelDelayRetry: (() => void) | null = null
|
||||
|
@ -87,14 +91,21 @@ const getMusicPlayUrl = async(musicInfo: LX.Music.MusicInfo | LX.Download.ListIt
|
|||
if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
|
||||
// const type = getPlayType(appSetting['player.highQuality'], musicInfo)
|
||||
let toggleMusicInfo = ('progress' in musicInfo ? musicInfo.metadata.musicInfo : musicInfo).meta.toggleMusicInfo
|
||||
|
||||
return getMusicUrl({
|
||||
musicInfo,
|
||||
return (toggleMusicInfo ? getMusicUrl({
|
||||
musicInfo: toggleMusicInfo,
|
||||
isRefresh,
|
||||
onToggleSource(mInfo) {
|
||||
if (diffCurrentMusicInfo(musicInfo)) return
|
||||
setAllStatus(window.i18n.t('toggle_source_try'))
|
||||
},
|
||||
allowToggleSource: false,
|
||||
}) : Promise.reject(new Error('not found'))).catch(async() => {
|
||||
return getMusicUrl({
|
||||
musicInfo,
|
||||
isRefresh,
|
||||
onToggleSource(mInfo) {
|
||||
if (diffCurrentMusicInfo(musicInfo)) return
|
||||
setAllStatus(window.i18n.t('toggle_source_try'))
|
||||
},
|
||||
})
|
||||
}).then(url => {
|
||||
if (window.lx.isPlayedStop || diffCurrentMusicInfo(musicInfo)) return null
|
||||
|
||||
|
@ -118,7 +129,7 @@ export const setMusicUrl = (musicInfo: LX.Music.MusicInfo | LX.Download.ListItem
|
|||
// if (appSetting['player.autoSkipOnError']) addLoadTimeout()
|
||||
if (!diffCurrentMusicInfo(musicInfo)) return
|
||||
if (cancelDelayRetry) cancelDelayRetry()
|
||||
gettingUrlId = musicInfo.id
|
||||
gettingUrlId = createGettingUrlId(musicInfo)
|
||||
void getMusicPlayUrl(musicInfo, isRefresh).then((url) => {
|
||||
if (!url) return
|
||||
setResource(url)
|
||||
|
@ -458,7 +469,7 @@ export const playPrev = async(isAutoToggle = false): Promise<void> => {
|
|||
export const play = () => {
|
||||
if (playMusicInfo.musicInfo == null) return
|
||||
if (isEmpty()) {
|
||||
if (playMusicInfo.musicInfo.id != gettingUrlId) setMusicUrl(playMusicInfo.musicInfo)
|
||||
if (createGettingUrlId(playMusicInfo.musicInfo) != gettingUrlId) setMusicUrl(playMusicInfo.musicInfo)
|
||||
return
|
||||
}
|
||||
setPlay()
|
||||
|
|
|
@ -210,23 +210,40 @@ const downloadLyric = (downloadInfo: LX.Download.ListItem) => {
|
|||
}
|
||||
|
||||
const getUrl = async(downloadInfo: LX.Download.ListItem, isRefresh: boolean = false) => {
|
||||
return getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: false,
|
||||
let toggleMusicInfo = downloadInfo.metadata.musicInfo.meta.toggleMusicInfo
|
||||
return (toggleMusicInfo ? getMusicUrl({
|
||||
musicInfo: toggleMusicInfo,
|
||||
isRefresh,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
allowToggleSource: false,
|
||||
}) : Promise.reject(new Error('not found'))).catch(() => {
|
||||
return getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: false,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
})
|
||||
}).catch(() => '')
|
||||
}
|
||||
const handleRefreshUrl = (downloadInfo: LX.Download.ListItem) => {
|
||||
setStatusText(downloadInfo, window.i18n.t('download_status_error_refresh_url'))
|
||||
getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
let toggleMusicInfo = downloadInfo.metadata.musicInfo.meta.toggleMusicInfo
|
||||
;(toggleMusicInfo ? getMusicUrl({
|
||||
musicInfo: toggleMusicInfo,
|
||||
isRefresh: true,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
allowToggleSource: false,
|
||||
}) : Promise.reject(new Error('not found'))).catch(() => {
|
||||
return getMusicUrl({
|
||||
musicInfo: downloadInfo.metadata.musicInfo,
|
||||
isRefresh: true,
|
||||
quality: downloadInfo.metadata.quality,
|
||||
allowToggleSource: appSetting['download.isUseOtherSource'],
|
||||
})
|
||||
})
|
||||
.catch(() => '')
|
||||
.then(url => {
|
||||
// commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||
// commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
|
||||
setUrl(downloadInfo, url)
|
||||
void window.lx.worker.download.updateUrl(downloadInfo.id, url)
|
||||
})
|
||||
|
|
|
@ -83,8 +83,12 @@ export const windowSizeActive = computed(() => {
|
|||
return windowSizeList.find(i => i.id === appSetting['common.windowSizeId']) ?? windowSizeList[0]
|
||||
})
|
||||
|
||||
export const getSourceI18nPrefix = () => {
|
||||
return appSetting['common.sourceNameType'] == 'real' ? 'source_' : 'source_alias_'
|
||||
}
|
||||
|
||||
export const sourceNames = computed(() => {
|
||||
const prefix = appSetting['common.sourceNameType'] == 'real' ? 'source_' : 'source_alias_'
|
||||
const prefix = getSourceI18nPrefix()
|
||||
const sourceNames: Record<LX.OnlineSource | 'all', string> = {
|
||||
kw: 'kw',
|
||||
tx: 'tx',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { checkPath, joinPath, extname, basename, readFile, getFileStats } from '@common/utils/nodejs'
|
||||
import { formatPlayTime } from '@common/utils/common'
|
||||
import type { IComment } from 'music-metadata/lib/type'
|
||||
|
||||
export const checkDownloadFileAvailable = async(musicInfo: LX.Download.ListItem, savePath: string): Promise<boolean> => {
|
||||
return musicInfo.isComplate && !/\.ape$/.test(musicInfo.metadata.fileName) &&
|
||||
|
@ -188,7 +189,10 @@ export const getLocalMusicFileLyric = async(path: string): Promise<string | null
|
|||
// 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
|
||||
if (ust) {
|
||||
const value = ust.value as IComment
|
||||
if (value.text && value.text.length > 10) return value.text
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
@ -59,8 +59,21 @@ export default {
|
|||
},
|
||||
supportQuality,
|
||||
|
||||
async findMusic({ name, singer, albumName, interval, source: s }) {
|
||||
async searchMusic({ name, singer, source: s, limit = 10 }) {
|
||||
const trimStr = str => typeof str == 'string' ? str.trim() : str
|
||||
const musicName = trimStr(name)
|
||||
const tasks = []
|
||||
const excludeSource = ['xm']
|
||||
for (const source of sources.sources) {
|
||||
if (!sources[source.id].musicSearch || source.id == s || excludeSource.includes(source.id)) continue
|
||||
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${singer || ''}`.trim(), 1, limit).catch(_ => null))
|
||||
}
|
||||
return (await Promise.all(tasks)).filter(s => s)
|
||||
},
|
||||
|
||||
async findMusic({ name, singer, albumName, interval, source: s }) {
|
||||
const lists = await this.searchMusic({ name, singer, source: s, limit: 25 })
|
||||
|
||||
const singersRxp = /、|&|;|;|\/|,|,|\|/
|
||||
const sortSingle = singer => singersRxp.test(singer)
|
||||
? singer.split(singersRxp).sort((a, b) => a.localeCompare(b)).join('、')
|
||||
|
@ -86,21 +99,18 @@ export default {
|
|||
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 == s || excludeSource.includes(source.id)) continue
|
||||
|
||||
tasks.push(sources[source.id].musicSearch.search(`${musicName} ${singer || ''}`.trim(), 1, 25).then(res => {
|
||||
for (const item of res.list) {
|
||||
item.name = trimStr(item.name)
|
||||
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
|
||||
) ||
|
||||
const result = lists.map(source => {
|
||||
for (const item of source.list) {
|
||||
item.name = trimStr(item.name)
|
||||
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
|
||||
) ||
|
||||
(
|
||||
(interval ? item.interval == interval : true) && item.lowerCaseName == lowerCaseName &&
|
||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||
|
@ -113,17 +123,15 @@ export default {
|
|||
item.lowerCaseName == lowerCaseName && (lowerCaseAlbumName ? item.lowerCaseAlbumName == lowerCaseAlbumName : true) &&
|
||||
(item.sortedSinger.includes(sortedSinger) || sortedSinger.includes(item.sortedSinger))
|
||||
)
|
||||
) {
|
||||
return item
|
||||
}
|
||||
if (!singer) {
|
||||
if (item.lowerCaseName == lowerCaseName && (interval ? item.interval == interval : true)) return item
|
||||
}
|
||||
) {
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}).catch(_ => null))
|
||||
}
|
||||
const result = (await Promise.all(tasks)).filter(s => s)
|
||||
if (!singer) {
|
||||
if (item.lowerCaseName == lowerCaseName && (interval ? item.interval == interval : true)) return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).filter(s => s)
|
||||
const newResult = []
|
||||
if (result.length) {
|
||||
newResult.push(...sortMusic(result, item => item.sortedSinger == sortedSinger && item.lowerCaseName == lowerCaseName && item.interval == interval))
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
<template>
|
||||
<material-modal :show="show" teleport="#view" bg-close @close="handleClose">
|
||||
<main :class="$style.main">
|
||||
<base-tab v-model="source" :class="$style.tab" :list="tabs" />
|
||||
<div class="scroll" :class="$style.list">
|
||||
<template v-if="list.length">
|
||||
<div v-for="(item, index) in list" :key="item.id" :class="$style.listItem">
|
||||
<div :class="$style.num">{{ index + 1 }}</div>
|
||||
<div :class="$style.textContent">
|
||||
<h3 :class="$style.text" :aria-label="`${item.name} - ${item.singer}`">{{ item.name }}</h3>
|
||||
<h3 v-if="item.meta.albumName" :class="[$style.text, $style.albumName]" :aria-label="item.meta.albumName">
|
||||
{{ item.singer }}
|
||||
<span v-if="item.meta.albumName"> - {{ item.meta.albumName }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div :class="$style.label">{{ item.interval }}</div>
|
||||
<div :class="$style.btns">
|
||||
<button type="button" :class="$style.btn" @click="openDetail(item)">
|
||||
<svg-icon name="share" />
|
||||
</button>
|
||||
<button type="button" :class="$style.btn" @click="handleToggle(item)">
|
||||
<svg v-once version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="50%" viewBox="0 0 287.386 287.386" space="preserve">
|
||||
<use xlink:href="#icon-testPlay" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else :class="$style.noItem">
|
||||
<p v-text="noItemLabel" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.footer">
|
||||
<div :class="$style.info">
|
||||
<h2>
|
||||
<div :class="$style.nameLabel">
|
||||
<span :class="$style.name">{{ musicInfo.name }}</span>
|
||||
<span :class="$style.label">{{ musicInfo.source }} {{ musicInfo.interval }}</span>
|
||||
</div>
|
||||
<div :class="$style.singer">
|
||||
{{ musicInfo.singer }}
|
||||
<span v-if="musicInfo.meta.albumName"> - {{ musicInfo.meta.albumName }}</span>
|
||||
</div>
|
||||
</h2>
|
||||
<template v-if="toggleMusicInfo">
|
||||
<span style="flex: none;">→</span>
|
||||
<h2>
|
||||
<div :class="$style.nameLabel">
|
||||
<span :class="$style.name">{{ toggleMusicInfo.name }}</span>
|
||||
<span :class="$style.label">{{ toggleMusicInfo.source }} {{ musicInfo.interval }}</span>
|
||||
</div>
|
||||
<div :class="$style.singer">
|
||||
{{ toggleMusicInfo.singer }}
|
||||
<span v-if="toggleMusicInfo.meta.albumName"> - {{ toggleMusicInfo.meta.albumName }}</span>
|
||||
</div>
|
||||
</h2>
|
||||
</template>
|
||||
</div>
|
||||
<base-btn :disabled="!toggleMusicInfo" :class="$style.btn" @click="handleClean">{{ $t('music_toggle_clean') }}</base-btn>
|
||||
</div>
|
||||
</main>
|
||||
</material-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { openUrl } from '@common/utils/electron'
|
||||
import { getSourceI18nPrefix } from '@renderer/store'
|
||||
import { toNewMusicInfo, toOldMusicInfo } from '@renderer/utils'
|
||||
import musicSdk from '@renderer/utils/musicSdk'
|
||||
import { markRaw } from 'vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
musicInfo: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ['update:show', 'toggle'],
|
||||
data() {
|
||||
return {
|
||||
tabs: [],
|
||||
lists: {},
|
||||
source: '',
|
||||
isError: false,
|
||||
loading: false,
|
||||
searchKey: 0,
|
||||
toggleMusicInfo: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
return this.lists[this.source] ?? []
|
||||
},
|
||||
noItemLabel() {
|
||||
return this.loading ? this.$t('list__loading') : this.isError ? this.$t('list__load_failed') : this.$t('no_item')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if (n) {
|
||||
this.isError = false
|
||||
const musicInfo = this.musicInfo
|
||||
this.toggleMusicInfo = musicInfo.meta.toggleMusicInfo
|
||||
this.tabs = []
|
||||
this.lists = {}
|
||||
this.loading = true
|
||||
const searchKey = this.searchKey = Math.random()
|
||||
void musicSdk.searchMusic({
|
||||
name: musicInfo.name,
|
||||
singer: musicInfo.singer,
|
||||
source: '',
|
||||
albumName: musicInfo.meta.albumName,
|
||||
interval: musicInfo.interval ?? '',
|
||||
}).then((lists) => {
|
||||
if (this.searchKey != searchKey) return
|
||||
const prefix = getSourceI18nPrefix()
|
||||
this.tabs = lists.map(item => {
|
||||
return {
|
||||
id: item.source,
|
||||
label: window.i18n.t(prefix + item.source),
|
||||
}
|
||||
})
|
||||
if (lists.length) this.source = lists[0].source
|
||||
for (const s of lists) this.lists[s.source] = s.list.map(s => markRaw(toNewMusicInfo(s)))
|
||||
}).catch(() => {
|
||||
if (this.searchKey != searchKey) return
|
||||
this.isError = true
|
||||
}).finally(() => {
|
||||
if (this.searchKey != searchKey) return
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$emit('update:show', false)
|
||||
},
|
||||
handleClean() {
|
||||
this.handleToggle(null)
|
||||
},
|
||||
openDetail(minfo) {
|
||||
const url = musicSdk[minfo.source]?.getMusicDetailPageUrl(toOldMusicInfo(minfo))
|
||||
if (!url) return
|
||||
void openUrl(url)
|
||||
},
|
||||
handleToggle(info) {
|
||||
this.toggleMusicInfo = info
|
||||
this.$emit('toggle', info)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.main {
|
||||
padding: 10px 7px 0;
|
||||
width: 560px;
|
||||
// min-width: 280px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 0;
|
||||
// max-height: 100%;
|
||||
// overflow: hidden;
|
||||
}
|
||||
|
||||
.list {
|
||||
min-height: 200px;
|
||||
min-width: 460px;
|
||||
// background-color: @color-search-form-background;
|
||||
font-size: 13px;
|
||||
transition-property: height;
|
||||
margin-top: 10px;
|
||||
padding: 0 7px;
|
||||
// position: relative;
|
||||
.listItem {
|
||||
position: relative;
|
||||
padding: 10px 5px;
|
||||
transition: background-color .2s ease;
|
||||
line-height: 1.4;
|
||||
height: 100%;
|
||||
// overflow: hidden;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
// &:last-child {
|
||||
// border-bottom-left-radius: 4px;
|
||||
// border-bottom-right-radius: 4px;
|
||||
// }
|
||||
}
|
||||
|
||||
.num {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
.textContent {
|
||||
flex: auto;
|
||||
padding-left: 5px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
.text {
|
||||
max-width: 100%;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.albumName {
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
// .mixin-ellipsis-1;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// transform: rotate(45deg);
|
||||
// background-color:
|
||||
}
|
||||
.btns {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: @form-radius;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
padding: 4px 7px;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
line-height: 0;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-font-active);
|
||||
}
|
||||
}
|
||||
|
||||
.noItem {
|
||||
position: relative;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: var(--color-font-label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 7px;
|
||||
.info {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding-right: 10px;
|
||||
gap: 10px;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
|
||||
h2 {
|
||||
min-width: 0;
|
||||
color: var(--color-font);
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
.nameLabel {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
.name {
|
||||
.mixin-ellipsis;
|
||||
}
|
||||
.label {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
padding: 0 5px;
|
||||
color: var(--color-primary);
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// transform: rotate(45deg);
|
||||
// background-color:
|
||||
}
|
||||
.singer {
|
||||
// font-size: 0.9em;
|
||||
color: var(--color-font-label);
|
||||
.mixin-ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: none;
|
||||
// box-sizing: border-box;
|
||||
// margin-left: 15px;
|
||||
// margin-bottom: 15px;
|
||||
// height: 36px;
|
||||
// line-height: 36px;
|
||||
// padding: 0 10px !important;
|
||||
min-width: 70px;
|
||||
// .mixin-ellipsis-1;
|
||||
|
||||
+.btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
|
@ -98,6 +98,7 @@
|
|||
<common-download-multiple-modal v-model:show="isShowDownloadMultiple" :list="selectedList" teleport="#view" @confirm="removeAllSelect" />
|
||||
<search-list :list="list" :visible="isShowSearchBar" @action="handleMusicSearchAction" />
|
||||
<music-sort-modal v-model:show="isShowMusicSortModal" :music-info="selectedSortMusicInfo" :selected-num="selectedNum" @confirm="sortMusic" />
|
||||
<music-toggle-modal v-model:show="isShowMusicToggleModal" :music-info="selectedToggleMusicInfo" @toggle="toggleSource" />
|
||||
<base-menu v-model="isShowItemMenu" :menus="menus" :xy="menuLocation" item-name="name" @menu-click="handleMenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -107,6 +108,7 @@ import { clipboardWriteText } from '@common/utils/electron'
|
|||
import { assertApiSupport } from '@renderer/store/utils'
|
||||
import SearchList from './components/SearchList.vue'
|
||||
import MusicSortModal from './components/MusicSortModal.vue'
|
||||
import MusicToggleModal from './components/MusicToggleModal.vue'
|
||||
import useListInfo from './useListInfo'
|
||||
import useList from './useList'
|
||||
import useMenu from './useMenu'
|
||||
|
@ -117,12 +119,14 @@ import useSort from './useSort'
|
|||
import useMusicActions from './useMusicActions'
|
||||
import useSearch from './useSearch'
|
||||
import useListScroll from './useListScroll'
|
||||
import useMusicToggle from './useMusicToggle'
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
export default {
|
||||
name: 'MusicList',
|
||||
components: {
|
||||
SearchList,
|
||||
MusicSortModal,
|
||||
MusicToggleModal,
|
||||
},
|
||||
props: {
|
||||
listId: {
|
||||
|
@ -197,6 +201,13 @@ export default {
|
|||
sortMusic,
|
||||
} = useSort({ props, list, selectedList, removeAllSelect })
|
||||
|
||||
const {
|
||||
handleShowMusicToggleModal,
|
||||
isShowMusicToggleModal,
|
||||
selectedToggleMusicInfo,
|
||||
toggleSource,
|
||||
} = useMusicToggle(props, list)
|
||||
|
||||
const {
|
||||
handleSearch,
|
||||
handleOpenMusicDetail,
|
||||
|
@ -218,6 +229,7 @@ export default {
|
|||
handleShowDownloadModal,
|
||||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
handleShowMusicToggleModal,
|
||||
handleSearch,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
|
@ -338,6 +350,10 @@ export default {
|
|||
handleRestoreScroll,
|
||||
|
||||
actionButtonsVisible,
|
||||
|
||||
isShowMusicToggleModal,
|
||||
selectedToggleMusicInfo,
|
||||
toggleSource,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ export default ({
|
|||
handlePlayMusic,
|
||||
handlePlayMusicLater,
|
||||
handleSearch,
|
||||
handleShowMusicToggleModal,
|
||||
handleShowMusicAddModal,
|
||||
handleShowMusicMoveModal,
|
||||
handleShowSortModal,
|
||||
|
@ -26,6 +27,7 @@ export default ({
|
|||
addTo: true,
|
||||
moveTo: true,
|
||||
sort: true,
|
||||
toggleSource: true,
|
||||
download: true,
|
||||
search: true,
|
||||
dislike: true,
|
||||
|
@ -68,6 +70,11 @@ export default ({
|
|||
action: 'sort',
|
||||
disabled: !itemMenuControl.sort,
|
||||
},
|
||||
{
|
||||
name: t('list__toggle_source'),
|
||||
action: 'toggleSource',
|
||||
disabled: !itemMenuControl.toggleSource,
|
||||
},
|
||||
{
|
||||
name: t('list__copy_name'),
|
||||
action: 'copyName',
|
||||
|
@ -142,6 +149,9 @@ export default ({
|
|||
case 'sort':
|
||||
handleShowSortModal(index)
|
||||
break
|
||||
case 'toggleSource':
|
||||
handleShowMusicToggleModal(index)
|
||||
break
|
||||
case 'download':
|
||||
handleShowDownloadModal(index)
|
||||
break
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// import { updateListMusicsPosition } from '@renderer/store/list/action'
|
||||
import { ref, nextTick } from '@common/utils/vueTools'
|
||||
import { updateListMusics } from '@renderer/store/list/listManage'
|
||||
import { playList } from '@renderer/core/player'
|
||||
import { getListMusicsFromCache } from '@renderer/store/list/action'
|
||||
|
||||
export default (props, list) => {
|
||||
const isShowMusicToggleModal = ref(false)
|
||||
const musicInfo = ref(null)
|
||||
|
||||
const handleShowMusicToggleModal = (index) => {
|
||||
musicInfo.value = list.value[index]
|
||||
nextTick(() => {
|
||||
isShowMusicToggleModal.value = true
|
||||
})
|
||||
}
|
||||
|
||||
const toggleSource = (toggleMusicInfo) => {
|
||||
const id = musicInfo.value.id
|
||||
const index = list.value.findIndex(m => m.id == id)
|
||||
if (index < 0) {
|
||||
isShowMusicToggleModal.value = false
|
||||
return
|
||||
}
|
||||
musicInfo.value.meta.toggleMusicInfo = toggleMusicInfo
|
||||
updateListMusics([
|
||||
{
|
||||
id: props.listId,
|
||||
musicInfo: {
|
||||
...musicInfo.value,
|
||||
meta: {
|
||||
...musicInfo.value.meta,
|
||||
toggleMusicInfo,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
const rawInfo = getListMusicsFromCache(props.listId)[index]
|
||||
rawInfo.meta.toggleMusicInfo = toggleMusicInfo
|
||||
playList(props.listId, index)
|
||||
}
|
||||
|
||||
return {
|
||||
isShowMusicToggleModal,
|
||||
selectedToggleMusicInfo: musicInfo,
|
||||
handleShowMusicToggleModal,
|
||||
toggleSource,
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div :class="$style.container">
|
||||
<div :class="$style.header">
|
||||
<base-tab v-model="source" :class="$style.tab" :list="sources" @change="handleSourceChange" />
|
||||
<base-tab v-model="searchType" :class="$style.tab" :list="searchTypes" @change="handleTypeChange" />
|
||||
<base-tab v-model="source" :list="sources" @change="handleSourceChange" />
|
||||
<base-tab v-model="searchType" :list="searchTypes" @change="handleTypeChange" />
|
||||
</div>
|
||||
<div :class="$style.main">
|
||||
<song-list-list v-if="searchType == 'songlist'" v-show="searchText" :page="page" :source-id="source" />
|
||||
|
|