Full Code of DIYgod/APlayer for AI

master 52e9feee9c90 cached
43 files
145.7 KB
39.8k tokens
95 symbols
1 requests
Download .txt
Repository: DIYgod/APlayer
Branch: master
Commit: 52e9feee9c90
Files: 43
Total size: 145.7 KB

Directory structure:
gitextract_5z3xtwg5/

├── .eslintignore
├── .eslintrc
├── .github/
│   └── ISSUE_TEMPLATE.md
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── demo/
│   ├── demo.js
│   └── index.html
├── docs/
│   ├── .nojekyll
│   ├── CNAME
│   ├── README.md
│   ├── config.js
│   ├── ecosystem.md
│   ├── index.html
│   ├── landing.html
│   ├── support.md
│   └── zh-Hans/
│       ├── README.md
│       ├── ecosystem.md
│       └── support.md
├── package.json
├── src/
│   ├── css/
│   │   └── index.scss
│   ├── js/
│   │   ├── bar.js
│   │   ├── controller.js
│   │   ├── events.js
│   │   ├── icons.js
│   │   ├── index.js
│   │   ├── list.js
│   │   ├── lrc.js
│   │   ├── options.js
│   │   ├── player.js
│   │   ├── storage.js
│   │   ├── template.js
│   │   ├── timer.js
│   │   └── utils.js
│   └── template/
│       ├── list-item.art
│       ├── lrc.art
│       └── player.art
├── tea.yaml
└── webpack/
    ├── dev.config.js
    └── prod.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintignore
================================================
dist
demo
docs

================================================
FILE: .eslintrc
================================================
{
    "extends": ["eslint:recommended", "plugin:prettier/recommended"],
    "plugins": ["prettier"],
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "env": {
        "es6": true,
        "browser": true,
        "node": true,
    },
    "rules": {
        "block-scoped-var": 1,
        "curly": 1,
        "eqeqeq": 1,
        "no-global-assign": 1,
        "no-implicit-globals": 1,
        "no-labels": 1,
        "no-multi-str": 1,
        "comma-spacing": 1,
        "comma-style": 1,
        "func-call-spacing": 1,
        "keyword-spacing": 1,
        "linebreak-style": 1,
        "no-multiple-empty-lines": 1,
        "space-infix-ops": 1,
        "arrow-spacing": 1,
        "no-var": 1,
        "prefer-const": 1,
        "no-unsafe-negation": 1,
        "array-callback-return": 1,
        "dot-notation": 1,
        "no-eval": 1,
        "no-extend-native": 1,
        "no-extra-label": 1,
        "semi": 1,
        "space-before-blocks": 1,
        "space-in-parens": 1,
        "space-unary-ops": 1,
        "spaced-comment": 1,
        "arrow-body-style": 1,
        "arrow-parens": 1,
        "no-restricted-imports": 1,
        "no-duplicate-imports": 1,
        "no-useless-computed-key": 1,
        "no-useless-rename": 1,
        "rest-spread-spacing": 1,
        "no-trailing-spaces": 1,
        "no-control-regex": 0,
        "prettier/prettier": 0,
        "no-await-in-loop": 1,
        "require-atomic-updates": 0,
        "no-prototype-builtins": 0
    }
}


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
中文用户请注意:请尽量用**英文**描述你的 issue,这样能够让尽可能多的人帮到你。

If you want to report a bug, please provide the following information:

-   The steps to reproduce.
-   A minimal demo of the problem via https://jsfiddle.net or http://codepen.io/pen if possible.
-   Which versions of APlayer, and which browser / OS are affected by this issue?


================================================
FILE: .gitignore
================================================
.idea
node_modules
demo2
npm-debug.log

================================================
FILE: .prettierignore
================================================
dist
demo
docs

================================================
FILE: .prettierrc
================================================
{
    "printWidth": 233,
    "tabWidth": 4,
    "singleQuote": true,
    "trailingComma": "es5",
    "arrowParens": "always"
}


================================================
FILE: .travis.yml
================================================
language: node_js

