Full Code of Copay/cPlayer for AI

next c145cf977550 cached
31 files
136.5 KB
45.2k tokens
123 symbols
1 requests
Download .txt
Repository: Copay/cPlayer
Branch: next
Commit: c145cf977550
Files: 31
Total size: 136.5 KB

Directory structure:
gitextract_qf7drz_x/

├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── index.html
├── package.json
├── postcss.config.js
├── readme-zh-CN.md
├── readme.md
├── src/
│   ├── cplayer.html
│   ├── example.html
│   ├── example.ts
│   ├── lib/
│   │   ├── helper/
│   │   │   ├── parseHTML.ts
│   │   │   ├── returntypeof.ts
│   │   │   └── shallowEqual.ts
│   │   ├── index.ts
│   │   ├── interfaces.ts
│   │   ├── lyric.ts
│   │   ├── mediaSession.ts
│   │   ├── playmode/
│   │   │   ├── listloop.ts
│   │   │   ├── listrandom.ts
│   │   │   └── singlecycle.ts
│   │   ├── polyfill.js
│   │   └── view.ts
│   ├── manifest.json
│   ├── neko.css
│   └── scss/
│       └── cplayer.scss
├── tsconfig.json
├── webpack.config.example.js
├── webpack.config.js
└── webpack.config.prod.js

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

================================================
FILE: .babelrc
================================================
  {
    "presets": [
      "@babel/env"
    ]
  }

================================================
FILE: .gitignore
================================================
/node_modules
yarn*
.DS_Store
/example
/dist
/lib

================================================
FILE: .npmignore
================================================
example/

src/example/

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

Copyright (c) 2016 Corps

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: index.html
================================================
<script src='./dist/cplayer.js'></script>

