xref: /MusicFree/src/entry/bootstrap.ts (revision 34bfbe58c7be2b25145bbf29046f8a418be73f02)
1import { check, PERMISSIONS, request } from "react-native-permissions";
2import RNTrackPlayer, { AppKilledPlaybackBehavior, Capability } from "react-native-track-player";
3import "react-native-get-random-values";
4import Config from "@/core/config.ts";
5import pathConst from "@/constants/pathConst";
6import { checkAndCreateDir } from "@/utils/fileUtils";
7import { errorLog, trace } from "@/utils/log";
8import PluginManager from "@/core/pluginManager";
9import Network from "@/core/network";
10import { ImgAsset } from "@/constants/assetsConst";
11import LocalMusicSheet from "@/core/localMusicSheet";
12import { Linking, Platform } from "react-native";
13import Theme from "@/core/theme";
14import LyricManager from "@/core/lyricManager";
15import Toast from "@/utils/toast";
16import { emptyFunction, localPluginHash, supportLocalMediaType } from "@/constants/commonConst";
17import TrackPlayer from "@/core/trackPlayer";
18import musicHistory from "@/core/musicHistory";
19import PersistStatus from "@/core/persistStatus.ts";
20import { perfLogger } from "@/utils/perfLogger";
21import * as SplashScreen from "expo-splash-screen";
22import MusicSheet from "@/core/musicSheet";
23import NativeUtils from "@/native/utils";
24import { showDialog } from "@/components/dialogs/useDialog.ts";
25
26/** app加载前执行
27 * 1. 检查权限
28 * 2. 数据初始化
29 */
30
31async function _bootstrap() {
32    await SplashScreen.preventAutoHideAsync()
33        .then(result =>
34            console.log(
35                `SplashScreen.preventAutoHideAsync() succeeded: ${result}`,
36            ),
37        )
38        .catch(console.warn); // it's good to explicitly catch and inspect any error
39    const logger = perfLogger();
40    // 1. 检查权限
41    if (Platform.OS === 'android' && Platform.Version >= 30) {
42        const hasPermission = await NativeUtils.checkStoragePermission();
43        if (
44            !hasPermission &&
45            !PersistStatus.get('app.skipBootstrapStorageDialog')
46        ) {
47            showDialog('CheckStorage');
48        }
49    } else {
50        const [readStoragePermission, writeStoragePermission] =
51            await Promise.all([
52                check(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE),
53                check(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE),
54            ]);
55        if (
56            !(
57                readStoragePermission === 'granted' &&
58                writeStoragePermission === 'granted'
59            )
60        ) {
61            await request(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE);
62            await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
63        }
64    }
65    logger.mark('权限检查完成');
66
67    // 2. 数据初始化
68    /** 初始化路径 */
69    await setupFolder();
70    trace('文件夹初始化完成');
71    logger.mark('文件夹初始化完成');
72
73    // 加载配置
74    await Promise.all([
75        Config.setup().then(() => {
76            logger.mark('Config');
77        }),
78        MusicSheet.setup().then(() => {
79            logger.mark('MusicSheet');
80        }),
81        musicHistory.setupMusicHistory().then(() => {
82            logger.mark('musicHistory');
83        }),
84    ]);
85    trace('配置初始化完成');
86    logger.mark('配置初始化完成');
87
88    // 加载插件
89    try {
90        await RNTrackPlayer.setupPlayer({
91            maxCacheSize:
92                Config.getConfig('basic.maxCacheSize') ?? 1024 * 1024 * 512,
93        });
94    } catch (e: any) {
95        if (
96            e?.message !==
97            'The player has already been initialized via setupPlayer.'
98        ) {
99            throw e;
100        }
101    }
102    logger.mark('加载播放器');
103
104    const capabilities = Config.getConfig('basic.showExitOnNotification')
105        ? [
106              Capability.Play,
107              Capability.Pause,
108              Capability.SkipToNext,
109              Capability.SkipToPrevious,
110              Capability.Stop,
111          ]
112        : [
113              Capability.Play,
114              Capability.Pause,
115              Capability.SkipToNext,
116              Capability.SkipToPrevious,
117          ];
118    await RNTrackPlayer.updateOptions({
119        icon: ImgAsset.logoTransparent,
120        progressUpdateEventInterval: 1,
121        android: {
122            alwaysPauseOnInterruption: true,
123            appKilledPlaybackBehavior:
124                AppKilledPlaybackBehavior.ContinuePlayback,
125        },
126        capabilities: capabilities,
127        compactCapabilities: capabilities,
128        notificationCapabilities: [...capabilities, Capability.SeekTo],
129    });
130    logger.mark('播放器初始化完成');
131    trace('播放器初始化完成');
132
133    await PluginManager.setup();
134    logger.mark('插件初始化完成');
135
136    trace('插件初始化完成');
137    await TrackPlayer.setupTrackPlayer();
138    trace('播放列表初始化完成');
139    logger.mark('播放列表初始化完成');
140
141    await LocalMusicSheet.setup();
142    trace('本地音乐初始化完成');
143    logger.mark('本地音乐初始化完成');
144
145    Theme.setup();
146    trace('主题初始化完成');
147    logger.mark('主题初始化完成');
148
149    await LyricManager.setup();
150
151    logger.mark('歌词初始化完成');
152
153    extraMakeup();
154    ErrorUtils.setGlobalHandler(error => {
155        errorLog('未捕获的错误', error);
156    });
157}
158
159/** 初始化 */
160async function setupFolder() {
161    await Promise.all([
162        checkAndCreateDir(pathConst.dataPath),
163        checkAndCreateDir(pathConst.logPath),
164        checkAndCreateDir(pathConst.cachePath),
165        checkAndCreateDir(pathConst.pluginPath),
166        checkAndCreateDir(pathConst.lrcCachePath),
167        checkAndCreateDir(pathConst.downloadCachePath),
168        checkAndCreateDir(pathConst.localLrcPath),
169        checkAndCreateDir(pathConst.downloadPath).then(() => {
170            checkAndCreateDir(pathConst.downloadMusicPath);
171        }),
172    ]);
173}
174
175export default async function () {
176    try {
177        await _bootstrap();
178    } catch (e) {
179        errorLog('初始化出错', e);
180    }
181    // 隐藏开屏动画
182    console.log('HIDE');
183    await SplashScreen.hideAsync();
184}
185
186/** 不需要阻塞的 */
187async function extraMakeup() {
188    // 自动更新
189    try {
190        // 初始化网络状态
191        Network.setup();
192
193        if (Config.getConfig('basic.autoUpdatePlugin')) {
194            const lastUpdated = PersistStatus.get('app.pluginUpdateTime') || 0;
195            const now = Date.now();
196            if (Math.abs(now - lastUpdated) > 86400000) {
197                PersistStatus.set('app.pluginUpdateTime', now);
198                const plugins = PluginManager.getValidPlugins();
199                for (let i = 0; i < plugins.length; ++i) {
200                    const srcUrl = plugins[i].instance.srcUrl;
201                    if (srcUrl) {
202                        // 静默失败
203                        await PluginManager.installPluginFromUrl(srcUrl).catch(emptyFunction);
204                    }
205                }
206            }
207        }
208    } catch {}
209
210    async function handleLinkingUrl(url: string) {
211        // 插件
212        try {
213            if (url.startsWith('musicfree://install/')) {
214                const plugins = url
215                    .slice(20)
216                    .split(',')
217                    .map(decodeURIComponent);
218                await Promise.all(
219                    plugins.map(it =>
220                        PluginManager.installPluginFromUrl(it).catch(emptyFunction),
221                    ),
222                );
223                Toast.success('安装成功~');
224            } else if (url.endsWith('.js')) {
225                PluginManager.installPluginFromLocalFile(url, {
226                    notCheckVersion: Config.getConfig(
227                        'basic.notCheckPluginVersion',
228                    ),
229                })
230                    .then(res => {
231                        if (res.success) {
232                            Toast.success(`插件「${res.pluginName}」安装成功~`);
233                        } else {
234                            Toast.warn("安装失败: " + res.message);
235                        }
236                    })
237                    .catch(e => {
238                        console.log(e);
239                        Toast.warn(e?.message ?? '无法识别此插件');
240                    });
241            } else if (supportLocalMediaType.some(it => url.endsWith(it))) {
242                // 本地播放
243                const musicItem = await PluginManager.getByHash(
244                    localPluginHash,
245                )?.instance?.importMusicItem?.(url);
246                console.log(musicItem);
247                if (musicItem) {
248                    TrackPlayer.play(musicItem);
249                }
250            }
251        } catch {}
252    }
253
254    // 开启监听
255    Linking.addEventListener('url', data => {
256        if (data.url) {
257            handleLinkingUrl(data.url);
258        }
259    });
260    const initUrl = await Linking.getInitialURL();
261    if (initUrl) {
262        handleLinkingUrl(initUrl);
263    }
264
265    if (Config.getConfig('basic.autoPlayWhenAppStart')) {
266        TrackPlayer.play();
267    }
268}
269