node_js:
  - lts/*

script:
  - npm run build

cache:
  yarn: true
  directories:
    - node_modules

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) DIYgod <diy.d.god@gmail.com> (https://www.anotherhome.net/)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
<img src="https://i.imgur.com/LnPvZvO.png" alt="ADPlayer" width="100">
</p>
<h1 align="center">APlayer</h1>

> Wow, such a lovely HTML5 music player

[![npm](https://img.shields.io/npm/v/aplayer.svg?style=flat-square)](https://www.npmjs.com/package/aplayer)
[![npm](https://img.shields.io/npm/l/aplayer.svg?style=flat-square)](https://github.com/MoePlayer/APlayer/blob/master/LICENSE)
[![npm](https://img.shields.io/npm/dt/aplayer.svg?style=flat-square)](https://www.npmjs.com/package/aplayer)
[![size](https://badge-size.herokuapp.com/MoePlayer/APlayer/master/dist/APlayer.min.js?compression=gzip&style=flat-square)](https://github.com/MoePlayer/APlayer/tree/master/dist)
[![Travis](https://img.shields.io/travis/MoePlayer/APlayer.svg?style=flat-square)](https://travis-ci.org/MoePlayer/APlayer)
[![devDependency Status](https://img.shields.io/david/dev/MoePlayer/aplayer.svg?style=flat-square)](https://david-dm.org/MoePlayer/APlayer#info=devDependencies)
[![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?style=flat-square)](https://github.com/MoePlayer/APlayer#donate)

## Introduction

![image](https://i.imgur.com/JDrJXCr.png)

APlayer is a lovely HTML5 music player.

**APlayer supports:**

-   Media formats - MP4 H.264 (AAC or MP3) - WAVE PCM - Ogg Theora Vorbis
-   Features - Playlist - Lyrics

Using APlayer on your project? [Let me know!](https://github.com/MoePlayer/APlayer/issues/79)

**[Docs](https://aplayer.js.org)**

**[中文文档](https://aplayer.js.org/#/zh-Hans/)**

## Join the Discussion

-   [Telegram Group](https://t.me/adplayer)
-   [QQ Group](https://shang.qq.com/wpa/qunwpa?idkey=bf22213ae0028a82e5adf3f286dfd4f01e0997dc9f1dcd8e831a0a85e799be17): 415835947

## Related Projects

### Plugins

-   [APlayer-Typecho-Plugin](https://github.com/zgq354/APlayer-Typecho-Plugin): Typecho
-   [hexo-tag-aplayer](https://github.com/grzhan/hexo-tag-aplayer): Hexo
-   [Hermit-X(APlayer for WordPress)](https://github.com/liwanglin12/Hermit-X): WordPress
-   [APlayerHandle](https://github.com/kn007/APlayerHandle): WordPress
-   [APlayer_for_Z-BlogPHP](https://github.com/fghrsh/APlayer_for_Z-BlogPHP): Z-BlogPHP
-   [react-aplayer](https://github.com/sabrinaluo/react-aplayer): React
-   [Vue-APlayer](https://github.com/SevenOutman/vue-aplayer): Vue
-   [vue-aplayer](https://github.com/MoeFE/vue-aplayer): Vue
-   [php-aplayer](https://github.com/Daryl-L/php-aplayer): PHP
-   [aplayer-hugo-module](https://github.com/Runzelee/aplayer-hugo-module): Hugo

### Tooling

-   [APlayer-Controler](https://github.com/Mashiro-Sorata/APlayer-Controler): controling tool
-   [MetingJS](https://github.com/metowolf/MetingJS): work with Meting music API
-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/APlayer/issues/79)

## Who use APlayer?

-   [bilibili](https://www.bilibili.com/): 国内知名的视频弹幕网站
-   [黑客派](https://hacpai.com/): 程序员和设计师的聚集地,一个活跃的小众社区
-   [浙江大学 CC98 论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛,中国各大学中较活跃的 BBS 之一
-   [Jelly Rue](http://jellyrue.com/): Jelly Rue, an indie pop-rock band from Tartu.
-   [Opus](http://www.opusopus.co/): An artist-exploration data visualization application
-   [站长之家](http://www.chinaz.com/15year/index.html): 针对中文站点提供资讯、技术、资源、服务
-   [LLSupport](https://www.lovelivesupport.com/): This site provides a lot of information about LoveLive
-   [歌词千寻](https://www.lrcgc.com/diy): 每日更新的 LRC 歌词网站
-   [iSearch](http://i.oppsu.cn): 一个提供 iTunes 搜索,试听,高清专辑封面获取,查看最新音乐动态等综合性平台
-   [LRC 歌词编辑器](https://github.com/MoeFE/Lyric): 一款非常实用的在线 LRC 歌词编辑器
-   [Аэростатика](https://aerostatica.ru/)
-   [HealthDig](https://healthdig.co): 每天只需两分钟的重点新闻资讯
-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/APlayer/issues/79)

## Current Premium Sponsors

### Special Sponsors

<a href="https://www.dogecloud.com/?ref=dplayer" target="_blank">
    <img width="222px" src="https://player.dogecloud.com/img/logo_with_product3.png">
</a>

## Contributors

This project exists thanks to all the people who contribute.

<a href="https://github.com/MoePlayer/APlayer/graphs/contributors"><img src="https://opencollective.com/APlayer/contributors.svg?width=890" /></a>

## Donate

APlayer is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing.

You can support APlayer via donations.

### Recurring Donation

-   Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod)
-   Become a Sponser on [Patreon](https://www.patreon.com/DIYgod)
-   Become a Sponser on [爱发电](https://afdian.net/@diygod)
-   Contact us directly: i@diygod.me

### One-time Donation

We accept donations via the following ways:

-   [WeChat Pay](https://diygod.me/images/wx.jpg)
-   [Alipay](https://diygod.me/images/zfb.jpg)
-   [Paypal](https://www.paypal.me/DIYgod)

## Author

**APlayer** © [DIYgod](https://github.com/DIYgod), Released under the [MIT](./LICENSE) License.<br>
Authored and maintained by DIYgod with help from contributors ([list](https://github.com/DIYgod/APlayer/contributors)).

> [Blog](https://diygod.me) · GitHub [@DIYgod](https://github.com/DIYgod) · Twitter [@DIYgod](https://twitter.com/DIYgod) · Telegram Channel [@awesomeDIYgod](https://t.me/awesomeDIYgod)


================================================
FILE: demo/demo.js
================================================
const ap1 = new APlayer({
    element: document.getElementById('player1'),
    mini: false,
    autoplay: false,
    lrcType: false,
    mutex: true,
    preload: 'metadata',
    audio: [{
        name: '光るなら',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        theme: '#ebd0c2'
    }]
});
ap1.on('play', function () {
    console.log('play');
});
ap1.on('play', function () {
    console.log('play play');
});
ap1.on('pause', function () {
    console.log('pause');
});
ap1.on('canplay', function () {
    console.log('canplay');
});
ap1.on('playing', function () {
    console.log('playing');
});
ap1.on('ended', function () {
    console.log('ended');
});
ap1.on('error', function () {
    console.log('error');
});

const ap2 = new APlayer({
    element: document.getElementById('player2'),
    mini: true,
    autoplay: false,
    lrcType: false,
    mutex: true,
    audio: [{
        name: '光るなら',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
        theme: '#ebd0c2'
    }]
});

const ap3 = new APlayer({
    element: document.getElementById('player3'),
    mini: false,
    autoplay: false,
    lrcType: 3,
    mutex: true,
    audio: [{
        name: '光るなら',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
        theme: '#ebd0c2'
    }]
});

const ap4 = new APlayer({
    element: document.getElementById('player4'),
    mini: false,
    autoplay: false,
    lrcType: false,
    mutex: true,
    theme: '#ad7a86',
    order: 'random',
    audio: [{
        name: '光るなら',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
        theme: '#ebd0c2'
    }, {
        name: 'トリカゴ',
        artist: 'XX:me',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
        theme: '#46718b'
    }, {
        name: '前前前世',
        artist: 'RADWIMPS',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
        theme: '#505d6b'
    }, {
        name: '光るなら(HLS)',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hls/hikarunara.m3u8',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        theme: '#ebd0c2',
        type: 'hls'
    }]
});

const ap5 = new APlayer({
    element: document.getElementById('player5'),
    mini: false,
    autoplay: false,
    lrcType: 3,
    mutex: true,
    theme: '#e9e9e9',
    listFolded: false,
    listMaxHeight: 80,
    audio: [{
        name: '光るなら',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
    }, {
        name: 'トリカゴ',
        artist: 'XX:me',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
    }, {
        name: '前前前世',
        artist: 'RADWIMPS',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
        lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
    }]
});
const colorThief = new ColorThief();
const image = new Image();
const xhr = new XMLHttpRequest();
const setTheme = (index) => {
    if (!ap5.list.audios[index].theme) {
        xhr.onload = function(){
            let coverUrl = URL.createObjectURL(this.response);
            image.onload = function(){
                let color = colorThief.getColor(image);
                ap5.theme(`rgb(${color[0]}, ${color[1]}, ${color[2]})`, index);
                URL.revokeObjectURL(coverUrl)
            };
            image.src = coverUrl;
        }
        xhr.open('GET', ap5.list.audios[index].cover, true);
        xhr.responseType = 'blob';
        xhr.send();
    }
};
setTheme(ap5.list.index);
ap5.on('listswitch', (data) => {
    setTheme(data.index);
});

const ap6 = new APlayer({
    element: document.getElementById('player6'),
    mutex: true,
    audio: [{
        name: '光るなら(HLS)',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hls/hikarunara.m3u8',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        theme: '#ebd0c2',
        type: 'hls'
    }]
});
const ap7 = new APlayer({
    element: document.getElementById('player7'),
    mutex: true,
    audio: [{
        name: '光るなら(HLS)',
        artist: 'Goose house',
        url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hls/hikarunara.m3u8',
        cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        theme: '#ebd0c2',
        type: 'customHls',
    }],
    customAudioType: {
        'customHls': function (audioElement, audio, player) {
            if (Hls.isSupported()) {
                const hls = new Hls();
                hls.loadSource(audio.url);
                hls.attachMedia(audioElement);
            }
            else if (audioElement.canPlayType('application/x-mpegURL') || audioElement.canPlayType('application/vnd.apple.mpegURL')) {
                audioElement.src = audio.url;
            }
            else {
                player.notice('Error: HLS is not supported.');
            }
        }
    }
});

const ap8 = new APlayer({
    element: document.getElementById('player8'),
    mutex: true,
    theme: '#ad7a86',
    order: 'random',
    lrcType: 3,
    fixed: true,
});
$.ajax({
    url: 'https://api.i-meto.com/meting/api?server=netease&type=playlist&id=35798529',
    success: function (list) {
        ap8.list.add(JSON.parse(list));
    }
});

================================================
FILE: demo/index.html
================================================
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>APlayer Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
        }
        .container {
            max-width: 32rem;
            margin-left: auto;
            margin-right: auto;
            margin-bottom: 150px;
        }
        h1 {
            font-size: 54px;
            color: #333;
            margin: 30px 0 10px;
        }
        h2 {
            font-size: 22px;
            color: #555;
        }
        h3 {
            font-size: 24px;
            color: #555;
        }
        hr {
            display: block;
            width: 7rem;
            height: 1px;
            margin: 2.5rem 0;
            background-color: #eee;
            border: 0;
        }
        a {
            color: #08c;
            text-decoration: none;
        }
        p {
            font-size: 18px;
        }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/vconsole/dist/vconsole.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"></script>
    <script src="APlayer.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/color-thief-don@2.0.2/src/color-thief.js"></script>
</head>
<body>
    <div class="container">
        <h1>APlayer</h1>
        <h2>Wow, such a beautiful html5 music player</h2>
        <p>Made by <a href="https://www.anotherhome.net/" target="_blank">DIYgod</a>. Available on <a href="https://github.com/DIYgod/APlayer" target="_blank">GitHub</a>. Licensed MIT.</p>
        <hr>
        <h3>Normal</h3>
        <div id="player1"></div>
        <p></p>
        <button onclick="ap1.play()">ap.play()</button>
        <button onclick="ap1.seek(100)">ap.seek(100)</button>
        <button onclick="ap1.pause()">ap.pause()</button>
        <button onclick="ap1.toggle()">ap.toggle()</button>
        <button onclick="ap1.volume(0.1)">ap.volume(0.1)</button>
        <button onclick="ap1.addAudio([
            {
                name: 'あっちゅ~ま青春!',
                artist: '七森中☆ごらく部',
                url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yuruyuri.mp3',
                cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yuruyuri.jpg'
            },
            {
                name: 'secret base~君がくれたもの~',
                artist: '茅野愛衣',
                url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/secretbase.mp3',
                cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/secretbase.jpg'
            },
            {
                name: '回レ!雪月花',
                artist: '小倉唯',
                url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/snowmoonflowers.mp3',
                cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/snowmoonflowers.jpg'
            }
        ])">ap.addAudio(...)</button>
        <button onclick="ap1.destroy()">ap.destroy()</button>
        <h3>With playlist</h3>
        <div id="player4" class="aplayer"></div>
        <p></p>
        <button onclick="ap4.switchAudio(2)">ap.switchAudio(2)</button>
        <h3>With lyrics</h3>
        <div id="player3" class="aplayer"></div>
        <h3>With playlist and lyrics</h3>
        <div id="player5" class="aplayer"></div>
        <h3>Narrow</h3>
        <div id="player2" class="aplayer"></div>
        <h3>HLS</h3>
        <div id="player6" class="aplayer"></div>
        <div id="player7" class="aplayer"></div>
        <div id="player8" class="aplayer"></div>
    </div>
    <script>
        if (/mobile/i.test(window.navigator.userAgent)) {
            new VConsole();
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/jquery"></script>
    <script src="demo.js"></script>
</body>
</html>

================================================
FILE: docs/.nojekyll
================================================


================================================
FILE: docs/CNAME
================================================
aplayer.js.org

================================================
FILE: docs/README.md
================================================
---
search: english
---

# APlayer

🍭 Wow, such a beautiful HTML5 music player

## Special Sponsors

<a href="https://www.dogecloud.com/?ref=dplayer" target="_blank">
    <img width="222px" src="https://player.dogecloud.com/img/logo_with_product3.png">
</a>

## Installation

Using npm:

```
npm install aplayer --save
```

Using Yarn:

```
yarn add aplayer
```

## Quick Start

<div class="aplayer-wrap">
    <div id="aplayer2"><button class="docute-button load">Click to load player</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg'
    }]
});
```

Work with module bundler:

```js
import 'aplayer/dist/APlayer.min.css';
import APlayer from 'aplayer';

const ap = new APlayer(options);
```

## Options

Name | Default | Description
----|-------|----
container | document.querySelector('.aplayer') | player container
fixed | false | enable fixed mode, [see more details](https://aplayer.js.org/#/home?id=fixed-mode)
mini | false | enable mini mode, [see more details](https://aplayer.js.org/#/home?id=mini-mode)
autoplay | false | audio autoplay
theme | '#b7daff' | main color
loop | 'all' | player loop play, values: 'all', 'one', 'none'
order | 'list' | player play order, values: 'list', 'random'
preload | 'auto' | values: 'none', 'metadata', 'auto'
volume | 0.7 | default volume, notice that player will remember user setting, default volume will not work after user set volume themselves
audio | - | audio info, should be an object or object array
audio.name | - | audio name
audio.artist | - | audio artist
audio.url | - | audio url
audio.cover | - | audio cover
audio.lrc | - | [see more details](https://aplayer.js.org/#/home?id=lrc)
audio.theme | - | main color when switching to this audio, it has priority over the above theme
audio.type | 'auto' | values: 'auto', 'hls', 'normal' or other custom type, [see more details](https://aplayer.js.org/#/home?id=mse-support)
customAudioType | - | [see more details](https://aplayer.js.org/#/home?id=mse-support)
mutex | true | prevent to play multiple player at the same time, pause other players when this player start play
lrcType | 0 | [see more details](https://aplayer.js.org/#/home?id=lrc)
listFolded | false | indicate whether list should folded at first
listMaxHeight | - | list max height
storageName | 'aplayer-setting' | localStorage key that store player setting

For example:

<div class="aplayer-wrap">
    <div id="aplayer3"><button class="docute-button load">Click to load player</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    mini: false,
    autoplay: false,
    theme: '#FADFA3',
    loop: 'all',
    order: 'random',
    preload: 'auto',
    volume: 0.7,
    mutex: true,
    listFolded: false,
    listMaxHeight: 90,
    lrcType: 3,
    audio: [
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg',
            lrc: 'lrc1.lrc',
            theme: '#ebd0c2'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg',
            lrc: 'lrc2.lrc',
            theme: '#46718b'
        }
    ]
});
```

## API

+ `APlayer.version`: static property, return the version of APlayer

+ `ap.play()`: play audio

+ `ap.pause()`: pause audio

+ `ap.seek(time: number)`: seek to specified time, the unit of time is second

  ```js
  ap.seek(100);
  ```

+ `ap.toggle()`: toggle between play and pause

+ `ap.on(event: string, handler: function)`: bind audio and player events, [see more details](https://aplayer.js.org/#/home?id=event-binding)

+ `ap.volume(percentage: number, nostorage: boolean)`: set audio volume

  ```js
  ap.volume(0.1, true);
  ```

+ `ap.theme(color: string, index: number)`: set player theme, the default of index is current audio index

  ```js
  ap.theme('#000', 0);
  ```

+ `ap.setMode(mode: string)`: set player mode, the value of mode should be 'mini' or 'normal'

+ `ap.mode`: return current player mode, 'mini' or 'normal'

+ `ap.notice(text: string, time: number, opacity: number)`: show message, the unit of time is millisecond, the default of time is 2000, the default of opacity is 0.8, setting time to 0 can disable notice autohide.

  ```js
  ap.notice('Amazing player', 2000, 0.8);
  ```

+ `ap.skipBack()`: skip to previous audio

+ `ap.skipForward()`: skip to next audio

+ `ap.destroy()`: destroy player

+ `ap.lrc`

  + `ap.lrc.show()`: show lrc

  + `ap.lrc.hide()`: hide lrc

  + `ap.lrc.toggle()`: toggle lrc between show and hide

+ `ap.list`

  + `ap.list.show()`: show list

  + `ap.list.hide()`: hide list

  + `ap.list.toggle()`: toggle list between show and hide

  + `ap.list.add(audios: array | object)`: add new audios to the list

  ```js
  ap.list.add([{
      name: 'name',
      artist: 'artist',
      url: 'url.mp3',
      cover: 'cover.jpg',
      lrc: 'lrc.lrc',
      theme: '#ebd0c2'
  }]);
  ```

  + `ap.list.remove(index: number)`: remove an audio from the list

  ```js
  ap.list.remove(1);
  ```

  + `ap.list.switch()`: switch to an audio in the list

  ```js
  ap.list.switch(1);
  ```

  + `ap.list.clear()`: remove all audios from the list

+ `ap.audio`: native audio

 + `ap.audio.currentTime`: returns the current playback position

 + `ap.audio.duration`: returns audio total time

 + `ap.audio.paused`: returns whether the audio paused

 + most [native api](http://www.w3schools.com/tags/ref_av_dom.asp) are supported

## Event binding

`ap.on(event, handler)`

```js
ap.on('ended', function () {
    console.log('player ended');
});
```

Audio events

- abort
- canplay
- canplaythrough
- durationchange
- emptied
- ended
- error
- loadeddata
- loadedmetadata
- loadstart
- mozaudioavailable
- pause
- play
- playing
- progress
- ratechange
- seeked
- seeking
- stalled
- suspend
- timeupdate
- volumechange
- waiting

Player events

- listshow
- listhide
- listadd
- listremove
- listswitch
- listclear
- noticeshow
- noticehide
- destroy
- lrcshow
- lrchide

## LRC

We have three ways to pass LRC to APlayer, indicate the way to pass LRC by option `lrcType`, then put lrc to option `audio.lrc` or HTML.

<div class="aplayer-wrap">
    <div id="aplayer4"><button class="docute-button load">Click to load player</div>
</div>

### LRC file

The first way, put LRC to a LRC file, LRC file will be loaded when this audio start to play.

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 3,
    audio: {
        name: 'name',
        artist: 'artist',
        url: 'demo.mp3',
        cover: 'demo.jpg',
        lrc: 'lrc.lrc'
    }
});
```

### LRC string in JS

The second way, put LRC to a JS string.

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 1,
    audio: {
        name: 'name',
        artist: 'artist',
        url: 'demo.mp3',
        cover: 'demo.jpg',
        lrc: '[00:00.00]APlayer\n[00:04.01]is\n[00:08.02]amazing'
    }
});
```

### LRC in HTML

The third way, put LRC to HTML.

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="player">
    <pre class="aplayer-lrc-content">
        [00:00.00]APlayer audio1
        [00:04.01]is
        [00:08.02]amazing
        <!-- ... -->
    </pre>
    <pre class="aplayer-lrc-content">
        [00:00.00]APlayer audio2
        [00:04.01]is
        [00:08.02]amazing
        <!-- ... -->
    </pre>
</div>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 2,
    audio: [[
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg'
        }
    ]]
});
```

### LRC format

The following LRC format are supported:

`[mm:ss]APlayer`

`[mm:ss.xx]is`

`[mm:ss.xxx]amazing`

`[mm:ss.xx][mm:ss.xx]APlayer`

`[mm:ss.xx]<mm:ss.xx>is`

`[mm:ss.xx]amazing[mm:ss.xx]APlayer`

## Playlist

APlayer will show a playlist when it has more than one audio, option `listFolded` indicates whether list should folded at first, and option `listMaxHeight` indicates list max height.

<div class="aplayer-wrap">
    <div id="aplayer5"><button class="docute-button load">Click to load player</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    listFolded: false,
    listMaxHeight: 90,
    lrcType: 3,
    audio: [
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg',
            lrc: 'lrc1.lrc',
            theme: '#ebd0c2'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg',
            lrc: 'lrc2.lrc',
            theme: '#46718b'
        }
    ]
});
```

## Fixed mode

APlayer can be fixed to page bottom via fixed mode, fixed mode is a very different mode, enjoy it!

<div class="aplayer-wrap">
    <div id="aplayer9"><button class="docute-button load">Click to load player</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    fixed: true,
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg',
    }]
});
```

## Mini mode

If you don't have enough space for normal player, mini mode might be your choice.

Please note that mini mode is conflicted with fixed mode.

<div class="aplayer-wrap">
    <div id="aplayer6"><button class="docute-button load">Click to load player</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    mini: true,
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg',
    }]
});
```

## MSE support

### HLS

It requires the library [hls.js](https://github.com/video-dev/hls.js) and it should be loaded before `APlayer.min.js`.

<div class="aplayer-wrap">
    <div id="aplayer7"><button class="docute-button load">Click to load player</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="hls.min.js"></script>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'HLS',
        artist: 'artist',
        url: 'url.m3u8',
        cover: 'cover.jpg',
        type: 'hls'
    }]
});
```

```js
// another way, use customType
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'HLS',
        artist: 'artist',
        url: 'url.m3u8',
        cover: 'cover.jpg',
        type: 'customHls'
    }],
    customAudioType: {
        'customHls': function (audioElement, audio, player) {
            if (Hls.isSupported()) {
                const hls = new Hls();
                hls.loadSource(audio.url);
                hls.attachMedia(audioElement);
            }
            else if (audioElement.canPlayType('application/x-mpegURL') || audioElement.canPlayType('application/vnd.apple.mpegURL')) {
                audioElement.src = audio.url;
            }
            else {
                player.notice('Error: HLS is not supported.');
            }
        }
    }
});
```

## Self-adapting theme according to cover

It requires the library [color-thief](https://github.com/lokesh/color-thief/blob/master/src/color-thief.js).

<div class="aplayer-wrap">
    <div id="aplayer8"><button class="docute-button load">Click to load player</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
<script src="color-thief.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    theme: '#e9e9e9',
    audio: [{
        name: 'name1',
        artist: 'artist1',
        url: 'url1.mp3',
        cover: 'cover1.jpg'
    }, {
        name: 'name2',
        artist: 'artist2',
        url: 'url2.mp3',
        cover: 'cover2.jpg'
    }]
});

const colorThief = new ColorThief();
const image = new Image();
const xhr = new XMLHttpRequest();
const setTheme = (index) => {
    if (!ap.list.audios[index].theme) {
        xhr.onload = function(){
            let coverUrl = URL.createObjectURL(this.response);
            image.onload = function(){
                let color = colorThief.getColor(image);
                ap.theme(`rgb(${color[0]}, ${color[1]}, ${color[2]})`, index);
                URL.revokeObjectURL(coverUrl)
            };
            image.src = coverUrl;
        }
        xhr.open('GET', ap.list.audios[index].cover, true);
        xhr.responseType = 'blob';
        xhr.send();
    }
};
setTheme(ap.list.index);
ap.on('listswitch', (index) => {
    setTheme(index);
});
```

## CDN

- [jsDelivr](https://www.jsdelivr.com/package/npm/aplayer)
- [cdnjs](https://cdnjs.com/libraries/aplayer)
- [unpkg](https://unpkg.com/aplayer/)

## FAQ

### Why can't player autoplay in some mobile browsers?

Most mobile browsers forbid audio autoplay, you wont be able to achieve it without hacks.

================================================
FILE: docs/config.js
================================================
const langs = [
    { title: 'English', path: '/home', matchPath: /^\/(home|ecosystem|support)/ },
    { title: '简体中文', path: '/zh-Hans/', matchPath: /^\/zh-Hans/ },
];

docute.init({
    landing: 'landing.html',
    title: 'APlayer',
    repo: 'DIYgod/APlayer',
    twitter: 'DIYgod',
    'edit-link': 'https://github.com/MoePlayer/APlayer/tree/master/docs',
    nav: {
        default: [
            {
                title: 'Home', path: '/home'
            },
            {
                title: 'Ecosystem', path: '/ecosystem'
            },
            {
                title: 'Support APlayer', path: '/support'
            },
            {
                title: 'Languages', type: 'dropdown', items: langs
            }
        ],
        'zh-Hans': [
            {
                title: '首页', path: '/zh-Hans/'
            },
            {
                title: '生态', path: '/zh-Hans/ecosystem'
            },
            {
                title: '支持 APlayer', path: '/zh-Hans/support'
            },
            {
                title: '选择语言', type: 'dropdown', items: langs
            }
        ],
    },
    plugins: [
        docsearch({
            apiKey: '',
            indexName: 'aplayer',
            tags: ['english', 'zh-Hans'],
            url: 'https://aplayer.js.org'
        }),
        evanyou(),
        player(),
        insertUmamiJS('8c8b314e-99d9-4ac4-bee9-0e9826bb2b1c', 'https://umami.diygod.dev/script.js')
    ]
});

function insertUmamiJS(id, url) {
    var script = document.createElement('script');
    script.setAttribute("data-website-id",id);
    script.src = url;
    script.async = true;
    document.head.appendChild(script);
}

function player () {
    return function (context) {
        context.event.on('landing:updated', function () {
            console.log('landing:updated');
            clearPlayer();
            aplayer0();
            aplayer1();
        });
        context.event.on('content:updated', function () {
            console.log('content:updated');
            clearPlayer();
            for (let i = 0; i < document.querySelectorAll('.load').length; i++) {
                document.querySelectorAll('.load')[i].addEventListener('click', function () {
                    window[this.parentElement.id] && window[this.parentElement.id]();
                });
            }
        });
    };
}

function clearPlayer () {
    for (let i = 0; i < 10; i++) {
        if (window['ap' + i]) {
            window['ap' + i].destroy();
        }
    }
}

function aplayer1 () {
    window.ap1 = new APlayer({
        container: document.getElementById('aplayer1'),
        theme: '#F57F17',
        lrcType: 3,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
            theme: '#46718b'
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
            theme: '#505d6b'
        }]
    });
}

function aplayer0 () {
    window.ap0 = new APlayer({
        container: document.getElementById('aplayer0'),
        fixed: true,
        lrcType: 3,
        audio: [{
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
            theme: '#505d6b'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
            theme: '#46718b'
        }, {
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }]
    });
}

function aplayer2 () {
    window.ap2 = new APlayer({
        container: document.getElementById('aplayer2'),
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            theme: '#ebd0c2'
        }]
    });
}

function aplayer3 () {
    window.ap3 = new APlayer({
        container: document.getElementById('aplayer3'),
        mini: false,
        autoplay: false,
        loop: 'all',
        order: 'random',
        preload: 'auto',
        volume: 0.7,
        mutex: true,
        listFolded: false,
        listMaxHeight: 90,
        lrcType: 3,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
            theme: '#46718b'
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
            theme: '#505d6b'
        }]
    });
}

function aplayer4 () {
    window.ap4 = new APlayer({
        container: document.getElementById('aplayer4'),
        lrcType: 3,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }]
    });
}

function aplayer5 () {
    window.ap5 = new APlayer({
        container: document.getElementById('aplayer5'),
        lrcType: 3,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
            theme: '#46718b'
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
            theme: '#505d6b'
        }]
    });
}

function aplayer6 () {
    window.ap6 = new APlayer({
        container: document.getElementById('aplayer6'),
        mini: true,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            theme: '#ebd0c2'
        }]
    });
}

function aplayer7 () {
    window.ap7 = new APlayer({
        container: document.getElementById('aplayer7'),
        audio: [{
            name: '光るなら(HLS)',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hls/hikarunara.m3u8',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            theme: '#ebd0c2',
            type: 'hls'
        }, {
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            theme: '#ebd0c2'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            theme: '#46718b'
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            theme: '#505d6b'
        }]
    });
}

function aplayer8 () {
    window.ap8 = new APlayer({
        container: document.getElementById('aplayer8'),
        theme: '#e9e9e9',
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
        }]
    });

    const colorThief = new ColorThief();
    window.ap8.on('switchaudio', function (index) {
        if (!window.ap8.options.audio[index].theme) {
            colorThief.getColorAsync(window.ap8.options.audio[index].cover, function (color) {
                window.ap8.theme(`rgb(${color[0]}, ${color[1]}, ${color[2]})`, index);
            });
        }
    });
}

function aplayer9 () {
    window.ap9 = new APlayer({
        container: document.getElementById('aplayer9'),
        fixed: true,
        lrcType: 3,
        audio: [{
            name: '光るなら',
            artist: 'Goose house',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/hikarunara.lrc',
            theme: '#ebd0c2'
        }, {
            name: 'トリカゴ',
            artist: 'XX:me',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/darling.lrc',
            theme: '#46718b'
        }, {
            name: '前前前世',
            artist: 'RADWIMPS',
            url: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.mp3',
            cover: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.jpg',
            lrc: 'https://cn-south-17-aplayer-46154810.oss.dogecdn.com/yourname.lrc',
            theme: '#505d6b'
        }]
    });
}

================================================
FILE: docs/ecosystem.md
================================================
---
search: english
---

# Ecosystem

Let's make APlayer better, feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/APlayer/issues/79)

## Help

### Joining the Discussion

- [Telegram Group](https://t.me/adplayer)
- [QQ Group](https://shang.qq.com/wpa/qunwpa?idkey=bf22213ae0028a82e5adf3f286dfd4f01e0997dc9f1dcd8e831a0a85e799be17): 415835947

### Creating issue

- [MoePlayer/APlayer/issues](https://github.com/MoePlayer/APlayer/issues)

## Related Projects

### Plugins

- [APlayer-Typecho-Plugin](https://github.com/zgq354/APlayer-Typecho-Plugin): Typecho
- [hexo-tag-aplayer](https://github.com/grzhan/hexo-tag-aplayer): Hexo
- [Hermit-X(APlayer for WordPress)](https://github.com/liwanglin12/Hermit-X): WordPress
- [APlayerHandle](https://github.com/kn007/APlayerHandle): WordPress
- [APlayer_for_Z-BlogPHP](https://github.com/fghrsh/APlayer_for_Z-BlogPHP): Z-BlogPHP
- [react-aplayer](https://github.com/sabrinaluo/react-aplayer): React
- [Vue-APlayer](https://github.com/SevenOutman/vue-aplayer): Vue
- [vue-aplayer](https://github.com/MoeFE/vue-aplayer): Vue
- [php-aplayer](https://github.com/Daryl-L/php-aplayer): PHP
- [aplayer-hugo-module](https://github.com/Runzelee/aplayer-hugo-module): Hugo

### Tooling

- [APlayer-Controler](https://github.com/Mashiro-Sorata/APlayer-Controler): controling tool
- [MetingJS](https://github.com/metowolf/MetingJS): work with Meting music API

## Who use APlayer?

- [bilibili](https://www.bilibili.com/): 国内知名的视频弹幕网站
- [黑客派](https://hacpai.com/): 程序员和设计师的聚集地,一个活跃的小众社区
- [浙江大学CC98论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛,中国各大学中较活跃的BBS之一
- [Jelly Rue](http://jellyrue.com/): Jelly Rue, an indie pop-rock band from Tartu.
- [Opus](http://www.opusopus.co/): An artist-exploration data visualization application
- [站长之家](http://www.chinaz.com/15year/index.html): 针对中文站点提供资讯、技术、资源、服务
- [LLSupport](https://www.lovelivesupport.com/): This site provides a lot of information about LoveLive
- [歌词千寻](https://www.lrcgc.com/diy): 每日更新的LRC歌词网站
- [iSearch](http://i.oppsu.cn): 一个提供 iTunes 搜索,试听,高清专辑封面获取,查看最新音乐动态等综合性平台
- [LRC歌词编辑器](https://github.com/MoeFE/Lyric): 一款非常实用的在线LRC歌词编辑器
- [Аэростатика](https://aerostatica.ru/)
- [HealthDig](https://healthdig.co): 每天只需两分钟的重点新闻资讯

================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
  <title>APlayer</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docute@3.4.12/dist/docute.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docute@3.4.12/dist/theme-github.css" />
  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-48084758-8"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { dataLayer.push(arguments); }
    gtag('js', new Date());

    gtag('config', 'UA-48084758-8');
  </script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.css">
  <script src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/color-thief-don@2.0.2/src/color-thief.js"></script>
  <style>
    body {
      text-rendering: auto;
    }
    #evanyou-canvas {
      z-index: -1 !important;
    }
    .aplayer-wrap {
      max-width: 700px;
      margin: 20px 0;
    }
    .sidebar-toggle {
      z-index: 90;
    }
  </style>
</head>
<body>
  <!-- don't remove this part start -->
  <div id="app"></div>
  <script src="https://cdn.jsdelivr.net/npm/docute@3.4.12/plugins/docsearch.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/docute-evanyou/dist/evanyou.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/docute@3.4.12/dist/docute.js"></script>
  <script src="./config.js"></script>
  <!-- livereload script placeholder -->
  <!-- don't remove this part end -->
</body>
</html>


================================================
FILE: docs/landing.html
================================================
<h1>APlayer</h1>

<h3>🍭 Wow, such a beautiful HTML5 music player.</h3>

<div class="aplayer-wrap">
    <div id="aplayer1"></div>
</div>
<div id="aplayer0"></div>

<div class="landing-buttons">
    <a class="landing-button" target="_blank" href="https://github.com/MoePlayer/APlayer">
        GitHub
    </a>

    <a class="landing-button" router-link="/home">
        Docs
    </a>
</div>

<style>
    h1 {
        margin: 0;
        margin-top: -50px;
        font-weight: normal;
        font-size: 40px;
        letter-spacing: 1px;
    }

    h3 {
        margin-top: 20px;
        color: #999;
        font-weight: normal;
        letter-spacing: 1px;
    }

    .landing {
        padding: 10px;
        display: -webkit-box;
        display: -ms-flexbox;
        display: flex;
        -webkit-box-align: center;
        -ms-flex-align: center;
        align-items: center;
        -webkit-box-pack: center;
        -ms-flex-pack: center;
        justify-content: center;
        -webkit-box-orient: vertical;
        -webkit-box-direction: normal;
        -ms-flex-direction: column;
        flex-direction: column;
        height: 100%;
        -webkit-user-select: none;
        user-select: none;
    }

    .features {
        margin-top: 20px;
        margin-bottom: 10px;
        font-size: 16px;
        line-height: 1.7;
    }

    .landing-button {
        border: 1px solid #ccc;
        border-radius: 33px;
        padding: 10px 30px;
        background-color: white;
        display: inline-block;
        margin-right: 20px;
        color: #333;
    }

    .landing-button:hover {
        border-color: #42b983;
        color: #42b983;
        text-decoration: none;
    }

    .aplayer-wrap {
        width: 600px;
        max-width: 100%;
        margin: 20px 0 40px;
    }
</style>

================================================
FILE: docs/support.md
================================================
---
search: english
---

# Sponsor APlayer Development

APlayer is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing.

If you run a business and are using APlayer in a revenue-generating product, it makes business sense to sponsor APlayer development: it ensures the project that your product relies on stays healthy and actively maintained.

If you are an individual user and have enjoyed the productivity of using APlayer, consider donating as a sign of appreciation - like buying me coffee once in a while :)

You can support APlayer via donations.

### Recurring Donation

-   Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod)
-   Become a Sponser on [Patreon](https://www.patreon.com/DIYgod)
-   Become a Sponser on [爱发电](https://afdian.net/@diygod)
-   Contact us directly: i@diygod.me

### One-time Donation

We accept donations via the following ways:

-   [WeChat Pay](https://diygod.me/images/wx.jpg)
-   [Alipay](https://diygod.me/images/zfb.jpg)
-   [Paypal](https://www.paypal.me/DIYgod)

## Current Premium Sponsors

### Special Sponsors

<a href="https://www.dogecloud.com/?ref=dplayer" target="_blank">
    <img width="222px" src="https://player.dogecloud.com/img/logo_with_product3.png">
</a>

## APlayer contributors

This project exists thanks to all the people who contribute.

<a href="https://github.com/MoePlayer/APlayer/graphs/contributors"><img src="https://opencollective.com/APlayer/contributors.svg?width=890" /></a>

================================================
FILE: docs/zh-Hans/README.md
================================================
---
nav: zh-Hans
search: zh-Hans
---

# APlayer

🍭 Wow, such a beautiful HTML5 music player

## 特别赞助商

<a href="https://www.dogecloud.com/?ref=dplayer" target="_blank">
    <img width="222px" src="https://player.dogecloud.com/img/logo_with_product3.png">
</a>

## 安装

使用 npm:

```
npm install aplayer --save
```

使用 Yarn:

```
yarn add aplayer
```

## 入门

<div class="aplayer-wrap">
    <div id="aplayer2"><button class="docute-button load">点击加载播放器</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg'
    }]
});
```

使用模块管理器:

```js
import 'APlayer/dist/APlayer.min.css';
import APlayer from 'APlayer';

const ap = new APlayer(options);
```

## 参数

名称 | 默认值 | 描述
----|-------|----
container | document.querySelector('.aplayer') | 播放器容器元素
fixed | false | 开启吸底模式, [详情](https://aplayer.js.org/#/home?id=fixed-mode)
mini | false | 开启迷你模式, [详情](https://aplayer.js.org/#/home?id=mini-mode)
autoplay | false | 音频自动播放
theme | '#b7daff' | 主题色
loop | 'all' | 音频循环播放, 可选值: 'all', 'one', 'none'
order | 'list' | 音频循环顺序, 可选值: 'list', 'random'
preload | 'auto' | 预加载,可选值: 'none', 'metadata', 'auto'
volume | 0.7 | 默认音量,请注意播放器会记忆用户设置,用户手动设置音量后默认音量即失效
audio | - | 音频信息, 应该是一个对象或对象数组
audio.name | - | 音频名称
audio.artist | - | 音频艺术家
audio.url | - | 音频链接
audio.cover | - | 音频封面
audio.lrc | - | [详情](https://aplayer.js.org/#/home?id=lrc)
audio.theme | - | 切换到此音频时的主题色,比上面的 theme 优先级高
audio.type | 'auto' | 可选值: 'auto', 'hls', 'normal' 或其他自定义类型, [详情](https://aplayer.js.org/#/home?id=mse-support)
customAudioType | - | 自定义类型,[详情](https://aplayer.js.org/#/home?id=mse-support)
mutex | true | 互斥,阻止多个播放器同时播放,当前播放器播放时暂停其他播放器
lrcType | 0 | [详情](https://aplayer.js.org/#/home?id=lrc)
listFolded | false | 列表默认折叠
listMaxHeight | - | 列表最大高度
storageName | 'aplayer-setting' | 存储播放器设置的 localStorage key

例如:

<div class="aplayer-wrap">
    <div id="aplayer3"><button class="docute-button load">点击加载播放器</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    mini: false,
    autoplay: false,
    theme: '#FADFA3',
    loop: 'all',
    order: 'random',
    preload: 'auto',
    volume: 0.7,
    mutex: true,
    listFolded: false,
    listMaxHeight: 90,
    lrcType: 3,
    audio: [
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg',
            lrc: 'lrc1.lrc',
            theme: '#ebd0c2'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg',
            lrc: 'lrc2.lrc',
            theme: '#46718b'
        }
    ]
});
```

## API

+ `APlayer.version`: 静态属性, 返回 APlayer 的版本号

+ `ap.play()`: 播放音频

+ `ap.pause()`: 暂停音频

+ `ap.seek(time: number)`: 跳转到特定时间,时间的单位为秒

  ```js
  ap.seek(100);
  ```

+ `ap.toggle()`: 切换播放和暂停

+ `ap.on(event: string, handler: function)`: 绑定音频和播放器事件,[详情](https://aplayer.js.org/#/home?id=event-binding)
  
+ `ap.volume(percentage: number, nostorage: boolean)`: 设置音频音量

  ```js
  ap.volume(0.1, true);
  ```

+ `ap.theme(color: string, index: number)`: 设置播放器主题色, index 默认为当前音频的 index

  ```js
  ap.theme('#000', 0);
  ```

+ `ap.setMode(mode: string)`: 设置播放器模式,mode 取值应为 'mini' 或 'normal'

+ `ap.mode`: 返回播放器当前模式,'mini' 或 'normal'

+ `ap.notice(text: string, time: number, opacity: number)`: 显示通知,时间的单位为毫秒,默认时间 2000 毫秒,默认透明度 0.8,设置时间为 0 可以取消通知自动隐藏

  ```js
  ap.notice('Amazing player', 2000, 0.8);
  ```

+ `ap.skipBack()`: 切换到上一首音频

+ `ap.skipForward()`: 切换到下一首音频

+ `ap.destroy()`: 销毁播放器

+ `ap.lrc`

  + `ap.lrc.show()`: 显示歌词

  + `ap.lrc.hide()`: 隐藏歌词

  + `ap.lrc.toggle()`: 显示/隐藏歌词

+ `ap.list`

  + `ap.list.show()`: 显示播放列表

  + `ap.list.hide()`: 隐藏播放列表

  + `ap.list.toggle()`: 显示/隐藏播放列表

  + `ap.list.add(audios: array | object)`: 添加一个或几个新音频到播放列表

  ```js
  ap.list.add([{
      name: 'name',
      artist: 'artist',
      url: 'url.mp3',
      cover: 'cover.jpg',
      lrc: 'lrc.lrc',
      theme: '#ebd0c2'
  }]);
  ```

  + `ap.list.remove(index: number)`: 移除播放列表中的一个音频

  ```js
  ap.list.remove(1);
  ```

  + `ap.list.switch()`: 切换到播放列表里的其他音频

  ```js
  ap.list.switch(1);
  ```

  + `ap.list.clear()`: 清空播放列表

+ `ap.audio`: 原生 audio

 + `ap.audio.currentTime`: 返回音频当前播放时间

 + `ap.audio.duration`: 返回音频总时间

 + `ap.audio.paused`: 返回音频是否暂停

 + 支持大多数[原生audio接口](http://www.w3schools.com/tags/ref_av_dom.asp)

## 事件绑定

`ap.on(event, handler)`

```js
ap.on('ended', function () {
    console.log('player ended');
});
```

音频事件

- abort
- canplay
- canplaythrough
- durationchange
- emptied
- ended
- error
- loadeddata
- loadedmetadata
- loadstart
- mozaudioavailable
- pause
- play
- playing
- progress
- ratechange
- seeked
- seeking
- stalled
- suspend
- timeupdate
- volumechange
- waiting

播放器事件

- listshow
- listhide
- listadd
- listremove
- listswitch
- listclear
- noticeshow
- noticehide
- destroy
- lrcshow
- lrchide

## 歌词

我们有三种方式来给 APlayer 传递歌词,使用 `lrcType` 参数指明使用哪种方式,然后把歌词放到 `audio.lrc` 参数或者 HTML 里。

<div class="aplayer-wrap">
    <div id="aplayer4"><button class="docute-button load">点击加载播放器</div>
</div>

### LRC 文件方式

第一种方式,把歌词放到 LRC 文件里,音频播放时会加载对应的 LRC 文件。

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 3,
    audio: {
        name: 'name',
        artist: 'artist',
        url: 'demo.mp3',
        cover: 'demo.jpg',
        lrc: 'lrc.lrc'
    }
});
```

### JS 字符串方式

第二种方式,把歌词放到 JS 字符串里面。

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 1,
    audio: {
        name: 'name',
        artist: 'artist',
        url: 'demo.mp3',
        cover: 'demo.jpg',
        lrc: '[00:00.00]APlayer\n[00:04.01]is\n[00:08.02]amazing'
    }
});
```

### HTML 方式

第三种方式,把歌词放到 HTML 里面。

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="player">
    <pre class="aplayer-lrc-content">
        [00:00.00]APlayer audio1
        [00:04.01]is
        [00:08.02]amazing
        <!-- ... -->
    </pre>
    <pre class="aplayer-lrc-content">
        [00:00.00]APlayer audio2
        [00:04.01]is
        [00:08.02]amazing
        <!-- ... -->
    </pre>
</div>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    lrcType: 2,
    audio: [[
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg'
        }
    ]]
});
```

### 歌词格式

支持下面格式的歌词:

`[mm:ss]APlayer`

`[mm:ss.xx]is`

`[mm:ss.xxx]amazing`

`[mm:ss.xx][mm:ss.xx]APlayer`

`[mm:ss.xx]<mm:ss.xx>is`

`[mm:ss.xx]amazing[mm:ss.xx]APlayer`

## 播放列表

当有多个音频时会 APlayer 会展示一个播放列表,`listFolded` 参数指明列表是否默认折叠,`listMaxHeight` 参数指明列表最大高度。

<div class="aplayer-wrap">
    <div id="aplayer5"><button class="docute-button load">点击加载播放器</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    listFolded: false,
    listMaxHeight: 90,
    lrcType: 3,
    audio: [
        {
            name: 'name1',
            artist: 'artist1',
            url: 'url1.mp3',
            cover: 'cover1.jpg',
            lrc: 'lrc1.lrc',
            theme: '#ebd0c2'
        },
        {
            name: 'name2',
            artist: 'artist2',
            url: 'url2.mp3',
            cover: 'cover2.jpg',
            lrc: 'lrc2.lrc',
            theme: '#46718b'
        }
    ]
});
```
## 吸底模式

APlayer 可以通过吸底模式固定在页面底部,这种模式跟普通模式有很大不同。

<div class="aplayer-wrap">
    <div id="aplayer9"><button class="docute-button load">点击加载播放器</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    fixed: true,
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg',
    }]
});
```

## 迷你模式

如果你没有足够空间来放置正常模式的播放器,那么你可以考虑使用迷你模式。

请注意迷你模式与吸底模式冲突。

<div class="aplayer-wrap">
    <div id="aplayer6"><button class="docute-button load">点击加载播放器</div>
</div>

```js
const ap = new APlayer({
    container: document.getElementById('player'),
    mini: true,
    audio: [{
        name: 'name',
        artist: 'artist',
        url: 'url.mp3',
        cover: 'cover.jpg',
    }]
});
```

## MSE 支持

### HLS

需要在 `APlayer.min.js` 前面加载 [hls.js](https://github.com/video-dev/hls.js)。

<div class="aplayer-wrap">
    <div id="aplayer7"><button class="docute-button load">点击加载播放器</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="hls.min.js"></script>
<script src="APlayer.min.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'HLS',
        artist: 'artist',
        url: 'url.m3u8',
        cover: 'cover.jpg',
        type: 'hls'
    }]
});
```

```js
// 另一种方式,使用 customAudioType
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    audio: [{
        name: 'HLS',
        artist: 'artist',
        url: 'url.m3u8',
        cover: 'cover.jpg',
        type: 'customHls'
    }],
    customAudioType: {
        'customHls': function (audioElement, audio, player) {
            if (Hls.isSupported()) {
                const hls = new Hls();
                hls.loadSource(audio.url);
                hls.attachMedia(audioElement);
            }
            else if (audioElement.canPlayType('application/x-mpegURL') || audioElement.canPlayType('application/vnd.apple.mpegURL')) {
                audioElement.src = audio.url;
            }
            else {
                player.notice('Error: HLS is not supported.');
            }
        }
    }
});
```

## 根据封面自适应主题色

需要额外加载 [color-thief.js](https://github.com/lokesh/color-thief/blob/master/src/color-thief.js)

<div class="aplayer-wrap">
    <div id="aplayer8"><button class="docute-button load">点击加载播放器</div>
</div>

```html
<link rel="stylesheet" href="APlayer.min.css">
<div id="aplayer"></div>
<script src="APlayer.min.js"></script>
<script src="color-thief.js"></script>
```

```js
const ap = new APlayer({
    container: document.getElementById('aplayer'),
    theme: '#e9e9e9',
    audio: [{
        name: 'name1',
        artist: 'artist1',
        url: 'url1.mp3',
        cover: 'cover1.jpg'
    }, {
        name: 'name2',
        artist: 'artist2',
        url: 'url2.mp3',
        cover: 'cover2.jpg'
    }]
});

const colorThief = new ColorThief();
const image = new Image();
const xhr = new XMLHttpRequest();
const setTheme = (index) => {
    if (!ap.list.audios[index].theme) {
        xhr.onload = function(){
            let coverUrl = URL.createObjectURL(this.response);
            image.onload = function(){
                let color = colorThief.getColor(image);
                ap.theme(`rgb(${color[0]}, ${color[1]}, ${color[2]})`, index);
                URL.revokeObjectURL(coverUrl)
            };
            image.src = coverUrl;
        }
        xhr.open('GET', ap.list.audios[index].cover, true);
        xhr.responseType = 'blob';
        xhr.send();
    }
};
setTheme(ap.list.index);
ap.on('listswitch', (index) => {
    setTheme(index);
});
```

## CDN

- [jsDelivr](https://www.jsdelivr.com/package/npm/aplayer)
- [cdnjs](https://cdnjs.com/libraries/aplayer)
- [unpkg](https://unpkg.com/aplayer/)

## 常见问题

### 为什么播放器不能在手机上自动播放?

大多数移动端浏览器禁止了音频自动播放。

================================================
FILE: docs/zh-Hans/ecosystem.md
================================================
---
nav: zh-Hans
search: zh-Hans
---

# 生态

让 APlayer 变得更好,请随意在 [`Let me know!`](https://github.com/MoePlayer/APlayer/issues/79) 提交你的项目和产品

## 帮助

### 参与讨论

- [Telegram 群](https://t.me/adplayer)
- [QQ 群](https://shang.qq.com/wpa/qunwpa?idkey=bf22213ae0028a82e5adf3f286dfd4f01e0997dc9f1dcd8e831a0a85e799be17): 415835947

### 提交 issue

- [MoePlayer/APlayer/issues](https://github.com/MoePlayer/APlayer/issues)

## 相关项目

### 插件

- [APlayer-Typecho-Plugin](https://github.com/zgq354/APlayer-Typecho-Plugin): Typecho
- [hexo-tag-aplayer](https://github.com/grzhan/hexo-tag-aplayer): Hexo
- [Hermit-X(APlayer for WordPress)](https://github.com/liwanglin12/Hermit-X): WordPress
- [APlayerHandle](https://github.com/kn007/APlayerHandle): WordPress
- [APlayer_for_Z-BlogPHP](https://github.com/fghrsh/APlayer_for_Z-BlogPHP): Z-BlogPHP
- [react-aplayer](https://github.com/sabrinaluo/react-aplayer): React
- [Vue-APlayer](https://github.com/SevenOutman/vue-aplayer): Vue
- [vue-aplayer](https://github.com/MoeFE/vue-aplayer): Vue
- [php-aplayer](https://github.com/Daryl-L/php-aplayer): PHP

### 工具

- [APlayer-Controler](https://github.com/Mashiro-Sorata/APlayer-Controler): controling tool
- [MetingJS](https://github.com/metowolf/MetingJS): work with Meting music API

## 谁在用 APlayer?

- [bilibili](https://www.bilibili.com/): 国内知名的视频弹幕网站
- [黑客派](https://hacpai.com/): 程序员和设计师的聚集地,一个活跃的小众社区
- [浙江大学CC98论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛,中国各大学中较活跃的BBS之一
- [Jelly Rue](http://jellyrue.com/): Jelly Rue, an indie pop-rock band from Tartu.
- [Opus](http://www.opusopus.co/): An artist-exploration data visualization application
- [站长之家](http://www.chinaz.com/15year/index.html): 针对中文站点提供资讯、技术、资源、服务
- [LLSupport](https://www.lovelivesupport.com/): This site provides a lot of information about LoveLive
- [歌词千寻](https://www.lrcgc.com/diy): 每日更新的LRC歌词网站
- [iSearch](http://i.oppsu.cn): 一个提供 iTunes 搜索,试听,高清专辑封面获取,查看最新音乐动态等综合性平台
- [LRC歌词编辑器](https://github.com/MoeFE/Lyric): 一款非常实用的在线LRC歌词编辑器
- [Аэростатика](https://aerostatica.ru/)

================================================
FILE: docs/zh-Hans/support.md
================================================
---
nav: zh-Hans
search: zh-Hans
---

# 赞助 APlayer 的研发

APlayer 是采用 MIT 许可的开源项目,使用完全免费。 但是随着项目规模的增长,也需要有相应的资金支持才能持续项目的维护的开发。

如果你是企业经营者并且将 APlayer 用在商业产品中,那么赞助 APlayer 有商业上的益处:可以让你的产品所依赖的框架保持健康并得到积极的维护。

如果你是个人开发者并且享受 APlayer 带来的高开发效率,可以用捐助来表示你的谢意 —— 比如偶尔给我买杯咖啡 :)

你可以通过下列的方法来赞助 APlayer 的开发.

## 周期性赞助

-   通过 [GitHub](https://github.com/sponsors/DIYgod) 赞助
-   通过 [Patreon](https://www.patreon.com/DIYgod) 赞助
-   通过 [爱发电](https://afdian.net/@diygod) 赞助
-   给我们发邮件联系赞助事宜: <i@diygod.me>

## 一次性赞助

我们通过以下方式接受赞助:

-   [微信支付](https://diygod.me/images/wx.jpg)
-   [支付宝](https://diygod.me/images/zfb.jpg)
-   [Paypal](https://www.paypal.me/DIYgod)

## 当前的顶级赞助商

### 特别赞助商

<a href="https://www.dogecloud.com/?ref=dplayer" target="_blank">
    <img width="222px" src="https://player.dogecloud.com/img/logo_with_product3.png">
</a>

## APlayer 贡献者

感谢所有贡献者。

<a href="https://github.com/MoePlayer/APlayer/graphs/contributors"><img src="https://opencollective.com/APlayer/contributors.svg?width=890" /></a>

================================================
FILE: package.json
================================================
{
    "name": "aplayer",
    "version": "1.10.1",
    "description": "Wow, such a beautiful html5 music player",
    "main": "dist/APlayer.min.js",
    "style": "dist/APlayer.min.css",
    "scripts": {
        "start": "npm run dev",
        "build": "cross-env NODE_ENV=production webpack --config webpack/prod.config.js --progress --display-error-details --colors",
        "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack/dev.config.js --watch --colors",
        "test": "eslint src webpack",
        "format": "eslint \"**/*.js\" --fix && prettier \"**/*.{js,json,md}\" --write",
        "format:staged": "eslint \"**/*.js\" --fix && pretty-quick --staged --verbose --pattern \"**/*.{js,json,md}\"",
        "format:check": "eslint \"**/*.js\" && prettier-check \"**/*.{js,json,md}\""
    },
    "files": [
        "dist"
    ],
    "repository": {
        "url": "git+https://github.com/DIYgod/APlayer.git",
        "type": "git"
    },
    "keywords": [
        "player",
        "music",
        "html5"
    ],
    "gitHooks": {
        "pre-commit": "npm run format:staged"
    },
    "author": "DIYgod",
    "license": "MIT",
    "bugs": {
        "url": "https://github.com/DIYgod/APlayer/issues"
    },
    "homepage": "https://github.com/DIYgod/APlayer#readme",
    "devDependencies": {
        "@babel/core": "^7.6.0",
        "@babel/preset-env": "^7.4.5",
        "@vuepress/plugin-back-to-top": "1.7.1",
        "@vuepress/plugin-google-analytics": "1.7.1",
        "@vuepress/plugin-pwa": "1.7.1",
        "art-template": "4.13.2",
        "art-template-loader": "1.4.3",
        "autoprefixer": "^9.6.1",
        "babel-loader": "^8.0.6",
        "cross-env": "^7.0.0",
        "css-loader": "^5.0.0",
        "cssnano": "^4.1.10",
        "eslint": "^7.0.0",
        "eslint-config-prettier": "^6.3.0",
        "eslint-loader": "^4.0.0",
        "eslint-plugin-prettier": "^3.1.1",
        "exports-loader": "^1.0.0",
        "file-loader": "^6.0.0",
        "git-revision-webpack-plugin": "^3.0.3",
        "mini-css-extract-plugin": "1.3.0",
        "node-sass": "^5.0.0",
        "postcss-loader": "^3.0.0",
        "prettier": "^2.0.1",
        "prettier-check": "^2.0.0",
        "pretty-quick": "^3.0.0",
        "sass-loader": "^10.0.0",
        "strip-loader": "^0.1.2",
        "style-loader": "^2.0.0",
        "svg-inline-loader": "0.8.2",
        "template-string-optimize-loader": "^3.0.0",
        "url-loader": "^4.0.0",
        "vuepress": "1.7.1",
        "webpack": "^4.40.2",
        "webpack-cli": "3.3.12",
        "webpack-dev-server": "^3.8.1",
        "yorkie": "^2.0.0"
    },
    "dependencies": {
        "balloon-css": "^1.0.3",
        "promise-polyfill": "8.2.0",
        "smoothscroll": "0.4.0"
    }
}


