跳转到内容

快速开始

下面快速介绍如何将 AMLL 歌词组件集成到你的项目中。请注意,AMLL 不提供 CDN 引入方式,必须使用 bundler。

有关除组件库之外的其他周围工具包,请直接查阅 API 参考

安装 AMLL 核心库:

Terminal window
npm install @applemusic-like-lyrics/core

此外,AMLL 将一些图形与动画库声明为 peer,这是为了复用项目中可能存在的相关依赖。一些包管理器或配置下可能会自动安装 peer,若没有,需要手动安装。

Terminal window
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

下面的示例会用 @applemusic-like-lyrics/lyric 解析 TTML 歌词文件。如果你的项目已经能直接提供 LyricLine[],可以跳过这个包。

Terminal window
npm install @applemusic-like-lyrics/lyric

接下来你可以使用原生包,也可以使用 React 或 Vue 绑定。

AMLL 核心库是框架无关的。无论是否使用框架,均可使用此方法引入。

下面假设:

  • 页面中已有 <audio id="audio"><div id="lyric-player">,并给歌词容器设置了明确高度
  • TTML 歌词文件在 /lyrics/song.ttml 上提供
import { LyricPlayer } from "@applemusic-like-lyrics/core";
import { parseTTML } from "@applemusic-like-lyrics/lyric";
// 一般地,打包器会处理 CSS 导入;如果出现异常,请查阅你使用的打包器文档
import "@applemusic-like-lyrics/core/style.css";
const audio = document.querySelector("#audio");
const playerHost = document.querySelector("#lyric-player");
const player = new LyricPlayer();
playerHost.appendChild(player.getElement());
async function loadLyric() {
const ttml = await fetch("/lyrics/song.ttml").then((res) => res.text());
const currentTime = Math.round(audio.currentTime * 1000);
player.setLyricLines(parseTTML(ttml).lines, currentTime);
player.setCurrentTime(currentTime, true);
}
// 播放时持续同步音频进度
let lastFrameTime = -1;
function onFrame(frameTime) {
const delta = lastFrameTime === -1 ? 0 : frameTime - lastFrameTime;
lastFrameTime = frameTime;
if (!audio.paused) {
player.setCurrentTime(Math.round(audio.currentTime * 1000));
}
player.update(delta);
requestAnimationFrame(onFrame);
}
audio.addEventListener("play", () => player.resume());
audio.addEventListener("pause", () => player.pause());
audio.addEventListener("seeked", () => {
// 跳转后立即对齐歌词位置
player.setCurrentTime(Math.round(audio.currentTime * 1000), true);
});
loadLyric();
requestAnimationFrame(onFrame);

有关更详细的时序管理,请转到 时序与生命周期

你可以前往 API 参考:Core 核心 获取详细的接口文档。

确保你已安装 reactreact-dom 包。然后安装 AMLL React 绑定包:

Terminal window
npm install @applemusic-like-lyrics/react

React 绑定包拥有具名导出 LyricPlayer 作为核心组件。下面是一段示例,假设 TTML 歌词文件在 /lyrics/song.ttml 上提供、音频文件在 /music/song.m4a 上提供。

import { useEffect, useRef, useState } from "react";
import type { LyricLine } from "@applemusic-like-lyrics/core";
import { LyricPlayer } from "@applemusic-like-lyrics/react";
import { parseTTML } from "@applemusic-like-lyrics/lyric";
// 一般地,打包器会处理 CSS 导入;如果出现异常,请查阅你使用的打包器文档
import "@applemusic-like-lyrics/core/style.css";
function App() {
const audioRef = useRef<HTMLAudioElement>(null);
const [lyricLines, setLyricLines] = useState<LyricLine[]>([]);
const [currentTime, setCurrentTime] = useState(0);
const [playing, setPlaying] = useState(false);
function syncCurrentTime() {
const audio = audioRef.current;
if (audio) setCurrentTime(Math.round(audio.currentTime * 1000));
}
useEffect(() => {
let canceled = false;
// parseTTML 返回包含元数据的对象,组件只需要其中的 lines
fetch("/lyrics/song.ttml")
.then((res) => res.text())
.then((ttml) => {
if (!canceled) setLyricLines(parseTTML(ttml).lines);
});
return () => {
canceled = true;
};
}, []);
useEffect(() => {
let frameId = 0;
const onFrame = () => {
const audio = audioRef.current;
if (audio && !audio.paused) {
setCurrentTime(Math.round(audio.currentTime * 1000));
}
frameId = requestAnimationFrame(onFrame);
};
frameId = requestAnimationFrame(onFrame);
return () => cancelAnimationFrame(frameId);
}, []);
return (
<>
<LyricPlayer
lyricLines={lyricLines}
currentTime={currentTime}
playing={playing}
style={{ height: 420 }}
/>
<audio
ref={audioRef}
src="/music/song.m4a"
controls
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
onEnded={() => setPlaying(false)}
onSeeked={syncCurrentTime}
/>
</>
);
}
export default App;

你可以前往 API 参考:React 绑定 获取详细的接口文档。

确保你已安装 Vue。然后安装 AMLL Vue 绑定包:

Terminal window
npm install @applemusic-like-lyrics/vue

Vue 绑定包拥有具名导出 LyricPlayer 作为核心组件。歌词对象、播放进度等均以组件的响应式属性传入。下面是一段示例,假设 TTML 歌词文件在 /lyrics/song.ttml 上提供、音频文件在 /music/song.m4a 上提供。

<template>
<LyricPlayer
class="lyric-player"
:lyric-lines="lyricLines"
:current-time="currentTime"
:playing="playing"
/>
<audio
ref="audioEl"
src="/music/song.m4a"
controls
@play="onPlay"
@pause="onPause"
@ended="onPause"
@seeked="syncCurrentTime"
/>
</template>
<script setup lang="ts">
import type { LyricLine } from "@applemusic-like-lyrics/core";
import { parseTTML } from "@applemusic-like-lyrics/lyric";
import { LyricPlayer } from "@applemusic-like-lyrics/vue";
import {
onBeforeUnmount,
onMounted,
ref,
shallowRef,
useTemplateRef,
} from "vue";
// 一般地,打包器会处理 CSS 导入;如果出现异常,请查阅你使用的打包器文档
import "@applemusic-like-lyrics/core/style.css";
const audioEl = useTemplateRef<HTMLAudioElement>("audioEl");
const lyricLines = shallowRef<LyricLine[]>([]);
const currentTime = ref(0);
const playing = ref(false);
let frameId = 0;
async function loadLyric() {
const ttml = await fetch("/lyrics/song.ttml").then((res) => res.text());
// parseTTML 返回包含元数据的对象,组件只需要其中的 lines
lyricLines.value = parseTTML(ttml).lines;
}
function syncCurrentTime() {
if (audioEl.value) {
currentTime.value = Math.round(audioEl.value.currentTime * 1000);
}
}
function onFrame() {
syncCurrentTime();
if (playing.value) frameId = requestAnimationFrame(onFrame);
}
function onPlay() {
playing.value = true;
cancelAnimationFrame(frameId);
onFrame();
}
function onPause() {
playing.value = false;
cancelAnimationFrame(frameId);
}
onMounted(() => void loadLyric());
onBeforeUnmount(() => cancelAnimationFrame(frameId));
</script>
<style scoped>
.lyric-player {
height: 420px;
}
</style>

你可以前往 API 参考:Vue 绑定 获取详细的接口文档。