修改包管理

This commit is contained in:
Harvey Zack committed 2018-11-07 17:12:42 +08:00
1 parent c5c673ae79
commit 0aa018db6d
99 files changed
+70 -70

No files matched your search

-21
View File
@@ -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})});
+11
View File
@@ -0,0 +1,11 @@
{
"presets": [
[
"@babel/env",
{
"modules": false
}
]
],
"plugins": ["@babel/plugin-external-helpers", "@babel/plugin-transform-runtime"]
}
+3
View File
@@ -0,0 +1,3 @@
# artplayer
ArtPlayer is a modern HTML5 video player
+16
View File
@@ -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
+8
View File
@@ -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
+54
View File
@@ -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"
}
}
+65
View File
@@ -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
}
})
]
};
+9
View File
@@ -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"'
};
+58
View File
@@ -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'
]
}
};
+64
View File
@@ -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>'
};
+25
View File
@@ -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'));
});
}
}
+153
View File
@@ -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');
}
}
+17
View File
@@ -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');
});
}
}
+148
View File
@@ -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`);
}
}
}
+27
View File
@@ -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();
});
}
}
+101
View File
@@ -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`);
}
}
+72
View File
@@ -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());
}
}
+50
View File
@@ -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;
}
}
}
});
}
}
+84
View File
@@ -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();
}
}
+3
View File
@@ -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

+41
View File
@@ -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);
+36
View File
@@ -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

+3
View File
@@ -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

+3
View File
@@ -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

+3
View File
@@ -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

+4
View File
@@ -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

+3
View File
@@ -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

+152
View File
@@ -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;
+141
View File
@@ -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);
}
}
+39
View File
@@ -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);
}
}
+22
View File
@@ -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);
}
}
+22
View File
@@ -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);
}
}
+121
View File
@@ -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);
});
}
}
+27
View File
@@ -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(':'));
}
}
});
}
+15
View File
@@ -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();
}
}
});
}
+42
View File
@@ -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
});
}
+11
View File
@@ -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);
}
});
}
+69
View File
@@ -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();
}
}
});
}
+24
View File
@@ -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);
}
}
});
}
+17
View File
@@ -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;
}
});
}
+100
View File
@@ -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}'`);
}
+14
View File
@@ -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, '———— 先占坑,暂无设置 ————');
}
}
+82
View File
@@ -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;
}
}
}
+248
View File
@@ -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;
}
}
}
+15
View File
@@ -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';
+38
View File
@@ -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;
}
}
}
+18
View File
@@ -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;
}
}
}
+16
View File
@@ -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;
}
}
+21
View File
@@ -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;
}
}
}
+20
View File
@@ -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;
}
}
}
+76
View File
@@ -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;
}
}
}
+54
View File
@@ -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;
}
}
}
+15
View File
@@ -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;
}
}
+128
View File
@@ -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);
});
}
}
+63
View File
@@ -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');
}
}
+150
View File
@@ -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;
}