================================================
FILE: src/css/index.scss
================================================
$aplayer-height: 66px;
$lrc-height: 30px;
$aplayer-height-lrc: $aplayer-height + $lrc-height - 6;

.aplayer {
    background: #fff;
    font-family: Arial, Helvetica, sans-serif;
    margin: 5px;
    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.07), 0 1px 5px 0 rgba(0, 0, 0, 0.1);
    border-radius: 2px;
    overflow: hidden;
    user-select: none;
    line-height: initial;
    position: relative;

    * {
        box-sizing: content-box;
    }

    svg {
        width: 100%;
        height: 100%;
        
        path,
        circle {
            fill: #fff;
        }
    }

    &.aplayer-withlist {
        .aplayer-info {
            border-bottom: 1px solid #e9e9e9;
        }
        .aplayer-list {
            display: block;
        }
        .aplayer-info .aplayer-controller .aplayer-time .aplayer-icon.aplayer-icon-menu {
            display: inline;
        }
        .aplayer-icon-order {
            display: inline;
        }
    }

    &.aplayer-withlrc {
        .aplayer-pic {
            height: $aplayer-height-lrc;
            width: $aplayer-height-lrc;
        }
        .aplayer-info {
            margin-left: $aplayer-height-lrc;
            height: $aplayer-height-lrc;
            padding: 10px 7px 0 7px;
        }
        .aplayer-lrc {
            display: block;
        }
    }

    &.aplayer-narrow {
        width: $aplayer-height;
        
        .aplayer-info {
            display: none;
        }
        .aplayer-list {
            display: none;
        }
        .aplayer-pic,
        .aplayer-body {
            height: $aplayer-height;
            width: $aplayer-height;
        }
    }

    &.aplayer-fixed {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        margin: 0;
        z-index: 99;
        overflow: visible;
        max-width: 400px;
        box-shadow: none;

        .aplayer-list {
            margin-bottom: 65px;
            border: 1px solid #eee;
            border-bottom: none;
        }

        .aplayer-body {
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            margin: 0;
            z-index: 99;
            background: #fff;
            padding-right: 18px;
            transition: all 0.3s ease;
            max-width: 400px;
        }

        .aplayer-lrc {
            display: block;
            position: fixed;
            bottom: 10px;
            left: 0;
            right: 0;
            margin: 0;
            z-index: 98;
            pointer-events: none;
            text-shadow: -1px -1px 0 #fff;

            &:before,
            &:after {
                display: none;
            }
        }

        .aplayer-info {
            transform: scaleX(1);
            transform-origin: 0 0;
            transition: all 0.3s ease;
            border-bottom: none;
            border-top: 1px solid #e9e9e9;

            .aplayer-music {
                width: calc(100% - 105px);
            }
        }

        .aplayer-miniswitcher {
            display: block;
        }

        &.aplayer-narrow {
            .aplayer-info {
                display: block;
                transform: scaleX(0);
            }
            .aplayer-body {
                width: $aplayer-height !important;
            }

            .aplayer-miniswitcher .aplayer-icon {
                transform: rotateY(0);
            }
        }

        .aplayer-icon-back,
        .aplayer-icon-play,
        .aplayer-icon-forward,
        .aplayer-icon-lrc {
            display: inline-block;
        }

        .aplayer-icon-back,
        .aplayer-icon-play,
        .aplayer-icon-forward,
        .aplayer-icon-menu {
            position: absolute;
            bottom: 27px;
            width: 20px;
            height: 20px;
        }

        .aplayer-icon-back {
            right: 75px;
        }

        .aplayer-icon-play {
            right: 50px;
        }

        .aplayer-icon-forward {
            right: 25px;
        }

        .aplayer-icon-menu {
            right: 0;
        }
    }

    &.aplayer-mobile {
        .aplayer-icon-volume-down {
            display: none;
        }
    }

    &.aplayer-arrow {
        .aplayer-icon-order,
        .aplayer-icon-loop {
            display: none;
        }
    }

    &.aplayer-loading {
        .aplayer-info .aplayer-controller .aplayer-loading-icon {
            display: block;
        }

        .aplayer-info .aplayer-controller .aplayer-bar-wrap .aplayer-bar .aplayer-played .aplayer-thumb {
            transform: scale(1);
        }
    }

    .aplayer-body {
        position: relative;
    }

    .aplayer-icon {
        width: 15px;
        height: 15px;
        border: none;
        background-color: transparent;
        outline: none;
        cursor: pointer;
        opacity: .8;
        vertical-align: middle;
        padding: 0;
        font-size: 12px;
        margin: 0;
        display: inline-block;

        path {
            transition: all .2s ease-in-out;
        }
    }

    .aplayer-icon-order,
    .aplayer-icon-back,
    .aplayer-icon-play,
    .aplayer-icon-forward,
    .aplayer-icon-lrc {
        display: none;
    }

    .aplayer-icon-lrc-inactivity {
        svg {
            opacity: 0.4;
        }
    }

    .aplayer-icon-forward {
        transform: rotate(180deg);
    }

    .aplayer-lrc-content {
        display: none;
    }

    .aplayer-pic {
        position: relative;
        float: left;
        height: $aplayer-height;
        width: $aplayer-height;
        background-size: cover;
        background-position: center;
        transition: all 0.3s ease;
        cursor: pointer;

        &:hover .aplayer-button {
            opacity: 1;
        }

        .aplayer-button {
            position: absolute;
            border-radius: 50%;
            opacity: 0.8;
            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
            background: rgba(0, 0, 0, 0.2);
            transition: all 0.1s ease;

            path {
                fill: #fff;
            }
        }

        .aplayer-hide {
            display: none;
        }

        .aplayer-play {
            width: 26px;
            height: 26px;
            border: 2px solid #fff;
            bottom: 50%;
            right: 50%;
            margin: 0 -15px -15px 0;
            svg {
                position: absolute;
                top: 3px;
                left: 4px;
                height: 20px;
                width: 20px;
            }
        }

        .aplayer-pause {
            width: 16px;
            height: 16px;
            border: 2px solid #fff;
            bottom: 4px;
            right: 4px;
            svg {
                position: absolute;
                top: 2px;
                left: 2px;
                height: 12px;
                width: 12px;
            }
        }
    }

    .aplayer-info {
        margin-left: $aplayer-height;
        padding: 14px 7px 0 10px;
        height: $aplayer-height;
        box-sizing: border-box;

        .aplayer-music {
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            margin: 0 0 13px 5px;
            user-select: text;
            cursor: default;
            padding-bottom: 2px;
            height: 20px;

            .aplayer-title {
                font-size: 14px;
            }

            .aplayer-author {
                font-size: 12px;
                color: #666;
            }
        }

        .aplayer-controller {
            position: relative;
            display: flex;

            .aplayer-bar-wrap {
                margin: 0 0 0 5px;
                padding: 4px 0;
                cursor: pointer !important;
                flex: 1;

                &:hover {
                    .aplayer-bar .aplayer-played .aplayer-thumb {
                        transform: scale(1);
                    }
                }

                .aplayer-bar {
                    position: relative;
                    height: 2px;
                    width: 100%;
                    background: #cdcdcd;

                    .aplayer-loaded {
                        position: absolute;
                        left: 0;
                        top: 0;
                        bottom: 0;
                        background: #aaa;
                        height: 2px;
                        transition: all 0.5s ease;
                    }

                    .aplayer-played {
                        position: absolute;
                        left: 0;
                        top: 0;
                        bottom: 0;
                        height: 2px;

                        .aplayer-thumb {
                            position: absolute;
                            top: 0;
                            right: 5px;
                            margin-top: -4px;
                            margin-right: -10px;
                            height: 10px;
                            width: 10px;
                            border-radius: 50%;
                            cursor: pointer;
                            transition: all .3s ease-in-out;
                            transform: scale(0);
                        }
                    }
                }
            }

            .aplayer-time {
                position: relative;
                right: 0;
                bottom: 4px;
                height: 17px;
                color: #999;
                font-size: 11px;
                padding-left: 7px;

                .aplayer-time-inner {
                    vertical-align: middle;
                }

                .aplayer-icon {
                    cursor: pointer;
                    transition: all 0.2s ease;

                    path {
                        fill: #666;
                    }

                    &.aplayer-icon-loop {
                        margin-right: 2px;
                    }

                    &:hover {
                        path {
                            fill: #000;
                        }
                    }

                    &.aplayer-icon-menu {
                        display: none;
                    }
                }

                &.aplayer-time-narrow {
                    .aplayer-icon-mode {
                        display: none;
                    }

                    .aplayer-icon-menu {
                        display: none;
                    }
                }
            }

            .aplayer-volume-wrap {
                position: relative;
                display: inline-block;
                margin-left: 3px;
                cursor: pointer !important;

                &:hover .aplayer-volume-bar-wrap {
                    height: 40px;
                }

                .aplayer-volume-bar-wrap {
                    position: absolute;
                    bottom: 15px;
                    right: -3px;
                    width: 25px;
                    height: 0;
                    z-index: 99;
                    overflow: hidden;
                    transition: all .2s ease-in-out;

                    &.aplayer-volume-bar-wrap-active {
                        height: 40px;
                    }

                    .aplayer-volume-bar {
                        position: absolute;
                        bottom: 0;
                        right: 10px;
                        width: 5px;
                        height: 35px;
                        background: #aaa;
                        border-radius: 2.5px;
                        overflow: hidden;

                        .aplayer-volume {
                            position: absolute;
                            bottom: 0;
                            right: 0;
                            width: 5px;
                            transition: all 0.1s ease;
                        }
                    }
                }
            }

            .aplayer-loading-icon {
                display: none;

                svg {
                    position: absolute;
                    animation: rotate 1s linear infinite;
                }
            }
        }
    }

    .aplayer-lrc {
        display: none;
        position: relative;
        height: $lrc-height;
        text-align: center;
        overflow: hidden;
        margin: -10px 0 7px;

        &:before {
            position: absolute;
            top: 0;
            z-index: 1;
            display: block;
            overflow: hidden;
            width: 100%;
            height: 10%;
            content: ' ';
            background: -moz-linear-gradient(top, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
            background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
            background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%);
            filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#00ffffff',GradientType=0 );
        }

        &:after {
            position: absolute;
            bottom: 0;
            z-index: 1;
            display: block;
            overflow: hidden;
            width: 100%;
            height: 33%;
            content: ' ';
            background: -moz-linear-gradient(top, rgba(255,255,255,0) 0%, rgba(255,255,255,0.8) 100%);
            background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%);
            background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(255,255,255,0.8) 100%);
            filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#ccffffff',GradientType=0 );
        }

        p {
            font-size: 12px;
            color: #666;
            line-height: 16px !important;
            height: 16px !important;
            padding: 0 !important;
            margin: 0 !important;
            transition: all 0.5s ease-out;
            opacity: 0.4;
            overflow: hidden;

            &.aplayer-lrc-current {
                opacity: 1;
                overflow: visible;
                height: initial !important;
                min-height: 16px;
            }
        }

        &.aplayer-lrc-hide {
            display: none;
        }

        .aplayer-lrc-contents {
            width: 100%;
            transition: all 0.5s ease-out;
            user-select: text;
            cursor: default;
        }
    }

    .aplayer-list {
        overflow: auto;
        transition: all 0.5s ease;
        will-change: height;
        display: none;
        overflow: hidden;
        list-style-type: none;
        margin: 0;
        padding: 0;
        overflow-y: auto;

        &::-webkit-scrollbar {
            width: 5px;
        }

        &::-webkit-scrollbar-thumb {
            border-radius: 3px;
            background-color: #eee;
        }

        &::-webkit-scrollbar-thumb:hover {
            background-color: #ccc;
        }

        li {
            position: relative;
            height: 32px;
            line-height: 32px;
            padding: 0 15px;
            font-size: 12px;
            border-top: 1px solid #e9e9e9;
            cursor: pointer;
            transition: all 0.2s ease;
            overflow: hidden;
            margin: 0;

            &:first-child {
                border-top: none;
            }

            &:hover {
                background: #efefef;
            }

            &.aplayer-list-light {
                background: #e9e9e9;

                .aplayer-list-cur {
                    display: inline-block;
                }
            }

            .aplayer-list-cur {
                display: none;
                width: 3px;
                height: 22px;
                position: absolute;
                left: 0;
                top: 5px;
                cursor: pointer;
            }
            .aplayer-list-index {
                color: #666;
                margin-right: 12px;
                cursor: pointer;
            }
            .aplayer-list-author {
                color: #666;
                float: right;
                cursor: pointer;
            }
        }
    }

    .aplayer-notice {
        opacity: 0;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 12px;
        border-radius: 4px;
        padding: 5px 10px;
        transition: all .3s ease-in-out;
        overflow: hidden;
        color: #fff;
        pointer-events: none;
        background-color: #f4f4f5;
        color: #909399;
    }

    .aplayer-miniswitcher {
        display: none;
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        height: 100%;
        background: #e6e6e6;
        width: 18px;
        border-radius: 0 2px 2px 0;

        .aplayer-icon {
            height: 100%;
            width: 100%;
            transform: rotateY(180deg);
            transition: all 0.3s ease;

            path {
                fill: #666;
            }

            &:hover {
                path {
                    fill: #000;
                }
            }
        }
    }
}

