UNPKG

chimee

Version:

a video-player aims to bring wonderful experience on browser

1,299 lines (1,171 loc) 167 kB
/** * chimee v0.12.0 * (c) 2017-2020 toxic-johann * Released under MIT */ 'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var _typeof = _interopDefault(require('babel-runtime/helpers/typeof')); var _Object$getPrototypeOf = _interopDefault(require('babel-runtime/core-js/object/get-prototype-of')); var _classCallCheck = _interopDefault(require('babel-runtime/helpers/classCallCheck')); var _possibleConstructorReturn = _interopDefault(require('babel-runtime/helpers/possibleConstructorReturn')); var _createClass = _interopDefault(require('babel-runtime/helpers/createClass')); var _inherits = _interopDefault(require('babel-runtime/helpers/inherits')); var chimeeHelper = require('chimee-helper'); var _Object$defineProperty = _interopDefault(require('babel-runtime/core-js/object/define-property')); var _Number$isNaN = _interopDefault(require('babel-runtime/core-js/number/is-nan')); var toxicDecorators = require('toxic-decorators'); var _Object$getOwnPropertyDescriptor = _interopDefault(require('babel-runtime/core-js/object/get-own-property-descriptor')); var _Object$keys = _interopDefault(require('babel-runtime/core-js/object/keys')); var _Object$assign = _interopDefault(require('babel-runtime/core-js/object/assign')); var _JSON$stringify = _interopDefault(require('babel-runtime/core-js/json/stringify')); var _defineProperty = _interopDefault(require('babel-runtime/helpers/defineProperty')); var _toConsumableArray = _interopDefault(require('babel-runtime/helpers/toConsumableArray')); var _Promise = _interopDefault(require('babel-runtime/core-js/promise')); var _get = _interopDefault(require('babel-runtime/helpers/get')); var _slicedToArray = _interopDefault(require('babel-runtime/helpers/slicedToArray')); var esFullscreen = _interopDefault(require('es-fullscreen')); var _Map = _interopDefault(require('babel-runtime/core-js/map')); var _getIterator = _interopDefault(require('babel-runtime/core-js/get-iterator')); var _Object$entries = _interopDefault(require('babel-runtime/core-js/object/entries')); var _regeneratorRuntime = _interopDefault(require('babel-runtime/regenerator')); var _asyncToGenerator = _interopDefault(require('babel-runtime/helpers/asyncToGenerator')); var global = _interopDefault(require('core-js/es7/global')); var tempCurrentTime = 0; var NativeVideoKernel = function (_CustEvent) { _inherits(NativeVideoKernel, _CustEvent); _createClass(NativeVideoKernel, null, [{ key: 'isSupport', /* istanbul ignore next */ value: function isSupport() { return true; } }]); function NativeVideoKernel(videoElement, config, customConfig) { _classCallCheck(this, NativeVideoKernel); var _this = _possibleConstructorReturn(this, (NativeVideoKernel.__proto__ || _Object$getPrototypeOf(NativeVideoKernel)).call(this)); if (!chimeeHelper.isElement(videoElement)) throw new Error('You must pass in an legal video element but not ' + (typeof videoElement === 'undefined' ? 'undefined' : _typeof(videoElement))); _this.video = videoElement; _this.config = config; _this.customConfig = customConfig; return _this; } _createClass(NativeVideoKernel, [{ key: 'load', value: function load(src) { this.video.setAttribute('src', src); this.video.src = src; } }, { key: 'startLoad', value: function startLoad(src) { /* istanbul ignore next */ var currentTime = this.video.currentTime || tempCurrentTime; this.load(src); this.seek(currentTime); } // https://developer.mozilla.org/de/docs/Web/HTML/Using_HTML5_audio_and_video#Stopping_the_download_of_media }, { key: 'stopLoad', value: function stopLoad() { tempCurrentTime = this.video.currentTime; this.video.src = ''; this.video.removeAttribute('src'); } }, { key: 'destroy', value: function destroy() { /* istanbul ignore next */ if (chimeeHelper.isElement(this.video)) this.stopLoad(); } }, { key: 'play', value: function play() { return this.video.play(); } }, { key: 'pause', value: function pause() { return this.video.pause(); } }, { key: 'refresh', value: function refresh() { this.video.src = this.config.src; } }, { key: 'attachMedia', value: function attachMedia() {} }, { key: 'seek', value: function seek(seconds) { this.video.currentTime = seconds; } }]); return NativeVideoKernel; }(chimeeHelper.CustEvent); var LOG_TAG = 'chimee'; var boxSuffixMap = { flv: '.flv', hls: '.m3u8', native: '.mp4' }; // return the config box // or choose the right one according to the src function getLegalBox(_ref) { var src = _ref.src, box = _ref.box; if (chimeeHelper.isString(box) && box) return box; src = src.toLowerCase(); for (var key in boxSuffixMap) { var suffix = boxSuffixMap[key]; if (src.indexOf(suffix) > -1) return key; } return 'native'; } var ChimeeKernel = function () { /** * kernelWrapper * @param {any} wrap videoElement * @param {any} option * @class kernel */ function ChimeeKernel(videoElement, config) { _classCallCheck(this, ChimeeKernel); if (!chimeeHelper.isElement(videoElement)) throw new Error('You must pass in an video element to the chimee-kernel'); this.config = config; this.videoElement = videoElement; this.initVideoKernel(); } _createClass(ChimeeKernel, [{ key: 'destroy', value: function destroy() { this.videoKernel.destroy(); } }, { key: 'initVideoKernel', value: function initVideoKernel() { var config = this.config; var box = getLegalBox(config); this.box = box; var VideoKernel = this.chooseVideoKernel(this.box, config.preset); if (!chimeeHelper.isFunction(VideoKernel)) throw new Error('We can\'t find video kernel for ' + box + '. Please check your config and make sure it\'s installed or provided'); var customConfig = config.presetConfig[this.box]; // TODO: nowaday, kernels all get config from one config // it's not a good way, because custom config may override kernel config // so we may remove this code when we check all the chimee-kernel-* setting if (customConfig) chimeeHelper.deepAssign(config, customConfig); this.videoKernel = new VideoKernel(this.videoElement, config, customConfig); } // choose the right video kernel according to the box setting }, { key: 'chooseVideoKernel', value: function chooseVideoKernel(box, preset) { switch (box) { case 'native': // $FlowFixMe: it's the same as videoKernel return NativeVideoKernel; case 'mp4': return this.getMp4Kernel(preset.mp4); case 'flv': case 'hls': return preset[box]; default: throw new Error('We currently do not support box ' + box + ', please contact us through https://github.com/Chimeejs/chimee/issues.'); } } // fetch the legal mp4 kernel // if it's not exist or not support // we will fall back to the native video kernel }, { key: 'getMp4Kernel', value: function getMp4Kernel(Mp4Kernel) { var hasLegalMp4Kernel = Mp4Kernel && chimeeHelper.isFunction(Mp4Kernel.isSupport); // $FlowFixMe: we have make sure it's an kernel now var supportMp4Kernel = hasLegalMp4Kernel && Mp4Kernel.isSupport(); // $FlowFixMe: we have make sure it's an kernel now if (supportMp4Kernel) return Mp4Kernel; if (hasLegalMp4Kernel) chimeeHelper.Log.warn(LOG_TAG, 'mp4 decode is not support in this browser, we will switch to the native video kernel'); this.box = 'native'; // $FlowFixMe: it's the same as videoKernel return NativeVideoKernel; } }, { key: 'attachMedia', value: function attachMedia() { this.videoKernel.attachMedia(); } }, { key: 'load', value: function load() { var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.config.src; this.config.src = src; this.videoKernel.load(src); } }, { key: 'startLoad', value: function startLoad() { /* istanbul ignore if */ if (!chimeeHelper.isFunction(this.videoKernel.startLoad)) throw new Error('This video kernel do not support startLoad, please contact us on https://github.com/Chimeejs/chimee/issues'); this.videoKernel.startLoad(this.config.src); } }, { key: 'stopLoad', value: function stopLoad() { /* istanbul ignore else */ if (chimeeHelper.isFunction(this.videoKernel.stopLoad)) this.videoKernel.stopLoad(); } }, { key: 'play', value: function play() { this.videoKernel.play(); } }, { key: 'pause', value: function pause() { this.videoKernel.pause(); } }, { key: 'seek', value: function seek(seconds) { if (!chimeeHelper.isNumber(seconds)) { chimeeHelper.Log.error(LOG_TAG, 'When you try to seek, you must offer us a number, but not ' + (typeof seconds === 'undefined' ? 'undefined' : _typeof(seconds))); return; } this.videoKernel.seek(seconds); } }, { key: 'refresh', value: function refresh() { this.videoKernel.refresh(); } }, { key: 'on', value: function on(key, fn) { this.videoKernel.on(key, fn); } }, { key: 'off', value: function off(key, fn) { this.videoKernel.off(key, fn); } }, { key: 'currentTime', get: function get() { return this.videoElement.currentTime || 0; } }]); return ChimeeKernel; }(); var videoEvents = ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'interruptbegin', 'interruptend', 'loadeddata', 'loadedmetadata', 'loadstart', 'mozaudioavailable', 'pause', 'play', 'playing', 'progress', 'ratechange', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting', 'enterpictureinpicture', 'leavepictureinpicture']; var videoReadOnlyProperties = ['buffered', 'currentSrc', 'duration', 'error', 'ended', 'networkState', 'paused', 'readyState', 'seekable', 'sinkId', 'controlsList', 'tabIndex', 'dataset', 'offsetHeight', 'offsetLeft', 'offsetParent', 'offsetTop', 'offsetWidth']; var domEvents = ['beforeinput', 'blur', 'click', 'compositionend', 'compositionstart', 'compositionupdate', 'dblclick', 'focus', 'focusin', 'focusout', 'input', 'keydown', 'keypress', 'keyup', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'resize', 'scroll', 'select', 'wheel', 'mousewheel', 'contextmenu', 'touchstart', 'touchmove', 'touchend', 'fullscreen']; var esFullscreenEvents = ['fullscreenchange']; var passiveEvents = ['wheel', 'mousewheel', 'touchstart', 'touchmove']; var selfProcessorEvents = ['silentLoad', 'fullscreen']; var mustListenVideoDomEvents = ['mouseenter', 'mouseleave']; var kernelMethods = ['play', 'pause', 'seek', 'startLoad', 'stopLoad']; var dispatcherEventMethodMap = { load: 'load', enterpictureinpicture: 'requestPictureInPicture', leavepictureinpicture: 'exitPictureInPicture' }; var kernelEvents = ['mediaInfo', 'heartbeat', 'error']; var domMethods = ['focus', 'fullscreen', 'requestFullscreen', 'exitFullscreen']; var videoMethods = ['canPlayType', 'captureStream', 'setSinkId']; /** * checker for on, off, once function * @param {string} key * @param {Function} fn */ function eventBinderCheck(key, fn) { if (!chimeeHelper.isString(key)) throw new TypeError('key parameter must be String'); if (!chimeeHelper.isFunction(fn)) throw new TypeError('fn parameter must be Function'); } /** * checker for attr or css function */ function attrAndStyleCheck() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (args.length > 2) { return ['set'].concat(args); } if (args.length === 2) { if (['video', 'container', 'wrapper', 'videoElement'].indexOf(args[0]) > -1) { return ['get'].concat(args); } return ['set', 'container'].concat(args); } return ['get', 'container'].concat(args); } var _dec, _dec2, _class, _descriptor, _descriptor2, _descriptor3, _descriptor4, _descriptor5, _descriptor6, _descriptor7; function _initDefineProp(target, property, descriptor, context) { if (!descriptor) return; _Object$defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; } function stringOrVoid(value) { return chimeeHelper.isString(value) ? value : undefined; } function accessorVideoProperty(property) { return toxicDecorators.accessor({ get: function get(value) { return this.dispatcher.videoConfigReady && this.inited ? this.dom.videoElement[property] : value; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; this.dom.videoElement[property] = value; return value; } }); } function accessorVideoAttribute(attribute) { var _ref = chimeeHelper.isObject(attribute) ? attribute : { set: attribute, get: attribute, isBoolean: false }, _set = _ref.set, _get$$1 = _ref.get, isBoolean = _ref.isBoolean; return toxicDecorators.accessor({ get: function get(value) { return this.dispatcher.videoConfigReady && this.inited ? this.dom.videoElement[_get$$1] : value; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; var val = isBoolean ? value ? '' : undefined /* istanbul ignore next */ : value === null ? undefined : value; this.dom.setAttr('video', _set, val); return value; } }, { preSet: false }); } function accessorCustomAttribute(attribute, isBoolean) { return toxicDecorators.accessor({ get: function get(value) { var attrValue = this.dom.getAttr('video', attribute); return this.dispatcher.videoConfigReady && this.inited ? isBoolean ? !!attrValue : attrValue : value; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; var val = isBoolean ? value || undefined : value === null ? undefined : value; this.dom.setAttr('video', attribute, val); return value; } }); } function accessorWidthAndHeight(property) { return toxicDecorators.accessor({ get: function get(value) { if (!this.dispatcher.videoConfigReady || !this.inited) return value; var attr = this.dom.getAttr('video', property); var prop = this.dom.videoElement[property]; if (chimeeHelper.isNumeric(attr) && chimeeHelper.isNumber(prop)) return prop; return attr || undefined; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; var val = void 0; if (value === undefined || chimeeHelper.isNumber(value)) { val = value; } else if (chimeeHelper.isString(value) && !_Number$isNaN(parseFloat(value))) { val = value; } this.dom.setAttr('video', property, val); return val; } }); } var accessorMap = { src: [toxicDecorators.alwaysString(), toxicDecorators.accessor({ set: function set(val) { // must check val !== this.src here // as we will set config.src in the video // the may cause dead lock if (this.dispatcher.readySync && this.autoload && val !== this.src) this.needToLoadSrc = true; return val; } }), toxicDecorators.accessor({ set: function set(val) { if (this.needToLoadSrc) { // unlock it at first, to avoid deadlock this.needToLoadSrc = false; this.dispatcher.binder.emit({ name: 'load', target: 'plugin', id: 'dispatcher' }, val); } return val; } }, { preSet: false })], autoload: toxicDecorators.alwaysBoolean(), autoplay: [toxicDecorators.alwaysBoolean(), accessorVideoProperty('autoplay')], controls: [toxicDecorators.alwaysBoolean(), accessorVideoProperty('controls')], width: [accessorWidthAndHeight('width')], height: [accessorWidthAndHeight('height')], crossOrigin: [toxicDecorators.accessor({ set: stringOrVoid }), accessorVideoAttribute({ set: 'crossorigin', get: 'crossOrigin' })], loop: [toxicDecorators.alwaysBoolean(), accessorVideoProperty('loop')], defaultMuted: [toxicDecorators.alwaysBoolean(), accessorVideoAttribute({ get: 'defaultMuted', set: 'muted', isBoolean: true })], muted: [toxicDecorators.alwaysBoolean(), accessorVideoProperty('muted')], preload: [toxicDecorators.accessor({ set: function set(value) { var options = ['none', 'auto', 'metadata', '']; return options.indexOf(value) > -1 ? value : 'none'; } }, { preSet: true }), accessorVideoAttribute('preload')], poster: [ // 因为如果在 video 上随便加一个字符串,他会将其拼接到地址上,所以这里要避免 // 单元测试无法检测 toxicDecorators.alwaysString(), toxicDecorators.accessor({ get: function get(value) { return this.dispatcher.videoConfigReady && this.inited ? this.dom.videoElement.poster : value; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; if (value.length) this.dom.setAttr('video', 'poster', value); return value; } })], playsInline: [toxicDecorators.accessor({ get: function get(value) { var playsInline = this.dom.videoElement.playsInline; return this.dispatcher.videoConfigReady && this.inited ? playsInline === undefined ? value : playsInline : value; }, set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; this.dom.videoElement.playsInline = value; var val = value ? '' : undefined; this.dom.setAttr('video', 'playsinline', val); this.dom.setAttr('video', 'webkit-playsinline', val); this.dom.setAttr('video', 'x5-playsinline', val); return value; } }), toxicDecorators.alwaysBoolean()], x5VideoPlayerFullscreen: [toxicDecorators.accessor({ set: function set(value) { return !!value; }, get: function get(value) { return !!value; } }), accessorCustomAttribute('x5-video-player-fullscreen', true)], x5VideoOrientation: [toxicDecorators.accessor({ set: stringOrVoid }), accessorCustomAttribute('x5-video-orientation')], x5VideoPlayerType: [toxicDecorators.accessor({ set: function set(value) { if (!this.dispatcher.videoConfigReady) return value; var val = value === 'h5-page' ? 'h5-page' : undefined; this.dom.setAttr('video', 'x5-video-player-type', val); return value; }, get: function get(value) { return this.dispatcher.videoConfigReady && value || (this.dom.getAttr('video', 'x5-video-player-type') ? 'h5-page' : undefined); } })], xWebkitAirplay: [toxicDecorators.accessor({ set: function set(value) { return !!value; }, get: function get(value) { return !!value; } }), accessorCustomAttribute('x-webkit-airplay', true)], playbackRate: [toxicDecorators.alwaysNumber(1), accessorVideoProperty('playbackRate')], defaultPlaybackRate: [accessorVideoProperty('defaultPlaybackRate'), toxicDecorators.alwaysNumber(1)], disableRemotePlayback: [toxicDecorators.alwaysBoolean(), accessorVideoProperty('disableRemotePlayback')], volume: [toxicDecorators.alwaysNumber(1), accessorVideoProperty('volume')] }; var VideoConfig = (_dec = toxicDecorators.initBoolean(), _dec2 = toxicDecorators.initString(function (str) { return str.toLocaleLowerCase(); }), (_class = function () { // 转为供 kernel 使用的内部参数 function VideoConfig(dispatcher, config) { _classCallCheck(this, VideoConfig); _initDefineProp(this, 'needToLoadSrc', _descriptor, this); _initDefineProp(this, 'changeWatchable', _descriptor2, this); _initDefineProp(this, 'inited', _descriptor3, this); this.src = ''; _initDefineProp(this, 'isLive', _descriptor4, this); _initDefineProp(this, 'box', _descriptor5, this); this.preset = {}; this.presetConfig = {}; this.autoload = true; this.autoplay = false; this.controls = false; this.width = '100%'; this.height = '100%'; this.crossOrigin = undefined; this.loop = false; this.defaultMuted = false; this.muted = false; this.preload = 'auto'; this.poster = undefined; this.playsInline = false; this.x5VideoPlayerFullscreen = false; this.x5VideoOrientation = undefined; this.x5VideoPlayerType = undefined; this.xWebkitAirplay = false; this.playbackRate = 1; this.defaultPlaybackRate = 1; this.disableRemotePlayback = false; this.volume = 1; _initDefineProp(this, '_kernelProperty', _descriptor6, this); _initDefineProp(this, '_realDomAttr', _descriptor7, this); toxicDecorators.applyDecorators(this, accessorMap, { self: true }); Object.defineProperty(this, 'dispatcher', { value: dispatcher, enumerable: false, writable: false, configurable: false }); Object.defineProperty(this, 'dom', { value: dispatcher.dom, enumerable: false, writable: false, configurable: false }); chimeeHelper.deepAssign(this, config); } // 此处 box 只能置空,因为 kernel 会自动根据你的安装 kernel 和相关地址作智能判断。 // 曾经 bug 详见 https://github.com/Chimeejs/chimee-kernel/issues/1 // kernels 不在 videoConfig 上设置默认值,防止判断出错 _createClass(VideoConfig, [{ key: 'init', value: function init() { var _this = this; this._realDomAttr.forEach(function (key) { // $FlowFixMe: we have check the computed here _this[key] = _this[key]; }); this.inited = true; } }]); return VideoConfig; }(), (_descriptor = _applyDecoratedDescriptor(_class.prototype, 'needToLoadSrc', [toxicDecorators.nonenumerable], { enumerable: true, initializer: function initializer() { return false; } }), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, 'changeWatchable', [toxicDecorators.nonenumerable], { enumerable: true, initializer: function initializer() { return true; } }), _descriptor3 = _applyDecoratedDescriptor(_class.prototype, 'inited', [toxicDecorators.nonenumerable], { enumerable: true, initializer: function initializer() { return false; } }), _descriptor4 = _applyDecoratedDescriptor(_class.prototype, 'isLive', [_dec, toxicDecorators.configurable], { enumerable: true, initializer: function initializer() { return false; } }), _descriptor5 = _applyDecoratedDescriptor(_class.prototype, 'box', [_dec2, toxicDecorators.configurable], { enumerable: true, initializer: function initializer() { return ''; } }), _descriptor6 = _applyDecoratedDescriptor(_class.prototype, '_kernelProperty', [toxicDecorators.frozen], { enumerable: true, initializer: function initializer() { return ['isLive', 'box', 'preset', 'kernels', 'presetConfig']; } }), _descriptor7 = _applyDecoratedDescriptor(_class.prototype, '_realDomAttr', [toxicDecorators.frozen], { enumerable: true, initializer: function initializer() { return ['src', 'controls', 'width', 'height', 'crossOrigin', 'loop', 'muted', 'preload', 'poster', 'autoplay', 'playsInline', 'x5VideoPlayerFullscreen', 'x5VideoOrientation', 'xWebkitAirplay', 'playbackRate', 'defaultPlaybackRate', 'autoload', 'disableRemotePlayback', 'defaultMuted', 'volume', 'x5VideoPlayerType']; } })), _class)); var _dec$1, _dec2$1, _dec3, _dec4, _dec5, _dec6, _dec7, _dec8, _dec9, _dec10, _dec11, _dec12, _dec13, _dec14, _dec15, _dec16, _dec17, _dec18, _dec19, _class$1, _class2; function _applyDecoratedDescriptor$1(target, property, decorators, descriptor, context) { var desc = {}; Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object['define' + 'Property'](target, property, desc); desc = null; } return desc; } var VideoWrapper = (_dec$1 = toxicDecorators.autobindClass(), _dec2$1 = toxicDecorators.alias('silentLoad'), _dec3 = toxicDecorators.alias('fullScreen'), _dec4 = toxicDecorators.alias('$fullScreen'), _dec5 = toxicDecorators.alias('fullscreen'), _dec6 = toxicDecorators.alias('emit'), _dec7 = toxicDecorators.alias('emitSync'), _dec8 = toxicDecorators.alias('on'), _dec9 = toxicDecorators.alias('addEventListener'), _dec10 = toxicDecorators.before(eventBinderCheck), _dec11 = toxicDecorators.alias('off'), _dec12 = toxicDecorators.alias('removeEventListener'), _dec13 = toxicDecorators.before(eventBinderCheck), _dec14 = toxicDecorators.alias('once'), _dec15 = toxicDecorators.before(eventBinderCheck), _dec16 = toxicDecorators.alias('css'), _dec17 = toxicDecorators.before(attrAndStyleCheck), _dec18 = toxicDecorators.alias('attr'), _dec19 = toxicDecorators.before(attrAndStyleCheck), _dec$1(_class$1 = (_class2 = function () { function VideoWrapper() { _classCallCheck(this, VideoWrapper); this.__events = {}; this.__unwatchHandlers = []; } _createClass(VideoWrapper, [{ key: '__wrapAsVideo', value: function __wrapAsVideo(videoConfig) { var _this = this; // bind video read only properties on instance, so that you can get info like buffered videoReadOnlyProperties.forEach(function (key) { _Object$defineProperty(_this, key, { get: function get() { return this.__dispatcher.dom.videoElement[key]; }, set: undefined, configurable: false, enumerable: false }); }); // bind videoMethods like canplaytype on instance videoMethods.forEach(function (key) { _Object$defineProperty(_this, key, { get: function get() { var video = this.__dispatcher.dom.videoElement; return chimeeHelper.bind(video[key], video); }, set: undefined, configurable: false, enumerable: false }); }); // bind video config properties on instance, so that you can just set src by this var props = videoConfig._realDomAttr.concat(videoConfig._kernelProperty).reduce(function (props, key) { props[key] = [toxicDecorators.accessor({ get: function get() { // $FlowFixMe: support computed key here return videoConfig[key]; }, set: function set(value) { // $FlowFixMe: support computed key here videoConfig[key] = value; return value; } }), toxicDecorators.nonenumerable]; return props; }, {}); toxicDecorators.applyDecorators(this, props, { self: true }); kernelMethods.forEach(function (key) { _Object$defineProperty(_this, key, { value: function value() { var _this2 = this; for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return new _Promise(function (resolve) { var _dispatcher$binder; var id = _this2.__id; _this2.__dispatcher.binder.once({ id: id, name: '_' + key, fn: resolve }); (_dispatcher$binder = _this2.__dispatcher.binder)[/^(seek)$/.test(key) ? 'emitSync' : 'emit'].apply(_dispatcher$binder, [{ target: 'video', name: key, id: id }].concat(_toConsumableArray(args))); }); }, configurable: true, enumerable: false, writable: true }); }); domMethods.forEach(function (key) { if (key === 'fullscreen') return; _Object$defineProperty(_this, key, { value: function value() { var _dispatcher$dom; return (_dispatcher$dom = this.__dispatcher.dom)[key].apply(_dispatcher$dom, arguments); }, configurable: true, enumerable: false, writable: true }); }); } }, { key: '$watch', value: function $watch(key, handler) { var _this3 = this; var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, deep = _ref.deep, _ref$diff = _ref.diff, diff = _ref$diff === undefined ? true : _ref$diff, other = _ref.other, _ref$proxy = _ref.proxy, proxy = _ref$proxy === undefined ? false : _ref$proxy; if (!chimeeHelper.isString(key) && !chimeeHelper.isArray(key)) throw new TypeError('$watch only accept string and Array<string> as key to find the target to spy on, but not ' + key + ', whose type is ' + (typeof key === 'undefined' ? 'undefined' : _typeof(key))); var watching = true; var watcher = function watcher() { if (watching && (!(this instanceof VideoConfig) || this.dispatcher.changeWatchable)) chimeeHelper.bind(handler, this).apply(undefined, arguments); }; var unwatcher = function unwatcher() { watching = false; var index = _this3.__unwatchHandlers.indexOf(unwatcher); if (index > -1) _this3.__unwatchHandlers.splice(index, 1); }; var keys = chimeeHelper.isString(key) ? key.split('.') : key; var property = keys.pop(); var videoConfig = this.__dispatcher.videoConfig; var target = keys.length === 0 && !other && videoConfig._realDomAttr.indexOf(property) > -1 ? videoConfig : ['isFullscreen', 'fullscreenElement'].indexOf(property) > -1 ? this.__dispatcher.dom : chimeeHelper.getDeepProperty(other || this, keys, { throwError: true }); toxicDecorators.applyDecorators(target, _defineProperty({}, property, toxicDecorators.watch(watcher, { deep: deep, diff: diff, proxy: proxy })), { self: true }); this.__unwatchHandlers.push(unwatcher); return unwatcher; } }, { key: '$set', value: function $set(obj, property, value) { if (!chimeeHelper.isObject(obj) && !chimeeHelper.isArray(obj)) throw new TypeError('$set only support Array or Object, but not ' + obj + ', whose type is ' + (typeof obj === 'undefined' ? 'undefined' : _typeof(obj))); // $FlowFixMe: we have custom this function if (!chimeeHelper.isFunction(obj.__set)) { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') chimeeHelper.Log.warn('chimee', _JSON$stringify(obj) + ' has not been deep watch. There is no need to use $set.'); // $FlowFixMe: we support computed string on array here obj[property] = value; return; } obj.__set(property, value); } }, { key: '$del', value: function $del(obj, property) { if (!chimeeHelper.isObject(obj) && !chimeeHelper.isArray(obj)) throw new TypeError('$del only support Array or Object, but not ' + obj + ', whose type is ' + (typeof obj === 'undefined' ? 'undefined' : _typeof(obj))); // $FlowFixMe: we have custom this function if (!chimeeHelper.isFunction(obj.__del)) { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') chimeeHelper.Log.warn('chimee', _JSON$stringify(obj) + ' has not been deep watch. There is no need to use $del.'); // $FlowFixMe: we support computed string on array here delete obj[property]; return; } obj.__del(property); } }, { key: 'load', value: function load() { var _this4 = this; for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } return new _Promise(function (resolve) { var _dispatcher$binder2; _this4.__dispatcher.binder.once({ id: _this4.__id, name: '_load', target: 'plugin', fn: resolve }); (_dispatcher$binder2 = _this4.__dispatcher.binder).emit.apply(_dispatcher$binder2, [{ name: 'load', target: 'plugin', id: _this4.__id }].concat(args)); }); } }, { key: '$silentLoad', value: function $silentLoad() { var _this5 = this; for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { args[_key3] = arguments[_key3]; } return this.__dispatcher.binder.emit({ name: 'silentLoad', target: 'video', id: this.__id }).then(function () { var _dispatcher; return (_dispatcher = _this5.__dispatcher).silentLoad.apply(_dispatcher, args); }).then(function (result) { _this5.__dispatcher.binder.trigger({ name: 'silentLoad', target: 'video', id: _this5.__id }, result); }); } /** * call fullscreen api on some specific element * @param {boolean} flag true means fullscreen and means exit fullscreen * @param {string} element the element you want to fullscreen, default it's container, you can choose from video | container | wrapper */ }, { key: '$fullscreen', value: function $fullscreen() { var flag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; var element = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'container'; if (!this.__dispatcher.binder.emitSync({ name: 'fullscreen', id: this.__id, target: 'video-dom' }, flag, element)) return false; var result = this.__dispatcher.dom.fullscreen(flag, element); this.__dispatcher.binder.triggerSync({ name: 'fullscreen', id: this.__id, target: 'video-dom' }, flag, element); return result; } /** * emit an event * @param {string} key event's name * @param {...args} args */ }, { key: '$emit', value: function $emit(key) { var _dispatcher$binder3; var target = void 0; if (chimeeHelper.isObject(key) && chimeeHelper.isString(key.name) && chimeeHelper.isString(key.target)) { target = key.target; key = key.name; } if (!chimeeHelper.isString(key)) throw new TypeError('emit key parameter must be String'); /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && domEvents.indexOf(key.replace(/^\w_/, '')) > -1) { chimeeHelper.Log.warn('plugin', 'You are try to emit ' + key + ' event. As emit is wrapped in Promise. It make you can\'t use event.preventDefault and event.stopPropagation. So we advice you to use emitSync'); } for (var _len4 = arguments.length, args = Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) { args[_key4 - 1] = arguments[_key4]; } return (_dispatcher$binder3 = this.__dispatcher.binder).emit.apply(_dispatcher$binder3, [{ name: key, id: this.__id, target: target }].concat(_toConsumableArray(args))); } /** * emit a sync event * @param {string} key event's name * @param {...args} args */ }, { key: '$emitSync', value: function $emitSync(key) { var _dispatcher$binder4; var target = void 0; if (chimeeHelper.isObject(key) && chimeeHelper.isString(key.name) && chimeeHelper.isString(key.target)) { target = key.target; key = key.name; } if (!chimeeHelper.isString(key)) throw new TypeError('emitSync key parameter must be String'); for (var _len5 = arguments.length, args = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) { args[_key5 - 1] = arguments[_key5]; } return (_dispatcher$binder4 = this.__dispatcher.binder).emitSync.apply(_dispatcher$binder4, [{ name: key, id: this.__id, target: target }].concat(_toConsumableArray(args))); } /** * bind event handler through this function * @param {string} key event's name * @param {Function} fn event's handler */ }, { key: '$on', value: function $on(key, fn) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var eventInfo = _Object$assign({}, options, { name: key, id: this.__id, fn: fn }); this.__dispatcher.binder.on(eventInfo); // set on __events as mark so that i can destroy it when i destroy this.__addEvents(key, fn); } /** * remove event handler through this function * @param {string} key event's name * @param {Function} fn event's handler */ }, { key: '$off', value: function $off(key, fn) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var eventInfo = _Object$assign({}, options, { name: key, id: this.__id, fn: fn }); this.__dispatcher.binder.off(eventInfo); this.__removeEvents(key, fn); } /** * bind one time event handler * @param {string} key event's name * @param {Function} fn event's handler */ }, { key: '$once', value: function $once(key, fn) { var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var self = this; var boundFn = function boundFn() { chimeeHelper.bind(fn, this).apply(undefined, arguments); self.__removeEvents(key, boundFn); }; self.__addEvents(key, boundFn); var eventInfo = _Object$assign({}, options, { name: key, id: this.__id, fn: boundFn }); this.__dispatcher.binder.once(eventInfo); } /** * set style * @param {string} element optional, default to be video, you can choose from video | container | wrapper * @param {string} attribute the atrribue name * @param {any} value optional, when it's no offer, we consider you want to get the attribute's value. When it's offered, we consider you to set the attribute's value, if the value you passed is undefined, that means you want to remove the value; */ }, { key: '$css', value: function $css(method) { var _dispatcher$dom2; for (var _len6 = arguments.length, args = Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) { args[_key6 - 1] = arguments[_key6]; } return (_dispatcher$dom2 = this.__dispatcher.dom)[method + 'Style'].apply(_dispatcher$dom2, args); } /** * set attr * @param {string} element optional, default to be video, you can choose from video | container | wrapper * @param {string} attribute the atrribue nameß * @param {any} value optional, when it's no offer, we consider you want to get the attribute's value. When it's offered, we consider you to set the attribute's value, if the value you passed is undefined, that means you want to remove the value; */ }, { key: '$attr', value: function $attr(method) { var _dispatcher$dom3; for (var _len7 = arguments.length, args = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) { args[_key7 - 1] = arguments[_key7]; } if (method === 'set' && /video/.test(args[0])) { if (!this.__dispatcher.videoConfigReady) { /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') chimeeHelper.Log.warn('chimee', this.__id + ' is tring to set attribute on video before video inited. Please wait until the inited event has benn trigger'); return args[2]; } if (this.__dispatcher.videoConfig._realDomAttr.indexOf(args[1]) > -1) { var key = args[1], val = args[2]; this.__dispatcher.videoConfig[key] = val; return val; } } return (_dispatcher$dom3 = this.__dispatcher.dom)[method + 'Attr'].apply(_dispatcher$dom3, args); } }, { key: 'requestPictureInPicture', value: function requestPictureInPicture() { return this.__dispatcher.binder.emit({ target: 'video', name: 'enterpictureinpicture', id: this.__id }); } }, { key: 'exitPictureInPicture', value: function exitPictureInPicture() { return this.__dispatcher.binder.emit({ target: 'video', name: 'leavepictureinpicture', id: this.__id }); } }, { key: '__addEvents', value: function __addEvents(key, fn) { this.__events[key] = this.__events[key] || []; this.__events[key].push(fn); } }, { key: '__removeEvents', value: function __removeEvents(key, fn) { if (chimeeHelper.isEmpty(this.__events[key])) return; var index = this.__events[key].indexOf(fn); if (index < 0) return; this.__events[key].splice(index, 1); if (chimeeHelper.isEmpty(this.__events[key])) delete this.__events[key]; } }, { key: '__destroy', value: function __destroy() { var _this6 = this; this.__unwatchHandlers.forEach(function (unwatcher) { return unwatcher(); }); _Object$keys(this.__events).forEach(function (key) { if (!chimeeHelper.isArray(_this6.__events[key])) return; _this6.__events[key].forEach(function (fn) { return _this6.$off(key, fn); }); }); delete this.__events; } }, { key: 'currentTime', get: function get() { return this.__dispatcher.kernel.currentTime; }, set: function set(second) { this.__dispatcher.binder.emitSync({ name: 'seek', target: 'video', id: this.__id }, second); } }, { key: 'inPictureInPictureMode', get: function get() { return this.__dispatcher.inPictureInPictureMode; } }, { key: 'pictureInPictureWindow', get: function get() { return window.__chimee_picture_in_picture_window; } }, { key: '$plugins', get: function get() { return this.__dispatcher.plugins; } }, { key: '$pluginOrder', get: function get() { return this.__dispatcher.order; } }, { key: '$wrapper', get: function get() { return this.__dispatcher.dom.wrapper; } }, { key: '$container', get: function get() { return this.__dispatcher.dom.container; } }, { key: '$video', get: function get() { return this.__dispatcher.dom.videoElement; } }, { key: 'isFullscreen', get: function get() { return this.__dispatcher.dom.isFullscreen; } }, { key: 'fullscreenElement', get: function get() { return this.__dispatcher.dom.fullscreenElement; } }, { key: 'container', get: function get() { return this.__dispatcher.containerConfig; }, set: function set(config) { if (!chimeeHelper.isObject(config)) { throw new Error('The config of container must be Object, but not ' + (typeof config === 'undefined' ? 'undefined' : _typeof(config)) + '.'); } chimeeHelper.deepAssign(this.__dispatcher.containerConfig, config); return this.__dispatcher.container; } }, { key: 'videoRequireGuardedAttributes', get: function get() { return this.__dispatcher.dom.videoRequireGuardedAttributes; } }]); return VideoWrapper; }(), (_applyDecoratedDescriptor$1(_class2.prototype, '$silentLoad', [_dec2$1], _Object$getOwnPropertyDescriptor(_class2.prototype, '$silentLoad'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$fullscreen', [_dec3, _dec4, _dec5], _Object$getOwnPropertyDescriptor(_class2.prototype, '$fullscreen'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$emit', [_dec6], _Object$getOwnPropertyDescriptor(_class2.prototype, '$emit'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$emitSync', [_dec7], _Object$getOwnPropertyDescriptor(_class2.prototype, '$emitSync'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$on', [_dec8, _dec9, _dec10], _Object$getOwnPropertyDescriptor(_class2.prototype, '$on'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$off', [_dec11, _dec12, _dec13], _Object$getOwnPropertyDescriptor(_class2.prototype, '$off'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$once', [_dec14, _dec15], _Object$getOwnPropertyDescriptor(_class2.prototype, '$once'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$css', [_dec16, _dec17], _Object$getOwnPropertyDescriptor(_class2.prototype, '$css'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$attr', [_dec18, _dec19], _Object$getOwnPropertyDescriptor(_class2.prototype, '$attr'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, 'inPictureInPictureMode', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, 'inPictureInPictureMode'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, 'pictureInPictureWindow', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, 'pictureInPictureWindow'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$plugins', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, '$plugins'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$pluginOrder', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, '$pluginOrder'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$wrapper', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, '$wrapper'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$container', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, '$container'), _class2.prototype), _applyDecoratedDescriptor$1(_class2.prototype, '$video', [toxicDecorators.nonenumerable], _Object$getOwnPropertyDescriptor(_class2.prototype, '$video'), _class2.prototype)), _class2)) || _class$1); var _dec$2, _class$2; /** * <pre> * Plugin is the class for plugin developer. * When we use a plugin, we will generate an instance of plugin. * Developer can do most of things base on this plugin * </pre> */ var Plugin = (_dec$2 = toxicDecorators.autobindClass(), _dec$2(_class$2 = function (_VideoWrapper) { _inherits(Plugin, _VideoWrapper); /** * <pre> * to create a plugin, we need three parameter * 1. the config of a plugin * 2. the dispatcher * 3. this option for plugin to read * this is the plugin base class, which you can get on Chimee * You can just extends it and then install * But in that way you must remember to pass the arguments to super() * </pre> * @param {string} PluginConfig.id camelize from plugin's name or class name. * @param {string} PluginConfig.name plugin's name or class name * @param {Number} PluginConfig.level the level of z-index * @param {Boolean} PluginConfig.operable to tell if the plugin can be operable, if not, we will add pointer-events: none on it. * @param {Function} PluginConfig.create the create function which we will called when plugin is used. sth like constructor in object style. * @param {Function} PluginConfig.destroy function to be called when we destroy a plugin * @param {Object} PluginConfig.events You can set some events handler in this object, we will bind it once you use the plugin. * @param {Object} PluginConfig.data dataset we will bind on data in object style * @param {Object<{get: Function, set: Function}} PluginConfig.computed dataset we will handle by getter and setter * @param {Object<Function>} PluginConfig.methods some function we will bind on plugin * @param {string|HTMLElment} PluginConfig.el can be string or HTMLElement, we will use this to create the dom for plugin * @param {boolean} PluginConfig.penetrate boolean to let us do we need to forward the dom events for this plugin. * @param {Dispatcher} dispatcher referrence of dispatcher * @param {Object} option PluginOption that will pass to the plugin * @return {Plugin} plugin instance */ function Plugin() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, id = _ref.id, name = _ref.name, _ref$level = _ref.level, level = _ref$level === undefined ? 0 : _ref$level, _ref