zoukankan      html  css  js  c++  java
  • 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 使用。

  • 相关阅读:
    电子商务网站的设计与实现(四):项目名称malling和一期开发计划
    电子商务网站的设计与实现(三):四大子系统,登录-账务-前端-后端
    电子商务网站的设计与实现(三):四大子系统,登录-账务-前端-后端
    删除垃圾软件,系统和网络出现故障
    删除垃圾软件,系统和网络出现故障
    2014年工作中遇到的20个问题:201-220
    2014年工作中遇到的20个问题:201-220
    电子商务网站的设计与实现(二):一期功能清单
    电子商务网站的设计与实现(二):一期功能清单
    CDS view注解解析
  • 原文地址:https://www.cnblogs.com/guangzan/p/14932931.html
Copyright © 2011-2022 走看看