xref: /MusicFree/src/entry/bootstrap.ts (revision 6613e77203923e5b1742a49281bfa5de03fc1440)
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.downloadCachePath),
176        checkAndCreateDir(pathConst.localLrcPath),
177        checkAndCreateDir(pathConst.downloadPath).then(() => {
178            checkAndCreateDir(pathConst.downloadMusicPath);
179        }),
180    ]);
181}
182
183export default async function () {
184    try {
185        await _bootstrap();
186    } catch (e) {
187        errorLog('初始化出错', e);
188    }
189    // 隐藏开屏动画
190    console.log('HIDE');
191    await SplashScreen.hideAsync();
192}
193
194/** 不需要阻塞的 */
195async function extraMakeup() {
196    // 自动更新
197    try {
198        // 初始化网络状态
199        Network.setup();
200
201        if (Config.get('setting.basic.autoUpdatePlugin')) {
202            const lastUpdated = PersistStatus.get('app.pluginUpdateTime') || 0;
203            const now = Date.now();
204            if (Math.abs(now - lastUpdated) > 86400000) {
205                PersistStatus.set('app.pluginUpdateTime', now);
206                const plugins = PluginManager.getValidPlugins();
207                for (let i = 0; i < plugins.length; ++i) {
208                    const srcUrl = plugins[i].instance.srcUrl;
209                    if (srcUrl) {
210                        await PluginManager.installPluginFromUrl(srcUrl);
211                    }
212                }
213            }
214        }
215    } catch {}
216
217    async function handleLinkingUrl(url: string) {
218        // 插件
219        try {
220            if (url.startsWith('musicfree://install/')) {
221                const plugins = url
222                    .slice(20)
223                    .split(',')
224                    .map(decodeURIComponent);
225                await Promise.all(
226                    plugins.map(it =>
227                        PluginManager.installPluginFromUrl(it).catch(() => {}),
228                    ),
229                );
230                Toast.success('安装成功~');
231            } else if (url.endsWith('.js')) {
232                PluginManager.installPlugin(url, {
233                    notCheckVersion: Config.get(
234                        'setting.basic.notCheckPluginVersion',
235                    ),
236                })
237                    .then(res => {
238                        Toast.success(`插件「${res.name}」安装成功~`);
239                    })
240                    .catch(e => {
241                        console.log(e);
242                        Toast.warn(e?.message ?? '无法识别此插件');
243                    });
244            } else if (supportLocalMediaType.some(it => url.endsWith(it))) {
245                // 本地播放
246                const musicItem = await PluginManager.getByHash(
247                    localPluginHash,
248                )?.instance?.importMusicItem?.(url);
249                console.log(musicItem);
250                if (musicItem) {
251                    TrackPlayer.play(musicItem);
252                }
253            }
254        } catch {}
255    }
256
257    // 开启监听
258    Linking.addEventListener('url', data => {
259        if (data.url) {
260            handleLinkingUrl(data.url);
261        }
262    });
263    const initUrl = await Linking.getInitialURL();
264    if (initUrl) {
265        handleLinkingUrl(initUrl);
266    }
267
268    if (Config.get('setting.basic.autoPlayWhenAppStart')) {
269        TrackPlayer.play();
270    }
271}
272