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.ts"; 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 { emptyFunction, localPluginHash, supportLocalMediaType } from "@/constants/commonConst"; 17import TrackPlayer from "@/core/trackPlayer"; 18import musicHistory from "@/core/musicHistory"; 19import PersistStatus from "@/core/persistStatus.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 */ 30 31async function _bootstrap() { 32 await SplashScreen.preventAutoHideAsync() 33 .then(result => 34 console.log( 35 `SplashScreen.preventAutoHideAsync() succeeded: ${result}`, 36 ), 37 ) 38 .catch(console.warn); // it's good to explicitly catch and inspect any error 39 const logger = perfLogger(); 40 // 1. 检查权限 41 if (Platform.OS === 'android' && Platform.Version >= 30) { 42 const hasPermission = await NativeUtils.checkStoragePermission(); 43 if ( 44 !hasPermission && 45 !PersistStatus.get('app.skipBootstrapStorageDialog') 46 ) { 47 showDialog('CheckStorage'); 48 } 49 } else { 50 const [readStoragePermission, writeStoragePermission] = 51 await Promise.all([ 52 check(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE), 53 check(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE), 54 ]); 55 if ( 56 !( 57 readStoragePermission === 'granted' && 58 writeStoragePermission === 'granted' 59 ) 60 ) { 61 await request(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE); 62 await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); 63 } 64 } 65 logger.mark('权限检查完成'); 66 67 // 2. 数据初始化 68 /** 初始化路径 */ 69 await setupFolder(); 70 trace('文件夹初始化完成'); 71 logger.mark('文件夹初始化完成'); 72 73 // 加载配置 74 await Promise.all([ 75 Config.setup().then(() => { 76 logger.mark('Config'); 77 }), 78 MusicSheet.setup().then(() => { 79 logger.mark('MusicSheet'); 80 }), 81 musicHistory.setupMusicHistory().then(() => { 82 logger.mark('musicHistory'); 83 }), 84 ]); 85 trace('配置初始化完成'); 86 logger.mark('配置初始化完成'); 87 88 // 加载插件 89 try { 90 await RNTrackPlayer.setupPlayer({ 91 maxCacheSize: 92 Config.getConfig('basic.maxCacheSize') ?? 1024 * 1024 * 512, 93 }); 94 } catch (e: any) { 95 if ( 96 e?.message !== 97 'The player has already been initialized via setupPlayer.' 98 ) { 99 throw e; 100 } 101 } 102 logger.mark('加载播放器'); 103 104 const capabilities = Config.getConfig('basic.showExitOnNotification') 105 ? [ 106 Capability.Play, 107 Capability.Pause, 108 Capability.SkipToNext, 109 Capability.SkipToPrevious, 110 Capability.Stop, 111 ] 112 : [ 113 Capability.Play, 114 Capability.Pause, 115 Capability.SkipToNext, 116 Capability.SkipToPrevious, 117 ]; 118 await RNTrackPlayer.updateOptions({ 119 icon: ImgAsset.logoTransparent, 120 progressUpdateEventInterval: 1, 121 android: { 122 alwaysPauseOnInterruption: true, 123 appKilledPlaybackBehavior: 124 AppKilledPlaybackBehavior.ContinuePlayback, 125 }, 126 capabilities: capabilities, 127 compactCapabilities: capabilities, 128 notificationCapabilities: [...capabilities, Capability.SeekTo], 129 }); 130 logger.mark('播放器初始化完成'); 131 trace('播放器初始化完成'); 132 133 await PluginManager.setup(); 134 logger.mark('插件初始化完成'); 135 136 trace('插件初始化完成'); 137 await TrackPlayer.setupTrackPlayer(); 138 trace('播放列表初始化完成'); 139 logger.mark('播放列表初始化完成'); 140 141 await LocalMusicSheet.setup(); 142 trace('本地音乐初始化完成'); 143 logger.mark('本地音乐初始化完成'); 144 145 Theme.setup(); 146 trace('主题初始化完成'); 147 logger.mark('主题初始化完成'); 148 149 await LyricManager.setup(); 150 151 logger.mark('歌词初始化完成'); 152 153 extraMakeup(); 154 ErrorUtils.setGlobalHandler(error => { 155 errorLog('未捕获的错误', error); 156 }); 157} 158 159/** 初始化 */ 160async function setupFolder() { 161 await Promise.all([ 162 checkAndCreateDir(pathConst.dataPath), 163 checkAndCreateDir(pathConst.logPath), 164 checkAndCreateDir(pathConst.cachePath), 165 checkAndCreateDir(pathConst.pluginPath), 166 checkAndCreateDir(pathConst.lrcCachePath), 167 checkAndCreateDir(pathConst.downloadCachePath), 168 checkAndCreateDir(pathConst.localLrcPath), 169 checkAndCreateDir(pathConst.downloadPath).then(() => { 170 checkAndCreateDir(pathConst.downloadMusicPath); 171 }), 172 ]); 173} 174 175export default async function () { 176 try { 177 await _bootstrap(); 178 } catch (e) { 179 errorLog('初始化出错', e); 180 } 181 // 隐藏开屏动画 182 console.log('HIDE'); 183 await SplashScreen.hideAsync(); 184} 185 186/** 不需要阻塞的 */ 187async function extraMakeup() { 188 // 自动更新 189 try { 190 // 初始化网络状态 191 Network.setup(); 192 193 if (Config.getConfig('basic.autoUpdatePlugin')) { 194 const lastUpdated = PersistStatus.get('app.pluginUpdateTime') || 0; 195 const now = Date.now(); 196 if (Math.abs(now - lastUpdated) > 86400000) { 197 PersistStatus.set('app.pluginUpdateTime', now); 198 const plugins = PluginManager.getValidPlugins(); 199 for (let i = 0; i < plugins.length; ++i) { 200 const srcUrl = plugins[i].instance.srcUrl; 201 if (srcUrl) { 202 // 静默失败 203 await PluginManager.installPluginFromUrl(srcUrl).catch(emptyFunction); 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(emptyFunction), 221 ), 222 ); 223 Toast.success('安装成功~'); 224 } else if (url.endsWith('.js')) { 225 PluginManager.installPluginFromLocalFile(url, { 226 notCheckVersion: Config.getConfig( 227 '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.getConfig('basic.autoPlayWhenAppStart')) { 262 TrackPlayer.play(); 263 } 264} 265