@keyframes aplayer-roll {
    0%{left:0}
    100%{left: -100%}
}

@keyframes rotate {
    0% {
        transform: rotate(0)
    }
    100% {
        transform: rotate(360deg)
    }
}

================================================
FILE: src/js/bar.js
================================================
class Bar {
    constructor(template) {
        this.elements = {};
        this.elements.volume = template.volume;
        this.elements.played = template.played;
        this.elements.loaded = template.loaded;
    }

    /**
     * Update progress
     *
     * @param {String} type - Point out which bar it is
     * @param {Number} percentage
     * @param {String} direction - Point out the direction of this bar, Should be height or width
     */
    set(type, percentage, direction) {
        percentage = Math.max(percentage, 0);
        percentage = Math.min(percentage, 1);
        this.elements[type].style[direction] = percentage * 100 + '%';
    }

    get(type, direction) {
        return parseFloat(this.elements[type].style[direction]) / 100;
    }
}

export default Bar;


================================================
FILE: src/js/controller.js
================================================
import utils from './utils';
import Icons from './icons';

class Controller {
    constructor(player) {
        this.player = player;

        this.initPlayButton();
        this.initPlayBar();
        this.initOrderButton();
        this.initLoopButton();
        this.initMenuButton();
        if (!utils.isMobile) {
            this.initVolumeButton();
        }
        this.initMiniSwitcher();
        this.initSkipButton();
        this.initLrcButton();
    }

