import "react-native-get-random-values"; import { getCurrentDialog, showDialog } from "@/components/dialogs/useDialog.ts"; import { ImgAsset } from "@/constants/assetsConst"; import { emptyFunction, localPluginHash, supportLocalMediaType } from "@/constants/commonConst"; import pathConst from "@/constants/pathConst"; import Config from "@/core/appConfig"; import downloader, { DownloadFailReason, DownloaderEvent } from "@/core/downloader"; import LocalMusicSheet from "@/core/localMusicSheet"; import lyricManager from "@/core/lyricManager"; import musicHistory from "@/core/musicHistory"; import MusicSheet from "@/core/musicSheet"; import PluginManager from "@/core/pluginManager"; import Theme from "@/core/theme"; import TrackPlayer from "@/core/trackPlayer"; import NativeUtils from "@/native/utils"; import { checkAndCreateDir } from "@/utils/fileUtils"; import { errorLog, trace } from "@/utils/log"; import { IPerfLogger, perfLogger } from "@/utils/perfLogger"; import PersistStatus from "@/utils/persistStatus"; import Toast from "@/utils/toast"; import * as SplashScreen from "expo-splash-screen"; import { Linking, Platform } from "react-native"; import { PERMISSIONS, check, request } from "react-native-permissions"; import RNTrackPlayer, { AppKilledPlaybackBehavior, Capability } from "react-native-track-player"; import i18n from "@/core/i18n"; import bootstrapAtom from "./bootstrap.atom"; import { getTrackPlayerOptionPayload } from "./trackPlayerOptions"; import { getDefaultStore } from "jotai"; // 依赖管理 PluginManager.injectDependencies(Config); musicHistory.injectDependencies(Config); TrackPlayer.injectDependencies(Config, musicHistory, PluginManager); downloader.injectDependencies(Config, PluginManager); lyricManager.injectDependencies(TrackPlayer, Config, PluginManager); MusicSheet.injectDependencies(Config); async function bootstrapImpl() { await SplashScreen.preventAutoHideAsync() .then(result => console.log( `SplashScreen.preventAutoHideAsync() succeeded: ${result}`, ), ) .catch(console.warn); // it's good to explicitly catch and inspect any error const logger = perfLogger(); // 1. 检查权限 if (Platform.OS === "android" && Platform.Version >= 30) { const hasPermission = await NativeUtils.checkStoragePermission(); if ( !hasPermission && !PersistStatus.get("app.skipBootstrapStorageDialog") ) { showDialog("CheckStorage"); } } else { const [readStoragePermission, writeStoragePermission] = await Promise.all([ check(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE), check(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE), ]); if ( !( readStoragePermission === "granted" && writeStoragePermission === "granted" ) ) { await request(PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE); await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE); } } logger.mark("权限检查完成"); // 2. 数据初始化 /** 初始化路径 */ await setupFolder(); trace("文件夹初始化完成"); logger.mark("文件夹初始化完成"); // 加载配置 await Promise.all([ Config.setup().then(() => { logger.mark("Config"); }), MusicSheet.setup().then(() => { logger.mark("MusicSheet"); }), musicHistory.setup().then(() => { logger.mark("musicHistory"); }), ]); trace("配置初始化完成"); logger.mark("配置初始化完成"); // 加载插件 await PluginManager.setup(); logger.mark("插件初始化完成"); trace("插件初始化完成"); await initTrackPlayer(logger).catch(err => { // 初始化播放器出错,延迟初始化 const bootstrapState = getDefaultStore().get(bootstrapAtom); if (bootstrapState.state === "Loading") { getDefaultStore().set(bootstrapAtom, { state: "TrackPlayerError", reason: err, }); } }); await LocalMusicSheet.setup(); trace("本地音乐初始化完成"); logger.mark("本地音乐初始化完成"); Theme.setup(); trace("主题初始化完成"); logger.mark("主题初始化完成"); extraMakeup(); i18n.setup(); logger.mark("语言模块初始化完成"); ErrorUtils.setGlobalHandler(error => { errorLog("未捕获的错误", error); }); } /** 初始化 */ async function setupFolder() { await Promise.all([ checkAndCreateDir(pathConst.dataPath), checkAndCreateDir(pathConst.logPath), checkAndCreateDir(pathConst.cachePath), checkAndCreateDir(pathConst.pluginPath), checkAndCreateDir(pathConst.lrcCachePath), checkAndCreateDir(pathConst.downloadCachePath), checkAndCreateDir(pathConst.localLrcPath), checkAndCreateDir(pathConst.downloadPath).then(() => { checkAndCreateDir(pathConst.downloadMusicPath); }), ]); } export async function initTrackPlayer(logger?: IPerfLogger) { try { await RNTrackPlayer.setupPlayer({ maxCacheSize: Config.getConfig("basic.maxCacheSize") ?? 1024 * 1024 * 512, }); } catch (e: any) { if ( e?.message !== "The player has already been initialized via setupPlayer." ) { throw e; } } logger?.mark("加载播放器"); await RNTrackPlayer.updateOptions({ icon: ImgAsset.logoTransparent, progressUpdateEventInterval: 1, ...getTrackPlayerOptionPayload( !!Config.getConfig("basic.showExitOnNotification"), Capability, AppKilledPlaybackBehavior, ), }); logger?.mark("播放器初始化完成"); trace("播放器初始化完成"); await TrackPlayer.setupTrackPlayer(); trace("播放列表初始化完成"); logger?.mark("播放列表初始化完成"); await lyricManager.setup(); logger?.mark("歌词初始化完成"); } /** 不需要阻塞的 */ async function extraMakeup() { // 自动更新 try { if (Config.getConfig("basic.autoUpdatePlugin")) { const lastUpdated = PersistStatus.get("app.pluginUpdateTime") || 0; const now = Date.now(); if (Math.abs(now - lastUpdated) > 86400000) { PersistStatus.set("app.pluginUpdateTime", now); const plugins = PluginManager.getEnabledPlugins(); for (let i = 0; i < plugins.length; ++i) { const srcUrl = plugins[i].instance.srcUrl; if (srcUrl) { // 静默失败 await PluginManager.installPluginFromUrl(srcUrl).catch(emptyFunction); } } } } } catch { } async function handleLinkingUrl(url: string) { // 插件 try { if (url.startsWith("musicfree://install/")) { const plugins = url .slice(20) .split(",") .map(decodeURIComponent); await Promise.all( plugins.map(it => PluginManager.installPluginFromUrl(it).catch(emptyFunction), ), ); Toast.success("安装成功~"); } else if (url.endsWith(".js")) { PluginManager.installPluginFromLocalFile(url, { notCheckVersion: Config.getConfig( "basic.notCheckPluginVersion", ), }) .then(res => { if (res.success) { Toast.success(`插件「${res.pluginName}」安装成功~`); } else { Toast.warn("安装失败: " + res.message); } }) .catch(e => { console.log(e); Toast.warn(e?.message ?? "无法识别此插件"); }); } else if (supportLocalMediaType.some(it => url.endsWith(it))) { // 本地播放 const musicItem = await PluginManager.getByHash( localPluginHash, )?.instance?.importMusicItem?.(url); console.log(musicItem); if (musicItem) { TrackPlayer.play(musicItem); } } } catch { } } // 开启监听 Linking.addEventListener("url", data => { if (data.url) { handleLinkingUrl(data.url); } }); const initUrl = await Linking.getInitialURL(); if (initUrl) { handleLinkingUrl(initUrl); } if (Config.getConfig("basic.autoPlayWhenAppStart")) { TrackPlayer.play(); } } function bindEvents() { // 下载事件 downloader.on(DownloaderEvent.DownloadError, (reason) => { if (reason === DownloadFailReason.NetworkOffline) { Toast.warn("当前无网络连接,请等待网络恢复后重试"); } else if (reason === DownloadFailReason.NotAllowToDownloadInCellular) { if (getCurrentDialog()?.name !== "SimpleDialog") { showDialog("SimpleDialog", { title: "流量提醒", content: "当前非WIFI环境,为节省流量,请到侧边栏设置中打开【使用移动网络下载】功能后方可继续下载", }); } } }); downloader.on(DownloaderEvent.DownloadQueueCompleted, () => { Toast.success("下载任务已完成"); }); } export default async function () { try { getDefaultStore().set(bootstrapAtom, { "state": "Loading", }); await bootstrapImpl(); bindEvents(); getDefaultStore().set(bootstrapAtom, { "state": "Done", }); } catch (e: any) { errorLog("初始化出错", e); if (getDefaultStore().get(bootstrapAtom).state === "Loading") { getDefaultStore().set(bootstrapAtom, { state: "Fatal", reason: e, }); } } // 隐藏开屏动画 console.log("HIDE"); await SplashScreen.hideAsync(); }