• MetingJS 是如何配合 Aplayer 加载歌单的


    Meting.js 介绍

    image

    Meting.js 依赖 APlayer.js,扩展了 APlayer.js 的功能,能够使 APlayer.js 加载网易云音乐、QQ 音乐、虾米音乐中的歌单。

    安装

    <!-- require APlayer -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css">
    <script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
    <!-- require MetingJS -->
    <script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>
    

    使用

    加载歌单

    <meting-js
    	server="netease"
    	type="playlist"
    	id="60198">
    </meting-js>
    

    通过 auto 属性加载单曲:

    <meting-js
    	auto="https://y.qq.com/n/yqq/song/001RGrEX3ija5X.html">
    </meting-js>
    

    通过 url 加载单曲:

    <meting-js
    	name="rainymood"
    	artist="rainymood"
    	url="https://rainymood.com/audio1110/0.m4a"
    	cover="https://rainymood.com/i/badge.jpg">
    </meting-js>
    

    加载托管在其他服务器上的单曲:

    <meting-js
    	name="rainymood"
    	artist="rainymood"
    	url="https://rainymood.com/audio1110/0.m4a"
    	cover="https://rainymood.com/i/badge.jpg"
    	fixed="true">
    	<pre hidden>
    		[00:00.00]This
    		[00:04.01]is
    		[00:08.02]lyric
    	</pre>
    </meting-js>
    

    源码解析

    class MetingJSElement extends HTMLElement {
      /**
       * 当自定义元素第一次被连接到文档 DOM 时被调用
       * connectedCallback
       * https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks
       */
      connectedCallback() {
        if (window.APlayer && window.fetch) {
          this._init()
          this._parse()
        }
      }
    
      /**
       * 与 connectedCallback 反
       */
      disconnectedCallback() {
        if (!this.lock) {
          this.aplayer.destroy()
        }
      }
    
      /**
       * 驼峰化
       * @param { string } str
       * @returns { string } str
       */
      _camelize(str) {
        return str
          .replace(/^[_.- ]+/, '')
          .toLowerCase()
          .replace(/[_.- ]+(w|$)/g, (m, p1) => p1.toUpperCase())
      }
    
      /**
       * 初始化
       */
      _init() {
        let config = {}
    
        // attributes -> NamedNodeMap
        // https://developer.mozilla.org/zh-CN/docs/Web/API/NamedNodeMap
        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 = {}
    
        // 构建 meta
        // config 保留 keys 数组中没有的属性
        // keys 中有 config 中也有的属性给 meta 赋值,没有的先设为 undefined
        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://api.i-meto.com/meting/api?server=:server&type=:type&id=:id&r=:r'
    
        if (this.meta.auto) this._parse_link()
      }
    
      /**
       * 解析 auto 属性的值
       * 将解析后的结果赋值给 meta 对象的 server、type、id
       */
      _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) {
          // 返回匹配
          // eg: "https://y.qq.com/n/yqq/song/001RGrEX3ija5X.html"
          // ["y.qq.com/n/yqq/song/001RGrEX3ija5X.html", "001RGrEX3ija5X"]
          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
          }
        }
      }
    
      /**
       * 对不同 url 仅行处理
       * 生成配置并加载 APlayer
       */
      _parse() {
        if (this.meta.url) {
          // 直接构建 APlayer 配置并加载 APlayer
          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
        }
    
        // 1. 通过 meta 拼凑接口参数获得完整接口 (_init 中存放的默认 api)
        // 2. 请求接口,得到播放列表数据
        // 3. 加载 APlayer
        let url = this.api
          .replace(':server', this.meta.server)
          .replace(':type', this.meta.type)
          .replace(':id', this.meta.id)
          .replace(':auth', this.meta.auth)
          .replace(':r', Math.random())
    
        fetch(url)
          .then(res => res.json())
          .then(result => this._loadPlayer(result))
      }
    
      _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)
      }
    }
    
    // 创建标签
    // customElements -> https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
    if (window.customElements && !window.customElements.get('meting-js')) {
      window.MetingJSElement = MetingJSElement
      window.customElements.define('meting-js', MetingJSElement)
    }
    

    总结

    Meting.js 支持加载歌单的的核心在于路径解析以及通过请求内置接口返回歌单列表数据。关键一点, Meting.js 使用了 JavaScript customElements API,可能为了使用者方便,但这导致没有显式向外暴露任何 APlayer 实例,对于稍复杂的场景可能无法处理,只能进行魔改。Meting.js 比较适合较简单的 SPA 应用。关于兼容性,IE11 不支持 customElements API。

    image

    理想情况是:Meting.js 与 APlayer.js 解耦,Meting.js 以工具函数的形式存在,仅处理 url 并抛出歌单数据,供 APlayer.js 使用。

  • 相关阅读:
    [LeetCode] Implement Queue using Stacks 用栈来实现队列
    [LeetCode] Power of Two 判断2的次方数
    [LeetCode] 230. Kth Smallest Element in a BST 二叉搜索树中的第K小的元素
    cvReleaseImage 释放内存出错
    FlyCapture2 fc2Image OpenCV IplImage Conversion 两种图像格式之间的转换
    FlyCapture2 Qt5 MinGW Configuration
    [LeetCode] 14. Longest Common Prefix 最长共同前缀
    [LeetCode] 19. Remove Nth Node From End of List 移除链表倒数第N个节点
    [LeetCode] 229. Majority Element II 求大多数之二
    [LeetCode] 23. Merge k Sorted Lists 合并k个有序链表
  • 原文地址:https://www.cnblogs.com/guangzan/p/14932931.html
Copyright © 2020-2023  润新知