[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\npatreon: DIYgod\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "中文用户请注意：请尽量用**英文**描述你的 issue，这样能够让尽可能多的人帮到你。\n\nIf you want to report a bug, please provide the following information:\n\n-   The steps to reproduce.\n-   A minimal demo of the problem via https://jsfiddle.net or http://codepen.io/pen if possible.\n-   Which versions of DPlayer, and which browser / OS are affected by this issue?\n\n<!-- Love DPlayer? Please consider supporting our project:\n👉  https://github.com/MoePlayer/DPlayer#donate -->\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\nnode_modules\ndemo2\ndocs2\nnpm-debug.log\nDPlayer.log*\nwxw\n.vscode\npackage-lock.json\ndocs/.vuepress/dist"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/usr/bin/env sh\n. \"$(dirname -- \"$0\")/_/husky.sh\"\n\npnpm exec lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": "dist\ndemo"
  },
  {
    "path": ".prettierrc",
    "content": "{\n    \"printWidth\": 233,\n    \"tabWidth\": 4,\n    \"singleQuote\": true,\n    \"trailingComma\": \"es5\",\n    \"arrowParens\": \"always\"\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - lts/*\nscript:\n  - npm run build\n  - npm run docs:build\ndeploy:\n  provider: pages\n  skip-cleanup: true\n  local_dir: docs/.vuepress/dist\n  github-token: $GITHUB_TOKEN # a token generated on github allowing travis to push code on you repository\n  keep-history: true\n  on:\n    branch: master"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) DIYgod <diy.d.god@gmail.com> (https://www.anotherhome.net/)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n<img src=\"https://i.imgur.com/LnPvZvO.png\" alt=\"ADPlayer\" width=\"100\">\n</p>\n<h1 align=\"center\">DPlayer</h1>\n\n> 🍭 Wow, such a lovely HTML5 danmaku video player\n\n[![npm](https://img.shields.io/npm/v/dplayer.svg?style=flat-square)](https://www.npmjs.com/package/dplayer)\n[![npm](https://img.shields.io/npm/l/dplayer.svg?style=flat-square)](https://github.com/MoePlayer/DPlayer/blob/master/LICENSE)\n[![npm](https://img.shields.io/npm/dt/dplayer.svg?style=flat-square)](https://www.npmjs.com/package/dplayer)\n[![](https://data.jsdelivr.com/v1/package/npm/dplayer/badge)](https://www.jsdelivr.com/package/npm/dplayer)\n\n## Introduction\n\n![image](https://i.imgur.com/207ch36.jpg)\n\nDPlayer is a lovely HTML5 danmaku video player to help people build video and danmaku easily.\n\n**DPlayer supports:**\n\n-   Streaming formats\n    -   [HLS](https://github.com/video-dev/hls.js)\n    -   [FLV](https://github.com/Bilibili/flv.js)\n    -   [MPEG DASH](https://github.com/Dash-Industry-Forum/dash.js)\n    -   [WebTorrent](https://github.com/webtorrent/webtorrent)\n    -   Any other custom streaming formats\n-   Media formats\n    -   MP4 H.264\n    -   WebM\n    -   Ogg Theora Vorbis\n-   Features\n    -   Danmaku\n    -   Screenshot\n    -   Hotkeys\n    -   Quality switching\n    -   Thumbnails\n    -   Subtitle\n\nUsing DPlayer on your project? [Let me know!](https://github.com/DIYgod/DPlayer/issues/31)\n\n**[Docs](https://dplayer.diygod.dev/)**\n\n**[中文文档](https://dplayer.diygod.dev/zh/)**\n\n## Thanks\n\n### Sponsors\n\n<div>\n<a href=\"https://www.dogecloud.com/?ref=dplayer\" target=\"_blank\">\n    <img height=\"60px\" src=\"https://player.dogecloud.com/img/logo_with_product3.png\">\n</a>\n</div>\n\n### Contributors\n\n<a href=\"https://github.com/MoePlayer/DPlayer/graphs/contributors\"><img src=\"https://opencollective.com/DPlayer/contributors.svg?width=890\" /></a>\n\n## Related Projects\n\nFeel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n\n### Tooling\n\n-   [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails): generate video thumbnails\n\n### Danmaku api\n\n-   [DPlayer-node](https://github.com/MoePlayer/DPlayer-node): Node.js\n-   [laravel-danmaku](https://github.com/MoePlayer/laravel-danmaku): PHP\n-   [dplayer-live-backend](https://github.com/Izumi-kun/dplayer-live-backend): Node.js, WebSocket live backend\n-   [RailsGun](https://github.com/MoePlayer/RailsGun): Ruby\n\n### Plugins\n\n-   [DPlayer-for-typecho](https://github.com/volio/DPlayer-for-typecho): Typecho\n-   [Hexo-tag-dplayer](https://github.com/NextMoe/hexo-tag-dplayer): Hexo\n-   [DPlayer_for_Z-BlogPHP](https://github.com/fghrsh/DPlayer_for_Z-BlogPHP): Z-BlogPHP\n-   [DPlayer for Discuz!](https://coding.net/u/Click_04/p/video/git): Discuz!\n-   [DPlayer for WordPress](https://github.com/BlueCocoa/DPlayer-WordPress): WordPress\n-   [DPlayerHandle](https://github.com/kn007/DPlayerHandle): WordPress\n-   [Selection](https://github.com/GreatSatan79/Selection): WordPress\n-   [Vue-DPlayer](https://github.com/sinchang/vue-dplayer): Vue\n-   [react-dplayer](https://github.com/hnsylitao/react-dplayer): React\n-   [rc-dplayer](https://github.com/tianfeng98/rc-dplayer): React\n\n### Other\n\n-   [DPlayer-Lite](https://github.com/kn007/DPlayer-Lite): lite version\n-   [hlsjs-p2p-engine](https://github.com/cdnbye/hlsjs-p2p-engine): Let your viewers become your unlimitedly scalable CDN\n-   [CBPlayer](https://github.com/cdnbye/CBPlayer): Dplayer with CDNBye P2P plugin built in, supporting HLS, MP4 and MPEG-DASH P2P streaming.\n-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n\n## Who use DPlayer?\n\n-   [学习强国](https://itunes.apple.com/cn/app/%E5%AD%A6%E4%B9%A0%E5%BC%BA%E5%9B%BD/id1426355645?mt=8): “学习强国”学习平台精心打造的手机客户端\n-   [小红书](https://www.xiaohongshu.com/): 中国最大的生活社区分享平台，同时也是发现全球好物的电商平台\n-   [极客时间](https://time.geekbang.org/): 极客邦科技出品的一款 IT 内容知识服务 App\n-   [嘀哩嘀哩](http://www.dilidili.wang/): 兴趣使然的无名小站（D 站）\n-   [银色子弹](https://www.sbsub.com/): 银色子弹，简称银弹，由多数柯南热爱者聚集在一起的组织\n-   [浙江大学 CC98 论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛，中国各大学中较活跃的 BBS 之一\n-   [纸飞机南航青年网络社区](http://my.nuaa.edu.cn/video-video.html): 南京航空航天大学门户网站\n-   [otomads](https://otomads.com/): 专注于音 MAD 的视频弹幕网站\n-   [Cloudreve](https://github.com/HFO4/Cloudreve): 基于 ThinkPHP 构建的网盘系统\n-   [oneindex](https://github.com/donwa/oneindex): Onedrive Directory Index\n-   [arozos](https://github.com/tobychui/arozos): General purposed Web Desktop Operating Platform / OS for Raspberry Pis\n-   [新东方云教室](https://roombox.xdf.cn/)\n-   [BBHouse](https://github.com/endcloud/bbhouse-tauri): A Bilibili Cross-Platform Desktop Client Powered By Tauri\n-   [Tampermonkey 阿里云盘](https://greasyfork.org/zh-CN/scripts/425955-%E9%98%BF%E9%87%8C%E4%BA%91%E7%9B%98)\n-   [arozos](https://github.com/tobychui/arozos)\n-   [GBCLStudio/fof-upload-qcloud](https://github.com/GBCLStudio/FoF-Upload-Qcloud)\n-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n\n## Donate\n\nDPlayer is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing.\n\nYou can support DPlayer via donations.\n\n### Recurring Donation\n\n-   Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod)\n-   Become a Sponser on [Patreon](https://www.patreon.com/DIYgod)\n-   Become a Sponser on [爱发电](https://afdian.net/@diygod)\n-   Contact us directly: i@diygod.me\n\n### One-time Donation\n\nWe accept donations via the following ways:\n\n-   [WeChat Pay](https://diygod.me/images/wx.jpg)\n-   [Alipay](https://diygod.me/images/zfb.jpg)\n-   [Paypal](https://www.paypal.me/DIYgod)\n\n## Author\n\n**DPlayer** © [DIYgod](https://github.com/DIYgod), Released under the [MIT](./LICENSE) License.<br>\nAuthored and maintained by DIYgod with help from contributors ([list](https://github.com/DIYgod/DPlayer/contributors)).\n\n> [Blog](https://diygod.me) · GitHub [@DIYgod](https://github.com/DIYgod) · Twitter [@DIYgod](https://twitter.com/DIYgod) · Telegram Channel [@awesomeDIYgod](https://t.me/awesomeDIYgod)\n"
  },
  {
    "path": "demo/demo.css",
    "content": "body {\n    max-width: 700px;\n    margin: 0 auto;\n    padding: 45px 10px;\n    position: relative;\n}\n\n.show-dialog {\n    cursor: pointer;\n    border: 1px solid #f00;\n    width: 120px;\n    height: 40px;\n    line-height: 40px!important;\n    text-align: center;\n    border-radius: 4px;\n}\n.float-dplayer {\n    display: none;\n    width: 100%;\n    height: 100%;\n    background: rgba(0, 0, 0, .3);\n    position: fixed;\n    top: 0;\n    left: 0;\n    z-index: 99;\n}\n.dplayer-container {\n    width: 700px;\n    height: 358px;\n    border: 1px solid #f00;\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n}\n.close-dialog {\n    position: absolute;\n    right: 40px;\n    top: 10px;\n    font-size: 40px!important;\n    font-weight: normal!important;\n    color: #fff;\n    cursor: pointer;\n    z-index: 100;\n}\n\n.example {\n    position: relative;\n    margin: 15px 0 0;\n    padding: 39px 19px 14px;\n    background-color: #fff;\n    border-radius: 4px 4px 0 0;\n    border: 1px solid #ddd;\n}\n\n@media (max-width:500px) {\n    .example {\n        padding: 39px 10px 14px;\n    }\n}\n\n.example:after {\n    content: \"Example\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    padding: 2px 8px;\n    font-size: 12px;\n    font-weight: bold;\n    background-color: #f5f5f5;\n    color: #9da0a4;\n    border-radius: 4px 0 4px 0;\n}\n\n.highlight {\n    position: relative;\n    border-radius: 0 0 4px 4px;\n    border: 1px solid #ddd;\n    border-top: none;\n}\n\n.highlight.highlight-middle {\n    margin: 0;\n    border-radius: 0;\n}\n\n.highlight-html:after {\n    content: \"HTML\";\n    position: absolute;\n    top: 0;\n    right: 0;\n    padding: 2px 8px;\n    font-size: 12px;\n    font-weight: bold;\n    background-color: #f5f5f5;\n    color: #555;\n    border-radius: 0 0 0 4px;\n}\n\n.highlight-js:after {\n    content: \"js\";\n    position: absolute;\n    top: 0;\n    right: 0;\n    padding: 2px 8px;\n    font-size: 12px;\n    font-weight: bold;\n    background-color: #f5f5f5;\n    color: #555;\n    border-radius: 0 0 0 4px;\n}\n\n.btn {\n    background: #fff;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n    height: 40px;\n    font-size: 14px;\n    margin: 0 0 10px;\n    cursor: pointer;\n    outline: none;\n}\n\nsummary {\n    outline: none;\n    cursor: pointer;\n    margin-bottom: 10px;\n}\n\n#events {\n    font-size: 12px;\n    margin-top: 5px;\n    height: 120px;\n    overflow: scroll;\n}\n\n#events p {\n    margin: 0;\n    line-height: 17px;\n}\n\n/* github-corner */\n.github-corner:hover .octo-arm {\n    animation: octocat-wave 560ms ease-in-out\n}\n\n@keyframes octocat-wave {\n    0%,\n    100% {\n        transform: rotate(0)\n    }\n    20%,\n    60% {\n        transform: rotate(-25deg)\n    }\n    40%,\n    80% {\n        transform: rotate(10deg)\n    }\n}\n\n@media (max-width:500px) {\n    .github-corner:hover .octo-arm {\n        animation: none\n    }\n    .github-corner .octo-arm {\n        animation: octocat-wave 560ms ease-in-out\n    }\n}\n\n/* modernizr */\n.adownload li.adownload{ color: green; }.canvas li.canvas{ color: green; }.cssanimations li.cssanimations{ color: green; }.csstransforms li.csstransforms{ color: green; }.documentfragment li.documentfragment{ color: green; }.fullscreen li.fullscreen{ color: green; }.localstorage li.localstorage{ color: green; }.svg li.svg{ color: green; }.texttrackapi li.texttrackapi{ color: green; }.track li.track{ color: green; }.todataurljpeg li.todataurljpeg{ color: green; }.todataurlpng li.todataurlpng{ color: green; }.todataurlwebp li.todataurlwebp{ color: green; }.video li.video{ color: green; }.websockets li.websockets{ color: green; }.setclasses li.setclasses{ color: green; }\n.no-adownload li.adownload{ color: red; }.no-canvas li.canvas{ color: red; }.no-cssanimations li.cssanimations{ color: red; }.no-csstransforms li.csstransforms{ color: red; }.no-documentfragment li.documentfragment{ color: red; }.no-fullscreen li.fullscreen{ color: red; }.no-localstorage li.localstorage{ color: red; }.no-svg li.svg{ color: red; }.no-texttrackapi li.texttrackapi{ color: red; }.no-track li.track{ color: red; }.no-todataurljpeg li.todataurljpeg{ color: red; }.no-todataurlpng li.todataurlpng{ color: red; }.no-todataurlwebp li.todataurlwebp{ color: red; }.no-video li.video{ color: red; }.no-websockets li.websockets{ color: red; }.no-setclasses li.setclasses{ color: red; }"
  },
  {
    "path": "demo/demo.js",
    "content": "// stats.js: JavaScript Performance Monitor\nconst stats = new Stats();\nstats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom\ndocument.body.appendChild(stats.dom);\nfunction animate() {\n    stats.begin();\n    // monitored code goes here\n    stats.end();\n\n    requestAnimationFrame(animate);\n}\nrequestAnimationFrame(animate);\n\ninitPlayers();\nhandleEvent();\n\nfunction handleEvent() {\n    document.getElementById('dplayer-dialog').addEventListener('click', (e) => {\n        const $clickDom = e.currentTarget;\n        const isShowStatus = $clickDom.getAttribute('data-show');\n\n        if (isShowStatus) {\n            document.getElementById('float-dplayer').style.display = 'none';\n        } else {\n            $clickDom.setAttribute('data-show', 1);\n            document.getElementById('float-dplayer').style.display = 'block';\n        }\n    });\n\n    document.getElementById('close-dialog').addEventListener('click', () => {\n        const $openDialogBtnDom = document.getElementById('dplayer-dialog');\n\n        $openDialogBtnDom.setAttribute('data-show', '');\n        document.getElementById('float-dplayer').style.display = 'none';\n    });\n}\n\nfunction initPlayers() {\n    // dplayer-float\n    window.dpFloat = new DPlayer({\n        container: document.getElementById('dplayer-container'),\n        preload: 'none',\n        screenshot: true,\n        video: {\n            url: 'http://static.smartisanos.cn/common/video/t1-ui.mp4',\n            pic: 'http://static.smartisanos.cn/pr/img/video/video_03_cc87ce5bdb.jpg',\n            thumbnails: 'http://static.smartisanos.cn/pr/img/video/video_03_cc87ce5bdb.jpg'\n        },\n        subtitle: {\n            url: 'subtitle test'\n        },\n        danmaku: {\n            id: '9E2E3368B56CDBB4',\n            api: 'https://api.prprpr.me/dplayer/'\n        }\n    });\n    // dp1\n    window.dp1 = new DPlayer({\n        container: document.getElementById('dplayer1'),\n        preload: 'none',\n        screenshot: true,\n        video: {\n            url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n            pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n            thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg'\n        },\n        subtitle: {\n            url: [\n                {\n                    url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.vtt',\n                    lang: 'zh-cn',\n                    name: '光',\n                },\n                {\n                    url: 'https://gist.githubusercontent.com/samdutton/ca37f3adaf4e23679957b8083e061177/raw/e19399fbccbc069a2af4266e5120ae6bad62699a/sample.vtt',\n                    lang: 'en-us',\n                    name: 'github',\n                },\n            ],\n            defaultSubtitle: 7,\n            type: 'webvtt',\n            fontSize: '25px',\n            bottom: '10%',\n            color: '#b7daff'\n        },\n        danmaku: {\n            id: '9E2E3368B56CDBB4',\n            api: 'https://api.prprpr.me/dplayer/',\n            addition: ['https://s-sh-17-dplayercdn.oss.dogecdn.com/1678963.json']\n        }\n    });\n\n    // dp2\n    window.dp2 = new DPlayer({\n        container: document.getElementById('dplayer2'),\n        preload: 'none',\n        autoplay: false,\n        theme: '#FADFA3',\n        loop: true,\n        screenshot: true,\n        airplay: true,\n        chromecast: true,\n        hotkey: true,\n        logo: 'https://i.loli.net/2019/06/06/5cf8c5d94521136430.png',\n        volume: 0.2,\n        mutex: true,\n        lang: 'zh-cn',\n        video: {\n            url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n            pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n            thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg',\n            type: 'auto'\n        },\n        subtitle: {\n            url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.vtt',\n            type: 'webvtt',\n            fontSize: '25px',\n            bottom: '10%',\n            color: '#b7daff'\n        },\n        danmaku: {\n            id: '9E2E3368B56CDBB4',\n            api: 'https://api.prprpr.me/dplayer/',\n            addition: ['https://s-sh-17-dplayercdn.oss.dogecdn.com/1678963.json'],\n            token: 'tokendemo',\n            maximum: 3000,\n            user: 'DIYgod',\n            bottom: '15%',\n            unlimited: true,\n            speedRate: 0.5,\n        },\n        contextmenu: [\n            {\n                text: 'custom contextmenu',\n                link: 'https://github.com/MoePlayer/DPlayer'\n            }\n        ]\n    });\n\n    const events = [\n        'abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'ended', 'error',\n        'loadeddata', 'loadedmetadata', 'loadstart', 'mozaudioavailable', 'pause', 'play',\n        'playing', 'ratechange', 'seeked', 'seeking', 'stalled',\n        'volumechange', 'waiting',\n        'screenshot',\n        'thumbnails_show', 'thumbnails_hide',\n        'danmaku_show', 'danmaku_hide', 'danmaku_clear',\n        'danmaku_loaded', 'danmaku_send', 'danmaku_opacity',\n        'contextmenu_show', 'contextmenu_hide',\n        'notice_show', 'notice_hide',\n        'quality_start', 'quality_end',\n        'destroy',\n        'resize',\n        'fullscreen', 'fullscreen_cancel', 'webfullscreen', 'webfullscreen_cancel',\n        'subtitle_show', 'subtitle_hide', 'subtitle_change'\n    ];\n    const eventsEle = document.getElementById('events');\n    for (let i = 0; i < events.length; i++) {\n        dp2.on(events[i], (info) => {\n            eventsEle.innerHTML += `<p>Event: ${events[i]} ${info?`Data: <span>${JSON.stringify(info)}</span>`:''}</p>`;\n            eventsEle.scrollTop = eventsEle.scrollHeight;\n        });\n    }\n\n    // dp3\n    // window.dp3 = new DPlayer({\n    //     container: document.getElementById('dplayer3'),\n    //     preload: 'none',\n    //     video: {\n    //         quality: [{\n    //             name: 'HD',\n    //             url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.m3u8',\n    //             type: 'hls'\n    //         }, {\n    //             name: 'SD',\n    //             url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n    //             type: 'normal'\n    //         }],\n    //         defaultQuality: 0,\n    //         pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png'\n    //     }\n    // });\n\n    // // dp4\n    // window.dp4 = new DPlayer({\n    //     container: document.getElementById('dplayer4'),\n    //     preload: 'none',\n    //     video: {\n    //         url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.m3u8',\n    //         type: 'hls'\n    //     }\n    // });\n\n    // // dp5\n    // window.dp5 = new DPlayer({\n    //     container: document.getElementById('dplayer5'),\n    //     preload: 'none',\n    //     video: {\n    //         url: 'https://moeplayer.b0.upaiyun.com/dplayer/hikarunara.flv',\n    //         type: 'flv'\n    //     }\n    // });\n\n    // window.dp8 = new DPlayer({\n    //     container: document.getElementById('dplayer8'),\n    //     preload: 'none',\n    //     video: {\n    //         url: 'https://moeplayer.b0.upaiyun.com/dplayer/dash/hikarunara.mpd',\n    //         type: 'dash'\n    //     }\n    // });\n\n    // window.dp9 = new DPlayer({\n    //     container: document.getElementById('dplayer9'),\n    //     video: {\n    //         url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',\n    //         type: 'webtorrent'\n    //     }\n    // });\n\n    // window.dp6 = new DPlayer({\n    //     container: document.getElementById('dplayer6'),\n    //     preload: 'none',\n    //     live: true,\n    //     danmaku: true,\n    //     apiBackend: {\n    //         read: function (endpoint, callback) {\n    //             console.log('假装 WebSocket 连接成功');\n    //             callback();\n    //         },\n    //         send: function (endpoint, danmakuData, callback) {\n    //             console.log('假装通过 WebSocket 发送数据', danmakuData);\n    //             callback();\n    //         }\n    //     },\n    //     video: {\n    //         url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.m3u8',\n    //         type: 'hls'\n    //     }\n    // });\n\n    // window.dp10 = new DPlayer({\n    //     container: document.getElementById('dplayer10'),\n    //     video: {\n    //         url: 'https://qq.webrtc.win/tv/Pear-Demo-Yosemite_National_Park.mp4',\n    //         type: 'pearplayer',\n    //         customType: {\n    //             'pearplayer': function (video, player) {\n    //                 new PearPlayer(video, {\n    //                     src: video.src,\n    //                     autoplay: player.options.autoplay\n    //                 });\n    //             }\n    //         }\n    //     }\n    // });\n}\n\nfunction clearPlayers() {\n    for (let i = 0; i < 6; i++) {\n        window['dp' + (i + 1)].pause();\n        document.getElementById('dplayer' + (i + 1)).innerHTML = '';\n    }\n}\n\nfunction switchDPlayer() {\n    if (dp2.options.danmaku.id !== '5rGf5Y2X55qu6Z2p') {\n        dp2.switchVideo({\n            url: 'http://static.smartisanos.cn/common/video/t1-ui.mp4',\n            pic: 'http://static.smartisanos.cn/pr/img/video/video_03_cc87ce5bdb.jpg',\n            type: 'auto',\n        }, {\n            id: '5rGf5Y2X55qu6Z2p',\n            api: 'https://api.prprpr.me/dplayer/',\n            maximum: 3000,\n            user: 'DIYgod'\n        });\n    } else {\n        dp2.switchVideo({\n            url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n            pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n            thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg',\n            type: 'auto'\n        }, {\n            id: '9E2E3368B56CDBB42',\n            api: 'https://api.prprpr.me/dplayer/',\n            maximum: 3000,\n            user: 'DIYgod'\n        });\n    }\n}"
  },
  {
    "path": "demo/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>DPlayer Demo</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n    <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/github-markdown-css\">\n    <link rel=\"stylesheet\" href=\"demo.css\">\n    <script src=\"https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/dashjs/dist/dash.all.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/pearplayer\"></script>\n    <script src=\"DPlayer.js\"></script>\n</head>\n\n<body class=\"markdown-body\">\n    <h2>在 dialog 中承载视频</h2>\n    <h3 id=\"dplayer-dialog\" class=\"show-dialog\">Show Dialog</h3>\n    <div id=\"float-dplayer\" class=\"float-dplayer\">\n        <div id=\"dplayer-container\" class=\"dplayer-container\"></div>\n        <h3 id=\"close-dialog\" class=\"close-dialog\">X</h3>\n    </div>\n\n    <h2 id=\"quick-start\">Quick Start</h2>\n    <div class=\"example\">\n        <div id=\"dplayer1\"></div>\n    </div>\n\n    <h2 id=\"options\">Options</h2>\n    <div class=\"example\">\n        <button class=\"btn\" onclick=\"switchDPlayer()\">Switch Video</button>\n        <button class=\"btn\" onclick=\"dp2.notice('Notice演示')\">notice</button>\n        <button class=\"btn\" onclick=\"dp2.seek(120)\">seek</button>\n        <button class=\"btn\" onclick=\"dp2.volume(0.2)\">volume</button>\n        <button class=\"btn\" onclick=\"dp2.toggle()\">toggle</button>\n        <button class=\"btn\" onclick=\"dp2.destroy()\">destroy</button>\n        <button class=\"btn\" onclick=\"dp2.danmaku.hide()\">danmaku.hide</button>\n        <button class=\"btn\" onclick=\"dp2.danmaku.show()\">danmaku.show</button>\n        <div id=\"dplayer2\"></div>\n        <div id=\"events\"></div>\n    </div>\n\n    <h2 id=\"quality-switching\">Quality switching</h2>\n    <div class=\"example\">\n        <button class=\"btn\" onclick=\"dp3.switchQuality(1)\">Switch quality</button>\n        <div id=\"dplayer3\"></div>\n    </div>\n\n    <h2 id=\"hls-support\">HLS support</h2>\n    <div class=\"example\">\n        <div id=\"dplayer4\"></div>\n    </div>\n\n    <h2 id=\"dash-support\">MPEG DASH support</h2>\n    <div class=\"example\">\n        <div id=\"dplayer8\"></div>\n    </div>\n\n    <h2 id=\"flv-support\">FLV support</h2>\n    <div class=\"example\">\n        <div id=\"dplayer5\"></div>\n    </div>\n\n    <h2 id=\"webtorrent\">WebTorrent</h2>\n    <div class=\"example\">\n        <div id=\"dplayer9\"></div>\n    </div>\n\n    <h2 id=\"live\">Live</h2>\n    <div class=\"example\">\n        <button class=\"btn\" onclick=\"dp6.danmaku.draw({text: '假装收到 WebSocket 弹幕', color: '#fff', type: 'right'})\">假装收到 WebSocket 弹幕</button>\n        <div id=\"dplayer6\"></div>\n    </div>\n\n    <h2 id=\"custon-type\">Custon video type</h2>\n    <div class=\"example\">\n        <div id=\"dplayer10\"></div>\n    </div>\n\n    <script src=\"https://cdn.jsdelivr.net/npm/stats.js\"></script>\n    <script src=\"demo.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "demo/modernizr.js",
    "content": "/*!\n * modernizr v3.5.0\n * Build https://modernizr.com/download?-adownload-canvas-cssanimations-csstransforms-documentfragment-fullscreen-localstorage-svg-texttrackapi_track-todataurljpeg_todataurlpng_todataurlwebp-video-websockets-setclasses-dontmin\n *\n * Copyright (c)\n *  Faruk Ates\n *  Paul Irish\n *  Alex Sexton\n *  Ryan Seddon\n *  Patrick Kettner\n *  Stu Cox\n *  Richard Herrera\n\n * MIT License\n */\n\n/*\n * Modernizr tests which native CSS3 and HTML5 features are available in the\n * current UA and makes the results available to you in two ways: as properties on\n * a global `Modernizr` object, and as classes on the `<html>` element. This\n * information allows you to progressively enhance your pages with a granular level\n * of control over the experience.\n*/\n\n;(function(window, document, undefined){\n  var classes = [];\n  \n\n  var tests = [];\n  \n\n  /**\n   *\n   * ModernizrProto is the constructor for Modernizr\n   *\n   * @class\n   * @access public\n   */\n\n  var ModernizrProto = {\n    // The current version, dummy\n    _version: '3.5.0',\n\n    // Any settings that don't work as separate modules\n    // can go in here as configuration.\n    _config: {\n      'classPrefix': '',\n      'enableClasses': true,\n      'enableJSClass': true,\n      'usePrefixes': true\n    },\n\n    // Queue of tests\n    _q: [],\n\n    // Stub these for people who are listening\n    on: function(test, cb) {\n      // I don't really think people should do this, but we can\n      // safe guard it a bit.\n      // -- NOTE:: this gets WAY overridden in src/addTest for actual async tests.\n      // This is in case people listen to synchronous tests. I would leave it out,\n      // but the code to *disallow* sync tests in the real version of this\n      // function is actually larger than this.\n      var self = this;\n      setTimeout(function() {\n        cb(self[test]);\n      }, 0);\n    },\n\n    addTest: function(name, fn, options) {\n      tests.push({name: name, fn: fn, options: options});\n    },\n\n    addAsyncTest: function(fn) {\n      tests.push({name: null, fn: fn});\n    }\n  };\n\n  \n\n  // Fake some of Object.create so we can force non test results to be non \"own\" properties.\n  var Modernizr = function() {};\n  Modernizr.prototype = ModernizrProto;\n\n  // Leak modernizr globally when you `require` it rather than force it here.\n  // Overwrite name so constructor name is nicer :D\n  Modernizr = new Modernizr();\n\n  \n/*!\n{\n  \"name\": \"SVG\",\n  \"property\": \"svg\",\n  \"caniuse\": \"svg\",\n  \"tags\": [\"svg\"],\n  \"authors\": [\"Erik Dahlstrom\"],\n  \"polyfills\": [\n    \"svgweb\",\n    \"raphael\",\n    \"amplesdk\",\n    \"canvg\",\n    \"svg-boilerplate\",\n    \"sie\",\n    \"dojogfx\",\n    \"fabricjs\"\n  ]\n}\n!*/\n/* DOC\nDetects support for SVG in `<embed>` or `<object>` elements.\n*/\n\n  Modernizr.addTest('svg', !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect);\n\n/*!\n{\n  \"name\": \"WebSockets Support\",\n  \"property\": \"websockets\",\n  \"authors\": [\"Phread [fearphage]\", \"Mike Sherov [mikesherov]\", \"Burak Yigit Kaya [BYK]\"],\n  \"caniuse\": \"websockets\",\n  \"tags\": [\"html5\"],\n  \"warnings\": [\n    \"This test will reject any old version of WebSockets even if it is not prefixed such as in Safari 5.1\"\n  ],\n  \"notes\": [{\n    \"name\": \"CLOSING State and Spec\",\n    \"href\": \"https://www.w3.org/TR/websockets/#the-websocket-interface\"\n  }],\n  \"polyfills\": [\n    \"sockjs\",\n    \"socketio\",\n    \"kaazing-websocket-gateway\",\n    \"websocketjs\",\n    \"atmosphere\",\n    \"graceful-websocket\",\n    \"portal\",\n    \"datachannel\"\n  ]\n}\n!*/\n\n  var supports = false;\n  try {\n    supports = 'WebSocket' in window && window.WebSocket.CLOSING === 2;\n  } catch (e) {}\n  Modernizr.addTest('websockets', supports);\n\n/*!\n{\n  \"name\": \"Local Storage\",\n  \"property\": \"localstorage\",\n  \"caniuse\": \"namevalue-storage\",\n  \"tags\": [\"storage\"],\n  \"knownBugs\": [],\n  \"notes\": [],\n  \"warnings\": [],\n  \"polyfills\": [\n    \"joshuabell-polyfill\",\n    \"cupcake\",\n    \"storagepolyfill\",\n    \"amplifyjs\",\n    \"yui-cacheoffline\"\n  ]\n}\n!*/\n\n  // In FF4, if disabled, window.localStorage should === null.\n\n  // Normally, we could not test that directly and need to do a\n  //   `('localStorage' in window)` test first because otherwise Firefox will\n  //   throw bugzil.la/365772 if cookies are disabled\n\n  // Similarly, in Chrome with \"Block third-party cookies and site data\" enabled,\n  // attempting to access `window.sessionStorage` will throw an exception. crbug.com/357625\n\n  // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem\n  // will throw the exception:\n  //   QUOTA_EXCEEDED_ERROR DOM Exception 22.\n  // Peculiarly, getItem and removeItem calls do not throw.\n\n  // Because we are forced to try/catch this, we'll go aggressive.\n\n  // Just FWIW: IE8 Compat mode supports these features completely:\n  //   www.quirksmode.org/dom/html5.html\n  // But IE8 doesn't support either with local files\n\n  Modernizr.addTest('localstorage', function() {\n    var mod = 'modernizr';\n    try {\n      localStorage.setItem(mod, mod);\n      localStorage.removeItem(mod);\n      return true;\n    } catch (e) {\n      return false;\n    }\n  });\n\n\n  /**\n   * is returns a boolean if the typeof an obj is exactly type.\n   *\n   * @access private\n   * @function is\n   * @param {*} obj - A thing we want to check the type of\n   * @param {string} type - A string to compare the typeof against\n   * @returns {boolean}\n   */\n\n  function is(obj, type) {\n    return typeof obj === type;\n  }\n  ;\n\n  /**\n   * Run through all tests and detect their support in the current UA.\n   *\n   * @access private\n   */\n\n  function testRunner() {\n    var featureNames;\n    var feature;\n    var aliasIdx;\n    var result;\n    var nameIdx;\n    var featureName;\n    var featureNameSplit;\n\n    for (var featureIdx in tests) {\n      if (tests.hasOwnProperty(featureIdx)) {\n        featureNames = [];\n        feature = tests[featureIdx];\n        // run the test, throw the return value into the Modernizr,\n        // then based on that boolean, define an appropriate className\n        // and push it into an array of classes we'll join later.\n        //\n        // If there is no name, it's an 'async' test that is run,\n        // but not directly added to the object. That should\n        // be done with a post-run addTest call.\n        if (feature.name) {\n          featureNames.push(feature.name.toLowerCase());\n\n          if (feature.options && feature.options.aliases && feature.options.aliases.length) {\n            // Add all the aliases into the names list\n            for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) {\n              featureNames.push(feature.options.aliases[aliasIdx].toLowerCase());\n            }\n          }\n        }\n\n        // Run the test, or use the raw value if it's not a function\n        result = is(feature.fn, 'function') ? feature.fn() : feature.fn;\n\n\n        // Set each of the names on the Modernizr object\n        for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) {\n          featureName = featureNames[nameIdx];\n          // Support dot properties as sub tests. We don't do checking to make sure\n          // that the implied parent tests have been added. You must call them in\n          // order (either in the test, or make the parent test a dependency).\n          //\n          // Cap it to TWO to make the logic simple and because who needs that kind of subtesting\n          // hashtag famous last words\n          featureNameSplit = featureName.split('.');\n\n          if (featureNameSplit.length === 1) {\n            Modernizr[featureNameSplit[0]] = result;\n          } else {\n            // cast to a Boolean, if not one already\n            if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) {\n              Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]);\n            }\n\n            Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result;\n          }\n\n          classes.push((result ? '' : 'no-') + featureNameSplit.join('-'));\n        }\n      }\n    }\n  }\n  ;\n\n  /**\n   * docElement is a convenience wrapper to grab the root element of the document\n   *\n   * @access private\n   * @returns {HTMLElement|SVGElement} The root element of the document\n   */\n\n  var docElement = document.documentElement;\n  \n/*!\n{\n  \"name\": \"Document Fragment\",\n  \"property\": \"documentfragment\",\n  \"notes\": [{\n    \"name\": \"W3C DOM Level 1 Reference\",\n    \"href\": \"https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-B63ED1A3\"\n  }, {\n    \"name\": \"SitePoint Reference\",\n    \"href\": \"http://reference.sitepoint.com/javascript/DocumentFragment\"\n  }, {\n    \"name\": \"QuirksMode Compatibility Tables\",\n    \"href\": \"http://www.quirksmode.org/m/w3c_core.html#t112\"\n  }],\n  \"authors\": [\"Ron Waldon (@jokeyrhyme)\"],\n  \"knownBugs\": [\"false-positive on Blackberry 9500, see QuirksMode note\"],\n  \"tags\": []\n}\n!*/\n/* DOC\nAppend multiple elements to the DOM within a single insertion.\n*/\n\n  Modernizr.addTest('documentfragment', function() {\n    return 'createDocumentFragment' in document &&\n      'appendChild' in docElement;\n  });\n\n\n  /**\n   * A convenience helper to check if the document we are running in is an SVG document\n   *\n   * @access private\n   * @returns {boolean}\n   */\n\n  var isSVG = docElement.nodeName.toLowerCase() === 'svg';\n  \n\n  /**\n   * setClasses takes an array of class names and adds them to the root element\n   *\n   * @access private\n   * @function setClasses\n   * @param {string[]} classes - Array of class names\n   */\n\n  // Pass in an and array of class names, e.g.:\n  //  ['no-webp', 'borderradius', ...]\n  function setClasses(classes) {\n    var className = docElement.className;\n    var classPrefix = Modernizr._config.classPrefix || '';\n\n    if (isSVG) {\n      className = className.baseVal;\n    }\n\n    // Change `no-js` to `js` (independently of the `enableClasses` option)\n    // Handle classPrefix on this too\n    if (Modernizr._config.enableJSClass) {\n      var reJS = new RegExp('(^|\\\\s)' + classPrefix + 'no-js(\\\\s|$)');\n      className = className.replace(reJS, '$1' + classPrefix + 'js$2');\n    }\n\n    if (Modernizr._config.enableClasses) {\n      // Add the new classes\n      className += ' ' + classPrefix + classes.join(' ' + classPrefix);\n      if (isSVG) {\n        docElement.className.baseVal = className;\n      } else {\n        docElement.className = className;\n      }\n    }\n\n  }\n\n  ;\n\n  /**\n   * createElement is a convenience wrapper around document.createElement. Since we\n   * use createElement all over the place, this allows for (slightly) smaller code\n   * as well as abstracting away issues with creating elements in contexts other than\n   * HTML documents (e.g. SVG documents).\n   *\n   * @access private\n   * @function createElement\n   * @returns {HTMLElement|SVGElement} An HTML or SVG element\n   */\n\n  function createElement() {\n    if (typeof document.createElement !== 'function') {\n      // This is the case in IE7, where the type of createElement is \"object\".\n      // For this reason, we cannot call apply() as Object is not a Function.\n      return document.createElement(arguments[0]);\n    } else if (isSVG) {\n      return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]);\n    } else {\n      return document.createElement.apply(document, arguments);\n    }\n  }\n\n  ;\n/*!\n{\n  \"name\": \"Canvas\",\n  \"property\": \"canvas\",\n  \"caniuse\": \"canvas\",\n  \"tags\": [\"canvas\", \"graphics\"],\n  \"polyfills\": [\"flashcanvas\", \"excanvas\", \"slcanvas\", \"fxcanvas\"]\n}\n!*/\n/* DOC\nDetects support for the `<canvas>` element for 2D drawing.\n*/\n\n  // On the S60 and BB Storm, getContext exists, but always returns undefined\n  // so we actually have to call getContext() to verify\n  // github.com/Modernizr/Modernizr/issues/issue/97/\n  Modernizr.addTest('canvas', function() {\n    var elem = createElement('canvas');\n    return !!(elem.getContext && elem.getContext('2d'));\n  });\n\n/*!\n{\n  \"name\": \"HTML5 Video\",\n  \"property\": \"video\",\n  \"caniuse\": \"video\",\n  \"tags\": [\"html5\"],\n  \"knownBugs\": [\n    \"Without QuickTime, `Modernizr.video.h264` will be `undefined`; https://github.com/Modernizr/Modernizr/issues/546\"\n  ],\n  \"polyfills\": [\n    \"html5media\",\n    \"mediaelementjs\",\n    \"sublimevideo\",\n    \"videojs\",\n    \"leanbackplayer\",\n    \"videoforeverybody\"\n  ]\n}\n!*/\n/* DOC\nDetects support for the video element, as well as testing what types of content it supports.\n\nSubproperties are provided to describe support for `ogg`, `h264` and `webm` formats, e.g.:\n\n```javascript\nModernizr.video         // true\nModernizr.video.ogg     // 'probably'\n```\n*/\n\n  // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845\n  //                     thx to NielsLeenheer and zcorpan\n\n  // Note: in some older browsers, \"no\" was a return value instead of empty string.\n  //   It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2\n  //   It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5\n\n  Modernizr.addTest('video', function() {\n    var elem = createElement('video');\n    var bool = false;\n\n    // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224\n    try {\n      bool = !!elem.canPlayType\n      if (bool) {\n        bool = new Boolean(bool);\n        bool.ogg = elem.canPlayType('video/ogg; codecs=\"theora\"').replace(/^no$/, '');\n\n        // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546\n        bool.h264 = elem.canPlayType('video/mp4; codecs=\"avc1.42E01E\"').replace(/^no$/, '');\n\n        bool.webm = elem.canPlayType('video/webm; codecs=\"vp8, vorbis\"').replace(/^no$/, '');\n\n        bool.vp9 = elem.canPlayType('video/webm; codecs=\"vp9\"').replace(/^no$/, '');\n\n        bool.hls = elem.canPlayType('application/x-mpegURL; codecs=\"avc1.42E01E\"').replace(/^no$/, '');\n      }\n    } catch (e) {}\n\n    return bool;\n  });\n\n/*!\n{\n  \"name\": \"a[download] Attribute\",\n  \"property\": \"adownload\",\n  \"caniuse\" : \"download\",\n  \"tags\": [\"media\", \"attribute\"],\n  \"builderAliases\": [\"a_download\"],\n  \"notes\": [{\n    \"name\": \"WhatWG Reference\",\n    \"href\": \"https://developers.whatwg.org/links.html#downloading-resources\"\n  }]\n}\n!*/\n/* DOC\nWhen used on an `<a>`, this attribute signifies that the resource it points to should be downloaded by the browser rather than navigating to it.\n*/\n\n  Modernizr.addTest('adownload', !window.externalHost && 'download' in createElement('a'));\n\n/*!\n{\n  \"name\": \"canvas.toDataURL type support\",\n  \"property\": [\"todataurljpeg\", \"todataurlpng\", \"todataurlwebp\"],\n  \"tags\": [\"canvas\"],\n  \"builderAliases\": [\"canvas_todataurl_type\"],\n  \"async\" : false,\n  \"notes\": [{\n    \"name\": \"MDN article\",\n    \"href\": \"https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement.toDataURL\"\n  }]\n}\n!*/\n\n\n  var canvas = createElement('canvas');\n\n  Modernizr.addTest('todataurljpeg', function() {\n    return !!Modernizr.canvas && canvas.toDataURL('image/jpeg').indexOf('data:image/jpeg') === 0;\n  });\n  Modernizr.addTest('todataurlpng', function() {\n    return !!Modernizr.canvas && canvas.toDataURL('image/png').indexOf('data:image/png') === 0;\n  });\n  Modernizr.addTest('todataurlwebp', function() {\n    var supports = false;\n\n    // firefox 3 throws an error when you use an \"invalid\" toDataUrl\n    try {\n      supports = !!Modernizr.canvas && canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;\n    } catch (e) {}\n\n    return supports;\n  });\n\n\n/*!\n{\n  \"name\": \"Track element and Timed Text Track\",\n  \"property\": [\"texttrackapi\", \"track\"],\n  \"tags\": [\"elem\"],\n  \"builderAliases\": [\"elem_track\"],\n  \"authors\": [\"Addy Osmani\"],\n  \"notes\": [{\n    \"name\": \"W3 track Element Spec\",\n    \"href\": \"http://www.w3.org/TR/html5/video.html#the-track-element\"\n  },{\n    \"name\": \"W3 track API Spec\",\n    \"href\": \"http://www.w3.org/TR/html5/media-elements.html#text-track-api\"\n  }],\n  \"warnings\": [\"While IE10 has implemented the track element, IE10 does not expose the underlying APIs to create timed text tracks by JS (really sad)\"]\n}\n!*/\n\n  Modernizr.addTest('texttrackapi', typeof (createElement('video').addTextTrack) === 'function');\n\n  // a more strict test for track including UI support: document.createElement('track').kind === 'subtitles'\n  Modernizr.addTest('track', 'kind' in createElement('track'));\n\n\n  /**\n   * cssToDOM takes a kebab-case string and converts it to camelCase\n   * e.g. box-sizing -> boxSizing\n   *\n   * @access private\n   * @function cssToDOM\n   * @param {string} name - String name of kebab-case prop we want to convert\n   * @returns {string} The camelCase version of the supplied name\n   */\n\n  function cssToDOM(name) {\n    return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) {\n      return m1 + m2.toUpperCase();\n    }).replace(/^-/, '');\n  }\n  ;\n\n  /**\n   * If the browsers follow the spec, then they would expose vendor-specific styles as:\n   *   elem.style.WebkitBorderRadius\n   * instead of something like the following (which is technically incorrect):\n   *   elem.style.webkitBorderRadius\n\n   * WebKit ghosts their properties in lowercase but Opera & Moz do not.\n   * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+\n   *   erik.eae.net/archives/2008/03/10/21.48.10/\n\n   * More here: github.com/Modernizr/Modernizr/issues/issue/21\n   *\n   * @access private\n   * @returns {string} The string representing the vendor-specific style properties\n   */\n\n  var omPrefixes = 'Moz O ms Webkit';\n  \n\n  var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []);\n  ModernizrProto._cssomPrefixes = cssomPrefixes;\n  \n\n  /**\n   * atRule returns a given CSS property at-rule (eg @keyframes), possibly in\n   * some prefixed form, or false, in the case of an unsupported rule\n   *\n   * @memberof Modernizr\n   * @name Modernizr.atRule\n   * @optionName Modernizr.atRule()\n   * @optionProp atRule\n   * @access public\n   * @function atRule\n   * @param {string} prop - String name of the @-rule to test for\n   * @returns {string|boolean} The string representing the (possibly prefixed)\n   * valid version of the @-rule, or `false` when it is unsupported.\n   * @example\n   * ```js\n   *  var keyframes = Modernizr.atRule('@keyframes');\n   *\n   *  if (keyframes) {\n   *    // keyframes are supported\n   *    // could be `@-webkit-keyframes` or `@keyframes`\n   *  } else {\n   *    // keyframes === `false`\n   *  }\n   * ```\n   *\n   */\n\n  var atRule = function(prop) {\n    var length = prefixes.length;\n    var cssrule = window.CSSRule;\n    var rule;\n\n    if (typeof cssrule === 'undefined') {\n      return undefined;\n    }\n\n    if (!prop) {\n      return false;\n    }\n\n    // remove literal @ from beginning of provided property\n    prop = prop.replace(/^@/, '');\n\n    // CSSRules use underscores instead of dashes\n    rule = prop.replace(/-/g, '_').toUpperCase() + '_RULE';\n\n    if (rule in cssrule) {\n      return '@' + prop;\n    }\n\n    for (var i = 0; i < length; i++) {\n      // prefixes gives us something like -o-, and we want O_\n      var prefix = prefixes[i];\n      var thisRule = prefix.toUpperCase() + '_' + rule;\n\n      if (thisRule in cssrule) {\n        return '@-' + prefix.toLowerCase() + '-' + prop;\n      }\n    }\n\n    return false;\n  };\n\n  ModernizrProto.atRule = atRule;\n\n  \n\n  /**\n   * List of JavaScript DOM values used for tests\n   *\n   * @memberof Modernizr\n   * @name Modernizr._domPrefixes\n   * @optionName Modernizr._domPrefixes\n   * @optionProp domPrefixes\n   * @access public\n   * @example\n   *\n   * Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather\n   * than kebab-case properties, all properties are their Capitalized variant\n   *\n   * ```js\n   * Modernizr._domPrefixes === [ \"Moz\", \"O\", \"ms\", \"Webkit\" ];\n   * ```\n   */\n\n  var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []);\n  ModernizrProto._domPrefixes = domPrefixes;\n  \n\n\n  /**\n   * contains checks to see if a string contains another string\n   *\n   * @access private\n   * @function contains\n   * @param {string} str - The string we want to check for substrings\n   * @param {string} substr - The substring we want to search the first string for\n   * @returns {boolean}\n   */\n\n  function contains(str, substr) {\n    return !!~('' + str).indexOf(substr);\n  }\n\n  ;\n\n  /**\n   * fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill.\n   *\n   * @access private\n   * @function fnBind\n   * @param {function} fn - a function you want to change `this` reference to\n   * @param {object} that - the `this` you want to call the function with\n   * @returns {function} The wrapped version of the supplied function\n   */\n\n  function fnBind(fn, that) {\n    return function() {\n      return fn.apply(that, arguments);\n    };\n  }\n\n  ;\n\n  /**\n   * testDOMProps is a generic DOM property test; if a browser supports\n   *   a certain property, it won't return undefined for it.\n   *\n   * @access private\n   * @function testDOMProps\n   * @param {array.<string>} props - An array of properties to test for\n   * @param {object} obj - An object or Element you want to use to test the parameters again\n   * @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check\n   * @returns {false|*} returns false if the prop is unsupported, otherwise the value that is supported\n   */\n  function testDOMProps(props, obj, elem) {\n    var item;\n\n    for (var i in props) {\n      if (props[i] in obj) {\n\n        // return the property name as a string\n        if (elem === false) {\n          return props[i];\n        }\n\n        item = obj[props[i]];\n\n        // let's bind a function\n        if (is(item, 'function')) {\n          // bind to obj unless overriden\n          return fnBind(item, elem || obj);\n        }\n\n        // return the unbound function or obj or value\n        return item;\n      }\n    }\n    return false;\n  }\n\n  ;\n\n  /**\n   * Create our \"modernizr\" element that we do most feature tests on.\n   *\n   * @access private\n   */\n\n  var modElem = {\n    elem: createElement('modernizr')\n  };\n\n  // Clean up this element\n  Modernizr._q.push(function() {\n    delete modElem.elem;\n  });\n\n  \n\n  var mStyle = {\n    style: modElem.elem.style\n  };\n\n  // kill ref for gc, must happen before mod.elem is removed, so we unshift on to\n  // the front of the queue.\n  Modernizr._q.unshift(function() {\n    delete mStyle.style;\n  });\n\n  \n\n  /**\n   * domToCSS takes a camelCase string and converts it to kebab-case\n   * e.g. boxSizing -> box-sizing\n   *\n   * @access private\n   * @function domToCSS\n   * @param {string} name - String name of camelCase prop we want to convert\n   * @returns {string} The kebab-case version of the supplied name\n   */\n\n  function domToCSS(name) {\n    return name.replace(/([A-Z])/g, function(str, m1) {\n      return '-' + m1.toLowerCase();\n    }).replace(/^ms-/, '-ms-');\n  }\n  ;\n\n\n  /**\n   * wrapper around getComputedStyle, to fix issues with Firefox returning null when\n   * called inside of a hidden iframe\n   *\n   * @access private\n   * @function computedStyle\n   * @param {HTMLElement|SVGElement} - The element we want to find the computed styles of\n   * @param {string|null} [pseudoSelector]- An optional pseudo element selector (e.g. :before), of null if none\n   * @returns {CSSStyleDeclaration}\n   */\n\n  function computedStyle(elem, pseudo, prop) {\n    var result;\n\n    if ('getComputedStyle' in window) {\n      result = getComputedStyle.call(window, elem, pseudo);\n      var console = window.console;\n\n      if (result !== null) {\n        if (prop) {\n          result = result.getPropertyValue(prop);\n        }\n      } else {\n        if (console) {\n          var method = console.error ? 'error' : 'log';\n          console[method].call(console, 'getComputedStyle returning null, its possible modernizr test results are inaccurate');\n        }\n      }\n    } else {\n      result = !pseudo && elem.currentStyle && elem.currentStyle[prop];\n    }\n\n    return result;\n  }\n\n  ;\n\n  /**\n   * getBody returns the body of a document, or an element that can stand in for\n   * the body if a real body does not exist\n   *\n   * @access private\n   * @function getBody\n   * @returns {HTMLElement|SVGElement} Returns the real body of a document, or an\n   * artificially created element that stands in for the body\n   */\n\n  function getBody() {\n    // After page load injecting a fake body doesn't work so check if body exists\n    var body = document.body;\n\n    if (!body) {\n      // Can't use the real body create a fake one.\n      body = createElement(isSVG ? 'svg' : 'body');\n      body.fake = true;\n    }\n\n    return body;\n  }\n\n  ;\n\n  /**\n   * injectElementWithStyles injects an element with style element and some CSS rules\n   *\n   * @access private\n   * @function injectElementWithStyles\n   * @param {string} rule - String representing a css rule\n   * @param {function} callback - A function that is used to test the injected element\n   * @param {number} [nodes] - An integer representing the number of additional nodes you want injected\n   * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes\n   * @returns {boolean}\n   */\n\n  function injectElementWithStyles(rule, callback, nodes, testnames) {\n    var mod = 'modernizr';\n    var style;\n    var ret;\n    var node;\n    var docOverflow;\n    var div = createElement('div');\n    var body = getBody();\n\n    if (parseInt(nodes, 10)) {\n      // In order not to give false positives we create a node for each test\n      // This also allows the method to scale for unspecified uses\n      while (nodes--) {\n        node = createElement('div');\n        node.id = testnames ? testnames[nodes] : mod + (nodes + 1);\n        div.appendChild(node);\n      }\n    }\n\n    style = createElement('style');\n    style.type = 'text/css';\n    style.id = 's' + mod;\n\n    // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.\n    // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270\n    (!body.fake ? div : body).appendChild(style);\n    body.appendChild(div);\n\n    if (style.styleSheet) {\n      style.styleSheet.cssText = rule;\n    } else {\n      style.appendChild(document.createTextNode(rule));\n    }\n    div.id = mod;\n\n    if (body.fake) {\n      //avoid crashing IE8, if background image is used\n      body.style.background = '';\n      //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible\n      body.style.overflow = 'hidden';\n      docOverflow = docElement.style.overflow;\n      docElement.style.overflow = 'hidden';\n      docElement.appendChild(body);\n    }\n\n    ret = callback(div, rule);\n    // If this is done after page load we don't want to remove the body so check if body exists\n    if (body.fake) {\n      body.parentNode.removeChild(body);\n      docElement.style.overflow = docOverflow;\n      // Trigger layout so kinetic scrolling isn't disabled in iOS6+\n      // eslint-disable-next-line\n      docElement.offsetHeight;\n    } else {\n      div.parentNode.removeChild(div);\n    }\n\n    return !!ret;\n\n  }\n\n  ;\n\n  /**\n   * nativeTestProps allows for us to use native feature detection functionality if available.\n   * some prefixed form, or false, in the case of an unsupported rule\n   *\n   * @access private\n   * @function nativeTestProps\n   * @param {array} props - An array of property names\n   * @param {string} value - A string representing the value we want to check via @supports\n   * @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise\n   */\n\n  // Accepts a list of property names and a single value\n  // Returns `undefined` if native detection not available\n  function nativeTestProps(props, value) {\n    var i = props.length;\n    // Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface\n    if ('CSS' in window && 'supports' in window.CSS) {\n      // Try every prefixed variant of the property\n      while (i--) {\n        if (window.CSS.supports(domToCSS(props[i]), value)) {\n          return true;\n        }\n      }\n      return false;\n    }\n    // Otherwise fall back to at-rule (for Opera 12.x)\n    else if ('CSSSupportsRule' in window) {\n      // Build a condition string for every prefixed variant\n      var conditionText = [];\n      while (i--) {\n        conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')');\n      }\n      conditionText = conditionText.join(' or ');\n      return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) {\n        return computedStyle(node, null, 'position') == 'absolute';\n      });\n    }\n    return undefined;\n  }\n  ;\n\n  // testProps is a generic CSS / DOM property test.\n\n  // In testing support for a given CSS property, it's legit to test:\n  //    `elem.style[styleName] !== undefined`\n  // If the property is supported it will return an empty string,\n  // if unsupported it will return undefined.\n\n  // We'll take advantage of this quick test and skip setting a style\n  // on our modernizr element, but instead just testing undefined vs\n  // empty string.\n\n  // Property names can be provided in either camelCase or kebab-case.\n\n  function testProps(props, prefixed, value, skipValueTest) {\n    skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest;\n\n    // Try native detect first\n    if (!is(value, 'undefined')) {\n      var result = nativeTestProps(props, value);\n      if (!is(result, 'undefined')) {\n        return result;\n      }\n    }\n\n    // Otherwise do it properly\n    var afterInit, i, propsLength, prop, before;\n\n    // If we don't have a style element, that means we're running async or after\n    // the core tests, so we'll need to create our own elements to use\n\n    // inside of an SVG element, in certain browsers, the `style` element is only\n    // defined for valid tags. Therefore, if `modernizr` does not have one, we\n    // fall back to a less used element and hope for the best.\n    // for strict XHTML browsers the hardly used samp element is used\n    var elems = ['modernizr', 'tspan', 'samp'];\n    while (!mStyle.style && elems.length) {\n      afterInit = true;\n      mStyle.modElem = createElement(elems.shift());\n      mStyle.style = mStyle.modElem.style;\n    }\n\n    // Delete the objects if we created them.\n    function cleanElems() {\n      if (afterInit) {\n        delete mStyle.style;\n        delete mStyle.modElem;\n      }\n    }\n\n    propsLength = props.length;\n    for (i = 0; i < propsLength; i++) {\n      prop = props[i];\n      before = mStyle.style[prop];\n\n      if (contains(prop, '-')) {\n        prop = cssToDOM(prop);\n      }\n\n      if (mStyle.style[prop] !== undefined) {\n\n        // If value to test has been passed in, do a set-and-check test.\n        // 0 (integer) is a valid property value, so check that `value` isn't\n        // undefined, rather than just checking it's truthy.\n        if (!skipValueTest && !is(value, 'undefined')) {\n\n          // Needs a try catch block because of old IE. This is slow, but will\n          // be avoided in most cases because `skipValueTest` will be used.\n          try {\n            mStyle.style[prop] = value;\n          } catch (e) {}\n\n          // If the property value has changed, we assume the value used is\n          // supported. If `value` is empty string, it'll fail here (because\n          // it hasn't changed), which matches how browsers have implemented\n          // CSS.supports()\n          if (mStyle.style[prop] != before) {\n            cleanElems();\n            return prefixed == 'pfx' ? prop : true;\n          }\n        }\n        // Otherwise just return true, or the property name if this is a\n        // `prefixed()` call\n        else {\n          cleanElems();\n          return prefixed == 'pfx' ? prop : true;\n        }\n      }\n    }\n    cleanElems();\n    return false;\n  }\n\n  ;\n\n  /**\n   * testPropsAll tests a list of DOM properties we want to check against.\n   * We specify literally ALL possible (known and/or likely) properties on\n   * the element including the non-vendor prefixed one, for forward-\n   * compatibility.\n   *\n   * @access private\n   * @function testPropsAll\n   * @param {string} prop - A string of the property to test for\n   * @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip\n   * @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against\n   * @param {string} [value] - A string of a css value\n   * @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set\n   * @returns {false|string} returns the string version of the property, or false if it is unsupported\n   */\n  function testPropsAll(prop, prefixed, elem, value, skipValueTest) {\n\n    var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),\n      props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');\n\n    // did they call .prefixed('boxSizing') or are we just testing a prop?\n    if (is(prefixed, 'string') || is(prefixed, 'undefined')) {\n      return testProps(props, prefixed, value, skipValueTest);\n\n      // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])\n    } else {\n      props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');\n      return testDOMProps(props, prefixed, elem);\n    }\n  }\n\n  // Modernizr.testAllProps() investigates whether a given style property,\n  // or any of its vendor-prefixed variants, is recognized\n  //\n  // Note that the property names must be provided in the camelCase variant.\n  // Modernizr.testAllProps('boxSizing')\n  ModernizrProto.testAllProps = testPropsAll;\n\n  \n\n  /**\n   * prefixed returns the prefixed or nonprefixed property name variant of your input\n   *\n   * @memberof Modernizr\n   * @name Modernizr.prefixed\n   * @optionName Modernizr.prefixed()\n   * @optionProp prefixed\n   * @access public\n   * @function prefixed\n   * @param {string} prop - String name of the property to test for\n   * @param {object} [obj] - An object to test for the prefixed properties on\n   * @param {HTMLElement} [elem] - An element used to test specific properties against\n   * @returns {string|false} The string representing the (possibly prefixed) valid\n   * version of the property, or `false` when it is unsupported.\n   * @example\n   *\n   * Modernizr.prefixed takes a string css value in the DOM style camelCase (as\n   * opposed to the css style kebab-case) form and returns the (possibly prefixed)\n   * version of that property that the browser actually supports.\n   *\n   * For example, in older Firefox...\n   * ```js\n   * prefixed('boxSizing')\n   * ```\n   * returns 'MozBoxSizing'\n   *\n   * In newer Firefox, as well as any other browser that support the unprefixed\n   * version would simply return `boxSizing`. Any browser that does not support\n   * the property at all, it will return `false`.\n   *\n   * By default, prefixed is checked against a DOM element. If you want to check\n   * for a property on another object, just pass it as a second argument\n   *\n   * ```js\n   * var rAF = prefixed('requestAnimationFrame', window);\n   *\n   * raf(function() {\n   *  renderFunction();\n   * })\n   * ```\n   *\n   * Note that this will return _the actual function_ - not the name of the function.\n   * If you need the actual name of the property, pass in `false` as a third argument\n   *\n   * ```js\n   * var rAFProp = prefixed('requestAnimationFrame', window, false);\n   *\n   * rafProp === 'WebkitRequestAnimationFrame' // in older webkit\n   * ```\n   *\n   * One common use case for prefixed is if you're trying to determine which transition\n   * end event to bind to, you might do something like...\n   * ```js\n   * var transEndEventNames = {\n   *     'WebkitTransition' : 'webkitTransitionEnd', * Saf 6, Android Browser\n   *     'MozTransition'    : 'transitionend',       * only for FF < 15\n   *     'transition'       : 'transitionend'        * IE10, Opera, Chrome, FF 15+, Saf 7+\n   * };\n   *\n   * var transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];\n   * ```\n   *\n   * If you want a similar lookup, but in kebab-case, you can use [prefixedCSS](#modernizr-prefixedcss).\n   */\n\n  var prefixed = ModernizrProto.prefixed = function(prop, obj, elem) {\n    if (prop.indexOf('@') === 0) {\n      return atRule(prop);\n    }\n\n    if (prop.indexOf('-') != -1) {\n      // Convert kebab-case to camelCase\n      prop = cssToDOM(prop);\n    }\n    if (!obj) {\n      return testPropsAll(prop, 'pfx');\n    } else {\n      // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'\n      return testPropsAll(prop, obj, elem);\n    }\n  };\n\n  \n/*!\n{\n  \"name\": \"Fullscreen API\",\n  \"property\": \"fullscreen\",\n  \"caniuse\": \"fullscreen\",\n  \"notes\": [{\n    \"name\": \"MDN documentation\",\n    \"href\": \"https://developer.mozilla.org/en/API/Fullscreen\"\n  }],\n  \"polyfills\": [\"screenfulljs\"],\n  \"builderAliases\": [\"fullscreen_api\"]\n}\n!*/\n/* DOC\nDetects support for the ability to make the current website take over the user's entire screen\n*/\n\n  // github.com/Modernizr/Modernizr/issues/739\n  Modernizr.addTest('fullscreen', !!(prefixed('exitFullscreen', document, false) || prefixed('cancelFullScreen', document, false)));\n\n\n  /**\n   * testAllProps determines whether a given CSS property is supported in the browser\n   *\n   * @memberof Modernizr\n   * @name Modernizr.testAllProps\n   * @optionName Modernizr.testAllProps()\n   * @optionProp testAllProps\n   * @access public\n   * @function testAllProps\n   * @param {string} prop - String naming the property to test (either camelCase or kebab-case)\n   * @param {string} [value] - String of the value to test\n   * @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection\n   * @example\n   *\n   * testAllProps determines whether a given CSS property, in some prefixed form,\n   * is supported by the browser.\n   *\n   * ```js\n   * testAllProps('boxSizing')  // true\n   * ```\n   *\n   * It can optionally be given a CSS value in string form to test if a property\n   * value is valid\n   *\n   * ```js\n   * testAllProps('display', 'block') // true\n   * testAllProps('display', 'penguin') // false\n   * ```\n   *\n   * A boolean can be passed as a third parameter to skip the value check when\n   * native detection (@supports) isn't available.\n   *\n   * ```js\n   * testAllProps('shapeOutside', 'content-box', true);\n   * ```\n   */\n\n  function testAllProps(prop, value, skipValueTest) {\n    return testPropsAll(prop, undefined, undefined, value, skipValueTest);\n  }\n  ModernizrProto.testAllProps = testAllProps;\n  \n/*!\n{\n  \"name\": \"CSS Animations\",\n  \"property\": \"cssanimations\",\n  \"caniuse\": \"css-animation\",\n  \"polyfills\": [\"transformie\", \"csssandpaper\"],\n  \"tags\": [\"css\"],\n  \"warnings\": [\"Android < 4 will pass this test, but can only animate a single property at a time\"],\n  \"notes\": [{\n    \"name\" : \"Article: 'Dispelling the Android CSS animation myths'\",\n    \"href\": \"https://goo.gl/OGw5Gm\"\n  }]\n}\n!*/\n/* DOC\nDetects whether or not elements can be animated using CSS\n*/\n\n  Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true));\n\n/*!\n{\n  \"name\": \"CSS Transforms\",\n  \"property\": \"csstransforms\",\n  \"caniuse\": \"transforms2d\",\n  \"tags\": [\"css\"]\n}\n!*/\n\n  Modernizr.addTest('csstransforms', function() {\n    // Android < 3.0 is buggy, so we sniff and blacklist\n    // http://git.io/hHzL7w\n    return navigator.userAgent.indexOf('Android 2.') === -1 &&\n           testAllProps('transform', 'scale(1)', true);\n  });\n\n\n  // Run each test\n  testRunner();\n\n  // Remove the \"no-js\" class if it exists\n  setClasses(classes);\n\n  delete ModernizrProto.addTest;\n  delete ModernizrProto.addAsyncTest;\n\n  // Run the things that are supposed to run after the tests\n  for (var i = 0; i < Modernizr._q.length; i++) {\n    Modernizr._q[i]();\n  }\n\n  // Leak Modernizr namespace\n  window.Modernizr = Modernizr;\n\n\n;\n\n})(window, document);"
  },
  {
    "path": "docs/.vuepress/components/DPlayer.vue",
    "content": "<template>\n<div class=\"dplayer-wrap\">\n    <div class=\"dplayer\" ref=\"dplayer\"><button class=\"load\" v-on:click=\"load\">Load demo</button></div>\n</div>\n</template>\n<script>\nexport default {\n    props: {\n        immediate: {\n            type: Boolean,\n            default: false,\n        },\n        options: {\n            type: Object,\n            default: () => ({\n                autoplay: false,\n                theme: '#FADFA3',\n                loop: true,\n                lang: 'zh-cn',\n                hotkey: true,\n                preload: 'auto',\n                logo: 'https://i.loli.net/2019/06/06/5cf8c5d94521136430.png',\n                volume: 0.7,\n                mutex: true,\n                screenshot: true,\n                subtitle: {\n                    url: 'https://s-sh-17-dplayercdn.oss.dogecdn.com/hikarunara.vtt',\n                    type: 'webvtt',\n                    fontSize: '20px',\n                    bottom: '10%',\n                    color: '#FADFA3'\n                },\n                video: {\n                    url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n                    pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n                    thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg',\n                    type: 'auto'\n                },\n                danmaku: {\n                    id: '9E2E3368B56CDBB4',\n                    api: 'https://api.prprpr.me/dplayer/',\n                    token: 'tokendemo',\n                    user: 'DIYgod',\n                    addition: ['https://s-sh-17-dplayercdn.oss.dogecdn.com/1678963.json']\n                },\n                contextmenu: [\n                    {\n                        text: 'custom1',\n                        link: 'https://github.com/DIYgod/DPlayer'\n                    },\n                    {\n                        text: 'custom2',\n                        click: (player) => {\n                            console.log(player);\n                        }\n                    }\n                ],\n                highlight: [\n                    {\n                        time: 20,\n                        text: '这是第 20 秒'\n                    },\n                    {\n                        time: 120,\n                        text: '这是 2 分钟'\n                    }\n                ]\n            })\n        },\n    },\n    methods: {\n        load: function () {\n            this.options.container = this.$refs.dplayer;\n            setTimeout(() => {\n                this.dplayer = new window.DPlayer(this.options);\n            }, 0);\n        }\n    },\n    mounted: function () {\n        if (this.immediate) {\n            setTimeout(() => {\n                this.dplayer = new window.DPlayer(this.options);\n            }, 0);\n        }\n    },\n    beforeDestroy: function () {\n        this.dplayer && this.dplayer.destroy();\n    }\n}\n</script>\n<style>\n</style>\n"
  },
  {
    "path": "docs/.vuepress/config.js",
    "content": "module.exports = {\n    plugins: {\n        '@vuepress/back-to-top': true,\n        umami: {\n            trackerUrl: 'https://umami.diygod.dev',\n            siteId: 'f3b469a2-8054-4bbb-8873-4035761aa4e3',\n        },\n    },\n    locales: {\n        '/zh/': {\n            lang: 'zh-CN',\n            title: 'DPlayer',\n            description: '🍭 Wow, such a lovely HTML5 danmaku video player',\n        },\n        '/': {\n            lang: 'en-US',\n            title: 'DPlayer',\n            description: '🍭 Wow, such a lovely HTML5 danmaku video player',\n        },\n    },\n    head: [\n        ['link', { rel: 'icon', href: `/logo.png` }],\n        ['script', { src: 'https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js' }],\n        ['script', { src: 'https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js' }],\n        ['script', { src: 'https://cdn.jsdelivr.net/npm/dashjs/dist/dash.all.min.js' }],\n        ['script', { src: 'https://cdn.jsdelivr.net/webtorrent/latest/webtorrent.min.js' }],\n        ['script', { src: 'https://cdn.jsdelivr.net/npm/dplayer/dist/DPlayer.min.js' }],\n    ],\n    theme: 'vuepress-theme-dplayer',\n    themeConfig: {\n        repo: 'MoePlayer/DPlayer',\n        editLinks: true,\n        docsDir: '.',\n        locales: {\n            '/zh/': {\n                lang: 'zh-CN',\n                selectText: '选择语言',\n                label: '简体中文',\n                editLinkText: '在 GitHub 上编辑此页',\n                lastUpdated: '上次更新',\n                nav: [\n                    {\n                        text: '指南',\n                        link: '/zh/guide/',\n                    },\n                    {\n                        text: '生态',\n                        link: '/zh/ecosystem/',\n                    },\n                    {\n                        text: '支持 DPlayer',\n                        link: '/zh/support/',\n                    },\n                ],\n            },\n            '/': {\n                lang: 'en-US',\n                selectText: 'Languages',\n                label: 'English',\n                editLinkText: 'Edit this page on GitHub',\n                lastUpdated: 'Last Updated',\n                nav: [\n                    {\n                        text: 'Guide',\n                        link: '/guide/',\n                    },\n                    {\n                        text: 'Ecosystem',\n                        link: '/ecosystem/',\n                    },\n                    {\n                        text: 'Support DPlayer',\n                        link: '/support/',\n                    },\n                ],\n            },\n        },\n    },\n};\n"
  },
  {
    "path": "docs/.vuepress/styles/index.styl",
    "content": ".navbar .home-link .site-name {\n    color: #F5712C;\n}\n\n.page .custom-block.tip {\n    border-color: #F5712C;\n}\n\n.icon.outbound {\n    display: none;\n}\n\na {\n  word-break: break-all;\n}\n\n#指南 {\n    display: none;\n}\n\n#guide {\n    display: none;\n}\n\n#app .global-ui .sw-update-popup {\n    border: 1px solid #F5712C;\n}\n\n.routes .sidebar-group-items > li > .sidebar-sub-headers > .sidebar-sub-header > a {\n    color: $accentColor;\n}\n\n#dplayer {\n    margin-top: -1.5rem;\n    margin-bottom: 1rem;\n}\n\n.hero .description {\n    display: none;\n}\n\n.hero .action {\n    display: none;\n}\n\n.hero.custom .action {\n    display: block;\n}"
  },
  {
    "path": "docs/.vuepress/styles/palette.styl",
    "content": "$accentColor = #F5712C\n"
  },
  {
    "path": "docs/.vuepress/theme/components/CarbonAds.vue",
    "content": "<script>\nexport default {\n  name: 'CarbonAds',\n\n  watch: {\n    '$route' (to, from) {\n      if (\n        to.path !== from.path\n        // Only reload if the ad has been loaded\n        // otherwise it's possible that the script is appended but\n        // the ads are not loaded yet. This would result in duplicated ads.\n        && this.$el.querySelector('#carbonads')\n      ) {\n        this.$el.innerHTML = ''\n        this.load()\n      }\n    }\n  },\n\n  mounted () {\n    this.load()\n  },\n\n  methods: {\n    load () {\n      const s = document.createElement('script')\n      s.id = '_carbonads_js'\n      s.src = `//cdn.carbonads.com/carbon.js?serve=CEAI6277&placement=dplayerjsorg`\n      this.$el.appendChild(s)\n    }\n  },\n\n  render (h) {\n    return h('div', { class: 'carbon-ads' })\n  }\n}\n</script>\n\n<style lang=\"stylus\">\n.carbon-ads\n  min-height 102px\n  padding 1.5rem 1.5rem 0\n  margin-bottom -0.5rem\n  font-size 0.75rem\n  a\n    color #444\n    font-weight normal\n    display inline\n  .carbon-img\n    float left\n    margin-right 1rem\n    border 1px solid $borderColor\n    img\n      display block\n  .carbon-poweredby\n    color #999\n    display block\n    margin-top 0.5em\n\n@media (max-width: $MQMobile)\n  .carbon-ads\n    .carbon-img img\n      width 100px\n      height 77px\n</style>\n"
  },
  {
    "path": "docs/.vuepress/theme/index.js",
    "content": "module.exports = {\n    extend: '@vuepress/theme-default',\n};\n"
  },
  {
    "path": "docs/.vuepress/theme/layouts/Layout.vue",
    "content": "<template>\n  <ParentLayout>\n    <template #sidebar-top>\n      <CarbonAds />\n    </template>\n  </ParentLayout>\n</template>\n\n<script>\nimport ParentLayout from '@parent-theme/layouts/Layout.vue'\nimport CarbonAds from '@theme/components/CarbonAds.vue'\n\nexport default {\n  name: 'Layout',\n\n  components: {\n    ParentLayout,\n    CarbonAds,\n  }\n}\n</script>\n"
  },
  {
    "path": "docs/.vuepress/theme/package.json",
    "content": "{\n    \"name\": \"vuepress-theme-dplayer\",\n    \"main\": \"index.js\"\n}\n"
  },
  {
    "path": "docs/README.md",
    "content": "---\nhome: true\nactionText: Get Started →\nactionLink: /guide/\nfooter: MIT Licensed | Made with love by DIYgod\n---\n\n<div>\n  <DPlayer :immediate=\"true\"></DPlayer>\n</div>\n\n<div class=\"hero custom\"><p class=\"action\"><router-link to=\"/guide/\" class=\"nav-link action-button\">Get Started →</router-link></p></div>\n"
  },
  {
    "path": "docs/ecosystem.md",
    "content": "---\nsidebar: auto\n---\n\n# Ecosystem\n\nLet's make DPlayer better, feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n\n## Help\n\n### Creating issue\n\n-   [MoePlayer/DPlayer/issues](https://github.com/MoePlayer/DPlayer/issues)\n\n## Related Projects\n\n### Tooling\n\n-   [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails): generate video thumbnails\n\n### Danmaku api\n\n-   [DPlayer-node](https://github.com/MoePlayer/DPlayer-node): Node.js\n-   [laravel-danmaku](https://github.com/MoePlayer/laravel-danmaku): PHP\n-   [dplayer-live-backend](https://github.com/Izumi-kun/dplayer-live-backend): Node.js, WebSocket live backend\n-   [RailsGun](https://github.com/MoePlayer/RailsGun): Ruby\n\n### Plugins\n\n-   [DPlayer-for-typecho](https://github.com/volio/DPlayer-for-typecho): Typecho\n-   [Hexo-tag-dplayer](https://github.com/NextMoe/hexo-tag-dplayer): Hexo\n-   [DPlayer_for_Z-BlogPHP](https://github.com/fghrsh/DPlayer_for_Z-BlogPHP): Z-BlogPHP\n-   [DPlayer for Discuz!](https://coding.net/u/Click_04/p/video/git): Discuz!\n-   [DPlayer for WordPress](https://github.com/BlueCocoa/DPlayer-WordPress): WordPress\n-   [DPlayerHandle](https://github.com/kn007/DPlayerHandle): WordPress\n-   [Selection](https://github.com/GreatSatan79/Selection): WordPress\n-   [Vue-DPlayer](https://github.com/sinchang/vue-dplayer): Vue\n-   [react-dplayer](https://github.com/hnsylitao/react-dplayer): React\n-   [rc-dplayer](https://github.com/tianfeng98/rc-dplayer): React\n\n### Other\n\n-   [DPlayer-Lite](https://github.com/kn007/DPlayer-Lite): lite version\n-   [hlsjs-p2p-engine](https://github.com/cdnbye/hlsjs-p2p-engine)\n\n## Who use DPlayer?\n\n-   [学习强国](https://itunes.apple.com/cn/app/%E5%AD%A6%E4%B9%A0%E5%BC%BA%E5%9B%BD/id1426355645?mt=8): “学习强国”学习平台精心打造的手机客户端\n-   [小红书](https://www.xiaohongshu.com/): 中国最大的生活社区分享平台，同时也是发现全球好物的电商平台\n-   [极客时间](https://time.geekbang.org/): 极客邦科技出品的一款 IT 内容知识服务 App\n-   [嘀哩嘀哩](http://www.dilidili.wang/): 兴趣使然的无名小站（D 站）\n-   [银色子弹](https://www.sbsub.com/): 银色子弹，简称银弹，由多数柯南热爱者聚集在一起的组织\n-   [浙江大学 CC98 论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛，中国各大学中较活跃的 BBS 之一\n-   [纸飞机南航青年网络社区](http://my.nuaa.edu.cn/video-video.html): 南京航空航天大学门户网站\n-   [otomads](https://otomads.com/): 专注于音 MAD 的视频弹幕网站\n-   [Cloudreve](https://github.com/HFO4/Cloudreve): 基于 ThinkPHP 构建的网盘系统\n-   [arozos](https://github.com/tobychui/arozos): General purposed Web Desktop Operating Platform / OS for Raspberry Pis\n-   [新东方云教室](https://roombox.xdf.cn/)\n-   [BBHouse](https://github.com/endcloud/bbhouse-tauri): A Bilibili Cross-Platform Desktop Client Powered By Tauri\n-   [Tampermonkey 阿里云盘](https://greasyfork.org/zh-CN/scripts/425955-%E9%98%BF%E9%87%8C%E4%BA%91%E7%9B%98)\n-   [arozos](https://github.com/tobychui/arozos)\n-   [GBCLStudio/fof-upload-qcloud](https://github.com/GBCLStudio/FoF-Upload-Qcloud)\n-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n\n## Contributors\n\nThis project exists thanks to all the people who contribute.\n\n<a href=\"https://github.com/MoePlayer/DPlayer/graphs/contributors\"><img src=\"https://opencollective.com/DPlayer/contributors.svg?width=890\" /></a>\n"
  },
  {
    "path": "docs/guide.md",
    "content": "---\nsidebar: auto\n---\n\n# Guide\n\n# DPlayer\n\n🍭 Wow, such a lovely HTML5 danmaku video player\n\n<DPlayer :immediate=\"true\"></DPlayer>\n\n&nbsp;\n\n## Special Thanks\n\n### Sponsors\n\n&nbsp;\n\n<div>\n<a href=\"https://www.dogecloud.com/?ref=dplayer\" target=\"_blank\">\n    <img height=\"60px\" src=\"https://player.dogecloud.com/img/logo_with_product3.png\">\n</a>\n</div>\n\n## Installation\n\nUsing npm:\n\n```\nnpm install dplayer --save\n```\n\nUsing Yarn:\n\n```\nyarn add dplayer\n```\n\n## Quick Start\n\nAt first, let's initialize a simplest DPlayer\n\nLoad DPlayer files\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"DPlayer.min.js\"></script>\n```\n\nOr work with module bundler:\n\n```js\nimport DPlayer from 'dplayer';\n\nconst dp = new DPlayer(options);\n```\n\nInitialization in js:\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    screenshot: true,\n    video: {\n        url: 'demo.mp4',\n        pic: 'demo.jpg',\n        thumbnails: 'thumbnails.jpg',\n    },\n    subtitle: {\n        url: 'webvtt.vtt',\n    },\n    danmaku: {\n        id: 'demo',\n        api: 'https://api.prprpr.me/dplayer/',\n    },\n});\n```\n\n## Options\n\nYou can custom your player instance by those options\n\n| Name                 | Default                            | Description                                                                                                                                                |\n| -------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| container            | document.querySelector('.dplayer') | player container                                                                                                                                           |\n| live                 | false                              | enable live mode, see [#live](#live)                                                                                                                       |\n| autoplay             | false                              | video autoplay                                                                                                                                             |\n| theme                | '#b7daff'                          | main color                                                                                                                                                 |\n| loop                 | false                              | video loop                                                                                                                                                 |\n| lang                 | navigator.language.toLowerCase()   | values: 'en', 'zh-cn', 'zh-tw'                                                                                                                             |\n| screenshot           | false                              | enable screenshot, if true, video and video poster must enable Cross-Origin                                                                                |\n| airplay              | false                              | enable airplay in Safari                                                                                                                                   |\n| chromecast           | false                              | enable Chromecast                                                                                                                                          |\n| hotkey               | true                               | enable hotkey, support FF, FR, volume control, play & pause                                                                                                |\n| preload              | 'auto'                             | values: 'none', 'metadata', 'auto'                                                                                                                         |\n| volume               | 0.7                                | default volume, notice that player will remember user setting, default volume will not work after user set volume themselves                               |\n| playbackSpeed        | [0.5, 0.75, 1, 1.25, 1.5, 2]       | optional playback speed, or or you can set a custom one                                                                                                    |\n| logo                 | -                                  | showing logo in the top left corner, you can adjust its size and position by CSS                                                                           |\n| apiBackend           | -                                  | getting and sending danmaku in your way, see [#live](#live)                                                                                                |\n| preventClickToggle   | false                              | prevent toggle video play/pause status when click player                                                                                                   |\n| video                | -                                  | video info                                                                                                                                                 |\n| video.quality        | -                                  | see [#Quality switching](#quality-switching)                                                                                                               |\n| video.defaultQuality | -                                  | see [#Quality switching](#quality-switching)                                                                                                               |\n| video.url            | -                                  | video url                                                                                                                                                  |\n| video.pic            | -                                  | video poster                                                                                                                                               |\n| video.thumbnails     | -                                  | video thumbnails, generated by [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails)                                                       |\n| video.type           | 'auto'                             | values: 'auto', 'hls', 'flv', 'dash', 'webtorrent', 'normal' or other custom type, see [#MSE support](#mse-support)                                        |\n| video.customType     | -                                  | custom video type, see [#MSE support](#mse-support)                                                                                                        |\n| subtitle             | -                                  | external subtitle                                                                                                                                          |\n| subtitle.url         | `required`                         | subtitle url                                                                                                                                               |\n| subtitle.type        | 'webvtt'                           | subtitle type, values: 'webvtt', 'ass', but only webvtt is supported for now                                                                               |\n| subtitle.fontSize    | '20px'                             | subtitle font size                                                                                                                                         |\n| subtitle.bottom      | '40px'                             | the distance between the subtitle and player bottom, values like: '10px' '10%'                                                                             |\n| subtitle.color       | '#fff'                             | subtitle color                                                                                                                                             |\n| danmaku              | -                                  | showing danmaku                                                                                                                                            |\n| danmaku.id           | `required`                         | danmaku pool id, it must be unique                                                                                                                         |\n| danmaku.api          | `required`                         | see [#Danmaku API](#danmaku-api)                                                                                                                           |\n| danmaku.token        | -                                  | back end verification token                                                                                                                                |\n| danmaku.maximum      | -                                  | danmaku maximum quantity                                                                                                                                   |\n| danmaku.addition     | -                                  | additional danmaku, see [#bilibili danmaku](#bilibili-danmaku)                                                                                             |\n| danmaku.user         | 'DIYgod'                           | danmaku user name                                                                                                                                          |\n| danmaku.bottom       | -                                  | values like: '10px' '10%', the distance between the danmaku bottom and player bottom, in order to prevent warding off subtitle                             |\n| danmaku.unlimited    | false                              | display all danmaku even though danmaku overlap, notice that player will remember user setting, default setting will not work after user set it themselves |\n| danmaku.speedRate    | 1                                  | danmaku speed multiplier, the larger the faster                                                                                                            |\n| contextmenu          | []                                 | custom contextmenu                                                                                                                                         |\n| highlight            | []                                 | custom time markers upon progress bar                                                                                                                      |\n| mutex                | true                               | prevent to play multiple player at the same time, pause other players when this player start play                                                          |\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    autoplay: false,\n    theme: '#FADFA3',\n    loop: true,\n    lang: 'zh-cn',\n    screenshot: true,\n    hotkey: true,\n    preload: 'auto',\n    logo: 'logo.png',\n    volume: 0.7,\n    mutex: true,\n    video: {\n        url: 'dplayer.mp4',\n        pic: 'dplayer.png',\n        thumbnails: 'thumbnails.jpg',\n        type: 'auto',\n    },\n    subtitle: {\n        url: 'dplayer.vtt',\n        type: 'webvtt',\n        fontSize: '25px',\n        bottom: '10%',\n        color: '#b7daff',\n    },\n    danmaku: {\n        id: '9E2E3368B56CDBB4',\n        api: 'https://api.prprpr.me/dplayer/',\n        token: 'tokendemo',\n        maximum: 1000,\n        addition: ['https://api.prprpr.me/dplayer/v3/bilibili?aid=4157142'],\n        user: 'DIYgod',\n        bottom: '15%',\n        unlimited: true,\n        speedRate: 0.5,\n    },\n    contextmenu: [\n        {\n            text: 'custom1',\n            link: 'https://github.com/DIYgod/DPlayer',\n        },\n        {\n            text: 'custom2',\n            click: (player) => {\n                console.log(player);\n            },\n        },\n    ],\n    highlight: [\n        {\n            text: 'marker for 20s',\n            time: 20,\n        },\n        {\n            text: 'marker for 2mins',\n            time: 120,\n        },\n    ],\n});\n```\n\n## API\n\n-   `dp.play()`: play video\n\n-   `dp.pause()`: pause video\n\n-   `dp.seek(time: number)`: seek to specified time\n\n    ```js\n    dp.seek(100);\n    ```\n\n-   `dp.toggle()`: toggle between play and pause\n\n-   `dp.on(event: string, handler: function)`: bind video and player events, [see more details](https://dplayer.diygod.dev/guide.html#event-binding)\n\n-   `dp.switchVideo(video, danmaku)`: switch to a new video\n\n    ```js\n    dp.switchVideo(\n        {\n            url: 'second.mp4',\n            pic: 'second.png',\n            thumbnails: 'second.jpg',\n        },\n        {\n            id: 'test',\n            api: 'https://api.prprpr.me/dplayer/',\n            maximum: 3000,\n            user: 'DIYgod',\n        }\n    );\n    ```\n\n-   `dp.notice(text: string, time: number, opacity: number)`: show message, the unit of time is millisecond, the default of time is 2000, the default of opacity is 0.8\n\n    ```js\n    dp.notice('Amazing player', 2000, 0.8);\n    ```\n\n-   `dp.switchQuality(index: number)`: switch quality\n\n-   `dp.destroy()`: destroy player\n\n-   `dp.speed(rate: number)`: set video speed\n\n-   `dp.volume(percentage: number, nostorage: boolean, nonotice: boolean)`: set video volume\n\n    ```js\n    dp.volume(0.1, true, false);\n    ```\n\n-   `dp.video`: native video\n\n-   `dp.video.currentTime`: returns the current playback position\n\n-   `dp.video.duration`: returns video total time\n\n-   `dp.video.paused`: returns whether the video paused\n\n-   most [native api](http://www.w3schools.com/tags/ref_av_dom.asp) are supported\n\n-   `dp.danmaku`\n\n-   `dp.danmaku.send(danmaku, callback: function)`: submit a new danmaku to back end\n\n    ```js\n    dp.danmaku.send(\n        {\n            text: 'dplayer is amazing',\n            color: '#b7daff',\n            type: 'right', // should be `top` `bottom` or `right`\n        },\n        function () {\n            console.log('success');\n        }\n    );\n    ```\n\n-   `dp.danmaku.draw(danmaku)`: draw a new danmaku to player in real time\n\n    ```js\n    dp.danmaku.draw({\n        text: 'DIYgod is amazing',\n        color: '#fff',\n        type: 'top',\n    });\n    ```\n\n-   `dp.danmaku.opacity(percentage: number)`: set danmaku opacity, opacity should between 0 and 1\n\n    ```js\n    dp.danmaku.opacity(0.5);\n    ```\n\n-   `dp.danmaku.clear()`: clear all danmakus\n\n-   `dp.danmaku.hide()`: hide danmaku\n\n-   `dp.danmaku.show()`: show danmaku\n\n-   `dp.fullScreen`: two type: `web` or `browser`, the default one is `browser`\n\n-   `dp.fullScreen.request(type: string)`: request fullscreen\n\n    ```js\n    dp.fullScreen.request('web');\n    ```\n\n-   `dp.fullScreen.cancel(type: string)`: cancel fullscreen\n\n    ```js\n    dp.fullScreen.cancel('web');\n    ```\n\n## Event binding\n\n`dp.on(event, handler)`\n\n```js\ndp.on('ended', function () {\n    console.log('player ended');\n});\n```\n\nVideo events\n\n-   abort\n-   canplay\n-   canplaythrough\n-   durationchange\n-   emptied\n-   ended\n-   error\n-   loadeddata\n-   loadedmetadata\n-   loadstart\n-   mozaudioavailable\n-   pause\n-   play\n-   playing\n-   progress\n-   ratechange\n-   seeked\n-   seeking\n-   stalled\n-   suspend\n-   timeupdate\n-   volumechange\n-   waiting\n\nPlayer events\n\n-   screenshot\n-   thumbnails_show\n-   thumbnails_hide\n-   danmaku_show\n-   danmaku_hide\n-   danmaku_clear\n-   danmaku_loaded\n-   danmaku_send\n-   danmaku_opacity\n-   contextmenu_show\n-   contextmenu_hide\n-   notice_show\n-   notice_hide\n-   quality_start\n-   quality_end\n-   destroy\n-   resize\n-   fullscreen\n-   fullscreen_cancel\n-   webfullscreen\n-   webfullscreen_cancel\n-   subtitle_show\n-   subtitle_hide\n-   subtitle_change\n\n## Quality switching\n\nSet video url and video type in `video.quality`, set default quality by `video.defaultQuality`.\n\n<DPlayer :options=\"{\n    video: {\n        quality: [{\n            name: 'HD',\n            url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n            type: 'hls'\n        }, {\n            name: 'SD',\n            url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n            type: 'normal'\n        }],\n        defaultQuality: 0,\n        pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n        thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg'\n    }\n}\"></DPlayer>\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        quality: [\n            {\n                name: 'HD',\n                url: 'demo.m3u8',\n                type: 'hls',\n            },\n            {\n                name: 'SD',\n                url: 'demo.mp4',\n                type: 'normal',\n            },\n        ],\n        defaultQuality: 0,\n        pic: 'demo.png',\n        thumbnails: 'thumbnails.jpg',\n    },\n});\n```\n\nWhen using `customType` ([Work with other MSE library](#work-with-other-mse-library)), you should listen for the `destroy` and `quality_end` events to destroy the MSE library instance in a timely manner to avoid memory leaks.\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        quality: [\n            {\n                name: 'HD',\n                url: 'demo_hd.m3u8',\n                type: 'Hls',\n            },\n            {\n                name: 'SD',\n                url: 'demo_sd.m3u8',\n                type: 'Hls',\n            },\n        ],\n        defaultQuality: 0,\n        customType: {\n            Hls: (video, player) => {\n                const hls = new window.Hls();\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n\n                hls.sessionId = crypto.randomUUID();\n                player.sessionId = hls.sessionId;\n                player.events.on('destroy', () => {\n                    hls.destroy();\n                });\n                player.events.on('quality_end', () => {\n                    if (hls.sessionId !== player.sessionId) {\n                        hls.destroy();\n                    }\n                });\n            },\n        },\n    },\n});\n```\n\n## Danmaku\n\n### Danmaku API\n\n`danmaku.api`\n\n<!-- **Ready-made API**\n\nurl: https://api.prprpr.me/dplayer/ -->\n\nDaily backup data: [DPlayer-data](https://github.com/DIYgod/DPlayer-data)\n\n**Setting up yourself**\n\n[DPlayer-node](https://github.com/MoePlayer/DPlayer-node)\n\n### bilibili danmaku\n\n`danmaku.addition`\n\nAPI: <https://api.prprpr.me/dplayer/v3/bilibili?aid=[aid]>\n\n```js\nconst option = {\n    danmaku: {\n        // ...\n        addition: ['https://api.prprpr.me/dplayer/v3/bilibili?aid=[aid]'],\n    },\n};\n```\n\n## MSE support\n\n### HLS\n\nIt requires the library [hls.js](https://github.com/video-dev/hls.js) and it should be loaded before `DPlayer.min.js`.\n\n<DPlayer :options=\"{\n    video: {\n        url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n        type: 'hls'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"hls.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: 'hls',\n    },\n    pluginOptions: {\n        hls: {\n            // hls config\n        },\n    },\n});\nconsole.log(dp.plugins.hls); // Hls instance\n```\n\n```js\n// another way, use customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: 'customHls',\n        customType: {\n            customHls: function (video, player) {\n                const hls = new Hls();\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n            },\n        },\n    },\n});\n```\n\n### MPEG DASH\n\nIt requires the library [dash.js](https://github.com/Dash-Industry-Forum/dash.js) and it should be loaded before `DPlayer.min.js`.\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"dash.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.mpd',\n        type: 'dash',\n    },\n    pluginOptions: {\n        dash: {\n            // dash config\n        },\n    },\n});\nconsole.log(dp.plugins.dash); // Dash instance\n```\n\n```js\n// another way, use customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.mpd',\n        type: 'customDash',\n        customType: {\n            customDash: function (video, player) {\n                dashjs.MediaPlayer().create().initialize(video, video.src, false);\n            },\n        },\n    },\n});\n```\n\n### MPEG DASH (Shaka)\n\nIt requires the library [shaka-player](https://github.com/google/shaka-player) and it should be loaded before `DPlayer.min.js`.\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"shaka-player.compiled.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    screenshot: true,\n    video: {\n        url: 'demo.mpd',\n        type: 'shakaDash',\n        customType: {\n            shakaDash: function (video, player) {\n                var src = video.src;\n                var playerShaka = new shaka.Player(video); // 将会修改 video.src\n                playerShaka.load(src);\n            },\n        },\n    },\n});\n```\n\n### FLV\n\nIt requires the library [flv.js](https://github.com/Bilibili/flv.js) and it should be loaded before `DPlayer.min.js`.\n\n<DPlayer :options=\"{\n    video: {\n        url: 'https://api.dogecloud.com/player/get.flv?vcode=5ac682e6f8231991&userId=17&ext=.flv',\n        type: 'flv'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"flv.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.flv',\n        type: 'flv',\n    },\n    pluginOptions: {\n        flv: {\n            // refer to https://github.com/bilibili/flv.js/blob/master/docs/api.md#flvjscreateplayer\n            mediaDataSource: {\n                // mediaDataSource config\n            },\n            config: {\n                // config\n            },\n        },\n    },\n});\nconsole.log(dp.plugins.flv); // flv instance\n```\n\n```js\n// another way, use customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.flv',\n        type: 'customFlv',\n        customType: {\n            customFlv: function (video, player) {\n                const flvPlayer = flvjs.createPlayer({\n                    type: 'flv',\n                    url: video.src,\n                });\n                flvPlayer.attachMediaElement(video);\n                flvPlayer.load();\n            },\n        },\n    },\n});\n```\n\n### WebTorrent\n\nIt requires the library [webtorrent](https://github.com/webtorrent/webtorrent) and it should be loaded before `DPlayer.min.js`.\n\n<DPlayer :options=\"{\n    video: {\n        url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',\n        type: 'webtorrent'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"webtorrent.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'magnet:demo',\n        type: 'webtorrent',\n    },\n    pluginOptions: {\n        webtorrent: {\n            // webtorrent config\n        },\n    },\n});\nconsole.log(dp.plugins.webtorrent); // WebTorrent instance\n```\n\n```js\n// another way, use customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'magnet:demo',\n        type: 'customWebTorrent',\n        customType: {\n            customWebTorrent: function (video, player) {\n                player.container.classList.add('dplayer-loading');\n                const client = new WebTorrent();\n                const torrentId = video.src;\n                client.add(torrentId, (torrent) => {\n                    const file = torrent.files.find((file) => file.name.endsWith('.mp4'));\n                    file.renderTo(\n                        video,\n                        {\n                            autoplay: player.options.autoplay,\n                        },\n                        () => {\n                            player.container.classList.remove('dplayer-loading');\n                        }\n                    );\n                });\n            },\n        },\n    },\n});\n```\n\n### Work with other MSE library\n\nDPlayer can work with any MSE library via `customType` option.\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"https://cdn.jsdelivr.net/npm/cdnbye@latest\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nvar type = 'normal';\nif (Hls.isSupported() && Hls.WEBRTC_SUPPORT) {\n    type = 'customHls';\n}\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: type,\n        customType: {\n            customHls: function (video, player) {\n                const hls = new Hls({\n                    debug: false,\n                    // Other hlsjsConfig options provided by hls.js\n                    p2pConfig: {\n                        live: false,\n                        // Other p2pConfig options provided by CDNBye http://www.cdnbye.com/en/\n                    },\n                });\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n            },\n        },\n    },\n});\n```\n\n## Live\n\nYou can use DPlayer in live, but if you want live danmaku, you should prepare a WebSocket backend yourself.\n\n<DPlayer :options=\"{\n    live: true,\n    video: {\n        url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n        type: 'hls'\n    }\n}\"></DPlayer>\n\nInit player:\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    live: true,\n    danmaku: true,\n    apiBackend: {\n        read: function (options) {\n            console.log('Pretend to connect WebSocket');\n            options.success([]);\n        },\n        send: function (options) {\n            console.log('Pretend to send danmaku via WebSocket', options.data);\n            options.success();\n        },\n    },\n    video: {\n        url: 'demo.m3u8',\n        type: 'hls',\n    },\n});\n```\n\nDraw danmaku after getting a danmaku via WebSocket:\n\n```js\nconst danmaku = {\n    text: 'Get a danmaku via WebSocket',\n    color: '#fff',\n    type: 'right',\n};\ndp.danmaku.draw(danmaku);\n```\n\n## FAQ\n\n### Why can't player be full screen?\n\nIf player is contained in a iframe, try adding the `allowfullscreen` attribute to the iframe.\n\nFor full browser support it should look like this:\n\n```html\n<iframe src=\"example.com\" allowfullscreen=\"allowfullscreen\" mozallowfullscreen=\"mozallowfullscreen\" msallowfullscreen=\"msallowfullscreen\" oallowfullscreen=\"oallowfullscreen\" webkitallowfullscreen=\"webkitallowfullscreen\"></iframe>\n```\n\n### Why can't player autoplay in some mobile browsers?\n\nMost mobile browsers forbid video autoplay, you wont be able to achieve it without hacks."
  },
  {
    "path": "docs/package.json",
    "content": "{\n    \"name\": \"docs\",\n    \"scripts\": {\n        \"docs:dev\": \"vuepress dev\",\n        \"docs:build\": \"vuepress build\"\n    },\n    \"author\": \"\",\n    \"license\": \"ISC\",\n    \"devDependencies\": {\n        \"@vuepress/plugin-back-to-top\": \"^1.9.10\",\n        \"@vuepress/plugin-google-analytics\": \"^1.9.10\",\n        \"vuepress\": \"^1.9.10\",\n        \"vuepress-plugin-umami\": \"^0.0.4\"\n    }\n}\n"
  },
  {
    "path": "docs/support.md",
    "content": "---\nsidebar: auto\n---\n\n# Sponsor DPlayer Development\n\nDPlayer is an MIT licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing.\n\nIf you run a business and are using DPlayer in a revenue-generating product, it makes business sense to sponsor DPlayer development: it ensures the project that your product relies on stays healthy and actively maintained.\n\nIf you are an individual user and have enjoyed the productivity of using DPlayer, consider donating as a sign of appreciation - like buying me coffee once in a while :)\n\nYou can support DPlayer via donations.\n\n### Recurring Donation\n\nRecurring donors will be rewarded via express issue response, or even have your name displayed on our GitHub page and website.\n\n-   Become a Sponser on [GitHub](https://github.com/sponsors/DIYgod)\n-   Become a Sponser on [Patreon](https://www.patreon.com/DIYgod)\n-   Become a Sponser on [爱发电](https://afdian.net/@diygod)\n-   Contact us directly: i@diygod.me\n\n### One-time Donation\n\nWe accept donations via the following ways:\n\n-   [WeChat Pay](https://diygod.me/images/wx.jpg)\n-   [Alipay](https://diygod.me/images/zfb.jpg)\n-   [Paypal](https://www.paypal.me/DIYgod)\n\n## Sponsors\n\n<a href=\"https://www.dogecloud.com/?ref=dplayer\" target=\"_blank\">\n    <img width=\"222px\" src=\"hhttps://player.dogecloud.com/img/logo_with_product3.png\">\n</a>\n\n## DPlayer contributors\n\nThis project exists thanks to all the people who contribute.\n\n<a href=\"https://github.com/MoePlayer/DPlayer/graphs/contributors\"><img src=\"https://opencollective.com/DPlayer/contributors.svg?width=890\" /></a>\n"
  },
  {
    "path": "docs/zh/README.md",
    "content": "---\nhome: true\nactionText: Get Started →\nactionLink: /zh/guide/\nfooter: MIT Licensed | Made with love by DIYgod\n---\n\n<div>\n  <DPlayer :immediate=\"true\"></DPlayer>\n</div>\n\n<div class=\"hero custom\"><p class=\"action\"><router-link to=\"/zh/guide/\" class=\"nav-link action-button\">快速上手 →</router-link></p></div>\n"
  },
  {
    "path": "docs/zh/ecosystem.md",
    "content": "---\nsidebar: auto\n---\n\n# 生态\n\n让 DPlayer 变得更好，请随意在 [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31) 提交你的项目和产品\n\n## 帮助\n\n### 提交 issue\n\n-   [MoePlayer/DPlayer/issues](https://github.com/MoePlayer/DPlayer/issues)\n\n## 相关项目\n\n### 工具\n\n-   [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails): generate video thumbnails\n\n### 弹幕接口\n\n-   [DPlayer-node](https://github.com/MoePlayer/DPlayer-node): Node.js\n-   [laravel-danmaku](https://github.com/MoePlayer/laravel-danmaku): PHP\n-   [dplayer-live-backend](https://github.com/Izumi-kun/dplayer-live-backend): Node.js, WebSocket live backend\n-   [RailsGun](https://github.com/MoePlayer/RailsGun): Ruby\n\n### 插件\n\n-   [DPlayer-for-typecho](https://github.com/volio/DPlayer-for-typecho): Typecho\n-   [Hexo-tag-dplayer](https://github.com/NextMoe/hexo-tag-dplayer): Hexo\n-   [DPlayer_for_Z-BlogPHP](https://github.com/fghrsh/DPlayer_for_Z-BlogPHP): Z-BlogPHP\n-   [DPlayer for Discuz!](https://coding.net/u/Click_04/p/video/git): Discuz!\n-   [DPlayer for WordPress](https://github.com/BlueCocoa/DPlayer-WordPress): WordPress\n-   [DPlayerHandle](https://github.com/kn007/DPlayerHandle): WordPress\n-   [Selection](https://github.com/GreatSatan79/Selection): WordPress\n-   [Vue-DPlayer](https://github.com/sinchang/vue-dplayer): Vue\n-   [react-dplayer](https://github.com/hnsylitao/react-dplayer): React\n-   [rc-dplayer](https://github.com/tianfeng98/rc-dplayer): React\n\n### 其他\n\n-   [DPlayer-Lite](https://github.com/kn007/DPlayer-Lite): lite version\n-   [hlsjs-p2p-engine](https://github.com/cdnbye/hlsjs-p2p-engine)\n\n## 谁在用 DPlayer?\n\n-   [学习强国](https://itunes.apple.com/cn/app/%E5%AD%A6%E4%B9%A0%E5%BC%BA%E5%9B%BD/id1426355645?mt=8): “学习强国”学习平台精心打造的手机客户端\n-   [小红书](https://www.xiaohongshu.com/): 中国最大的生活社区分享平台，同时也是发现全球好物的电商平台\n-   [极客时间](https://time.geekbang.org/): 极客邦科技出品的一款 IT 内容知识服务 App\n-   [嘀哩嘀哩](http://www.dilidili.wang/): 兴趣使然的无名小站（D 站）\n-   [银色子弹](https://www.sbsub.com/): 银色子弹，简称银弹，由多数柯南热爱者聚集在一起的组织\n-   [浙江大学 CC98 论坛](https://zh.wikipedia.org/wiki/CC98%E8%AE%BA%E5%9D%9B): 浙江大学校网内规模最大的论坛，中国各大学中较活跃的 BBS 之一\n-   [纸飞机南航青年网络社区](http://my.nuaa.edu.cn/video-video.html): 南京航空航天大学门户网站\n-   [otomads](https://otomads.com/): 专注于音 MAD 的视频弹幕网站\n-   [Cloudreve](https://github.com/HFO4/Cloudreve): 基于 ThinkPHP 构建的网盘系统\n-   [arozos](https://github.com/tobychui/arozos): General purposed Web Desktop Operating Platform / OS for Raspberry Pis\n-   [新东方云教室](https://roombox.xdf.cn/)\n-   [BBHouse](https://github.com/endcloud/bbhouse-tauri): A Bilibili Cross-Platform Desktop Client Powered By Tauri\n-   [Tampermonkey 阿里云盘](https://greasyfork.org/zh-CN/scripts/425955-%E9%98%BF%E9%87%8C%E4%BA%91%E7%9B%98)\n-   [arozos](https://github.com/tobychui/arozos)\n-   [GBCLStudio/fof-upload-qcloud](https://github.com/GBCLStudio/FoF-Upload-Qcloud)\n-   Feel free to submit yours in [`Let me know!`](https://github.com/MoePlayer/DPlayer/issues/31)\n"
  },
  {
    "path": "docs/zh/guide.md",
    "content": "---\nsidebar: auto\n---\n\n# 指南\n\n# DPlayer\n\n🍭 Wow, such a lovely HTML5 danmaku video player\n\n<DPlayer :immediate=\"true\"></DPlayer>\n\n&nbsp;\n\n## Special Thanks\n\n### Sponsors\n\n<div>\n<a href=\"https://www.dogecloud.com/?ref=dplayer\" target=\"_blank\">\n    <img height=\"60px\" src=\"https://player.dogecloud.com/img/logo_with_product3.png\">\n</a>\n</div>\n\n## 安装\n\n使用 npm:\n\n```\nnpm install dplayer --save\n```\n\n使用 Yarn:\n\n```\nyarn add dplayer\n```\n\n## 快速开始\n\n我们先尝试初始化一个最简单的 DPlayer\n\n加载播放器文件:\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n或者使用模块管理器:\n\n```js\nimport DPlayer from 'dplayer';\n\nconst dp = new DPlayer(options);\n```\n\n在 js 里初始化:\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.mp4',\n    },\n});\n```\n\n一个最简单的 DPlayer 就初始化好了，它只有最基本的视频播放功能\n\n## 参数\n\nDPlayer 有丰富的参数可以自定义你的播放器实例\n\n| 名称                 | 默认值                             | 描述                                                                                                    |\n| -------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| container            | document.querySelector('.dplayer') | 播放器容器元素                                                                                          |\n| live                 | false                              | 开启直播模式, 见[#直播](#直播)                                                                          |\n| autoplay             | false                              | 视频自动播放                                                                                            |\n| theme                | '#b7daff'                          | 主题色                                                                                                  |\n| loop                 | false                              | 视频循环播放                                                                                            |\n| lang                 | navigator.language.toLowerCase()   | 可选值: 'en', 'zh-cn', 'zh-tw'                                                                          |\n| screenshot           | false                              | 开启截图，如果开启，视频和视频封面需要允许跨域                                                          |\n| hotkey               | true                               | 开启热键，支持快进、快退、音量控制、播放暂停                                                            |\n| airplay              | false                              | 在 Safari 中开启 AirPlay                                                                                |\n| chromecast           | false                              | 启用 Chromecast                                                                                         |\n| preload              | 'auto'                             | 视频预加载，可选值: 'none', 'metadata', 'auto'                                                          |\n| volume               | 0.7                                | 默认音量，请注意播放器会记忆用户设置，用户手动设置音量后默认音量即失效                                  |\n| playbackSpeed        | [0.5, 0.75, 1, 1.25, 1.5, 2]       | 可选的播放速率，可以设置成自定义的数组                                                                  |\n| logo                 | -                                  | 在左上角展示一个 logo，你可以通过 CSS 调整它的大小和位置                                                |\n| apiBackend           | -                                  | 自定义获取和发送弹幕行为，见[#直播](#直播)                                                              |\n| preventClickToggle   | false                              | 阻止点击播放器时候自动切换播放/暂停                                                                     |\n| video                | -                                  | 视频信息                                                                                                |\n| video.quality        | -                                  | 见[#清晰度切换](#清晰度切换)                                                                            |\n| video.defaultQuality | -                                  | 见[#清晰度切换](#清晰度切换)                                                                            |\n| video.url            | -                                  | 视频链接                                                                                                |\n| video.pic            | -                                  | 视频封面                                                                                                |\n| video.thumbnails     | -                                  | 视频缩略图，可以使用 [DPlayer-thumbnails](https://github.com/MoePlayer/DPlayer-thumbnails) 生成         |\n| video.type           | 'auto'                             | 可选值: 'auto', 'hls', 'flv', 'dash', 'webtorrent', 'normal' 或其他自定义类型, 见[#MSE 支持](#mse-支持) |\n| video.customType     | -                                  | 自定义类型, 见[#MSE 支持](#mse-支持)                                                                    |\n| subtitle             | -                                  | 外挂字幕                                                                                                |\n| subtitle.url         | `required`                         | 字幕链接                                                                                                |\n| subtitle.type        | 'webvtt'                           | 字幕类型，可选值: 'webvtt', 'ass'，目前只支持 webvtt                                                    |\n| subtitle.fontSize    | '20px'                             | 字幕字号                                                                                                |\n| subtitle.bottom      | '40px'                             | 字幕距离播放器底部的距离，取值形如: '10px' '10%'                                                        |\n| subtitle.color       | '#fff'                             | 字幕颜色                                                                                                |\n| danmaku              | -                                  | 显示弹幕                                                                                                |\n| danmaku.id           | `required`                         | 弹幕池 id，必须唯一                                                                                     |\n| danmaku.api          | `required`                         | 见[#弹幕接口](#弹幕接口)                                                                                |\n| danmaku.token        | -                                  | 弹幕后端验证 token                                                                                      |\n| danmaku.maximum      | -                                  | 弹幕最大数量                                                                                            |\n| danmaku.addition     | -                                  | 额外外挂弹幕，见[#bilibili 弹幕](#bilibili-弹幕)                                                        |\n| danmaku.user         | 'DIYgod'                           | 弹幕用户名                                                                                              |\n| danmaku.bottom       | -                                  | 弹幕距离播放器底部的距离，防止遮挡字幕，取值形如: '10px' '10%'                                          |\n| danmaku.unlimited    | false                              | 海量弹幕模式，即使重叠也展示全部弹幕，请注意播放器会记忆用户设置，用户手动设置后即失效                  |\n| danmaku.speedRate    | 1                                  | 弹幕速度倍率，越大速度越快                                                                              |\n| contextmenu          | []                                 | 自定义右键菜单                                                                                          |\n| highlight            | []                                 | 自定义进度条提示点                                                                                      |\n| mutex                | true                               | 互斥，阻止多个播放器同时播放，当前播放器播放时暂停其他播放器                                            |\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    autoplay: false,\n    theme: '#FADFA3',\n    loop: true,\n    lang: 'zh-cn',\n    screenshot: true,\n    hotkey: true,\n    preload: 'auto',\n    logo: 'logo.png',\n    volume: 0.7,\n    mutex: true,\n    video: {\n        url: 'dplayer.mp4',\n        pic: 'dplayer.png',\n        thumbnails: 'thumbnails.jpg',\n        type: 'auto',\n    },\n    subtitle: {\n        url: 'dplayer.vtt',\n        type: 'webvtt',\n        fontSize: '25px',\n        bottom: '10%',\n        color: '#b7daff',\n    },\n    danmaku: {\n        id: '9E2E3368B56CDBB4',\n        api: 'https://api.prprpr.me/dplayer/',\n        token: 'tokendemo',\n        maximum: 1000,\n        addition: ['https://api.prprpr.me/dplayer/v3/bilibili?aid=4157142'],\n        user: 'DIYgod',\n        bottom: '15%',\n        unlimited: true,\n        speedRate: 0.5,\n    },\n    contextmenu: [\n        {\n            text: 'custom1',\n            link: 'https://github.com/DIYgod/DPlayer',\n        },\n        {\n            text: 'custom2',\n            click: (player) => {\n                console.log(player);\n            },\n        },\n    ],\n    highlight: [\n        {\n            time: 20,\n            text: '这是第 20 秒',\n        },\n        {\n            time: 120,\n            text: '这是 2 分钟',\n        },\n    ],\n});\n```\n\n## API\n\n-   `dp.play()`: 播放视频\n\n-   `dp.pause()`: 暂停视频\n\n-   `dp.seek(time: number)`: 跳转到特定时间\n\n    ```js\n    dp.seek(100);\n    ```\n\n-   `dp.toggle()`: 切换播放和暂停\n\n-   `dp.on(event: string, handler: function)`: 绑定视频和播放器事件，见[#事件绑定](#事件绑定)\n\n-   `dp.switchVideo(video, danmaku)`: 切换到其他视频\n\n    ```js\n    dp.switchVideo(\n        {\n            url: 'second.mp4',\n            pic: 'second.png',\n            thumbnails: 'second.jpg',\n        },\n        {\n            id: 'test',\n            api: 'https://api.prprpr.me/dplayer/',\n            maximum: 3000,\n            user: 'DIYgod',\n        }\n    );\n    ```\n\n-   `dp.notice(text: string, time: number)`: 显示通知，时间的单位为毫秒，默认时间 2000 毫秒，默认透明度 0.8\n\n-   `dp.switchQuality(index: number)`: 切换清晰度\n\n-   `dp.destroy()`: 销毁播放器\n\n-   `dp.speed(rate: number)`: 设置视频速度\n\n-   `dp.volume(percentage: number, nostorage: boolean, nonotice: boolean)`: 设置视频音量\n\n    ```js\n    dp.volume(0.1, true, false);\n    ```\n\n-   `dp.video`: 原生 video\n\n-   `dp.video.currentTime`: 返回视频当前播放时间\n\n-   `dp.video.duration`: 返回视频总时间\n\n-   `dp.video.paused`: 返回视频是否暂停\n\n-   支持大多数[原生 video 接口](http://www.w3schools.com/tags/ref_av_dom.asp)\n\n-   `dp.danmaku`\n\n-   `dp.danmaku.send(danmaku, callback: function)`: 提交一个新弹幕\n\n    ```js\n    dp.danmaku.send(\n        {\n            text: 'dplayer is amazing',\n            color: '#b7daff',\n            type: 'right', // should be `top` `bottom` or `right`\n        },\n        function () {\n            console.log('success');\n        }\n    );\n    ```\n\n-   `dp.danmaku.draw(danmaku)`: 实时绘制一个新弹幕\n\n    ```js\n    dp.danmaku.draw({\n        text: 'DIYgod is amazing',\n        color: '#fff',\n        type: 'top',\n    });\n    ```\n\n-   `dp.danmaku.opacity(percentage: number)`: 设置弹幕透明度，透明度值在 0 到 1 之间\n\n    ```js\n    dp.danmaku.opacity(0.5);\n    ```\n\n-   `dp.danmaku.clear()`: 清除所有弹幕\n\n-   `dp.danmaku.hide()`: 隐藏弹幕\n\n-   `dp.danmaku.show()`: 显示弹幕\n\n-   `dp.fullScreen`: 两个类型：`web` 和 `browser`，默认类型是 `browser`\n\n-   `dp.fullScreen.request(type: string)`: 进入全屏\n\n    ```js\n    dp.fullScreen.request('web');\n    ```\n\n-   `dp.fullScreen.cancel(type: string)`: 退出全屏\n\n    ```js\n    dp.fullScreen.cancel('web');\n    ```\n\n## 事件绑定\n\n`dp.on(event, handler)`\n\n```js\ndp.on('ended', function () {\n    console.log('player ended');\n});\n```\n\n视频事件\n\n-   abort\n-   canplay\n-   canplaythrough\n-   durationchange\n-   emptied\n-   ended\n-   error\n-   loadeddata\n-   loadedmetadata\n-   loadstart\n-   mozaudioavailable\n-   pause\n-   play\n-   playing\n-   progress\n-   ratechange\n-   seeked\n-   seeking\n-   stalled\n-   suspend\n-   timeupdate\n-   volumechange\n-   waiting\n\n播放器事件\n\n-   screenshot\n-   thumbnails_show\n-   thumbnails_hide\n-   danmaku_show\n-   danmaku_hide\n-   danmaku_clear\n-   danmaku_loaded\n-   danmaku_send\n-   danmaku_opacity\n-   contextmenu_show\n-   contextmenu_hide\n-   notice_show\n-   notice_hide\n-   quality_start\n-   quality_end\n-   destroy\n-   resize\n-   fullscreen\n-   fullscreen_cancel\n-   subtitle_show\n-   subtitle_hide\n-   subtitle_change\n\n## 清晰度切换\n\n在 `video.quality` 里设置不同清晰度的视频链接和类型，`video.defaultQuality` 设置默认清晰度\n\n<DPlayer :options=\"{\n    video: {\n        quality: [{\n            name: 'HD',\n            url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n            type: 'hls'\n        }, {\n            name: 'SD',\n            url: 'https://api.dogecloud.com/player/get.mp4?vcode=5ac682e6f8231991&userId=17&ext=.mp4',\n            type: 'normal'\n        }],\n        defaultQuality: 0,\n        pic: 'https://i.loli.net/2019/06/06/5cf8c5d9c57b510947.png',\n        thumbnails: 'https://i.loli.net/2019/06/06/5cf8c5d9cec8510758.jpg'\n    }\n}\"></DPlayer>\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        quality: [\n            {\n                name: 'HD',\n                url: 'demo.m3u8',\n                type: 'hls',\n            },\n            {\n                name: 'SD',\n                url: 'demo.mp4',\n                type: 'normal',\n            },\n        ],\n        defaultQuality: 0,\n        pic: 'demo.png',\n        thumbnails: 'thumbnails.jpg',\n    },\n});\n```\n\n在使用 `customType` [配合其他 MSE 库使用](#配合其他-mse-库使用)时，需要监听 `destroy` 和 `quality_end` 事件及时销毁 MSE 库实例，避免内存泄漏\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        quality: [\n            {\n                name: 'HD',\n                url: 'demo_hd.m3u8',\n                type: 'Hls',\n            },\n            {\n                name: 'SD',\n                url: 'demo_sd.m3u8',\n                type: 'Hls',\n            },\n        ],\n        defaultQuality: 0,\n        customType: {\n            Hls: (video, player) => {\n                const hls = new window.Hls();\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n\n                hls.sessionId = crypto.randomUUID();\n                player.sessionId = hls.sessionId;\n                player.events.on('destroy', () => {\n                    hls.destroy();\n                });\n                player.events.on('quality_end', () => {\n                    if (hls.sessionId !== player.sessionId) {\n                        hls.destroy();\n                    }\n                });\n            },\n        },\n    },\n});\n```\n\n## 弹幕\n\n### 弹幕接口\n\n`danmaku.api`\n\n<!-- **现成的接口**\n\n链接: https://api.prprpr.me/dplayer/ -->\n\n每日备份: [DPlayer-data](https://github.com/DIYgod/DPlayer-data)\n\n**自己搭建**\n\n[DPlayer-node](https://github.com/MoePlayer/DPlayer-node)\n\n### bilibili 弹幕\n\n`danmaku.addition`\n\nAPI: <https://api.prprpr.me/dplayer/v3/bilibili?aid=[aid]>\n\n```js\nconst option = {\n    danmaku: {\n        // ...\n        addition: ['https://api.prprpr.me/dplayer/v3/bilibili?aid=[aid]'],\n    },\n};\n```\n\n## MSE 支持\n\n### HLS\n\n需要在 `DPlayer.min.js` 前面加载 [hls.js](https://github.com/video-dev/hls.js)。\n\n<DPlayer :options=\"{\n    video: {\n        url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n        type: 'hls'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"hls.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: 'hls',\n    },\n    pluginOptions: {\n        hls: {\n            // hls config\n        },\n    },\n});\nconsole.log(dp.plugins.hls); // Hls 实例\n```\n\n```js\n// 另一种方式，使用 customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: 'customHls',\n        customType: {\n            customHls: function (video, player) {\n                const hls = new Hls();\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n            },\n        },\n    },\n});\n```\n\n### MPEG DASH\n\n需要在 `DPlayer.min.js` 前面加载 [dash.js](https://github.com/Dash-Industry-Forum/dash.js)。\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"dash.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.mpd',\n        type: 'dash',\n    },\n    pluginOptions: {\n        dash: {\n            // dash config\n        },\n    },\n});\nconsole.log(dp.plugins.dash); // Dash 实例\n```\n\n```js\n// 另一种方式，使用 customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.mpd',\n        type: 'customDash',\n        customType: {\n            customDash: function (video, player) {\n                dashjs.MediaPlayer().create().initialize(video, video.src, false);\n            },\n        },\n    },\n});\n```\n\n### MPEG DASH (Shaka)\n\n需要在 `DPlayer.min.js` 前面加载 [shaka-player.compiled.js](https://github.com/google/shaka-player)。\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"shaka-player.compiled.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    screenshot: true,\n    video: {\n        url: 'demo.mpd',\n        type: 'shakaDash',\n        customType: {\n            shakaDash: function (video, player) {\n                var src = video.src;\n                var playerShaka = new shaka.Player(video); // 将会修改 video.src\n                playerShaka.load(src);\n            },\n        },\n    },\n});\n```\n\n### FLV\n\n需要在 `DPlayer.min.js` 前面加载 [flv.js](https://github.com/Bilibili/flv.js)。\n\n<DPlayer :options=\"{\n    video: {\n        url: 'https://api.dogecloud.com/player/get.flv?vcode=5ac682e6f8231991&userId=17&ext=.flv',\n        type: 'flv'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"flv.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.flv',\n        type: 'flv',\n    },\n    pluginOptions: {\n        flv: {\n            // refer to https://github.com/bilibili/flv.js/blob/master/docs/api.md#flvjscreateplayer\n            mediaDataSource: {\n                // mediaDataSource config\n            },\n            config: {\n                // config\n            },\n        },\n    },\n});\nconsole.log(dp.plugins.flv); // flv 实例\n```\n\n```js\n// 另一种方式，使用 customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.flv',\n        type: 'customFlv',\n        customType: {\n            customFlv: function (video, player) {\n                const flvPlayer = flvjs.createPlayer({\n                    type: 'flv',\n                    url: video.src,\n                });\n                flvPlayer.attachMediaElement(video);\n                flvPlayer.load();\n            },\n        },\n    },\n});\n```\n\n### WebTorrent\n\n需要在 `DPlayer.min.js` 前面加载 [webtorrent](https://github.com/webtorrent/webtorrent)。\n\n<DPlayer :options=\"{\n    video: {\n        url: 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',\n        type: 'webtorrent'\n    }\n}\"></DPlayer>\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"webtorrent.min.js\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'magnet:demo',\n        type: 'webtorrent',\n    },\n    pluginOptions: {\n        webtorrent: {\n            // webtorrent config\n        },\n    },\n});\nconsole.log(dp.plugins.webtorrent); // WebTorrent 实例\n```\n\n```js\n// 另一种方式，使用 customType\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'magnet:demo',\n        type: 'customWebTorrent',\n        customType: {\n            customWebTorrent: function (video, player) {\n                player.container.classList.add('dplayer-loading');\n                const client = new WebTorrent();\n                const torrentId = video.src;\n                client.add(torrentId, (torrent) => {\n                    const file = torrent.files.find((file) => file.name.endsWith('.mp4'));\n                    file.renderTo(\n                        video,\n                        {\n                            autoplay: player.options.autoplay,\n                        },\n                        () => {\n                            player.container.classList.remove('dplayer-loading');\n                        }\n                    );\n                });\n            },\n        },\n    },\n});\n```\n\n### 配合其他 MSE 库使用\n\nDPlayer 可以通过 `customType` 参数与任何 MSE 库一起使用，例如支持 P2P 插件：\n\n```html\n<div id=\"dplayer\"></div>\n<script src=\"https://cdn.jsdelivr.net/npm/cdnbye@latest\"></script>\n<script src=\"DPlayer.min.js\"></script>\n```\n\n```js\nvar type = 'normal';\nif (Hls.isSupported() && Hls.WEBRTC_SUPPORT) {\n    type = 'customHls';\n}\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    video: {\n        url: 'demo.m3u8',\n        type: type,\n        customType: {\n            customHls: function (video, player) {\n                const hls = new Hls({\n                    debug: false,\n                    // Other hlsjsConfig options provided by hls.js\n                    p2pConfig: {\n                        live: false, // 如果是直播设为true\n                        // Other p2pConfig options provided by CDNBye http://www.cdnbye.com/cn/\n                    },\n                });\n                hls.loadSource(video.src);\n                hls.attachMedia(video);\n            },\n        },\n    },\n});\n```\n\n## 直播\n\n你可以把 DPlayer 用在直播当中，但如果你想要直播弹幕，你需要自己准备一个 WebSocket 后端。\n\n<DPlayer :options=\"{\n    live: true,\n    video: {\n        url: 'https://api.dogecloud.com/player/get.m3u8?vcode=5ac682e6f8231991&userId=17&ext=.m3u8',\n        type: 'hls'\n    }\n}\"></DPlayer>\n\n初始化播放器:\n\n```js\nconst dp = new DPlayer({\n    container: document.getElementById('dplayer'),\n    live: true,\n    danmaku: true,\n    apiBackend: {\n        read: function (options) {\n            console.log('Pretend to connect WebSocket');\n            options.success([]);\n        },\n        send: function (options) {\n            console.log('Pretend to send danmaku via WebSocket', options.data);\n            options.success();\n        },\n    },\n    video: {\n        url: 'demo.m3u8',\n        type: 'hls',\n    },\n});\n```\n\n通过 WebSocket 获取到弹幕之后，通过 `dp.danmaku.draw` 绘制弹幕:\n\n```js\nconst danmaku = {\n    text: 'Get a danmaku via WebSocket',\n    color: '#fff',\n    type: 'right',\n};\ndp.danmaku.draw(danmaku);\n```\n\n## 常见问题\n\n### 为什么播放器不能全屏？\n\n如果播放器被包含在 iframe 里，尝试在 iframe 上添加 `allowfullscreen` 属性。\n\n为了完善的浏览器兼容性，它应该是这样：\n\n```html\n<iframe src=\"example.com\" allowfullscreen=\"allowfullscreen\" mozallowfullscreen=\"mozallowfullscreen\" msallowfullscreen=\"msallowfullscreen\" oallowfullscreen=\"oallowfullscreen\" webkitallowfullscreen=\"webkitallowfullscreen\"></iframe>\n```\n\n### 为什么播放器不能在手机上自动播放？\n\n大多数移动端浏览器禁止了视频自动播放。"
  },
  {
    "path": "docs/zh/support.md",
    "content": "---\nsidebar: auto\n---\n\n# 赞助 DPlayer 的研发\n\nDPlayer 是采用 MIT 许可的开源项目，使用完全免费。 但是随着项目规模的增长，也需要有相应的资金支持才能持续项目的维护的开发。\n\n如果你是企业经营者并且将 DPlayer 用在商业产品中，那么赞助 DPlayer 有商业上的益处：可以让你的产品所依赖的框架保持健康并得到积极的维护。\n\n如果你是个人开发者并且享受 DPlayer 带来的高开发效率，可以用捐助来表示你的谢意 —— 比如偶尔给我买杯咖啡 :)\n\n你可以通过下列的方法来赞助 DPlayer 的开发.\n\n## 周期性赞助\n\n-   通过 [GitHub](https://github.com/sponsors/DIYgod) 赞助\n-   通过 [Patreon](https://www.patreon.com/DIYgod) 赞助\n-   通过 [爱发电](https://afdian.net/@diygod) 赞助\n-   给我们发邮件联系赞助事宜: <i@diygod.me>\n\n## 一次性赞助\n\n我们通过以下方式接受赞助:\n\n-   [微信支付](https://diygod.me/images/wx.jpg)\n-   [支付宝](https://diygod.me/images/zfb.jpg)\n-   [Paypal](https://www.paypal.me/DIYgod)\n\n## 赞助商\n\n<div>\n<a href=\"https://www.dogecloud.com/?ref=dplayer\" target=\"_blank\">\n    <img height=\"60px\" src=\"https://player.dogecloud.com/img/logo_with_product3.png\">\n</a>\n</div>\n\n## DPlayer 贡献者\n\n感谢所有贡献者。\n\n<a href=\"https://github.com/MoePlayer/DPlayer/graphs/contributors\"><img src=\"https://opencollective.com/DPlayer/contributors.svg?width=890\" /></a>\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"dplayer\",\n    \"version\": \"1.27.1\",\n    \"description\": \"Wow, such a lovely HTML5 danmaku video player\",\n    \"main\": \"dist/DPlayer.min.js\",\n    \"scripts\": {\n        \"start\": \"npm run dev\",\n        \"build\": \"cross-env NODE_ENV=production webpack --config webpack/prod.config.js --progress\",\n        \"dev\": \"cross-env NODE_ENV=development webpack serve --config webpack/dev.config.js\",\n        \"test\": \"\",\n        \"format\": \"prettier \\\"**/*.{js,json,md}\\\" --write\",\n        \"docs:dev\": \"vuepress dev docs\",\n        \"docs:build\": \"vuepress build docs\",\n        \"prepare\": \"husky install\"\n    },\n    \"lint-staged\": {\n        \"**/*\": \"prettier --write --ignore-unknown\"\n    },\n    \"files\": [\n        \"dist\"\n    ],\n    \"repository\": {\n        \"url\": \"git+https://github.com/DIYgod/DPlayer.git\",\n        \"type\": \"git\"\n    },\n    \"keywords\": [\n        \"player\",\n        \"danmaku\",\n        \"video\",\n        \"html5\"\n    ],\n    \"author\": \"DIYgod\",\n    \"license\": \"MIT\",\n    \"bugs\": {\n        \"url\": \"https://github.com/DIYgod/DPlayer/issues\"\n    },\n    \"homepage\": \"https://github.com/DIYgod/DPlayer#readme\",\n    \"devDependencies\": {\n        \"@babel/core\": \"^7.22.10\",\n        \"@babel/preset-env\": \"^7.22.10\",\n        \"@vuepress/plugin-pwa\": \"1.9.9\",\n        \"art-template\": \"4.13.2\",\n        \"art-template-loader\": \"1.4.3\",\n        \"autoprefixer\": \"^10.4.15\",\n        \"babel-loader\": \"^9.1.3\",\n        \"cross-env\": \"^7.0.0\",\n        \"css-loader\": \"^6.8.1\",\n        \"cssnano\": \"^6.0.1\",\n        \"exports-loader\": \"^4.0.0\",\n        \"file-loader\": \"^6.0.0\",\n        \"git-revision-webpack-plugin\": \"^5.0.0\",\n        \"husky\": \"^8.0.3\",\n        \"less\": \"^4.2.0\",\n        \"less-loader\": \"^11.1.3\",\n        \"lint-staged\": \"^13.3.0\",\n        \"mini-css-extract-plugin\": \"2.7.6\",\n        \"postcss\": \"^8.4.27\",\n        \"postcss-loader\": \"^7.3.3\",\n        \"postcss-preset-env\": \"^9.1.1\",\n        \"prettier\": \"^3.0.1\",\n        \"prettier-check\": \"^2.0.0\",\n        \"pretty-quick\": \"^3.0.0\",\n        \"strip-loader\": \"^0.1.2\",\n        \"style-loader\": \"^3.3.3\",\n        \"svg-inline-loader\": \"0.8.2\",\n        \"template-string-optimize-loader\": \"^3.0.0\",\n        \"url-loader\": \"^4.1.0\",\n        \"webpack\": \"^5.88.2\",\n        \"webpack-cli\": \"5.1.4\",\n        \"webpack-dev-server\": \"^4.15.1\",\n        \"yorkie\": \"^2.0.0\"\n    },\n    \"dependencies\": {\n        \"axios\": \"1.4.0\",\n        \"balloon-css\": \"^1.0.3\",\n        \"promise-polyfill\": \"8.3.0\"\n    }\n}\n"
  },
  {
    "path": "src/css/balloon.less",
    "content": "@import '../../node_modules/balloon-css/balloon.css';\n\n[data-balloon]:before {\n    display: none;\n}\n\n[data-balloon]:after {\n    padding: 0.3em 0.7em;\n    background: rgba(17, 17, 17, 0.7);\n}\n\n[data-balloon][data-balloon-pos=\"up\"]:after {\n    margin-bottom: 0;\n}"
  },
  {
    "path": "src/css/bezel.less",
    "content": ".dplayer-bezel {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    font-size: 22px;\n    color: #fff;\n    pointer-events: none;\n    .dplayer-bezel-icon {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        margin: -26px 0 0 -26px;\n        height: 52px;\n        width: 52px;\n        padding: 12px;\n        box-sizing: border-box;\n        background: rgba(0, 0, 0, .5);\n        border-radius: 50%;\n        opacity: 0;\n        pointer-events: none;\n        &.dplayer-bezel-transition {\n            animation: bezel-hide .5s linear;\n        }\n        @keyframes bezel-hide {\n            from {\n                opacity: 1;\n                transform: scale(1);\n            }\n            to {\n                opacity: 0;\n                transform: scale(2);\n            }\n        }\n    }\n    .dplayer-danloading {\n        position: absolute;\n        top: 50%;\n        margin-top: -7px;\n        width: 100%;\n        text-align: center;\n        font-size: 14px;\n        line-height: 14px;\n        animation: my-face 5s infinite ease-in-out;\n    }\n    .diplayer-loading-icon {\n        display: none;\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        margin: -18px 0 0 -18px;\n        height: 36px;\n        width: 36px;\n        pointer-events: none;\n        .diplayer-loading-hide {\n            display: none;\n        }\n        .diplayer-loading-dot {\n            animation: diplayer-loading-dot-fade .8s ease infinite;\n            opacity: 0;\n            transform-origin: 4px 4px;\n            each(range(7), {\n                &.diplayer-loading-dot-@{value} {\n                    animation-delay: (@value * 0.1s);\n                }\n            });\n        }\n        @keyframes diplayer-loading-dot-fade {\n            0% {\n                opacity: .7;\n                transform: scale(1.2, 1.2)\n            }\n            50% {\n                opacity: .25;\n                transform: scale(.9, .9)\n            }\n            to {\n                opacity: .25;\n                transform: scale(.85, .85)\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/css/controller.less",
    "content": ".dplayer-controller-mask {\n    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) repeat-x bottom;\n    height: 98px;\n    width: 100%;\n    position: absolute;\n    bottom: 0;\n    transition: all 0.3s ease;\n}\n\n.dplayer-controller {\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    height: 41px;\n    padding: 0 20px;\n    user-select: none;\n    transition: all 0.3s ease;\n    &.dplayer-controller-comment {\n        .dplayer-icons {\n            display: none;\n        }\n        .dplayer-icons.dplayer-comment-box {\n            display: block;\n        }\n    }\n    .dplayer-bar-wrap {\n        padding: 5px 0;\n        cursor: pointer;\n        position: absolute;\n        bottom: 33px;\n        width: calc(100% - 40px);\n        height: 3px;\n        &:hover {\n            .dplayer-bar .dplayer-played .dplayer-thumb {\n                transform: scale(1);\n            }\n            .dplayer-highlight {\n                display: block;\n                width: 8px;\n                transform: translateX(-4px);\n                top: 4px;\n                height: 40%;\n            }\n        }\n        .dplayer-highlight {\n            z-index: 12;\n            position: absolute;\n            top: 5px;\n            width: 6px;\n            height: 20%;\n            border-radius: 6px;\n            background-color: #fff;\n            text-align: center;\n            transform: translateX(-3px);\n            transition: all .2s ease-in-out;\n            &:hover {\n                .dplayer-highlight-text {\n                    display: block;\n                }\n                &~.dplayer-bar-preview {\n                    opacity: 0;\n                }\n                &~.dplayer-bar-time {\n                    opacity: 0;\n                }\n            }\n            .dplayer-highlight-text {\n                display: none;\n                position: absolute;\n                left: 50%;\n                top: -24px;\n                padding: 5px 8px;\n                background-color: rgba(0, 0, 0, .62);\n                color: #fff;\n                border-radius: 4px;\n                font-size: 12px;\n                white-space: nowrap;\n                transform: translateX(-50%);\n            }\n        }\n        .dplayer-bar-preview {\n            position: absolute;\n            background: #fff;\n            pointer-events: none;\n            display: none;\n            background-size: 16000px 100%;\n        }\n        .dplayer-bar-preview-canvas {\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            z-index: 1;\n            pointer-events: none;\n        }\n        .dplayer-bar-time {\n            &.hidden {\n                opacity: 0;\n            }\n            position: absolute;\n            left: 0px;\n            top: -20px;\n            border-radius: 4px;\n            padding: 5px 7px;\n            background-color: rgba(0, 0, 0, 0.62);\n            color: #fff;\n            font-size: 12px;\n            text-align: center;\n            opacity: 1;\n            transition: opacity .1s ease-in-out;\n            word-wrap: normal;\n            word-break: normal;\n            z-index: 2;\n            pointer-events: none;\n        }\n        .dplayer-bar {\n            position: relative;\n            height: 3px;\n            width: 100%;\n            background: rgba(255, 255, 255, .2);\n            cursor: pointer;\n            .dplayer-loaded {\n                position: absolute;\n                left: 0;\n                top: 0;\n                bottom: 0;\n                background: rgba(255, 255, 255, .4);\n                height: 3px;\n                transition: all 0.5s ease;\n                will-change: width;\n            }\n            .dplayer-played {\n                position: absolute;\n                left: 0;\n                top: 0;\n                bottom: 0;\n                height: 3px;\n                will-change: width;\n                .dplayer-thumb {\n                    position: absolute;\n                    top: 0;\n                    right: 5px;\n                    margin-top: -4px;\n                    margin-right: -10px;\n                    height: 11px;\n                    width: 11px;\n                    border-radius: 50%;\n                    cursor: pointer;\n                    transition: all .3s ease-in-out;\n                    transform: scale(0);\n                }\n            }\n        }\n    }\n    .dplayer-icons {\n        height: 38px;\n        position: absolute;\n        bottom: 0;\n        &.dplayer-comment-box {\n            display: none;\n            position: absolute;\n            transition: all .3s ease-in-out;\n            z-index: 2;\n            height: 38px;\n            bottom: 0;\n            left: 20px;\n            right: 20px;\n            color: #fff;\n            .dplayer-icon {\n                padding: 7px;\n            }\n            .dplayer-comment-setting-icon {\n                position: absolute;\n                left: 0;\n                top: 0;\n            }\n            .dplayer-send-icon {\n                position: absolute;\n                right: 0;\n                top: 0;\n            }\n            .dplayer-comment-setting-box {\n                position: absolute;\n                background: rgba(28, 28, 28, 0.9);\n                bottom: 41px;\n                left: 0;\n                box-shadow: 0 0 25px rgba(0, 0, 0, .3);\n                border-radius: 4px;\n                padding: 10px 10px 16px;\n                font-size: 14px;\n                width: 204px;\n                transition: all .3s ease-in-out;\n                transform: scale(0);\n                &.dplayer-comment-setting-open {\n                    transform: scale(1);\n                }\n                input[type=radio] {\n                    display: none;\n                }\n                label {\n                    cursor: pointer;\n                }\n                .dplayer-comment-setting-title {\n                    font-size: 13px;\n                    color: #fff;\n                    line-height: 30px;\n                }\n                .dplayer-comment-setting-type {\n                    font-size: 0;\n                    .dplayer-comment-setting-title {\n                        margin-bottom: 6px;\n                    }\n                    label {\n                        &:nth-child(2) {\n                            span {\n                                border-radius: 4px 0 0 4px;\n                            }\n                        }\n                        &:nth-child(4) {\n                            span {\n                                border-radius: 0 4px 4px 0;\n                            }\n                        }\n                    }\n                    span {\n                        width: 33%;\n                        padding: 4px 6px;\n                        line-height: 16px;\n                        display: inline-block;\n                        font-size: 12px;\n                        color: #fff;\n                        border: 1px solid #fff;\n                        margin-right: -1px;\n                        box-sizing: border-box;\n                        text-align: center;\n                        cursor: pointer;\n                    }\n                    input:checked+span {\n                        background: #E4E4E6;\n                        color: #1c1c1c;\n                    }\n                }\n                .dplayer-comment-setting-color {\n                    font-size: 0;\n                    label {\n                        font-size: 0;\n                        padding: 6px;\n                        display: inline-block;\n                    }\n                    span {\n                        width: 22px;\n                        height: 22px;\n                        display: inline-block;\n                        border-radius: 50%;\n                        box-sizing: border-box;\n                        cursor: pointer;\n                        &:hover {\n                            animation: my-face 5s infinite ease-in-out;\n                        }\n                    }\n                }\n            }\n            .dplayer-comment-input {\n                outline: none;\n                border: none;\n                padding: 8px 31px;\n                font-size: 14px;\n                line-height: 18px;\n                text-align: center;\n                border-radius: 4px;\n                background: none;\n                margin: 0;\n                height: 100%;\n                box-sizing: border-box;\n                width: 100%;\n                color: #fff;\n                &::placeholder {\n                    color: #fff;\n                    opacity: 0.8;\n                }\n                &::-ms-clear {\n                    display: none;\n                }\n            }\n        }\n        &.dplayer-icons-left {\n            .dplayer-icon {\n                padding: 7px;\n            }\n        }\n        &.dplayer-icons-right {\n            right: 20px;\n            .dplayer-icon {\n                padding: 8px;\n            }\n        }\n        .dplayer-time,\n        .dplayer-live-badge {\n            line-height: 38px;\n            color: #eee;\n            text-shadow: 0 0 2px rgba(0, 0, 0, .5);\n            vertical-align: middle;\n            font-size: 13px;\n            cursor: default;\n        }\n        .dplayer-live-dot {\n            display: inline-block;\n            width: 6px;\n            height: 6px;\n            vertical-align: 4%;\n            margin-right: 5px;\n            content: '';\n            border-radius: 6px;\n        }\n        .dplayer-icon {\n            width: 40px;\n            height: 100%;\n            border: none;\n            background-color: transparent;\n            outline: none;\n            cursor: pointer;\n            vertical-align: middle;\n            box-sizing: border-box;\n            display: inline-block;\n            .dplayer-icon-content {\n                transition: all .2s ease-in-out;\n                opacity: .8;\n            }\n            &:hover {\n                .dplayer-icon-content {\n                    opacity: 1;\n                }\n            }\n            &.dplayer-quality-icon {\n                color: #fff;\n                width: auto;\n                line-height: 22px;\n                font-size: 14px;\n            }\n            &.dplayer-comment-icon {\n                padding: 10px 9px 9px;\n            }\n            &.dplayer-setting-icon {\n                padding-top: 8.5px;\n            }\n            &.dplayer-volume-icon {\n                width: 43px;\n            }\n        }\n        .dplayer-volume {\n            position: relative;\n            display: inline-block;\n            cursor: pointer;\n            height: 100%;\n            &:hover {\n                .dplayer-volume-bar-wrap .dplayer-volume-bar {\n                    width: 45px;\n                }\n                .dplayer-volume-bar-wrap .dplayer-volume-bar .dplayer-volume-bar-inner .dplayer-thumb {\n                    transform: scale(1);\n                }\n            }\n            &.dplayer-volume-active {\n                .dplayer-volume-bar-wrap .dplayer-volume-bar {\n                    width: 45px;\n                }\n                .dplayer-volume-bar-wrap .dplayer-volume-bar .dplayer-volume-bar-inner .dplayer-thumb {\n                    transform: scale(1);\n                }\n            }\n            .dplayer-volume-bar-wrap {\n                display: inline-block;\n                margin: 0 10px 0 -5px;\n                vertical-align: middle;\n                height: 100%;\n                .dplayer-volume-bar {\n                    position: relative;\n                    top: 17px;\n                    width: 0;\n                    height: 3px;\n                    background: #aaa;\n                    transition: all 0.3s ease-in-out;\n                    .dplayer-volume-bar-inner {\n                        position: absolute;\n                        bottom: 0;\n                        left: 0;\n                        height: 100%;\n                        transition: all 0.1s ease;\n                        will-change: width;\n                        .dplayer-thumb {\n                            position: absolute;\n                            top: 0;\n                            right: 5px;\n                            margin-top: -4px;\n                            margin-right: -10px;\n                            height: 11px;\n                            width: 11px;\n                            border-radius: 50%;\n                            cursor: pointer;\n                            transition: all .3s ease-in-out;\n                            transform: scale(0);\n                        }\n                    }\n                }\n            }\n        }\n        .dplayer-subtitle-btn {\n            display: inline-block;\n            height: 100%;\n        }\n        .dplayer-subtitles {\n            display: inline-block;\n            height: 100%;\n            .dplayer-subtitles-box {\n                position: absolute;\n                right: 0;\n                bottom: 50px;\n                transform: scale(0);\n                width: fit-content;\n                max-width: 240px;\n                min-width: 120px;\n                border-radius: 2px;\n                background: rgba(28, 28, 28, 0.9);\n                padding: 7px 0;\n                transition: all .3s ease-in-out;\n                overflow: auto;\n                z-index: 2;\n                &.dplayer-subtitles-panel {\n                    display: block;\n                }\n                &.dplayer-subtitles-box-open {\n                    transform: scale(1);\n                }\n            }\n            .dplayer-subtitles-item {\n                height: 30px;\n                padding: 5px 10px;\n                box-sizing: border-box;\n                cursor: pointer;\n                position: relative;\n                &:hover {\n                    background-color: rgba(255, 255, 255, .1);\n                }\n            }\n        }\n        .dplayer-setting {\n            display: inline-block;\n            height: 100%;\n            .dplayer-setting-box {\n                position: absolute;\n                right: 0;\n                bottom: 50px;\n                transform: scale(0);\n                width: 150px;\n                border-radius: 2px;\n                background: rgba(28, 28, 28, 0.9);\n                padding: 7px 0;\n                transition: all .3s ease-in-out;\n                overflow: hidden;\n                z-index: 2;\n                &>div {\n                    display: none;\n                    &.dplayer-setting-origin-panel {\n                        display: block;\n                    }\n                }\n                &.dplayer-setting-box-open {\n                    transform: scale(1);\n                }\n                &.dplayer-setting-box-narrow {\n                    width: 70px;\n                    text-align: center;\n                }\n                &.dplayer-setting-box-speed {\n                    .dplayer-setting-origin-panel {\n                        display: none;\n                    }\n                    .dplayer-setting-speed-panel {\n                        display: block;\n                    }\n                }\n            }\n            .dplayer-setting-item,\n            .dplayer-setting-speed-item {\n                height: 30px;\n                padding: 5px 10px;\n                box-sizing: border-box;\n                cursor: pointer;\n                position: relative;\n                &:hover {\n                    background-color: rgba(255, 255, 255, .1);\n                }\n            }\n            .dplayer-setting-danmaku {\n                padding: 5px 0;\n                .dplayer-label {\n                    padding: 0 10px;\n                    display: inline;\n                }\n                &:hover {\n                    .dplayer-label {\n                        display: none;\n                    }\n                    .dplayer-danmaku-bar-wrap {\n                        display: inline-block;\n                    }\n                }\n                &.dplayer-setting-danmaku-active {\n                    .dplayer-label {\n                        display: none;\n                    }\n                    .dplayer-danmaku-bar-wrap {\n                        display: inline-block;\n                    }\n                }\n                .dplayer-danmaku-bar-wrap {\n                    padding: 0 10px;\n                    box-sizing: border-box;\n                    display: none;\n                    vertical-align: middle;\n                    height: 100%;\n                    width: 100%;\n                    .dplayer-danmaku-bar {\n                        position: relative;\n                        top: 8.5px;\n                        width: 100%;\n                        height: 3px;\n                        background: #fff;\n                        transition: all 0.3s ease-in-out;\n                        .dplayer-danmaku-bar-inner {\n                            position: absolute;\n                            bottom: 0;\n                            left: 0;\n                            height: 100%;\n                            transition: all 0.1s ease;\n                            background: #aaa;\n                            will-change: width;\n                            .dplayer-thumb {\n                                position: absolute;\n                                top: 0;\n                                right: 5px;\n                                margin-top: -4px;\n                                margin-right: -10px;\n                                height: 11px;\n                                width: 11px;\n                                border-radius: 50%;\n                                cursor: pointer;\n                                transition: all .3s ease-in-out;\n                                background: #aaa;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        .dplayer-full {\n            display: inline-block;\n            height: 100%;\n            position: relative;\n            &:hover {\n                .dplayer-full-in-icon {\n                    display: block;\n                }\n            }\n            .dplayer-full-in-icon {\n                position: absolute;\n                top: -30px;\n                z-index: 1;\n                display: none;\n            }\n        }\n        .dplayer-quality {\n            position: relative;\n            display: inline-block;\n            height: 100%;\n            z-index: 2;\n            &:hover {\n                .dplayer-quality-list {\n                    display: block;\n                }\n                .dplayer-quality-mask {\n                    display: block;\n                }\n            }\n            .dplayer-quality-mask {\n                display: none;\n                position: absolute;\n                bottom: 38px;\n                left: -18px;\n                width: 80px;\n                padding-bottom: 12px;\n            }\n            .dplayer-quality-list {\n                display: none;\n                font-size: 12px;\n                width: 80px;\n                border-radius: 2px;\n                background: rgba(28, 28, 28, 0.9);\n                padding: 5px 0;\n                transition: all .3s ease-in-out;\n                overflow: hidden;\n                color: #fff;\n                text-align: center;\n            }\n            .dplayer-quality-item {\n                height: 25px;\n                box-sizing: border-box;\n                cursor: pointer;\n                line-height: 25px;\n                &:hover {\n                    background-color: rgba(255, 255, 255, .1);\n                }\n            }\n        }\n        .dplayer-comment {\n            display: inline-block;\n            height: 100%;\n        }\n        .dplayer-label {\n            color: #eee;\n            font-size: 13px;\n            display: inline-block;\n            vertical-align: middle;\n            white-space: nowrap;\n        }\n        .dplayer-toggle {\n            width: 32px;\n            height: 20px;\n            text-align: center;\n            font-size: 0;\n            vertical-align: middle;\n            position: absolute;\n            top: 5px;\n            right: 10px;\n            input {\n                max-height: 0;\n                max-width: 0;\n                display: none;\n            }\n            input+label {\n                display: inline-block;\n                position: relative;\n                box-shadow: rgb(223, 223, 223) 0 0 0 0 inset;\n                border: 1px solid rgb(223, 223, 223);\n                height: 20px;\n                width: 32px;\n                border-radius: 10px;\n                box-sizing: border-box;\n                cursor: pointer;\n                transition: .2s ease-in-out;\n            }\n            input+label:before {\n                content: \"\";\n                position: absolute;\n                display: block;\n                height: 18px;\n                width: 18px;\n                top: 0;\n                left: 0;\n                border-radius: 15px;\n                transition: .2s ease-in-out;\n            }\n            input+label:after {\n                content: \"\";\n                position: absolute;\n                display: block;\n                left: 0;\n                top: 0;\n                border-radius: 15px;\n                background: #fff;\n                transition: .2s ease-in-out;\n                box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);\n                height: 18px;\n                width: 18px;\n            }\n            input:checked+label {\n                border-color: rgba(255, 255, 255, 0.5);\n            }\n            input:checked+label:before {\n                width: 30px;\n                background: rgba(255, 255, 255, 0.5);\n            }\n            input:checked+label:after {\n                left: 12px;\n            }\n        }\n    }\n}\n\n.dplayer-mobile-play {\n    display: none;\n    width: 50px;\n    height: 50px;\n    border: none;\n    background-color: transparent;\n    outline: none;\n    cursor: pointer;\n    box-sizing: border-box;\n    position: absolute;\n    bottom: 0;\n    opacity: 0.8;\n    position: absolute;\n    left: 50%;\n    top: 50%;\n    transform: translate(-50%, -50%);\n}"
  },
  {
    "path": "src/css/danmaku.less",
    "content": ".dplayer-danmaku {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    font-size: 22px;\n    color: #fff;\n    .dplayer-danmaku-item {\n        display: inline-block;\n        pointer-events: none;\n        user-select: none;\n        cursor: default;\n        white-space: nowrap;\n        text-shadow: 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.5);\n        &--demo {\n            position: absolute;\n            visibility: hidden;\n        }\n    }\n    .dplayer-danmaku-right {\n        position: absolute;\n        right: 0;\n        transform: translateX(100%);\n        &.dplayer-danmaku-move {\n            will-change: transform;\n            animation-name: danmaku;\n            animation-timing-function: linear;\n            animation-play-state: paused;\n        }\n    }\n    @keyframes danmaku {\n        from {\n            transform: translateX(100%);\n        }\n    }\n    .dplayer-danmaku-top,\n    .dplayer-danmaku-bottom {\n        position: absolute;\n        width: 100%;\n        text-align: center;\n        visibility: hidden;\n        &.dplayer-danmaku-move {\n            will-change: visibility;\n            animation-name: danmaku-center;\n            animation-timing-function: linear;\n            animation-play-state: paused;\n        }\n    }\n    @keyframes danmaku-center {\n        from {\n            visibility: visible;\n        }\n        to {\n            visibility: visible;\n        }\n    }\n}\n"
  },
  {
    "path": "src/css/global.less",
    "content": "@keyframes my-face {\n    2% {\n        transform: translate(0, 1.5px) rotate(1.5deg);\n    }\n    4% {\n        transform: translate(0, -1.5px) rotate(-0.5deg);\n    }\n    6% {\n        transform: translate(0, 1.5px) rotate(-1.5deg);\n    }\n    8% {\n        transform: translate(0, -1.5px) rotate(-1.5deg);\n    }\n    10% {\n        transform: translate(0, 2.5px) rotate(1.5deg);\n    }\n    12% {\n        transform: translate(0, -0.5px) rotate(1.5deg);\n    }\n    14% {\n        transform: translate(0, -1.5px) rotate(1.5deg);\n    }\n    16% {\n        transform: translate(0, -0.5px) rotate(-1.5deg);\n    }\n    18% {\n        transform: translate(0, 0.5px) rotate(-1.5deg);\n    }\n    20% {\n        transform: translate(0, -1.5px) rotate(2.5deg);\n    }\n    22% {\n        transform: translate(0, 0.5px) rotate(-1.5deg);\n    }\n    24% {\n        transform: translate(0, 1.5px) rotate(1.5deg);\n    }\n    26% {\n        transform: translate(0, 0.5px) rotate(0.5deg);\n    }\n    28% {\n        transform: translate(0, 0.5px) rotate(1.5deg);\n    }\n    30% {\n        transform: translate(0, -0.5px) rotate(2.5deg);\n    }\n    32% {\n        transform: translate(0, 1.5px) rotate(-0.5deg);\n    }\n    34% {\n        transform: translate(0, 1.5px) rotate(-0.5deg);\n    }\n    36% {\n        transform: translate(0, -1.5px) rotate(2.5deg);\n    }\n    38% {\n        transform: translate(0, 1.5px) rotate(-1.5deg);\n    }\n    40% {\n        transform: translate(0, -0.5px) rotate(2.5deg);\n    }\n    42% {\n        transform: translate(0, 2.5px) rotate(-1.5deg);\n    }\n    44% {\n        transform: translate(0, 1.5px) rotate(0.5deg);\n    }\n    46% {\n        transform: translate(0, -1.5px) rotate(2.5deg);\n    }\n    48% {\n        transform: translate(0, -0.5px) rotate(0.5deg);\n    }\n    50% {\n        transform: translate(0, 0.5px) rotate(0.5deg);\n    }\n    52% {\n        transform: translate(0, 2.5px) rotate(2.5deg);\n    }\n    54% {\n        transform: translate(0, -1.5px) rotate(1.5deg);\n    }\n    56% {\n        transform: translate(0, 2.5px) rotate(2.5deg);\n    }\n    58% {\n        transform: translate(0, 0.5px) rotate(2.5deg);\n    }\n    60% {\n        transform: translate(0, 2.5px) rotate(2.5deg);\n    }\n    62% {\n        transform: translate(0, -0.5px) rotate(2.5deg);\n    }\n    64% {\n        transform: translate(0, -0.5px) rotate(1.5deg);\n    }\n    66% {\n        transform: translate(0, 1.5px) rotate(-0.5deg);\n    }\n    68% {\n        transform: translate(0, -1.5px) rotate(-0.5deg);\n    }\n    70% {\n        transform: translate(0, 1.5px) rotate(0.5deg);\n    }\n    72% {\n        transform: translate(0, 2.5px) rotate(1.5deg);\n    }\n    74% {\n        transform: translate(0, -0.5px) rotate(0.5deg);\n    }\n    76% {\n        transform: translate(0, -0.5px) rotate(2.5deg);\n    }\n    78% {\n        transform: translate(0, -0.5px) rotate(1.5deg);\n    }\n    80% {\n        transform: translate(0, 1.5px) rotate(1.5deg);\n    }\n    82% {\n        transform: translate(0, -0.5px) rotate(0.5deg);\n    }\n    84% {\n        transform: translate(0, 1.5px) rotate(2.5deg);\n    }\n    86% {\n        transform: translate(0, -1.5px) rotate(-1.5deg);\n    }\n    88% {\n        transform: translate(0, -0.5px) rotate(2.5deg);\n    }\n    90% {\n        transform: translate(0, 2.5px) rotate(-0.5deg);\n    }\n    92% {\n        transform: translate(0, 0.5px) rotate(-0.5deg);\n    }\n    94% {\n        transform: translate(0, 2.5px) rotate(0.5deg);\n    }\n    96% {\n        transform: translate(0, -0.5px) rotate(1.5deg);\n    }\n    98% {\n        transform: translate(0, -1.5px) rotate(-0.5deg);\n    }\n    0%,\n    100% {\n        transform: translate(0, 0) rotate(0deg);\n    }\n}"
  },
  {
    "path": "src/css/index.less",
    "content": "@import './global';\n@import './player';\n@import './balloon';\n@import './bezel';\n@import './controller';\n@import './danmaku';\n@import './logo';\n@import './menu';\n@import './notice';\n@import './subtitle';\n@import './video';\n@import './info-panel';"
  },
  {
    "path": "src/css/info-panel.less",
    "content": ".dplayer-info-panel {\n    position: absolute;\n    top: 10px;\n    left: 10px;\n    width: 400px;\n    background: rgba(28, 28, 28, 0.8);\n    padding: 10px;\n    color: #fff;\n    font-size: 12px;\n    border-radius: 2px;\n\n    &-hide {\n        display: none;\n    }\n\n    .dplayer-info-panel-close {\n        cursor: pointer;\n        position: absolute;\n        right: 10px;\n        top: 10px;\n    }\n\n    .dplayer-info-panel-item {\n        & > span {\n            display: inline-block;\n            vertical-align: middle;\n            line-height: 15px;\n            white-space: nowrap;\n            text-overflow: ellipsis;\n            overflow: hidden;\n        }\n    }\n\n    .dplayer-info-panel-item-title {\n        width: 100px;\n        text-align: right;\n        margin-right: 10px;\n    }\n    \n    .dplayer-info-panel-item-data {\n        width: 260px;\n    }\n}"
  },
  {
    "path": "src/css/logo.less",
    "content": ".dplayer-logo {\n    pointer-events: none;\n    position: absolute;\n    left: 20px;\n    top: 20px;\n    max-width: 50px;\n    max-height: 50px;\n    img {\n        max-width: 100%;\n        max-height: 100%;\n        background: none;\n    }\n}"
  },
  {
    "path": "src/css/menu.less",
    "content": ".dplayer-menu {\n    position: absolute;\n    width: 170px;\n    border-radius: 2px;\n    background: rgba(28, 28, 28, 0.85);\n    padding: 5px 0;\n    overflow: hidden;\n    z-index: 3;\n    display: none;\n    &.dplayer-menu-show {\n        display: block;\n    }\n    .dplayer-menu-item {\n        height: 30px;\n        box-sizing: border-box;\n        cursor: pointer;\n        &:hover {\n            background-color: rgba(255, 255, 255, .1);\n        }\n        a {\n            display: inline-block;\n            padding: 0 10px;\n            line-height: 30px;\n            color: #eee;\n            font-size: 13px;\n            display: inline-block;\n            vertical-align: middle;\n            width: 100%;\n            box-sizing: border-box;\n            white-space: nowrap;\n            text-overflow: ellipsis;\n            overflow: hidden;\n            &:hover {\n                text-decoration: none;\n            }\n        }\n    }\n}"
  },
  {
    "path": "src/css/notice.less",
    "content": ".dplayer-notice-list{\n    position: absolute;\n    bottom: 60px;\n    left: 20px;\n\n    .dplayer-notice {\n        border-radius: 2px;\n        background: rgba(28, 28, 28, 0.9);\n        transition: all .3s ease-in-out;\n        overflow: hidden;\n        color: #fff;\n        display: table;\n        pointer-events: none;\n        animation: showNotice .3s ease 1 forwards;\n    }\n\n    .remove-notice{\n        animation: removeNotice .3s ease 1 forwards;\n    }\n}\n\n@keyframes showNotice {\n    from {\n        padding: 0;\n        font-size: 0;\n        margin-top: 0;\n    }\n    to {\n        padding: 7px 20px;\n        font-size: 14px;\n        margin-top: 5px;\n    }\n}\n\n@keyframes removeNotice {\n    0%{\n        padding: 7px 20px;\n        font-size: 14px;\n        margin-top: 5px;\n    }\n    20%{\n        font-size: 12px;\n    }\n    21%{\n        font-size: 0;\n        padding: 7px 10px;\n    }\n    100%{\n        padding: 0;\n        margin-top: 0;\n        font-size: 0;\n    }\n}\n"
  },
  {
    "path": "src/css/player.less",
    "content": ".dplayer {\n    position: relative;\n    overflow: hidden;\n    user-select: none;\n    line-height: 1;\n\n    * {\n        box-sizing: content-box;\n    }\n\n    svg {\n        width: 100%;\n        height: 100%;\n\n        path,\n        circle {\n            fill: #fff;\n        }\n    }\n\n    &:-webkit-full-screen {\n        width: 100%;\n        height: 100%;\n        background: #000;\n        position: fixed;\n        z-index: 100000;\n        left: 0;\n        top: 0;\n        margin: 0;\n        padding: 0;\n        transform: translate(0, 0);\n        \n    }\n\n    &.dplayer-no-danmaku {\n        .dplayer-controller .dplayer-icons .dplayer-setting .dplayer-setting-box {\n            .dplayer-setting-showdan,\n            .dplayer-setting-danmaku,\n            .dplayer-setting-danunlimit {\n                display: none;\n            }\n        }\n\n        .dplayer-controller .dplayer-icons .dplayer-comment {\n            display: none;\n        }\n\n        .dplayer-danmaku {\n            display: none;\n        }\n    }\n\n    &.dplayer-live {\n        .dplayer-time {\n            display: none;\n        }\n        .dplayer-bar-wrap {\n            display: none;\n        }\n        .dplayer-setting-speed {\n            display: none;\n        }\n        .dplayer-setting-loop {\n            display: none;\n        }\n\n        &.dplayer-no-danmaku {\n            .dplayer-setting {\n                display: none;\n            }\n        }\n    }\n\n    &.dplayer-arrow {\n        .dplayer-danmaku {\n            font-size: 18px;\n        }\n        .dplayer-icon {\n            margin: 0 -3px;\n        }\n    }\n\n    &.dplayer-playing {\n        .dplayer-danmaku .dplayer-danmaku-move {\n            animation-play-state: running;\n        }\n\n        @media (min-width: 900px) {\n            .dplayer-controller-mask {\n                opacity: 0;\n            }\n            .dplayer-controller {\n                opacity: 0;\n            }\n\n            &:hover {\n                .dplayer-controller-mask {\n                    opacity: 1;\n                }\n                .dplayer-controller {\n                    opacity: 1;\n                }\n            }\n        }\n    }\n\n    &.dplayer-loading {\n        .dplayer-bezel .diplayer-loading-icon {\n            display: block;\n        }\n    }\n\n    &.dplayer-loading,\n    &.dplayer-paused {\n        .dplayer-danmaku,\n        .dplayer-danmaku-move {\n            animation-play-state: paused;\n        }\n    }\n\n    &.dplayer-hide-controller {\n        cursor: none;\n\n        .dplayer-controller-mask {\n            opacity: 0;\n            transform: translateY(100%);\n        }\n        .dplayer-controller {\n            opacity: 0;\n            transform: translateY(100%);\n        }\n    }\n    &.dplayer-show-controller {\n        .dplayer-controller-mask {\n            opacity: 1;\n        }\n        .dplayer-controller {\n            opacity: 1;\n        }\n    }\n    &.dplayer-fulled {\n        position: fixed;\n        z-index: 100000;\n        left: 0;\n        top: 0;\n        width: 100% !important;\n        height: 100% !important;\n    }\n    &.dplayer-mobile {\n        .dplayer-controller .dplayer-icons {\n            .dplayer-volume,\n            .dplayer-camera-icon,\n            .dplayer-airplay-icon,\n            .dplayer-chromecast-icon,\n            .dplayer-play-icon {\n                display: none;\n            }\n            .dplayer-full .dplayer-full-in-icon {\n                position: static;\n                display: inline-block;\n            }\n        }\n\n        .dplayer-bar-time {\n            display: none;\n        }\n\n        &.dplayer-hide-controller {\n            .dplayer-mobile-play {\n                display: none;\n            }\n        }\n\n        .dplayer-mobile-play {\n            display: block;\n        }\n    }\n}\n\n// To hide scroll bar, apply this class to <body>\n.dplayer-web-fullscreen-fix {\n    position: fixed;\n    top: 0;\n    left: 0;\n    margin: 0;\n    padding: 0;\n}\n"
  },
  {
    "path": "src/css/subtitle.less",
    "content": ".dplayer-subtitle {\n    position: absolute;\n    bottom: 40px;\n    width: 90%;\n    left: 5%;\n    text-align: center;\n    color: #fff;\n    text-shadow: 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.5);\n    font-size: 20px;\n    &.dplayer-subtitle-hide {\n        display: none;\n    }\n}"
  },
  {
    "path": "src/css/video.less",
    "content": ".dplayer-mask {\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    z-index: 1;\n    display: none;\n    &.dplayer-mask-show {\n        display: block;\n    }\n}\n\n.dplayer-video-wrap {\n    position: relative;\n    background: #000;\n    font-size: 0;\n    width: 100%;\n    height: 100%;\n    .dplayer-video {\n        width: 100%;\n        height: 100%;\n        display: none;\n    }\n    .dplayer-video-current {\n        display: block;\n    }\n    .dplayer-video-prepare {\n        display: none;\n    }\n}"
  },
  {
    "path": "src/js/api.js",
    "content": "import axios from 'axios';\n\nexport default {\n    send: (options) => {\n        axios\n            .post(options.url, options.data)\n            .then((response) => {\n                const data = response.data;\n                if (!data || data.code !== 0) {\n                    options.error && options.error(data && data.msg);\n                    return;\n                }\n                options.success && options.success(data);\n            })\n            .catch((e) => {\n                console.error(e);\n                options.error && options.error();\n            });\n    },\n\n    read: (options) => {\n        axios\n            .get(options.url)\n            .then((response) => {\n                const data = response.data;\n                if (!data || data.code !== 0) {\n                    options.error && options.error(data && data.msg);\n                    return;\n                }\n                options.success &&\n                    options.success(\n                        data.data.map((item) => ({\n                            time: item[0],\n                            type: item[1],\n                            color: item[2],\n                            author: item[3],\n                            text: item[4],\n                        }))\n                    );\n            })\n            .catch((e) => {\n                console.error(e);\n                options.error && options.error();\n            });\n    },\n};\n"
  },
  {
    "path": "src/js/bar.js",
    "content": "class Bar {\n    constructor(template) {\n        this.elements = {};\n        this.elements.volume = template.volumeBar;\n        this.elements.played = template.playedBar;\n        this.elements.loaded = template.loadedBar;\n        this.elements.danmaku = template.danmakuOpacityBar;\n    }\n\n    /**\n     * Update progress\n     *\n     * @param {String} type - Point out which bar it is\n     * @param {Number} percentage\n     * @param {String} direction - Point out the direction of this bar, Should be height or width\n     */\n    set(type, percentage, direction) {\n        percentage = Math.max(percentage, 0);\n        percentage = Math.min(percentage, 1);\n        this.elements[type].style[direction] = percentage * 100 + '%';\n    }\n\n    get(type) {\n        return parseFloat(this.elements[type].style.width) / 100;\n    }\n}\n\nexport default Bar;\n"
  },
  {
    "path": "src/js/bezel.js",
    "content": "class Bezel {\n    constructor(container) {\n        this.container = container;\n\n        this.container.addEventListener('animationend', () => {\n            this.container.classList.remove('dplayer-bezel-transition');\n        });\n    }\n\n    switch(icon) {\n        this.container.innerHTML = icon;\n        this.container.classList.add('dplayer-bezel-transition');\n    }\n}\n\nexport default Bezel;\n"
  },
  {
    "path": "src/js/comment.js",
    "content": "import utils from './utils';\n\nclass Comment {\n    constructor(player) {\n        this.player = player;\n\n        this.player.template.mask.addEventListener('click', () => {\n            this.hide();\n        });\n        this.player.template.commentButton.addEventListener('click', () => {\n            this.show();\n        });\n        this.player.template.commentSettingButton.addEventListener('click', () => {\n            this.toggleSetting();\n        });\n\n        this.player.template.commentColorSettingBox.addEventListener('click', () => {\n            const sele = this.player.template.commentColorSettingBox.querySelector('input:checked+span');\n            if (sele) {\n                const color = this.player.template.commentColorSettingBox.querySelector('input:checked').value;\n                this.player.template.commentSettingFill.style.fill = color;\n                this.player.template.commentInput.style.color = color;\n                this.player.template.commentSendFill.style.fill = color;\n            }\n        });\n\n        this.player.template.commentInput.addEventListener('click', () => {\n            this.hideSetting();\n        });\n        this.player.template.commentInput.addEventListener('keydown', (e) => {\n            const event = e || window.event;\n            if (event.keyCode === 13) {\n                this.send();\n            }\n        });\n\n        this.player.template.commentSendButton.addEventListener('click', () => {\n            this.send();\n        });\n    }\n\n    show() {\n        this.player.controller.disableAutoHide = true;\n        this.player.template.controller.classList.add('dplayer-controller-comment');\n        this.player.template.mask.classList.add('dplayer-mask-show');\n        this.player.container.classList.add('dplayer-show-controller');\n        this.player.template.commentInput.focus();\n    }\n\n    hide() {\n        this.player.template.controller.classList.remove('dplayer-controller-comment');\n        this.player.template.mask.classList.remove('dplayer-mask-show');\n        this.player.container.classList.remove('dplayer-show-controller');\n        this.player.controller.disableAutoHide = false;\n        this.hideSetting();\n    }\n\n    showSetting() {\n        this.player.template.commentSettingBox.classList.add('dplayer-comment-setting-open');\n    }\n\n    hideSetting() {\n        this.player.template.commentSettingBox.classList.remove('dplayer-comment-setting-open');\n    }\n\n    toggleSetting() {\n        if (this.player.template.commentSettingBox.classList.contains('dplayer-comment-setting-open')) {\n            this.hideSetting();\n        } else {\n            this.showSetting();\n        }\n    }\n\n    send() {\n        this.player.template.commentInput.blur();\n\n        // text can't be empty\n        if (!this.player.template.commentInput.value.replace(/^\\s+|\\s+$/g, '')) {\n            this.player.notice(this.player.tran('please-input-danmaku'));\n            return;\n        }\n\n        this.player.danmaku.send(\n            {\n                text: this.player.template.commentInput.value,\n                color: utils.color2Number(this.player.container.querySelector('.dplayer-comment-setting-color input:checked').value),\n                type: parseInt(this.player.container.querySelector('.dplayer-comment-setting-type input:checked').value),\n            },\n            () => {\n                this.player.template.commentInput.value = '';\n                this.hide();\n            }\n        );\n    }\n}\n\nexport default Comment;\n"
  },
  {
    "path": "src/js/contextmenu.js",
    "content": "class ContextMenu {\n    constructor(player) {\n        this.player = player;\n        this.shown = false;\n\n        Array.prototype.slice.call(this.player.template.menuItem).forEach((item, index) => {\n            if (this.player.options.contextmenu[index].click) {\n                item.addEventListener('click', () => {\n                    this.player.options.contextmenu[index].click(this.player);\n                    this.hide();\n                });\n            }\n        });\n\n        this.contextmenuHandler = (e) => {\n            if (this.shown) {\n                this.hide();\n                return;\n            }\n\n            const event = e || window.event;\n            event.preventDefault();\n\n            const clientRect = this.player.container.getBoundingClientRect();\n            this.show(event.clientX - clientRect.left, event.clientY - clientRect.top);\n\n            this.player.template.mask.addEventListener('click', () => {\n                this.hide();\n            });\n        };\n        this.player.container.addEventListener('contextmenu', this.contextmenuHandler);\n    }\n\n    show(x, y) {\n        this.player.template.menu.classList.add('dplayer-menu-show');\n\n        const clientRect = this.player.container.getBoundingClientRect();\n        if (x + this.player.template.menu.offsetWidth >= clientRect.width) {\n            this.player.template.menu.style.right = clientRect.width - x + 'px';\n            this.player.template.menu.style.left = 'initial';\n        } else {\n            this.player.template.menu.style.left = x + 'px';\n            this.player.template.menu.style.right = 'initial';\n        }\n        if (y + this.player.template.menu.offsetHeight >= clientRect.height) {\n            this.player.template.menu.style.bottom = clientRect.height - y + 'px';\n            this.player.template.menu.style.top = 'initial';\n        } else {\n            this.player.template.menu.style.top = y + 'px';\n            this.player.template.menu.style.bottom = 'initial';\n        }\n\n        this.player.template.mask.classList.add('dplayer-mask-show');\n\n        this.shown = true;\n        this.player.events.trigger('contextmenu_show');\n    }\n\n    hide() {\n        this.player.template.mask.classList.remove('dplayer-mask-show');\n        this.player.template.menu.classList.remove('dplayer-menu-show');\n\n        this.shown = false;\n        this.player.events.trigger('contextmenu_hide');\n    }\n\n    destroy() {\n        this.player.container.removeEventListener('contextmenu', this.contextmenuHandler);\n    }\n}\n\nexport default ContextMenu;\n"
  },
  {
    "path": "src/js/controller.js",
    "content": "import utils from './utils';\nimport Thumbnails from './thumbnails';\nimport Icons from './icons';\n\nlet cast;\nlet runOnce = true;\nlet isCasting = false;\n\nclass Controller {\n    constructor(player) {\n        this.player = player;\n\n        this.autoHideTimer = 0;\n        if (!utils.isMobile) {\n            this.setAutoHideHandler = this.setAutoHide.bind(this);\n            this.player.container.addEventListener('mousemove', this.setAutoHideHandler);\n            this.player.container.addEventListener('click', this.setAutoHideHandler);\n            this.player.on('play', this.setAutoHideHandler);\n            this.player.on('pause', this.setAutoHideHandler);\n        }\n\n        this.initPlayButton();\n        this.initThumbnails();\n        this.initPlayedBar();\n        this.initFullButton();\n        this.initQualityButton();\n        this.initScreenshotButton();\n        // if subtitle url not array, not init old single subtitle button\n        if (this.player.options.subtitle) {\n            if (typeof this.player.options.subtitle.url === 'string') {\n                this.initSubtitleButton();\n            }\n        }\n        this.initHighlights();\n        this.initAirplayButton();\n        this.initChromecastButton();\n        if (!utils.isMobile) {\n            this.initVolumeButton();\n        }\n    }\n\n    initPlayButton() {\n        this.player.template.playButton.addEventListener('click', () => {\n            this.player.toggle();\n        });\n\n        this.player.template.mobilePlayButton.addEventListener('click', () => {\n            this.player.toggle();\n        });\n\n        if (!utils.isMobile) {\n            if (!this.player.options.preventClickToggle) {\n                this.player.template.videoWrap.addEventListener('click', () => {\n                    this.player.toggle();\n                });\n                this.player.template.controllerMask.addEventListener('click', () => {\n                    this.player.toggle();\n                });\n            }\n        } else {\n            this.player.template.videoWrap.addEventListener('click', () => {\n                this.toggle();\n            });\n            this.player.template.controllerMask.addEventListener('click', () => {\n                this.toggle();\n            });\n        }\n    }\n\n    initHighlights() {\n        this.player.on('durationchange', () => {\n            if (this.player.video.duration !== 1 && this.player.video.duration !== Infinity) {\n                if (this.player.options.highlight) {\n                    const highlights = this.player.template.playedBarWrap.querySelectorAll('.dplayer-highlight');\n                    [].slice.call(highlights, 0).forEach((item) => {\n                        this.player.template.playedBarWrap.removeChild(item);\n                    });\n                    for (let i = 0; i < this.player.options.highlight.length; i++) {\n                        if (!this.player.options.highlight[i].text || !this.player.options.highlight[i].time) {\n                            continue;\n                        }\n                        const p = document.createElement('div');\n                        p.classList.add('dplayer-highlight');\n                        p.style.left = (this.player.options.highlight[i].time / this.player.video.duration) * 100 + '%';\n                        p.innerHTML = '<span class=\"dplayer-highlight-text\">' + this.player.options.highlight[i].text + '</span>';\n                        this.player.template.playedBarWrap.insertBefore(p, this.player.template.playedBarTime);\n                    }\n                }\n            }\n        });\n    }\n\n    initThumbnails() {\n        if (this.player.options.video.thumbnails) {\n            this.thumbnails = new Thumbnails({\n                container: this.player.template.barPreview,\n                barWidth: this.player.template.barWrap.offsetWidth,\n                url: this.player.options.video.thumbnails,\n                events: this.player.events,\n            });\n\n            this.player.on('loadedmetadata', () => {\n                this.thumbnails.resize(160, (this.player.video.videoHeight / this.player.video.videoWidth) * 160, this.player.template.barWrap.offsetWidth);\n            });\n        }\n    }\n\n    initPlayedBar() {\n        const thumbMove = (e) => {\n            let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.playedBarWrap)) / this.player.template.playedBarWrap.clientWidth;\n            percentage = Math.max(percentage, 0);\n            percentage = Math.min(percentage, 1);\n            this.player.bar.set('played', percentage, 'width');\n            this.player.template.ptime.innerHTML = utils.secondToTime(percentage * this.player.video.duration);\n        };\n\n        const thumbUp = (e) => {\n            document.removeEventListener(utils.nameMap.dragEnd, thumbUp);\n            document.removeEventListener(utils.nameMap.dragMove, thumbMove);\n            let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.playedBarWrap)) / this.player.template.playedBarWrap.clientWidth;\n            percentage = Math.max(percentage, 0);\n            percentage = Math.min(percentage, 1);\n            this.player.bar.set('played', percentage, 'width');\n            this.player.seek(this.player.bar.get('played') * this.player.video.duration);\n            this.player.moveBar = false;\n        };\n\n        this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragStart, () => {\n            this.player.moveBar = true;\n            document.addEventListener(utils.nameMap.dragMove, thumbMove);\n            document.addEventListener(utils.nameMap.dragEnd, thumbUp);\n        });\n\n        this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragMove, (e) => {\n            if (this.player.video.duration) {\n                const px = this.player.template.playedBarWrap.getBoundingClientRect().left;\n                const tx = (e.clientX || e.changedTouches[0].clientX) - px;\n                if (tx < 0 || tx > this.player.template.playedBarWrap.offsetWidth) {\n                    return;\n                }\n                const time = this.player.video.duration * (tx / this.player.template.playedBarWrap.offsetWidth);\n                if (utils.isMobile) {\n                    this.thumbnails && this.thumbnails.show();\n                }\n                this.thumbnails && this.thumbnails.move(tx);\n                this.player.template.playedBarTime.style.left = `${tx - (time >= 3600 ? 25 : 20)}px`;\n                this.player.template.playedBarTime.innerText = utils.secondToTime(time);\n                this.player.template.playedBarTime.classList.remove('hidden');\n            }\n        });\n\n        this.player.template.playedBarWrap.addEventListener(utils.nameMap.dragEnd, () => {\n            if (utils.isMobile) {\n                this.thumbnails && this.thumbnails.hide();\n            }\n        });\n\n        if (!utils.isMobile) {\n            this.player.template.playedBarWrap.addEventListener('mouseenter', () => {\n                if (this.player.video.duration) {\n                    this.thumbnails && this.thumbnails.show();\n                    this.player.template.playedBarTime.classList.remove('hidden');\n                }\n            });\n\n            this.player.template.playedBarWrap.addEventListener('mouseleave', () => {\n                if (this.player.video.duration) {\n                    this.thumbnails && this.thumbnails.hide();\n                    this.player.template.playedBarTime.classList.add('hidden');\n                }\n            });\n        }\n    }\n\n    initFullButton() {\n        this.player.template.browserFullButton.addEventListener('click', () => {\n            this.player.fullScreen.toggle('browser');\n        });\n\n        this.player.template.webFullButton.addEventListener('click', () => {\n            this.player.fullScreen.toggle('web');\n        });\n    }\n\n    initVolumeButton() {\n        const vWidth = 35;\n\n        const volumeMove = (event) => {\n            const e = event || window.event;\n            const percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.volumeBarWrap) - 5.5) / vWidth;\n            this.player.volume(percentage);\n        };\n        const volumeUp = () => {\n            document.removeEventListener(utils.nameMap.dragEnd, volumeUp);\n            document.removeEventListener(utils.nameMap.dragMove, volumeMove);\n            this.player.template.volumeButton.classList.remove('dplayer-volume-active');\n        };\n\n        this.player.template.volumeBarWrapWrap.addEventListener('click', (event) => {\n            const e = event || window.event;\n            const percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.volumeBarWrap) - 5.5) / vWidth;\n            this.player.volume(percentage);\n        });\n        this.player.template.volumeBarWrapWrap.addEventListener(utils.nameMap.dragStart, () => {\n            document.addEventListener(utils.nameMap.dragMove, volumeMove);\n            document.addEventListener(utils.nameMap.dragEnd, volumeUp);\n            this.player.template.volumeButton.classList.add('dplayer-volume-active');\n        });\n        this.player.template.volumeButtonIcon.addEventListener('click', () => {\n            if (this.player.video.muted) {\n                this.player.video.muted = false;\n                this.player.switchVolumeIcon();\n                this.player.bar.set('volume', this.player.volume(), 'width');\n            } else {\n                this.player.video.muted = true;\n                this.player.template.volumeIcon.innerHTML = Icons.volumeOff;\n                this.player.bar.set('volume', 0, 'width');\n            }\n        });\n    }\n\n    initQualityButton() {\n        if (this.player.options.video.quality) {\n            this.player.template.qualityList.addEventListener('click', (e) => {\n                if (e.target.classList.contains('dplayer-quality-item')) {\n                    this.player.switchQuality(e.target.dataset.index);\n                }\n            });\n        }\n    }\n\n    initScreenshotButton() {\n        if (this.player.options.screenshot) {\n            this.player.template.camareButton.addEventListener('click', () => {\n                const canvas = document.createElement('canvas');\n                canvas.width = this.player.video.videoWidth;\n                canvas.height = this.player.video.videoHeight;\n                canvas.getContext('2d').drawImage(this.player.video, 0, 0, canvas.width, canvas.height);\n\n                let dataURL;\n                canvas.toBlob((blob) => {\n                    dataURL = URL.createObjectURL(blob);\n                    const link = document.createElement('a');\n                    link.href = dataURL;\n                    link.download = 'DPlayer.png';\n                    link.style.display = 'none';\n                    document.body.appendChild(link);\n                    link.click();\n                    document.body.removeChild(link);\n                    URL.revokeObjectURL(dataURL);\n                    this.player.events.trigger('screenshot', dataURL);\n                });\n            });\n        }\n    }\n\n    initAirplayButton() {\n        if (this.player.options.airplay) {\n            if (window.WebKitPlaybackTargetAvailabilityEvent) {\n                this.player.video.addEventListener(\n                    'webkitplaybacktargetavailabilitychanged',\n                    function (event) {\n                        switch (event.availability) {\n                            case 'available':\n                                this.template.airplayButton.disable = false;\n                                break;\n\n                            default:\n                                this.template.airplayButton.disable = true;\n                        }\n\n                        this.template.airplayButton.addEventListener(\n                            'click',\n                            function () {\n                                this.video.webkitShowPlaybackTargetPicker();\n                            }.bind(this)\n                        );\n                    }.bind(this.player)\n                );\n            } else {\n                this.player.template.airplayButton.style.display = 'none';\n            }\n        }\n    }\n\n    initChromecast() {\n        const script = window.document.createElement('script');\n        script.setAttribute('type', 'text/javascript');\n        script.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1');\n        window.document.body.appendChild(script);\n\n        window.__onGCastApiAvailable = (isAvailable) => {\n            if (isAvailable) {\n                cast = window.chrome.cast;\n                const sessionRequest = new cast.SessionRequest(cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID);\n                const apiConfig = new cast.ApiConfig(\n                    sessionRequest,\n                    () => {},\n                    (status) => {\n                        if (status === cast.ReceiverAvailability.AVAILABLE) {\n                            console.log('chromecast: ', status);\n                        }\n                    }\n                );\n                cast.initialize(apiConfig, () => {});\n            }\n        };\n    }\n\n    initChromecastButton() {\n        if (this.player.options.chromecast) {\n            if (runOnce) {\n                runOnce = false;\n                this.initChromecast();\n            }\n            const discoverDevices = () => {\n                cast.requestSession(\n                    (s) => {\n                        this.session = s;\n                        launchMedia(this.player.options.video.url);\n                    },\n                    (err) => {\n                        if (err.code === 'cancel') {\n                            this.session = undefined;\n                        } else {\n                            console.error('Error selecting a cast device', err);\n                        }\n                    }\n                );\n            };\n\n            const launchMedia = (media) => {\n                const mediaInfo = new cast.media.MediaInfo(media);\n                const request = new cast.media.LoadRequest(mediaInfo);\n\n                if (!this.session) {\n                    window.open(media);\n                    return false;\n                }\n                this.session.loadMedia(request, onMediaDiscovered.bind(this, 'loadMedia'), onMediaError).play();\n                return true;\n            };\n\n            const onMediaDiscovered = (how, media) => {\n                this.currentMedia = media;\n            };\n\n            const onMediaError = (err) => {\n                console.error('Error launching media', err);\n            };\n\n            this.player.template.chromecastButton.addEventListener('click', () => {\n                if (isCasting) {\n                    isCasting = false;\n                    this.currentMedia.stop();\n                    this.session.stop();\n                    this.initChromecast();\n                } else {\n                    isCasting = true;\n                    discoverDevices();\n                }\n            });\n        }\n    }\n\n    initSubtitleButton() {\n        this.player.events.on('subtitle_show', () => {\n            this.player.template.subtitleButton.dataset.balloon = this.player.tran('hide-subs');\n            this.player.template.subtitleButtonInner.style.opacity = '';\n            this.player.user.set('subtitle', 1);\n        });\n        this.player.events.on('subtitle_hide', () => {\n            this.player.template.subtitleButton.dataset.balloon = this.player.tran('show-subs');\n            this.player.template.subtitleButtonInner.style.opacity = '0.4';\n            this.player.user.set('subtitle', 0);\n        });\n\n        this.player.template.subtitleButton.addEventListener('click', () => {\n            this.player.subtitle.toggle();\n        });\n    }\n\n    setAutoHide() {\n        this.show();\n        clearTimeout(this.autoHideTimer);\n        this.autoHideTimer = setTimeout(() => {\n            if (this.player.video.played.length && !this.player.paused && !this.disableAutoHide) {\n                this.hide();\n            }\n        }, 3000);\n    }\n\n    show() {\n        this.player.container.classList.remove('dplayer-hide-controller');\n    }\n\n    hide() {\n        this.player.container.classList.add('dplayer-hide-controller');\n        this.player.setting.hide();\n        this.player.comment && this.player.comment.hide();\n    }\n\n    isShow() {\n        return !this.player.container.classList.contains('dplayer-hide-controller');\n    }\n\n    toggle() {\n        if (this.isShow()) {\n            this.hide();\n        } else {\n            this.show();\n        }\n    }\n\n    destroy() {\n        if (!utils.isMobile) {\n            this.player.container.removeEventListener('mousemove', this.setAutoHideHandler);\n            this.player.container.removeEventListener('click', this.setAutoHideHandler);\n        }\n        clearTimeout(this.autoHideTimer);\n    }\n}\n\nexport default Controller;\n"
  },
  {
    "path": "src/js/danmaku.js",
    "content": "import utils from './utils';\n\nclass Danmaku {\n    constructor(options) {\n        this.options = options;\n        this.player = this.options.player;\n        this.container = this.options.container;\n        this.danTunnel = {\n            right: {},\n            top: {},\n            bottom: {},\n        };\n        this.danIndex = 0;\n        this.dan = [];\n        this.showing = true;\n        this._opacity = this.options.opacity;\n        this.events = this.options.events;\n        this.unlimited = this.options.unlimited;\n        this._measure('');\n\n        this.load();\n    }\n\n    load() {\n        let apiurl;\n        if (this.options.api.maximum) {\n            apiurl = `${this.options.api.address}v3/?id=${this.options.api.id}&max=${this.options.api.maximum}`;\n        } else {\n            apiurl = `${this.options.api.address}v3/?id=${this.options.api.id}`;\n        }\n        const endpoints = (this.options.api.addition || []).slice(0);\n        endpoints.push(apiurl);\n        this.events && this.events.trigger('danmaku_load_start', endpoints);\n\n        this._readAllEndpoints(endpoints, (results) => {\n            this.dan = [].concat.apply([], results).sort((a, b) => a.time - b.time);\n            window.requestAnimationFrame(() => {\n                this.frame();\n            });\n\n            this.options.callback();\n\n            this.events && this.events.trigger('danmaku_load_end');\n        });\n    }\n\n    reload(newAPI) {\n        this.options.api = newAPI;\n        this.dan = [];\n        this.clear();\n        this.load();\n    }\n\n    /**\n     * Asynchronously read danmaku from all API endpoints\n     */\n    _readAllEndpoints(endpoints, callback) {\n        const results = [];\n        let readCount = 0;\n\n        for (let i = 0; i < endpoints.length; ++i) {\n            this.options.apiBackend.read({\n                url: endpoints[i],\n                success: (data) => {\n                    results[i] = data;\n\n                    ++readCount;\n                    if (readCount === endpoints.length) {\n                        callback(results);\n                    }\n                },\n                error: (msg) => {\n                    this.options.error(msg || this.options.tran('danmaku-failed'));\n                    results[i] = [];\n\n                    ++readCount;\n                    if (readCount === endpoints.length) {\n                        callback(results);\n                    }\n                },\n            });\n        }\n    }\n\n    send(dan, callback) {\n        const danmakuData = {\n            token: this.options.api.token,\n            id: this.options.api.id,\n            author: this.options.api.user,\n            time: this.options.time(),\n            text: dan.text,\n            color: dan.color,\n            type: dan.type,\n        };\n        this.options.apiBackend.send({\n            url: this.options.api.address + 'v3/',\n            data: danmakuData,\n            success: callback,\n            error: (msg) => {\n                this.options.error(msg || this.options.tran('danmaku-failed'));\n            },\n        });\n\n        this.dan.splice(this.danIndex, 0, danmakuData);\n        this.danIndex++;\n        const danmaku = {\n            text: this.htmlEncode(danmakuData.text),\n            color: danmakuData.color,\n            type: danmakuData.type,\n            border: `2px solid ${this.options.borderColor}`,\n        };\n        this.draw(danmaku);\n\n        this.events && this.events.trigger('danmaku_send', danmakuData);\n    }\n\n    frame() {\n        if (this.dan.length && !this.paused && this.showing) {\n            let item = this.dan[this.danIndex];\n            const dan = [];\n            while (item && this.options.time() > parseFloat(item.time)) {\n                dan.push(item);\n                item = this.dan[++this.danIndex];\n            }\n            this.draw(dan);\n        }\n        window.requestAnimationFrame(() => {\n            this.frame();\n        });\n    }\n\n    opacity(percentage) {\n        if (percentage !== undefined) {\n            const items = this.container.getElementsByClassName('dplayer-danmaku-item');\n            for (let i = 0; i < items.length; i++) {\n                items[i].style.opacity = percentage;\n            }\n            this._opacity = percentage;\n\n            this.events && this.events.trigger('danmaku_opacity', this._opacity);\n        }\n        return this._opacity;\n    }\n\n    /**\n     * Push a danmaku into DPlayer\n     *\n     * @param {Object Array} dan - {text, color, type}\n     * text - danmaku content\n     * color - danmaku color, default: `#fff`\n     * type - danmaku type, `right` `top` `bottom`, default: `right`\n     */\n    draw(dan) {\n        if (this.showing) {\n            const itemHeight = this.options.height;\n            const danWidth = this.container.offsetWidth;\n            const danHeight = this.container.offsetHeight;\n            const itemY = parseInt(danHeight / itemHeight);\n\n            const danItemRight = (ele) => {\n                const eleWidth = ele.offsetWidth || parseInt(ele.style.width);\n                const eleRight = ele.getBoundingClientRect().right || this.container.getBoundingClientRect().right + eleWidth;\n                return this.container.getBoundingClientRect().right - eleRight;\n            };\n\n            const danSpeed = (width) => (danWidth + width) / 5;\n\n            const getTunnel = (ele, type, width) => {\n                const tmp = danWidth / danSpeed(width);\n\n                for (let i = 0; this.unlimited || i < itemY; i++) {\n                    const item = this.danTunnel[type][i + ''];\n                    if (item && item.length) {\n                        if (type !== 'right') {\n                            continue;\n                        }\n                        for (let j = 0; j < item.length; j++) {\n                            const danRight = danItemRight(item[j]) - 10;\n                            if (danRight <= danWidth - tmp * danSpeed(parseInt(item[j].style.width)) || danRight <= 0) {\n                                break;\n                            }\n                            if (j === item.length - 1) {\n                                this.danTunnel[type][i + ''].push(ele);\n                                ele.addEventListener('animationend', () => {\n                                    this.danTunnel[type][i + ''].splice(0, 1);\n                                });\n                                return i % itemY;\n                            }\n                        }\n                    } else {\n                        this.danTunnel[type][i + ''] = [ele];\n                        ele.addEventListener('animationend', () => {\n                            this.danTunnel[type][i + ''].splice(0, 1);\n                        });\n                        return i % itemY;\n                    }\n                }\n                return -1;\n            };\n\n            if (Object.prototype.toString.call(dan) !== '[object Array]') {\n                dan = [dan];\n            }\n\n            const docFragment = document.createDocumentFragment();\n\n            for (let i = 0; i < dan.length; i++) {\n                dan[i].type = utils.number2Type(dan[i].type);\n                if (!dan[i].color) {\n                    dan[i].color = 16777215;\n                }\n                const item = document.createElement('div');\n                item.classList.add('dplayer-danmaku-item');\n                item.classList.add(`dplayer-danmaku-${dan[i].type}`);\n                if (dan[i].border) {\n                    item.innerHTML = `<span style=\"border:${dan[i].border}\">${dan[i].text}</span>`;\n                } else {\n                    item.innerHTML = dan[i].text;\n                }\n                item.style.opacity = this._opacity;\n                item.style.color = utils.number2Color(dan[i].color);\n                item.addEventListener('animationend', () => {\n                    this.container.removeChild(item);\n                });\n\n                const itemWidth = this._measure(dan[i].text);\n                let tunnel;\n\n                // adjust\n                switch (dan[i].type) {\n                    case 'right':\n                        tunnel = getTunnel(item, dan[i].type, itemWidth);\n                        if (tunnel >= 0) {\n                            item.style.width = itemWidth + 1 + 'px';\n                            item.style.top = itemHeight * tunnel + 'px';\n                            item.style.transform = `translateX(-${danWidth}px)`;\n                        }\n                        break;\n                    case 'top':\n                        tunnel = getTunnel(item, dan[i].type);\n                        if (tunnel >= 0) {\n                            item.style.top = itemHeight * tunnel + 'px';\n                        }\n                        break;\n                    case 'bottom':\n                        tunnel = getTunnel(item, dan[i].type);\n                        if (tunnel >= 0) {\n                            item.style.bottom = itemHeight * tunnel + 'px';\n                        }\n                        break;\n                    default:\n                        console.error(`Can't handled danmaku type: ${dan[i].type}`);\n                }\n\n                if (tunnel >= 0) {\n                    // move\n                    item.classList.add('dplayer-danmaku-move');\n                    item.style.animationDuration = this._danAnimation(dan[i].type);\n\n                    // insert\n                    docFragment.appendChild(item);\n                }\n            }\n\n            this.container.appendChild(docFragment);\n\n            return docFragment;\n        }\n    }\n\n    play() {\n        this.paused = false;\n    }\n\n    pause() {\n        this.paused = true;\n    }\n\n    _measure(text) {\n        if (!this.context) {\n            const measureStyle = getComputedStyle(this.container.getElementsByClassName('dplayer-danmaku-item')[0], null);\n            this.context = document.createElement('canvas').getContext('2d');\n            this.context.font = measureStyle.getPropertyValue('font');\n        }\n        return this.context.measureText(text).width;\n    }\n\n    seek() {\n        this.clear();\n        for (let i = 0; i < this.dan.length; i++) {\n            if (this.dan[i].time >= this.options.time()) {\n                this.danIndex = i;\n                break;\n            }\n            this.danIndex = this.dan.length;\n        }\n    }\n\n    clear() {\n        this.danTunnel = {\n            right: {},\n            top: {},\n            bottom: {},\n        };\n        this.danIndex = 0;\n        this.options.container.innerHTML = '';\n\n        this.events && this.events.trigger('danmaku_clear');\n    }\n\n    htmlEncode(str) {\n        return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\\//g, '&#x2f;');\n    }\n\n    resize() {\n        const danWidth = this.container.offsetWidth;\n        const items = this.container.getElementsByClassName('dplayer-danmaku-item');\n        for (let i = 0; i < items.length; i++) {\n            items[i].style.transform = `translateX(-${danWidth}px)`;\n        }\n    }\n\n    hide() {\n        this.showing = false;\n        this.pause();\n        this.clear();\n\n        this.events && this.events.trigger('danmaku_hide');\n    }\n\n    show() {\n        this.seek();\n        this.showing = true;\n        this.play();\n\n        this.events && this.events.trigger('danmaku_show');\n    }\n\n    unlimit(boolean) {\n        this.unlimited = boolean;\n    }\n\n    speed(rate) {\n        this.options.api.speedRate = rate;\n    }\n\n    _danAnimation(position) {\n        const rate = this.options.api.speedRate || 1;\n        const isFullScreen = !!this.player.fullScreen.isFullScreen();\n        const animations = {\n            top: `${(isFullScreen ? 6 : 4) / rate}s`,\n            right: `${(isFullScreen ? 8 : 5) / rate}s`,\n            bottom: `${(isFullScreen ? 6 : 4) / rate}s`,\n        };\n        return animations[position];\n    }\n}\n\nexport default Danmaku;\n"
  },
  {
    "path": "src/js/events.js",
    "content": "class Events {\n    constructor() {\n        this.events = {};\n\n        this.videoEvents = [\n            'abort',\n            'canplay',\n            'canplaythrough',\n            'durationchange',\n            'emptied',\n            'ended',\n            'error',\n            'loadeddata',\n            'loadedmetadata',\n            'loadstart',\n            'mozaudioavailable',\n            'pause',\n            'play',\n            'playing',\n            'progress',\n            'ratechange',\n            'seeked',\n            'seeking',\n            'stalled',\n            'suspend',\n            'timeupdate',\n            'volumechange',\n            'waiting',\n        ];\n        this.playerEvents = [\n            'screenshot',\n            'thumbnails_show',\n            'thumbnails_hide',\n            'danmaku_show',\n            'danmaku_hide',\n            'danmaku_clear',\n            'danmaku_loaded',\n            'danmaku_send',\n            'danmaku_opacity',\n            'contextmenu_show',\n            'contextmenu_hide',\n            'notice_show',\n            'notice_hide',\n            'quality_start',\n            'quality_end',\n            'destroy',\n            'resize',\n            'fullscreen',\n            'fullscreen_cancel',\n            'webfullscreen',\n            'webfullscreen_cancel',\n            'subtitle_show',\n            'subtitle_hide',\n            'subtitle_change',\n        ];\n    }\n\n    on(name, callback) {\n        if (this.type(name) && typeof callback === 'function') {\n            if (!this.events[name]) {\n                this.events[name] = [];\n            }\n            this.events[name].push(callback);\n        }\n    }\n\n    trigger(name, info) {\n        if (this.events[name] && this.events[name].length) {\n            for (let i = 0; i < this.events[name].length; i++) {\n                this.events[name][i](info);\n            }\n        }\n    }\n\n    type(name) {\n        if (this.playerEvents.indexOf(name) !== -1) {\n            return 'player';\n        } else if (this.videoEvents.indexOf(name) !== -1) {\n            return 'video';\n        }\n\n        console.error(`Unknown event name: ${name}`);\n        return null;\n    }\n}\n\nexport default Events;\n"
  },
  {
    "path": "src/js/fullscreen.js",
    "content": "import utils from './utils';\n\nclass FullScreen {\n    constructor(player) {\n        this.player = player;\n        this.lastScrollPosition = { left: 0, top: 0 };\n        this.player.events.on('webfullscreen', () => {\n            this.player.resize();\n        });\n        this.player.events.on('webfullscreen_cancel', () => {\n            this.player.resize();\n            utils.setScrollPosition(this.lastScrollPosition);\n        });\n\n        this.fullscreenchange = () => {\n            this.player.resize();\n            if (this.isFullScreen('browser')) {\n                this.player.events.trigger('fullscreen');\n            } else {\n                utils.setScrollPosition(this.lastScrollPosition);\n                this.player.events.trigger('fullscreen_cancel');\n            }\n        };\n        this.docfullscreenchange = () => {\n            const fullEle = document.fullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;\n            if (fullEle && fullEle !== this.player.container) {\n                return;\n            }\n            this.player.resize();\n            if (fullEle) {\n                this.player.events.trigger('fullscreen');\n            } else {\n                utils.setScrollPosition(this.lastScrollPosition);\n                this.player.events.trigger('fullscreen_cancel');\n            }\n        };\n        if (/Firefox/.test(navigator.userAgent)) {\n            document.addEventListener('mozfullscreenchange', this.docfullscreenchange);\n            document.addEventListener('fullscreenchange', this.docfullscreenchange);\n        } else {\n            this.player.container.addEventListener('fullscreenchange', this.fullscreenchange);\n            this.player.container.addEventListener('webkitfullscreenchange', this.fullscreenchange);\n            document.addEventListener('msfullscreenchange', this.docfullscreenchange);\n            document.addEventListener('MSFullscreenChange', this.docfullscreenchange);\n        }\n    }\n\n    isFullScreen(type = 'browser') {\n        switch (type) {\n            case 'browser':\n                return document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;\n            case 'web':\n                return this.player.container.classList.contains('dplayer-fulled');\n        }\n    }\n\n    request(type = 'browser') {\n        const anotherType = type === 'browser' ? 'web' : 'browser';\n        const anotherTypeOn = this.isFullScreen(anotherType);\n        if (!anotherTypeOn) {\n            this.lastScrollPosition = utils.getScrollPosition();\n        }\n\n        switch (type) {\n            case 'browser':\n                if (this.player.container.requestFullscreen) {\n                    this.player.container.requestFullscreen();\n                } else if (this.player.container.mozRequestFullScreen) {\n                    this.player.container.mozRequestFullScreen();\n                } else if (this.player.container.webkitRequestFullscreen) {\n                    this.player.container.webkitRequestFullscreen();\n                } else if (this.player.video.webkitEnterFullscreen) {\n                    // Safari for iOS\n                    this.player.video.webkitEnterFullscreen();\n                } else if (this.player.video.webkitEnterFullScreen) {\n                    this.player.video.webkitEnterFullScreen();\n                } else if (this.player.container.msRequestFullscreen) {\n                    this.player.container.msRequestFullscreen();\n                }\n                break;\n            case 'web':\n                this.player.container.classList.add('dplayer-fulled');\n                document.body.classList.add('dplayer-web-fullscreen-fix');\n                this.player.events.trigger('webfullscreen');\n                break;\n        }\n\n        if (anotherTypeOn) {\n            this.cancel(anotherType);\n        }\n    }\n\n    cancel(type = 'browser') {\n        switch (type) {\n            case 'browser':\n                if (document.cancelFullScreen) {\n                    document.cancelFullScreen();\n                } else if (document.mozCancelFullScreen) {\n                    document.mozCancelFullScreen();\n                } else if (document.webkitCancelFullScreen) {\n                    document.webkitCancelFullScreen();\n                } else if (document.webkitCancelFullscreen) {\n                    document.webkitCancelFullscreen();\n                } else if (document.msCancelFullScreen) {\n                    document.msCancelFullScreen();\n                } else if (document.msExitFullscreen) {\n                    document.msExitFullscreen();\n                }\n                break;\n            case 'web':\n                this.player.container.classList.remove('dplayer-fulled');\n                document.body.classList.remove('dplayer-web-fullscreen-fix');\n                this.player.events.trigger('webfullscreen_cancel');\n                break;\n        }\n    }\n\n    toggle(type = 'browser') {\n        if (this.isFullScreen(type)) {\n            this.cancel(type);\n        } else {\n            this.request(type);\n        }\n    }\n\n    destroy() {\n        if (/Firefox/.test(navigator.userAgent)) {\n            document.removeEventListener('mozfullscreenchange', this.docfullscreenchange);\n            document.removeEventListener('fullscreenchange', this.docfullscreenchange);\n        } else {\n            this.player.container.removeEventListener('fullscreenchange', this.fullscreenchange);\n            this.player.container.removeEventListener('webkitfullscreenchange', this.fullscreenchange);\n            document.removeEventListener('msfullscreenchange', this.docfullscreenchange);\n            document.removeEventListener('MSFullscreenChange', this.docfullscreenchange);\n        }\n    }\n}\n\nexport default FullScreen;\n"
  },
  {
    "path": "src/js/hotkey.js",
    "content": "class HotKey {\n    constructor(player) {\n        this.player = player;\n        this.doHotKeyHandler = this.doHotKey.bind(this);\n        this.cancelFullScreenHandler = this.cancelFullScreen.bind(this);\n        if (this.player.options.hotkey) {\n            document.addEventListener('keydown', this.doHotKeyHandler);\n        }\n\n        document.addEventListener('keydown', this.cancelFullScreenHandler);\n    }\n\n    doHotKey(e) {\n        if (this.player.focus) {\n            const tag = document.activeElement.tagName.toUpperCase();\n            const editable = document.activeElement.getAttribute('contenteditable');\n            if (tag !== 'INPUT' && tag !== 'TEXTAREA' && editable !== '' && editable !== 'true') {\n                const event = e || window.event;\n                let percentage;\n                switch (event.keyCode) {\n                    case 32:\n                        event.preventDefault();\n                        this.player.toggle();\n                        break;\n                    case 37:\n                        event.preventDefault();\n                        if (this.player.options.live) {\n                            break;\n                        }\n                        this.player.seek(this.player.video.currentTime - 5);\n                        this.player.controller.setAutoHide();\n                        break;\n                    case 39:\n                        event.preventDefault();\n                        if (this.player.options.live) {\n                            break;\n                        }\n                        this.player.seek(this.player.video.currentTime + 5);\n                        this.player.controller.setAutoHide();\n                        break;\n                    case 38:\n                        event.preventDefault();\n                        percentage = this.player.volume() + 0.1;\n                        this.player.volume(percentage);\n                        break;\n                    case 40:\n                        event.preventDefault();\n                        percentage = this.player.volume() - 0.1;\n                        this.player.volume(percentage);\n                        break;\n                }\n            }\n        }\n    }\n\n    cancelFullScreen(e) {\n        const event = e || window.event;\n        switch (event.keyCode) {\n            case 27:\n                if (this.player.fullScreen.isFullScreen('web')) {\n                    this.player.fullScreen.cancel('web');\n                }\n                break;\n        }\n    }\n\n    destroy() {\n        if (this.player.options.hotkey) {\n            document.removeEventListener('keydown', this.doHotKeyHandler);\n        }\n        document.removeEventListener('keydown', this.cancelFullScreenHandler);\n    }\n}\n\nexport default HotKey;\n"
  },
  {
    "path": "src/js/i18n.js",
    "content": "/*\nW3C def language codes is :\n    language-code = primary-code ( \"-\" subcode )\n        primary-code    ISO 639-1   ( the names of language with 2 code )\n        subcode         ISO 3166    ( the names of countries )\n\nNOTE: use lowercase to prevent case typo from user!\nUse this as shown below..... */\n\nfunction i18n(lang) {\n    this.lang = lang;\n    // in case someone says en-us, and en is present!\n    this.fallbackLang = this.lang.includes('-') ? this.lang.split('-')[0] : this.lang;\n    this.tran = (key) => {\n        key = key.toLowerCase();\n        if (tranTxt[this.lang] && tranTxt[this.lang][key]) {\n            return tranTxt[this.lang][key];\n        } else if (tranTxt[this.fallbackLang] && tranTxt[this.fallbackLang][key]) {\n            return tranTxt[this.fallbackLang][key];\n        } else if (standard[key]) {\n            return standard[key];\n        } else {\n            return key;\n        }\n    };\n}\n\n// abstract model for recognizing if valid translations are present\n// const model = {\n//     'danmaku-loading': [],\n//     top: [],\n//     bottom: [],\n//     rolling: [],\n//     'input-danmaku-enter': [],\n//     'about-author': [],\n//     'dplayer-feedback': [],\n//     'about-dplayer': [],\n//     loop: [],\n//     speed: [],\n//     'opacity-danmaku': [],\n//     normal: [],\n//     'please-input-danmaku': [],\n//     'set-danmaku-color': [],\n//     'set-danmaku-type': [],\n//     'show-danmaku': [],\n//     'video-failed': [],\n//     'danmaku-failed': [],\n//     'danmaku-send-failed': [],\n//     'switching-quality': [{ symbol: '%q', name: 'Quality', example: '720p' }],\n//     'switched-quality': [{ symbol: '%q', name: 'Quality', example: '720p' }],\n//     ff: [{ symbol: '%s', name: 'Seconds', example: '5' }],\n//     rew: [{ symbol: '%s', name: 'Seconds', example: '5' }],\n//     'unlimited-danmaku': [],\n//     'send-danmaku': [],\n//     setting: [],\n//     fullscreen: [],\n//     'web-fullscreen': [],\n//     send: [],\n//     screenshot: [],\n//     airplay: [],\n//     chromecast: [],\n//     'show-subs': [],\n//     'hide-subs': [],\n//     volume: [],\n//     live: [],\n//     'video-info': [],\n// };\n\n// Standard english translations\nconst standard = {\n    'danmaku-loading': 'Danmaku is loading',\n    top: 'Top',\n    bottom: 'Bottom',\n    rolling: 'Rolling',\n    'input-danmaku-enter': 'Input danmaku, hit Enter',\n    'about-author': 'About author',\n    'dplayer-feedback': 'DPlayer feedback',\n    'about-dplayer': 'About DPlayer',\n    loop: 'Loop',\n    speed: 'Speed',\n    'opacity-danmaku': 'Opacity for danmaku',\n    normal: 'Normal',\n    'please-input-danmaku': 'Please input danmaku content!',\n    'set-danmaku-color': 'Set danmaku color',\n    'set-danmaku-type': 'Set danmaku type',\n    'show-danmaku': 'Show danmaku',\n    'video-failed': 'Video load failed',\n    'danmaku-failed': 'Danmaku load failed',\n    'danmaku-send-failed': 'Danmaku send failed',\n    'switching-quality': 'Switching to %q quality',\n    'switched-quality': 'Switched to %q quality',\n    ff: 'FF %s s',\n    rew: 'REW %s s',\n    'unlimited-danmaku': 'Unlimited danmaku',\n    'send-danmaku': 'Send danmaku',\n    setting: 'Setting',\n    fullscreen: 'Full screen',\n    'web-fullscreen': 'Web full screen',\n    send: 'Send',\n    screenshot: 'Screenshot',\n    airplay: 'AirPlay',\n    chromecast: 'ChromeCast',\n    subtitle: 'Subtitle',\n    off: 'Off',\n    'show-subs': 'Show subtitle',\n    'hide-subs': 'Hide subtitle',\n    volume: 'Volume',\n    live: 'Live',\n    'video-info': 'Video info',\n};\n\n// add translation text here\nconst tranTxt = {\n    en: standard,\n    'zh-cn': {\n        'danmaku-loading': '弹幕加载中',\n        top: '顶部',\n        bottom: '底部',\n        rolling: '滚动',\n        'input-danmaku-enter': '输入弹幕，回车发送',\n        'about-author': '关于作者',\n        'dplayer-feedback': '播放器意见反馈',\n        'about-dplayer': '关于 DPlayer 播放器',\n        loop: '洗脑循环',\n        speed: '速度',\n        'opacity-danmaku': '弹幕透明度',\n        normal: '正常',\n        'please-input-danmaku': '要输入弹幕内容啊喂！',\n        'set-danmaku-color': '设置弹幕颜色',\n        'set-danmaku-type': '设置弹幕类型',\n        'show-danmaku': '显示弹幕',\n        'video-failed': '视频加载失败',\n        'danmaku-failed': '弹幕加载失败',\n        'danmaku-send-failed': '弹幕发送失败',\n        'switching-quality': '正在切换至 %q 画质',\n        'switched-quality': '已经切换至 %q 画质',\n        ff: '快进 %s 秒',\n        rew: '快退 %s 秒',\n        'unlimited-danmaku': '海量弹幕',\n        'send-danmaku': '发送弹幕',\n        setting: '设置',\n        fullscreen: '全屏',\n        'web-fullscreen': '页面全屏',\n        send: '发送',\n        screenshot: '截图',\n        airplay: '无线投屏',\n        chromecast: 'ChromeCast',\n        subtitle: '字幕',\n        off: '关闭',\n        'show-subs': '显示字幕',\n        'hide-subs': '隐藏字幕',\n        volume: '音量',\n        live: '直播',\n        'video-info': '视频统计信息',\n    },\n    'zh-tw': {\n        'danmaku-loading': '彈幕載入中',\n        top: '頂部',\n        bottom: '底部',\n        rolling: '滾動',\n        'input-danmaku-enter': '輸入彈幕，Enter 發送',\n        'about-author': '關於作者',\n        'dplayer-feedback': '播放器意見回饋',\n        'about-dplayer': '關於 DPlayer 播放器',\n        loop: '循環播放',\n        speed: '速度',\n        'opacity-danmaku': '彈幕透明度',\n        normal: '正常',\n        'please-input-danmaku': '請輸入彈幕內容啊！',\n        'set-danmaku-color': '設定彈幕顏色',\n        'set-danmaku-type': '設定彈幕類型',\n        'show-danmaku': '顯示彈幕',\n        'video-failed': '影片載入失敗',\n        'danmaku-failed': '彈幕載入失敗',\n        'danmaku-send-failed': '彈幕發送失敗',\n        'switching-quality': '正在切換至 %q 畫質',\n        'switched-quality': '已經切換至 %q 畫質',\n        ff: '快進 %s 秒',\n        rew: '快退 %s 秒',\n        'unlimited-danmaku': '巨量彈幕',\n        'send-danmaku': '發送彈幕',\n        setting: '設定',\n        fullscreen: '全螢幕',\n        'web-fullscreen': '頁面全螢幕',\n        send: '發送',\n        screenshot: '截圖',\n        airplay: '無線投屏',\n        chromecast: 'ChromeCast',\n        subtitle: '字幕',\n        off: '關閉',\n        'show-subs': '顯示字幕',\n        'hide-subs': '隱藏字幕',\n        volume: '音量',\n        live: '直播',\n        'video-info': '影片統計訊息',\n    },\n    'ko-kr': {\n        'danmaku-loading': 'Danmaku를 불러오는 중입니다.',\n        top: 'Top',\n        bottom: 'Bottom',\n        rolling: 'Rolling',\n        'input-danmaku-enter': 'Danmaku를 입력하고 Enter를 누르세요.',\n        'about-author': '만든이',\n        'dplayer-feedback': '피드백 보내기',\n        'about-dplayer': 'DPlayer 정보',\n        loop: '반복',\n        speed: '배속',\n        'opacity-danmaku': 'Danmaku 불투명도',\n        normal: '표준',\n        'please-input-danmaku': 'Danmaku를 입력하세요!',\n        'set-danmaku-color': 'Danmaku 색상',\n        'set-danmaku-type': 'Danmaku 설정',\n        'show-danmaku': 'Danmaku 표시',\n        'video-failed': '비디오를 불러오지 못했습니다.',\n        'danmaku-failed': 'Danmaku를 불러오지 못했습니다.',\n        'danmaku-send-failed': 'Danmaku 전송에 실패했습니다.',\n        'Switching to': '',\n        'Switched to': '',\n        'switching-quality': '전환 %q 화질',\n        'switched-quality': '전환 됨 %q 화질',\n        ff: '앞으로 %s 초',\n        rew: '뒤로 %s 초',\n        'unlimited-danmaku': '끝없는 Danmaku',\n        'send-danmaku': 'Danmaku 보내기',\n        setting: '설정',\n        fullscreen: '전체 화면',\n        'web-fullscreen': '웹 내 전체화면',\n        send: '보내기',\n        screenshot: '화면 캡쳐',\n        airplay: '에어플레이',\n        chromecast: 'ChromeCast',\n        subtitle: '부제',\n        off: '끄다',\n        'show-subs': '자막 보이기',\n        'hide-subs': '자막 숨기기',\n        Volume: '볼륨',\n        live: '생방송',\n        'video-info': '비디오 정보',\n    },\n    de: {\n        'danmaku-loading': 'Danmaku lädt...',\n        top: 'Oben',\n        bottom: 'Unten',\n        rolling: 'Rollend',\n        'input-danmaku-enter': 'Drücke Enter nach dem Einfügen vom Danmaku',\n        'about-author': 'Über den Autor',\n        'dplayer-feedback': 'DPlayer Feedback',\n        'about-dplayer': 'Über DPlayer',\n        loop: 'Wiederholen',\n        speed: 'Geschwindigkeit',\n        'opacity-danmaku': 'Transparenz für Danmaku',\n        normal: 'Normal',\n        'please-input-danmaku': 'Bitte Danmaku Inhalt eingeben!',\n        'set-danmaku-color': 'Danmaku Farbe festlegen',\n        'set-danmaku-type': 'Danmaku Typ festlegen',\n        'show-danmaku': 'Zeige Danmaku',\n        'video-failed': 'Das Video konnte nicht geladen werden',\n        'danmaku-failed': 'Danmaku konnte nicht geladen werden',\n        'danmaku-send-failed': 'Das senden von Danmaku ist fehgeschlagen',\n        'switching-quality': 'Wechsle zur %q Qualität',\n        'switched-quality': 'Zur %q Qualität gewechselt',\n        ff: '%s s Vorwärts',\n        rew: '%s s Zurück',\n        'unlimited-danmaku': 'Unlimitiertes Danmaku',\n        'send-danmaku': 'Sende Danmaku',\n        setting: 'Einstellungen',\n        fullscreen: 'Vollbild',\n        'web-fullscreen': 'Browser Vollbild',\n        send: 'Senden',\n        screenshot: 'Screenshot',\n        airplay: 'AirPlay',\n        'show-subs': 'Zeige Untertitel',\n        chromecast: 'ChromeCast',\n        subtitle: 'Untertitel',\n        off: 'Schließung',\n        'hide-subs': 'Verstecke Untertitel',\n        volume: 'Lautstärke',\n        live: 'Live',\n        'video-info': 'Video Info',\n    },\n    \"ja\":{\n        'danmaku-loading':'コメントを読み込んでいます',\n        top:'トップ',\n        bottom:'ボトム',\n        rolling:'スクロール',\n        'input-danmaku-enter':'コメントを入力し，Enter で送信',\n        'about-author':'作者について',\n        'dplayer-feedback':'DPlayer についての意見・要望',\n        'about-dplayer':'DPlayer について',\n        loop:'くり返し',\n        speed:'再生速度',\n        'opacity-danmaku':'コメントの透明度',\n        normal:'通常',\n        'please-input-danmaku':'コメントを入力してください!',\n        'set-danmaku-color':'コメントの色',\n        'set-danmaku-type':'コメントの種類',\n        'show-danmaku':'コメント表示',\n        'video-failed':'ビデオの読み込みに失敗',\n        'danmaku-failed':'コメントの読み込みに失敗',\n        'danmaku-send-failed':'コメントの送信に失敗',\n        'switching-quality':'%qへ切り替え中',\n        'switched-quality':'%qへ切り替え完了',\n        quality:'へ',\n        ff:'%s 秒早送り',\n        rew:'%s 秒早戻し',\n        'unlimited-danmaku':'コメント無制限',\n        'send-danmaku':'コメント送信',\n        setting:'設定',\n        fullscreen:'全画面表示',\n        'web-fullscreen':'ブラウザ全体表示',\n        send:'送信',\n        screenshot:'画面コピー',\n        airplay:'AirPlay',\n        'show-subs':'字幕表示',\n        chromecast: 'ChromeCast',\n        subtitle: '字幕',\n        off: 'OFF',\n        'hide-subs':'字幕非表示',\n        volume:'音量',\n        live:'ライブ',\n        'video-info':'動画情報'\n    },\n    'ru': {\n        'danmaku-loading': 'Загрузка комментариев',\n        top: 'Вверху',\n        bottom: 'Внизу',\n        rolling: 'Лента',\n        'input-danmaku-enter': 'Введите комментарий, нажмите Enter',\n        'about-author': 'Об авторе',\n        'dplayer-feedback': 'Обратная связь',\n        'about-dplayer': 'О плеере',\n        loop: 'Закольцевать',\n        speed: 'Скорость',\n        'opacity-danmaku': 'Прозрачность',\n        normal: 'Обычный',\n        'please-input-danmaku': 'Пожалуйста, введите комментарий!',\n        'set-danmaku-color': 'Цвет комментария',\n        'set-danmaku-type': 'Тип комментария',\n        'show-danmaku': 'Показать комментарии',\n        'video-failed': 'Ошибка загрузки видео',\n        'danmaku-failed': 'Ошибка загрузки комментариев',\n        'danmaku-send-failed': 'Ошибка отправки комментария',\n        'switching-quality': 'Переключение качества на: %q',\n        'switched-quality': 'Установлено качество: %q',\n        ff: 'Вперёд: %s с',\n        rew: 'Назад: %s с',\n        'unlimited-danmaku': 'Без ограничений',\n        'send-danmaku': 'Отправить комментарий',\n        setting: 'Настройки',\n        fullscreen: 'На весь экран',\n        'web-fullscreen': 'На весь Web экран',\n        send: 'Отправить',\n        screenshot: 'Скриншот',\n        airplay: 'AirPlay',\n        chromecast: 'ChromeCast',\n        subtitle: 'Комментарий',\n        off: 'Выкл',\n        'show-subs': 'Показать комментарии',\n        'hide-subs': 'Скрыть комментарии',\n        volume: 'Громкость',\n        live: 'Живые комментарии',\n        'video-info': 'Информация о видео',\n    },\n};\n\nexport { i18n };\n"
  },
  {
    "path": "src/js/icons.js",
    "content": "import play from '../assets/play.svg';\nimport pause from '../assets/pause.svg';\nimport volumeUp from '../assets/volume-up.svg';\nimport volumeDown from '../assets/volume-down.svg';\nimport volumeOff from '../assets/volume-off.svg';\nimport full from '../assets/full.svg';\nimport fullWeb from '../assets/full-web.svg';\nimport setting from '../assets/setting.svg';\nimport right from '../assets/right.svg';\nimport comment from '../assets/comment.svg';\nimport commentOff from '../assets/comment-off.svg';\nimport send from '../assets/send.svg';\nimport pallette from '../assets/pallette.svg';\nimport camera from '../assets/camera.svg';\nimport airplay from '../assets/airplay.svg';\nimport subtitle from '../assets/subtitle.svg';\nimport loading from '../assets/loading.svg';\nimport chromecast from '../assets/chromecast.svg';\n\nconst Icons = {\n    play: play,\n    pause: pause,\n    volumeUp: volumeUp,\n    volumeDown: volumeDown,\n    volumeOff: volumeOff,\n    full: full,\n    fullWeb: fullWeb,\n    setting: setting,\n    right: right,\n    comment: comment,\n    commentOff: commentOff,\n    send: send,\n    pallette: pallette,\n    camera: camera,\n    subtitle: subtitle,\n    loading: loading,\n    airplay: airplay,\n    chromecast: chromecast,\n};\n\nexport default Icons;\n"
  },
  {
    "path": "src/js/index.js",
    "content": "import '../css/index.less';\nimport DPlayer from './player';\n\n/* global DPLAYER_VERSION GIT_HASH */\nconsole.log(`${'\\n'} %c DPlayer v${DPLAYER_VERSION} ${GIT_HASH} %c https://dplayer.diygod.dev ${'\\n'}${'\\n'}`, 'color: #fadfa3; background: #030307; padding:5px 0;', 'background: #fadfa3; padding:5px 0;');\n\nexport default DPlayer;\n"
  },
  {
    "path": "src/js/info-panel.js",
    "content": "/* global DPLAYER_VERSION GIT_HASH */\n\nclass InfoPanel {\n    constructor(player) {\n        this.container = player.template.infoPanel;\n        this.template = player.template;\n        this.video = player.video;\n        this.player = player;\n\n        this.template.infoPanelClose.addEventListener('click', () => {\n            this.hide();\n        });\n    }\n\n    show() {\n        this.beginTime = Date.now();\n        this.update();\n        this.player.timer.enable('info');\n        this.player.timer.enable('fps');\n        this.container.classList.remove('dplayer-info-panel-hide');\n    }\n\n    hide() {\n        this.player.timer.disable('info');\n        this.player.timer.disable('fps');\n        this.container.classList.add('dplayer-info-panel-hide');\n    }\n\n    triggle() {\n        if (this.container.classList.contains('dplayer-info-panel-hide')) {\n            this.show();\n        } else {\n            this.hide();\n        }\n    }\n\n    update() {\n        this.template.infoVersion.innerHTML = `v${DPLAYER_VERSION} ${GIT_HASH}`;\n        this.template.infoType.innerHTML = this.player.type;\n        this.template.infoUrl.innerHTML = this.player.options.video.url;\n        this.template.infoResolution.innerHTML = `${this.player.video.videoWidth} x ${this.player.video.videoHeight}`;\n        this.template.infoDuration.innerHTML = this.player.video.duration;\n        if (this.player.options.danmaku) {\n            this.template.infoDanmakuId.innerHTML = this.player.options.danmaku.id;\n            this.template.infoDanmakuApi.innerHTML = this.player.options.danmaku.api;\n            this.template.infoDanmakuAmount.innerHTML = this.player.danmaku.dan.length;\n        }\n    }\n\n    fps(value) {\n        this.template.infoFPS.innerHTML = `${value.toFixed(1)}`;\n    }\n}\n\nexport default InfoPanel;\n"
  },
  {
    "path": "src/js/options.js",
    "content": "/* global DPLAYER_VERSION */\nimport defaultApiBackend from './api.js';\n\nexport default (options) => {\n    // default options\n    const defaultOption = {\n        container: options.element || document.getElementsByClassName('dplayer')[0],\n        live: false,\n        autoplay: false,\n        theme: '#b7daff',\n        loop: false,\n        lang: (navigator.language || navigator.browserLanguage).toLowerCase(),\n        screenshot: false,\n        airplay: true,\n        chromecast: false,\n        hotkey: true,\n        preload: 'metadata',\n        volume: 0.7,\n        playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2],\n        apiBackend: defaultApiBackend,\n        video: {},\n        contextmenu: [],\n        mutex: true,\n        pluginOptions: { hls: {}, flv: {}, dash: {}, webtorrent: {} },\n        preventClickToggle: false,\n    };\n    for (const defaultKey in defaultOption) {\n        if (defaultOption.hasOwnProperty(defaultKey) && !options.hasOwnProperty(defaultKey)) {\n            options[defaultKey] = defaultOption[defaultKey];\n        }\n    }\n    if (options.video) {\n        !options.video.type && (options.video.type = 'auto');\n    }\n    if (typeof options.danmaku === 'object' && options.danmaku) {\n        !options.danmaku.user && (options.danmaku.user = 'DIYgod');\n    }\n    if (options.subtitle) {\n        !options.subtitle.type && (options.subtitle.type = 'webvtt');\n        !options.subtitle.fontSize && (options.subtitle.fontSize = '20px');\n        !options.subtitle.bottom && (options.subtitle.bottom = '40px');\n        !options.subtitle.color && (options.subtitle.color = '#fff');\n    }\n\n    if (options.video.quality) {\n        options.video.url = options.video.quality[options.video.defaultQuality].url;\n    }\n\n    if (options.lang) {\n        options.lang = options.lang.toLowerCase();\n    }\n\n    options.contextmenu = options.contextmenu.concat([\n        {\n            key: 'video-info',\n            click: (player) => {\n                player.infoPanel.triggle();\n            },\n        },\n        {\n            key: 'about-author',\n            link: 'https://diygod.me',\n        },\n        {\n            text: `DPlayer v${DPLAYER_VERSION}`,\n            link: 'https://github.com/MoePlayer/DPlayer',\n        },\n    ]);\n\n    return options;\n};\n"
  },
  {
    "path": "src/js/player.js",
    "content": "import Promise from 'promise-polyfill';\n\nimport utils from './utils';\nimport handleOption from './options';\nimport { i18n } from './i18n';\nimport Template from './template';\nimport Icons from './icons';\nimport Danmaku from './danmaku';\nimport Events from './events';\nimport FullScreen from './fullscreen';\nimport User from './user';\nimport Subtitle from './subtitle';\nimport Subtitles from './subtitles';\nimport Bar from './bar';\nimport Timer from './timer';\nimport Bezel from './bezel';\nimport Controller from './controller';\nimport Setting from './setting';\nimport Comment from './comment';\nimport HotKey from './hotkey';\nimport ContextMenu from './contextmenu';\nimport InfoPanel from './info-panel';\nimport tplVideo from '../template/video.art';\n\nlet index = 0;\nconst instances = [];\n\nclass DPlayer {\n    /**\n     * DPlayer constructor function\n     *\n     * @param {Object} options - See README\n     * @constructor\n     */\n    constructor(options) {\n        this.options = handleOption({ preload: options.video.type === 'webtorrent' ? 'none' : 'metadata', ...options });\n\n        if (this.options.video.quality) {\n            this.qualityIndex = this.options.video.defaultQuality;\n            this.quality = this.options.video.quality[this.options.video.defaultQuality];\n        }\n        this.tran = new i18n(this.options.lang).tran;\n        this.events = new Events();\n        this.user = new User(this);\n        this.container = this.options.container;\n        this.noticeList = {};\n\n        this.container.classList.add('dplayer');\n        if (!this.options.danmaku) {\n            this.container.classList.add('dplayer-no-danmaku');\n        }\n        if (this.options.live) {\n            this.container.classList.add('dplayer-live');\n        } else {\n            this.container.classList.remove('dplayer-live');\n        }\n        if (utils.isMobile) {\n            this.container.classList.add('dplayer-mobile');\n        }\n        this.arrow = this.container.offsetWidth <= 500;\n        if (this.arrow) {\n            this.container.classList.add('dplayer-arrow');\n        }\n\n        // multi subtitles defaultSubtitle add index, off option\n        if (this.options.subtitle) {\n            if (Array.isArray(this.options.subtitle.url)) {\n                const offSubtitle = {\n                    subtitle: '',\n                    lang: 'off',\n                };\n                this.options.subtitle.url.push(offSubtitle);\n                if (this.options.subtitle.defaultSubtitle) {\n                    if (typeof this.options.subtitle.defaultSubtitle === 'string') {\n                        // defaultSubtitle is string, match in lang then name.\n                        this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) =>\n                            /* if (sub.lang === this.options.subtitle.defaultSubtitle) {\n                            return true;\n                        } else if (sub.name === this.options.subtitle.defaultSubtitle) {\n                            return true;\n                        } else {\n                            return false;\n                        } */\n                            sub.lang === this.options.subtitle.defaultSubtitle ? true : sub.name === this.options.subtitle.defaultSubtitle ? true : false\n                        );\n                    } else if (typeof this.options.subtitle.defaultSubtitle === 'number') {\n                        // defaultSubtitle is int, directly use for index\n                        this.options.subtitle.index = this.options.subtitle.defaultSubtitle;\n                    }\n                }\n                // defaultSubtitle not match or not exist or index bound(when defaultSubtitle is int), try browser language.\n                if (this.options.subtitle.index === -1 || !this.options.subtitle.index || this.options.subtitle.index > this.options.subtitle.url.length - 1) {\n                    this.options.subtitle.index = this.options.subtitle.url.findIndex((sub) => sub.lang === this.options.lang);\n                }\n                // browser language not match, default off title\n                if (this.options.subtitle.index === -1) {\n                    this.options.subtitle.index = this.options.subtitle.url.length - 1;\n                }\n            }\n        }\n\n        this.template = new Template({\n            container: this.container,\n            options: this.options,\n            index: index,\n            tran: this.tran,\n        });\n\n        this.video = this.template.video;\n\n        this.bar = new Bar(this.template);\n\n        this.bezel = new Bezel(this.template.bezel);\n\n        this.fullScreen = new FullScreen(this);\n\n        this.controller = new Controller(this);\n\n        if (this.options.danmaku) {\n            this.danmaku = new Danmaku({\n                player: this,\n                container: this.template.danmaku,\n                opacity: this.user.get('opacity'),\n                callback: () => {\n                    setTimeout(() => {\n                        this.template.danmakuLoading.style.display = 'none';\n\n                        // autoplay\n                        if (this.options.autoplay) {\n                            this.play();\n                        }\n                    }, 0);\n                },\n                error: (msg) => {\n                    this.notice(msg);\n                },\n                apiBackend: this.options.apiBackend,\n                borderColor: this.options.theme,\n                height: this.arrow ? 24 : 30,\n                time: () => this.video.currentTime,\n                unlimited: this.user.get('unlimited'),\n                api: {\n                    id: this.options.danmaku.id,\n                    address: this.options.danmaku.api,\n                    token: this.options.danmaku.token,\n                    maximum: this.options.danmaku.maximum,\n                    addition: this.options.danmaku.addition,\n                    user: this.options.danmaku.user,\n                    speedRate: this.options.danmaku.speedRate,\n                },\n                events: this.events,\n                tran: (msg) => this.tran(msg),\n            });\n\n            this.comment = new Comment(this);\n        }\n\n        this.setting = new Setting(this);\n        this.plugins = {};\n        this.docClickFun = () => {\n            this.focus = false;\n        };\n        this.containerClickFun = () => {\n            this.focus = true;\n        };\n        document.addEventListener('click', this.docClickFun, true);\n        this.container.addEventListener('click', this.containerClickFun, true);\n\n        this.paused = true;\n\n        this.timer = new Timer(this);\n\n        this.hotkey = new HotKey(this);\n\n        this.contextmenu = new ContextMenu(this);\n\n        this.initVideo(this.video, (this.quality && this.quality.type) || this.options.video.type);\n\n        this.infoPanel = new InfoPanel(this);\n\n        if (!this.danmaku && this.options.autoplay) {\n            this.play();\n        }\n\n        this.moveBar = false;\n\n        index++;\n        instances.push(this);\n    }\n\n    /**\n     * Seek video\n     */\n    seek(time) {\n        time = Math.max(time, 0);\n        if (this.video.duration) {\n            time = Math.min(time, this.video.duration);\n        }\n        if (this.video.currentTime < time) {\n            this.notice(`${this.tran('ff').replace('%s', (time - this.video.currentTime).toFixed(0))}`);\n        } else if (this.video.currentTime > time) {\n            this.notice(`${this.tran('rew').replace('%s', (this.video.currentTime - time).toFixed(0))}`);\n        }\n\n        this.video.currentTime = time;\n\n        if (this.danmaku) {\n            this.danmaku.seek();\n        }\n\n        this.bar.set('played', time / this.video.duration, 'width');\n        this.template.ptime.innerHTML = utils.secondToTime(time);\n    }\n\n    /**\n     * Play video\n     */\n    play(fromNative) {\n        this.paused = false;\n        if (this.video.paused && !utils.isMobile) {\n            this.bezel.switch(Icons.play);\n        }\n\n        this.template.playButton.innerHTML = Icons.pause;\n        this.template.mobilePlayButton.innerHTML = Icons.pause;\n\n        if (!fromNative) {\n            const playedPromise = Promise.resolve(this.video.play());\n            playedPromise\n                .catch(() => {\n                    this.pause();\n                })\n                .then(() => {});\n        }\n        this.timer.enable('loading');\n        this.container.classList.remove('dplayer-paused');\n        this.container.classList.add('dplayer-playing');\n        if (this.danmaku) {\n            this.danmaku.play();\n        }\n        if (this.options.mutex) {\n            for (let i = 0; i < instances.length; i++) {\n                if (this !== instances[i]) {\n                    instances[i].pause();\n                }\n            }\n        }\n    }\n\n    /**\n     * Pause video\n     */\n    pause(fromNative) {\n        this.paused = true;\n        this.container.classList.remove('dplayer-loading');\n\n        if (!this.video.paused && !utils.isMobile) {\n            this.bezel.switch(Icons.pause);\n        }\n\n        this.template.playButton.innerHTML = Icons.play;\n        this.template.mobilePlayButton.innerHTML = Icons.play;\n        if (!fromNative) {\n            this.video.pause();\n        }\n        this.timer.disable('loading');\n        this.container.classList.remove('dplayer-playing');\n        this.container.classList.add('dplayer-paused');\n        if (this.danmaku) {\n            this.danmaku.pause();\n        }\n    }\n\n    switchVolumeIcon() {\n        if (this.volume() >= 0.95) {\n            this.template.volumeIcon.innerHTML = Icons.volumeUp;\n        } else if (this.volume() > 0) {\n            this.template.volumeIcon.innerHTML = Icons.volumeDown;\n        } else {\n            this.template.volumeIcon.innerHTML = Icons.volumeOff;\n        }\n    }\n\n    /**\n     * Set volume\n     */\n    volume(percentage, nostorage, nonotice) {\n        percentage = parseFloat(percentage);\n        if (!isNaN(percentage)) {\n            percentage = Math.max(percentage, 0);\n            percentage = Math.min(percentage, 1);\n            this.bar.set('volume', percentage, 'width');\n            const formatPercentage = `${(percentage * 100).toFixed(0)}%`;\n            this.template.volumeBarWrapWrap.dataset.balloon = formatPercentage;\n            if (!nostorage) {\n                this.user.set('volume', percentage);\n            }\n            if (!nonotice) {\n                this.notice(`${this.tran('volume')} ${(percentage * 100).toFixed(0)}%`, undefined, undefined, 'volume');\n            }\n\n            this.video.volume = percentage;\n            if (this.video.muted) {\n                this.video.muted = false;\n            }\n            this.switchVolumeIcon();\n        }\n\n        return this.video.volume;\n    }\n\n    /**\n     * Toggle between play and pause\n     */\n    toggle() {\n        if (this.video.paused) {\n            this.play();\n        } else {\n            this.pause();\n        }\n    }\n\n    /**\n     * attach event\n     */\n    on(name, callback) {\n        this.events.on(name, callback);\n    }\n\n    /**\n     * Switch to a new video\n     *\n     * @param {Object} video - new video info\n     * @param {Object} danmaku - new danmaku info\n     */\n    switchVideo(video, danmakuAPI) {\n        this.pause();\n        this.video.poster = video.pic ? video.pic : '';\n        this.video.src = video.url;\n        this.initMSE(this.video, video.type || 'auto');\n        if (danmakuAPI) {\n            this.template.danmakuLoading.style.display = 'block';\n            this.bar.set('played', 0, 'width');\n            this.bar.set('loaded', 0, 'width');\n            this.template.ptime.innerHTML = '00:00';\n            this.template.danmaku.innerHTML = '';\n            if (this.danmaku) {\n                this.danmaku.reload({\n                    id: danmakuAPI.id,\n                    address: danmakuAPI.api,\n                    token: danmakuAPI.token,\n                    maximum: danmakuAPI.maximum,\n                    addition: danmakuAPI.addition,\n                    user: danmakuAPI.user,\n                });\n            }\n        }\n    }\n\n    initMSE(video, type) {\n        this.type = type;\n        if (this.options.video.customType && this.options.video.customType[type]) {\n            if (Object.prototype.toString.call(this.options.video.customType[type]) === '[object Function]') {\n                this.options.video.customType[type](this.video, this);\n            } else {\n                console.error(`Illegal customType: ${type}`);\n            }\n        } else {\n            if (this.type === 'auto') {\n                if (/m3u8(#|\\?|$)/i.exec(video.src)) {\n                    this.type = 'hls';\n                } else if (/.flv(#|\\?|$)/i.exec(video.src)) {\n                    this.type = 'flv';\n                } else if (/.mpd(#|\\?|$)/i.exec(video.src)) {\n                    this.type = 'dash';\n                } else {\n                    this.type = 'normal';\n                }\n            }\n\n            if (this.type === 'hls' && (video.canPlayType('application/x-mpegURL') || video.canPlayType('application/vnd.apple.mpegURL'))) {\n                this.type = 'normal';\n            }\n\n            const src = this.quality.url; // 真实的视频 url, 用于切换清晰度时销毁实例\n            switch (this.type) {\n                // https://github.com/video-dev/hls.js\n                case 'hls':\n                    if (window.Hls) {\n                        if (window.Hls.isSupported()) {\n                            const options = this.options.pluginOptions.hls;\n                            const hls = new window.Hls(options);\n                            this.plugins.hls = hls;\n                            hls.loadSource(video.src);\n                            hls.attachMedia(video);\n                            this.events.on('destroy', () => {\n                                hls.destroy();\n                                delete this.plugins.hls;\n                            });\n                            // 切换清晰度时销毁实例\n                            this.events.on('quality_end', () => {\n                                if (src !== this.quality.url) {\n                                    hls.destroy();\n                                }\n                            });\n                        } else {\n                            this.notice('Error: Hls is not supported.');\n                        }\n                    } else {\n                        this.notice(\"Error: Can't find Hls.\");\n                    }\n                    break;\n\n                // https://github.com/Bilibili/flv.js\n                case 'flv':\n                    if (window.flvjs) {\n                        if (window.flvjs.isSupported()) {\n                            const flvPlayer = window.flvjs.createPlayer(\n                                Object.assign(this.options.pluginOptions.flv.mediaDataSource || {}, {\n                                    type: 'flv',\n                                    url: video.src,\n                                }),\n                                this.options.pluginOptions.flv.config\n                            );\n                            this.plugins.flvjs = flvPlayer;\n                            flvPlayer.attachMediaElement(video);\n                            flvPlayer.load();\n                            this.events.on('destroy', () => {\n                                flvPlayer.unload();\n                                flvPlayer.detachMediaElement();\n                                flvPlayer.destroy();\n                                delete this.plugins.flvjs;\n                            });\n                            // 切换清晰度时销毁实例\n                            this.events.on('quality_end', () => {\n                                if (src !== this.quality.url) {\n                                    flvPlayer.unload();\n                                    flvPlayer.detachMediaElement();\n                                    flvPlayer.destroy();\n                                }\n                            });\n                        } else {\n                            this.notice('Error: flvjs is not supported.');\n                        }\n                    } else {\n                        this.notice(\"Error: Can't find flvjs.\");\n                    }\n                    break;\n\n                // https://github.com/Dash-Industry-Forum/dash.js\n                case 'dash':\n                    if (window.dashjs) {\n                        const dashjsPlayer = window.dashjs.MediaPlayer().create();\n                        dashjsPlayer.initialize(video, video.src, false, 0);\n                        const options = this.options.pluginOptions.dash;\n                        dashjsPlayer.updateSettings(options);\n                        this.plugins.dash = dashjsPlayer;\n                        this.events.on('destroy', () => {\n                            window.dashjs.MediaPlayer().reset();\n                            delete this.plugins.dash;\n                        });\n                        // 切换清晰度时销毁实例\n                        this.events.on('quality_end', () => {\n                            if (src !== this.quality.url) {\n                                window.dashjs.MediaPlayer().reset();\n                            }\n                        });\n                    } else {\n                        this.notice(\"Error: Can't find dashjs.\");\n                    }\n                    break;\n\n                // https://github.com/webtorrent/webtorrent\n                case 'webtorrent':\n                    if (window.WebTorrent) {\n                        if (window.WebTorrent.WEBRTC_SUPPORT) {\n                            this.container.classList.add('dplayer-loading');\n                            const options = this.options.pluginOptions.webtorrent;\n                            const client = new window.WebTorrent(options);\n                            this.plugins.webtorrent = client;\n                            const torrentId = video.src;\n                            video.src = '';\n                            video.preload = 'metadata';\n                            video.addEventListener('durationchange', () => this.container.classList.remove('dplayer-loading'), { once: true });\n                            client.add(torrentId, (torrent) => {\n                                const file = torrent.files.find((file) => file.name.endsWith('.mp4'));\n                                file.renderTo(this.video, {\n                                    autoplay: this.options.autoplay,\n                                    controls: false,\n                                });\n                            });\n                            this.events.on('destroy', () => {\n                                client.remove(torrentId);\n                                client.destroy();\n                                delete this.plugins.webtorrent;\n                            });\n                            // 切换清晰度时销毁实例\n                            this.events.on('quality_end', () => {\n                                if (src !== this.quality.url) {\n                                    client.remove(torrentId);\n                                    client.destroy();\n                                }\n                            });\n                        } else {\n                            this.notice('Error: Webtorrent is not supported.');\n                        }\n                    } else {\n                        this.notice(\"Error: Can't find Webtorrent.\");\n                    }\n                    break;\n            }\n        }\n    }\n\n    initVideo(video, type) {\n        this.initMSE(video, type);\n\n        /**\n         * video events\n         */\n        // show video time: the metadata has loaded or changed\n        this.on('durationchange', () => {\n            // compatibility: Android browsers will output 1 or Infinity at first\n            if (video.duration !== 1 && video.duration !== Infinity) {\n                this.template.dtime.innerHTML = utils.secondToTime(video.duration);\n            }\n        });\n\n        // show video loaded bar: to inform interested parties of progress downloading the media\n        this.on('progress', () => {\n            const percentage = video.buffered.length ? video.buffered.end(video.buffered.length - 1) / video.duration : 0;\n            this.bar.set('loaded', percentage, 'width');\n        });\n\n        // video download error: an error occurs\n        this.on('error', () => {\n            if (!this.video.error) {\n                // Not a video load error, may be poster load failed, see #307\n                return;\n            }\n            this.tran && this.notice && this.type !== 'webtorrent' && this.notice(this.tran('video-failed'));\n        });\n\n        // video end\n        this.on('ended', () => {\n            this.bar.set('played', 1, 'width');\n            if (!this.setting.loop) {\n                this.pause();\n            } else {\n                this.seek(0);\n                this.play();\n            }\n            if (this.danmaku) {\n                this.danmaku.danIndex = 0;\n            }\n        });\n\n        this.on('play', () => {\n            if (this.paused) {\n                this.play(true);\n            }\n        });\n\n        this.on('pause', () => {\n            if (!this.paused) {\n                this.pause(true);\n            }\n        });\n\n        this.on('timeupdate', () => {\n            if (!this.moveBar) {\n                this.bar.set('played', this.video.currentTime / this.video.duration, 'width');\n            }\n            const currentTime = utils.secondToTime(this.video.currentTime);\n            if (this.template.ptime.innerHTML !== currentTime) {\n                this.template.ptime.innerHTML = currentTime;\n            }\n        });\n\n        for (let i = 0; i < this.events.videoEvents.length; i++) {\n            video.addEventListener(this.events.videoEvents[i], (e) => {\n                this.events.trigger(this.events.videoEvents[i], e);\n            });\n        }\n\n        this.volume(this.user.get('volume'), true, true);\n\n        if (this.options.subtitle) {\n            // init old single subtitle function(sub show and style)\n            this.subtitle = new Subtitle(this.template.subtitle, this.video, this.options.subtitle, this.events);\n            // init multi subtitles function(sub update)\n            if (Array.isArray(this.options.subtitle.url)) {\n                this.subtitles = new Subtitles(this);\n            }\n            if (!this.user.get('subtitle')) {\n                this.subtitle.hide();\n            }\n        }\n    }\n\n    switchQuality(index) {\n        index = typeof index === 'string' ? parseInt(index) : index;\n        if (this.qualityIndex === index || this.switchingQuality) {\n            return;\n        } else {\n            this.prevIndex = this.qualityIndex;\n            this.qualityIndex = index;\n        }\n        this.switchingQuality = true;\n        this.quality = this.options.video.quality[index];\n        this.template.qualityButton.innerHTML = this.quality.name;\n\n        const paused = this.video.paused;\n        this.video.pause();\n        const videoHTML = tplVideo({\n            current: false,\n            pic: null,\n            screenshot: this.options.screenshot,\n            preload: 'auto',\n            url: this.quality.url,\n            subtitle: this.options.subtitle,\n        });\n        const videoEle = new DOMParser().parseFromString(videoHTML, 'text/html').body.firstChild;\n        this.template.videoWrap.insertBefore(videoEle, this.template.videoWrap.getElementsByTagName('div')[0]);\n        this.prevVideo = this.video;\n        this.video = videoEle;\n        this.initVideo(this.video, this.quality.type || this.options.video.type);\n        this.seek(this.prevVideo.currentTime);\n        this.notice(`${this.tran('switching-quality').replace('%q', this.quality.name)}`, -1, undefined, 'switch-quality');\n        this.events.trigger('quality_start', this.quality);\n\n        this.on('canplay', () => {\n            if (this.prevVideo) {\n                if (this.video.currentTime !== this.prevVideo.currentTime) {\n                    this.seek(this.prevVideo.currentTime);\n                    return;\n                }\n                this.template.videoWrap.removeChild(this.prevVideo);\n                this.video.classList.add('dplayer-video-current');\n                if (!paused) {\n                    this.video.play();\n                }\n                this.prevVideo = null;\n                this.notice(`${this.tran('switched-quality').replace('%q', this.quality.name)}`, undefined, undefined, 'switch-quality');\n                this.switchingQuality = false;\n\n                this.events.trigger('quality_end');\n            }\n        });\n\n        this.on('error', () => {\n            if (!this.video.error) {\n                return;\n            }\n            if (this.prevVideo) {\n                this.template.videoWrap.removeChild(this.video);\n                this.video = this.prevVideo;\n                if (!paused) {\n                    this.video.play();\n                }\n                this.qualityIndex = this.prevIndex;\n                this.quality = this.options.video.quality[this.qualityIndex];\n                this.noticeTime = setTimeout(() => {\n                    this.template.notice.style.opacity = 0;\n                    this.events.trigger('notice_hide');\n                }, 3000);\n                this.prevVideo = null;\n                this.switchingQuality = false;\n            }\n        });\n    }\n\n    notice(text, time = 2000, opacity = 0.8, id) {\n        let oldNoticeEle;\n        if (id) {\n            oldNoticeEle = document.getElementById(`dplayer-notice-${id}`);\n            if (oldNoticeEle) {\n                oldNoticeEle.innerHTML = text;\n            }\n            if (this.noticeList[id]) {\n                clearTimeout(this.noticeList[id]);\n                this.noticeList[id] = null;\n            }\n        }\n        if (!oldNoticeEle) {\n            const notice = Template.NewNotice(text, opacity, id);\n            this.template.noticeList.appendChild(notice);\n            oldNoticeEle = notice;\n        }\n\n        this.events.trigger('notice_show', oldNoticeEle);\n\n        if (time > 0) {\n            this.noticeList[id] = setTimeout(\n                (function (e, dp) {\n                    return () => {\n                        e.addEventListener('animationend', () => {\n                            dp.template.noticeList.removeChild(e);\n                        });\n                        e.classList.add('remove-notice');\n                        dp.events.trigger('notice_hide');\n                        dp.noticeList[id] = null;\n                    };\n                })(oldNoticeEle, this),\n                time\n            );\n        }\n    }\n\n    resize() {\n        if (this.danmaku) {\n            this.danmaku.resize();\n        }\n        if (this.controller.thumbnails) {\n            this.controller.thumbnails.resize(160, (this.video.videoHeight / this.video.videoWidth) * 160, this.template.barWrap.offsetWidth);\n        }\n        this.events.trigger('resize');\n    }\n\n    speed(rate) {\n        this.video.playbackRate = rate;\n    }\n\n    destroy() {\n        instances.splice(instances.indexOf(this), 1);\n        this.pause();\n        document.removeEventListener('click', this.docClickFun, true);\n        this.container.removeEventListener('click', this.containerClickFun, true);\n        this.fullScreen.destroy();\n        this.hotkey.destroy();\n        this.contextmenu.destroy();\n        this.controller.destroy();\n        this.timer.destroy();\n        this.video.src = '';\n        this.container.innerHTML = '';\n        this.events.trigger('destroy');\n    }\n\n    static get version() {\n        /* global DPLAYER_VERSION */\n        return DPLAYER_VERSION;\n    }\n}\n\nexport default DPlayer;\n"
  },
  {
    "path": "src/js/setting.js",
    "content": "import utils from './utils';\n\nclass Setting {\n    constructor(player) {\n        this.player = player;\n\n        this.player.template.mask.addEventListener('click', () => {\n            this.hide();\n        });\n        this.player.template.settingButton.addEventListener('click', () => {\n            this.show();\n        });\n\n        // loop\n        this.loop = this.player.options.loop;\n        this.player.template.loopToggle.checked = this.loop;\n        this.player.template.loop.addEventListener('click', () => {\n            this.player.template.loopToggle.checked = !this.player.template.loopToggle.checked;\n            if (this.player.template.loopToggle.checked) {\n                this.loop = true;\n            } else {\n                this.loop = false;\n            }\n            this.hide();\n        });\n\n        // show danmaku\n        this.showDanmaku = this.player.user.get('danmaku');\n        if (!this.showDanmaku) {\n            this.player.danmaku && this.player.danmaku.hide();\n        }\n        this.player.template.showDanmakuToggle.checked = this.showDanmaku;\n        this.player.template.showDanmaku.addEventListener('click', () => {\n            this.player.template.showDanmakuToggle.checked = !this.player.template.showDanmakuToggle.checked;\n            if (this.player.template.showDanmakuToggle.checked) {\n                this.showDanmaku = true;\n                this.player.danmaku.show();\n            } else {\n                this.showDanmaku = false;\n                this.player.danmaku.hide();\n            }\n            this.player.user.set('danmaku', this.showDanmaku ? 1 : 0);\n            this.hide();\n        });\n\n        // unlimit danmaku\n        this.unlimitDanmaku = this.player.user.get('unlimited');\n        this.player.template.unlimitDanmakuToggle.checked = this.unlimitDanmaku;\n        this.player.template.unlimitDanmaku.addEventListener('click', () => {\n            this.player.template.unlimitDanmakuToggle.checked = !this.player.template.unlimitDanmakuToggle.checked;\n            if (this.player.template.unlimitDanmakuToggle.checked) {\n                this.unlimitDanmaku = true;\n                this.player.danmaku.unlimit(true);\n            } else {\n                this.unlimitDanmaku = false;\n                this.player.danmaku.unlimit(false);\n            }\n            this.player.user.set('unlimited', this.unlimitDanmaku ? 1 : 0);\n            this.hide();\n        });\n\n        // speed\n        this.player.template.speed.addEventListener('click', () => {\n            this.player.template.settingBox.classList.add('dplayer-setting-box-narrow');\n            this.player.template.settingBox.classList.add('dplayer-setting-box-speed');\n        });\n        for (let i = 0; i < this.player.template.speedItem.length; i++) {\n            this.player.template.speedItem[i].addEventListener('click', () => {\n                this.player.speed(this.player.template.speedItem[i].dataset.speed);\n                this.hide();\n            });\n        }\n\n        // danmaku opacity\n        if (this.player.danmaku) {\n            const dWidth = 130;\n            this.player.on('danmaku_opacity', (percentage) => {\n                this.player.bar.set('danmaku', percentage, 'width');\n                this.player.user.set('opacity', percentage);\n            });\n            this.player.danmaku.opacity(this.player.user.get('opacity'));\n\n            const danmakuMove = (event) => {\n                const e = event || window.event;\n                let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.danmakuOpacityBarWrap)) / dWidth;\n                percentage = Math.max(percentage, 0);\n                percentage = Math.min(percentage, 1);\n                this.player.danmaku.opacity(percentage);\n            };\n            const danmakuUp = () => {\n                document.removeEventListener(utils.nameMap.dragEnd, danmakuUp);\n                document.removeEventListener(utils.nameMap.dragMove, danmakuMove);\n                this.player.template.danmakuOpacityBox.classList.remove('dplayer-setting-danmaku-active');\n            };\n\n            this.player.template.danmakuOpacityBarWrapWrap.addEventListener('click', (event) => {\n                const e = event || window.event;\n                let percentage = ((e.clientX || e.changedTouches[0].clientX) - utils.getBoundingClientRectViewLeft(this.player.template.danmakuOpacityBarWrap)) / dWidth;\n                percentage = Math.max(percentage, 0);\n                percentage = Math.min(percentage, 1);\n                this.player.danmaku.opacity(percentage);\n            });\n            this.player.template.danmakuOpacityBarWrapWrap.addEventListener(utils.nameMap.dragStart, () => {\n                document.addEventListener(utils.nameMap.dragMove, danmakuMove);\n                document.addEventListener(utils.nameMap.dragEnd, danmakuUp);\n                this.player.template.danmakuOpacityBox.classList.add('dplayer-setting-danmaku-active');\n            });\n        }\n    }\n\n    hide() {\n        this.player.template.settingBox.classList.remove('dplayer-setting-box-open');\n        this.player.template.mask.classList.remove('dplayer-mask-show');\n        setTimeout(() => {\n            this.player.template.settingBox.classList.remove('dplayer-setting-box-narrow');\n            this.player.template.settingBox.classList.remove('dplayer-setting-box-speed');\n        }, 300);\n\n        this.player.controller.disableAutoHide = false;\n    }\n\n    show() {\n        this.player.template.settingBox.classList.add('dplayer-setting-box-open');\n        this.player.template.mask.classList.add('dplayer-mask-show');\n\n        this.player.controller.disableAutoHide = true;\n    }\n}\n\nexport default Setting;\n"
  },
  {
    "path": "src/js/subtitle.js",
    "content": "class Subtitle {\n    constructor(container, video, options, events) {\n        this.container = container;\n        this.video = video;\n        this.options = options;\n        this.events = events;\n\n        this.init();\n    }\n\n    init() {\n        this.container.style.fontSize = this.options.fontSize;\n        this.container.style.bottom = this.options.bottom;\n        this.container.style.color = this.options.color;\n\n        if (this.video.textTracks && this.video.textTracks[0]) {\n            const track = this.video.textTracks[0];\n\n            track.oncuechange = () => {\n                const cue = track.activeCues[track.activeCues.length - 1];\n                this.container.innerHTML = '';\n                if (cue) {\n                    const template = document.createElement('div');\n                    template.appendChild(cue.getCueAsHTML());\n                    const trackHtml = template.innerHTML\n                        .split(/\\r?\\n/)\n                        .map((item) => `<p>${item}</p>`)\n                        .join('');\n                    this.container.innerHTML = trackHtml;\n                }\n                this.events.trigger('subtitle_change');\n            };\n        }\n    }\n\n    show() {\n        this.container.classList.remove('dplayer-subtitle-hide');\n        this.events.trigger('subtitle_show');\n    }\n\n    hide() {\n        this.container.classList.add('dplayer-subtitle-hide');\n        this.events.trigger('subtitle_hide');\n    }\n\n    toggle() {\n        if (this.container.classList.contains('dplayer-subtitle-hide')) {\n            this.show();\n        } else {\n            this.hide();\n        }\n    }\n}\n\nexport default Subtitle;\n"
  },
  {
    "path": "src/js/subtitles.js",
    "content": "class Subtitles {\n    constructor(player) {\n        this.player = player;\n\n        this.player.template.mask.addEventListener('click', () => {\n            this.hide();\n        });\n        this.player.template.subtitlesButton.addEventListener('click', () => {\n            this.adaptiveHeight();\n            this.show();\n        });\n\n        const lastItemIndex = this.player.template.subtitlesItem.length - 1;\n        for (let i = 0; i < lastItemIndex; i++) {\n            this.player.template.subtitlesItem[i].addEventListener('click', () => {\n                this.hide();\n                if (this.player.options.subtitle.index !== i) {\n                    // clear subtitle show for new subtitle don't have now duration time. If don't, will display last subtitle.\n                    this.player.template.subtitle.innerHTML = `<p></p>`;\n                    // update video track src\n                    this.player.template.subtrack.src = this.player.template.subtitlesItem[i].dataset.subtitle;\n                    // update options current subindex for reload (such as changeQuality)\n                    this.player.options.subtitle.index = i;\n                    if (this.player.template.subtitle.classList.contains('dplayer-subtitle-hide')) {\n                        this.subContainerShow();\n                    }\n                }\n            });\n        }\n        this.player.template.subtitlesItem[lastItemIndex].addEventListener('click', () => {\n            this.hide();\n            if (this.player.options.subtitle.index !== lastItemIndex) {\n                // clear subtitle show for new subtitle don't have now duration time. If don't, will display last subtitle.\n                this.player.template.subtitle.innerHTML = `<p></p>`;\n                // update video track src\n                this.player.template.subtrack.src = '';\n                // update options current subindex for reload (such as changeQuality)\n                this.player.options.subtitle.index = lastItemIndex;\n                this.subContainerHide();\n            }\n        });\n    }\n\n    subContainerShow() {\n        this.player.template.subtitle.classList.remove('dplayer-subtitle-hide');\n        this.player.events.trigger('subtitle_show');\n    }\n\n    subContainerHide() {\n        this.player.template.subtitle.classList.add('dplayer-subtitle-hide');\n        this.player.events.trigger('subtitle_hide');\n    }\n\n    hide() {\n        this.player.template.subtitlesBox.classList.remove('dplayer-subtitles-box-open');\n        this.player.template.mask.classList.remove('dplayer-mask-show');\n        this.player.controller.disableAutoHide = false;\n    }\n\n    show() {\n        this.player.template.subtitlesBox.classList.add('dplayer-subtitles-box-open');\n        this.player.template.mask.classList.add('dplayer-mask-show');\n        this.player.controller.disableAutoHide = true;\n    }\n\n    adaptiveHeight() {\n        const curBoxHeight = this.player.template.subtitlesItem.length * 30 + 14;\n        const stdMaxHeight = this.player.template.videoWrap.offsetHeight * 0.8;\n        if (curBoxHeight >= stdMaxHeight - 50) {\n            this.player.template.subtitlesBox.style.bottom = '8px';\n            this.player.template.subtitlesBox.style['max-height'] = stdMaxHeight - 8 + 'px';\n        } else {\n            this.player.template.subtitlesBox.style.bottom = '50px';\n            this.player.template.subtitlesBox.style['max-height'] = stdMaxHeight - 50 + 'px';\n        }\n    }\n}\n\nexport default Subtitles;\n"
  },
  {
    "path": "src/js/template.js",
    "content": "import Icons from './icons';\nimport tplPlayer from '../template/player.art';\nimport utils from './utils';\n\nclass Template {\n    constructor(options) {\n        this.container = options.container;\n        this.options = options.options;\n        this.index = options.index;\n        this.tran = options.tran;\n        this.init();\n    }\n\n    init() {\n        this.container.innerHTML = tplPlayer({\n            options: this.options,\n            index: this.index,\n            tran: this.tran,\n            icons: Icons,\n            mobile: utils.isMobile,\n            video: {\n                current: true,\n                pic: this.options.video.pic,\n                screenshot: this.options.screenshot,\n                airplay: utils.isSafari && !utils.isChrome ? this.options.airplay : false,\n                chromecast: this.options.chromecast,\n                preload: this.options.preload,\n                url: this.options.video.url,\n                subtitle: this.options.subtitle,\n            },\n        });\n\n        this.volumeBar = this.container.querySelector('.dplayer-volume-bar-inner');\n        this.volumeBarWrap = this.container.querySelector('.dplayer-volume-bar');\n        this.volumeBarWrapWrap = this.container.querySelector('.dplayer-volume-bar-wrap');\n        this.volumeButton = this.container.querySelector('.dplayer-volume');\n        this.volumeButtonIcon = this.container.querySelector('.dplayer-volume-icon');\n        this.volumeIcon = this.container.querySelector('.dplayer-volume-icon .dplayer-icon-content');\n        this.playedBar = this.container.querySelector('.dplayer-played');\n        this.loadedBar = this.container.querySelector('.dplayer-loaded');\n        this.playedBarWrap = this.container.querySelector('.dplayer-bar-wrap');\n        this.playedBarTime = this.container.querySelector('.dplayer-bar-time');\n        this.danmaku = this.container.querySelector('.dplayer-danmaku');\n        this.danmakuLoading = this.container.querySelector('.dplayer-danloading');\n        this.video = this.container.querySelector('.dplayer-video-current');\n        this.bezel = this.container.querySelector('.dplayer-bezel-icon');\n        this.playButton = this.container.querySelector('.dplayer-play-icon');\n        this.mobilePlayButton = this.container.querySelector('.dplayer-mobile-play');\n        this.videoWrap = this.container.querySelector('.dplayer-video-wrap');\n        this.controllerMask = this.container.querySelector('.dplayer-controller-mask');\n        this.ptime = this.container.querySelector('.dplayer-ptime');\n        this.settingButton = this.container.querySelector('.dplayer-setting-icon');\n        this.settingBox = this.container.querySelector('.dplayer-setting-box');\n        this.mask = this.container.querySelector('.dplayer-mask');\n        this.loop = this.container.querySelector('.dplayer-setting-loop');\n        this.loopToggle = this.container.querySelector('.dplayer-setting-loop .dplayer-toggle-setting-input');\n        this.showDanmaku = this.container.querySelector('.dplayer-setting-showdan');\n        this.showDanmakuToggle = this.container.querySelector('.dplayer-showdan-setting-input');\n        this.unlimitDanmaku = this.container.querySelector('.dplayer-setting-danunlimit');\n        this.unlimitDanmakuToggle = this.container.querySelector('.dplayer-danunlimit-setting-input');\n        this.speed = this.container.querySelector('.dplayer-setting-speed');\n        this.speedItem = this.container.querySelectorAll('.dplayer-setting-speed-item');\n        this.danmakuOpacityBar = this.container.querySelector('.dplayer-danmaku-bar-inner');\n        this.danmakuOpacityBarWrap = this.container.querySelector('.dplayer-danmaku-bar');\n        this.danmakuOpacityBarWrapWrap = this.container.querySelector('.dplayer-danmaku-bar-wrap');\n        this.danmakuOpacityBox = this.container.querySelector('.dplayer-setting-danmaku');\n        this.dtime = this.container.querySelector('.dplayer-dtime');\n        this.controller = this.container.querySelector('.dplayer-controller');\n        this.commentInput = this.container.querySelector('.dplayer-comment-input');\n        this.commentButton = this.container.querySelector('.dplayer-comment-icon');\n        this.commentSettingBox = this.container.querySelector('.dplayer-comment-setting-box');\n        this.commentSettingButton = this.container.querySelector('.dplayer-comment-setting-icon');\n        this.commentSettingFill = this.container.querySelector('.dplayer-comment-setting-icon path');\n        this.commentSendButton = this.container.querySelector('.dplayer-send-icon');\n        this.commentSendFill = this.container.querySelector('.dplayer-send-icon path');\n        this.commentColorSettingBox = this.container.querySelector('.dplayer-comment-setting-color');\n        this.browserFullButton = this.container.querySelector('.dplayer-full-icon');\n        this.webFullButton = this.container.querySelector('.dplayer-full-in-icon');\n        this.menu = this.container.querySelector('.dplayer-menu');\n        this.menuItem = this.container.querySelectorAll('.dplayer-menu-item');\n        this.qualityList = this.container.querySelector('.dplayer-quality-list');\n        this.camareButton = this.container.querySelector('.dplayer-camera-icon');\n        this.airplayButton = this.container.querySelector('.dplayer-airplay-icon');\n        this.chromecastButton = this.container.querySelector('.dplayer-chromecast-icon');\n        this.subtitleButton = this.container.querySelector('.dplayer-subtitle-icon');\n        this.subtitleButtonInner = this.container.querySelector('.dplayer-subtitle-icon .dplayer-icon-content');\n        this.subtitlesButton = this.container.querySelector('.dplayer-subtitles-icon');\n        this.subtitlesBox = this.container.querySelector('.dplayer-subtitles-box');\n        this.subtitlesItem = this.container.querySelectorAll('.dplayer-subtitles-item');\n        this.subtitle = this.container.querySelector('.dplayer-subtitle');\n        this.subtrack = this.container.querySelector('.dplayer-subtrack');\n        this.qualityButton = this.container.querySelector('.dplayer-quality-icon');\n        this.barPreview = this.container.querySelector('.dplayer-bar-preview');\n        this.barWrap = this.container.querySelector('.dplayer-bar-wrap');\n        this.noticeList = this.container.querySelector('.dplayer-notice-list');\n        this.infoPanel = this.container.querySelector('.dplayer-info-panel');\n        this.infoPanelClose = this.container.querySelector('.dplayer-info-panel-close');\n        this.infoVersion = this.container.querySelector('.dplayer-info-panel-item-version .dplayer-info-panel-item-data');\n        this.infoFPS = this.container.querySelector('.dplayer-info-panel-item-fps .dplayer-info-panel-item-data');\n        this.infoType = this.container.querySelector('.dplayer-info-panel-item-type .dplayer-info-panel-item-data');\n        this.infoUrl = this.container.querySelector('.dplayer-info-panel-item-url .dplayer-info-panel-item-data');\n        this.infoResolution = this.container.querySelector('.dplayer-info-panel-item-resolution .dplayer-info-panel-item-data');\n        this.infoDuration = this.container.querySelector('.dplayer-info-panel-item-duration .dplayer-info-panel-item-data');\n        this.infoDanmakuId = this.container.querySelector('.dplayer-info-panel-item-danmaku-id .dplayer-info-panel-item-data');\n        this.infoDanmakuApi = this.container.querySelector('.dplayer-info-panel-item-danmaku-api .dplayer-info-panel-item-data');\n        this.infoDanmakuAmount = this.container.querySelector('.dplayer-info-panel-item-danmaku-amount .dplayer-info-panel-item-data');\n    }\n\n    static NewNotice(text, opacity, id) {\n        const notice = document.createElement('div');\n        notice.classList.add('dplayer-notice');\n        notice.style.opacity = opacity;\n        notice.innerText = text;\n        if (id) {\n            notice.id = `dplayer-notice-${id}`;\n        }\n        return notice;\n    }\n}\n\nexport default Template;\n"
  },
  {
    "path": "src/js/thumbnails.js",
    "content": "class Thumbnails {\n    constructor(options) {\n        this.container = options.container;\n        this.barWidth = options.barWidth;\n        this.container.style.backgroundImage = `url('${options.url}')`;\n        this.events = options.events;\n    }\n\n    resize(width, height, barWrapWidth) {\n        this.container.style.width = `${width}px`;\n        this.container.style.height = `${height}px`;\n        this.container.style.top = `${-height + 2}px`;\n        this.barWidth = barWrapWidth;\n    }\n\n    show() {\n        this.container.style.display = 'block';\n        this.events && this.events.trigger('thumbnails_show');\n    }\n\n    move(position) {\n        this.container.style.backgroundPosition = `-${(Math.ceil((position / this.barWidth) * 100) - 1) * 160}px 0`;\n        this.container.style.left = `${Math.min(Math.max(position - this.container.offsetWidth / 2, -10), this.barWidth - 150)}px`;\n    }\n\n    hide() {\n        this.container.style.display = 'none';\n\n        this.events && this.events.trigger('thumbnails_hide');\n    }\n}\n\nexport default Thumbnails;\n"
  },
  {
    "path": "src/js/timer.js",
    "content": "class Timer {\n    constructor(player) {\n        this.player = player;\n\n        window.requestAnimationFrame = (() =>\n            window.requestAnimationFrame ||\n            window.webkitRequestAnimationFrame ||\n            window.mozRequestAnimationFrame ||\n            window.oRequestAnimationFrame ||\n            window.msRequestAnimationFrame ||\n            function (callback) {\n                window.setTimeout(callback, 1000 / 60);\n            })();\n\n        this.types = ['loading', 'info', 'fps'];\n\n        this.init();\n    }\n\n    init() {\n        this.types.map((item) => {\n            if (item !== 'fps') {\n                this[`init${item}Checker`]();\n            }\n            return item;\n        });\n    }\n\n    initloadingChecker() {\n        let lastPlayPos = 0;\n        let currentPlayPos = 0;\n        let bufferingDetected = false;\n        this.loadingChecker = setInterval(() => {\n            if (this.enableloadingChecker) {\n                // whether the video is buffering\n                currentPlayPos = this.player.video.currentTime;\n                if (!bufferingDetected && currentPlayPos === lastPlayPos && !this.player.video.paused) {\n                    this.player.container.classList.add('dplayer-loading');\n                    bufferingDetected = true;\n                }\n                if (bufferingDetected && currentPlayPos > lastPlayPos && !this.player.video.paused) {\n                    this.player.container.classList.remove('dplayer-loading');\n                    bufferingDetected = false;\n                }\n                lastPlayPos = currentPlayPos;\n            }\n        }, 100);\n    }\n\n    initfpsChecker() {\n        window.requestAnimationFrame(() => {\n            if (this.enablefpsChecker) {\n                this.initfpsChecker();\n                if (!this.fpsStart) {\n                    this.fpsStart = new Date();\n                    this.fpsIndex = 0;\n                } else {\n                    this.fpsIndex++;\n                    const fpsCurrent = new Date();\n                    if (fpsCurrent - this.fpsStart > 1000) {\n                        this.player.infoPanel.fps((this.fpsIndex / (fpsCurrent - this.fpsStart)) * 1000);\n                        this.fpsStart = new Date();\n                        this.fpsIndex = 0;\n                    }\n                }\n            } else {\n                this.fpsStart = 0;\n                this.fpsIndex = 0;\n            }\n        });\n    }\n\n    initinfoChecker() {\n        this.infoChecker = setInterval(() => {\n            if (this.enableinfoChecker) {\n                this.player.infoPanel.update();\n            }\n        }, 1000);\n    }\n\n    enable(type) {\n        this[`enable${type}Checker`] = true;\n\n        if (type === 'fps') {\n            this.initfpsChecker();\n        }\n    }\n\n    disable(type) {\n        this[`enable${type}Checker`] = false;\n    }\n\n    destroy() {\n        this.types.map((item) => {\n            this[`enable${item}Checker`] = false;\n            this[`${item}Checker`] && clearInterval(this[`${item}Checker`]);\n            return item;\n        });\n    }\n}\n\nexport default Timer;\n"
  },
  {
    "path": "src/js/user.js",
    "content": "import utils from './utils';\n\nclass User {\n    constructor(player) {\n        this.storageName = {\n            opacity: 'dplayer-danmaku-opacity',\n            volume: 'dplayer-volume',\n            unlimited: 'dplayer-danmaku-unlimited',\n            danmaku: 'dplayer-danmaku-show',\n            subtitle: 'dplayer-subtitle-show',\n        };\n        this.default = {\n            opacity: 0.7,\n            volume: player.options.hasOwnProperty('volume') ? player.options.volume : 0.7,\n            unlimited: (player.options.danmaku && player.options.danmaku.unlimited ? 1 : 0) || 0,\n            danmaku: 1,\n            subtitle: 1,\n        };\n        this.data = {};\n\n        this.init();\n    }\n\n    init() {\n        for (const item in this.storageName) {\n            const name = this.storageName[item];\n            this.data[item] = parseFloat(utils.storage.get(name) || this.default[item]);\n        }\n    }\n\n    get(key) {\n        return this.data[key];\n    }\n\n    set(key, value) {\n        this.data[key] = value;\n        utils.storage.set(this.storageName[key], value);\n    }\n}\n\nexport default User;\n"
  },
  {
    "path": "src/js/utils.js",
    "content": "const isMobile = /mobile|android|iphone|ipod|phone|ipad/i.test(window.navigator.userAgent);\n\nconst utils = {\n    /**\n     * Parse second to time string\n     *\n     * @param {Number} second\n     * @return {String} 00:00 or 00:00:00\n     */\n    secondToTime: (second) => {\n        second = second || 0;\n        if (second === 0 || second === Infinity || second.toString() === 'NaN') {\n            return '00:00';\n        }\n        const add0 = (num) => (num < 10 ? '0' + num : '' + num);\n        const hour = Math.floor(second / 3600);\n        const min = Math.floor((second - hour * 3600) / 60);\n        const sec = Math.floor(second - hour * 3600 - min * 60);\n        return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':');\n    },\n\n    /**\n     * control play progress\n     */\n    // get element's view position\n    getElementViewLeft: (element) => {\n        let actualLeft = element.offsetLeft;\n        let current = element.offsetParent;\n        const elementScrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;\n        if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) {\n            while (current !== null) {\n                actualLeft += current.offsetLeft;\n                current = current.offsetParent;\n            }\n        } else {\n            while (current !== null && current !== element) {\n                actualLeft += current.offsetLeft;\n                current = current.offsetParent;\n            }\n        }\n        return actualLeft - elementScrollLeft;\n    },\n\n    /**\n    * optimize control play progress\n\n    * optimize get element's view position,for float dialog video player\n    * getBoundingClientRect 在 IE8 及以下返回的值缺失 width、height 值\n    * getBoundingClientRect 在 Firefox 11 及以下返回的值会把 transform 的值也包含进去\n    * getBoundingClientRect 在 Opera 10.5 及以下返回的值缺失 width、height 值\n    */\n    getBoundingClientRectViewLeft(element) {\n        const scrollTop = window.scrollY || window.pageYOffset || document.body.scrollTop + ((document.documentElement && document.documentElement.scrollTop) || 0);\n\n        if (element.getBoundingClientRect) {\n            if (typeof this.getBoundingClientRectViewLeft.offset !== 'number') {\n                let temp = document.createElement('div');\n                temp.style.cssText = 'position:absolute;top:0;left:0;';\n                document.body.appendChild(temp);\n                this.getBoundingClientRectViewLeft.offset = -temp.getBoundingClientRect().top - scrollTop;\n                document.body.removeChild(temp);\n                temp = null;\n            }\n            const rect = element.getBoundingClientRect();\n            const offset = this.getBoundingClientRectViewLeft.offset;\n\n            return rect.left + offset;\n        } else {\n            // not support getBoundingClientRect\n            return this.getElementViewLeft(element);\n        }\n    },\n\n    getScrollPosition() {\n        return {\n            left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,\n            top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0,\n        };\n    },\n\n    setScrollPosition({ left = 0, top = 0 }) {\n        if (this.isFirefox) {\n            document.documentElement.scrollLeft = left;\n            document.documentElement.scrollTop = top;\n        } else {\n            window.scrollTo(left, top);\n        }\n    },\n\n    isMobile: isMobile,\n\n    isFirefox: /firefox/i.test(window.navigator.userAgent),\n\n    isChrome: /chrome/i.test(window.navigator.userAgent),\n\n    isSafari: /safari/i.test(window.navigator.userAgent),\n\n    storage: {\n        set: (key, value) => {\n            localStorage.setItem(key, value);\n        },\n\n        get: (key) => localStorage.getItem(key),\n    },\n\n    nameMap: {\n        dragStart: isMobile ? 'touchstart' : 'mousedown',\n        dragMove: isMobile ? 'touchmove' : 'mousemove',\n        dragEnd: isMobile ? 'touchend' : 'mouseup',\n    },\n\n    color2Number: (color) => {\n        if (color[0] === '#') {\n            color = color.substr(1);\n        }\n        if (color.length === 3) {\n            color = `${color[0]}${color[0]}${color[1]}${color[1]}${color[2]}${color[2]}`;\n        }\n        return (parseInt(color, 16) + 0x000000) & 0xffffff;\n    },\n\n    number2Color: (number) => '#' + ('00000' + number.toString(16)).slice(-6),\n\n    number2Type: (number) => {\n        switch (number) {\n            case 0:\n                return 'right';\n            case 1:\n                return 'top';\n            case 2:\n                return 'bottom';\n            default:\n                return 'right';\n        }\n    },\n};\n\nexport default utils;\n"
  },
  {
    "path": "src/template/player.art",
    "content": "<div class=\"dplayer-mask\"></div>\n<div class=\"dplayer-video-wrap\">\n    {{ include './video.art' video }}\n    {{ if options.logo }}\n    <div class=\"dplayer-logo\">\n        <img src=\"{{ options.logo }}\">\n    </div>\n    {{ /if }}\n    <div class=\"dplayer-danmaku\"{{ if options.danmaku && options.danmaku.bottom }} style=\"margin-bottom:{{ options.danmaku.bottom }}\"{{ /if }}>\n        <div class=\"dplayer-danmaku-item dplayer-danmaku-item--demo\"></div>\n    </div>\n    <div class=\"dplayer-subtitle\"></div>\n    <div class=\"dplayer-bezel\">\n        <span class=\"dplayer-bezel-icon\"></span>\n        {{ if options.danmaku }}\n        <span class=\"dplayer-danloading\">{{ tran('danmaku-loading') }}</span>\n        {{ /if }}\n        <span class=\"diplayer-loading-icon\">{{@ icons.loading }}</span>\n    </div>\n</div>\n<div class=\"dplayer-controller-mask\"></div>\n<div class=\"dplayer-controller\">\n    <div class=\"dplayer-icons dplayer-comment-box\">\n        <button class=\"dplayer-icon dplayer-comment-setting-icon\" data-balloon=\"{{ tran('setting') }}\" data-balloon-pos=\"up\">\n            <span class=\"dplayer-icon-content\">{{@ icons.pallette }}</span>\n        </button>\n        <div class=\"dplayer-comment-setting-box\">\n            <div class=\"dplayer-comment-setting-color\">\n                <div class=\"dplayer-comment-setting-title\">{{ tran('set-danmaku-color') }}</div>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#fff\" checked>\n                    <span style=\"background: #fff;\"></span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#e54256\">\n                    <span style=\"background: #e54256\"></span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#ffe133\">\n                    <span style=\"background: #ffe133\"></span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#64DD17\">\n                    <span style=\"background: #64DD17\"></span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#39ccff\">\n                    <span style=\"background: #39ccff\"></span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-color-{{ index }}\" value=\"#D500F9\">\n                    <span style=\"background: #D500F9\"></span>\n                </label>\n            </div>\n            <div class=\"dplayer-comment-setting-type\">\n                <div class=\"dplayer-comment-setting-title\">{{ tran('set-danmaku-type') }}</div>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-type-{{ index }}\" value=\"1\">\n                    <span>{{ tran('top') }}</span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-type-{{ index }}\" value=\"0\" checked>\n                    <span>{{ tran('rolling') }}</span>\n                </label>\n                <label>\n                    <input type=\"radio\" name=\"dplayer-danmaku-type-{{ index }}\" value=\"2\">\n                    <span>{{ tran('bottom') }}</span>\n                </label>\n            </div>\n        </div>\n        <input class=\"dplayer-comment-input\" type=\"text\" placeholder=\"{{ tran('input-danmaku-enter') }}\" maxlength=\"30\">\n        <button class=\"dplayer-icon dplayer-send-icon\" data-balloon=\"{{ tran('send') }}\" data-balloon-pos=\"up\">\n            <span class=\"dplayer-icon-content\">{{@ icons.send }}</span>\n        </button>\n    </div>\n    <div class=\"dplayer-icons dplayer-icons-left\">\n        <button class=\"dplayer-icon dplayer-play-icon\">\n            <span class=\"dplayer-icon-content\">{{@ icons.play }}</span>\n        </button>\n        <div class=\"dplayer-volume\">\n            <button class=\"dplayer-icon dplayer-volume-icon\">\n                <span class=\"dplayer-icon-content\">{{@ icons.volumeDown }}</span>\n            </button>\n            <div class=\"dplayer-volume-bar-wrap\" data-balloon-pos=\"up\">\n                <div class=\"dplayer-volume-bar\">\n                    <div class=\"dplayer-volume-bar-inner\" style=\"background: {{ options.theme }};\">\n                        <span class=\"dplayer-thumb\" style=\"background: {{ options.theme }}\"></span>\n                    </div>\n                </div>\n            </div>\n        </div>\n        <span class=\"dplayer-time\">\n            <span class=\"dplayer-ptime\">0:00</span> /\n            <span class=\"dplayer-dtime\">0:00</span>\n        </span>\n        {{ if options.live }}\n        <span class=\"dplayer-live-badge\"><span class=\"dplayer-live-dot\" style=\"background: {{ options.theme }};\"></span>{{ tran('live') }}</span>\n        {{ /if }}\n    </div>\n    <div class=\"dplayer-icons dplayer-icons-right\">\n        {{ if options.video.quality }}\n        <div class=\"dplayer-quality\">\n            <button class=\"dplayer-icon dplayer-quality-icon\">{{ options.video.quality[options.video.defaultQuality].name }}</button>\n            <div class=\"dplayer-quality-mask\">\n                <div class=\"dplayer-quality-list\">\n                {{ each options.video.quality }}\n                    <div class=\"dplayer-quality-item\" data-index=\"{{ $index }}\">{{ $value.name }}</div>\n                {{ /each }}\n                </div>\n            </div>\n        </div>\n        {{ /if }}\n        {{ if options.screenshot }}\n        <div class=\"dplayer-icon dplayer-camera-icon\" data-balloon=\"{{ tran('screenshot') }}\" data-balloon-pos=\"up\">\n            <span class=\"dplayer-icon-content\">{{@ icons.camera }}</span>\n        </div>\n        {{ /if }}\n        {{ if options.airplay }}\n        <div class=\"dplayer-icon dplayer-airplay-icon\" data-balloon=\"{{ tran('airplay') }}\" data-balloon-pos=\"up\">\n            <span class=\"dplayer-icon-content\">{{@ icons.airplay }}</span>\n        </div>\n        {{ /if }}\n        {{ if options.chromecast }}\n        <div class=\"dplayer-icon dplayer-chromecast-icon\" data-balloon=\"{{ tran('chromecast') }}\" data-balloon-pos=\"up\">\n            <span class=\"dplayer-icon-content\">{{@ icons.chromecast }}</span>\n        </div>\n        {{ /if }}\n        <div class=\"dplayer-comment\">\n            <button class=\"dplayer-icon dplayer-comment-icon\" data-balloon=\"{{ tran('send-danmaku') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.comment }}</span>\n            </button>\n        </div>\n        {{ if options.subtitle }}\n        {{ if (typeof options.subtitle.url === 'string') }}\n        <div class=\"dplayer-subtitle-btn\">\n            <button class=\"dplayer-icon dplayer-subtitle-icon\" data-balloon=\"{{ tran('hide-subs') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.subtitle }}</span>\n            </button>\n        </div>\n        {{ else }}\n        <div class=\"dplayer-subtitles\">\n            <button class=\"dplayer-icon dplayer-subtitles-icon\" data-balloon=\"{{ tran('subtitle') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.subtitle }}</span>\n            </button>\n            <div class=\"dplayer-subtitles-box\">\n                <div class=\"dplayer-subtitles-panel\">\n                    {{ each options.subtitle.url }}\n                        <div class=\"dplayer-subtitles-item\" data-subtitle=\"{{ $value.url }}\">\n                            <!-- if lang, show tran(lang). if lang and name, show name + (tran(lang)). if name, show name. off option use lang for translation. -->\n                            <span class=\"dplayer-label\">{{ $value.lang ? $value.name ?  $value.name+' ('+tran($value.lang)+')' : tran($value.lang) : $value.name }}</span>\n                        </div>\n                    {{ /each }}\n                </div>\n            </div>\n        </div>\n        {{ /if }}\n        {{ /if }}\n        <div class=\"dplayer-setting\">\n            <button class=\"dplayer-icon dplayer-setting-icon\" data-balloon=\"{{ tran('setting') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.setting }}</span>\n            </button>\n            <div class=\"dplayer-setting-box\">\n                <div class=\"dplayer-setting-origin-panel\">\n                    <div class=\"dplayer-setting-item dplayer-setting-speed\">\n                        <span class=\"dplayer-label\">{{ tran('speed') }}</span>\n                        <div class=\"dplayer-toggle\">{{@ icons.right }}</div>\n                    </div>\n                    <div class=\"dplayer-setting-item dplayer-setting-loop\">\n                        <span class=\"dplayer-label\">{{ tran('loop') }}</span>\n                        <div class=\"dplayer-toggle\">\n                            <input class=\"dplayer-toggle-setting-input\" type=\"checkbox\" name=\"dplayer-toggle\">\n                            <label for=\"dplayer-toggle\"></label>\n                        </div>\n                    </div>\n                    <div class=\"dplayer-setting-item dplayer-setting-showdan\">\n                        <span class=\"dplayer-label\">{{ tran('show-danmaku') }}</span>\n                        <div class=\"dplayer-toggle\">\n                            <input class=\"dplayer-showdan-setting-input\" type=\"checkbox\" name=\"dplayer-toggle-dan\">\n                            <label for=\"dplayer-toggle-dan\"></label>\n                        </div>\n                    </div>\n                    <div class=\"dplayer-setting-item dplayer-setting-danunlimit\">\n                        <span class=\"dplayer-label\">{{ tran('unlimited-danmaku') }}</span>\n                        <div class=\"dplayer-toggle\">\n                            <input class=\"dplayer-danunlimit-setting-input\" type=\"checkbox\" name=\"dplayer-toggle-danunlimit\">\n                            <label for=\"dplayer-toggle-danunlimit\"></label>\n                        </div>\n                    </div>\n                    <div class=\"dplayer-setting-item dplayer-setting-danmaku\">\n                        <span class=\"dplayer-label\">{{ tran('opacity-danmaku') }}</span>\n                        <div class=\"dplayer-danmaku-bar-wrap\">\n                            <div class=\"dplayer-danmaku-bar\">\n                                <div class=\"dplayer-danmaku-bar-inner\">\n                                    <span class=\"dplayer-thumb\"></span>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n                <div class=\"dplayer-setting-speed-panel\">\n                    {{ each options.playbackSpeed }}\n                        <div class=\"dplayer-setting-speed-item\" data-speed=\"{{ $value }}\">\n                            <span class=\"dplayer-label\">{{ $value === 1 ? tran('normal') : $value }}</span>\n                        </div>\n                    {{ /each }}\n                </div>\n            </div>\n        </div>\n        <div class=\"dplayer-full\">\n            <button class=\"dplayer-icon dplayer-full-in-icon\" data-balloon=\"{{ tran('web-fullscreen') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.fullWeb }}</span>\n            </button>\n            <button class=\"dplayer-icon dplayer-full-icon\" data-balloon=\"{{ tran('fullscreen') }}\" data-balloon-pos=\"up\">\n                <span class=\"dplayer-icon-content\">{{@ icons.full }}</span>\n            </button>\n        </div>\n    </div>\n    <div class=\"dplayer-bar-wrap\">\n        <div class=\"dplayer-bar-time hidden\">00:00</div>\n        <div class=\"dplayer-bar-preview\"></div>\n        <div class=\"dplayer-bar\">\n            <div class=\"dplayer-loaded\" style=\"width: 0;\"></div>\n            <div class=\"dplayer-played\" style=\"width: 0; background: {{ options.theme }}\">\n                <span class=\"dplayer-thumb\" style=\"background: {{ options.theme }}\"></span>\n            </div>\n        </div>\n    </div>\n</div>\n<div class=\"dplayer-info-panel dplayer-info-panel-hide\">\n    <div class=\"dplayer-info-panel-close\">[x]</div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-version\">\n        <span class=\"dplayer-info-panel-item-title\">Player version</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-fps\">\n        <span class=\"dplayer-info-panel-item-title\">Player FPS</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-type\">\n        <span class=\"dplayer-info-panel-item-title\">Video type</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-url\">\n        <span class=\"dplayer-info-panel-item-title\">Video url</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-resolution\">\n        <span class=\"dplayer-info-panel-item-title\">Video resolution</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-duration\">\n        <span class=\"dplayer-info-panel-item-title\">Video duration</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    {{ if options.danmaku }}\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-danmaku-id\">\n        <span class=\"dplayer-info-panel-item-title\">Danmaku id</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-danmaku-api\">\n        <span class=\"dplayer-info-panel-item-title\">Danmaku api</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    <div class=\"dplayer-info-panel-item dplayer-info-panel-item-danmaku-amount\">\n        <span class=\"dplayer-info-panel-item-title\">Danmaku amount</span>\n        <span class=\"dplayer-info-panel-item-data\"></span>\n    </div>\n    {{ /if }}\n</div>\n<div class=\"dplayer-menu\">\n    {{ each options.contextmenu }}\n        <div class=\"dplayer-menu-item\">\n            <a{{ if $value.link }} target=\"_blank\"{{ /if }} href=\"{{ $value.link || 'javascript:void(0);' }}\">{{ if $value.key }} {{ tran($value.key) }}{{ /if }}{{ if $value.text }} {{$value.text}}{{ /if }}</a>\n        </div>\n    {{ /each }}\n</div>\n<div class=\"dplayer-notice-list\"></div>\n<button class=\"dplayer-mobile-play\">\n    {{@ icons.play }}\n</button>"
  },
  {
    "path": "src/template/video.art",
    "content": "{{ set enableSubtitle = subtitle && subtitle.type === 'webvtt' }}\n<video\n    class=\"dplayer-video {{ if current }}dplayer-video-current{{ /if }}\"\n    webkit-playsinline\n    {{ if airplay }} x-webkit-airplay=\"allow\" {{ /if }}\n    playsinline\n    {{ if pic }}poster=\"{{ pic }}\"{{ /if }}\n    {{ if screenshot || enableSubtitle }}crossorigin=\"anonymous\"{{ /if }}\n    {{ if preload }}preload=\"{{ preload }}\"{{ /if }}\n    {{ if airplay }}\n    nosrc\n    {{ else if url }}\n    src=\"{{ url }}\"\n    {{ /if }}\n    >\n    {{ if airplay }}\n    <source src=\"{{ url }}\">\n    {{ /if}}\n    {{ if enableSubtitle }}\n    <track class=\"dplayer-subtrack\" kind=\"metadata\" default src=\"{{ typeof subtitle.url === 'string' ? subtitle.url : subtitle.url[subtitle.index].url }}\"></track>\n    {{ /if }}\n</video>"
  },
  {
    "path": "tea.yaml",
    "content": "# https://tea.xyz/what-is-this-file\n---\nversion: 1.0.0\ncodeOwners:\n    - '0x185bfcef7b37010e2511309048a130f477f54fBf'\nquorum: 1\n"
  },
  {
    "path": "webpack/dev.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst { GitRevisionPlugin } = require('git-revision-webpack-plugin');\nconst gitRevisionPlugin = new GitRevisionPlugin();\n\nmodule.exports = {\n    mode: 'development',\n\n    devtool: 'cheap-module-source-map',\n\n    entry: {\n        DPlayer: './src/js/index.js',\n    },\n\n    output: {\n        path: path.resolve(__dirname, '..', 'dist'),\n        filename: '[name].js',\n        library: '[name]',\n        libraryTarget: 'umd',\n        libraryExport: 'default',\n        umdNamedDefine: true,\n        publicPath: '/',\n    },\n\n    resolve: {\n        modules: ['node_modules'],\n        extensions: ['.js', '.less'],\n        fallback: {\n            dgram: false,\n            fs: false,\n            net: false,\n            tls: false,\n        },\n    },\n\n    module: {\n        strictExportPresence: true,\n        rules: [\n            {\n                test: /\\.js$/,\n                use: [\n                    {\n                        loader: 'babel-loader',\n                        options: {\n                            cacheDirectory: true,\n                            presets: ['@babel/preset-env'],\n                        },\n                    },\n                ],\n            },\n            {\n                test: /\\.less$/,\n                use: [\n                    'style-loader',\n                    {\n                        loader: 'css-loader',\n                        options: {\n                            importLoaders: 1,\n                        },\n                    },\n                    {\n                        loader: 'postcss-loader',\n                        options: {\n                            postcssOptions: {\n                                plugins: ['postcss-preset-env'],\n                            },\n                        },\n                    },\n                    'less-loader',\n                ],\n            },\n            {\n                test: /\\.(png|jpg)$/,\n                loader: 'url-loader',\n                options: {\n                    limit: 40000,\n                },\n            },\n            {\n                test: /\\.svg$/,\n                loader: 'svg-inline-loader',\n            },\n            {\n                test: /\\.art$/,\n                loader: 'art-template-loader',\n            },\n        ],\n    },\n\n    devServer: {\n        static: {\n            directory: path.join(__dirname, '..', 'demo'),\n        },\n        compress: true,\n        open: true,\n    },\n\n    plugins: [\n        new webpack.DefinePlugin({\n            DPLAYER_VERSION: `\"${require('../package.json').version}\"`,\n            GIT_HASH: JSON.stringify(gitRevisionPlugin.version()),\n        }),\n    ],\n\n    performance: {\n        hints: false,\n    },\n};\n"
  },
  {
    "path": "webpack/prod.config.js",
    "content": "const path = require('path');\nconst webpack = require('webpack');\nconst { GitRevisionPlugin } = require('git-revision-webpack-plugin');\nconst gitRevisionPlugin = new GitRevisionPlugin();\n\nmodule.exports = {\n    mode: 'production',\n\n    bail: true,\n\n    devtool: 'source-map',\n\n    entry: {\n        DPlayer: './src/js/index.js',\n    },\n\n    output: {\n        path: path.resolve(__dirname, '..', 'dist'),\n        filename: '[name].min.js',\n        library: '[name]',\n        libraryTarget: 'umd',\n        libraryExport: 'default',\n        umdNamedDefine: true,\n        publicPath: '/',\n    },\n\n    resolve: {\n        modules: ['node_modules'],\n        extensions: ['.js', '.less'],\n        fallback: {\n            dgram: false,\n            fs: false,\n            net: false,\n            tls: false,\n        },\n    },\n\n    module: {\n        strictExportPresence: true,\n        rules: [\n            {\n                test: /\\.js$/,\n                use: [\n                    'template-string-optimize-loader',\n                    {\n                        loader: 'babel-loader',\n                        options: {\n                            cacheDirectory: true,\n                            presets: ['@babel/preset-env'],\n                        },\n                    },\n                ],\n            },\n            {\n                test: /\\.less$/,\n                use: [\n                    'style-loader',\n                    {\n                        loader: 'css-loader',\n                        options: {\n                            importLoaders: 1,\n                        },\n                    },\n                    {\n                        loader: 'postcss-loader',\n                        options: {\n                            postcssOptions: {\n                                plugins: ['postcss-preset-env'],\n                            },\n                        },\n                    },\n                    'less-loader',\n                ],\n            },\n            {\n                test: /\\.(png|jpg)$/,\n                loader: 'url-loader',\n                options: {\n                    limit: 40000,\n                },\n            },\n            {\n                test: /\\.svg$/,\n                loader: 'svg-inline-loader',\n            },\n            {\n                test: /\\.art$/,\n                loader: 'art-template-loader',\n            },\n        ],\n    },\n\n    plugins: [\n        new webpack.DefinePlugin({\n            DPLAYER_VERSION: `\"${require('../package.json').version}\"`,\n            GIT_HASH: JSON.stringify(gitRevisionPlugin.version()),\n        }),\n    ],\n};\n"
  }
]