================================================
FILE: package.json
================================================
{
  "name": "cplayer",
  "version": "3.2.2",
  "description": "A beautiful and clean WEB Music Player by HTML5.",
  "main": "./dist/cplayer.js",
  "scripts": {
    "clean": "rimraf ./dist && rimraf ./lib && rimraf ./example",
    "dev": "webpack-dev-server --inline --progress",
    "build": "npm run clean && tsc && webpack --config webpack.config.example.js && webpack --config webpack.config.prod.js && npm run build:noview",
    "build:noview": "cross-env noview=true suffix=\"-noview\" webpack --config webpack.config.prod.js"
  },
  "keywords": [
    "cPlayer",
    "html5",
    "audio",
    "music",
    "player",
    "es6",
    "cplayer"
  ],
  "browserslist": [
    "> 1%",
    "Last 2 versions",
    "IE 10"
  ],
  "maintainers": [
    {
      "name": "Corps",
      "email": "zsq01@live.com",
      "web": "https://corps.set-fire.com"
    }
  ],
  "contributors": [
    {
      "name": "Corps",
      "email": "zsq01@live.com",
      "web": "https://corps.set-fire.com"
    }
  ],
  "bugs": "zsq01@live.com",
  "license": "MIT",
  "repository": "https://github.com/MoePlayer/cPlayer",
  "devDependencies": {
    "@babel/core": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.8.3",
    "@babel/preset-env": "7.8.3",
    "@types/node": "^13.1.6",
    "@types/wicg-mediasession": "^1.0.3",
    "@types/highlight.js": "^9.12.3",
    "highlight.js": "^9.17.1",
    "autoprefixer": "^9.7.3",
    "babel-loader": "^8.0.6",
    "copy-webpack-plugin": "^5.1.1",
    "cross-env": "^6.0.3",
    "css-loader": "^3.4.2",
    "file-loader": "^5.0.2",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "node-sass": "^4.13.0",
    "postcss": "^7.0.26",
    "postcss-loader": "^3.0.0",
    "raw-loader": "^4.0.0",
    "rimraf": "^3.0.0",
    "sass-loader": "^8.0.2",
    "style-loader": "^1.1.2",
    "ts-loader": "^6.2.1",
    "typescript": "^3.7.4",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.5",
    "webpack-bundle-analyzer": "^3.6.0",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.10.1"
  },
  "typings": "./lib/index.d.ts",
  "dependencies": {}
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

================================================
FILE: readme-zh-CN.md
================================================
# cPlayer

[![](https://badge.fury.io/js/cplayer.svg)](https://www.npmjs.com/package/cplayer) [![GitHub stars](https://img.shields.io/github/stars/MoePlayer/cPlayer.svg?style=social&label=Star&style=plastic)](https://github.com/MoePlayer/cPlayer) ![](http://img.badgesize.io/https://raw.githubusercontent.com/MoePlayer/cPlayer/master/dist/cplayer.js?compression=gzip)

![](./previews.png)

A beautiful and clean WEB Music Player by HTML5. [demo here](http://cplayer.js.org/).

# Feature

* 歌词显示
* 多歌曲播放列表
* 三种播放模式 单曲循环 列表循环 随机播放
* 专为触控优化的界面
* 模块化 定制性
* [Media Session 支持](https://developers.google.com/web/updates/2017/02/media-session)

# Quick Start

``` html
<div id="app"></div>
<!-- 加载 cplayer 脚本 -->
<script src=".../cplayer.js"></script>
<script>
  let player = new cplayer({
    element: document.getElementById('app'),
    playlist: [
      {
        src: '歌曲资源链接...',
        poster: '封面链接...',
        name: '歌曲名称...',
        artist: '歌手名称...',
        lyric: '歌词...',
        sublyric: '副歌词,一般为翻译...',
        album: '专辑,唱片...'
      },
      {
        ...
      },
      ......
    ]
  })
</script>
```

## webpack

```
npm install cplayer --save
```

```
import cplayer from 'cplayer';

new cplayer({
  ...
})
```

# 相关项目

[hexo-tag-cplayer](https://github.com/EYHN/hexo-tag-cplayer)

# Option

|OPTION|default content|description|
|:-----|:-------------:|:----------|
|element|`document.body`|注入播放器的目标元素。|
|playlist|`[]`|播放列表。|
|zoomOutKana|`false`|日语优化,缩小显示歌词中的假名。|
|playmode|`listloop`|默认播放模式。|
|volume|`1`|默认音量|
|point|`0`|开始播放的歌曲索引。|
|showPlaylist|`false`|显示播放列表,而不是当前歌曲信息。|
|autoplay|`false`|自动播放(移动端不可用)。|
|width|`''`|播放器宽度。|
|size|`12px`|播放器尺寸。|
|style|`''`|附加的css样式。|
|shadowDom|`'true'`|启用 [shadow DOM](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM)。|
|showPlaylistButton|`'true'`|显示播放列表按钮|
|dropDownMenuMode|`'bottom'`|菜单(播放列表和歌曲信息)的显示模式, ‘bottom’ 底部、 'top' 顶部、 'none' 不显示|

# Apis

- `cplayer.mode` 播放模式 目前支持3种播放模式。
  - `listloop` 列表循环
  - `singlecycle` 单曲循环
  - `listrandom` 列表随机播放
  ```
  cplayer.mode //获取当前播放模式

  cplayer.mode = 'listloop' //设置当前播放模式为列表循环
  ```
- `cplayer.volume` 音量 0.0 ~ 1.0。
- `cplayer.playlist` 只读 获取当前播放列表。
- `cplayer.nowplay` 只读 获取当前正在播放的歌曲。
- `cplayer.nowplaypoint` 只读 获取当前正在播放的歌曲在播放列表中的索引。
- `cplayer.played` 只读 是否正在播放。
- `cplayer.paused` 只读 是否已经暂停。
- `cplayer.toggleMode()` 切换播放模式 按 listloop > singlecycle > listrandom 的顺序
- `cplayer.setMode(playmode: string)` 设置播放模式与修改 `cplayer.mode` 等效。
- `cplayer.getMode()` 获取播放模式与获取 `cplayer.mode` 等效。
- `cplayer.play()` 开始播放
- `cplayer.pause()` 暂停播放
- `cplayer.to(id: number)` 跳转到指定曲目 id:歌曲的索引
- `cplayer.next()` 下一首
- `cplayer.prev()` 上一首
- `cplayer.togglePlayState()` 切换播放状态,暂停 > 播放,播放 > 暂停。
- `cplayer.add(item: IAudioItem)` 添加歌曲。
- `cplayer.remove(item: IAudioItem)` 删除歌曲。
- `cplayer.setVolume()` 设置音量与修改 `cplayer.volume` 等效。
- `cplayer.destroy()` 销毁播放器。
- `cplayer.view.getRootElement()` 获取 `<c-player />` 元素。
- `cplayer.view.showInfo()` 关闭播放列表,显示当前歌曲信息。
- `cplayer.view.showPlaylist()` 显示播放列表。
- `cplayer.view.toggleDropDownMenu()` 切换播放列表,关闭 > 打开,打开 > 关闭。

### Event

- `started`: 每首歌开始时触发,此时已经开始播放。
- `ended`: 歌曲播放到末尾时触发
- `play`: 开始播放时触发
- `pause`: 暂停播放时触发

> `play 事件` 和 `pause 事件` 必定交替触发。
> 需要注意的是上一首歌结束自动切换到下一首时不会触发 `play 事件`, 但会触发 `started 事件` 和 `openaudio 事件`。

- `playmodechange`: `play 事件` 和 `pause 事件` 的结合体
- `openaudio`: 打开音频时触发,但此时还不一定有音频数据。
- `volumechange`: 音量被改变时触发
- `timeupdate`: 更新播放时间

## 常见问题

<details><summary>如何播放网易云上的音乐?</summary><br>


### 在 `cplayer.js` 之后执行以下脚本

``` javascript
cplayer.prototype.add163 = function add163(id) {
  if (!id) throw new Error("Unable Property.");
  return fetch("https://music.huaji8.top/?id=" + id).then(function(res){return res.json()}).then(function(data){
    let obj = {
      name: data.info.songs[0].name,
      artist: data.info.songs[0].ar.map(function(ar){ return ar.name }).join(','),
      poster: data.pic.url,
      lyric: data.lyric.lyric,
      sublyric: data.lyric.tlyric,
      src: data.url.url,
      album: data.info.songs[0].al.name
    }
    this.add(obj);
    return obj;
  }.bind(this))
}
```

### 使用:

``` javascript
player.add163(12345678) //加入网易云id为 12345678 的歌曲
```

</details>


<details><summary>我只需要一个封装好的 audio api,不想要 UI ?</summary><br>

`dist` 文件夹中有 `cplayer-noview.js` 是去 UI 版的 cplayer。

</details>

================================================
FILE: readme.md
================================================
# cPlayer

[![](https://badge.fury.io/js/cplayer.svg)](https://www.npmjs.com/package/cplayer) [![GitHub stars](https://img.shields.io/github/stars/MoePlayer/cPlayer.svg?style=social&label=Star&style=plastic)](https://github.com/MoePlayer/cPlayer) ![](http://img.badgesize.io/https://raw.githubusercontent.com/MoePlayer/cPlayer/master/dist/cplayer.js?compression=gzip)

![](./previews.png)

A beautiful and clean WEB Music Player by HTML5. [demo here](http://cplayer.js.org/).

# Feature

* Lyrics display
* Playlists
* Three play modes, Single cycle, list loop, random play
* Designed for touch devices
* Modular Customizable
* [Media Session Support](https://developers.google.com/web/updates/2017/02/media-session)

# Quick Start

``` html
<div id="app"></div>
<!-- 加载 cplayer 脚本 -->
<script src=".../cplayer.js"></script>
<script>
  let player = new cplayer({
    element: document.getElementById('app'),
    playlist: [
      {
        src: '歌曲资源链接...',
        poster: '封面链接...',
        name: '歌曲名称...',
        artist: '歌手名称...',
        lyric: '歌词...',
        sublyric: '副歌词,一般为翻译...',
        album: '专辑,唱片...'
      },
      {
        ...
      },
      ......
    ]
  })
</script>
```

## webpack

```
npm install cplayer --save
```

```
import cplayer from 'cplayer';

new cplayer({
  ...
})
```

# 相关项目

- [hexo-tag-cplayer](https://github.com/EYHN/hexo-tag-cplayer)
- [vue-player](https://github.com/adnasa/vue-cplayer)
- [react-cplayer](https://github.com/Fallenhh/react-cplayer)

# Option

|OPTION|default content|description|
|:-----|:-------------:|:----------|
|element|`document.body`|注入播放器的目标元素。|
|playlist|`[]`|播放列表。|
|zoomOutKana|`false`|日语优化,缩小显示歌词中的假名。|
|playmode|`listloop`|默认播放模式。|
|volume|`1`|默认音量|
|point|`0`|开始播放的歌曲索引。|
|showPlaylist|`false`|显示播放列表,而不是当前歌曲信息。|
|autoplay|`false`|自动播放(移动端不可用)。|
|width|`''`|播放器宽度。|
|size|`12px`|播放器尺寸。|
|style|`''`|附加的css样式。|
|shadowDom|`'true'`|启用 [shadow DOM](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM)。|
|showPlaylistButton|`'true'`|显示播放列表按钮|
|dropDownMenuMode|`'bottom'`|菜单(播放列表和歌曲信息)的显示模式, ‘bottom’ 底部、 'top' 顶部、 'none' 不显示|

# Apis

- `cplayer.mode` 播放模式 目前支持3种播放模式。
  - `listloop` 列表循环
  - `singlecycle` 单曲循环
  - `listrandom` 列表随机播放
  ```
  cplayer.mode //获取当前播放模式

  cplayer.mode = 'listloop' //设置当前播放模式为列表循环
  ```
- `cplayer.volume` 音量 0.0 ~ 1.0。
- `cplayer.playlist` 只读 获取当前播放列表。
- `cplayer.nowplay` 只读 获取当前正在播放的歌曲。
- `cplayer.nowplaypoint` 只读 获取当前正在播放的歌曲在播放列表中的索引。
- `cplayer.played` 只读 是否正在播放。
- `cplayer.paused` 只读 是否已经暂停。
- `cplayer.toggleMode()` 切换播放模式 按 listloop > singlecycle > listrandom 的顺序
- `cplayer.setMode(playmode: string)` 设置播放模式与修改 `cplayer.mode` 等效。
- `cplayer.getMode()` 获取播放模式与获取 `cplayer.mode` 等效。
- `cplayer.play()` 开始播放
- `cplayer.pause()` 暂停播放
- `cplayer.to(id: number)` 跳转到指定曲目 id:歌曲的索引
- `cplayer.next()` 下一首
- `cplayer.prev()` 上一首
- `cplayer.togglePlayState()` 切换播放状态,暂停 > 播放,播放 > 暂停。
- `cplayer.add(item: IAudioItem)` 添加歌曲。
- `cplayer.remove(item: IAudioItem)` 删除歌曲。
- `cplayer.setVolume()` 设置音量与修改 `cplayer.volume` 等效。
- `cplayer.destroy()` 销毁播放器。
- `cplayer.view.getRootElement()` 获取 `<c-player />` 元素。
- `cplayer.view.showInfo()` 关闭播放列表,显示当前歌曲信息。
- `cplayer.view.showPlaylist()` 显示播放列表。
- `cplayer.view.toggleDropDownMenu()` 切换播放列表,关闭 > 打开,打开 > 关闭。

### Event

- `started`: 每首歌开始时触发,此时已经开始播放。
- `ended`: 歌曲播放到末尾时触发
- `play`: 开始播放时触发
- `pause`: 暂停播放时触发

> `play 事件` 和 `pause 事件` 必定交替触发。
> 需要注意的是上一首歌结束自动切换到下一首时不会触发 `play 事件`, 但会触发 `started 事件` 和 `openaudio 事件`。

- `playmodechange`: `play 事件` 和 `pause 事件` 的结合体
- `openaudio`: 打开音频时触发,但此时还不一定有音频数据。
- `volumechange`: 音量被改变时触发
- `timeupdate`: 更新播放时间

## 常见问题

<details><summary>如何播放网易云上的音乐?</summary><br>


### 在 `cplayer.js` 之后执行以下脚本

``` javascript
cplayer.prototype.add163 = function add163(id) {
  if (!id) throw new Error("Unable Property.");
  return fetch("https://music.huaji8.top/?id=" + id).then(function(res){return res.json()}).then(function(data){
    let obj = {
      name: data.info.songs[0].name,
      artist: data.info.songs[0].ar.map(function(ar){ return ar.name }).join(','),
      poster: data.pic.url,
      lyric: data.lyric.lyric,
      sublyric: data.lyric.tlyric,
      src: data.url.url,
      album: data.info.songs[0].al.name
    }
    this.add(obj);
    return obj;
  }.bind(this))
}
```

### 使用:

``` javascript
player.add163(12345678) //加入网易云id为 12345678 的歌曲
```

</details>


<details><summary>我只需要一个封装好的 audio api,不想要 UI ?</summary><br>

`dist` 文件夹中有 `cplayer-noview.js` 是去 UI 版的 cplayer。

</details>


================================================
FILE: src/cplayer.html
================================================
<c-player loaded>
  <div class="cp-mainbody">
    <div class="cp-poster">

    </div>
    <div class="cp-center-container">
      <div class="cp-controls">
        <a class="cp-prev-button">
          <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-prev-icon" viewBox="0 0 1024 1024"
            version="1.1">
            <path d="M943.705 11.8c10.321 5.183 17.325 15.601 17.534 27.675l-0.004 938.318c-0.167 12.186-7.229 22.684-17.457 27.782-4.857 2.548-10.527 4.026-16.543 4.026a35.75 35.75 0 0 1-18.217-4.955l-716.617-469c-9.689-5.299-16.151-15.421-16.151-27.053 0-11.63 6.462-21.753 15.991-26.972L909.186 12.666c5.177-3.048 11.404-4.848 18.052-4.848a35.878 35.878 0 0 1 16.665 4.077z"
            />
            <path d="M228.435 77.809v868.712c-3.889 42.573-39.416 75.664-82.673 75.664s-78.784-33.091-82.649-75.34l-0.024-869.036C65.9 34.259 101.911 0 145.924 0s80.024 34.259 82.822 77.564z"
            />
          </svg>
        </a>
        <a class="cp-play-button">
          <span class="cp-play-icon cp-play-icon-paused">
            <div class="cp-play-icon-left"></div>
            <div class="cp-play-icon-right"></div>
            <div class="cp-play-icon-triangle-1"></div>
            <div class="cp-play-icon-triangle-2"></div>
          </span>
        </a>
        <a class="cp-next-button">
          <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-next-icon" viewBox="0 0 1024 1024"
            version="1.1">
            <path d="M943.705 11.8c10.321 5.183 17.325 15.601 17.534 27.675l-0.004 938.318c-0.167 12.186-7.229 22.684-17.457 27.782-4.857 2.548-10.527 4.026-16.543 4.026a35.75 35.75 0 0 1-18.217-4.955l-716.617-469c-9.689-5.299-16.151-15.421-16.151-27.053 0-11.63 6.462-21.753 15.991-26.972L909.186 12.666c5.177-3.048 11.404-4.848 18.052-4.848a35.878 35.878 0 0 1 16.665 4.077z"
            />
            <path d="M228.435 77.809v868.712c-3.889 42.573-39.416 75.664-82.673 75.664s-78.784-33.091-82.649-75.34l-0.024-869.036C65.9 34.259 101.911 0 145.924 0s80.024 34.259 82.822 77.564z"
            />
          </svg>
        </a>
      </div>

      <div class="cp-lyric">
        <span class="cp-lyric-text"></span>
      </div>
    </div>
    <a class="cp-volume-button">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-volume-icon cp-icon-dark" viewBox="0 200 1024 650"
        version="1.1">
        <path d="M607.869008 364.026176l-49.340512 49.340528c25.412064 25.160448 41.150352 60.055392 41.150352 98.637648 0 38.573568-15.738288 73.477184-41.150352 98.62896l49.340512 49.340512c37.836112-37.888176 61.244-90.195872 61.244-147.969488 0-57.782272-23.407888-110.081328-61.244-147.978176z"
        />
        <path d="M807.937968 512c0-96.086912-39.050752-183.055296-102.134064-245.904368L656.95792 314.9416c50.398992 50.45104 81.563248 120.1108 81.563248 197.0584 0 76.938912-31.172944 146.598672-81.563248 197.0584l48.845984 48.845968C768.887216 695.055296 807.937968 608.086896 807.937968 512z"
        />
        <path d="M946.780288 512.004336c0-134.434896-54.598176-256.107376-142.807248-344.082192l-49.088912 49.088896c75.65488 75.411952 122.470672 179.732384 122.470672 294.993296 0 115.252224-46.815792 219.581328-122.470672 294.984608l49.088912 49.088912c88.209072-87.966144 142.807248-209.647312 142.807248-344.07352z"
        />
        <path d="M247.605111 659.304938 458.566804 854.551527 458.566804 169.448479 251.58222 364.704275 53.490893 364.704275 53.490893 659.304938Z"
        />
      </svg>
      <div class="cp-volume-container">
        <div>
          <span class="cp-volume-controller">
            <span class="cp-volume-fill"></span>
            <span class="cp-volume-controller-button"></span>
          </span>
        </div>
      </div>
    </a>
    <a class="cp-list-button">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-list-icon cp-icon-dark" viewBox="0 0 1024 1024"
        version="1.1">
        <path d="M168.57 820.6c0 14.34-9.56 23.89-23.89 23.89H96.89c-14.34 0-23.89-9.55-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89h47.79c14.34 0 23.89 9.56 23.89 23.89z m0-284.33c0 14.34-9.56 23.89-23.89 23.89H96.89c-14.34 0-23.89-9.56-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89h47.79c14.34 0 23.89 9.56 23.89 23.89z m0-286.72c0 14.34-9.56 23.89-23.89 23.89H96.89c-14.34 0-23.89-9.56-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89h47.79c14.34 0 23.89 9.56 23.89 23.89zM969 820.6c0 14.34-7.17 23.89-21.5 23.89H314.32c-14.34 0-23.89-9.56-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89H945.1c14.34 0 23.89 9.56 23.89 23.89z m0-284.33c0 14.34-7.17 23.89-21.5 23.89H314.32c-14.34 0-23.89-9.56-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89H945.1c14.34 0 23.89 9.56 23.89 23.89z m0-286.72c0 14.34-7.17 23.89-21.5 23.89H314.32c-14.34 0-23.89-9.56-23.89-23.89v-47.79c0-14.34 9.56-23.89 23.89-23.89H945.1c14.34 0 23.89 9.56 23.89 23.89z"
        />
      </svg>
    </a>
    <a class="cp-mode-button" data-mode="listloop">
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-loop-icon cp-icon-dark" viewBox="0 0 1024 1024"
        version="1.1">
        <path d="M157 624c17.3-4.8 27.4-22.7 22.6-40-5.4-19.4-8.1-39.5-8.1-59.9 0-123.1 100.1-223.2 223.2-223.2h302.4v57.6c0 18.5 12.9 25.6 28.6 15.9l138.6-85.8c15.7-9.7 15.9-25.9 0.3-35.9l-139.2-89.9c-15.5-10-28.2-3.1-28.2 15.4V236H394.7c-77 0-149.3 30-203.8 84.4-54.4 54.4-84.4 126.8-84.4 203.8 0 26.2 3.5 52.2 10.5 77.3 4 14.4 17.1 23.8 31.3 23.8 2.9-0.1 5.8-0.4 8.7-1.3z m748.7-202.1c-4-14.4-17.1-23.8-31.3-23.8-2.9 0-5.8 0.4-8.7 1.2-17.3 4.8-27.4 22.7-22.6 40 5.4 19.4 8.1 39.5 8.1 59.9 0 123.1-100.1 223.2-223.2 223.2H325.5v-57.6c0-18.5-12.9-25.6-28.6-15.9l-138.6 85.8c-15.7 9.7-15.9 25.9-0.3 35.9l139.2 89.9c15.5 10 28.2 3.1 28.2-15.4v-57.9h302.4c77 0 149.3-30 203.8-84.4C886 648.4 916 576 916 499c0.1-26-3.4-52-10.3-77.1z"
        />
      </svg>
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-single-icon cp-icon-dark" viewBox="0 0 1024 1024"
        version="1.1">
        <path d="M102.5 653.9c17.3-4.8 27.4-22.7 22.6-40-5.4-19.4-8.1-43.4-8.1-63.8C117 427 217.1 323 340.2 323h132.3c1.4-20 5.4-40 11.7-60h-144c-77 0-149.4 31.4-203.8 85.8C82 403.2 52 476.3 52 553.3c0 26.2 3.5 52.6 10.5 77.6 4 14.4 17.1 24 31.3 24 2.9 0 5.8-0.2 8.7-1z m471 109.1H272v-68.3c0-18.5-13.3-25.6-29-15.9l-138.8 85.8c-15.7 9.7-16 25.9-0.5 35.9l139.7 89.9c15.5 10 28.7 3.1 28.7-15.4v-52h301.5c77 0 149.4-32.9 203.8-87.3 34.7-34.7 59.5-78.2 72.9-124-24 9-49.6 19.4-76.2 21.5-36.5 74-112.7 129.8-200.6 129.8zM752 127.9c-121.5 0-220 98.5-220 220s98.5 220 220 220 220-98.5 220-220-98.5-220-220-220zM792 463h-40V298.7c-20 14-36.2 24.5-60 31.3v-41.8c11.5-2.9 23.8-10.4 36.7-17.6 13-7.9 23.8-7.7 32.4-27.7H792V463z"
        />
      </svg>
      <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="cp-random-icon cp-icon-dark" viewBox="0 0 1024 1024"
        version="1.1">
        <path d="M112 405.2h218.2c7.1 0 21 5.8 26 10.8l38 38.6c13.6 13.8 35.7 13.9 49.5 0.4 13.8-13.6 13.9-35.7 0.4-49.5l-38-38.6c-18.1-18.4-50-31.7-75.8-31.7H112c-19.3 0-35 15.7-35 35 0 19.4 15.7 35 35 35z m623.2 0h62.4v42.6c0 10.9 7.5 15 16.7 8.8l125.8-83.9c9.3-6.2 9.2-16.2 0-22.4l-125.8-83.9c-9.3-6.2-16.7-2.2-16.7 8.8v60h-62.4c-53.5 0-123.5 29.3-161 67.4L393.9 585.7c-24.3 24.7-76.4 46.5-111.1 46.5H112c-19.3 0-35 15.7-35 35s15.7 35 35 35h170.7c53.5 0 123.5-29.3 161-67.4L624 451.7c24.4-24.7 76.4-46.5 111.2-46.5z m79.1 161.2c-9.3-6.2-16.7-2.2-16.7 8.8v57H681.9c-6.9 0-20.2-5.6-25-10.4l-37.3-37.9c-13.6-13.8-35.7-13.9-49.5-0.4-13.8 13.6-13.9 35.7-0.4 49.5l37.3 37.9c18 18.2 49.3 31.3 74.9 31.3h115.6v45.6c0 10.9 7.5 15 16.7 8.8L940 672.7c9.3-6.2 9.2-16.2 0-22.4l-125.7-83.9z"
        />
      </svg>
    </a>
    <div class="cp-progress-container">
      <span class="cp-progress-current-time">07:24</span>
      <div class="cp-progress">
        <div class="cp-progress-fill"></div>
        <span class="cp-progress-button"></span>
      </div>
      <span class="cp-progress-duration">24:50</span>
    </div>
  </div>
  <div class="cp-drop-down-menu cp-drop-down-menu-info">
    <div class="cp-audio-info">
      <span class="cp-audio-title"></span> - <span class="cp-audio-artist"></span>
    </div>
    <ul class="cp-playlist">
    </ul>
  </div>
</c-player>

================================================
FILE: src/example.html
================================================
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8" />
    <title>CPlayer</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="manifest" href="/manifest.json">
    <meta name="theme-color" content="#f8f8f8" />
</head>

<body>
    <div class="neko-container neko-no-gutters">
        <div class="center">
            <h1 class="title">cPlayer</h1>
            <div>
                <div class="neko-m-h-2 neko-m-v-2" style="display: inline-block" >
                    <h3>默认样式</h3>
                    <div id="app1"></div>
                </div>
                <div class="neko-m-h-2 neko-m-v-2" style="display: inline-block">
                    <h3>暗黑样式</h3>
                    <div id="app2"></div>
                </div>
            </div>
            <div>
                <div class="neko-m-h-2 neko-m-v-2" style="display: inline-block">
                    <h3>大图样式</h3>
                    <div id="app3"></div>
                </div>
                <div class="neko-m-h-2 neko-m-v-2" style="display: inline-block">
                    <h3>大图暗黑样式</h3>
                    <div id="app4"></div>
                </div>
            </div>
        </div>
        <div class="neko-m-v-2 btn-line center">
            <button class="neko-btn outline neko-color-blue" id="add163">从网易云添加音乐</button>
            <button class="neko-btn outline neko-color-blue" id="openplaylist">打开播放列表</button>
            <button class="neko-btn outline neko-color-blue" id="closeplaylist">关闭播放列表</button>
            <button class="neko-btn outline neko-color-blue" id="remove">删除一首歌曲</button>
            <a href="https://github.com/MoePlayer/cPlayer/blob/next/src/example.ts" rel="noopener" target="_blank"><button class="neko-btn outline neko-color-blue">查看此页面源代码</button></a>
        </div>
        <div class="neko-m-v-2 center">
            <h1>Getting Started</h1>
            <pre class="getstart neko-helper-center"><code class="html">&lt;div id="app"&gt;&lt;/div&gt;
&lt;!-- 加载 cplayer 脚本 --&gt;
&lt;script src="https://cdn.jsdelivr.net/gh/MoePlayer/cPlayer/dist/cplayer.js"&gt;&lt;/script&gt;
&lt;script&gt;
  let player = new cplayer({
    element: document.getElementById('app'),
    playlist: [
      {
        src: '歌曲资源链接...',
        poster: '封面链接...',
        name: '歌曲名称...',
        artist: '歌手名称...',
        lyric: '歌词...',
        sublyric: '副歌词,一般为翻译...'
      },
      {
        ...
      },
      ......
    ]
  })
&lt;/script&gt;</code></pre>
        </div>
        <div class="neko-m-v-2 btn-line center">
            <a href="https://github.com/MoePlayer/cPlayer" rel="noopener" target="_blank"><button class="neko-btn shadow neko-color-blue">更多详情..</button></a>
        </div>
    </div>
    <a href="https://github.com/MoePlayer/cPlayer" rel="noopener" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="./example/fork-me-at-github.png" alt="Fork me on GitHub"></a>
    <style>
        body {
            background-color: #f8f8f8;
            background-repeat: repeat;
            background-position: 50% 50%;
            background-size: cover;
            background-origin: padding-box;
            background-attachment: scroll;
            padding: 50px 0px;
            margin: 0px;
        }

        *,
        *::before,
        *::after {
            box-sizing: border-box;
        }

        .btn-line .neko-btn {
            margin: 10px 8px;
        }

        .center {
            text-align: center;
        }

        .title {
            font-size: 55px;
        }

        pre.getstart {
            width: 600px;
            text-align: left;
        }

        pre.getstart code {
            border-radius: 5px;
        }

        @media screen and (max-width:600px) {
            pre.getstart {
                width: auto;
                margin: 0px 20px;
            }
        }
    </style>
</body>

</html>

================================================
FILE: src/example.ts
================================================
import cplayer from "./lib";
import { IAudioItem } from "./lib/interfaces";
import cplayerView from "./lib/view";

require('./neko.css');
require('highlight.js/styles/ocean.css');

const hljs = require('highlight.js/lib/highlight');
const javascript = require('highlight.js/lib/languages/javascript');
const xml = require('highlight.js/lib/languages/xml');
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('xml', xml);

hljs.initHighlightingOnLoad();

window.addEventListener("load",
  function () {
    function from163(id: string) {
      if (!id) throw new Error("Unable Property.");
      return fetch("https://music.huaji8.top/?id=" + id).then(function (res: any) { return res.json() }).then((data) => {
        let obj = {
          name: data.info.songs[0].name,
          artist: data.info.songs[0].ar.map(function (ar: any) { return ar.name }).join(','),
          poster: data.pic.url,
          lyric: data.lyric.lyric,
          sublyric: data.lyric.tlyric,
          src: data.url.url,
          album: data.info.songs[0].al.name
        }
        return obj;
      })
    }

    let playlist: IAudioItem[] = [
      {
        src: require('./example/music-1.mp3'),
        poster: require('./example/music-1.jpg'),
        name: 'チルドレンレコード',
        artist: '96猫,伊東歌詞太郎',
        lyric: '[00:28.12]白いイヤホンを耳にあて\n[00:30.51]少しニヤッとして合図する\n[00:32.74]染み込んだこの温度が\n[00:35.07]ドアをノックした瞬間に\n[00:37.47]溢れそうになるよ\n[00:38.97]「まだ視えない?」\n[00:39.81]目を凝らして臨む争奪戦\n[00:41.96]あの日躊躇した脳裏から\n[00:44.07]「今だ、取り戻せ」と\n[00:45.66]コードが鳴り出しそう\n[00:48.61]愛しくて、辛くて\n[00:51.39]世界を嫌ったヒトの\n[00:57.71]酷く理不尽な 「構成」\n[01:00.46]肯定していちゃ未来は生み出せない\n[01:04.98]少年少女前を向く\n[01:07.26]暮れる炎天さえ希望論だって\n[01:09.70]「ツレモドセ」\n[01:10.69]「ツレモドセ」\n[01:12.00]三日月が赤く燃え上がる\n[01:14.04]さぁさぁ、コードを0で刻め\n[01:16.54]想像力の外側の世界へ\n[01:18.86]オーバーな空想戦線へ\n[01:29.87]「お先にどうぞ」って舌を出す\n[01:32.28]余裕ぶった無邪気な目\n[01:34.50]「ほら出番だ」パスワードで\n[01:36.79]目を覚ましたじゃじゃ馬は止まらない\n[01:39.25]もう夜が深くなる\n[01:41.21]「オコサマ」なら燃える延長戦\n[01:43.64]逆境ぐあいがクールだろ\n[01:45.80]寝れないねまだまだ\n[01:47.30]ほら早く!早く\n[01:48.66]イン?テンポで視線を合わせて\n[01:50.81]ハイタッチでビートが鳴り出せば\n[01:52.95]考えてちゃ遅いでしょう\n[01:55.05]ほらノっかってこうぜ\n[01:56.93]ワンコードで視線を合わせて\n[01:59.78]ぶっ飛んだグルーヴが渦巻けば\n[02:02.05]冗談じゃない見えるはず\n[02:04.02]そのハイエンドの風景の隙間に\n[02:06.75]さぁどうだい\n[02:07.76]この暑さも\n[02:08.73]すれ違いそうだった価値観も\n[02:10.93]「悪くないかな」\n[02:12.08]目を開き 手を取り合ったら\n[02:15.84]案外チープな言葉も\n[02:17.69]「合い言葉だ」って言い合える\n[02:19.97]少しだけ前を向ける\n[02:24.75]少年少女、前を向く\n[02:26.91]揺れる炎天すら希望論だって\n[02:29.26]思い出し、口に出す\n[02:31.52]不可思議な出会いと別れを\n[02:34.03]「ねぇねぇ、突飛な世界のこと\n[02:36.13]散々だって笑い飛ばせたんだ」\n[02:38.49]合図が終わる\n[02:40.71]少年少女前を向け\n[02:43.06]眩む炎天すら希望論だって\n[02:45.44]「ツカミトレ」\n[02:46.50]「ツカミトレ」と\n[02:47.71]太陽が赤く燃え上がる\n[02:49.77]さぁさぁ、コールだ。\n[02:51.34]最後にしよう\n[02:52.49]最善策はその目を見開いた\n[02:54.75]オーバーな妄想戦線\n[02:56.71]感情性のメビウスの先へ\n',
        sublyric: '[00:28.12]戴上白色耳机\n[00:30.51]稍微扬起嘴角做出信号\n[00:32.74]渗入体内的这个温度\n[00:35.07]在敲门的那一瞬间\n[00:37.47]也要满溢出来了\n[00:38.97] 「还看不见吗?」\n[00:39.81]凝视面对这场争夺战\n[00:41.96]那天在犹豫的脑袋中\n[00:44.07]「就是现在,拿回来吧」\n[00:45.66]似乎响起了这样的信号\n[00:48.61]深爱著,煎熬著\n[00:51.39]讨厌著世界的人的\n[00:57.71]残酷无道理的「构成」\n[01:00.46]如果承认了就没有未来可言\n[01:04.98]少年少女前进吧\n[01:07.26]连垂暮的炽热烈日都成了希望论\n[01:09.70]「带回来吧」\n[01:10.69]「带回来吧」\n[01:12.00]赤红新月高高燃起\n[01:14.04]来吧来吧,刻上0的记号\n[01:16.54]前往超乎想像的世界\n[01:18.86] 前往超载的空想战线\n[01:29.87]「你先吧」吐出舌头\n[01:32.28]表示还有馀裕的天真眼神\n[01:34.50]「好了登场吧」的密码\n[01:36.79]醒来的悍马停不下来\n[01:39.25]夜已深\n[01:41.21] 「小孩」进行斗志高昂延长赛\n[01:43.64]身陷逆境听起来很酷吧?\n[01:45.80]还睡不著呢 \n[01:47.30]好了快一点!快一点!\n[01:48.66]抓准节拍(in tempo)对上视线\n[01:50.81]击掌打出响亮节奏(beat)\n[01:52.95]思考的话不就太慢了吗?\n[01:55.05]好了敲响门铃吧\n[01:56.93]一个信号(one code)对上视线\n[01:59.78]飞跃的轨迹(groove)也卷起漩涡\n[02:02.05]别开玩笑了应该看得到吧\n[02:04.02]从那高级奢侈(high end)的风景缝隙间\n[02:06.75]觉得怎么样呢?\n[02:07.76]份燥热也 \n[02:08.73]貌似碰巧的价值观也\n[02:10.93]「不算太糟呐」 \n[02:12.08]睁开眼睛,相互握手之後\n[02:15.84]廉价的话语也意外能\n[02:17.69]「是暗语喔」的互相说着\n[02:19.97]能稍微地向前行\n[02:24.75]少年少女前进吧\n[02:26.91]连晃动的炽热夏日都成了希望论\n[02:29.26]回想起来,缓缓道出\n[02:31.52]那不可思议的相遇与离别\n[02:34.03]「呐呐,那飞跃的世界的事情\n[02:36.13] 虽然悲惨但就笑一笑让它过去吧」\n[02:38.49]信号终止\n[02:40.71]少年少女前进吧 \n[02:43.06]连眩目的炽热夏日都成了希望论\n[02:45.44] 「紧抓住吧」\n[02:46.50]「紧抓住吧」\n[02:47.71]赤红烈日高高燃起\n[02:49.77]来吧来吧,在呼唤我们了\n[02:51.34]努力到最後吧\n[02:52.49]上上策张开了那个眼睛\n[02:54.75]超载的妄想战线\n[02:56.71]朝向感性的梅比斯环的前方',
        album: 'アイリス'
      },
      {
        src: require('./example/music-2.mp3'),
        poster: require('./example/music-2.png'),
        name: 'ひねくれネジと雨',
        artist: 'ねこぼーろ',
        lyric: '[by:吃土少女Z]\n[00:00.00] 作曲 : ねこぼーろ\n[00:01.00] 作词 : ねこぼーろ\n[00:35.07]「ねえ 鼓膜 溶ける感覚\n[00:39.73]指の 先で 光る体温\n[00:45.13]僕は 未だ わからないよ」\n[00:50.50]\n[00:51.00]時が経てば 忘れてしまう\n[00:55.50]いつかの君も 色褪せてしまう\n[01:00.96]でも僕は 未だ、「忘れないよ」\n[01:06.78]\n[01:07.44]まわる まわる 世界は\n[01:09.71]僕の事など無視をして\n[01:12.78]何も知らずに そっと\n[01:15.10]僕の心 錆び付かせる\n[01:18.16]もう君を守るなんて言えないな\n[01:22.94]\n[01:23.44]こわれ こわれる 僕は\n[01:25.92]誰も 信じられなくなる\n[01:28.96]「誰も知らずに そっと\n[01:31.30]雨に溶けて 無くなる」とか\n[01:34.33]ああそんなふざけた事 言えないな ああ\n[01:41.10]\n[01:45.19]ああ 鼓膜 突き破る赤\n[01:49.52]頭の裏で 溶けてなくなる\n[01:54.90]そう僕はまだ 聴こえ「ないよ」\n[02:00.99]\n[02:01.35]まわる まわる 世界は\n[02:03.65]僕の事など無視をして\n[02:06.73]何も知らずに そっと\n[02:09.06]僕の鼓動 錆び付かせる\n[02:12.06]もう君を見る事無く消えたいな ああ\n[02:18.19]\n[02:31.74]\n[02:33.74]相対 曖昧な 返答でごまかし\n[02:39.09]大体 反対な 顔を作る\n[02:44.49]後悔 先に立たずだ\n[02:48.55]\n[02:49.98]―nonsense―\n[03:20.32]\n[03:22.32]まわる まわる 世界は\n[03:24.57]僕の事など無視をして\n[03:27.60]何も知らずに そっと\n[03:29.92]僕の心 錆び付かせる\n[03:32.93]「もう君を守るなんて言えないな」\n[03:37.84]\n[03:38.55]こわれ こわれる 僕は\n[03:40.78]誰も 信じられなくなる\n[03:43.77]「誰も知らずに そっと\n[03:46.11]雨に溶けて 無くなる」とか\n[03:49.20]ああそんなふざけた事言えないな\n[03:53.88]\n[03:54.64]ああ\n[03:55.81]まわる\n[03:56.33]まわる\n[03:56.96]まわるまわる\n[03:58.26]まわるまわるまわる\n[04:00.95]これで終わる落ちる目眩だ\n[04:04.67]ああ ああ ああ ああ ああ\n[04:12.14]\n',
        sublyric: '[by:Tsumugi-mio]\n[00:24.23]\n[00:35.07]呐呐 鼓膜 快要融化的感觉\n[00:39.73]指尖 前面 是那光芒的体温\n[00:45.13]现在 的我 还未曾知晓\n[00:51.00]我即将要忘记 那遥远的时光\n[00:55.50]总有一天你也 将会褪去颜色\n[01:00.96]「但是现在的我还 没有 忘记哟」\n[01:07.44]回转的 回转的 这个世界将我全然无视\n[01:09.71]就像什么都不知道一样\n[01:12.78]悄悄地\n[01:15.10]我的心生锈\n[01:18.16]「会守护你的」\n[01:23.44]这句话已经说不出口\n[01:25.92]谁也不会相信\n[01:28.96]悄悄地\n[01:31.30]像雨一样融化掉\n[01:34.33]像这样不可能的事情 我是说不出口的\n[01:45.19]啊啊 扎破 鼓膜的赤红颜色\n[01:49.52]头脑里面 就像要溶化了一样\n[01:54.90]这样的我 再一次变得「听不见了」\n[02:01.35]回转的 回转的 这个世界将我全然无视\n[02:03.65]就像什么都不知道一样\n[02:06.73]悄悄地\n[02:09.06]让我的心最后的跳动生锈\n[02:12.06]你的事情已经像开始就不存在一般消失掉了啊\n[02:33.74]暧昧的回答 那才是谎话\n[02:39.09]你做出的大致都\n[02:44.49]是反对的神情呢\n[02:49.98]- - - - - - - -\n[03:22.32]回转的 回转的 这个世界将我全然无视\n[03:24.57]就像什么都不知道一样\n[03:27.60]悄悄地\n[03:29.92]使我的心生锈\n[03:32.93]「会守护你的」\n[03:38.55]这句话已经说不出口\n[03:40.78]快要坏掉 快要坏掉的\n[03:43.77]谁也不知道 悄悄地\n[03:46.11]像雨一样融化掉 消失不见\n[03:49.20]之类的像这样不可能的事情 说不出口\n[03:54.64]啊啊\n[03:55.81]回转着\n[03:56.33]回转着\n[03:56.96]回转回转着\n[03:58.26]回转着回转着回转着\n[04:00.95]终末时遗留下的头晕\n[04:04.67]啊啊 啊啊 啊啊 啊啊\n',
        album: 'TEXT'
      },
      {
        src: require('./example/music-3.mp3'),
        poster: require('./example/music-3.jpg'),
        name: 'In my room',
        artist: 'FELT',
        album: 'Grow Color'
      },
      {
        src: require('./example/video-1.mp4'),
        poster: require('./example/video-1.png'),
        name: 'ボーカロイドたちがただテッテーテレッテーするだけ',
        type: 'video'
      }
    ];

    const options = {
      zoomOutKana: true,
      volume: 0.75,
      dropDownMenuMode: 'bottom'
    };

    let players = [ new cplayer({
      ...options,
      playlist,
      element: document.getElementById('app1'),
      shadowDom: false
    }), new cplayer({
      ...options,
      playlist: playlist.push(playlist.shift()) && playlist,
      element: document.getElementById('app2'),
      dark: true
    }), new cplayer({
      ...options,
      playlist: playlist.push(playlist.shift()) && playlist,
      element: document.getElementById('app3'),
      big: true
    }), new cplayer({
      ...options,
      playlist: playlist.push(playlist.shift()) && playlist,
      element: document.getElementById('app4'),
      big: true,
      dark: true
    })];

    (window as any).cplayerView = cplayerView;

    document.getElementById('add163').addEventListener("click", () => {
      let id163 = prompt('输入音乐的网易云ID:', '').trim();
      if (id163) {
        from163(id163).then(audio => {
          players.forEach(player => {
            player.view.showPlaylist();
            setTimeout(() => {
              player.add(audio);
            }, 500);
          })
        })
      }
    });

    document.getElementById('openplaylist').addEventListener("click", (e) => {
      players.forEach(player => player.view.showPlaylist());
    });

    document.getElementById('closeplaylist').addEventListener("click", (e) => {
      players.forEach(player => player.view.showInfo());
    });

    document.getElementById('remove').addEventListener("click", (e) => {
      players.forEach(player => player.view.showPlaylist());
      setTimeout(() => {
        players.forEach(player => player.remove(player.playlist[player.playlist.length - 1]));
      }, 600)
    });

    players[0].on('ended', () =>{
      console.log('Event: ended');
    }).on('play', () => {
      console.log('Event: play');
    }).on('pause', () => {
      console.log('Event: pause');
    }).on('playmodechange', () => {
      console.log('Event: playmodechange');
    }).on('openaudio', () => {
      console.log('Event: openaudio');
    }).on('playstatechange', () => {
      console.log('Event: playstatechange');
    }).on('started', () => {
      console.log('Event: started');
    });

    (window as any).demoPlayer = players[0];
    (window as any).demoPlayers = players;
    (window as any).playlist = playlist;
  }
)


================================================
FILE: src/lib/helper/parseHTML.ts
================================================
export default function parseHTML(elem: string) {
    let fragment = document.createDocumentFragment();
    let newelement = document.createElement('div');
    fragment.appendChild(newelement);
    newelement.innerHTML = elem;
    fragment.removeChild(fragment.firstChild);
    fragment.appendChild(newelement.firstChild);
    return fragment;
}

================================================
FILE: src/lib/helper/returntypeof.ts
================================================
export default function returntypeof<RT>(expression: (...params: any[]) => RT): RT {
  return {} as RT;
}

================================================
FILE: src/lib/helper/shallowEqual.ts
================================================
const hasOwn = Object.prototype.hasOwnProperty

function is(x: any, y: any) {
  if (x == y) {
    return x != 0 || y != 0 || 1 / x == 1 / y
  } else {
    return x != x && y != y
  }
}

export default function shallowEqual(objA: any, objB: any) {
  if (is(objA, objB)) return true

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) ||
        !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}

================================================
FILE: src/lib/index.ts
================================================
require('./polyfill')
import { listloopPlaymode } from './playmode/listloop';
import { IAudioItem, Iplaymode, IplaymodeConstructor, Iplaylist } from './interfaces';
import { EventEmitter } from 'events';
import View, { ICplayerViewOption } from './view';
import { decodeLyricStr } from "./lyric";
import { singlecyclePlaymode } from "./playmode/singlecycle";
import { listrandomPlaymode } from "./playmode/listrandom";
import shallowEqual from "./helper/shallowEqual";
import { cplayerMediaSessionPlugin } from "./mediaSession";

let cplayerView:typeof View = undefined;

if (!process.env.cplayer_noview) {
  cplayerView = require('./view').default;
}

export interface ICplayerOption {
  playlist?: Iplaylist;
  playmode?: string;
  volume?: number;
  point?: number;
  autoplay?: boolean;
}

const defaultOption: ICplayerOption = {
  playlist: [],
  point: 0,
  volume: 1,
  playmode: 'listloop',
  autoplay: false
}

const playmodes: { [key: string]: IplaymodeConstructor } = {
  listloop: listloopPlaymode,
  singlecycle: singlecyclePlaymode,
  listrandom: listrandomPlaymode
}

function playlistPreFilter(playlist: Iplaylist) {
  return playlist.map((audio, index) => {
    let res = {
      ...audio
    };
    if (typeof audio.lyric === 'string' && audio.lyric.replace(/\n+/gi, "\n").trim()) {
      res.lyric = decodeLyricStr(audio.lyric)
    }
    if (typeof audio.sublyric === 'string' && audio.sublyric.replace(/\n+/gi, "\n").trim()) {
      res.sublyric = decodeLyricStr(audio.sublyric)
    }
    return res;
  })
}

export default class cplayer extends EventEmitter {
  private __paused = true;
  public view: View;
  public audioElement: HTMLAudioElement | HTMLVideoElement;
  private playmode: Iplaymode;
  private playmodeName: string = 'listloop';
  private audioElementType: string;
  private _volume: number = 0;
  set mode(playmode: string) {
    this.setMode(playmode);
  }
  set volume(volume: number) {
    this.setVolume(volume);
  }
  get volume() {
    return this._volume
  }
  get mode() {
    return this.playmodeName;
  }
  get playlist() {
    return this.playmode.playlist;
  }
  get nowplay() {
    return this.playmode && this.playmode.now();
  }
  get nowplaypoint() {
    return this.playmode && this.playmode.nowpoint();
  }
  get played() {
    return !this.__paused;
  }
  get paused() {
    return this.__paused;
  }
  get duration() {
    if (this.audioElement) {
      return this.audioElement.duration;
    }
    return 0;
  }
  get currentTime() {
    if (this.audioElement) {
      return this.audioElement.currentTime;
    }
    return 0;
  }

  constructor(options: ICplayerOption & ICplayerViewOption) {
    super();
    options = {
      ...defaultOption,
      ...options
    }
    this.playmode = new playmodes[options.playmode](playlistPreFilter(options.playlist), options.point);
    if (!process.env.cplayer_noview)this.view = new cplayerView(this, options);
    cplayerMediaSessionPlugin(this);
    // 同步调用会导致,用户的事件得不到触发
    setImmediate(() => {
      this.openAudio();
      this.setVolume(options.volume);
      if (options.autoplay && this.playlist.length > 0) {
        this.play(true).catch((e) => {
          console.log(e);
          this.pause();
        });
      }
    });
  }

  private initializeEventEmitter(element: HTMLElement) {
    const a = element.addEventListener.bind(element);
    const e = this.eventHandlers;
    a('timeupdate', e.handleTimeUpdate);
    a('canplaythrough', e.handleCanPlayThrough);
    a('pause', e.handlePause);
    a('play', e.handlePlay);
    a('playing', e.handlePlaying)
    a('ended', e.handleEnded);
    a('loadeddata', e.handleLoadeddata);
  }

  private removeEventEmitter(element: HTMLElement) {
    const r = element.removeEventListener.bind(element);
    const e = this.eventHandlers;
    r('timeupdate', e.handleTimeUpdate);
    r('canplaythrough', e.handleCanPlayThrough);
    r('pause', e.handlePause);
    r('play', e.handlePlay);
    r('playing', e.handlePlaying)
    r('ended', e.handleEnded);
    r('loadeddata', e.handleLoadeddata);
  }

  private eventHandlers: { [key: string]: (...args: any[]) => void } = {
    handlePlay: (...args) => {
      if (this.__paused) {
        this.pause();
      }
    },
    handlePlaying: (...args) => {
      if (this.audioElement.currentTime === 0) {
        this.emit('started');
      }
    },
    handleTimeUpdate: (...args) => {
      let time = this.audioElement.duration;
      let playedTime = this.audioElement.currentTime;
      this.emit('timeupdate', playedTime, time);
    },
    handleCanPlayThrough: (...args) => {
      this.emit('canplaythrough', ...args);
    },
    handlePause: (...args) => {
      if (!this.__paused && !this.audioElement.ended) {
        this.play(true).catch((e) => {
          console.log(e);
          this.pause();
        });
      }
    },
    handleEnded: (...args) => {
      this.emit('ended', ...args);
      if (!this.__paused) {
        this.next();
      }
    },
    handlePlayListChange: (...args) => {
      this.emit('playlistchange', ...args);
    },
    handlePlaymodeChange: (mode: string = this.mode) => {
      this.emit('playmodechange', mode);
    },
    handleLoadeddata: (...args) => {
      let time = this.audioElement.duration;
      let playedTime = this.audioElement.currentTime;
      this.emit('timeupdate', playedTime, time);
    }
  }

  public setCurrentTime(currentTime: number | string) {
    if (typeof currentTime === 'string') {
      currentTime.trim();
      if (currentTime[currentTime.length - 1] === '%') {
        const percentage = parseFloat(currentTime);
        currentTime = this.duration * (percentage / 100);
      }
    }
    if (this.audioElement) {
      this.audioElement.currentTime = parseFloat(currentTime.toString());
    }
  }

  private isPlaying() {
    return !!this.audioElement && this.audioElement.currentTime > 0 && !this.audioElement.paused && !this.audioElement.ended && this.audioElement.readyState > 2;
  }

  public openAudio(audio: IAudioItem = this.nowplay) {
    if (audio) {
      if (audio.type === 'video') {
        if (!(this.audioElementType === 'HTMLVideoElement')) {
          if(typeof this.audioElement !== 'undefined') {
            this.removeEventEmitter(this.audioElement);
            this.audioElement.src = '';
          }
          this.audioElement = document.createElement('video');
          if (this.audioElement instanceof HTMLVideoElement) {
            this.audioElementType = 'HTMLVideoElement';
            this.audioElement.loop = false;
            this.audioElement.autoplay = false;
            this.audioElement.poster = audio.poster;
            this.audioElement.setAttribute('playsinline', 'true');
            this.audioElement.setAttribute('webkit-playsinline', 'true');
          }
          this.initializeEventEmitter(this.audioElement);
          this.emit('audioelementchange', this.audioElement);
        }
      } else { 
        if (!(this.audioElementType === 'HTMLAudioElement')) {
          if(typeof this.audioElement !== 'undefined') { 
            this.removeEventEmitter(this.audioElement);
            this.audioElement.src = '';
          }
          this.audioElement = new Audio();
          this.audioElementType = 'HTMLAudioElement';
          this.audioElement.loop = false;
          this.audioElement.autoplay = false;
          this.initializeEventEmitter(this.audioElement);
          this.emit('audioelementchange', this.audioElement);
        }
      }
      this.setVolume(this.volume);
      this.audioElement.src = this.nowplay.src;
      this.emit('openaudio', audio);
      if (!this.__paused) {
        this.play();
      }
    }
  }

  public toggleMode() {
    switch (this.playmodeName) {
      case 'listloop': this.setMode('singlecycle'); break;
      case 'singlecycle': this.setMode('listrandom'); break;
      case 'listrandom': this.setMode('listloop'); break;
    }
  }

  public setMode(playmode: string) {
    if (typeof playmode === 'string') {
      if (this.playmodeName !== playmode) {
        if (playmodes[playmode]) {
          this.playmode = new playmodes[playmode](this.playlist, this.nowplaypoint);
          this.playmodeName = playmode;
          this.eventHandlers.handlePlaymodeChange();
        }
      }
    }
  }

  public getMode() {
    return this.mode;
  }

  public play(Forced: boolean = false) {
    let isPlaying = this.isPlaying();
    let res;
    if (!isPlaying && this.playlist.length > 0 || Forced) {
      res = this.audioElement.play();
    }
    if (this.__paused) {
      this.__paused = false;
      this.emit('playstatechange', this.__paused);
      this.emit('play');
    }
    return res;
  }

  public pause(Forced: boolean = false) {
    let isPlaying = this.isPlaying();
    if (isPlaying && this.playlist.length > 0 || Forced) {
      this.audioElement.pause();
    }
    if (!this.__paused) {
      this.__paused = true;
      this.emit('playstatechange', this.__paused);
      this.emit('pause');
    }
  }

  public to(id: number) {
    this.playmode.to(id);
    this.openAudio();
  }

  public next() {
    this.playmode.next();
    this.openAudio();
  }

  public prev() {
    this.playmode.prev();
    this.openAudio();
  }

  public togglePlayState() {
    if (this.__paused) {
      this.play();
    } else {
      this.pause();
    }
  }

  public add(item: IAudioItem) {
    item = (playlistPreFilter([item] as Iplaylist))[0];
    this.playmode.addMusic(item);
    this.eventHandlers.handlePlayListChange();
    if (this.playlist.length === 1) {
      this.to(0);
    }
  }

  public remove(item: IAudioItem) {
    let needUpdate = this.playmode.removeMusic(item);
    this.eventHandlers.handlePlayListChange();
    if (needUpdate) {
      this.openAudio();
    }
  }

  public setVolume(volume: number | string) {
    this._volume = parseFloat(volume as string);
    if (this.audioElement)
      this.audioElement.volume = Math.max(0.0, Math.min(1.0, this._volume));
    this.emit('volumechange', this.volume);
  }

  public destroy() {
    if (this.audioElement) {
      this.audioElement.src = null;
      this.audioElement.removeEventListener("timeupdate", this.eventHandlers.handleTimeUpdate);
      this.removeAllListeners();  
    }
    if (this.view) this.view.destroy();
    Object.getOwnPropertyNames(this).forEach((name: keyof cplayer) => delete this[name]);
    (this as any).__proto__ = Object;
  }
}

if (!process.env.cplayer_noview) {
  function parseCPlayerTag() {
    Array.prototype.forEach.call(document.querySelectorAll('template[cplayer]'),(element: Element) => {
      element.attributes.getNamedItem('loaded') ||
        new cplayer({
          generateBeforeElement: true,
          deleteElementAfterGenerate: true,
          element,
          ...JSON.parse(element.innerHTML)
        })
    })
  }

  window.addEventListener("load", parseCPlayerTag);
}

(window as any).cplayer = cplayer;

================================================
FILE: src/lib/interfaces.ts
================================================
import { Lyric } from "./lyric";

export interface IAudioItem {
  name: string; //名称
  poster?: string; //海报
  artist?: string; //艺术家
  src: string; //音频
  lyric?: Lyric | string; // 歌词
  sublyric?: Lyric | string; // 小歌词;
  album?: string; // 专辑 & 唱片
  type?: string;
}

export type Iplaylist = IAudioItem[];

export interface Iplaymode {
  next(): IAudioItem;
  prev(): IAudioItem;
  now(): IAudioItem;
  nowpoint(): number;
  to(id: number): void;
  addMusic(item: IAudioItem): void;
  removeMusic(item: IAudioItem): boolean;
  playlist: Iplaylist;
}

export interface IplaymodeConstructor {
  new(playlist: Iplaylist, point: number): Iplaymode;
}

================================================
FILE: src/lib/lyric.ts
================================================
export interface ILyricItem {
  time: number;
  word: string;
}


export class Lyric {
  raw: string;
  items: ILyricItem[] = [];
  public getLyric(time: number) {
    return this.items.reduce((p, c) => {
      if (c.time < time && (!p || p.time < c.time)) {
        return c;
      }
      return p;
    }, undefined)
  }
  public getNextLyric(time: number) {
    return this.items.reduce((p, c) => {
      if (c.time > time && (!p || p.time > c.time)) {
        return c;
      }
      return p;
    }, undefined)
  }
  public toString() {
    return this.raw;
  }
  constructor(items: ILyricItem[] ,raw: string) {
    this.items = items;
    this.raw = raw;
  }
}

export function decodeLyricStr(lyricStr: string, options?: {}) {
  if (typeof lyricStr !== 'string') return lyricStr;
  let lyric: ILyricItem[] = [];
  lyricStr.replace(/\n+/gi, "\n").trim().split("\n").forEach((lyricStrItem) => {
    lyric.push(...decodeLyricStrItem(lyricStrItem));
  });
  if (lyric.length == 0) return undefined;
  return new Lyric(lyric, lyricStr);
}

export function decodeLyricStrItem(lyricItemStr: string): ILyricItem[] {
  let res: ILyricItem[] = [];
  let timestr = lyricItemStr.match(/\[\d+\:[\.\d]+\]/gi);
  let word = /(?:\[\d+\:[\.\d]+\])*(.*)/gi.exec(lyricItemStr)[1].trim()
  if (timestr && word) {
    timestr.forEach((timestr) => {
      let z = /\[(\d+)\:([\.\d]+)\]/gi.exec(timestr.trim());
      let time = parseInt(z[1]) * 60 * 1000 + parseFloat(z[2]) * 1000;
      res.push({
        time,
        word
      });
    })
  }
  return res;
}

================================================
FILE: src/lib/mediaSession.ts
================================================
import cplayer from './';
import { IAudioItem } from '../lib/interfaces';

const defaultPoster = require('../defaultposter.jpg')

export function cplayerMediaSessionPlugin(player: cplayer)
{
  if ('mediaSession' in navigator) {
    if (player.nowplay) navigator.mediaSession.metadata = mediaMetadata(player.nowplay);
    navigator.mediaSession.setActionHandler('play', () => player.play());
    navigator.mediaSession.setActionHandler('pause', () => player.pause());
    navigator.mediaSession.setActionHandler('previoustrack', () => player.prev());
    navigator.mediaSession.setActionHandler('nexttrack', () => player.next());
    player.on('openaudio', () => {
      navigator.mediaSession.metadata = mediaMetadata(player.nowplay);
    })
  }
}

export function mediaMetadata(audio: IAudioItem) {
  return new MediaMetadata({
    title: audio.name,
    artist: audio.artist,
    album: audio.album,
    artwork: [
      {
        sizes: '720x720',
        src: audio.poster || defaultPoster
      }
    ]
  });
}


================================================
FILE: src/lib/playmode/listloop.ts
================================================
import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';
import shallowEqual from "../helper/shallowEqual";

export function baseRemoveMusic(item: IAudioItem, playlist: Iplaylist, nowpoint: number, newpoint: (point: number) => number) {
  let targetPoint: number;
  let needupdate = false;
  playlist.forEach((a, index) => {
    if (shallowEqual(a, item)) {
      targetPoint = index;
    }
  })
  if (typeof targetPoint !== 'undefined') {
    playlist.splice(targetPoint, 1);
    if (nowpoint > targetPoint) {
      nowpoint--;
      needupdate = false;
    } else if (nowpoint === targetPoint) {
      nowpoint = newpoint(nowpoint);
      needupdate = true;
    }
  }
  return { playlist, nowpoint, needupdate };
}

export class listloopPlaymode implements Iplaymode {
  private __playlist: Iplaylist = [];
  private point = 0;
  get playlist() {
    return this.__playlist;
  }

  constructor(playlist: Iplaylist = [], point: number = 0) {
    this.__playlist = playlist;
    this.to(point);
  }

  public next() {
    this.point = this.nextPoint();
    return this.playlist[this.point];
  }

  public prev() {
    this.point = this.prevPoint();
    return this.playlist[this.point];
  }

  public now() {
    return this.playlist[this.point];
  }

  public nowpoint() {
    return this.point
  }

  public to(point: number) {
    this.point = Math.max(0, Math.min(point, this.__playlist.length - 1));
  }

  public addMusic(item: IAudioItem) {
    this.__playlist.push(item);
  }

  private nextPoint() {
    let res = this.point + 1;
    if (res >= this.__playlist.length) {
      res = 0;
    }
    return res;
  }

  private prevPoint() {
    let res = this.point - 1;
    if (res < 0) {
      res = this.__playlist.length - 1
    }
    return res;
  }

  public removeMusic(item: IAudioItem) {
    let { playlist, nowpoint, needupdate } = baseRemoveMusic(item, this.__playlist, this.point, (point) => Math.max(0, Math.min(point, this.__playlist.length - 1)))
    this.__playlist = playlist;
    this.point = nowpoint;
    return needupdate;
  }
}

================================================
FILE: src/lib/playmode/listrandom.ts
================================================
import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';
import shallowEqual from "../helper/shallowEqual";
import { baseRemoveMusic } from "./listloop";

export class listrandomPlaymode implements Iplaymode {
  private __playlist: Iplaylist = [];
  private point = 0;
  get playlist() {
    return this.__playlist;
  }

  constructor(playlist: Iplaylist = [], point: number = 0) {
    this.__playlist = playlist;
    this.to(point);
  }

  public next() {
    this.point = this.randomPoint();
    return this.__playlist[this.point];
  }

  public prev() {
    this.point = this.randomPoint();
    return this.__playlist[this.point];
  }

  public now() {
    return this.__playlist[this.point];
  }

  public nowpoint() {
    return this.point;
  }

  public to(point: number) {
    this.point = Math.max(0, Math.min(point, this.__playlist.length - 1));
  }

  public addMusic(item:IAudioItem){
    this.__playlist.push(item);
  }

  private randomPoint(): number {
    if (this.__playlist.length > 1) {
      let random = Math.floor(this.__playlist.length * Math.random());
      if (random === this.point) {
        return this.randomPoint();
      } else {
        return random;
      }
    } else return 0;
  }

  public removeMusic(item: IAudioItem) {
    let {playlist,nowpoint,needupdate} = baseRemoveMusic(item, this.__playlist, this.point, (point) => this.randomPoint())
    this.__playlist = playlist;
    this.point = nowpoint;
    return needupdate;
  }
}

================================================
FILE: src/lib/playmode/singlecycle.ts
================================================
import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';
import shallowEqual from "../helper/shallowEqual";
import { baseRemoveMusic } from "./listloop";

export class singlecyclePlaymode implements Iplaymode {
  private __playlist: Iplaylist = [];
  private audio: IAudioItem;
  private point: number;
  get playlist() {
    return this.__playlist;
  }

  constructor(playlist: Iplaylist = [], point: number = 0) {
    this.__playlist = playlist;
    this.to(point);
  }

  public next() {
    return this.__playlist[this.point];
  }

  public prev() {
    return this.__playlist[this.point];
  }

  public now() {
    return this.__playlist[this.point];
  }

  public nowpoint() {
    return this.point;
  }

  public to(point: number) {
    this.point = Math.max(0, Math.min(point, this.__playlist.length - 1));
  }

  public addMusic(item: IAudioItem) {
    this.__playlist.push(item);
  }

  public removeMusic(item: IAudioItem) {
    let {playlist,nowpoint,needupdate} = baseRemoveMusic(item, this.__playlist, this.point, (point) => Math.max(0, Math.min(point, this.__playlist.length - 1)))
    this.__playlist = playlist;
    this.point = nowpoint;
    return needupdate;
  }
}

================================================
FILE: src/lib/polyfill.js
================================================
if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
        value: function assign(target, varArgs) { // .length of function is 2
            'use strict';
            if (target == null) { // TypeError if undefined or null
                throw new TypeError('Cannot convert undefined or null to object');
            }

            var to = Object(target);

            for (var index = 1; index < arguments.length; index++) {
                var nextSource = arguments[index];

                if (nextSource != null) { // Skip over if undefined or null
                    for (var nextKey in nextSource) {
                        // Avoid bugs when hasOwnProperty is shadowed
                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                            to[nextKey] = nextSource[nextKey];
                        }
                    }
                }
            }
            return to;
        },
        writable: true,
        configurable: true
    });
}

================================================
FILE: src/lib/view.ts
================================================
import { IAudioItem } from './interfaces';
import cplayer from './';
import returntypeof from './helper/returntypeof';
import { EventEmitter } from 'events';
import parseHTML from "./helper/parseHTML";
import { ILyricItem } from "./lyric";

const defaultPoster = require('../defaultposter.jpg')
const htmlTemplate = require('../cplayer.html');
const playIcon = require('../playicon.svg');
const style = require('!css-loader!postcss-loader!sass-loader!../scss/cplayer.scss');

function kanaFilter(str: string) {
  const starttag = '<span class="cp-lyric-text-zoomout">';
  const endtag = '</span>';
  let res = '';
  let startflag = false;
  for(let i = 0; i < str.length; i++) {
    let ch = str.charAt(i);
    let kano = /[ぁ-んァ-ン]/.test(ch);
    if (kano && !startflag) {
      res += starttag;
      startflag = true;
    }
    if (!kano && startflag) {
      res += endtag;
      startflag = false;
    }
    res += ch;
  }
  if (startflag) {
    res += endtag;
  }
  return res;
}

function buildLyric(lyric: string, sublyric?: string, zoomOutKana: boolean = false) {
  return (zoomOutKana ? kanaFilter(lyric) : lyric) + (sublyric ? `<span class="cp-lyric-text-sub">${sublyric}</span>` : '')
}

function secondNumber2TimeStr(secondTime: number) {
  const minute: string = parseInt((secondTime / 60).toString()).toString().padStart(2, '0');
  const second: string = parseInt((secondTime % 60).toString()).toString().padStart(2, '0');
  return minute + ':' + second;
}

export interface ICplayerViewOption {
  element?: Element;
  generateBeforeElement?: boolean;
  deleteElementAfterGenerate?: boolean;
  zoomOutKana?: boolean;
  showPlaylist?: boolean;
  showPlaylistButton?: boolean;
  width?: string;
  size?: string;
  style?: string;
  dark?: boolean;
  big?: boolean;
  shadowDom?: boolean;
  dropDownMenuMode?: 'bottom' | 'top' | 'none' | string;
}

const defaultOption: ICplayerViewOption = {
  element: document.body,
  generateBeforeElement: false,
  deleteElementAfterGenerate: false,
  zoomOutKana: false,
  showPlaylist: false,
  showPlaylistButton: true,
  dropDownMenuMode: 'bottom',
  width: '',
  size: '12px',
  style: '',
  shadowDom: true
}


function createStyleElement(style: string) {
  const styleElement = document.createElement('style');
  styleElement.id = 'cplayer-style';
  styleElement.innerHTML = style;
  return styleElement;
}

function createShadowElement(targetElement: Element, htmlTemplate: string, style: string) {
  let shadowRoot = (targetElement as any).createShadowRoot() as ShadowRoot;
  shadowRoot.innerHTML = htmlTemplate;
  shadowRoot.appendChild(createStyleElement(style));
  return shadowRoot.firstChild as HTMLElement;
}

function createBeforeElement(targetElement: Element, htmlTemplate: string, style: string) {
  let element = document.createElement('div');
  element.innerHTML = htmlTemplate;
  targetElement.parentNode.insertBefore(element, targetElement);
  if (!document.getElementById('cplayer-style')) {
    document.body.appendChild(createStyleElement(style));
  }
  return element.firstChild as HTMLElement;
}

function createBeforeShadowElement(targetElement: Element, htmlTemplate: string, style: string) {
  let element = document.createElement('div');
  let shadowRoot = (element as any).createShadowRoot() as ShadowRoot;
  shadowRoot.innerHTML = htmlTemplate;
  shadowRoot.appendChild(createStyleElement(style));
  targetElement.parentNode.insertBefore(element, targetElement);
  return shadowRoot.firstChild as HTMLElement;
}

function createElement(targetElement: Element, htmlTemplate: string, style: string) {
  targetElement.innerHTML = htmlTemplate;
  if (!document.getElementById('cplayer-style')) {
    document.body.appendChild(createStyleElement(style));
  }
  return targetElement.firstChild as HTMLElement;
}

export default class cplayerView extends EventEmitter {
  private elementLinks = returntypeof(this.getElementLinks);
  private rootElement: HTMLElement;
  private player: cplayer;
  private dropDownMenuShowInfo = true;
  private options: ICplayerViewOption;

  constructor(player: cplayer, options: ICplayerViewOption) {
    super();
    this.options = {
      ...defaultOption,
      ...options
    };
    this.player = player;
    if (this.options.generateBeforeElement) {
      if ((this.options.element as any).createShadowRoot && options.shadowDom !== false) {
        this.rootElement = createBeforeShadowElement(this.options.element, htmlTemplate, style + this.options.style);
      } else {
        this.rootElement = createBeforeElement(this.options.element, htmlTemplate, style + this.options.style);
      }
    } else {
      if ((this.options.element as any).createShadowRoot && options.shadowDom !== false) {
        this.rootElement = createShadowElement(this.options.element, htmlTemplate, style + this.options.style);
      } else {
        this.rootElement = createElement(this.options.element, htmlTemplate, style + this.options.style);
      }
    }
    if (options.deleteElementAfterGenerate) {
      options.element.parentElement.removeChild(options.element);
    }
    this.rootElement.style.width = this.options.width;
    this.rootElement.style.fontSize = this.options.size;
    this.elementLinks = this.getElementLinks();
    this.injectEventListener();
    this.setPlayIcon(this.player.paused);
    this.dropDownMenuShowInfo = !this.options.showPlaylist;
    if (this.dropDownMenuShowInfo) {
      this.showInfo();
    } else this.showPlaylist();
    if (!this.options.showPlaylistButton)
      this.elementLinks.button.list.style.display = 'none';
    else
      this.elementLinks.button.list.style.display = '';
    this.elementLinks.dropDownMenu.classList.add('cp-drop-down-menu-' + this.options.dropDownMenuMode)
    if (this.options.dark) {
      this.dark();
    }
    if (this.options.big) {
      this.big();
    }

    // this.setPoster(this.player.nowplay.poster || defaultPoster);
    this.setProgress(this.player.currentTime / this.player.duration,
      this.player.currentTime,
      this.player.duration);
    // this.elementLinks.title.innerText = this.player.nowplay.name;
    // this.elementLinks.artist.innerText = this.player.nowplay.artist || '';
    this.updateLyric();
    this.updatePlaylist();
  }

  public getRootElement() {
    return this.rootElement;
  }

  public dark() {
    this.rootElement.classList.add('cp-dark');
  }

  public big() {
    this.rootElement.classList.add('cp-big');
  }

  private getPlayListLinks(rootElement: Element = this.rootElement) {
    return rootElement.querySelectorAll('.cp-playlist li');
  }

  private getElementLinks(rootElement: Element = this.rootElement) {
    let gebc: (className: string) => Element = className => rootElement.getElementsByClassName(className)[0];
    return {
      icon: {
        play: gebc('cp-play-icon') as HTMLElement,
        mode: gebc('cp-mode-icon') as HTMLElement,
      },
      button: {
        prev: gebc('cp-prev-button') as HTMLElement,
        play: gebc('cp-play-button') as HTMLElement,
        next: gebc('cp-next-button') as HTMLElement,
        volume: gebc('cp-volume-icon') as HTMLElement,
        list: gebc('cp-list-button') as HTMLElement,
        mode: gebc('cp-mode-button') as HTMLElement
      },
      progress: gebc('cp-progress') as HTMLElement,
      progressFill: gebc('cp-progress-fill') as HTMLElement,
      progressButton: gebc('cp-progress-button') as HTMLElement,
      progressDuration: gebc('cp-progress-duration') as HTMLElement,
      progressCurrentTime: gebc('cp-progress-current-time') as HTMLElement,
      poster: gebc('cp-poster') as HTMLElement,
      title: gebc('cp-audio-title') as HTMLElement,
      artist: gebc('cp-audio-artist') as HTMLElement,
      lyric: gebc('cp-lyric-text') as HTMLElement,
      lyricContainer: gebc('cp-lyric') as HTMLElement,
      volumeController: gebc('cp-volume-controller') as HTMLElement,
      volumeFill: gebc('cp-volume-fill') as HTMLElement,
      volumeControllerButton: gebc('cp-volume-controller-button') as HTMLElement,
      volumeControllerContainer: gebc('cp-volume-container') as HTMLElement,
      dropDownMenu: gebc('cp-drop-down-menu') as HTMLElement,
      playlist: gebc('cp-playlist') as HTMLElement,
      playlistItems: this.getPlayListLinks(rootElement)
    }
  }

  private setPlayIcon(paused: boolean) {
    if (paused) {
      this.elementLinks.icon.play.classList.add('cp-play-icon-paused');
    } else {
      this.elementLinks.icon.play.classList.remove('cp-play-icon-paused');
    }
  }

  private setProgress(point: number, currentTime: number, duration: number) {
    this.elementLinks.progressFill.style.width = `${point * 100}%`;
    this.elementLinks.progressButton.style.right = (1 - point) * 100 + '%';
    this.elementLinks.progressCurrentTime.innerText = secondNumber2TimeStr(currentTime);
    this.elementLinks.progressDuration.innerText = secondNumber2TimeStr(duration);
  }

  private setPoster(src: string) {
    this.elementLinks.poster.style.backgroundImage = `url("${src}")`;
  }

  private __OldVolume = 1;
  private setVolume(volume: number) {
    if (this.__OldVolume !== volume) {
      this.elementLinks.volumeFill.style.width = `${volume * 100}%`;
      this.elementLinks.volumeControllerButton.style.right = (1 - volume) * 100 + '%';
      this.__OldVolume = volume
    }
  }

  private setMode(mode: string) {
    var modeattr = document.createAttribute('data-mode');
    modeattr.value = mode;
    this.elementLinks.button.mode.attributes.setNamedItem(modeattr);
  }

  public showInfo() {
    let dropDownMenu = this.elementLinks.dropDownMenu;
    dropDownMenu.style.height = '';
    dropDownMenu.classList.remove('cp-drop-down-menu-playlist');
    dropDownMenu.classList.add('cp-drop-down-menu-info');
    this.dropDownMenuShowInfo = true;
  }

  public showPlaylist() {
    let dropDownMenu = this.elementLinks.dropDownMenu;
    dropDownMenu.style.height = this.player.playlist.length * 2.08333 + 'em';
    dropDownMenu.classList.remove('cp-drop-down-menu-info');
    dropDownMenu.classList.add('cp-drop-down-menu-playlist');
    this.dropDownMenuShowInfo = false;
  }

  public toggleDropDownMenu() {
    if (this.dropDownMenuShowInfo) {
      this.showPlaylist();
    } else {
      this.showInfo();
    }
  }

  private setVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.add('cp-volume-container-show');
  }

  private toggleVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.toggle('cp-volume-container-show');
  }

  private removeVolumeControllerKeepShow() {
    this.elementLinks.volumeControllerContainer.classList.remove('cp-volume-container-show');
  }

  private __OldLyric = '';
  private __OldTotalTime = 0;

  private setLyric(lyric: string, time: number = 0, totalTime: number = 0) {
    if (this.__OldLyric !== lyric || this.__OldTotalTime !== totalTime) {
      this.elementLinks.lyric.innerHTML = lyric;
      this.elementLinks.lyric.style.transition = '';
      this.elementLinks.lyric.style.transform = '';
      if (totalTime !== 0) {
        let lyricWidth = this.elementLinks.lyric.clientWidth;
        let lyricContainerWidth = this.elementLinks.lyricContainer.clientWidth;
        if (lyricWidth > lyricContainerWidth) {
          let duration = totalTime - time;
          let targetOffset = (lyricWidth - lyricContainerWidth);
          let timepage = lyricContainerWidth / lyricWidth * duration;
          let startTime = Math.min(timepage * 0.6, duration);
          let moveTime = duration - timepage;

          this.elementLinks.lyric.style.transition = `transform ${moveTime}ms linear ${startTime}ms`
          this.elementLinks.lyric.style.transform = `translateX(-${targetOffset}px)`;
        }
      }
      this.__OldLyric = lyric;
      this.__OldTotalTime = totalTime;
    }
  }

  private updatePlaylist() {
    var lis = this.player.playlist.map((audio, index) => {
      var element = document.createElement('li');
      element.innerHTML = `
        ${index === this.player.nowplaypoint ? playIcon : '<span class="cp-play-icon"></span>'}
        <span>${audio.name}</span><span class='cp-playlist-artist'>${audio.artist ? ' - ' + audio.artist : ''}</span>
      `
      return element;
    })
    this.elementLinks.playlist.innerHTML = '';
    lis.forEach((li) => {
      this.elementLinks.playlist.appendChild(li);
    })
    this.elementLinks.playlistItems = this.getPlayListLinks();
    this.injectPlayListEventListener();
    if (!this.dropDownMenuShowInfo) {
      this.elementLinks.dropDownMenu.style.height = this.player.playlist.length * 2.08333 + 'em';
    }
  }

  private injectPlayListEventListener() {
    Array.prototype.forEach.call(this.elementLinks.playlistItems,((i: Element, index: number) => {
      i.addEventListener('click', (event) => {
        this.handleClickPlayList(index, event);
      })
    }))
  }

  private injectEventListener() {
    this.elementLinks.button.play.addEventListener('click', this.handleClickPlayButton);
    this.elementLinks.button.prev.addEventListener('click', this.handleClickPrevButton);
    this.elementLinks.button.next.addEventListener('click', this.handleClickNextButton);
    this.elementLinks.button.volume.addEventListener('click', this.handleClickVolumeButton);
    this.elementLinks.button.list.addEventListener('click', this.handleClickListButton);
    this.elementLinks.button.mode.addEventListener('click', this.handleClickModeButton);
    this.elementLinks.volumeController.addEventListener('mousemove', this.handleMouseVolumeController)
    this.elementLinks.volumeController.addEventListener('mousedown', this.handleMouseVolumeController)
    this.elementLinks.volumeController.addEventListener('touchmove', this.handleTouchVolumeController, {passive: true} as any)

    this.elementLinks.progress.addEventListener('mousemove', this.handleMouseProgress)
    this.elementLinks.progress.addEventListener('mousedown', this.handleMouseProgress)
    this.elementLinks.progress.addEventListener('touchmove', this.handleTouchProgress, {passive: true} as any)

    this.player.addListener('playstatechange', this.handlePlayStateChange);
    this.player.addListener('timeupdate', this.handleTimeUpdate);
    this.player.addListener('openaudio', this.handleOpenAudio);
    this.player.addListener('volumechange', this.handleVolumeChange);
    this.player.addListener('playmodechange', this.handleModeChange);
    this.player.addListener('playlistchange', this.handlePlaylistchange);
    this.player.addListener('audioelementchange', this.handleAudioElementChange);
    this.injectPlayListEventListener();
  }

  private handlePlaylistchange = () => {
    this.updatePlaylist()
  }

  private updateLyric(playedTime: number = 0) {
    if (!this.player.nowplay) {
      this.setLyric(null);
      return;
    }

    if (this.player.nowplay.lyric && typeof this.player.nowplay.lyric !== 'string' && this.player.played) {
      let lyric = this.player.nowplay.lyric.getLyric(playedTime * 1000);
      let nextLyric = this.player.nowplay.lyric.getNextLyric(playedTime * 1000);
      if (lyric) {
        let sublyric: ILyricItem;
        if (this.player.nowplay.sublyric && typeof this.player.nowplay.sublyric !== 'string') {
          sublyric = this.player.nowplay.sublyric.getLyric(playedTime * 1000);
        }
        if (nextLyric) {
          let duration = nextLyric.time - lyric.time;
          let currentTime = playedTime * 1000 - lyric.time;
          this.setLyric(buildLyric(lyric.word, sublyric ? sublyric.word : undefined, this.options.zoomOutKana), currentTime, duration);
        } else {
          let duration = this.player.duration - lyric.time;
          let currentTime = playedTime * 1000 - lyric.time;
          this.setLyric(buildLyric(lyric.word, sublyric ? sublyric.word : undefined, this.options.zoomOutKana), currentTime, duration);
        }
      } else {
        this.setLyric(buildLyric(this.player.nowplay.name, this.player.nowplay.artist, false), playedTime * 1000, nextLyric.time);
      }
    } else {
      this.setLyric(buildLyric(this.player.nowplay.name, this.player.nowplay.artist, false));
    }
  }

  private handleClickListButton = () => {
    this.toggleDropDownMenu();
  }

  private handleClickModeButton = () => {
    this.player.toggleMode();
  }

  private handleClickPlayList = (point: number, event: Event) => {
    if (this.player.nowplaypoint !== point){
      this.player.to(point);
      this.player.play();
    }
  }

  private handleClickPlayButton = () => {
    this.player.togglePlayState();
  }

  private handleClickVolumeButton = () => {
    this.toggleVolumeControllerKeepShow();
  }

  private handleOpenAudio = (audio: IAudioItem) => {
    if (audio.type !== 'video') {
      this.setPoster(audio.poster || defaultPoster);
    } else {
      this.setPoster('none');
    }
    this.setProgress(0,0,0);
    this.elementLinks.title.innerText = audio.name;
    this.elementLinks.artist.innerText = audio.artist || '';
    this.updateLyric();
    this.updatePlaylist();
  }

  private handleModeChange = (mode: string) => {
    this.setMode(mode);
  }

  private handleVolumeChange = (volume: number) => {
    this.setVolume(volume);
  };

  private handleTimeUpdate = (playedTime: number, time: number) => {
    this.setProgress(playedTime / time, playedTime, time);
    this.updateLyric(playedTime);
  }

  private handleClickPrevButton = () => {
    this.player.prev();
    this.player.play();
  }

  private handleClickNextButton = () => {
    this.player.next();
    this.player.play();
  }

  private handlePlayStateChange = (paused: boolean) => {
    this.setPlayIcon(paused);
  }

  private handleMouseVolumeController = (event: MouseEvent) => {
    this.removeVolumeControllerKeepShow()
    if (event.buttons === 1 || typeof event.buttons === 'undefined' && event.which === 1) {
      let volume = Math.max(0, Math.min(1.0,
        (event.clientX - this.elementLinks.volumeController.getBoundingClientRect().left) / this.elementLinks.volumeController.clientWidth
      ));
      this.player.setVolume(volume);
      this.setVolume(volume);
    }
  };

  private handleTouchVolumeController = (event: TouchEvent) => {
    this.removeVolumeControllerKeepShow()
    let volume = Math.max(0, Math.min(1.0,
      (event.targetTouches[0].clientX - this.elementLinks.volumeController.getBoundingClientRect().left) / this.elementLinks.volumeController.clientWidth
    ));
    this.player.setVolume(volume);
    this.setVolume(volume);
  };

  private handleAudioElementChange = (element: HTMLAudioElement | HTMLVideoElement) => {
    if (element instanceof HTMLVideoElement) {
      this.elementLinks.poster.appendChild(element);
    }
    else {
      this.elementLinks.poster.innerHTML = '';
    }
  }

  private handleMouseProgress = (event: MouseEvent) => {
    if (event.buttons === 1 || typeof event.buttons === 'undefined' && event.which === 1) {
      let progress = Math.max(0, Math.min(1.0,
        (event.clientX - this.elementLinks.progress.getBoundingClientRect().left) / this.elementLinks.progress.clientWidth
      ));
      this.player.setCurrentTime(progress * 100 + '%');
    }
  };

  private handleTouchProgress = (event: TouchEvent) => {
    let progress = Math.max(0, Math.min(1.0,
      (event.targetTouches[0].clientX - this.elementLinks.progress.getBoundingClientRect().left) / this.elementLinks.progress.clientWidth
    ));
    this.player.setCurrentTime(progress * 100 + '%');
  }

  public destroy() {
    this.rootElement.parentElement.removeChild(this.rootElement);
  }
}


================================================
FILE: src/manifest.json
================================================
{
    "short_name": "cPlayer",
    "name": "A beautiful and clean WEB Music Player by HTML5.",
    "start_url": "index.html"
  }

================================================
FILE: src/neko.css
================================================
body {
  margin: 0px;
  font-family: "Roboto", sans-serif;
  -webkit-font-smoothing: antialiased; }

*,
*::before,
*::after {
  -webkit-box-sizing: border-box;
          box-sizing: border-box; }

.neko-container {
  padding-right: 8px;
  padding-left: 8px;
  margin-right: auto;
  margin-left: auto;
  max-width: 980px;
  position: relative; }
  @media (min-width: 576px) {
    .neko-container {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 768px) {
    .neko-container {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 992px) {
    .neko-container {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 1200px) {
    .neko-container {
      padding-right: 8px;
      padding-left: 8px; } }

.neko-container-fluid {
  padding-right: 8px;
  padding-left: 8px;
  position: relative; }
  @media (min-width: 576px) {
    .neko-container-fluid {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 768px) {
    .neko-container-fluid {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 992px) {
    .neko-container-fluid {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 1200px) {
    .neko-container-fluid {
      padding-right: 8px;
      padding-left: 8px; } }

.neko-row {
  margin-right: -8px;
  margin-left: -8px;
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
  -webkit-flex-wrap: wrap;
      -ms-flex-wrap: wrap;
          flex-wrap: wrap;
  position: relative; }
  @media (min-width: 576px) {
    .neko-row {
      margin-right: -8px;
      margin-left: -8px; } }
  @media (min-width: 768px) {
    .neko-row {
      margin-right: -8px;
      margin-left: -8px; } }
  @media (min-width: 992px) {
    .neko-row {
      margin-right: -8px;
      margin-left: -8px; } }
  @media (min-width: 1200px) {
    .neko-row {
      margin-right: -8px;
      margin-left: -8px; } }

.neko-no-gutters {
  margin-right: 0;
  margin-left: 0; }
  .neko-no-gutters > .col,
  .neko-no-gutters > [class*="col-"] {
    padding-right: 0;
    padding-left: 0; }

.neko-no-gutters {
  margin-right: 0;
  margin-left: 0; }
  .neko-no-gutters > .col,
  .neko-no-gutters > [class*="col-"] {
    padding-right: 0;
    padding-left: 0; }

.neko-col-1, .neko-col-2, .neko-col-3, .neko-col-4, .neko-col-5, .neko-col-6, .neko-col-7, .neko-col-8, .neko-col-9, .neko-col-10, .neko-col-11, .neko-col-12, .neko-col,
.neko-col-auto, .neko-col-sm-1, .neko-col-sm-2, .neko-col-sm-3, .neko-col-sm-4, .neko-col-sm-5, .neko-col-sm-6, .neko-col-sm-7, .neko-col-sm-8, .neko-col-sm-9, .neko-col-sm-10, .neko-col-sm-11, .neko-col-sm-12, .neko-col-sm,
.neko-col-sm-auto, .neko-col-md-1, .neko-col-md-2, .neko-col-md-3, .neko-col-md-4, .neko-col-md-5, .neko-col-md-6, .neko-col-md-7, .neko-col-md-8, .neko-col-md-9, .neko-col-md-10, .neko-col-md-11, .neko-col-md-12, .neko-col-md,
.neko-col-md-auto, .neko-col-lg-1, .neko-col-lg-2, .neko-col-lg-3, .neko-col-lg-4, .neko-col-lg-5, .neko-col-lg-6, .neko-col-lg-7, .neko-col-lg-8, .neko-col-lg-9, .neko-col-lg-10, .neko-col-lg-11, .neko-col-lg-12, .neko-col-lg,
.neko-col-lg-auto, .neko-col-xl-1, .neko-col-xl-2, .neko-col-xl-3, .neko-col-xl-4, .neko-col-xl-5, .neko-col-xl-6, .neko-col-xl-7, .neko-col-xl-8, .neko-col-xl-9, .neko-col-xl-10, .neko-col-xl-11, .neko-col-xl-12, .neko-col-xl,
.neko-col-xl-auto {
  position: relative;
  width: 100%;
  min-height: 1px;
  padding-right: 8px;
  padding-left: 8px; }
  @media (min-width: 576px) {
    .neko-col-1, .neko-col-2, .neko-col-3, .neko-col-4, .neko-col-5, .neko-col-6, .neko-col-7, .neko-col-8, .neko-col-9, .neko-col-10, .neko-col-11, .neko-col-12, .neko-col,
    .neko-col-auto, .neko-col-sm-1, .neko-col-sm-2, .neko-col-sm-3, .neko-col-sm-4, .neko-col-sm-5, .neko-col-sm-6, .neko-col-sm-7, .neko-col-sm-8, .neko-col-sm-9, .neko-col-sm-10, .neko-col-sm-11, .neko-col-sm-12, .neko-col-sm,
    .neko-col-sm-auto, .neko-col-md-1, .neko-col-md-2, .neko-col-md-3, .neko-col-md-4, .neko-col-md-5, .neko-col-md-6, .neko-col-md-7, .neko-col-md-8, .neko-col-md-9, .neko-col-md-10, .neko-col-md-11, .neko-col-md-12, .neko-col-md,
    .neko-col-md-auto, .neko-col-lg-1, .neko-col-lg-2, .neko-col-lg-3, .neko-col-lg-4, .neko-col-lg-5, .neko-col-lg-6, .neko-col-lg-7, .neko-col-lg-8, .neko-col-lg-9, .neko-col-lg-10, .neko-col-lg-11, .neko-col-lg-12, .neko-col-lg,
    .neko-col-lg-auto, .neko-col-xl-1, .neko-col-xl-2, .neko-col-xl-3, .neko-col-xl-4, .neko-col-xl-5, .neko-col-xl-6, .neko-col-xl-7, .neko-col-xl-8, .neko-col-xl-9, .neko-col-xl-10, .neko-col-xl-11, .neko-col-xl-12, .neko-col-xl,
    .neko-col-xl-auto {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 768px) {
    .neko-col-1, .neko-col-2, .neko-col-3, .neko-col-4, .neko-col-5, .neko-col-6, .neko-col-7, .neko-col-8, .neko-col-9, .neko-col-10, .neko-col-11, .neko-col-12, .neko-col,
    .neko-col-auto, .neko-col-sm-1, .neko-col-sm-2, .neko-col-sm-3, .neko-col-sm-4, .neko-col-sm-5, .neko-col-sm-6, .neko-col-sm-7, .neko-col-sm-8, .neko-col-sm-9, .neko-col-sm-10, .neko-col-sm-11, .neko-col-sm-12, .neko-col-sm,
    .neko-col-sm-auto, .neko-col-md-1, .neko-col-md-2, .neko-col-md-3, .neko-col-md-4, .neko-col-md-5, .neko-col-md-6, .neko-col-md-7, .neko-col-md-8, .neko-col-md-9, .neko-col-md-10, .neko-col-md-11, .neko-col-md-12, .neko-col-md,
    .neko-col-md-auto, .neko-col-lg-1, .neko-col-lg-2, .neko-col-lg-3, .neko-col-lg-4, .neko-col-lg-5, .neko-col-lg-6, .neko-col-lg-7, .neko-col-lg-8, .neko-col-lg-9, .neko-col-lg-10, .neko-col-lg-11, .neko-col-lg-12, .neko-col-lg,
    .neko-col-lg-auto, .neko-col-xl-1, .neko-col-xl-2, .neko-col-xl-3, .neko-col-xl-4, .neko-col-xl-5, .neko-col-xl-6, .neko-col-xl-7, .neko-col-xl-8, .neko-col-xl-9, .neko-col-xl-10, .neko-col-xl-11, .neko-col-xl-12, .neko-col-xl,
    .neko-col-xl-auto {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 992px) {
    .neko-col-1, .neko-col-2, .neko-col-3, .neko-col-4, .neko-col-5, .neko-col-6, .neko-col-7, .neko-col-8, .neko-col-9, .neko-col-10, .neko-col-11, .neko-col-12, .neko-col,
    .neko-col-auto, .neko-col-sm-1, .neko-col-sm-2, .neko-col-sm-3, .neko-col-sm-4, .neko-col-sm-5, .neko-col-sm-6, .neko-col-sm-7, .neko-col-sm-8, .neko-col-sm-9, .neko-col-sm-10, .neko-col-sm-11, .neko-col-sm-12, .neko-col-sm,
    .neko-col-sm-auto, .neko-col-md-1, .neko-col-md-2, .neko-col-md-3, .neko-col-md-4, .neko-col-md-5, .neko-col-md-6, .neko-col-md-7, .neko-col-md-8, .neko-col-md-9, .neko-col-md-10, .neko-col-md-11, .neko-col-md-12, .neko-col-md,
    .neko-col-md-auto, .neko-col-lg-1, .neko-col-lg-2, .neko-col-lg-3, .neko-col-lg-4, .neko-col-lg-5, .neko-col-lg-6, .neko-col-lg-7, .neko-col-lg-8, .neko-col-lg-9, .neko-col-lg-10, .neko-col-lg-11, .neko-col-lg-12, .neko-col-lg,
    .neko-col-lg-auto, .neko-col-xl-1, .neko-col-xl-2, .neko-col-xl-3, .neko-col-xl-4, .neko-col-xl-5, .neko-col-xl-6, .neko-col-xl-7, .neko-col-xl-8, .neko-col-xl-9, .neko-col-xl-10, .neko-col-xl-11, .neko-col-xl-12, .neko-col-xl,
    .neko-col-xl-auto {
      padding-right: 8px;
      padding-left: 8px; } }
  @media (min-width: 1200px) {
    .neko-col-1, .neko-col-2, .neko-col-3, .neko-col-4, .neko-col-5, .neko-col-6, .neko-col-7, .neko-col-8, .neko-col-9, .neko-col-10, .neko-col-11, .neko-col-12, .neko-col,
    .neko-col-auto, .neko-col-sm-1, .neko-col-sm-2, .neko-col-sm-3, .neko-col-sm-4, .neko-col-sm-5, .neko-col-sm-6, .neko-col-sm-7, .neko-col-sm-8, .neko-col-sm-9, .neko-col-sm-10, .neko-col-sm-11, .neko-col-sm-12, .neko-col-sm,
    .neko-col-sm-auto, .neko-col-md-1, .neko-col-md-2, .neko-col-md-3, .neko-col-md-4, .neko-col-md-5, .neko-col-md-6, .neko-col-md-7, .neko-col-md-8, .neko-col-md-9, .neko-col-md-10, .neko-col-md-11, .neko-col-md-12, .neko-col-md,
    .neko-col-md-auto, .neko-col-lg-1, .neko-col-lg-2, .neko-col-lg-3, .neko-col-lg-4, .neko-col-lg-5, .neko-col-lg-6, .neko-col-lg-7, .neko-col-lg-8, .neko-col-lg-9, .neko-col-lg-10, .neko-col-lg-11, .neko-col-lg-12, .neko-col-lg,
    .neko-col-lg-auto, .neko-col-xl-1, .neko-col-xl-2, .neko-col-xl-3, .neko-col-xl-4, .neko-col-xl-5, .neko-col-xl-6, .neko-col-xl-7, .neko-col-xl-8, .neko-col-xl-9, .neko-col-xl-10, .neko-col-xl-11, .neko-col-xl-12, .neko-col-xl,
    .neko-col-xl-auto {
      padding-right: 8px;
      padding-left: 8px; } }

@media (max-width: 575px) {
  .neko-hidden-down {
    display: none; } }

.neko-col {
  -webkit-flex-basis: 0;
      -ms-flex-preferred-size: 0;
          flex-basis: 0;
  -webkit-box-flex: 1;
  -webkit-flex-grow: 1;
      -ms-flex-positive: 1;
          flex-grow: 1;
  max-width: 100%; }

.neko-col-auto {
  -webkit-box-flex: 0;
  -webkit-flex: 0 0 auto;
      -ms-flex: 0 0 auto;
          flex: 0 0 auto;
  width: auto; }

.neko-hidden-up {
  display: none; }

.neko-col-1 {
  width: 8.33333%; }

.neko-col-2 {
  width: 16.66667%; }

.neko-col-3 {
  width: 25%; }

.neko-col-4 {
  width: 33.33333%; }

.neko-col-5 {
  width: 41.66667%; }

.neko-col-6 {
  width: 50%; }

.neko-col-7 {
  width: 58.33333%; }

.neko-col-8 {
  width: 66.66667%; }

.neko-col-9 {
  width: 75%; }

.neko-col-10 {
  width: 83.33333%; }

.neko-col-11 {
  width: 91.66667%; }

.neko-col-12 {
  width: 100%; }

.neko-offset-1 {
  margin-left: 8.33333%; }

.neko-offset-2 {
  margin-left: 16.66667%; }

.neko-offset-3 {
  margin-left: 25%; }

.neko-offset-4 {
  margin-left: 33.33333%; }

.neko-offset-5 {
  margin-left: 41.66667%; }

.neko-offset-6 {
  margin-left: 50%; }

.neko-offset-7 {
  margin-left: 58.33333%; }

.neko-offset-8 {
  margin-left: 66.66667%; }

.neko-offset-9 {
  margin-left: 75%; }

.neko-offset-10 {
  margin-left: 83.33333%; }

.neko-offset-11 {
  margin-left: 91.66667%; }

@media (max-width: 767px) {
  .neko-hidden-sm-down {
    display: none; } }

@media (min-width: 576px) {
  .neko-col-sm {
    -webkit-flex-basis: 0;
        -ms-flex-preferred-size: 0;
            flex-basis: 0;
    -webkit-box-flex: 1;
    -webkit-flex-grow: 1;
        -ms-flex-positive: 1;
            flex-grow: 1;
    max-width: 100%; }
  .neko-col-sm-auto {
    -webkit-box-flex: 0;
    -webkit-flex: 0 0 auto;
        -ms-flex: 0 0 auto;
            flex: 0 0 auto;
    width: auto; }
  .neko-hidden-sm-up {
    display: none; }
  .neko-col-sm-1 {
    width: 8.33333%; }
  .neko-col-sm-2 {
    width: 16.66667%; }
  .neko-col-sm-3 {
    width: 25%; }
  .neko-col-sm-4 {
    width: 33.33333%; }
  .neko-col-sm-5 {
    width: 41.66667%; }
  .neko-col-sm-6 {
    width: 50%; }
  .neko-col-sm-7 {
    width: 58.33333%; }
  .neko-col-sm-8 {
    width: 66.66667%; }
  .neko-col-sm-9 {
    width: 75%; }
  .neko-col-sm-10 {
    width: 83.33333%; }
  .neko-col-sm-11 {
    width: 91.66667%; }
  .neko-col-sm-12 {
    width: 100%; }
  .neko-offset-sm-1 {
    margin-left: 8.33333%; }
  .neko-offset-sm-2 {
    margin-left: 16.66667%; }
  .neko-offset-sm-3 {
    margin-left: 25%; }
  .neko-offset-sm-4 {
    margin-left: 33.33333%; }
  .neko-offset-sm-5 {
    margin-left: 41.66667%; }
  .neko-offset-sm-6 {
    margin-left: 50%; }
  .neko-offset-sm-7 {
    margin-left: 58.33333%; }
  .neko-offset-sm-8 {
    margin-left: 66.66667%; }
  .neko-offset-sm-9 {
    margin-left: 75%; }
  .neko-offset-sm-10 {
    margin-left: 83.33333%; }
  .neko-offset-sm-11 {
    margin-left: 91.66667%; } }

@media (max-width: 991px) {
  .neko-hidden-md-down {
    display: none; } }

@media (min-width: 768px) {
  .neko-col-md {
    -webkit-flex-basis: 0;
        -ms-flex-preferred-size: 0;
            flex-basis: 0;
    -webkit-box-flex: 1;
    -webkit-flex-grow: 1;
        -ms-flex-positive: 1;
            flex-grow: 1;
    max-width: 100%; }
  .neko-col-md-auto {
    -webkit-box-flex: 0;
    -webkit-flex: 0 0 auto;
        -ms-flex: 0 0 auto;
            flex: 0 0 auto;
    width: auto; }
  .neko-hidden-md-up {
    display: none; }
  .neko-col-md-1 {
    width: 8.33333%; }
  .neko-col-md-2 {
    width: 16.66667%; }
  .neko-col-md-3 {
    width: 25%; }
  .neko-col-md-4 {
    width: 33.33333%; }
  .neko-col-md-5 {
    width: 41.66667%; }
  .neko-col-md-6 {
    width: 50%; }
  .neko-col-md-7 {
    width: 58.33333%; }
  .neko-col-md-8 {
    width: 66.66667%; }
  .neko-col-md-9 {
    width: 75%; }
  .neko-col-md-10 {
    width: 83.33333%; }
  .neko-col-md-11 {
    width: 91.66667%; }
  .neko-col-md-12 {
    width: 100%; }
  .neko-offset-md-1 {
    margin-left: 8.33333%; }
  .neko-offset-md-2 {
    margin-left: 16.66667%; }
  .neko-offset-md-3 {
    margin-left: 25%; }
  .neko-offset-md-4 {
    margin-left: 33.33333%; }
  .neko-offset-md-5 {
    margin-left: 41.66667%; }
  .neko-offset-md-6 {
    margin-left: 50%; }
  .neko-offset-md-7 {
    margin-left: 58.33333%; }
  .neko-offset-md-8 {
    margin-left: 66.66667%; }
  .neko-offset-md-9 {
    margin-left: 75%; }
  .neko-offset-md-10 {
    margin-left: 83.33333%; }
  .neko-offset-md-11 {
    margin-left: 91.66667%; } }

@media (max-width: 1199px) {
  .neko-hidden-lg-down {
    display: none; } }

@media (min-width: 992px) {
  .neko-col-lg {
    -webkit-flex-basis: 0;
        -ms-flex-preferred-size: 0;
            flex-basis: 0;
    -webkit-box-flex: 1;
    -webkit-flex-grow: 1;
        -ms-flex-positive: 1;
            flex-grow: 1;
    max-width: 100%; }
  .neko-col-lg-auto {
    -webkit-box-flex: 0;
    -webkit-flex: 0 0 auto;
        -ms-flex: 0 0 auto;
            flex: 0 0 auto;
    width: auto; }
  .neko-hidden-lg-up {
    display: none; }
  .neko-col-lg-1 {
    width: 8.33333%; }
  .neko-col-lg-2 {
    width: 16.66667%; }
  .neko-col-lg-3 {
    width: 25%; }
  .neko-col-lg-4 {
    width: 33.33333%; }
  .neko-col-lg-5 {
    width: 41.66667%; }
  .neko-col-lg-6 {
    width: 50%; }
  .neko-col-lg-7 {
    width: 58.33333%; }
  .neko-col-lg-8 {
    width: 66.66667%; }
  .neko-col-lg-9 {
    width: 75%; }
  .neko-col-lg-10 {
    width: 83.33333%; }
  .neko-col-lg-11 {
    width: 91.66667%; }
  .neko-col-lg-12 {
    width: 100%; }
  .neko-offset-lg-1 {
    margin-left: 8.33333%; }
  .neko-offset-lg-2 {
    margin-left: 16.66667%; }
  .neko-offset-lg-3 {
    margin-left: 25%; }
  .neko-offset-lg-4 {
    margin-left: 33.33333%; }
  .neko-offset-lg-5 {
    margin-left: 41.66667%; }
  .neko-offset-lg-6 {
    margin-left: 50%; }
  .neko-offset-lg-7 {
    margin-left: 58.33333%; }
  .neko-offset-lg-8 {
    margin-left: 66.66667%; }
  .neko-offset-lg-9 {
    margin-left: 75%; }
  .neko-offset-lg-10 {
    margin-left: 83.33333%; }
  .neko-offset-lg-11 {
    margin-left: 91.66667%; } }

.neko-hidden-xl-down {
  display: none; }

@media (min-width: 1200px) {
  .neko-col-xl {
    -webkit-flex-basis: 0;
        -ms-flex-preferred-size: 0;
            flex-basis: 0;
    -webkit-box-flex: 1;
    -webkit-flex-grow: 1;
        -ms-flex-positive: 1;
            flex-grow: 1;
    max-width: 100%; }
  .neko-col-xl-auto {
    -webkit-box-flex: 0;
    -webkit-flex: 0 0 auto;
        -ms-flex: 0 0 auto;
            flex: 0 0 auto;
    width: auto; }
  .neko-hidden-xl-up {
    display: none; }
  .neko-col-xl-1 {
    width: 8.33333%; }
  .neko-col-xl-2 {
    width: 16.66667%; }
  .neko-col-xl-3 {
    width: 25%; }
  .neko-col-xl-4 {
    width: 33.33333%; }
  .neko-col-xl-5 {
    width: 41.66667%; }
  .neko-col-xl-6 {
    width: 50%; }
  .neko-col-xl-7 {
    width: 58.33333%; }
  .neko-col-xl-8 {
    width: 66.66667%; }
  .neko-col-xl-9 {
    width: 75%; }
  .neko-col-xl-10 {
    width: 83.33333%; }
  .neko-col-xl-11 {
    width: 91.66667%; }
  .neko-col-xl-12 {
    width: 100%; }
  .neko-offset-xl-1 {
    margin-left: 8.33333%; }
  .neko-offset-xl-2 {
    margin-left: 16.66667%; }
  .neko-offset-xl-3 {
    margin-left: 25%; }
  .neko-offset-xl-4 {
    margin-left: 33.33333%; }
  .neko-offset-xl-5 {
    margin-left: 41.66667%; }
  .neko-offset-xl-6 {
    margin-left: 50%; }
  .neko-offset-xl-7 {
    margin-left: 58.33333%; }
  .neko-offset-xl-8 {
    margin-left: 66.66667%; }
  .neko-offset-xl-9 {
    margin-left: 75%; }
  .neko-offset-xl-10 {
    margin-left: 83.33333%; }
  .neko-offset-xl-11 {
    margin-left: 91.66667%; } }

.neko-btn {
  display: inline-block;
  height: 36px;
  line-height: 36px;
  min-width: 88px;
  margin: 0;
  padding: 0 5px;
  font-family: "Roboto", sans-serif;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  text-decoration: none;
  outline: none;
  position: relative;
  border-radius: 2px;
  -webkit-user-select: none;
     -moz-user-select: none;
      -ms-user-select: none;
          user-select: none;
  overflow: hidden;
  text-align: center;
  -webkit-tap-highlight-color: transparent;
  -webkit-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, -webkit-transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
  transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, -webkit-transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
  -o-transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, -o-transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
  transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
  transition: opacity 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, -webkit-transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, -o-transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; }
  @media (min-width: 576px) {
    .neko-btn {
      -webkit-transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
      -o-transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
      transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; } }

.neko-btn {
  color: #ffffff;
  border: 1px solid #5a666f;
  background-color: #5a666f; }
  .neko-btn:hover {
    background-color: #313439; }
  .neko-btn.outline {
    color: #5a666f;
    background-color: transparent; }
    .neko-btn.outline:hover {
      color: #ffffff;
      background-color: #5a666f; }
  .neko-btn.shadow:hover {
    -webkit-box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px;
            box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px; }
  .neko-btn.merge {
    color: #5a666f;
    border: 0;
    background-color: transparent; }
    .neko-btn.merge:hover {
      color: #ffffff;
      background-color: #5a666f; }

.neko-btn.neko-color-black {
  color: #ffffff;
  border: 1px solid #5a666f;
  background-color: #5a666f; }
  .neko-btn.neko-color-black:hover {
    background-color: #313439; }

.neko-btn.neko-color-white {
  color: #ffffff;
  border: 1px solid #ffffff;
  background-color: #ffffff; }

.neko-btn.neko-color-red {
  color: #ffffff;
  border: 1px solid #d32f2f;
  background-color: #d32f2f; }
  .neko-btn.neko-color-red:hover {
    background-color: #c62828; }

.neko-btn.neko-color-pink {
  color: #ffffff;
  border: 1px solid #c2185b;
  background-color: #c2185b; }
  .neko-btn.neko-color-pink:hover {
    background-color: #880e4f; }

.neko-btn.neko-color-purple {
  color: #ffffff;
  border: 1px solid #7b1fa2;
  background-color: #7b1fa2; }
  .neko-btn.neko-color-purple:hover {
    background-color: #c62828; }

.neko-btn.neko-color-deep-purple {
  color: #ffffff;
  border: 1px solid #512da8;
  background-color: #512da8; }
  .neko-btn.neko-color-deep-purple:hover {
    background-color: #4527a0; }

.neko-btn.neko-color-indigo {
  color: #ffffff;
  border: 1px solid #303f9f;
  background-color: #303f9f; }
  .neko-btn.neko-color-indigo:hover {
    background-color: #283593; }

.neko-btn.neko-color-blue {
  color: #ffffff;
  border: 1px solid #1976d2;
  background-color: #1976d2; }
  .neko-btn.neko-color-blue:hover {
    background-color: #1565c0; }

.neko-btn.neko-color-light-blue {
  color: #ffffff;
  border: 1px solid #0288d1;
  background-color: #0288d1; }
  .neko-btn.neko-color-light-blue:hover {
    background-color: #0277bd; }

.neko-btn.neko-color-cyan {
  color: #ffffff;
  border: 1px solid #0097a7;
  background-color: #0097a7; }
  .neko-btn.neko-color-cyan:hover {
    background-color: #00838f; }

.neko-btn.neko-color-teal {
  color: #ffffff;
  border: 1px solid #00796b;
  background-color: #00796b; }
  .neko-btn.neko-color-teal:hover {
    background-color: #00695c; }

.neko-btn.neko-color-green {
  color: #ffffff;
  border: 1px solid #388e3c;
  background-color: #388e3c; }
  .neko-btn.neko-color-green:hover {
    background-color: #2e7d32; }

.neko-btn.neko-color-light-green {
  color: #ffffff;
  border: 1px solid #689f38;
  background-color: #689f38; }
  .neko-btn.neko-color-light-green:hover {
    background-color: #558b2f; }

.neko-btn.neko-color-lime {
  color: #ffffff;
  border: 1px solid #afb42b;
  background-color: #afb42b; }
  .neko-btn.neko-color-lime:hover {
    background-color: #9e9d24; }

.neko-btn.neko-color-yellow {
  color: #ffffff;
  border: 1px solid #fbc02d;
  background-color: #fbc02d; }
  .neko-btn.neko-color-yellow:hover {
    background-color: #f9a825; }

.neko-btn.neko-color-amber {
  color: #ffffff;
  border: 1px solid #ffa000;
  background-color: #ffa000; }
  .neko-btn.neko-color-amber:hover {
    background-color: #ff8f00; }

.neko-btn.neko-color-orange {
  color: #ffffff;
  border: 1px solid #f57c00;
  background-color: #f57c00; }
  .neko-btn.neko-color-orange:hover {
    background-color: #ef6c00; }

.neko-btn.neko-color-deep-orange {
  color: #ffffff;
  border: 1px solid #e64a19;
  background-color: #e64a19; }
  .neko-btn.neko-color-deep-orange:hover {
    background-color: #d84315; }

.neko-btn.neko-color-brown {
  color: #ffffff;
  border: 1px solid #5d4037;
  background-color: #5d4037; }
  .neko-btn.neko-color-brown:hover {
    background-color: #4e342e; }

.neko-btn.neko-color-grey {
  color: #ffffff;
  border: 1px solid #616161;
  background-color: #616161; }
  .neko-btn.neko-color-grey:hover {
    background-color: #424242; }

.neko-btn.neko-color-blue-grey {
  color: #ffffff;
  border: 1px solid #455a64;
  background-color: #455a64; }
  .neko-btn.neko-color-blue-grey:hover {
    background-color: #37474f; }

.neko-btn.outline.neko-color-black {
  color: #5a666f;
  background-color: transparent; }
  .neko-btn.outline.neko-color-black:hover {
    color: #ffffff;
    background-color: #5a666f; }

.neko-btn.outline.neko-color-white {
  color: #ffffff;
  background-color: transparent; }
  .neko-btn.outline.neko-color-white:hover {
    color: #ffffff;
    background-color: #ffffff; }

.neko-btn.outline.neko-color-red {
  color: #d32f2f;
  background-color: transparent; }
  .neko-btn.outline.neko-color-red:hover {
    color: #ffffff;
    background-color: #d32f2f; }

.neko-btn.outline.neko-color-pink {
  color: #c2185b;
  background-color: transparent; }
  .neko-btn.outline.neko-color-pink:hover {
    color: #ffffff;
    background-color: #c2185b; }

.neko-btn.outline.neko-color-purple {
  color: #7b1fa2;
  background-color: transparent; }
  .neko-btn.outline.neko-color-purple:hover {
    color: #ffffff;
    background-color: #7b1fa2; }

.neko-btn.outline.neko-color-deep-purple {
  color: #512da8;
  background-color: transparent; }
  .neko-btn.outline.neko-color-deep-purple:hover {
    color: #ffffff;
    background-color: #512da8; }

.neko-btn.outline.neko-color-indigo {
  color: #303f9f;
  background-color: transparent; }
  .neko-btn.outline.neko-color-indigo:hover {
    color: #ffffff;
    background-color: #303f9f; }

.neko-btn.outline.neko-color-blue {
  color: #1976d2;
  background-color: transparent; }
  .neko-btn.outline.neko-color-blue:hover {
    color: #ffffff;
    background-color: #1976d2; }

.neko-btn.outline.neko-color-light-blue {
  color: #0288d1;
  background-color: transparent; }
  .neko-btn.outline.neko-color-light-blue:hover {
    color: #ffffff;
    background-color: #0288d1; }

.neko-btn.outline.neko-color-cyan {
  color: #0097a7;
  background-color: transparent; }
  .neko-btn.outline.neko-color-cyan:hover {
    color: #ffffff;
    background-color: #0097a7; }

.neko-btn.outline.neko-color-teal {
  color: #00796b;
  background-color: transparent; }
  .neko-btn.outline.neko-color-teal:hover {
    color: #ffffff;
    background-color: #00796b; }

.neko-btn.outline.neko-color-green {
  color: #388e3c;
  background-color: transparent; }
  .neko-btn.outline.neko-color-green:hover {
    color: #ffffff;
    background-color: #388e3c; }

.neko-btn.outline.neko-color-light-green {
  color: #689f38;
  background-color: transparent; }
  .neko-btn.outline.neko-color-light-green:hover {
    color: #ffffff;
    background-color: #689f38; }

.neko-btn.outline.neko-color-lime {
  color: #afb42b;
  background-color: transparent; }
  .neko-btn.outline.neko-color-lime:hover {
    color: #ffffff;
    background-color: #afb42b; }

.neko-btn.outline.neko-color-yellow {
  color: #fbc02d;
  background-color: transparent; }
  .neko-btn.outline.neko-color-yellow:hover {
    color: #ffffff;
    background-color: #fbc02d; }

.neko-btn.outline.neko-color-amber {
  color: #ffa000;
  background-color: transparent; }
  .neko-btn.outline.neko-color-amber:hover {
    color: #ffffff;
    background-color: #ffa000; }

.neko-btn.outline.neko-color-orange {
  color: #f57c00;
  background-color: transparent; }
  .neko-btn.outline.neko-color-orange:hover {
    color: #ffffff;
    background-color: #f57c00; }

.neko-btn.outline.neko-color-deep-orange {
  color: #e64a19;
  background-color: transparent; }
  .neko-btn.outline.neko-color-deep-orange:hover {
    color: #ffffff;
    background-color: #e64a19; }

.neko-btn.outline.neko-color-brown {
  color: #5d4037;
  background-color: transparent; }
  .neko-btn.outline.neko-color-brown:hover {
    color: #ffffff;
    background-color: #5d4037; }

.neko-btn.outline.neko-color-grey {
  color: #616161;
  background-color: transparent; }
  .neko-btn.outline.neko-color-grey:hover {
    color: #ffffff;
    background-color: #616161; }

.neko-btn.outline.neko-color-blue-grey {
  color: #455a64;
  background-color: transparent; }
  .neko-btn.outline.neko-color-blue-grey:hover {
    color: #ffffff;
    background-color: #455a64; }

.neko-btn.merge.neko-color-black {
  color: #5a666f;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-black:hover {
    color: #ffffff;
    background-color: #5a666f; }

.neko-btn.merge.neko-color-white {
  color: #ffffff;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-white:hover {
    color: #ffffff;
    background-color: #ffffff; }

.neko-btn.merge.neko-color-red {
  color: #d32f2f;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-red:hover {
    color: #ffffff;
    background-color: #d32f2f; }

.neko-btn.merge.neko-color-pink {
  color: #c2185b;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-pink:hover {
    color: #ffffff;
    background-color: #c2185b; }

.neko-btn.merge.neko-color-purple {
  color: #7b1fa2;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-purple:hover {
    color: #ffffff;
    background-color: #7b1fa2; }

.neko-btn.merge.neko-color-deep-purple {
  color: #512da8;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-deep-purple:hover {
    color: #ffffff;
    background-color: #512da8; }

.neko-btn.merge.neko-color-indigo {
  color: #303f9f;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-indigo:hover {
    color: #ffffff;
    background-color: #303f9f; }

.neko-btn.merge.neko-color-blue {
  color: #1976d2;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-blue:hover {
    color: #ffffff;
    background-color: #1976d2; }

.neko-btn.merge.neko-color-light-blue {
  color: #0288d1;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-light-blue:hover {
    color: #ffffff;
    background-color: #0288d1; }

.neko-btn.merge.neko-color-cyan {
  color: #0097a7;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-cyan:hover {
    color: #ffffff;
    background-color: #0097a7; }

.neko-btn.merge.neko-color-teal {
  color: #00796b;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-teal:hover {
    color: #ffffff;
    background-color: #00796b; }

.neko-btn.merge.neko-color-green {
  color: #388e3c;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-green:hover {
    color: #ffffff;
    background-color: #388e3c; }

.neko-btn.merge.neko-color-light-green {
  color: #689f38;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-light-green:hover {
    color: #ffffff;
    background-color: #689f38; }

.neko-btn.merge.neko-color-lime {
  color: #afb42b;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-lime:hover {
    color: #ffffff;
    background-color: #afb42b; }

.neko-btn.merge.neko-color-yellow {
  color: #fbc02d;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-yellow:hover {
    color: #ffffff;
    background-color: #fbc02d; }

.neko-btn.merge.neko-color-amber {
  color: #ffa000;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-amber:hover {
    color: #ffffff;
    background-color: #ffa000; }

.neko-btn.merge.neko-color-orange {
  color: #f57c00;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-orange:hover {
    color: #ffffff;
    background-color: #f57c00; }

.neko-btn.merge.neko-color-deep-orange {
  color: #e64a19;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-deep-orange:hover {
    color: #ffffff;
    background-color: #e64a19; }

.neko-btn.merge.neko-color-brown {
  color: #5d4037;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-brown:hover {
    color: #ffffff;
    background-color: #5d4037; }

.neko-btn.merge.neko-color-grey {
  color: #616161;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-grey:hover {
    color: #ffffff;
    background-color: #616161; }

.neko-btn.merge.neko-color-blue-grey {
  color: #455a64;
  border: 0;
  background-color: transparent; }
  .neko-btn.merge.neko-color-blue-grey:hover {
    color: #ffffff;
    background-color: #455a64; }

.neko-helper-center, .neko-container, .neko-container-fluid {
  display: block;
  margin-left: auto;
  margin-right: auto; }

.neko-hidden {
  display: none; }

.neko-m-l-1 {
  margin-left: 8px; }

.neko-m-r-1 {
  margin-right: 8px; }

.neko-m-t-1 {
  margin-top: 8px; }

.neko-m-b-1 {
  margin-bottom: 8px; }

.neko-m-h-1 {
  margin-left: 8px;
  margin-right: 8px; }

.neko-m-v-1 {
  margin-top: 8px;
  margin-bottom: 8px; }

.neko-m-l-2 {
  margin-left: 16px; }

.neko-m-r-2 {
  margin-right: 16px; }

.neko-m-t-2 {
  margin-top: 16px; }

.neko-m-b-2 {
  margin-bottom: 16px; }

.neko-m-h-2 {
  margin-left: 16px;
  margin-right: 16px; }

.neko-m-v-2 {
  margin-top: 16px;
  margin-bottom: 16px; }

.neko-m-l-3 {
  margin-left: 24px; }

.neko-m-r-3 {
  margin-right: 24px; }

.neko-m-t-3 {
  margin-top: 24px; }

.neko-m-b-3 {
  margin-bottom: 24px; }

.neko-m-h-3 {
  margin-left: 24px;
  margin-right: 24px; }

.neko-m-v-3 {
  margin-top: 24px;
  margin-bottom: 24px; }

.neko-m-l-4 {
  margin-left: 32px; }

.neko-m-r-4 {
  margin-right: 32px; }

.neko-m-t-4 {
  margin-top: 32px; }

.neko-m-b-4 {
  margin-bottom: 32px; }

.neko-m-h-4 {
  margin-left: 32px;
  margin-right: 32px; }

.neko-m-v-4 {
  margin-top: 32px;
  margin-bottom: 32px; }

.neko-m-l-5 {
  margin-left: 40px; }

.neko-m-r-5 {
  margin-right: 40px; }

.neko-m-t-5 {
  margin-top: 40px; }

.neko-m-b-5 {
  margin-bottom: 40px; }

.neko-m-h-5 {
  margin-left: 40px;
  margin-right: 40px; }

.neko-m-v-5 {
  margin-top: 40px;
  margin-bottom: 40px; }

.neko-p-l-1 {
  padding-left: 8px; }

.neko-p-r-1 {
  padding-right: 8px; }

.neko-p-t-1 {
  padding-top: 8px; }

.neko-p-b-1 {
  padding-bottom: 8px; }

.neko-p-h-1 {
  padding-left: 8px;
  padding-right: 8px; }

.neko-p-v-1 {
  padding-top: 8px;
  padding-bottom: 8px; }

.neko-p-l-2 {
  padding-left: 16px; }

.neko-p-r-2 {
  padding-right: 16px; }

.neko-p-t-2 {
  padding-top: 16px; }

.neko-p-b-2 {
  padding-bottom: 16px; }

.neko-p-h-2 {
  padding-left: 16px;
  padding-right: 16px; }

.neko-p-v-2 {
  padding-top: 16px;
  padding-bottom: 16px; }

.neko-p-l-3 {
  padding-left: 24px; }

.neko-p-r-3 {
  padding-right: 24px; }

.neko-p-t-3 {
  padding-top: 24px; }

.neko-p-b-3 {
  padding-bottom: 24px; }

.neko-p-h-3 {
  padding-left: 24px;
  padding-right: 24px; }

.neko-p-v-3 {
  padding-top: 24px;
  padding-bottom: 24px; }

.neko-p-l-4 {
  padding-left: 32px; }

.neko-p-r-4 {
  padding-right: 32px; }

.neko-p-t-4 {
  padding-top: 32px; }

.neko-p-b-4 {
  padding-bottom: 32px; }

.neko-p-h-4 {
  padding-left: 32px;
  padding-right: 32px; }

.neko-p-v-4 {
  padding-top: 32px;
  padding-bottom: 32px; }

.neko-p-l-5 {
  padding-left: 40px; }

.neko-p-r-5 {
  padding-right: 40px; }

.neko-p-t-5 {
  padding-top: 40px; }

.neko-p-b-5 {
  padding-bottom: 40px; }

.neko-p-h-5 {
  padding-left: 40px;
  padding-right: 40px; }

.neko-p-v-5 {
  padding-top: 40px;
  padding-bottom: 40px; }

================================================
FILE: src/scss/cplayer.scss
================================================
$name-prefix: "cp-";

$primaryColor: #F44336;
$primaryDarkColor: #B71C1C;
$accentLightColor: #BDBDBD;
$accentColor: #757575;
$accentDarkColor: #616161;
$TextColor: #424242;
$backgroundColor: #fff;
$base-font-size: 12px;
$enable-transitions: true;

@mixin transition($transition...) {
  @if $enable-transitions {
    transition: $transition;
  }
}

@function strip-units($number){
  @return $number / ($number * 0 + 1);
}

@function toem($target-size,$context:$base-font-size){
  @if not unitless($target-size){
    $target-size: strip-units($target-size);
  }
  @if not unitless($context){
    $context: strip-units($context);
  }
  @return ($target-size / $context) * 1em;
}

@mixin box_shadow ($level) {
    @if $level == 1 {
        box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
    } @else if $level == 2 {
        box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px;
    } @else if $level == 3 {
        box-shadow: rgba(0, 0, 0, 0.188235) 0px 10px 30px, rgba(0, 0, 0, 0.227451) 0px 6px 10px;
    } @else if $level == 4 {
        box-shadow: rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px;
    } @else if $level == 5 {
        box-shadow: rgba(0, 0, 0, 0.298039) 0px 19px 60px, rgba(0, 0, 0, 0.219608) 0px 15px 20px;
    }
}

@keyframes miss-pointer-event {
    0% {pointer-events: none;}
    50% {pointer-events: none;}
    100% {pointer-events: auto;}
}

$mainbody-height: toem(50px);
$mainbody-width: toem(300px);
$big-progress-height: toem(35px);
$border-radius: toem(4px);

c-player {
    display: inline-flex;
    flex-direction: column;
    position: relative;
    font-size: $base-font-size;
    width: 300px;
    text-align: left;
	.#{$name-prefix}mainbody {
        display: flex;
        align-items: center;
        flex-direction: row;
		min-height: $mainbody-height;
        min-width: $mainbody-width;
        flex-wrap: wrap;
        background-color: $backgroundColor;
        position: relative;
        z-index: 1;
        border-radius: $border-radius;
        @include box_shadow(1);
        > * {
            margin-right: toem(7px)
        }
        &:hover{
            & + .#{$name-prefix}drop-down-menu.#{$name-prefix}drop-down-menu-info {
                height: toem(25px);
            }
        }
    }
    .#{$name-prefix}drop-down-menu {
        $lineheight: 25px;
        $margin: 2px;
        margin: toem(0px) toem($margin) 0;
        display: block;
        text-align: center;
        font-size: toem(12px);
        line-height: toem($lineheight);
        background-color: rgba($backgroundColor, 0.6);
        position: relative;
        z-index: 0;
        height: toem(0px);
        overflow: hidden;
        border-bottom-right-radius: $border-radius;
        border-bottom-left-radius: $border-radius;
        @include box_shadow(1);
        @include transition(transform 0.25s ease 0.1s, height 0.25s ease 0.1s);
        span.#{$name-prefix}audio-title {
            max-width: toem(170px);
            overflow: hidden;
            text-overflow: ellipsis;
        }
        span.#{$name-prefix}audio-artist {
            opacity: 0.6;
            max-width: toem(100px);
            overflow: hidden;
            text-overflow: ellipsis;
        }
        &.#{$name-prefix}drop-down-menu-top {
            order: -1;
        }
        &.#{$name-prefix}drop-down-menu-none {
            display: none;
        }
        &.#{$name-prefix}drop-down-menu-bottom {
            order: 1;
        }
        &.#{$name-prefix}drop-down-menu-info .#{$name-prefix}playlist {
            display: none;
        }
        &.#{$name-prefix}drop-down-menu-info:hover {
            height: toem(25px);
        }
        &.#{$name-prefix}drop-down-menu-playlist .#{$name-prefix}audio-info {
            display: none;
        }
        &.#{$name-prefix}drop-down-menu-playlist {
            height: auto;
        }
    }
    .#{$name-prefix}playlist {
        margin: toem(0px);
        padding: toem(0px);
        list-style: none;
        text-align: left;
        li {
            padding: toem(0px) toem(10px);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            .#{$name-prefix}play-icon {
                display: inline-block;
                width: toem(12px);
                height: toem(12px);
                vertical-align: text-top;
                margin: 0px toem(5px) 0 0;
                fill: rgba($primaryDarkColor, 0.8);
            }
            cursor: pointer;
        }
    }
    .#{$name-prefix}playlist-artist {
        opacity: 0.46;
    }
    .#{$name-prefix}center-container {
        flex-grow: 1;
        position: relative;
        height: $mainbody-height;
        pointer-events: auto;
        .#{$name-prefix}controls {
            opacity: 0;
        }
        &:hover {
            .#{$name-prefix}controls {
                opacity: 1;
                animation: miss-pointer-event 100ms;
                animation-fill-mode: both;
            }
            .#{$name-prefix}lyric {
                opacity: 0;
            }
        }
    }
    .#{$name-prefix}controls {
        display: flex;
        justify-content: center;
        align-items: center;
        position: absolute;
        width: 100%;
        height: 100%;
        z-index: 1;
        @include transition(opacity 0.25s ease);
        > * {
            margin-right: toem(15px);
            -webkit-tap-highlight-color: transparent;
        }
    }
    .#{$name-prefix}lyric {
        $margin-left: toem(10px);
        $margin-top: toem(5px);
        display: flex;
        align-items: center;
        position: absolute;
        width: calc(100% - #{$margin-left * 2});
        height: calc(100% - #{$margin-top * 2});
        overflow: hidden;
        margin: $margin-top $margin-left;
        font-size: toem(14px);
        @include transition(opacity 0.25s ease);
    }
    .#{$name-prefix}lyric-text {
        flex-grow: 1;
        white-space: nowrap;
        text-align: center;
        color: $TextColor;
        .#{$name-prefix}lyric-text-sub {
            display: block;
            color: $accentColor;
            font-size: toem(12px, 14px);
        }
        .#{$name-prefix}lyric-text-zoomout {
            font-size: toem(12px);
            transform: translateY(toem(1px));
            display: inline-block;
            margin: 0px toem(2px, 14px);
        }
    }
    .#{$name-prefix}progress-container {
        flex-basis: 100%;
        margin: toem(0px);
        opacity: 0.8;
        position: absolute;
        width: 100%;
        bottom: 0px;
        border-bottom-left-radius: $border-radius;
        border-bottom-right-radius: $border-radius;
        overflow: hidden;
        pointer-events: none;
        > span {
            display: none
        }
    }
    &.#{$name-prefix}big .#{$name-prefix}progress-container {
        position: relative;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        height: $big-progress-height;
        padding: 0 toem(5px);
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
        justify-content: space-between;
        pointer-events: auto;
        color: $TextColor;
        > span {
            display: inline;
            margin: 0 toem(15px);
            font-size: 0.8em
        }
    }
    .#{$name-prefix}progress {
        overflow: hidden;
        .#{$name-prefix}progress-fill {
            display: block;
            height: toem(2px);
            background: $primaryColor;
        }
        .#{$name-prefix}progress-button {
            display: none;
        }
    }
    &.#{$name-prefix}big .#{$name-prefix}progress {
        @extend %#{$name-prefix}-controller;
        overflow: visible;
        .#{$name-prefix}progress-fill {
            @extend %#{$name-prefix}-controller-fill;
        }
        .#{$name-prefix}progress-button {
            @extend %#{$name-prefix}-controller-button;
        }
    }
    .#{$name-prefix}poster {
        width: $mainbody-height;
        height: $mainbody-height;
        background-repeat: repeat;
        background-position: 50% 50%;
        background-size: cover;
        background-color: #fafafa;
        background-origin: padding-box;
        background-attachment: scroll;
        border-right: toem(1px) solid #eee;
        background-image: url("../defaultposter.jpg");
        border-top-left-radius: $border-radius;
        border-bottom-left-radius: $border-radius;
        overflow: hidden;
        video {
            width: 100%;
            height: 100%;
        }
    }
    &.#{$name-prefix}big .#{$name-prefix}poster {
        flex-basis: 100%;
        width: $mainbody-width;
        height: $mainbody-width * 0.5625;
        border-right: none;
        border-bottom: toem(1px) solid #eee;
        margin: 0;
        border-top-left-radius: $border-radius;
        border-bottom-left-radius: 0;
        border-top-right-radius: $border-radius;
        border-bottom-right-radius: 0;
    }
    .#{$name-prefix}play-icon {
        $size: toem(12.5px);
        width:$size;
        height:$size;
        display: block;
        overflow: hidden;
        position: relative;
        .#{$name-prefix}play-icon-left {
            height: 100%;
            float: left;
            background-color: $primaryColor;
            width: 36%;
            @include transition(width 0.25s ease);
            overflow: hidden;
        }
        .#{$name-prefix}play-icon-right {
            height: 100%;
            float: right;
            background-color: $primaryColor;
            width: 36%;
            @include transition(width 0.25s ease);
            overflow: hidden;
        }
        .#{$name-prefix}play-icon-triangle-1 {
            transform: translate(0, -100%)
        }
        .#{$name-prefix}play-icon-triangle-2 {
            transform: translate(0, 100%)
        }
        .#{$name-prefix}play-icon-triangle-1,
        .#{$name-prefix}play-icon-triangle-2 {
            position: absolute;
            top: 0;
            right: 0;
            background-color: transparent;
            width: 0;
            height: 0;
            border-right: $size solid $backgroundColor;
            border-top: $size / 2 solid transparent;
            border-bottom: $size / 2 solid transparent;
            @include transition(transform 0.25s ease);
        }
        &.#{$name-prefix}play-icon-paused {
            .#{$name-prefix}play-icon-left {
                width: 50%;
            }
            .#{$name-prefix}play-icon-right {
                width: 50%;
            }
            .#{$name-prefix}play-icon-triangle-1 {
                transform: translate(0, -50%);
            }
            .#{$name-prefix}play-icon-triangle-2 {
                transform: translate(0, 50%);
            }
        }
        &.#{$name-prefix}play-icon-hover {
            .#{$name-prefix}play-icon-left {
                background-color: $primaryDarkColor;
            }
            .#{$name-prefix}play-icon-right {
                background-color: $primaryDarkColor;
            }
        }
    }
    .#{$name-prefix}play-button,
    .#{$name-prefix}volume-button,
    .#{$name-prefix}prev-button,
    .#{$name-prefix}list-button,
    .#{$name-prefix}mode-button,
    .#{$name-prefix}next-button {
        display: inline-block;
        $size: toem(12.5px);
        width: $size;
        height: $size;
        cursor: pointer;
    }
    .#{$name-prefix}volume-button {
        width: toem(20px);
        position: relative;
    }
    .#{$name-prefix}list-button {
        $size: toem(17.5px);
        width: $size;
        height: $size;
    }
    .#{$name-prefix}mode-button {
        $size: toem(19px);
        width: $size;
        height: $size;
    }
    .#{$name-prefix}play-button {
        $size: toem(30px);
        height: $size;
        width: $size;
        border: $primaryColor solid 1px;
        border-radius: 50%;
        .#{$name-prefix}play-icon.#{$name-prefix}play-icon-paused {
            margin: toem(8.25px);
        }
        .#{$name-prefix}play-icon {
            margin: toem(8.25px);
        }
        &:hover {
            border-color: $primaryDarkColor;
            @extend .#{$name-prefix}play-icon.#{$name-prefix}play-icon-hover;
        }
    }
    .#{$name-prefix}icon {
        $size: toem(12.5px);
        height: $size;
        width: $size;
        path {
            fill: $primaryColor;
        }
        &:hover {
            path {
                fill: $primaryDarkColor;
            }
        }
        &.#{$name-prefix}icon-dark {
            path {
                fill: $accentColor;
            }
            &:hover {
                path {
                    fill: $accentDarkColor;
                }
            }
        }
    }
    .#{$name-prefix}prev-icon {
        @extend .#{$name-prefix}icon;
    }
    .#{$name-prefix}next-icon {
        @extend .#{$name-prefix}icon;
        transform: rotateZ(180deg);
    }
    .#{$name-prefix}volume-icon {
        @extend .#{$name-prefix}icon;
        width: toem(20px);
    }
    .#{$name-prefix}random-icon,
    .#{$name-prefix}single-icon,
    .#{$name-prefix}loop-icon {
        @extend .#{$name-prefix}icon;
        $size: toem(19px);
        width: $size;
        height: $size;
        display: none;
    }
    .#{$name-prefix}mode-button[data-mode=listloop] .#{$name-prefix}loop-icon,
    .#{$name-prefix}mode-button[data-mode=singlecycle] .#{$name-prefix}single-icon,
    .#{$name-prefix}mode-button[data-mode=listrandom] .#{$name-prefix}random-icon {
        display: block;
    }
    .#{$name-prefix}list-icon {
        @extend .#{$name-prefix}icon;
        $size: toem(17.5px);
        width: $size;
        height: $size;
    }
    .#{$name-prefix}volume-container {
        $height: toem(25px);
        $width: toem(130px);
        position: absolute;
        left: 50%;
        top: -7px;
        height: $height;
        width: $width;
        transform: translateX(-50%) translateY(-120%);
        z-index: 1;
        visibility: hidden;
        text-align: left;
        border-radius: $border-radius;
        padding: 0 15px;
        background: $backgroundColor;
        @include box_shadow(1);
        &.#{$name-prefix}volume-container-show {
            visibility: visible;
        }
        &:hover {
            visibility: visible;
        }
        &:before {
            $size: toem(10px);
            content: '';
            width: toem(0px);
            height: toem(0px);
            display: inline-block;
            border-top: $size solid $backgroundColor;
            border-left: $size solid transparent;
            border-right: $size solid transparent;
            position: absolute;
            bottom: toem(0px);
            left: 50%;
            transform: translate(-50%,100%);
        }
        &:after {
            $size: toem(14.142135624px);
            content: '';
            width: $size;
            height: $size;
            display: inline-block;
            position: absolute;
            bottom: toem(0px);
            left: 50%;
            transform: translate(-50%,50%) rotate(45deg);
            @include box_shadow(1);
        }
        > div {
            width: 100%;
            height: 100%;
            background: $backgroundColor;
            border-radius: $border-radius;
            position: relative;
            z-index: 1;
            display: flex;
            justify-content: center;
            align-items: center;
        }
    }
    .#{$name-prefix}volume-controller {
        @extend %#{$name-prefix}-controller;
        .#{$name-prefix}volume-fill {
            @extend %#{$name-prefix}-controller-fill;
        }
        .#{$name-prefix}volume-controller-button {
            @extend %#{$name-prefix}-controller-button;
        }
    }
    %#{$name-prefix}-controller {
        display: inline-block;
        $height: $border-radius;
        height: $height;
        width: 100%;
        border-radius: $height / 2;
        background: $accentLightColor;
        line-height: 0;
        touch-action: none;
        position: relative;
        %#{$name-prefix}-controller-fill {
            display: inline-block;
            height: 100%;
            width: 100%;
            border-radius: $height / 2;
            background: $accentDarkColor;
        }
        %#{$name-prefix}-controller-button {
            $size: toem(15px);
            height: $size;
            width: $size;
            position: absolute;
            right: toem(0px);
            top: toem(-5.5px);
            display: inline-block;
            border-radius: 50%;
            border: toem(2px) solid $backgroundColor;
            background: $accentColor;
            transform: translateX(50%);
            &:hover {
                background: $accentDarkColor;
            }
        }
    }
    svg {
        vertical-align: top;
    }
	*,
	*::before,
	*::after {
		box-sizing: border-box;
	}
}

$dark-primaryColor: #a1a1a1;
$dark-primaryDarkColor: #BDBDBD;
$dark-accentLightColor: #BDBDBD;
$dark-accentColor: #757575;
$dark-accentDarkColor: #616161;
$dark-TextColor: #bbb;
$dark-backgroundColor: #222;
$dark-posterBgColor: #333;

c-player.#{$name-prefix}dark {
    color: $dark-TextColor;
	.#{$name-prefix}mainbody {
        background-color: $dark-backgroundColor;
    }
    .#{$name-prefix}drop-down-menu {
        background-color: rgba($dark-backgroundColor, 0.9);
    }
    .#{$name-prefix}playlist {
        li {
            .#{$name-prefix}play-icon {
                fill: rgba($dark-primaryDarkColor, 0.8);
            }
        }
    }
    .#{$name-prefix}lyric-text {
        color: $dark-TextColor;
        .#{$name-prefix}lyric-text-sub {
            color: $dark-accentColor;
        }
    }
    .#{$name-prefix}progress {
        .#{$name-prefix}progress-fill {
            background: $dark-primaryDarkColor;
        }
    }
    .#{$name-prefix}poster {
        background-color: $dark-posterBgColor;
        border-right-color: $dark-posterBgColor;
    }
    &.#{$name-prefix}big .#{$name-prefix}poster {
        border-bottom-color: $dark-posterBgColor;
    }
    &.#{$name-prefix}big .#{$name-prefix}progress-container {
        color: $dark-TextColor;
    }
    &.#{$name-prefix}big .#{$name-prefix}progress {
        @extend %#{$name-prefix}-controller;
        overflow: visible;
        .#{$name-prefix}progress-fill {
            @extend %#{$name-prefix}-controller-fill;
        }
        .#{$name-prefix}progress-button {
            @extend %#{$name-prefix}-controller-button;
        }
    }
    .#{$name-prefix}play-icon {
        .#{$name-prefix}play-icon-left {
            background-color: $dark-primaryColor;
        }
        .#{$name-prefix}play-icon-right {
            background-color: $dark-primaryColor;
        }
        .#{$name-prefix}play-icon-triangle-1,
        .#{$name-prefix}play-icon-triangle-2 {
            border-right-color: $dark-backgroundColor;
        }
        &.#{$name-prefix}play-icon-hover {
            .#{$name-prefix}play-icon-left {
                background-color: $dark-primaryDarkColor;
            }
            .#{$name-prefix}play-icon-right {
                background-color: $dark-primaryDarkColor;
            }
        }
    }
    .#{$name-prefix}play-button {
        border-color: $dark-primaryColor;
        &:hover {
            border-color: $dark-primaryDarkColor;
        }
    }
    .#{$name-prefix}icon {
        path {
            fill: $dark-primaryColor;
        }
        &:hover {
            path {
                fill: $dark-primaryDarkColor;
            }
        }
        &.#{$name-prefix}icon-dark {
            path {
                fill: $dark-accentColor;
            }
            &:hover {
                path {
                    fill: $dark-accentDarkColor;
                }
            }
        }
    }
    .#{$name-prefix}volume-container {
        background: $dark-backgroundColor;
        &:before {
            border-top-color: $dark-backgroundColor;
        }
        > div {
            background: $dark-backgroundColor;
        }
    }
    %#{$name-prefix}-controller {
        background: $dark-accentDarkColor;
        %#{$name-prefix}-controller-fill {
            background: $dark-accentLightColor;
        }
        %#{$name-prefix}-controller-button {
            border-color: $dark-backgroundColor;
            background: $dark-accentColor;
            &:hover {
                background: $dark-accentLightColor;
            }
        }
    }
}

================================================
FILE: tsconfig.json
================================================
{
    "compilerOptions": {
        "declaration": true,
        "outDir": "./lib/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "commonjs",
        "target": "es2017",
        "jsx": "react",
        "experimentalDecorators": true,
        "typeRoots": [
            "./node_modules/@types"
        ]
    },
    "include": [
        "src/lib/**/*"
    ]
}


================================================
FILE: webpack.config.example.js
================================================
var webpack = require('webpack');
var path = require('path');
const CopyPlugin = require('copy-webpack-plugin');

var HtmlWebpackPlugin = require('html-webpack-plugin');

const GLOBALS = {
    'process.env.NODE_ENV': JSON.stringify('production'),
    __DEV__: true
};

var HtmlWebpackConfig = {
    title: 'hexo',
    filename: 'index.html',
    template: "./src/example.html",
    hash: true,
    showErrors: true,
    inject: 'head'
};

module.exports = {
    mode: 'production',

    entry: [
        "./src/example.ts"
    ],
    output: {
        filename: "cplayerexample.js",
        path: __dirname + "/example"
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    plugins: [
        new webpack.DefinePlugin(GLOBALS),
        new HtmlWebpackPlugin(HtmlWebpackConfig),
        new CopyPlugin([
            { from: __dirname + '/src/manifest.json', to: 'manifest.json' }
          ])
    ],

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
    },

    module: {
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: [
                    {
                        loader: 'babel-loader'
                    },
                    {
                        loader: "ts-loader"
                    }
                ]
            },
            {
                test: /\.(scss)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: 'postcss-loader'
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            },
            {
                test: /\.(html|svg)$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        minimize: true
                    }
                }
            },
            {
                test: /\.(css)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "postcss-loader"
                    }
                ]
            },
            {
                test: /\.(ttf|otf|woff|woff2|eot|png|jpg|mp3|mp4)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        esModule: false
                    }
                }]
            },
            {
                test: /\.(js|jsx)$/,
                exclude: path.resolve(__dirname, "node_modules"),
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ],
            }
        ]
    }
}

