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