diff --git a/README.md b/README.md index 6cb9e24a1..9cce11a03 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,8 @@ var art = new Artplayer({ | Project | Description | Demo | | ----------------------------------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------- | -| [artplayer-plugin-danmu](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-plugin-danmu) | Danmu plugin for ArtPlayer | `WIP` | +| [artplayer-plugin-danmu](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-plugin-danmu) | Danmu plugin for ArtPlayer | `WIP` | +| [artplayer-plugin-gif](https://github.com/zhw2590582/ArtPlayer/tree/master/packages/artplayer-plugin-gif) | Gif plugin for ArtPlayer | [demo](https://artplayer.org/gif/) | | [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 diff --git a/docs/lab/css/codemirror.css b/docs/assets/css/codemirror.css similarity index 100% rename from docs/lab/css/codemirror.css rename to docs/assets/css/codemirror.css diff --git a/docs/lab/css/consola.css b/docs/assets/css/consola.css similarity index 100% rename from docs/lab/css/consola.css rename to docs/assets/css/consola.css diff --git a/docs/lab/css/normalize.css b/docs/assets/css/normalize.css similarity index 100% rename from docs/lab/css/normalize.css rename to docs/assets/css/normalize.css diff --git a/docs/lab/img/favicon.ico b/docs/assets/img/favicon.ico similarity index 100% rename from docs/lab/img/favicon.ico rename to docs/assets/img/favicon.ico diff --git a/docs/lab/js/codemirror.js b/docs/assets/js/codemirror.js similarity index 100% rename from docs/lab/js/codemirror.js rename to docs/assets/js/codemirror.js diff --git a/docs/lab/js/consola.js b/docs/assets/js/consola.js similarity index 100% rename from docs/lab/js/consola.js rename to docs/assets/js/consola.js diff --git a/docs/lab/js/javascript.js b/docs/assets/js/javascript.js similarity index 100% rename from docs/lab/js/javascript.js rename to docs/assets/js/javascript.js diff --git a/docs/gif/css/common.css b/docs/gif/css/common.css new file mode 100644 index 000000000..4e7c98a94 --- /dev/null +++ b/docs/gif/css/common.css @@ -0,0 +1,51 @@ +html, body, .container { + height: 100%; +} + +.container { + display: flex; + flex-direction: column; +} + +.part-top { + display: flex; + height: 70%; +} + +.part-bottom { + position: relative; + height: 30%; +} + +.part-left { + width: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.part-right { + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.part-left .artplayer { + width: 500px; + height: 500px; + background: #fff; +} + +.part-right .preview { + display: flex; + align-items: center; + justify-content: center; + width: 500px; + height: 500px; + background: #f3f3f3; +} + +.part-right .preview img { + max-width: 100%; +} \ No newline at end of file diff --git a/docs/gif/index.html b/docs/gif/index.html new file mode 100644 index 000000000..25c172ab0 --- /dev/null +++ b/docs/gif/index.html @@ -0,0 +1,45 @@ + + + + + Gif plugin for ArtPlayer + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/docs/gif/js/common.js b/docs/gif/js/common.js new file mode 100644 index 000000000..cc522d489 --- /dev/null +++ b/docs/gif/js/common.js @@ -0,0 +1,34 @@ +'use strict'; + +(function() { + consola.creat({ + target: '.part-bottom', + size: '100%', + zIndex: 99, + }); + + console.info('Welcome, 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 art = new Artplayer({ + container: '.artplayer', + url: 'https://blog.zhw-island.com/assets-cdn/video/one-more-time-one-more-chance-480p.mp4', + title: '【新海诚动画】『秒速5センチメートル』', + autoSize: true, + controls: [ + { + name: 'preview', + position: 'right', + html: '打开', + mounted: $preview => { + art.plugins.localPreview.attach($preview); + }, + }, + ], + plugins: [artplayerPluginGif] + }); +})(); diff --git a/docs/lab/index.html b/docs/lab/index.html index e2266d58e..7e09c98af 100644 --- a/docs/lab/index.html +++ b/docs/lab/index.html @@ -8,11 +8,11 @@ - - + + + + - - @@ -37,9 +37,9 @@
- - - + + + + diff --git a/docs/thumbnail/js/common.js b/docs/thumbnail/js/common.js index 7987b5a32..d23c7694a 100644 --- a/docs/thumbnail/js/common.js +++ b/docs/thumbnail/js/common.js @@ -30,7 +30,7 @@ zIndex: 99, }); - console.info('Welcome to this tool, if you like it, consider star it, thank you.'); + console.info('Welcome, if you like it, consider star it, thank you.'); console.info('https://github.com/zhw2590582/ArtPlayer'); window.addEventListener('error', function(err) { diff --git a/docs/thumbnail/js/consola.js b/docs/thumbnail/js/consola.js deleted file mode 100644 index 7fa05c34c..000000000 --- a/docs/thumbnail/js/consola.js +++ /dev/null @@ -1 +0,0 @@ -!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;t0&&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",'
\n
\n
Console 0
\n
Clean
\n
\n
\n
'),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",'
\n
\n
'+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)+"
\n
"),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;o1&&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]})}}}])}); \ No newline at end of file diff --git a/docs/uncompiled/artplayer-plugin-gif.js b/docs/uncompiled/artplayer-plugin-gif.js new file mode 100644 index 000000000..1600412c3 --- /dev/null +++ b/docs/uncompiled/artplayer-plugin-gif.js @@ -0,0 +1,2864 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global['artplayer-plugin-gif'] = {}))); +}(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 createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; + } + + var gifshot = createCommonjsModule(function (module, exports) { + + (function (window, document, navigator, undefined) { + /* + utils.js + ======== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + var utils = { + URL: window.URL || window.webkitURL || window.mozURL || window.msURL, + getUserMedia: function () { + var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + return getUserMedia ? getUserMedia.bind(navigator) : getUserMedia; + }(), + requestAnimFrame: window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame, + requestTimeout: function requestTimeout(callback, delay) { + callback = callback || utils.noop; + delay = delay || 0; + + if (!utils.requestAnimFrame) { + return setTimeout(callback, delay); + } + + var start = new Date().getTime(); + var handle = new Object(); + var requestAnimFrame = utils.requestAnimFrame; + + var loop = function loop() { + var current = new Date().getTime(); + var delta = current - start; + delta >= delay ? callback.call() : handle.value = requestAnimFrame(loop); + }; + + handle.value = requestAnimFrame(loop); + return handle; + }, + Blob: window.Blob || window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder, + btoa: function () { + var btoa = window.btoa || function (input) { + var output = ''; + var i = 0; + var l = input.length; + var key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + var chr1 = void 0; + var chr2 = void 0; + var chr3 = void 0; + var enc1 = void 0; + var enc2 = void 0; + var enc3 = void 0; + var enc4 = void 0; + + while (i < l) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = (chr1 & 3) << 4 | chr2 >> 4; + enc3 = (chr2 & 15) << 2 | chr3 >> 6; + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); + } + + return output; + }; + + return btoa ? btoa.bind(window) : utils.noop; + }(), + isObject: function isObject(obj) { + return obj && Object.prototype.toString.call(obj) === '[object Object]'; + }, + isEmptyObject: function isEmptyObject(obj) { + return utils.isObject(obj) && !Object.keys(obj).length; + }, + isArray: function isArray(arr) { + return arr && Array.isArray(arr); + }, + isFunction: function isFunction(func) { + return func && typeof func === 'function'; + }, + isElement: function isElement(elem) { + return elem && elem.nodeType === 1; + }, + isString: function isString(value) { + return typeof value === 'string' || Object.prototype.toString.call(value) === '[object String]'; + }, + isSupported: { + canvas: function canvas() { + var el = document.createElement('canvas'); + return el && el.getContext && el.getContext('2d'); + }, + webworkers: function webworkers() { + return window.Worker; + }, + blob: function blob() { + return utils.Blob; + }, + Uint8Array: function Uint8Array() { + return window.Uint8Array; + }, + Uint32Array: function Uint32Array() { + return window.Uint32Array; + }, + videoCodecs: function () { + var testEl = document.createElement('video'); + var supportObj = { + 'mp4': false, + 'h264': false, + 'ogv': false, + 'ogg': false, + 'webm': false + }; + + try { + if (testEl && testEl.canPlayType) { + // Check for MPEG-4 support + supportObj.mp4 = testEl.canPlayType('video/mp4; codecs="mp4v.20.8"') !== ''; // Check for h264 support + + supportObj.h264 = (testEl.canPlayType('video/mp4; codecs="avc1.42E01E"') || testEl.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')) !== ''; // Check for Ogv support + + supportObj.ogv = testEl.canPlayType('video/ogg; codecs="theora"') !== ''; // Check for Ogg support + + supportObj.ogg = testEl.canPlayType('video/ogg; codecs="theora"') !== ''; // Check for Webm support + + supportObj.webm = testEl.canPlayType('video/webm; codecs="vp8, vorbis"') !== -1; + } + } catch (e) {} + + return supportObj; + }() + }, + noop: function noop() {}, + each: function each(collection, callback) { + var x = void 0; + var len = void 0; + + if (utils.isArray(collection)) { + x = -1; + len = collection.length; + + while (++x < len) { + if (callback(x, collection[x]) === false) { + break; + } + } + } else if (utils.isObject(collection)) { + for (x in collection) { + if (collection.hasOwnProperty(x)) { + if (callback(x, collection[x]) === false) { + break; + } + } + } + } + }, + mergeOptions: function mergeOptions(defaultOptions, userOptions) { + if (!utils.isObject(defaultOptions) || !utils.isObject(userOptions) || !Object.keys) { + return; + } + + var newObj = {}; + utils.each(defaultOptions, function (key, val) { + newObj[key] = defaultOptions[key]; + }); + utils.each(userOptions, function (key, val) { + var currentUserOption = userOptions[key]; + + if (!utils.isObject(currentUserOption)) { + newObj[key] = currentUserOption; + } else { + if (!defaultOptions[key]) { + newObj[key] = currentUserOption; + } else { + newObj[key] = utils.mergeOptions(defaultOptions[key], currentUserOption); + } + } + }); + return newObj; + }, + setCSSAttr: function setCSSAttr(elem, attr, val) { + if (!utils.isElement(elem)) { + return; + } + + if (utils.isString(attr) && utils.isString(val)) { + elem.style[attr] = val; + } else if (utils.isObject(attr)) { + utils.each(attr, function (key, val) { + elem.style[key] = val; + }); + } + }, + removeElement: function removeElement(node) { + if (!utils.isElement(node)) { + return; + } + + if (node.parentNode) { + node.parentNode.removeChild(node); + } + }, + createWebWorker: function createWebWorker(content) { + if (!utils.isString(content)) { + return {}; + } + + try { + var blob = new utils.Blob([content], { + 'type': 'text/javascript' + }); + var objectUrl = utils.URL.createObjectURL(blob); + var worker = new Worker(objectUrl); + return { + 'objectUrl': objectUrl, + 'worker': worker + }; + } catch (e) { + return '' + e; + } + }, + getExtension: function getExtension(src) { + return src.substr(src.lastIndexOf('.') + 1, src.length); + }, + getFontSize: function getFontSize() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + if (!document.body || options.resizeFont === false) { + return options.fontSize; + } + + var text = options.text; + var containerWidth = options.gifWidth; + var fontSize = parseInt(options.fontSize, 10); + var minFontSize = parseInt(options.minFontSize, 10); + var div = document.createElement('div'); + var span = document.createElement('span'); + div.setAttribute('width', containerWidth); + div.appendChild(span); + span.innerHTML = text; + span.style.fontSize = fontSize + 'px'; + span.style.textIndent = '-9999px'; + span.style.visibility = 'hidden'; + document.body.appendChild(span); + + while (span.offsetWidth > containerWidth && fontSize >= minFontSize) { + span.style.fontSize = --fontSize + 'px'; + } + + document.body.removeChild(span); + return fontSize + 'px'; + }, + webWorkerError: false + }; + var utils$2 = Object.freeze({ + default: utils + }); + /* + error.js + ======== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + var error = { + validate: function validate(skipObj) { + skipObj = utils.isObject(skipObj) ? skipObj : {}; + var errorObj = {}; + utils.each(error.validators, function (indece, currentValidator) { + var errorCode = currentValidator.errorCode; + + if (!skipObj[errorCode] && !currentValidator.condition) { + errorObj = currentValidator; + errorObj.error = true; + return false; + } + }); + delete errorObj.condition; + return errorObj; + }, + isValid: function isValid(skipObj) { + var errorObj = error.validate(skipObj); + var isValid = errorObj.error !== true ? true : false; + return isValid; + }, + validators: [{ + condition: utils.isFunction(utils.getUserMedia), + errorCode: 'getUserMedia', + errorMsg: 'The getUserMedia API is not supported in your browser' + }, { + condition: utils.isSupported.canvas(), + errorCode: 'canvas', + errorMsg: 'Canvas elements are not supported in your browser' + }, { + condition: utils.isSupported.webworkers(), + errorCode: 'webworkers', + errorMsg: 'The Web Workers API is not supported in your browser' + }, { + condition: utils.isFunction(utils.URL), + errorCode: 'window.URL', + errorMsg: 'The window.URL API is not supported in your browser' + }, { + condition: utils.isSupported.blob(), + errorCode: 'window.Blob', + errorMsg: 'The window.Blob File API is not supported in your browser' + }, { + condition: utils.isSupported.Uint8Array(), + errorCode: 'window.Uint8Array', + errorMsg: 'The window.Uint8Array function constructor is not supported in your browser' + }, { + condition: utils.isSupported.Uint32Array(), + errorCode: 'window.Uint32Array', + errorMsg: 'The window.Uint32Array function constructor is not supported in your browser' + }], + messages: { + videoCodecs: { + errorCode: 'videocodec', + errorMsg: 'The video codec you are trying to use is not supported in your browser' + } + } + }; + var error$2 = Object.freeze({ + default: error + }); + /* + defaultOptions.js + ================= + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Helpers + + var noop = function noop() {}; + + var defaultOptions = { + sampleInterval: 10, + numWorkers: 2, + filter: '', + gifWidth: 200, + gifHeight: 200, + interval: 0.1, + numFrames: 10, + frameDuration: 1, + keepCameraOn: false, + images: [], + video: null, + webcamVideoElement: null, + cameraStream: null, + text: '', + fontWeight: 'normal', + fontSize: '16px', + minFontSize: '10px', + resizeFont: false, + fontFamily: 'sans-serif', + fontColor: '#ffffff', + textAlign: 'center', + textBaseline: 'bottom', + textXCoordinate: null, + textYCoordinate: null, + progressCallback: noop, + completeCallback: noop, + saveRenderingContexts: false, + savedRenderingContexts: [], + crossOrigin: 'Anonymous' + }; + var defaultOptions$2 = Object.freeze({ + default: defaultOptions + }); + /* + isSupported.js + ============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + function isSupported() { + return error.isValid(); + } + /* + isWebCamGIFSupported.js + ======================= + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + + function isWebCamGIFSupported() { + return error.isValid(); + } + /* + isSupported.js + ============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function isSupported$1() { + var options = { + getUserMedia: true + }; + return error.isValid(options); + } + /* + isExistingVideoGIFSupported.js + ============================== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function isExistingVideoGIFSupported(codecs) { + var hasValidCodec = false; + + if (utils.isArray(codecs) && codecs.length) { + utils.each(codecs, function (indece, currentCodec) { + if (utils.isSupported.videoCodecs[currentCodec]) { + hasValidCodec = true; + } + }); + + if (!hasValidCodec) { + return false; + } + } else if (utils.isString(codecs) && codecs.length) { + if (!utils.isSupported.videoCodecs[codecs]) { + return false; + } + } + + return error.isValid({ + 'getUserMedia': true + }); + } + /* + NeuQuant.js + =========== + */ + + /* + * NeuQuant Neural-Net Quantization Algorithm + * ------------------------------------------ + * + * Copyright (c) 1994 Anthony Dekker + * + * NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See + * "Kohonen neural networks for optimal colour quantization" in "Network: + * Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of + * the algorithm. + * + * Any party obtaining a copy of these files from the author, directly or + * indirectly, is granted, free of charge, a full and unrestricted irrevocable, + * world-wide, paid up, royalty-free, nonexclusive right and license to deal in + * this software and documentation files (the "Software"), including without + * limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons who + * receive copies from any such party to do so, with the only requirement being + * that this copyright notice remain intact. + */ + + /* + * This class handles Neural-Net quantization algorithm + * @author Kevin Weiner (original Java version - kweiner@fmsware.com) + * @author Thibault Imbert (AS3 version - bytearray.org) + * @version 0.1 AS3 implementation + * @version 0.2 JS->AS3 "translation" by antimatter15 + * @version 0.3 JS clean up + using modern JS idioms by sole - http://soledadpenades.com + * Also implement fix in color conversion described at http://stackoverflow.com/questions/16371712/neuquant-js-javascript-color-quantization-hidden-bug-in-js-conversion + */ + + + function NeuQuant() { + var netsize = 256; // number of colours used + // four primes near 500 - assume no image has a length so large + // that it is divisible by all four primes + + var prime1 = 499; + var prime2 = 491; + var prime3 = 487; + var prime4 = 503; // minimum size for input image + + var minpicturebytes = 3 * prime4; // Network Definitions + + var maxnetpos = netsize - 1; + var netbiasshift = 4; // bias for colour values + + var ncycles = 100; // no. of learning cycles + // defs for freq and bias + + var intbiasshift = 16; // bias for fractions + + var intbias = 1 << intbiasshift; + var gammashift = 10; // gamma = 1024 + var betashift = 10; + var beta = intbias >> betashift; // beta = 1/1024 + + var betagamma = intbias << gammashift - betashift; // defs for decreasing radius factor + // For 256 colors, radius starts at 32.0 biased by 6 bits + // and decreases by a factor of 1/30 each cycle + + var initrad = netsize >> 3; + var radiusbiasshift = 6; + var radiusbias = 1 << radiusbiasshift; + var initradius = initrad * radiusbias; + var radiusdec = 30; // defs for decreasing alpha factor + // Alpha starts at 1.0 biased by 10 bits + + var alphabiasshift = 10; + var initalpha = 1 << alphabiasshift; + var alphadec; // radbias and alpharadbias used for radpower calculation + + var radbiasshift = 8; + var radbias = 1 << radbiasshift; + var alpharadbshift = alphabiasshift + radbiasshift; + var alpharadbias = 1 << alpharadbshift; // Input image + + var thepicture; // Height * Width * 3 + + var lengthcount; // Sampling factor 1..30 + + var samplefac; // The network itself + + var network; + var netindex = []; // for network lookup - really 256 + + var bias = []; // bias and freq arrays for learning + + var freq = []; + var radpower = []; + + function NeuQuantConstructor(thepic, len, sample) { + var i; + var p; + thepicture = thepic; + lengthcount = len; + samplefac = sample; + network = new Array(netsize); + + for (i = 0; i < netsize; i++) { + network[i] = new Array(4); + p = network[i]; + p[0] = p[1] = p[2] = (i << netbiasshift + 8) / netsize | 0; + freq[i] = intbias / netsize | 0; // 1 / netsize + + bias[i] = 0; + } + } + + function colorMap() { + var map = []; + var index = new Array(netsize); + + for (var i = 0; i < netsize; i++) { + index[network[i][3]] = i; + } + + var k = 0; + + for (var l = 0; l < netsize; l++) { + var j = index[l]; + map[k++] = network[j][0]; + map[k++] = network[j][1]; + map[k++] = network[j][2]; + } + + return map; + } // Insertion sort of network and building of netindex[0..255] + // (to do after unbias) + + + function inxbuild() { + var i; + var j; + var smallpos; + var smallval; + var p; + var q; + var previouscol; + var startpos; + previouscol = 0; + startpos = 0; + + for (i = 0; i < netsize; i++) { + p = network[i]; + smallpos = i; + smallval = p[1]; // index on g + // find smallest in i..netsize-1 + + for (j = i + 1; j < netsize; j++) { + q = network[j]; + + if (q[1] < smallval) { + // index on g + smallpos = j; + smallval = q[1]; // index on g + } + } + + q = network[smallpos]; // swap p (i) and q (smallpos) entries + + if (i != smallpos) { + j = q[0]; + q[0] = p[0]; + p[0] = j; + j = q[1]; + q[1] = p[1]; + p[1] = j; + j = q[2]; + q[2] = p[2]; + p[2] = j; + j = q[3]; + q[3] = p[3]; + p[3] = j; + } // smallval entry is now in position i + + + if (smallval != previouscol) { + netindex[previouscol] = startpos + i >> 1; + + for (j = previouscol + 1; j < smallval; j++) { + netindex[j] = i; + } + + previouscol = smallval; + startpos = i; + } + } + + netindex[previouscol] = startpos + maxnetpos >> 1; + + for (j = previouscol + 1; j < 256; j++) { + netindex[j] = maxnetpos; // really 256 + } + } // Main Learning Loop + + + function learn() { + var i; + var j; + var b; + var g; + var r; + var radius; + var rad; + var alpha; + var step; + var delta; + var samplepixels; + var p; + var pix; + var lim; + + if (lengthcount < minpicturebytes) { + samplefac = 1; + } + + alphadec = 30 + (samplefac - 1) / 3; + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = lengthcount / (3 * samplefac); + delta = samplepixels / ncycles | 0; + alpha = initalpha; + radius = initradius; + rad = radius >> radiusbiasshift; + + if (rad <= 1) { + rad = 0; + } + + for (i = 0; i < rad; i++) { + radpower[i] = alpha * ((rad * rad - i * i) * radbias / (rad * rad)); + } + + if (lengthcount < minpicturebytes) { + step = 3; + } else if (lengthcount % prime1 !== 0) { + step = 3 * prime1; + } else { + if (lengthcount % prime2 !== 0) { + step = 3 * prime2; + } else { + if (lengthcount % prime3 !== 0) { + step = 3 * prime3; + } else { + step = 3 * prime4; + } + } + } + + i = 0; + + while (i < samplepixels) { + b = (p[pix + 0] & 0xff) << netbiasshift; + g = (p[pix + 1] & 0xff) << netbiasshift; + r = (p[pix + 2] & 0xff) << netbiasshift; + j = contest(b, g, r); + altersingle(alpha, j, b, g, r); + + if (rad !== 0) { + // Alter neighbours + alterneigh(rad, j, b, g, r); + } + + pix += step; + + if (pix >= lim) { + pix -= lengthcount; + } + + i++; + + if (delta === 0) { + delta = 1; + } + + if (i % delta === 0) { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + + if (rad <= 1) { + rad = 0; + } + + for (j = 0; j < rad; j++) { + radpower[j] = alpha * ((rad * rad - j * j) * radbias / (rad * rad)); + } + } + } + } // Search for BGR values 0..255 (after net is unbiased) and return colour index + + + function map(b, g, r) { + var i; + var j; + var dist; + var a; + var bestd; + var p; + var best; // Biggest possible distance is 256 * 3 + + bestd = 1000; + best = -1; + i = netindex[g]; // index on g + + j = i - 1; // start at netindex[g] and work outwards + + while (i < netsize || j >= 0) { + if (i < netsize) { + p = network[i]; + dist = p[1] - g; // inx key + + if (dist >= bestd) { + i = netsize; // stop iter + } else { + i++; + + if (dist < 0) { + dist = -dist; + } + + a = p[0] - b; + + if (a < 0) { + a = -a; + } + + dist += a; + + if (dist < bestd) { + a = p[2] - r; + + if (a < 0) { + a = -a; + } + + dist += a; + + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + + if (j >= 0) { + p = network[j]; + dist = g - p[1]; // inx key - reverse dif + + if (dist >= bestd) { + j = -1; // stop iter + } else { + j--; + + if (dist < 0) { + dist = -dist; + } + + a = p[0] - b; + + if (a < 0) { + a = -a; + } + + dist += a; + + if (dist < bestd) { + a = p[2] - r; + + if (a < 0) { + a = -a; + } + + dist += a; + + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + } + + return best; + } + + function process() { + learn(); + unbiasnet(); + inxbuild(); + return colorMap(); + } // Unbias network to give byte values 0..255 and record position i + // to prepare for sort + + + function unbiasnet() { + var i; + + for (i = 0; i < netsize; i++) { + network[i][0] >>= netbiasshift; + network[i][1] >>= netbiasshift; + network[i][2] >>= netbiasshift; + network[i][3] = i; // record colour no + } + } // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) + // in radpower[|i-j|] + + + function alterneigh(rad, i, b, g, r) { + var j; + var k; + var lo; + var hi; + var a; + var m; + var p; + lo = i - rad; + + if (lo < -1) { + lo = -1; + } + + hi = i + rad; + + if (hi > netsize) { + hi = netsize; + } + + j = i + 1; + k = i - 1; + m = 1; + + while (j < hi || k > lo) { + a = radpower[m++]; + + if (j < hi) { + p = network[j++]; + + try { + p[0] -= a * (p[0] - b) / alpharadbias | 0; + p[1] -= a * (p[1] - g) / alpharadbias | 0; + p[2] -= a * (p[2] - r) / alpharadbias | 0; + } catch (e) {} + } + + if (k > lo) { + p = network[k--]; + + try { + p[0] -= a * (p[0] - b) / alpharadbias | 0; + p[1] -= a * (p[1] - g) / alpharadbias | 0; + p[2] -= a * (p[2] - r) / alpharadbias | 0; + } catch (e) {} + } + } + } // Move neuron i towards biased (b,g,r) by factor alpha + + + function altersingle(alpha, i, b, g, r) { + // alter hit neuron + var n = network[i]; + var alphaMult = alpha / initalpha; + n[0] -= alphaMult * (n[0] - b) | 0; + n[1] -= alphaMult * (n[1] - g) | 0; + n[2] -= alphaMult * (n[2] - r) | 0; + } // Search for biased BGR values + + + function contest(b, g, r) { + // finds closest neuron (min dist) and updates freq + // finds best neuron (min dist-bias) and returns position + // for frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma*((1/netsize)-freq[i]) + var i; + var dist; + var a; + var biasdist; + var betafreq; + var bestpos; + var bestbiaspos; + var bestd; + var bestbiasd; + var n; + bestd = ~(1 << 31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + + for (i = 0; i < netsize; i++) { + n = network[i]; + dist = n[0] - b; + + if (dist < 0) { + dist = -dist; + } + + a = n[1] - g; + + if (a < 0) { + a = -a; + } + + dist += a; + a = n[2] - r; + + if (a < 0) { + a = -a; + } + + dist += a; + + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + + biasdist = dist - (bias[i] >> intbiasshift - netbiasshift); + + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = freq[i] >> betashift; + freq[i] -= betafreq; + bias[i] += betafreq << gammashift; + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return bestbiaspos; + } + + NeuQuantConstructor.apply(this, arguments); + var exports = {}; + exports.map = map; + exports.process = process; + return exports; + } + /* + processFrameWorker.js + ===================== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + + function workerCode() { + var self = this; + + try { + self.onmessage = function (ev) { + var data = ev.data || {}; + var response; + + if (data.gifshot) { + response = workerMethods.run(data); + postMessage(response); + } + }; + } catch (e) {} + + var workerMethods = { + dataToRGB: function dataToRGB(data, width, height) { + var length = width * height * 4; + var i = 0; + var rgb = []; + + while (i < length) { + rgb.push(data[i++]); + rgb.push(data[i++]); + rgb.push(data[i++]); + i++; // for the alpha channel which we don't care about + } + + return rgb; + }, + componentizedPaletteToArray: function componentizedPaletteToArray(paletteRGB) { + paletteRGB = paletteRGB || []; + var paletteArray = []; + + for (var i = 0; i < paletteRGB.length; i += 3) { + var r = paletteRGB[i]; + var g = paletteRGB[i + 1]; + var b = paletteRGB[i + 2]; + paletteArray.push(r << 16 | g << 8 | b); + } + + return paletteArray; + }, + // This is the "traditional" Animated_GIF style of going from RGBA to indexed color frames + 'processFrameWithQuantizer': function processFrameWithQuantizer(imageData, width, height, sampleInterval) { + var rgbComponents = this.dataToRGB(imageData, width, height); + var nq = new NeuQuant(rgbComponents, rgbComponents.length, sampleInterval); + var paletteRGB = nq.process(); + var paletteArray = new Uint32Array(this.componentizedPaletteToArray(paletteRGB)); + var numberPixels = width * height; + var indexedPixels = new Uint8Array(numberPixels); + var k = 0; + + for (var i = 0; i < numberPixels; i++) { + var r = rgbComponents[k++]; + var g = rgbComponents[k++]; + var b = rgbComponents[k++]; + indexedPixels[i] = nq.map(r, g, b); + } + + return { + pixels: indexedPixels, + palette: paletteArray + }; + }, + 'run': function run(frame) { + frame = frame || {}; + var _frame = frame, + height = _frame.height, + palette = _frame.palette, + sampleInterval = _frame.sampleInterval, + width = _frame.width; + var imageData = frame.data; + return this.processFrameWithQuantizer(imageData, width, height, sampleInterval); + } + }; + return workerMethods; + } + /* + gifWriter.js + ============ + */ + // (c) Dean McNamee , 2013. + // + // https://github.com/deanm/omggif + // + // 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. + // + // omggif is a JavaScript implementation of a GIF 89a encoder and decoder, + // including animation and compression. It does not rely on any specific + // underlying system, so should run in the browser, Node, or Plask. + + + function gifWriter(buf, width, height, gopts) { + var p = 0; + gopts = gopts === undefined ? {} : gopts; + var loop_count = gopts.loop === undefined ? null : gopts.loop; + var global_palette = gopts.palette === undefined ? null : gopts.palette; + if (width <= 0 || height <= 0 || width > 65535 || height > 65535) throw "Width/Height invalid."; + + function check_palette_and_num_colors(palette) { + var num_colors = palette.length; + if (num_colors < 2 || num_colors > 256 || num_colors & num_colors - 1) throw "Invalid code/color length, must be power of 2 and 2 .. 256."; + return num_colors; + } // - Header. + + + buf[p++] = 0x47; + buf[p++] = 0x49; + buf[p++] = 0x46; // GIF + + buf[p++] = 0x38; + buf[p++] = 0x39; + buf[p++] = 0x61; // 89a + // Handling of Global Color Table (palette) and background index. + + var gp_num_colors_pow2 = 0; + var background = 0; // - Logical Screen Descriptor. + // NOTE(deanm): w/h apparently ignored by implementations, but set anyway. + + buf[p++] = width & 0xff; + buf[p++] = width >> 8 & 0xff; + buf[p++] = height & 0xff; + buf[p++] = height >> 8 & 0xff; // NOTE: Indicates 0-bpp original color resolution (unused?). + + buf[p++] = (global_palette !== null ? 0x80 : 0) | // Global Color Table Flag. + gp_num_colors_pow2; // NOTE: No sort flag (unused?). + + buf[p++] = background; // Background Color Index. + + buf[p++] = 0; // Pixel aspect ratio (unused?). + + if (loop_count !== null) { + // Netscape block for looping. + if (loop_count < 0 || loop_count > 65535) throw "Loop count invalid."; // Extension code, label, and length. + + buf[p++] = 0x21; + buf[p++] = 0xff; + buf[p++] = 0x0b; // NETSCAPE2.0 + + buf[p++] = 0x4e; + buf[p++] = 0x45; + buf[p++] = 0x54; + buf[p++] = 0x53; + buf[p++] = 0x43; + buf[p++] = 0x41; + buf[p++] = 0x50; + buf[p++] = 0x45; + buf[p++] = 0x32; + buf[p++] = 0x2e; + buf[p++] = 0x30; // Sub-block + + buf[p++] = 0x03; + buf[p++] = 0x01; + buf[p++] = loop_count & 0xff; + buf[p++] = loop_count >> 8 & 0xff; + buf[p++] = 0x00; // Terminator. + } + + var ended = false; + + this.addFrame = function (x, y, w, h, indexed_pixels, opts) { + if (ended === true) { + --p; + ended = false; + } // Un-end. + + + opts = opts === undefined ? {} : opts; // TODO(deanm): Bounds check x, y. Do they need to be within the virtual + // canvas width/height, I imagine? + + if (x < 0 || y < 0 || x > 65535 || y > 65535) throw "x/y invalid."; + if (w <= 0 || h <= 0 || w > 65535 || h > 65535) throw "Width/Height invalid."; + if (indexed_pixels.length < w * h) throw "Not enough pixels for the frame size."; + var using_local_palette = true; + var palette = opts.palette; + + if (palette === undefined || palette === null) { + using_local_palette = false; + palette = global_palette; + } + + if (palette === undefined || palette === null) throw "Must supply either a local or global palette."; + var num_colors = check_palette_and_num_colors(palette); // Compute the min_code_size (power of 2), destroying num_colors. + + var min_code_size = 0; + + while (num_colors >>= 1) { + ++min_code_size; + } + + num_colors = 1 << min_code_size; // Now we can easily get it back. + + var delay = opts.delay === undefined ? 0 : opts.delay; // From the spec: + // 0 - No disposal specified. The decoder is + // not required to take any action. + // 1 - Do not dispose. The graphic is to be left + // in place. + // 2 - Restore to background color. The area used by the + // graphic must be restored to the background color. + // 3 - Restore to previous. The decoder is required to + // restore the area overwritten by the graphic with + // what was there prior to rendering the graphic. + // 4-7 - To be defined. + // NOTE(deanm): Dispose background doesn't really work, apparently most + // browsers ignore the background palette index and clear to transparency. + + var disposal = opts.disposal === undefined ? 0 : opts.disposal; + if (disposal < 0 || disposal > 3) // 4-7 is reserved. + throw "Disposal out of range."; + var use_transparency = false; + var transparent_index = 0; + + if (opts.transparent !== undefined && opts.transparent !== null) { + use_transparency = true; + transparent_index = opts.transparent; + if (transparent_index < 0 || transparent_index >= num_colors) throw "Transparent color index."; + } + + if (disposal !== 0 || use_transparency || delay !== 0) { + // - Graphics Control Extension + buf[p++] = 0x21; + buf[p++] = 0xf9; // Extension / Label. + + buf[p++] = 4; // Byte size. + + buf[p++] = disposal << 2 | (use_transparency === true ? 1 : 0); + buf[p++] = delay & 0xff; + buf[p++] = delay >> 8 & 0xff; + buf[p++] = transparent_index; // Transparent color index. + + buf[p++] = 0; // Block Terminator. + } // - Image Descriptor + + + buf[p++] = 0x2c; // Image Seperator. + + buf[p++] = x & 0xff; + buf[p++] = x >> 8 & 0xff; // Left. + + buf[p++] = y & 0xff; + buf[p++] = y >> 8 & 0xff; // Top. + + buf[p++] = w & 0xff; + buf[p++] = w >> 8 & 0xff; + buf[p++] = h & 0xff; + buf[p++] = h >> 8 & 0xff; // NOTE: No sort flag (unused?). + // TODO(deanm): Support interlace. + + buf[p++] = using_local_palette === true ? 0x80 | min_code_size - 1 : 0; // - Local Color Table + + if (using_local_palette === true) { + for (var i = 0, il = palette.length; i < il; ++i) { + var rgb = palette[i]; + buf[p++] = rgb >> 16 & 0xff; + buf[p++] = rgb >> 8 & 0xff; + buf[p++] = rgb & 0xff; + } + } + + p = GifWriterOutputLZWCodeStream(buf, p, min_code_size < 2 ? 2 : min_code_size, indexed_pixels); + }; + + this.end = function () { + if (ended === false) { + buf[p++] = 0x3b; // Trailer. + + ended = true; + } + + return p; + }; // Main compression routine, palette indexes -> LZW code stream. + // |index_stream| must have at least one entry. + + + function GifWriterOutputLZWCodeStream(buf, p, min_code_size, index_stream) { + buf[p++] = min_code_size; + var cur_subblock = p++; // Pointing at the length field. + + var clear_code = 1 << min_code_size; + var code_mask = clear_code - 1; + var eoi_code = clear_code + 1; + var next_code = eoi_code + 1; + var cur_code_size = min_code_size + 1; // Number of bits per code. + + var cur_shift = 0; // We have at most 12-bit codes, so we should have to hold a max of 19 + // bits here (and then we would write out). + + var cur = 0; + + function emit_bytes_to_buffer(bit_block_size) { + while (cur_shift >= bit_block_size) { + buf[p++] = cur & 0xff; + cur >>= 8; + cur_shift -= 8; + + if (p === cur_subblock + 256) { + // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + } + + function emit_code(c) { + cur |= c << cur_shift; + cur_shift += cur_code_size; + emit_bytes_to_buffer(8); + } // I am not an expert on the topic, and I don't want to write a thesis. + // However, it is good to outline here the basic algorithm and the few data + // structures and optimizations here that make this implementation fast. + // The basic idea behind LZW is to build a table of previously seen runs + // addressed by a short id (herein called output code). All data is + // referenced by a code, which represents one or more values from the + // original input stream. All input bytes can be referenced as the same + // value as an output code. So if you didn't want any compression, you + // could more or less just output the original bytes as codes (there are + // some details to this, but it is the idea). In order to achieve + // compression, values greater then the input range (codes can be up to + // 12-bit while input only 8-bit) represent a sequence of previously seen + // inputs. The decompressor is able to build the same mapping while + // decoding, so there is always a shared common knowledge between the + // encoding and decoder, which is also important for "timing" aspects like + // how to handle variable bit width code encoding. + // + // One obvious but very important consequence of the table system is there + // is always a unique id (at most 12-bits) to map the runs. 'A' might be + // 4, then 'AA' might be 10, 'AAA' 11, 'AAAA' 12, etc. This relationship + // can be used for an effecient lookup strategy for the code mapping. We + // need to know if a run has been seen before, and be able to map that run + // to the output code. Since we start with known unique ids (input bytes), + // and then from those build more unique ids (table entries), we can + // continue this chain (almost like a linked list) to always have small + // integer values that represent the current byte chains in the encoder. + // This means instead of tracking the input bytes (AAAABCD) to know our + // current state, we can track the table entry for AAAABC (it is guaranteed + // to exist by the nature of the algorithm) and the next character D. + // Therefor the tuple of (table_entry, byte) is guaranteed to also be + // unique. This allows us to create a simple lookup key for mapping input + // sequences to codes (table indices) without having to store or search + // any of the code sequences. So if 'AAAA' has a table entry of 12, the + // tuple of ('AAAA', K) for any input byte K will be unique, and can be our + // key. This leads to a integer value at most 20-bits, which can always + // fit in an SMI value and be used as a fast sparse array / object key. + // Output code for the current contents of the index buffer. + + + var ib_code = index_stream[0] & code_mask; // Load first input index. + + var code_table = {}; // Key'd on our 20-bit "tuple". + + emit_code(clear_code); // Spec says first code should be a clear code. + // First index already loaded, process the rest of the stream. + + for (var i = 1, il = index_stream.length; i < il; ++i) { + var k = index_stream[i] & code_mask; + var cur_key = ib_code << 8 | k; // (prev, k) unique tuple. + + var cur_code = code_table[cur_key]; // buffer + k. + // Check if we have to create a new code table entry. + + if (cur_code === undefined) { + // We don't have buffer + k. + // Emit index buffer (without k). + // This is an inline version of emit_code, because this is the core + // writing routine of the compressor (and V8 cannot inline emit_code + // because it is a closure here in a different context). Additionally + // we can call emit_byte_to_buffer less often, because we can have + // 30-bits (from our 31-bit signed SMI), and we know our codes will only + // be 12-bits, so can safely have 18-bits there without overflow. + // emit_code(ib_code); + cur |= ib_code << cur_shift; + cur_shift += cur_code_size; + + while (cur_shift >= 8) { + buf[p++] = cur & 0xff; + cur >>= 8; + cur_shift -= 8; + + if (p === cur_subblock + 256) { + // Finished a subblock. + buf[cur_subblock] = 255; + cur_subblock = p++; + } + } + + if (next_code === 4096) { + // Table full, need a clear. + emit_code(clear_code); + next_code = eoi_code + 1; + cur_code_size = min_code_size + 1; + code_table = {}; + } else { + // Table not full, insert a new entry. + // Increase our variable bit code sizes if necessary. This is a bit + // tricky as it is based on "timing" between the encoding and + // decoder. From the encoders perspective this should happen after + // we've already emitted the index buffer and are about to create the + // first table entry that would overflow our current code bit size. + if (next_code >= 1 << cur_code_size) ++cur_code_size; + code_table[cur_key] = next_code++; // Insert into code table. + } + + ib_code = k; // Index buffer to single input k. + } else { + ib_code = cur_code; // Index buffer to sequence in code table. + } + } + + emit_code(ib_code); // There will still be something in the index buffer. + + emit_code(eoi_code); // End Of Information. + // Flush / finalize the sub-blocks stream to the buffer. + + emit_bytes_to_buffer(1); // Finish the sub-blocks, writing out any unfinished lengths and + // terminating with a sub-block of length 0. If we have already started + // but not yet used a sub-block it can just become the terminator. + + if (cur_subblock + 1 === p) { + // Started but unused. + buf[cur_subblock] = 0; + } else { + // Started and used, write length and additional terminator block. + buf[cur_subblock] = p - cur_subblock - 1; + buf[p++] = 0; + } + + return p; + } + } + /* + animatedGIF.js + ============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + // Helpers + + + var noop$2 = function noop() {}; + + var AnimatedGIF = function AnimatedGIF(options) { + this.canvas = null; + this.ctx = null; + this.repeat = 0; + this.frames = []; + this.numRenderedFrames = 0; + this.onRenderCompleteCallback = noop$2; + this.onRenderProgressCallback = noop$2; + this.workers = []; + this.availableWorkers = []; + this.generatingGIF = false; + this.options = options; // Constructs and initializes the the web workers appropriately + + this.initializeWebWorkers(options); + }; + + AnimatedGIF.prototype = { + 'workerMethods': workerCode(), + 'initializeWebWorkers': function initializeWebWorkers(options) { + var self = this; + var processFrameWorkerCode = NeuQuant.toString() + '(' + workerCode.toString() + '());'; + var webWorkerObj = void 0; + var objectUrl = void 0; + var webWorker = void 0; + var numWorkers = void 0; + var x = -1; + var workerError = ''; + numWorkers = options.numWorkers; + + while (++x < numWorkers) { + webWorkerObj = utils.createWebWorker(processFrameWorkerCode); + + if (utils.isObject(webWorkerObj)) { + objectUrl = webWorkerObj.objectUrl; + webWorker = webWorkerObj.worker; + self.workers.push({ + worker: webWorker, + objectUrl: objectUrl + }); + self.availableWorkers.push(webWorker); + } else { + workerError = webWorkerObj; + utils.webWorkerError = !!webWorkerObj; + } + } + + this.workerError = workerError; + this.canvas = document.createElement('canvas'); + this.canvas.width = options.gifWidth; + this.canvas.height = options.gifHeight; + this.ctx = this.canvas.getContext('2d'); + this.frames = []; + }, + // Return a worker for processing a frame + getWorker: function getWorker() { + return this.availableWorkers.pop(); + }, + // Restores a worker to the pool + freeWorker: function freeWorker(worker) { + this.availableWorkers.push(worker); + }, + byteMap: function () { + var byteMap = []; + + for (var i = 0; i < 256; i++) { + byteMap[i] = String.fromCharCode(i); + } + + return byteMap; + }(), + bufferToString: function bufferToString(buffer) { + var numberValues = buffer.length; + var str = ''; + var x = -1; + + while (++x < numberValues) { + str += this.byteMap[buffer[x]]; + } + + return str; + }, + onFrameFinished: function onFrameFinished(progressCallback) { + // The GIF is not written until we're done with all the frames + // because they might not be processed in the same order + var self = this; + var frames = self.frames; + var options = self.options; + var hasExistingImages = !!(options.images || []).length; + var allDone = frames.every(function (frame) { + return !frame.beingProcessed && frame.done; + }); + self.numRenderedFrames++; + + if (hasExistingImages) { + progressCallback(self.numRenderedFrames / frames.length); + } + + self.onRenderProgressCallback(self.numRenderedFrames * 0.75 / frames.length); + + if (allDone) { + if (!self.generatingGIF) { + self.generateGIF(frames, self.onRenderCompleteCallback); + } + } else { + utils.requestTimeout(function () { + self.processNextFrame(); + }, 1); + } + }, + processFrame: function processFrame(position) { + var AnimatedGifContext = this; + var options = this.options; + var _options = this.options, + progressCallback = _options.progressCallback, + sampleInterval = _options.sampleInterval; + var frames = this.frames; + var frame = void 0; + var worker = void 0; + + var done = function done() { + var ev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var data = ev.data; // Delete original data, and free memory + + delete frame.data; + frame.pixels = Array.prototype.slice.call(data.pixels); + frame.palette = Array.prototype.slice.call(data.palette); + frame.done = true; + frame.beingProcessed = false; + AnimatedGifContext.freeWorker(worker); + AnimatedGifContext.onFrameFinished(progressCallback); + }; + + frame = frames[position]; + + if (frame.beingProcessed || frame.done) { + this.onFrameFinished(); + return; + } + + frame.sampleInterval = sampleInterval; + frame.beingProcessed = true; + frame.gifshot = true; + worker = this.getWorker(); + + if (worker) { + // Process the frame in a web worker + worker.onmessage = done; + worker.postMessage(frame); + } else { + // Process the frame in the current thread + done({ + 'data': AnimatedGifContext.workerMethods.run(frame) + }); + } + }, + startRendering: function startRendering(completeCallback) { + this.onRenderCompleteCallback = completeCallback; + + for (var i = 0; i < this.options.numWorkers && i < this.frames.length; i++) { + this.processFrame(i); + } + }, + processNextFrame: function processNextFrame() { + var position = -1; + + for (var i = 0; i < this.frames.length; i++) { + var frame = this.frames[i]; + + if (!frame.done && !frame.beingProcessed) { + position = i; + break; + } + } + + if (position >= 0) { + this.processFrame(position); + } + }, + // Takes the already processed data in frames and feeds it to a new + // GifWriter instance in order to get the binary GIF file + generateGIF: function generateGIF(frames, callback) { + // TODO: Weird: using a simple JS array instead of a typed array, + // the files are WAY smaller o_o. Patches/explanations welcome! + var buffer = []; // new Uint8Array(width * height * frames.length * 5); + + var gifOptions = { + loop: this.repeat + }; + var options = this.options; + var interval = options.interval; + var frameDuration = options.frameDuration; + var existingImages = options.images; + var hasExistingImages = !!existingImages.length; + var height = options.gifHeight; + var width = options.gifWidth; + var gifWriter$$1 = new gifWriter(buffer, width, height, gifOptions); + var onRenderProgressCallback = this.onRenderProgressCallback; + var delay = hasExistingImages ? interval * 100 : 0; + var bufferToString = void 0; + var gif = void 0; + this.generatingGIF = true; + utils.each(frames, function (iterator, frame) { + var framePalette = frame.palette; + onRenderProgressCallback(0.75 + 0.25 * frame.position * 1.0 / frames.length); + + for (var i = 0; i < frameDuration; i++) { + gifWriter$$1.addFrame(0, 0, width, height, frame.pixels, { + palette: framePalette, + delay: delay + }); + } + }); + gifWriter$$1.end(); + onRenderProgressCallback(1.0); + this.frames = []; + this.generatingGIF = false; + + if (utils.isFunction(callback)) { + bufferToString = this.bufferToString(buffer); + gif = 'data:image/gif;base64,' + utils.btoa(bufferToString); + callback(gif); + } + }, + // From GIF: 0 = loop forever, null = not looping, n > 0 = loop n times and stop + setRepeat: function setRepeat(r) { + this.repeat = r; + }, + addFrame: function addFrame(element, gifshotOptions) { + gifshotOptions = utils.isObject(gifshotOptions) ? gifshotOptions : {}; + var self = this; + var ctx = self.ctx; + var options = self.options; + var width = options.gifWidth; + var height = options.gifHeight; + var fontSize = utils.getFontSize(gifshotOptions); + var _gifshotOptions = gifshotOptions, + filter = _gifshotOptions.filter, + fontColor = _gifshotOptions.fontColor, + fontFamily = _gifshotOptions.fontFamily, + fontWeight = _gifshotOptions.fontWeight, + gifHeight = _gifshotOptions.gifHeight, + gifWidth = _gifshotOptions.gifWidth, + text = _gifshotOptions.text, + textAlign = _gifshotOptions.textAlign, + textBaseline = _gifshotOptions.textBaseline; + var textXCoordinate = gifshotOptions.textXCoordinate ? gifshotOptions.textXCoordinate : textAlign === 'left' ? 1 : textAlign === 'right' ? width : width / 2; + var textYCoordinate = gifshotOptions.textYCoordinate ? gifshotOptions.textYCoordinate : textBaseline === 'top' ? 1 : textBaseline === 'center' ? height / 2 : height; + var font = fontWeight + ' ' + fontSize + ' ' + fontFamily; + var imageData = void 0; + + try { + ctx.filter = filter; + ctx.drawImage(element, 0, 0, width, height); + + if (text) { + ctx.font = font; + ctx.fillStyle = fontColor; + ctx.textAlign = textAlign; + ctx.textBaseline = textBaseline; + ctx.fillText(text, textXCoordinate, textYCoordinate); + } + + imageData = ctx.getImageData(0, 0, width, height); + self.addFrameImageData(imageData); + } catch (e) { + return '' + e; + } + }, + addFrameImageData: function addFrameImageData() { + var imageData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var frames = this.frames; + var imageDataArray = imageData.data; + this.frames.push({ + 'data': imageDataArray, + 'width': imageData.width, + 'height': imageData.height, + 'palette': null, + 'dithering': null, + 'done': false, + 'beingProcessed': false, + 'position': frames.length + }); + }, + onRenderProgress: function onRenderProgress(callback) { + this.onRenderProgressCallback = callback; + }, + isRendering: function isRendering() { + return this.generatingGIF; + }, + getBase64GIF: function getBase64GIF(completeCallback) { + var self = this; + + var onRenderComplete = function onRenderComplete(gif) { + self.destroyWorkers(); + utils.requestTimeout(function () { + completeCallback(gif); + }, 0); + }; + + self.startRendering(onRenderComplete); + }, + destroyWorkers: function destroyWorkers() { + if (this.workerError) { + return; + } + + var workers = this.workers; // Explicitly ask web workers to die so they are explicitly GC'ed + + utils.each(workers, function (iterator, workerObj) { + var worker = workerObj.worker; + var objectUrl = workerObj.objectUrl; + worker.terminate(); + utils.URL.revokeObjectURL(objectUrl); + }); + } + }; + /* + getBase64GIF.js + =============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + function getBase64GIF(animatedGifInstance, callback) { + // This is asynchronous, rendered with WebWorkers + animatedGifInstance.getBase64GIF(function (image) { + callback({ + error: false, + errorCode: '', + errorMsg: '', + image: image + }); + }); + } + /* + existingImages.js + ================= + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + + function existingImages() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = obj.callback, + images = obj.images, + options = obj.options; + var imagesLength = obj.imagesLength; + var skipObj = { + 'getUserMedia': true, + 'window.URL': true + }; + var errorObj = error.validate(skipObj); + var loadedImages = []; + var loadedImagesLength = 0; + var tempImage = void 0; + var ag = void 0; + + if (errorObj.error) { + return callback(errorObj); + } // change workerPath to point to where Animated_GIF.worker.js is + + + ag = new AnimatedGIF(options); + utils.each(images, function (index, image) { + var currentImage = image; + + if (image.src) { + currentImage = currentImage.src; + } + + if (utils.isElement(currentImage)) { + if (options.crossOrigin) { + currentImage.crossOrigin = options.crossOrigin; + } + + loadedImages[index] = currentImage; + loadedImagesLength += 1; + + if (loadedImagesLength === imagesLength) { + addLoadedImagesToGif(); + } + } else if (utils.isString(currentImage)) { + tempImage = new Image(); + + if (options.crossOrigin) { + tempImage.crossOrigin = options.crossOrigin; + } + + (function (tempImage) { + if (image.text) { + tempImage.text = image.text; + } + + tempImage.onerror = function (e) { + var obj = void 0; + --imagesLength; // skips over images that error out + + if (imagesLength === 0) { + obj = {}; + obj.error = 'None of the requested images was capable of being retrieved'; + return callback(obj); + } + }; + + tempImage.onload = function (e) { + if (image.text) { + loadedImages[index] = { + img: tempImage, + text: tempImage.text + }; + } else { + loadedImages[index] = tempImage; + } + + loadedImagesLength += 1; + + if (loadedImagesLength === imagesLength) { + addLoadedImagesToGif(); + } + + utils.removeElement(tempImage); + }; + + tempImage.src = currentImage; + })(tempImage); + + utils.setCSSAttr(tempImage, { + position: 'fixed', + opacity: '0' + }); + document.body.appendChild(tempImage); + } + }); + + function addLoadedImagesToGif() { + utils.each(loadedImages, function (index, loadedImage) { + if (loadedImage) { + if (loadedImage.text) { + ag.addFrame(loadedImage.img, options, loadedImage.text); + } else { + ag.addFrame(loadedImage, options); + } + } + }); + getBase64GIF(ag, callback); + } + } + /* + screenShot.js + ============= + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + // Helpers + + + var noop$3 = function noop() {}; + + var screenShot = { + getGIF: function getGIF() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = arguments[1]; + callback = utils.isFunction(callback) ? callback : noop$3; + var canvas = document.createElement('canvas'); + var context = void 0; + var existingImages = options.images; + var hasExistingImages = !!existingImages.length; + var cameraStream = options.cameraStream, + crop = options.crop, + filter = options.filter, + fontColor = options.fontColor, + fontFamily = options.fontFamily, + fontWeight = options.fontWeight, + keepCameraOn = options.keepCameraOn, + numWorkers = options.numWorkers, + progressCallback = options.progressCallback, + saveRenderingContexts = options.saveRenderingContexts, + savedRenderingContexts = options.savedRenderingContexts, + text = options.text, + textAlign = options.textAlign, + textBaseline = options.textBaseline, + videoElement = options.videoElement, + videoHeight = options.videoHeight, + videoWidth = options.videoWidth, + webcamVideoElement = options.webcamVideoElement; + var gifWidth = Number(options.gifWidth); + var gifHeight = Number(options.gifHeight); + var interval = Number(options.interval); + var sampleInterval = Number(options.sampleInterval); + var waitBetweenFrames = hasExistingImages ? 0 : interval * 1000; + var renderingContextsToSave = []; + var numFrames = savedRenderingContexts.length ? savedRenderingContexts.length : options.numFrames; + var pendingFrames = numFrames; + var ag = new AnimatedGIF(options); + var fontSize = utils.getFontSize(options); + var textXCoordinate = options.textXCoordinate ? options.textXCoordinate : textAlign === 'left' ? 1 : textAlign === 'right' ? gifWidth : gifWidth / 2; + var textYCoordinate = options.textYCoordinate ? options.textYCoordinate : textBaseline === 'top' ? 1 : textBaseline === 'center' ? gifHeight / 2 : gifHeight; + var font = fontWeight + ' ' + fontSize + ' ' + fontFamily; + var sourceX = crop ? Math.floor(crop.scaledWidth / 2) : 0; + var sourceWidth = crop ? videoWidth - crop.scaledWidth : 0; + var sourceY = crop ? Math.floor(crop.scaledHeight / 2) : 0; + var sourceHeight = crop ? videoHeight - crop.scaledHeight : 0; + + var captureFrames = function captureSingleFrame() { + var framesLeft = pendingFrames - 1; + + if (savedRenderingContexts.length) { + context.putImageData(savedRenderingContexts[numFrames - pendingFrames], 0, 0); + finishCapture(); + } else { + drawVideo(); + } + + function drawVideo() { + try { + // Makes sure the canvas video heights/widths are in bounds + if (sourceWidth > videoWidth) { + sourceWidth = videoWidth; + } + + if (sourceHeight > videoHeight) { + sourceHeight = videoHeight; + } + + if (sourceX < 0) { + sourceX = 0; + } + + if (sourceY < 0) { + sourceY = 0; + } + + context.filter = filter; + context.drawImage(videoElement, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, gifWidth, gifHeight); + finishCapture(); + } catch (e) { + // There is a Firefox bug that sometimes throws NS_ERROR_NOT_AVAILABLE and + // and IndexSizeError errors when drawing a video element to the canvas + if (e.name === 'NS_ERROR_NOT_AVAILABLE') { + // Wait 100ms before trying again + utils.requestTimeout(drawVideo, 100); + } else { + throw e; + } + } + } + + function finishCapture() { + var imageData = void 0; + + if (saveRenderingContexts) { + renderingContextsToSave.push(context.getImageData(0, 0, gifWidth, gifHeight)); + } // If there is text to display, make sure to display it on the canvas after the image is drawn + + + if (text) { + context.font = font; + context.fillStyle = fontColor; + context.textAlign = textAlign; + context.textBaseline = textBaseline; + context.fillText(text, textXCoordinate, textYCoordinate); + } + + imageData = context.getImageData(0, 0, gifWidth, gifHeight); + ag.addFrameImageData(imageData); + pendingFrames = framesLeft; // Call back with an r value indicating how far along we are in capture + + progressCallback((numFrames - pendingFrames) / numFrames); + + if (framesLeft > 0) { + // test + utils.requestTimeout(captureSingleFrame, waitBetweenFrames); + } + + if (!pendingFrames) { + ag.getBase64GIF(function (image) { + callback({ + 'error': false, + 'errorCode': '', + 'errorMsg': '', + 'image': image, + 'cameraStream': cameraStream, + 'videoElement': videoElement, + 'webcamVideoElement': webcamVideoElement, + 'savedRenderingContexts': renderingContextsToSave, + 'keepCameraOn': keepCameraOn + }); + }); + } + } + }; + + numFrames = numFrames !== undefined ? numFrames : 10; + interval = interval !== undefined ? interval : 0.1; // In seconds + + canvas.width = gifWidth; + canvas.height = gifHeight; + context = canvas.getContext('2d'); + + (function capture() { + if (!savedRenderingContexts.length && videoElement.currentTime === 0) { + utils.requestTimeout(capture, 100); + return; + } + + captureFrames(); + })(); + }, + getCropDimensions: function getCropDimensions() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var width = obj.videoWidth; + var height = obj.videoHeight; + var gifWidth = obj.gifWidth; + var gifHeight = obj.gifHeight; + var result = { + width: 0, + height: 0, + scaledWidth: 0, + scaledHeight: 0 + }; + + if (width > height) { + result.width = Math.round(width * (gifHeight / height)) - gifWidth; + result.scaledWidth = Math.round(result.width * (height / gifHeight)); + } else { + result.height = Math.round(height * (gifWidth / width)) - gifHeight; + result.scaledHeight = Math.round(result.height * (width / gifWidth)); + } + + return result; + } + }; + /* + videoStream.js + ============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + var videoStream = { + loadedData: false, + defaultVideoDimensions: { + width: 640, + height: 480 + }, + findVideoSize: function findVideoSizeMethod(obj) { + findVideoSizeMethod.attempts = findVideoSizeMethod.attempts || 0; + var cameraStream = obj.cameraStream, + completedCallback = obj.completedCallback, + videoElement = obj.videoElement; + + if (!videoElement) { + return; + } + + if (videoElement.videoWidth > 0 && videoElement.videoHeight > 0) { + videoElement.removeEventListener('loadeddata', videoStream.findVideoSize); + completedCallback({ + videoElement: videoElement, + cameraStream: cameraStream, + videoWidth: videoElement.videoWidth, + videoHeight: videoElement.videoHeight + }); + } else { + if (findVideoSizeMethod.attempts < 10) { + findVideoSizeMethod.attempts += 1; + utils.requestTimeout(function () { + videoStream.findVideoSize(obj); + }, 400); + } else { + completedCallback({ + videoElement: videoElement, + cameraStream: cameraStream, + videoWidth: videoStream.defaultVideoDimensions.width, + videoHeight: videoStream.defaultVideoDimensions.height + }); + } + } + }, + onStreamingTimeout: function onStreamingTimeout(callback) { + if (utils.isFunction(callback)) { + callback({ + error: true, + errorCode: 'getUserMedia', + errorMsg: 'There was an issue with the getUserMedia API - Timed out while trying to start streaming', + image: null, + cameraStream: {} + }); + } + }, + stream: function stream(obj) { + var existingVideo = utils.isArray(obj.existingVideo) ? obj.existingVideo[0] : obj.existingVideo; + var cameraStream = obj.cameraStream, + completedCallback = obj.completedCallback, + streamedCallback = obj.streamedCallback, + videoElement = obj.videoElement; + + if (utils.isFunction(streamedCallback)) { + streamedCallback(); + } + + if (existingVideo) { + if (utils.isString(existingVideo)) { + videoElement.src = existingVideo; + videoElement.innerHTML = ''; + } else if (existingVideo instanceof Blob) { + try { + videoElement.src = utils.URL.createObjectURL(existingVideo); + } catch (e) {} + + videoElement.innerHTML = ''; + } + } else if (videoElement.mozSrcObject) { + videoElement.mozSrcObject = cameraStream; + } else if (utils.URL) { + try { + videoElement.srcObject = cameraStream; + videoElement.src = utils.URL.createObjectURL(cameraStream); + } catch (e) { + videoElement.srcObject = cameraStream; + } + } + + videoElement.play(); + utils.requestTimeout(function checkLoadedData() { + checkLoadedData.count = checkLoadedData.count || 0; + + if (videoStream.loadedData === true) { + videoStream.findVideoSize({ + videoElement: videoElement, + cameraStream: cameraStream, + completedCallback: completedCallback + }); + videoStream.loadedData = false; + } else { + checkLoadedData.count += 1; + + if (checkLoadedData.count > 10) { + videoStream.findVideoSize({ + videoElement: videoElement, + cameraStream: cameraStream, + completedCallback: completedCallback + }); + } else { + checkLoadedData(); + } + } + }, 0); + }, + startStreaming: function startStreaming(obj) { + var errorCallback = utils.isFunction(obj.error) ? obj.error : utils.noop; + var streamedCallback = utils.isFunction(obj.streamed) ? obj.streamed : utils.noop; + var completedCallback = utils.isFunction(obj.completed) ? obj.completed : utils.noop; + var crossOrigin = obj.crossOrigin, + existingVideo = obj.existingVideo, + lastCameraStream = obj.lastCameraStream, + options = obj.options, + webcamVideoElement = obj.webcamVideoElement; + var videoElement = utils.isElement(existingVideo) ? existingVideo : webcamVideoElement ? webcamVideoElement : document.createElement('video'); + + if (crossOrigin) { + videoElement.crossOrigin = options.crossOrigin; + } + + videoElement.autoplay = true; + videoElement.loop = true; + videoElement.muted = true; + videoElement.addEventListener('loadeddata', function (event) { + videoStream.loadedData = true; + + if (options.offset) { + videoElement.currentTime = options.offset; + } + }); + + if (existingVideo) { + videoStream.stream({ + videoElement: videoElement, + existingVideo: existingVideo, + completedCallback: completedCallback + }); + } else if (lastCameraStream) { + videoStream.stream({ + videoElement: videoElement, + cameraStream: lastCameraStream, + streamedCallback: streamedCallback, + completedCallback: completedCallback + }); + } else { + utils.getUserMedia({ + video: true + }, function (stream) { + videoStream.stream({ + videoElement: videoElement, + cameraStream: stream, + streamedCallback: streamedCallback, + completedCallback: completedCallback + }); + }, errorCallback); + } + }, + startVideoStreaming: function startVideoStreaming(callback) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var timeoutLength = options.timeout !== undefined ? options.timeout : 0; + var originalCallback = options.callback; + var webcamVideoElement = options.webcamVideoElement; + var noGetUserMediaSupportTimeout = void 0; // Some browsers apparently have support for video streaming because of the + // presence of the getUserMedia function, but then do not answer our + // calls for streaming. + // So we'll set up this timeout and if nothing happens after a while, we'll + // conclude that there's no actual getUserMedia support. + + if (timeoutLength > 0) { + noGetUserMediaSupportTimeout = utils.requestTimeout(function () { + videoStream.onStreamingTimeout(originalCallback); + }, 10000); + } + + videoStream.startStreaming({ + error: function error() { + originalCallback({ + error: true, + errorCode: 'getUserMedia', + errorMsg: 'There was an issue with the getUserMedia API - the user probably denied permission', + image: null, + cameraStream: {} + }); + }, + streamed: function streamed() { + // The streaming started somehow, so we can assume there is getUserMedia support + clearTimeout(noGetUserMediaSupportTimeout); + }, + completed: function completed() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var cameraStream = obj.cameraStream, + videoElement = obj.videoElement, + videoHeight = obj.videoHeight, + videoWidth = obj.videoWidth; + callback({ + cameraStream: cameraStream, + videoElement: videoElement, + videoHeight: videoHeight, + videoWidth: videoWidth + }); + }, + lastCameraStream: options.lastCameraStream, + webcamVideoElement: webcamVideoElement, + crossOrigin: options.crossOrigin, + options: options + }); + }, + stopVideoStreaming: function stopVideoStreaming(obj) { + obj = utils.isObject(obj) ? obj : {}; + var _obj = obj, + keepCameraOn = _obj.keepCameraOn, + videoElement = _obj.videoElement, + webcamVideoElement = _obj.webcamVideoElement; + var cameraStream = obj.cameraStream || {}; + var cameraStreamTracks = cameraStream.getTracks ? cameraStream.getTracks() || [] : []; + var hasCameraStreamTracks = !!cameraStreamTracks.length; + var firstCameraStreamTrack = cameraStreamTracks[0]; + + if (!keepCameraOn && hasCameraStreamTracks) { + if (utils.isFunction(firstCameraStreamTrack.stop)) { + // Stops the camera stream + firstCameraStreamTrack.stop(); + } + } + + if (utils.isElement(videoElement) && !webcamVideoElement) { + // Pauses the video, revokes the object URL (freeing up memory), and remove the video element + videoElement.pause(); // Destroys the object url + + if (utils.isFunction(utils.URL.revokeObjectURL) && !utils.webWorkerError) { + if (videoElement.src) { + utils.URL.revokeObjectURL(videoElement.src); + } + } // Removes the video element from the DOM + + + utils.removeElement(videoElement); + } + } + }; + /* + stopVideoStreaming.js + ===================== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + function stopVideoStreaming(options) { + options = utils.isObject(options) ? options : {}; + videoStream.stopVideoStreaming(options); + } + /* + createAndGetGIF.js + ================== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function createAndGetGIF(obj, callback) { + var options = obj.options || {}; + var images = options.images, + video = options.video; + var gifWidth = Number(options.gifWidth); + var gifHeight = Number(options.gifHeight); + var numFrames = Number(options.numFrames); + var cameraStream = obj.cameraStream, + videoElement = obj.videoElement, + videoWidth = obj.videoWidth, + videoHeight = obj.videoHeight; + var cropDimensions = screenShot.getCropDimensions({ + videoWidth: videoWidth, + videoHeight: videoHeight, + gifHeight: gifHeight, + gifWidth: gifWidth + }); + var completeCallback = callback; + options.crop = cropDimensions; + options.videoElement = videoElement; + options.videoWidth = videoWidth; + options.videoHeight = videoHeight; + options.cameraStream = cameraStream; + + if (!utils.isElement(videoElement)) { + return; + } + + videoElement.width = gifWidth + cropDimensions.width; + videoElement.height = gifHeight + cropDimensions.height; + + if (!options.webcamVideoElement) { + utils.setCSSAttr(videoElement, { + position: 'fixed', + opacity: '0' + }); + document.body.appendChild(videoElement); + } // Firefox doesn't seem to obey autoplay if the element is not in the DOM when the content + // is loaded, so we must manually trigger play after adding it, or the video will be frozen + + + videoElement.play(); + screenShot.getGIF(options, function (obj) { + if ((!images || !images.length) && (!video || !video.length)) { + stopVideoStreaming(obj); + } + + completeCallback(obj); + }); + } + /* + existingVideo.js + ================ + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function existingVideo() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = obj.callback, + existingVideo = obj.existingVideo, + options = obj.options; + var skipObj = { + getUserMedia: true, + 'window.URL': true + }; + var errorObj = error.validate(skipObj); + var videoType = void 0; + var videoSrc = void 0; + + if (errorObj.error) { + return callback(errorObj); + } + + if (utils.isElement(existingVideo) && existingVideo.src) { + videoSrc = existingVideo.src; + videoType = utils.getExtension(videoSrc); + + if (!utils.isSupported.videoCodecs[videoType]) { + return callback(error.messages.videoCodecs); + } + } else if (utils.isArray(existingVideo)) { + utils.each(existingVideo, function (iterator, videoSrc) { + if (videoSrc instanceof Blob) { + videoType = videoSrc.type.substr(videoSrc.type.lastIndexOf('/') + 1, videoSrc.length); + } else { + videoType = videoSrc.substr(videoSrc.lastIndexOf('.') + 1, videoSrc.length); + } + + if (utils.isSupported.videoCodecs[videoType]) { + existingVideo = videoSrc; + return false; + } + }); + } + + videoStream.startStreaming({ + completed: function completed(obj) { + obj.options = options || {}; + createAndGetGIF(obj, callback); + }, + existingVideo: existingVideo, + crossOrigin: options.crossOrigin, + options: options + }); + } + /* + existingWebcam.js + ================= + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function existingWebcam() { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = obj.callback, + lastCameraStream = obj.lastCameraStream, + options = obj.options, + webcamVideoElement = obj.webcamVideoElement; + + if (!isWebCamGIFSupported()) { + return callback(error.validate()); + } + + if (options.savedRenderingContexts.length) { + screenShot.getGIF(options, function (obj) { + callback(obj); + }); + return; + } + + videoStream.startVideoStreaming(function () { + var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + obj.options = options || {}; + createAndGetGIF(obj, callback); + }, { + lastCameraStream: lastCameraStream, + callback: callback, + webcamVideoElement: webcamVideoElement, + crossOrigin: options.crossOrigin + }); + } + /* + createGIF.js + ============ + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + function createGIF(userOptions, callback) { + callback = utils.isFunction(userOptions) ? userOptions : callback; + userOptions = utils.isObject(userOptions) ? userOptions : {}; + + if (!utils.isFunction(callback)) { + return; + } + + var options = utils.mergeOptions(defaultOptions, userOptions) || {}; + var lastCameraStream = userOptions.cameraStream; + var images = options.images; + var imagesLength = images ? images.length : 0; + var video = options.video; + var webcamVideoElement = options.webcamVideoElement; + options = utils.mergeOptions(options, { + 'gifWidth': Math.floor(options.gifWidth), + 'gifHeight': Math.floor(options.gifHeight) + }); // If the user would like to create a GIF from an existing image(s) + + if (imagesLength) { + existingImages({ + 'images': images, + 'imagesLength': imagesLength, + 'callback': callback, + 'options': options + }); + } else if (video) { + // If the user would like to create a GIF from an existing HTML5 video + existingVideo({ + 'existingVideo': video, + callback: callback, + options: options + }); + } else { + // If the user would like to create a GIF from a webcam stream + existingWebcam({ + lastCameraStream: lastCameraStream, + callback: callback, + webcamVideoElement: webcamVideoElement, + options: options + }); + } + } + /* + takeSnapShot.js + =============== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + + + function takeSnapShot(userOptions, callback) { + callback = utils.isFunction(userOptions) ? userOptions : callback; + userOptions = utils.isObject(userOptions) ? userOptions : {}; + + if (!utils.isFunction(callback)) { + return; + } + + var mergedOptions = utils.mergeOptions(defaultOptions, userOptions); + var options = utils.mergeOptions(mergedOptions, { + 'interval': .1, + 'numFrames': 1, + 'gifWidth': Math.floor(mergedOptions.gifWidth), + 'gifHeight': Math.floor(mergedOptions.gifHeight) + }); + createGIF(options, callback); + } + /* + API.js + ====== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Dependencies + + + var API = { + 'utils': utils$2, + 'error': error$2, + 'defaultOptions': defaultOptions$2, + 'createGIF': createGIF, + 'takeSnapShot': takeSnapShot, + 'stopVideoStreaming': stopVideoStreaming, + 'isSupported': isSupported, + 'isWebCamGIFSupported': isWebCamGIFSupported, + 'isExistingVideoGIFSupported': isExistingVideoGIFSupported, + 'isExistingImagesGIFSupported': isSupported$1, + 'VERSION': '0.4.5' + }; + /* + index.js + ======== + */ + + /* Copyright 2017 Yahoo Inc. + * Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. + */ + // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, and plain browser loading + + if (typeof undefined === 'function' && undefined.amd) { + undefined([], function () { + return API; + }); + } else { + module.exports = API; + } + })(typeof window !== "undefined" ? window : {}, typeof document !== "undefined" ? document : { + createElement: function createElement() {} + }, typeof window !== "undefined" ? window.navigator : {}); + }); + + function i18nMix(i18n) { + i18n.update({ + 'zh-cn': { + 'Long press, gif length is between 1 second and 5 seconds': '长按,gif 长度为 1 ~ 5 秒', + 'Gif time is too short': 'Gif 时间太短', + 'Start creating gif, please wait': '开始创建 gif,请稍等', + 'Create gif successfully': '创建 gif 成功', + 'There is another gif in the processing': '正有另一个 gif 在创建中' + }, + 'zh-tw': { + 'Long press, gif length is between 1 second and 5 seconds': '長按,gif 長度為 1 ~ 5 秒', + 'Gif time is too short': 'Gif 時間太短', + 'Start creating gif, please wait': '開始創建 gif,請稍等', + 'Create gif successfully': '創建 gif 成功', + 'There is another gif in the processing': '正有另一個 gif 在創建中' + } + }); + } + + function artplayerPluginGif(art) { + var _art$constructor$util = art.constructor.utils, + errorHandle = _art$constructor$util.errorHandle, + clamp = _art$constructor$util.clamp, + downloadImage = _art$constructor$util.downloadImage; + var i18n = art.i18n, + notice = art.notice, + layers = art.layers, + controls = art.controls, + player = art.player, + _art$option = art.option, + theme = _art$option.theme, + title = _art$option.title, + proxy = art.events.proxy, + $video = art.template.$video; + i18nMix(i18n); + layers.add({ + name: 'artplayer-plugin-gif-progress', + html: '
', + style: { + position: 'absolute', + top: '0', + left: '0', + height: '3px', + width: '0%', + 'background-color': theme + } + }); + var $progress = layers['artplayer-plugin-gif-progress'].$ref; + var isProcessing = false; + var pressStartTime = 0; + var progressTimer = null; + + function cleanTimer() { + $progress.style.width = '0%'; + clearTimeout(progressTimer); + progressTimer = null; + } + + function createGif() { + cleanTimer(); + var pressTime = new Date() - pressStartTime; + + if (isProcessing) { + notice.show(i18n.get('There is another gif in the processing')); + } else if (pressTime < 1000) { + notice.show(i18n.get('Gif time is too short')); + } else { + var numFrames = Math.floor(clamp(pressTime, 1000, 5000) / 100); + var videoWidth = $video.videoWidth, + videoHeight = $video.videoHeight; + art.plugins.artplayerPluginGif.create({ + numFrames: numFrames, + gifHeight: 200, + gifWidth: videoWidth / videoHeight * 200 + }, function (image) { + downloadImage(image, "".concat(title || 'unnamed', ".gif")); + }); + } + } + + controls.add({ + name: 'artplayer-plugin-gif', + position: 'right', + html: 'GIF', + mounted: function mounted($gif) { + proxy($gif, 'mousedown', function () { + cleanTimer(); + pressStartTime = new Date(); + notice.show(i18n.get('Long press, gif length is between 1 second and 5 seconds')); + + (function loop() { + progressTimer = setTimeout(function () { + var width = parseInt($progress.style.width, 10); + + if (width <= 100) { + $progress.style.width = "".concat(width + 1, "%"); + loop(); + } else { + createGif(); + } + }, 50); + })(); + }); + proxy($gif, 'mouseup', function () { + createGif(); + }); + } + }); + return { + name: 'artplayerPluginGif', + isProcessing: isProcessing, + create: function create() { + var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = arguments.length > 1 ? arguments[1] : undefined; + isProcessing = true; + art.emit('artplayerPluginGif:create:start'); + notice.show("".concat(i18n.get('Start creating gif, please wait'), ": ").concat(config.numFrames / 10 || 1, " s"), false, 5000); + gifshot.createGIF(objectSpread({}, config, { + offset: player.currentTime, + video: [$video.src] + }), function (obj) { + if (obj.error) { + notice.show(obj.errorMsg); + errorHandle(false, obj.errorMsg); + } else if (typeof callback === 'function') { + callback(obj.image); + notice.show(i18n.get('Create gif successfully')); + } + + isProcessing = false; + art.emit('artplayerPluginGif:create:end'); + }); + } + }; + } + + window.artplayerPluginGif = artplayerPluginGif; + + exports.default = artplayerPluginGif; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/docs/uncompiled/artplayer.css b/docs/uncompiled/artplayer.css index 7eb9eb32c..48cb2939b 100644 --- a/docs/uncompiled/artplayer.css +++ b/docs/uncompiled/artplayer.css @@ -1,18 +1,18 @@ -.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 .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-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-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{pointer-events:all} -.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-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-playBig{position:absolute;right:30px;bottom:65px;opacity:.6} -.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.art-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 .artplayer-controls-left,.artplayer-video-player .artplayer-bottom .artplayer-controls .artplayer-controls-right{display:flex}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control{opacity:.9;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}.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:hover .art-volume-panel{width:60px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-time{padding:0 10px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality{position:relative;z-index:30;padding:0 10px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .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}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .art-qualitys .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,.5)}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .art-qualitys .art-quality-item:hover{background-color:hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality:hover .art-qualitys{display:block}.artplayer-video-player.artplayer-controls-show .artplayer-bottom,.artplayer-video-player.artplayer-hover .artplayer-bottom{opacity:1;visibility:visible}.artplayer-video-player.artplayer-destroy .art-progress-indicator,.artplayer-video-player.artplayer-destroy .art-progress-tip,.artplayer-video-player.artplayer-error .art-progress-indicator,.artplayer-video-player.artplayer-error .art-progress-tip{display:none!important} -.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-contextmenu{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-contextmenu a{color:#fff;text-decoration:none}.artplayer-video-player .artplayer-contextmenu .art-contextmenu span{display:inline-block;padding:0 7px}.artplayer-video-player .artplayer-contextmenu .art-contextmenu span.current,.artplayer-video-player .artplayer-contextmenu .art-contextmenu span:hover{color:#00c9ff}.artplayer-video-player .artplayer-contextmenu .art-contextmenu:hover{background-color:hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-contextmenu .art-contextmenu:last-child{border-bottom:none} +.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-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.art-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 .artplayer-controls-left,.artplayer-video-player .artplayer-bottom .artplayer-controls .artplayer-controls-right{display:flex}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control{opacity:.9;font-size:12px;height:36px;min-width:36px;line-height:36px;text-align:center;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}.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:hover .art-volume-panel{width:60px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-time{padding:0 10px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality{position:relative;z-index:30;padding:0 10px}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .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}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .art-qualitys .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,.5)}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality .art-qualitys .art-quality-item:hover{background-color:hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-bottom .artplayer-controls .art-control-quality:hover .art-qualitys{display:block}.artplayer-video-player.artplayer-controls-show .artplayer-bottom,.artplayer-video-player.artplayer-hover .artplayer-bottom{opacity:1;visibility:visible}.artplayer-video-player.artplayer-destroy .art-progress-indicator,.artplayer-video-player.artplayer-destroy .art-progress-tip,.artplayer-video-player.artplayer-error .art-progress-indicator,.artplayer-video-player.artplayer-error .art-progress-tip{display:none!important} .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-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;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-left:5px}.artplayer-video-player .artplayer-info .artplayer-info-close{position:absolute;top:5px;right:5px;cursor:pointer} +.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-contextmenu{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-contextmenu a{color:#fff;text-decoration:none}.artplayer-video-player .artplayer-contextmenu .art-contextmenu span{display:inline-block;padding:0 7px}.artplayer-video-player .artplayer-contextmenu .art-contextmenu span.current,.artplayer-video-player .artplayer-contextmenu .art-contextmenu span:hover{color:#00c9ff}.artplayer-video-player .artplayer-contextmenu .art-contextmenu:hover{background-color:hsla(0,0%,100%,.1)}.artplayer-video-player .artplayer-contextmenu .art-contextmenu:last-child{border-bottom:none} .artplayer-video-player.artplayer-hide-cursor *{cursor:none!important} .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:300px;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-y:auto;width:100%;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 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-setting .art-setting-btns{display:flex;margin-left:-4px;margin-right:-4px}.artplayer-video-player .artplayer-setting .art-setting-btns .art-setting-btn{flex:1;padding:0 4px}.artplayer-video-player .artplayer-setting .art-setting-btns .art-setting-btn span{display:block;height:20px;line-height:20px;text-align:center;color:#fff;background:hsla(0,0%,100%,.3);border-radius:2px;cursor:pointer}.artplayer-video-player .artplayer-setting .art-setting-btns .art-setting-btn.current span,.artplayer-video-player .artplayer-setting .art-setting-btns .art-setting-btn:hover span{color:#fff;background:#00a1d6} +.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;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-left:5px}.artplayer-video-player .artplayer-info .artplayer-info-close{position:absolute;top:5px;right:5px;cursor:pointer} +.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[data-aspect-ratio] video{box-sizing:content-box;-o-object-fit:fill;object-fit:fill} .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;z-index:9999;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{display:flex;align-items:center;justify-content:center}.artplayer-video-player.artplayer-pip .artplayer-mask .art-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} -.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[data-flip=horizontal] .artplayer-video{-webkit-transform:scaleX(-1);transform:scaleX(-1)}.artplayer-video-player[data-flip=vertical] .artplayer-video{-webkit-transform:scaleY(-1);transform:scaleY(-1)} .artplayer-auto-size{display:flex;align-items:center;justify-content:center} 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]:hover:after,[data-balloon][data-balloon-pos=down-left]:hover:before,[data-balloon][data-balloon-pos=down-left][data-balloon-visible]:after,[data-balloon][data-balloon-pos=down-left][data-balloon-visible]:before{-webkit-transform:translate(0);transform:translate(0)}[data-balloon][data-balloon-pos=down-right]:after{right:0;margin-top:11px;top:100%;-webkit-transform:translateY(-10px);transform:translateY(-10px)}[data-balloon][data-balloon-pos=down-right]: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;right:5px;margin-top:5px;top:100%;-webkit-transform:translateY(-10px);transform:translateY(-10px)}[data-balloon][data-balloon-pos=down-right]:hover:after,[data-balloon][data-balloon-pos=down-right]:hover:before,[data-balloon][data-balloon-pos=down-right][data-balloon-visible]:after,[data-balloon][data-balloon-pos=down-right][data-balloon-visible]:before{-webkit-transform:translate(0);transform:translate(0)}[data-balloon][data-balloon-pos=left]:after{margin-right:11px;right:100%;top:50%;-webkit-transform:translate(10px,-50%);transform:translate(10px,-50%)}[data-balloon][data-balloon-pos=left]:before{background:no-repeat url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='36'%3E%3Cpath fill='rgba(17,17,17,0.9)' d='M0 33.342V1.338c0 6 12.002 11.627 12.002 16.002C12.002 21.715 0 27.406 0 33.342z'/%3E%3C/svg%3E");background-size:100% auto;width:6px;height:18px;margin-right:5px;right:100%;top:50%;-webkit-transform:translate(10px,-50%);transform:translate(10px,-50%)}[data-balloon][data-balloon-pos=left]:hover:after,[data-balloon][data-balloon-pos=left]:hover:before,[data-balloon][data-balloon-pos=left][data-balloon-visible]:after,[data-balloon][data-balloon-pos=left][data-balloon-visible]:before{-webkit-transform:translateY(-50%);transform:translateY(-50%)}[data-balloon][data-balloon-pos=right]:after{left:100%;margin-left:11px;top:50%;-webkit-transform:translate(-10px,-50%);transform:translate(-10px,-50%)}[data-balloon][data-balloon-pos=right]:before{background:no-repeat url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='36'%3E%3Cpath fill='rgba(17,17,17,0.9)' d='M12 2.658v32.004c0-6-12.002-11.627-12.002-16.002C-.002 14.285 12 8.594 12 2.658z'/%3E%3C/svg%3E");background-size:100% auto;width:6px;height:18px;left:100%;margin-left:5px;top:50%;-webkit-transform:translate(-10px,-50%);transform:translate(-10px,-50%)}[data-balloon][data-balloon-pos=right]:hover:after,[data-balloon][data-balloon-pos=right]:hover:before,[data-balloon][data-balloon-pos=right][data-balloon-visible]:after,[data-balloon][data-balloon-pos=right][data-balloon-visible]:before{-webkit-transform:translateY(-50%);transform:translateY(-50%)}[data-balloon][data-balloon-length=small]:after{white-space:normal;width:80px}[data-balloon][data-balloon-length=medium]:after{white-space:normal;width:150px}[data-balloon][data-balloon-length=large]:after{white-space:normal;width:260px}[data-balloon][data-balloon-length=xlarge]:after{white-space:normal;width:380px}@media screen and (max-width:768px){[data-balloon][data-balloon-length=xlarge]:after{white-space:normal;width:90vw}}[data-balloon][data-balloon-length=fit]:after{white-space:normal;width:100%} \ No newline at end of file diff --git a/docs/uncompiled/artplayer.js b/docs/uncompiled/artplayer.js index 6791bce56..05f20f192 100644 --- a/docs/uncompiled/artplayer.js +++ b/docs/uncompiled/artplayer.js @@ -622,6 +622,15 @@ type: 'text/vtt' })); } + function downloadImage(url, name) { + var elink = document.createElement('a'); + setStyle(elink, 'display', 'none'); + elink.href = url; + elink.download = name; + document.body.appendChild(elink); + elink.click(); + document.body.removeChild(elink); + } var utils = /*#__PURE__*/Object.freeze({ ArtPlayerError: ArtPlayerError, @@ -644,7 +653,8 @@ inverseClass: inverseClass, tooltip: tooltip, srtToVtt: srtToVtt, - vttToBlob: vttToBlob + vttToBlob: vttToBlob, + downloadImage: downloadImage }); function validElement(paths, value, type) { @@ -1501,13 +1511,7 @@ canvas.height = $video.videoHeight; canvas.getContext('2d').drawImage($video, 0, 0); var dataUri = canvas.toDataURL('image/png'); - var elink = document.createElement('a'); - setStyle(elink, 'display', 'none'); - elink.href = dataUri; - elink.download = "".concat(option.title || 'artplayer', "_").concat(secondToTime($video.currentTime), ".png"); - document.body.appendChild(elink); - elink.click(); - document.body.removeChild(elink); + downloadImage(dataUri, "".concat(option.title || 'artplayer', "_").concat(secondToTime($video.currentTime), ".png")); return dataUri; } diff --git a/packages/artplayer-plugin-gif/.npmignore b/packages/artplayer-plugin-gif/.npmignore new file mode 100644 index 000000000..429145126 --- /dev/null +++ b/packages/artplayer-plugin-gif/.npmignore @@ -0,0 +1,9 @@ +node_modules +.DS_Store +yarn.lock +package-lock.json +test +coverage +.nyc_output +.cache +docs \ No newline at end of file diff --git a/packages/artplayer-plugin-gif/.npmrc b/packages/artplayer-plugin-gif/.npmrc new file mode 100644 index 000000000..9cf949503 --- /dev/null +++ b/packages/artplayer-plugin-gif/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/packages/artplayer-plugin-gif/README.md b/packages/artplayer-plugin-gif/README.md new file mode 100644 index 000000000..c4e27d982 --- /dev/null +++ b/packages/artplayer-plugin-gif/README.md @@ -0,0 +1,63 @@ +# artplayer-plugin-gif + +Gif plugin for ArtPlayer, use [gifshot](https://github.com/yahoo/gifshot) as a dependency + +## Demo + +[Checkout the demo](https://artplayer.org/gif/) from Github Pages + +## Install + +Install with `npm` + +``` +$ npm install artplayer-plugin-gif +``` + +Or install with `yarn` + +``` +$ yarn add artplayer-plugin-gif +``` + +```js +import artplayerPluginGif from 'artplayer-plugin-gif'; +``` + +Or umd builds are also available + +```html + +``` + +Will expose the global variable to `window.artplayerPluginGif`. + +## Usage + +This will add a button called "GIF" in the bottom control bar. + +```js +var art = new Artplayer({ + container: '.artplayer-app', + url: 'path/to/video.mp4', + plugins: [artplayerPluginGif], +}); +``` + +Or can be called via api + +```js +art.plugins.artplayerPluginGif.create( + { + // some configuration + // https://github.com/yahoo/gifshot#options + }, + function(imageUrl) { + console.log(imageUrl); + }, +); +``` + +## License + +MIT © [Harvey Zack](https://www.zhw-island.com/) diff --git a/packages/artplayer-plugin-gif/package.json b/packages/artplayer-plugin-gif/package.json new file mode 100644 index 000000000..ca1d957a3 --- /dev/null +++ b/packages/artplayer-plugin-gif/package.json @@ -0,0 +1,27 @@ +{ + "name": "artplayer-plugin-gif", + "version": "1.0.5", + "description": "Gif plugin for ArtPlayer", + "main": "dist/artplayer-plugin-gif.js", + "style": "dist/artplayer-plugin-gif.css", + "scripts": { + "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 ", + "license": "MIT", + "bugs": { + "url": "https://github.com/zhw2590582/ArtPlayer/issues" + }, + "homepage": "https://github.com/zhw2590582/ArtPlayer#readme", + "dependencies": { + "gifshot": "^0.4.5" + } +} diff --git a/packages/artplayer-plugin-gif/src/index.js b/packages/artplayer-plugin-gif/src/index.js new file mode 100644 index 000000000..cb81ea917 --- /dev/null +++ b/packages/artplayer-plugin-gif/src/index.js @@ -0,0 +1,145 @@ +import gifshot from 'gifshot'; + +function i18nMix(i18n) { + i18n.update({ + 'zh-cn': { + 'Long press, gif length is between 1 second and 5 seconds': '长按,gif 长度为 1 ~ 5 秒', + 'Gif time is too short': 'Gif 时间太短', + 'Start creating gif, please wait': '开始创建 gif,请稍等', + 'Create gif successfully': '创建 gif 成功', + 'There is another gif in the processing': '正有另一个 gif 在创建中', + }, + 'zh-tw': { + 'Long press, gif length is between 1 second and 5 seconds': '長按,gif 長度為 1 ~ 5 秒', + 'Gif time is too short': 'Gif 時間太短', + 'Start creating gif, please wait': '開始創建 gif,請稍等', + 'Create gif successfully': '創建 gif 成功', + 'There is another gif in the processing': '正有另一個 gif 在創建中', + }, + }); +} + +function artplayerPluginGif(art) { + const { errorHandle, clamp, downloadImage } = art.constructor.utils; + const { + i18n, + notice, + layers, + controls, + player, + option: { theme, title }, + events: { proxy }, + template: { $video }, + } = art; + + i18nMix(i18n); + + layers.add({ + name: 'artplayer-plugin-gif-progress', + html: '
', + style: { + position: 'absolute', + top: '0', + left: '0', + height: '3px', + width: '0%', + 'background-color': theme, + }, + }); + + const $progress = layers['artplayer-plugin-gif-progress'].$ref; + let isProcessing = false; + let pressStartTime = 0; + let progressTimer = null; + + function cleanTimer() { + $progress.style.width = '0%'; + clearTimeout(progressTimer); + progressTimer = null; + } + + function createGif() { + cleanTimer(); + const pressTime = new Date() - pressStartTime; + + if (isProcessing) { + notice.show(i18n.get('There is another gif in the processing')); + } else if (pressTime < 1000) { + notice.show(i18n.get('Gif time is too short')); + } else { + const numFrames = Math.floor(clamp(pressTime, 1000, 5000) / 100); + const { videoWidth, videoHeight } = $video; + art.plugins.artplayerPluginGif.create( + { + numFrames, + gifHeight: 200, + gifWidth: (videoWidth / videoHeight) * 200, + }, + image => { + downloadImage(image, `${title || 'unnamed'}.gif`); + }, + ); + } + } + + controls.add({ + name: 'artplayer-plugin-gif', + position: 'right', + html: 'GIF', + mounted: $gif => { + proxy($gif, 'mousedown', () => { + cleanTimer(); + pressStartTime = new Date(); + notice.show(i18n.get('Long press, gif length is between 1 second and 5 seconds')); + + (function loop() { + progressTimer = setTimeout(() => { + const width = parseInt($progress.style.width, 10); + if (width <= 100) { + $progress.style.width = `${width + 1}%`; + loop(); + } else { + createGif(); + } + }, 50); + })(); + }); + + proxy($gif, 'mouseup', () => { + createGif(); + }); + }, + }); + + return { + name: 'artplayerPluginGif', + isProcessing, + create(config = {}, callback) { + isProcessing = true; + art.emit('artplayerPluginGif:create:start'); + notice.show(`${i18n.get('Start creating gif, please wait')}: ${config.numFrames / 10 || 1} s`, false, 5000); + gifshot.createGIF( + { + ...config, + offset: player.currentTime, + video: [$video.src], + }, + obj => { + if (obj.error) { + notice.show(obj.errorMsg); + errorHandle(false, obj.errorMsg); + } else if (typeof callback === 'function') { + callback(obj.image); + notice.show(i18n.get('Create gif successfully')); + } + + isProcessing = false; + art.emit('artplayerPluginGif:create:end'); + }, + ); + }, + }; +} + +window.artplayerPluginGif = artplayerPluginGif; +export default artplayerPluginGif; diff --git a/packages/artplayer/src/player/screenshotMix.js b/packages/artplayer/src/player/screenshotMix.js index 0a954ea8d..830d58cc9 100644 --- a/packages/artplayer/src/player/screenshotMix.js +++ b/packages/artplayer/src/player/screenshotMix.js @@ -1,4 +1,4 @@ -import { setStyle, secondToTime } from '../utils'; +import { secondToTime, downloadImage } from '../utils'; export default function screenshotMix(art, player) { const { @@ -13,13 +13,7 @@ export default function screenshotMix(art, player) { 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); + downloadImage(dataUri, `${option.title || 'artplayer'}_${secondToTime($video.currentTime)}.png`); return dataUri; } diff --git a/packages/artplayer/src/style/bottom.scss b/packages/artplayer/src/style/bottom.scss index aa8d707be..d27cfc73c 100644 --- a/packages/artplayer/src/style/bottom.scss +++ b/packages/artplayer/src/style/bottom.scss @@ -136,7 +136,9 @@ opacity: .9; font-size: 12px; height: 36px; + min-width: 36px; line-height: 36px; + text-align: center; cursor: pointer; .art-icon { display: flex; diff --git a/packages/artplayer/src/utils/index.js b/packages/artplayer/src/utils/index.js index 2de572a34..91ac2512d 100644 --- a/packages/artplayer/src/utils/index.js +++ b/packages/artplayer/src/utils/index.js @@ -170,3 +170,13 @@ export function vttToBlob(vttText) { }), ); } + +export function downloadImage(url, name) { + const elink = document.createElement('a'); + setStyle(elink, 'display', 'none'); + elink.href = url; + elink.download = name; + document.body.appendChild(elink); + elink.click(); + document.body.removeChild(elink); +} \ No newline at end of file