1*41ddce91Smaotoumaoimport { internalSerializeKey, supportLocalMediaType } from "@/constants/commonConst"; 2*41ddce91Smaotoumaoimport pathConst from "@/constants/pathConst"; 3*41ddce91Smaotoumaoimport { addFileScheme, escapeCharacter, mkdirR } from "@/utils/fileUtils"; 4*41ddce91Smaotoumaoimport { errorLog } from "@/utils/log"; 5*41ddce91Smaotoumaoimport { isSameMediaItem } from "@/utils/mediaItem"; 6*41ddce91Smaotoumaoimport { getQualityOrder } from "@/utils/qualities"; 7*41ddce91Smaotoumaoimport StateMapper from "@/utils/stateMapper"; 8*41ddce91Smaotoumaoimport Toast from "@/utils/toast"; 9*41ddce91Smaotoumaoimport { produce } from "immer"; 10*41ddce91Smaotoumaoimport { InteractionManager } from "react-native"; 11*41ddce91Smaotoumaoimport { copyFile, downloadFile, exists, unlink } from "react-native-fs"; 122a3194f5S猫头猫 13*41ddce91Smaotoumaoimport Config from "./config.ts"; 14*41ddce91Smaotoumaoimport LocalMusicSheet from "./localMusicSheet"; 15*41ddce91Smaotoumaoimport MediaMeta from "./mediaExtra"; 16*41ddce91Smaotoumaoimport Network from "./network"; 17*41ddce91Smaotoumaoimport PluginManager from "./pluginManager"; 18*41ddce91Smaotoumaoimport { check, PERMISSIONS } from "react-native-permissions"; 19*41ddce91Smaotoumaoimport path from "path-browserify"; 20*41ddce91Smaotoumaoimport { getCurrentDialog, hideDialog, showDialog } from "@/components/dialogs/useDialog"; 21*41ddce91Smaotoumaoimport { nanoid } from "nanoid"; 22e14deecfS猫头猫 23dc160d50S猫头猫/** 队列中的元素 */ 24e14deecfS猫头猫interface IDownloadMusicOptions { 25dc160d50S猫头猫 /** 要下载的音乐 */ 26e14deecfS猫头猫 musicItem: IMusic.IMusicItem; 27dc160d50S猫头猫 /** 目标文件名 */ 28e14deecfS猫头猫 filename: string; 29dc160d50S猫头猫 /** 下载id */ 30e14deecfS猫头猫 jobId?: number; 31dc160d50S猫头猫 /** 下载音质 */ 32dc160d50S猫头猫 quality?: IMusic.IQualityKey; 33e14deecfS猫头猫} 34dc160d50S猫头猫 35e14deecfS猫头猫/** 下载中 */ 36e14deecfS猫头猫let downloadingMusicQueue: IDownloadMusicOptions[] = []; 37e14deecfS猫头猫/** 队列中 */ 38e14deecfS猫头猫let pendingMusicQueue: IDownloadMusicOptions[] = []; 39dc160d50S猫头猫/** 下载进度 */ 40e14deecfS猫头猫let downloadingProgress: Record<string, {progress: number; size: number}> = {}; 41e1f817d7S猫头猫/** 错误信息 */ 42e1f817d7S猫头猫let hasError: boolean = false; 43e14deecfS猫头猫 44e14deecfS猫头猫const downloadingQueueStateMapper = new StateMapper( 45e14deecfS猫头猫 () => downloadingMusicQueue, 46e14deecfS猫头猫); 47e14deecfS猫头猫const pendingMusicQueueStateMapper = new StateMapper(() => pendingMusicQueue); 48e14deecfS猫头猫const downloadingProgressStateMapper = new StateMapper( 49e14deecfS猫头猫 () => downloadingProgress, 50e14deecfS猫头猫); 51e14deecfS猫头猫 52dc160d50S猫头猫/** 匹配文件后缀 */ 53dc160d50S猫头猫const getExtensionName = (url: string) => { 54dc160d50S猫头猫 const regResult = url.match( 55dc160d50S猫头猫 /^https?\:\/\/.+\.([^\?\.]+?$)|(?:([^\.]+?)\?.+$)/, 56dc160d50S猫头猫 ); 57dc160d50S猫头猫 if (regResult) { 58dc160d50S猫头猫 return regResult[1] ?? regResult[2] ?? 'mp3'; 59dc160d50S猫头猫 } else { 60dc160d50S猫头猫 return 'mp3'; 61dc160d50S猫头猫 } 62dc160d50S猫头猫}; 63dc160d50S猫头猫 64dc160d50S猫头猫/** 生成下载文件 */ 6542a9f3e6Smaotoumaoconst getDownloadPath = (fileName: string) => { 66b6261296S猫头猫 const dlPath = 67*41ddce91Smaotoumao Config.getConfig('basic.downloadPath') ?? pathConst.downloadMusicPath; 68b6261296S猫头猫 if (!dlPath.endsWith('/')) { 69b6261296S猫头猫 return `${dlPath}/${fileName ?? ''}`; 70b6261296S猫头猫 } 7179f1d840Szhuguibiao return fileName ? dlPath + fileName : dlPath; 72b6261296S猫头猫}; 73b6261296S猫头猫 7442a9f3e6Smaotoumaoconst getCacheDownloadPath = (fileName: string) => { 7542a9f3e6Smaotoumao const cachePath = pathConst.downloadCachePath; 7642a9f3e6Smaotoumao if (!cachePath.endsWith('/')) { 7742a9f3e6Smaotoumao return `${cachePath}/${fileName ?? ''}`; 7842a9f3e6Smaotoumao } 7942a9f3e6Smaotoumao return fileName ? cachePath + fileName : cachePath; 8042a9f3e6Smaotoumao}; 8142a9f3e6Smaotoumao 82e14deecfS猫头猫/** 从待下载中移除 */ 83e14deecfS猫头猫function removeFromPendingQueue(item: IDownloadMusicOptions) { 84dc160d50S猫头猫 const targetIndex = pendingMusicQueue.findIndex(_ => 85dc160d50S猫头猫 isSameMediaItem(_.musicItem, item.musicItem), 86e14deecfS猫头猫 ); 87dc160d50S猫头猫 if (targetIndex !== -1) { 88dc160d50S猫头猫 pendingMusicQueue = pendingMusicQueue 89dc160d50S猫头猫 .slice(0, targetIndex) 90dc160d50S猫头猫 .concat(pendingMusicQueue.slice(targetIndex + 1)); 91e14deecfS猫头猫 pendingMusicQueueStateMapper.notify(); 92e14deecfS猫头猫 } 93dc160d50S猫头猫} 94e14deecfS猫头猫 95e14deecfS猫头猫/** 从下载中队列移除 */ 96e14deecfS猫头猫function removeFromDownloadingQueue(item: IDownloadMusicOptions) { 97dc160d50S猫头猫 const targetIndex = downloadingMusicQueue.findIndex(_ => 98dc160d50S猫头猫 isSameMediaItem(_.musicItem, item.musicItem), 99e14deecfS猫头猫 ); 100dc160d50S猫头猫 if (targetIndex !== -1) { 101dc160d50S猫头猫 downloadingMusicQueue = downloadingMusicQueue 102dc160d50S猫头猫 .slice(0, targetIndex) 103dc160d50S猫头猫 .concat(downloadingMusicQueue.slice(targetIndex + 1)); 104e14deecfS猫头猫 downloadingQueueStateMapper.notify(); 105e14deecfS猫头猫 } 106dc160d50S猫头猫} 107e14deecfS猫头猫 108e14deecfS猫头猫/** 防止高频同步 */ 109e14deecfS猫头猫let progressNotifyTimer: any = null; 110e14deecfS猫头猫function startNotifyProgress() { 111e14deecfS猫头猫 if (progressNotifyTimer) { 112e14deecfS猫头猫 return; 113e14deecfS猫头猫 } 114e14deecfS猫头猫 115e14deecfS猫头猫 progressNotifyTimer = setTimeout(() => { 116e14deecfS猫头猫 progressNotifyTimer = null; 117e14deecfS猫头猫 downloadingProgressStateMapper.notify(); 118e14deecfS猫头猫 startNotifyProgress(); 119dc160d50S猫头猫 }, 500); 120e14deecfS猫头猫} 121e14deecfS猫头猫 122e14deecfS猫头猫function stopNotifyProgress() { 123e14deecfS猫头猫 if (progressNotifyTimer) { 124dc160d50S猫头猫 clearTimeout(progressNotifyTimer); 125e14deecfS猫头猫 } 126e14deecfS猫头猫 progressNotifyTimer = null; 127e14deecfS猫头猫} 128e14deecfS猫头猫 129e14deecfS猫头猫/** 生成下载文件名 */ 130e14deecfS猫头猫function generateFilename(musicItem: IMusic.IMusicItem) { 131f55de8c2S猫头猫 return `${escapeCharacter(musicItem.platform)}@${escapeCharacter( 132a8557e3dS猫头猫 musicItem.id, 133f55de8c2S猫头猫 )}@${escapeCharacter(musicItem.title)}@${escapeCharacter( 134a8557e3dS猫头猫 musicItem.artist, 135a8557e3dS猫头猫 )}`.slice(0, 200); 136e14deecfS猫头猫} 137e14deecfS猫头猫 13860dbde7dS猫头猫/** todo 可以配置一个说明文件 */ 1394060c00aS猫头猫// async function loadLocalJson(dirBase: string) { 1404060c00aS猫头猫// const jsonPath = dirBase + 'data.json'; 1414060c00aS猫头猫// if (await exists(jsonPath)) { 1424060c00aS猫头猫// try { 1434060c00aS猫头猫// const result = await readFile(jsonPath, 'utf8'); 1444060c00aS猫头猫// return JSON.parse(result); 1454060c00aS猫头猫// } catch { 1464060c00aS猫头猫// return {}; 1474060c00aS猫头猫// } 1484060c00aS猫头猫// } 1494060c00aS猫头猫// return {}; 1504060c00aS猫头猫// } 151e14deecfS猫头猫 152e14deecfS猫头猫let maxDownload = 3; 153dc160d50S猫头猫/** 队列下载*/ 154e14deecfS猫头猫async function downloadNext() { 155e14deecfS猫头猫 // todo 最大同时下载3个,可设置 156e14deecfS猫头猫 if ( 157e14deecfS猫头猫 downloadingMusicQueue.length >= maxDownload || 158e14deecfS猫头猫 pendingMusicQueue.length === 0 159e14deecfS猫头猫 ) { 160e14deecfS猫头猫 return; 161e14deecfS猫头猫 } 162dc160d50S猫头猫 // 下一个下载的为pending的第一个 163dc160d50S猫头猫 let nextDownloadItem = pendingMusicQueue[0]; 164dc160d50S猫头猫 const musicItem = nextDownloadItem.musicItem; 165e14deecfS猫头猫 let url = musicItem.url; 166e14deecfS猫头猫 let headers = musicItem.headers; 167dc160d50S猫头猫 removeFromPendingQueue(nextDownloadItem); 168e14deecfS猫头猫 downloadingMusicQueue = produce(downloadingMusicQueue, draft => { 169dc160d50S猫头猫 draft.push(nextDownloadItem); 170e14deecfS猫头猫 }); 171e14deecfS猫头猫 downloadingQueueStateMapper.notify(); 172dc160d50S猫头猫 const quality = nextDownloadItem.quality; 173bc307175S猫头猫 const plugin = PluginManager.getByName(musicItem.platform); 174e14deecfS猫头猫 // 插件播放 175dc160d50S猫头猫 try { 1768b88e961S猫头猫 if (plugin) { 177abaede57S猫头猫 const qualityOrder = getQualityOrder( 178dc160d50S猫头猫 quality ?? 179*41ddce91Smaotoumao Config.getConfig('basic.defaultDownloadQuality') ?? 180abaede57S猫头猫 'standard', 181*41ddce91Smaotoumao Config.getConfig('basic.downloadQualityOrder') ?? 'asc', 182abaede57S猫头猫 ); 183abaede57S猫头猫 let data: IPlugin.IMediaSourceResult | null = null; 184abaede57S猫头猫 for (let quality of qualityOrder) { 185abaede57S猫头猫 try { 186abaede57S猫头猫 data = await plugin.methods.getMediaSource( 187abaede57S猫头猫 musicItem, 188abaede57S猫头猫 quality, 189dc160d50S猫头猫 1, 190dc160d50S猫头猫 true, 191abaede57S猫头猫 ); 192abaede57S猫头猫 if (!data?.url) { 193abaede57S猫头猫 continue; 194b7048bd1S猫头猫 } 195abaede57S猫头猫 break; 196abaede57S猫头猫 } catch {} 197abaede57S猫头猫 } 198abaede57S猫头猫 url = data?.url ?? url; 199e14deecfS猫头猫 headers = data?.headers; 200dc160d50S猫头猫 } 201dc160d50S猫头猫 if (!url) { 202dc160d50S猫头猫 throw new Error('empty'); 203dc160d50S猫头猫 } 204e1f817d7S猫头猫 } catch (e: any) { 205e14deecfS猫头猫 /** 无法下载,跳过 */ 206e1f817d7S猫头猫 errorLog('下载失败-无法获取下载链接', { 207e1f817d7S猫头猫 item: { 208e1f817d7S猫头猫 id: nextDownloadItem.musicItem.id, 209e1f817d7S猫头猫 title: nextDownloadItem.musicItem.title, 210e1f817d7S猫头猫 platform: nextDownloadItem.musicItem.platform, 211e1f817d7S猫头猫 quality: nextDownloadItem.quality, 212e1f817d7S猫头猫 }, 213e1f817d7S猫头猫 reason: e?.message ?? e, 214e1f817d7S猫头猫 }); 215e1f817d7S猫头猫 hasError = true; 216dc160d50S猫头猫 removeFromDownloadingQueue(nextDownloadItem); 217e14deecfS猫头猫 return; 218e14deecfS猫头猫 } 219dc160d50S猫头猫 /** 预处理完成,接下来去下载音乐 */ 220dc160d50S猫头猫 downloadNextAfterInteraction(); 2218fc75cb2S猫头猫 let extension = getExtensionName(url); 2228fc75cb2S猫头猫 const extensionWithDot = `.${extension}`; 2238fc75cb2S猫头猫 if (supportLocalMediaType.every(_ => _ !== extensionWithDot)) { 2248fc75cb2S猫头猫 extension = 'mp3'; 2258fc75cb2S猫头猫 } 226dc160d50S猫头猫 /** 目标下载地址 */ 22742a9f3e6Smaotoumao const cacheDownloadPath = addFileScheme( 22842a9f3e6Smaotoumao getCacheDownloadPath(`${nanoid()}.${extension}`), 22942a9f3e6Smaotoumao ); 2309899f4b8S猫头猫 const targetDownloadPath = addFileScheme( 2319899f4b8S猫头猫 getDownloadPath(`${nextDownloadItem.filename}.${extension}`), 232dc160d50S猫头猫 ); 233e14deecfS猫头猫 const {promise, jobId} = downloadFile({ 234e14deecfS猫头猫 fromUrl: url ?? '', 23542a9f3e6Smaotoumao toFile: cacheDownloadPath, 236e14deecfS猫头猫 headers: headers, 237e14deecfS猫头猫 background: true, 238e14deecfS猫头猫 begin(res) { 239e14deecfS猫头猫 downloadingProgress = produce(downloadingProgress, _ => { 240dc160d50S猫头猫 _[nextDownloadItem.filename] = { 241e14deecfS猫头猫 progress: 0, 242e14deecfS猫头猫 size: res.contentLength, 243e14deecfS猫头猫 }; 244e14deecfS猫头猫 }); 245e14deecfS猫头猫 startNotifyProgress(); 246e14deecfS猫头猫 }, 247e14deecfS猫头猫 progress(res) { 248e14deecfS猫头猫 downloadingProgress = produce(downloadingProgress, _ => { 249dc160d50S猫头猫 _[nextDownloadItem.filename] = { 250e14deecfS猫头猫 progress: res.bytesWritten, 251e14deecfS猫头猫 size: res.contentLength, 252e14deecfS猫头猫 }; 253e14deecfS猫头猫 }); 254e14deecfS猫头猫 }, 255e14deecfS猫头猫 }); 256dc160d50S猫头猫 nextDownloadItem = {...nextDownloadItem, jobId}; 257483fe585S猫头猫 258483fe585S猫头猫 let folder = path.dirname(targetDownloadPath); 259483fe585S猫头猫 let folderExists = await exists(folder); 260483fe585S猫头猫 261483fe585S猫头猫 if (!folderExists) { 262483fe585S猫头猫 await mkdirR(folder); 263483fe585S猫头猫 } 26442a9f3e6Smaotoumao 265e14deecfS猫头猫 try { 266e14deecfS猫头猫 await promise; 26742a9f3e6Smaotoumao await copyFile(cacheDownloadPath, targetDownloadPath); 268dc160d50S猫头猫 /** 下载完成 */ 269dc160d50S猫头猫 LocalMusicSheet.addMusicDraft({ 270e14deecfS猫头猫 ...musicItem, 2710e4173cdS猫头猫 [internalSerializeKey]: { 272dc160d50S猫头猫 localPath: targetDownloadPath, 273e14deecfS猫头猫 }, 274e14deecfS猫头猫 }); 27543eb30bfS猫头猫 MediaMeta.update(musicItem, { 27660dbde7dS猫头猫 downloaded: true, 27743eb30bfS猫头猫 localPath: targetDownloadPath, 27860dbde7dS猫头猫 }); 279bc307175S猫头猫 // const primaryKey = plugin?.instance.primaryKey ?? []; 280bc307175S猫头猫 // if (!primaryKey.includes('id')) { 281bc307175S猫头猫 // primaryKey.push('id'); 282bc307175S猫头猫 // } 283bc307175S猫头猫 // const stringifyMeta: Record<string, any> = { 284bc307175S猫头猫 // title: musicItem.title, 285bc307175S猫头猫 // artist: musicItem.artist, 286bc307175S猫头猫 // album: musicItem.album, 287bc307175S猫头猫 // lrc: musicItem.lrc, 288bc307175S猫头猫 // platform: musicItem.platform, 289bc307175S猫头猫 // }; 290bc307175S猫头猫 // primaryKey.forEach(_ => { 291bc307175S猫头猫 // stringifyMeta[_] = musicItem[_]; 292bc307175S猫头猫 // }); 293bc307175S猫头猫 2943d4d06d9S猫头猫 // await Mp3Util.getMediaTag(filePath).then(_ => { 2953d4d06d9S猫头猫 // console.log(_); 2963d4d06d9S猫头猫 // }).catch(console.log); 297dc160d50S猫头猫 } catch (e: any) { 298bc307175S猫头猫 console.log(e, 'downloaderror'); 299dc160d50S猫头猫 /** 下载出错 */ 300e1f817d7S猫头猫 errorLog('下载失败', { 301e1f817d7S猫头猫 item: { 302e1f817d7S猫头猫 id: nextDownloadItem.musicItem.id, 303e1f817d7S猫头猫 title: nextDownloadItem.musicItem.title, 304e1f817d7S猫头猫 platform: nextDownloadItem.musicItem.platform, 305e1f817d7S猫头猫 quality: nextDownloadItem.quality, 306e1f817d7S猫头猫 }, 307e1f817d7S猫头猫 reason: e?.message ?? e, 308483fe585S猫头猫 targetDownloadPath: targetDownloadPath, 309e1f817d7S猫头猫 }); 310e1f817d7S猫头猫 hasError = true; 31142a9f3e6Smaotoumao } finally { 31242a9f3e6Smaotoumao await unlink(cacheDownloadPath); 313dc160d50S猫头猫 } 314dc160d50S猫头猫 removeFromDownloadingQueue(nextDownloadItem); 315dc160d50S猫头猫 downloadingProgress = produce(downloadingProgress, draft => { 316dc160d50S猫头猫 if (draft[nextDownloadItem.filename]) { 317dc160d50S猫头猫 delete draft[nextDownloadItem.filename]; 318dc160d50S猫头猫 } 319dc160d50S猫头猫 }); 320dc160d50S猫头猫 downloadNextAfterInteraction(); 321e14deecfS猫头猫 if (downloadingMusicQueue.length === 0) { 322e14deecfS猫头猫 stopNotifyProgress(); 323dc160d50S猫头猫 LocalMusicSheet.saveLocalSheet(); 324e1f817d7S猫头猫 if (hasError) { 3250412c91bS猫头猫 try { 3260412c91bS猫头猫 const perm = await check( 3270412c91bS猫头猫 PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE, 3280412c91bS猫头猫 ); 3290412c91bS猫头猫 if (perm !== 'granted') { 330483fe585S猫头猫 Toast.warn('权限不足,请检查是否授予写入文件的权限'); 3310412c91bS猫头猫 } else { 3320412c91bS猫头猫 throw new Error(); 3330412c91bS猫头猫 } 3340412c91bS猫头猫 } catch { 335dffbbaffS猫头猫 if (getCurrentDialog()?.name !== 'SimpleDialog') { 336dffbbaffS猫头猫 showDialog('SimpleDialog', { 337dffbbaffS猫头猫 title: '下载失败', 338dffbbaffS猫头猫 content: 339dffbbaffS猫头猫 '部分歌曲下载失败,如果无法下载请检查系统设置中是否授予完整文件读写权限;或者去【侧边栏-权限管理】中查看文件读写权限是否勾选', 340dffbbaffS猫头猫 onOk: hideDialog, 341dffbbaffS猫头猫 }); 342dffbbaffS猫头猫 } 3430412c91bS猫头猫 } 344e1f817d7S猫头猫 } else { 3452a3194f5S猫头猫 Toast.success('下载完成'); 346e1f817d7S猫头猫 } 347e1f817d7S猫头猫 hasError = false; 348e14deecfS猫头猫 downloadingMusicQueue = []; 349e14deecfS猫头猫 pendingMusicQueue = []; 350e14deecfS猫头猫 downloadingQueueStateMapper.notify(); 351e14deecfS猫头猫 pendingMusicQueueStateMapper.notify(); 352e14deecfS猫头猫 } 353e14deecfS猫头猫} 354e14deecfS猫头猫 355dc160d50S猫头猫async function downloadNextAfterInteraction() { 356dc160d50S猫头猫 InteractionManager.runAfterInteractions(downloadNext); 357dc160d50S猫头猫} 358dc160d50S猫头猫 359dc160d50S猫头猫/** 加入下载队列 */ 360dc160d50S猫头猫function downloadMusic( 361dc160d50S猫头猫 musicItems: IMusic.IMusicItem | IMusic.IMusicItem[], 362dc160d50S猫头猫 quality?: IMusic.IQualityKey, 363dc160d50S猫头猫) { 364ef714860S猫头猫 if (Network.isOffline()) { 365ef714860S猫头猫 Toast.warn('当前无网络,无法下载'); 366ef714860S猫头猫 return; 367ef714860S猫头猫 } 368ef714860S猫头猫 if ( 369ef714860S猫头猫 Network.isCellular() && 370*41ddce91Smaotoumao !Config.getConfig('basic.useCelluarNetworkDownload') && 3712dd57e49S猫头猫 getCurrentDialog()?.name !== 'SimpleDialog' 372ef714860S猫头猫 ) { 3732dd57e49S猫头猫 showDialog('SimpleDialog', { 3742dd57e49S猫头猫 title: '流量提醒', 3752dd57e49S猫头猫 content: 3762dd57e49S猫头猫 '当前非WIFI环境,侧边栏设置中打开【使用移动网络下载】功能后可继续下载', 3772dd57e49S猫头猫 }); 378ef714860S猫头猫 return; 379ef714860S猫头猫 } 380e14deecfS猫头猫 // 如果已经在下载中 381e14deecfS猫头猫 if (!Array.isArray(musicItems)) { 382e14deecfS猫头猫 musicItems = [musicItems]; 383e14deecfS猫头猫 } 384e1f817d7S猫头猫 hasError = false; 385e14deecfS猫头猫 musicItems = musicItems.filter( 386e14deecfS猫头猫 musicItem => 387e14deecfS猫头猫 pendingMusicQueue.findIndex(_ => 388e14deecfS猫头猫 isSameMediaItem(_.musicItem, musicItem), 389e14deecfS猫头猫 ) === -1 && 390e14deecfS猫头猫 downloadingMusicQueue.findIndex(_ => 391e14deecfS猫头猫 isSameMediaItem(_.musicItem, musicItem), 392f970486eS猫头猫 ) === -1 && 393f970486eS猫头猫 !LocalMusicSheet.isLocalMusic(musicItem), 394e14deecfS猫头猫 ); 395fd2b24adS猫头猫 const enqueueData = musicItems.map(_ => { 396fd2b24adS猫头猫 return { 397e14deecfS猫头猫 musicItem: _, 398e14deecfS猫头猫 filename: generateFilename(_), 399dc160d50S猫头猫 quality, 400fd2b24adS猫头猫 }; 401fd2b24adS猫头猫 }); 402e14deecfS猫头猫 if (enqueueData.length) { 403e14deecfS猫头猫 pendingMusicQueue = pendingMusicQueue.concat(enqueueData); 404e14deecfS猫头猫 pendingMusicQueueStateMapper.notify(); 405*41ddce91Smaotoumao maxDownload = +(Config.getConfig('basic.maxDownload') ?? 3); 406dc160d50S猫头猫 downloadNextAfterInteraction(); 407e14deecfS猫头猫 } 408e14deecfS猫头猫} 409e14deecfS猫头猫 410e14deecfS猫头猫const Download = { 411e14deecfS猫头猫 downloadMusic, 412e14deecfS猫头猫 useDownloadingMusic: downloadingQueueStateMapper.useMappedState, 413e14deecfS猫头猫 usePendingMusic: pendingMusicQueueStateMapper.useMappedState, 414e14deecfS猫头猫 useDownloadingProgress: downloadingProgressStateMapper.useMappedState, 415e14deecfS猫头猫}; 416e14deecfS猫头猫 417e14deecfS猫头猫export default Download; 418