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