================================================
FILE: webpack.config.js
================================================
var webpack = require('webpack');
var path = require('path');

var HtmlWebpackPlugin = require('html-webpack-plugin');

const GLOBALS = {
    'process.env.NODE_ENV': JSON.stringify('development'),
    __DEV__: true
};

var HtmlWebpackConfig = {
    title: 'hexo',
    filename: 'index.html',
    template: "./src/example.html",
    hash: true,
    showErrors: true,
    inject: 'head'
};


module.exports = {
    mode: 'development',

    entry: [
        "./src/example.ts"
    ],
    output: {
        filename: "cplayerexample.js",
        path: __dirname + "/example"
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    plugins: [
        new webpack.DefinePlugin(GLOBALS),
        new HtmlWebpackPlugin(HtmlWebpackConfig)
    ],

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
    },

    module: {
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: [
                    {
                        loader: 'babel-loader'
                    },
                    {
                        loader: "ts-loader"
                    }
                ]
            },
            {
                test: /\.(scss)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: 'postcss-loader'
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            },
            {
                test: /\.(html|svg)$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        minimize: true
                    }
                }
            },
            {
                test: /\.(css)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "postcss-loader"
                    }
                ]
            },
            {
                test: /\.(ttf|otf|woff|woff2|eot|png|jpg|mp3|mp4)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        esModule: false
                    }
                }]
            },
            {
                test: /\.(js|jsx)$/,
                exclude: path.resolve(__dirname, "node_modules"),
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ],
            }
        ]
    },
    devServer: {
        port: process.env.PORT || 8888,
        host: 'localhost',
        publicPath: '/',
        contentBase: './src',
        historyApiFallback: true,
        open: true,
        disableHostCheck: true,
        watchContentBase: true,
        compress: true,
        headers: {
            "access-control-allow-origin":"*"
        },
        proxy: {
            // OPTIONAL: proxy configuration:
            // '/optional-prefix/**': { // path pattern to rewrite
            //   target: 'http://target-host.com',
            //   pathRewrite: path => path.replace(/^\/[^\/]+\//, '')   // strip first path segment
            // }
        }
    }
}

