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