mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
完成截图工具
This commit is contained in:
1 parent
0ba8efc2b9
commit
5078533ad8
14 files changed
+667
-305
No files matched your search
@@ -82,10 +82,10 @@ var art = new Artplayer({
|
||||
|
||||
## Ecosystem
|
||||
|
||||
| Project | Description | Demo |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------ |
|
||||
| [artplayer-plugin-danmu](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-plugin-danmu) | Danmu plugin for ArtPlayer | `None` |
|
||||
| [artplayer-tool-thumbnail](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-tool-thumbnail) | Thumbnail tool for ArtPlayer | `None` |
|
||||
| Project | Description | Demo |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------- |
|
||||
| [artplayer-plugin-danmu](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-plugin-danmu) | Danmu plugin for ArtPlayer | `WIP` |
|
||||
| [artplayer-tool-thumbnail](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-tool-thumbnail) | Thumbnail tool for ArtPlayer | [demo](https://artplayer.org/thumbnail/) |
|
||||
|
||||
## QQ Group
|
||||
|
||||
|
||||
@@ -90,7 +90,8 @@ html, body {
|
||||
}
|
||||
|
||||
.left-part .setup .item .name{
|
||||
width: 50px;
|
||||
width: 65px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.left-part .setup .item .input {
|
||||
@@ -122,6 +123,10 @@ html, body {
|
||||
background: #97a8be;
|
||||
}
|
||||
|
||||
.left-part .btns .btn.create {
|
||||
background: #3ca5ea;
|
||||
}
|
||||
|
||||
.left-part .btns .btn.download {
|
||||
background: #3ca5ea;
|
||||
}
|
||||
@@ -131,17 +136,29 @@ html, body {
|
||||
}
|
||||
|
||||
.rigth-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rigth-part .img {
|
||||
.rigth-part .preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 70%;
|
||||
border: 2px dashed #d9d9d9;
|
||||
background-color: #f5f5f5;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: contain;
|
||||
}
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rigth-part .preview canvas {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.rigth-part .console {
|
||||
position: relative;
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.consola-container{opacity:.9;display:flex;flex-direction:column;position:absolute;background-color:#242424}.consola-container.right{top:0;right:0;bottom:0;height:100%;width:0}.consola-container.bottom{left:0;right:0;bottom:0;width:100%;height:0}.consola-container .consola-tools{left:0;right:0;top:0;z-index:2;display:flex;justify-content:space-between;align-items:center;flex-direction:row;height:30px;padding:0 10px;color:hsla(0,0%,100%,.8);background-color:#141618;border-bottom:1px solid rgba(0,0,0,.3);font-size:12px;box-sizing:border-box}.consola-container .consola-tools .consola-clean{cursor:pointer}.consola-container .consola-wrap{flex:1;position:relative;z-index:1;overflow-y:auto;word-break:break-word;box-sizing:border-box}.consola-container .consola-wrap .method.method-log{position:relative;display:flex;color:hsla(0,0%,100%,.9);background-color:transparent;border-top:1px solid hsla(0,0%,100%,.05);border-bottom:1px solid hsla(0,0%,100%,.05);margin-top:-1px;margin-bottom:0;padding-left:10px;box-sizing:border-box}.consola-container .consola-wrap .method.method-log .method-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABpSURBVChTY6A92LNnj96uXbvKoVzsYMeOHVbbt29/D1T4eP/+/QJQYVSwe/duD6CCr0B8A8iWgwqjAqBk2NatW38B6bPbtm0TBYkBFbsA+c9ANFgRCBCtEASAAoSthgGiPAMD2IOHgQEA521bM7uG52wAAAAASUVORK5CYII=)}.consola-container .consola-wrap .method.method-info{position:relative;display:flex;color:hsla(0,0%,100%,.9);background-color:transparent;border-top:1px solid hsla(0,0%,100%,.05);border-bottom:1px solid hsla(0,0%,100%,.05);margin-top:-1px;margin-bottom:0;padding-left:10px;box-sizing:border-box}.consola-container .consola-wrap .method.method-info .method-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADISURBVChTY4ABp/AztmZBZ07qe538rO114rOa8+GTskYHbKHSEOARd6nLIOTsf61gIA46U6kVePYQiK3uc/K/hPG+LrCi8IyrtkZh5yCKgk/80w46ba0RdGYGhH/2v6rXyf88qtttGVwSLp2ECQLxeiAu1wo6uwpJ7L+o2f6TDA6xZz8jCyqFnuHXCj4djywmZXHoM/EK0azGqhBsNYpngL6VCTnGqRF4xgKo+D5IDO4ZEEAKnjcQBafvqwWf/YoSPDCAP8AZGAC7mLM81zgOTQAAAABJRU5ErkJggg==)}.consola-container .consola-wrap .method.method-warn{position:relative;display:flex;color:#ffdc9e;background-color:#332b00;border-top:1px solid #650;border-bottom:1px solid #650;margin-top:-1px;margin-bottom:1px;padding-left:10px;box-sizing:border-box}.consola-container .consola-wrap .method.method-warn .method-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACkSURBVChTbY7BCoJQFERn0Q/3BX1JuxQjsSCXiV8gtCgxhCIrKIRIqKDVzXl5w5cNHBjm6eGinXiAXu5inY2xYm/mbpIh+vcFhLA3sx0athNUhymEsP+10lAEEA17x8o/9wFuNGnYuVlWve0SQl7P0sBu3aq2R1Q/1JzSkYGd29eqNv2wjdnUuvNRciC/N+qe+7gidbA8zyHkOINsvA/sumcOkjcabcBmw2+mMgAAAABJRU5ErkJggg==)}.consola-container .consola-wrap .method.method-error{position:relative;display:flex;color:#ff8080;background-color:#290000;border-top:1px solid #5b0000;border-bottom:1px solid #5b0000;margin-top:-1px;margin-bottom:1px;padding-left:10px;box-sizing:border-box}.consola-container .consola-wrap .method.method-error .method-icon{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADESURBVChTY4CB7ZI8tmfU5E6e01b+DMIgNkgMKg0BR9Vkux6YWPx/bemIgkFiIDmwogOaqrYPzazAEm8DwuGKYGyQHEgNw0VT05Mwib9v3v7/kJEHxiA2TDFIDcNNU4vPMFPACj58/P/v40cwGyYOUsNwy8IZRSFIEUgxskKQGoZrzp4ErQapYbgYHG371M4dLACTQGaD5EBqwD6/FpzQ9dTBE64IhkFiIDmwIhi4mlJqey8o4eR9r8jPIAxig8QgsgwMAFZz1YtGPXgjAAAAAElFTkSuQmCC)}.consola-container .consola-wrap .method.method-debug{position:relative;display:flex;color:#4d88ff;background-color:transparent;border-top:1px solid hsla(0,0%,100%,.05);border-bottom:1px solid hsla(0,0%,100%,.05);margin-top:-1px;margin-bottom:0;padding-left:10px;box-sizing:border-box}.consola-container .consola-wrap .method.method-debug .method-icon{background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 459 459'%3e%3cpath fill='%234D88FF' d='M433.5 127.5h-71.4a177.7 177.7 0 0 0-45.9-51L357 35.7 321.3 0l-56.1 56.1c-10.2-2.6-23-5.1-35.7-5.1s-25.5 2.5-35.7 5.1L137.7 0 102 35.7l40.8 40.8a177.7 177.7 0 0 0-45.9 51H25.5v51H79c-2.5 7.7-2.5 17.9-2.5 25.5v25.5h-51v51h51V306a88 88 0 0 0 2.5 25.5H25.5v51h71.4A152.2 152.2 0 0 0 229.5 459c56.1 0 107.1-30.6 132.6-76.5h71.4v-51H380c2.5-7.7 2.5-17.9 2.5-25.5v-25.5h51v-51h-51V204c0-7.7 0-17.9-2.5-25.5h53.5v-51zm-153 204h-102v-51h102v51zm0-102h-102v-51h102v51z'/%3e%3c/svg%3e")}.consola-container .consola-wrap .method *{vertical-align:top;box-sizing:border-box;font-family:Consolas,Lucida Console,Courier New,monospace;white-space:pre-wrap;font-size:12px}.consola-container .consola-wrap .method .method-icon{width:10px;height:18px;background-image:noneLine truncated
|
||||
+19
-22
@@ -11,55 +11,52 @@
|
||||
<link rel="shortcut icon" href="./img/favicon.ico">
|
||||
<link rel="stylesheet" href="./css/normalize.css">
|
||||
<link rel="stylesheet" href="./css/common.css">
|
||||
<link rel="stylesheet" href="./css/consola.css">
|
||||
<link rel="stylesheet" href="../uncompiled/artplayer.css">
|
||||
<link rel="stylesheet" href="../uncompiled/artplayer-tool-thumbnail.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="left-part">
|
||||
<div class="upload">
|
||||
点击上传视频文件, 支持格式:mp4、ogg、webm
|
||||
Click to upload video file, support format: mp4, ogg, webm
|
||||
<input class="file" type="file">
|
||||
</div>
|
||||
<div class="artplayer"></div>
|
||||
<div class="result">
|
||||
<div class="item">
|
||||
<div class="name">长宽:</div>
|
||||
<span class="aspectRatio">0 x 0</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">时长:</div>
|
||||
<span class="duration">0s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setup">
|
||||
<div class="item">
|
||||
<div class="name">数量:</div>
|
||||
<input class="input number" type="number" min="10" max="100" placeholder="10 - 100" value="100">
|
||||
<div class="name">Delay:</div>
|
||||
<input class="input delay" type="number" min="100" max="1000" placeholder="100 - 1000" value="300">
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">宽度:</div>
|
||||
<input class="input width" type="number" min="10" max="100" placeholder="100 - 500" value="100">
|
||||
<div class="name">Number:</div>
|
||||
<input class="input number" type="number" min="10" max="100" placeholder="10 - 100" value="60">
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">高度:</div>
|
||||
<input class="input height" type="number" min="10" max="100" placeholder="100 - 500" value="100">
|
||||
<div class="name">Width:</div>
|
||||
<input class="input width" type="number" min="100" max="500" placeholder="100 - 500" value="160">
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">纵数:</div>
|
||||
<div class="name">Height:</div>
|
||||
<input class="input height" type="number" min="100" max="500" placeholder="100 - 500" value="90">
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="name">Column:</div>
|
||||
<input class="input column" type="number" min="10" max="100" placeholder="10 - 100" value="10">
|
||||
</div>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<div class="btn reset">重置</div>
|
||||
<div class="btn download">下载</div>
|
||||
<div class="btn reset">Reset</div>
|
||||
<div class="btn download">Download</div>
|
||||
<div class="btn create">Create</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rigth-part">
|
||||
<div class="img"></div>
|
||||
<div class="preview"></div>
|
||||
<div class="console"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="./js/consola.js"></script>
|
||||
<script src="../uncompiled/artplayer.js"></script>
|
||||
<script src="../uncompiled/artplayer-tool-thumbnail.js"></script>
|
||||
<script src="./js/common.js"></script>
|
||||
|
||||
+82
-39
@@ -1,59 +1,102 @@
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
var $img = document.querySelector('.img');
|
||||
var $preview = document.querySelector('.preview');
|
||||
var $upload = document.querySelector('.upload');
|
||||
var $file = document.querySelector('.file');
|
||||
var $reset = document.querySelector('.reset');
|
||||
var $create = document.querySelector('.create');
|
||||
var $download = document.querySelector('.download');
|
||||
var $artplayer = document.querySelector('.artplayer');
|
||||
var $aspectRatio = document.querySelector('.aspectRatio');
|
||||
var $duration = document.querySelector('.duration');
|
||||
var $delay = document.querySelector('.delay');
|
||||
var $number = document.querySelector('.number');
|
||||
var $width = document.querySelector('.width');
|
||||
var $height = document.querySelector('.height');
|
||||
var $column = document.querySelector('.column');
|
||||
|
||||
var thumbnail = new ArtplayerToolThumbnail({
|
||||
fileInput: $file,
|
||||
callbackVideoUrl: function(url) {
|
||||
$upload.style.display = 'none';
|
||||
$artplayer.style.display = 'block';
|
||||
restartArtplayer();
|
||||
},
|
||||
callbackThumbnailUrl: function(url) {
|
||||
$img.style.backgroundImage = `url(${url})`;
|
||||
},
|
||||
callbackDone: function(videoUrl, thumbnailUrl) {
|
||||
restartArtplayer();
|
||||
},
|
||||
function getConfig() {
|
||||
return {
|
||||
delay: Number($delay.value),
|
||||
number: Number($number.value),
|
||||
width: Number($width.value),
|
||||
height: Number($height.value),
|
||||
column: Number($column.value),
|
||||
};
|
||||
}
|
||||
|
||||
consola.creat({
|
||||
target: '.console',
|
||||
size: '100%',
|
||||
zIndex: 99,
|
||||
});
|
||||
|
||||
function restartArtplayer() {
|
||||
console.info('Welcome to this tool, if you like it, consider star it, thank you.');
|
||||
console.info('https://github.com/zhw2590582/ArtPlayer');
|
||||
|
||||
window.addEventListener('error', function(err) {
|
||||
console.error(err.message);
|
||||
});
|
||||
|
||||
var thumbnail = new ArtplayerToolThumbnail({
|
||||
fileInput: $file,
|
||||
});
|
||||
|
||||
$reset.addEventListener('click', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
$create.addEventListener('click', function() {
|
||||
thumbnail.setup(getConfig()).start();
|
||||
});
|
||||
|
||||
$download.addEventListener('click', function() {
|
||||
thumbnail.download();
|
||||
});
|
||||
|
||||
thumbnail.on('file', function(file) {
|
||||
console.log('Read video successfully: ' + file.name);
|
||||
});
|
||||
|
||||
thumbnail.on('video', function(video) {
|
||||
console.log('Video size: ' + video.videoWidth + ' x ' + video.videoHeight);
|
||||
console.log('Video duration: ' + video.duration + 's');
|
||||
thumbnail.setup(getConfig()).start();
|
||||
});
|
||||
|
||||
thumbnail.on('canvas', function(canvas) {
|
||||
console.log('Build canvas successfully');
|
||||
console.log('Canvas size: ' + canvas.width + ' x ' + canvas.height);
|
||||
});
|
||||
|
||||
thumbnail.on('update', function(percentage, url) {
|
||||
console.log('Processing: ' + Math.floor(percentage.toFixed(2) * 100) + '%');
|
||||
$preview.style.backgroundImage = 'url(' + url + ')';
|
||||
});
|
||||
|
||||
thumbnail.on('download', function(name) {
|
||||
console.log('Start download preview: ' + name);
|
||||
});
|
||||
|
||||
thumbnail.on('done', function() {
|
||||
console.log('Build preview image complete');
|
||||
$upload.style.display = 'none';
|
||||
$artplayer.style.display = 'block';
|
||||
|
||||
Artplayer.instances.forEach(function(art) {
|
||||
art.destroy(true);
|
||||
});
|
||||
|
||||
var option = {
|
||||
new Artplayer({
|
||||
container: $artplayer,
|
||||
url: thumbnail.videoUrl,
|
||||
};
|
||||
|
||||
if (thumbnail.thumbnailUrl) {
|
||||
option.thumbnails = {
|
||||
thumbnails: {
|
||||
url: thumbnail.thumbnailUrl,
|
||||
};
|
||||
}
|
||||
|
||||
var art = new Artplayer(option);
|
||||
|
||||
art.on('firstCanplay', function() {
|
||||
var $video = art.template.$video;
|
||||
$aspectRatio.textContent = $video.videoWidth + ' x ' + $video.videoHeight;
|
||||
$duration.textContent = $video.duration + 's';
|
||||
thumbnail.mergeOption({
|
||||
videoElement: $video,
|
||||
});
|
||||
|
||||
if (!thumbnail.thumbnailUrl) {
|
||||
thumbnail.getThumbnailUrl();
|
||||
}
|
||||
number: thumbnail.option.number,
|
||||
width: thumbnail.option.width,
|
||||
height: thumbnail.option.height,
|
||||
column: thumbnail.option.column,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('Build player complete');
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1 @@
|
||||
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var n=t();for(var o in n)("object"==typeof exports?exports:e)[o]=n[o]}}(window,function(){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";var o=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},r=c(n(1));n(2);var i=c(n(4));function c(e){return e&&e.__esModule?e:{default:e}}var l={},u={target:"body",position:"bottom",size:"300px",zIndex:99},a=["log","warn","error","info","debug"],s=void 0,f=void 0,d=void 0;l.creat=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(e=o({},u,e),!document.querySelector(".consola-container"))return s=document.querySelector(e.target),i.default.insertHtml(s,"beforeend",'<div class="consola-container '+e.position+'" style="z-index: '+e.zIndex+"; "+("bottom"===e.position?"height":"width")+": "+e.size+'">\n <div class="consola-tools">\n <div class="consola-count">Console <span>0</span></div>\n <div class="consola-clean">Clean</div>\n </div>\n <div class="consola-wrap"></div>\n </div>'),f=s.querySelector(".consola-wrap"),d=s.querySelector(".consola-count span"),s.querySelector(".consola-clean").addEventListener("click",l.clean),function(e,t,n){var o=function(t){var o=e[t];e[t]=function(){o.apply(this,arguments),n(t,[].concat(Array.prototype.slice.call(arguments)))}},r=!0,i=!1,c=void 0;try{for(var l,u=t[Symbol.iterator]();!(r=(l=u.next()).done);r=!0){o(l.value)}}catch(e){i=!0,c=e}finally{try{!r&&u.return&&u.return()}finally{if(i)throw c}}}(window.console,a,function(e,t){i.default.insertHtml(f,"beforeend",'<div data-method="'+e+'" class="method method-'+e+'">\n <div class="method-icon"></div>\n <div class="method-text">'+function(e){var t=document.createElement("div");e=e.map(function(e){return(0,r.default)(e)}),t.textContent=e;var n=t.innerHTML;return t=null,n}(t)+"</div>\n </div>"),d.innerHTML=f.children.length,f.scrollTo(0,f.scrollHeight)}),l},l.clean=function(){return f.innerHTML="",d.innerHTML="0",l},window.consola=l,e.exports=l},function(e,t){e.exports=function e(t){switch(typeof t){case"undefined":return"undefined";case"object":let n=Object.prototype.toString.call(t).slice(8,-1).toLowerCase();switch(n){case"null":return"null";case"array":return"["+t.map(t=>e(t)).join(", ")+"]";case"object":return"{ "+Object.keys(t).map(n=>n+": "+e(t[n])).join(", ")+" }";default:try{return t.toString()}catch(e){return"[Unknown type: "+n+"]"}}default:return t.toString()}}},function(e,t,n){},,function(e,t,n){"use strict";function o(e){for(var t=e.length,n=[],o=0;o<t;o+=1)n.push(e[o]);return n}e.exports={select:function(e){return document.querySelector(e)},selectAll:function(e){return o((arguments.length>1&&void 0!==arguments[1]?arguments[1]:document).querySelectorAll(e))},find:function(e,t){return o(e.querySelectorAll(t))},removeClass:function(e,t){e.classList.remove(t)},addClass:function(e,t){e.classList.add(t)},hasClass:function(e,t){return e.classList.contains(t)},removeElement:function(e){e&&e.parentNode&&e.parentNode.removeChild(e)},closest:function(e,t){for(var n=e.matches||e.webkitMatchesSelector||e.mozMatchesSelector||e.msMatchesSelector;e&&!n.call(e,t);)e=e.parentElement;return e},getStyle:function(e,t,n){var o=window.getComputedStyle(e,null).getPropertyValue(t);return n?Number.parseFloat(o,10):o},blur:function(){document.activeElement&&document.activeElement.blur()},insertHtml:function(e,t,n){var o=["beforebegin","afterbegin","beforeend","afterend"];if(!o.includes(t))throw new TypeError("'position' must be one of them: "+o.join("、"));e.insertAdjacentHTML(t,n)},setAttributes:function(e,t){Object.keys(t).forEach(function(n){!1!==t[n]?e.setAttribute(n,t[n]):e.removeAttribute(n)})},setStyles:function(e,t){Object.keys(t).forEach(function(n){e.style[n]=t[n]})}}}])});
|
||||
@@ -1 +0,0 @@
|
||||
.artplayer-tool-thumbnail{display:flex}
|
||||
@@ -4,44 +4,6 @@
|
||||
(factory((global['artplayer-tool-thumbnail'] = {})));
|
||||
}(this, (function (exports) { 'use strict';
|
||||
|
||||
function _defineProperty(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
var defineProperty = _defineProperty;
|
||||
|
||||
function _objectSpread(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i] != null ? arguments[i] : {};
|
||||
var ownKeys = Object.keys(source);
|
||||
|
||||
if (typeof Object.getOwnPropertySymbols === 'function') {
|
||||
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
||||
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
||||
}));
|
||||
}
|
||||
|
||||
ownKeys.forEach(function (key) {
|
||||
defineProperty(target, key, source[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
var objectSpread = _objectSpread;
|
||||
|
||||
function _classCallCheck(instance, Constructor) {
|
||||
if (!(instance instanceof Constructor)) {
|
||||
throw new TypeError("Cannot call a class as a function");
|
||||
@@ -68,92 +30,295 @@
|
||||
|
||||
var createClass = _createClass;
|
||||
|
||||
function errorHandle(condition, msg) {
|
||||
if (!condition) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
function createCommonjsModule(fn, module) {
|
||||
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
function runPromisesInSeries(ps) {
|
||||
return ps.reduce(function (p, next) {
|
||||
return p.then(next);
|
||||
}, Promise.resolve());
|
||||
var _typeof_1 = createCommonjsModule(function (module) {
|
||||
function _typeof2(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof2 = function _typeof2(obj) { return typeof obj; }; } else { _typeof2 = function _typeof2(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof2(obj); }
|
||||
|
||||
function _typeof(obj) {
|
||||
if (typeof Symbol === "function" && _typeof2(Symbol.iterator) === "symbol") {
|
||||
module.exports = _typeof = function _typeof(obj) {
|
||||
return _typeof2(obj);
|
||||
};
|
||||
} else {
|
||||
module.exports = _typeof = function _typeof(obj) {
|
||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : _typeof2(obj);
|
||||
};
|
||||
}
|
||||
|
||||
return _typeof(obj);
|
||||
}
|
||||
|
||||
module.exports = _typeof;
|
||||
});
|
||||
|
||||
function _assertThisInitialized(self) {
|
||||
if (self === void 0) {
|
||||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
var assertThisInitialized = _assertThisInitialized;
|
||||
|
||||
function _possibleConstructorReturn(self, call) {
|
||||
if (call && (_typeof_1(call) === "object" || typeof call === "function")) {
|
||||
return call;
|
||||
}
|
||||
|
||||
return assertThisInitialized(self);
|
||||
}
|
||||
|
||||
var possibleConstructorReturn = _possibleConstructorReturn;
|
||||
|
||||
var getPrototypeOf = createCommonjsModule(function (module) {
|
||||
function _getPrototypeOf(o) {
|
||||
module.exports = _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
|
||||
return o.__proto__ || Object.getPrototypeOf(o);
|
||||
};
|
||||
return _getPrototypeOf(o);
|
||||
}
|
||||
|
||||
module.exports = _getPrototypeOf;
|
||||
});
|
||||
|
||||
var setPrototypeOf = createCommonjsModule(function (module) {
|
||||
function _setPrototypeOf(o, p) {
|
||||
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||
o.__proto__ = p;
|
||||
return o;
|
||||
};
|
||||
|
||||
return _setPrototypeOf(o, p);
|
||||
}
|
||||
|
||||
module.exports = _setPrototypeOf;
|
||||
});
|
||||
|
||||
function _inherits(subClass, superClass) {
|
||||
if (typeof superClass !== "function" && superClass !== null) {
|
||||
throw new TypeError("Super expression must either be null or a function");
|
||||
}
|
||||
|
||||
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
||||
constructor: {
|
||||
value: subClass,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
if (superClass) setPrototypeOf(subClass, superClass);
|
||||
}
|
||||
|
||||
var inherits = _inherits;
|
||||
|
||||
function E () {
|
||||
// Keep this empty so it's easier to inherit from
|
||||
// (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
|
||||
}
|
||||
|
||||
E.prototype = {
|
||||
on: function (name, callback, ctx) {
|
||||
var e = this.e || (this.e = {});
|
||||
|
||||
(e[name] || (e[name] = [])).push({
|
||||
fn: callback,
|
||||
ctx: ctx
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
once: function (name, callback, ctx) {
|
||||
var self = this;
|
||||
function listener () {
|
||||
self.off(name, listener);
|
||||
callback.apply(ctx, arguments);
|
||||
}
|
||||
listener._ = callback;
|
||||
return this.on(name, listener, ctx);
|
||||
},
|
||||
|
||||
emit: function (name) {
|
||||
var data = [].slice.call(arguments, 1);
|
||||
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
|
||||
var i = 0;
|
||||
var len = evtArr.length;
|
||||
|
||||
for (i; i < len; i++) {
|
||||
evtArr[i].fn.apply(evtArr[i].ctx, data);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
off: function (name, callback) {
|
||||
var e = this.e || (this.e = {});
|
||||
var evts = e[name];
|
||||
var liveEvents = [];
|
||||
|
||||
if (evts && callback) {
|
||||
for (var i = 0, len = evts.length; i < len; i++) {
|
||||
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
|
||||
liveEvents.push(evts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove event from queue to prevent memory leak
|
||||
// Suggested by https://github.com/lazd
|
||||
// Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
|
||||
|
||||
(liveEvents.length)
|
||||
? e[name] = liveEvents
|
||||
: delete e[name];
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
var tinyEmitter = E;
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(function (resolve) {
|
||||
return setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
function runPromisesInSeries(ps) {
|
||||
return ps.reduce(function (p, next) {
|
||||
return p.then(next);
|
||||
}, Promise.resolve());
|
||||
}
|
||||
function getFileName(name) {
|
||||
var nameArray = name.split('.');
|
||||
nameArray.pop();
|
||||
return nameArray.join('.');
|
||||
}
|
||||
function clamp(num, a, b) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
|
||||
}
|
||||
|
||||
var ArtplayerToolThumbnail =
|
||||
/*#__PURE__*/
|
||||
function () {
|
||||
function ArtplayerToolThumbnail(option) {
|
||||
function (_Emitter) {
|
||||
inherits(ArtplayerToolThumbnail, _Emitter);
|
||||
|
||||
function ArtplayerToolThumbnail() {
|
||||
var _this;
|
||||
|
||||
var option = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
classCallCheck(this, ArtplayerToolThumbnail);
|
||||
|
||||
if (option.fileInput) {
|
||||
errorHandle(option.fileInput.tagName === 'INPUT' && option.fileInput.type === 'file', 'This fileInput is not a file input');
|
||||
}
|
||||
_this = possibleConstructorReturn(this, getPrototypeOf(ArtplayerToolThumbnail).call(this));
|
||||
_this.processing = false;
|
||||
_this.option = {};
|
||||
|
||||
if (option.videoElement) {
|
||||
errorHandle(option.fileInput.tagName === 'VIDEO', 'This videoElement is not a video element');
|
||||
}
|
||||
_this.setup(Object.assign({}, ArtplayerToolThumbnail.DEFAULTS, option));
|
||||
|
||||
if (option.callbackVideoUrl) {
|
||||
errorHandle(typeof option.callbackVideoUrl === 'function', 'This callbackVideoUrl is not a function');
|
||||
}
|
||||
_this.video = ArtplayerToolThumbnail.creatVideo();
|
||||
_this.inputChange = _this.inputChange.bind(assertThisInitialized(assertThisInitialized(_this)));
|
||||
|
||||
if (option.callbackThumbnailUrl) {
|
||||
errorHandle(typeof option.callbackThumbnailUrl === 'function', 'This callbackThumbnailUrl is not a function');
|
||||
}
|
||||
_this.option.fileInput.addEventListener('change', _this.inputChange);
|
||||
|
||||
this.option = objectSpread({}, ArtplayerToolThumbnail.DEFAULTS, option);
|
||||
this.getVideoUrl();
|
||||
return _this;
|
||||
}
|
||||
|
||||
createClass(ArtplayerToolThumbnail, [{
|
||||
key: "mergeOption",
|
||||
value: function mergeOption() {
|
||||
var option = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
this.option = objectSpread({}, this.option, option);
|
||||
return this.option;
|
||||
}
|
||||
}, {
|
||||
key: "getVideoUrl",
|
||||
value: function getVideoUrl() {
|
||||
var _this = this;
|
||||
|
||||
var fileType = ['video/mp4', 'video/ogg', 'video/webm'];
|
||||
this.option.fileInput.addEventListener('change', function () {
|
||||
var file = _this.option.fileInput.files[0];
|
||||
|
||||
if (file) {
|
||||
errorHandle(fileType.includes(file.type), "Only file types are supported: ".concat(fileType.toString()));
|
||||
var videoUrl = URL.createObjectURL(file);
|
||||
_this.videoUrl = videoUrl;
|
||||
|
||||
if (_this.option.callbackVideoUrl) {
|
||||
_this.option.callbackVideoUrl(videoUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "getThumbnailUrl",
|
||||
value: function getThumbnailUrl() {
|
||||
key: "setup",
|
||||
value: function setup() {
|
||||
var _this2 = this;
|
||||
|
||||
var option = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
this.mergeOption(option);
|
||||
this.option = Object.assign({}, this.option, option);
|
||||
var _this$option = this.option,
|
||||
fileInput = _this$option.fileInput,
|
||||
delay = _this$option.delay,
|
||||
number = _this$option.number,
|
||||
width = _this$option.width,
|
||||
height = _this$option.height,
|
||||
column = _this$option.column,
|
||||
videoElement = _this$option.videoElement,
|
||||
callbackDone = _this$option.callbackDone;
|
||||
var duration = videoElement.duration;
|
||||
column = _this$option.column;
|
||||
this.errorHandle(fileInput.tagName === 'INPUT' && fileInput.type === 'file', 'The \'fileInput\' is not a usable file input like: <input type="file">');
|
||||
['delay', 'number', 'width', 'height', 'column'].forEach(function (item) {
|
||||
_this2.errorHandle(typeof _this2.option[item] === 'number', "The '".concat(item, "' is not a number"));
|
||||
});
|
||||
this.option.delay = clamp(delay, 100, 1000);
|
||||
this.option.number = clamp(number, 10, 100);
|
||||
this.option.width = clamp(width, 100, 500);
|
||||
this.option.height = clamp(height, 100, 500);
|
||||
this.option.column = clamp(column, 10, 100);
|
||||
return this;
|
||||
}
|
||||
}, {
|
||||
key: "inputChange",
|
||||
value: function inputChange() {
|
||||
var _this3 = this;
|
||||
|
||||
var delay = this.option.delay;
|
||||
var fileType = ['video/mp4', 'audio/ogg', 'video/webm'];
|
||||
var file = this.option.fileInput.files[0];
|
||||
|
||||
if (file) {
|
||||
this.errorHandle(fileType.includes(file.type), "Only file types are supported: ".concat(fileType.toString(), ", but got ").concat(file.type));
|
||||
var videoUrl = URL.createObjectURL(file);
|
||||
this.videoUrl = videoUrl;
|
||||
this.file = file;
|
||||
this.emit('file', this.file);
|
||||
this.video.src = videoUrl;
|
||||
sleep(delay).then(function () {
|
||||
_this3.emit('video', _this3.video);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
key: "start",
|
||||
value: function start() {
|
||||
var _this4 = this;
|
||||
|
||||
this.errorHandle(this.file && this.video, 'Please select the video file first');
|
||||
this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...');
|
||||
var _this$option2 = this.option,
|
||||
width = _this$option2.width,
|
||||
height = _this$option2.height,
|
||||
number = _this$option2.number,
|
||||
delay = _this$option2.delay;
|
||||
var screenshotDate = this.creatScreenshotDate();
|
||||
var canvas = this.creatCanvas();
|
||||
var context2D = canvas.getContext('2d');
|
||||
this.emit('canvas', canvas);
|
||||
var promiseList = screenshotDate.map(function (item, index) {
|
||||
return function () {
|
||||
_this4.video.currentTime = item.time;
|
||||
return sleep(delay).then(function () {
|
||||
context2D.drawImage(_this4.video, item.x, item.y, width, height);
|
||||
canvas.toBlob(function (blob) {
|
||||
_this4.thumbnailUrl = URL.createObjectURL(blob);
|
||||
|
||||
_this4.emit('update', (index + 1) / number, _this4.thumbnailUrl);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
this.processing = true;
|
||||
runPromisesInSeries(promiseList).then(function () {
|
||||
sleep(delay).then(function () {
|
||||
_this4.processing = false;
|
||||
|
||||
_this4.emit('done');
|
||||
});
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "creatScreenshotDate",
|
||||
value: function creatScreenshotDate() {
|
||||
var _this$option3 = this.option,
|
||||
number = _this$option3.number,
|
||||
width = _this$option3.width,
|
||||
height = _this$option3.height,
|
||||
column = _this$option3.column;
|
||||
var duration = this.video.duration;
|
||||
var timeGap = duration / number;
|
||||
var timePoints = [timeGap];
|
||||
|
||||
@@ -162,53 +327,68 @@
|
||||
timePoints.push(last + timeGap);
|
||||
}
|
||||
|
||||
var screenshotDate = timePoints.map(function (item, index) {
|
||||
return timePoints.map(function (item, index) {
|
||||
return {
|
||||
time: item - timeGap / 2,
|
||||
x: index % column * width,
|
||||
y: Math.floor(index / column) * height
|
||||
};
|
||||
});
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = width * column;
|
||||
canvas.height = Math.ceil(number / column) * height;
|
||||
var promiseList = screenshotDate.map(function (item) {
|
||||
return _this2.getScreenshot(canvas, item);
|
||||
});
|
||||
runPromisesInSeries(promiseList).then(function () {
|
||||
var thumbnailUrl = canvas.toDataURL('image/png');
|
||||
_this2.thumbnailUrl = thumbnailUrl;
|
||||
|
||||
if (callbackDone) {
|
||||
callbackDone(_this2.videoUrl, thumbnailUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
key: "getScreenshot",
|
||||
value: function getScreenshot(canvas, item) {
|
||||
var _this3 = this;
|
||||
|
||||
return function () {
|
||||
var context2D = canvas.getContext('2d');
|
||||
var _this3$option = _this3.option,
|
||||
width = _this3$option.width,
|
||||
height = _this3$option.height,
|
||||
videoElement = _this3$option.videoElement,
|
||||
delay = _this3$option.delay,
|
||||
callbackThumbnailUrl = _this3$option.callbackThumbnailUrl;
|
||||
videoElement.currentTime = item.time;
|
||||
return sleep(delay).then(function () {
|
||||
context2D.drawImage(videoElement, item.x, item.y, width, height);
|
||||
var thumbnailUrl = canvas.toDataURL('image/png');
|
||||
|
||||
if (callbackThumbnailUrl) {
|
||||
callbackThumbnailUrl(thumbnailUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
key: "creatCanvas",
|
||||
value: function creatCanvas() {
|
||||
var _this$option4 = this.option,
|
||||
number = _this$option4.number,
|
||||
width = _this$option4.width,
|
||||
height = _this$option4.height,
|
||||
column = _this$option4.column;
|
||||
var canvas = document.createElement('canvas');
|
||||
var context2D = canvas.getContext('2d');
|
||||
canvas.width = width * column;
|
||||
canvas.height = Math.ceil(number / column) * height + 30;
|
||||
context2D.fillStyle = 'black';
|
||||
context2D.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context2D.font = '14px Georgia';
|
||||
context2D.fillStyle = '#fff';
|
||||
context2D.fillText("From: https://artplayer.org/thumbnail, Number: ".concat(number, ", Width: ").concat(width, ", Height: ").concat(height, ", Column: ").concat(column), 10, canvas.height - 12);
|
||||
return canvas;
|
||||
}
|
||||
}, {
|
||||
key: "download",
|
||||
value: function download() {
|
||||
this.errorHandle(this.file && this.thumbnailUrl, 'Download does not seem to be ready');
|
||||
this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...');
|
||||
var elink = document.createElement('a');
|
||||
var name = "".concat(getFileName(this.file.name), ".png");
|
||||
elink.download = name;
|
||||
elink.href = this.thumbnailUrl;
|
||||
document.body.appendChild(elink);
|
||||
elink.click();
|
||||
document.body.removeChild(elink);
|
||||
this.emit('download', name);
|
||||
}
|
||||
}, {
|
||||
key: "errorHandle",
|
||||
value: function errorHandle(condition, msg) {
|
||||
if (!condition) {
|
||||
this.emit('error', msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
}], [{
|
||||
key: "creatVideo",
|
||||
value: function creatVideo() {
|
||||
var video = document.createElement('video');
|
||||
video.style.position = 'absolute';
|
||||
video.style.top = '-9999px';
|
||||
video.style.left = '-9999px';
|
||||
video.muted = true;
|
||||
video.controls = true;
|
||||
document.body.appendChild(video);
|
||||
return video;
|
||||
}
|
||||
}, {
|
||||
key: "DEFAULTS",
|
||||
get: function get() {
|
||||
return {
|
||||
@@ -222,7 +402,7 @@
|
||||
}]);
|
||||
|
||||
return ArtplayerToolThumbnail;
|
||||
}();
|
||||
}(tinyEmitter);
|
||||
|
||||
window.ArtplayerToolThumbnail = ArtplayerToolThumbnail;
|
||||
|
||||
|
||||
+2
-1
@@ -58,6 +58,7 @@
|
||||
"rollup-plugin-postcss": "^1.6.2",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
"rollup-plugin-svgo": "^1.0.1",
|
||||
"rollup-plugin-uglify": "^6.0.0"
|
||||
"rollup-plugin-uglify": "^6.0.0",
|
||||
"tiny-emitter": "^2.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,78 @@
|
||||
# artplayer-plugin-danmu
|
||||
Danmu plugin for ArtPlayer
|
||||
# artplayer-tool-thumbnail
|
||||
thumbnail tool for ArtPlayer
|
||||
|
||||
## Demo
|
||||
|
||||
[Checkout the demo](https://artplayer.org/thumbnail/) from Github Pages
|
||||
|
||||
## Install
|
||||
|
||||
Install with `npm`
|
||||
|
||||
```
|
||||
$ npm install artplayer-tool-thumbnail
|
||||
```
|
||||
|
||||
Or install with `yarn`
|
||||
|
||||
```
|
||||
$ yarn add artplayer-tool-thumbnail
|
||||
```
|
||||
|
||||
```js
|
||||
import ArtplayerToolThumbnail from 'artplayer-tool-thumbnail';
|
||||
```
|
||||
|
||||
Or umd builds are also available
|
||||
|
||||
```html
|
||||
<script src="artplayer-tool-thumbnail"></script>
|
||||
```
|
||||
|
||||
Will expose the global variable to `window.ArtplayerToolThumbnail`.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<input class="file" type="file">
|
||||
```
|
||||
|
||||
```js
|
||||
var thumbnail = new ArtplayerToolThumbnail({
|
||||
fileInput: document.querySelector('.file'),
|
||||
delay: 300,
|
||||
number: 60,
|
||||
width: 160,
|
||||
height: 90,
|
||||
column: 10,
|
||||
});
|
||||
|
||||
thumbnail.on('file', file => {
|
||||
console.log(file);
|
||||
});
|
||||
|
||||
thumbnail.on('video', video => {
|
||||
console.log(video);
|
||||
thumbnail.start();
|
||||
});
|
||||
|
||||
thumbnail.on('canvas', canvas => {
|
||||
console.log(canvas);
|
||||
});
|
||||
|
||||
thumbnail.on('update', (percentage, url) => {
|
||||
console.log(percentage, url);
|
||||
});
|
||||
|
||||
thumbnail.on('download', name => {
|
||||
console.log(name);
|
||||
});
|
||||
|
||||
thumbnail.on('done', () => {
|
||||
console.log('done');
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Harvey Zack](https://www.zhw-island.com/)
|
||||
@@ -1,48 +1,15 @@
|
||||
import './index.scss';
|
||||
import Emitter from 'tiny-emitter';
|
||||
import { runPromisesInSeries, sleep, getFileName, clamp } from './utils';
|
||||
|
||||
function errorHandle(condition, msg) {
|
||||
if (!condition) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
function runPromisesInSeries(ps) {
|
||||
return ps.reduce((p, next) => p.then(next), Promise.resolve());
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
class ArtplayerToolThumbnail {
|
||||
constructor(option) {
|
||||
if (option.fileInput) {
|
||||
errorHandle(
|
||||
option.fileInput.tagName === 'INPUT' && option.fileInput.type === 'file',
|
||||
'This fileInput is not a file input',
|
||||
);
|
||||
}
|
||||
|
||||
if (option.videoElement) {
|
||||
errorHandle(option.fileInput.tagName === 'VIDEO', 'This videoElement is not a video element');
|
||||
}
|
||||
|
||||
if (option.callbackVideoUrl) {
|
||||
errorHandle(typeof option.callbackVideoUrl === 'function', 'This callbackVideoUrl is not a function');
|
||||
}
|
||||
|
||||
if (option.callbackThumbnailUrl) {
|
||||
errorHandle(
|
||||
typeof option.callbackThumbnailUrl === 'function',
|
||||
'This callbackThumbnailUrl is not a function',
|
||||
);
|
||||
}
|
||||
|
||||
this.option = {
|
||||
...ArtplayerToolThumbnail.DEFAULTS,
|
||||
...option,
|
||||
};
|
||||
this.getVideoUrl();
|
||||
class ArtplayerToolThumbnail extends Emitter {
|
||||
constructor(option = {}) {
|
||||
super();
|
||||
this.processing = false;
|
||||
this.option = {};
|
||||
this.setup(Object.assign({}, ArtplayerToolThumbnail.DEFAULTS, option));
|
||||
this.video = ArtplayerToolThumbnail.creatVideo();
|
||||
this.inputChange = this.inputChange.bind(this);
|
||||
this.option.fileInput.addEventListener('change', this.inputChange);
|
||||
}
|
||||
|
||||
static get DEFAULTS() {
|
||||
@@ -55,70 +22,137 @@ class ArtplayerToolThumbnail {
|
||||
};
|
||||
}
|
||||
|
||||
mergeOption(option = {}) {
|
||||
this.option = {
|
||||
...this.option,
|
||||
...option,
|
||||
};
|
||||
return this.option;
|
||||
setup(option = {}) {
|
||||
this.option = Object.assign({}, this.option, option);
|
||||
const { fileInput, delay, number, width, height, column } = this.option;
|
||||
|
||||
this.errorHandle(
|
||||
fileInput.tagName === 'INPUT' && fileInput.type === 'file',
|
||||
'The \'fileInput\' is not a usable file input like: <input type="file">',
|
||||
);
|
||||
|
||||
['delay', 'number', 'width', 'height', 'column'].forEach(item => {
|
||||
this.errorHandle(typeof this.option[item] === 'number', `The '${item}' is not a number`);
|
||||
});
|
||||
|
||||
this.option.delay = clamp(delay, 100, 1000);
|
||||
this.option.number = clamp(number, 10, 100);
|
||||
this.option.width = clamp(width, 100, 500);
|
||||
this.option.height = clamp(height, 100, 500);
|
||||
this.option.column = clamp(column, 10, 100);
|
||||
return this;
|
||||
}
|
||||
|
||||
getVideoUrl() {
|
||||
const fileType = ['video/mp4', 'video/ogg', 'video/webm'];
|
||||
this.option.fileInput.addEventListener('change', () => {
|
||||
const file = this.option.fileInput.files[0];
|
||||
if (file) {
|
||||
errorHandle(fileType.includes(file.type), `Only file types are supported: ${fileType.toString()}`);
|
||||
const videoUrl = URL.createObjectURL(file);
|
||||
this.videoUrl = videoUrl;
|
||||
if (this.option.callbackVideoUrl) {
|
||||
this.option.callbackVideoUrl(videoUrl);
|
||||
}
|
||||
}
|
||||
static creatVideo() {
|
||||
const video = document.createElement('video');
|
||||
video.style.position = 'absolute';
|
||||
video.style.top = '-9999px';
|
||||
video.style.left = '-9999px';
|
||||
video.muted = true;
|
||||
video.controls = true;
|
||||
document.body.appendChild(video);
|
||||
return video;
|
||||
}
|
||||
|
||||
inputChange() {
|
||||
const { delay } = this.option;
|
||||
const fileType = ['video/mp4', 'audio/ogg', 'video/webm'];
|
||||
const file = this.option.fileInput.files[0];
|
||||
if (file) {
|
||||
this.errorHandle(
|
||||
fileType.includes(file.type),
|
||||
`Only file types are supported: ${fileType.toString()}, but got ${file.type}`,
|
||||
);
|
||||
const videoUrl = URL.createObjectURL(file);
|
||||
this.videoUrl = videoUrl;
|
||||
this.file = file;
|
||||
this.emit('file', this.file);
|
||||
this.video.src = videoUrl;
|
||||
sleep(delay).then(() => {
|
||||
this.emit('video', this.video);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.errorHandle(this.file && this.video, 'Please select the video file first');
|
||||
this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...');
|
||||
const { width, height, number, delay } = this.option;
|
||||
const screenshotDate = this.creatScreenshotDate();
|
||||
const canvas = this.creatCanvas();
|
||||
const context2D = canvas.getContext('2d');
|
||||
this.emit('canvas', canvas);
|
||||
const promiseList = screenshotDate.map((item, index) => () => {
|
||||
this.video.currentTime = item.time;
|
||||
return sleep(delay).then(() => {
|
||||
context2D.drawImage(this.video, item.x, item.y, width, height);
|
||||
canvas.toBlob(blob => {
|
||||
this.thumbnailUrl = URL.createObjectURL(blob);
|
||||
this.emit('update', (index + 1) / number, this.thumbnailUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
this.processing = true;
|
||||
runPromisesInSeries(promiseList).then(() => {
|
||||
sleep(delay).then(() => {
|
||||
this.processing = false;
|
||||
this.emit('done');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getThumbnailUrl(option = {}) {
|
||||
this.mergeOption(option);
|
||||
const { number, width, height, column, videoElement, callbackDone } = this.option;
|
||||
const { duration } = videoElement;
|
||||
creatScreenshotDate() {
|
||||
const { number, width, height, column } = this.option;
|
||||
const { duration } = this.video;
|
||||
const timeGap = duration / number;
|
||||
const timePoints = [timeGap];
|
||||
while (timePoints.length < number) {
|
||||
const last = timePoints[timePoints.length - 1];
|
||||
timePoints.push(last + timeGap);
|
||||
}
|
||||
const screenshotDate = timePoints.map((item, index) => ({
|
||||
return timePoints.map((item, index) => ({
|
||||
time: item - timeGap / 2,
|
||||
x: (index % column) * width,
|
||||
y: Math.floor(index / column) * height,
|
||||
}));
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width * column;
|
||||
canvas.height = Math.ceil(number / column) * height;
|
||||
const promiseList = screenshotDate.map(item => this.getScreenshot(canvas, item));
|
||||
runPromisesInSeries(promiseList).then(() => {
|
||||
const thumbnailUrl = canvas.toDataURL('image/png');
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
if (callbackDone) {
|
||||
callbackDone(this.videoUrl, thumbnailUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getScreenshot(canvas, item) {
|
||||
return () => {
|
||||
const context2D = canvas.getContext('2d');
|
||||
const { width, height, videoElement, delay, callbackThumbnailUrl } = this.option;
|
||||
videoElement.currentTime = item.time;
|
||||
return sleep(delay).then(() => {
|
||||
context2D.drawImage(videoElement, item.x, item.y, width, height);
|
||||
const thumbnailUrl = canvas.toDataURL('image/png');
|
||||
if (callbackThumbnailUrl) {
|
||||
callbackThumbnailUrl(thumbnailUrl);
|
||||
}
|
||||
});
|
||||
};
|
||||
creatCanvas() {
|
||||
const { number, width, height, column } = this.option;
|
||||
const canvas = document.createElement('canvas');
|
||||
const context2D = canvas.getContext('2d');
|
||||
canvas.width = width * column;
|
||||
canvas.height = Math.ceil(number / column) * height + 30;
|
||||
context2D.fillStyle = 'black';
|
||||
context2D.fillRect(0, 0, canvas.width, canvas.height);
|
||||
context2D.font = '14px Georgia';
|
||||
context2D.fillStyle = '#fff';
|
||||
context2D.fillText(
|
||||
`From: https://artplayer.org/thumbnail, Number: ${number}, Width: ${width}, Height: ${height}, Column: ${column}`,
|
||||
10,
|
||||
canvas.height - 12,
|
||||
);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
download() {
|
||||
this.errorHandle(this.file && this.thumbnailUrl, 'Download does not seem to be ready');
|
||||
this.errorHandle(!this.processing, 'There is currently a task in progress, please wait a moment...');
|
||||
const elink = document.createElement('a');
|
||||
const name = `${getFileName(this.file.name)}.png`;
|
||||
elink.download = name;
|
||||
elink.href = this.thumbnailUrl;
|
||||
document.body.appendChild(elink);
|
||||
elink.click();
|
||||
document.body.removeChild(elink);
|
||||
this.emit('download', name);
|
||||
}
|
||||
|
||||
errorHandle(condition, msg) {
|
||||
if (!condition) {
|
||||
this.emit('error', msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.artplayer-tool-thumbnail {
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function runPromisesInSeries(ps) {
|
||||
return ps.reduce((p, next) => p.then(next), Promise.resolve());
|
||||
}
|
||||
|
||||
export function getFileName(name) {
|
||||
const nameArray = name.split('.');
|
||||
nameArray.pop();
|
||||
return nameArray.join('.');
|
||||
}
|
||||
|
||||
export function clamp(num, a, b) {
|
||||
return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
|
||||
}
|
||||
+2
-2
@@ -23,10 +23,10 @@ function develop(projectPath) {
|
||||
logger.success('finished building all bundles');
|
||||
break;
|
||||
case 'ERROR':
|
||||
logger.fatal(`error: ${event.error}`);
|
||||
logger.warn(`error: ${event.error}`);
|
||||
break;
|
||||
case 'FATAL':
|
||||
logger.fatal(`fatal: ${event.error}`);
|
||||
logger.warn(`fatal: ${event.error}`);
|
||||
break;
|
||||
default:
|
||||
logger.warn(`unknown event: ${event.code}`);
|
||||
|
||||
Reference in new issue
Block a user