================================================
FILE: webpack.config.prod.js
================================================
var webpack = require('webpack');
var path = require('path');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const GLOBALS = {
    'process.env.NODE_ENV': JSON.stringify('production'),
    'process.env.cplayer_noview': JSON.stringify(!!process.env.noview),
    __DEV__: false
};

module.exports = {
    mode: 'production',

    entry: [
        "./src/lib/index.ts"
    ],
    output: {
        filename: "cplayer" + (process.env.suffix || '') + ".js",
        path: __dirname + "/dist",
        library: 'cplayer-umd',
        libraryTarget: 'umd',
        umdNamedDefine: true
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    plugins: [
        !!process.env.analyzer ? new BundleAnalyzerPlugin() : undefined,
        new webpack.DefinePlugin(GLOBALS)
    ].filter(function(e){return e}),

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
    },

    module: {
        strictExportPresence: true,
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: [
                    {
                        loader: 'babel-loader'
                    },
                    {
                        loader: "ts-loader"
                    }
                ]
            },
            {
                test: /\.(scss)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: 'postcss-loader',
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            },
            {
                test: /\.(html|svg)$/,
                use: {
                    loader: 'html-loader',
                    options: {
                        minimize: true
                    }
                }
            },
            {
                test: /\.(css)$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "postcss-loader"
                    }
                ]
            },
            {
                test: /\.(ttf|otf|woff|woff2|eot|png|jpg|mp3|mp4)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 8192,
                        esModule: false
                    }
                }]
            },
            {
                test: /\.(js|jsx)$/,
                exclude: path.resolve(__dirname, "node_modules"),
                use: [
                    {
                        loader: 'babel-loader'
                    }
                ],
            }
        ]
    }
}
Download .txt
gitextract_qf7drz_x/

