xref: /MusicFree/src/core/download.ts (revision 41ddce918e1138d8f16e522cc7c19ac86ceca698)
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