- // ==UserScript==
- // @name Apple Music 歌词增强
- // @namespace https://github.com/akashiwest/AML-Enhancer
- // @version 1.100
- // @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 = [];
-
- // 创建固定容器(含内部内容容器)用于显示歌词
- 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.1';
- 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 toggleButton = document.createElement('button');
- const infoButton = document.createElement('button');
- toggleButton.id = 'toggle-size-button';
- infoButton.id = 'info-button';
-
- toggleButton.innerText = '放大';
- Object.assign(toggleButton.style, {
- position: 'absolute',
- bottom: '10px',
- right: '10px',
- padding: '5px 10px',
- fontSize: '14px',
- border: 'none',
- borderRadius: '15px',
- background: '#ddd',
- cursor: 'pointer',
- zIndex: '10000',
- opacity: '0.8'
- });
- infoButton.innerText = 'O';
- Object.assign(infoButton.style, {
- position: 'absolute',
- bottom: '10px',
- right: '65px',
- padding: '5px 8px',
- fontSize: '14px',
- border: 'none',
- borderRadius: '15px',
- background: '#ddd',
- cursor: 'pointer',
- zIndex: '10000',
- opacity: '0.3'
- });
- 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 = '放大';
- }
- });
- lyricsDiv.appendChild(toggleButton);
- lyricsDiv.appendChild(infoButton);
- infoButton.addEventListener('click', function(e) {
- e.stopPropagation();
- window.open('https://github.com/akashiwest/AML-Enhancer', '_blank');
- });
-
- // 拖拽功能
- lyricsDiv.onmousedown = dragMouseDown;
- let pos3 = 0, pos4 = 0;
- function dragMouseDown(e) {
- // 点击按钮不触发
- if (e.target === toggleButton) 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();
- }
- 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;
- let currentIndex = 0;
- for (let i = 0; i < currentLyrics.length; i++) {
- if (currentTime >= 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);
- });
- }
- })();