├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── index.html
├── package.json
├── postcss.config.js
├── readme-zh-CN.md
├── readme.md
├── src/
│   ├── cplayer.html
│   ├── example.html
│   ├── example.ts
│   ├── lib/
│   │   ├── helper/
│   │   │   ├── parseHTML.ts
│   │   │   ├── returntypeof.ts
│   │   │   └── shallowEqual.ts
│   │   ├── index.ts
│   │   ├── interfaces.ts
│   │   ├── lyric.ts
│   │   ├── mediaSession.ts
│   │   ├── playmode/
│   │   │   ├── listloop.ts
│   │   │   ├── listrandom.ts
│   │   │   └── singlecycle.ts
│   │   ├── polyfill.js
│   │   └── view.ts
│   ├── manifest.json
│   ├── neko.css
│   └── scss/
│       └── cplayer.scss
├── tsconfig.json
├── webpack.config.example.js
├── webpack.config.js
└── webpack.config.prod.js
Download .txt
SYMBOL INDEX (123 symbols across 15 files)

FILE: src/example.ts
  function from163 (line 18) | function from163(id: string) {

FILE: src/lib/helper/parseHTML.ts
  function parseHTML (line 1) | function parseHTML(elem: string) {

FILE: src/lib/helper/returntypeof.ts
  function returntypeof (line 1) | function returntypeof<RT>(expression: (...params: any[]) => RT): RT {

FILE: src/lib/helper/shallowEqual.ts
  function is (line 3) | function is(x: any, y: any) {
  function shallowEqual (line 11) | function shallowEqual(objA: any, objB: any) {

FILE: src/lib/index.ts
  type ICplayerOption (line 18) | interface ICplayerOption {
  function playlistPreFilter (line 40) | function playlistPreFilter(playlist: Iplaylist) {
  class cplayer (line 55) | class cplayer extends EventEmitter {
    method mode (line 63) | set mode(playmode: string) {
    method volume (line 66) | set volume(volume: number) {
    method volume (line 69) | get volume() {
    method mode (line 72) | get mode() {
    method playlist (line 75) | get playlist() {
    method nowplay (line 78) | get nowplay() {
    method nowplaypoint (line 81) | get nowplaypoint() {
    method played (line 84) | get played() {
    method paused (line 87) | get paused() {
    method duration (line 90) | get duration() {
    method currentTime (line 96) | get currentTime() {
    method constructor (line 103) | constructor(options: ICplayerOption & ICplayerViewOption) {
    method initializeEventEmitter (line 125) | private initializeEventEmitter(element: HTMLElement) {
    method removeEventEmitter (line 137) | private removeEventEmitter(element: HTMLElement) {
    method setCurrentTime (line 195) | public setCurrentTime(currentTime: number | string) {
    method isPlaying (line 208) | private isPlaying() {
    method openAudio (line 212) | public openAudio(audio: IAudioItem = this.nowplay) {
    method toggleMode (line 255) | public toggleMode() {
    method setMode (line 263) | public setMode(playmode: string) {
    method getMode (line 275) | public getMode() {
    method play (line 279) | public play(Forced: boolean = false) {
    method pause (line 293) | public pause(Forced: boolean = false) {
    method to (line 305) | public to(id: number) {
    method next (line 310) | public next() {
    method prev (line 315) | public prev() {
    method togglePlayState (line 320) | public togglePlayState() {
    method add (line 328) | public add(item: IAudioItem) {
    method remove (line 337) | public remove(item: IAudioItem) {
    method setVolume (line 345) | public setVolume(volume: number | string) {
    method destroy (line 352) | public destroy() {
  function parseCPlayerTag (line 365) | function parseCPlayerTag() {

FILE: src/lib/interfaces.ts
  type IAudioItem (line 3) | interface IAudioItem {
  type Iplaylist (line 14) | type Iplaylist = IAudioItem[];
  type Iplaymode (line 16) | interface Iplaymode {
  type IplaymodeConstructor (line 27) | interface IplaymodeConstructor {

FILE: src/lib/lyric.ts
  type ILyricItem (line 1) | interface ILyricItem {
  class Lyric (line 7) | class Lyric {
    method getLyric (line 10) | public getLyric(time: number) {
    method getNextLyric (line 18) | public getNextLyric(time: number) {
    method toString (line 26) | public toString() {
    method constructor (line 29) | constructor(items: ILyricItem[] ,raw: string) {
  function decodeLyricStr (line 35) | function decodeLyricStr(lyricStr: string, options?: {}) {
  function decodeLyricStrItem (line 45) | function decodeLyricStrItem(lyricItemStr: string): ILyricItem[] {

FILE: src/lib/mediaSession.ts
  function cplayerMediaSessionPlugin (line 6) | function cplayerMediaSessionPlugin(player: cplayer)
  function mediaMetadata (line 20) | function mediaMetadata(audio: IAudioItem) {

FILE: src/lib/playmode/listloop.ts
  function baseRemoveMusic (line 4) | function baseRemoveMusic(item: IAudioItem, playlist: Iplaylist, nowpoint...
  class listloopPlaymode (line 25) | class listloopPlaymode implements Iplaymode {
    method playlist (line 28) | get playlist() {
    method constructor (line 32) | constructor(playlist: Iplaylist = [], point: number = 0) {
    method next (line 37) | public next() {
    method prev (line 42) | public prev() {
    method now (line 47) | public now() {
    method nowpoint (line 51) | public nowpoint() {
    method to (line 55) | public to(point: number) {
    method addMusic (line 59) | public addMusic(item: IAudioItem) {
    method nextPoint (line 63) | private nextPoint() {
    method prevPoint (line 71) | private prevPoint() {
    method removeMusic (line 79) | public removeMusic(item: IAudioItem) {

FILE: src/lib/playmode/listrandom.ts
  class listrandomPlaymode (line 5) | class listrandomPlaymode implements Iplaymode {
    method playlist (line 8) | get playlist() {
    method constructor (line 12) | constructor(playlist: Iplaylist = [], point: number = 0) {
    method next (line 17) | public next() {
    method prev (line 22) | public prev() {
    method now (line 27) | public now() {
    method nowpoint (line 31) | public nowpoint() {
    method to (line 35) | public to(point: number) {
    method addMusic (line 39) | public addMusic(item:IAudioItem){
    method randomPoint (line 43) | private randomPoint(): number {
    method removeMusic (line 54) | public removeMusic(item: IAudioItem) {

FILE: src/lib/playmode/singlecycle.ts
  class singlecyclePlaymode (line 5) | class singlecyclePlaymode implements Iplaymode {
    method playlist (line 9) | get playlist() {
    method constructor (line 13) | constructor(playlist: Iplaylist = [], point: number = 0) {
    method next (line 18) | public next() {
    method prev (line 22) | public prev() {
    method now (line 26) | public now() {
    method nowpoint (line 30) | public nowpoint() {
    method to (line 34) | public to(point: number) {
    method addMusic (line 38) | public addMusic(item: IAudioItem) {
    method removeMusic (line 42) | public removeMusic(item: IAudioItem) {

FILE: src/lib/view.ts
  function kanaFilter (line 13) | function kanaFilter(str: string) {
  function buildLyric (line 37) | function buildLyric(lyric: string, sublyric?: string, zoomOutKana: boole...
  function secondNumber2TimeStr (line 41) | function secondNumber2TimeStr(secondTime: number) {
  type ICplayerViewOption (line 47) | interface ICplayerViewOption {
  function createStyleElement (line 78) | function createStyleElement(style: string) {
  function createShadowElement (line 85) | function createShadowElement(targetElement: Element, htmlTemplate: strin...
  function createBeforeElement (line 92) | function createBeforeElement(targetElement: Element, htmlTemplate: strin...
  function createBeforeShadowElement (line 102) | function createBeforeShadowElement(targetElement: Element, htmlTemplate:...
  function createElement (line 111) | function createElement(targetElement: Element, htmlTemplate: string, sty...
  class cplayerView (line 119) | class cplayerView extends EventEmitter {
    method constructor (line 126) | constructor(player: cplayer, options: ICplayerViewOption) {
    method getRootElement (line 180) | public getRootElement() {
    method dark (line 184) | public dark() {
    method big (line 188) | public big() {
    method getPlayListLinks (line 192) | private getPlayListLinks(rootElement: Element = this.rootElement) {
    method getElementLinks (line 196) | private getElementLinks(rootElement: Element = this.rootElement) {
    method setPlayIcon (line 231) | private setPlayIcon(paused: boolean) {
    method setProgress (line 239) | private setProgress(point: number, currentTime: number, duration: numb...
    method setPoster (line 246) | private setPoster(src: string) {
    method setVolume (line 251) | private setVolume(volume: number) {
    method setMode (line 259) | private setMode(mode: string) {
    method showInfo (line 265) | public showInfo() {
    method showPlaylist (line 273) | public showPlaylist() {
    method toggleDropDownMenu (line 281) | public toggleDropDownMenu() {
    method setVolumeControllerKeepShow (line 289) | private setVolumeControllerKeepShow() {
    method toggleVolumeControllerKeepShow (line 293) | private toggleVolumeControllerKeepShow() {
    method removeVolumeControllerKeepShow (line 297) | private removeVolumeControllerKeepShow() {
    method setLyric (line 304) | private setLyric(lyric: string, time: number = 0, totalTime: number = ...
    method updatePlaylist (line 328) | private updatePlaylist() {
    method injectPlayListEventListener (line 348) | private injectPlayListEventListener() {
    method injectEventListener (line 356) | private injectEventListener() {
    method updateLyric (line 385) | private updateLyric(playedTime: number = 0) {
    method destroy (line 524) | public destroy() {

FILE: webpack.config.example.js
  constant GLOBALS (line 7) | const GLOBALS = {

FILE: webpack.config.js
  constant GLOBALS (line 6) | const GLOBALS = {

FILE: webpack.config.prod.js
  constant GLOBALS (line 5) | const GLOBALS = {
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (155K chars).
[
  {
    "path": ".babelrc",
    "chars": 49,
    "preview": "  {\n    \"presets\": [\n      \"@babel/env\"\n    ]\n  }"
  },
  {
    "path": ".gitignore",
    "chars": 54,
    "preview": "/node_modules\r\nyarn*\r\n.DS_Store\r\n/example\r\n/dist\r\n/lib"
  },
  {
    "path": ".npmignore",
    "chars": 22,
    "preview": "example/\n\nsrc/example/"
  },
  {
    "path": "LICENSE",
    "chars": 1093,
    "preview": "The MIT License (MIT)\r\n\r\nCopyright (c) 2016 Corps\r\n\r\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "index.html",
    "chars": 41,
    "preview": "<script src='./dist/cplayer.js'></script>"
  },
  {
    "path": "package.json",
    "chars": 2093,
    "preview": "{\n  \"name\": \"cplayer\",\n  \"version\": \"3.2.2\",\n  \"description\": \"A beautiful and clean WEB Music Player by HTML5.\",\n  \"mai"
  },
  {
    "path": "postcss.config.js",
    "chars": 65,
    "preview": "module.exports = {\n  plugins: [\n    require('autoprefixer')\n  ]\n}"
  },
  {
    "path": "readme-zh-CN.md",
    "chars": 4326,
    "preview": "# cPlayer\n\n[![](https://badge.fury.io/js/cplayer.svg)](https://www.npmjs.com/package/cplayer) [![GitHub stars](https://i"
  },
  {
    "path": "readme.md",
    "chars": 4524,
    "preview": "# cPlayer\n\n[![](https://badge.fury.io/js/cplayer.svg)](https://www.npmjs.com/package/cplayer) [![GitHub stars](https://i"
  },
  {
    "path": "src/cplayer.html",
    "chars": 8477,
    "preview": "<c-player loaded>\n  <div class=\"cp-mainbody\">\n    <div class=\"cp-poster\">\n\n    </div>\n    <div class=\"cp-center-containe"
  },
  {
    "path": "src/example.html",
    "chars": 4009,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n    <meta charset=\"UTF-8\" />\n    <title>CPlayer</title>\n    <meta http-equiv"
  },
  {
    "path": "src/example.ts",
    "chars": 9504,
    "preview": "import cplayer from \"./lib\";\nimport { IAudioItem } from \"./lib/interfaces\";\nimport cplayerView from \"./lib/view\";\n\nrequi"
  },
  {
    "path": "src/lib/helper/parseHTML.ts",
    "chars": 345,
    "preview": "export default function parseHTML(elem: string) {\n    let fragment = document.createDocumentFragment();\n    let neweleme"
  },
  {
    "path": "src/lib/helper/returntypeof.ts",
    "chars": 105,
    "preview": "export default function returntypeof<RT>(expression: (...params: any[]) => RT): RT {\n  return {} as RT;\n}"
  },
  {
    "path": "src/lib/helper/shallowEqual.ts",
    "chars": 701,
    "preview": "const hasOwn = Object.prototype.hasOwnProperty\n\nfunction is(x: any, y: any) {\n  if (x == y) {\n    return x != 0 || y != "
  },
  {
    "path": "src/lib/index.ts",
    "chars": 10921,
    "preview": "require('./polyfill')\nimport { listloopPlaymode } from './playmode/listloop';\nimport { IAudioItem, Iplaymode, IplaymodeC"
  },
  {
    "path": "src/lib/interfaces.ts",
    "chars": 650,
    "preview": "import { Lyric } from \"./lyric\";\n\nexport interface IAudioItem {\n  name: string; //名称\n  poster?: string; //海报\n  artist?: "
  },
  {
    "path": "src/lib/lyric.ts",
    "chars": 1546,
    "preview": "export interface ILyricItem {\n  time: number;\n  word: string;\n}\n\n\nexport class Lyric {\n  raw: string;\n  items: ILyricIte"
  },
  {
    "path": "src/lib/mediaSession.ts",
    "chars": 1016,
    "preview": "import cplayer from './';\nimport { IAudioItem } from '../lib/interfaces';\n\nconst defaultPoster = require('../defaultpost"
  },
  {
    "path": "src/lib/playmode/listloop.ts",
    "chars": 2063,
    "preview": "import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';\nimport shallowEqual from \"../helper/shallowEqual\";\n\nex"
  },
  {
    "path": "src/lib/playmode/listrandom.ts",
    "chars": 1478,
    "preview": "import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';\nimport shallowEqual from \"../helper/shallowEqual\";\nimp"
  },
  {
    "path": "src/lib/playmode/singlecycle.ts",
    "chars": 1193,
    "preview": "import { IAudioItem, Iplaymode, Iplaylist } from '../interfaces';\nimport shallowEqual from \"../helper/shallowEqual\";\nimp"
  },
  {
    "path": "src/lib/polyfill.js",
    "chars": 1112,
    "preview": "if (typeof Object.assign != 'function') {\n    // Must be writable: true, enumerable: false, configurable: true\n    Objec"
  },
  {
    "path": "src/lib/view.ts",
    "chars": 19596,
    "preview": "import { IAudioItem } from './interfaces';\nimport cplayer from './';\nimport returntypeof from './helper/returntypeof';\ni"
  },
  {
    "path": "src/manifest.json",
    "chars": 128,
    "preview": "{\n    \"short_name\": \"cPlayer\",\n    \"name\": \"A beautiful and clean WEB Music Player by HTML5.\",\n    \"start_url\": \"index.h"
  },
  {
    "path": "src/neko.css",
    "chars": 33111,
    "preview": "body {\n  margin: 0px;\n  font-family: \"Roboto\", sans-serif;\n  -webkit-font-smoothing: antialiased; }\n\n*,\n*::before,\n*::af"
  },
  {
    "path": "src/scss/cplayer.scss",
    "chars": 21416,
    "preview": "$name-prefix: \"cp-\";\r\n\r\n$primaryColor: #F44336;\r\n$primaryDarkColor: #B71C1C;\r\n$accentLightColor: #BDBDBD;\r\n$accentColor:"
  },
  {
    "path": "tsconfig.json",
    "chars": 388,
    "preview": "{\n    \"compilerOptions\": {\n        \"declaration\": true,\n        \"outDir\": \"./lib/\",\n        \"sourceMap\": true,\n        \""
  },
  {
    "path": "webpack.config.example.js",
    "chars": 3100,
    "preview": "var webpack = require('webpack');\nvar path = require('path');\nconst CopyPlugin = require('copy-webpack-plugin');\n\nvar Ht"
  },
  {
    "path": "webpack.config.js",
    "chars": 3609,
    "preview": "var webpack = require('webpack');\nvar path = require('path');\n\nvar HtmlWebpackPlugin = require('html-webpack-plugin');\n\n"
  },
  {
    "path": "webpack.config.prod.js",
    "chars": 3072,
    "preview": "var webpack = require('webpack');\nvar path = require('path');\nvar BundleAnalyzerPlugin = require('webpack-bundle-analyze"
  }
]

About this extraction

This page contains the full source code of the Copay/cPlayer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (136.5 KB), approximately 45.2k tokens, and a symbol index with 123 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!