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