    initPlayButton() {
        this.player.template.pic.addEventListener('click', () => {
            this.player.toggle();
        });
    }

    initPlayBar() {
        const thumbMove = (e) => {
            let percentage = ((e.clientX || e.changedTouches[0].clientX) - this.player.template.barWrap.getBoundingClientRect().left) / this.player.template.barWrap.clientWidth;
            percentage = Math.max(percentage, 0);
            percentage = Math.min(percentage, 1);
            this.player.bar.set('played', percentage, 'width');
            this.player.lrc && this.player.lrc.update(percentage * this.player.duration);
            this.player.template.ptime.innerHTML = utils.secondToTime(percentage * this.player.duration);
        };

        const thumbUp = (e) => {
            document.removeEventListener(utils.nameMap.dragEnd, thumbUp);
            document.removeEventListener(utils.nameMap.dragMove, thumbMove);
            let percentage = ((e.clientX || e.changedTouches[0].clientX) - this.player.template.barWrap.getBoundingClientRect().left) / this.player.template.barWrap.clientWidth;
            percentage = Math.max(percentage, 0);
            percentage = Math.min(percentage, 1);
            this.player.bar.set('played', percentage, 'width');
            this.player.seek(percentage * this.player.duration);
            this.player.disableTimeupdate = false;
        };

        this.player.template.barWrap.addEventListener(utils.nameMap.dragStart, () => {
            this.player.disableTimeupdate = true;
            document.addEventListener(utils.nameMap.dragMove, thumbMove);
            document.addEventListener(utils.nameMap.dragEnd, thumbUp);
        });
    }

    initVolumeButton() {
        this.player.template.volumeButton.addEventListener('click', () => {
            if (this.player.audio.muted) {
                this.player.volume(this.player.audio.volume, true);
            } else {
                this.player.audio.muted = true;
                this.player.switchVolumeIcon();
                this.player.bar.set('volume', 0, 'height');
            }
        });

        const thumbMove = (e) => {
            let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - this.player.template.volumeBar.getBoundingClientRect().top) / this.player.template.volumeBar.clientHeight;
            percentage = Math.max(percentage, 0);
            percentage = Math.min(percentage, 1);
            this.player.volume(percentage);
        };

        const thumbUp = (e) => {
            this.player.template.volumeBarWrap.classList.remove('aplayer-volume-bar-wrap-active');
            document.removeEventListener(utils.nameMap.dragEnd, thumbUp);
            document.removeEventListener(utils.nameMap.dragMove, thumbMove);
            let percentage = 1 - ((e.clientY || e.changedTouches[0].clientY) - this.player.template.volumeBar.getBoundingClientRect().top) / this.player.template.volumeBar.clientHeight;
            percentage = Math.max(percentage, 0);
            percentage = Math.min(percentage, 1);
            this.player.volume(percentage);
        };

        this.player.template.volumeBarWrap.addEventListener(utils.nameMap.dragStart, () => {
            this.player.template.volumeBarWrap.classList.add('aplayer-volume-bar-wrap-active');
            document.addEventListener(utils.nameMap.dragMove, thumbMove);
            document.addEventListener(utils.nameMap.dragEnd, thumbUp);
        });
    }

    initOrderButton() {
        this.player.template.order.addEventListener('click', () => {
            if (this.player.options.order === 'list') {
                this.player.options.order = 'random';
                this.player.template.order.innerHTML = Icons.orderRandom;
            } else if (this.player.options.order === 'random') {
                this.player.options.order = 'list';
                this.player.template.order.innerHTML = Icons.orderList;
            }
        });
    }

    initLoopButton() {
        this.player.template.loop.addEventListener('click', () => {
            if (this.player.list.audios.length > 1) {
                if (this.player.options.loop === 'one') {
                    this.player.options.loop = 'none';
                    this.player.template.loop.innerHTML = Icons.loopNone;
                } else if (this.player.options.loop === 'none') {
                    this.player.options.loop = 'all';
                    this.player.template.loop.innerHTML = Icons.loopAll;
                } else if (this.player.options.loop === 'all') {
                    this.player.options.loop = 'one';
                    this.player.template.loop.innerHTML = Icons.loopOne;
                }
            } else {
                if (this.player.options.loop === 'one' || this.player.options.loop === 'all') {
                    this.player.options.loop = 'none';
                    this.player.template.loop.innerHTML = Icons.loopNone;
                } else if (this.player.options.loop === 'none') {
                    this.player.options.loop = 'all';
                    this.player.template.loop.innerHTML = Icons.loopAll;
                }
            }
        });
    }

    initMenuButton() {
        this.player.template.menu.addEventListener('click', () => {
            this.player.list.toggle();
        });
    }

    initMiniSwitcher() {
        this.player.template.miniSwitcher.addEventListener('click', () => {
            this.player.setMode(this.player.mode === 'mini' ? 'normal' : 'mini');
        });
    }

    initSkipButton() {
        this.player.template.skipBackButton.addEventListener('click', () => {
            this.player.skipBack();
        });
        this.player.template.skipForwardButton.addEventListener('click', () => {
            this.player.skipForward();
        });
        this.player.template.skipPlayButton.addEventListener('click', () => {
            this.player.toggle();
        });
    }

    initLrcButton() {
        this.player.template.lrcButton.addEventListener('click', () => {
            if (this.player.template.lrcButton.classList.contains('aplayer-icon-lrc-inactivity')) {
                this.player.template.lrcButton.classList.remove('aplayer-icon-lrc-inactivity');
                this.player.lrc && this.player.lrc.show();
            } else {
                this.player.template.lrcButton.classList.add('aplayer-icon-lrc-inactivity');
                this.player.lrc && this.player.lrc.hide();
            }
        });
    }
}

export default Controller;


================================================
FILE: src/js/events.js
================================================
class Events {
    constructor() {
        this.events = {};

        this.audioEvents = [
            'abort',
            'canplay',
            'canplaythrough',
            'durationchange',
            'emptied',
            'ended',
            'error',
            'loadeddata',
            'loadedmetadata',
            'loadstart',
            'mozaudioavailable',
            'pause',
            'play',
            'playing',
            'progress',
            'ratechange',
            'seeked',
            'seeking',
            'stalled',
            'suspend',
            'timeupdate',
            'volumechange',
            'waiting',
        ];
        this.playerEvents = ['destroy', 'listshow', 'listhide', 'listadd', 'listremove', 'listswitch', 'listclear', 'noticeshow', 'noticehide', 'lrcshow', 'lrchide'];
    }

    on(name, callback) {
        if (this.type(name) && typeof callback === 'function') {
            if (!this.events[name]) {
                this.events[name] = [];
            }
            this.events[name].push(callback);
        }
    }

    trigger(name, data) {
        if (this.events[name] && this.events[name].length) {
            for (let i = 0; i < this.events[name].length; i++) {
                this.events[name][i](data);
            }
        }
    }

    type(name) {
        if (this.playerEvents.indexOf(name) !== -1) {
            return 'player';
        } else if (this.audioEvents.indexOf(name) !== -1) {
            return 'audio';
        }

        console.error(`Unknown event name: ${name}`);
        return null;
    }
}

export default Events;


================================================
FILE: src/js/icons.js
================================================
import play from '../assets/play.svg';
import pause from '../assets/pause.svg';
import volumeUp from '../assets/volume-up.svg';
import volumeDown from '../assets/volume-down.svg';
import volumeOff from '../assets/volume-off.svg';
import orderRandom from '../assets/order-random.svg';
import orderList from '../assets/order-list.svg';
import menu from '../assets/menu.svg';
import loopAll from '../assets/loop-all.svg';
import loopOne from '../assets/loop-one.svg';
import loopNone from '../assets/loop-none.svg';
import loading from '../assets/loading.svg';
import right from '../assets/right.svg';
import skip from '../assets/skip.svg';
import lrc from '../assets/lrc.svg';

const Icons = {
    play: play,
    pause: pause,
    volumeUp: volumeUp,
    volumeDown: volumeDown,
    volumeOff: volumeOff,
    orderRandom: orderRandom,
    orderList: orderList,
    menu: menu,
    loopAll: loopAll,
    loopOne: loopOne,
    loopNone: loopNone,
    loading: loading,
    right: right,
    skip: skip,
    lrc: lrc,
};

export default Icons;


================================================
FILE: src/js/index.js
================================================
import '../css/index.scss';
import APlayer from './player';

/* global APLAYER_VERSION GIT_HASH */
console.log(`${'\n'} %c APlayer v${APLAYER_VERSION} ${GIT_HASH} %c http://aplayer.js.org ${'\n'}`, 'color: #fadfa3; background: #030307; padding:5px 0;', 'background: #fadfa3; padding:5px 0;');

export default APlayer;


================================================
FILE: src/js/list.js
================================================
import tplListItem from '../template/list-item.art';
import utils from './utils';
import smoothScroll from 'smoothscroll';

class List {
    constructor(player) {
        this.player = player;
        this.index = 0;
        this.audios = this.player.options.audio;
        this.showing = true;
        this.player.template.list.style.height = `${Math.min(this.player.template.list.scrollHeight, this.player.options.listMaxHeight)}px`;

        this.bindEvents();
    }

    bindEvents() {
        this.player.template.list.addEventListener('click', (e) => {
            let target;
            if (e.target.tagName.toUpperCase() === 'LI') {
                target = e.target;
            } else {
                target = e.target.parentElement;
            }
            const audioIndex = parseInt(target.getElementsByClassName('aplayer-list-index')[0].innerHTML) - 1;
            if (audioIndex !== this.index) {
                this.switch(audioIndex);
                this.player.play();
            } else {
                this.player.toggle();
            }
        });
    }

    show() {
        this.showing = true;
        this.player.template.list.scrollTop = this.index * 33;
        this.player.template.list.style.height = `${Math.min(this.player.template.list.scrollHeight, this.player.options.listMaxHeight)}px`;
        this.player.events.trigger('listshow');
    }

    hide() {
        this.showing = false;
        this.player.template.list.style.height = `${Math.min(this.player.template.list.scrollHeight, this.player.options.listMaxHeight)}px`;
        setTimeout(() => {
            this.player.template.list.style.height = '0px';
            this.player.events.trigger('listhide');
        }, 0);
    }

    toggle() {
        if (this.showing) {
            this.hide();
        } else {
            this.show();
        }
    }

    add(audios) {
        this.player.events.trigger('listadd', {
            audios: audios,
        });

        if (Object.prototype.toString.call(audios) !== '[object Array]') {
            audios = [audios];
        }
        audios.map((item) => {
            item.name = item.name || item.title || 'Audio name';
            item.artist = item.artist || item.author || 'Audio artist';
            item.cover = item.cover || item.pic;
            item.type = item.type || 'normal';
            return item;
        });

        const wasSingle = !(this.audios.length > 1);
        const wasEmpty = this.audios.length === 0;

        this.player.template.list.innerHTML += tplListItem({
            theme: this.player.options.theme,
            audio: audios,
            index: this.audios.length + 1,
        });

        this.audios = this.audios.concat(audios);

        if (wasSingle && this.audios.length > 1) {
            this.player.container.classList.add('aplayer-withlist');
        }

        this.player.randomOrder = utils.randomOrder(this.audios.length);
        this.player.template.listCurs = this.player.container.querySelectorAll('.aplayer-list-cur');

        this.player.template.listCurs[this.audios.length - 1].style.backgroundColor = audios.theme || this.player.options.theme;

        if (wasEmpty) {
            if (this.player.options.order === 'random') {
                this.switch(this.player.randomOrder[0]);
            } else {
                this.switch(0);
            }
        }
    }

    remove(index) {
        this.player.events.trigger('listremove', {
            index: index,
        });
        if (this.audios[index]) {
            if (this.audios.length > 1) {
                const list = this.player.container.querySelectorAll('.aplayer-list li');
                list[index].remove();

                this.audios.splice(index, 1);
                this.player.lrc && this.player.lrc.remove(index);

                if (index === this.index) {
                    if (this.audios[index]) {
                        this.switch(index);
                    } else {
                        this.switch(index - 1);
                    }
                }
                if (this.index > index) {
                    this.index--;
                }

                for (let i = index; i < list.length; i++) {
                    list[i].getElementsByClassName('aplayer-list-index')[0].textContent = i;
                }
                if (this.audios.length === 1) {
                    this.player.container.classList.remove('aplayer-withlist');
                }

                this.player.template.listCurs = this.player.container.querySelectorAll('.aplayer-list-cur');
            } else {
                this.clear();
            }
        }
    }

