为网页版 Apple Music 提供翻译歌词,数据来源为网易云音乐
// ==UserScript==
// @name Apple Music 歌词增强
// @namespace https://github.com/akashiwest/AML-Enhancer
// @version 1.200
// @description 为网页版 Apple Music 提供翻译歌词,数据来源为网易云音乐
// @author Akashi
// @license GNU GPL 3.0
// @match https://*.music.apple.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function() {
'use strict';
// 定义扩展和缩小时容器的高度
const expandedHeight = '240px';
const minimizedHeight = '130px';
// 全局变量保存主歌词与翻译歌词数据
let currentLyrics = [];
let currentTLyrics = [];
// 歌词时间偏移量(秒)
let lyricsOffset = 0;
// 当前歌曲信息
let currentSongTitle = '';
// 偏移存储对象 - 在实际环境中可以改为localStorage
// 格式: { "歌名": 偏移值(秒) }
let offsetStorage = {};
// 存储/读取偏移值的函数
function saveOffset(songTitle, offset) {
if (!songTitle) return;
try {
let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
storedOffsets[songTitle] = offset;
localStorage.setItem('lyricsOffsets', JSON.stringify(storedOffsets));
} catch (e) {
console.error('保存歌词偏移失败:', e);
}
}
function loadOffset(songTitle) {
if (!songTitle) return 0;
try {
let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
return storedOffsets[songTitle] || 0;
} catch (e) {
console.error('读取歌词偏移失败:', e);
return 0;
}
}
// 清除指定歌曲的偏移
function clearOffset(songTitle) {
if (!songTitle) return;
try {
let storedOffsets = JSON.parse(localStorage.getItem('lyricsOffsets') || '{}');
delete storedOffsets[songTitle];
localStorage.setItem('lyricsOffsets', JSON.stringify(storedOffsets));
} catch (e) {
console.error('清除歌词偏移失败:', e);
}
}
// 创建固定容器(含内部内容容器)用于显示歌词
function createLyricsDisplay() {
const lyricsDiv = document.createElement('div');
lyricsDiv.id = 'lyrics-display';
// 默认缩小
lyricsDiv.dataset.isMinimized = 'true';
Object.assign(lyricsDiv.style, {
position: 'fixed',
right: '20px',
top: '60px',
width: '850px',
height: minimizedHeight,
overflow: 'hidden',
borderRadius: '20px',
backdropFilter: 'saturate(200%) blur(25px)',
background: 'rgba(250,250,250,0.72)',
zIndex: '9999',
padding: '20px 30px',
fontSize: '28px',
color: '#565656',
textAlign: 'center',
boxShadow: '0 5px 30px rgba(0, 0, 0, 0.4)',
fontWeight: 'bold',
msOverflowStyle: 'none',
scrollbarWidth: 'none',
// 高度切换的动画效果,仅对 height 生效
transition: 'height 0.3s ease'
});
// 内部容器,滚动时带平滑动画
const lyricsContent = document.createElement('div');
lyricsContent.id = 'lyrics-content';
lyricsContent.style.transition = 'transform 0.3s ease-out';
lyricsContent.style.transform = 'translateY(0)';
lyricsDiv.appendChild(lyricsContent);
const defaultGroup = document.createElement('div');
defaultGroup.className = 'lyric-group';
defaultGroup.style.height = '70px';
defaultGroup.style.marginBottom = '10px';
const defaultText = document.createElement('div');
defaultText.className = 'main-lyric';
defaultText.innerText = 'Apple Music 歌词翻译 v1.2';
defaultText.style.fontSize = '32px';
defaultText.style.color = '#252525';
defaultText.style.fontWeight = 'bold';
defaultText.style.marginTop = '25px';
defaultGroup.appendChild(defaultText);
lyricsContent.appendChild(defaultGroup);
lyricsDiv.appendChild(lyricsContent);
// 创建右下角按钮容器
const rightButtonContainer = document.createElement('div');
Object.assign(rightButtonContainer.style, {
position: 'absolute',
bottom: '10px',
right: '10px',
display: 'flex',
gap: '5px',
alignItems: 'center'
});
// 创建左下角按钮容器
const leftButtonContainer = document.createElement('div');
Object.assign(leftButtonContainer.style, {
position: 'absolute',
bottom: '10px',
left: '10px',
display: 'flex',
gap: '5px',
alignItems: 'center'
});
// 减号按钮
const minusButton = document.createElement('button');
minusButton.id = 'minus-button';
minusButton.innerText = '−';
Object.assign(minusButton.style, {
padding: '5px 8px',
fontSize: '16px',
fontWeight: 'bold',
border: 'none',
borderRadius: '15px',
background: '#ddd',
cursor: 'pointer',
zIndex: '10000',
opacity: '0.8',
minWidth: '30px'
});
// 偏移显示按钮
const offsetButton = document.createElement('button');
offsetButton.id = 'offset-button';
offsetButton.innerText = '+0.0s';
Object.assign(offsetButton.style, {
padding: '5px 10px',
fontSize: '12px',
border: 'none',
borderRadius: '15px',
background: '#ddd',
cursor: 'pointer',
zIndex: '10000',
opacity: '0.8',
minWidth: '50px'
});
// 加号按钮
const plusButton = document.createElement('button');
plusButton.id = 'plus-button';
plusButton.innerText = '+';
Object.assign(plusButton.style, {
padding: '5px 8px',
fontSize: '16px',
fontWeight: 'bold',
border: 'none',
borderRadius: '15px',
background: '#ddd',
cursor: 'pointer',
zIndex: '10000',
opacity: '0.8',
minWidth: '30px'
});
// 切换按钮
const toggleButton = document.createElement('button');
toggleButton.id = 'toggle-size-button';
toggleButton.innerText = '放大';
Object.assign(toggleButton.style, {
padding: '5px 10px',
fontSize: '14px',
border: 'none',
borderRadius: '15px',
background: '#ddd',
cursor: 'pointer',
zIndex: '10000',
opacity: '0.8'
});
// 信息按钮
const infoButton = document.createElement('button');
infoButton.id = 'info-button';
infoButton.innerText = 'O';
Object.assign(infoButton.style, {
padding: '5px 8px',
fontSize: '14px',
border: 'none',
borderRadius: '15px',
background: '#ddd',
cursor: 'pointer',
zIndex: '10000',
opacity: '0.3'
});
// 更新偏移显示
function updateOffsetDisplay() {
const offsetValue = lyricsOffset >= 0 ? `+${lyricsOffset.toFixed(1)}s` : `${lyricsOffset.toFixed(1)}s`;
offsetButton.innerText = offsetValue;
// 自动保存当前歌曲的偏移值
if (currentSongTitle) {
saveOffset(currentSongTitle, lyricsOffset);
}
}
// 减号按钮事件
minusButton.addEventListener('click', function(e) {
e.stopPropagation();
lyricsOffset -= 0.2;
updateOffsetDisplay();
});
// 偏移按钮事件(点击归零并清除存储)
offsetButton.addEventListener('click', function(e) {
e.stopPropagation();
lyricsOffset = 0;
updateOffsetDisplay();
// 清除当前歌曲的存储偏移
if (currentSongTitle) {
clearOffset(currentSongTitle);
}
});
// 加号按钮事件
plusButton.addEventListener('click', function(e) {
e.stopPropagation();
lyricsOffset += 0.2;
updateOffsetDisplay();
});
// 切换按钮事件
toggleButton.addEventListener('click', function(e) {
e.stopPropagation();
if (lyricsDiv.dataset.isMinimized === 'true') {
// 扩展
lyricsDiv.style.height = expandedHeight;
lyricsDiv.dataset.isMinimized = 'false';
toggleButton.innerText = '缩小';
} else {
// 缩小
lyricsDiv.style.height = minimizedHeight;
lyricsDiv.dataset.isMinimized = 'true';
toggleButton.innerText = '放大';
}
});
// 信息按钮事件
infoButton.addEventListener('click', function(e) {
e.stopPropagation();
window.open('https://imakashi.com/blog/archives/AML_Enhancer.html', '_blank');
});
// 将按钮添加到对应的容器
rightButtonContainer.appendChild(minusButton);
rightButtonContainer.appendChild(offsetButton);
rightButtonContainer.appendChild(plusButton);
rightButtonContainer.appendChild(toggleButton);
leftButtonContainer.appendChild(infoButton);
lyricsDiv.appendChild(rightButtonContainer);
lyricsDiv.appendChild(leftButtonContainer);
// 拖拽功能
lyricsDiv.onmousedown = dragMouseDown;
let pos3 = 0, pos4 = 0;
function dragMouseDown(e) {
// 点击按钮不触发拖拽
if (e.target === toggleButton || e.target === infoButton ||
e.target === minusButton || e.target === offsetButton || e.target === plusButton) return;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e.preventDefault();
const pos1 = pos3 - e.clientX;
const pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
lyricsDiv.style.top = `${lyricsDiv.offsetTop - pos2}px`;
lyricsDiv.style.left = `${lyricsDiv.offsetLeft - pos1}px`;
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
document.body.appendChild(lyricsDiv);
return lyricsDiv;
}
// 根据播放器 title 获取歌曲ID,并调用歌词接口
function getSongId() {
const audioPlayer = document.getElementById('apple-music-player');
if (!audioPlayer) {
console.log('当前页面未找到音频播放器');
return;
}
let title = audioPlayer.title;
// 取标题中第一个"-"前面的部分(可根据实际情况调整)
const secondDashIndex = title.indexOf('-', title.indexOf('-') + 1);
if (secondDashIndex !== -1) {
title = title.substring(0, secondDashIndex).trim();
}
// 更新当前歌曲标题并加载对应的偏移值
currentSongTitle = title;
lyricsOffset = loadOffset(currentSongTitle);
// 更新显示
const offsetButton = document.getElementById('offset-button');
if (offsetButton) {
const offsetValue = lyricsOffset >= 0 ? `+${lyricsOffset.toFixed(1)}s` : `${lyricsOffset.toFixed(1)}s`;
offsetButton.innerText = offsetValue;
}
showMessage(title);
const apiUrl = `https://music.163.com/api/search/pc?s=${encodeURIComponent(title)}&offset=0&limit=1&type=1`;
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
responseType: "json",
onload: function(response) {
if (response.status === 200) {
const data = response.response;
if (data.result && data.result.songs && data.result.songs.length > 0) {
const firstSongId = data.result.songs[0].id;
getLyrics(firstSongId);
console.log(apiUrl, 'ID - ' + firstSongId);
} else {
showMessage("未找到歌曲");
}
} else {
showMessage("请求失败");
}
},
onerror: function() {
console.error("未知错误");
}
});
}
// 提示信息
function showMessage(msg) {
const lyricsContent = document.getElementById('lyrics-content');
if (lyricsContent) {
const messageGroup = document.createElement('div');
messageGroup.className = 'lyric-group';
messageGroup.style.height = '70px';
messageGroup.style.marginBottom = '10px';
const messageText = document.createElement('div');
messageText.className = 'main-lyric';
messageText.innerText = msg;
messageText.style.fontSize = '32px';
messageText.style.color = '#252525';
messageText.style.fontWeight = 'bold';
messageText.style.filter = 'blur(0) !important';
// 添加容器样式以确保垂直居中
messageGroup.style.display = 'flex';
messageGroup.style.alignItems = 'center';
messageGroup.style.justifyContent = 'center';
messageGroup.style.height = '100%';
messageGroup.style.marginTop = '25px';
messageGroup.appendChild(messageText);
lyricsContent.innerHTML = '';
lyricsContent.appendChild(messageGroup);
}
}
// 获取歌词(同时获取主歌词和翻译歌词)并解析后渲染
function getLyrics(songId) {
const apiUrl = `https://music.163.com/api/song/lyric?lv=1&kv=1&tv=-1&id=${songId}`;
showMessage('歌词正在加载中 ...');
GM_xmlhttpRequest({
method: "GET",
url: apiUrl,
responseType: "json",
onload: function(response) {
if (response.status === 200) {
const data = response.response;
if (!data || (!data.lrc && !data.tlyric)) {
showMessage('未找到匹配歌词');
currentLyrics = [];
currentTLyrics = [];
return;
}
const lyricsLines = data.lrc ? data.lrc.lyric : "";
const tlyricsLines = data.tlyric ? data.tlyric.lyric : "";
currentLyrics = parseLyrics(lyricsLines);
currentTLyrics = parseLyrics(tlyricsLines);
if (currentLyrics.length === 0) {
showMessage('暂无歌词');
return;
}
renderLyrics();
const audioPlayer = document.getElementById('apple-music-player');
if (audioPlayer) {
audioPlayer.dataset.songId = songId;
}
} else {
showMessage('歌词获取失败');
currentLyrics = [];
currentTLyrics = [];
}
},
onerror: function(err) {
console.error(err);
showMessage('歌词获取失败');
currentLyrics = [];
currentTLyrics = [];
}
});
}
// 解析歌词文本(格式:[mm:ss.xxx]歌词内容)
function parseLyrics(lyricsText) {
return lyricsText.split('\n').filter(line => line.trim() !== '').map(line => {
const matches = line.match(/\[(\d{2}):(\d{2})(?:\.(\d{1,3}))?\](.*)/);
if (matches) {
const minutes = parseInt(matches[1], 10);
const seconds = parseInt(matches[2], 10);
let milliseconds = matches[3] ? parseInt(matches[3], 10) : 0;
if (milliseconds < 100 && milliseconds >= 10) {
milliseconds *= 10;
}
const text = matches[4].trim();
const totalSeconds = minutes * 60 + seconds + milliseconds / 1000;
return { startTime: totalSeconds, text: text };
}
}).filter(Boolean);
}
// 渲染歌词:每组歌词显示为两行(主歌词及对应翻译),每组之间有间隙
function renderLyrics() {
const lyricsContent = document.getElementById('lyrics-content');
if (!lyricsContent) return;
lyricsContent.innerHTML = '';
const groupHeight = 70; // 每组固定高度(包括两行与间隙)
currentLyrics.forEach((lyric, index) => {
const groupDiv = document.createElement('div');
groupDiv.className = 'lyric-group';
groupDiv.dataset.index = index;
groupDiv.style.height = groupHeight + 'px';
groupDiv.style.marginBottom = '10px';
// 主歌词行
const mainDiv = document.createElement('div');
mainDiv.className = 'main-lyric';
mainDiv.innerText = lyric.text;
mainDiv.style.fontSize = '28px';
mainDiv.style.color = '#565656';
// 匹配翻译歌词
let translationText = "";
if (currentTLyrics && currentTLyrics.length > 0) {
const tLine = currentTLyrics.find(t => Math.abs(t.startTime - lyric.startTime) < 0.5);
if (tLine) {
translationText = tLine.text;
}
}
const transDiv = document.createElement('div');
transDiv.className = 'translation-lyric';
transDiv.innerText = translationText;
transDiv.style.fontSize = '20px';
transDiv.style.color = '#888';
transDiv.style.marginTop = '5px';
groupDiv.appendChild(mainDiv);
groupDiv.appendChild(transDiv);
lyricsContent.appendChild(groupDiv);
});
}
// 当前歌词高亮
function updateLyricScroll(currentTime) {
if (currentLyrics.length === 0) return;
// 应用时间偏移
const adjustedTime = currentTime + lyricsOffset;
let currentIndex = 0;
for (let i = 0; i < currentLyrics.length; i++) {
if (adjustedTime >= currentLyrics[i].startTime) {
currentIndex = i;
} else {
break;
}
}
const lyricsContent = document.getElementById('lyrics-content');
if (lyricsContent === null) return;
const groups = lyricsContent.getElementsByClassName('lyric-group');
for (let i = 0; i < groups.length; i++) {
const mainDiv = groups[i].querySelector('.main-lyric');
const transDiv = groups[i].querySelector('.translation-lyric');
if (!mainDiv || !transDiv) continue;
if (i === currentIndex) {
mainDiv.style.color = '#252525';
mainDiv.style.fontWeight = 'bold';
mainDiv.style.fontSize = '32px';
mainDiv.style.filter = 'blur(0)';
transDiv.style.filter = 'blur(0)';
transDiv.style.color = '#353535';
transDiv.style.fontWeight = 'bold';
transDiv.style.fontSize = '24px';
} else {
mainDiv.style.color = '#565656';
mainDiv.style.filter = 'blur(3px)';
mainDiv.style.marginTop = '20px';
mainDiv.style.fontWeight = 'normal';
mainDiv.style.fontSize = '28px';
transDiv.style.filter = 'blur(3px)';
transDiv.style.color = '#888';
transDiv.style.fontWeight = 'normal';
transDiv.style.fontSize = '20px';
}
}
// 计算滚动偏移(groupHeight + 下边距),不知道怎么调的,反正按照现在这样数值设置了看着还可以
const groupHeight = 90;
const container = document.getElementById('lyrics-display');
const containerHeight = container.clientHeight;
const offset = (currentIndex * groupHeight) - (containerHeight / 2 - groupHeight / 2) + 30;
const lyricsContentDiv = document.getElementById('lyrics-content');
lyricsContentDiv.style.transform = `translateY(-${offset}px)`;
}
const lyricsDisplay = createLyricsDisplay();
// 更新滚动
document.addEventListener('timeupdate', function(event) {
const audioPlayer = event.target;
if (audioPlayer.id === 'apple-music-player') {
const startOffset = parseFloat(audioPlayer.dataset.startOffset) || 0;
const effectiveTime = audioPlayer.currentTime - startOffset;
updateLyricScroll(effectiveTime);
}
}, true);
// 每秒检测歌曲标题变化(切歌)
setInterval(function() {
const audioPlayer = document.getElementById('apple-music-player');
if (audioPlayer) {
let title = audioPlayer.title;
if (title) {
const secondDashIndex = title.indexOf('-', title.indexOf('-') + 1);
if (secondDashIndex !== -1) {
title = title.substring(0, secondDashIndex).trim();
}
if (title !== audioPlayer.dataset.lastTitle) {
audioPlayer.dataset.lastTitle = title;
audioPlayer.dataset.startOffset = audioPlayer.currentTime;
const lyricsContent = document.getElementById('lyrics-content');
if (lyricsContent) {
lyricsContent.innerHTML = '';
}
getSongId();
}
}
}
}, 1000);
// 当前曲播放结束时也尝试重新获取歌词(适用于自动切换下一曲)
const audioPlayer = document.getElementById('apple-music-player');
if (audioPlayer) {
audioPlayer.addEventListener('ended', function() {
setTimeout(() => {
audioPlayer.dataset.startOffset = audioPlayer.currentTime;
getSongId();
}, 500);
});
}
})();