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