    switch(index) {
        this.player.events.trigger('listswitch', {
            index: index,
        });

        if (typeof index !== 'undefined' && this.audios[index]) {
            this.index = index;

            const audio = this.audios[this.index];

            // set html
            this.player.template.pic.style.backgroundImage = audio.cover ? `url('${audio.cover}')` : '';
            this.player.theme(this.audios[this.index].theme || this.player.options.theme, this.index, false);
            this.player.template.title.innerHTML = audio.name;
            this.player.template.author.innerHTML = audio.artist ? ' - ' + audio.artist : '';

            const light = this.player.container.getElementsByClassName('aplayer-list-light')[0];
            if (light) {
                light.classList.remove('aplayer-list-light');
            }
            this.player.container.querySelectorAll('.aplayer-list li')[this.index].classList.add('aplayer-list-light');

            smoothScroll(this.index * 33, 500, null, this.player.template.list);

            this.player.setAudio(audio);

            this.player.lrc && this.player.lrc.switch(this.index);
            this.player.lrc && this.player.lrc.update(0);

            // set duration time
            if (this.player.duration !== 1) {
                // compatibility: Android browsers will output 1 at first
                this.player.template.dtime.innerHTML = utils.secondToTime(this.player.duration);
            }
        }
    }

    clear() {
        this.player.events.trigger('listclear');
        this.index = 0;
        this.player.container.classList.remove('aplayer-withlist');
        this.player.pause();
        this.audios = [];
        this.player.lrc && this.player.lrc.clear();
        this.player.audio.src = '';
        this.player.template.list.innerHTML = '';
        this.player.template.pic.style.backgroundImage = '';
        this.player.theme(this.player.options.theme, this.index, false);
        this.player.template.title.innerHTML = 'No audio';
        this.player.template.author.innerHTML = '';
        this.player.bar.set('loaded', 0, 'width');
        this.player.template.dtime.innerHTML = utils.secondToTime(0);
    }
}

export default List;


================================================
FILE: src/js/lrc.js
================================================
import tplLrc from '../template/lrc.art';

class Lrc {
    constructor(options) {
        this.container = options.container;
        this.async = options.async;
        this.player = options.player;
        this.parsed = [];
        this.index = 0;
        this.current = [];
    }

    show() {
        this.player.events.trigger('lrcshow');
        this.player.template.lrcWrap.classList.remove('aplayer-lrc-hide');
    }

    hide() {
        this.player.events.trigger('lrchide');
        this.player.template.lrcWrap.classList.add('aplayer-lrc-hide');
    }

    toggle() {
        if (this.player.template.lrcWrap.classList.contains('aplayer-lrc-hide')) {
            this.show();
        } else {
            this.hide();
        }
    }

    update(currentTime = this.player.audio.currentTime) {
        if (this.index > this.current.length - 1 || currentTime < this.current[this.index][0] || (!this.current[this.index + 1] || currentTime >= this.current[this.index + 1][0])) {
            for (let i = 0; i < this.current.length; i++) {
                if (currentTime >= this.current[i][0] && (!this.current[i + 1] || currentTime < this.current[i + 1][0])) {
                    this.index = i;
                    this.container.style.transform = `translateY(${-this.index * 16}px)`;
                    this.container.style.webkitTransform = `translateY(${-this.index * 16}px)`;
                    this.container.getElementsByClassName('aplayer-lrc-current')[0].classList.remove('aplayer-lrc-current');
                    this.container.getElementsByTagName('p')[i].classList.add('aplayer-lrc-current');
                }
            }
        }
    }

    switch(index) {
        if (!this.parsed[index]) {
            if (!this.async) {
                if (this.player.list.audios[index].lrc) {
                    this.parsed[index] = this.parse(this.player.list.audios[index].lrc);
                } else {
                    this.parsed[index] = [['00:00', 'Not available']];
                }
            } else {
                this.parsed[index] = [['00:00', 'Loading']];
                const xhr = new XMLHttpRequest();
                xhr.onreadystatechange = () => {
                    if (index === this.player.list.index && xhr.readyState === 4) {
                        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
                            this.parsed[index] = this.parse(xhr.responseText);
                        } else {
                            this.player.notice(`LRC file request fails: status ${xhr.status}`);
                            this.parsed[index] = [['00:00', 'Not available']];
                        }
                        this.container.innerHTML = tplLrc({
                            lyrics: this.parsed[index],
                        });
                        this.update(0);
                        this.current = this.parsed[index];
                    }
                };
                const apiurl = this.player.list.audios[index].lrc;
                xhr.open('get', apiurl, true);
                xhr.send(null);
            }
        }

        this.container.innerHTML = tplLrc({
            lyrics: this.parsed[index],
        });
        this.current = this.parsed[index];
        this.update(0);
    }

    /**
     * Parse lrc, suppose multiple time tag
     *
     * @param {String} lrc_s - Format:
     * [mm:ss]lyric
     * [mm:ss.xx]lyric
     * [mm:ss.xxx]lyric
     * [mm:ss.xx][mm:ss.xx][mm:ss.xx]lyric
     * [mm:ss.xx]<mm:ss.xx>lyric
     *
     * @return {String} [[time, text], [time, text], [time, text], ...]
     */
    parse(lrc_s) {
        if (lrc_s) {
            lrc_s = lrc_s.replace(/([^\]^\n])\[/g, (match, p1) => p1 + '\n[');
            const lyric = lrc_s.split('\n');
            let lrc = [];
            const lyricLen = lyric.length;
            for (let i = 0; i < lyricLen; i++) {
                // match lrc time
                const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g);
                // match lrc text
                const lrcText = lyric[i]
                    .replace(/.*\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g, '')
                    .replace(/<(\d{2}):(\d{2})(\.(\d{2,3}))?>/g, '')
                    .replace(/^\s+|\s+$/g, '');

                if (lrcTimes) {
                    // handle multiple time tag
                    const timeLen = lrcTimes.length;
                    for (let j = 0; j < timeLen; j++) {
                        const oneTime = /\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/.exec(lrcTimes[j]);
                        const min2sec = oneTime[1] * 60;
                        const sec2sec = parseInt(oneTime[2]);
                        const msec2sec = oneTime[4] ? parseInt(oneTime[4]) / ((oneTime[4] + '').length === 2 ? 100 : 1000) : 0;
                        const lrcTime = min2sec + sec2sec + msec2sec;
                        lrc.push([lrcTime, lrcText]);
                    }
                }
            }
            // sort by time
            lrc = lrc.filter((item) => item[1]);
            lrc.sort((a, b) => a[0] - b[0]);
            return lrc;
        } else {
            return [];
        }
    }

    remove(index) {
        this.parsed.splice(index, 1);
    }

    clear() {
        this.parsed = [];
        this.container.innerHTML = '';
    }
}

export default Lrc;


================================================
FILE: src/js/options.js
================================================
export default (options) => {
    // default options
    const defaultOption = {
        container: options.element || document.getElementsByClassName('aplayer')[0],
        mini: options.narrow || options.fixed || false,
        fixed: false,
        autoplay: false,
        mutex: true,
        lrcType: options.showlrc || options.lrc || 0,
        preload: 'metadata',
        theme: '#b7daff',
        loop: 'all',
        order: 'list',
        volume: 0.7,
        listFolded: options.fixed,
        listMaxHeight: options.listmaxheight || 250,
        audio: options.music || [],
        storageName: 'aplayer-setting',
    };
    for (const defaultKey in defaultOption) {
        if (defaultOption.hasOwnProperty(defaultKey) && !options.hasOwnProperty(defaultKey)) {
            options[defaultKey] = defaultOption[defaultKey];
        }
    }

    options.listMaxHeight = parseFloat(options.listMaxHeight);

    if (Object.prototype.toString.call(options.audio) !== '[object Array]') {
        options.audio = [options.audio];
    }

    options.audio.map((item) => {
        item.name = item.name || item.title || 'Audio name';
        item.artist = item.artist || item.author || 'Audio artist';
        item.cover = item.cover || item.pic;
        item.type = item.type || 'normal';
        return item;
    });

    if (options.audio.length <= 1 && options.loop === 'one') {
        options.loop = 'all';
    }

    return options;
};


================================================
FILE: src/js/player.js
================================================
import Promise from 'promise-polyfill';

import utils from './utils';
import Icons from './icons';
import handleOption from './options';
import Template from './template';
import Bar from './bar';
import Storage from './storage';
import Lrc from './lrc';
import Controller from './controller';
import Timer from './timer';
import Events from './events';
import List from './list';

const instances = [];

class APlayer {
    /**
     * APlayer constructor function
     *
     * @param {Object} options - See README
     * @constructor
     */
    constructor(options) {
        this.options = handleOption(options);
        this.container = this.options.container;
        this.paused = true;
        this.playedPromise = Promise.resolve();
        this.mode = 'normal';

        this.randomOrder = utils.randomOrder(this.options.audio.length);

        this.container.classList.add('aplayer');
        if (this.options.lrcType && !this.options.fixed) {
            this.container.classList.add('aplayer-withlrc');
        }
        if (this.options.audio.length > 1) {
            this.container.classList.add('aplayer-withlist');
        }
        if (utils.isMobile) {
            this.container.classList.add('aplayer-mobile');
        }
        this.arrow = this.container.offsetWidth <= 300;
        if (this.arrow) {
            this.container.classList.add('aplayer-arrow');
        }

        // save lrc
        if (this.options.lrcType === 2 || this.options.lrcType === true) {
            const lrcEle = this.container.getElementsByClassName('aplayer-lrc-content');
            for (let i = 0; i < lrcEle.length; i++) {
                if (this.options.audio[i]) {
                    this.options.audio[i].lrc = lrcEle[i].innerHTML;
                }
            }
        }

        this.template = new Template({
            container: this.container,
            options: this.options,
            randomOrder: this.randomOrder,
        });

        if (this.options.fixed) {
            this.container.classList.add('aplayer-fixed');
            this.template.body.style.width = this.template.body.offsetWidth - 18 + 'px';
        }
        if (this.options.mini) {
            this.setMode('mini');
            this.template.info.style.display = 'block';
        }
        if (this.template.info.offsetWidth < 200) {
            this.template.time.classList.add('aplayer-time-narrow');
        }

        if (this.options.lrcType) {
            this.lrc = new Lrc({
                container: this.template.lrc,
                async: this.options.lrcType === 3,
                player: this,
            });
        }
        this.events = new Events();
        this.storage = new Storage(this);
        this.bar = new Bar(this.template);
        this.controller = new Controller(this);
        this.timer = new Timer(this);
        this.list = new List(this);

        this.initAudio();
        this.bindEvents();
        if (this.options.order === 'random') {
            this.list.switch(this.randomOrder[0]);
        } else {
            this.list.switch(0);
        }

        // autoplay
        if (this.options.autoplay) {
            this.play();
        }

        instances.push(this);
    }

    initAudio() {
        this.audio = document.createElement('audio');
        this.audio.preload = this.options.preload;

        for (let i = 0; i < this.events.audioEvents.length; i++) {
            this.audio.addEventListener(this.events.audioEvents[i], (e) => {
                this.events.trigger(this.events.audioEvents[i], e);
            });
        }

        this.volume(this.storage.get('volume'), true);
    }

    bindEvents() {
        this.on('play', () => {
            if (this.paused) {
                this.setUIPlaying();
            }
        });

        this.on('pause', () => {
            if (!this.paused) {
                this.setUIPaused();
            }
        });

        this.on('timeupdate', () => {
            if (!this.disableTimeupdate) {
                this.bar.set('played', this.audio.currentTime / this.duration, 'width');
                this.lrc && this.lrc.update();
                const currentTime = utils.secondToTime(this.audio.currentTime);
                if (this.template.ptime.innerHTML !== currentTime) {
                    this.template.ptime.innerHTML = currentTime;
                }
            }
        });

        // show audio time: the metadata has loaded or changed
        this.on('durationchange', () => {
            if (this.duration !== 1) {
                // compatibility: Android browsers will output 1 at first
                this.template.dtime.innerHTML = utils.secondToTime(this.duration);
            }
        });

        // Can seek now
        this.on('loadedmetadata', () => {
            this.seek(0);
            if (!this.paused) {
                this.audio.play();
            }
        });

        // show audio loaded bar: to inform interested parties of progress downloading the media
        this.on('canplay', () => {
            const percentage = this.audio.buffered.length ? this.audio.buffered.end(this.audio.buffered.length - 1) / this.duration : 0;
            this.bar.set('loaded', percentage, 'width');
        });
        this.on('progress', () => {
            const percentage = this.audio.buffered.length ? this.audio.buffered.end(this.audio.buffered.length - 1) / this.duration : 0;
            this.bar.set('loaded', percentage, 'width');
        });

        // audio download error: an error occurs
        let skipTime;
        this.on('error', () => {
            if (this.list.audios.length > 1) {
                this.notice('An audio error has occurred, player will skip forward in 2 seconds.');
                skipTime = setTimeout(() => {
                    this.skipForward();
                    if (!this.paused) {
                        this.play();
                    }
                }, 2000);
            } else if (this.list.audios.length === 1) {
                this.notice('An audio error has occurred.');
            }
        });
        this.events.on('listswitch', () => {
            skipTime && clearTimeout(skipTime);
        });

        // multiple audio play
        this.on('ended', () => {
            if (this.options.loop === 'none') {
                if (this.options.order === 'list') {
                    if (this.list.index < this.list.audios.length - 1) {
                        this.list.switch((this.list.index + 1) % this.list.audios.length);
                        this.play();
                    } else {
                        this.list.switch((this.list.index + 1) % this.list.audios.length);
                        this.pause();
                    }
                } else if (this.options.order === 'random') {
                    if (this.randomOrder.indexOf(this.list.index) < this.randomOrder.length - 1) {
                        this.list.switch(this.nextIndex());
                        this.play();
                    } else {
                        this.list.switch(this.nextIndex());
                        this.pause();
                    }
                }
            } else if (this.options.loop === 'one') {
                this.list.switch(this.list.index);
                this.play();
            } else if (this.options.loop === 'all') {
                this.skipForward();
                this.play();
            }
        });
    }

