314 lines
10 KiB
TypeScript
314 lines
10 KiB
TypeScript
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();
|
|
}
|