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