forked from miao-moe/QZMusic_PC
fork(fix): Clone AMLL 并修复 BUG
- 将AMLL Clone到本以地进行修复和优化(emm虽然这很不优雅但是暂时无时间做子模块和Fork) - 修复在当前播放歌词行不可见的视口Seek会出现滚动偏移的问题
This commit is contained in:
76
amll-local/packages/vue/CHANGELOG.md
Normal file
76
amll-local/packages/vue/CHANGELOG.md
Normal file
@@ -0,0 +1,76 @@
|
||||
## 0.5.1 (2026-05-17)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **fix:** 修复含对唱时歌词错误提前导致的多行高亮 ([#521](https://github.com/amll-dev/applemusic-like-lyrics/pull/521))
|
||||
- **refactor:** 引入歌词组来包装主歌词和背景人声 & 前置背景人声 ([#531](https://github.com/amll-dev/applemusic-like-lyrics/pull/531))
|
||||
- **chore:** 更正 package.json 协议声明 ([#534](https://github.com/amll-dev/applemusic-like-lyrics/pull/534))
|
||||
|
||||
仓库根目录的 LICENSE 文件为 AGPL v3.0 协议,但是 package.json 中的 `license` 字段为 `GPL-3.0`。经与原开发者确认,package.json 中的 `license` 字段有误。仓库与其所有产出的 npm 包均应为 AGPL v3 only 协议,SPDX: `AGPL-3.0-only`。因此,更正各包 `package.json` 的 `license` 字段为 `AGPL-3.0-only`。
|
||||
- **chore(core):** 优化类型定义 ([#519](https://github.com/amll-dev/applemusic-like-lyrics/pull/519))
|
||||
|
||||
### Contributors
|
||||
|
||||
- apoint123 [@apoint123](https://github.com/apoint123)
|
||||
- Linho [@Linho1219](https://github.com/Linho1219)
|
||||
|
||||
## 0.5.0 (2026-05-12)
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- **refactor:** 整理核心播放器代码结构,将抽象接口部分集中到统一目录 ([#508](https://github.com/amll-dev/applemusic-like-lyrics/pull/508))
|
||||
- **refactor:** 整理核心播放器抽象类中时间线、滚动与单行布局部分的结构与状态管理 ([#509](https://github.com/amll-dev/applemusic-like-lyrics/pull/509))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **fix:** 修复 setCurrentTime 在提供 isSeek 标志时,实际排版未遵守标志导致布局异常漂移的问题 ([#509](https://github.com/amll-dev/applemusic-like-lyrics/pull/509))
|
||||
- **fix:** 修复在同一行时间内拖拽进度条时逐字动画不同步的问题 ([#509](https://github.com/amll-dev/applemusic-like-lyrics/pull/509))
|
||||
- **fix:** 修复暂停状态下点击行跳转时仍播放逐字动画的问题 ([#509](https://github.com/amll-dev/applemusic-like-lyrics/pull/509))
|
||||
|
||||
### Contributors
|
||||
|
||||
- Linho [@Linho1219](https://github.com/Linho1219)
|
||||
|
||||
## 0.4.2 (2026-05-01)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **feat(core):** 平衡行长度时优先在标点处换行 ([#503](https://github.com/amll-dev/applemusic-like-lyrics/pull/503))
|
||||
- **fix:** 修复背景行注音高度错误 ([#497](https://github.com/amll-dev/applemusic-like-lyrics/pull/497))
|
||||
- **fix(core):** 修正平衡行长度时的行宽度计算 ([#502](https://github.com/amll-dev/applemusic-like-lyrics/pull/502))
|
||||
|
||||
### Contributors
|
||||
|
||||
- apoint123 [@apoint123](https://github.com/apoint123)
|
||||
- Linho [@Linho1219](https://github.com/Linho1219)
|
||||
|
||||
## 0.4.1 (2026-04-23)
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **fix:** 在各绑定中暴露歌词优化选项 ([#492](https://github.com/amll-dev/applemusic-like-lyrics/pull/492))
|
||||
- **fix(vue):** 修复掩码模式错误的类型 ([#496](https://github.com/amll-dev/applemusic-like-lyrics/pull/496))
|
||||
- **refactor(core):** 重构平均行长度实现 ([#494](https://github.com/amll-dev/applemusic-like-lyrics/pull/494))
|
||||
|
||||
### Contributors
|
||||
|
||||
- apoint123 [@apoint123](https://github.com/apoint123)
|
||||
|
||||
## 0.4.0 (2026-04-14)
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- **chore:** 移除 canvas 歌词渲染器 ([#476](https://github.com/amll-dev/applemusic-like-lyrics/pull/476))
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **refactor:** 重构核心库测试组织模式 ([3db83c93](https://github.com/amll-dev/applemusic-like-lyrics/commit/3db83c93))
|
||||
- **docs:** 修正 optimize-lyric.ts 和 OptimizeLyricOptions 里 cleanUnintentionalOverlaps 的文档和注释 ([75a8c0bb](https://github.com/amll-dev/applemusic-like-lyrics/commit/75a8c0bb))
|
||||
- **chore:** 更换工具链 ([#476](https://github.com/amll-dev/applemusic-like-lyrics/pull/476))
|
||||
- **chore:** 在项目范围内启用 isolatedDeclarations ([#480](https://github.com/amll-dev/applemusic-like-lyrics/pull/480))
|
||||
|
||||
### Contributors
|
||||
|
||||
- apoint123 [@apoint123](https://github.com/apoint123)
|
||||
- Linho [@Linho1219](https://github.com/Linho1219)
|
||||
- MoYingJi [@MoYingJi](https://github.com/MoYingJi)
|
||||
52
amll-local/packages/vue/README-CN.md
Normal file
52
amll-local/packages/vue/README-CN.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# AMLL for Vue
|
||||
|
||||
[English](./README.md) / 简体中文
|
||||
|
||||
> 警告:此为个人项目,且尚未完成开发,可能仍有大量问题,所以请勿直接用于生产环境!
|
||||
|
||||

|
||||
[](https://www.npmjs.com/package/@applemusic-like-lyrics/vue)
|
||||
[](https://www.npmjs.com/package/@applemusic-like-lyrics/vue)
|
||||
|
||||
AMLL 组件库的 Vue 绑定,你可以通过此库来更加方便地使用 AMLL 歌词组件。
|
||||
|
||||
详情可以访问 [Core 核心组件的 README.md](../core/README-CN.md)。
|
||||
|
||||
## 安装
|
||||
|
||||
安装使用的依赖(如果以下列出的依赖包没有安装的话需要自行安装):
|
||||
```bash
|
||||
npm install @pixi/app @pixi/core @pixi/display @pixi/filter-blur @pixi/filter-bulge-pinch @pixi/filter-color-matrix @pixi/sprite jss jss-preset-default # 使用 npm
|
||||
yarn add @pixi/app @pixi/core @pixi/display @pixi/filter-blur @pixi/filter-bulge-pinch @pixi/filter-color-matrix @pixi/sprite jss jss-preset-default # 使用 yarn
|
||||
```
|
||||
|
||||
安装 Vue 绑定需要使用的依赖(如果以下列出的依赖包没有安装的话需要自行安装):
|
||||
```bash
|
||||
npm install vue # 使用 npm
|
||||
yarn add vue # 使用 yarn
|
||||
```
|
||||
|
||||
安装本体框架:
|
||||
```bash
|
||||
npm install @applemusic-like-lyrics/vue # 使用 npm
|
||||
yarn add @applemusic-like-lyrics/vue # 使用 yarn
|
||||
```
|
||||
|
||||
## 使用方式摘要
|
||||
|
||||
由于 Vue 组件不方便生成 API 文档,所以还请自行查阅类型定义文件确定用法。
|
||||
|
||||
(或者参考 React 绑定,二者属性和引用方式完全一致)
|
||||
|
||||
一个测试用途的程序可以在 [../playground/vue/src/test.ts](../playground/vue/src/test.ts) 里找到。
|
||||
|
||||
```vue
|
||||
<tamplate>
|
||||
<LyricPlayer :lyric-lines="[]" :current-time="0" />
|
||||
</tamplate>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LyricPlayer } from "@applemusic-like-lyrics/vue";
|
||||
|
||||
</script>
|
||||
```
|
||||
52
amll-local/packages/vue/README.md
Normal file
52
amll-local/packages/vue/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# AMLL for Vue
|
||||
|
||||
English / [简体中文](./README-CN.md)
|
||||
|
||||
> Warning: This is a personal project and is still under development. There may still be many issues, so please do not use it directly in production environments!
|
||||
|
||||

|
||||
[](https://www.npmjs.com/package/@applemusic-like-lyrics/vue)
|
||||
[](https://www.npmjs.com/package/@applemusic-like-lyrics/vue)
|
||||
|
||||
Vue binding for the AMLL component library, which allows you to use AMLL lyric components more conveniently.
|
||||
|
||||
For more details, please visit [Core component README.md](../core/README.md).
|
||||
|
||||
## Installation
|
||||
|
||||
Install the required dependencies (if the dependencies listed below are not installed, you need to install them yourself):
|
||||
```bash
|
||||
npm install @pixi/app @pixi/core @pixi/display @pixi/filter-blur @pixi/filter-bulge-pinch @pixi/filter-color-matrix @pixi/sprite jss jss-preset-default # using npm
|
||||
yarn add @pixi/app @pixi/core @pixi/display @pixi/filter-blur @pixi/filter-bulge-pinch @pixi/filter-color-matrix @pixi/sprite jss jss-preset-default # using yarn
|
||||
```
|
||||
|
||||
Install the dependencies required for Vue binding (if the dependencies listed below are not installed, you need to install them yourself):
|
||||
```bash
|
||||
npm install vue # using npm
|
||||
yarn add vue # using yarn
|
||||
```
|
||||
|
||||
Install the framework:
|
||||
```bash
|
||||
npm install @applemusic-like-lyrics/vue # using npm
|
||||
yarn add @applemusic-like-lyrics/vue # using yarn
|
||||
```
|
||||
|
||||
## Usage Summary
|
||||
|
||||
Since Vue components are not convenient for generating API documentation, please refer to the type definition files to determine usage.
|
||||
|
||||
(Or refer to the React binding, as both have identical properties and reference methods)
|
||||
|
||||
A test program can be found in [../playground/vue/src/test.ts](../playground/vue/src/test.ts).
|
||||
|
||||
```vue
|
||||
<tamplate>
|
||||
<LyricPlayer :lyric-lines="[]" :current-time="0" />
|
||||
</tamplate>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LyricPlayer } from "@applemusic-like-lyrics/vue";
|
||||
|
||||
</script>
|
||||
```
|
||||
71
amll-local/packages/vue/package.json
Normal file
71
amll-local/packages/vue/package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "@applemusic-like-lyrics/vue",
|
||||
"version": "0.5.1",
|
||||
"description": "AMLL 组件库的 Vue 绑定",
|
||||
"repository": {
|
||||
"url": "https://github.com/amll-dev/applemusic-like-lyrics.git",
|
||||
"directory": "packages/vue",
|
||||
"type": "git"
|
||||
},
|
||||
"type": "module",
|
||||
"license": "AGPL-3.0-only",
|
||||
"nx": {
|
||||
"tags": [
|
||||
"library"
|
||||
],
|
||||
"targets": {
|
||||
"nx-release-publish": {
|
||||
"executor": "@nx/js:release-publish",
|
||||
"dependsOn": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"typecheck": "tsgo -b",
|
||||
"build-only": "tsdown",
|
||||
"build": "run-p typecheck \"build-only {@}\" --",
|
||||
"build:dev": "tsdown",
|
||||
"fmt": "biome format --write ./src",
|
||||
"dev": "nx run @applemusic-like-lyrics/playground-vue:dev"
|
||||
},
|
||||
"main": "./dist/amll-vue.cjs",
|
||||
"module": "./dist/amll-vue.mjs",
|
||||
"typings": "./dist/amll-vue.d.mts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/amll-vue.d.mts",
|
||||
"default": "./dist/amll-vue.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/amll-vue.d.cts",
|
||||
"default": "./dist/amll-vue.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@applemusic-like-lyrics/core": "file:../core"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applemusic-like-lyrics/core": "file:../core",
|
||||
"@biomejs/biome": "^2.4.8",
|
||||
"@pixi/app": "^7.4.3",
|
||||
"@pixi/core": "^7.4.3",
|
||||
"@pixi/display": "^7.4.3",
|
||||
"@pixi/filter-blur": "^7.4.3",
|
||||
"@pixi/filter-color-matrix": "^7.4.3",
|
||||
"@pixi/sprite": "^7.4.3",
|
||||
"@rolldown/plugin-babel": "^0.2.3",
|
||||
"@vue/babel-plugin-jsx": "^2.0.1",
|
||||
"tsdown": "^0.22.0",
|
||||
"typedoc": "^0.28.17",
|
||||
"typedoc-plugin-markdown": "^4.11.0",
|
||||
"vue": "^3.5.30"
|
||||
}
|
||||
}
|
||||
182
amll-local/packages/vue/src/BackgroundRender.tsx
Normal file
182
amll-local/packages/vue/src/BackgroundRender.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import {
|
||||
type AbstractBaseRenderer,
|
||||
type BaseRenderer,
|
||||
BackgroundRender as CoreBackgroundRender,
|
||||
MeshGradientRenderer,
|
||||
} from "@applemusic-like-lyrics/core";
|
||||
import {
|
||||
defineComponent,
|
||||
type ExtractPublicPropTypes,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
type PropType,
|
||||
type Ref,
|
||||
ref,
|
||||
type ShallowRef,
|
||||
useTemplateRef,
|
||||
watchEffect,
|
||||
} from "vue";
|
||||
|
||||
/**
|
||||
* 背景渲染组件的引用
|
||||
*/
|
||||
export interface BackgroundRenderRef {
|
||||
/**
|
||||
* 背景渲染实例引用
|
||||
*/
|
||||
bgRender?: Ref<AbstractBaseRenderer | undefined>;
|
||||
/**
|
||||
* 将背景渲染实例的元素包裹起来的 DIV 元素实例
|
||||
*/
|
||||
wrapperEl: Readonly<ShallowRef<HTMLDivElement | null>>;
|
||||
}
|
||||
|
||||
const backgroundRenderProps = {
|
||||
/**
|
||||
* 设置背景专辑资源
|
||||
*/
|
||||
album: {
|
||||
type: [String, Object] as PropType<
|
||||
string | HTMLImageElement | HTMLVideoElement
|
||||
>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置专辑资源是否为视频
|
||||
*/
|
||||
albumIsVideo: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前背景动画帧率,如果为 `undefined` 则默认为 `30`
|
||||
*/
|
||||
fps: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前播放状态,如果为 `undefined` 则默认为 `true`
|
||||
*/
|
||||
playing: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前动画流动速度,如果为 `undefined` 则默认为 `2`
|
||||
*/
|
||||
flowSpeed: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置背景是否根据“是否有歌词”这个特征调整自身效果,例如有歌词时会变得更加活跃
|
||||
*
|
||||
* 部分渲染器会根据这个特征调整自身效果
|
||||
*
|
||||
* 如果不确定是否需要赋值或无法知晓是否包含歌词,请传入 true 或不做任何处理(默认值为 true)
|
||||
*/
|
||||
hasLyric: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置低频的音量大小,范围在 80hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
|
||||
*
|
||||
* 部分渲染器会根据音量大小调整背景效果(例如根据鼓点跳动)
|
||||
*
|
||||
* 如果无法获取到类似的数据,请传入 undefined 或 1.0 作为默认值,或不做任何处理(默认值即 1.0)
|
||||
*/
|
||||
lowFreqVolume: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前渲染缩放比例,如果为 `undefined` 则默认为 `0.5`
|
||||
*/
|
||||
renderScale: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置渲染器,如果为 `undefined` 则默认为 `MeshGradientRenderer`
|
||||
* 默认渲染器有可能会随着版本更新而更换
|
||||
*/
|
||||
renderer: {
|
||||
type: Object as PropType<{
|
||||
new (...args: ConstructorParameters<typeof BaseRenderer>): BaseRenderer;
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type BackgroundRenderProps = ExtractPublicPropTypes<
|
||||
typeof backgroundRenderProps
|
||||
>;
|
||||
|
||||
export const BackgroundRender = defineComponent({
|
||||
name: "BackgroundRender",
|
||||
props: backgroundRenderProps,
|
||||
setup(props, { expose }) {
|
||||
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper-ref");
|
||||
const bgRenderRef = ref<AbstractBaseRenderer>();
|
||||
|
||||
onMounted(() => {
|
||||
if (wrapperRef.value) {
|
||||
bgRenderRef.value = CoreBackgroundRender.new(
|
||||
props.renderer ?? MeshGradientRenderer,
|
||||
);
|
||||
const el = bgRenderRef.value.getElement();
|
||||
el.style.width = "100%";
|
||||
el.style.height = "100%";
|
||||
wrapperRef.value.appendChild(el);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (bgRenderRef.value) {
|
||||
bgRenderRef.value.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.album)
|
||||
bgRenderRef.value?.setAlbum(props.album, props.albumIsVideo);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.fps) bgRenderRef.value?.setFPS(props.fps);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.playing) bgRenderRef.value?.pause();
|
||||
else bgRenderRef.value?.resume();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.flowSpeed) bgRenderRef.value?.setFlowSpeed(props.flowSpeed);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.renderScale)
|
||||
bgRenderRef.value?.setRenderScale(props.renderScale);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.lowFreqVolume)
|
||||
bgRenderRef.value?.setLowFreqVolume(props.lowFreqVolume);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.hasLyric !== undefined)
|
||||
bgRenderRef.value?.setHasLyric(props.hasLyric ?? true);
|
||||
});
|
||||
|
||||
expose<BackgroundRenderRef>({
|
||||
bgRender: bgRenderRef,
|
||||
wrapperEl: wrapperRef,
|
||||
});
|
||||
|
||||
return () => <div style="display: contents;" ref="wrapper-ref" />;
|
||||
},
|
||||
});
|
||||
387
amll-local/packages/vue/src/LyricPlayer.tsx
Normal file
387
amll-local/packages/vue/src/LyricPlayer.tsx
Normal file
@@ -0,0 +1,387 @@
|
||||
import {
|
||||
type BaseRenderer,
|
||||
LyricPlayer as CoreLyricPlayer,
|
||||
type LyricLine,
|
||||
type LyricLineMouseEvent,
|
||||
type LyricPlayerBase,
|
||||
MaskObsceneWordsMode,
|
||||
type OptimizeLyricOptions,
|
||||
type spring,
|
||||
} from "@applemusic-like-lyrics/core";
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
type ExtractPublicPropTypes,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
type PropType,
|
||||
type Ref,
|
||||
ref,
|
||||
type ShallowRef,
|
||||
type SlotsType,
|
||||
Teleport,
|
||||
useTemplateRef,
|
||||
watch,
|
||||
watchEffect,
|
||||
} from "vue";
|
||||
|
||||
const lyricPlayerProps = {
|
||||
/**
|
||||
* 是否禁用歌词播放组件,默认为 `false`,歌词组件启用后将会开始逐帧更新歌词的动画效果,并对传入的其他参数变更做出反馈。
|
||||
*
|
||||
* 如果禁用了歌词组件动画,你也可以通过引用取得原始渲染组件实例,手动逐帧调用其 `update` 函数来更新动画效果。
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 是否演出部分效果,目前会控制播放间奏点的动画的播放暂停与否,默认为 `true`
|
||||
*/
|
||||
playing: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 设置歌词行的对齐方式,如果为 `undefined` 则默认为 `center`
|
||||
*
|
||||
* - 设置成 `top` 的话将会向目标歌词行的顶部对齐
|
||||
* - 设置成 `bottom` 的话将会向目标歌词行的底部对齐
|
||||
* - 设置成 `center` 的话将会向目标歌词行的垂直中心对齐
|
||||
*/
|
||||
alignAnchor: {
|
||||
type: String as PropType<"top" | "bottom" | "center">,
|
||||
default: "center",
|
||||
},
|
||||
/**
|
||||
* 设置默认的歌词行对齐位置,相对于整个歌词播放组件的大小位置,如果为 `undefined`
|
||||
* 则默认为 `0.5`
|
||||
*
|
||||
* 可以设置一个 `[0.0-1.0]` 之间的任意数字,代表组件高度由上到下的比例位置
|
||||
*/
|
||||
alignPosition: {
|
||||
type: Number,
|
||||
default: 0.5,
|
||||
},
|
||||
/**
|
||||
* 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
|
||||
*
|
||||
* 如果启用,则会通过弹簧算法实时处理歌词位置,但是需要性能足够强劲的电脑方可流畅运行
|
||||
*
|
||||
* 如果不启用,则会回退到基于 `transition` 的过渡效果,对低性能的机器比较友好,但是效果会比较单一
|
||||
*/
|
||||
enableSpring: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 设置是否启用歌词行的模糊效果,默认为 `true`
|
||||
*/
|
||||
enableBlur: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 设置是否使用物理弹簧算法实现歌词动画效果,默认启用
|
||||
*
|
||||
* 如果启用,则会通过弹簧算法实时处理歌词位置,但是需要性能足够强劲的电脑方可流畅运行
|
||||
*
|
||||
* 如果不启用,则会回退到基于 `transition` 的过渡效果,对低性能的机器比较友好,但是效果会比较单一
|
||||
*/
|
||||
enableScale: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* 设置是否隐藏已经播放过的歌词行,默认不隐藏
|
||||
*/
|
||||
hidePassedLines: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* 设置歌词中不雅用语的掩码模式,默认为 `MaskObsceneWordsMode.Disabled`,即不掩码
|
||||
*/
|
||||
maskObsceneWordsMode: {
|
||||
type: String as PropType<MaskObsceneWordsMode>,
|
||||
default: MaskObsceneWordsMode.Disabled,
|
||||
},
|
||||
/**
|
||||
* 设置歌词优化选项
|
||||
*/
|
||||
optimizeOptions: {
|
||||
type: Object as PropType<OptimizeLyricOptions>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前播放歌词,要注意传入后这个数组内的信息不得修改,否则会发生错误
|
||||
*/
|
||||
lyricLines: {
|
||||
type: Object as PropType<LyricLine[]>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置当前播放进度,单位为毫秒且**必须是整数**,此时将会更新内部的歌词进度信息
|
||||
* 内部会根据调用间隔和播放进度自动决定如何滚动和显示歌词,所以这个的调用频率越快越准确越好
|
||||
*/
|
||||
currentTime: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* 设置文字动画的渐变宽度,单位以歌词行的主文字字体大小的倍数为单位,默认为 0.5,即一个全角字符的一半宽度
|
||||
*
|
||||
* 如果要模拟 Apple Music for Android 的效果,可以设置为 1
|
||||
*
|
||||
* 如果要模拟 Apple Music for iPad 的效果,可以设置为 0.5
|
||||
*
|
||||
* 如果想要近乎禁用渐变效果,可以设置成非常接近 0 的小数(例如 `0.0001` ),但是**不可以为 0**
|
||||
*/
|
||||
wordFadeWidth: {
|
||||
type: Number,
|
||||
default: 0.5,
|
||||
},
|
||||
/**
|
||||
* 设置所有歌词行在横坐标上的弹簧属性,包括重量、弹力和阻力。
|
||||
*
|
||||
* @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
|
||||
*/
|
||||
linePosXSpringParams: {
|
||||
type: Object as PropType<Partial<spring.SpringParams>>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置所有歌词行在纵坐标上的弹簧属性,包括重量、弹力和阻力。
|
||||
*
|
||||
* @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
|
||||
*/
|
||||
linePosYSpringParams: {
|
||||
type: Object as PropType<Partial<spring.SpringParams>>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置所有歌词行在缩放大小上的弹簧属性,包括重量、弹力和阻力。
|
||||
*
|
||||
* @param params 需要设置的弹簧属性,提供的属性将会覆盖原来的属性,未提供的属性将会保持原样
|
||||
*/
|
||||
lineScaleSpringParams: {
|
||||
type: Object as PropType<Partial<spring.SpringParams>>,
|
||||
required: false,
|
||||
},
|
||||
/**
|
||||
* 设置渲染器,如果为 `undefined` 则默认为 `MeshGradientRenderer`
|
||||
* 默认渲染器有可能会随着版本更新而更换
|
||||
*/
|
||||
lyricPlayer: {
|
||||
type: Object as PropType<{
|
||||
new (...args: ConstructorParameters<typeof BaseRenderer>): BaseRenderer;
|
||||
}>,
|
||||
required: false,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 歌词播放组件的属性
|
||||
*/
|
||||
export type LyricPlayerProps = ExtractPublicPropTypes<typeof lyricPlayerProps>;
|
||||
|
||||
const lyricPlayerEmits = {
|
||||
lineClick: (_: LyricLineMouseEvent) => true,
|
||||
lineContextmenu: (_: LyricLineMouseEvent) => true,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 歌词播放组件的事件
|
||||
*/
|
||||
export type LyricPlayerEmits = typeof lyricPlayerEmits;
|
||||
|
||||
/**
|
||||
* 歌词播放组件的引用
|
||||
*/
|
||||
export interface LyricPlayerRef {
|
||||
/**
|
||||
* 歌词播放实例
|
||||
*/
|
||||
lyricPlayer: Ref<LyricPlayerBase | undefined>;
|
||||
/**
|
||||
* 将歌词播放实例的元素包裹起来的 DIV 元素实例
|
||||
*/
|
||||
wrapperEl: Readonly<ShallowRef<HTMLDivElement | null>>;
|
||||
}
|
||||
|
||||
export const LyricPlayer = defineComponent({
|
||||
name: "LyricPlayer",
|
||||
props: lyricPlayerProps,
|
||||
emits: lyricPlayerEmits,
|
||||
slots: Object as SlotsType<{
|
||||
"bottom-line": () => void;
|
||||
}>,
|
||||
setup(props, { expose, emit, attrs, slots }) {
|
||||
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper-ref");
|
||||
const playerRef = ref<CoreLyricPlayer>();
|
||||
|
||||
const lineClickHandler = (e: Event) =>
|
||||
emit("lineClick", e as LyricLineMouseEvent);
|
||||
const lineContextMenuHandler = (e: Event) =>
|
||||
emit("lineContextmenu", e as LyricLineMouseEvent);
|
||||
|
||||
onMounted(() => {
|
||||
const wrapper = wrapperRef.value;
|
||||
if (wrapper) {
|
||||
playerRef.value = new CoreLyricPlayer();
|
||||
wrapper.appendChild(playerRef.value.getElement());
|
||||
playerRef.value.addEventListener("line-click", lineClickHandler);
|
||||
playerRef.value.addEventListener(
|
||||
"line-contextmenu",
|
||||
lineContextMenuHandler,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (playerRef.value) {
|
||||
playerRef.value.removeEventListener("line-click", lineClickHandler);
|
||||
playerRef.value.removeEventListener(
|
||||
"line-contextmenu",
|
||||
lineContextMenuHandler,
|
||||
);
|
||||
playerRef.value.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect((onCleanup) => {
|
||||
if (!props.disabled) {
|
||||
let canceled = false;
|
||||
let lastTime = -1;
|
||||
const onFrame = (time: number) => {
|
||||
if (canceled) return;
|
||||
if (lastTime === -1) {
|
||||
lastTime = time;
|
||||
}
|
||||
playerRef.value?.update(time - lastTime);
|
||||
lastTime = time;
|
||||
requestAnimationFrame(onFrame);
|
||||
};
|
||||
requestAnimationFrame(onFrame);
|
||||
onCleanup(() => {
|
||||
canceled = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.playing !== undefined) {
|
||||
if (props.playing) {
|
||||
playerRef.value?.resume();
|
||||
} else {
|
||||
playerRef.value?.pause();
|
||||
}
|
||||
} else playerRef.value?.resume();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.alignAnchor !== undefined)
|
||||
playerRef.value?.setAlignAnchor(props.alignAnchor);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.hidePassedLines !== undefined)
|
||||
playerRef.value?.setHidePassedLines(props.hidePassedLines);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.maskObsceneWordsMode !== undefined)
|
||||
playerRef.value?.setMaskObsceneWords(props.maskObsceneWordsMode);
|
||||
else playerRef.value?.setMaskObsceneWords(MaskObsceneWordsMode.Disabled);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.alignPosition !== undefined)
|
||||
playerRef.value?.setAlignPosition(props.alignPosition);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.enableSpring !== undefined)
|
||||
playerRef.value?.setEnableSpring(props.enableSpring);
|
||||
else playerRef.value?.setEnableSpring(true);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.enableBlur !== undefined)
|
||||
playerRef.value?.setEnableBlur(props.enableBlur);
|
||||
else playerRef.value?.setEnableBlur(true);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.enableScale !== undefined)
|
||||
playerRef.value?.setEnableScale(props.enableScale);
|
||||
else playerRef.value?.setEnableScale(true);
|
||||
});
|
||||
|
||||
watch(
|
||||
[playerRef, () => props.lyricLines, () => props.optimizeOptions],
|
||||
([player, lyricLines, optimizeOptions]) => {
|
||||
if (!player) return;
|
||||
|
||||
if (optimizeOptions !== undefined) {
|
||||
player.setOptimizeOptions(optimizeOptions);
|
||||
}
|
||||
|
||||
if (lyricLines !== undefined) {
|
||||
player.setLyricLines(lyricLines);
|
||||
} else {
|
||||
player.setLyricLines([]);
|
||||
}
|
||||
|
||||
if (props.currentTime !== undefined) {
|
||||
player.setCurrentTime(props.currentTime, true);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.currentTime !== undefined)
|
||||
playerRef.value?.setCurrentTime(props.currentTime);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.wordFadeWidth !== undefined)
|
||||
playerRef.value?.setWordFadeWidth(props.wordFadeWidth);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.linePosXSpringParams !== undefined)
|
||||
playerRef.value?.setLinePosXSpringParams(props.linePosXSpringParams);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.linePosYSpringParams !== undefined)
|
||||
playerRef.value?.setLinePosYSpringParams(props.linePosYSpringParams);
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.lineScaleSpringParams !== undefined)
|
||||
playerRef.value?.setLineScaleSpringParams(props.lineScaleSpringParams);
|
||||
});
|
||||
|
||||
const bottomLineEl = computed(() =>
|
||||
playerRef.value?.getBottomLineElement(),
|
||||
);
|
||||
|
||||
expose<LyricPlayerRef>({
|
||||
lyricPlayer: playerRef,
|
||||
wrapperEl: wrapperRef,
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div ref="wrapper-ref" {...attrs}>
|
||||
{bottomLineEl.value && (
|
||||
<Teleport to={bottomLineEl.value}>
|
||||
{slots["bottom-line"]?.()}
|
||||
</Teleport>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
2
amll-local/packages/vue/src/index.ts
Normal file
2
amll-local/packages/vue/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./BackgroundRender";
|
||||
export * from "./LyricPlayer";
|
||||
9
amll-local/packages/vue/tsconfig.dts.json
Normal file
9
amll-local/packages/vue/tsconfig.dts.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// 让 tsgo 解析 dist 下的类型声明而非源码,避免在 core/src 下生成 .d.ts 文件
|
||||
"paths": {
|
||||
"@applemusic-like-lyrics/core": ["../core/dist/amll-core.d.mts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
10
amll-local/packages/vue/tsconfig.json
Normal file
10
amll-local/packages/vue/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
// vue 的类型系统非常复杂,基本无法手动定义出来
|
||||
"isolatedDeclarations": false,
|
||||
"jsxImportSource": "vue",
|
||||
"jsx": "preserve"
|
||||
}
|
||||
}
|
||||
17
amll-local/packages/vue/tsdown.config.ts
Normal file
17
amll-local/packages/vue/tsdown.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import pluginBabel from "@rolldown/plugin-babel";
|
||||
import { defineConfig } from "tsdown";
|
||||
import { baseConfig } from "../../tsdown.base.ts";
|
||||
|
||||
export default defineConfig({
|
||||
...baseConfig,
|
||||
entry: { "amll-vue": "./src/index.ts" },
|
||||
dts: {
|
||||
tsgo: true,
|
||||
tsconfig: "./tsconfig.dts.json",
|
||||
},
|
||||
plugins: [
|
||||
pluginBabel({
|
||||
plugins: ["@vue/babel-plugin-jsx"],
|
||||
}),
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user