Greasy Fork is available in English.
在网易云音乐 Web 版上启用 MediaSession 支持
- // ==UserScript==// @name 163 Music MediaMetadata// @version 1.0// @description 在网易云音乐 Web 版上启用 MediaSession 支持// @match *://music.163.com/*// @include *://music.163.com/*// @author 864907600cc// @icon https://secure.gravatar.com/avatar/147834caf9ccb0a66b2505c753747867// @run-at document-end// @grant none// @namespace http://ext.ccloli.com// ==/UserScript=='use strict';/* globals CryptoJS, MediaMetadata */(() => {if (!navigator.mediaSession || typeof MediaMetadata === 'undefined') {console.log('The browser doesn\'t support MediaSession');return;}/*** document.querySelector** @param {string} selector - 选择器* @returns {Node|null} 节点*/const $ = (selector) => document.querySelector(selector);/*** 获取缩放后的图片 URL** @param {string} url - 图片 URL* @param {number} [size] - 图片缩放尺寸* @returns {string} 缩放后的图片 URL*/const getResizedPicUrl = (url, size) => {return `${url}${size ? `?param=${size}y${size}` : ''}`;};/*** 获取图片完整 URL** @param {number|string} id - 文件 ID* @returns {string} 图片 URL*/const getPicUrl = (id) => {const key = '3go8&$8*3*3h0k(2)2';const idStr = `${id}`;const idStrLen = idStr.length;const token = idStr.split('').map((e, i) => {return String.fromCharCode(e.charCodeAt(0) ^ key.charCodeAt(i % idStrLen));}).join('');const r###lt = CryptoJS.MD5(token).toString(CryptoJS.enc.Base64).replace(/\/|\+/g, match => ({'/': '_','+': '-'})[match]);return `https://p1.music.126.net/${r###lt}/${idStr}.jpg`;};/*** 从播放列表中生成对应曲目的 metadata** @param {number} id - 歌曲 ID* @returns {object} metadata 数据内容*/const getSongMetadata = (id) => {let r###lt;try {const playlist = JSON.parse(localStorage.getItem('track-queue'));const item = playlist.find(e => e.id === +id);if (item) {const album = item.album || {};r###lt = {title: item.name,artist: (item.artists || []).map(e => e.name).join('/'),album: album.name,artwork: [{src: getResizedPicUrl(album.picUrl || getPicUrl(album.pic_str || album.pic), 160),sizes: '160x160',type: 'image/jpeg'}, {src: getResizedPicUrl(album.picUrl || getPicUrl(album.pic_str || album.pic), 320),sizes: '320x320',type: 'image/jpeg'}, {src: getResizedPicUrl(album.picUrl || getPicUrl(album.pic_str || album.pic), 480),sizes: '480x480',type: 'image/jpeg'}]};}} catch (err) {console.log(err);}return r###lt;};/*** 从页面内 DOM 生成 metadata(不含专辑名)** @returns {object} metadata 数据内容*/const generateCurrentMetadata = () => {const coverUrl = $('.m-playbar .head > img').getAttribute('src').split('?').shift();return {title: $('.m-playbar .words .name').getAttribute('title'),artist: $('.m-playbar .words .by > span').getAttribute('title'),artwork: [{src: getResizedPicUrl(coverUrl),sizes: '160x160',type: 'image/jpeg'}, {src: getResizedPicUrl(coverUrl),sizes: '320x320',type: 'image/jpeg'}, {src: getResizedPicUrl(coverUrl),sizes: '480x480',type: 'image/jpeg'}]};};/*** 设置 metadata 到 mediaSession 上** @param {object} [data] - metadata 数据*/const setMetadata = (data) => {if (data) {navigator.mediaSession.metadata = new MediaMetadata(data);}else {navigator.mediaSession.metadata = null;}};/*** 更新当前曲目的 media metadata**/const updateCurrent = () => {const id = ($('.m-playbar .words .name').getAttribute('href').match(/\?id=(\d+)/) || [])[1];if (id) {const data = getSongMetadata(id) || generateCurrentMetadata();setMetadata(data);}};/*** 处理客户端播放面板操作回调**/const setActionHandler = () => {navigator.mediaSession.setActionHandler('previoustrack', () => {$('.m-playbar .btns .prv').click();});navigator.mediaSession.setActionHandler('nexttrack', () => {$('.m-playbar .btns .nxt').click();});};/*** 初始化函数**/const init = () => {if ($('.m-playbar')) {// 使用 animation 事件监听可能会覆盖其他使用相同方法处理的脚本,改用 MutationObserverconst observer = new MutationObserver((mutations) => {if (mutations && mutations[0]) {updateCurrent();}});observer.observe($('.m-playbar .words'), {attributes: true, childList: true, subtree: true});setActionHandler();updateCurrent();}};init();})();