@sanyueqi/web-components
Version:
Web components
577 lines (498 loc) • 21 kB
JavaScript
(function (factory) {
typeof define === 'function' && define.amd ? define(factory) :
factory();
})((function () { 'use strict';
/**
* 音乐播放器插件
* 基于 APlayer 和 MetingJS 的音乐播放器
*/
class MusicPlayerPlugin {
playId = 'aplayer-container'
constructor() {
this.loadedAPlayer = false;
this.loadedMetingJS = false;
this.hasMusicInteracted = false;
}
/**
* 初始化音乐播放器
* @param {Object} config - 播放器配置
*/
init(config) {
if (!config.enable) return;
if (config.playId) {
this.playId = config.playId;
}
// 确保在浏览器环境中运行
if (typeof window === 'undefined') return;
this.config = config;
// 预处理配置
this.processConfig();
// 加载 CSS
this.loadCSS();
// 初始化播放器
this.initPlayer();
}
/**
* 预处理配置
*/
processConfig() {
// 处理 APlayer CSS 路径 - 修改这里
this.aplayerCssPath = this.config.aplayerCss
? (this.config.aplayerCss.startsWith("http://") || this.config.aplayerCss.startsWith("https://"))
? this.config.aplayerCss
: this.processUrl(this.config.aplayerCss)
: "https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css"; // 默认 CDN
// 处理 APlayer JS 路径 - 修改这里
this.aplayerJsPath = this.config.aplayerJs
? (this.config.aplayerJs.startsWith("http://") || this.config.aplayerJs.startsWith("https://"))
? this.config.aplayerJs
: this.processUrl(this.config.aplayerJs)
: "https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js"; // 默认 CDN
// 处理 MetingJS 路径 - 修改这里
this.metingJsPath = this.config.meting?.jsPath
? (this.config.meting.jsPath.startsWith("http://") || this.config.meting.jsPath.startsWith("https://"))
? this.config.meting.jsPath
: this.processUrl(this.config.meting.jsPath)
: "https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"; // 默认 CDN
// 预处理本地音乐列表(保持不变)
this.processedLocalPlaylist = this.config.mode === "local" && this.config.local?.playlist
? this.config.local.playlist.map((song) => ({
...song,
url: this.processUrl(song.url),
cover: song.cover ? this.processUrl(song.cover) : undefined,
}))
: null;
}
/**
* 处理 URL 路径
* @param {string} path - 原始路径
* @returns {string} 处理后的完整 URL
*/
processUrl(path) {
if (!path.startsWith('http')) {
return this.joinUrl("", this.getBaseUrl(), path);
} else {
return path;
}
}
/**
* 获取基础 URL
* @returns {string} 基础 URL
*/
getBaseUrl() {
// 从 window 获取
if (typeof window !== 'undefined') {
return window.location.pathname.includes('/')
? window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1)
: '';
}
return '';
}
/**
* 拼接 URL
* @param {...string} parts - URL 部分
* @returns {string} 拼接后的 URL
*/
joinUrl(...parts) {
const joined = parts.join("/");
return joined.replace(/\/+/g, "/");
}
/**
* 加载 CSS 文件
*/
loadCSS() {
// 检查是否已经加载过
if (document.querySelector(`link[href="${this.aplayerCssPath}"]`)) {
return;
}
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = this.aplayerCssPath;
document.head.appendChild(link);
}
/**
* 加载 APlayer JS
* @returns {Promise} Promise 对象
*/
loadAPlayer() {
return new Promise((resolve) => {
if (window.APlayer) {
this.loadedAPlayer = true;
resolve(window.APlayer);
return;
}
const script = document.createElement("script");
script.src = this.aplayerJsPath;
script.async = true;
script.onload = () => {
this.loadedAPlayer = true;
resolve(window.APlayer);
};
script.onerror = () => {
console.error("Failed to load APlayer");
resolve(null);
};
document.head.appendChild(script);
});
}
/**
* 加载 MetingJS
* @returns {Promise} Promise 对象
*/
loadMetingJS() {
return new Promise((resolve) => {
if (window.customElements?.get("meting-js")) {
this.loadedMetingJS = true;
resolve(true);
return;
}
const script = document.createElement("script");
script.src = this.metingJsPath;
script.async = true;
script.onload = () => {
this.loadedMetingJS = true;
// 如果配置了全局 API,设置它
if (this.config.mode === "meting" && this.config.meting?.api) {
window.meting_api = this.config.meting.api;
}
this.setupMetingElements();
resolve(true);
};
script.onerror = () => {
console.error("Failed to load MetingJS");
resolve(false);
};
document.head.appendChild(script);
});
}
/**
* 设置 Meting 元素
*/
setupMetingElements() {
setTimeout(() => {
const metingElements = document.querySelectorAll('meting-js');
metingElements.forEach((element) => {
this.setupAPlayerContainer(element.aplayer);
});
}, 100);
}
/**
* 设置 APlayer 容器
* @param {Object} aplayer - APlayer 实例
*/
setupAPlayerContainer(aplayer) {
if (!aplayer) return;
const checkAPlayer = setInterval(() => {
if (aplayer && aplayer.container) {
clearInterval(checkAPlayer);
aplayer.container.setAttribute('data-positioned', 'right');
requestAnimationFrame(() => {
if (aplayer.container) {
aplayer.container.style.right = '0';
aplayer.container.style.left = 'unset';
setTimeout(() => {
aplayer.container.setAttribute('data-initialized', 'true');
// 如果配置了默认隐藏歌词,则隐藏歌词
if (this.config.player?.lrcHidden && aplayer.lrc) {
aplayer.lrc.hide();
const lrcButton = aplayer.container.querySelector('.aplayer-icon-lrc');
if (lrcButton) {
lrcButton.classList.add('aplayer-icon-lrc-inactivity');
}
}
// 处理自动播放
if (this.config.player?.autoplay && aplayer.paused) {
this.tryAutoplay(aplayer);
}
}, 200);
}
});
}
}, 50);
setTimeout(() => clearInterval(checkAPlayer), 10000);
}
/**
* 尝试自动播放
* @param {Object} aplayer - APlayer 实例
*/
tryAutoplay(aplayer) {
if (!this.config.player?.autoplay) return;
if (this.hasMusicInteracted) {
if (aplayer && aplayer.paused) {
const playPromise = aplayer.play();
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch((error) => {
if (error.name !== 'NotAllowedError') {
console.log('自动播放失败:', error.name);
}
});
}
}
} else {
const enableAutoplay = () => {
if (this.hasMusicInteracted) return;
this.hasMusicInteracted = true;
if (aplayer && aplayer.paused) {
const playPromise = aplayer.play();
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch((error) => {
if (error.name !== 'NotAllowedError') {
console.log('自动播放失败:', error.name);
}
});
}
}
};
['click', 'keydown', 'touchstart', 'scroll'].forEach(eventType => {
document.addEventListener(eventType, enableAutoplay, {
once: true,
passive: true
});
});
}
}
/**
* 创建播放器容器
*/
createContainer() {
// 检查容器是否已存在
let container = document.getElementById(`#${this.playId}`);
if (container) {
return container;
}
container = document.createElement('div');
container.id = this.playId;
if (this.config.responsive?.mobile?.hide) {
container.classList.add('mobile-hide');
}
document.body.appendChild(container);
return container;
}
/**
* 渲染 Meting 模式播放器
* @param {HTMLElement} container - 容器元素
*/
renderMetingPlayer(container) {
if (this.config.mode !== "meting" || !this.config.meting) return;
const metingElement = document.createElement('meting-js');
metingElement.setAttribute('server', this.config.meting.server || "netease");
metingElement.setAttribute('type', this.config.meting.type || "playlist");
metingElement.setAttribute('id', this.config.meting.id || "");
if (this.config.meting.api) {
metingElement.setAttribute('api', this.config.meting.api);
}
if (this.config.meting.auth) {
metingElement.setAttribute('auth', this.config.meting.auth);
}
// 设置播放器属性
this.setPlayerAttributes(metingElement);
container.appendChild(metingElement);
}
/**
* 渲染本地模式播放器
* @param {HTMLElement} container - 容器元素
*/
renderLocalPlayer(container) {
if (this.config.mode !== "local" || !this.processedLocalPlaylist || this.processedLocalPlaylist.length === 0) {
return;
}
if (this.processedLocalPlaylist.length === 1) {
const song = this.processedLocalPlaylist[0];
const metingElement = document.createElement('meting-js');
metingElement.setAttribute('name', song.name || "");
metingElement.setAttribute('artist', song.artist || "");
metingElement.setAttribute('url', song.url || "");
if (song.cover) {
metingElement.setAttribute('cover', song.cover);
}
// 设置播放器属性
this.setPlayerAttributes(metingElement, song.lrc ? "2" : "0");
// 添加歌词
if (song.lrc) {
const lrcElement = document.createElement('pre');
lrcElement.hidden = true;
lrcElement.textContent = song.lrc;
metingElement.appendChild(lrcElement);
}
container.appendChild(metingElement);
} else {
const localPlayerDiv = document.createElement('div');
localPlayerDiv.id = 'local-aplayer';
container.appendChild(localPlayerDiv);
}
}
/**
* 设置播放器属性
* @param {HTMLElement} element - 元素
* @param {string} lrcType - 歌词类型
*/
setPlayerAttributes(element, lrcType = null) {
const playerConfig = this.config.player || {};
element.setAttribute('fixed', (playerConfig.fixed ?? true) ? "true" : "false");
element.setAttribute('mini', (playerConfig.mini ?? true) ? "true" : "false");
element.setAttribute('autoplay', playerConfig.autoplay ? "true" : "false");
element.setAttribute('theme', playerConfig.theme || "#b7daff");
element.setAttribute('loop', playerConfig.loop || "all");
element.setAttribute('order', playerConfig.order || "list");
element.setAttribute('preload', playerConfig.preload || "auto");
element.setAttribute('volume', String(playerConfig.volume ?? 0.7));
element.setAttribute('mutex', playerConfig.mutex !== false ? "true" : "false");
element.setAttribute('lrc-type', lrcType !== null ? lrcType : String(playerConfig.lrcType ?? 3));
element.setAttribute('list-folded', playerConfig.listFolded ? "true" : "false");
element.setAttribute('list-max-height', playerConfig.listMaxHeight || "340px");
element.setAttribute('storage-name', playerConfig.storageName || "aplayer-setting");
}
/**
* 初始化本地播放器(多个音乐)
*/
async initLocalPlayer() {
if (
this.config.mode !== "local" ||
!this.processedLocalPlaylist ||
this.processedLocalPlaylist.length <= 1
) {
return;
}
const APlayerClass = await this.loadAPlayer();
if (!APlayerClass) return;
const container = document.getElementById("local-aplayer");
if (!container) return;
const audioList = this.processedLocalPlaylist.map((song) => ({
name: song.name,
artist: song.artist,
url: song.url,
cover: song.cover,
lrc: song.lrc || undefined,
type: "auto",
}));
const aplayerOptions = {
container: container,
audio: audioList,
mutex: this.config.player?.mutex !== false,
lrcType: this.config.player?.lrcType !== undefined ? this.config.player.lrcType : 0,
fixed: this.config.player?.fixed ?? true,
mini: this.config.player?.mini ?? true,
autoplay: this.config.player?.autoplay || false,
theme: this.config.player?.theme || "#b7daff",
loop: this.config.player?.loop || "all",
order: this.config.player?.order || "list",
preload: this.config.player?.preload || "auto",
volume: this.config.player?.volume || 0.7,
listFolded: this.config.player?.listFolded || false,
listMaxHeight: this.config.player?.listMaxHeight || "340px",
storageName: this.config.player?.storageName || "aplayer-setting",
};
try {
const aplayer = new APlayerClass(aplayerOptions);
this.setupAPlayerContainer(aplayer);
} catch (error) {
console.error("Failed to initialize APlayer:", error);
}
}
/**
* 添加样式
*/
addStyles() {
const style = document.createElement('style');
style.textContent = `
#${this.playId} {
position: relative;
z-index: 1000;
}
#${this.playId} .aplayer-fixed {
z-index: 9999;
}
#${this.playId} .aplayer.aplayer-fixed {
animation: none !important;
}
#${this.playId} .aplayer.aplayer-fixed .aplayer-body {
animation: none !important;
transition: none !important;
}
#${this.playId} .aplayer.aplayer-fixed[data-positioned="right"] {
right: 0 !important;
left: unset !important;
}
(max-width: 768px) {
#${this.playId}.mobile-hide {
display: none !important;
}
}
`;
document.head.appendChild(style);
}
/**
* 初始化播放器
*/
async initPlayer() {
// 创建容器
const container = this.createContainer();
// 添加样式
this.addStyles();
// 根据模式渲染播放器
if (this.config.mode === "meting") {
this.renderMetingPlayer(container);
await this.loadAPlayer();
await this.loadMetingJS();
} else if (this.config.mode === "local") {
this.renderLocalPlayer(container);
if (this.processedLocalPlaylist && this.processedLocalPlaylist.length > 1) {
// 多个音乐,使用 APlayer
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => this.initLocalPlayer());
} else {
await this.initLocalPlayer();
}
} else {
// 单个音乐,使用 MetingJS
await this.loadAPlayer();
await this.loadMetingJS();
}
}
}
/**
* 销毁播放器
*/
destroy() {
const container = document.getElementById(`#${this.playId}`);
if (container) {
container.remove();
}
// 移除样式
const styles = document.querySelectorAll('style');
styles.forEach(style => {
if (style.textContent.includes(`#${this.playId}`)) {
style.remove();
}
});
}
}
// 创建全局实例
const musicPlayer = new MusicPlayerPlugin();
/**
* 初始化音乐播放器
* @param {Object} config - 播放器配置
*/
function initMusicPlayer(config) {
musicPlayer.init(config);
}
/**
* 销毁音乐播放器
*/
function destroyMusicPlayer() {
musicPlayer.destroy();
}
// 为了兼容性,同时支持CommonJS和浏览器全局变量
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
init: initMusicPlayer,
destroy: destroyMusicPlayer
};
}
if (typeof window !== 'undefined') {
window.MusicPlayer = {
init: initMusicPlayer,
destroy: destroyMusicPlayer
};
}
}));