当前位置:南蛮子懋和 > 技术 > Z-BlogPHP > 巧用张洪HeoMusic思路构建了我的“歌单”

巧用张洪HeoMusic思路构建了我的“歌单”

南蛮子懋和2025年04月01日Z-BlogPHP414
文章摘要
DaoGPT
此内容根据文章AI生成,并经过人工审核,仅用于文章内容的解释与总结
投诉

image.png

在音乐爱好者的世界里,拥有一个能自由听取的个性化歌单是许多人的梦想。为了实现这一目标,我借鉴了张洪 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>

通过这次实践,我不仅实现了歌单听取自由化的目标,还在技术运用和版权意识上有了更深的体会。希望我的经验能为其他音乐爱好者和开发者提供一些参考,共同打造更加优质、合法的音乐环境。

https://www.dao.js.cn/music

分享给朋友:

评论列表

清酒与友
清酒与友   UC Browser 13.7.2.1636  Apple iPhone
2025年04月01日

巧用张洪HeoMusic的独特思路,我成功构建了个性化的歌单,让音乐聆听体验更加丰富多元。

HansJack
HansJack Google Chrome 134.0.0.0  Android 10
2025年04月01日

使用api?跟以前的x5内核解析电影一样,通过多个api链接获取电影。我还是感觉使用cookie获取歌曲好点,至少播放是使用自己账号的权益,使用api难免会造成免费音乐软件一样的窘境。

HansJack  Google Chrome 134.0.0.0 Android 10 回复:
你博客为什么总在黑夜和白天模式切来切去?验证码是不是有bug(错好久才对),我是入机?
2025年04月01日
南蛮子懋和  QQBrowser 13.7.6351.400 Windows 10 x64 回复:
目前我就是通过自己账号的cookie获取的自己的歌单,只是通过api反代了一下url,因为部分可分享的、对外可用外链的一些音乐平台的打开响应速度,并不是很理想。关于评论存在的一些所谓问题,是因为CDN没有刷新,我也在评论框里文字提示了,同样黑夜白天切换都是根据时间自动切换的。
2025年04月02日

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。

请如实填写常用的真实邮箱,方便后续的回复邮件通知。