    setAudio(audio) {
        if (this.hls) {
            this.hls.destroy();
            this.hls = null;
        }
        let type = audio.type;
        if (this.options.customAudioType && this.options.customAudioType[type]) {
            if (Object.prototype.toString.call(this.options.customAudioType[type]) === '[object Function]') {
                this.options.customAudioType[type](this.audio, audio, this);
            } else {
                console.error(`Illegal customType: ${type}`);
            }
        } else {
            if (!type || type === 'auto') {
                if (/m3u8(#|\?|$)/i.exec(audio.url)) {
                    type = 'hls';
                } else {
                    type = 'normal';
                }
            }
            if (type === 'hls') {
                if (window.Hls.isSupported()) {
                    this.hls = new window.Hls();
                    this.hls.loadSource(audio.url);
                    this.hls.attachMedia(this.audio);
                } else if (this.audio.canPlayType('application/x-mpegURL') || this.audio.canPlayType('application/vnd.apple.mpegURL')) {
                    this.audio.src = audio.url;
                } else {
                    this.notice('Error: HLS is not supported.');
                }
            } else if (type === 'normal') {
                this.audio.src = audio.url;
            }
        }
    }

    theme(color = this.list.audios[this.list.index].theme || this.options.theme, index = this.list.index, isReset = true) {
        if (isReset) {
            this.list.audios[index] && (this.list.audios[index].theme = color);
        }
        this.template.listCurs[index] && (this.template.listCurs[index].style.backgroundColor = color);
        if (index === this.list.index) {
            this.template.pic.style.backgroundColor = color;
            this.template.played.style.background = color;
            this.template.thumb.style.background = color;
            this.template.volume.style.background = color;
        }
    }

    seek(time) {
        time = Math.max(time, 0);
        time = Math.min(time, this.duration);
        this.audio.currentTime = time;
        this.bar.set('played', time / this.duration, 'width');
        this.template.ptime.innerHTML = utils.secondToTime(time);
    }

    get duration() {
        return isNaN(this.audio.duration) ? 0 : this.audio.duration;
    }

    setUIPlaying() {
        if (this.paused) {
            this.paused = false;
            this.template.button.classList.remove('aplayer-play');
            this.template.button.classList.add('aplayer-pause');
            this.template.button.innerHTML = '';
            setTimeout(() => {
                this.template.button.innerHTML = Icons.pause;
            }, 100);
            this.template.skipPlayButton.innerHTML = Icons.pause;
        }

        this.timer.enable('loading');

        if (this.options.mutex) {
            for (let i = 0; i < instances.length; i++) {
                if (this !== instances[i]) {
                    instances[i].pause();
                }
            }
        }
    }

    play() {
        this.setUIPlaying();

        const playPromise = this.audio.play();
        if (playPromise) {
            playPromise.catch((e) => {
                console.warn(e);
                if (e.name === 'NotAllowedError') {
                    this.setUIPaused();
                }
            });
        }
    }

    setUIPaused() {
        if (!this.paused) {
            this.paused = true;

            this.template.button.classList.remove('aplayer-pause');
            this.template.button.classList.add('aplayer-play');
            this.template.button.innerHTML = '';
            setTimeout(() => {
                this.template.button.innerHTML = Icons.play;
            }, 100);
            this.template.skipPlayButton.innerHTML = Icons.play;
        }

        this.container.classList.remove('aplayer-loading');
        this.timer.disable('loading');
    }

    pause() {
        this.setUIPaused();
        this.audio.pause();
    }

    switchVolumeIcon() {
        if (this.volume() >= 0.95) {
            this.template.volumeButton.innerHTML = Icons.volumeUp;
        } else if (this.volume() > 0) {
            this.template.volumeButton.innerHTML = Icons.volumeDown;
        } else {
            this.template.volumeButton.innerHTML = Icons.volumeOff;
        }
    }

    /**
     * Set volume
     */
    volume(percentage, nostorage) {
        percentage = parseFloat(percentage);
        if (!isNaN(percentage)) {
            percentage = Math.max(percentage, 0);
            percentage = Math.min(percentage, 1);
            this.bar.set('volume', percentage, 'height');
            if (!nostorage) {
                this.storage.set('volume', percentage);
            }

            this.audio.volume = percentage;
            if (this.audio.muted) {
                this.audio.muted = false;
            }

            this.switchVolumeIcon();
        }

        return this.audio.muted ? 0 : this.audio.volume;
    }

    /**
     * bind events
     */
    on(name, callback) {
        this.events.on(name, callback);
    }

    /**
     * toggle between play and pause
     */
    toggle() {
        if (this.template.button.classList.contains('aplayer-play')) {
            this.play();
        } else if (this.template.button.classList.contains('aplayer-pause')) {
            this.pause();
        }
    }

    // abandoned
    switchAudio(index) {
        this.list.switch(index);
    }

    // abandoned
    addAudio(audios) {
        this.list.add(audios);
    }

    // abandoned
    removeAudio(index) {
        this.list.remove(index);
    }

    /**
     * destroy this player
     */
    destroy() {
        instances.splice(instances.indexOf(this), 1);
        this.pause();
        this.container.innerHTML = '';
        this.audio.src = '';
        this.timer.destroy();
        this.events.trigger('destroy');
    }

    setMode(mode = 'normal') {
        this.mode = mode;
        if (mode === 'mini') {
            this.container.classList.add('aplayer-narrow');
        } else if (mode === 'normal') {
            this.container.classList.remove('aplayer-narrow');
        }
    }

    notice(text, time = 2000, opacity = 0.8) {
        this.template.notice.innerHTML = text;
        this.template.notice.style.opacity = opacity;
        if (this.noticeTime) {
            clearTimeout(this.noticeTime);
        }
        this.events.trigger('noticeshow', {
            text: text,
        });
        if (time) {
            this.noticeTime = setTimeout(() => {
                this.template.notice.style.opacity = 0;
                this.events.trigger('noticehide');
            }, time);
        }
    }

    prevIndex() {
        if (this.list.audios.length > 1) {
            if (this.options.order === 'list') {
                return this.list.index - 1 < 0 ? this.list.audios.length - 1 : this.list.index - 1;
            } else if (this.options.order === 'random') {
                const index = this.randomOrder.indexOf(this.list.index);
                if (index === 0) {
                    return this.randomOrder[this.randomOrder.length - 1];
                } else {
                    return this.randomOrder[index - 1];
                }
            }
        } else {
            return 0;
        }
    }

    nextIndex() {
        if (this.list.audios.length > 1) {
            if (this.options.order === 'list') {
                return (this.list.index + 1) % this.list.audios.length;
            } else if (this.options.order === 'random') {
                const index = this.randomOrder.indexOf(this.list.index);
                if (index === this.randomOrder.length - 1) {
                    return this.randomOrder[0];
                } else {
                    return this.randomOrder[index + 1];
                }
            }
        } else {
            return 0;
        }
    }

    skipBack() {
        this.list.switch(this.prevIndex());
    }

    skipForward() {
        this.list.switch(this.nextIndex());
    }

    static get version() {
        /* global APLAYER_VERSION */
        return APLAYER_VERSION;
    }
}

export default APlayer;


================================================
FILE: src/js/storage.js
================================================
import utils from './utils';

class Storage {
    constructor(player) {
        this.storageName = player.options.storageName;

        this.data = JSON.parse(utils.storage.get(this.storageName));
        if (!this.data) {
            this.data = {};
        }
        this.data.volume = this.data.volume || player.options.volume;
    }

    get(key) {
        return this.data[key];
    }

    set(key, value) {
        this.data[key] = value;
        utils.storage.set(this.storageName, JSON.stringify(this.data));
    }
}

export default Storage;


================================================
FILE: src/js/template.js
================================================
import Icons from './icons';
import tplPlayer from '../template/player.art';

class Template {
    constructor(options) {
        this.container = options.container;
        this.options = options.options;
        this.randomOrder = options.randomOrder;
        this.init();
    }

    init() {
        let cover = '';
        if (this.options.audio.length) {
            if (this.options.order === 'random') {
                cover = this.options.audio[this.randomOrder[0]].cover;
            } else {
                cover = this.options.audio[0].cover;
            }
        }

        this.container.innerHTML = tplPlayer({
            options: this.options,
            icons: Icons,
            cover: cover,
            getObject: (obj) => obj,
        });

        this.lrc = this.container.querySelector('.aplayer-lrc-contents');
        this.lrcWrap = this.container.querySelector('.aplayer-lrc');
        this.ptime = this.container.querySelector('.aplayer-ptime');
        this.info = this.container.querySelector('.aplayer-info');
        this.time = this.container.querySelector('.aplayer-time');
        this.barWrap = this.container.querySelector('.aplayer-bar-wrap');
        this.button = this.container.querySelector('.aplayer-button');
        this.body = this.container.querySelector('.aplayer-body');
        this.list = this.container.querySelector('.aplayer-list');
        this.listCurs = this.container.querySelectorAll('.aplayer-list-cur');
        this.played = this.container.querySelector('.aplayer-played');
        this.loaded = this.container.querySelector('.aplayer-loaded');
        this.thumb = this.container.querySelector('.aplayer-thumb');
        this.volume = this.container.querySelector('.aplayer-volume');
        this.volumeBar = this.container.querySelector('.aplayer-volume-bar');
        this.volumeButton = this.container.querySelector('.aplayer-time button');
        this.volumeBarWrap = this.container.querySelector('.aplayer-volume-bar-wrap');
        this.loop = this.container.querySelector('.aplayer-icon-loop');
        this.order = this.container.querySelector('.aplayer-icon-order');
        this.menu = this.container.querySelector('.aplayer-icon-menu');
        this.pic = this.container.querySelector('.aplayer-pic');
        this.title = this.container.querySelector('.aplayer-title');
        this.author = this.container.querySelector('.aplayer-author');
        this.dtime = this.container.querySelector('.aplayer-dtime');
        this.notice = this.container.querySelector('.aplayer-notice');
        this.miniSwitcher = this.container.querySelector('.aplayer-miniswitcher');
        this.skipBackButton = this.container.querySelector('.aplayer-icon-back');
        this.skipForwardButton = this.container.querySelector('.aplayer-icon-forward');
        this.skipPlayButton = this.container.querySelector('.aplayer-icon-play');
        this.lrcButton = this.container.querySelector('.aplayer-icon-lrc');
    }
}

export default Template;


================================================
FILE: src/js/timer.js
================================================
class Timer {
    constructor(player) {
        this.player = player;

        window.requestAnimationFrame = (() =>
            window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame ||
            window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function(callback) {
                window.setTimeout(callback, 1000 / 60);
            })();

        this.types = ['loading'];

        this.init();
    }

    init() {
        this.types.forEach((item) => {
            this[`init${item}Checker`]();
        });
    }

    initloadingChecker() {
        let lastPlayPos = 0;
        let currentPlayPos = 0;
        let bufferingDetected = false;
        this.loadingChecker = setInterval(() => {
            if (this.enableloadingChecker) {
                // whether the audio is buffering
                currentPlayPos = this.player.audio.currentTime;
                if (!bufferingDetected && currentPlayPos === lastPlayPos && !this.player.audio.paused) {
                    this.player.container.classList.add('aplayer-loading');
                    bufferingDetected = true;
                }
                if (bufferingDetected && currentPlayPos > lastPlayPos && !this.player.audio.paused) {
                    this.player.container.classList.remove('aplayer-loading');
                    bufferingDetected = false;
                }
                lastPlayPos = currentPlayPos;
            }
        }, 100);
    }

    enable(type) {
        this[`enable${type}Checker`] = true;

        if (type === 'fps') {
            this.initfpsChecker();
        }
    }

    disable(type) {
        this[`enable${type}Checker`] = false;
    }

    destroy() {
        this.types.forEach((item) => {
            this[`enable${item}Checker`] = false;
            this[`${item}Checker`] && clearInterval(this[`${item}Checker`]);
        });
    }
}

export default Timer;


================================================
FILE: src/js/utils.js
================================================
const isMobile = /mobile/i.test(window.navigator.userAgent);

const utils = {
    /**
     * Parse second to time string
     *
     * @param {Number} second
     * @return {String} 00:00 or 00:00:00
     */
    secondToTime: (second) => {
        const add0 = (num) => (num < 10 ? '0' + num : '' + num);
        const hour = Math.floor(second / 3600);
        const min = Math.floor((second - hour * 3600) / 60);
        const sec = Math.floor(second - hour * 3600 - min * 60);
        return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':');
    },

    isMobile: isMobile,

    storage: {
        set: (key, value) => {
            localStorage.setItem(key, value);
        },

        get: (key) => localStorage.getItem(key),
    },

    nameMap: {
        dragStart: isMobile ? 'touchstart' : 'mousedown',
        dragMove: isMobile ? 'touchmove' : 'mousemove',
        dragEnd: isMobile ? 'touchend' : 'mouseup',
    },

    /**
     * get random order, using Fisher–Yates shuffle
     */
    randomOrder: (length) => {
        function shuffle(arr) {
            for (let i = arr.length - 1; i >= 0; i--) {
                const randomIndex = Math.floor(Math.random() * (i + 1));
                const itemAtIndex = arr[randomIndex];
                arr[randomIndex] = arr[i];
                arr[i] = itemAtIndex;
            }
            return arr;
        }
        return shuffle(
            [...Array(length)].map(function(item, i) {
                return i;
            })
        );
    },
};

export default utils;


================================================
FILE: src/template/list-item.art
================================================
{{each audio}}
<li>
    <span class="aplayer-list-cur" style="background-color: {{ $value.theme || theme }};"></span>
    <span class="aplayer-list-index">{{ $index + index }}</span>
    <span class="aplayer-list-title">{{ $value.name }}</span>
    <span class="aplayer-list-author">{{ $value.artist }}</span>
</li>
{{/each}}

================================================
FILE: src/template/lrc.art
================================================
{{each lyrics}}
    <p{{ if $index === 0 }} class="aplayer-lrc-current"{{ /if }}>{{$value[1]}}</p>
{{/each}}

================================================
FILE: src/template/player.art
================================================
{{ if !options.fixed }}
<div class="aplayer-body">
    <div class="aplayer-pic" style="{{ if cover  }}background-image: url(&quot;{{ cover }}&quot;);{{ /if }}background-color: {{ options.theme }};">
        <div class="aplayer-button aplayer-play">{{@ icons.play }}</div>
    </div>
    <div class="aplayer-info">
        <div class="aplayer-music">
            <span class="aplayer-title">No audio</span>
            <span class="aplayer-author"></span>
        </div>
        <div class="aplayer-lrc">
            <div class="aplayer-lrc-contents" style="transform: translateY(0); -webkit-transform: translateY(0);"></div>
        </div>
        <div class="aplayer-controller">
            <div class="aplayer-bar-wrap">
                <div class="aplayer-bar">
                    <div class="aplayer-loaded" style="width: 0"></div>
                    <div class="aplayer-played" style="width: 0; background: {{ options.theme }};">
                        <span class="aplayer-thumb" style="background: {{ options.theme }};">
                            <span class="aplayer-loading-icon">{{@ icons.loading }}</span>
                        </span>
                    </div>
                </div>
            </div>
            <div class="aplayer-time">
                <span class="aplayer-time-inner">
                    <span class="aplayer-ptime">00:00</span> / <span class="aplayer-dtime">00:00</span>
                </span>
                <span class="aplayer-icon aplayer-icon-back">
                    {{@ icons.skip }}
                </span>
                <span class="aplayer-icon aplayer-icon-play">
                    {{@ icons.play }}
                </span>
                <span class="aplayer-icon aplayer-icon-forward">
                    {{@ icons.skip }}
                </span>
                <div class="aplayer-volume-wrap">
                    <button type="button" class="aplayer-icon aplayer-icon-volume-down">
                        {{@ icons.volumeDown }}
                    </button>
                    <div class="aplayer-volume-bar-wrap">
                        <div class="aplayer-volume-bar">
                            <div class="aplayer-volume" style="height: 80%; background: {{ options.theme }};"></div>
                        </div>
                    </div>
                </div>
                <button type="button" class="aplayer-icon aplayer-icon-order">
                    {{ if options.order === 'list' }}{{@ icons.orderList }}{{ else if options.order === 'random' }}{{@ icons.orderRandom }}{{ /if }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-loop">
                    {{ if options.loop === 'one' }}{{@ icons.loopOne }}{{ else if options.loop === 'all' }}{{@ icons.loopAll }}{{ else if options.loop === 'none' }}{{@ icons.loopNone }}{{ /if }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-menu">
                    {{@ icons.menu }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-lrc">
                    {{@ icons.lrc }}
                </button>
            </div>
        </div>
    </div>
    <div class="aplayer-notice"></div>
    <div class="aplayer-miniswitcher"><button class="aplayer-icon">{{@ icons.right }}</button></div>
</div>
<ol class="aplayer-list{{ if options.listFolded }} aplayer-list-hide{{ /if }}">
    {{ include './list-item.art' getObject({
        theme: options.theme,
        audio: options.audio,
        index: 1
    }) }}
</ol>
{{ else }}
<ol class="aplayer-list{{ if options.listFolded }} aplayer-list-hide{{ /if }}">
    {{ include './list-item.art' getObject({
        theme: options.theme,
        audio: options.audio,
        index: 1
    }) }}
</ol>
<div class="aplayer-body">
    <div class="aplayer-pic" style="{{ if cover  }}background-image: url(&quot;{{ cover }}&quot;);{{ /if }}background-color: {{ options.theme }};">
        <div class="aplayer-button aplayer-play">{{@ icons.play }}</div>
    </div>
    <div class="aplayer-info" style="display: none;">
        <div class="aplayer-music">
            <span class="aplayer-title">No audio</span>
            <span class="aplayer-author"></span>
        </div>
        <div class="aplayer-controller">
            <div class="aplayer-bar-wrap">
                <div class="aplayer-bar">
                    <div class="aplayer-loaded" style="width: 0"></div>
                    <div class="aplayer-played" style="width: 0; background: {{ options.theme }};">
                        <span class="aplayer-thumb" style="background: {{ options.theme }};">
                            <span class="aplayer-loading-icon">{{@ icons.loading }}</span>
                        </span>
                    </div>
                </div>
            </div>
            <div class="aplayer-time">
                <span class="aplayer-time-inner">
                    <span class="aplayer-ptime">00:00</span> / <span class="aplayer-dtime">00:00</span>
                </span>
                <span class="aplayer-icon aplayer-icon-back">
                    {{@ icons.skip }}
                </span>
                <span class="aplayer-icon aplayer-icon-play">
                    {{@ icons.play }}
                </span>
                <span class="aplayer-icon aplayer-icon-forward">
                    {{@ icons.skip }}
                </span>
                <div class="aplayer-volume-wrap">
                    <button type="button" class="aplayer-icon aplayer-icon-volume-down">
                        {{@ icons.volumeDown }}
                    </button>
                    <div class="aplayer-volume-bar-wrap">
                        <div class="aplayer-volume-bar">
                            <div class="aplayer-volume" style="height: 80%; background: {{ options.theme }};"></div>
                        </div>
                    </div>
                </div>
                <button type="button" class="aplayer-icon aplayer-icon-order">
                    {{ if options.order === 'list' }}{{@ icons.orderList }}{{ else if options.order === 'random' }}{{@ icons.orderRandom }}{{ /if }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-loop">
                    {{ if options.loop === 'one' }}{{@ icons.loopOne }}{{ else if options.loop === 'all' }}{{@ icons.loopAll }}{{ else if options.loop === 'none' }}{{@ icons.loopNone }}{{ /if }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-menu">
                    {{@ icons.menu }}
                </button>
                <button type="button" class="aplayer-icon aplayer-icon-lrc">
                    {{@ icons.lrc }}
                </button>
            </div>
        </div>
    </div>
    <div class="aplayer-notice"></div>
    <div class="aplayer-miniswitcher"><button class="aplayer-icon">{{@ icons.right }}</button></div>
</div>
<div class="aplayer-lrc">
    <div class="aplayer-lrc-contents" style="transform: translateY(0); -webkit-transform: translateY(0);"></div>
</div>
{{/if}}

================================================
FILE: tea.yaml
================================================
# https://tea.xyz/what-is-this-file
---
version: 1.0.0
codeOwners:
  - '0x185bfcef7b37010e2511309048a130f477f54fBf'
quorum: 1


================================================
FILE: webpack/dev.config.js
================================================
const path = require('path');
const webpack = require('webpack');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');

module.exports = {
    mode: 'development',

    devtool: 'cheap-module-source-map',

    entry: {
        APlayer: './src/js/index.js',
    },

    output: {
        path: path.resolve(__dirname, '..', 'dist'),
        filename: '[name].js',
        library: '[name]',
        libraryTarget: 'umd',
        libraryExport: 'default',
        umdNamedDefine: true,
        publicPath: '/',
    },

    resolve: {
        modules: ['node_modules'],
        extensions: ['.js', '.scss'],
    },

    module: {
        strictExportPresence: true,
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            cacheDirectory: true,
                            presets: ['@babel/preset-env'],
                        },
                    },
                ],
            },
            {
                test: /\.scss$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1,
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [autoprefixer, cssnano],
                        },
                    },
                    'sass-loader',
                ],
            },
            {
                test: /\.(png|jpg)$/,
                loader: 'url-loader',
                options: {
                    limit: 40000,
                },
            },
            {
                test: /\.svg$/,
                loader: 'svg-inline-loader',
            },
            {
                test: /\.art$/,
                loader: 'art-template-loader',
            },
        ],
    },

    devServer: {
        compress: true,
        contentBase: path.resolve(__dirname, '..', 'demo'),
        clientLogLevel: 'none',
        quiet: false,
        open: true,
        historyApiFallback: {
            disableDotRule: true,
        },
        watchOptions: {
            ignored: /node_modules/,
        },
    },

    plugins: [
        new webpack.DefinePlugin({
            APLAYER_VERSION: `"${require('../package.json').version}"`,
            GIT_HASH: JSON.stringify(gitRevisionPlugin.version()),
        }),
    ],

    node: {
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
    },

    performance: {
        hints: false,
    },
};


