巧用张洪HeoMusic思路构建了我的“歌单”
在音乐爱好者的世界里,拥有一个能自由听取的个性化歌单是许多人的梦想。为了实现这一目标,我借鉴了张洪 Heomusic 的思路,成功搭建了属于自己的歌单页面。
https://blog.zhheo.com/p/45699256.html
张洪 Heomusic 的思路为整个项目提供了关键的指引方向,其独特的架构理念和对音乐播放系统的理解,成为我搭建歌单页面的基石。在这个基础上,我对Meting.min.js进行了二次更改,这一举措极大地拓展了音乐获取的自由度。通过对代码的精细调整,我可以更灵活地从不同渠道获取心仪的音乐,让歌单内容更加丰富多样。值得注意的是因为跨域等问题,我们需要自己构建、或者反代一个API,因为版权原因,暂不提供思路,我是使用的injahow/Meting-API使用cloudflare搭建的api。
为了实现多id合并并打乱顺序输出json,我对Meting.min.js进行了下述修改(尊重著作权,console.log输出版权信息)
class MetingJSElement extends htmlElement { constructor() { super(); this._initialized = false; } connectedCallback() { if (WINdow.APlayer && WINdow.fetch &&!this._initialized) { this._init(); this._parse(); this._initialized = true; } } disconnectedCallback() { if (!this.lock && this.APlayer) { this.APlayer.destroy(); } } _camelize(str) { return str .replace(/^[_.\- ]+/, '') .toLowerCase() .replace(/[_.\- ]+(\w|$)/g, (m, p1) => p1.toUpperCase()); } _init() { let config = {}; for (let i = 0; i < this.attributes.length; i += 1) { config[this._camelize(this.attributes[i].name)] = this.attributes[i].value; } let keys = [ 'server', 'type', 'id', 'api', 'auth', 'auto', 'lock', 'name', 'title', 'artist', 'author', 'url', 'cover', 'pic', 'lyric', 'lrc', ]; this.meta = {}; for (let key of keys) { this.meta[key] = config[key]; delete config[key]; } this.config = config; this.api = this.meta.api || window.Meting_api || 'https://xxx.xxx.xxx/?server=:server&type=:type&id=:id&r=:r'; if (this.meta.auto) this._parse_link(); // 支持多个 ID,将 id 属性按逗号分割成数组 this.meta.ids = this.meta.id.split(','); } _parse_link() { let rules = [ ['music.163.com.*song.*id=(\\d+)', 'netease', 'song'], ['music.163.com.*album.*id=(\\d+)', 'netease', 'album'], ['music.163.com.*artist.*id=(\\d+)', 'netease', 'artist'], ['music.163.com.*playlist.*id=(\\d+)', 'netease', 'playlist'], ['music.163.com.*discover/toplist.*id=(\\d+)', 'netease', 'playlist'], ['y.qq.com.*song/(\\w+).html', 'tencent', 'song'], ['y.qq.com.*album/(\\w+).HTML', 'tencent', 'album'], ['y.qq.com.*singer/(\\w+).HTML', 'tencent', 'artist'], ['y.qq.com.*playsquare/(\\w+).html', 'tencent', 'playlist'], ['y.qq.com.*playlist/(\\w+).html', 'tencent', 'playlist'], ['xiami.com.*song/(\\w+)', 'xiami', 'song'], ['xiami.com.*album/(\\w+)', 'xiami', 'album'], ['xiami.com.*artist/(\\w+)', 'xiami', 'artist'], ['xiami.com.*collect/(\\w+)', 'xiami', 'playlist'], ]; for (let rule of rules) { let patt = new RegExp(rule[0]); let res = patt.exec(this.meta.auto); if (res!== null) { this.meta.server = rule[1]; this.meta.type = rule[2]; this.meta.id = res[1]; return; } } } async _parse() { if (this.meta.url) { let result = { name: this.meta.name || this.meta.title || 'Audio name', artist: this.meta.artist || this.meta.author || 'Audio artist', url: this.meta.url, cover: this.meta.cover || this.meta.pic, lrc: this.meta.lrc || this.meta.lyric || '', type: this.meta.type || 'auto', }; if (!result.lrc) { this.meta.lrcType = 0; } if (this.innerText) { result.lrc = this.innerText; this.meta.lrcType = 2; } this._loadPlayer([result]); return; } const allMusicData = []; for (const id of this.meta.ids) { let url = this.api .replace(':server', this.meta.server) .replace(':type', this.meta.type) .replace(':id', id) .replace(':auth', this.meta.auth) .replace(':r', Math.random()); try { const res = await fetch(url); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const text = await res.text(); const result = JSON.parse(text); if (Array.isArray(result)) { allMusicData.push(...result); } else { console.error(`Data for ID ${id} is not an array:`, result); } } catch (error) { console.error(`Fetch error for ID ${id}:`, error); } } // 对获取到的所有音乐数据进行随机排序 const shuffledMusicData = this._shuffleArray(allMusicData); this._loadPlayer(shuffledMusicData); } _shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } _loadPlayer(data) { let defaultOption = { audio: data, mutex: true, lrcType: this.meta.lrcType || 3, storageName: 'Metingjs' }; if (!data.length) return; let options = { ...defaultOption, ...this.config, }; for (let optkey in options) { if (options[optkey] === 'true' || options[optkey] === 'false') { options[optkey] = (options[optkey] === 'true'); } } let div = document.createElement('div'); options.contAIner = div; this.appendChild(div); this.APlayer = new APlayer(options); } } console.log('\n %c MetingJS v2.0.1 %c https://github.com/metowolf/MetingJS \n', 'color: #fadfa3; background: #030307; padding:5px 0;', 'background: #fadfa3; padding:5px 0;'); if (window.customElements &&!window.customElements.GET('meting-js')) { window.MetingJSElement = MetingJSElement; window.customElements.define('meting-js', MetingJSElement); }
然而,在享受技术带来的便利时,我们绝不能忽视著作权的重要性。每一首音乐都是创作者的心血结晶,我们应当在合法合规的前提下使用这些作品,确保对知识产权的尊重。
在前端渲染方面,我选择了APlayer播放器。APlayer以其简洁美观的界面和强大的功能,为用户带来了优质的播放体验。它能够流畅地展示歌曲信息、控制播放进度,并且支持多种主题自定义,使歌单页面在功能性和美观性上达到了较好的平衡。
在前端仅需要随便往哪里插入下列代码即可(我是获取的“我收藏的歌单”下列的歌单,id是什么,需要自行理解,我尊重各站版权)
<link rel="stylesheet" href="/sucAI/APlayer/APlayer.min.css"> <script src="/sucai/APlayer/APlayer.min.js"></script> <script src="/sucai/APlayer/Meting.min.js"></script> <meting-js server="netease" type="playlist" id="6948853317,326934130,446132364,978446985,2286543721,7235859079" autoplay="true" order="list" preload="auto" list-max-height="100vh"></meting-js>
通过这次实践,我不仅实现了歌单听取自由化的目标,还在技术运用和版权意识上有了更深的体会。希望我的经验能为其他音乐爱好者和开发者提供一些参考,共同打造更加优质、合法的音乐环境。