// ==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 // @downloadURL https://update.greasyfork.icu/scripts/493948/Apple%20Music%20%E6%AD%8C%E8%AF%8D%E5%A2%9E%E5%BC%BA.user.js // @updateURL https://update.greasyfork.icu/scripts/493948/Apple%20Music%20%E6%AD%8C%E8%AF%8D%E5%A2%9E%E5%BC%BA.meta.js // ==/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); }); } })();