修改包管理
No files matched your search
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Harvey Zack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
.artplayerPluginDanmu{display:flex}
|
||||
@@ -1,8 +0,0 @@
|
||||
/*!
|
||||
* artplayer-plugin-danmu.js v1.0.0
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer#readme
|
||||
* (c) 2017-2018 Harvey Zack
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(e["artplayer-plugin-danmu"]={})}(this,function(e){"use strict";var n=function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")},t=function e(){n(this,e),console.log(this)};window.ArtplayerPluginDanmu=t,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": ["@babel/plugin-external-helpers", "@babel/plugin-transform-runtime"]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# artplayer
|
||||
ArtPlayer is a modern HTML5 video player
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.artplayer-video-player .artplayer-video{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;width:100%;height:100%;background-color:#000;outline:0;cursor:pointer}
|
||||
.artplayer-video-player{position:relative;width:100%;height:100%;overflow:hidden;outline:0;font-family:Roboto,Arial,Helvetica,sans-serif;color:#eee;text-align:left;direction:ltr;font-size:14px;line-height:1.3;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0);touch-action:manipulation;-ms-high-contrast-adjust:none}.artplayer-video-player *{box-sizing:border-box}.artplayer-video-player .art-icon{display:inline-block;line-height:1.5}.artplayer-video-player .art-icon svg{fill:#fff}
|
||||
.artplayer-video-player .artplayer-subtitle{position:absolute;z-index:20;bottom:40px;width:100%;padding:0 20px;text-align:center;color:#fff;text-shadow:.5px .5px .5px rgba(0,0,0,.5);font-size:20px;pointer-events:none}.artplayer-video-player .artplayer-subtitle p{margin:5px 0}
|
||||
.artplayer-video-player .artplayer-layers{position:absolute;z-index:30;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.artplayer-video-player .artplayer-layers .art-layer{position:absolute;pointer-events:all}
|
||||
.artplayer-video-player .artplayer-mask{display:none;position:absolute;z-index:40;left:0;top:0;right:0;bottom:0;width:100%;height:100%;pointer-events:none}.artplayer-video-player .artplayer-mask .art-icon-playBig{position:absolute;right:30px;bottom:65px;opacity:.6}
|
||||
.artplayer-video-player .artplayer-loading{display:none;position:absolute;z-index:60;left:0;top:0;right:0;bottom:0;width:100%;height:100%;align-items:center;justify-content:center;pointer-events:none}
|
||||
.artplayer-video-player .artplayer-notice{display:none;font-size:14px;position:absolute;z-index:70;left:0;top:0;padding:10px;width:100%;pointer-events:none}.artplayer-video-player .artplayer-notice .artplayer-notice-inner{display:inline-block;padding:5px 10px;color:#fff;background:rgba(0,0,0,.6);border-radius:3px}
|
||||
.artplayer-video-player .artplayer-bottom{position:absolute;z-index:50;left:0;right:0;bottom:0;height:100px;padding:60px 10px 0;opacity:0;visibility:hidden;transition:all .2s ease-in-out;pointer-events:none;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) repeat-x bottom}.artplayer-video-player .artplayer-bottom .artplayer-progress{position:relative;pointer-events:all}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress{position:relative;height:5px;padding:1px 0;cursor:pointer}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner{position:relative;height:100%;background:hsla(0,0%,100%,.2)}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-loaded{position:absolute;z-index:10;left:0;top:0;right:0;bottom:0;height:100%;width:0;background:hsla(0,0%,100%,.4)}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-played{position:absolute;z-index:20;left:0;top:0;right:0;bottom:0;height:100%;width:0}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-highlight{position:absolute;z-index:30;left:0;top:0;right:0;bottom:0;height:0}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-highlight span{display:inline-block;position:absolute;left:0;top:0;width:7px;height:3px;background:#fff;border-radius:3px}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-indicator{display:none;position:absolute;z-index:40;top:-5px;left:-6.5px;width:13px;height:13px;border-radius:50%}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-indicator.show-indicator{display:block}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress .art-control-progress-inner .art-progress-tip{display:none;position:absolute;z-index:50;top:-25px;left:0;height:20px;padding:0 5px;line-height:20px;color:#fff;font-size:12px;text-align:center;background:rgba(0,0,0,.7);border-radius:3px;font-weight:700}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress:hover .art-progress-indicator,.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-progress:hover .art-progress-tip{display:block}.artplayer-video-player .artplayer-bottom .artplayer-progress .art-control-thumbnails{display:none;position:absolute;bottom:8px;left:0;pointer-events:none;background-color:rgba(0,0,0,.7)}.artplayer-video-player .artplayer-bottom .artplayer-controls{position:relative;pointer-events:all;display:flex;align-items:center;justify-content:space-between;height:36px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control{opacity:.9;float:left;font-size:12px;height:36px;line-height:36px;cursor:pointer}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control .art-icon{display:flex;align-items:center;justify-content:center;float:left;height:36px;width:36px;padding:7px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control:hover{opacity:1}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel{position:relative;float:left;width:0;height:100%;transition:margin .2s cubic-bezier(.4,0,1,1),width .2s cubic-bezier(.4,0,1,1);overflow:hidden}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel .art-volume-slider-handle{position:absolute;top:50%;left:0;width:12px;height:12px;border-radius:12px;margin-top:-6px;background:#fff}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel .art-volume-slider-handle:before{left:-54px;background:#fff}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel .art-volume-slider-handle:after{left:6px;background:hsla(0,0%,100%,.2)}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel .art-volume-slider-handle:after,.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel .art-volume-slider-handle:before{content:"";position:absolute;display:block;top:50%;height:3px;margin-top:-2px;width:60px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-volume .art-volume-panel.art-volume-panel-hover{width:60px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-time{padding:0 10px}.artplayer-video-playeLine truncated
|
||||
.artplayer-video-player .artplayer-contextmenu{display:none;position:absolute;z-index:110;left:0;top:0;min-width:200px;padding:5px 0;background:rgba(0,0,0,.8);border-radius:3px}.artplayer-video-player .artplayer-contextmenu .art-menu{cursor:pointer;font-size:13px;display:block;color:#fff;padding:7px 10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-shadow:0 0 2px rgba(0,0,0,.5);border-bottom:1px solid hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-contextmenu .art-menu a{color:#fff;text-decoration:none}.artplayer-video-player .artplayer-contextmenu .art-menu span{display:inline-block;padding:0 7px}.artplayer-video-player .artplayer-contextmenu .art-menu span.current,.artplayer-video-player .artplayer-contextmenu .art-menu span:hover{color:#00c9ff}.artplayer-video-player .artplayer-contextmenu .art-menu:hover{background-color:hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-contextmenu .art-menu:last-child{border-bottom:none}
|
||||
.artplayer-video-player .artplayer-info{display:none;position:absolute;left:10px;top:10px;z-index:90;width:350px;min-height:150px;padding:10px;color:#fff;font-size:12px;font-family:Noto Sans CJK SC DemiLight,Roboto,Segoe UI,Tahoma,Arial,Helvetica,sans-serif;-webkit-font-smoothing:antialiased;background:rgba(0,0,0,.7)}.artplayer-video-player .artplayer-info .art-info-item{display:flex;margin-bottom:5px}.artplayer-video-player .artplayer-info .art-info-item .art-info-title{width:100px;text-align:right}.artplayer-video-player .artplayer-info .art-info-item .art-info-content{flex:1;padding-left:5px}.artplayer-video-player .artplayer-info .artplayer-info-close{position:absolute;top:5px;right:5px;cursor:pointer}
|
||||
.artplayer-video-player .artplayer-setting{display:none;position:absolute;z-index:80;left:0;top:0;height:100%;width:100%;align-items:center;justify-content:center;pointer-events:none}.artplayer-video-player .artplayer-setting .artplayer-setting-inner{position:relative;width:350px;height:250px;font-size:12px;background:rgba(0,0,0,.8);border-radius:3px;pointer-events:all;overflow:hidden}.artplayer-video-player .artplayer-setting .artplayer-setting-inner .artplayer-setting-body{overflow-x:hidden;overflow-y:auto;width:365px;height:100%}.artplayer-video-player .artplayer-setting .artplayer-setting-inner .artplayer-setting-body .art-setting{border-bottom:1px solid hsla(0,0%,100%,.1);padding:12px 35px 12px 20px}.artplayer-video-player .artplayer-setting .artplayer-setting-inner .artplayer-setting-body .art-setting .art-setting-header{color:#fff;margin-bottom:10px}.artplayer-video-player .artplayer-setting .artplayer-setting-inner .artplayer-setting-body .art-setting .art-setting-body{color:hsla(0,0%,100%,.8)}.artplayer-video-player .artplayer-setting .artplayer-setting-inner .artplayer-setting-close{position:absolute;top:0;right:0;width:20px;height:20px;line-height:20px;text-align:center;color:hsla(0,0%,100%,.8);background:rgba(0,0,0,.5);font-size:20px;cursor:pointer}
|
||||
.artplayer-video-player.artplayer-hide-cursor *{cursor:none!important}
|
||||
.artplayer-video-player[data-aspect-ratio] video{box-sizing:content-box;-o-object-fit:fill;object-fit:fill}
|
||||
.artplayer-video-player.artplayer-web-fullscreen{position:fixed;z-index:9999;width:100%;height:100%;left:0;top:0;right:0;bottom:0}
|
||||
.artplayer-video-player .artplayer-pip-header{display:none;position:absolute;z-index:100;left:0;top:0;right:0;height:35px;line-height:35px;color:#fff;background:rgba(0,0,0,.5);align-items:center;justify-content:space-between;opacity:0;visibility:hidden;transition:all .2s ease-in-out}.artplayer-video-player .artplayer-pip-header .artplayer-pip-title{flex:1;padding:0 10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:move}.artplayer-video-player .artplayer-pip-header .artplayer-pip-close{width:35px;text-align:center;font-size:22px;cursor:pointer}.artplayer-video-player.is-dragging{opacity:.7}.artplayer-video-player.artplayer-pip{position:fixed;right:20px;bottom:20px;width:400px;height:225px;box-shadow:0 2px 5px 0 rgba(0,0,0,.16),0 3px 6px 0 rgba(0,0,0,.2)}.artplayer-video-player.artplayer-pip .artplayer-pip-header{display:flex}.artplayer-video-player.artplayer-pip.artplayer-hover .artplayer-pip-header{opacity:1;visibility:visible}.artplayer-video-player.artplayer-pip .artplayer-mask{align-items:center;justify-content:center}.artplayer-video-player.artplayer-pip .artplayer-mask .art-icon-playBig{position:static}.artplayer-video-player.artplayer-pip .artplayer-bottom,.artplayer-video-player.artplayer-pip .artplayer-contextmenu,.artplayer-video-player.artplayer-pip .artplayer-danmu,.artplayer-video-player.artplayer-pip .artplayer-info,.artplayer-video-player.artplayer-pip .artplayer-layers,.artplayer-video-player.artplayer-pip .artplayer-notice,.artplayer-video-player.artplayer-pip .artplayer-setting,.artplayer-video-player.artplayer-pip .artplayer-subtitle{display:none!important}
|
||||
button[data-balloon]{overflow:visible}[data-balloon]{position:relative;cursor:pointer}[data-balloon]:after{font-family:sans-serif!important;font-weight:400!important;font-style:normal!important;text-shadow:none!important;font-size:12px!important;background:rgba(17,17,17,.9);border-radius:4px;color:#fff;content:attr(data-balloon);padding:.5em 1em;white-space:nowrap}[data-balloon]:after,[data-balloon]:before{filter:alpha(opactiy=0);-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";-moz-opacity:0;-khtml-opacity:0;opacity:0;pointer-events:none;transition:all .18s ease-out .18s;position:absolute;z-index:10}[data-balloon]:before{background:no-repeat url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='12'%3E%3Cpath fill='rgba(17,17,17,0.9)' d='M2.658 0h32.004c-6 0-11.627 12.002-16.002 12.002C14.285 12.002 8.594 0 2.658 0z'/%3E%3C/svg%3E");background-size:100% auto;width:18px;height:6px;content:""}[data-balloon]:hover:after,[data-balloon]:hover:before,[data-balloon][data-balloon-visible]:after,[data-balloon][data-balloon-visible]:before{filter:alpha(opactiy=100);-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";-moz-opacity:1;-khtml-opacity:1;opacity:1;pointer-events:auto}[data-balloon].font-awesome:after{font-family:FontAwesome}[data-balloon][data-balloon-break]:after{white-space:pre}[data-balloon][data-balloon-blunt]:after,[data-balloon][data-balloon-blunt]:before{transition:none}[data-balloon][data-balloon-pos=up]:after{margin-bottom:11px}[data-balloon][data-balloon-pos=up]:after,[data-balloon][data-balloon-pos=up]:before{bottom:100%;left:50%;-webkit-transform:translate(-50%,10px);transform:translate(-50%,10px);-webkit-transform-origin:top;transform-origin:top}[data-balloon][data-balloon-pos=up]:before{margin-bottom:5px}[data-balloon][data-balloon-pos=up]:hover:after,[data-balloon][data-balloon-pos=up]:hover:before,[data-balloon][data-balloon-pos=up][data-balloon-visible]:after,[data-balloon][data-balloon-pos=up][data-balloon-visible]:before{-webkit-transform:translate(-50%);transform:translate(-50%)}[data-balloon][data-balloon-pos=up-left]:after{left:0;margin-bottom:11px}[data-balloon][data-balloon-pos=up-left]:after,[data-balloon][data-balloon-pos=up-left]:before{bottom:100%;-webkit-transform:translateY(10px);transform:translateY(10px);-webkit-transform-origin:top;transform-origin:top}[data-balloon][data-balloon-pos=up-left]:before{left:5px;margin-bottom:5px}[data-balloon][data-balloon-pos=up-left]:hover:after,[data-balloon][data-balloon-pos=up-left]:hover:before,[data-balloon][data-balloon-pos=up-left][data-balloon-visible]:after,[data-balloon][data-balloon-pos=up-left][data-balloon-visible]:before{-webkit-transform:translate(0);transform:translate(0)}[data-balloon][data-balloon-pos=up-right]:after{right:0;margin-bottom:11px}[data-balloon][data-balloon-pos=up-right]:after,[data-balloon][data-balloon-pos=up-right]:before{bottom:100%;-webkit-transform:translateY(10px);transform:translateY(10px);-webkit-transform-origin:top;transform-origin:top}[data-balloon][data-balloon-pos=up-right]:before{right:5px;margin-bottom:5px}[data-balloon][data-balloon-pos=up-right]:hover:after,[data-balloon][data-balloon-pos=up-right]:hover:before,[data-balloon][data-balloon-pos=up-right][data-balloon-visible]:after,[data-balloon][data-balloon-pos=up-right][data-balloon-visible]:before{-webkit-transform:translate(0);transform:translate(0)}[data-balloon][data-balloon-pos=down]:after{margin-top:11px}[data-balloon][data-balloon-pos=down]:after,[data-balloon][data-balloon-pos=down]:before{left:50%;top:100%;-webkit-transform:translate(-50%,-10px);transform:translate(-50%,-10px)}[data-balloon][data-balloon-pos=down]:before{background:no-repeat url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='12'%3E%3Cpath fill='rgba(17,17,17,0.9)' d='M33.342 12H1.338c6 0 11.627-12.002 16.002-12.002C21.715-.002 27.406 12 33.342 12z'/%3E%3C/svg%3E");background-size:100% auto;width:18px;height:6px;margin-top:5px}[data-balloon][data-balloon-pos=down]:hover:after,[data-balloon][data-balloon-pos=down]:hover:before,[data-balloon][data-balloon-pos=down][data-balloon-visible]:after,[data-balloon][data-balloon-pos=down][data-balloon-visible]:before{-webkit-transform:translate(-50%);transform:translate(-50%)}[data-balloon][data-balloon-pos=down-left]:after{left:0;margin-top:11px;top:100%;-webkit-transform:translateY(-10px);transform:translateY(-10px)}[data-balloon][data-balloon-pos=down-left]:before{background:no-repeat url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='12'%3E%3Cpath fill='rgba(17,17,17,0.9)' d='M33.342 12H1.338c6 0 11.627-12.002 16.002-12.002C21.715-.002 27.406 12 33.342 12z'/%3E%3C/svg%3E");background-size:100% auto;width:18px;height:6px;left:5px;margin-top:5px;top:100%;-webkit-transform:translateY(-10px);transform:translateY(-10px)}[data-balloon][data-balloon-pos=down-left]Line truncated
|
||||
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* artplayer.js v1.0.1
|
||||
* Github: https://github.com/zhw2590582/ArtPlayer#readme
|
||||
* (c) 2017-2018 Harvey Zack
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.artplayer={})}(this,function(t){"use strict";var Ot=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")};function i(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}var r=function(t,e,n){return e&&i(t.prototype,e),n&&i(t,n),t},o="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t,e){return t(e={exports:{}},e.exports),e.exports}var l=e(function(e){function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function i(t){return"function"==typeof Symbol&&"symbol"===n(Symbol.iterator)?e.exports=i=function(t){return n(t)}:e.exports=i=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":n(t)},i(t)}e.exports=i});var a=function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t};var s=function(t,e){return!e||"object"!==l(e)&&"function"!=typeof e?a(t):e},c=e(function(e){function n(t){return e.exports=n=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)},n(t)}e.exports=n}),u=e(function(n){function i(t,e){return n.exports=i=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},i(t,e)}n.exports=i});var h=function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&u(t,e)};var d=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t};var f=function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},i=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(i=i.concat(Object.getOwnPropertySymbols(n).filter(function(t){return Object.getOwnPropertyDescriptor(n,t).enumerable}))),i.forEach(function(t){d(e,t,n[t])})}return e};var n=function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}};var p=function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)};var v=function(){throw new TypeError("Invalid attempt to spread non-iterable instance")};var y=function(t){return n(t)||p(t)||v()};var m=function(t){return-1!==Function.toString.call(t).indexOf("[native code]")},g=e(function(i){function r(t,e,n){return!function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}()?i.exports=r=function(t,e,n){var i=[null];i.push.apply(i,e);var r=new(Function.bind.apply(t,i));return n&&u(r,n.prototype),r}:i.exports=r=Reflect.construct,r.apply(null,arguments)}i.exports=r}),b=function(t){function i(t,e){var n;return Ot(this,i),n=s(this,c(i).call(this,t)),"function"==typeof Error.captureStackTrace&&Error.captureStackTrace(a(a(n)),e||n.constructor),n.name="ArtPlayerError",n}return h(i,t),i}(e(function(e){function i(t){var n="function"==typeof Map?new Map:void 0;return e.exports=i=function(t){if(null===t||!m(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==n){if(n.has(t))return n.get(t);n.set(t,e)}function e(){return g(t,arguments,c(this).constructor)}return e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),u(e,t)},i(t)}e.exports=i})(Error));function w(t,e){var n="function"==typeof t;if(n?!n():!t)throw new b(e)}function jt(t,e,n){return Math.max(Math.min(t,Math.max(e,n)),Math.min(e,n))}function x(t){return t.includes("?")?x(t.split("?")[0]):t.includes("#")?x(t.split("#")[0]):t.trim().toLowerCase().split(".").pop()}function At(t,e){return e instanceof Element?t.appendChild(e):t.insertAdjacentHTML("beforeend",e),t.lastElementChild}function $(t,e,n){var i=Array.from(t.children);e.dataset.index=n;var r=i.find(function(t){return Number(t.dataset.index)>=Number(n)});return r?r.insertAdjacentElement("beforebegin",e):At(t,e),e}function Dt(t,e,n){return t.style[e]=n,t}function k(e,n){return Object.keys(n).forEach(function(t){Dt(e,t,n[t])}),e}function Ht(t){var e=Math.floor(t/3600),n=Math.floor((t-3600*e)/60),i=Math.floor(t-3600*e-60*n);return(0<e?[e,n,i]:[n,i]).map(function(t){return t<10?"0".concat(t):String(t)}).join(":")}function E(){for(var t=function(t){return null!==t&&"object"===l(t)},e={},n=arguments.length,Line truncated
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "artplayer",
|
||||
"version": "1.0.1",
|
||||
"description": "ArtPlayer is a modern HTML5 video player",
|
||||
"main": "dist/artplayer.js",
|
||||
"style": "dist/artplayer.css",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development rollup -c -m -w",
|
||||
"build": "cross-env NODE_ENV=production rollup -c",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"keywords": [
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"homepage": "https://github.com/zhw2590582/ArtPlayer#readme",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/plugin-external-helpers": "^7.0.0",
|
||||
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"autoprefixer": "^9.3.1",
|
||||
"browser-env": "^3.2.5",
|
||||
"cross-env": "^5.2.0",
|
||||
"cssnano": "^4.1.7",
|
||||
"node-sass": "^4.9.4",
|
||||
"rollup": "^0.67.0",
|
||||
"rollup-plugin-babel": "^4.0.3",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-eslint": "^5.0.0",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"rollup-plugin-postcss": "^1.6.2",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
"rollup-plugin-svgo": "^1.0.1",
|
||||
"rollup-plugin-uglify": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"balloon-css": "^0.5.0",
|
||||
"draggabilly": "^2.2.0",
|
||||
"option-validator": "^1.1.0",
|
||||
"screenfull": "^3.3.3",
|
||||
"tiny-emitter": "^2.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
const path = require('path');
|
||||
const babel = require('rollup-plugin-babel');
|
||||
const commonjs = require('rollup-plugin-commonjs');
|
||||
const nodeResolve = require('rollup-plugin-node-resolve');
|
||||
const postcss = require('rollup-plugin-postcss');
|
||||
const { eslint } = require('rollup-plugin-eslint');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssnano = require('cssnano');
|
||||
const replace = require('rollup-plugin-replace');
|
||||
const svgo = require('rollup-plugin-svgo');
|
||||
const { uglify } = require('rollup-plugin-uglify');
|
||||
const { version, homepage, name } = require('./package.json');
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const banner =
|
||||
'/*!\n' +
|
||||
` * ${name}.js v${version}\n` +
|
||||
` * Github: ${homepage}\n` +
|
||||
` * (c) 2017-${new Date().getFullYear()} Harvey Zack\n` +
|
||||
' * Released under the MIT License.\n' +
|
||||
' */\n';
|
||||
|
||||
export default {
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
name: name,
|
||||
file: isProd ? `dist/${name}.js` : path.join(process.cwd(), '../../', `docs/js/${name}.js`),
|
||||
format: 'umd',
|
||||
exports: 'named'
|
||||
},
|
||||
plugins: [
|
||||
eslint({
|
||||
exclude: [
|
||||
'node_modules/**',
|
||||
'src/**/*.scss',
|
||||
'src/**/*.svg'
|
||||
]
|
||||
}),
|
||||
postcss({
|
||||
plugins: [autoprefixer, cssnano],
|
||||
extract: isProd ? `dist/${name}.css` : path.join(process.cwd(), '../../', `docs/css/${name}.css`)
|
||||
}),
|
||||
nodeResolve(),
|
||||
commonjs(),
|
||||
babel({
|
||||
runtimeHelpers: true,
|
||||
exclude: 'node_modules/**',
|
||||
presets: [['@babel/env', { modules: false }]],
|
||||
plugins: ['@babel/plugin-external-helpers', '@babel/plugin-transform-runtime']
|
||||
}),
|
||||
svgo({
|
||||
raw: true
|
||||
}),
|
||||
replace({
|
||||
exclude: 'node_modules/**',
|
||||
__ENV__: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
__VERSION__: version
|
||||
}),
|
||||
isProd && uglify({
|
||||
output: {
|
||||
preamble: banner
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import mimeCodec from './mimeCodec';
|
||||
import mse from './mse';
|
||||
import video from './video';
|
||||
|
||||
export default {
|
||||
mimeCodec,
|
||||
mse,
|
||||
video
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
mp4: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
||||
webm: 'video/webm; codecs="vorbis, vp8"',
|
||||
ts: 'video/mp2t; codecs="avc1.42E01E, mp4a.40.2"'
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
export default {
|
||||
mediaSource: {
|
||||
propertys: [
|
||||
'activeSourceBuffers',
|
||||
'duration',
|
||||
'readyState',
|
||||
'sourceBuffers'
|
||||
],
|
||||
methods: [
|
||||
'addSourceBuffer',
|
||||
'endOfStream',
|
||||
'removeSourceBuffer',
|
||||
'clearLiveSeekableRange',
|
||||
'setLiveSeekableRange'
|
||||
],
|
||||
events: [
|
||||
'sourceclose',
|
||||
'sourceended',
|
||||
'sourceopen'
|
||||
]
|
||||
},
|
||||
sourceBuffer: {
|
||||
propertys: [
|
||||
'mode',
|
||||
'updating',
|
||||
'buffered',
|
||||
'timestampOffset',
|
||||
'audioTracks',
|
||||
'videoTracks',
|
||||
'textTracks',
|
||||
'appendWindowStart',
|
||||
'appendWindowEnd',
|
||||
'trackDefaults'
|
||||
],
|
||||
methods: [
|
||||
'appendBuffer',
|
||||
'appendStream',
|
||||
'abort',
|
||||
'remove'
|
||||
],
|
||||
events: [
|
||||
'abort',
|
||||
'error',
|
||||
'update',
|
||||
'updateend',
|
||||
'updatestart'
|
||||
]
|
||||
},
|
||||
sourceBufferList: {
|
||||
propertys: [
|
||||
'length'
|
||||
],
|
||||
events: [
|
||||
'addsourcebuffer',
|
||||
'removesourcebuffer'
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
export default {
|
||||
propertys: [
|
||||
'audioTracks',
|
||||
'autoplay',
|
||||
'buffered',
|
||||
'controller',
|
||||
'controls',
|
||||
'crossOrigin',
|
||||
'currentSrc',
|
||||
'currentTime',
|
||||
'defaultMuted',
|
||||
'defaultPlaybackRate',
|
||||
'duration',
|
||||
'ended',
|
||||
'error',
|
||||
'loop',
|
||||
'mediaGroup',
|
||||
'muted',
|
||||
'networkState',
|
||||
'paused',
|
||||
'playbackRate',
|
||||
'played',
|
||||
'preload',
|
||||
'readyState',
|
||||
'seekable',
|
||||
'seeking',
|
||||
'src',
|
||||
'startDate',
|
||||
'textTracks',
|
||||
'videoTracks',
|
||||
'volume'
|
||||
],
|
||||
methods: [
|
||||
'addTextTrack',
|
||||
'canPlayType',
|
||||
'load',
|
||||
'play',
|
||||
'pause'
|
||||
],
|
||||
events: [
|
||||
'abort',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'durationchange',
|
||||
'emptied',
|
||||
'ended',
|
||||
'error',
|
||||
'loadeddata',
|
||||
'loadedmetadata',
|
||||
'loadstart',
|
||||
'pause',
|
||||
'play',
|
||||
'playing',
|
||||
'progress',
|
||||
'ratechange',
|
||||
'seeked',
|
||||
'seeking',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'timeupdate',
|
||||
'volumechange',
|
||||
'waiting'
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { sublings } from '../utils';
|
||||
|
||||
export default function aspectRatio(art) {
|
||||
const { option, i18n, player, contextmenu } = art;
|
||||
return {
|
||||
disable: !option.aspectRatio,
|
||||
name: 'aspectRatio',
|
||||
index: 20,
|
||||
html: `
|
||||
${i18n.get('Aspect ratio')}:
|
||||
<span data-ratio="default" class="default current">${i18n.get('Default')}</span>
|
||||
<span data-ratio="4:3">4:3</span>
|
||||
<span data-ratio="16:9">16:9</span>
|
||||
`,
|
||||
click: event => {
|
||||
const { target } = event;
|
||||
const { ratio } = target.dataset;
|
||||
if (ratio) {
|
||||
player.aspectRatio(ratio.split(':'));
|
||||
sublings(target).forEach(item => item.classList.remove('current'));
|
||||
target.classList.add('current');
|
||||
contextmenu.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default function close(art) {
|
||||
return {
|
||||
disable: false,
|
||||
name: 'close',
|
||||
index: 50,
|
||||
html: art.i18n.get('Close'),
|
||||
click: () => {
|
||||
art.contextmenu.hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { append, insertByIndex, setStyles, setStyle } from '../utils';
|
||||
import playbackRate from './playbackRate';
|
||||
import aspectRatio from './aspectRatio';
|
||||
import info from './info';
|
||||
import version from './version';
|
||||
import close from './close';
|
||||
|
||||
let id = 0;
|
||||
export default class Contextmenu {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const { option, refs, events: { proxy } } = this.art;
|
||||
|
||||
option.contextmenu.push(
|
||||
playbackRate,
|
||||
aspectRatio,
|
||||
info,
|
||||
version,
|
||||
close
|
||||
);
|
||||
|
||||
option.contextmenu.forEach(item => {
|
||||
this.add(item);
|
||||
});
|
||||
|
||||
proxy(refs.$player, 'contextmenu', event => {
|
||||
event.preventDefault();
|
||||
this.show();
|
||||
this.setPos(event);
|
||||
});
|
||||
|
||||
proxy(refs.$player, 'click', event => {
|
||||
if (refs.$contextmenu && !event.composedPath().includes(refs.$contextmenu)) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add(item) {
|
||||
if (!item.disable) {
|
||||
const { refs, events: { proxy } } = this.art;
|
||||
id++;
|
||||
const menu = typeof item === 'function' ? item(this.art) : item;
|
||||
const $menu = document.createElement('div');
|
||||
$menu.setAttribute('class', `art-menu art-menu-${menu.name || id}`);
|
||||
append($menu, menu.html);
|
||||
setStyles($menu, menu.style || {});
|
||||
if (menu.click) {
|
||||
proxy($menu, 'click', event => {
|
||||
event.preventDefault();
|
||||
menu.click.call(this, event);
|
||||
this.art.emit('contextmenu:click', $menu);
|
||||
});
|
||||
}
|
||||
this[`$${menu.name || id}`] = $menu;
|
||||
insertByIndex(refs.$contextmenu, $menu, menu.index || id);
|
||||
}
|
||||
}
|
||||
|
||||
setPos(event) {
|
||||
const { refs } = this.art;
|
||||
const mouseX = event.clientX;
|
||||
const mouseY = event.clientY;
|
||||
const { height: cHeight, width: cWidth, left: cLeft, top: cTop } = refs.$player.getBoundingClientRect();
|
||||
const { height: mHeight, width: mWidth } = refs.$contextmenu.getBoundingClientRect();
|
||||
let menuLeft = mouseX - cLeft;
|
||||
let menuTop = mouseY - cTop;
|
||||
|
||||
if (mouseX + mWidth > cLeft + cWidth) {
|
||||
menuLeft = cWidth - mWidth;
|
||||
}
|
||||
|
||||
if (mouseY + mHeight > cTop + cHeight) {
|
||||
menuTop = cHeight - mHeight;
|
||||
}
|
||||
|
||||
setStyle(refs.$contextmenu, 'left', `${menuLeft}px`);
|
||||
setStyle(refs.$contextmenu, 'top', `${menuTop}px`);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { refs: { $contextmenu } } = this.art;
|
||||
setStyle($contextmenu, 'display', 'none');
|
||||
this.art.emit('contextmenu:hide', $contextmenu);
|
||||
}
|
||||
|
||||
show() {
|
||||
const { refs: { $contextmenu } } = this.art;
|
||||
setStyle($contextmenu, 'display', 'block');
|
||||
this.art.emit('contextmenu:show', $contextmenu);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export default function info(art) {
|
||||
return {
|
||||
disable: false,
|
||||
name: 'info',
|
||||
index: 30,
|
||||
html: art.i18n.get('Video info'),
|
||||
click: () => {
|
||||
art.info.show();
|
||||
art.contextmenu.hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { sublings } from '../utils';
|
||||
|
||||
export default function playbackRate(art) {
|
||||
const { option, i18n, player, contextmenu } = art;
|
||||
return {
|
||||
disable: !option.playbackRate,
|
||||
name: 'playbackRate',
|
||||
index: 10,
|
||||
html: `
|
||||
${i18n.get('Play speed')}:
|
||||
<span data-rate="0.5">0.5</span>
|
||||
<span data-rate="0.75">0.75</span>
|
||||
<span data-rate="1" class="normal current">${i18n.get('Normal')}</span>
|
||||
<span data-rate="1.25">1.25</span>
|
||||
<span data-rate="1.5">1.5</span>
|
||||
<span data-rate="2.0">2.0</span>
|
||||
`,
|
||||
click: event => {
|
||||
const { target } = event;
|
||||
const { rate } = target.dataset;
|
||||
if (rate) {
|
||||
player.playbackRate(Number(rate));
|
||||
sublings(target).forEach(item => item.classList.remove('current'));
|
||||
target.classList.add('current');
|
||||
contextmenu.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
disable: false,
|
||||
name: 'version',
|
||||
index: 40,
|
||||
html: '<a href="https://github.com/zhw2590582/artplayer" target="_blank">ArtPlayer __VERSION__</a>'
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Danmu {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, danmu } = art;
|
||||
this.$danmu = append($control, icons.danmu);
|
||||
tooltip(this.$danmu, i18n.get('Hide danmu'));
|
||||
proxy($control, 'click', () => {
|
||||
if (danmu.isOpen) {
|
||||
danmu.hide();
|
||||
setStyle(this.$danmu, 'opacity', '1');
|
||||
tooltip(this.$danmu, i18n.get('Show danmu'));
|
||||
} else {
|
||||
danmu.show();
|
||||
setStyle(this.$danmu, 'opacity', '0.8');
|
||||
tooltip(this.$danmu, i18n.get('Hide danmu'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Fullscreen {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, player } = art;
|
||||
this.$fullscreen = append($control, icons.fullscreen);
|
||||
tooltip(this.$fullscreen, i18n.get('Fullscreen'));
|
||||
|
||||
proxy($control, 'click', () => {
|
||||
player.fullscreenToggle();
|
||||
});
|
||||
|
||||
art.on('fullscreen:enabled', () => {
|
||||
setStyle(this.$fullscreen, 'opacity', '0.8');
|
||||
tooltip(this.$fullscreen, i18n.get('Exit fullscreen'));
|
||||
});
|
||||
|
||||
art.on('fullscreen:exit', () => {
|
||||
setStyle(this.$fullscreen, 'opacity', '1');
|
||||
tooltip(this.$fullscreen, i18n.get('Fullscreen'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class FullscreenWeb {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, player } = art;
|
||||
this.$fullscreenWeb = append($control, icons.fullscreenWeb);
|
||||
tooltip(this.$fullscreenWeb, i18n.get('Web fullscreen'));
|
||||
|
||||
proxy($control, 'click', () => {
|
||||
player.fullscreenWebToggle();
|
||||
});
|
||||
|
||||
art.on('fullscreenWeb:enabled', () => {
|
||||
setStyle(this.$fullscreenWeb, 'opacity', '0.8');
|
||||
tooltip(this.$fullscreenWeb, i18n.get('Exit web fullscreen'));
|
||||
});
|
||||
|
||||
art.on('fullscreenWeb:exit', () => {
|
||||
setStyle(this.$fullscreenWeb, 'opacity', '1');
|
||||
tooltip(this.$fullscreenWeb, i18n.get('Web fullscreen'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { insertByIndex } from '../utils';
|
||||
import Fullscreen from './fullscreen';
|
||||
import FullscreenWeb from './fullscreenWeb';
|
||||
import Pip from './pip';
|
||||
import PlayAndPause from './playAndPause';
|
||||
import Progress from './progress';
|
||||
import Subtitle from './subtitle';
|
||||
import Time from './time';
|
||||
import Volume from './volume';
|
||||
import Setting from './setting';
|
||||
import Thumbnails from './thumbnails';
|
||||
import Screenshot from './screenshot';
|
||||
import Quality from './quality';
|
||||
|
||||
let id = 0;
|
||||
export default class Controls {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.art.on('firstCanplay', () => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
this.add(new Progress({
|
||||
name: 'progress',
|
||||
disable: false,
|
||||
position: 'top',
|
||||
index: 10
|
||||
}));
|
||||
|
||||
this.add(new Thumbnails({
|
||||
name: 'thumbnails',
|
||||
disable: !this.art.option.thumbnails.url,
|
||||
position: 'top',
|
||||
index: 20
|
||||
}));
|
||||
|
||||
this.add(new PlayAndPause({
|
||||
name: 'playAndPause',
|
||||
disable: false,
|
||||
position: 'left',
|
||||
index: 10
|
||||
}));
|
||||
|
||||
this.add(new Volume({
|
||||
name: 'volume',
|
||||
disable: false,
|
||||
position: 'left',
|
||||
index: 20
|
||||
}));
|
||||
|
||||
this.add(new Time({
|
||||
name: 'time',
|
||||
disable: false,
|
||||
position: 'left',
|
||||
index: 30
|
||||
}));
|
||||
|
||||
this.add(new Quality({
|
||||
name: 'quality',
|
||||
disable: this.art.option.quality.length === 0,
|
||||
position: 'right',
|
||||
index: 10
|
||||
}));
|
||||
|
||||
this.add(new Screenshot({
|
||||
name: 'screenshot',
|
||||
disable: !this.art.option.screenshot,
|
||||
position: 'right',
|
||||
index: 20
|
||||
}));
|
||||
|
||||
this.add(new Subtitle({
|
||||
name: 'subtitle',
|
||||
disable: !this.art.option.subtitle.url,
|
||||
position: 'right',
|
||||
index: 30
|
||||
}));
|
||||
|
||||
this.add(new Setting({
|
||||
name: 'setting',
|
||||
disable: !this.art.option.setting,
|
||||
position: 'right',
|
||||
index: 40
|
||||
}));
|
||||
|
||||
this.add(new Pip({
|
||||
name: 'pip',
|
||||
disable: !this.art.option.pip,
|
||||
position: 'right',
|
||||
index: 50
|
||||
}));
|
||||
|
||||
this.add(new FullscreenWeb({
|
||||
name: 'fullscreenWeb',
|
||||
disable: !this.art.option.fullscreenWeb,
|
||||
position: 'right',
|
||||
index: 60
|
||||
}));
|
||||
|
||||
this.add(new Fullscreen({
|
||||
name: 'fullscreen',
|
||||
disable: !this.art.option.fullscreen,
|
||||
position: 'right',
|
||||
index: 70
|
||||
}));
|
||||
|
||||
this.art.option.controls.forEach(item => {
|
||||
this.add(item);
|
||||
});
|
||||
}
|
||||
|
||||
add(control) {
|
||||
const { option } = control;
|
||||
if (option && !option.disable) {
|
||||
id++;
|
||||
const name = option.name || control.constructor.name.toLowerCase() || `control${id}`;
|
||||
const $control = document.createElement('div');
|
||||
$control.setAttribute('class', `art-control art-control-${name}`);
|
||||
this.mount(option.position, $control, option.index || id);
|
||||
control.apply && control.apply(this.art, $control);
|
||||
this[name] = control;
|
||||
}
|
||||
}
|
||||
|
||||
mount(position, $control, index) {
|
||||
const { $progress, $controlsLeft, $controlsRight } = this.art.refs;
|
||||
switch (position) {
|
||||
case 'top':
|
||||
insertByIndex($progress, $control, index);
|
||||
break;
|
||||
case 'left':
|
||||
insertByIndex($controlsLeft, $control, index);
|
||||
break;
|
||||
case 'right':
|
||||
insertByIndex($controlsRight, $control, index);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
const { $player } = this.art.refs;
|
||||
$player.classList.add('artplayer-controls-show');
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $player } = this.art.refs;
|
||||
$player.classList.remove('artplayer-controls-show');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { append, tooltip } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Pip {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, player } = art;
|
||||
this.$pip = append($control, icons.pip);
|
||||
tooltip(this.$pip, i18n.get('Mini player'));
|
||||
proxy($control, 'click', () => {
|
||||
player.pipEnabled();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class PlayAndPause {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, player, i18n } = art;
|
||||
this.$play = append($control, icons.play);
|
||||
this.$pause = append($control, icons.pause);
|
||||
tooltip(this.$play, i18n.get('Play'));
|
||||
tooltip(this.$pause, i18n.get('Pause'));
|
||||
setStyle(this.$pause, 'display', 'none');
|
||||
|
||||
proxy(this.$play, 'click', () => {
|
||||
player.play();
|
||||
});
|
||||
|
||||
proxy(this.$pause, 'click', () => {
|
||||
player.pause();
|
||||
});
|
||||
|
||||
art.on('video:playing', () => {
|
||||
setStyle(this.$play, 'display', 'none');
|
||||
setStyle(this.$pause, 'display', 'block');
|
||||
});
|
||||
|
||||
art.on('video:pause', () => {
|
||||
setStyle(this.$play, 'display', 'block');
|
||||
setStyle(this.$pause, 'display', 'none');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { append, clamp, secondToTime, setStyle } from '../utils';
|
||||
|
||||
export default class Progress {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
this.isDroging = false;
|
||||
this.getLoaded = this.getLoaded.bind(this);
|
||||
this.getPlayed = this.getPlayed.bind(this);
|
||||
this.set = this.set.bind(this);
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
this.art = art;
|
||||
this.$control = $control;
|
||||
const { option: { highlight, theme }, events: { proxy }, refs: { $video }, player } = art;
|
||||
|
||||
append(
|
||||
$control,
|
||||
`
|
||||
<div class="art-control-progress-inner">
|
||||
<div class="art-progress-loaded"></div>
|
||||
<div class="art-progress-played" style="background: ${theme}"></div>
|
||||
<div class="art-progress-highlight"></div>
|
||||
<div class="art-progress-indicator" style="background: ${theme}"></div>
|
||||
<div class="art-progress-tip art-tip"></div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
this.$loaded = $control.querySelector('.art-progress-loaded');
|
||||
this.$played = $control.querySelector('.art-progress-played');
|
||||
this.$highlight = $control.querySelector('.art-progress-highlight');
|
||||
this.$indicator = $control.querySelector('.art-progress-indicator');
|
||||
this.$tip = $control.querySelector('.art-progress-tip');
|
||||
|
||||
this.set('loaded', this.getLoaded());
|
||||
highlight.forEach(item => {
|
||||
const left = Number(item.time) / $video.duration;
|
||||
append(this.$highlight, `<span data-text="${item.text}" data-time="${item.time}" style="left: ${left * 100}%"></span>`);
|
||||
});
|
||||
|
||||
this.art.on('video:progress', () => {
|
||||
this.set('loaded', this.getLoaded());
|
||||
});
|
||||
|
||||
this.art.on('video:timeupdate', () => {
|
||||
this.set('played', this.getPlayed());
|
||||
});
|
||||
|
||||
this.art.on('video:ended', () => {
|
||||
this.set('played', 1);
|
||||
});
|
||||
|
||||
proxy($control, 'mousemove', event => {
|
||||
setStyle(this.$tip, 'display', 'block');
|
||||
if (event.composedPath().indexOf(this.$highlight) > -1) {
|
||||
this.showHighlight(event);
|
||||
} else {
|
||||
this.showTime(event);
|
||||
}
|
||||
});
|
||||
|
||||
proxy($control, 'mouseout', () => {
|
||||
setStyle(this.$tip, 'display', 'none');
|
||||
});
|
||||
|
||||
proxy($control, 'click', event => {
|
||||
if (event.target !== this.$indicator) {
|
||||
const { second, percentage } = this.getPosFromEvent(event);
|
||||
this.set('played', percentage);
|
||||
player.seek(second);
|
||||
}
|
||||
});
|
||||
|
||||
proxy(this.$indicator, 'mousedown', () => {
|
||||
this.isDroging = true;
|
||||
});
|
||||
|
||||
proxy(document, 'mousemove', event => {
|
||||
if (this.isDroging) {
|
||||
const { second, percentage } = this.getPosFromEvent(event);
|
||||
this.$indicator.classList.add('show-indicator');
|
||||
this.set('played', percentage);
|
||||
player.seek(second);
|
||||
}
|
||||
});
|
||||
|
||||
proxy(document, 'mouseup', () => {
|
||||
if (this.isDroging) {
|
||||
this.isDroging = false;
|
||||
this.$indicator.classList.remove('show-indicator');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showHighlight(event) {
|
||||
const { $video } = this.art.refs;
|
||||
const { text, time } = event.target.dataset;
|
||||
this.$tip.innerHTML = text;
|
||||
const left =
|
||||
Number(time) / $video.duration * this.$control.clientWidth +
|
||||
event.target.clientWidth / 2 -
|
||||
this.$tip.clientWidth / 2;
|
||||
setStyle(this.$tip, 'left', `${left}px`);
|
||||
}
|
||||
|
||||
showTime(event) {
|
||||
const { width, time } = this.getPosFromEvent(event);
|
||||
const tipWidth = this.$tip.clientWidth;
|
||||
this.$tip.innerHTML = time;
|
||||
if (width <= tipWidth / 2) {
|
||||
setStyle(this.$tip, 'left', 0);
|
||||
} else if (width > this.$control.clientWidth - tipWidth / 2) {
|
||||
setStyle(this.$tip, 'left', `${this.$control.clientWidth - tipWidth}px`);
|
||||
} else {
|
||||
setStyle(this.$tip, 'left', `${width - tipWidth / 2}px`);
|
||||
}
|
||||
}
|
||||
|
||||
getPlayed() {
|
||||
const { $video } = this.art.refs;
|
||||
return $video.currentTime / $video.duration;
|
||||
}
|
||||
|
||||
getLoaded() {
|
||||
const { $video } = this.art.refs;
|
||||
return $video.buffered.length
|
||||
? $video.buffered.end($video.buffered.length - 1) / $video.duration
|
||||
: 0;
|
||||
}
|
||||
|
||||
getPosFromEvent(event) {
|
||||
const { $video, $progress } = this.art.refs;
|
||||
const { left } = $progress.getBoundingClientRect();
|
||||
const width = clamp(event.x - left, 0, $progress.clientWidth);
|
||||
const second = width / $progress.clientWidth * $video.duration;
|
||||
const time = secondToTime(second);
|
||||
const percentage = clamp(width / $progress.clientWidth, 0, 1);
|
||||
return { second, time, width, percentage };
|
||||
}
|
||||
|
||||
set(type, percentage) {
|
||||
setStyle(this[`$${type}`], 'width', `${percentage * 100}%`);
|
||||
if (type === 'played') {
|
||||
setStyle(this.$indicator, 'left', `calc(${percentage * 100}% - 6.5px)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { append } from '../utils';
|
||||
|
||||
export default class Quality {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
this.playIndex = -1;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { option, events: { proxy, hover }, player } = art;
|
||||
const defaultQuality = option.quality.find(item => item.default) || option.quality[0];
|
||||
this.playIndex = option.quality.indexOf(defaultQuality);
|
||||
const $qualityName = append($control, `<div class="art-quality-name">${defaultQuality.name}</div>`);
|
||||
const qualityList = option.quality.map((item, index) => `<div class="art-quality-item" data-index="${index}">${item.name}</div>`).join('');
|
||||
const $qualitys = append($control, `<div class="art-qualitys">${qualityList}</div>`);
|
||||
|
||||
hover($control, () => {
|
||||
$control.classList.add('hover');
|
||||
}, () => {
|
||||
$control.classList.remove('hover');
|
||||
});
|
||||
|
||||
proxy($qualitys, 'click', event => {
|
||||
const index = Number(event.target.dataset.index);
|
||||
const { url, name } = option.quality[index];
|
||||
if (url && name && this.playIndex !== index) {
|
||||
player.switch(url, name);
|
||||
$qualityName.innerHTML = name;
|
||||
this.playIndex = index;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { append, tooltip } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Screenshot {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, player } = art;
|
||||
this.$screenshot = append($control, icons.screenshot);
|
||||
tooltip(this.$screenshot, i18n.get('Screenshot'));
|
||||
proxy(this.$screenshot, 'click', () => {
|
||||
player.screenshot();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Setting {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, setting } = art;
|
||||
this.$setting = append($control, icons.setting);
|
||||
tooltip(this.$setting, i18n.get('Show setting'));
|
||||
|
||||
proxy($control, 'click', () => {
|
||||
setting.toggle();
|
||||
});
|
||||
|
||||
art.on('setting:show', () => {
|
||||
setStyle(this.$setting, 'opacity', '0.8');
|
||||
tooltip(this.$setting, i18n.get('Hide setting'));
|
||||
});
|
||||
|
||||
art.on('setting:hide', () => {
|
||||
setStyle(this.$setting, 'opacity', '1');
|
||||
tooltip(this.$setting, i18n.get('Show setting'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { append, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Subtitle {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
const { events: { proxy }, i18n, subtitle } = art;
|
||||
this.$subtitle = append($control, icons.subtitle);
|
||||
tooltip(this.$subtitle, i18n.get('Hide subtitle'));
|
||||
|
||||
proxy($control, 'click', () => {
|
||||
subtitle.toggle();
|
||||
});
|
||||
|
||||
art.on('subtitle:show', () => {
|
||||
setStyle(this.$subtitle, 'opacity', '0.8');
|
||||
tooltip(this.$subtitle, i18n.get('Hide subtitle'));
|
||||
});
|
||||
|
||||
art.on('subtitle:hide', () => {
|
||||
setStyle(this.$subtitle, 'opacity', '1');
|
||||
tooltip(this.$subtitle, i18n.get('Show subtitle'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { errorHandle, setStyle } from '../utils';
|
||||
|
||||
export default class Thumbnails {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
this.loading = false;
|
||||
this.isLoad = false;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
this.art = art;
|
||||
errorHandle(art.controls.progress, '\'thumbnails\' control dependent on \'progress\' control');
|
||||
const { refs: { $progress }, events: { proxy } } = art;
|
||||
this.$control = $control;
|
||||
|
||||
proxy($progress, 'mousemove', event => {
|
||||
if (!this.loading) {
|
||||
this.loading = true;
|
||||
this.load(this.art.option.thumbnails.url).then(() => {
|
||||
this.isLoad = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isLoad) {
|
||||
setStyle($control, 'display', 'block');
|
||||
this.showThumbnails(event);
|
||||
}
|
||||
});
|
||||
|
||||
proxy($progress, 'mouseout', () => {
|
||||
setStyle($control, 'display', 'none');
|
||||
});
|
||||
}
|
||||
|
||||
load(url) {
|
||||
const { proxy } = this.art.events;
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.src = url;
|
||||
|
||||
if (image.complete) {
|
||||
return resolve(image);
|
||||
}
|
||||
|
||||
proxy(image, 'load', () => resolve(image));
|
||||
proxy(image, 'error', () => reject(image));
|
||||
});
|
||||
}
|
||||
|
||||
showThumbnails(event) {
|
||||
const { refs: { $progress }, controls } = this.art;
|
||||
const { width: posWidth } = controls.progress.getPosFromEvent(event);
|
||||
const { url, height, width, number, column } = this.art.option.thumbnails;
|
||||
const perWidth = $progress.clientWidth / number;
|
||||
const perIndex = Math.ceil(posWidth / perWidth);
|
||||
let yIndex = Math.ceil(perIndex / column);
|
||||
let xIndex = perIndex % column || column;
|
||||
|
||||
setStyle(this.$control, 'backgroundImage', `url(${url})`);
|
||||
setStyle(this.$control, 'height', `${height}px`);
|
||||
setStyle(this.$control, 'width', `${width}px`);
|
||||
setStyle(this.$control, 'backgroundPosition', `-${--xIndex * width}px -${--yIndex * height}px`);
|
||||
|
||||
if (posWidth <= width / 2) {
|
||||
setStyle(this.$control, 'left', 0);
|
||||
} else if (posWidth > $progress.clientWidth - width / 2) {
|
||||
setStyle(this.$control, 'left', `${$progress.clientWidth - width}px`);
|
||||
} else {
|
||||
setStyle(this.$control, 'left', `${posWidth - width / 2}px`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { secondToTime } from '../utils';
|
||||
|
||||
export default class Time {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
$control.innerHTML = '00:00 / 00:00';
|
||||
|
||||
function setTime() {
|
||||
$control.innerHTML = `${secondToTime(art.player.currentTime())} / ${secondToTime(art.player.duration())}`;
|
||||
}
|
||||
|
||||
art.on('video:canplay', () => {
|
||||
setTime();
|
||||
});
|
||||
|
||||
art.on('video:timeupdate', () => {
|
||||
setTime();
|
||||
});
|
||||
|
||||
art.on('video:seeking', () => {
|
||||
setTime();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { append, getStorage, clamp, tooltip, setStyle } from '../utils';
|
||||
import icons from '../icons';
|
||||
|
||||
export default class Volume {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
this.isDroging = false;
|
||||
}
|
||||
|
||||
apply(art, $control) {
|
||||
this.art = art;
|
||||
this.$control = $control;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const { events: { proxy }, player, i18n } = this.art;
|
||||
this.$volume = append(this.$control, icons.volume);
|
||||
this.$volumeClose = append(this.$control, icons.volumeClose);
|
||||
this.$volumePanel = append(this.$control, '<div class="art-volume-panel"></div>');
|
||||
this.$volumeHandle = append(this.$volumePanel, '<div class="art-volume-slider-handle"></div>');
|
||||
tooltip(this.$volume, i18n.get('Mute'));
|
||||
setStyle(this.$volumeClose, 'display', 'none');
|
||||
|
||||
const volume = getStorage('volume');
|
||||
this.setVolumeHandle(volume);
|
||||
player.volume(volume);
|
||||
|
||||
proxy(this.$volume, 'click', () => {
|
||||
setStyle(this.$volume, 'display', 'none');
|
||||
setStyle(this.$volumeClose, 'display', 'block');
|
||||
player.volume(0);
|
||||
});
|
||||
|
||||
proxy(this.$volumeClose, 'click', () => {
|
||||
setStyle(this.$volume, 'display', 'block');
|
||||
setStyle(this.$volumeClose, 'display', 'none');
|
||||
player.volume(getStorage('volume'));
|
||||
});
|
||||
|
||||
proxy(this.$control, 'mouseenter', () => {
|
||||
this.$volumePanel.classList.add('art-volume-panel-hover');
|
||||
|
||||
// TODO
|
||||
setTimeout(() => {
|
||||
this.setVolumeHandle(player.volume());
|
||||
}, 200);
|
||||
});
|
||||
|
||||
proxy(this.$control, 'mouseleave', () => {
|
||||
this.$volumePanel.classList.remove('art-volume-panel-hover');
|
||||
});
|
||||
|
||||
proxy(this.$volumePanel, 'click', event => {
|
||||
this.volumeChangeFromEvent(event);
|
||||
});
|
||||
|
||||
proxy(this.$volumeHandle, 'mousedown', () => {
|
||||
this.isDroging = true;
|
||||
});
|
||||
|
||||
proxy(this.$volumeHandle, 'mousemove', event => {
|
||||
if (this.isDroging) {
|
||||
this.volumeChangeFromEvent(event);
|
||||
}
|
||||
});
|
||||
|
||||
proxy(document, 'mouseup', () => {
|
||||
if (this.isDroging) {
|
||||
this.isDroging = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.art.on('video:volumechange', () => {
|
||||
const percentage = player.volume();
|
||||
this.setVolumeHandle(percentage);
|
||||
if (percentage === 0) {
|
||||
setStyle(this.$volume, 'display', 'none');
|
||||
setStyle(this.$volumeClose, 'display', 'block');
|
||||
} else {
|
||||
setStyle(this.$volume, 'display', 'block');
|
||||
setStyle(this.$volumeClose, 'display', 'none');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
volumeChangeFromEvent(event) {
|
||||
const { player } = this.art;
|
||||
const { left: panelLeft, width: panelWidth } = this.$volumePanel.getBoundingClientRect();
|
||||
const { width: handleWidth } = this.$volumeHandle.getBoundingClientRect();
|
||||
const percentage = clamp(event.x - panelLeft - handleWidth / 2, 0, panelWidth - handleWidth / 2) / (panelWidth - handleWidth);
|
||||
player.volume(percentage);
|
||||
}
|
||||
|
||||
setVolumeHandle(percentage = 0.7) {
|
||||
const { width: panelWidth } = this.$volumePanel.getBoundingClientRect();
|
||||
const { width: handleWidth } = this.$volumeHandle.getBoundingClientRect();
|
||||
const width = handleWidth / 2 + (panelWidth - handleWidth) * percentage - handleWidth / 2;
|
||||
setStyle(this.$volumeHandle, 'left', `${width}px`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { debounce } from './utils';
|
||||
|
||||
export default class Events {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.destroyEvents = [];
|
||||
this.proxy = this.proxy.bind(this);
|
||||
this.hover = this.hover.bind(this);
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const { refs: { $player } } = this.art;
|
||||
|
||||
this.hover($player, () => {
|
||||
$player.classList.add('artplayer-hover');
|
||||
this.art.emit('hoverenter');
|
||||
}, () => {
|
||||
$player.classList.remove('artplayer-hover');
|
||||
$player.classList.remove('artplayer-hide-cursor');
|
||||
this.art.emit('hoverleave');
|
||||
});
|
||||
|
||||
this.proxy(document, ['click', 'contextmenu'], event => {
|
||||
if (event.composedPath().indexOf($player) > -1) {
|
||||
this.art.isFocus = true;
|
||||
} else {
|
||||
this.art.isFocus = false;
|
||||
this.art.contextmenu.hide();
|
||||
}
|
||||
});
|
||||
|
||||
const hideCursor = debounce(() => {
|
||||
$player.classList.add('artplayer-hide-cursor');
|
||||
if (this.art.player.fullscreenState || this.art.player.fullscreenWebState) {
|
||||
$player.classList.remove('artplayer-hover');
|
||||
this.art.controls.hide();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
this.proxy($player, 'mousemove', () => {
|
||||
$player.classList.remove('artplayer-hide-cursor');
|
||||
this.art.controls.show();
|
||||
if (!this.art.player.pipState) {
|
||||
hideCursor();
|
||||
} else {
|
||||
hideCursor.clearTimeout();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
proxy(target, name, callback, option = {}) {
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(item => this.proxy(target, item, callback, option));
|
||||
return;
|
||||
}
|
||||
|
||||
target.addEventListener(name, callback, option);
|
||||
this.destroyEvents.push(() => {
|
||||
target.removeEventListener(name, callback, option);
|
||||
});
|
||||
}
|
||||
|
||||
hover(target, mouseenter, mouseleave) {
|
||||
this.proxy(target, 'mouseenter', mouseenter);
|
||||
this.proxy(target, 'mouseleave', mouseleave);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.destroyEvents.forEach(event => event());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
export default class Hotkey {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
if (this.art.option.hotkey) {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
const { player, events: { proxy } } = this.art;
|
||||
|
||||
proxy(window, 'keydown', event => {
|
||||
if (this.art.isFocus) {
|
||||
const tag = document.activeElement.tagName.toUpperCase();
|
||||
const editable = document.activeElement.getAttribute('contenteditable');
|
||||
if (
|
||||
tag !== 'INPUT' &&
|
||||
tag !== 'TEXTAREA' &&
|
||||
editable !== '' &&
|
||||
editable !== 'true'
|
||||
) {
|
||||
switch (event.keyCode) {
|
||||
case 39:
|
||||
event.preventDefault();
|
||||
player.seek(player.currentTime() + 10);
|
||||
break;
|
||||
case 37:
|
||||
event.preventDefault();
|
||||
player.seek(player.currentTime() - 10);
|
||||
break;
|
||||
case 38:
|
||||
event.preventDefault();
|
||||
player.volume(player.volume() + 0.05);
|
||||
break;
|
||||
case 40:
|
||||
event.preventDefault();
|
||||
player.volume(player.volume() - 0.05);
|
||||
break;
|
||||
case 32:
|
||||
event.preventDefault();
|
||||
player.toggle();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
let i18nMap = {
|
||||
'zh-cn': {
|
||||
'About author': '关于作者',
|
||||
'Video info': '视频统计信息',
|
||||
'Close': '关闭',
|
||||
'Video load failed': '视频加载失败',
|
||||
'Volume': '音量',
|
||||
'Play': '播放',
|
||||
'Pause': '暂停',
|
||||
'Rate': '速度',
|
||||
'Mute': '静音',
|
||||
'Reconnect': '重新连接',
|
||||
'Hide subtitle': '隐藏字幕',
|
||||
'Show subtitle': '显示字幕',
|
||||
'Hide danmu': '隐藏弹幕',
|
||||
'Show danmu': '显示弹幕',
|
||||
'Screenshot': '截图',
|
||||
'Play speed': '播放速度',
|
||||
'Aspect ratio': '画面比例',
|
||||
'Default': '默认',
|
||||
'Normal': '正常',
|
||||
'Switch video': '切换',
|
||||
'Switch subtitle': '切换字幕',
|
||||
'Fullscreen': '全屏',
|
||||
'Exit fullscreen': '退出全屏',
|
||||
'Web fullscreen': '网页全屏',
|
||||
'Exit web fullscreen': '退出网页全屏',
|
||||
'Common': '常规',
|
||||
'Hide setting': '隐藏设置',
|
||||
'Show setting': '显示设置',
|
||||
'Mini player': '迷你播放器'
|
||||
},
|
||||
'zh-tw': {
|
||||
'About author': '關於作者',
|
||||
'Video info': '影片統計訊息',
|
||||
'Close': '關閉',
|
||||
'Video load failed': '影片載入失敗',
|
||||
'Volume': '音量',
|
||||
'Play': '播放',
|
||||
'Pause': '暫停',
|
||||
'Rate': '速度',
|
||||
'Mute': '靜音',
|
||||
'Reconnect': '重新連接',
|
||||
'Hide subtitle': '隱藏字幕',
|
||||
'Show subtitle': '顯示字幕',
|
||||
'Hide danmu': '隱藏彈幕',
|
||||
'Show danmu': '顯示彈幕',
|
||||
'Screenshot': '截圖',
|
||||
'Play speed': '播放速度',
|
||||
'Aspect ratio': '畫面比例',
|
||||
'Default': '默認',
|
||||
'Normal': '正常',
|
||||
'Switch video': '切換',
|
||||
'Switch subtitle': '切換字幕',
|
||||
'Fullscreen': '全屏',
|
||||
'Exit fullscreen': '退出全屏',
|
||||
'Web fullscreen': '網頁全屏',
|
||||
'Exit web fullscreen': '退出網頁全屏',
|
||||
'Common': '常規',
|
||||
'Hide setting': '隱藏設置',
|
||||
'Show setting': '顯示設置',
|
||||
'Mini player': '迷你播放器'
|
||||
}
|
||||
};
|
||||
|
||||
export default class I18n {
|
||||
constructor({ option }) {
|
||||
this.option = option;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.language = i18nMap[this.option.lang.toLowerCase()] || {};
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.language[key] || key;
|
||||
}
|
||||
|
||||
update(callback) {
|
||||
i18nMap = callback(i18nMap);
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" style="width: 85%; height: 85%">
|
||||
<path d="M54,2.5H6c-3.252,0-6,2.748-6,6v33c0,3.252,2.748,6,6,6h14.555l8.702,9.669C29.446,57.38,29.717,57.5,30,57.5 s0.554-0.12,0.743-0.331l8.702-9.669H54c3.252,0,6-2.748,6-6v-33C60,5.248,57.252,2.5,54,2.5z M16,28.5c-2.206,0-4-1.794-4-4 s1.794-4,4-4s4,1.794,4,4S18.206,28.5,16,28.5z M30,28.5c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S32.206,28.5,30,28.5z M44,28.5c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S46.206,28.5,44,28.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" style="width: 80%; height: 80%">
|
||||
<path d="M4,18h14V4H4V18z M6,6h10v10H6V6z"/>
|
||||
<polygon points="2,16 0,16 0,22 6,22 6,20 2,20"/>
|
||||
<polygon points="2,2 6,2 6,0 0,0 0,6 2,6"/>
|
||||
<polygon points="20,20 16,20 16,22 22,22 22,16 20,16"/>
|
||||
<polygon points="16,0 16,2 20,2 20,6 22,6 22,0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 350 B |
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="width: 80%; height: 80%">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M236.454,172.808L132.421,68.789l43.184-43.184C185.014,16.197,178.365,0,165,0H15C6.709,0,0,6.709,0,15v150
|
||||
c0,13.241,16.066,20.112,25.606,10.606l43.198-43.169l104.004,104.018c5.856,5.856,15.351,5.859,21.209,0.001l42.437-42.437
|
||||
C242.286,188.187,242.334,178.688,236.454,172.808z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M497,0H347c-13.361,0-20.018,16.193-10.606,25.605l43.184,43.184L275.544,172.792c-5.844,5.844-5.868,15.358,0,21.226
|
||||
l42.437,42.437c5.86,5.86,15.352,5.859,21.211,0l104.019-104.034l43.184,43.184C495.93,185.141,512,178.23,512,165V15
|
||||
C512,6.709,505.291,0,497,0z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M486.396,336.393l-43.184,43.184L339.193,275.544c-5.856-5.856-15.349-5.862-21.211,0l-42.437,42.437
|
||||
c-5.868,5.868-5.844,15.382,0,21.226l104.034,104.004l-43.184,43.184C326.986,495.803,333.635,512,347,512h150
|
||||
c8.291,0,15-6.709,15-15V347C512,333.639,495.807,326.982,486.396,336.393z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M236.456,317.983l-42.437-42.437c-5.625-5.625-15.586-5.625-21.211,0L68.789,379.579l-43.184-43.184
|
||||
C16.283,327.04,0,333.563,0,347v150c0,8.291,6.709,15,15,15h150c13.361,0,20.018-16.193,10.606-25.605l-43.184-43.184
|
||||
l104.034-104.017C242.336,333.314,242.289,323.816,236.456,317.983z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,41 @@
|
||||
import loading from './loading.svg';
|
||||
import playBig from './play-big.svg';
|
||||
import play from './play.svg';
|
||||
import pause from './pause.svg';
|
||||
import volume from './volume.svg';
|
||||
import volumeClose from './volume-close.svg';
|
||||
import subtitle from './subtitle.svg';
|
||||
import screenshot from './screenshot.svg';
|
||||
import danmu from './danmu.svg';
|
||||
import setting from './setting.svg';
|
||||
import fullscreen from './fullscreen.svg';
|
||||
import fullscreenWeb from './fullscreen-web.svg';
|
||||
import pip from './pip.svg';
|
||||
|
||||
const icons = {
|
||||
loading,
|
||||
playBig,
|
||||
play,
|
||||
pause,
|
||||
volume,
|
||||
volumeClose,
|
||||
subtitle,
|
||||
screenshot,
|
||||
danmu,
|
||||
setting,
|
||||
fullscreen,
|
||||
fullscreenWeb,
|
||||
pip
|
||||
};
|
||||
|
||||
function creatDomFromSvg(map) {
|
||||
const result = {};
|
||||
Object.keys(map).forEach(name => {
|
||||
const tmp = document.createElement('div');
|
||||
tmp.innerHTML = `<i class="art-icon art-icon-${name}">${map[name]}</i>`;
|
||||
[result[name]] = tmp.childNodes;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export default creatDomFromSvg(icons);
|
||||
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="50px" height="50px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-default">
|
||||
<rect x="0" y="0" width="100" height="100" fill="none" class="bk"/>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(0 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-1s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(30 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(60 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(90 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.75s" repeatCount="indefinite"/></rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(120 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(150 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(180 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.5s" repeatCount="indefinite"/></rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(210 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(240 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(270 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.25s" repeatCount="indefinite"/></rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(300 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="47" y="40" width="6" height="20" rx="5" ry="5" fill="#ffffff" transform="rotate(330 50 50) translate(0 -30)">
|
||||
<animate attributeName="opacity" from="1" to="0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%" viewBox="0 0 22 22">
|
||||
<path d="M7 3a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V5a2 2 0 0 0-2-2zM15 3a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V5a2 2 0 0 0-2-2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 36 36" style="width: 100%; height: 100%">
|
||||
<path d="M25,17 L17,17 L17,23 L25,23 L25,17 L25,17 Z M29,25 L29,10.98 C29,9.88 28.1,9 27,9 L9,9 C7.9,9 7,9.88 7,10.98 L7,25 C7,26.1 7.9,27 9,27 L27,27 C28.1,27 29,26.1 29,25 L29,25 Z M27,25.02 L9,25.02 L9,10.97 L27,10.97 L27,25.02 L27,25.02 Z" fill="#fff" id="ytp-id-20"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
@@ -0,0 +1,3 @@
|
||||
<svg style="width: 60px; height: 60px; filter: drop-shadow(0px 1px 1px black);" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" >
|
||||
<path d="M20,2H4C1.8,2,0,3.8,0,6v12c0,2.2,1.8,4,4,4h16c2.2,0,4-1.8,4-4V6C24,3.8,22.2,2,20,2z M15.6,12.8L10.5,16 C9.9,16.5,9,16,9,15.2V8.8C9,8,9.9,7.5,10.5,8l5.1,3.2C16.3,11.5,16.3,12.5,15.6,12.8z" id="video"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 359 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%" viewBox="0 0 22 22">
|
||||
<path d="M17.982 9.275L8.06 3.27A2.013 2.013 0 0 0 5 4.994v12.011a2.017 2.017 0 0 0 3.06 1.725l9.922-6.005a2.017 2.017 0 0 0 0-3.45z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 245 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" style="width: 100%; height: 100%">
|
||||
<g id="surface1">
|
||||
<path d="M 19.402344 6 C 17.019531 6 14.96875 7.679688 14.5 10.011719 L 14.097656 12 L 9 12 C 6.238281 12 4 14.238281 4 17 L 4 38 C 4 40.761719 6.238281 43 9 43 L 41 43 C 43.761719 43 46 40.761719 46 38 L 46 17 C 46 14.238281 43.761719 12 41 12 L 35.902344 12 L 35.5 10.011719 C 35.03125 7.679688 32.980469 6 30.597656 6 Z M 25 17 C 30.519531 17 35 21.480469 35 27 C 35 32.519531 30.519531 37 25 37 C 19.480469 37 15 32.519531 15 27 C 15 21.480469 19.480469 17 25 17 Z M 25 19 C 20.589844 19 17 22.589844 17 27 C 17 31.410156 20.589844 35 25 35 C 29.410156 35 33 31.410156 33 27 C 33 22.589844 29.410156 19 25 19 Z "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 748 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%" viewBox="0 0 22 22">
|
||||
<circle cx="11" cy="11" r="2"></circle>
|
||||
<path d="M19.164 8.861L17.6 8.6a6.978 6.978 0 0 0-1.186-2.099l.574-1.533a1 1 0 0 0-.436-1.217l-1.997-1.153a1.001 1.001 0 0 0-1.272.23l-1.008 1.225a7.04 7.04 0 0 0-2.55.001L8.716 2.829a1 1 0 0 0-1.272-.23L5.447 3.751a1 1 0 0 0-.436 1.217l.574 1.533A6.997 6.997 0 0 0 4.4 8.6l-1.564.261A.999.999 0 0 0 2 9.847v2.306c0 .489.353.906.836.986l1.613.269a7 7 0 0 0 1.228 2.075l-.558 1.487a1 1 0 0 0 .436 1.217l1.997 1.153c.423.244.961.147 1.272-.23l1.04-1.263a7.089 7.089 0 0 0 2.272 0l1.04 1.263a1 1 0 0 0 1.272.23l1.997-1.153a1 1 0 0 0 .436-1.217l-.557-1.487c.521-.61.94-1.31 1.228-2.075l1.613-.269a.999.999 0 0 0 .835-.986V9.847a.999.999 0 0 0-.836-.986zM11 15a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 843 B |
@@ -0,0 +1,4 @@
|
||||
<svg style="width: 100%; height: 100%" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h48v48H0z" fill="none"/>
|
||||
<path d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 314 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%" viewBox="0 0 22 22">
|
||||
<path d="M15 11a3.998 3.998 0 0 0-2-3.465v2.636l1.865 1.865A4.02 4.02 0 0 0 15 11z"></path>
|
||||
<path d="M13.583 5.583A5.998 5.998 0 0 1 17 11a6 6 0 0 1-.585 2.587l1.477 1.477a8.001 8.001 0 0 0-3.446-11.286 1 1 0 0 0-.863 1.805zM18.778 18.778l-2.121-2.121-1.414-1.414-1.415-1.415L13 13l-2-2-3.889-3.889-3.889-3.889a.999.999 0 1 0-1.414 1.414L5.172 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39v-3.131l2.587 2.587-.01.005a1 1 0 0 0 .86 1.806c.215-.102.424-.214.627-.333l2.3 2.3a1.001 1.001 0 0 0 1.414-1.416zM11 5.04a.5.5 0 0 0-.813-.39L8.682 5.854 11 8.172V5.04z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 698 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%" viewBox="0 0 22 22">
|
||||
<path d="M10.188 4.65L6 8H5a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h1l4.188 3.35a.5.5 0 0 0 .812-.39V5.04a.498.498 0 0 0-.812-.39zM14.446 3.778a1 1 0 0 0-.862 1.804 6.002 6.002 0 0 1-.007 10.838 1 1 0 0 0 .86 1.806A8.001 8.001 0 0 0 19 11a8.001 8.001 0 0 0-4.554-7.222z"></path><path d="M15 11a3.998 3.998 0 0 0-2-3.465v6.93A3.998 3.998 0 0 0 15 11z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 454 B |
@@ -0,0 +1,152 @@
|
||||
import './style';
|
||||
import 'balloon-css/balloon.min.css';
|
||||
import * as utils from './utils';
|
||||
import optionValidator from 'option-validator';
|
||||
import scheme from './scheme';
|
||||
import config from './config';
|
||||
import Emitter from 'tiny-emitter';
|
||||
import Template from './template';
|
||||
import I18n from './i18n';
|
||||
import Player from './player';
|
||||
import Controls from './controls';
|
||||
import Contextmenu from './contextmenu';
|
||||
import Info from './info';
|
||||
import Subtitle from './subtitle';
|
||||
import Events from './events';
|
||||
import Hotkey from './hotkey';
|
||||
import Layers from './layers';
|
||||
import Loading from './loading';
|
||||
import Notice from './notice';
|
||||
import Mask from './mask';
|
||||
import Setting from './setting';
|
||||
|
||||
let id = 0;
|
||||
class Artplayer extends Emitter {
|
||||
constructor(option) {
|
||||
super();
|
||||
this.emit('init:start');
|
||||
this.option = utils.deepMerge({}, Artplayer.DEFAULTS, option);
|
||||
optionValidator(this.option, scheme);
|
||||
this.init();
|
||||
this.emit('init:end');
|
||||
}
|
||||
|
||||
static get version() {
|
||||
return '__VERSION__';
|
||||
}
|
||||
|
||||
static get config() {
|
||||
return config;
|
||||
}
|
||||
|
||||
static get utils() {
|
||||
return utils;
|
||||
}
|
||||
|
||||
static get DEFAULTS() {
|
||||
return {
|
||||
container: '.artplayer',
|
||||
url: '',
|
||||
poster: '',
|
||||
title: '',
|
||||
volume: 0.7,
|
||||
thumbnails: {
|
||||
url: '',
|
||||
number: 60,
|
||||
width: 160,
|
||||
height: 90,
|
||||
column: 10
|
||||
},
|
||||
screenshot: true,
|
||||
autoplay: false,
|
||||
playbackRate: true,
|
||||
aspectRatio: true,
|
||||
loop: false,
|
||||
type: '',
|
||||
mimeCodec: '',
|
||||
layers: [],
|
||||
contextmenu: [],
|
||||
quality: [],
|
||||
loading: '',
|
||||
play: '',
|
||||
theme: '#f00',
|
||||
setting: false,
|
||||
hotkey: true,
|
||||
pip: true,
|
||||
mutex: true,
|
||||
fullscreen: true,
|
||||
fullscreenWeb: true,
|
||||
subtitle: {
|
||||
url: '',
|
||||
style: {}
|
||||
},
|
||||
controls: [],
|
||||
highlight: [],
|
||||
moreVideoAttr: {
|
||||
'crossOrigin': 'anonymous',
|
||||
'controls': false,
|
||||
'preload': 'auto',
|
||||
'webkit-playsinline': true,
|
||||
'playsinline': true
|
||||
},
|
||||
lang: navigator.language.toLowerCase()
|
||||
};
|
||||
}
|
||||
|
||||
static use(plugin) {
|
||||
const installedPlugins = this.plugins || (this.plugins = []);
|
||||
if (installedPlugins.indexOf === -1) {
|
||||
installedPlugins.push(plugin);
|
||||
const args = Array.from(arguments).slice(1);
|
||||
args.unshift(this);
|
||||
plugin.apply(null, args);
|
||||
this.prototype.emit('use', plugin);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isFocus = false;
|
||||
this.isPlaying = false;
|
||||
this.refs = {};
|
||||
|
||||
if (this.option.container instanceof Element) {
|
||||
this.refs.$container = this.option.container;
|
||||
} else {
|
||||
this.refs.$container = document.querySelector(this.option.container);
|
||||
}
|
||||
|
||||
this.template = new Template(this);
|
||||
this.i18n = new I18n(this);
|
||||
this.notice = new Notice(this);
|
||||
this.events = new Events(this);
|
||||
this.player = new Player(this);
|
||||
this.layers = new Layers(this);
|
||||
this.controls = new Controls(this);
|
||||
this.contextmenu = new Contextmenu(this);
|
||||
this.subtitle = new Subtitle(this);
|
||||
this.info = new Info(this);
|
||||
this.loading = new Loading(this);
|
||||
this.hotkey = new Hotkey(this);
|
||||
this.mask = new Mask(this);
|
||||
this.setting = new Setting(this);
|
||||
|
||||
this.id = id++;
|
||||
Artplayer.instances.push(this);
|
||||
}
|
||||
|
||||
destroy(removeHtml = false) {
|
||||
this.events.destroy();
|
||||
Artplayer.instances.splice(Artplayer.instances.indexOf(this), 1);
|
||||
if (removeHtml) {
|
||||
this.refs.$container.innerHTML = '';
|
||||
} else {
|
||||
this.refs.$player.classList.add('artplayer-destroy');
|
||||
}
|
||||
this.emit('destroy');
|
||||
}
|
||||
}
|
||||
|
||||
Artplayer.instances = [];
|
||||
window.Artplayer = Artplayer;
|
||||
export default Artplayer;
|
||||
@@ -0,0 +1,141 @@
|
||||
import { append, setStyle } from './utils';
|
||||
|
||||
export default class Info {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const { refs: { $infoClose, $infoPanel }, events: { proxy } } = this.art;
|
||||
proxy($infoClose, 'click', () => {
|
||||
this.hide();
|
||||
});
|
||||
this.art.on('switch', () => {
|
||||
if ($infoPanel.innerHTML) {
|
||||
this.getHeader();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
const { $info, $infoPanel } = this.art.refs;
|
||||
setStyle($info, 'display', 'block');
|
||||
if (!$infoPanel.innerHTML) {
|
||||
append($infoPanel, this.creatInfo());
|
||||
this.getHeader();
|
||||
}
|
||||
clearTimeout(this.timer);
|
||||
this.loop();
|
||||
this.art.emit('info:show', $info);
|
||||
}
|
||||
|
||||
creatInfo() {
|
||||
const infoHtml = [];
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item ">
|
||||
<div class="art-info-title">Player version:</div>
|
||||
<div class="art-info-content">__VERSION__</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video url:</div>
|
||||
<div class="art-info-content" data-video="currentSrc"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video type:</div>
|
||||
<div class="art-info-content" data-head="Content-Type"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video size:</div>
|
||||
<div class="art-info-content" data-head="Content-length"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video volume:</div>
|
||||
<div class="art-info-content" data-video="volume"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video time:</div>
|
||||
<div class="art-info-content" data-video="currentTime"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video duration:</div>
|
||||
<div class="art-info-content" data-video="duration"></div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
infoHtml.push(`
|
||||
<div class="art-info-item">
|
||||
<div class="art-info-title">Video resolution:</div>
|
||||
<div class="art-info-content">
|
||||
<span data-video="videoWidth"></span> x <span data-video="videoHeight"></span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
return infoHtml.join('');
|
||||
}
|
||||
|
||||
getHeader() {
|
||||
const { $infoPanel, $video } = this.art.refs;
|
||||
const url = $video.src;
|
||||
const types = Array.from($infoPanel.querySelectorAll('[data-head]'));
|
||||
types.forEach(item => {
|
||||
item.innerHTML = 'loading...';
|
||||
});
|
||||
fetch(url, {
|
||||
method: 'HEAD'
|
||||
}).then(data => {
|
||||
types.forEach(item => {
|
||||
const value = data.headers.get(item.dataset.head);
|
||||
item.innerHTML = value !== undefined ? value : 'unknown';
|
||||
});
|
||||
}).catch(() => {
|
||||
types.forEach(item => {
|
||||
item.innerHTML = 'unknown';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
readInfo() {
|
||||
const { $infoPanel, $video } = this.art.refs;
|
||||
const types = Array.from($infoPanel.querySelectorAll('[data-video]'));
|
||||
types.forEach(item => {
|
||||
const value = $video[item.dataset.video];
|
||||
item.innerHTML = value !== undefined ? value : 'unknown';
|
||||
});
|
||||
}
|
||||
|
||||
loop() {
|
||||
this.readInfo();
|
||||
this.timer = setTimeout(() => {
|
||||
this.readInfo();
|
||||
this.loop();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $info } = this.art.refs;
|
||||
setStyle($info, 'display', 'none');
|
||||
clearTimeout(this.timer);
|
||||
this.art.emit('info:hide', $info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { append, setStyles, setStyle, insertByIndex } from './utils';
|
||||
|
||||
let id = 0;
|
||||
export default class Layers {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.add = this.add.bind(this);
|
||||
this.art.option.layers.forEach(item => {
|
||||
this.add(item);
|
||||
});
|
||||
}
|
||||
|
||||
add(item, callback) {
|
||||
if (!item.disable) {
|
||||
const { refs: { $layers } } = this.art;
|
||||
id++;
|
||||
const $layer = document.createElement('div');
|
||||
$layer.setAttribute('class', `art-layer art-layer-${item.name || id}`);
|
||||
setStyle($layer, 'z-index', item.index || id);
|
||||
append($layer, item.html);
|
||||
setStyles($layer, item.style || {});
|
||||
this.art.emit('layers:add', $layer);
|
||||
callback && callback($layer);
|
||||
insertByIndex($layers, $layer, item.index || id);
|
||||
}
|
||||
}
|
||||
|
||||
show() {
|
||||
const { $layers } = this.art.refs;
|
||||
setStyle($layers, 'display', 'block');
|
||||
this.art.emit('layers:show', $layers);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $layers } = this.art.refs;
|
||||
setStyle($layers, 'display', 'none');
|
||||
this.art.emit('layers:hide', $layers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import icons from './icons';
|
||||
import { append, setStyle } from './utils';
|
||||
|
||||
export default class Loading {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
const { option, refs: { $loading } } = art;
|
||||
append($loading, option.loading || icons.loading);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $loading } = this.art.refs;
|
||||
setStyle($loading, 'display', 'none');
|
||||
this.art.emit('loading:hide', $loading);
|
||||
}
|
||||
|
||||
show() {
|
||||
const { $loading } = this.art.refs;
|
||||
setStyle($loading, 'display', 'flex');
|
||||
this.art.emit('loading:show', $loading);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import icons from './icons';
|
||||
import { append, setStyle } from './utils';
|
||||
|
||||
export default class Mask {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
const { option, refs: { $mask } } = art;
|
||||
append($mask, option.play || icons.playBig);
|
||||
}
|
||||
|
||||
show() {
|
||||
const { $mask } = this.art.refs;
|
||||
setStyle($mask, 'display', 'flex');
|
||||
this.art.emit('mask:show', $mask);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $mask } = this.art.refs;
|
||||
setStyle($mask, 'display', 'none');
|
||||
this.art.emit('mask:show', $mask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import ArtPlayerError from './utils/ArtPlayerError';
|
||||
import { errorHandle, getExt } from './utils';
|
||||
import config from './config';
|
||||
|
||||
export default class Mse {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.setMimeCodec();
|
||||
this.init();
|
||||
this.eventBind();
|
||||
this.eventStart();
|
||||
this.repeat = 0;
|
||||
this.maxRepeat = 5;
|
||||
}
|
||||
|
||||
setMimeCodec() {
|
||||
const { option } = this.art;
|
||||
if (!option.type) {
|
||||
const type = getExt(option.url);
|
||||
errorHandle(
|
||||
Object.keys(config.mimeCodec).includes(type),
|
||||
`Can't find video's type '${type}' from '${option.url}'`
|
||||
);
|
||||
option.type = type;
|
||||
}
|
||||
|
||||
if (!option.mimeCodec) {
|
||||
const mimeCodec = config.mimeCodec[option.type];
|
||||
errorHandle(
|
||||
mimeCodec,
|
||||
`Can't find video's mimeCodec from ${option.type}`
|
||||
);
|
||||
option.mimeCodec = mimeCodec;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
const {
|
||||
option,
|
||||
refs: { $video }
|
||||
} = this.art;
|
||||
errorHandle(
|
||||
MediaSource.isTypeSupported(option.mimeCodec),
|
||||
`Unsupported MIME type or codec: ${option.mimeCodec}`
|
||||
);
|
||||
this.mediaSource = new MediaSource();
|
||||
$video.src = URL.createObjectURL(this.mediaSource);
|
||||
this.art.events.destroyEvents.push(() => {
|
||||
URL.revokeObjectURL($video.src);
|
||||
});
|
||||
}
|
||||
|
||||
eventBind() {
|
||||
const { proxy } = this.art.events;
|
||||
const { mediaSource, sourceBufferList } = config.mse;
|
||||
|
||||
mediaSource.events.forEach(eventName => {
|
||||
proxy(this.mediaSource, eventName, event => {
|
||||
this.art.emit(`mediaSource:${event.type}`, event);
|
||||
});
|
||||
});
|
||||
|
||||
sourceBufferList.events.forEach(eventName => {
|
||||
proxy(this.mediaSource.sourceBuffers, eventName, event => {
|
||||
this.art.emit(`sourceBuffers:${event.type}`, event);
|
||||
});
|
||||
proxy(this.mediaSource.activeSourceBuffers, eventName, event => {
|
||||
this.art.emit(`activeSourceBuffers:${event.type}`, event);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
eventStart() {
|
||||
const { option } = this.art;
|
||||
const { proxy } = this.art.events;
|
||||
const { sourceBuffer } = config.mse;
|
||||
|
||||
this.art.on('mediaSource:sourceopen', () => {
|
||||
this.sourceBuffer = this.mediaSource.addSourceBuffer(option.mimeCodec);
|
||||
sourceBuffer.events.forEach(eventName => {
|
||||
proxy(this.sourceBuffer, eventName, event => {
|
||||
this.art.emit(`sourceBuffer:${event.type}`, event);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.art.on('sourceBuffer:updateend', () => {
|
||||
this.mediaSource.endOfStream();
|
||||
});
|
||||
|
||||
this.fetchUrl();
|
||||
}
|
||||
|
||||
fetchUrl() {
|
||||
const { option: { url }, notice, i18n, loading } = this.art;
|
||||
this.art.emit('player:fetch:start', url);
|
||||
this.request(url)
|
||||
.then(response => {
|
||||
this.sourceBuffer.appendBuffer(response);
|
||||
this.art.emit('player:fetch:success', url);
|
||||
})
|
||||
.catch(err => {
|
||||
if (this.repeat++ < this.maxRepeat) {
|
||||
this.fetchUrl();
|
||||
} else {
|
||||
notice.show(i18n.get('Video load failed'));
|
||||
console.warn(err);
|
||||
loading.hide();
|
||||
this.art.emit('player:fetch:failure', url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
request(url) {
|
||||
return fetch(url)
|
||||
.then(response => response.arrayBuffer())
|
||||
.catch(err => {
|
||||
throw new ArtPlayerError(err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { setStyle } from './utils';
|
||||
|
||||
export default class Notice {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
show(msg, autoHide = true, time = 1000) {
|
||||
const { $notice, $noticeInner } = this.art.refs;
|
||||
setStyle($notice, 'display', 'block');
|
||||
$noticeInner.innerHTML = msg instanceof Error ? msg.message.trim() : msg;
|
||||
if (autoHide) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.hide();
|
||||
}, time);
|
||||
}
|
||||
this.art.emit('notice:show', $notice);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { $notice } = this.art.refs;
|
||||
setStyle($notice, 'display', 'none');
|
||||
this.art.emit('notice:hide', $notice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { setStyle, sublings } from '../utils';
|
||||
|
||||
export default function aspectRatioMix(art, player) {
|
||||
const { refs: { $video, $player }, i18n, notice } = art;
|
||||
|
||||
Object.defineProperty(player, 'aspectRatio', {
|
||||
value: ratio => {
|
||||
const ratioName = ratio.length === 2 ? `${ratio[0]}:${ratio[1]}` : i18n.get('Default');
|
||||
|
||||
if (ratio.length === 2) {
|
||||
const { videoWidth, videoHeight } = $video;
|
||||
const { clientWidth, clientHeight } = $player;
|
||||
const videoRatio = videoWidth / videoHeight;
|
||||
const setupRatio = Number(ratio[0]) / Number(ratio[1]);
|
||||
|
||||
if (videoRatio > setupRatio) {
|
||||
const percentage = setupRatio * videoHeight / videoWidth;
|
||||
setStyle($video, 'width', `${percentage * 100}%`);
|
||||
setStyle($video, 'height', '100%');
|
||||
setStyle($video, 'padding', `0 ${(clientWidth - clientWidth * percentage) / 2}px`);
|
||||
} else {
|
||||
const percentage = videoWidth / setupRatio / videoHeight;
|
||||
setStyle($video, 'width', '100%');
|
||||
setStyle($video, 'height', `${percentage * 100}%`);
|
||||
setStyle($video, 'padding', `${(clientHeight - clientHeight * percentage) / 2}px 0`);
|
||||
}
|
||||
|
||||
$player.dataset.aspectRatio = ratioName;
|
||||
} else {
|
||||
player.aspectRatioRemove();
|
||||
}
|
||||
|
||||
notice.show(`${i18n.get('Aspect ratio')}: ${ratioName}`);
|
||||
art.emit('aspectRatio', ratio);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'aspectRatioRemove', {
|
||||
value: () => {
|
||||
setStyle($video, 'width', null);
|
||||
setStyle($video, 'height', null);
|
||||
setStyle($video, 'padding', null);
|
||||
delete $player.dataset.aspectRatio;
|
||||
if (art.contextmenu.$aspectRatio) {
|
||||
const $default = art.contextmenu.$aspectRatio.querySelector('.default');
|
||||
sublings($default).forEach(item => item.classList.remove('current'));
|
||||
$default.classList.add('current');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'aspectRatioReset', {
|
||||
value: () => {
|
||||
const { aspectRatio } = $player.dataset;
|
||||
if (aspectRatio) {
|
||||
player.aspectRatio(aspectRatio.split(':'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { clamp, sleep } from '../utils';
|
||||
|
||||
export default function attrInit(art, player) {
|
||||
const { option, refs: { $video } } = art;
|
||||
Object.keys(option.moreVideoAttr).forEach(key => {
|
||||
$video[key] = option.moreVideoAttr[key];
|
||||
});
|
||||
$video.volume = clamp(option.volume, 0, 1);
|
||||
$video.poster = option.poster;
|
||||
$video.autoplay = option.autoplay;
|
||||
sleep().then(() => {
|
||||
art.emit('beforeMountUrl', option.url);
|
||||
$video.src = player.mountUrl(option.url);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default function currentTimeMix(art, player) {
|
||||
Object.defineProperty(player, 'currentTime', {
|
||||
value: () => art.refs.$video.currentTime || 0
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default function durationMix(art, player) {
|
||||
Object.defineProperty(player, 'duration', {
|
||||
value: () => art.refs.$video.duration || 0
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import config from '../config';
|
||||
import { sleep } from '../utils';
|
||||
|
||||
export default function eventInit(art, player) {
|
||||
const { option, events: { proxy }, refs: { $player, $video }, i18n, notice } = art;
|
||||
let firstCanplay = false;
|
||||
|
||||
proxy($video, 'click', () => {
|
||||
player.toggle();
|
||||
});
|
||||
|
||||
config.video.events.forEach(eventName => {
|
||||
proxy($video, eventName, event => {
|
||||
art.emit(`video:${event.type}`, event);
|
||||
});
|
||||
});
|
||||
|
||||
art.on('video:loadstart', () => {
|
||||
art.loading.show();
|
||||
});
|
||||
|
||||
art.on('video:loadeddata', () => {
|
||||
art.loading.hide();
|
||||
});
|
||||
|
||||
art.on('video:waiting', () => {
|
||||
art.loading.show();
|
||||
});
|
||||
|
||||
art.on('video:seeking', () => {
|
||||
art.loading.show();
|
||||
});
|
||||
|
||||
art.on('video:canplay', () => {
|
||||
if (!firstCanplay) {
|
||||
firstCanplay = true;
|
||||
art.emit('firstCanplay');
|
||||
}
|
||||
|
||||
art.controls.show();
|
||||
art.mask.show();
|
||||
art.loading.hide();
|
||||
if (option.autoplay) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
|
||||
art.on('video:playing', () => {
|
||||
art.isPlaying = true;
|
||||
art.controls.hide();
|
||||
art.mask.hide();
|
||||
});
|
||||
|
||||
art.on('video:pause', () => {
|
||||
art.isPlaying = false;
|
||||
art.controls.show();
|
||||
art.mask.show();
|
||||
});
|
||||
|
||||
art.on('video:ended', () => {
|
||||
art.isPlaying = false;
|
||||
art.controls.show();
|
||||
art.mask.show();
|
||||
if (option.loop) {
|
||||
player.seek(0);
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
|
||||
art.on('video:error', () => {
|
||||
if (player.reconnectTime < player.maxReconnectTime) {
|
||||
sleep(1000).then(() => {
|
||||
player.reconnectTime++;
|
||||
$video.src = option.url;
|
||||
notice.show(`${i18n.get('Reconnect')}: ${player.reconnectTime}`);
|
||||
});
|
||||
} else {
|
||||
art.isPlaying = false;
|
||||
art.loading.hide();
|
||||
art.controls.hide();
|
||||
$player.classList.add('artplayer-error');
|
||||
sleep(1000).then(() => {
|
||||
notice.show(i18n.get('Video load failed'), false);
|
||||
art.destroy();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import screenfull from 'screenfull';
|
||||
|
||||
export default function fullscreenMix(art, player) {
|
||||
const { notice, events: { destroyEvents }, refs: { $player } } = art;
|
||||
|
||||
const screenfullChange = () => {
|
||||
player.aspectRatioReset();
|
||||
art.emit('fullscreen', screenfull.isFullscreen);
|
||||
};
|
||||
|
||||
const screenfullError = () => {
|
||||
notice.show('Your browser does not seem to support full screen functionality.');
|
||||
};
|
||||
|
||||
screenfull.on('change', screenfullChange);
|
||||
screenfull.on('error', screenfullError);
|
||||
destroyEvents.push(() => {
|
||||
screenfull.off('change', screenfullChange);
|
||||
screenfull.off('error', screenfullError);
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenState', {
|
||||
get: () => screenfull.isFullscreen
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenEnabled', {
|
||||
value: () => {
|
||||
if (player.fullscreenWebState) {
|
||||
player.fullscreenWebExit();
|
||||
}
|
||||
$player.classList.add('artplayer-fullscreen');
|
||||
screenfull.request($player);
|
||||
art.emit('fullscreen:enabled');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenExit', {
|
||||
value: () => {
|
||||
if (player.fullscreenWebState) {
|
||||
player.fullscreenWebExit();
|
||||
}
|
||||
$player.classList.remove('artplayer-fullscreen');
|
||||
screenfull.exit();
|
||||
art.emit('fullscreen:exit');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenToggle', {
|
||||
value: () => {
|
||||
if (player.fullscreenState) {
|
||||
player.fullscreenExit();
|
||||
} else {
|
||||
player.fullscreenEnabled();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
export default function fullscreenWebMix(art, player) {
|
||||
const { refs: { $player } } = art;
|
||||
|
||||
Object.defineProperty(player, 'fullscreenWebState', {
|
||||
get: () => $player.classList.contains('artplayer-web-fullscreen')
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenWebEnabled', {
|
||||
value: () => {
|
||||
if (player.fullscreenState) {
|
||||
player.fullscreenExit();
|
||||
}
|
||||
$player.classList.add('artplayer-web-fullscreen');
|
||||
player.aspectRatioReset();
|
||||
art.emit('fullscreenWeb:enabled');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenWebExit', {
|
||||
value: () => {
|
||||
if (player.fullscreenState) {
|
||||
player.fullscreenExit();
|
||||
}
|
||||
$player.classList.remove('artplayer-web-fullscreen');
|
||||
player.aspectRatioReset();
|
||||
art.emit('fullscreenWeb:exit');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'fullscreenWebToggle', {
|
||||
value: () => {
|
||||
if (player.fullscreenWebState) {
|
||||
player.fullscreenWebExit();
|
||||
} else {
|
||||
player.fullscreenWebEnabled();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import mountUrlMix from './mountUrlMix';
|
||||
import attrInit from './attrInit';
|
||||
import eventInit from './eventInit';
|
||||
import playMix from './playMix';
|
||||
import pauseMin from './pauseMin';
|
||||
import toggleMix from './toggleMix';
|
||||
import seekMix from './seekMix';
|
||||
import volumeMix from './volumeMix';
|
||||
import currentTimeMix from './currentTimeMix';
|
||||
import durationMix from './durationMix';
|
||||
import switchMix from './switchMix';
|
||||
import playbackRateMix from './playbackRateMix';
|
||||
import aspectRatioMix from './aspectRatioMix';
|
||||
import screenshotMix from './screenshotMix';
|
||||
import fullscreenMix from './fullscreenMix';
|
||||
import fullscreenWebMix from './fullscreenWebMix';
|
||||
import pipMix from './pipMix';
|
||||
|
||||
export default class Player {
|
||||
constructor(art) {
|
||||
this.reconnectTime = 0;
|
||||
this.maxReconnectTime = 5;
|
||||
|
||||
mountUrlMix(art, this);
|
||||
attrInit(art, this);
|
||||
eventInit(art, this);
|
||||
playMix(art, this);
|
||||
pauseMin(art, this);
|
||||
toggleMix(art, this);
|
||||
seekMix(art, this);
|
||||
volumeMix(art, this);
|
||||
currentTimeMix(art, this);
|
||||
durationMix(art, this);
|
||||
switchMix(art, this);
|
||||
playbackRateMix(art, this);
|
||||
aspectRatioMix(art, this);
|
||||
screenshotMix(art, this);
|
||||
fullscreenMix(art, this);
|
||||
fullscreenWebMix(art, this);
|
||||
pipMix(art, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default function mountUrlMix(art, player) {
|
||||
Object.defineProperty(player, 'mountUrl', {
|
||||
writable: true,
|
||||
value: url => url
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default function pauseMin(art, player) {
|
||||
const { refs: { $video }, i18n, notice } = art;
|
||||
|
||||
Object.defineProperty(player, 'pause', {
|
||||
value: () => {
|
||||
$video.pause();
|
||||
notice.show(i18n.get('Pause'));
|
||||
art.emit('pause', $video);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import Draggabilly from 'draggabilly';
|
||||
import { setStyle, append } from '../utils';
|
||||
|
||||
export default function pipMix(art, player) {
|
||||
const { option, i18n, refs: { $player, $pipClose, $pipTitle }, events: { destroyEvents, proxy } } = art;
|
||||
let cachePos = null;
|
||||
let draggie = null;
|
||||
|
||||
Object.defineProperty(player, 'pipState', {
|
||||
get: () => $player.classList.contains('artplayer-pip')
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'pipDraggie', {
|
||||
get: () => draggie
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'pipEnabled', {
|
||||
value: () => {
|
||||
if (!draggie) {
|
||||
draggie = new Draggabilly($player, {
|
||||
handle: '.artplayer-pip-header'
|
||||
});
|
||||
|
||||
append($pipTitle, option.title || i18n.get('Mini player'));
|
||||
|
||||
proxy($pipClose, 'click', () => {
|
||||
player.pipExit();
|
||||
});
|
||||
|
||||
destroyEvents.push(() => {
|
||||
draggie.destroy();
|
||||
});
|
||||
} else {
|
||||
setStyle($player, 'left', `${cachePos.x}px`);
|
||||
setStyle($player, 'top', `${cachePos.y}px`);
|
||||
}
|
||||
$player.classList.add('artplayer-pip');
|
||||
player.fullscreenExit();
|
||||
player.fullscreenWebExit();
|
||||
player.aspectRatioRemove();
|
||||
player.playbackRateRemove();
|
||||
art.emit('pip', true);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'pipExit', {
|
||||
value: () => {
|
||||
$player.classList.remove('artplayer-pip');
|
||||
cachePos = draggie.position;
|
||||
setStyle($player, 'left', null);
|
||||
setStyle($player, 'top', null);
|
||||
player.fullscreenExit();
|
||||
player.fullscreenWebExit();
|
||||
player.aspectRatioRemove();
|
||||
player.playbackRateRemove();
|
||||
art.emit('pip', false);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'pipToggle', {
|
||||
value: () => {
|
||||
if (player.pipState) {
|
||||
player.pipExit();
|
||||
} else {
|
||||
player.pipEnabled();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export default function playMix(art, player) {
|
||||
const { refs: { $video }, i18n, notice, option: { mutex } } = art;
|
||||
|
||||
Object.defineProperty(player, 'play', {
|
||||
value: () => {
|
||||
const promise = $video.play();
|
||||
if (promise !== undefined) {
|
||||
promise.then().catch(err => {
|
||||
notice.show(err, true, 3000);
|
||||
console.warn(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (mutex) {
|
||||
art.constructor.instances
|
||||
.filter(item => item !== art)
|
||||
.forEach(item => item.player.pause());
|
||||
}
|
||||
|
||||
notice.show(i18n.get('Play'));
|
||||
art.emit('play', $video);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { clamp, sublings } from '../utils';
|
||||
|
||||
export default function playbackRateMix(art, player) {
|
||||
const { refs: { $video, $player }, i18n, notice } = art;
|
||||
|
||||
Object.defineProperty(player, 'playbackRate', {
|
||||
value: rate => {
|
||||
const newRate = clamp(rate, 0.1, 10);
|
||||
$video.playbackRate = newRate;
|
||||
$player.dataset.playbackRate = newRate;
|
||||
notice.show(`${i18n.get('Rate')}: ${newRate === 1 ? i18n.get('Normal') : `${newRate}x`}`);
|
||||
art.emit('playbackRate', newRate);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'playbackRateRemove', {
|
||||
value: () => {
|
||||
player.playbackRate(1);
|
||||
delete $player.dataset.playbackRate;
|
||||
if (art.contextmenu.$playbackRate) {
|
||||
const $normal = art.contextmenu.$playbackRate.querySelector('.normal');
|
||||
sublings($normal).forEach(item => item.classList.remove('current'));
|
||||
$normal.classList.add('current');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(player, 'playbackRateReset', {
|
||||
value: () => {
|
||||
const { playbackRate } = $player.dataset;
|
||||
if (playbackRate) {
|
||||
player.playbackRate(Number(playbackRate));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { setStyle, secondToTime } from '../utils';
|
||||
|
||||
export default function screenshotMix(art, player) {
|
||||
const { option, notice } = art;
|
||||
|
||||
function captureFrame() {
|
||||
const { $video } = art.refs;
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = $video.videoWidth;
|
||||
canvas.height = $video.videoHeight;
|
||||
canvas.getContext('2d').drawImage($video, 0, 0);
|
||||
const dataUri = canvas.toDataURL('image/png');
|
||||
const elink = document.createElement('a');
|
||||
setStyle(elink, 'display', 'none');
|
||||
elink.href = dataUri;
|
||||
elink.download = `${option.title || 'artplayer'}_${secondToTime($video.currentTime)}.png`;
|
||||
document.body.appendChild(elink);
|
||||
elink.click();
|
||||
document.body.removeChild(elink);
|
||||
}
|
||||
|
||||
Object.defineProperty(player, 'screenshot', {
|
||||
value: () => {
|
||||
try {
|
||||
captureFrame();
|
||||
art.emit('screenshot');
|
||||
} catch (error) {
|
||||
notice.show(error);
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { secondToTime } from '../utils';
|
||||
|
||||
export default function seekMix(art, player) {
|
||||
const { refs: { $video }, notice } = art;
|
||||
|
||||
Object.defineProperty(player, 'seek', {
|
||||
value: time => {
|
||||
let newTime = Math.max(time, 0);
|
||||
if ($video.duration) {
|
||||
newTime = Math.min(newTime, $video.duration);
|
||||
}
|
||||
$video.currentTime = newTime;
|
||||
notice.show(`${secondToTime(newTime)} / ${secondToTime($video.duration)}`);
|
||||
art.emit('seek', newTime);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export default function switchMix(art, player) {
|
||||
const { refs: { $video }, i18n, notice, isPlaying, option } = art;
|
||||
|
||||
Object.defineProperty(player, 'switch', {
|
||||
value: (url, name = 'unknown') => {
|
||||
const currentTime = player.currentTime();
|
||||
art.emit('beforeMountUrl', url);
|
||||
$video.src = player.mountUrl(url);
|
||||
option.url = url;
|
||||
player.playbackRateRemove();
|
||||
player.aspectRatioRemove();
|
||||
player.reconnectTime = 0;
|
||||
player.seek(currentTime);
|
||||
if (isPlaying) {
|
||||
player.play();
|
||||
}
|
||||
notice.show(`${i18n.get('Switch video')}: ${name}`);
|
||||
art.emit('switch', url);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export default function toggleMix(art, player) {
|
||||
Object.defineProperty(player, 'toggle', {
|
||||
value: () => {
|
||||
if (art.isPlaying) {
|
||||
player.pause();
|
||||
} else {
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { clamp, setStorage } from '../utils';
|
||||
|
||||
export default function volumeMix(art, player) {
|
||||
const { refs: { $video }, i18n, notice } = art;
|
||||
|
||||
Object.defineProperty(player, 'volume', {
|
||||
value: percentage => {
|
||||
if (percentage !== undefined) {
|
||||
$video.volume = clamp(percentage, 0, 1);
|
||||
notice.show(`${i18n.get('Volume')}: ${parseInt($video.volume * 100)}`);
|
||||
if ($video.volume !== 0) {
|
||||
setStorage('volume', $video.volume);
|
||||
}
|
||||
art.emit('volume', $video.volume);
|
||||
}
|
||||
return $video.volume || 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import validElement from './validElement';
|
||||
|
||||
export default {
|
||||
container: {
|
||||
validator: validElement,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
title: 'string',
|
||||
volume: 'number',
|
||||
thumbnails: {
|
||||
type: 'object',
|
||||
child: {
|
||||
url: 'string',
|
||||
number: 'number',
|
||||
width: 'number',
|
||||
height: 'number',
|
||||
column: 'number'
|
||||
}
|
||||
},
|
||||
screenshot: 'boolean',
|
||||
autoplay: 'boolean',
|
||||
playbackRate: 'boolean',
|
||||
aspectRatio: 'boolean',
|
||||
loop: 'boolean',
|
||||
type: {
|
||||
type: 'string'
|
||||
},
|
||||
mimeCodec: 'string',
|
||||
layers: {
|
||||
type: 'array',
|
||||
child: {
|
||||
name: 'string',
|
||||
index: 'number',
|
||||
html: {
|
||||
validator: validElement
|
||||
},
|
||||
style: 'object'
|
||||
}
|
||||
},
|
||||
contextmenu: {
|
||||
type: 'array',
|
||||
child: {
|
||||
type: 'object|function',
|
||||
name: 'string',
|
||||
html: {
|
||||
validator: validElement
|
||||
},
|
||||
click: 'function'
|
||||
}
|
||||
},
|
||||
quality: {
|
||||
type: 'array',
|
||||
child: {
|
||||
default: 'boolean',
|
||||
name: 'string',
|
||||
url: 'string'
|
||||
}
|
||||
},
|
||||
loading: validElement,
|
||||
play: validElement,
|
||||
theme: 'string',
|
||||
hotkey: 'boolean',
|
||||
pip: 'boolean',
|
||||
mutex: 'boolean',
|
||||
fullscreen: 'boolean',
|
||||
fullscreenWeb: 'boolean',
|
||||
subtitle: {
|
||||
type: 'object',
|
||||
child: {
|
||||
url: 'string',
|
||||
style: 'object'
|
||||
}
|
||||
},
|
||||
controls: {
|
||||
type: 'array',
|
||||
child: {
|
||||
option: {
|
||||
type: 'object',
|
||||
child: {
|
||||
disable: 'boolean',
|
||||
position: 'boolean',
|
||||
index: 'number'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
highlight: {
|
||||
type: 'array',
|
||||
child: {
|
||||
time: 'number',
|
||||
text: 'string'
|
||||
}
|
||||
},
|
||||
moreVideoAttr: 'object',
|
||||
lang: 'string'
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ArtPlayerError } from '../utils';
|
||||
|
||||
export default function validElement(paths, value, type) {
|
||||
if (type === 'string') {
|
||||
if (value.trim() === '') {
|
||||
throw new ArtPlayerError(`${paths.join('.')} can not be empty'`);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Element) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ArtPlayerError(`${paths.join('.')} require 'string' or 'Element' type, but got '${type}'`);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { append } from '../utils';
|
||||
|
||||
export default class Common {
|
||||
constructor(option) {
|
||||
this.option = option;
|
||||
}
|
||||
|
||||
apply(art, $setting) {
|
||||
this.art = art;
|
||||
this.$header = $setting.querySelector('.art-setting-header');
|
||||
this.$body = $setting.querySelector('.art-setting-body');
|
||||
append(this.$body, '———— 先占坑,暂无设置 ————');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { setStyle, append } from '../utils';
|
||||
import Common from './common';
|
||||
|
||||
let id = 0;
|
||||
export default class Setting {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.state = false;
|
||||
this.$settings = [];
|
||||
if (art.option.setting) {
|
||||
this.init();
|
||||
this.mount();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
const { refs: { $settingClose }, events: { proxy } } = this.art;
|
||||
|
||||
proxy($settingClose, 'click', () => {
|
||||
this.hide();
|
||||
});
|
||||
|
||||
this.add(new Common({
|
||||
name: 'common',
|
||||
title: 'Common',
|
||||
disable: false,
|
||||
index: 10
|
||||
}));
|
||||
}
|
||||
|
||||
add(setting) {
|
||||
const { option } = setting;
|
||||
if (option && !option.disable) {
|
||||
id++;
|
||||
const name = option.name || setting.constructor.name.toLowerCase() || `setting${id}`;
|
||||
const title = option.title || name;
|
||||
const $setting = document.createElement('div');
|
||||
$setting.setAttribute('class', `art-setting art-setting-${name}`);
|
||||
$setting.dataset.settingIndex = option.index || id;
|
||||
append($setting, `<div class="art-setting-header">${this.art.i18n.get(title)}</div>`);
|
||||
append($setting, '<div class="art-setting-body"></div>');
|
||||
this.$settings.push($setting);
|
||||
setting.apply && setting.apply(this.art, $setting);
|
||||
this[name] = setting;
|
||||
}
|
||||
}
|
||||
|
||||
mount() {
|
||||
this.$settings
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Number(a.dataset.settingIndex) - Number(b.dataset.settingIndex)
|
||||
)
|
||||
.forEach($setting => {
|
||||
append(this.art.refs.$settingBody, $setting);
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
const { refs: { $setting }, i18n, notice } = this.art;
|
||||
setStyle($setting, 'display', 'flex');
|
||||
this.state = true;
|
||||
notice.show(i18n.get('Show setting'));
|
||||
this.art.emit('setting:show', $setting);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { refs: { $setting }, i18n, notice } = this.art;
|
||||
setStyle($setting, 'display', 'none');
|
||||
this.state = false;
|
||||
notice.show(i18n.get('Hide setting'));
|
||||
this.art.emit('setting:hide', $setting);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.state) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.artplayer-video-player {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
outline: 0;
|
||||
font-family: Roboto,Arial,Helvetica,sans-serif;
|
||||
color: #eee;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
font-size: 14px;
|
||||
line-height: 1.3;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
touch-action: manipulation;
|
||||
-ms-high-contrast-adjust: none;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.art-icon {
|
||||
display: inline-block;
|
||||
line-height: 1.5;
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.artplayer-video-player {
|
||||
&[data-aspect-ratio] {
|
||||
video {
|
||||
box-sizing: content-box;
|
||||
object-fit: fill;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-bottom {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100px;
|
||||
padding: 60px 10px 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all .2s ease-in-out;
|
||||
pointer-events: none;
|
||||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==) repeat-x bottom;
|
||||
|
||||
.artplayer-progress {
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
.art-control-progress {
|
||||
position: relative;
|
||||
height: 5px;
|
||||
padding: 1px 0;
|
||||
cursor: pointer;
|
||||
|
||||
.art-control-progress-inner {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: rgba(255,255,255,.2);
|
||||
.art-progress-loaded {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background: rgba(255,255,255,.4);
|
||||
}
|
||||
|
||||
.art-progress-played {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.art-progress-highlight {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 0;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 7px;
|
||||
height: 3px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.art-progress-indicator {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
top: -5px;
|
||||
left: -6.5px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.show-indicator {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.art-progress-tip {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: -25px;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
padding: 0 5px;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background: rgba(0,0,0,.7);
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.art-progress-indicator, .art-progress-tip {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.art-control-thumbnails {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
background-color: rgba(0,0,0,.7);
|
||||
}
|
||||
}
|
||||
|
||||
.artplayer-controls {
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 36px;
|
||||
|
||||
.art-control {
|
||||
opacity: .9;
|
||||
float: left;
|
||||
font-size: 12px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
.art-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
float: left;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
padding: 7px;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.art-control-volume {
|
||||
.art-volume-panel {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
transition: margin .2s cubic-bezier(0.4,0.0,1,1),width .2s cubic-bezier(0.4,0.0,1,1);
|
||||
overflow: hidden;
|
||||
|
||||
.art-volume-slider-handle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
margin-top: -6px;
|
||||
background: #fff;
|
||||
|
||||
&::before {
|
||||
left: -54px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 6px;
|
||||
background: rgba(255,255,255,.2);
|
||||
}
|
||||
|
||||
&::before, &::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
height: 3px;
|
||||
margin-top: -2px;
|
||||
width: 60px
|
||||
}
|
||||
}
|
||||
|
||||
&.art-volume-panel-hover {
|
||||
width: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.art-control-time {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.art-control-quality {
|
||||
position: relative;
|
||||
z-index: 30;
|
||||
padding: 0 10px;
|
||||
.art-qualitys {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
bottom: 35px;
|
||||
width: 100px;
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,.7);
|
||||
border-radius: 3px;
|
||||
.art-quality-item {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
&:hover {
|
||||
background-color: rgba(255,255,255,.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hover .art-qualitys{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.artplayer-controls-show, &.artplayer-hover {
|
||||
.artplayer-bottom {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&.artplayer-error, &.artplayer-destroy {
|
||||
.art-progress-indicator, .art-progress-tip {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-contextmenu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 110;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-width: 200px;
|
||||
padding: 5px 0;
|
||||
background:rgba(0, 0, 0, 0.8);
|
||||
border-radius: 3px;
|
||||
|
||||
.art-menu {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
color: #fff;
|
||||
padding: 7px 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 1px solid rgba(255,255,255,.1);
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 0 7px;
|
||||
&:hover, &.current {
|
||||
color: #00c9ff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255,255,255,.1);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.artplayer-video-player {
|
||||
&.artplayer-web-fullscreen {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.artplayer-video-player {
|
||||
&.artplayer-hide-cursor {
|
||||
* {
|
||||
cursor: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import './artplayer.scss';
|
||||
import './video.scss';
|
||||
import './subtitle.scss';
|
||||
import './layers.scss';
|
||||
import './mask.scss';
|
||||
import './loading.scss';
|
||||
import './bottom.scss';
|
||||
import './notice.scss';
|
||||
import './contextmenu.scss';
|
||||
import './setting.scss';
|
||||
import './info.scss';
|
||||
import './hideCursor.scss';
|
||||
import './aspectRatio.scss';
|
||||
import './fullscreenWeb.scss';
|
||||
import './pip.scss';
|
||||
@@ -0,0 +1,38 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-info {
|
||||
display: none;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 90;
|
||||
width: 350px;
|
||||
min-height: 150px;
|
||||
padding: 10px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-family: Noto Sans CJK SC DemiLight,Roboto,Segoe UI,Tahoma,Arial,Helvetica,sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background: rgba(0,0,0,.7);
|
||||
|
||||
.art-info-item {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
.art-info-title {
|
||||
width: 100px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.art-info-content {
|
||||
flex: 1;
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.artplayer-info-close {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-layers {
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
.art-layer {
|
||||
position: absolute;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-loading {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 60;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-mask {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
|
||||
.art-icon-playBig {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 65px;
|
||||
opacity: .6;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-notice {
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
z-index: 70;
|
||||
left: 0;
|
||||
top: 0;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
.artplayer-notice-inner {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-pip-header {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all .2s ease-in-out;
|
||||
.artplayer-pip-title {
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: move;
|
||||
}
|
||||
.artplayer-pip-close {
|
||||
width: 35px;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-dragging {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.artplayer-pip {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
width: 400px;
|
||||
height: 225px;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 3px 6px 0 rgba(0,0,0,0.20);
|
||||
|
||||
.artplayer-pip-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.artplayer-hover {
|
||||
.artplayer-pip-header {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.artplayer-mask {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.art-icon-playBig {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
.artplayer-contextmenu,
|
||||
.artplayer-bottom,
|
||||
.artplayer-danmu,
|
||||
.artplayer-info,
|
||||
.artplayer-layers,
|
||||
.artplayer-notice,
|
||||
.artplayer-setting,
|
||||
.artplayer-subtitle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-setting {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 80;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
.artplayer-setting-inner {
|
||||
position: relative;
|
||||
width: 350px;
|
||||
height: 250px;
|
||||
font-size: 12px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 3px;
|
||||
pointer-events: all;
|
||||
overflow: hidden;
|
||||
.artplayer-setting-body {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 365px;
|
||||
height: 100%;
|
||||
.art-setting {
|
||||
border-bottom: 1px solid rgba(255,255,255,.1);
|
||||
padding: 12px 35px 12px 20px;
|
||||
.art-setting-header {
|
||||
color: #fff;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.art-setting-body {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
.artplayer-setting-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, .8);
|
||||
background: rgba(0, 0, 0, .5);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-subtitle {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
bottom: 40px;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
text-shadow: 0.5px 0.5px 0.5px rgba(0, 0, 0, 0.5);
|
||||
font-size: 20px;
|
||||
pointer-events: none;
|
||||
|
||||
p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.artplayer-video-player {
|
||||
.artplayer-video {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { errorHandle, getExt, setStyles, setStyle } from './utils';
|
||||
|
||||
export default class Subtitle {
|
||||
constructor(art) {
|
||||
this.art = art;
|
||||
this.state = true;
|
||||
this.vttText = '';
|
||||
const { url } = this.art.option.subtitle;
|
||||
if (url) {
|
||||
this.checkExt(url);
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
const { events: { proxy }, option: { subtitle }, refs: { $video, $subtitle } } = this.art;
|
||||
setStyles($subtitle, subtitle.style || {});
|
||||
const $track = document.createElement('track');
|
||||
$track.default = true;
|
||||
$track.kind = 'metadata';
|
||||
this.load(subtitle.url).then(data => {
|
||||
$track.src = data;
|
||||
$video.appendChild($track);
|
||||
this.art.refs.$track = $track;
|
||||
if ($video.textTracks && $video.textTracks[0]) {
|
||||
const [track] = $video.textTracks;
|
||||
proxy(track, 'cuechange', () => {
|
||||
const [cue] = track.activeCues;
|
||||
$subtitle.innerHTML = '';
|
||||
if (cue) {
|
||||
const template = document.createElement('div');
|
||||
template.appendChild(cue.getCueAsHTML());
|
||||
$subtitle.innerHTML = template.innerHTML
|
||||
.split(/\r?\n/)
|
||||
.map(item => `<p>${item}</p>`)
|
||||
.join('');
|
||||
}
|
||||
this.art.emit('subtitle:update', $subtitle);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
load(url) {
|
||||
const { notice } = this.art;
|
||||
let type;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
type = response.headers.get('Content-Type');
|
||||
return response.text();
|
||||
})
|
||||
.then(text => {
|
||||
if ((/x-subrip/gi).test(type)) {
|
||||
this.vttText = this.srtToVtt(text);
|
||||
} else {
|
||||
this.vttText = text;
|
||||
}
|
||||
return this.vttToBlob(this.vttText);
|
||||
})
|
||||
.catch(err => {
|
||||
notice.show(err);
|
||||
console.warn(err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
srtToVtt(text) {
|
||||
return 'WEBVTT \r\n\r\n'.concat(
|
||||
text
|
||||
.replace(/\{\\([ibu])\}/g, '</$1>')
|
||||
.replace(/\{\\([ibu])1\}/g, '<$1>')
|
||||
.replace(/\{([ibu])\}/g, '<$1>')
|
||||
.replace(/\{\/([ibu])\}/g, '</$1>')
|
||||
.replace(/(\d\d:\d\d:\d\d),(\d\d\d)/g, '$1.$2')
|
||||
.concat('\r\n\r\n')
|
||||
);
|
||||
}
|
||||
|
||||
vttToBlob(vttText) {
|
||||
return URL.createObjectURL(
|
||||
new Blob([vttText], {
|
||||
type: 'text/vtt'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
checkExt(url) {
|
||||
const ext = getExt(url);
|
||||
errorHandle(
|
||||
ext === 'vtt' || ext === 'srt',
|
||||
`'subtitle.url' option require 'vtt' or 'srt' format, but got '${ext}'.`
|
||||
);
|
||||
}
|
||||
|
||||
show() {
|
||||
const { refs: { $subtitle }, i18n, notice } = this.art;
|
||||
setStyle($subtitle, 'display', 'block');
|
||||
this.state = true;
|
||||
notice.show(i18n.get('Show subtitle'));
|
||||
this.art.emit('subtitle:show', $subtitle);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { refs: { $subtitle }, i18n, notice } = this.art;
|
||||
setStyle($subtitle, 'display', 'none');
|
||||
this.state = false;
|
||||
notice.show(i18n.get('Hide subtitle'));
|
||||
this.art.emit('subtitle:hide', $subtitle);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if (this.state) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
switch(url) {
|
||||
const { $track } = this.art.refs;
|
||||
this.checkExt(url);
|
||||
errorHandle($track, 'You need to initialize the subtitle option first.');
|
||||
this.load(url).then(data => {
|
||||
$track.src = data;
|
||||
this.art.emit('subtitle:switch', url);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
export default class Template {
|
||||
constructor(art) {
|
||||
const { refs } = art;
|
||||
refs.$container.innerHTML = `
|
||||
<div class="artplayer-video-player">
|
||||
<video class="artplayer-video"></video>
|
||||
<div class="artplayer-subtitle"></div>
|
||||
<div class="artplayer-layers"></div>
|
||||
<div class="artplayer-mask"></div>
|
||||
<div class="artplayer-bottom">
|
||||
<div class="artplayer-progress"></div>
|
||||
<div class="artplayer-controls">
|
||||
<div class="artplayer-controls-left"></div>
|
||||
<div class="artplayer-controls-right"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="artplayer-loading"></div>
|
||||
<div class="artplayer-notice">
|
||||
<div class="artplayer-notice-inner"></div>
|
||||
</div>
|
||||
<div class="artplayer-setting">
|
||||
<div class="artplayer-setting-inner">
|
||||
<div class="artplayer-setting-body"></div>
|
||||
<div class="artplayer-setting-close">×</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="artplayer-info">
|
||||
<div class="artplayer-info-panel"></div>
|
||||
<div class="artplayer-info-close">[x]</div>
|
||||
</div>
|
||||
<div class="artplayer-pip-header">
|
||||
<div class="artplayer-pip-title"></div>
|
||||
<div class="artplayer-pip-close">×</div>
|
||||
</div>
|
||||
<div class="artplayer-contextmenu"></div>
|
||||
</div>
|
||||
`;
|
||||
refs.$player = refs.$container.querySelector('.artplayer-video-player');
|
||||
refs.$video = refs.$container.querySelector('.artplayer-video');
|
||||
refs.$subtitle = refs.$container.querySelector('.artplayer-subtitle');
|
||||
refs.$bottom = refs.$container.querySelector('.artplayer-bottom');
|
||||
refs.$progress = refs.$container.querySelector('.artplayer-progress');
|
||||
refs.$controls = refs.$container.querySelector('.artplayer-controls');
|
||||
refs.$controlsLeft = refs.$container.querySelector('.artplayer-controls-left');
|
||||
refs.$controlsRight = refs.$container.querySelector('.artplayer-controls-right');
|
||||
refs.$layers = refs.$container.querySelector('.artplayer-layers');
|
||||
refs.$loading = refs.$container.querySelector('.artplayer-loading');
|
||||
refs.$notice = refs.$container.querySelector('.artplayer-notice');
|
||||
refs.$noticeInner = refs.$container.querySelector('.artplayer-notice-inner');
|
||||
refs.$mask = refs.$container.querySelector('.artplayer-mask');
|
||||
refs.$setting = refs.$container.querySelector('.artplayer-setting');
|
||||
refs.$settingInner = refs.$container.querySelector('.artplayer-setting-inner');
|
||||
refs.$settingBody = refs.$container.querySelector('.artplayer-setting-body');
|
||||
refs.$settingClose = refs.$container.querySelector('.artplayer-setting-close');
|
||||
refs.$info = refs.$container.querySelector('.artplayer-info');
|
||||
refs.$infoPanel = refs.$container.querySelector('.artplayer-info-panel');
|
||||
refs.$infoClose = refs.$container.querySelector('.artplayer-info-close');
|
||||
refs.$pipHeader = refs.$container.querySelector('.artplayer-pip-header');
|
||||
refs.$pipTitle = refs.$container.querySelector('.artplayer-pip-title');
|
||||
refs.$pipClose = refs.$container.querySelector('.artplayer-pip-close');
|
||||
refs.$contextmenu = refs.$container.querySelector('.artplayer-contextmenu');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
export const instances = [];
|
||||
|
||||
export class ArtPlayerError extends Error {
|
||||
constructor(message, context) {
|
||||
super(message);
|
||||
if (typeof Error.captureStackTrace === 'function') {
|
||||
Error.captureStackTrace(this, context || this.constructor);
|
||||
}
|
||||
this.name = 'ArtPlayerError';
|
||||
}
|
||||
}
|
||||
|
||||
export function errorHandle(condition, msg) {
|
||||
const isFun = typeof condition === 'function';
|
||||
if (isFun ? !isFun() : !condition) {
|
||||
throw new ArtPlayerError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
export function clamp(num, a, b) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
|
||||
}
|
||||
|
||||
export function getExt(url) {
|
||||
if (url.includes('?')) {
|
||||
return getExt(url.split('?')[0]);
|
||||
}
|
||||
|
||||
if (url.includes('#')) {
|
||||
return getExt(url.split('#')[0]);
|
||||
}
|
||||
|
||||
return url
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split('.')
|
||||
.pop();
|
||||
}
|
||||
|
||||
export function append(parent, child) {
|
||||
if (child instanceof Element) {
|
||||
parent.appendChild(child);
|
||||
} else {
|
||||
parent.insertAdjacentHTML('beforeend', child);
|
||||
}
|
||||
return parent.lastElementChild;
|
||||
}
|
||||
|
||||
export function insertByIndex(parent, child, index) {
|
||||
const childs = Array.from(parent.children);
|
||||
child.dataset.index = index;
|
||||
const nextChild = childs.find(item => Number(item.dataset.index) >= Number(index));
|
||||
if (nextChild) {
|
||||
nextChild.insertAdjacentElement('beforebegin', child);
|
||||
} else {
|
||||
append(parent, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
export function setStyle(element, key, value) {
|
||||
element.style[key] = value;
|
||||
return element;
|
||||
}
|
||||
|
||||
export function setStyles(element, styles) {
|
||||
Object.keys(styles).forEach(key => {
|
||||
setStyle(element, key, styles[key]);
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
export function secondToTime(second) {
|
||||
const add0 = num => num < 10 ? `0${num}` : String(num);
|
||||
const hour = Math.floor(second / 3600);
|
||||
const min = Math.floor((second - hour * 3600) / 60);
|
||||
const sec = Math.floor(second - hour * 3600 - min * 60);
|
||||
return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':');
|
||||
}
|
||||
|
||||
export function deepMerge(...sources) {
|
||||
const isObject = value => value !== null && typeof value === 'object';
|
||||
let returnValue = {};
|
||||
for (const source of sources) {
|
||||
if (Array.isArray(source)) {
|
||||
if (!Array.isArray(returnValue)) {
|
||||
returnValue = [];
|
||||
}
|
||||
returnValue = [...returnValue, ...source];
|
||||
} else if (isObject(source)) {
|
||||
if (source instanceof Element) {
|
||||
return source;
|
||||
}
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
let value = source[key];
|
||||
if (isObject(value) && key in returnValue) {
|
||||
value = deepMerge(returnValue[key], value);
|
||||
}
|
||||
returnValue = { ...returnValue, [key]: value };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
export function getStorage(key) {
|
||||
const storage = JSON.parse(localStorage.getItem('artplayer_settings')) || {};
|
||||
return key ? storage[key] : storage;
|
||||
}
|
||||
|
||||
export function setStorage(key, value) {
|
||||
const storage = Object.assign({}, getStorage(), {
|
||||
[key]: value
|
||||
});
|
||||
localStorage.setItem('artplayer_settings', JSON.stringify(storage));
|
||||
}
|
||||
|
||||
export function tooltip(target, msg, pos = 'up') {
|
||||
target.setAttribute('data-balloon', msg);
|
||||
target.setAttribute('data-balloon-pos', pos);
|
||||
}
|
||||
|
||||
export function sleep(ms = 0) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function sublings(target) {
|
||||
return Array.from(target.parentElement.children).filter(item => item !== target);
|
||||
}
|
||||
|
||||
export function debounce(func, wait, context) {
|
||||
let timeout;
|
||||
function fn() {
|
||||
const args = arguments;
|
||||
const later = function later() {
|
||||
timeout = null;
|
||||
func.apply(context, args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
}
|
||||
|
||||
fn.clearTimeout = function ct() {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
|
||||
return fn;
|
||||
}
|
||||