================================================
FILE: webpack/prod.config.js
================================================
const path = require('path');
const webpack = require('webpack');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin();
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');

module.exports = {
    mode: 'production',

    bail: true,

    devtool: 'source-map',

    entry: {
        APlayer: './src/js/index.js',
    },

    output: {
        path: path.resolve(__dirname, '..', 'dist'),
        filename: '[name].min.js',
        library: '[name]',
        libraryTarget: 'umd',
        libraryExport: 'default',
        umdNamedDefine: true,
        publicPath: '/',
    },

    resolve: {
        modules: ['node_modules'],
        extensions: ['.js', '.scss'],
    },

    module: {
        strictExportPresence: true,
        rules: [
            {
                test: /\.js$/,
                use: [
                    'template-string-optimize-loader',
                    {
                        loader: 'babel-loader',
                        options: {
                            cacheDirectory: true,
                            presets: ['@babel/preset-env'],
                        },
                    },
                ],
            },
            {
                test: /\.scss$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1,
                        },
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            plugins: [autoprefixer, cssnano],
                        },
                    },
                    'sass-loader',
                ],
            },
            {
                test: /\.(png|jpg)$/,
                loader: 'url-loader',
                options: {
                    limit: 40000,
                },
            },
            {
                test: /\.svg$/,
                loader: 'svg-inline-loader',
            },
            {
                test: /\.art$/,
                loader: 'art-template-loader',
            },
        ],
    },

    plugins: [
        new webpack.DefinePlugin({
            APLAYER_VERSION: `"${require('../package.json').version}"`,
            GIT_HASH: JSON.stringify(gitRevisionPlugin.version()),
        }),
    ],

    node: {
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
    },
};
Download .txt
gitextract_5z3xtwg5/

├── .eslintignore
├── .eslintrc
├── .github/
│   └── ISSUE_TEMPLATE.md
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── demo/
│   ├── demo.js
│   └── index.html
├── docs/
│   ├── .nojekyll
│   ├── CNAME
│   ├── README.md
│   ├── config.js
│   ├── ecosystem.md
│   ├── index.html
│   ├── landing.html
│   ├── support.md
│   └── zh-Hans/
│       ├── README.md
│       ├── ecosystem.md
│       └── support.md
├── package.json
├── src/
│   ├── css/
│   │   └── index.scss
│   ├── js/
│   │   ├── bar.js
│   │   ├── controller.js
│   │   ├── events.js
│   │   ├── icons.js
│   │   ├── index.js
│   │   ├── list.js
│   │   ├── lrc.js
│   │   ├── options.js
│   │   ├── player.js
│   │   ├── storage.js
│   │   ├── template.js
│   │   ├── timer.js
│   │   └── utils.js
│   └── template/
│       ├── list-item.art
│       ├── lrc.art
│       └── player.art
├── tea.yaml
└── webpack/
    ├── dev.config.js
    └── prod.config.js
Download .txt
SYMBOL INDEX (95 symbols across 11 files)

FILE: docs/config.js
  function insertUmamiJS (line 55) | function insertUmamiJS(id, url) {
  function player (line 63) | function player () {
  function clearPlayer (line 83) | function clearPlayer () {
  function aplayer1 (line 91) | function aplayer1 () {
  function aplayer0 (line 121) | function aplayer0 () {
  function aplayer2 (line 151) | function aplayer2 () {
  function aplayer3 (line 164) | function aplayer3 () {
  function aplayer4 (line 202) | function aplayer4 () {
  function aplayer5 (line 217) | function aplayer5 () {
  function aplayer6 (line 246) | function aplayer6 () {
  function aplayer7 (line 260) | function aplayer7 () {
  function aplayer8 (line 292) | function aplayer8 () {
  function aplayer9 (line 324) | function aplayer9 () {

FILE: src/js/bar.js
  class Bar (line 1) | class Bar {
    method constructor (line 2) | constructor(template) {
    method set (line 16) | set(type, percentage, direction) {
    method get (line 22) | get(type, direction) {

FILE: src/js/controller.js
  class Controller (line 4) | class Controller {
    method constructor (line 5) | constructor(player) {
    method initPlayButton (line 21) | initPlayButton() {
    method initPlayBar (line 27) | initPlayBar() {
    method initVolumeButton (line 55) | initVolumeButton() {
    method initOrderButton (line 90) | initOrderButton() {
    method initLoopButton (line 102) | initLoopButton() {
    method initMenuButton (line 127) | initMenuButton() {
    method initMiniSwitcher (line 133) | initMiniSwitcher() {
    method initSkipButton (line 139) | initSkipButton() {
    method initLrcButton (line 151) | initLrcButton() {

FILE: src/js/events.js
  class Events (line 1) | class Events {
    method constructor (line 2) | constructor() {
    method on (line 33) | on(name, callback) {
    method trigger (line 42) | trigger(name, data) {
    method type (line 50) | type(name) {

FILE: src/js/list.js
  class List (line 5) | class List {
    method constructor (line 6) | constructor(player) {
    method bindEvents (line 16) | bindEvents() {
    method show (line 34) | show() {
    method hide (line 41) | hide() {
    method toggle (line 50) | toggle() {
    method add (line 58) | add(audios) {
    method remove (line 103) | remove(index) {
    method switch (line 140) | switch(index) {
    method clear (line 177) | clear() {

FILE: src/js/lrc.js
  class Lrc (line 3) | class Lrc {
    method constructor (line 4) | constructor(options) {
    method show (line 13) | show() {
    method hide (line 18) | hide() {
    method toggle (line 23) | toggle() {
    method update (line 31) | update(currentTime = this.player.audio.currentTime) {
    method switch (line 45) | switch(index) {
    method parse (line 96) | parse(lrc_s) {
    method remove (line 133) | remove(index) {
    method clear (line 137) | clear() {

FILE: src/js/player.js
  class APlayer (line 17) | class APlayer {
    method constructor (line 24) | constructor(options) {
    method initAudio (line 106) | initAudio() {
    method bindEvents (line 119) | bindEvents() {
    method setAudio (line 218) | setAudio(audio) {
    method theme (line 254) | theme(color = this.list.audios[this.list.index].theme || this.options....
    method seek (line 267) | seek(time) {
    method duration (line 275) | get duration() {
    method setUIPlaying (line 279) | setUIPlaying() {
    method play (line 302) | play() {
    method setUIPaused (line 316) | setUIPaused() {
    method pause (line 333) | pause() {
    method switchVolumeIcon (line 338) | switchVolumeIcon() {
    method volume (line 351) | volume(percentage, nostorage) {
    method on (line 375) | on(name, callback) {
    method toggle (line 382) | toggle() {
    method switchAudio (line 391) | switchAudio(index) {
    method addAudio (line 396) | addAudio(audios) {
    method removeAudio (line 401) | removeAudio(index) {
    method destroy (line 408) | destroy() {
    method setMode (line 417) | setMode(mode = 'normal') {
    method notice (line 426) | notice(text, time = 2000, opacity = 0.8) {
    method prevIndex (line 443) | prevIndex() {
    method nextIndex (line 460) | nextIndex() {
    method skipBack (line 477) | skipBack() {
    method skipForward (line 481) | skipForward() {
    method version (line 485) | static get version() {

FILE: src/js/storage.js
  class Storage (line 3) | class Storage {
    method constructor (line 4) | constructor(player) {
    method get (line 14) | get(key) {
    method set (line 18) | set(key, value) {

FILE: src/js/template.js
  class Template (line 4) | class Template {
    method constructor (line 5) | constructor(options) {
    method init (line 12) | init() {

FILE: src/js/timer.js
  class Timer (line 1) | class Timer {
    method constructor (line 2) | constructor(player) {
    method init (line 20) | init() {
    method initloadingChecker (line 26) | initloadingChecker() {
    method enable (line 47) | enable(type) {
    method disable (line 55) | disable(type) {
    method destroy (line 59) | destroy() {

FILE: src/js/utils.js
  function shuffle (line 38) | function shuffle(arr) {
Condensed preview — 43 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (157K chars).
[
  {
    "path": ".eslintignore",
    "chars": 14,
    "preview": "dist\ndemo\ndocs"
  },
  {
    "path": ".eslintrc",
    "chars": 1532,
    "preview": "{\n    \"extends\": [\"eslint:recommended\", \"plugin:prettier/recommended\"],\n    \"plugins\": [\"prettier\"],\n    \"parserOptions\""
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 325,
    "preview": "中文用户请注意:请尽量用**英文**描述你的 issue,这样能够让尽可能多的人帮到你。\n\nIf you want to report a bug, please provide the following information:\n\n- "
  },
  {
    "path": ".gitignore",
    "chars": 38,
    "preview": ".idea\nnode_modules\ndemo2\nnpm-debug.log"
  },
  {
    "path": ".prettierignore",
    "chars": 14,
    "preview": "dist\ndemo\ndocs"
  },
  {
    "path": ".prettierrc",
    "chars": 127,
    "preview": "{\n    \"printWidth\": 233,\n    \"tabWidth\": 4,\n    \"singleQuote\": true,\n    \"trailingComma\": \"es5\",\n    \"arrowParens\": \"alw"
  },
  {
    "path": ".travis.yml",
    "chars": 119,
    "preview": "language: node_js\n\nnode_js:\n  - lts/*\n\nscript:\n  - npm run build\n\ncache:\n  yarn: true\n  directories:\n    - node_modules"
  },
  {
    "path": "LICENSE",
    "chars": 1121,
    "preview": "The MIT License (MIT)\n\nCopyright (c) DIYgod <diy.d.god@gmail.com> (https://www.anotherhome.net/)\n\nPermission is hereby g"
  },
  {
    "path": "README.md",
    "chars": 5362,
    "preview": "<p align=\"center\">\n<img src=\"https://i.imgur.com/LnPvZvO.png\" alt=\"ADPlayer\" width=\"100\">\n</p>\n<h1 align=\"center\">APlaye"
  },
  {
    "path": "demo/demo.js",
    "chars": 7021,
    "preview": "const ap1 = new APlayer({\n    element: document.getElementById('player1'),\n    mini: false,\n    autoplay: false,\n    lrc"
  },
  {
    "path": "demo/index.html",
    "chars": 3865,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>APlayer Demo</title>\n    <meta name=\"viewport\" conte"
  },
  {
    "path": "docs/.nojekyll",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "docs/CNAME",
    "chars": 14,
    "preview": "aplayer.js.org"
  },
  {
    "path": "docs/README.md",
    "chars": 13559,
    "preview": "---\nsearch: english\n---\n\n# APlayer\n\n🍭 Wow, such a beautiful HTML5 music player\n\n## Special Sponsors\n\n<a href=\"https://ww"
  },
  {
    "path": "docs/config.js",
    "chars": 12924,
    "preview": "const langs = [\n    { title: 'English', path: '/home', matchPath: /^\\/(home|ecosystem|support)/ },\n    { title: '简体中文', "
  },
  {
    "path": "docs/ecosystem.md",
    "chars": 2270,
    "preview": "---\nsearch: english\n---\n\n# Ecosystem\n\nLet's make APlayer better, feel free to submit yours in [`Let me know!`](https://g"
  },
  {
    "path": "docs/index.html",
    "chars": 1850,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <meta"
  },
  {
    "path": "docs/landing.html",
    "chars": 1806,
    "preview": "<h1>APlayer</h1>\n\n<h3>🍭 Wow, such a beautiful HTML5 music player.</h3>\n\n<div class=\"aplayer-wrap\">\n    <div id=\"aplayer1"
  },
  {
    "path": "docs/support.md",
    "chars": 1615,
    "preview": "---\nsearch: english\n---\n\n# Sponsor APlayer Development\n\nAPlayer is an MIT licensed open source project and completely fr"
  },
  {
    "path": "docs/zh-Hans/README.md",
    "chars": 11657,
    "preview": "---\nnav: zh-Hans\nsearch: zh-Hans\n---\n\n# APlayer\n\n🍭 Wow, such a beautiful HTML5 music player\n\n## 特别赞助商\n\n<a href=\"https://"
  },
  {
    "path": "docs/zh-Hans/ecosystem.md",
    "chars": 2059,
    "preview": "---\nnav: zh-Hans\nsearch: zh-Hans\n---\n\n# 生态\n\n让 APlayer 变得更好,请随意在 [`Let me know!`](https://github.com/MoePlayer/APlayer/is"
  },
  {
    "path": "docs/zh-Hans/support.md",
    "chars": 999,
    "preview": "---\nnav: zh-Hans\nsearch: zh-Hans\n---\n\n# 赞助 APlayer 的研发\n\nAPlayer 是采用 MIT 许可的开源项目,使用完全免费。 但是随着项目规模的增长,也需要有相应的资金支持才能持续项目的维护"
  },
  {
    "path": "package.json",
    "chars": 2779,
    "preview": "{\n    \"name\": \"aplayer\",\n    \"version\": \"1.10.1\",\n    \"description\": \"Wow, such a beautiful html5 music player\",\n    \"ma"
  },
  {
    "path": "src/css/index.scss",
    "chars": 17487,
    "preview": "$aplayer-height: 66px;\n$lrc-height: 30px;\n$aplayer-height-lrc: $aplayer-height + $lrc-height - 6;\n\n.aplayer {\n    backgr"
  },
  {
    "path": "src/js/bar.js",
    "chars": 789,
    "preview": "class Bar {\n    constructor(template) {\n        this.elements = {};\n        this.elements.volume = template.volume;\n    "
  },
  {
    "path": "src/js/controller.js",
    "chars": 7013,
    "preview": "import utils from './utils';\nimport Icons from './icons';\n\nclass Controller {\n    constructor(player) {\n        this.pla"
  },
  {
    "path": "src/js/events.js",
    "chars": 1619,
    "preview": "class Events {\n    constructor() {\n        this.events = {};\n\n        this.audioEvents = [\n            'abort',\n        "
  },
  {
    "path": "src/js/icons.js",
    "chars": 1040,
    "preview": "import play from '../assets/play.svg';\nimport pause from '../assets/pause.svg';\nimport volumeUp from '../assets/volume-u"
  },
  {
    "path": "src/js/index.js",
    "chars": 318,
    "preview": "import '../css/index.scss';\nimport APlayer from './player';\n\n/* global APLAYER_VERSION GIT_HASH */\nconsole.log(`${'\\n'} "
  },
  {
    "path": "src/js/list.js",
    "chars": 6922,
    "preview": "import tplListItem from '../template/list-item.art';\nimport utils from './utils';\nimport smoothScroll from 'smoothscroll"
  },
  {
    "path": "src/js/lrc.js",
    "chars": 5390,
    "preview": "import tplLrc from '../template/lrc.art';\n\nclass Lrc {\n    constructor(options) {\n        this.container = options.conta"
  },
  {
    "path": "src/js/options.js",
    "chars": 1448,
    "preview": "export default (options) => {\n    // default options\n    const defaultOption = {\n        container: options.element || d"
  },
  {
    "path": "src/js/player.js",
    "chars": 15744,
    "preview": "import Promise from 'promise-polyfill';\n\nimport utils from './utils';\nimport Icons from './icons';\nimport handleOption f"
  },
  {
    "path": "src/js/storage.js",
    "chars": 550,
    "preview": "import utils from './utils';\n\nclass Storage {\n    constructor(player) {\n        this.storageName = player.options.storag"
  },
  {
    "path": "src/js/template.js",
    "chars": 3005,
    "preview": "import Icons from './icons';\nimport tplPlayer from '../template/player.art';\n\nclass Template {\n    constructor(options) "
  },
  {
    "path": "src/js/timer.js",
    "chars": 1989,
    "preview": "class Timer {\n    constructor(player) {\n        this.player = player;\n\n        window.requestAnimationFrame = (() =>\n   "
  },
  {
    "path": "src/js/utils.js",
    "chars": 1548,
    "preview": "const isMobile = /mobile/i.test(window.navigator.userAgent);\n\nconst utils = {\n    /**\n     * Parse second to time string"
  },
  {
    "path": "src/template/list-item.art",
    "chars": 325,
    "preview": "{{each audio}}\n<li>\n    <span class=\"aplayer-list-cur\" style=\"background-color: {{ $value.theme || theme }};\"></span>\n  "
  },
  {
    "path": "src/template/lrc.art",
    "chars": 108,
    "preview": "{{each lyrics}}\n    <p{{ if $index === 0 }} class=\"aplayer-lrc-current\"{{ /if }}>{{$value[1]}}</p>\n{{/each}}"
  },
  {
    "path": "src/template/player.art",
    "chars": 7175,
    "preview": "{{ if !options.fixed }}\n<div class=\"aplayer-body\">\n    <div class=\"aplayer-pic\" style=\"{{ if cover  }}background-image: "
  },
  {
    "path": "tea.yaml",
    "chars": 126,
    "preview": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n  - '0x185bfcef7b37010e2511309048a130f477f54fBf'\nquor"
  },
  {
    "path": "webpack/dev.config.js",
    "chars": 2903,
    "preview": "const path = require('path');\nconst webpack = require('webpack');\nconst GitRevisionPlugin = require('git-revision-webpac"
  },
  {
    "path": "webpack/prod.config.js",
    "chars": 2586,
    "preview": "const path = require('path');\nconst webpack = require('webpack');\nconst GitRevisionPlugin = require('git-revision-webpac"
  }
]

About this extraction

This page contains the full source code of the DIYgod/APlayer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 43 files (145.7 KB), approximately 39.8k tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!