myButton
+ * // -> myButton === myComponent.children()[0];
+ * ```
+ * Pass in options for child constructors and options for children of the child
+ * ```js
+ * var myButton = myComponent.addChild('MyButton', {
+ * text: 'Press Me',
+ * buttonChildExample: {
+ * buttonChildOption: true
+ * }
+ * });
+ * ```
+ *
+ * @param {String|Component} child The class name or instance of a child to add
+ * @param {Object=} options Options, including options to be passed to children of the child.
+ * @param {Number} index into our children array to attempt to add the child
+ * @return {Component} The child component (created by this process if a string was used)
+ * @method addChild
+ */
+
+ Component.prototype.addChild = function addChild(child) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+ var index = arguments.length <= 2 || arguments[2] === undefined ? this.children_.length : arguments[2];
+
+ var component = undefined;
+ var componentName = undefined;
+
+ // If child is a string, create nt with options
+ if (typeof child === 'string') {
+ componentName = child;
+
+ // Options can also be specified as a boolean, so convert to an empty object if false.
+ if (!options) {
+ options = {};
+ }
+
+ // Same as above, but true is deprecated so show a warning.
+ if (options === true) {
+ _utilsLogJs2['default'].warn('Initializing a child component with `true` is deprecated. Children should be defined in an array when possible, but if necessary use an object instead of `true`.');
+ options = {};
+ }
+
+ // If no componentClass in options, assume componentClass is the name lowercased
+ // (e.g. playButton)
+ var componentClassName = options.componentClass || _utilsToTitleCaseJs2['default'](componentName);
+
+ // Set name through options
+ options.name = componentName;
+
+ // Create a new object & element for this controls set
+ // If there's no .player_, this is a player
+ var ComponentClass = Component.getComponent(componentClassName);
+
+ if (!ComponentClass) {
+ throw new Error('Component ' + componentClassName + ' does not exist');
+ }
+
+ // data stored directly on the videojs object may be
+ // misidentified as a component to retain
+ // backwards-compatibility with 4.x. check to make sure the
+ // component class can be instantiated.
+ if (typeof ComponentClass !== 'function') {
+ return null;
+ }
+
+ component = new ComponentClass(this.player_ || this, options);
+
+ // child is a component instance
+ } else {
+ component = child;
+ }
+
+ this.children_.splice(index, 0, component);
+
+ if (typeof component.id === 'function') {
+ this.childIndex_[component.id()] = component;
+ }
+
+ // If a name wasn't used to create the component, check if we can use the
+ // name function of the component
+ componentName = componentName || component.name && component.name();
+
+ if (componentName) {
+ this.childNameIndex_[componentName] = component;
+ }
+
+ // Add the UI object's element to the container div (box)
+ // Having an element is not required
+ if (typeof component.el === 'function' && component.el()) {
+ var childNodes = this.contentEl().children;
+ var refNode = childNodes[index] || null;
+ this.contentEl().insertBefore(component.el(), refNode);
+ }
+
+ // Return so it can stored on parent object if desired.
+ return component;
+ };
+
+ /**
+ * Remove a child component from this component's list of children, and the
+ * child component's element from this component's element
+ *
+ * @param {Component} component Component to remove
+ * @method removeChild
+ */
+
+ Component.prototype.removeChild = function removeChild(component) {
+ if (typeof component === 'string') {
+ component = this.getChild(component);
+ }
+
+ if (!component || !this.children_) {
+ return;
+ }
+
+ var childFound = false;
+
+ for (var i = this.children_.length - 1; i >= 0; i--) {
+ if (this.children_[i] === component) {
+ childFound = true;
+ this.children_.splice(i, 1);
+ break;
+ }
+ }
+
+ if (!childFound) {
+ return;
+ }
+
+ this.childIndex_[component.id()] = null;
+ this.childNameIndex_[component.name()] = null;
+
+ var compEl = component.el();
+
+ if (compEl && compEl.parentNode === this.contentEl()) {
+ this.contentEl().removeChild(component.el());
+ }
+ };
+
+ /**
+ * Add and initialize default child components from options
+ * ```js
+ * // when an instance of MyComponent is created, all children in options
+ * // will be added to the instance by their name strings and options
+ * MyComponent.prototype.options_ = {
+ * children: [
+ * 'myChildComponent'
+ * ],
+ * myChildComponent: {
+ * myChildOption: true
+ * }
+ * };
+ *
+ * // Or when creating the component
+ * var myComp = new MyComponent(player, {
+ * children: [
+ * 'myChildComponent'
+ * ],
+ * myChildComponent: {
+ * myChildOption: true
+ * }
+ * });
+ * ```
+ * The children option can also be an array of
+ * child options objects (that also include a 'name' key).
+ * This can be used if you have two child components of the
+ * same type that need different options.
+ * ```js
+ * var myComp = new MyComponent(player, {
+ * children: [
+ * 'button',
+ * {
+ * name: 'button',
+ * someOtherOption: true
+ * },
+ * {
+ * name: 'button',
+ * someOtherOption: false
+ * }
+ * ]
+ * });
+ * ```
+ *
+ * @method initChildren
+ */
+
+ Component.prototype.initChildren = function initChildren() {
+ var _this = this;
+
+ var children = this.options_.children;
+
+ if (children) {
+ (function () {
+ // `this` is `parent`
+ var parentOptions = _this.options_;
+
+ var handleAdd = function handleAdd(child) {
+ var name = child.name;
+ var opts = child.opts;
+
+ // Allow options for children to be set at the parent options
+ // e.g. videojs(id, { controlBar: false });
+ // instead of videojs(id, { children: { controlBar: false });
+ if (parentOptions[name] !== undefined) {
+ opts = parentOptions[name];
+ }
+
+ // Allow for disabling default components
+ // e.g. options['children']['posterImage'] = false
+ if (opts === false) {
+ return;
+ }
+
+ // Allow options to be passed as a simple boolean if no configuration
+ // is necessary.
+ if (opts === true) {
+ opts = {};
+ }
+
+ // We also want to pass the original player options to each component as well so they don't need to
+ // reach back into the player for options later.
+ opts.playerOptions = _this.options_.playerOptions;
+
+ // Create and add the child component.
+ // Add a direct reference to the child by name on the parent instance.
+ // If two of the same component are used, different names should be supplied
+ // for each
+ var newChild = _this.addChild(name, opts);
+ if (newChild) {
+ _this[name] = newChild;
+ }
+ };
+
+ // Allow for an array of children details to passed in the options
+ var workingChildren = undefined;
+ var Tech = Component.getComponent('Tech');
+
+ if (Array.isArray(children)) {
+ workingChildren = children;
+ } else {
+ workingChildren = Object.keys(children);
+ }
+
+ workingChildren
+ // children that are in this.options_ but also in workingChildren would
+ // give us extra children we do not want. So, we want to filter them out.
+ .concat(Object.keys(_this.options_).filter(function (child) {
+ return !workingChildren.some(function (wchild) {
+ if (typeof wchild === 'string') {
+ return child === wchild;
+ } else {
+ return child === wchild.name;
+ }
+ });
+ })).map(function (child) {
+ var name = undefined,
+ opts = undefined;
+
+ if (typeof child === 'string') {
+ name = child;
+ opts = children[name] || _this.options_[name] || {};
+ } else {
+ name = child.name;
+ opts = child;
+ }
+
+ return { name: name, opts: opts };
+ }).filter(function (child) {
+ // we have to make sure that child.name isn't in the techOrder since
+ // techs are registerd as Components but can't aren't compatible
+ // See https://github.com/videojs/video.js/issues/2772
+ var c = Component.getComponent(child.opts.componentClass || _utilsToTitleCaseJs2['default'](child.name));
+ return c && !Tech.isTech(c);
+ }).forEach(handleAdd);
+ })();
+ }
+ };
+
+ /**
+ * Allows sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ Component.prototype.buildCSSClass = function buildCSSClass() {
+ // Child classes can include a function that does:
+ // return 'CLASS NAME' + this._super();
+ return '';
+ };
+
+ /**
+ * Add an event listener to this component's element
+ * ```js
+ * var myFunc = function(){
+ * var myComponent = this;
+ * // Do something when the event is fired
+ * };
+ *
+ * myComponent.on('eventType', myFunc);
+ * ```
+ * The context of myFunc will be myComponent unless previously bound.
+ * Alternatively, you can add a listener to another element or component.
+ * ```js
+ * myComponent.on(otherElement, 'eventName', myFunc);
+ * myComponent.on(otherComponent, 'eventName', myFunc);
+ * ```
+ * The benefit of using this over `VjsEvents.on(otherElement, 'eventName', myFunc)`
+ * and `otherComponent.on('eventName', myFunc)` is that this way the listeners
+ * will be automatically cleaned up when either component is disposed.
+ * It will also bind myComponent as the context of myFunc.
+ * **NOTE**: When using this on elements in the page other than window
+ * and document (both permanent), if you remove the element from the DOM
+ * you need to call `myComponent.trigger(el, 'dispose')` on it to clean up
+ * references to it and allow the browser to garbage collect it.
+ *
+ * @param {String|Component} first The event type or other component
+ * @param {Function|String} second The event handler or event type
+ * @param {Function} third The event handler
+ * @return {Component}
+ * @method on
+ */
+
+ Component.prototype.on = function on(first, second, third) {
+ var _this2 = this;
+
+ if (typeof first === 'string' || Array.isArray(first)) {
+ Events.on(this.el_, first, Fn.bind(this, second));
+
+ // Targeting another component or element
+ } else {
+ (function () {
+ var target = first;
+ var type = second;
+ var fn = Fn.bind(_this2, third);
+
+ // When this component is disposed, remove the listener from the other component
+ var removeOnDispose = function removeOnDispose() {
+ return _this2.off(target, type, fn);
+ };
+
+ // Use the same function ID so we can remove it later it using the ID
+ // of the original listener
+ removeOnDispose.guid = fn.guid;
+ _this2.on('dispose', removeOnDispose);
+
+ // If the other component is disposed first we need to clean the reference
+ // to the other component in this component's removeOnDispose listener
+ // Otherwise we create a memory leak.
+ var cleanRemover = function cleanRemover() {
+ return _this2.off('dispose', removeOnDispose);
+ };
+
+ // Add the same function ID so we can easily remove it later
+ cleanRemover.guid = fn.guid;
+
+ // Check if this is a DOM node
+ if (first.nodeName) {
+ // Add the listener to the other element
+ Events.on(target, type, fn);
+ Events.on(target, 'dispose', cleanRemover);
+
+ // Should be a component
+ // Not using `instanceof Component` because it makes mock players difficult
+ } else if (typeof first.on === 'function') {
+ // Add the listener to the other component
+ target.on(type, fn);
+ target.on('dispose', cleanRemover);
+ }
+ })();
+ }
+
+ return this;
+ };
+
+ /**
+ * Remove an event listener from this component's element
+ * ```js
+ * myComponent.off('eventType', myFunc);
+ * ```
+ * If myFunc is excluded, ALL listeners for the event type will be removed.
+ * If eventType is excluded, ALL listeners will be removed from the component.
+ * Alternatively you can use `off` to remove listeners that were added to other
+ * elements or components using `myComponent.on(otherComponent...`.
+ * In this case both the event type and listener function are REQUIRED.
+ * ```js
+ * myComponent.off(otherElement, 'eventType', myFunc);
+ * myComponent.off(otherComponent, 'eventType', myFunc);
+ * ```
+ *
+ * @param {String=|Component} first The event type or other component
+ * @param {Function=|String} second The listener function or event type
+ * @param {Function=} third The listener for other component
+ * @return {Component}
+ * @method off
+ */
+
+ Component.prototype.off = function off(first, second, third) {
+ if (!first || typeof first === 'string' || Array.isArray(first)) {
+ Events.off(this.el_, first, second);
+ } else {
+ var target = first;
+ var type = second;
+ // Ensure there's at least a guid, even if the function hasn't been used
+ var fn = Fn.bind(this, third);
+
+ // Remove the dispose listener on this component,
+ // which was given the same guid as the event listener
+ this.off('dispose', fn);
+
+ if (first.nodeName) {
+ // Remove the listener
+ Events.off(target, type, fn);
+ // Remove the listener for cleaning the dispose listener
+ Events.off(target, 'dispose', fn);
+ } else {
+ target.off(type, fn);
+ target.off('dispose', fn);
+ }
+ }
+
+ return this;
+ };
+
+ /**
+ * Add an event listener to be triggered only once and then removed
+ * ```js
+ * myComponent.one('eventName', myFunc);
+ * ```
+ * Alternatively you can add a listener to another element or component
+ * that will be triggered only once.
+ * ```js
+ * myComponent.one(otherElement, 'eventName', myFunc);
+ * myComponent.one(otherComponent, 'eventName', myFunc);
+ * ```
+ *
+ * @param {String|Component} first The event type or other component
+ * @param {Function|String} second The listener function or event type
+ * @param {Function=} third The listener function for other component
+ * @return {Component}
+ * @method one
+ */
+
+ Component.prototype.one = function one(first, second, third) {
+ var _this3 = this,
+ _arguments = arguments;
+
+ if (typeof first === 'string' || Array.isArray(first)) {
+ Events.one(this.el_, first, Fn.bind(this, second));
+ } else {
+ (function () {
+ var target = first;
+ var type = second;
+ var fn = Fn.bind(_this3, third);
+
+ var newFunc = function newFunc() {
+ _this3.off(target, type, newFunc);
+ fn.apply(null, _arguments);
+ };
+
+ // Keep the same function ID so we can remove it later
+ newFunc.guid = fn.guid;
+
+ _this3.on(target, type, newFunc);
+ })();
+ }
+
+ return this;
+ };
+
+ /**
+ * Trigger an event on an element
+ * ```js
+ * myComponent.trigger('eventName');
+ * myComponent.trigger({'type':'eventName'});
+ * myComponent.trigger('eventName', {data: 'some data'});
+ * myComponent.trigger({'type':'eventName'}, {data: 'some data'});
+ * ```
+ *
+ * @param {Event|Object|String} event A string (the type) or an event object with a type attribute
+ * @param {Object} [hash] data hash to pass along with the event
+ * @return {Component} self
+ * @method trigger
+ */
+
+ Component.prototype.trigger = function trigger(event, hash) {
+ Events.trigger(this.el_, event, hash);
+ return this;
+ };
+
+ /**
+ * Bind a listener to the component's ready state.
+ * Different from event listeners in that if the ready event has already happened
+ * it will trigger the function immediately.
+ *
+ * @param {Function} fn Ready listener
+ * @param {Boolean} sync Exec the listener synchronously if component is ready
+ * @return {Component}
+ * @method ready
+ */
+
+ Component.prototype.ready = function ready(fn) {
+ var sync = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
+
+ if (fn) {
+ if (this.isReady_) {
+ if (sync) {
+ fn.call(this);
+ } else {
+ // Call the function asynchronously by default for consistency
+ this.setTimeout(fn, 1);
+ }
+ } else {
+ this.readyQueue_ = this.readyQueue_ || [];
+ this.readyQueue_.push(fn);
+ }
+ }
+ return this;
+ };
+
+ /**
+ * Trigger the ready listeners
+ *
+ * @return {Component}
+ * @method triggerReady
+ */
+
+ Component.prototype.triggerReady = function triggerReady() {
+ this.isReady_ = true;
+
+ // Ensure ready is triggerd asynchronously
+ this.setTimeout(function () {
+ var readyQueue = this.readyQueue_;
+
+ // Reset Ready Queue
+ this.readyQueue_ = [];
+
+ if (readyQueue && readyQueue.length > 0) {
+ readyQueue.forEach(function (fn) {
+ fn.call(this);
+ }, this);
+ }
+
+ // Allow for using event listeners also
+ this.trigger('ready');
+ }, 1);
+ };
+
+ /**
+ * Finds a single DOM element matching `selector` within the component's
+ * `contentEl` or another custom context.
+ *
+ * @method $
+ * @param {String} selector
+ * A valid CSS selector, which will be passed to `querySelector`.
+ *
+ * @param {Element|String} [context=document]
+ * A DOM element within which to query. Can also be a selector
+ * string in which case the first matching element will be used
+ * as context. If missing (or no element matches selector), falls
+ * back to `document`.
+ *
+ * @return {Element|null}
+ */
+
+ Component.prototype.$ = function $(selector, context) {
+ return Dom.$(selector, context || this.contentEl());
+ };
+
+ /**
+ * Finds a all DOM elements matching `selector` within the component's
+ * `contentEl` or another custom context.
+ *
+ * @method $$
+ * @param {String} selector
+ * A valid CSS selector, which will be passed to `querySelectorAll`.
+ *
+ * @param {Element|String} [context=document]
+ * A DOM element within which to query. Can also be a selector
+ * string in which case the first matching element will be used
+ * as context. If missing (or no element matches selector), falls
+ * back to `document`.
+ *
+ * @return {NodeList}
+ */
+
+ Component.prototype.$$ = function $$(selector, context) {
+ return Dom.$$(selector, context || this.contentEl());
+ };
+
+ /**
+ * Check if a component's element has a CSS class name
+ *
+ * @param {String} classToCheck Classname to check
+ * @return {Component}
+ * @method hasClass
+ */
+
+ Component.prototype.hasClass = function hasClass(classToCheck) {
+ return Dom.hasElClass(this.el_, classToCheck);
+ };
+
+ /**
+ * Add a CSS class name to the component's element
+ *
+ * @param {String} classToAdd Classname to add
+ * @return {Component}
+ * @method addClass
+ */
+
+ Component.prototype.addClass = function addClass(classToAdd) {
+ Dom.addElClass(this.el_, classToAdd);
+ return this;
+ };
+
+ /**
+ * Remove a CSS class name from the component's element
+ *
+ * @param {String} classToRemove Classname to remove
+ * @return {Component}
+ * @method removeClass
+ */
+
+ Component.prototype.removeClass = function removeClass(classToRemove) {
+ Dom.removeElClass(this.el_, classToRemove);
+ return this;
+ };
+
+ /**
+ * Add or remove a CSS class name from the component's element
+ *
+ * @param {String} classToToggle
+ * @param {Boolean|Function} [predicate]
+ * Can be a function that returns a Boolean. If `true`, the class
+ * will be added; if `false`, the class will be removed. If not
+ * given, the class will be added if not present and vice versa.
+ *
+ * @return {Component}
+ * @method toggleClass
+ */
+
+ Component.prototype.toggleClass = function toggleClass(classToToggle, predicate) {
+ Dom.toggleElClass(this.el_, classToToggle, predicate);
+ return this;
+ };
+
+ /**
+ * Show the component element if hidden
+ *
+ * @return {Component}
+ * @method show
+ */
+
+ Component.prototype.show = function show() {
+ this.removeClass('vjs-hidden');
+ return this;
+ };
+
+ /**
+ * Hide the component element if currently showing
+ *
+ * @return {Component}
+ * @method hide
+ */
+
+ Component.prototype.hide = function hide() {
+ this.addClass('vjs-hidden');
+ return this;
+ };
+
+ /**
+ * Lock an item in its visible state
+ * To be used with fadeIn/fadeOut.
+ *
+ * @return {Component}
+ * @private
+ * @method lockShowing
+ */
+
+ Component.prototype.lockShowing = function lockShowing() {
+ this.addClass('vjs-lock-showing');
+ return this;
+ };
+
+ /**
+ * Unlock an item to be hidden
+ * To be used with fadeIn/fadeOut.
+ *
+ * @return {Component}
+ * @private
+ * @method unlockShowing
+ */
+
+ Component.prototype.unlockShowing = function unlockShowing() {
+ this.removeClass('vjs-lock-showing');
+ return this;
+ };
+
+ /**
+ * Set or get the width of the component (CSS values)
+ * Setting the video tag dimension values only works with values in pixels.
+ * Percent values will not work.
+ * Some percents can be used, but width()/height() will return the number + %,
+ * not the actual computed width/height.
+ *
+ * @param {Number|String=} num Optional width number
+ * @param {Boolean} skipListeners Skip the 'resize' event trigger
+ * @return {Component} This component, when setting the width
+ * @return {Number|String} The width, when getting
+ * @method width
+ */
+
+ Component.prototype.width = function width(num, skipListeners) {
+ return this.dimension('width', num, skipListeners);
+ };
+
+ /**
+ * Get or set the height of the component (CSS values)
+ * Setting the video tag dimension values only works with values in pixels.
+ * Percent values will not work.
+ * Some percents can be used, but width()/height() will return the number + %,
+ * not the actual computed width/height.
+ *
+ * @param {Number|String=} num New component height
+ * @param {Boolean=} skipListeners Skip the resize event trigger
+ * @return {Component} This component, when setting the height
+ * @return {Number|String} The height, when getting
+ * @method height
+ */
+
+ Component.prototype.height = function height(num, skipListeners) {
+ return this.dimension('height', num, skipListeners);
+ };
+
+ /**
+ * Set both width and height at the same time
+ *
+ * @param {Number|String} width Width of player
+ * @param {Number|String} height Height of player
+ * @return {Component} The component
+ * @method dimensions
+ */
+
+ Component.prototype.dimensions = function dimensions(width, height) {
+ // Skip resize listeners on width for optimization
+ return this.width(width, true).height(height);
+ };
+
+ /**
+ * Get or set width or height
+ * This is the shared code for the width() and height() methods.
+ * All for an integer, integer + 'px' or integer + '%';
+ * Known issue: Hidden elements officially have a width of 0. We're defaulting
+ * to the style.width value and falling back to computedStyle which has the
+ * hidden element issue. Info, but probably not an efficient fix:
+ * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
+ *
+ * @param {String} widthOrHeight 'width' or 'height'
+ * @param {Number|String=} num New dimension
+ * @param {Boolean=} skipListeners Skip resize event trigger
+ * @return {Component} The component if a dimension was set
+ * @return {Number|String} The dimension if nothing was set
+ * @private
+ * @method dimension
+ */
+
+ Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
+ if (num !== undefined) {
+ // Set to zero if null or literally NaN (NaN !== NaN)
+ if (num === null || num !== num) {
+ num = 0;
+ }
+
+ // Check if using css width/height (% or px) and adjust
+ if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
+ this.el_.style[widthOrHeight] = num;
+ } else if (num === 'auto') {
+ this.el_.style[widthOrHeight] = '';
+ } else {
+ this.el_.style[widthOrHeight] = num + 'px';
+ }
+
+ // skipListeners allows us to avoid triggering the resize event when setting both width and height
+ if (!skipListeners) {
+ this.trigger('resize');
+ }
+
+ // Return component
+ return this;
+ }
+
+ // Not setting a value, so getting it
+ // Make sure element exists
+ if (!this.el_) {
+ return 0;
+ }
+
+ // Get dimension value from style
+ var val = this.el_.style[widthOrHeight];
+ var pxIndex = val.indexOf('px');
+
+ if (pxIndex !== -1) {
+ // Return the pixel value with no 'px'
+ return parseInt(val.slice(0, pxIndex), 10);
+ }
+
+ // No px so using % or no style was set, so falling back to offsetWidth/height
+ // If component has display:none, offset will return 0
+ // TODO: handle display:none and no dimension style using px
+ return parseInt(this.el_['offset' + _utilsToTitleCaseJs2['default'](widthOrHeight)], 10);
+ };
+
+ /**
+ * Get width or height of computed style
+ * @param {String} widthOrHeight 'width' or 'height'
+ * @return {Number|Boolean} The bolean false if nothing was set
+ * @method currentDimension
+ */
+
+ Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
+ var computedWidthOrHeight = 0;
+
+ if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
+ throw new Error('currentDimension only accepts width or height value');
+ }
+
+ if (typeof _globalWindow2['default'].getComputedStyle === 'function') {
+ var computedStyle = _globalWindow2['default'].getComputedStyle(this.el_);
+ computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
+ } else if (this.el_.currentStyle) {
+ // ie 8 doesn't support computed style, shim it
+ // return clientWidth or clientHeight instead for better accuracy
+ var rule = 'offset' + _utilsToTitleCaseJs2['default'](widthOrHeight);
+ computedWidthOrHeight = this.el_[rule];
+ }
+
+ // remove 'px' from variable and parse as integer
+ computedWidthOrHeight = parseFloat(computedWidthOrHeight);
+ return computedWidthOrHeight;
+ };
+
+ /**
+ * Get an object which contains width and height values of computed style
+ * @return {Object} The dimensions of element
+ * @method currentDimensions
+ */
+
+ Component.prototype.currentDimensions = function currentDimensions() {
+ return {
+ width: this.currentDimension('width'),
+ height: this.currentDimension('height')
+ };
+ };
+
+ /**
+ * Get width of computed style
+ * @return {Integer}
+ * @method currentWidth
+ */
+
+ Component.prototype.currentWidth = function currentWidth() {
+ return this.currentDimension('width');
+ };
+
+ /**
+ * Get height of computed style
+ * @return {Integer}
+ * @method currentHeight
+ */
+
+ Component.prototype.currentHeight = function currentHeight() {
+ return this.currentDimension('height');
+ };
+
+ /**
+ * Emit 'tap' events when touch events are supported
+ * This is used to support toggling the controls through a tap on the video.
+ * We're requiring them to be enabled because otherwise every component would
+ * have this extra overhead unnecessarily, on mobile devices where extra
+ * overhead is especially bad.
+ *
+ * @private
+ * @method emitTapEvents
+ */
+
+ Component.prototype.emitTapEvents = function emitTapEvents() {
+ // Track the start time so we can determine how long the touch lasted
+ var touchStart = 0;
+ var firstTouch = null;
+
+ // Maximum movement allowed during a touch event to still be considered a tap
+ // Other popular libs use anywhere from 2 (hammer.js) to 15, so 10 seems like a nice, round number.
+ var tapMovementThreshold = 10;
+
+ // The maximum length a touch can be while still being considered a tap
+ var touchTimeThreshold = 200;
+
+ var couldBeTap = undefined;
+
+ this.on('touchstart', function (event) {
+ // If more than one finger, don't consider treating this as a click
+ if (event.touches.length === 1) {
+ // Copy the touches object to prevent modifying the original
+ firstTouch = _objectAssign2['default']({}, event.touches[0]);
+ // Record start time so we can detect a tap vs. "touch and hold"
+ touchStart = new Date().getTime();
+ // Reset couldBeTap tracking
+ couldBeTap = true;
+ }
+ });
+
+ this.on('touchmove', function (event) {
+ // If more than one finger, don't consider treating this as a click
+ if (event.touches.length > 1) {
+ couldBeTap = false;
+ } else if (firstTouch) {
+ // Some devices will throw touchmoves for all but the slightest of taps.
+ // So, if we moved only a small distance, this could still be a tap
+ var xdiff = event.touches[0].pageX - firstTouch.pageX;
+ var ydiff = event.touches[0].pageY - firstTouch.pageY;
+ var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
+
+ if (touchDistance > tapMovementThreshold) {
+ couldBeTap = false;
+ }
+ }
+ });
+
+ var noTap = function noTap() {
+ couldBeTap = false;
+ };
+
+ // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
+ this.on('touchleave', noTap);
+ this.on('touchcancel', noTap);
+
+ // When the touch ends, measure how long it took and trigger the appropriate
+ // event
+ this.on('touchend', function (event) {
+ firstTouch = null;
+ // Proceed only if the touchmove/leave/cancel event didn't happen
+ if (couldBeTap === true) {
+ // Measure how long the touch lasted
+ var touchTime = new Date().getTime() - touchStart;
+
+ // Make sure the touch was less than the threshold to be considered a tap
+ if (touchTime < touchTimeThreshold) {
+ // Don't let browser turn this into a click
+ event.preventDefault();
+ this.trigger('tap');
+ // It may be good to copy the touchend event object and change the
+ // type to tap, if the other event properties aren't exact after
+ // Events.fixEvent runs (e.g. event.target)
+ }
+ }
+ });
+ };
+
+ /**
+ * Report user touch activity when touch events occur
+ * User activity is used to determine when controls should show/hide. It's
+ * relatively simple when it comes to mouse events, because any mouse event
+ * should show the controls. So we capture mouse events that bubble up to the
+ * player and report activity when that happens.
+ * With touch events it isn't as easy. We can't rely on touch events at the
+ * player level, because a tap (touchstart + touchend) on the video itself on
+ * mobile devices is meant to turn controls off (and on). User activity is
+ * checked asynchronously, so what could happen is a tap event on the video
+ * turns the controls off, then the touchend event bubbles up to the player,
+ * which if it reported user activity, would turn the controls right back on.
+ * (We also don't want to completely block touch events from bubbling up)
+ * Also a touchmove, touch+hold, and anything other than a tap is not supposed
+ * to turn the controls back on on a mobile device.
+ * Here we're setting the default component behavior to report user activity
+ * whenever touch events happen, and this can be turned off by components that
+ * want touch events to act differently.
+ *
+ * @method enableTouchActivity
+ */
+
+ Component.prototype.enableTouchActivity = function enableTouchActivity() {
+ // Don't continue if the root player doesn't support reporting user activity
+ if (!this.player() || !this.player().reportUserActivity) {
+ return;
+ }
+
+ // listener for reporting that the user is active
+ var report = Fn.bind(this.player(), this.player().reportUserActivity);
+
+ var touchHolding = undefined;
+
+ this.on('touchstart', function () {
+ report();
+ // For as long as the they are touching the device or have their mouse down,
+ // we consider them active even if they're not moving their finger or mouse.
+ // So we want to continue to update that they are active
+ this.clearInterval(touchHolding);
+ // report at the same interval as activityCheck
+ touchHolding = this.setInterval(report, 250);
+ });
+
+ var touchEnd = function touchEnd(event) {
+ report();
+ // stop the interval that maintains activity if the touch is holding
+ this.clearInterval(touchHolding);
+ };
+
+ this.on('touchmove', report);
+ this.on('touchend', touchEnd);
+ this.on('touchcancel', touchEnd);
+ };
+
+ /**
+ * Creates timeout and sets up disposal automatically.
+ *
+ * @param {Function} fn The function to run after the timeout.
+ * @param {Number} timeout Number of ms to delay before executing specified function.
+ * @return {Number} Returns the timeout ID
+ * @method setTimeout
+ */
+
+ Component.prototype.setTimeout = function setTimeout(fn, timeout) {
+ fn = Fn.bind(this, fn);
+
+ // window.setTimeout would be preferable here, but due to some bizarre issue with Sinon and/or Phantomjs, we can't.
+ var timeoutId = _globalWindow2['default'].setTimeout(fn, timeout);
+
+ var disposeFn = function disposeFn() {
+ this.clearTimeout(timeoutId);
+ };
+
+ disposeFn.guid = 'vjs-timeout-' + timeoutId;
+
+ this.on('dispose', disposeFn);
+
+ return timeoutId;
+ };
+
+ /**
+ * Clears a timeout and removes the associated dispose listener
+ *
+ * @param {Number} timeoutId The id of the timeout to clear
+ * @return {Number} Returns the timeout ID
+ * @method clearTimeout
+ */
+
+ Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
+ _globalWindow2['default'].clearTimeout(timeoutId);
+
+ var disposeFn = function disposeFn() {};
+
+ disposeFn.guid = 'vjs-timeout-' + timeoutId;
+
+ this.off('dispose', disposeFn);
+
+ return timeoutId;
+ };
+
+ /**
+ * Creates an interval and sets up disposal automatically.
+ *
+ * @param {Function} fn The function to run every N seconds.
+ * @param {Number} interval Number of ms to delay before executing specified function.
+ * @return {Number} Returns the interval ID
+ * @method setInterval
+ */
+
+ Component.prototype.setInterval = function setInterval(fn, interval) {
+ fn = Fn.bind(this, fn);
+
+ var intervalId = _globalWindow2['default'].setInterval(fn, interval);
+
+ var disposeFn = function disposeFn() {
+ this.clearInterval(intervalId);
+ };
+
+ disposeFn.guid = 'vjs-interval-' + intervalId;
+
+ this.on('dispose', disposeFn);
+
+ return intervalId;
+ };
+
+ /**
+ * Clears an interval and removes the associated dispose listener
+ *
+ * @param {Number} intervalId The id of the interval to clear
+ * @return {Number} Returns the interval ID
+ * @method clearInterval
+ */
+
+ Component.prototype.clearInterval = function clearInterval(intervalId) {
+ _globalWindow2['default'].clearInterval(intervalId);
+
+ var disposeFn = function disposeFn() {};
+
+ disposeFn.guid = 'vjs-interval-' + intervalId;
+
+ this.off('dispose', disposeFn);
+
+ return intervalId;
+ };
+
+ /**
+ * Registers a component
+ *
+ * @param {String} name Name of the component to register
+ * @param {Object} comp The component to register
+ * @static
+ * @method registerComponent
+ */
+
+ Component.registerComponent = function registerComponent(name, comp) {
+ if (!Component.components_) {
+ Component.components_ = {};
+ }
+
+ Component.components_[name] = comp;
+ return comp;
+ };
+
+ /**
+ * Gets a component by name
+ *
+ * @param {String} name Name of the component to get
+ * @return {Component}
+ * @static
+ * @method getComponent
+ */
+
+ Component.getComponent = function getComponent(name) {
+ if (Component.components_ && Component.components_[name]) {
+ return Component.components_[name];
+ }
+
+ if (_globalWindow2['default'] && _globalWindow2['default'].videojs && _globalWindow2['default'].videojs[name]) {
+ _utilsLogJs2['default'].warn('The ' + name + ' component was added to the videojs object when it should be registered using videojs.registerComponent(name, component)');
+ return _globalWindow2['default'].videojs[name];
+ }
+ };
+
+ /**
+ * Sets up the constructor using the supplied init method
+ * or uses the init of the parent object
+ *
+ * @param {Object} props An object of properties
+ * @static
+ * @deprecated
+ * @method extend
+ */
+
+ Component.extend = function extend(props) {
+ props = props || {};
+
+ _utilsLogJs2['default'].warn('Component.extend({}) has been deprecated, use videojs.extend(Component, {}) instead');
+
+ // Set up the constructor using the supplied init method
+ // or using the init of the parent object
+ // Make sure to check the unobfuscated version for external libs
+ var init = props.init || props.init || this.prototype.init || this.prototype.init || function () {};
+ // In Resig's simple class inheritance (previously used) the constructor
+ // is a function that calls `this.init.apply(arguments)`
+ // However that would prevent us from using `ParentObject.call(this);`
+ // in a Child constructor because the `this` in `this.init`
+ // would still refer to the Child and cause an infinite loop.
+ // We would instead have to do
+ // `ParentObject.prototype.init.apply(this, arguments);`
+ // Bleh. We're not creating a _super() function, so it's good to keep
+ // the parent constructor reference simple.
+ var subObj = function subObj() {
+ init.apply(this, arguments);
+ };
+
+ // Inherit from this object's prototype
+ subObj.prototype = Object.create(this.prototype);
+ // Reset the constructor property for subObj otherwise
+ // instances of subObj would have the constructor of the parent Object
+ subObj.prototype.constructor = subObj;
+
+ // Make the class extendable
+ subObj.extend = Component.extend;
+
+ // Extend subObj's prototype with functions and other properties from props
+ for (var _name in props) {
+ if (props.hasOwnProperty(_name)) {
+ subObj.prototype[_name] = props[_name];
+ }
+ }
+
+ return subObj;
+ };
+
+ return Component;
+})();
+
+Component.registerComponent('Component', Component);
+exports['default'] = Component;
+module.exports = exports['default'];
+
+},{"./utils/dom.js":146,"./utils/events.js":147,"./utils/fn.js":148,"./utils/guid.js":150,"./utils/log.js":151,"./utils/merge-options.js":152,"./utils/to-title-case.js":155,"global/window":10,"object.assign":57}],72:[function(_dereq_,module,exports){
+/**
+ * @file audio-track-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackButtonJs = _dereq_('../track-button.js');
+
+var _trackButtonJs2 = _interopRequireDefault(_trackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _audioTrackMenuItemJs = _dereq_('./audio-track-menu-item.js');
+
+var _audioTrackMenuItemJs2 = _interopRequireDefault(_audioTrackMenuItemJs);
+
+/**
+ * The base class for buttons that toggle specific text track types (e.g. subtitles)
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends TrackButton
+ * @class AudioTrackButton
+ */
+
+var AudioTrackButton = (function (_TrackButton) {
+ _inherits(AudioTrackButton, _TrackButton);
+
+ function AudioTrackButton(player) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, AudioTrackButton);
+
+ options.tracks = player.audioTracks && player.audioTracks();
+
+ _TrackButton.call(this, player, options);
+
+ this.el_.setAttribute('aria-label', 'Audio Menu');
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Create a menu item for each audio track
+ *
+ * @return {Array} Array of menu items
+ * @method createItems
+ */
+
+ AudioTrackButton.prototype.createItems = function createItems() {
+ var items = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ var tracks = this.player_.audioTracks && this.player_.audioTracks();
+
+ if (!tracks) {
+ return items;
+ }
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+
+ items.push(new _audioTrackMenuItemJs2['default'](this.player_, {
+ // MenuItem is selectable
+ 'selectable': true,
+ 'track': track
+ }));
+ }
+
+ return items;
+ };
+
+ return AudioTrackButton;
+})(_trackButtonJs2['default']);
+
+_componentJs2['default'].registerComponent('AudioTrackButton', AudioTrackButton);
+exports['default'] = AudioTrackButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/fn.js":148,"../track-button.js":102,"./audio-track-menu-item.js":73}],73:[function(_dereq_,module,exports){
+/**
+ * @file audio-track-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');
+
+var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+/**
+ * The audio track menu item
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuItem
+ * @class AudioTrackMenuItem
+ */
+
+var AudioTrackMenuItem = (function (_MenuItem) {
+ _inherits(AudioTrackMenuItem, _MenuItem);
+
+ function AudioTrackMenuItem(player, options) {
+ var _this = this;
+
+ _classCallCheck(this, AudioTrackMenuItem);
+
+ var track = options.track;
+ var tracks = player.audioTracks();
+
+ // Modify options for parent MenuItem class's init.
+ options.label = track.label || track.language || 'Unknown';
+ options.selected = track.enabled;
+
+ _MenuItem.call(this, player, options);
+
+ this.track = track;
+
+ if (tracks) {
+ (function () {
+ var changeHandler = Fn.bind(_this, _this.handleTracksChange);
+
+ tracks.addEventListener('change', changeHandler);
+ _this.on('dispose', function () {
+ tracks.removeEventListener('change', changeHandler);
+ });
+ })();
+ }
+ }
+
+ /**
+ * Handle click on audio track
+ *
+ * @method handleClick
+ */
+
+ AudioTrackMenuItem.prototype.handleClick = function handleClick(event) {
+ var tracks = this.player_.audioTracks();
+
+ _MenuItem.prototype.handleClick.call(this, event);
+
+ if (!tracks) return;
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+
+ if (track === this.track) {
+ track.enabled = true;
+ }
+ }
+ };
+
+ /**
+ * Handle audio track change
+ *
+ * @method handleTracksChange
+ */
+
+ AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
+ this.selected(this.track.enabled);
+ };
+
+ return AudioTrackMenuItem;
+})(_menuMenuItemJs2['default']);
+
+_componentJs2['default'].registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
+exports['default'] = AudioTrackMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu-item.js":114,"../../utils/fn.js":148}],74:[function(_dereq_,module,exports){
+/**
+ * @file control-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+// Required children
+
+var _playToggleJs = _dereq_('./play-toggle.js');
+
+var _playToggleJs2 = _interopRequireDefault(_playToggleJs);
+
+var _timeControlsCurrentTimeDisplayJs = _dereq_('./time-controls/current-time-display.js');
+
+var _timeControlsCurrentTimeDisplayJs2 = _interopRequireDefault(_timeControlsCurrentTimeDisplayJs);
+
+var _timeControlsDurationDisplayJs = _dereq_('./time-controls/duration-display.js');
+
+var _timeControlsDurationDisplayJs2 = _interopRequireDefault(_timeControlsDurationDisplayJs);
+
+var _timeControlsTimeDividerJs = _dereq_('./time-controls/time-divider.js');
+
+var _timeControlsTimeDividerJs2 = _interopRequireDefault(_timeControlsTimeDividerJs);
+
+var _timeControlsRemainingTimeDisplayJs = _dereq_('./time-controls/remaining-time-display.js');
+
+var _timeControlsRemainingTimeDisplayJs2 = _interopRequireDefault(_timeControlsRemainingTimeDisplayJs);
+
+var _liveDisplayJs = _dereq_('./live-display.js');
+
+var _liveDisplayJs2 = _interopRequireDefault(_liveDisplayJs);
+
+var _progressControlProgressControlJs = _dereq_('./progress-control/progress-control.js');
+
+var _progressControlProgressControlJs2 = _interopRequireDefault(_progressControlProgressControlJs);
+
+var _fullscreenToggleJs = _dereq_('./fullscreen-toggle.js');
+
+var _fullscreenToggleJs2 = _interopRequireDefault(_fullscreenToggleJs);
+
+var _volumeControlVolumeControlJs = _dereq_('./volume-control/volume-control.js');
+
+var _volumeControlVolumeControlJs2 = _interopRequireDefault(_volumeControlVolumeControlJs);
+
+var _volumeMenuButtonJs = _dereq_('./volume-menu-button.js');
+
+var _volumeMenuButtonJs2 = _interopRequireDefault(_volumeMenuButtonJs);
+
+var _muteToggleJs = _dereq_('./mute-toggle.js');
+
+var _muteToggleJs2 = _interopRequireDefault(_muteToggleJs);
+
+var _textTrackControlsChaptersButtonJs = _dereq_('./text-track-controls/chapters-button.js');
+
+var _textTrackControlsChaptersButtonJs2 = _interopRequireDefault(_textTrackControlsChaptersButtonJs);
+
+var _textTrackControlsDescriptionsButtonJs = _dereq_('./text-track-controls/descriptions-button.js');
+
+var _textTrackControlsDescriptionsButtonJs2 = _interopRequireDefault(_textTrackControlsDescriptionsButtonJs);
+
+var _textTrackControlsSubtitlesButtonJs = _dereq_('./text-track-controls/subtitles-button.js');
+
+var _textTrackControlsSubtitlesButtonJs2 = _interopRequireDefault(_textTrackControlsSubtitlesButtonJs);
+
+var _textTrackControlsCaptionsButtonJs = _dereq_('./text-track-controls/captions-button.js');
+
+var _textTrackControlsCaptionsButtonJs2 = _interopRequireDefault(_textTrackControlsCaptionsButtonJs);
+
+var _audioTrackControlsAudioTrackButtonJs = _dereq_('./audio-track-controls/audio-track-button.js');
+
+var _audioTrackControlsAudioTrackButtonJs2 = _interopRequireDefault(_audioTrackControlsAudioTrackButtonJs);
+
+var _playbackRateMenuPlaybackRateMenuButtonJs = _dereq_('./playback-rate-menu/playback-rate-menu-button.js');
+
+var _playbackRateMenuPlaybackRateMenuButtonJs2 = _interopRequireDefault(_playbackRateMenuPlaybackRateMenuButtonJs);
+
+var _spacerControlsCustomControlSpacerJs = _dereq_('./spacer-controls/custom-control-spacer.js');
+
+var _spacerControlsCustomControlSpacerJs2 = _interopRequireDefault(_spacerControlsCustomControlSpacerJs);
+
+/**
+ * Container of main controls
+ *
+ * @extends Component
+ * @class ControlBar
+ */
+
+var ControlBar = (function (_Component) {
+ _inherits(ControlBar, _Component);
+
+ function ControlBar() {
+ _classCallCheck(this, ControlBar);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ ControlBar.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-control-bar',
+ dir: 'ltr'
+ }, {
+ 'role': 'group' // The control bar is a group, so it can contain menuitems
+ });
+ };
+
+ return ControlBar;
+})(_componentJs2['default']);
+
+ControlBar.prototype.options_ = {
+ children: ['playToggle', 'volumeMenuButton', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subtitlesButton', 'captionsButton', 'audioTrackButton', 'fullscreenToggle']
+};
+
+_componentJs2['default'].registerComponent('ControlBar', ControlBar);
+exports['default'] = ControlBar;
+module.exports = exports['default'];
+
+},{"../component.js":71,"./audio-track-controls/audio-track-button.js":72,"./fullscreen-toggle.js":75,"./live-display.js":76,"./mute-toggle.js":77,"./play-toggle.js":78,"./playback-rate-menu/playback-rate-menu-button.js":79,"./progress-control/progress-control.js":84,"./spacer-controls/custom-control-spacer.js":87,"./text-track-controls/captions-button.js":90,"./text-track-controls/chapters-button.js":91,"./text-track-controls/descriptions-button.js":93,"./text-track-controls/subtitles-button.js":95,"./time-controls/current-time-display.js":98,"./time-controls/duration-display.js":99,"./time-controls/remaining-time-display.js":100,"./time-controls/time-divider.js":101,"./volume-control/volume-control.js":104,"./volume-menu-button.js":106}],75:[function(_dereq_,module,exports){
+/**
+ * @file fullscreen-toggle.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _buttonJs = _dereq_('../button.js');
+
+var _buttonJs2 = _interopRequireDefault(_buttonJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * Toggle fullscreen video
+ *
+ * @extends Button
+ * @class FullscreenToggle
+ */
+
+var FullscreenToggle = (function (_Button) {
+ _inherits(FullscreenToggle, _Button);
+
+ function FullscreenToggle() {
+ _classCallCheck(this, FullscreenToggle);
+
+ _Button.apply(this, arguments);
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Handles click for full screen
+ *
+ * @method handleClick
+ */
+
+ FullscreenToggle.prototype.handleClick = function handleClick() {
+ if (!this.player_.isFullscreen()) {
+ this.player_.requestFullscreen();
+ this.controlText('Non-Fullscreen');
+ } else {
+ this.player_.exitFullscreen();
+ this.controlText('Fullscreen');
+ }
+ };
+
+ return FullscreenToggle;
+})(_buttonJs2['default']);
+
+FullscreenToggle.prototype.controlText_ = 'Fullscreen';
+
+_componentJs2['default'].registerComponent('FullscreenToggle', FullscreenToggle);
+exports['default'] = FullscreenToggle;
+module.exports = exports['default'];
+
+},{"../button.js":68,"../component.js":71}],76:[function(_dereq_,module,exports){
+/**
+ * @file live-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+/**
+ * Displays the live indicator
+ * TODO - Future make it click to snap to live
+ *
+ * @extends Component
+ * @class LiveDisplay
+ */
+
+var LiveDisplay = (function (_Component) {
+ _inherits(LiveDisplay, _Component);
+
+ function LiveDisplay(player, options) {
+ _classCallCheck(this, LiveDisplay);
+
+ _Component.call(this, player, options);
+
+ this.updateShowing();
+ this.on(this.player(), 'durationchange', this.updateShowing);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ LiveDisplay.prototype.createEl = function createEl() {
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-live-control vjs-control'
+ });
+
+ this.contentEl_ = Dom.createEl('div', {
+ className: 'vjs-live-display',
+ innerHTML: '
' + this.localize('Stream Type') + '' + this.localize('LIVE')
+ }, {
+ 'aria-live': 'off'
+ });
+
+ el.appendChild(this.contentEl_);
+ return el;
+ };
+
+ LiveDisplay.prototype.updateShowing = function updateShowing() {
+ if (this.player().duration() === Infinity) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ };
+
+ return LiveDisplay;
+})(_component2['default']);
+
+_component2['default'].registerComponent('LiveDisplay', LiveDisplay);
+exports['default'] = LiveDisplay;
+module.exports = exports['default'];
+
+},{"../component":71,"../utils/dom.js":146}],77:[function(_dereq_,module,exports){
+/**
+ * @file mute-toggle.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _button = _dereq_('../button');
+
+var _button2 = _interopRequireDefault(_button);
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+/**
+ * A button component for muting the audio
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Button
+ * @class MuteToggle
+ */
+
+var MuteToggle = (function (_Button) {
+ _inherits(MuteToggle, _Button);
+
+ function MuteToggle(player, options) {
+ _classCallCheck(this, MuteToggle);
+
+ _Button.call(this, player, options);
+
+ this.on(player, 'volumechange', this.update);
+
+ // hide mute toggle if the current tech doesn't support volume control
+ if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
+ this.addClass('vjs-hidden');
+ }
+
+ this.on(player, 'loadstart', function () {
+ this.update(); // We need to update the button to account for a default muted state.
+
+ if (player.tech_['featuresVolumeControl'] === false) {
+ this.addClass('vjs-hidden');
+ } else {
+ this.removeClass('vjs-hidden');
+ }
+ });
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Handle click on mute
+ *
+ * @method handleClick
+ */
+
+ MuteToggle.prototype.handleClick = function handleClick() {
+ this.player_.muted(this.player_.muted() ? false : true);
+ };
+
+ /**
+ * Update volume
+ *
+ * @method update
+ */
+
+ MuteToggle.prototype.update = function update() {
+ var vol = this.player_.volume(),
+ level = 3;
+
+ if (vol === 0 || this.player_.muted()) {
+ level = 0;
+ } else if (vol < 0.33) {
+ level = 1;
+ } else if (vol < 0.67) {
+ level = 2;
+ }
+
+ // Don't rewrite the button text if the actual text doesn't change.
+ // This causes unnecessary and confusing information for screen reader users.
+ // This check is needed because this function gets called every time the volume level is changed.
+ var toMute = this.player_.muted() ? 'Unmute' : 'Mute';
+ if (this.controlText() !== toMute) {
+ this.controlText(toMute);
+ }
+
+ /* TODO improve muted icon classes */
+ for (var i = 0; i < 4; i++) {
+ Dom.removeElClass(this.el_, 'vjs-vol-' + i);
+ }
+ Dom.addElClass(this.el_, 'vjs-vol-' + level);
+ };
+
+ return MuteToggle;
+})(_button2['default']);
+
+MuteToggle.prototype.controlText_ = 'Mute';
+
+_component2['default'].registerComponent('MuteToggle', MuteToggle);
+exports['default'] = MuteToggle;
+module.exports = exports['default'];
+
+},{"../button":68,"../component":71,"../utils/dom.js":146}],78:[function(_dereq_,module,exports){
+/**
+ * @file play-toggle.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _buttonJs = _dereq_('../button.js');
+
+var _buttonJs2 = _interopRequireDefault(_buttonJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * Button to toggle between play and pause
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Button
+ * @class PlayToggle
+ */
+
+var PlayToggle = (function (_Button) {
+ _inherits(PlayToggle, _Button);
+
+ function PlayToggle(player, options) {
+ _classCallCheck(this, PlayToggle);
+
+ _Button.call(this, player, options);
+
+ this.on(player, 'play', this.handlePlay);
+ this.on(player, 'pause', this.handlePause);
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Handle click to toggle between play and pause
+ *
+ * @method handleClick
+ */
+
+ PlayToggle.prototype.handleClick = function handleClick() {
+ if (this.player_.paused()) {
+ this.player_.play();
+ } else {
+ this.player_.pause();
+ }
+ };
+
+ /**
+ * Add the vjs-playing class to the element so it can change appearance
+ *
+ * @method handlePlay
+ */
+
+ PlayToggle.prototype.handlePlay = function handlePlay() {
+ this.removeClass('vjs-paused');
+ this.addClass('vjs-playing');
+ this.controlText('Pause'); // change the button text to "Pause"
+ };
+
+ /**
+ * Add the vjs-paused class to the element so it can change appearance
+ *
+ * @method handlePause
+ */
+
+ PlayToggle.prototype.handlePause = function handlePause() {
+ this.removeClass('vjs-playing');
+ this.addClass('vjs-paused');
+ this.controlText('Play'); // change the button text to "Play"
+ };
+
+ return PlayToggle;
+})(_buttonJs2['default']);
+
+PlayToggle.prototype.controlText_ = 'Play';
+
+_componentJs2['default'].registerComponent('PlayToggle', PlayToggle);
+exports['default'] = PlayToggle;
+module.exports = exports['default'];
+
+},{"../button.js":68,"../component.js":71}],79:[function(_dereq_,module,exports){
+/**
+ * @file playback-rate-menu-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuButtonJs = _dereq_('../../menu/menu-button.js');
+
+var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);
+
+var _menuMenuJs = _dereq_('../../menu/menu.js');
+
+var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);
+
+var _playbackRateMenuItemJs = _dereq_('./playback-rate-menu-item.js');
+
+var _playbackRateMenuItemJs2 = _interopRequireDefault(_playbackRateMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+/**
+ * The component for controlling the playback rate
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuButton
+ * @class PlaybackRateMenuButton
+ */
+
+var PlaybackRateMenuButton = (function (_MenuButton) {
+ _inherits(PlaybackRateMenuButton, _MenuButton);
+
+ function PlaybackRateMenuButton(player, options) {
+ _classCallCheck(this, PlaybackRateMenuButton);
+
+ _MenuButton.call(this, player, options);
+
+ this.updateVisibility();
+ this.updateLabel();
+
+ this.on(player, 'loadstart', this.updateVisibility);
+ this.on(player, 'ratechange', this.updateLabel);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ PlaybackRateMenuButton.prototype.createEl = function createEl() {
+ var el = _MenuButton.prototype.createEl.call(this);
+
+ this.labelEl_ = Dom.createEl('div', {
+ className: 'vjs-playback-rate-value',
+ innerHTML: 1.0
+ });
+
+ el.appendChild(this.labelEl_);
+
+ return el;
+ };
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Create the playback rate menu
+ *
+ * @return {Menu} Menu object populated with items
+ * @method createMenu
+ */
+
+ PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
+ var menu = new _menuMenuJs2['default'](this.player());
+ var rates = this.playbackRates();
+
+ if (rates) {
+ for (var i = rates.length - 1; i >= 0; i--) {
+ menu.addChild(new _playbackRateMenuItemJs2['default'](this.player(), { 'rate': rates[i] + 'x' }));
+ }
+ }
+
+ return menu;
+ };
+
+ /**
+ * Updates ARIA accessibility attributes
+ *
+ * @method updateARIAAttributes
+ */
+
+ PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
+ // Current playback rate
+ this.el().setAttribute('aria-valuenow', this.player().playbackRate());
+ };
+
+ /**
+ * Handle menu item click
+ *
+ * @method handleClick
+ */
+
+ PlaybackRateMenuButton.prototype.handleClick = function handleClick() {
+ // select next rate option
+ var currentRate = this.player().playbackRate();
+ var rates = this.playbackRates();
+
+ // this will select first one if the last one currently selected
+ var newRate = rates[0];
+ for (var i = 0; i < rates.length; i++) {
+ if (rates[i] > currentRate) {
+ newRate = rates[i];
+ break;
+ }
+ }
+ this.player().playbackRate(newRate);
+ };
+
+ /**
+ * Get possible playback rates
+ *
+ * @return {Array} Possible playback rates
+ * @method playbackRates
+ */
+
+ PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
+ return this.options_['playbackRates'] || this.options_.playerOptions && this.options_.playerOptions['playbackRates'];
+ };
+
+ /**
+ * Get whether playback rates is supported by the tech
+ * and an array of playback rates exists
+ *
+ * @return {Boolean} Whether changing playback rate is supported
+ * @method playbackRateSupported
+ */
+
+ PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
+ return this.player().tech_ && this.player().tech_['featuresPlaybackRate'] && this.playbackRates() && this.playbackRates().length > 0;
+ };
+
+ /**
+ * Hide playback rate controls when they're no playback rate options to select
+ *
+ * @method updateVisibility
+ */
+
+ PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility() {
+ if (this.playbackRateSupported()) {
+ this.removeClass('vjs-hidden');
+ } else {
+ this.addClass('vjs-hidden');
+ }
+ };
+
+ /**
+ * Update button label when rate changed
+ *
+ * @method updateLabel
+ */
+
+ PlaybackRateMenuButton.prototype.updateLabel = function updateLabel() {
+ if (this.playbackRateSupported()) {
+ this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
+ }
+ };
+
+ return PlaybackRateMenuButton;
+})(_menuMenuButtonJs2['default']);
+
+PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
+
+_componentJs2['default'].registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
+exports['default'] = PlaybackRateMenuButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu-button.js":113,"../../menu/menu.js":115,"../../utils/dom.js":146,"./playback-rate-menu-item.js":80}],80:[function(_dereq_,module,exports){
+/**
+ * @file playback-rate-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');
+
+var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * The specific menu item type for selecting a playback rate
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuItem
+ * @class PlaybackRateMenuItem
+ */
+
+var PlaybackRateMenuItem = (function (_MenuItem) {
+ _inherits(PlaybackRateMenuItem, _MenuItem);
+
+ function PlaybackRateMenuItem(player, options) {
+ _classCallCheck(this, PlaybackRateMenuItem);
+
+ var label = options['rate'];
+ var rate = parseFloat(label, 10);
+
+ // Modify options for parent MenuItem class's init.
+ options['label'] = label;
+ options['selected'] = rate === 1;
+ _MenuItem.call(this, player, options);
+
+ this.label = label;
+ this.rate = rate;
+
+ this.on(player, 'ratechange', this.update);
+ }
+
+ /**
+ * Handle click on menu item
+ *
+ * @method handleClick
+ */
+
+ PlaybackRateMenuItem.prototype.handleClick = function handleClick() {
+ _MenuItem.prototype.handleClick.call(this);
+ this.player().playbackRate(this.rate);
+ };
+
+ /**
+ * Update playback rate with selected rate
+ *
+ * @method update
+ */
+
+ PlaybackRateMenuItem.prototype.update = function update() {
+ this.selected(this.player().playbackRate() === this.rate);
+ };
+
+ return PlaybackRateMenuItem;
+})(_menuMenuItemJs2['default']);
+
+PlaybackRateMenuItem.prototype.contentElType = 'button';
+
+_componentJs2['default'].registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
+exports['default'] = PlaybackRateMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu-item.js":114}],81:[function(_dereq_,module,exports){
+/**
+ * @file load-progress-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+/**
+ * Shows load progress
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class LoadProgressBar
+ */
+
+var LoadProgressBar = (function (_Component) {
+ _inherits(LoadProgressBar, _Component);
+
+ function LoadProgressBar(player, options) {
+ _classCallCheck(this, LoadProgressBar);
+
+ _Component.call(this, player, options);
+ this.on(player, 'progress', this.update);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ LoadProgressBar.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-load-progress',
+ innerHTML: '
' + this.localize('Loaded') + ': 0%'
+ });
+ };
+
+ /**
+ * Update progress bar
+ *
+ * @method update
+ */
+
+ LoadProgressBar.prototype.update = function update() {
+ var buffered = this.player_.buffered();
+ var duration = this.player_.duration();
+ var bufferedEnd = this.player_.bufferedEnd();
+ var children = this.el_.children;
+
+ // get the percent width of a time compared to the total end
+ var percentify = function percentify(time, end) {
+ var percent = time / end || 0; // no NaN
+ return (percent >= 1 ? 1 : percent) * 100 + '%';
+ };
+
+ // update the width of the progress bar
+ this.el_.style.width = percentify(bufferedEnd, duration);
+
+ // add child elements to represent the individual buffered time ranges
+ for (var i = 0; i < buffered.length; i++) {
+ var start = buffered.start(i);
+ var end = buffered.end(i);
+ var part = children[i];
+
+ if (!part) {
+ part = this.el_.appendChild(Dom.createEl());
+ }
+
+ // set the percent based on the width of the progress bar (bufferedEnd)
+ part.style.left = percentify(start, bufferedEnd);
+ part.style.width = percentify(end - start, bufferedEnd);
+ }
+
+ // remove unused buffered range elements
+ for (var i = children.length; i > buffered.length; i--) {
+ this.el_.removeChild(children[i - 1]);
+ }
+ };
+
+ return LoadProgressBar;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('LoadProgressBar', LoadProgressBar);
+exports['default'] = LoadProgressBar;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146}],82:[function(_dereq_,module,exports){
+/**
+ * @file mouse-time-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+var _lodashCompatFunctionThrottle = _dereq_('lodash-compat/function/throttle');
+
+var _lodashCompatFunctionThrottle2 = _interopRequireDefault(_lodashCompatFunctionThrottle);
+
+/**
+ * The Mouse Time Display component shows the time you will seek to
+ * when hovering over the progress bar
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class MouseTimeDisplay
+ */
+
+var MouseTimeDisplay = (function (_Component) {
+ _inherits(MouseTimeDisplay, _Component);
+
+ function MouseTimeDisplay(player, options) {
+ var _this = this;
+
+ _classCallCheck(this, MouseTimeDisplay);
+
+ _Component.call(this, player, options);
+
+ if (options.playerOptions && options.playerOptions.controlBar && options.playerOptions.controlBar.progressControl && options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
+ this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
+ }
+
+ if (this.keepTooltipsInside) {
+ this.tooltip = Dom.createEl('div', { className: 'vjs-time-tooltip' });
+ this.el().appendChild(this.tooltip);
+ this.addClass('vjs-keep-tooltips-inside');
+ }
+
+ this.update(0, 0);
+
+ player.on('ready', function () {
+ _this.on(player.controlBar.progressControl.el(), 'mousemove', _lodashCompatFunctionThrottle2['default'](Fn.bind(_this, _this.handleMouseMove), 25));
+ });
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ MouseTimeDisplay.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-mouse-display'
+ });
+ };
+
+ MouseTimeDisplay.prototype.handleMouseMove = function handleMouseMove(event) {
+ var duration = this.player_.duration();
+ var newTime = this.calculateDistance(event) * duration;
+ var position = event.pageX - Dom.findElPosition(this.el().parentNode).left;
+
+ this.update(newTime, position);
+ };
+
+ MouseTimeDisplay.prototype.update = function update(newTime, position) {
+ var time = _utilsFormatTimeJs2['default'](newTime, this.player_.duration());
+
+ this.el().style.left = position + 'px';
+ this.el().setAttribute('data-current-time', time);
+
+ if (this.keepTooltipsInside) {
+ var clampedPosition = this.clampPosition_(position);
+ var difference = position - clampedPosition + 1;
+ var tooltipWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.tooltip).width);
+ var tooltipWidthHalf = tooltipWidth / 2;
+
+ this.tooltip.innerHTML = time;
+ this.tooltip.style.right = '-' + (tooltipWidthHalf - difference) + 'px';
+ }
+ };
+
+ MouseTimeDisplay.prototype.calculateDistance = function calculateDistance(event) {
+ return Dom.getPointerPosition(this.el().parentNode, event).x;
+ };
+
+ /**
+ * This takes in a horizontal position for the bar and returns a clamped position.
+ * Clamped position means that it will keep the position greater than half the width
+ * of the tooltip and smaller than the player width minus half the width o the tooltip.
+ * It will only clamp the position if `keepTooltipsInside` option is set.
+ *
+ * @param {Number} position the position the bar wants to be
+ * @return {Number} newPosition the (potentially) clamped position
+ * @method clampPosition_
+ */
+
+ MouseTimeDisplay.prototype.clampPosition_ = function clampPosition_(position) {
+ if (!this.keepTooltipsInside) {
+ return position;
+ }
+
+ var playerWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.player().el()).width);
+ var tooltipWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.tooltip).width);
+ var tooltipWidthHalf = tooltipWidth / 2;
+ var actualPosition = position;
+
+ if (position < tooltipWidthHalf) {
+ actualPosition = Math.ceil(tooltipWidthHalf);
+ } else if (position > playerWidth - tooltipWidthHalf) {
+ actualPosition = Math.floor(playerWidth - tooltipWidthHalf);
+ }
+
+ return actualPosition;
+ };
+
+ return MouseTimeDisplay;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('MouseTimeDisplay', MouseTimeDisplay);
+exports['default'] = MouseTimeDisplay;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/fn.js":148,"../../utils/format-time.js":149,"global/window":10,"lodash-compat/function/throttle":16}],83:[function(_dereq_,module,exports){
+/**
+ * @file play-progress-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+/**
+ * Shows play progress
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class PlayProgressBar
+ */
+
+var PlayProgressBar = (function (_Component) {
+ _inherits(PlayProgressBar, _Component);
+
+ function PlayProgressBar(player, options) {
+ _classCallCheck(this, PlayProgressBar);
+
+ _Component.call(this, player, options);
+ this.updateDataAttr();
+ this.on(player, 'timeupdate', this.updateDataAttr);
+ player.ready(Fn.bind(this, this.updateDataAttr));
+
+ if (options.playerOptions && options.playerOptions.controlBar && options.playerOptions.controlBar.progressControl && options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
+ this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
+ }
+
+ if (this.keepTooltipsInside) {
+ this.addClass('vjs-keep-tooltips-inside');
+ }
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ PlayProgressBar.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-play-progress vjs-slider-bar',
+ innerHTML: '
' + this.localize('Progress') + ': 0%'
+ });
+ };
+
+ PlayProgressBar.prototype.updateDataAttr = function updateDataAttr() {
+ var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
+ this.el_.setAttribute('data-current-time', _utilsFormatTimeJs2['default'](time, this.player_.duration()));
+ };
+
+ return PlayProgressBar;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('PlayProgressBar', PlayProgressBar);
+exports['default'] = PlayProgressBar;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/fn.js":148,"../../utils/format-time.js":149}],84:[function(_dereq_,module,exports){
+/**
+ * @file progress-control.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _seekBarJs = _dereq_('./seek-bar.js');
+
+var _seekBarJs2 = _interopRequireDefault(_seekBarJs);
+
+var _mouseTimeDisplayJs = _dereq_('./mouse-time-display.js');
+
+var _mouseTimeDisplayJs2 = _interopRequireDefault(_mouseTimeDisplayJs);
+
+/**
+ * The Progress Control component contains the seek bar, load progress,
+ * and play progress
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class ProgressControl
+ */
+
+var ProgressControl = (function (_Component) {
+ _inherits(ProgressControl, _Component);
+
+ function ProgressControl() {
+ _classCallCheck(this, ProgressControl);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ ProgressControl.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-progress-control vjs-control'
+ });
+ };
+
+ return ProgressControl;
+})(_componentJs2['default']);
+
+ProgressControl.prototype.options_ = {
+ children: ['seekBar']
+};
+
+_componentJs2['default'].registerComponent('ProgressControl', ProgressControl);
+exports['default'] = ProgressControl;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./mouse-time-display.js":82,"./seek-bar.js":85}],85:[function(_dereq_,module,exports){
+/**
+ * @file seek-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _sliderSliderJs = _dereq_('../../slider/slider.js');
+
+var _sliderSliderJs2 = _interopRequireDefault(_sliderSliderJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _loadProgressBarJs = _dereq_('./load-progress-bar.js');
+
+var _loadProgressBarJs2 = _interopRequireDefault(_loadProgressBarJs);
+
+var _playProgressBarJs = _dereq_('./play-progress-bar.js');
+
+var _playProgressBarJs2 = _interopRequireDefault(_playProgressBarJs);
+
+var _tooltipProgressBarJs = _dereq_('./tooltip-progress-bar.js');
+
+var _tooltipProgressBarJs2 = _interopRequireDefault(_tooltipProgressBarJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+/**
+ * Seek Bar and holder for the progress bars
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Slider
+ * @class SeekBar
+ */
+
+var SeekBar = (function (_Slider) {
+ _inherits(SeekBar, _Slider);
+
+ function SeekBar(player, options) {
+ _classCallCheck(this, SeekBar);
+
+ _Slider.call(this, player, options);
+ this.on(player, 'timeupdate', this.updateProgress);
+ this.on(player, 'ended', this.updateProgress);
+ player.ready(Fn.bind(this, this.updateProgress));
+
+ if (options.playerOptions && options.playerOptions.controlBar && options.playerOptions.controlBar.progressControl && options.playerOptions.controlBar.progressControl.keepTooltipsInside) {
+ this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside;
+ }
+
+ if (this.keepTooltipsInside) {
+ this.tooltipProgressBar = this.addChild('TooltipProgressBar');
+ }
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ SeekBar.prototype.createEl = function createEl() {
+ return _Slider.prototype.createEl.call(this, 'div', {
+ className: 'vjs-progress-holder'
+ }, {
+ 'aria-label': 'progress bar'
+ });
+ };
+
+ /**
+ * Update ARIA accessibility attributes
+ *
+ * @method updateARIAAttributes
+ */
+
+ SeekBar.prototype.updateProgress = function updateProgress() {
+ this.updateAriaAttributes(this.el_);
+
+ if (this.keepTooltipsInside) {
+ this.updateAriaAttributes(this.tooltipProgressBar.el_);
+ this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width;
+
+ var playerWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.player().el()).width);
+ var tooltipWidth = parseFloat(_globalWindow2['default'].getComputedStyle(this.tooltipProgressBar.tooltip).width);
+ var tooltipStyle = this.tooltipProgressBar.el().style;
+ tooltipStyle.maxWidth = Math.floor(playerWidth - tooltipWidth / 2) + 'px';
+ tooltipStyle.minWidth = Math.ceil(tooltipWidth / 2) + 'px';
+ tooltipStyle.right = '-' + tooltipWidth / 2 + 'px';
+ }
+ };
+
+ SeekBar.prototype.updateAriaAttributes = function updateAriaAttributes(el) {
+ // Allows for smooth scrubbing, when player can't keep up.
+ var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
+ el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete)
+ el.setAttribute('aria-valuetext', _utilsFormatTimeJs2['default'](time, this.player_.duration())); // human readable value of progress bar (time complete)
+ };
+
+ /**
+ * Get percentage of video played
+ *
+ * @return {Number} Percentage played
+ * @method getPercent
+ */
+
+ SeekBar.prototype.getPercent = function getPercent() {
+ var percent = this.player_.currentTime() / this.player_.duration();
+ return percent >= 1 ? 1 : percent;
+ };
+
+ /**
+ * Handle mouse down on seek bar
+ *
+ * @method handleMouseDown
+ */
+
+ SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
+ _Slider.prototype.handleMouseDown.call(this, event);
+
+ this.player_.scrubbing(true);
+
+ this.videoWasPlaying = !this.player_.paused();
+ this.player_.pause();
+ };
+
+ /**
+ * Handle mouse move on seek bar
+ *
+ * @method handleMouseMove
+ */
+
+ SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
+ var newTime = this.calculateDistance(event) * this.player_.duration();
+
+ // Don't let video end while scrubbing.
+ if (newTime === this.player_.duration()) {
+ newTime = newTime - 0.1;
+ }
+
+ // Set new time (tell player to seek to new time)
+ this.player_.currentTime(newTime);
+ };
+
+ /**
+ * Handle mouse up on seek bar
+ *
+ * @method handleMouseUp
+ */
+
+ SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
+ _Slider.prototype.handleMouseUp.call(this, event);
+
+ this.player_.scrubbing(false);
+ if (this.videoWasPlaying) {
+ this.player_.play();
+ }
+ };
+
+ /**
+ * Move more quickly fast forward for keyboard-only users
+ *
+ * @method stepForward
+ */
+
+ SeekBar.prototype.stepForward = function stepForward() {
+ this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
+ };
+
+ /**
+ * Move more quickly rewind for keyboard-only users
+ *
+ * @method stepBack
+ */
+
+ SeekBar.prototype.stepBack = function stepBack() {
+ this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
+ };
+
+ return SeekBar;
+})(_sliderSliderJs2['default']);
+
+SeekBar.prototype.options_ = {
+ children: ['loadProgressBar', 'mouseTimeDisplay', 'playProgressBar'],
+ 'barName': 'playProgressBar'
+};
+
+SeekBar.prototype.playerEvent = 'timeupdate';
+
+_componentJs2['default'].registerComponent('SeekBar', SeekBar);
+exports['default'] = SeekBar;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../slider/slider.js":123,"../../utils/fn.js":148,"../../utils/format-time.js":149,"./load-progress-bar.js":81,"./play-progress-bar.js":83,"./tooltip-progress-bar.js":86,"global/window":10,"object.assign":57}],86:[function(_dereq_,module,exports){
+/**
+ * @file play-progress-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+/**
+ * Shows play progress
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class PlayProgressBar
+ */
+
+var TooltipProgressBar = (function (_Component) {
+ _inherits(TooltipProgressBar, _Component);
+
+ function TooltipProgressBar(player, options) {
+ _classCallCheck(this, TooltipProgressBar);
+
+ _Component.call(this, player, options);
+ this.updateDataAttr();
+ this.on(player, 'timeupdate', this.updateDataAttr);
+ player.ready(Fn.bind(this, this.updateDataAttr));
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ TooltipProgressBar.prototype.createEl = function createEl() {
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-tooltip-progress-bar vjs-slider-bar',
+ innerHTML: '
\n
' + this.localize('Progress') + ': 0%'
+ });
+
+ this.tooltip = el.querySelector('.vjs-time-tooltip');
+
+ return el;
+ };
+
+ TooltipProgressBar.prototype.updateDataAttr = function updateDataAttr() {
+ var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
+ var formattedTime = _utilsFormatTimeJs2['default'](time, this.player_.duration());
+ this.el_.setAttribute('data-current-time', formattedTime);
+ this.tooltip.innerHTML = formattedTime;
+ };
+
+ return TooltipProgressBar;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('TooltipProgressBar', TooltipProgressBar);
+exports['default'] = TooltipProgressBar;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/fn.js":148,"../../utils/format-time.js":149}],87:[function(_dereq_,module,exports){
+/**
+ * @file custom-control-spacer.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _spacerJs = _dereq_('./spacer.js');
+
+var _spacerJs2 = _interopRequireDefault(_spacerJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * Spacer specifically meant to be used as an insertion point for new plugins, etc.
+ *
+ * @extends Spacer
+ * @class CustomControlSpacer
+ */
+
+var CustomControlSpacer = (function (_Spacer) {
+ _inherits(CustomControlSpacer, _Spacer);
+
+ function CustomControlSpacer() {
+ _classCallCheck(this, CustomControlSpacer);
+
+ _Spacer.apply(this, arguments);
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ CustomControlSpacer.prototype.createEl = function createEl() {
+ var el = _Spacer.prototype.createEl.call(this, {
+ className: this.buildCSSClass()
+ });
+
+ // No-flex/table-cell mode requires there be some content
+ // in the cell to fill the remaining space of the table.
+ el.innerHTML = ' ';
+ return el;
+ };
+
+ return CustomControlSpacer;
+})(_spacerJs2['default']);
+
+_componentJs2['default'].registerComponent('CustomControlSpacer', CustomControlSpacer);
+exports['default'] = CustomControlSpacer;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./spacer.js":88}],88:[function(_dereq_,module,exports){
+/**
+ * @file spacer.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * Just an empty spacer element that can be used as an append point for plugins, etc.
+ * Also can be used to create space between elements when necessary.
+ *
+ * @extends Component
+ * @class Spacer
+ */
+
+var Spacer = (function (_Component) {
+ _inherits(Spacer, _Component);
+
+ function Spacer() {
+ _classCallCheck(this, Spacer);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ Spacer.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Spacer.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: this.buildCSSClass()
+ });
+ };
+
+ return Spacer;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('Spacer', Spacer);
+
+exports['default'] = Spacer;
+module.exports = exports['default'];
+
+},{"../../component.js":71}],89:[function(_dereq_,module,exports){
+/**
+ * @file caption-settings-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');
+
+var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * The menu item for caption track settings menu
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends TextTrackMenuItem
+ * @class CaptionSettingsMenuItem
+ */
+
+var CaptionSettingsMenuItem = (function (_TextTrackMenuItem) {
+ _inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);
+
+ function CaptionSettingsMenuItem(player, options) {
+ _classCallCheck(this, CaptionSettingsMenuItem);
+
+ options['track'] = {
+ 'kind': options['kind'],
+ 'player': player,
+ 'label': options['kind'] + ' settings',
+ 'selectable': false,
+ 'default': false,
+ mode: 'disabled'
+ };
+
+ // CaptionSettingsMenuItem has no concept of 'selected'
+ options['selectable'] = false;
+
+ _TextTrackMenuItem.call(this, player, options);
+ this.addClass('vjs-texttrack-settings');
+ this.controlText(', opens ' + options['kind'] + ' settings dialog');
+ }
+
+ /**
+ * Handle click on menu item
+ *
+ * @method handleClick
+ */
+
+ CaptionSettingsMenuItem.prototype.handleClick = function handleClick() {
+ this.player().getChild('textTrackSettings').show();
+ this.player().getChild('textTrackSettings').el_.focus();
+ };
+
+ return CaptionSettingsMenuItem;
+})(_textTrackMenuItemJs2['default']);
+
+_componentJs2['default'].registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
+exports['default'] = CaptionSettingsMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./text-track-menu-item.js":97}],90:[function(_dereq_,module,exports){
+/**
+ * @file captions-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackButtonJs = _dereq_('./text-track-button.js');
+
+var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _captionSettingsMenuItemJs = _dereq_('./caption-settings-menu-item.js');
+
+var _captionSettingsMenuItemJs2 = _interopRequireDefault(_captionSettingsMenuItemJs);
+
+/**
+ * The button component for toggling and selecting captions
+ *
+ * @param {Object} player Player object
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends TextTrackButton
+ * @class CaptionsButton
+ */
+
+var CaptionsButton = (function (_TextTrackButton) {
+ _inherits(CaptionsButton, _TextTrackButton);
+
+ function CaptionsButton(player, options, ready) {
+ _classCallCheck(this, CaptionsButton);
+
+ _TextTrackButton.call(this, player, options, ready);
+ this.el_.setAttribute('aria-label', 'Captions Menu');
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Update caption menu items
+ *
+ * @method update
+ */
+
+ CaptionsButton.prototype.update = function update() {
+ var threshold = 2;
+ _TextTrackButton.prototype.update.call(this);
+
+ // if native, then threshold is 1 because no settings button
+ if (this.player().tech_ && this.player().tech_['featuresNativeTextTracks']) {
+ threshold = 1;
+ }
+
+ if (this.items && this.items.length > threshold) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ };
+
+ /**
+ * Create caption menu items
+ *
+ * @return {Array} Array of menu items
+ * @method createItems
+ */
+
+ CaptionsButton.prototype.createItems = function createItems() {
+ var items = [];
+
+ if (!(this.player().tech_ && this.player().tech_['featuresNativeTextTracks'])) {
+ items.push(new _captionSettingsMenuItemJs2['default'](this.player_, { 'kind': this.kind_ }));
+ }
+
+ return _TextTrackButton.prototype.createItems.call(this, items);
+ };
+
+ return CaptionsButton;
+})(_textTrackButtonJs2['default']);
+
+CaptionsButton.prototype.kind_ = 'captions';
+CaptionsButton.prototype.controlText_ = 'Captions';
+
+_componentJs2['default'].registerComponent('CaptionsButton', CaptionsButton);
+exports['default'] = CaptionsButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./caption-settings-menu-item.js":89,"./text-track-button.js":96}],91:[function(_dereq_,module,exports){
+/**
+ * @file chapters-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackButtonJs = _dereq_('./text-track-button.js');
+
+var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');
+
+var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);
+
+var _chaptersTrackMenuItemJs = _dereq_('./chapters-track-menu-item.js');
+
+var _chaptersTrackMenuItemJs2 = _interopRequireDefault(_chaptersTrackMenuItemJs);
+
+var _menuMenuJs = _dereq_('../../menu/menu.js');
+
+var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsToTitleCaseJs = _dereq_('../../utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+/**
+ * The button component for toggling and selecting chapters
+ * Chapters act much differently than other text tracks
+ * Cues are navigation vs. other tracks of alternative languages
+ *
+ * @param {Object} player Player object
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends TextTrackButton
+ * @class ChaptersButton
+ */
+
+var ChaptersButton = (function (_TextTrackButton) {
+ _inherits(ChaptersButton, _TextTrackButton);
+
+ function ChaptersButton(player, options, ready) {
+ _classCallCheck(this, ChaptersButton);
+
+ _TextTrackButton.call(this, player, options, ready);
+ this.el_.setAttribute('aria-label', 'Chapters Menu');
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Create a menu item for each text track
+ *
+ * @return {Array} Array of menu items
+ * @method createItems
+ */
+
+ ChaptersButton.prototype.createItems = function createItems() {
+ var items = [];
+
+ var tracks = this.player_.textTracks();
+
+ if (!tracks) {
+ return items;
+ }
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+ if (track['kind'] === this.kind_) {
+ items.push(new _textTrackMenuItemJs2['default'](this.player_, {
+ 'track': track
+ }));
+ }
+ }
+
+ return items;
+ };
+
+ /**
+ * Create menu from chapter buttons
+ *
+ * @return {Menu} Menu of chapter buttons
+ * @method createMenu
+ */
+
+ ChaptersButton.prototype.createMenu = function createMenu() {
+ var _this = this;
+
+ var tracks = this.player_.textTracks() || [];
+ var chaptersTrack = undefined;
+ var items = this.items || [];
+
+ for (var i = tracks.length - 1; i >= 0; i--) {
+
+ // We will always choose the last track as our chaptersTrack
+ var track = tracks[i];
+
+ if (track['kind'] === this.kind_) {
+ chaptersTrack = track;
+
+ break;
+ }
+ }
+
+ var menu = this.menu;
+ if (menu === undefined) {
+ menu = new _menuMenuJs2['default'](this.player_);
+ var title = Dom.createEl('li', {
+ className: 'vjs-menu-title',
+ innerHTML: _utilsToTitleCaseJs2['default'](this.kind_),
+ tabIndex: -1
+ });
+ menu.children_.unshift(title);
+ Dom.insertElFirst(title, menu.contentEl());
+ } else {
+ // We will empty out the menu children each time because we want a
+ // fresh new menu child list each time
+ items.forEach(function (item) {
+ return menu.removeChild(item);
+ });
+ // Empty out the ChaptersButton menu items because we no longer need them
+ items = [];
+ }
+
+ if (chaptersTrack && chaptersTrack.cues == null) {
+ chaptersTrack['mode'] = 'hidden';
+
+ var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(chaptersTrack);
+
+ if (remoteTextTrackEl) {
+ remoteTextTrackEl.addEventListener('load', function (event) {
+ return _this.update();
+ });
+ }
+ }
+
+ if (chaptersTrack && chaptersTrack.cues && chaptersTrack.cues.length > 0) {
+ var cues = chaptersTrack['cues'],
+ cue = undefined;
+
+ for (var i = 0, l = cues.length; i < l; i++) {
+ cue = cues[i];
+
+ var mi = new _chaptersTrackMenuItemJs2['default'](this.player_, {
+ 'track': chaptersTrack,
+ 'cue': cue
+ });
+
+ items.push(mi);
+
+ menu.addChild(mi);
+ }
+ }
+
+ if (items.length > 0) {
+ this.show();
+ }
+ // Assigning the value of items back to this.items for next iteration
+ this.items = items;
+ return menu;
+ };
+
+ return ChaptersButton;
+})(_textTrackButtonJs2['default']);
+
+ChaptersButton.prototype.kind_ = 'chapters';
+ChaptersButton.prototype.controlText_ = 'Chapters';
+
+_componentJs2['default'].registerComponent('ChaptersButton', ChaptersButton);
+exports['default'] = ChaptersButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu.js":115,"../../utils/dom.js":146,"../../utils/fn.js":148,"../../utils/to-title-case.js":155,"./chapters-track-menu-item.js":92,"./text-track-button.js":96,"./text-track-menu-item.js":97,"global/window":10}],92:[function(_dereq_,module,exports){
+/**
+ * @file chapters-track-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');
+
+var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+/**
+ * The chapter track menu item
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuItem
+ * @class ChaptersTrackMenuItem
+ */
+
+var ChaptersTrackMenuItem = (function (_MenuItem) {
+ _inherits(ChaptersTrackMenuItem, _MenuItem);
+
+ function ChaptersTrackMenuItem(player, options) {
+ _classCallCheck(this, ChaptersTrackMenuItem);
+
+ var track = options['track'];
+ var cue = options['cue'];
+ var currentTime = player.currentTime();
+
+ // Modify options for parent MenuItem class's init.
+ options['label'] = cue.text;
+ options['selected'] = cue['startTime'] <= currentTime && currentTime < cue['endTime'];
+ _MenuItem.call(this, player, options);
+
+ this.track = track;
+ this.cue = cue;
+ track.addEventListener('cuechange', Fn.bind(this, this.update));
+ }
+
+ /**
+ * Handle click on menu item
+ *
+ * @method handleClick
+ */
+
+ ChaptersTrackMenuItem.prototype.handleClick = function handleClick() {
+ _MenuItem.prototype.handleClick.call(this);
+ this.player_.currentTime(this.cue.startTime);
+ this.update(this.cue.startTime);
+ };
+
+ /**
+ * Update chapter menu item
+ *
+ * @method update
+ */
+
+ ChaptersTrackMenuItem.prototype.update = function update() {
+ var cue = this.cue;
+ var currentTime = this.player_.currentTime();
+
+ // vjs.log(currentTime, cue.startTime);
+ this.selected(cue['startTime'] <= currentTime && currentTime < cue['endTime']);
+ };
+
+ return ChaptersTrackMenuItem;
+})(_menuMenuItemJs2['default']);
+
+_componentJs2['default'].registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
+exports['default'] = ChaptersTrackMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu-item.js":114,"../../utils/fn.js":148}],93:[function(_dereq_,module,exports){
+/**
+ * @file descriptions-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackButtonJs = _dereq_('./text-track-button.js');
+
+var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+/**
+ * The button component for toggling and selecting descriptions
+ *
+ * @param {Object} player Player object
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends TextTrackButton
+ * @class DescriptionsButton
+ */
+
+var DescriptionsButton = (function (_TextTrackButton) {
+ _inherits(DescriptionsButton, _TextTrackButton);
+
+ function DescriptionsButton(player, options, ready) {
+ var _this = this;
+
+ _classCallCheck(this, DescriptionsButton);
+
+ _TextTrackButton.call(this, player, options, ready);
+ this.el_.setAttribute('aria-label', 'Descriptions Menu');
+
+ var tracks = player.textTracks();
+
+ if (tracks) {
+ (function () {
+ var changeHandler = Fn.bind(_this, _this.handleTracksChange);
+
+ tracks.addEventListener('change', changeHandler);
+ _this.on('dispose', function () {
+ tracks.removeEventListener('change', changeHandler);
+ });
+ })();
+ }
+ }
+
+ /**
+ * Handle text track change
+ *
+ * @method handleTracksChange
+ */
+
+ DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) {
+ var tracks = this.player().textTracks();
+ var disabled = false;
+
+ // Check whether a track of a different kind is showing
+ for (var i = 0, l = tracks.length; i < l; i++) {
+ var track = tracks[i];
+ if (track['kind'] !== this.kind_ && track['mode'] === 'showing') {
+ disabled = true;
+ break;
+ }
+ }
+
+ // If another track is showing, disable this menu button
+ if (disabled) {
+ this.disable();
+ } else {
+ this.enable();
+ }
+ };
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
+ };
+
+ return DescriptionsButton;
+})(_textTrackButtonJs2['default']);
+
+DescriptionsButton.prototype.kind_ = 'descriptions';
+DescriptionsButton.prototype.controlText_ = 'Descriptions';
+
+_componentJs2['default'].registerComponent('DescriptionsButton', DescriptionsButton);
+exports['default'] = DescriptionsButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/fn.js":148,"./text-track-button.js":96}],94:[function(_dereq_,module,exports){
+/**
+ * @file off-text-track-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');
+
+var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * A special menu item for turning of a specific type of text track
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends TextTrackMenuItem
+ * @class OffTextTrackMenuItem
+ */
+
+var OffTextTrackMenuItem = (function (_TextTrackMenuItem) {
+ _inherits(OffTextTrackMenuItem, _TextTrackMenuItem);
+
+ function OffTextTrackMenuItem(player, options) {
+ _classCallCheck(this, OffTextTrackMenuItem);
+
+ // Create pseudo track info
+ // Requires options['kind']
+ options['track'] = {
+ 'kind': options['kind'],
+ 'player': player,
+ 'label': options['kind'] + ' off',
+ 'default': false,
+ 'mode': 'disabled'
+ };
+
+ // MenuItem is selectable
+ options['selectable'] = true;
+
+ _TextTrackMenuItem.call(this, player, options);
+ this.selected(true);
+ }
+
+ /**
+ * Handle text track change
+ *
+ * @param {Object} event Event object
+ * @method handleTracksChange
+ */
+
+ OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
+ var tracks = this.player().textTracks();
+ var selected = true;
+
+ for (var i = 0, l = tracks.length; i < l; i++) {
+ var track = tracks[i];
+ if (track['kind'] === this.track['kind'] && track['mode'] === 'showing') {
+ selected = false;
+ break;
+ }
+ }
+
+ this.selected(selected);
+ };
+
+ return OffTextTrackMenuItem;
+})(_textTrackMenuItemJs2['default']);
+
+_componentJs2['default'].registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
+exports['default'] = OffTextTrackMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./text-track-menu-item.js":97}],95:[function(_dereq_,module,exports){
+/**
+ * @file subtitles-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackButtonJs = _dereq_('./text-track-button.js');
+
+var _textTrackButtonJs2 = _interopRequireDefault(_textTrackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * The button component for toggling and selecting subtitles
+ *
+ * @param {Object} player Player object
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends TextTrackButton
+ * @class SubtitlesButton
+ */
+
+var SubtitlesButton = (function (_TextTrackButton) {
+ _inherits(SubtitlesButton, _TextTrackButton);
+
+ function SubtitlesButton(player, options, ready) {
+ _classCallCheck(this, SubtitlesButton);
+
+ _TextTrackButton.call(this, player, options, ready);
+ this.el_.setAttribute('aria-label', 'Subtitles Menu');
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
+ };
+
+ return SubtitlesButton;
+})(_textTrackButtonJs2['default']);
+
+SubtitlesButton.prototype.kind_ = 'subtitles';
+SubtitlesButton.prototype.controlText_ = 'Subtitles';
+
+_componentJs2['default'].registerComponent('SubtitlesButton', SubtitlesButton);
+exports['default'] = SubtitlesButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./text-track-button.js":96}],96:[function(_dereq_,module,exports){
+/**
+ * @file text-track-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackButtonJs = _dereq_('../track-button.js');
+
+var _trackButtonJs2 = _interopRequireDefault(_trackButtonJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _textTrackMenuItemJs = _dereq_('./text-track-menu-item.js');
+
+var _textTrackMenuItemJs2 = _interopRequireDefault(_textTrackMenuItemJs);
+
+var _offTextTrackMenuItemJs = _dereq_('./off-text-track-menu-item.js');
+
+var _offTextTrackMenuItemJs2 = _interopRequireDefault(_offTextTrackMenuItemJs);
+
+/**
+ * The base class for buttons that toggle specific text track types (e.g. subtitles)
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuButton
+ * @class TextTrackButton
+ */
+
+var TextTrackButton = (function (_TrackButton) {
+ _inherits(TextTrackButton, _TrackButton);
+
+ function TextTrackButton(player) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, TextTrackButton);
+
+ options.tracks = player.textTracks();
+
+ _TrackButton.call(this, player, options);
+ }
+
+ /**
+ * Create a menu item for each text track
+ *
+ * @return {Array} Array of menu items
+ * @method createItems
+ */
+
+ TextTrackButton.prototype.createItems = function createItems() {
+ var items = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ // Add an OFF menu item to turn all tracks off
+ items.push(new _offTextTrackMenuItemJs2['default'](this.player_, { 'kind': this.kind_ }));
+
+ var tracks = this.player_.textTracks();
+
+ if (!tracks) {
+ return items;
+ }
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+
+ // only add tracks that are of the appropriate kind and have a label
+ if (track['kind'] === this.kind_) {
+ items.push(new _textTrackMenuItemJs2['default'](this.player_, {
+ // MenuItem is selectable
+ 'selectable': true,
+ 'track': track
+ }));
+ }
+ }
+
+ return items;
+ };
+
+ return TextTrackButton;
+})(_trackButtonJs2['default']);
+
+_componentJs2['default'].registerComponent('TextTrackButton', TextTrackButton);
+exports['default'] = TextTrackButton;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/fn.js":148,"../track-button.js":102,"./off-text-track-menu-item.js":94,"./text-track-menu-item.js":97}],97:[function(_dereq_,module,exports){
+/**
+ * @file text-track-menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuItemJs = _dereq_('../../menu/menu-item.js');
+
+var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * The specific menu item type for selecting a language within a text track kind
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuItem
+ * @class TextTrackMenuItem
+ */
+
+var TextTrackMenuItem = (function (_MenuItem) {
+ _inherits(TextTrackMenuItem, _MenuItem);
+
+ function TextTrackMenuItem(player, options) {
+ var _this = this;
+
+ _classCallCheck(this, TextTrackMenuItem);
+
+ var track = options['track'];
+ var tracks = player.textTracks();
+
+ // Modify options for parent MenuItem class's init.
+ options['label'] = track['label'] || track['language'] || 'Unknown';
+ options['selected'] = track['default'] || track['mode'] === 'showing';
+
+ _MenuItem.call(this, player, options);
+
+ this.track = track;
+
+ if (tracks) {
+ (function () {
+ var changeHandler = Fn.bind(_this, _this.handleTracksChange);
+
+ tracks.addEventListener('change', changeHandler);
+ _this.on('dispose', function () {
+ tracks.removeEventListener('change', changeHandler);
+ });
+ })();
+ }
+
+ // iOS7 doesn't dispatch change events to TextTrackLists when an
+ // associated track's mode changes. Without something like
+ // Object.observe() (also not present on iOS7), it's not
+ // possible to detect changes to the mode attribute and polyfill
+ // the change event. As a poor substitute, we manually dispatch
+ // change events whenever the controls modify the mode.
+ if (tracks && tracks.onchange === undefined) {
+ (function () {
+ var event = undefined;
+
+ _this.on(['tap', 'click'], function () {
+ if (typeof _globalWindow2['default'].Event !== 'object') {
+ // Android 2.3 throws an Illegal Constructor error for window.Event
+ try {
+ event = new _globalWindow2['default'].Event('change');
+ } catch (err) {}
+ }
+
+ if (!event) {
+ event = _globalDocument2['default'].createEvent('Event');
+ event.initEvent('change', true, true);
+ }
+
+ tracks.dispatchEvent(event);
+ });
+ })();
+ }
+ }
+
+ /**
+ * Handle click on text track
+ *
+ * @method handleClick
+ */
+
+ TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
+ var kind = this.track['kind'];
+ var tracks = this.player_.textTracks();
+
+ _MenuItem.prototype.handleClick.call(this, event);
+
+ if (!tracks) return;
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+
+ if (track['kind'] !== kind) {
+ continue;
+ }
+
+ if (track === this.track) {
+ track['mode'] = 'showing';
+ } else {
+ track['mode'] = 'disabled';
+ }
+ }
+ };
+
+ /**
+ * Handle text track change
+ *
+ * @method handleTracksChange
+ */
+
+ TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
+ this.selected(this.track['mode'] === 'showing');
+ };
+
+ return TextTrackMenuItem;
+})(_menuMenuItemJs2['default']);
+
+_componentJs2['default'].registerComponent('TextTrackMenuItem', TextTrackMenuItem);
+exports['default'] = TextTrackMenuItem;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../menu/menu-item.js":114,"../../utils/fn.js":148,"global/document":9,"global/window":10}],98:[function(_dereq_,module,exports){
+/**
+ * @file current-time-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+/**
+ * Displays the current time
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class CurrentTimeDisplay
+ */
+
+var CurrentTimeDisplay = (function (_Component) {
+ _inherits(CurrentTimeDisplay, _Component);
+
+ function CurrentTimeDisplay(player, options) {
+ _classCallCheck(this, CurrentTimeDisplay);
+
+ _Component.call(this, player, options);
+
+ this.on(player, 'timeupdate', this.updateContent);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ CurrentTimeDisplay.prototype.createEl = function createEl() {
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-current-time vjs-time-control vjs-control'
+ });
+
+ this.contentEl_ = Dom.createEl('div', {
+ className: 'vjs-current-time-display',
+ // label the current time for screen reader users
+ innerHTML: '
Current Time ' + '0:00'
+ }, {
+ // tell screen readers not to automatically read the time as it changes
+ 'aria-live': 'off'
+ });
+
+ el.appendChild(this.contentEl_);
+ return el;
+ };
+
+ /**
+ * Update current time display
+ *
+ * @method updateContent
+ */
+
+ CurrentTimeDisplay.prototype.updateContent = function updateContent() {
+ // Allows for smooth scrubbing, when player can't keep up.
+ var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
+ var localizedText = this.localize('Current Time');
+ var formattedTime = _utilsFormatTimeJs2['default'](time, this.player_.duration());
+ if (formattedTime !== this.formattedTime_) {
+ this.formattedTime_ = formattedTime;
+ this.contentEl_.innerHTML = '
' + localizedText + ' ' + formattedTime;
+ }
+ };
+
+ return CurrentTimeDisplay;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
+exports['default'] = CurrentTimeDisplay;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/format-time.js":149}],99:[function(_dereq_,module,exports){
+/**
+ * @file duration-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+/**
+ * Displays the duration
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class DurationDisplay
+ */
+
+var DurationDisplay = (function (_Component) {
+ _inherits(DurationDisplay, _Component);
+
+ function DurationDisplay(player, options) {
+ _classCallCheck(this, DurationDisplay);
+
+ _Component.call(this, player, options);
+
+ // this might need to be changed to 'durationchange' instead of 'timeupdate' eventually,
+ // however the durationchange event fires before this.player_.duration() is set,
+ // so the value cannot be written out using this method.
+ // Once the order of durationchange and this.player_.duration() being set is figured out,
+ // this can be updated.
+ this.on(player, 'timeupdate', this.updateContent);
+ this.on(player, 'loadedmetadata', this.updateContent);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ DurationDisplay.prototype.createEl = function createEl() {
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-duration vjs-time-control vjs-control'
+ });
+
+ this.contentEl_ = Dom.createEl('div', {
+ className: 'vjs-duration-display',
+ // label the duration time for screen reader users
+ innerHTML: '
' + this.localize('Duration Time') + ' 0:00'
+ }, {
+ // tell screen readers not to automatically read the time as it changes
+ 'aria-live': 'off'
+ });
+
+ el.appendChild(this.contentEl_);
+ return el;
+ };
+
+ /**
+ * Update duration time display
+ *
+ * @method updateContent
+ */
+
+ DurationDisplay.prototype.updateContent = function updateContent() {
+ var duration = this.player_.duration();
+ if (duration && this.duration_ !== duration) {
+ this.duration_ = duration;
+ var localizedText = this.localize('Duration Time');
+ var formattedTime = _utilsFormatTimeJs2['default'](duration);
+ this.contentEl_.innerHTML = '
' + localizedText + ' ' + formattedTime; // label the duration time for screen reader users
+ }
+ };
+
+ return DurationDisplay;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('DurationDisplay', DurationDisplay);
+exports['default'] = DurationDisplay;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/format-time.js":149}],100:[function(_dereq_,module,exports){
+/**
+ * @file remaining-time-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFormatTimeJs = _dereq_('../../utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+/**
+ * Displays the time left in the video
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class RemainingTimeDisplay
+ */
+
+var RemainingTimeDisplay = (function (_Component) {
+ _inherits(RemainingTimeDisplay, _Component);
+
+ function RemainingTimeDisplay(player, options) {
+ _classCallCheck(this, RemainingTimeDisplay);
+
+ _Component.call(this, player, options);
+
+ this.on(player, 'timeupdate', this.updateContent);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ RemainingTimeDisplay.prototype.createEl = function createEl() {
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-remaining-time vjs-time-control vjs-control'
+ });
+
+ this.contentEl_ = Dom.createEl('div', {
+ className: 'vjs-remaining-time-display',
+ // label the remaining time for screen reader users
+ innerHTML: '
' + this.localize('Remaining Time') + ' -0:00'
+ }, {
+ // tell screen readers not to automatically read the time as it changes
+ 'aria-live': 'off'
+ });
+
+ el.appendChild(this.contentEl_);
+ return el;
+ };
+
+ /**
+ * Update remaining time display
+ *
+ * @method updateContent
+ */
+
+ RemainingTimeDisplay.prototype.updateContent = function updateContent() {
+ if (this.player_.duration()) {
+ var localizedText = this.localize('Remaining Time');
+ var formattedTime = _utilsFormatTimeJs2['default'](this.player_.remainingTime());
+ if (formattedTime !== this.formattedTime_) {
+ this.formattedTime_ = formattedTime;
+ this.contentEl_.innerHTML = '
' + localizedText + ' -' + formattedTime;
+ }
+ }
+
+ // Allows for smooth scrubbing, when player can't keep up.
+ // var time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime();
+ // this.contentEl_.innerHTML = vjs.formatTime(time, this.player_.duration());
+ };
+
+ return RemainingTimeDisplay;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
+exports['default'] = RemainingTimeDisplay;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../utils/dom.js":146,"../../utils/format-time.js":149}],101:[function(_dereq_,module,exports){
+/**
+ * @file time-divider.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * The separator between the current time and duration.
+ * Can be hidden if it's not needed in the design.
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class TimeDivider
+ */
+
+var TimeDivider = (function (_Component) {
+ _inherits(TimeDivider, _Component);
+
+ function TimeDivider() {
+ _classCallCheck(this, TimeDivider);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ TimeDivider.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-time-control vjs-time-divider',
+ innerHTML: '
/
'
+ });
+ };
+
+ return TimeDivider;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('TimeDivider', TimeDivider);
+exports['default'] = TimeDivider;
+module.exports = exports['default'];
+
+},{"../../component.js":71}],102:[function(_dereq_,module,exports){
+/**
+ * @file track-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _menuMenuButtonJs = _dereq_('../menu/menu-button.js');
+
+var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+/**
+ * The base class for buttons that toggle specific text track types (e.g. subtitles)
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends MenuButton
+ * @class TrackButton
+ */
+
+var TrackButton = (function (_MenuButton) {
+ _inherits(TrackButton, _MenuButton);
+
+ function TrackButton(player, options) {
+ _classCallCheck(this, TrackButton);
+
+ var tracks = options.tracks;
+
+ _MenuButton.call(this, player, options);
+
+ if (this.items.length <= 1) {
+ this.hide();
+ }
+
+ if (!tracks) {
+ return;
+ }
+
+ var updateHandler = Fn.bind(this, this.update);
+ tracks.addEventListener('removetrack', updateHandler);
+ tracks.addEventListener('addtrack', updateHandler);
+
+ this.player_.on('dispose', function () {
+ tracks.removeEventListener('removetrack', updateHandler);
+ tracks.removeEventListener('addtrack', updateHandler);
+ });
+ }
+
+ return TrackButton;
+})(_menuMenuButtonJs2['default']);
+
+_componentJs2['default'].registerComponent('TrackButton', TrackButton);
+exports['default'] = TrackButton;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../menu/menu-button.js":113,"../utils/fn.js":148}],103:[function(_dereq_,module,exports){
+/**
+ * @file volume-bar.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _sliderSliderJs = _dereq_('../../slider/slider.js');
+
+var _sliderSliderJs2 = _interopRequireDefault(_sliderSliderJs);
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('../../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+// Required children
+
+var _volumeLevelJs = _dereq_('./volume-level.js');
+
+var _volumeLevelJs2 = _interopRequireDefault(_volumeLevelJs);
+
+/**
+ * The bar that contains the volume level and can be clicked on to adjust the level
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Slider
+ * @class VolumeBar
+ */
+
+var VolumeBar = (function (_Slider) {
+ _inherits(VolumeBar, _Slider);
+
+ function VolumeBar(player, options) {
+ _classCallCheck(this, VolumeBar);
+
+ _Slider.call(this, player, options);
+ this.on(player, 'volumechange', this.updateARIAAttributes);
+ player.ready(Fn.bind(this, this.updateARIAAttributes));
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ VolumeBar.prototype.createEl = function createEl() {
+ return _Slider.prototype.createEl.call(this, 'div', {
+ className: 'vjs-volume-bar vjs-slider-bar'
+ }, {
+ 'aria-label': 'volume level'
+ });
+ };
+
+ /**
+ * Handle mouse move on volume bar
+ *
+ * @method handleMouseMove
+ */
+
+ VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
+ this.checkMuted();
+ this.player_.volume(this.calculateDistance(event));
+ };
+
+ VolumeBar.prototype.checkMuted = function checkMuted() {
+ if (this.player_.muted()) {
+ this.player_.muted(false);
+ }
+ };
+
+ /**
+ * Get percent of volume level
+ *
+ * @retun {Number} Volume level percent
+ * @method getPercent
+ */
+
+ VolumeBar.prototype.getPercent = function getPercent() {
+ if (this.player_.muted()) {
+ return 0;
+ } else {
+ return this.player_.volume();
+ }
+ };
+
+ /**
+ * Increase volume level for keyboard users
+ *
+ * @method stepForward
+ */
+
+ VolumeBar.prototype.stepForward = function stepForward() {
+ this.checkMuted();
+ this.player_.volume(this.player_.volume() + 0.1);
+ };
+
+ /**
+ * Decrease volume level for keyboard users
+ *
+ * @method stepBack
+ */
+
+ VolumeBar.prototype.stepBack = function stepBack() {
+ this.checkMuted();
+ this.player_.volume(this.player_.volume() - 0.1);
+ };
+
+ /**
+ * Update ARIA accessibility attributes
+ *
+ * @method updateARIAAttributes
+ */
+
+ VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes() {
+ // Current value of volume bar as a percentage
+ var volume = (this.player_.volume() * 100).toFixed(2);
+ this.el_.setAttribute('aria-valuenow', volume);
+ this.el_.setAttribute('aria-valuetext', volume + '%');
+ };
+
+ return VolumeBar;
+})(_sliderSliderJs2['default']);
+
+VolumeBar.prototype.options_ = {
+ children: ['volumeLevel'],
+ 'barName': 'volumeLevel'
+};
+
+VolumeBar.prototype.playerEvent = 'volumechange';
+
+_componentJs2['default'].registerComponent('VolumeBar', VolumeBar);
+exports['default'] = VolumeBar;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"../../slider/slider.js":123,"../../utils/fn.js":148,"./volume-level.js":105}],104:[function(_dereq_,module,exports){
+/**
+ * @file volume-control.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+// Required children
+
+var _volumeBarJs = _dereq_('./volume-bar.js');
+
+var _volumeBarJs2 = _interopRequireDefault(_volumeBarJs);
+
+/**
+ * The component for controlling the volume level
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class VolumeControl
+ */
+
+var VolumeControl = (function (_Component) {
+ _inherits(VolumeControl, _Component);
+
+ function VolumeControl(player, options) {
+ _classCallCheck(this, VolumeControl);
+
+ _Component.call(this, player, options);
+
+ // hide volume controls when they're not supported by the current tech
+ if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
+ this.addClass('vjs-hidden');
+ }
+ this.on(player, 'loadstart', function () {
+ if (player.tech_['featuresVolumeControl'] === false) {
+ this.addClass('vjs-hidden');
+ } else {
+ this.removeClass('vjs-hidden');
+ }
+ });
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ VolumeControl.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-volume-control vjs-control'
+ });
+ };
+
+ return VolumeControl;
+})(_componentJs2['default']);
+
+VolumeControl.prototype.options_ = {
+ children: ['volumeBar']
+};
+
+_componentJs2['default'].registerComponent('VolumeControl', VolumeControl);
+exports['default'] = VolumeControl;
+module.exports = exports['default'];
+
+},{"../../component.js":71,"./volume-bar.js":103}],105:[function(_dereq_,module,exports){
+/**
+ * @file volume-level.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+/**
+ * Shows volume level
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class VolumeLevel
+ */
+
+var VolumeLevel = (function (_Component) {
+ _inherits(VolumeLevel, _Component);
+
+ function VolumeLevel() {
+ _classCallCheck(this, VolumeLevel);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ VolumeLevel.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-volume-level',
+ innerHTML: '
'
+ });
+ };
+
+ return VolumeLevel;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('VolumeLevel', VolumeLevel);
+exports['default'] = VolumeLevel;
+module.exports = exports['default'];
+
+},{"../../component.js":71}],106:[function(_dereq_,module,exports){
+/**
+ * @file volume-menu-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _popupPopupJs = _dereq_('../popup/popup.js');
+
+var _popupPopupJs2 = _interopRequireDefault(_popupPopupJs);
+
+var _popupPopupButtonJs = _dereq_('../popup/popup-button.js');
+
+var _popupPopupButtonJs2 = _interopRequireDefault(_popupPopupButtonJs);
+
+var _muteToggleJs = _dereq_('./mute-toggle.js');
+
+var _muteToggleJs2 = _interopRequireDefault(_muteToggleJs);
+
+var _volumeControlVolumeBarJs = _dereq_('./volume-control/volume-bar.js');
+
+var _volumeControlVolumeBarJs2 = _interopRequireDefault(_volumeControlVolumeBarJs);
+
+/**
+ * Button for volume popup
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends PopupButton
+ * @class VolumeMenuButton
+ */
+
+var VolumeMenuButton = (function (_PopupButton) {
+ _inherits(VolumeMenuButton, _PopupButton);
+
+ function VolumeMenuButton(player) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, VolumeMenuButton);
+
+ // Default to inline
+ if (options.inline === undefined) {
+ options.inline = true;
+ }
+
+ // If the vertical option isn't passed at all, default to true.
+ if (options.vertical === undefined) {
+ // If an inline volumeMenuButton is used, we should default to using
+ // a horizontal slider for obvious reasons.
+ if (options.inline) {
+ options.vertical = false;
+ } else {
+ options.vertical = true;
+ }
+ }
+
+ // The vertical option needs to be set on the volumeBar as well,
+ // since that will need to be passed along to the VolumeBar constructor
+ options.volumeBar = options.volumeBar || {};
+ options.volumeBar.vertical = !!options.vertical;
+
+ _PopupButton.call(this, player, options);
+
+ // Same listeners as MuteToggle
+ this.on(player, 'volumechange', this.volumeUpdate);
+ this.on(player, 'loadstart', this.volumeUpdate);
+
+ // hide mute toggle if the current tech doesn't support volume control
+ function updateVisibility() {
+ if (player.tech_ && player.tech_['featuresVolumeControl'] === false) {
+ this.addClass('vjs-hidden');
+ } else {
+ this.removeClass('vjs-hidden');
+ }
+ }
+
+ updateVisibility.call(this);
+ this.on(player, 'loadstart', updateVisibility);
+
+ this.on(this.volumeBar, ['slideractive', 'focus'], function () {
+ this.addClass('vjs-slider-active');
+ });
+
+ this.on(this.volumeBar, ['sliderinactive', 'blur'], function () {
+ this.removeClass('vjs-slider-active');
+ });
+
+ this.on(this.volumeBar, ['focus'], function () {
+ this.addClass('vjs-lock-showing');
+ });
+
+ this.on(this.volumeBar, ['blur'], function () {
+ this.removeClass('vjs-lock-showing');
+ });
+ }
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ VolumeMenuButton.prototype.buildCSSClass = function buildCSSClass() {
+ var orientationClass = '';
+ if (!!this.options_.vertical) {
+ orientationClass = 'vjs-volume-menu-button-vertical';
+ } else {
+ orientationClass = 'vjs-volume-menu-button-horizontal';
+ }
+
+ return 'vjs-volume-menu-button ' + _PopupButton.prototype.buildCSSClass.call(this) + ' ' + orientationClass;
+ };
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {Popup} The volume popup button
+ * @method createPopup
+ */
+
+ VolumeMenuButton.prototype.createPopup = function createPopup() {
+ var popup = new _popupPopupJs2['default'](this.player_, {
+ contentElType: 'div'
+ });
+
+ var vb = new _volumeControlVolumeBarJs2['default'](this.player_, this.options_.volumeBar);
+
+ popup.addChild(vb);
+
+ this.menuContent = popup;
+ this.volumeBar = vb;
+
+ this.attachVolumeBarEvents();
+
+ return popup;
+ };
+
+ /**
+ * Handle click on volume popup and calls super
+ *
+ * @method handleClick
+ */
+
+ VolumeMenuButton.prototype.handleClick = function handleClick() {
+ _muteToggleJs2['default'].prototype.handleClick.call(this);
+ _PopupButton.prototype.handleClick.call(this);
+ };
+
+ VolumeMenuButton.prototype.attachVolumeBarEvents = function attachVolumeBarEvents() {
+ this.menuContent.on(['mousedown', 'touchdown'], Fn.bind(this, this.handleMouseDown));
+ };
+
+ VolumeMenuButton.prototype.handleMouseDown = function handleMouseDown(event) {
+ this.on(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
+ this.on(this.el_.ownerDocument, ['mouseup', 'touchend'], this.handleMouseUp);
+ };
+
+ VolumeMenuButton.prototype.handleMouseUp = function handleMouseUp(event) {
+ this.off(['mousemove', 'touchmove'], Fn.bind(this.volumeBar, this.volumeBar.handleMouseMove));
+ };
+
+ return VolumeMenuButton;
+})(_popupPopupButtonJs2['default']);
+
+VolumeMenuButton.prototype.volumeUpdate = _muteToggleJs2['default'].prototype.update;
+VolumeMenuButton.prototype.controlText_ = 'Mute';
+
+_componentJs2['default'].registerComponent('VolumeMenuButton', VolumeMenuButton);
+exports['default'] = VolumeMenuButton;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../popup/popup-button.js":119,"../popup/popup.js":120,"../utils/fn.js":148,"./mute-toggle.js":77,"./volume-control/volume-bar.js":103}],107:[function(_dereq_,module,exports){
+/**
+ * @file error-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('./component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _modalDialog = _dereq_('./modal-dialog');
+
+var _modalDialog2 = _interopRequireDefault(_modalDialog);
+
+var _utilsDom = _dereq_('./utils/dom');
+
+var Dom = _interopRequireWildcard(_utilsDom);
+
+var _utilsMergeOptions = _dereq_('./utils/merge-options');
+
+var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions);
+
+/**
+ * Display that an error has occurred making the video unplayable.
+ *
+ * @extends ModalDialog
+ * @class ErrorDisplay
+ */
+
+var ErrorDisplay = (function (_ModalDialog) {
+ _inherits(ErrorDisplay, _ModalDialog);
+
+ /**
+ * Constructor for error display modal.
+ *
+ * @param {Player} player
+ * @param {Object} [options]
+ */
+
+ function ErrorDisplay(player, options) {
+ _classCallCheck(this, ErrorDisplay);
+
+ _ModalDialog.call(this, player, options);
+ this.on(player, 'error', this.open);
+ }
+
+ /**
+ * Include the old class for backward-compatibility.
+ *
+ * This can be removed in 6.0.
+ *
+ * @method buildCSSClass
+ * @deprecated
+ * @return {String}
+ */
+
+ ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
+ return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Generates the modal content based on the player error.
+ *
+ * @return {String|Null}
+ */
+
+ ErrorDisplay.prototype.content = function content() {
+ var error = this.player().error();
+ return error ? this.localize(error.message) : '';
+ };
+
+ return ErrorDisplay;
+})(_modalDialog2['default']);
+
+ErrorDisplay.prototype.options_ = _utilsMergeOptions2['default'](_modalDialog2['default'].prototype.options_, {
+ fillAlways: true,
+ temporary: false,
+ uncloseable: true
+});
+
+_component2['default'].registerComponent('ErrorDisplay', ErrorDisplay);
+exports['default'] = ErrorDisplay;
+module.exports = exports['default'];
+
+},{"./component":71,"./modal-dialog":116,"./utils/dom":146,"./utils/merge-options":152}],108:[function(_dereq_,module,exports){
+/**
+ * @file event-target.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+var _utilsEventsJs = _dereq_('./utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+var EventTarget = function EventTarget() {};
+
+EventTarget.prototype.allowedEvents_ = {};
+
+EventTarget.prototype.on = function (type, fn) {
+ // Remove the addEventListener alias before calling Events.on
+ // so we don't get into an infinite type loop
+ var ael = this.addEventListener;
+ this.addEventListener = function () {};
+ Events.on(this, type, fn);
+ this.addEventListener = ael;
+};
+EventTarget.prototype.addEventListener = EventTarget.prototype.on;
+
+EventTarget.prototype.off = function (type, fn) {
+ Events.off(this, type, fn);
+};
+EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
+
+EventTarget.prototype.one = function (type, fn) {
+ // Remove the addEventListener alias before calling Events.on
+ // so we don't get into an infinite type loop
+ var ael = this.addEventListener;
+ this.addEventListener = function () {};
+ Events.one(this, type, fn);
+ this.addEventListener = ael;
+};
+
+EventTarget.prototype.trigger = function (event) {
+ var type = event.type || event;
+
+ if (typeof event === 'string') {
+ event = {
+ type: type
+ };
+ }
+ event = Events.fixEvent(event);
+
+ if (this.allowedEvents_[type] && this['on' + type]) {
+ this['on' + type](event);
+ }
+
+ Events.trigger(this, event);
+};
+// The standard DOM EventTarget.dispatchEvent() is aliased to trigger()
+EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
+
+exports['default'] = EventTarget;
+module.exports = exports['default'];
+
+},{"./utils/events.js":147}],109:[function(_dereq_,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _utilsLog = _dereq_('./utils/log');
+
+var _utilsLog2 = _interopRequireDefault(_utilsLog);
+
+/*
+ * @file extend.js
+ *
+ * A combination of node inherits and babel's inherits (after transpile).
+ * Both work the same but node adds `super_` to the subClass
+ * and Bable adds the superClass as __proto__. Both seem useful.
+ */
+var _inherits = function _inherits(subClass, superClass) {
+ if (typeof superClass !== 'function' && superClass !== null) {
+ throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass);
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+
+ if (superClass) {
+ // node
+ subClass.super_ = superClass;
+ }
+};
+
+/*
+ * Function for subclassing using the same inheritance that
+ * videojs uses internally
+ * ```js
+ * var Button = videojs.getComponent('Button');
+ * ```
+ * ```js
+ * var MyButton = videojs.extend(Button, {
+ * constructor: function(player, options) {
+ * Button.call(this, player, options);
+ * },
+ * onClick: function() {
+ * // doSomething
+ * }
+ * });
+ * ```
+ */
+var extendFn = function extendFn(superClass) {
+ var subClassMethods = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ var subClass = function subClass() {
+ superClass.apply(this, arguments);
+ };
+ var methods = {};
+
+ if (typeof subClassMethods === 'object') {
+ if (typeof subClassMethods.init === 'function') {
+ _utilsLog2['default'].warn('Constructor logic via init() is deprecated; please use constructor() instead.');
+ subClassMethods.constructor = subClassMethods.init;
+ }
+ if (subClassMethods.constructor !== Object.prototype.constructor) {
+ subClass = subClassMethods.constructor;
+ }
+ methods = subClassMethods;
+ } else if (typeof subClassMethods === 'function') {
+ subClass = subClassMethods;
+ }
+
+ _inherits(subClass, superClass);
+
+ // Extend subObj's prototype with functions and other properties from props
+ for (var name in methods) {
+ if (methods.hasOwnProperty(name)) {
+ subClass.prototype[name] = methods[name];
+ }
+ }
+
+ return subClass;
+};
+
+exports['default'] = extendFn;
+module.exports = exports['default'];
+
+},{"./utils/log":151}],110:[function(_dereq_,module,exports){
+/**
+ * @file fullscreen-api.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/*
+ * Store the browser-specific methods for the fullscreen API
+ * @type {Object|undefined}
+ * @private
+ */
+var FullscreenApi = {};
+
+// browser API methods
+// map approach from Screenful.js - https://github.com/sindresorhus/screenfull.js
+var apiMap = [
+// Spec: https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
+['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
+// WebKit
+['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
+// Old WebKit (Safari 5.1)
+['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
+// Mozilla
+['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
+// Microsoft
+['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
+
+var specApi = apiMap[0];
+var browserApi = undefined;
+
+// determine the supported set of functions
+for (var i = 0; i < apiMap.length; i++) {
+ // check for exitFullscreen function
+ if (apiMap[i][1] in _globalDocument2['default']) {
+ browserApi = apiMap[i];
+ break;
+ }
+}
+
+// map the browser API names to the spec API names
+if (browserApi) {
+ for (var i = 0; i < browserApi.length; i++) {
+ FullscreenApi[specApi[i]] = browserApi[i];
+ }
+}
+
+exports['default'] = FullscreenApi;
+module.exports = exports['default'];
+
+},{"global/document":9}],111:[function(_dereq_,module,exports){
+/**
+ * @file loading-spinner.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('./component');
+
+var _component2 = _interopRequireDefault(_component);
+
+/* Loading Spinner
+================================================================================ */
+/**
+ * Loading spinner for waiting events
+ *
+ * @extends Component
+ * @class LoadingSpinner
+ */
+
+var LoadingSpinner = (function (_Component) {
+ _inherits(LoadingSpinner, _Component);
+
+ function LoadingSpinner() {
+ _classCallCheck(this, LoadingSpinner);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @method createEl
+ */
+
+ LoadingSpinner.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-loading-spinner',
+ dir: 'ltr'
+ });
+ };
+
+ return LoadingSpinner;
+})(_component2['default']);
+
+_component2['default'].registerComponent('LoadingSpinner', LoadingSpinner);
+exports['default'] = LoadingSpinner;
+module.exports = exports['default'];
+
+},{"./component":71}],112:[function(_dereq_,module,exports){
+/**
+ * @file media-error.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+/*
+ * Custom MediaError to mimic the HTML5 MediaError
+ *
+ * @param {Number} code The media error code
+ */
+var MediaError = function MediaError(code) {
+ if (typeof code === 'number') {
+ this.code = code;
+ } else if (typeof code === 'string') {
+ // default code is zero, so this is a custom error
+ this.message = code;
+ } else if (typeof code === 'object') {
+ // object
+ _objectAssign2['default'](this, code);
+ }
+
+ if (!this.message) {
+ this.message = MediaError.defaultMessages[this.code] || '';
+ }
+};
+
+/*
+ * The error code that refers two one of the defined
+ * MediaError types
+ *
+ * @type {Number}
+ */
+MediaError.prototype.code = 0;
+
+/*
+ * An optional message to be shown with the error.
+ * Message is not part of the HTML5 video spec
+ * but allows for more informative custom errors.
+ *
+ * @type {String}
+ */
+MediaError.prototype.message = '';
+
+/*
+ * An optional status code that can be set by plugins
+ * to allow even more detail about the error.
+ * For example the HLS plugin might provide the specific
+ * HTTP status code that was returned when the error
+ * occurred, then allowing a custom error overlay
+ * to display more information.
+ *
+ * @type {Array}
+ */
+MediaError.prototype.status = null;
+
+MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', // = 0
+'MEDIA_ERR_ABORTED', // = 1
+'MEDIA_ERR_NETWORK', // = 2
+'MEDIA_ERR_DECODE', // = 3
+'MEDIA_ERR_SRC_NOT_SUPPORTED', // = 4
+'MEDIA_ERR_ENCRYPTED' // = 5
+];
+
+MediaError.defaultMessages = {
+ 1: '网络异常,请刷新页面重试',
+ 2: 'A network error caused the media download to fail part-way.',
+ 3: '网络异常,请刷新页面重试',
+ 4: '该机型手机浏览器不支持,请使用传课APP观看',
+ 5: 'The media is encrypted and we do not have the keys to decrypt it.'
+};
+
+// Add types as properties on MediaError
+// e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
+for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
+ MediaError[MediaError.errorTypes[errNum]] = errNum;
+ // values should be accessible on both the class and instance
+ MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
+}
+
+exports['default'] = MediaError;
+module.exports = exports['default'];
+
+},{"object.assign":57}],113:[function(_dereq_,module,exports){
+/**
+ * @file menu-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _clickableComponentJs = _dereq_('../clickable-component.js');
+
+var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _menuJs = _dereq_('./menu.js');
+
+var _menuJs2 = _interopRequireDefault(_menuJs);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+/**
+ * A button class with a popup menu
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Button
+ * @class MenuButton
+ */
+
+var MenuButton = (function (_ClickableComponent) {
+ _inherits(MenuButton, _ClickableComponent);
+
+ function MenuButton(player) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, MenuButton);
+
+ _ClickableComponent.call(this, player, options);
+
+ this.update();
+
+ this.enabled_ = true;
+
+ this.el_.setAttribute('aria-haspopup', 'true');
+ this.el_.setAttribute('role', 'menuitem');
+ this.on('keydown', this.handleSubmenuKeyPress);
+ }
+
+ /**
+ * Update menu
+ *
+ * @method update
+ */
+
+ MenuButton.prototype.update = function update() {
+ var menu = this.createMenu();
+
+ if (this.menu) {
+ this.removeChild(this.menu);
+ }
+
+ this.menu = menu;
+ this.addChild(menu);
+
+ /**
+ * Track the state of the menu button
+ *
+ * @type {Boolean}
+ * @private
+ */
+ this.buttonPressed_ = false;
+ this.el_.setAttribute('aria-expanded', 'false');
+
+ if (this.items && this.items.length === 0) {
+ this.hide();
+ } else if (this.items && this.items.length > 1) {
+ this.show();
+ }
+ };
+
+ /**
+ * Create menu
+ *
+ * @return {Menu} The constructed menu
+ * @method createMenu
+ */
+
+ MenuButton.prototype.createMenu = function createMenu() {
+ var menu = new _menuJs2['default'](this.player_);
+
+ // Add a title list item to the top
+ if (this.options_.title) {
+ var title = Dom.createEl('li', {
+ className: 'vjs-menu-title',
+ innerHTML: _utilsToTitleCaseJs2['default'](this.options_.title),
+ tabIndex: -1
+ });
+ menu.children_.unshift(title);
+ Dom.insertElFirst(title, menu.contentEl());
+ }
+
+ this.items = this['createItems']();
+
+ if (this.items) {
+ // Add menu items to the menu
+ for (var i = 0; i < this.items.length; i++) {
+ menu.addItem(this.items[i]);
+ }
+ }
+
+ return menu;
+ };
+
+ /**
+ * Create the list of menu items. Specific to each subclass.
+ *
+ * @method createItems
+ */
+
+ MenuButton.prototype.createItems = function createItems() {};
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ MenuButton.prototype.createEl = function createEl() {
+ return _ClickableComponent.prototype.createEl.call(this, 'div', {
+ className: this.buildCSSClass()
+ });
+ };
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ MenuButton.prototype.buildCSSClass = function buildCSSClass() {
+ var menuButtonClass = 'vjs-menu-button';
+
+ // If the inline option is passed, we want to use different styles altogether.
+ if (this.options_.inline === true) {
+ menuButtonClass += '-inline';
+ } else {
+ menuButtonClass += '-popup';
+ }
+
+ return 'vjs-menu-button ' + menuButtonClass + ' ' + _ClickableComponent.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * When you click the button it adds focus, which
+ * will show the menu indefinitely.
+ * So we'll remove focus when the mouse leaves the button.
+ * Focus is needed for tab navigation.
+ * Allow sub components to stack CSS class names
+ *
+ * @method handleClick
+ */
+
+ MenuButton.prototype.handleClick = function handleClick() {
+ this.one('mouseout', Fn.bind(this, function () {
+ this.menu.unlockShowing();
+ this.el_.blur();
+ }));
+ if (this.buttonPressed_) {
+ this.unpressButton();
+ } else {
+ this.pressButton();
+ }
+ };
+
+ /**
+ * Handle key press on menu
+ *
+ * @param {Object} event Key press event
+ * @method handleKeyPress
+ */
+
+ MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {
+
+ // Escape (27) key or Tab (9) key unpress the 'button'
+ if (event.which === 27 || event.which === 9) {
+ if (this.buttonPressed_) {
+ this.unpressButton();
+ }
+ // Don't preventDefault for Tab key - we still want to lose focus
+ if (event.which !== 9) {
+ event.preventDefault();
+ }
+ // Up (38) key or Down (40) key press the 'button'
+ } else if (event.which === 38 || event.which === 40) {
+ if (!this.buttonPressed_) {
+ this.pressButton();
+ event.preventDefault();
+ }
+ } else {
+ _ClickableComponent.prototype.handleKeyPress.call(this, event);
+ }
+ };
+
+ /**
+ * Handle key press on submenu
+ *
+ * @param {Object} event Key press event
+ * @method handleSubmenuKeyPress
+ */
+
+ MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
+
+ // Escape (27) key or Tab (9) key unpress the 'button'
+ if (event.which === 27 || event.which === 9) {
+ if (this.buttonPressed_) {
+ this.unpressButton();
+ }
+ // Don't preventDefault for Tab key - we still want to lose focus
+ if (event.which !== 9) {
+ event.preventDefault();
+ }
+ }
+ };
+
+ /**
+ * Makes changes based on button pressed
+ *
+ * @method pressButton
+ */
+
+ MenuButton.prototype.pressButton = function pressButton() {
+ if (this.enabled_) {
+ this.buttonPressed_ = true;
+ this.menu.lockShowing();
+ this.el_.setAttribute('aria-expanded', 'true');
+ this.menu.focus(); // set the focus into the submenu
+ }
+ };
+
+ /**
+ * Makes changes based on button unpressed
+ *
+ * @method unpressButton
+ */
+
+ MenuButton.prototype.unpressButton = function unpressButton() {
+ if (this.enabled_) {
+ this.buttonPressed_ = false;
+ this.menu.unlockShowing();
+ this.el_.setAttribute('aria-expanded', 'false');
+ this.el_.focus(); // Set focus back to this menu button
+ }
+ };
+
+ /**
+ * Disable the menu button
+ *
+ * @return {Component}
+ * @method disable
+ */
+
+ MenuButton.prototype.disable = function disable() {
+ // Unpress, but don't force focus on this button
+ this.buttonPressed_ = false;
+ this.menu.unlockShowing();
+ this.el_.setAttribute('aria-expanded', 'false');
+
+ this.enabled_ = false;
+
+ return _ClickableComponent.prototype.disable.call(this);
+ };
+
+ /**
+ * Enable the menu button
+ *
+ * @return {Component}
+ * @method disable
+ */
+
+ MenuButton.prototype.enable = function enable() {
+ this.enabled_ = true;
+
+ return _ClickableComponent.prototype.enable.call(this);
+ };
+
+ return MenuButton;
+})(_clickableComponentJs2['default']);
+
+_componentJs2['default'].registerComponent('MenuButton', MenuButton);
+exports['default'] = MenuButton;
+module.exports = exports['default'];
+
+},{"../clickable-component.js":69,"../component.js":71,"../utils/dom.js":146,"../utils/fn.js":148,"../utils/to-title-case.js":155,"./menu.js":115}],114:[function(_dereq_,module,exports){
+/**
+ * @file menu-item.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _clickableComponentJs = _dereq_('../clickable-component.js');
+
+var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+/**
+ * The component for a menu item. `
`
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Button
+ * @class MenuItem
+ */
+
+var MenuItem = (function (_ClickableComponent) {
+ _inherits(MenuItem, _ClickableComponent);
+
+ function MenuItem(player, options) {
+ _classCallCheck(this, MenuItem);
+
+ _ClickableComponent.call(this, player, options);
+
+ this.selectable = options['selectable'];
+
+ this.selected(options['selected']);
+
+ if (this.selectable) {
+ // TODO: May need to be either menuitemcheckbox or menuitemradio,
+ // and may need logical grouping of menu items.
+ this.el_.setAttribute('role', 'menuitemcheckbox');
+ } else {
+ this.el_.setAttribute('role', 'menuitem');
+ }
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @param {String=} type Desc
+ * @param {Object=} props Desc
+ * @return {Element}
+ * @method createEl
+ */
+
+ MenuItem.prototype.createEl = function createEl(type, props, attrs) {
+ return _ClickableComponent.prototype.createEl.call(this, 'li', _objectAssign2['default']({
+ className: 'vjs-menu-item',
+ innerHTML: this.localize(this.options_['label']),
+ tabIndex: -1
+ }, props), attrs);
+ };
+
+ /**
+ * Handle a click on the menu item, and set it to selected
+ *
+ * @method handleClick
+ */
+
+ MenuItem.prototype.handleClick = function handleClick() {
+ this.selected(true);
+ };
+
+ /**
+ * Set this menu item as selected or not
+ *
+ * @param {Boolean} selected
+ * @method selected
+ */
+
+ MenuItem.prototype.selected = function selected(_selected) {
+ if (this.selectable) {
+ if (_selected) {
+ this.addClass('vjs-selected');
+ this.el_.setAttribute('aria-checked', 'true');
+ // aria-checked isn't fully supported by browsers/screen readers,
+ // so indicate selected state to screen reader in the control text.
+ this.controlText(', selected');
+ } else {
+ this.removeClass('vjs-selected');
+ this.el_.setAttribute('aria-checked', 'false');
+ // Indicate un-selected state to screen reader
+ // Note that a space clears out the selected state text
+ this.controlText(' ');
+ }
+ }
+ };
+
+ return MenuItem;
+})(_clickableComponentJs2['default']);
+
+_componentJs2['default'].registerComponent('MenuItem', MenuItem);
+exports['default'] = MenuItem;
+module.exports = exports['default'];
+
+},{"../clickable-component.js":69,"../component.js":71,"object.assign":57}],115:[function(_dereq_,module,exports){
+/**
+ * @file menu.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsEventsJs = _dereq_('../utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+/**
+ * The Menu component is used to build pop up menus, including subtitle and
+ * captions selection menus.
+ *
+ * @extends Component
+ * @class Menu
+ */
+
+var Menu = (function (_Component) {
+ _inherits(Menu, _Component);
+
+ function Menu(player, options) {
+ _classCallCheck(this, Menu);
+
+ _Component.call(this, player, options);
+
+ this.focusedChild_ = -1;
+
+ this.on('keydown', this.handleKeyPress);
+ }
+
+ /**
+ * Add a menu item to the menu
+ *
+ * @param {Object|String} component Component or component type to add
+ * @method addItem
+ */
+
+ Menu.prototype.addItem = function addItem(component) {
+ this.addChild(component);
+ component.on('click', Fn.bind(this, function () {
+ this.unlockShowing();
+ //TODO: Need to set keyboard focus back to the menuButton
+ }));
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Menu.prototype.createEl = function createEl() {
+ var contentElType = this.options_.contentElType || 'ul';
+ this.contentEl_ = Dom.createEl(contentElType, {
+ className: 'vjs-menu-content'
+ });
+ this.contentEl_.setAttribute('role', 'menu');
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ append: this.contentEl_,
+ className: 'vjs-menu'
+ });
+ el.setAttribute('role', 'presentation');
+ el.appendChild(this.contentEl_);
+
+ // Prevent clicks from bubbling up. Needed for Menu Buttons,
+ // where a click on the parent is significant
+ Events.on(el, 'click', function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ });
+
+ return el;
+ };
+
+ /**
+ * Handle key press for menu
+ *
+ * @param {Object} event Event object
+ * @method handleKeyPress
+ */
+
+ Menu.prototype.handleKeyPress = function handleKeyPress(event) {
+ if (event.which === 37 || event.which === 40) {
+ // Left and Down Arrows
+ event.preventDefault();
+ this.stepForward();
+ } else if (event.which === 38 || event.which === 39) {
+ // Up and Right Arrows
+ event.preventDefault();
+ this.stepBack();
+ }
+ };
+
+ /**
+ * Move to next (lower) menu item for keyboard users
+ *
+ * @method stepForward
+ */
+
+ Menu.prototype.stepForward = function stepForward() {
+ var stepChild = 0;
+
+ if (this.focusedChild_ !== undefined) {
+ stepChild = this.focusedChild_ + 1;
+ }
+ this.focus(stepChild);
+ };
+
+ /**
+ * Move to previous (higher) menu item for keyboard users
+ *
+ * @method stepBack
+ */
+
+ Menu.prototype.stepBack = function stepBack() {
+ var stepChild = 0;
+
+ if (this.focusedChild_ !== undefined) {
+ stepChild = this.focusedChild_ - 1;
+ }
+ this.focus(stepChild);
+ };
+
+ /**
+ * Set focus on a menu item in the menu
+ *
+ * @param {Object|String} item Index of child item set focus on
+ * @method focus
+ */
+
+ Menu.prototype.focus = function focus() {
+ var item = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
+
+ var children = this.children().slice();
+ var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
+
+ if (haveTitle) {
+ children.shift();
+ }
+
+ if (children.length > 0) {
+ if (item < 0) {
+ item = 0;
+ } else if (item >= children.length) {
+ item = children.length - 1;
+ }
+
+ this.focusedChild_ = item;
+
+ children[item].el_.focus();
+ }
+ };
+
+ return Menu;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('Menu', Menu);
+exports['default'] = Menu;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../utils/dom.js":146,"../utils/events.js":147,"../utils/fn.js":148}],116:[function(_dereq_,module,exports){
+/**
+ * @file modal-dialog.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _utilsDom = _dereq_('./utils/dom');
+
+var Dom = _interopRequireWildcard(_utilsDom);
+
+var _utilsFn = _dereq_('./utils/fn');
+
+var Fn = _interopRequireWildcard(_utilsFn);
+
+var _utilsLog = _dereq_('./utils/log');
+
+var _utilsLog2 = _interopRequireDefault(_utilsLog);
+
+var _component = _dereq_('./component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _closeButton = _dereq_('./close-button');
+
+var _closeButton2 = _interopRequireDefault(_closeButton);
+
+var MODAL_CLASS_NAME = 'vjs-modal-dialog';
+var ESC = 27;
+
+/**
+ * The `ModalDialog` displays over the video and its controls, which blocks
+ * interaction with the player until it is closed.
+ *
+ * Modal dialogs include a "Close" button and will close when that button
+ * is activated - or when ESC is pressed anywhere.
+ *
+ * @extends Component
+ * @class ModalDialog
+ */
+
+var ModalDialog = (function (_Component) {
+ _inherits(ModalDialog, _Component);
+
+ /**
+ * Constructor for modals.
+ *
+ * @param {Player} player
+ * @param {Object} [options]
+ * @param {Mixed} [options.content=undefined]
+ * Provide customized content for this modal.
+ *
+ * @param {String} [options.description]
+ * A text description for the modal, primarily for accessibility.
+ *
+ * @param {Boolean} [options.fillAlways=false]
+ * Normally, modals are automatically filled only the first time
+ * they open. This tells the modal to refresh its content
+ * every time it opens.
+ *
+ * @param {String} [options.label]
+ * A text label for the modal, primarily for accessibility.
+ *
+ * @param {Boolean} [options.temporary=true]
+ * If `true`, the modal can only be opened once; it will be
+ * disposed as soon as it's closed.
+ *
+ * @param {Boolean} [options.uncloseable=false]
+ * If `true`, the user will not be able to close the modal
+ * through the UI in the normal ways. Programmatic closing is
+ * still possible.
+ *
+ */
+
+ function ModalDialog(player, options) {
+ _classCallCheck(this, ModalDialog);
+
+ _Component.call(this, player, options);
+ this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
+
+ this.closeable(!this.options_.uncloseable);
+ this.content(this.options_.content);
+
+ // Make sure the contentEl is defined AFTER any children are initialized
+ // because we only want the contents of the modal in the contentEl
+ // (not the UI elements like the close button).
+ this.contentEl_ = Dom.createEl('div', {
+ className: MODAL_CLASS_NAME + '-content'
+ }, {
+ role: 'document'
+ });
+
+ this.descEl_ = Dom.createEl('p', {
+ className: MODAL_CLASS_NAME + '-description vjs-offscreen',
+ id: this.el().getAttribute('aria-describedby')
+ });
+
+ Dom.textContent(this.descEl_, this.description());
+ this.el_.appendChild(this.descEl_);
+ this.el_.appendChild(this.contentEl_);
+ }
+
+ /*
+ * Modal dialog default options.
+ *
+ * @type {Object}
+ * @private
+ */
+
+ /**
+ * Create the modal's DOM element
+ *
+ * @method createEl
+ * @return {Element}
+ */
+
+ ModalDialog.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: this.buildCSSClass(),
+ tabIndex: -1
+ }, {
+ 'aria-describedby': this.id() + '_description',
+ 'aria-hidden': 'true',
+ 'aria-label': this.label(),
+ role: 'dialog'
+ });
+ };
+
+ /**
+ * Build the modal's CSS class.
+ *
+ * @method buildCSSClass
+ * @return {String}
+ */
+
+ ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
+ return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
+ };
+
+ /**
+ * Handles key presses on the document, looking for ESC, which closes
+ * the modal.
+ *
+ * @method handleKeyPress
+ * @param {Event} e
+ */
+
+ ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
+ if (e.which === ESC && this.closeable()) {
+ this.close();
+ }
+ };
+
+ /**
+ * Returns the label string for this modal. Primarily used for accessibility.
+ *
+ * @return {String}
+ */
+
+ ModalDialog.prototype.label = function label() {
+ return this.options_.label || this.localize('Modal Window');
+ };
+
+ /**
+ * Returns the description string for this modal. Primarily used for
+ * accessibility.
+ *
+ * @return {String}
+ */
+
+ ModalDialog.prototype.description = function description() {
+ var desc = this.options_.description || this.localize('This is a modal window.');
+
+ // Append a universal closeability message if the modal is closeable.
+ if (this.closeable()) {
+ desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
+ }
+
+ return desc;
+ };
+
+ /**
+ * Opens the modal.
+ *
+ * @method open
+ * @return {ModalDialog}
+ */
+
+ ModalDialog.prototype.open = function open() {
+ if (!this.opened_) {
+ var player = this.player();
+
+ this.trigger('beforemodalopen');
+ this.opened_ = true;
+
+ // Fill content if the modal has never opened before and
+ // never been filled.
+ if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
+ this.fill();
+ }
+
+ // If the player was playing, pause it and take note of its previously
+ // playing state.
+ this.wasPlaying_ = !player.paused();
+
+ if (this.wasPlaying_) {
+ player.pause();
+ }
+
+ if (this.closeable()) {
+ this.on(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
+ }
+
+ player.controls(false);
+ this.show();
+ this.el().setAttribute('aria-hidden', 'false');
+ this.trigger('modalopen');
+ this.hasBeenOpened_ = true;
+ }
+ return this;
+ };
+
+ /**
+ * Whether or not the modal is opened currently.
+ *
+ * @method opened
+ * @param {Boolean} [value]
+ * If given, it will open (`true`) or close (`false`) the modal.
+ *
+ * @return {Boolean}
+ */
+
+ ModalDialog.prototype.opened = function opened(value) {
+ if (typeof value === 'boolean') {
+ this[value ? 'open' : 'close']();
+ }
+ return this.opened_;
+ };
+
+ /**
+ * Closes the modal.
+ *
+ * @method close
+ * @return {ModalDialog}
+ */
+
+ ModalDialog.prototype.close = function close() {
+ if (this.opened_) {
+ var player = this.player();
+
+ this.trigger('beforemodalclose');
+ this.opened_ = false;
+
+ if (this.wasPlaying_) {
+ player.play();
+ }
+
+ if (this.closeable()) {
+ this.off(this.el_.ownerDocument, 'keydown', Fn.bind(this, this.handleKeyPress));
+ }
+
+ player.controls(true);
+ this.hide();
+ this.el().setAttribute('aria-hidden', 'true');
+ this.trigger('modalclose');
+
+ if (this.options_.temporary) {
+ this.dispose();
+ }
+ }
+ return this;
+ };
+
+ /**
+ * Whether or not the modal is closeable via the UI.
+ *
+ * @method closeable
+ * @param {Boolean} [value]
+ * If given as a Boolean, it will set the `closeable` option.
+ *
+ * @return {Boolean}
+ */
+
+ ModalDialog.prototype.closeable = function closeable(value) {
+ if (typeof value === 'boolean') {
+ var closeable = this.closeable_ = !!value;
+ var _close = this.getChild('closeButton');
+
+ // If this is being made closeable and has no close button, add one.
+ if (closeable && !_close) {
+
+ // The close button should be a child of the modal - not its
+ // content element, so temporarily change the content element.
+ var temp = this.contentEl_;
+ this.contentEl_ = this.el_;
+ _close = this.addChild('closeButton');
+ this.contentEl_ = temp;
+ this.on(_close, 'close', this.close);
+ }
+
+ // If this is being made uncloseable and has a close button, remove it.
+ if (!closeable && _close) {
+ this.off(_close, 'close', this.close);
+ this.removeChild(_close);
+ _close.dispose();
+ }
+ }
+ return this.closeable_;
+ };
+
+ /**
+ * Fill the modal's content element with the modal's "content" option.
+ *
+ * The content element will be emptied before this change takes place.
+ *
+ * @method fill
+ * @return {ModalDialog}
+ */
+
+ ModalDialog.prototype.fill = function fill() {
+ return this.fillWith(this.content());
+ };
+
+ /**
+ * Fill the modal's content element with arbitrary content.
+ *
+ * The content element will be emptied before this change takes place.
+ *
+ * @method fillWith
+ * @param {Mixed} [content]
+ * The same rules apply to this as apply to the `content` option.
+ *
+ * @return {ModalDialog}
+ */
+
+ ModalDialog.prototype.fillWith = function fillWith(content) {
+ var contentEl = this.contentEl();
+ var parentEl = contentEl.parentNode;
+ var nextSiblingEl = contentEl.nextSibling;
+
+ this.trigger('beforemodalfill');
+ this.hasBeenFilled_ = true;
+
+ // Detach the content element from the DOM before performing
+ // manipulation to avoid modifying the live DOM multiple times.
+ parentEl.removeChild(contentEl);
+ this.empty();
+ Dom.insertContent(contentEl, content);
+ this.trigger('modalfill');
+
+ // Re-inject the re-filled content element.
+ if (nextSiblingEl) {
+ parentEl.insertBefore(contentEl, nextSiblingEl);
+ } else {
+ parentEl.appendChild(contentEl);
+ }
+
+ return this;
+ };
+
+ /**
+ * Empties the content element.
+ *
+ * This happens automatically anytime the modal is filled.
+ *
+ * @method empty
+ * @return {ModalDialog}
+ */
+
+ ModalDialog.prototype.empty = function empty() {
+ this.trigger('beforemodalempty');
+ Dom.emptyEl(this.contentEl());
+ this.trigger('modalempty');
+ return this;
+ };
+
+ /**
+ * Gets or sets the modal content, which gets normalized before being
+ * rendered into the DOM.
+ *
+ * This does not update the DOM or fill the modal, but it is called during
+ * that process.
+ *
+ * @method content
+ * @param {Mixed} [value]
+ * If defined, sets the internal content value to be used on the
+ * next call(s) to `fill`. This value is normalized before being
+ * inserted. To "clear" the internal content value, pass `null`.
+ *
+ * @return {Mixed}
+ */
+
+ ModalDialog.prototype.content = function content(value) {
+ if (typeof value !== 'undefined') {
+ this.content_ = value;
+ }
+ return this.content_;
+ };
+
+ return ModalDialog;
+})(_component2['default']);
+
+ModalDialog.prototype.options_ = {
+ temporary: true
+};
+
+_component2['default'].registerComponent('ModalDialog', ModalDialog);
+exports['default'] = ModalDialog;
+module.exports = exports['default'];
+
+},{"./close-button":70,"./component":71,"./utils/dom":146,"./utils/fn":148,"./utils/log":151}],117:[function(_dereq_,module,exports){
+/**
+ * @file player.js
+ */
+// Subclasses Component
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('./component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _utilsEventsJs = _dereq_('./utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+var _utilsDomJs = _dereq_('./utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('./utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsGuidJs = _dereq_('./utils/guid.js');
+
+var Guid = _interopRequireWildcard(_utilsGuidJs);
+
+var _utilsBrowserJs = _dereq_('./utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _utilsLogJs = _dereq_('./utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _utilsToTitleCaseJs = _dereq_('./utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js');
+
+var _utilsBufferJs = _dereq_('./utils/buffer.js');
+
+var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js');
+
+var stylesheet = _interopRequireWildcard(_utilsStylesheetJs);
+
+var _fullscreenApiJs = _dereq_('./fullscreen-api.js');
+
+var _fullscreenApiJs2 = _interopRequireDefault(_fullscreenApiJs);
+
+var _mediaErrorJs = _dereq_('./media-error.js');
+
+var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs);
+
+var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple');
+
+var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+var _utilsMergeOptionsJs = _dereq_('./utils/merge-options.js');
+
+var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);
+
+var _tracksTextTrackListConverterJs = _dereq_('./tracks/text-track-list-converter.js');
+
+var _tracksTextTrackListConverterJs2 = _interopRequireDefault(_tracksTextTrackListConverterJs);
+
+var _tracksAudioTrackListJs = _dereq_('./tracks/audio-track-list.js');
+
+var _tracksAudioTrackListJs2 = _interopRequireDefault(_tracksAudioTrackListJs);
+
+var _tracksVideoTrackListJs = _dereq_('./tracks/video-track-list.js');
+
+var _tracksVideoTrackListJs2 = _interopRequireDefault(_tracksVideoTrackListJs);
+
+// Include required child components (importing also registers them)
+
+var _techLoaderJs = _dereq_('./tech/loader.js');
+
+var _techLoaderJs2 = _interopRequireDefault(_techLoaderJs);
+
+var _posterImageJs = _dereq_('./poster-image.js');
+
+var _posterImageJs2 = _interopRequireDefault(_posterImageJs);
+
+var _tracksTextTrackDisplayJs = _dereq_('./tracks/text-track-display.js');
+
+var _tracksTextTrackDisplayJs2 = _interopRequireDefault(_tracksTextTrackDisplayJs);
+
+var _loadingSpinnerJs = _dereq_('./loading-spinner.js');
+
+var _loadingSpinnerJs2 = _interopRequireDefault(_loadingSpinnerJs);
+
+var _bigPlayButtonJs = _dereq_('./big-play-button.js');
+
+var _bigPlayButtonJs2 = _interopRequireDefault(_bigPlayButtonJs);
+
+var _controlBarControlBarJs = _dereq_('./control-bar/control-bar.js');
+
+var _controlBarControlBarJs2 = _interopRequireDefault(_controlBarControlBarJs);
+
+var _errorDisplayJs = _dereq_('./error-display.js');
+
+var _errorDisplayJs2 = _interopRequireDefault(_errorDisplayJs);
+
+var _tracksTextTrackSettingsJs = _dereq_('./tracks/text-track-settings.js');
+
+var _tracksTextTrackSettingsJs2 = _interopRequireDefault(_tracksTextTrackSettingsJs);
+
+var _modalDialog = _dereq_('./modal-dialog');
+
+var _modalDialog2 = _interopRequireDefault(_modalDialog);
+
+// Require html5 tech, at least for disposing the original video tag
+
+var _techTechJs = _dereq_('./tech/tech.js');
+
+var _techTechJs2 = _interopRequireDefault(_techTechJs);
+
+var _techHtml5Js = _dereq_('./tech/html5.js');
+
+var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js);
+
+/**
+ * An instance of the `Player` class is created when any of the Video.js setup methods are used to initialize a video.
+ * ```js
+ * var myPlayer = videojs('example_video_1');
+ * ```
+ * In the following example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
+ * ```html
+ *
+ * ```
+ * After an instance has been created it can be accessed globally using `Video('example_video_1')`.
+ *
+ * @param {Element} tag The original video tag used for configuring options
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends Component
+ * @class Player
+ */
+
+var Player = (function (_Component) {
+ _inherits(Player, _Component);
+
+ /**
+ * player's constructor function
+ *
+ * @constructs
+ * @method init
+ * @param {Element} tag The original video tag used for configuring options
+ * @param {Object=} options Player options
+ * @param {Function=} ready Ready callback function
+ */
+
+ function Player(tag, options, ready) {
+ var _this = this;
+
+ _classCallCheck(this, Player);
+
+ // Make sure tag ID exists
+ tag.id = tag.id || 'vjs_video_' + Guid.newGUID();
+
+ // Set Options
+ // The options argument overrides options set in the video tag
+ // which overrides globally set options.
+ // This latter part coincides with the load order
+ // (tag must exist before Player)
+ options = _objectAssign2['default'](Player.getTagSettings(tag), options);
+
+ // Delay the initialization of children because we need to set up
+ // player properties first, and can't use `this` before `super()`
+ options.initChildren = false;
+
+ // Same with creating the element
+ options.createEl = false;
+
+ // we don't want the player to report touch activity on itself
+ // see enableTouchActivity in Component
+ options.reportTouchActivity = false;
+
+ // Run base component initializing with new options
+ _Component.call(this, null, options, ready);
+
+ // if the global option object was accidentally blown away by
+ // someone, bail early with an informative error
+ if (!this.options_ || !this.options_.techOrder || !this.options_.techOrder.length) {
+ throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
+ }
+
+ this.tag = tag; // Store the original tag used to set options
+
+ // Store the tag attributes used to restore html5 element
+ this.tagAttributes = tag && Dom.getElAttributes(tag);
+
+ // Update current language
+ this.language(this.options_.language);
+
+ // Update Supported Languages
+ if (options.languages) {
+ (function () {
+ // Normalise player option languages to lowercase
+ var languagesToLower = {};
+
+ Object.getOwnPropertyNames(options.languages).forEach(function (name) {
+ languagesToLower[name.toLowerCase()] = options.languages[name];
+ });
+ _this.languages_ = languagesToLower;
+ })();
+ } else {
+ this.languages_ = Player.prototype.options_.languages;
+ }
+
+ // Cache for video property values.
+ this.cache_ = {};
+
+ // Set poster
+ this.poster_ = options.poster || '';
+
+ // Set controls
+ this.controls_ = !!options.controls;
+
+ // Original tag settings stored in options
+ // now remove immediately so native controls don't flash.
+ // May be turned back on by HTML5 tech if nativeControlsForTouch is true
+ tag.controls = false;
+
+ /*
+ * Store the internal state of scrubbing
+ *
+ * @private
+ * @return {Boolean} True if the user is scrubbing
+ */
+ this.scrubbing_ = false;
+
+ this.el_ = this.createEl();
+
+ // We also want to pass the original player options to each component and plugin
+ // as well so they don't need to reach back into the player for options later.
+ // We also need to do another copy of this.options_ so we don't end up with
+ // an infinite loop.
+ var playerOptionsCopy = _utilsMergeOptionsJs2['default'](this.options_);
+
+ // Load plugins
+ if (options.plugins) {
+ (function () {
+ var plugins = options.plugins;
+
+ Object.getOwnPropertyNames(plugins).forEach(function (name) {
+ if (typeof this[name] === 'function') {
+ this[name](plugins[name]);
+ } else {
+ _utilsLogJs2['default'].error('Unable to find plugin:', name);
+ }
+ }, _this);
+ })();
+ }
+
+ this.options_.playerOptions = playerOptionsCopy;
+
+ this.initChildren();
+
+ // Set isAudio based on whether or not an audio tag was used
+ this.isAudio(tag.nodeName.toLowerCase() === 'audio');
+
+ // Update controls className. Can't do this when the controls are initially
+ // set because the element doesn't exist yet.
+ if (this.controls()) {
+ this.addClass('vjs-controls-enabled');
+ } else {
+ this.addClass('vjs-controls-disabled');
+ }
+
+ // Set ARIA label and region role depending on player type
+ this.el_.setAttribute('role', 'region');
+ if (this.isAudio()) {
+ this.el_.setAttribute('aria-label', 'audio player');
+ } else {
+ this.el_.setAttribute('aria-label', 'video player');
+ }
+
+ if (this.isAudio()) {
+ this.addClass('vjs-audio');
+ }
+
+ if (this.flexNotSupported_()) {
+ this.addClass('vjs-no-flex');
+ }
+
+ // TODO: Make this smarter. Toggle user state between touching/mousing
+ // using events, since devices can have both touch and mouse events.
+ // if (browser.TOUCH_ENABLED) {
+ // this.addClass('vjs-touch-enabled');
+ // }
+
+ // iOS Safari has broken hover handling
+ if (!browser.IS_IOS) {
+ this.addClass('vjs-workinghover');
+ }
+
+ // Make player easily findable by ID
+ Player.players[this.id_] = this;
+
+ // When the player is first initialized, trigger activity so components
+ // like the control bar show themselves if needed
+ this.userActive(true);
+ this.reportUserActivity();
+ this.listenForUserActivity_();
+
+ this.on('fullscreenchange', this.handleFullscreenChange_);
+ this.on('stageclick', this.handleStageClick_);
+ }
+
+ /*
+ * Global player list
+ *
+ * @type {Object}
+ */
+
+ /**
+ * Destroys the video player and does any necessary cleanup
+ * ```js
+ * myPlayer.dispose();
+ * ```
+ * This is especially helpful if you are dynamically adding and removing videos
+ * to/from the DOM.
+ *
+ * @method dispose
+ */
+
+ Player.prototype.dispose = function dispose() {
+ this.trigger('dispose');
+ // prevent dispose from being called twice
+ this.off('dispose');
+
+ if (this.styleEl_ && this.styleEl_.parentNode) {
+ this.styleEl_.parentNode.removeChild(this.styleEl_);
+ }
+
+ // Kill reference to this player
+ Player.players[this.id_] = null;
+ if (this.tag && this.tag.player) {
+ this.tag.player = null;
+ }
+ if (this.el_ && this.el_.player) {
+ this.el_.player = null;
+ }
+
+ if (this.tech_) {
+ this.tech_.dispose();
+ }
+
+ _Component.prototype.dispose.call(this);
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Player.prototype.createEl = function createEl() {
+ var el = this.el_ = _Component.prototype.createEl.call(this, 'div');
+ var tag = this.tag;
+
+ // Remove width/height attrs from tag so CSS can make it 100% width/height
+ tag.removeAttribute('width');
+ tag.removeAttribute('height');
+
+ // Copy over all the attributes from the tag, including ID and class
+ // ID will now reference player box, not the video tag
+ var attrs = Dom.getElAttributes(tag);
+
+ Object.getOwnPropertyNames(attrs).forEach(function (attr) {
+ // workaround so we don't totally break IE7
+ // http://stackoverflow.com/questions/3653444/css-styles-not-applied-on-dynamic-elements-in-internet-explorer-7
+ if (attr === 'class') {
+ el.className = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ });
+
+ // Update tag id/class for use as HTML5 playback tech
+ // Might think we should do this after embedding in container so .vjs-tech class
+ // doesn't flash 100% width/height, but class only applies with .video-js parent
+ tag.playerId = tag.id;
+ tag.id += '_html5_api';
+ tag.className = 'vjs-tech';
+
+ // Make player findable on elements
+ tag.player = el.player = this;
+ // Default state of video is paused
+ this.addClass('vjs-paused');
+
+ // Add a style element in the player that we'll use to set the width/height
+ // of the player in a way that's still overrideable by CSS, just like the
+ // video element
+ if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) {
+ this.styleEl_ = stylesheet.createStyleElement('vjs-styles-dimensions');
+ var defaultsStyleEl = Dom.$('.vjs-styles-defaults');
+ var head = Dom.$('head');
+ head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
+ }
+
+ // Pass in the width/height/aspectRatio options which will update the style el
+ this.width(this.options_.width);
+ this.height(this.options_.height);
+ this.fluid(this.options_.fluid);
+ this.aspectRatio(this.options_.aspectRatio);
+
+ // Hide any links within the video/audio tag, because IE doesn't hide them completely.
+ var links = tag.getElementsByTagName('a');
+ for (var i = 0; i < links.length; i++) {
+ var linkEl = links.item(i);
+ Dom.addElClass(linkEl, 'vjs-hidden');
+ linkEl.setAttribute('hidden', 'hidden');
+ }
+
+ // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
+ // keep track of the original for later so we can know if the source originally failed
+ tag.initNetworkState_ = tag.networkState;
+
+ // Wrap video tag in div (el/box) container
+ if (tag.parentNode) {
+ tag.parentNode.insertBefore(el, tag);
+ }
+
+ // insert the tag as the first child of the player element
+ // then manually add it to the children array so that this.addChild
+ // will work properly for other components
+ Dom.insertElFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
+ this.children_.unshift(tag);
+
+ this.el_ = el;
+
+ return el;
+ };
+
+ /**
+ * Get/set player width
+ *
+ * @param {Number=} value Value for width
+ * @return {Number} Width when getting
+ * @method width
+ */
+
+ Player.prototype.width = function width(value) {
+ return this.dimension('width', value);
+ };
+
+ /**
+ * Get/set player height
+ *
+ * @param {Number=} value Value for height
+ * @return {Number} Height when getting
+ * @method height
+ */
+
+ Player.prototype.height = function height(value) {
+ return this.dimension('height', value);
+ };
+
+ /**
+ * Get/set dimension for player
+ *
+ * @param {String} dimension Either width or height
+ * @param {Number=} value Value for dimension
+ * @return {Component}
+ * @method dimension
+ */
+
+ Player.prototype.dimension = function dimension(_dimension, value) {
+ var privDimension = _dimension + '_';
+
+ if (value === undefined) {
+ return this[privDimension] || 0;
+ }
+
+ if (value === '') {
+ // If an empty string is given, reset the dimension to be automatic
+ this[privDimension] = undefined;
+ } else {
+ var parsedVal = parseFloat(value);
+
+ if (isNaN(parsedVal)) {
+ _utilsLogJs2['default'].error('Improper value "' + value + '" supplied for for ' + _dimension);
+ return this;
+ }
+
+ this[privDimension] = parsedVal;
+ }
+
+ this.updateStyleEl_();
+ return this;
+ };
+
+ /**
+ * Add/remove the vjs-fluid class
+ *
+ * @param {Boolean} bool Value of true adds the class, value of false removes the class
+ * @method fluid
+ */
+
+ Player.prototype.fluid = function fluid(bool) {
+ if (bool === undefined) {
+ return !!this.fluid_;
+ }
+
+ this.fluid_ = !!bool;
+
+ if (bool) {
+ this.addClass('vjs-fluid');
+ } else {
+ this.removeClass('vjs-fluid');
+ }
+ };
+
+ /**
+ * Get/Set the aspect ratio
+ *
+ * @param {String=} ratio Aspect ratio for player
+ * @return aspectRatio
+ * @method aspectRatio
+ */
+
+ Player.prototype.aspectRatio = function aspectRatio(ratio) {
+ if (ratio === undefined) {
+ return this.aspectRatio_;
+ }
+
+ // Check for width:height format
+ if (!/^\d+\:\d+$/.test(ratio)) {
+ throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
+ }
+ this.aspectRatio_ = ratio;
+
+ // We're assuming if you set an aspect ratio you want fluid mode,
+ // because in fixed mode you could calculate width and height yourself.
+ this.fluid(true);
+
+ this.updateStyleEl_();
+ };
+
+ /**
+ * Update styles of the player element (height, width and aspect ratio)
+ *
+ * @method updateStyleEl_
+ */
+
+ Player.prototype.updateStyleEl_ = function updateStyleEl_() {
+ if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE === true) {
+ var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
+ var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
+ var techEl = this.tech_ && this.tech_.el();
+
+ if (techEl) {
+ if (_width >= 0) {
+ techEl.width = _width;
+ }
+ if (_height >= 0) {
+ techEl.height = _height;
+ }
+ }
+
+ return;
+ }
+
+ var width = undefined;
+ var height = undefined;
+ var aspectRatio = undefined;
+ var idClass = undefined;
+
+ // The aspect ratio is either used directly or to calculate width and height.
+ if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
+ // Use any aspectRatio that's been specifically set
+ aspectRatio = this.aspectRatio_;
+ } else if (this.videoWidth()) {
+ // Otherwise try to get the aspect ratio from the video metadata
+ aspectRatio = this.videoWidth() + ':' + this.videoHeight();
+ } else {
+ // Or use a default. The video element's is 2:1, but 16:9 is more common.
+ aspectRatio = '16:9';
+ }
+
+ // Get the ratio as a decimal we can use to calculate dimensions
+ var ratioParts = aspectRatio.split(':');
+ var ratioMultiplier = ratioParts[1] / ratioParts[0];
+ var minRatio = Math.min(this.el_.offsetWidth / this.videoWidth(), this.el_.offsetHeight / this.videoHeight());
+
+ width = minRatio * this.videoWidth() || this.el_.offsetWidth;
+ height = minRatio * this.videoHeight || this.el_.offsetHeight;
+
+ // if (this.width_ !== undefined) {
+ // // Use any width that's been specifically set
+ // width = this.width_;
+ // } else if (this.height_ !== undefined) {
+ // // Or calulate the width from the aspect ratio if a height has been set
+ // width = this.height_ / ratioMultiplier;
+ // } else {
+ // // Or use the video's metadata, or use the video el's default of 300
+ // width = this.videoWidth() || 300;
+ // }
+ //
+ // if (this.height_ !== undefined) {
+ // // Use any height that's been specifically set
+ // height = this.height_;
+ // } else {
+ // // Otherwise calculate the height from the ratio and the width
+ // height = width * ratioMultiplier;
+ // }
+ //
+ // Ensure the CSS class is valid by starting with an alpha character
+ if (/^[^a-zA-Z]/.test(this.id())) {
+ idClass = 'dimensions-' + this.id();
+ } else {
+ idClass = this.id() + '-dimensions';
+ }
+
+ // Ensure the right class is still on the player for the style element
+ this.addClass(idClass);
+
+ stylesheet.setTextContent(this.styleEl_, '\n .' + idClass + ' {\n width: 100%;\n height: 100%;\n }\n\n .' + idClass + '.vjs-fluid {\n padding-top: ' + ratioMultiplier * 100 + '%;\n }\n ');
+ };
+
+ /**
+ * Load the Media Playback Technology (tech)
+ * Load/Create an instance of playback technology including element and API methods
+ * And append playback element in player div.
+ *
+ * @param {String} techName Name of the playback technology
+ * @param {String} source Video source
+ * @method loadTech_
+ * @private
+ */
+
+ Player.prototype.loadTech_ = function loadTech_(techName, source) {
+
+ // Pause and remove current playback technology
+ if (this.tech_) {
+ this.unloadTech_();
+ }
+
+ // get rid of the HTML5 video tag as soon as we are using another tech
+ if (techName !== 'Html5' && this.tag) {
+ _techTechJs2['default'].getTech('Html5').disposeMediaElement(this.tag);
+ this.tag.player = null;
+ this.tag = null;
+ }
+
+ this.techName_ = techName;
+
+ // Turn off API access because we're loading a new tech that might load asynchronously
+ this.isReady_ = false;
+
+ // Grab tech-specific options from player options and add source and parent element to use.
+ var techOptions = _objectAssign2['default']({
+ 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
+ 'source': source,
+ 'playerId': this.id(),
+ 'techId': this.id() + '_' + techName + '_api',
+ 'videoTracks': this.videoTracks_,
+ 'textTracks': this.textTracks_,
+ 'audioTracks': this.audioTracks_,
+ 'autoplay': this.options_.autoplay,
+ 'preload': this.options_.preload,
+ 'loop': this.options_.loop,
+ 'muted': this.options_.muted,
+ 'poster': this.poster(),
+ 'language': this.language(),
+ 'vtt.js': this.options_['vtt.js']
+ }, this.options_[techName.toLowerCase()]);
+
+ if (this.tag) {
+ techOptions.tag = this.tag;
+ }
+
+ if (source) {
+ this.currentType_ = source.type;
+ if (source.src === this.cache_.src && this.cache_.currentTime > 0) {
+ techOptions.startTime = this.cache_.currentTime;
+ }
+
+ this.cache_.src = source.src;
+ }
+
+ // Initialize tech instance
+ var techComponent = _techTechJs2['default'].getTech(techName);
+ // Support old behavior of techs being registered as components.
+ // Remove once that deprecated behavior is removed.
+ if (!techComponent) {
+ techComponent = _componentJs2['default'].getComponent(techName);
+ }
+ this.tech_ = new techComponent(techOptions);
+
+ // player.triggerReady is always async, so don't need this to be async
+ this.tech_.ready(Fn.bind(this, this.handleTechReady_), true);
+
+ _tracksTextTrackListConverterJs2['default'].jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
+
+ // Listen to all HTML5-defined events and trigger them on the player
+ this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
+ this.on(this.tech_, 'waiting', this.handleTechWaiting_);
+ this.on(this.tech_, 'canplay', this.handleTechCanPlay_);
+ this.on(this.tech_, 'canplaythrough', this.handleTechCanPlayThrough_);
+ this.on(this.tech_, 'playing', this.handleTechPlaying_);
+ this.on(this.tech_, 'ended', this.handleTechEnded_);
+ this.on(this.tech_, 'seeking', this.handleTechSeeking_);
+ this.on(this.tech_, 'seeked', this.handleTechSeeked_);
+ this.on(this.tech_, 'play', this.handleTechPlay_);
+ this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
+ this.on(this.tech_, 'pause', this.handleTechPause_);
+ this.on(this.tech_, 'progress', this.handleTechProgress_);
+ this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
+ this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
+ this.on(this.tech_, 'error', this.handleTechError_);
+ this.on(this.tech_, 'suspend', this.handleTechSuspend_);
+ this.on(this.tech_, 'abort', this.handleTechAbort_);
+ this.on(this.tech_, 'emptied', this.handleTechEmptied_);
+ this.on(this.tech_, 'stalled', this.handleTechStalled_);
+ this.on(this.tech_, 'loadedmetadata', this.handleTechLoadedMetaData_);
+ this.on(this.tech_, 'loadeddata', this.handleTechLoadedData_);
+ this.on(this.tech_, 'timeupdate', this.handleTechTimeUpdate_);
+ this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
+ this.on(this.tech_, 'volumechange', this.handleTechVolumeChange_);
+ this.on(this.tech_, 'texttrackchange', this.handleTechTextTrackChange_);
+ this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
+ this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
+
+ this.usingNativeControls(this.techGet_('controls'));
+
+ if (this.controls() && !this.usingNativeControls()) {
+ this.addTechControlsListeners_();
+ }
+
+ // Add the tech element in the DOM if it was not already there
+ // Make sure to not insert the original video element if using Html5
+ if (this.tech_.el().parentNode !== this.el() && (techName !== 'Html5' || !this.tag)) {
+ Dom.insertElFirst(this.tech_.el(), this.el());
+ }
+
+ // Get rid of the original video tag reference after the first tech is loaded
+ if (this.tag) {
+ this.tag.player = null;
+ this.tag = null;
+ }
+ };
+
+ /**
+ * Unload playback technology
+ *
+ * @method unloadTech_
+ * @private
+ */
+
+ Player.prototype.unloadTech_ = function unloadTech_() {
+ // Save the current text tracks so that we can reuse the same text tracks with the next tech
+ this.videoTracks_ = this.videoTracks();
+ this.textTracks_ = this.textTracks();
+ this.audioTracks_ = this.audioTracks();
+ this.textTracksJson_ = _tracksTextTrackListConverterJs2['default'].textTracksToJson(this.tech_);
+
+ this.isReady_ = false;
+
+ this.tech_.dispose();
+
+ this.tech_ = false;
+ };
+
+ /**
+ * Return a reference to the current tech.
+ * It will only return a reference to the tech if given an object with the
+ * `IWillNotUseThisInPlugins` property on it. This is try and prevent misuse
+ * of techs by plugins.
+ *
+ * @param {Object}
+ * @return {Object} The Tech
+ * @method tech
+ */
+
+ Player.prototype.tech = function tech(safety) {
+ if (safety && safety.IWillNotUseThisInPlugins) {
+ return this.tech_;
+ }
+ var errorText = '\n Please make sure that you are not using this inside of a plugin.\n To disable this alert and error, please pass in an object with\n `IWillNotUseThisInPlugins` to the `tech` method. See\n https://github.com/videojs/video.js/issues/2617 for more info.\n ';
+ _globalWindow2['default'].alert(errorText);
+ throw new Error(errorText);
+ };
+
+ /**
+ * Set up click and touch listeners for the playback element
+ *
+ * On desktops, a click on the video itself will toggle playback,
+ * on a mobile device a click on the video toggles controls.
+ * (toggling controls is done by toggling the user state between active and
+ * inactive)
+ * A tap can signal that a user has become active, or has become inactive
+ * e.g. a quick tap on an iPhone movie should reveal the controls. Another
+ * quick tap should hide them again (signaling the user is in an inactive
+ * viewing state)
+ * In addition to this, we still want the user to be considered inactive after
+ * a few seconds of inactivity.
+ * Note: the only part of iOS interaction we can't mimic with this setup
+ * is a touch and hold on the video element counting as activity in order to
+ * keep the controls showing, but that shouldn't be an issue. A touch and hold
+ * on any controls will still keep the user active
+ *
+ * @private
+ * @method addTechControlsListeners_
+ */
+
+ Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
+ // Make sure to remove all the previous listeners in case we are called multiple times.
+ this.removeTechControlsListeners_();
+
+ // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
+ // trigger mousedown/up.
+ // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
+ // Any touch events are set to block the mousedown event from happening
+ this.on(this.tech_, 'mousedown', this.handleTechClick_);
+
+ // If the controls were hidden we don't want that to change without a tap event
+ // so we'll check if the controls were already showing before reporting user
+ // activity
+ this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
+ this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
+ this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);
+
+ // The tap listener needs to come after the touchend listener because the tap
+ // listener cancels out any reportedUserActivity when setting userActive(false)
+ this.on(this.tech_, 'tap', this.handleTechTap_);
+ };
+
+ /**
+ * Remove the listeners used for click and tap controls. This is needed for
+ * toggling to controls disabled, where a tap/touch should do nothing.
+ *
+ * @method removeTechControlsListeners_
+ * @private
+ */
+
+ Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
+ // We don't want to just use `this.off()` because there might be other needed
+ // listeners added by techs that extend this.
+ this.off(this.tech_, 'tap', this.handleTechTap_);
+ this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
+ this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
+ this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
+ this.off(this.tech_, 'mousedown', this.handleTechClick_);
+ };
+
+ /**
+ * Player waits for the tech to be ready
+ *
+ * @method handleTechReady_
+ * @private
+ */
+
+ Player.prototype.handleTechReady_ = function handleTechReady_() {
+ this.triggerReady();
+
+ // Keep the same volume as before
+ if (this.cache_.volume) {
+ this.techCall_('setVolume', this.cache_.volume);
+ }
+
+ // Look if the tech found a higher resolution poster while loading
+ this.handleTechPosterChange_();
+
+ // Update the duration if available
+ this.handleTechDurationChange_();
+
+ // Chrome and Safari both have issues with autoplay.
+ // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
+ // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
+ // This fixes both issues. Need to wait for API, so it updates displays correctly
+ if (this.src() && this.tag && this.options_.autoplay && this.paused()) {
+ try {
+ delete this.tag.poster; // Chrome Fix. Fixed in Chrome v16.
+ } catch (e) {
+ _utilsLogJs2['default']('deleting tag.poster throws in some browsers', e);
+ }
+ this.play();
+ }
+ };
+
+ /**
+ * Fired when the user agent begins looking for media data
+ *
+ * @private
+ * @method handleTechLoadStart_
+ */
+
+ Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
+ // TODO: Update to use `emptied` event instead. See #1277.
+
+ this.removeClass('vjs-ended');
+
+ // reset the error state
+ this.error(null);
+
+ // If it's already playing we want to trigger a firstplay event now.
+ // The firstplay event relies on both the play and loadstart events
+ // which can happen in any order for a new source
+ if (!this.paused()) {
+ this.trigger('loadstart');
+ this.trigger('firstplay');
+ } else {
+ // reset the hasStarted state
+ this.hasStarted(false);
+ this.trigger('loadstart');
+ }
+ };
+
+ /**
+ * Add/remove the vjs-has-started class
+ *
+ * @param {Boolean} hasStarted The value of true adds the class the value of false remove the class
+ * @return {Boolean} Boolean value if has started
+ * @private
+ * @method hasStarted
+ */
+
+ Player.prototype.hasStarted = function hasStarted(_hasStarted) {
+ if (_hasStarted !== undefined) {
+ // only update if this is a new value
+ if (this.hasStarted_ !== _hasStarted) {
+ this.hasStarted_ = _hasStarted;
+ if (_hasStarted) {
+ this.addClass('vjs-has-started');
+ // trigger the firstplay event if this newly has played
+ this.trigger('firstplay');
+ } else {
+ this.removeClass('vjs-has-started');
+ }
+ }
+ return this;
+ }
+ return !!this.hasStarted_;
+ };
+
+ /**
+ * Fired whenever the media begins or resumes playback
+ *
+ * @private
+ * @method handleTechPlay_
+ */
+
+ Player.prototype.handleTechPlay_ = function handleTechPlay_() {
+ this.removeClass('vjs-ended');
+ this.removeClass('vjs-paused');
+ this.addClass('vjs-playing');
+
+ // hide the poster when the user hits play
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play
+ this.hasStarted(true);
+
+ this.trigger('play');
+ };
+
+ /**
+ * Fired whenever the media begins waiting
+ *
+ * @private
+ * @method handleTechWaiting_
+ */
+
+ Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
+ var _this2 = this;
+
+ this.addClass('vjs-waiting');
+ this.trigger('waiting');
+ this.one('timeupdate', function () {
+ return _this2.removeClass('vjs-waiting');
+ });
+ };
+
+ /**
+ * A handler for events that signal that waiting has ended
+ * which is not consistent between browsers. See #1351
+ *
+ * @private
+ * @method handleTechCanPlay_
+ */
+
+ Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
+ this.removeClass('vjs-waiting');
+ this.trigger('canplay');
+ };
+
+ /**
+ * A handler for events that signal that waiting has ended
+ * which is not consistent between browsers. See #1351
+ *
+ * @private
+ * @method handleTechCanPlayThrough_
+ */
+
+ Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
+ this.removeClass('vjs-waiting');
+ this.trigger('canplaythrough');
+ };
+
+ /**
+ * A handler for events that signal that waiting has ended
+ * which is not consistent between browsers. See #1351
+ *
+ * @private
+ * @method handleTechPlaying_
+ */
+
+ Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
+ this.removeClass('vjs-waiting');
+ this.trigger('playing');
+ };
+
+ /**
+ * Fired whenever the player is jumping to a new time
+ *
+ * @private
+ * @method handleTechSeeking_
+ */
+
+ Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
+ this.addClass('vjs-seeking');
+ this.trigger('seeking');
+ };
+
+ /**
+ * Fired when the player has finished jumping to a new time
+ *
+ * @private
+ * @method handleTechSeeked_
+ */
+
+ Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
+ this.removeClass('vjs-seeking');
+ this.trigger('seeked');
+ };
+
+ /**
+ * Fired the first time a video is played
+ * Not part of the HLS spec, and we're not sure if this is the best
+ * implementation yet, so use sparingly. If you don't have a reason to
+ * prevent playback, use `myPlayer.one('play');` instead.
+ *
+ * @private
+ * @method handleTechFirstPlay_
+ */
+
+ Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
+ //If the first starttime attribute is specified
+ //then we will start at the given offset in seconds
+ if (this.options_.starttime) {
+ this.currentTime(this.options_.starttime);
+ }
+
+ this.addClass('vjs-has-started');
+ this.trigger('firstplay');
+ };
+
+ /**
+ * Fired whenever the media has been paused
+ *
+ * @private
+ * @method handleTechPause_
+ */
+
+ Player.prototype.handleTechPause_ = function handleTechPause_() {
+ this.removeClass('vjs-playing');
+ this.addClass('vjs-paused');
+ this.trigger('pause');
+ };
+
+ /**
+ * Fired while the user agent is downloading media data
+ *
+ * @private
+ * @method handleTechProgress_
+ */
+
+ Player.prototype.handleTechProgress_ = function handleTechProgress_() {
+ this.trigger('progress');
+ };
+
+ /**
+ * Fired when the end of the media resource is reached (currentTime == duration)
+ *
+ * @private
+ * @method handleTechEnded_
+ */
+
+ Player.prototype.handleTechEnded_ = function handleTechEnded_() {
+ this.addClass('vjs-ended');
+ if (this.options_.loop) {
+ this.currentTime(0);
+ this.play();
+ } else if (!this.paused()) {
+ this.pause();
+ }
+
+ this.trigger('ended');
+ };
+
+ /**
+ * Fired when the duration of the media resource is first known or changed
+ *
+ * @private
+ * @method handleTechDurationChange_
+ */
+
+ Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
+ this.duration(this.techGet_('duration'));
+ };
+
+ /**
+ * Handle a click on the media element to play/pause
+ *
+ * @param {Object=} event Event object
+ * @private
+ * @method handleTechClick_
+ */
+
+ Player.prototype.handleTechClick_ = function handleTechClick_(event) {
+ // We're using mousedown to detect clicks thanks to Flash, but mousedown
+ // will also be triggered with right-clicks, so we need to prevent that
+ if (event.button !== 0) return;
+
+ // When controls are disabled a click should not toggle playback because
+ // the click is considered a control
+ if (this.controls()) {
+ if (this.paused()) {
+ this.play();
+ } else {
+ this.pause();
+ }
+ }
+ };
+
+ /**
+ * Handle a tap on the media element. It will toggle the user
+ * activity state, which hides and shows the controls.
+ *
+ * @private
+ * @method handleTechTap_
+ */
+
+ Player.prototype.handleTechTap_ = function handleTechTap_() {
+ this.userActive(!this.userActive());
+ };
+
+ /**
+ * Handle touch to start
+ *
+ * @private
+ * @method handleTechTouchStart_
+ */
+
+ Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
+ this.userWasActive = this.userActive();
+ };
+
+ /**
+ * Handle touch to move
+ *
+ * @private
+ * @method handleTechTouchMove_
+ */
+
+ Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
+ if (this.userWasActive) {
+ this.reportUserActivity();
+ }
+ };
+
+ /**
+ * Handle touch to end
+ *
+ * @private
+ * @method handleTechTouchEnd_
+ */
+
+ Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
+ // Stop the mouse events from also happening
+ event.preventDefault();
+ };
+
+ /**
+ * Fired when the player switches in or out of fullscreen mode
+ *
+ * @private
+ * @method handleFullscreenChange_
+ */
+
+ Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
+ if (this.isFullscreen()) {
+ this.addClass('vjs-fullscreen');
+ } else {
+ this.removeClass('vjs-fullscreen');
+ }
+ };
+
+ /**
+ * native click events on the SWF aren't triggered on IE11, Win8.1RT
+ * use stageclick events triggered from inside the SWF instead
+ *
+ * @private
+ * @method handleStageClick_
+ */
+
+ Player.prototype.handleStageClick_ = function handleStageClick_() {
+ this.reportUserActivity();
+ };
+
+ /**
+ * Handle Tech Fullscreen Change
+ *
+ * @private
+ * @method handleTechFullscreenChange_
+ */
+
+ Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
+ if (data) {
+ this.isFullscreen(data.isFullscreen);
+ }
+ this.onFullscreenChange();
+ // this.trigger('fullscreenchange');
+ };
+
+ Player.prototype.onFullscreenChange = function onFullscreenChange() {
+ this.trigger('fullscreenchange', { 'isFullScreen': this.isFullscreen() });
+ screen.orientation.lock('landscape-primary');
+ };
+
+ /**
+ * Fires when an error occurred during the loading of an audio/video
+ *
+ * @private
+ * @method handleTechError_
+ */
+
+ Player.prototype.handleTechError_ = function handleTechError_() {
+ var error = this.tech_.error();
+ this.error(error);
+ };
+
+ /**
+ * Fires when the browser is intentionally not getting media data
+ *
+ * @private
+ * @method handleTechSuspend_
+ */
+
+ Player.prototype.handleTechSuspend_ = function handleTechSuspend_() {
+ this.trigger('suspend');
+ };
+
+ /**
+ * Fires when the loading of an audio/video is aborted
+ *
+ * @private
+ * @method handleTechAbort_
+ */
+
+ Player.prototype.handleTechAbort_ = function handleTechAbort_() {
+ this.trigger('abort');
+ };
+
+ /**
+ * Fires when the current playlist is empty
+ *
+ * @private
+ * @method handleTechEmptied_
+ */
+
+ Player.prototype.handleTechEmptied_ = function handleTechEmptied_() {
+ this.trigger('emptied');
+ };
+
+ /**
+ * Fires when the browser is trying to get media data, but data is not available
+ *
+ * @private
+ * @method handleTechStalled_
+ */
+
+ Player.prototype.handleTechStalled_ = function handleTechStalled_() {
+ this.trigger('stalled');
+ };
+
+ /**
+ * Fires when the browser has loaded meta data for the audio/video
+ *
+ * @private
+ * @method handleTechLoadedMetaData_
+ */
+
+ Player.prototype.handleTechLoadedMetaData_ = function handleTechLoadedMetaData_() {
+ this.trigger('loadedmetadata');
+ };
+
+ /**
+ * Fires when the browser has loaded the current frame of the audio/video
+ *
+ * @private
+ * @method handleTechLoadedData_
+ */
+
+ Player.prototype.handleTechLoadedData_ = function handleTechLoadedData_() {
+ this.trigger('loadeddata');
+ };
+
+ /**
+ * Fires when the current playback position has changed
+ *
+ * @private
+ * @method handleTechTimeUpdate_
+ */
+
+ Player.prototype.handleTechTimeUpdate_ = function handleTechTimeUpdate_() {
+ this.trigger('timeupdate');
+ };
+
+ /**
+ * Fires when the playing speed of the audio/video is changed
+ *
+ * @private
+ * @method handleTechRateChange_
+ */
+
+ Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
+ this.trigger('ratechange');
+ };
+
+ /**
+ * Fires when the volume has been changed
+ *
+ * @private
+ * @method handleTechVolumeChange_
+ */
+
+ Player.prototype.handleTechVolumeChange_ = function handleTechVolumeChange_() {
+ this.trigger('volumechange');
+ };
+
+ /**
+ * Fires when the text track has been changed
+ *
+ * @private
+ * @method handleTechTextTrackChange_
+ */
+
+ Player.prototype.handleTechTextTrackChange_ = function handleTechTextTrackChange_() {
+ this.trigger('texttrackchange');
+ };
+
+ /**
+ * Get object for cached values.
+ *
+ * @return {Object}
+ * @method getCache
+ */
+
+ Player.prototype.getCache = function getCache() {
+ return this.cache_;
+ };
+
+ /**
+ * Pass values to the playback tech
+ *
+ * @param {String=} method Method
+ * @param {Object=} arg Argument
+ * @private
+ * @method techCall_
+ */
+
+ Player.prototype.techCall_ = function techCall_(method, arg) {
+ // If it's not ready yet, call method when it is
+ if (this.tech_ && !this.tech_.isReady_) {
+ this.tech_.ready(function () {
+ this[method](arg);
+ }, true);
+
+ // Otherwise call method now
+ } else {
+ try {
+ this.tech_[method](arg);
+ } catch (e) {
+ _utilsLogJs2['default'](e);
+ throw e;
+ }
+ }
+ };
+
+ /**
+ * Get calls can't wait for the tech, and sometimes don't need to.
+ *
+ * @param {String} method Tech method
+ * @return {Method}
+ * @private
+ * @method techGet_
+ */
+
+ Player.prototype.techGet_ = function techGet_(method) {
+ if (this.tech_ && this.tech_.isReady_) {
+
+ // Flash likes to die and reload when you hide or reposition it.
+ // In these cases the object methods go away and we get errors.
+ // When that happens we'll catch the errors and inform tech that it's not ready any more.
+ try {
+ return this.tech_[method]();
+ } catch (e) {
+ // When building additional tech libs, an expected method may not be defined yet
+ if (this.tech_[method] === undefined) {
+ _utilsLogJs2['default']('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
+ } else {
+ // When a method isn't available on the object it throws a TypeError
+ if (e.name === 'TypeError') {
+ _utilsLogJs2['default']('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
+ this.tech_.isReady_ = false;
+ } else {
+ _utilsLogJs2['default'](e);
+ }
+ }
+ throw e;
+ }
+ }
+
+ return;
+ };
+
+ /**
+ * start media playback
+ * ```js
+ * myPlayer.play();
+ * ```
+ *
+ * @return {Player} self
+ * @method play
+ */
+
+ Player.prototype.play = function play() {
+ // Only calls the tech's play if we already have a src loaded
+ if (this.src() || this.currentSrc()) {
+ this.techCall_('play');
+ } else {
+ this.tech_.one('loadstart', function () {
+ this.play();
+ });
+ }
+
+ return this;
+ };
+
+ /**
+ * Pause the video playback
+ * ```js
+ * myPlayer.pause();
+ * ```
+ *
+ * @return {Player} self
+ * @method pause
+ */
+
+ Player.prototype.pause = function pause() {
+ this.techCall_('pause');
+ return this;
+ };
+
+ /**
+ * Check if the player is paused
+ * ```js
+ * var isPaused = myPlayer.paused();
+ * var isPlaying = !myPlayer.paused();
+ * ```
+ *
+ * @return {Boolean} false if the media is currently playing, or true otherwise
+ * @method paused
+ */
+
+ Player.prototype.paused = function paused() {
+ // The initial state of paused should be true (in Safari it's actually false)
+ return this.techGet_('paused') === false ? false : true;
+ };
+
+ /**
+ * Returns whether or not the user is "scrubbing". Scrubbing is when the user
+ * has clicked the progress bar handle and is dragging it along the progress bar.
+ *
+ * @param {Boolean} isScrubbing True/false the user is scrubbing
+ * @return {Boolean} The scrubbing status when getting
+ * @return {Object} The player when setting
+ * @method scrubbing
+ */
+
+ Player.prototype.scrubbing = function scrubbing(isScrubbing) {
+ if (isScrubbing !== undefined) {
+ this.scrubbing_ = !!isScrubbing;
+
+ if (isScrubbing) {
+ this.addClass('vjs-scrubbing');
+ } else {
+ this.removeClass('vjs-scrubbing');
+ }
+
+ return this;
+ }
+
+ return this.scrubbing_;
+ };
+
+ /**
+ * Get or set the current time (in seconds)
+ * ```js
+ * // get
+ * var whereYouAt = myPlayer.currentTime();
+ * // set
+ * myPlayer.currentTime(120); // 2 minutes into the video
+ * ```
+ *
+ * @param {Number|String=} seconds The time to seek to
+ * @return {Number} The time in seconds, when not setting
+ * @return {Player} self, when the current time is set
+ * @method currentTime
+ */
+
+ Player.prototype.currentTime = function currentTime(seconds) {
+ if (seconds !== undefined) {
+
+ this.techCall_('setCurrentTime', seconds);
+
+ return this;
+ }
+
+ // cache last currentTime and return. default to 0 seconds
+ //
+ // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
+ // currentTime when scrubbing, but may not provide much performance benefit afterall.
+ // Should be tested. Also something has to read the actual current time or the cache will
+ // never get updated.
+ return this.cache_.currentTime = this.techGet_('currentTime') || 0;
+ };
+
+ /**
+ * Get the length in time of the video in seconds
+ * ```js
+ * var lengthOfVideo = myPlayer.duration();
+ * ```
+ * **NOTE**: The video must have started loading before the duration can be
+ * known, and in the case of Flash, may not be known until the video starts
+ * playing.
+ *
+ * @param {Number} seconds Duration when setting
+ * @return {Number} The duration of the video in seconds when getting
+ * @method duration
+ */
+
+ Player.prototype.duration = function duration(seconds) {
+ if (seconds === undefined) {
+ return this.cache_.duration || 0;
+ }
+
+ seconds = parseFloat(seconds) || 0;
+
+ // Standardize on Inifity for signaling video is live
+ if (seconds < 0) {
+ seconds = Infinity;
+ }
+
+ if (seconds !== this.cache_.duration) {
+ // Cache the last set value for optimized scrubbing (esp. Flash)
+ this.cache_.duration = seconds;
+
+ if (seconds === Infinity) {
+ this.addClass('vjs-live');
+ } else {
+ this.removeClass('vjs-live');
+ }
+
+ this.trigger('durationchange');
+ }
+
+ return this;
+ };
+
+ /**
+ * Calculates how much time is left.
+ * ```js
+ * var timeLeft = myPlayer.remainingTime();
+ * ```
+ * Not a native video element function, but useful
+ *
+ * @return {Number} The time remaining in seconds
+ * @method remainingTime
+ */
+
+ Player.prototype.remainingTime = function remainingTime() {
+ return this.duration() - this.currentTime();
+ };
+
+ // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
+ // Buffered returns a timerange object.
+ // Kind of like an array of portions of the video that have been downloaded.
+
+ /**
+ * Get a TimeRange object with the times of the video that have been downloaded
+ * If you just want the percent of the video that's been downloaded,
+ * use bufferedPercent.
+ * ```js
+ * // Number of different ranges of time have been buffered. Usually 1.
+ * numberOfRanges = bufferedTimeRange.length,
+ * // Time in seconds when the first range starts. Usually 0.
+ * firstRangeStart = bufferedTimeRange.start(0),
+ * // Time in seconds when the first range ends
+ * firstRangeEnd = bufferedTimeRange.end(0),
+ * // Length in seconds of the first time range
+ * firstRangeLength = firstRangeEnd - firstRangeStart;
+ * ```
+ *
+ * @return {Object} A mock TimeRange object (following HTML spec)
+ * @method buffered
+ */
+
+ Player.prototype.buffered = function buffered() {
+ var buffered = this.techGet_('buffered');
+
+ if (!buffered || !buffered.length) {
+ buffered = _utilsTimeRangesJs.createTimeRange(0, 0);
+ }
+
+ return buffered;
+ };
+
+ /**
+ * Get the percent (as a decimal) of the video that's been downloaded
+ * ```js
+ * var howMuchIsDownloaded = myPlayer.bufferedPercent();
+ * ```
+ * 0 means none, 1 means all.
+ * (This method isn't in the HTML5 spec, but it's very convenient)
+ *
+ * @return {Number} A decimal between 0 and 1 representing the percent
+ * @method bufferedPercent
+ */
+
+ Player.prototype.bufferedPercent = function bufferedPercent() {
+ return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration());
+ };
+
+ /**
+ * Get the ending time of the last buffered time range
+ * This is used in the progress bar to encapsulate all time ranges.
+ *
+ * @return {Number} The end of the last buffered time range
+ * @method bufferedEnd
+ */
+
+ Player.prototype.bufferedEnd = function bufferedEnd() {
+ var buffered = this.buffered(),
+ duration = this.duration(),
+ end = buffered.end(buffered.length - 1);
+
+ if (end > duration) {
+ end = duration;
+ }
+
+ return end;
+ };
+
+ /**
+ * Get or set the current volume of the media
+ * ```js
+ * // get
+ * var howLoudIsIt = myPlayer.volume();
+ * // set
+ * myPlayer.volume(0.5); // Set volume to half
+ * ```
+ * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
+ *
+ * @param {Number} percentAsDecimal The new volume as a decimal percent
+ * @return {Number} The current volume when getting
+ * @return {Player} self when setting
+ * @method volume
+ */
+
+ Player.prototype.volume = function volume(percentAsDecimal) {
+ var vol = undefined;
+
+ if (percentAsDecimal !== undefined) {
+ vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
+ this.cache_.volume = vol;
+ this.techCall_('setVolume', vol);
+
+ return this;
+ }
+
+ // Default to 1 when returning current volume.
+ vol = parseFloat(this.techGet_('volume'));
+ return isNaN(vol) ? 1 : vol;
+ };
+
+ /**
+ * Get the current muted state, or turn mute on or off
+ * ```js
+ * // get
+ * var isVolumeMuted = myPlayer.muted();
+ * // set
+ * myPlayer.muted(true); // mute the volume
+ * ```
+ *
+ * @param {Boolean=} muted True to mute, false to unmute
+ * @return {Boolean} True if mute is on, false if not when getting
+ * @return {Player} self when setting mute
+ * @method muted
+ */
+
+ Player.prototype.muted = function muted(_muted) {
+ if (_muted !== undefined) {
+ this.techCall_('setMuted', _muted);
+ return this;
+ }
+ return this.techGet_('muted') || false; // Default to false
+ };
+
+ // Check if current tech can support native fullscreen
+ // (e.g. with built in controls like iOS, so not our flash swf)
+ /**
+ * Check to see if fullscreen is supported
+ *
+ * @return {Boolean}
+ * @method supportsFullScreen
+ */
+
+ Player.prototype.supportsFullScreen = function supportsFullScreen() {
+ return this.techGet_('supportsFullScreen') || false;
+ };
+
+ /**
+ * Check if the player is in fullscreen mode
+ * ```js
+ * // get
+ * var fullscreenOrNot = myPlayer.isFullscreen();
+ * // set
+ * myPlayer.isFullscreen(true); // tell the player it's in fullscreen
+ * ```
+ * NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
+ * property and instead document.fullscreenElement is used. But isFullscreen is
+ * still a valuable property for internal player workings.
+ *
+ * @param {Boolean=} isFS Update the player's fullscreen state
+ * @return {Boolean} true if fullscreen false if not when getting
+ * @return {Player} self when setting
+ * @method isFullscreen
+ */
+
+ Player.prototype.isFullscreen = function isFullscreen(isFS) {
+ if (isFS !== undefined) {
+ this.isFullscreen_ = !!isFS;
+ return this;
+ }
+ return !!this.isFullscreen_;
+ };
+
+ /**
+ * Increase the size of the video to full screen
+ * ```js
+ * myPlayer.requestFullscreen();
+ * ```
+ * In some browsers, full screen is not supported natively, so it enters
+ * "full window mode", where the video fills the browser window.
+ * In browsers and devices that support native full screen, sometimes the
+ * browser's default controls will be shown, and not the Video.js custom skin.
+ * This includes most mobile devices (iOS, Android) and older versions of
+ * Safari.
+ *
+ * @return {Player} self
+ * @method requestFullscreen
+ */
+
+ Player.prototype.requestFullscreen = function requestFullscreen() {
+ var fsApi = _fullscreenApiJs2['default'];
+
+ this.isFullscreen(true);
+
+ if (fsApi.requestFullscreen) {
+ // the browser supports going fullscreen at the element level so we can
+ // take the controls fullscreen as well as the video
+
+ // Trigger fullscreenchange event after change
+ // We have to specifically add this each time, and remove
+ // when canceling fullscreen. Otherwise if there's multiple
+ // players on a page, they would all be reacting to the same fullscreen
+ // events
+ Events.on(_globalDocument2['default'], fsApi.fullscreenchange, Fn.bind(this, function documentFullscreenChange(e) {
+ this.isFullscreen(_globalDocument2['default'][fsApi.fullscreenElement]);
+
+ // If cancelling fullscreen, remove event listener.
+ if (this.isFullscreen() === false) {
+ Events.off(_globalDocument2['default'], fsApi.fullscreenchange, documentFullscreenChange);
+ }
+ this.onFullscreenChange();
+ //this.trigger('fullscreenchange');
+ }));
+
+ this.el_[fsApi.requestFullscreen]();
+ } else if (this.tech_.supportsFullScreen()) {
+ // we can't take the video.js controls fullscreen but we can go fullscreen
+ // with native controls
+ this.techCall_('enterFullScreen');
+ } else {
+ // fullscreen isn't supported so we'll just stretch the video element to
+ // fill the viewport
+ this.enterFullWindow();
+ this.onFullscreenChange();
+ //this.trigger('fullscreenchange');
+ }
+
+ return this;
+ };
+
+ /**
+ * Return the video to its normal size after having been in full screen mode
+ * ```js
+ * myPlayer.exitFullscreen();
+ * ```
+ *
+ * @return {Player} self
+ * @method exitFullscreen
+ */
+
+ Player.prototype.exitFullscreen = function exitFullscreen() {
+ var fsApi = _fullscreenApiJs2['default'];
+ this.isFullscreen(false);
+
+ // Check for browser element fullscreen support
+ if (fsApi.requestFullscreen) {
+ _globalDocument2['default'][fsApi.exitFullscreen]();
+ } else if (this.tech_.supportsFullScreen()) {
+ this.techCall_('exitFullScreen');
+ } else {
+ this.exitFullWindow();
+ this.onFullscreenChange();
+ // this.trigger('fullscreenchange');
+ }
+
+ return this;
+ };
+
+ /**
+ * When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
+ *
+ * @method enterFullWindow
+ */
+
+ Player.prototype.enterFullWindow = function enterFullWindow() {
+ this.isFullWindow = true;
+
+ // Storing original doc overflow value to return to when fullscreen is off
+ this.docOrigOverflow = _globalDocument2['default'].documentElement.style.overflow;
+
+ // Add listener for esc key to exit fullscreen
+ Events.on(_globalDocument2['default'], 'keydown', Fn.bind(this, this.fullWindowOnEscKey));
+
+ // Hide any scroll bars
+ _globalDocument2['default'].documentElement.style.overflow = 'hidden';
+
+ // Apply fullscreen styles
+ Dom.addElClass(_globalDocument2['default'].body, 'vjs-full-window');
+
+ this.trigger('enterFullWindow');
+ };
+
+ /**
+ * Check for call to either exit full window or full screen on ESC key
+ *
+ * @param {String} event Event to check for key press
+ * @method fullWindowOnEscKey
+ */
+
+ Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
+ if (event.keyCode === 27) {
+ if (this.isFullscreen() === true) {
+ this.exitFullscreen();
+ } else {
+ this.exitFullWindow();
+ }
+ }
+ };
+
+ /**
+ * Exit full window
+ *
+ * @method exitFullWindow
+ */
+
+ Player.prototype.exitFullWindow = function exitFullWindow() {
+ this.isFullWindow = false;
+ Events.off(_globalDocument2['default'], 'keydown', this.fullWindowOnEscKey);
+
+ // Unhide scroll bars.
+ _globalDocument2['default'].documentElement.style.overflow = this.docOrigOverflow;
+
+ // Remove fullscreen styles
+ Dom.removeElClass(_globalDocument2['default'].body, 'vjs-full-window');
+
+ // Resize the box, controller, and poster to original sizes
+ // this.positionAll();
+ this.trigger('exitFullWindow');
+ };
+
+ /**
+ * Check whether the player can play a given mimetype
+ *
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ * @method canPlayType
+ */
+
+ Player.prototype.canPlayType = function canPlayType(type) {
+ var can = undefined;
+
+ // Loop through each playback technology in the options order
+ for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
+ var techName = _utilsToTitleCaseJs2['default'](j[i]);
+ var tech = _techTechJs2['default'].getTech(techName);
+
+ // Support old behavior of techs being registered as components.
+ // Remove once that deprecated behavior is removed.
+ if (!tech) {
+ tech = _componentJs2['default'].getComponent(techName);
+ }
+
+ // Check if the current tech is defined before continuing
+ if (!tech) {
+ _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
+ continue;
+ }
+
+ // Check if the browser supports this technology
+ if (tech.isSupported()) {
+ can = tech.canPlayType(type);
+
+ if (can) {
+ return can;
+ }
+ }
+ }
+
+ return '';
+ };
+
+ /**
+ * Select source based on tech-order or source-order
+ * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
+ * defaults to tech-order selection
+ *
+ * @param {Array} sources The sources for a media asset
+ * @return {Object|Boolean} Object of source and tech order, otherwise false
+ * @method selectSource
+ */
+
+ Player.prototype.selectSource = function selectSource(sources) {
+ var _this3 = this;
+
+ // Get only the techs specified in `techOrder` that exist and are supported by the
+ // current platform
+ var techs = this.options_.techOrder.map(_utilsToTitleCaseJs2['default']).map(function (techName) {
+ // `Component.getComponent(...)` is for support of old behavior of techs
+ // being registered as components.
+ // Remove once that deprecated behavior is removed.
+ return [techName, _techTechJs2['default'].getTech(techName) || _componentJs2['default'].getComponent(techName)];
+ }).filter(function (_ref) {
+ var techName = _ref[0];
+ var tech = _ref[1];
+
+ // Check if the current tech is defined before continuing
+ if (tech) {
+ // Check if the browser supports this technology
+ return tech.isSupported();
+ }
+
+ _utilsLogJs2['default'].error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
+ return false;
+ });
+
+ // Iterate over each `innerArray` element once per `outerArray` element and execute
+ // `tester` with both. If `tester` returns a non-falsy value, exit early and return
+ // that value.
+ var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
+ var found = undefined;
+
+ outerArray.some(function (outerChoice) {
+ return innerArray.some(function (innerChoice) {
+ found = tester(outerChoice, innerChoice);
+
+ if (found) {
+ return true;
+ }
+ });
+ });
+
+ return found;
+ };
+
+ var foundSourceAndTech = undefined;
+ var flip = function flip(fn) {
+ return function (a, b) {
+ return fn(b, a);
+ };
+ };
+ var finder = function finder(_ref2, source) {
+ var techName = _ref2[0];
+ var tech = _ref2[1];
+
+ if (tech.canPlaySource(source, _this3.options_[techName.toLowerCase()])) {
+ return { source: source, tech: techName };
+ }
+ };
+
+ // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
+ // to select from them based on their priority.
+ if (this.options_.sourceOrder) {
+ // Source-first ordering
+ foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
+ } else {
+ // Tech-first ordering
+ foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
+ }
+
+ return foundSourceAndTech || false;
+ };
+
+ /**
+ * The source function updates the video source
+ * There are three types of variables you can pass as the argument.
+ * **URL String**: A URL to the the video file. Use this method if you are sure
+ * the current playback technology (HTML5/Flash) can support the source you
+ * provide. Currently only MP4 files can be used in both HTML5 and Flash.
+ * ```js
+ * myPlayer.src("http://www.example.com/path/to/video.mp4");
+ * ```
+ * **Source Object (or element):* * A javascript object containing information
+ * about the source file. Use this method if you want the player to determine if
+ * it can support the file using the type information.
+ * ```js
+ * myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
+ * ```
+ * **Array of Source Objects:* * To provide multiple versions of the source so
+ * that it can be played using HTML5 across browsers you can use an array of
+ * source objects. Video.js will detect which version is supported and load that
+ * file.
+ * ```js
+ * myPlayer.src([
+ * { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
+ * { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
+ * { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
+ * ]);
+ * ```
+ *
+ * @param {String|Object|Array=} source The source URL, object, or array of sources
+ * @return {String} The current video source when getting
+ * @return {String} The player when setting
+ * @method src
+ */
+
+ Player.prototype.src = function src(source) {
+ if (source === undefined) {
+ return this.techGet_('src');
+ }
+
+ var currentTech = _techTechJs2['default'].getTech(this.techName_);
+ // Support old behavior of techs being registered as components.
+ // Remove once that deprecated behavior is removed.
+ if (!currentTech) {
+ currentTech = _componentJs2['default'].getComponent(this.techName_);
+ }
+
+ // case: Array of source objects to choose from and pick the best to play
+ if (Array.isArray(source)) {
+ this.sourceList_(source);
+
+ // case: URL String (http://myvideo...)
+ } else if (typeof source === 'string') {
+ // create a source object from the string
+ this.src({ src: source });
+
+ // case: Source object { src: '', type: '' ... }
+ } else if (source instanceof Object) {
+ // check if the source has a type and the loaded tech cannot play the source
+ // if there's no type we'll just try the current tech
+ if (source.type && !currentTech.canPlaySource(source, this.options_[this.techName_.toLowerCase()])) {
+ // create a source list with the current source and send through
+ // the tech loop to check for a compatible technology
+ this.sourceList_([source]);
+ } else {
+ this.cache_.src = source.src;
+ this.currentType_ = source.type || '';
+
+ // wait until the tech is ready to set the source
+ this.ready(function () {
+
+ // The setSource tech method was added with source handlers
+ // so older techs won't support it
+ // We need to check the direct prototype for the case where subclasses
+ // of the tech do not support source handlers
+ if (currentTech.prototype.hasOwnProperty('setSource')) {
+ this.techCall_('setSource', source);
+ } else {
+ this.techCall_('src', source.src);
+ }
+
+ if (this.options_.preload === 'auto') {
+ this.load();
+ }
+
+ if (this.options_.autoplay) {
+ this.play();
+ }
+
+ // Set the source synchronously if possible (#2326)
+ }, true);
+ }
+ }
+
+ return this;
+ };
+
+ /**
+ * Handle an array of source objects
+ *
+ * @param {Array} sources Array of source objects
+ * @private
+ * @method sourceList_
+ */
+
+ Player.prototype.sourceList_ = function sourceList_(sources) {
+ var sourceTech = this.selectSource(sources);
+
+ if (sourceTech) {
+ if (sourceTech.tech === this.techName_) {
+ // if this technology is already loaded, set the source
+ this.src(sourceTech.source);
+ } else {
+ // load this technology with the chosen source
+ this.loadTech_(sourceTech.tech, sourceTech.source);
+ }
+ } else {
+ // We need to wrap this in a timeout to give folks a chance to add error event handlers
+ this.setTimeout(function () {
+ this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
+ }, 0);
+
+ // we could not find an appropriate tech, but let's still notify the delegate that this is it
+ // this needs a better comment about why this is needed
+ this.triggerReady();
+ }
+ };
+
+ /**
+ * Begin loading the src data.
+ *
+ * @return {Player} Returns the player
+ * @method load
+ */
+
+ Player.prototype.load = function load() {
+ this.techCall_('load');
+ return this;
+ };
+
+ /**
+ * Reset the player. Loads the first tech in the techOrder,
+ * and calls `reset` on the tech`.
+ *
+ * @return {Player} Returns the player
+ * @method reset
+ */
+
+ Player.prototype.reset = function reset() {
+ this.loadTech_(_utilsToTitleCaseJs2['default'](this.options_.techOrder[0]), null);
+ this.techCall_('reset');
+ return this;
+ };
+
+ /**
+ * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
+ * Can be used in conjuction with `currentType` to assist in rebuilding the current source object.
+ *
+ * @return {String} The current source
+ * @method currentSrc
+ */
+
+ Player.prototype.currentSrc = function currentSrc() {
+ return this.techGet_('currentSrc') || this.cache_.src || '';
+ };
+
+ /**
+ * Get the current source type e.g. video/mp4
+ * This can allow you rebuild the current source object so that you could load the same
+ * source and tech later
+ *
+ * @return {String} The source MIME type
+ * @method currentType
+ */
+
+ Player.prototype.currentType = function currentType() {
+ return this.currentType_ || '';
+ };
+
+ /**
+ * Get or set the preload attribute
+ *
+ * @param {Boolean} value Boolean to determine if preload should be used
+ * @return {String} The preload attribute value when getting
+ * @return {Player} Returns the player when setting
+ * @method preload
+ */
+
+ Player.prototype.preload = function preload(value) {
+ if (value !== undefined) {
+ this.techCall_('setPreload', value);
+ this.options_.preload = value;
+ return this;
+ }
+ return this.techGet_('preload');
+ };
+
+ /**
+ * Get or set the autoplay attribute.
+ *
+ * @param {Boolean} value Boolean to determine if video should autoplay
+ * @return {String} The autoplay attribute value when getting
+ * @return {Player} Returns the player when setting
+ * @method autoplay
+ */
+
+ Player.prototype.autoplay = function autoplay(value) {
+ if (value !== undefined) {
+ this.techCall_('setAutoplay', value);
+ this.options_.autoplay = value;
+ return this;
+ }
+ return this.techGet_('autoplay', value);
+ };
+
+ /**
+ * Get or set the loop attribute on the video element.
+ *
+ * @param {Boolean} value Boolean to determine if video should loop
+ * @return {String} The loop attribute value when getting
+ * @return {Player} Returns the player when setting
+ * @method loop
+ */
+
+ Player.prototype.loop = function loop(value) {
+ if (value !== undefined) {
+ this.techCall_('setLoop', value);
+ this.options_['loop'] = value;
+ return this;
+ }
+ return this.techGet_('loop');
+ };
+
+ /**
+ * Get or set the poster image source url
+ *
+ * ##### EXAMPLE:
+ * ```js
+ * // get
+ * var currentPoster = myPlayer.poster();
+ * // set
+ * myPlayer.poster('http://example.com/myImage.jpg');
+ * ```
+ *
+ * @param {String=} src Poster image source URL
+ * @return {String} poster URL when getting
+ * @return {Player} self when setting
+ * @method poster
+ */
+
+ Player.prototype.poster = function poster(src) {
+ if (src === undefined) {
+ return this.poster_;
+ }
+
+ // The correct way to remove a poster is to set as an empty string
+ // other falsey values will throw errors
+ if (!src) {
+ src = '';
+ }
+
+ // update the internal poster variable
+ this.poster_ = src;
+
+ // update the tech's poster
+ this.techCall_('setPoster', src);
+
+ // alert components that the poster has been set
+ this.trigger('posterchange');
+
+ return this;
+ };
+
+ /**
+ * Some techs (e.g. YouTube) can provide a poster source in an
+ * asynchronous way. We want the poster component to use this
+ * poster source so that it covers up the tech's controls.
+ * (YouTube's play button). However we only want to use this
+ * soruce if the player user hasn't set a poster through
+ * the normal APIs.
+ *
+ * @private
+ * @method handleTechPosterChange_
+ */
+
+ Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
+ if (!this.poster_ && this.tech_ && this.tech_.poster) {
+ this.poster_ = this.tech_.poster() || '';
+
+ // Let components know the poster has changed
+ this.trigger('posterchange');
+ }
+ };
+
+ /**
+ * Get or set whether or not the controls are showing.
+ *
+ * @param {Boolean} bool Set controls to showing or not
+ * @return {Boolean} Controls are showing
+ * @method controls
+ */
+
+ Player.prototype.controls = function controls(bool) {
+ if (bool !== undefined) {
+ bool = !!bool; // force boolean
+ // Don't trigger a change event unless it actually changed
+ if (this.controls_ !== bool) {
+ this.controls_ = bool;
+
+ if (this.usingNativeControls()) {
+ this.techCall_('setControls', bool);
+ }
+
+ if (bool) {
+ this.removeClass('vjs-controls-disabled');
+ this.addClass('vjs-controls-enabled');
+ this.trigger('controlsenabled');
+
+ if (!this.usingNativeControls()) {
+ this.addTechControlsListeners_();
+ }
+ } else {
+ this.removeClass('vjs-controls-enabled');
+ this.addClass('vjs-controls-disabled');
+ this.trigger('controlsdisabled');
+
+ if (!this.usingNativeControls()) {
+ this.removeTechControlsListeners_();
+ }
+ }
+ }
+ return this;
+ }
+ return !!this.controls_;
+ };
+
+ /**
+ * Toggle native controls on/off. Native controls are the controls built into
+ * devices (e.g. default iPhone controls), Flash, or other techs
+ * (e.g. Vimeo Controls)
+ * **This should only be set by the current tech, because only the tech knows
+ * if it can support native controls**
+ *
+ * @param {Boolean} bool True signals that native controls are on
+ * @return {Player} Returns the player
+ * @private
+ * @method usingNativeControls
+ */
+
+ Player.prototype.usingNativeControls = function usingNativeControls(bool) {
+ if (bool !== undefined) {
+ bool = !!bool; // force boolean
+ // Don't trigger a change event unless it actually changed
+ if (this.usingNativeControls_ !== bool) {
+ this.usingNativeControls_ = bool;
+ if (bool) {
+ this.addClass('vjs-using-native-controls');
+
+ /**
+ * player is using the native device controls
+ *
+ * @event usingnativecontrols
+ * @memberof Player
+ * @instance
+ * @private
+ */
+ this.trigger('usingnativecontrols');
+ } else {
+ this.removeClass('vjs-using-native-controls');
+
+ /**
+ * player is using the custom HTML controls
+ *
+ * @event usingcustomcontrols
+ * @memberof Player
+ * @instance
+ * @private
+ */
+ this.trigger('usingcustomcontrols');
+ }
+ }
+ return this;
+ }
+ return !!this.usingNativeControls_;
+ };
+
+ /**
+ * Set or get the current MediaError
+ *
+ * @param {*} err A MediaError or a String/Number to be turned into a MediaError
+ * @return {MediaError|null} when getting
+ * @return {Player} when setting
+ * @method error
+ */
+
+ Player.prototype.error = function error(err) {
+ if (err === undefined) {
+ return this.error_ || null;
+ }
+
+ // restoring to default
+ if (err === null) {
+ this.error_ = err;
+ this.removeClass('vjs-error');
+ this.errorDisplay.close();
+ return this;
+ }
+
+ // error instance
+ if (err instanceof _mediaErrorJs2['default']) {
+ this.error_ = err;
+ } else {
+ this.error_ = new _mediaErrorJs2['default'](err);
+ }
+
+ // add the vjs-error classname to the player
+ this.addClass('vjs-error');
+
+ // log the name of the error type and any message
+ // ie8 just logs "[object object]" if you just log the error object
+ _utilsLogJs2['default'].error('(CODE:' + this.error_.code + ' ' + _mediaErrorJs2['default'].errorTypes[this.error_.code] + ')', this.error_.message, this.error_);
+
+ // fire an error event on the player
+ this.trigger('error');
+
+ return this;
+ };
+
+ /**
+ * Returns whether or not the player is in the "ended" state.
+ *
+ * @return {Boolean} True if the player is in the ended state, false if not.
+ * @method ended
+ */
+
+ Player.prototype.ended = function ended() {
+ return this.techGet_('ended');
+ };
+
+ /**
+ * Returns whether or not the player is in the "seeking" state.
+ *
+ * @return {Boolean} True if the player is in the seeking state, false if not.
+ * @method seeking
+ */
+
+ Player.prototype.seeking = function seeking() {
+ return this.techGet_('seeking');
+ };
+
+ /**
+ * Returns the TimeRanges of the media that are currently available
+ * for seeking to.
+ *
+ * @return {TimeRanges} the seekable intervals of the media timeline
+ * @method seekable
+ */
+
+ Player.prototype.seekable = function seekable() {
+ return this.techGet_('seekable');
+ };
+
+ /**
+ * Report user activity
+ *
+ * @param {Object} event Event object
+ * @method reportUserActivity
+ */
+
+ Player.prototype.reportUserActivity = function reportUserActivity(event) {
+ this.userActivity_ = true;
+ };
+
+ /**
+ * Get/set if user is active
+ *
+ * @param {Boolean} bool Value when setting
+ * @return {Boolean} Value if user is active user when getting
+ * @method userActive
+ */
+
+ Player.prototype.userActive = function userActive(bool) {
+ if (bool !== undefined) {
+ bool = !!bool;
+ if (bool !== this.userActive_) {
+ this.userActive_ = bool;
+ if (bool) {
+ // If the user was inactive and is now active we want to reset the
+ // inactivity timer
+ this.userActivity_ = true;
+ this.removeClass('vjs-user-inactive');
+ this.addClass('vjs-user-active');
+ this.trigger('useractive');
+ } else {
+ // We're switching the state to inactive manually, so erase any other
+ // activity
+ this.userActivity_ = false;
+
+ // Chrome/Safari/IE have bugs where when you change the cursor it can
+ // trigger a mousemove event. This causes an issue when you're hiding
+ // the cursor when the user is inactive, and a mousemove signals user
+ // activity. Making it impossible to go into inactive mode. Specifically
+ // this happens in fullscreen when we really need to hide the cursor.
+ //
+ // When this gets resolved in ALL browsers it can be removed
+ // https://code.google.com/p/chromium/issues/detail?id=103041
+ if (this.tech_) {
+ this.tech_.one('mousemove', function (e) {
+ e.stopPropagation();
+ e.preventDefault();
+ });
+ }
+
+ this.removeClass('vjs-user-active');
+ this.addClass('vjs-user-inactive');
+ this.trigger('userinactive');
+ }
+ }
+ return this;
+ }
+ return this.userActive_;
+ };
+
+ /**
+ * Listen for user activity based on timeout value
+ *
+ * @private
+ * @method listenForUserActivity_
+ */
+
+ Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
+ var mouseInProgress = undefined,
+ lastMoveX = undefined,
+ lastMoveY = undefined;
+
+ var handleActivity = Fn.bind(this, this.reportUserActivity);
+
+ var handleMouseMove = function handleMouseMove(e) {
+ // #1068 - Prevent mousemove spamming
+ // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
+ if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
+ lastMoveX = e.screenX;
+ lastMoveY = e.screenY;
+ handleActivity();
+ }
+ };
+
+ var handleMouseDown = function handleMouseDown() {
+ handleActivity();
+ // For as long as the they are touching the device or have their mouse down,
+ // we consider them active even if they're not moving their finger or mouse.
+ // So we want to continue to update that they are active
+ this.clearInterval(mouseInProgress);
+ // Setting userActivity=true now and setting the interval to the same time
+ // as the activityCheck interval (250) should ensure we never miss the
+ // next activityCheck
+ mouseInProgress = this.setInterval(handleActivity, 250);
+ };
+
+ var handleMouseUp = function handleMouseUp(event) {
+ handleActivity();
+ // Stop the interval that maintains activity if the mouse/touch is down
+ this.clearInterval(mouseInProgress);
+ };
+
+ // Any mouse movement will be considered user activity
+ this.on('mousedown', handleMouseDown);
+ this.on('mousemove', handleMouseMove);
+ this.on('mouseup', handleMouseUp);
+
+ // Listen for keyboard navigation
+ // Shouldn't need to use inProgress interval because of key repeat
+ this.on('keydown', handleActivity);
+ this.on('keyup', handleActivity);
+
+ // Run an interval every 250 milliseconds instead of stuffing everything into
+ // the mousemove/touchmove function itself, to prevent performance degradation.
+ // `this.reportUserActivity` simply sets this.userActivity_ to true, which
+ // then gets picked up by this loop
+ // http://ejohn.org/blog/learning-from-twitter/
+ var inactivityTimeout = undefined;
+ var activityCheck = this.setInterval(function () {
+ // Check to see if mouse/touch activity has happened
+ if (this.userActivity_) {
+ // Reset the activity tracker
+ this.userActivity_ = false;
+
+ // If the user state was inactive, set the state to active
+ this.userActive(true);
+
+ // Clear any existing inactivity timeout to start the timer over
+ this.clearTimeout(inactivityTimeout);
+
+ var timeout = this.options_['inactivityTimeout'];
+ if (timeout > 0) {
+ // In milliseconds, if no more activity has occurred the
+ // user will be considered inactive
+ inactivityTimeout = this.setTimeout(function () {
+ // Protect against the case where the inactivityTimeout can trigger just
+ // before the next user activity is picked up by the activityCheck loop
+ // causing a flicker
+ if (!this.userActivity_) {
+ this.userActive(false);
+ }
+ }, timeout);
+ }
+ }
+ }, 250);
+ };
+
+ /**
+ * Gets or sets the current playback rate. A playback rate of
+ * 1.0 represents normal speed and 0.5 would indicate half-speed
+ * playback, for instance.
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
+ *
+ * @param {Number} rate New playback rate to set.
+ * @return {Number} Returns the new playback rate when setting
+ * @return {Number} Returns the current playback rate when getting
+ * @method playbackRate
+ */
+
+ Player.prototype.playbackRate = function playbackRate(rate) {
+ if (rate !== undefined) {
+ this.techCall_('setPlaybackRate', rate);
+ return this;
+ }
+
+ if (this.tech_ && this.tech_['featuresPlaybackRate']) {
+ return this.techGet_('playbackRate');
+ } else {
+ return 1.0;
+ }
+ };
+
+ /**
+ * Gets or sets the audio flag
+ *
+ * @param {Boolean} bool True signals that this is an audio player.
+ * @return {Boolean} Returns true if player is audio, false if not when getting
+ * @return {Player} Returns the player if setting
+ * @private
+ * @method isAudio
+ */
+
+ Player.prototype.isAudio = function isAudio(bool) {
+ if (bool !== undefined) {
+ this.isAudio_ = !!bool;
+ return this;
+ }
+
+ return !!this.isAudio_;
+ };
+
+ /**
+ * Returns the current state of network activity for the element, from
+ * the codes in the list below.
+ * - NETWORK_EMPTY (numeric value 0)
+ * The element has not yet been initialised. All attributes are in
+ * their initial states.
+ * - NETWORK_IDLE (numeric value 1)
+ * The element's resource selection algorithm is active and has
+ * selected a resource, but it is not actually using the network at
+ * this time.
+ * - NETWORK_LOADING (numeric value 2)
+ * The user agent is actively trying to download data.
+ * - NETWORK_NO_SOURCE (numeric value 3)
+ * The element's resource selection algorithm is active, but it has
+ * not yet found a resource to use.
+ *
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
+ * @return {Number} the current network activity state
+ * @method networkState
+ */
+
+ Player.prototype.networkState = function networkState() {
+ return this.techGet_('networkState');
+ };
+
+ /**
+ * Returns a value that expresses the current state of the element
+ * with respect to rendering the current playback position, from the
+ * codes in the list below.
+ * - HAVE_NOTHING (numeric value 0)
+ * No information regarding the media resource is available.
+ * - HAVE_METADATA (numeric value 1)
+ * Enough of the resource has been obtained that the duration of the
+ * resource is available.
+ * - HAVE_CURRENT_DATA (numeric value 2)
+ * Data for the immediate current playback position is available.
+ * - HAVE_FUTURE_DATA (numeric value 3)
+ * Data for the immediate current playback position is available, as
+ * well as enough data for the user agent to advance the current
+ * playback position in the direction of playback.
+ * - HAVE_ENOUGH_DATA (numeric value 4)
+ * The user agent estimates that enough data is available for
+ * playback to proceed uninterrupted.
+ *
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
+ * @return {Number} the current playback rendering state
+ * @method readyState
+ */
+
+ Player.prototype.readyState = function readyState() {
+ return this.techGet_('readyState');
+ };
+
+ /**
+ * Get a video track list
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
+ *
+ * @return {VideoTrackList} thes current video track list
+ * @method videoTracks
+ */
+
+ Player.prototype.videoTracks = function videoTracks() {
+ // if we have not yet loadTech_, we create videoTracks_
+ // these will be passed to the tech during loading
+ if (!this.tech_) {
+ this.videoTracks_ = this.videoTracks_ || new _tracksVideoTrackListJs2['default']();
+ return this.videoTracks_;
+ }
+
+ return this.tech_.videoTracks();
+ };
+
+ /**
+ * Get an audio track list
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
+ *
+ * @return {AudioTrackList} thes current audio track list
+ * @method audioTracks
+ */
+
+ Player.prototype.audioTracks = function audioTracks() {
+ // if we have not yet loadTech_, we create videoTracks_
+ // these will be passed to the tech during loading
+ if (!this.tech_) {
+ this.audioTracks_ = this.audioTracks_ || new _tracksAudioTrackListJs2['default']();
+ return this.audioTracks_;
+ }
+
+ return this.tech_.audioTracks();
+ };
+
+ /*
+ * Text tracks are tracks of timed text events.
+ * Captions - text displayed over the video for the hearing impaired
+ * Subtitles - text displayed over the video for those who don't understand language in the video
+ * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
+ * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
+ */
+
+ /**
+ * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
+ * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
+ *
+ * @return {Array} Array of track objects
+ * @method textTracks
+ */
+
+ Player.prototype.textTracks = function textTracks() {
+ // cannot use techGet_ directly because it checks to see whether the tech is ready.
+ // Flash is unlikely to be ready in time but textTracks should still work.
+ return this.tech_ && this.tech_['textTracks']();
+ };
+
+ /**
+ * Get an array of remote text tracks
+ *
+ * @return {Array}
+ * @method remoteTextTracks
+ */
+
+ Player.prototype.remoteTextTracks = function remoteTextTracks() {
+ return this.tech_ && this.tech_['remoteTextTracks']();
+ };
+
+ /**
+ * Get an array of remote html track elements
+ *
+ * @return {HTMLTrackElement[]}
+ * @method remoteTextTrackEls
+ */
+
+ Player.prototype.remoteTextTrackEls = function remoteTextTrackEls() {
+ return this.tech_ && this.tech_['remoteTextTrackEls']();
+ };
+
+ /**
+ * Add a text track
+ * In addition to the W3C settings we allow adding additional info through options.
+ * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
+ *
+ * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
+ * @param {String=} label Optional label
+ * @param {String=} language Optional language
+ * @method addTextTrack
+ */
+
+ Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
+ return this.tech_ && this.tech_['addTextTrack'](kind, label, language);
+ };
+
+ /**
+ * Add a remote text track
+ *
+ * @param {Object} options Options for remote text track
+ * @method addRemoteTextTrack
+ */
+
+ Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) {
+ return this.tech_ && this.tech_['addRemoteTextTrack'](options);
+ };
+
+ /**
+ * Remove a remote text track
+ *
+ * @param {Object} track Remote text track to remove
+ * @method removeRemoteTextTrack
+ */
+ // destructure the input into an object with a track argument, defaulting to arguments[0]
+ // default the whole argument to an empty object if nothing was passed in
+
+ Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() {
+ var _ref3 = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ var _ref3$track = _ref3.track;
+ var track = _ref3$track === undefined ? arguments[0] : _ref3$track;
+ // jshint ignore:line
+ this.tech_ && this.tech_['removeRemoteTextTrack'](track);
+ };
+
+ /**
+ * Get video width
+ *
+ * @return {Number} Video width
+ * @method videoWidth
+ */
+
+ Player.prototype.videoWidth = function videoWidth() {
+ return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
+ };
+
+ /**
+ * Get video height
+ *
+ * @return {Number} Video height
+ * @method videoHeight
+ */
+
+ Player.prototype.videoHeight = function videoHeight() {
+ return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
+ };
+
+ // Methods to add support for
+ // initialTime: function(){ return this.techCall_('initialTime'); },
+ // startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
+ // played: function(){ return this.techCall_('played'); },
+ // defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
+ // defaultMuted: function(){ return this.techCall_('defaultMuted'); }
+
+ /**
+ * The player's language code
+ * NOTE: The language should be set in the player options if you want the
+ * the controls to be built with a specific language. Changing the lanugage
+ * later will not update controls text.
+ *
+ * @param {String} code The locale string
+ * @return {String} The locale string when getting
+ * @return {Player} self when setting
+ * @method language
+ */
+
+ Player.prototype.language = function language(code) {
+ if (code === undefined) {
+ return this.language_;
+ }
+
+ this.language_ = ('' + code).toLowerCase();
+ return this;
+ };
+
+ /**
+ * Get the player's language dictionary
+ * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
+ * Languages specified directly in the player options have precedence
+ *
+ * @return {Array} Array of languages
+ * @method languages
+ */
+
+ Player.prototype.languages = function languages() {
+ return _utilsMergeOptionsJs2['default'](Player.prototype.options_.languages, this.languages_);
+ };
+
+ /**
+ * Converts track info to JSON
+ *
+ * @return {Object} JSON object of options
+ * @method toJSON
+ */
+
+ Player.prototype.toJSON = function toJSON() {
+ var options = _utilsMergeOptionsJs2['default'](this.options_);
+ var tracks = options.tracks;
+
+ options.tracks = [];
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+
+ // deep merge tracks and null out player so no circular references
+ track = _utilsMergeOptionsJs2['default'](track);
+ track.player = undefined;
+ options.tracks[i] = track;
+ }
+
+ return options;
+ };
+
+ /**
+ * Creates a simple modal dialog (an instance of the `ModalDialog`
+ * component) that immediately overlays the player with arbitrary
+ * content and removes itself when closed.
+ *
+ * @param {String|Function|Element|Array|Null} content
+ * Same as `ModalDialog#content`'s param of the same name.
+ *
+ * The most straight-forward usage is to provide a string or DOM
+ * element.
+ *
+ * @param {Object} [options]
+ * Extra options which will be passed on to the `ModalDialog`.
+ *
+ * @return {ModalDialog}
+ */
+
+ Player.prototype.createModal = function createModal(content, options) {
+ var player = this;
+
+ options = options || {};
+ options.content = content || '';
+
+ var modal = new _modalDialog2['default'](player, options);
+
+ player.addChild(modal);
+ modal.on('dispose', function () {
+ player.removeChild(modal);
+ });
+
+ return modal.open();
+ };
+
+ /**
+ * Gets tag settings
+ *
+ * @param {Element} tag The player tag
+ * @return {Array} An array of sources and track objects
+ * @static
+ * @method getTagSettings
+ */
+
+ Player.getTagSettings = function getTagSettings(tag) {
+ var baseOptions = {
+ 'sources': [],
+ 'tracks': []
+ };
+
+ var tagOptions = Dom.getElAttributes(tag);
+ var dataSetup = tagOptions['data-setup'];
+
+ // Check if data-setup attr exists.
+ if (dataSetup !== null) {
+ // Parse options JSON
+
+ var _safeParseTuple = _safeJsonParseTuple2['default'](dataSetup || '{}');
+
+ var err = _safeParseTuple[0];
+ var data = _safeParseTuple[1];
+
+ if (err) {
+ _utilsLogJs2['default'].error(err);
+ }
+ _objectAssign2['default'](tagOptions, data);
+ }
+
+ _objectAssign2['default'](baseOptions, tagOptions);
+
+ // Get tag children settings
+ if (tag.hasChildNodes()) {
+ var children = tag.childNodes;
+
+ for (var i = 0, j = children.length; i < j; i++) {
+ var child = children[i];
+ // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
+ var childName = child.nodeName.toLowerCase();
+ if (childName === 'source') {
+ baseOptions.sources.push(Dom.getElAttributes(child));
+ } else if (childName === 'track') {
+ baseOptions.tracks.push(Dom.getElAttributes(child));
+ }
+ }
+ }
+
+ return baseOptions;
+ };
+
+ return Player;
+})(_componentJs2['default']);
+
+Player.players = {};
+
+var navigator = _globalWindow2['default'].navigator;
+/*
+ * Player instance options, surfaced using options
+ * options = Player.prototype.options_
+ * Make changes in options, not here.
+ *
+ * @type {Object}
+ * @private
+ */
+Player.prototype.options_ = {
+ // Default order of fallback technology
+ techOrder: ['html5', 'flash'],
+ // techOrder: ['flash','html5'],
+
+ html5: {},
+ flash: {},
+
+ // defaultVolume: 0.85,
+ defaultVolume: 0.00, // The freakin seaguls are driving me crazy!
+
+ // default inactivity timeout
+ inactivityTimeout: 2000,
+
+ // default playback rates
+ playbackRates: [],
+ // Add playback rate selection by adding rates
+ // 'playbackRates': [0.5, 1, 1.5, 2],
+
+ // Included control sets
+ children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings'],
+
+ language: _globalDocument2['default'].getElementsByTagName('html')[0].getAttribute('lang') || navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language || 'en',
+
+ // locales and their language translations
+ languages: {},
+
+ // Default message to show when a video cannot be played.
+ notSupportedMessage: 'No compatible source was found for this media.'
+};
+
+/**
+ * Fired when the user agent begins looking for media data
+ *
+ * @event loadstart
+ */
+Player.prototype.handleTechLoadStart_;
+
+/**
+ * Fired when the player has initial duration and dimension information
+ *
+ * @event loadedmetadata
+ */
+Player.prototype.handleLoadedMetaData_;
+
+/**
+ * Fired when the player has downloaded data at the current playback position
+ *
+ * @event loadeddata
+ */
+Player.prototype.handleLoadedData_;
+
+/**
+ * Fired when the user is active, e.g. moves the mouse over the player
+ *
+ * @event useractive
+ */
+Player.prototype.handleUserActive_;
+
+/**
+ * Fired when the user is inactive, e.g. a short delay after the last mouse move or control interaction
+ *
+ * @event userinactive
+ */
+Player.prototype.handleUserInactive_;
+
+/**
+ * Fired when the current playback position has changed *
+ * During playback this is fired every 15-250 milliseconds, depending on the
+ * playback technology in use.
+ *
+ * @event timeupdate
+ */
+Player.prototype.handleTimeUpdate_;
+
+/**
+ * Fired when video playback ends
+ *
+ * @event ended
+ */
+Player.prototype.handleTechEnded_;
+
+/**
+ * Fired when the volume changes
+ *
+ * @event volumechange
+ */
+Player.prototype.handleVolumeChange_;
+
+/**
+ * Fired when an error occurs
+ *
+ * @event error
+ */
+Player.prototype.handleError_;
+
+Player.prototype.flexNotSupported_ = function () {
+ var elem = _globalDocument2['default'].createElement('i');
+
+ // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
+ // common flex features that we can rely on when checking for flex support.
+ return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style || 'msFlexOrder' in elem.style) /* IE10-specific (2012 flex spec) */;
+};
+
+_componentJs2['default'].registerComponent('Player', Player);
+exports['default'] = Player;
+module.exports = exports['default'];
+// If empty string, make it a parsable json object.
+
+},{"./big-play-button.js":67,"./component.js":71,"./control-bar/control-bar.js":74,"./error-display.js":107,"./fullscreen-api.js":110,"./loading-spinner.js":111,"./media-error.js":112,"./modal-dialog":116,"./poster-image.js":121,"./tech/html5.js":126,"./tech/loader.js":127,"./tech/tech.js":128,"./tracks/audio-track-list.js":129,"./tracks/text-track-display.js":134,"./tracks/text-track-list-converter.js":135,"./tracks/text-track-settings.js":137,"./tracks/video-track-list.js":142,"./utils/browser.js":144,"./utils/buffer.js":145,"./utils/dom.js":146,"./utils/events.js":147,"./utils/fn.js":148,"./utils/guid.js":150,"./utils/log.js":151,"./utils/merge-options.js":152,"./utils/stylesheet.js":153,"./utils/time-ranges.js":154,"./utils/to-title-case.js":155,"global/document":9,"global/window":10,"object.assign":57,"safe-json-parse/tuple":62}],118:[function(_dereq_,module,exports){
+/**
+ * @file plugins.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _playerJs = _dereq_('./player.js');
+
+var _playerJs2 = _interopRequireDefault(_playerJs);
+
+/**
+ * The method for registering a video.js plugin
+ *
+ * @param {String} name The name of the plugin
+ * @param {Function} init The function that is run when the player inits
+ * @method plugin
+ */
+var plugin = function plugin(name, init) {
+ _playerJs2['default'].prototype[name] = init;
+};
+
+exports['default'] = plugin;
+module.exports = exports['default'];
+
+},{"./player.js":117}],119:[function(_dereq_,module,exports){
+/**
+ * @file popup-button.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _clickableComponentJs = _dereq_('../clickable-component.js');
+
+var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs);
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _popupJs = _dereq_('./popup.js');
+
+var _popupJs2 = _interopRequireDefault(_popupJs);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+/**
+ * A button class with a popup control
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends ClickableComponent
+ * @class PopupButton
+ */
+
+var PopupButton = (function (_ClickableComponent) {
+ _inherits(PopupButton, _ClickableComponent);
+
+ function PopupButton(player) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, PopupButton);
+
+ _ClickableComponent.call(this, player, options);
+
+ this.update();
+ }
+
+ /**
+ * Update popup
+ *
+ * @method update
+ */
+
+ PopupButton.prototype.update = function update() {
+ var popup = this.createPopup();
+
+ if (this.popup) {
+ this.removeChild(this.popup);
+ }
+
+ this.popup = popup;
+ this.addChild(popup);
+
+ if (this.items && this.items.length === 0) {
+ this.hide();
+ } else if (this.items && this.items.length > 1) {
+ this.show();
+ }
+ };
+
+ /**
+ * Create popup - Override with specific functionality for component
+ *
+ * @return {Popup} The constructed popup
+ * @method createPopup
+ */
+
+ PopupButton.prototype.createPopup = function createPopup() {};
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ PopupButton.prototype.createEl = function createEl() {
+ return _ClickableComponent.prototype.createEl.call(this, 'div', {
+ className: this.buildCSSClass()
+ });
+ };
+
+ /**
+ * Allow sub components to stack CSS class names
+ *
+ * @return {String} The constructed class name
+ * @method buildCSSClass
+ */
+
+ PopupButton.prototype.buildCSSClass = function buildCSSClass() {
+ var menuButtonClass = 'vjs-menu-button';
+
+ // If the inline option is passed, we want to use different styles altogether.
+ if (this.options_.inline === true) {
+ menuButtonClass += '-inline';
+ } else {
+ menuButtonClass += '-popup';
+ }
+
+ return 'vjs-menu-button ' + menuButtonClass + ' ' + _ClickableComponent.prototype.buildCSSClass.call(this);
+ };
+
+ return PopupButton;
+})(_clickableComponentJs2['default']);
+
+_componentJs2['default'].registerComponent('PopupButton', PopupButton);
+exports['default'] = PopupButton;
+module.exports = exports['default'];
+
+},{"../clickable-component.js":69,"../component.js":71,"../utils/dom.js":146,"../utils/fn.js":148,"../utils/to-title-case.js":155,"./popup.js":120}],120:[function(_dereq_,module,exports){
+/**
+ * @file popup.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsEventsJs = _dereq_('../utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+/**
+ * The Popup component is used to build pop up controls.
+ *
+ * @extends Component
+ * @class Popup
+ */
+
+var Popup = (function (_Component) {
+ _inherits(Popup, _Component);
+
+ function Popup() {
+ _classCallCheck(this, Popup);
+
+ _Component.apply(this, arguments);
+ }
+
+ /**
+ * Add a popup item to the popup
+ *
+ * @param {Object|String} component Component or component type to add
+ * @method addItem
+ */
+
+ Popup.prototype.addItem = function addItem(component) {
+ this.addChild(component);
+ component.on('click', Fn.bind(this, function () {
+ this.unlockShowing();
+ }));
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Popup.prototype.createEl = function createEl() {
+ var contentElType = this.options_.contentElType || 'ul';
+ this.contentEl_ = Dom.createEl(contentElType, {
+ className: 'vjs-menu-content'
+ });
+ var el = _Component.prototype.createEl.call(this, 'div', {
+ append: this.contentEl_,
+ className: 'vjs-menu'
+ });
+ el.appendChild(this.contentEl_);
+
+ // Prevent clicks from bubbling up. Needed for Popup Buttons,
+ // where a click on the parent is significant
+ Events.on(el, 'click', function (event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ });
+
+ return el;
+ };
+
+ return Popup;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('Popup', Popup);
+exports['default'] = Popup;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../utils/dom.js":146,"../utils/events.js":147,"../utils/fn.js":148}],121:[function(_dereq_,module,exports){
+/**
+ * @file poster-image.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _clickableComponentJs = _dereq_('./clickable-component.js');
+
+var _clickableComponentJs2 = _interopRequireDefault(_clickableComponentJs);
+
+var _componentJs = _dereq_('./component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsFnJs = _dereq_('./utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsDomJs = _dereq_('./utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsBrowserJs = _dereq_('./utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+/**
+ * The component that handles showing the poster image.
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Button
+ * @class PosterImage
+ */
+
+var PosterImage = (function (_ClickableComponent) {
+ _inherits(PosterImage, _ClickableComponent);
+
+ function PosterImage(player, options) {
+ _classCallCheck(this, PosterImage);
+
+ _ClickableComponent.call(this, player, options);
+
+ this.update();
+ player.on('posterchange', Fn.bind(this, this.update));
+ }
+
+ /**
+ * Clean up the poster image
+ *
+ * @method dispose
+ */
+
+ PosterImage.prototype.dispose = function dispose() {
+ this.player().off('posterchange', this.update);
+ _ClickableComponent.prototype.dispose.call(this);
+ };
+
+ /**
+ * Create the poster's image element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ PosterImage.prototype.createEl = function createEl() {
+ var el = Dom.createEl('div', {
+ className: 'vjs-poster',
+
+ // Don't want poster to be tabbable.
+ tabIndex: -1
+ });
+
+ // To ensure the poster image resizes while maintaining its original aspect
+ // ratio, use a div with `background-size` when available. For browsers that
+ // do not support `background-size` (e.g. IE8), fall back on using a regular
+ // img element.
+ if (!browser.BACKGROUND_SIZE_SUPPORTED) {
+ this.fallbackImg_ = Dom.createEl('img');
+ el.appendChild(this.fallbackImg_);
+ }
+
+ return el;
+ };
+
+ /**
+ * Event handler for updates to the player's poster source
+ *
+ * @method update
+ */
+
+ PosterImage.prototype.update = function update() {
+ var url = this.player().poster();
+
+ this.setSrc(url);
+
+ // If there's no poster source we should display:none on this component
+ // so it's not still clickable or right-clickable
+ if (url) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ };
+
+ /**
+ * Set the poster source depending on the display method
+ *
+ * @param {String} url The URL to the poster source
+ * @method setSrc
+ */
+
+ PosterImage.prototype.setSrc = function setSrc(url) {
+ if (this.fallbackImg_) {
+ this.fallbackImg_.src = url;
+ } else {
+ var backgroundImage = '';
+ // Any falsey values should stay as an empty string, otherwise
+ // this will throw an extra error
+ if (url) {
+ backgroundImage = 'url("' + url + '")';
+ }
+
+ this.el_.style.backgroundImage = backgroundImage;
+ }
+ };
+
+ /**
+ * Event handler for clicks on the poster image
+ *
+ * @method handleClick
+ */
+
+ PosterImage.prototype.handleClick = function handleClick() {
+ // We don't want a click to trigger playback when controls are disabled
+ // but CSS should be hiding the poster to prevent that from happening
+ if (this.player_.paused()) {
+ this.player_.play();
+ } else {
+ this.player_.pause();
+ }
+ };
+
+ return PosterImage;
+})(_clickableComponentJs2['default']);
+
+_componentJs2['default'].registerComponent('PosterImage', PosterImage);
+exports['default'] = PosterImage;
+module.exports = exports['default'];
+
+},{"./clickable-component.js":69,"./component.js":71,"./utils/browser.js":144,"./utils/dom.js":146,"./utils/fn.js":148}],122:[function(_dereq_,module,exports){
+/**
+ * @file setup.js
+ *
+ * Functions for automatically setting up a player
+ * based on the data-setup attribute of the video tag
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+var _utilsEventsJs = _dereq_('./utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _windowLoaded = false;
+var videojs = undefined;
+
+// Automatically set up any tags that have a data-setup attribute
+var autoSetup = function autoSetup() {
+ // One day, when we stop supporting IE8, go back to this, but in the meantime...*hack hack hack*
+ // var vids = Array.prototype.slice.call(document.getElementsByTagName('video'));
+ // var audios = Array.prototype.slice.call(document.getElementsByTagName('audio'));
+ // var mediaEls = vids.concat(audios);
+
+ // Because IE8 doesn't support calling slice on a node list, we need to loop through each list of elements
+ // to build up a new, combined list of elements.
+ var vids = _globalDocument2['default'].getElementsByTagName('video');
+ var audios = _globalDocument2['default'].getElementsByTagName('audio');
+ var mediaEls = [];
+ if (vids && vids.length > 0) {
+ for (var i = 0, e = vids.length; i < e; i++) {
+ mediaEls.push(vids[i]);
+ }
+ }
+ if (audios && audios.length > 0) {
+ for (var i = 0, e = audios.length; i < e; i++) {
+ mediaEls.push(audios[i]);
+ }
+ }
+
+ // Check if any media elements exist
+ if (mediaEls && mediaEls.length > 0) {
+
+ for (var i = 0, e = mediaEls.length; i < e; i++) {
+ var mediaEl = mediaEls[i];
+
+ // Check if element exists, has getAttribute func.
+ // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
+ if (mediaEl && mediaEl.getAttribute) {
+
+ // Make sure this player hasn't already been set up.
+ if (mediaEl['player'] === undefined) {
+ var options = mediaEl.getAttribute('data-setup');
+
+ // Check if data-setup attr exists.
+ // We only auto-setup if they've added the data-setup attr.
+ if (options !== null) {
+ // Create new video.js instance.
+ var player = videojs(mediaEl);
+ }
+ }
+
+ // If getAttribute isn't defined, we need to wait for the DOM.
+ } else {
+ autoSetupTimeout(1);
+ break;
+ }
+ }
+
+ // No videos were found, so keep looping unless page is finished loading.
+ } else if (!_windowLoaded) {
+ autoSetupTimeout(1);
+ }
+};
+
+// Pause to let the DOM keep processing
+var autoSetupTimeout = function autoSetupTimeout(wait, vjs) {
+ if (vjs) {
+ videojs = vjs;
+ }
+
+ setTimeout(autoSetup, wait);
+};
+
+if (_globalDocument2['default'].readyState === 'complete') {
+ _windowLoaded = true;
+} else {
+ Events.one(_globalWindow2['default'], 'load', function () {
+ _windowLoaded = true;
+ });
+}
+
+var hasLoaded = function hasLoaded() {
+ return _windowLoaded;
+};
+
+exports.autoSetup = autoSetup;
+exports.autoSetupTimeout = autoSetupTimeout;
+exports.hasLoaded = hasLoaded;
+
+},{"./utils/events.js":147,"global/document":9,"global/window":10}],123:[function(_dereq_,module,exports){
+/**
+ * @file slider.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+/**
+ * The base functionality for sliders like the volume bar and seek bar
+ *
+ * @param {Player|Object} player
+ * @param {Object=} options
+ * @extends Component
+ * @class Slider
+ */
+
+var Slider = (function (_Component) {
+ _inherits(Slider, _Component);
+
+ function Slider(player, options) {
+ _classCallCheck(this, Slider);
+
+ _Component.call(this, player, options);
+
+ // Set property names to bar to match with the child Slider class is looking for
+ this.bar = this.getChild(this.options_.barName);
+
+ // Set a horizontal or vertical class on the slider depending on the slider type
+ this.vertical(!!this.options_.vertical);
+
+ this.on('mousedown', this.handleMouseDown);
+ this.on('touchstart', this.handleMouseDown);
+ this.on('focus', this.handleFocus);
+ this.on('blur', this.handleBlur);
+ this.on('click', this.handleClick);
+
+ this.on(player, 'controlsvisible', this.update);
+ this.on(player, this.playerEvent, this.update);
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @param {String} type Type of element to create
+ * @param {Object=} props List of properties in Object form
+ * @return {Element}
+ * @method createEl
+ */
+
+ Slider.prototype.createEl = function createEl(type) {
+ var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+ var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+ // Add the slider element class to all sub classes
+ props.className = props.className + ' vjs-slider';
+ props = _objectAssign2['default']({
+ tabIndex: 0
+ }, props);
+
+ attributes = _objectAssign2['default']({
+ 'role': 'slider',
+ 'aria-valuenow': 0,
+ 'aria-valuemin': 0,
+ 'aria-valuemax': 100,
+ tabIndex: 0
+ }, attributes);
+
+ return _Component.prototype.createEl.call(this, type, props, attributes);
+ };
+
+ /**
+ * Handle mouse down on slider
+ *
+ * @param {Object} event Mouse down event object
+ * @method handleMouseDown
+ */
+
+ Slider.prototype.handleMouseDown = function handleMouseDown(event) {
+ var doc = this.bar.el_.ownerDocument;
+
+ event.preventDefault();
+ Dom.blockTextSelection();
+
+ this.addClass('vjs-sliding');
+ this.trigger('slideractive');
+
+ this.on(doc, 'mousemove', this.handleMouseMove);
+ this.on(doc, 'mouseup', this.handleMouseUp);
+ this.on(doc, 'touchmove', this.handleMouseMove);
+ this.on(doc, 'touchend', this.handleMouseUp);
+
+ this.handleMouseMove(event);
+ };
+
+ /**
+ * To be overridden by a subclass
+ *
+ * @method handleMouseMove
+ */
+
+ Slider.prototype.handleMouseMove = function handleMouseMove() {};
+
+ /**
+ * Handle mouse up on Slider
+ *
+ * @method handleMouseUp
+ */
+
+ Slider.prototype.handleMouseUp = function handleMouseUp() {
+ var doc = this.bar.el_.ownerDocument;
+
+ Dom.unblockTextSelection();
+
+ this.removeClass('vjs-sliding');
+ this.trigger('sliderinactive');
+
+ this.off(doc, 'mousemove', this.handleMouseMove);
+ this.off(doc, 'mouseup', this.handleMouseUp);
+ this.off(doc, 'touchmove', this.handleMouseMove);
+ this.off(doc, 'touchend', this.handleMouseUp);
+
+ this.update();
+ };
+
+ /**
+ * Update slider
+ *
+ * @method update
+ */
+
+ Slider.prototype.update = function update() {
+ // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
+ // execution stack. The player is destroyed before then update will cause an error
+ if (!this.el_) return;
+
+ // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
+ // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
+ // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
+ var progress = this.getPercent();
+ var bar = this.bar;
+
+ // If there's no bar...
+ if (!bar) return;
+
+ // Protect against no duration and other division issues
+ if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
+ progress = 0;
+ }
+
+ // Convert to a percentage for setting
+ var percentage = (progress * 100).toFixed(2) + '%';
+
+ // Set the new bar width or height
+ if (this.vertical()) {
+ bar.el().style.height = percentage;
+ } else {
+ bar.el().style.width = percentage;
+ }
+ };
+
+ /**
+ * Calculate distance for slider
+ *
+ * @param {Object} event Event object
+ * @method calculateDistance
+ */
+
+ Slider.prototype.calculateDistance = function calculateDistance(event) {
+ var position = Dom.getPointerPosition(this.el_, event);
+ if (this.vertical()) {
+ return position.y;
+ }
+ return position.x;
+ };
+
+ /**
+ * Handle on focus for slider
+ *
+ * @method handleFocus
+ */
+
+ Slider.prototype.handleFocus = function handleFocus() {
+ this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
+ };
+
+ /**
+ * Handle key press for slider
+ *
+ * @param {Object} event Event object
+ * @method handleKeyPress
+ */
+
+ Slider.prototype.handleKeyPress = function handleKeyPress(event) {
+ if (event.which === 37 || event.which === 40) {
+ // Left and Down Arrows
+ event.preventDefault();
+ this.stepBack();
+ } else if (event.which === 38 || event.which === 39) {
+ // Up and Right Arrows
+ event.preventDefault();
+ this.stepForward();
+ }
+ };
+
+ /**
+ * Handle on blur for slider
+ *
+ * @method handleBlur
+ */
+
+ Slider.prototype.handleBlur = function handleBlur() {
+ this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
+ };
+
+ /**
+ * Listener for click events on slider, used to prevent clicks
+ * from bubbling up to parent elements like button menus.
+ *
+ * @param {Object} event Event object
+ * @method handleClick
+ */
+
+ Slider.prototype.handleClick = function handleClick(event) {
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ };
+
+ /**
+ * Get/set if slider is horizontal for vertical
+ *
+ * @param {Boolean} bool True if slider is vertical, false is horizontal
+ * @return {Boolean} True if slider is vertical, false is horizontal
+ * @method vertical
+ */
+
+ Slider.prototype.vertical = function vertical(bool) {
+ if (bool === undefined) {
+ return this.vertical_ || false;
+ }
+
+ this.vertical_ = !!bool;
+
+ if (this.vertical_) {
+ this.addClass('vjs-slider-vertical');
+ } else {
+ this.addClass('vjs-slider-horizontal');
+ }
+
+ return this;
+ };
+
+ return Slider;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('Slider', Slider);
+exports['default'] = Slider;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../utils/dom.js":146,"object.assign":57}],124:[function(_dereq_,module,exports){
+/**
+ * @file flash-rtmp.js
+ */
+'use strict';
+
+exports.__esModule = true;
+function FlashRtmpDecorator(Flash) {
+ Flash.streamingFormats = {
+ 'rtmp/mp4': 'MP4',
+ 'rtmp/flv': 'FLV'
+ };
+
+ Flash.streamFromParts = function (connection, stream) {
+ return connection + '&' + stream;
+ };
+
+ Flash.streamToParts = function (src) {
+ var parts = {
+ connection: '',
+ stream: ''
+ };
+
+ if (!src) return parts;
+
+ // Look for the normal URL separator we expect, '&'.
+ // If found, we split the URL into two pieces around the
+ // first '&'.
+ var connEnd = src.search(/&(?!\w+=)/);
+ var streamBegin = undefined;
+ if (connEnd !== -1) {
+ streamBegin = connEnd + 1;
+ } else {
+ // If there's not a '&', we use the last '/' as the delimiter.
+ connEnd = streamBegin = src.lastIndexOf('/') + 1;
+ if (connEnd === 0) {
+ // really, there's not a '/'?
+ connEnd = streamBegin = src.length;
+ }
+ }
+ parts.connection = src.substring(0, connEnd);
+ parts.stream = src.substring(streamBegin, src.length);
+
+ return parts;
+ };
+
+ Flash.isStreamingType = function (srcType) {
+ return srcType in Flash.streamingFormats;
+ };
+
+ // RTMP has four variations, any string starting
+ // with one of these protocols should be valid
+ Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
+
+ Flash.isStreamingSrc = function (src) {
+ return Flash.RTMP_RE.test(src);
+ };
+
+ /**
+ * A source handler for RTMP urls
+ * @type {Object}
+ */
+ Flash.rtmpSourceHandler = {};
+
+ /**
+ * Check if Flash can play the given videotype
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+ Flash.rtmpSourceHandler.canPlayType = function (type) {
+ if (Flash.isStreamingType(type)) {
+ return 'maybe';
+ }
+
+ return '';
+ };
+
+ /**
+ * Check if Flash can handle the source natively
+ * @param {Object} source The source object
+ * @param {Object} options The options passed to the tech
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+ Flash.rtmpSourceHandler.canHandleSource = function (source, options) {
+ var can = Flash.rtmpSourceHandler.canPlayType(source.type);
+
+ if (can) {
+ return can;
+ }
+
+ if (Flash.isStreamingSrc(source.src)) {
+ return 'maybe';
+ }
+
+ return '';
+ };
+
+ /**
+ * Pass the source to the flash object
+ * Adaptive source handlers will have more complicated workflows before passing
+ * video data to the video element
+ * @param {Object} source The source object
+ * @param {Flash} tech The instance of the Flash tech
+ * @param {Object} options The options to pass to the source
+ */
+ Flash.rtmpSourceHandler.handleSource = function (source, tech, options) {
+ var srcParts = Flash.streamToParts(source.src);
+
+ tech['setRtmpConnection'](srcParts.connection);
+ tech['setRtmpStream'](srcParts.stream);
+ };
+
+ // Register the native source handler
+ Flash.registerSourceHandler(Flash.rtmpSourceHandler);
+
+ return Flash;
+}
+
+exports['default'] = FlashRtmpDecorator;
+module.exports = exports['default'];
+
+},{}],125:[function(_dereq_,module,exports){
+/**
+ * @file flash.js
+ * VideoJS-SWF - Custom Flash Player with HTML5-ish API
+ * https://github.com/zencoder/video-js-swf
+ * Not using setupTriggers. Using global onEvent func to distribute events
+ */
+
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _tech = _dereq_('./tech');
+
+var _tech2 = _interopRequireDefault(_tech);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsUrlJs = _dereq_('../utils/url.js');
+
+var Url = _interopRequireWildcard(_utilsUrlJs);
+
+var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js');
+
+var _flashRtmp = _dereq_('./flash-rtmp');
+
+var _flashRtmp2 = _interopRequireDefault(_flashRtmp);
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+var navigator = _globalWindow2['default'].navigator;
+/**
+ * Flash Media Controller - Wrapper for fallback SWF API
+ *
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends Tech
+ * @class Flash
+ */
+
+var Flash = (function (_Tech) {
+ _inherits(Flash, _Tech);
+
+ function Flash(options, ready) {
+ _classCallCheck(this, Flash);
+
+ _Tech.call(this, options, ready);
+
+ // Set the source when ready
+ if (options.source) {
+ this.ready(function () {
+ this.setSource(options.source);
+ }, true);
+ }
+
+ // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
+ // This allows resetting the playhead when we catch the reload
+ if (options.startTime) {
+ this.ready(function () {
+ this.load();
+ this.play();
+ this.currentTime(options.startTime);
+ }, true);
+ }
+
+ // Add global window functions that the swf expects
+ // A 4.x workflow we weren't able to solve for in 5.0
+ // because of the need to hard code these functions
+ // into the swf for security reasons
+ _globalWindow2['default'].videojs = _globalWindow2['default'].videojs || {};
+ _globalWindow2['default'].videojs.Flash = _globalWindow2['default'].videojs.Flash || {};
+ _globalWindow2['default'].videojs.Flash.onReady = Flash.onReady;
+ _globalWindow2['default'].videojs.Flash.onEvent = Flash.onEvent;
+ _globalWindow2['default'].videojs.Flash.onError = Flash.onError;
+
+ this.on('seeked', function () {
+ this.lastSeekTarget_ = undefined;
+ });
+ }
+
+ // Create setters and getters for attributes
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Flash.prototype.createEl = function createEl() {
+ var options = this.options_;
+
+ // If video.js is hosted locally you should also set the location
+ // for the hosted swf, which should be relative to the page (not video.js)
+ // Otherwise this adds a CDN url.
+ // The CDN also auto-adds a swf URL for that specific version.
+ if (!options.swf) {
+ options.swf = '//vjs.zencdn.net/swf/5.0.1/video-js.swf';
+ }
+
+ // Generate ID for swf object
+ var objId = options.techId;
+
+ // Merge default flashvars with ones passed in to init
+ var flashVars = _objectAssign2['default']({
+
+ // SWF Callback Functions
+ 'readyFunction': 'videojs.Flash.onReady',
+ 'eventProxyFunction': 'videojs.Flash.onEvent',
+ 'errorEventProxyFunction': 'videojs.Flash.onError',
+
+ // Player Settings
+ 'autoplay': options.autoplay,
+ 'preload': options.preload,
+ 'loop': options.loop,
+ 'muted': options.muted
+
+ }, options.flashVars);
+
+ // Merge default parames with ones passed in
+ var params = _objectAssign2['default']({
+ 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
+ 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
+ }, options.params);
+
+ // Merge default attributes with ones passed in
+ var attributes = _objectAssign2['default']({
+ 'id': objId,
+ 'name': objId, // Both ID and Name needed or swf to identify itself
+ 'class': 'vjs-tech'
+ }, options.attributes);
+
+ this.el_ = Flash.embed(options.swf, flashVars, params, attributes);
+ this.el_.tech = this;
+
+ return this.el_;
+ };
+
+ /**
+ * Play for flash tech
+ *
+ * @method play
+ */
+
+ Flash.prototype.play = function play() {
+ if (this.ended()) {
+ this.setCurrentTime(0);
+ }
+ this.el_.vjs_play();
+ };
+
+ /**
+ * Pause for flash tech
+ *
+ * @method pause
+ */
+
+ Flash.prototype.pause = function pause() {
+ this.el_.vjs_pause();
+ };
+
+ /**
+ * Get/set video
+ *
+ * @param {Object=} src Source object
+ * @return {Object}
+ * @method src
+ */
+
+ Flash.prototype.src = function src(_src) {
+ if (_src === undefined) {
+ return this.currentSrc();
+ }
+
+ // Setting src through `src` not `setSrc` will be deprecated
+ return this.setSrc(_src);
+ };
+
+ /**
+ * Set video
+ *
+ * @param {Object=} src Source object
+ * @deprecated
+ * @method setSrc
+ */
+
+ Flash.prototype.setSrc = function setSrc(src) {
+ // Make sure source URL is absolute.
+ src = Url.getAbsoluteURL(src);
+ this.el_.vjs_src(src);
+
+ // Currently the SWF doesn't autoplay if you load a source later.
+ // e.g. Load player w/ no source, wait 2s, set src.
+ if (this.autoplay()) {
+ var tech = this;
+ this.setTimeout(function () {
+ tech.play();
+ }, 0);
+ }
+ };
+
+ /**
+ * Returns true if the tech is currently seeking.
+ * @return {boolean} true if seeking
+ */
+
+ Flash.prototype.seeking = function seeking() {
+ return this.lastSeekTarget_ !== undefined;
+ };
+
+ /**
+ * Set current time
+ *
+ * @param {Number} time Current time of video
+ * @method setCurrentTime
+ */
+
+ Flash.prototype.setCurrentTime = function setCurrentTime(time) {
+ var seekable = this.seekable();
+ if (seekable.length) {
+ // clamp to the current seekable range
+ time = time > seekable.start(0) ? time : seekable.start(0);
+ time = time < seekable.end(seekable.length - 1) ? time : seekable.end(seekable.length - 1);
+
+ this.lastSeekTarget_ = time;
+ this.trigger('seeking');
+ this.el_.vjs_setProperty('currentTime', time);
+ _Tech.prototype.setCurrentTime.call(this);
+ }
+ };
+
+ /**
+ * Get current time
+ *
+ * @param {Number=} time Current time of video
+ * @return {Number} Current time
+ * @method currentTime
+ */
+
+ Flash.prototype.currentTime = function currentTime(time) {
+ // when seeking make the reported time keep up with the requested time
+ // by reading the time we're seeking to
+ if (this.seeking()) {
+ return this.lastSeekTarget_ || 0;
+ }
+ return this.el_.vjs_getProperty('currentTime');
+ };
+
+ /**
+ * Get current source
+ *
+ * @method currentSrc
+ */
+
+ Flash.prototype.currentSrc = function currentSrc() {
+ if (this.currentSource_) {
+ return this.currentSource_.src;
+ } else {
+ return this.el_.vjs_getProperty('currentSrc');
+ }
+ };
+
+ /**
+ * Load media into player
+ *
+ * @method load
+ */
+
+ Flash.prototype.load = function load() {
+ this.el_.vjs_load();
+ };
+
+ /**
+ * Get poster
+ *
+ * @method poster
+ */
+
+ Flash.prototype.poster = function poster() {
+ this.el_.vjs_getProperty('poster');
+ };
+
+ /**
+ * Poster images are not handled by the Flash tech so make this a no-op
+ *
+ * @method setPoster
+ */
+
+ Flash.prototype.setPoster = function setPoster() {};
+
+ /**
+ * Determine if can seek in media
+ *
+ * @return {TimeRangeObject}
+ * @method seekable
+ */
+
+ Flash.prototype.seekable = function seekable() {
+ var duration = this.duration();
+ if (duration === 0) {
+ return _utilsTimeRangesJs.createTimeRange();
+ }
+ return _utilsTimeRangesJs.createTimeRange(0, duration);
+ };
+
+ /**
+ * Get buffered time range
+ *
+ * @return {TimeRangeObject}
+ * @method buffered
+ */
+
+ Flash.prototype.buffered = function buffered() {
+ var ranges = this.el_.vjs_getProperty('buffered');
+ if (ranges.length === 0) {
+ return _utilsTimeRangesJs.createTimeRange();
+ }
+ return _utilsTimeRangesJs.createTimeRange(ranges[0][0], ranges[0][1]);
+ };
+
+ /**
+ * Get fullscreen support -
+ * Flash does not allow fullscreen through javascript
+ * so always returns false
+ *
+ * @return {Boolean} false
+ * @method supportsFullScreen
+ */
+
+ Flash.prototype.supportsFullScreen = function supportsFullScreen() {
+ return false; // Flash does not allow fullscreen through javascript
+ };
+
+ /**
+ * Request to enter fullscreen
+ * Flash does not allow fullscreen through javascript
+ * so always returns false
+ *
+ * @return {Boolean} false
+ * @method enterFullScreen
+ */
+
+ Flash.prototype.enterFullScreen = function enterFullScreen() {
+ return false;
+ };
+
+ return Flash;
+})(_tech2['default']);
+
+var _api = Flash.prototype;
+var _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
+var _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
+
+function _createSetter(attr) {
+ var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
+ _api['set' + attrUpper] = function (val) {
+ return this.el_.vjs_setProperty(attr, val);
+ };
+}
+function _createGetter(attr) {
+ _api[attr] = function () {
+ return this.el_.vjs_getProperty(attr);
+ };
+}
+
+// Create getter and setters for all read/write attributes
+for (var i = 0; i < _readWrite.length; i++) {
+ _createGetter(_readWrite[i]);
+ _createSetter(_readWrite[i]);
+}
+
+// Create getters for read-only attributes
+for (var i = 0; i < _readOnly.length; i++) {
+ _createGetter(_readOnly[i]);
+}
+
+/* Flash Support Testing -------------------------------------------------------- */
+
+Flash.isSupported = function () {
+ return Flash.version()[0] >= 10;
+ // return swfobject.hasFlashPlayerVersion('10');
+};
+
+// Add Source Handler pattern functions to this tech
+_tech2['default'].withSourceHandlers(Flash);
+
+/*
+ * The default native source handler.
+ * This simply passes the source to the video element. Nothing fancy.
+ *
+ * @param {Object} source The source object
+ * @param {Flash} tech The instance of the Flash tech
+ */
+Flash.nativeSourceHandler = {};
+
+/**
+ * Check if Flash can play the given videotype
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+Flash.nativeSourceHandler.canPlayType = function (type) {
+ if (type in Flash.formats) {
+ return 'maybe';
+ }
+
+ return '';
+};
+
+/*
+ * Check Flash can handle the source natively
+ *
+ * @param {Object} source The source object
+ * @param {Object} options The options passed to the tech
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+Flash.nativeSourceHandler.canHandleSource = function (source, options) {
+ var type;
+
+ function guessMimeType(src) {
+ var ext = Url.getFileExtension(src);
+ if (ext) {
+ return 'video/' + ext;
+ }
+ return '';
+ }
+
+ if (!source.type) {
+ type = guessMimeType(source.src);
+ } else {
+ // Strip code information from the type because we don't get that specific
+ type = source.type.replace(/;.*/, '').toLowerCase();
+ }
+
+ return Flash.nativeSourceHandler.canPlayType(type);
+};
+
+/*
+ * Pass the source to the flash object
+ * Adaptive source handlers will have more complicated workflows before passing
+ * video data to the video element
+ *
+ * @param {Object} source The source object
+ * @param {Flash} tech The instance of the Flash tech
+ * @param {Object} options The options to pass to the source
+ */
+Flash.nativeSourceHandler.handleSource = function (source, tech, options) {
+ tech.setSrc(source.src);
+};
+
+/*
+ * Clean up the source handler when disposing the player or switching sources..
+ * (no cleanup is needed when supporting the format natively)
+ */
+Flash.nativeSourceHandler.dispose = function () {};
+
+// Register the native source handler
+Flash.registerSourceHandler(Flash.nativeSourceHandler);
+
+Flash.formats = {
+ 'video/flv': 'FLV',
+ 'video/x-flv': 'FLV',
+ 'video/mp4': 'MP4',
+ 'video/m4v': 'MP4'
+};
+
+Flash.onReady = function (currSwf) {
+ var el = Dom.getEl(currSwf);
+ var tech = el && el.tech;
+
+ // if there is no el then the tech has been disposed
+ // and the tech element was removed from the player div
+ if (tech && tech.el()) {
+ // check that the flash object is really ready
+ Flash.checkReady(tech);
+ }
+};
+
+// The SWF isn't always ready when it says it is. Sometimes the API functions still need to be added to the object.
+// If it's not ready, we set a timeout to check again shortly.
+Flash.checkReady = function (tech) {
+ // stop worrying if the tech has been disposed
+ if (!tech.el()) {
+ return;
+ }
+
+ // check if API property exists
+ if (tech.el().vjs_getProperty) {
+ // tell tech it's ready
+ tech.triggerReady();
+ } else {
+ // wait longer
+ this.setTimeout(function () {
+ Flash['checkReady'](tech);
+ }, 50);
+ }
+};
+
+// Trigger events from the swf on the player
+Flash.onEvent = function (swfID, eventName) {
+ var tech = Dom.getEl(swfID).tech;
+ tech.trigger(eventName);
+};
+
+// Log errors from the swf
+Flash.onError = function (swfID, err) {
+ var tech = Dom.getEl(swfID).tech;
+
+ // trigger MEDIA_ERR_SRC_NOT_SUPPORTED
+ if (err === 'srcnotfound') {
+ return tech.error(4);
+ }
+
+ // trigger a custom error
+ tech.error('FLASH: ' + err);
+};
+
+// Flash Version Check
+Flash.version = function () {
+ var version = '0,0,0';
+
+ // IE
+ try {
+ version = new _globalWindow2['default'].ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
+
+ // other browsers
+ } catch (e) {
+ try {
+ if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
+ version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
+ }
+ } catch (err) {}
+ }
+ return version.split(',');
+};
+
+// Flash embedding method. Only used in non-iframe mode
+Flash.embed = function (swf, flashVars, params, attributes) {
+ var code = Flash.getEmbedCode(swf, flashVars, params, attributes);
+
+ // Get element by embedding code and retrieving created element
+ var obj = Dom.createEl('div', { innerHTML: code }).childNodes[0];
+
+ return obj;
+};
+
+Flash.getEmbedCode = function (swf, flashVars, params, attributes) {
+ var objTag = '';
+ });
+
+ attributes = _objectAssign2['default']({
+ // Add swf to attributes (need both for IE and Others to work)
+ 'data': swf,
+
+ // Default to 100% width/height
+ 'width': '100%',
+ 'height': '100%'
+
+ }, attributes);
+
+ // Create Attributes string
+ Object.getOwnPropertyNames(attributes).forEach(function (key) {
+ attrsString += key + '="' + attributes[key] + '" ';
+ });
+
+ return '' + objTag + attrsString + '>' + paramsString + '';
+};
+
+// Run Flash through the RTMP decorator
+_flashRtmp2['default'](Flash);
+
+_component2['default'].registerComponent('Flash', Flash);
+_tech2['default'].registerTech('Flash', Flash);
+exports['default'] = Flash;
+module.exports = exports['default'];
+
+},{"../component":71,"../utils/dom.js":146,"../utils/time-ranges.js":154,"../utils/url.js":156,"./flash-rtmp":124,"./tech":128,"global/window":10,"object.assign":57}],126:[function(_dereq_,module,exports){
+/**
+ * @file html5.js
+ * HTML5 Media Controller - Wrapper for HTML5 Media API
+ */
+
+'use strict';
+
+exports.__esModule = true;
+
+var _templateObject = _taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used. \n This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used. \n This may prevent text tracks from loading.']);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; }
+
+var _techJs = _dereq_('./tech.js');
+
+var _techJs2 = _interopRequireDefault(_techJs);
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _utilsDomJs = _dereq_('../utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsUrlJs = _dereq_('../utils/url.js');
+
+var Url = _interopRequireWildcard(_utilsUrlJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsLogJs = _dereq_('../utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _tsml = _dereq_('tsml');
+
+var _tsml2 = _interopRequireDefault(_tsml);
+
+var _srcJsTracksTextTrackJs = _dereq_('../../../src/js/tracks/text-track.js');
+
+var _srcJsTracksTextTrackJs2 = _interopRequireDefault(_srcJsTracksTextTrackJs);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _objectAssign = _dereq_('object.assign');
+
+var _objectAssign2 = _interopRequireDefault(_objectAssign);
+
+var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js');
+
+var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);
+
+var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+/**
+ * HTML5 Media Controller - Wrapper for HTML5 Media API
+ *
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends Tech
+ * @class Html5
+ */
+
+var Html5 = (function (_Tech) {
+ _inherits(Html5, _Tech);
+
+ function Html5(options, ready) {
+ var _this = this;
+
+ _classCallCheck(this, Html5);
+
+ _Tech.call(this, options, ready);
+
+ var source = options.source;
+ var crossoriginTracks = false;
+
+ // Set the source if one is provided
+ // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
+ // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
+ // anyway so the error gets fired.
+ if (source && (this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
+ this.setSource(source);
+ } else {
+ this.handleLateInit_(this.el_);
+ }
+
+ if (this.el_.hasChildNodes()) {
+
+ var nodes = this.el_.childNodes;
+ var nodesLength = nodes.length;
+ var removeNodes = [];
+
+ while (nodesLength--) {
+ var node = nodes[nodesLength];
+ var nodeName = node.nodeName.toLowerCase();
+
+ if (nodeName === 'track') {
+ if (!this.featuresNativeTextTracks) {
+ // Empty video tag tracks so the built-in player doesn't use them also.
+ // This may not be fast enough to stop HTML5 browsers from reading the tags
+ // so we'll need to turn off any default tracks if we're manually doing
+ // captions and subtitles. videoElement.textTracks
+ removeNodes.push(node);
+ } else {
+ // store HTMLTrackElement and TextTrack to remote list
+ this.remoteTextTrackEls().addTrackElement_(node);
+ this.remoteTextTracks().addTrack_(node.track);
+ if (!crossoriginTracks && !this.el_.hasAttribute('crossorigin') && Url.isCrossOrigin(node.src)) {
+ crossoriginTracks = true;
+ }
+ }
+ }
+ }
+
+ for (var i = 0; i < removeNodes.length; i++) {
+ this.el_.removeChild(removeNodes[i]);
+ }
+ }
+
+ var trackTypes = ['audio', 'video'];
+
+ // ProxyNativeTextTracks
+ trackTypes.forEach(function (type) {
+ var capitalType = _utilsToTitleCaseJs2['default'](type);
+
+ if (!_this['featuresNative' + capitalType + 'Tracks']) {
+ return;
+ }
+ var tl = _this.el()[type + 'Tracks'];
+
+ if (tl && tl.addEventListener) {
+ tl.addEventListener('change', Fn.bind(_this, _this['handle' + capitalType + 'TrackChange_']));
+ tl.addEventListener('addtrack', Fn.bind(_this, _this['handle' + capitalType + 'TrackAdd_']));
+ tl.addEventListener('removetrack', Fn.bind(_this, _this['handle' + capitalType + 'TrackRemove_']));
+
+ // Remove (native) trackts that are not used anymore
+ _this.on('loadstart', _this['removeOld' + capitalType + 'Tracks_']);
+ }
+ });
+
+ if (this.featuresNativeTextTracks) {
+ if (crossoriginTracks) {
+ _utilsLogJs2['default'].warn(_tsml2['default'](_templateObject));
+ }
+
+ this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
+ this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
+ this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
+ this.proxyNativeTextTracks_();
+ }
+
+ // Determine if native controls should be used
+ // Our goal should be to get the custom controls on mobile solid everywhere
+ // so we can remove this all together. Right now this will block custom
+ // controls on touch enabled laptops like the Chrome Pixel
+ if (browser.TOUCH_ENABLED && options.nativeControlsForTouch === true || browser.IS_IPHONE || browser.IS_NATIVE_ANDROID) {
+ this.setControls(true);
+ }
+
+ this.triggerReady();
+ }
+
+ /* HTML5 Support Testing ---------------------------------------------------- */
+
+ /*
+ * Element for testing browser HTML5 video capabilities
+ *
+ * @type {Element}
+ * @constant
+ * @private
+ */
+
+ /**
+ * Dispose of html5 media element
+ *
+ * @method dispose
+ */
+
+ Html5.prototype.dispose = function dispose() {
+ var _this2 = this;
+
+ // Un-ProxyNativeTracks
+ ['audio', 'video', 'text'].forEach(function (type) {
+ var capitalType = _utilsToTitleCaseJs2['default'](type);
+ var tl = _this2.el_[type + 'Tracks'];
+
+ if (tl && tl.removeEventListener) {
+ tl.removeEventListener('change', _this2['handle' + capitalType + 'TrackChange_']);
+ tl.removeEventListener('addtrack', _this2['handle' + capitalType + 'TrackAdd_']);
+ tl.removeEventListener('removetrack', _this2['handle' + capitalType + 'TrackRemove_']);
+ }
+
+ // Stop removing old text tracks
+ if (tl) {
+ _this2.off('loadstart', _this2['removeOld' + capitalType + 'Tracks_']);
+ }
+ });
+
+ Html5.disposeMediaElement(this.el_);
+ // tech will handle clearing of the emulated track list
+ _Tech.prototype.dispose.call(this);
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ Html5.prototype.createEl = function createEl() {
+ var el = this.options_.tag;
+
+ // Check if this browser supports moving the element into the box.
+ // On the iPhone video will break if you move the element,
+ // So we have to create a brand new element.
+ if (!el || this['movingMediaElementInDOM'] === false) {
+
+ // If the original tag is still there, clone and remove it.
+ if (el) {
+ var clone = el.cloneNode(true);
+ el.parentNode.insertBefore(clone, el);
+ Html5.disposeMediaElement(el);
+ el = clone;
+ } else {
+ el = _globalDocument2['default'].createElement('video');
+
+ // determine if native controls should be used
+ var tagAttributes = this.options_.tag && Dom.getElAttributes(this.options_.tag);
+ var attributes = _utilsMergeOptionsJs2['default']({}, tagAttributes);
+ if (!browser.TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
+ delete attributes.controls;
+ }
+
+ Dom.setElAttributes(el, _objectAssign2['default'](attributes, {
+ id: this.options_.techId,
+ 'class': 'vjs-tech'
+ }));
+ }
+ }
+
+ // Update specific tag settings, in case they were overridden
+ var settingsAttrs = ['autoplay', 'preload', 'loop', 'muted'];
+ for (var i = settingsAttrs.length - 1; i >= 0; i--) {
+ var attr = settingsAttrs[i];
+ var overwriteAttrs = {};
+ if (typeof this.options_[attr] !== 'undefined') {
+ overwriteAttrs[attr] = this.options_[attr];
+ }
+ Dom.setElAttributes(el, overwriteAttrs);
+ }
+
+ return el;
+ // jenniisawesome = true;
+ };
+
+ // If we're loading the playback object after it has started loading
+ // or playing the video (often with autoplay on) then the loadstart event
+ // has already fired and we need to fire it manually because many things
+ // rely on it.
+
+ Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
+ var _this3 = this;
+
+ if (el.networkState === 0 || el.networkState === 3) {
+ // The video element hasn't started loading the source yet
+ // or didn't find a source
+ return;
+ }
+
+ if (el.readyState === 0) {
+ var _ret = (function () {
+ // NetworkState is set synchronously BUT loadstart is fired at the
+ // end of the current stack, usually before setInterval(fn, 0).
+ // So at this point we know loadstart may have already fired or is
+ // about to fire, and either way the player hasn't seen it yet.
+ // We don't want to fire loadstart prematurely here and cause a
+ // double loadstart so we'll wait and see if it happens between now
+ // and the next loop, and fire it if not.
+ // HOWEVER, we also want to make sure it fires before loadedmetadata
+ // which could also happen between now and the next loop, so we'll
+ // watch for that also.
+ var loadstartFired = false;
+ var setLoadstartFired = function setLoadstartFired() {
+ loadstartFired = true;
+ };
+ _this3.on('loadstart', setLoadstartFired);
+
+ var triggerLoadstart = function triggerLoadstart() {
+ // We did miss the original loadstart. Make sure the player
+ // sees loadstart before loadedmetadata
+ if (!loadstartFired) {
+ this.trigger('loadstart');
+ }
+ };
+ _this3.on('loadedmetadata', triggerLoadstart);
+
+ _this3.ready(function () {
+ this.off('loadstart', setLoadstartFired);
+ this.off('loadedmetadata', triggerLoadstart);
+
+ if (!loadstartFired) {
+ // We did miss the original native loadstart. Fire it now.
+ this.trigger('loadstart');
+ }
+ });
+
+ return {
+ v: undefined
+ };
+ })();
+
+ if (typeof _ret === 'object') return _ret.v;
+ }
+
+ // From here on we know that loadstart already fired and we missed it.
+ // The other readyState events aren't as much of a problem if we double
+ // them, so not going to go to as much trouble as loadstart to prevent
+ // that unless we find reason to.
+ var eventsToTrigger = ['loadstart'];
+
+ // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
+ eventsToTrigger.push('loadedmetadata');
+
+ // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
+ if (el.readyState >= 2) {
+ eventsToTrigger.push('loadeddata');
+ }
+
+ // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
+ if (el.readyState >= 3) {
+ eventsToTrigger.push('canplay');
+ }
+
+ // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
+ if (el.readyState >= 4) {
+ eventsToTrigger.push('canplaythrough');
+ }
+
+ // We still need to give the player time to add event listeners
+ this.ready(function () {
+ eventsToTrigger.forEach(function (type) {
+ this.trigger(type);
+ }, this);
+ });
+ };
+
+ Html5.prototype.proxyNativeTextTracks_ = function proxyNativeTextTracks_() {
+ var tt = this.el().textTracks;
+
+ if (tt) {
+ // Add tracks - if player is initialised after DOM loaded, textTracks
+ // will not trigger addtrack
+ for (var i = 0; i < tt.length; i++) {
+ this.textTracks().addTrack_(tt[i]);
+ }
+
+ if (tt.addEventListener) {
+ tt.addEventListener('change', this.handleTextTrackChange_);
+ tt.addEventListener('addtrack', this.handleTextTrackAdd_);
+ tt.addEventListener('removetrack', this.handleTextTrackRemove_);
+ }
+
+ // Remove (native) texttracks that are not used anymore
+ this.on('loadstart', this.removeOldTextTracks_);
+ }
+ };
+
+ Html5.prototype.handleTextTrackChange = function handleTextTrackChange(e) {
+ var tt = this.textTracks();
+ this.textTracks().trigger({
+ type: 'change',
+ target: tt,
+ currentTarget: tt,
+ srcElement: tt
+ });
+ };
+
+ Html5.prototype.handleTextTrackAdd = function handleTextTrackAdd(e) {
+ this.textTracks().addTrack_(e.track);
+ };
+
+ Html5.prototype.handleTextTrackRemove = function handleTextTrackRemove(e) {
+ this.textTracks().removeTrack_(e.track);
+ };
+
+ Html5.prototype.handleVideoTrackChange_ = function handleVideoTrackChange_(e) {
+ var vt = this.videoTracks();
+ this.videoTracks().trigger({
+ type: 'change',
+ target: vt,
+ currentTarget: vt,
+ srcElement: vt
+ });
+ };
+
+ Html5.prototype.handleVideoTrackAdd_ = function handleVideoTrackAdd_(e) {
+ this.videoTracks().addTrack_(e.track);
+ };
+
+ Html5.prototype.handleVideoTrackRemove_ = function handleVideoTrackRemove_(e) {
+ this.videoTracks().removeTrack_(e.track);
+ };
+
+ Html5.prototype.handleAudioTrackChange_ = function handleAudioTrackChange_(e) {
+ var audioTrackList = this.audioTracks();
+ this.audioTracks().trigger({
+ type: 'change',
+ target: audioTrackList,
+ currentTarget: audioTrackList,
+ srcElement: audioTrackList
+ });
+ };
+
+ Html5.prototype.handleAudioTrackAdd_ = function handleAudioTrackAdd_(e) {
+ this.audioTracks().addTrack_(e.track);
+ };
+
+ Html5.prototype.handleAudioTrackRemove_ = function handleAudioTrackRemove_(e) {
+ this.audioTracks().removeTrack_(e.track);
+ };
+
+ /**
+ * This is a helper function that is used in removeOldTextTracks_, removeOldAudioTracks_ and
+ * removeOldVideoTracks_
+ * @param {Track[]} techTracks Tracks for this tech
+ * @param {Track[]} elTracks Tracks for the HTML5 video element
+ * @private
+ */
+
+ Html5.prototype.removeOldTracks_ = function removeOldTracks_(techTracks, elTracks) {
+ // This will loop over the techTracks and check if they are still used by the HTML5 video element
+ // If not, they will be removed from the emulated list
+ var removeTracks = [];
+ if (!elTracks) {
+ return;
+ }
+
+ for (var i = 0; i < techTracks.length; i++) {
+ var techTrack = techTracks[i];
+
+ var found = false;
+ for (var j = 0; j < elTracks.length; j++) {
+ if (elTracks[j] === techTrack) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ removeTracks.push(techTrack);
+ }
+ }
+
+ for (var i = 0; i < removeTracks.length; i++) {
+ var _track = removeTracks[i];
+ techTracks.removeTrack_(_track);
+ }
+ };
+
+ Html5.prototype.removeOldTextTracks_ = function removeOldTextTracks_() {
+ var techTracks = this.textTracks();
+ var elTracks = this.el().textTracks;
+ this.removeOldTracks_(techTracks, elTracks);
+ };
+
+ Html5.prototype.removeOldAudioTracks_ = function removeOldAudioTracks_() {
+ var techTracks = this.audioTracks();
+ var elTracks = this.el().audioTracks;
+ this.removeOldTracks_(techTracks, elTracks);
+ };
+
+ Html5.prototype.removeOldVideoTracks_ = function removeOldVideoTracks_() {
+ var techTracks = this.videoTracks();
+ var elTracks = this.el().videoTracks;
+ this.removeOldTracks_(techTracks, elTracks);
+ };
+
+ /**
+ * Play for html5 tech
+ *
+ * @method play
+ */
+
+ Html5.prototype.play = function play() {
+ this.el_.play();
+ };
+
+ /**
+ * Pause for html5 tech
+ *
+ * @method pause
+ */
+
+ Html5.prototype.pause = function pause() {
+ this.el_.pause();
+ };
+
+ /**
+ * Paused for html5 tech
+ *
+ * @return {Boolean}
+ * @method paused
+ */
+
+ Html5.prototype.paused = function paused() {
+ return this.el_.paused;
+ };
+
+ /**
+ * Get current time
+ *
+ * @return {Number}
+ * @method currentTime
+ */
+
+ Html5.prototype.currentTime = function currentTime() {
+ return this.el_.currentTime;
+ };
+
+ /**
+ * Set current time
+ *
+ * @param {Number} seconds Current time of video
+ * @method setCurrentTime
+ */
+
+ Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
+ try {
+ this.el_.currentTime = seconds;
+ } catch (e) {
+ _utilsLogJs2['default'](e, 'Video is not ready. (Video.js)');
+ // this.warning(VideoJS.warnings.videoNotReady);
+ }
+ };
+
+ /**
+ * Get duration
+ *
+ * @return {Number}
+ * @method duration
+ */
+
+ Html5.prototype.duration = function duration() {
+ return this.el_.duration || 0;
+ };
+
+ /**
+ * Get a TimeRange object that represents the intersection
+ * of the time ranges for which the user agent has all
+ * relevant media
+ *
+ * @return {TimeRangeObject}
+ * @method buffered
+ */
+
+ Html5.prototype.buffered = function buffered() {
+ return this.el_.buffered;
+ };
+
+ /**
+ * Get volume level
+ *
+ * @return {Number}
+ * @method volume
+ */
+
+ Html5.prototype.volume = function volume() {
+ return this.el_.volume;
+ };
+
+ /**
+ * Set volume level
+ *
+ * @param {Number} percentAsDecimal Volume percent as a decimal
+ * @method setVolume
+ */
+
+ Html5.prototype.setVolume = function setVolume(percentAsDecimal) {
+ this.el_.volume = percentAsDecimal;
+ };
+
+ /**
+ * Get if muted
+ *
+ * @return {Boolean}
+ * @method muted
+ */
+
+ Html5.prototype.muted = function muted() {
+ return this.el_.muted;
+ };
+
+ /**
+ * Set muted
+ *
+ * @param {Boolean} If player is to be muted or note
+ * @method setMuted
+ */
+
+ Html5.prototype.setMuted = function setMuted(muted) {
+ this.el_.muted = muted;
+ };
+
+ /**
+ * Get player width
+ *
+ * @return {Number}
+ * @method width
+ */
+
+ Html5.prototype.width = function width() {
+ return this.el_.offsetWidth;
+ };
+
+ /**
+ * Get player height
+ *
+ * @return {Number}
+ * @method height
+ */
+
+ Html5.prototype.height = function height() {
+ return this.el_.offsetHeight;
+ };
+
+ /**
+ * Get if there is fullscreen support
+ *
+ * @return {Boolean}
+ * @method supportsFullScreen
+ */
+
+ Html5.prototype.supportsFullScreen = function supportsFullScreen() {
+ if (typeof this.el_.webkitEnterFullScreen === 'function') {
+ var userAgent = _globalWindow2['default'].navigator.userAgent;
+ // Seems to be broken in Chromium/Chrome && Safari in Leopard
+ if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * Request to enter fullscreen
+ *
+ * @method enterFullScreen
+ */
+
+ Html5.prototype.enterFullScreen = function enterFullScreen() {
+ var video = this.el_;
+
+ if ('webkitDisplayingFullscreen' in video) {
+ this.one('webkitbeginfullscreen', function () {
+ this.one('webkitendfullscreen', function () {
+ this.trigger('fullscreenchange', { isFullscreen: false });
+ });
+
+ this.trigger('fullscreenchange', { isFullscreen: true });
+ });
+ }
+
+ if (video.paused && video.networkState <= video.HAVE_METADATA) {
+ // attempt to prime the video element for programmatic access
+ // this isn't necessary on the desktop but shouldn't hurt
+ this.el_.play();
+
+ // playing and pausing synchronously during the transition to fullscreen
+ // can get iOS ~6.1 devices into a play/pause loop
+ this.setTimeout(function () {
+ video.pause();
+ video.webkitEnterFullScreen();
+ }, 0);
+ } else {
+ video.webkitEnterFullScreen();
+ }
+ };
+
+ /**
+ * Request to exit fullscreen
+ *
+ * @method exitFullScreen
+ */
+
+ Html5.prototype.exitFullScreen = function exitFullScreen() {
+ this.el_.webkitExitFullScreen();
+ };
+
+ /**
+ * Get/set video
+ *
+ * @param {Object=} src Source object
+ * @return {Object}
+ * @method src
+ */
+
+ Html5.prototype.src = function src(_src) {
+ if (_src === undefined) {
+ return this.el_.src;
+ } else {
+ // Setting src through `src` instead of `setSrc` will be deprecated
+ this.setSrc(_src);
+ }
+ };
+
+ /**
+ * Set video
+ *
+ * @param {Object} src Source object
+ * @deprecated
+ * @method setSrc
+ */
+
+ Html5.prototype.setSrc = function setSrc(src) {
+ this.el_.src = src;
+ };
+
+ /**
+ * Load media into player
+ *
+ * @method load
+ */
+
+ Html5.prototype.load = function load() {
+ this.el_.load();
+ };
+
+ /**
+ * Reset the tech. Removes all sources and calls `load`.
+ *
+ * @method reset
+ */
+
+ Html5.prototype.reset = function reset() {
+ Html5.resetMediaElement(this.el_);
+ };
+
+ /**
+ * Get current source
+ *
+ * @return {Object}
+ * @method currentSrc
+ */
+
+ Html5.prototype.currentSrc = function currentSrc() {
+ if (this.currentSource_) {
+ return this.currentSource_.src;
+ } else {
+ return this.el_.currentSrc;
+ }
+ };
+
+ /**
+ * Get poster
+ *
+ * @return {String}
+ * @method poster
+ */
+
+ Html5.prototype.poster = function poster() {
+ return this.el_.poster;
+ };
+
+ /**
+ * Set poster
+ *
+ * @param {String} val URL to poster image
+ * @method
+ */
+
+ Html5.prototype.setPoster = function setPoster(val) {
+ this.el_.poster = val;
+ };
+
+ /**
+ * Get preload attribute
+ *
+ * @return {String}
+ * @method preload
+ */
+
+ Html5.prototype.preload = function preload() {
+ return this.el_.preload;
+ };
+
+ /**
+ * Set preload attribute
+ *
+ * @param {String} val Value for preload attribute
+ * @method setPreload
+ */
+
+ Html5.prototype.setPreload = function setPreload(val) {
+ this.el_.preload = val;
+ };
+
+ /**
+ * Get autoplay attribute
+ *
+ * @return {String}
+ * @method autoplay
+ */
+
+ Html5.prototype.autoplay = function autoplay() {
+ return this.el_.autoplay;
+ };
+
+ /**
+ * Set autoplay attribute
+ *
+ * @param {String} val Value for preload attribute
+ * @method setAutoplay
+ */
+
+ Html5.prototype.setAutoplay = function setAutoplay(val) {
+ this.el_.autoplay = val;
+ };
+
+ /**
+ * Get controls attribute
+ *
+ * @return {String}
+ * @method controls
+ */
+
+ Html5.prototype.controls = function controls() {
+ return this.el_.controls;
+ };
+
+ /**
+ * Set controls attribute
+ *
+ * @param {String} val Value for controls attribute
+ * @method setControls
+ */
+
+ Html5.prototype.setControls = function setControls(val) {
+ this.el_.controls = !!val;
+ };
+
+ /**
+ * Get loop attribute
+ *
+ * @return {String}
+ * @method loop
+ */
+
+ Html5.prototype.loop = function loop() {
+ return this.el_.loop;
+ };
+
+ /**
+ * Set loop attribute
+ *
+ * @param {String} val Value for loop attribute
+ * @method setLoop
+ */
+
+ Html5.prototype.setLoop = function setLoop(val) {
+ this.el_.loop = val;
+ };
+
+ /**
+ * Get error value
+ *
+ * @return {String}
+ * @method error
+ */
+
+ Html5.prototype.error = function error() {
+ return this.el_.error;
+ };
+
+ /**
+ * Get whether or not the player is in the "seeking" state
+ *
+ * @return {Boolean}
+ * @method seeking
+ */
+
+ Html5.prototype.seeking = function seeking() {
+ return this.el_.seeking;
+ };
+
+ /**
+ * Get a TimeRanges object that represents the
+ * ranges of the media resource to which it is possible
+ * for the user agent to seek.
+ *
+ * @return {TimeRangeObject}
+ * @method seekable
+ */
+
+ Html5.prototype.seekable = function seekable() {
+ return this.el_.seekable;
+ };
+
+ /**
+ * Get if video ended
+ *
+ * @return {Boolean}
+ * @method ended
+ */
+
+ Html5.prototype.ended = function ended() {
+ return this.el_.ended;
+ };
+
+ /**
+ * Get the value of the muted content attribute
+ * This attribute has no dynamic effect, it only
+ * controls the default state of the element
+ *
+ * @return {Boolean}
+ * @method defaultMuted
+ */
+
+ Html5.prototype.defaultMuted = function defaultMuted() {
+ return this.el_.defaultMuted;
+ };
+
+ /**
+ * Get desired speed at which the media resource is to play
+ *
+ * @return {Number}
+ * @method playbackRate
+ */
+
+ Html5.prototype.playbackRate = function playbackRate() {
+ return this.el_.playbackRate;
+ };
+
+ /**
+ * Returns a TimeRanges object that represents the ranges of the
+ * media resource that the user agent has played.
+ * @return {TimeRangeObject} the range of points on the media
+ * timeline that has been reached through normal playback
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-played
+ */
+
+ Html5.prototype.played = function played() {
+ return this.el_.played;
+ };
+
+ /**
+ * Set desired speed at which the media resource is to play
+ *
+ * @param {Number} val Speed at which the media resource is to play
+ * @method setPlaybackRate
+ */
+
+ Html5.prototype.setPlaybackRate = function setPlaybackRate(val) {
+ this.el_.playbackRate = val;
+ };
+
+ /**
+ * Get the current state of network activity for the element, from
+ * the list below
+ * NETWORK_EMPTY (numeric value 0)
+ * NETWORK_IDLE (numeric value 1)
+ * NETWORK_LOADING (numeric value 2)
+ * NETWORK_NO_SOURCE (numeric value 3)
+ *
+ * @return {Number}
+ * @method networkState
+ */
+
+ Html5.prototype.networkState = function networkState() {
+ return this.el_.networkState;
+ };
+
+ /**
+ * Get a value that expresses the current state of the element
+ * with respect to rendering the current playback position, from
+ * the codes in the list below
+ * HAVE_NOTHING (numeric value 0)
+ * HAVE_METADATA (numeric value 1)
+ * HAVE_CURRENT_DATA (numeric value 2)
+ * HAVE_FUTURE_DATA (numeric value 3)
+ * HAVE_ENOUGH_DATA (numeric value 4)
+ *
+ * @return {Number}
+ * @method readyState
+ */
+
+ Html5.prototype.readyState = function readyState() {
+ return this.el_.readyState;
+ };
+
+ /**
+ * Get width of video
+ *
+ * @return {Number}
+ * @method videoWidth
+ */
+
+ Html5.prototype.videoWidth = function videoWidth() {
+ return this.el_.videoWidth;
+ };
+
+ /**
+ * Get height of video
+ *
+ * @return {Number}
+ * @method videoHeight
+ */
+
+ Html5.prototype.videoHeight = function videoHeight() {
+ return this.el_.videoHeight;
+ };
+
+ /**
+ * Get text tracks
+ *
+ * @return {TextTrackList}
+ * @method textTracks
+ */
+
+ Html5.prototype.textTracks = function textTracks() {
+ return _Tech.prototype.textTracks.call(this);
+ };
+
+ /**
+ * Creates and returns a text track object
+ *
+ * @param {String} kind Text track kind (subtitles, captions, descriptions
+ * chapters and metadata)
+ * @param {String=} label Label to identify the text track
+ * @param {String=} language Two letter language abbreviation
+ * @return {TextTrackObject}
+ * @method addTextTrack
+ */
+
+ Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
+ if (!this['featuresNativeTextTracks']) {
+ return _Tech.prototype.addTextTrack.call(this, kind, label, language);
+ }
+
+ return this.el_.addTextTrack(kind, label, language);
+ };
+
+ /**
+ * Creates a remote text track object and returns a html track element
+ *
+ * @param {Object} options The object should contain values for
+ * kind, language, label and src (location of the WebVTT file)
+ * @return {HTMLTrackElement}
+ * @method addRemoteTextTrack
+ */
+
+ Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ if (!this['featuresNativeTextTracks']) {
+ return _Tech.prototype.addRemoteTextTrack.call(this, options);
+ }
+
+ var htmlTrackElement = _globalDocument2['default'].createElement('track');
+
+ if (options.kind) {
+ htmlTrackElement.kind = options.kind;
+ }
+ if (options.label) {
+ htmlTrackElement.label = options.label;
+ }
+ if (options.language || options.srclang) {
+ htmlTrackElement.srclang = options.language || options.srclang;
+ }
+ if (options['default']) {
+ htmlTrackElement['default'] = options['default'];
+ }
+ if (options.id) {
+ htmlTrackElement.id = options.id;
+ }
+ if (options.src) {
+ htmlTrackElement.src = options.src;
+ }
+
+ this.el().appendChild(htmlTrackElement);
+
+ // store HTMLTrackElement and TextTrack to remote list
+ this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
+ this.remoteTextTracks().addTrack_(htmlTrackElement.track);
+
+ return htmlTrackElement;
+ };
+
+ /**
+ * Remove remote text track from TextTrackList object
+ *
+ * @param {TextTrackObject} track Texttrack object to remove
+ * @method removeRemoteTextTrack
+ */
+
+ Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
+ if (!this['featuresNativeTextTracks']) {
+ return _Tech.prototype.removeRemoteTextTrack.call(this, track);
+ }
+
+ var tracks = undefined,
+ i = undefined;
+
+ var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
+
+ // remove HTMLTrackElement and TextTrack from remote list
+ this.remoteTextTrackEls().removeTrackElement_(trackElement);
+ this.remoteTextTracks().removeTrack_(track);
+
+ tracks = this.$$('track');
+
+ i = tracks.length;
+ while (i--) {
+ if (track === tracks[i] || track === tracks[i].track) {
+ this.el().removeChild(tracks[i]);
+ }
+ }
+ };
+
+ return Html5;
+})(_techJs2['default']);
+
+Html5.TEST_VID = _globalDocument2['default'].createElement('video');
+var track = _globalDocument2['default'].createElement('track');
+track.kind = 'captions';
+track.srclang = 'en';
+track.label = 'English';
+Html5.TEST_VID.appendChild(track);
+
+/*
+ * Check if HTML5 video is supported by this browser/device
+ *
+ * @return {Boolean}
+ */
+Html5.isSupported = function () {
+ // IE9 with no Media Player is a LIAR! (#984)
+ try {
+ Html5.TEST_VID['volume'] = 0.5;
+ } catch (e) {
+ return false;
+ }
+
+ return !!Html5.TEST_VID.canPlayType;
+};
+
+// Add Source Handler pattern functions to this tech
+_techJs2['default'].withSourceHandlers(Html5);
+
+/*
+ * The default native source handler.
+ * This simply passes the source to the video element. Nothing fancy.
+ *
+ * @param {Object} source The source object
+ * @param {Html5} tech The instance of the HTML5 tech
+ */
+Html5.nativeSourceHandler = {};
+
+/*
+ * Check if the video element can play the given videotype
+ *
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+Html5.nativeSourceHandler.canPlayType = function (type) {
+ // IE9 on Windows 7 without MediaPlayer throws an error here
+ // https://github.com/videojs/video.js/issues/519
+ try {
+ return Html5.TEST_VID.canPlayType(type);
+ } catch (e) {
+ return '';
+ }
+};
+
+/*
+ * Check if the video element can handle the source natively
+ *
+ * @param {Object} source The source object
+ * @param {Object} options The options passed to the tech
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+Html5.nativeSourceHandler.canHandleSource = function (source, options) {
+ var match, ext;
+
+ // If a type was provided we should rely on that
+ if (source.type) {
+ return Html5.nativeSourceHandler.canPlayType(source.type);
+ } else if (source.src) {
+ // If no type, fall back to checking 'video/[EXTENSION]'
+ ext = Url.getFileExtension(source.src);
+
+ return Html5.nativeSourceHandler.canPlayType('video/' + ext);
+ }
+
+ return '';
+};
+
+/*
+ * Pass the source to the video element
+ * Adaptive source handlers will have more complicated workflows before passing
+ * video data to the video element
+ *
+ * @param {Object} source The source object
+ * @param {Html5} tech The instance of the Html5 tech
+ * @param {Object} options The options to pass to the source
+ */
+Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
+ tech.setSrc(source.src);
+};
+
+/*
+* Clean up the source handler when disposing the player or switching sources..
+* (no cleanup is needed when supporting the format natively)
+*/
+Html5.nativeSourceHandler.dispose = function () {};
+
+// Register the native source handler
+Html5.registerSourceHandler(Html5.nativeSourceHandler);
+
+/*
+ * Check if the volume can be changed in this browser/device.
+ * Volume cannot be changed in a lot of mobile devices.
+ * Specifically, it can't be changed from 1 on iOS.
+ *
+ * @return {Boolean}
+ */
+Html5.canControlVolume = function () {
+ // IE will error if Windows Media Player not installed #3315
+ try {
+ var volume = Html5.TEST_VID.volume;
+ Html5.TEST_VID.volume = volume / 2 + 0.1;
+ return volume !== Html5.TEST_VID.volume;
+ } catch (e) {
+ return false;
+ }
+};
+
+/*
+ * Check if playbackRate is supported in this browser/device.
+ *
+ * @return {Boolean}
+ */
+Html5.canControlPlaybackRate = function () {
+ // Playback rate API is implemented in Android Chrome, but doesn't do anything
+ // https://github.com/videojs/video.js/issues/3180
+ if (browser.IS_ANDROID && browser.IS_CHROME) {
+ return false;
+ }
+ // IE will error if Windows Media Player not installed #3315
+ try {
+ var playbackRate = Html5.TEST_VID.playbackRate;
+ Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
+ return playbackRate !== Html5.TEST_VID.playbackRate;
+ } catch (e) {
+ return false;
+ }
+};
+
+/*
+ * Check to see if native text tracks are supported by this browser/device
+ *
+ * @return {Boolean}
+ */
+Html5.supportsNativeTextTracks = function () {
+ var supportsTextTracks;
+
+ // Figure out native text track support
+ // If mode is a number, we cannot change it because it'll disappear from view.
+ // Browsers with numeric modes include IE10 and older (<=2013) samsung android models.
+ // Firefox isn't playing nice either with modifying the mode
+ // TODO: Investigate firefox: https://github.com/videojs/video.js/issues/1862
+ supportsTextTracks = !!Html5.TEST_VID.textTracks;
+ if (supportsTextTracks && Html5.TEST_VID.textTracks.length > 0) {
+ supportsTextTracks = typeof Html5.TEST_VID.textTracks[0]['mode'] !== 'number';
+ }
+ if (supportsTextTracks && browser.IS_FIREFOX) {
+ supportsTextTracks = false;
+ }
+ if (supportsTextTracks && !('onremovetrack' in Html5.TEST_VID.textTracks)) {
+ supportsTextTracks = false;
+ }
+
+ return supportsTextTracks;
+};
+
+/*
+ * Check to see if native video tracks are supported by this browser/device
+ *
+ * @return {Boolean}
+ */
+Html5.supportsNativeVideoTracks = function () {
+ var supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
+ return supportsVideoTracks;
+};
+
+/*
+ * Check to see if native audio tracks are supported by this browser/device
+ *
+ * @return {Boolean}
+ */
+Html5.supportsNativeAudioTracks = function () {
+ var supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
+ return supportsAudioTracks;
+};
+
+/**
+ * An array of events available on the Html5 tech.
+ *
+ * @private
+ * @type {Array}
+ */
+Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'volumechange'];
+
+/*
+ * Set the tech's volume control support status
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['featuresVolumeControl'] = Html5.canControlVolume();
+
+/*
+ * Set the tech's playbackRate support status
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['featuresPlaybackRate'] = Html5.canControlPlaybackRate();
+
+/*
+ * Set the tech's status on moving the video element.
+ * In iOS, if you move a video element in the DOM, it breaks video playback.
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['movingMediaElementInDOM'] = !browser.IS_IOS;
+
+/*
+ * Set the the tech's fullscreen resize support status.
+ * HTML video is able to automatically resize when going to fullscreen.
+ * (No longer appears to be used. Can probably be removed.)
+ */
+Html5.prototype['featuresFullscreenResize'] = true;
+
+/*
+ * Set the tech's progress event support status
+ * (this disables the manual progress events of the Tech)
+ */
+Html5.prototype['featuresProgressEvents'] = true;
+
+/*
+ * Sets the tech's status on native text track support
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
+
+/**
+ * Sets the tech's status on native text track support
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks();
+
+/**
+ * Sets the tech's status on native audio track support
+ *
+ * @type {Boolean}
+ */
+Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks();
+
+// HTML5 Feature detection and Device Fixes --------------------------------- //
+var canPlayType = undefined;
+var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
+var mp4RE = /^video\/mp4/i;
+
+Html5.patchCanPlayType = function () {
+ // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
+ if (browser.ANDROID_VERSION >= 4.0) {
+ if (!canPlayType) {
+ canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
+ }
+
+ Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
+ if (type && mpegurlRE.test(type)) {
+ return 'maybe';
+ }
+ return canPlayType.call(this, type);
+ };
+ }
+
+ // Override Android 2.2 and less canPlayType method which is broken
+ if (browser.IS_OLD_ANDROID) {
+ if (!canPlayType) {
+ canPlayType = Html5.TEST_VID.constructor.prototype.canPlayType;
+ }
+
+ Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
+ if (type && mp4RE.test(type)) {
+ return 'maybe';
+ }
+ return canPlayType.call(this, type);
+ };
+ }
+};
+
+Html5.unpatchCanPlayType = function () {
+ var r = Html5.TEST_VID.constructor.prototype.canPlayType;
+ Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
+ canPlayType = null;
+ return r;
+};
+
+// by default, patch the video element
+Html5.patchCanPlayType();
+
+Html5.disposeMediaElement = function (el) {
+ if (!el) {
+ return;
+ }
+
+ if (el.parentNode) {
+ el.parentNode.removeChild(el);
+ }
+
+ // remove any child track or source nodes to prevent their loading
+ while (el.hasChildNodes()) {
+ el.removeChild(el.firstChild);
+ }
+
+ // remove any src reference. not setting `src=''` because that causes a warning
+ // in firefox
+ el.removeAttribute('src');
+
+ // force the media element to update its loading state by calling load()
+ // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
+ if (typeof el.load === 'function') {
+ // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
+ (function () {
+ try {
+ el.load();
+ } catch (e) {
+ // not supported
+ }
+ })();
+ }
+};
+
+Html5.resetMediaElement = function (el) {
+ if (!el) {
+ return;
+ }
+
+ var sources = el.querySelectorAll('source');
+ var i = sources.length;
+ while (i--) {
+ el.removeChild(sources[i]);
+ }
+
+ // remove any src reference.
+ // not setting `src=''` because that throws an error
+ el.removeAttribute('src');
+
+ if (typeof el.load === 'function') {
+ // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
+ (function () {
+ try {
+ el.load();
+ } catch (e) {}
+ })();
+ }
+};
+
+_component2['default'].registerComponent('Html5', Html5);
+_techJs2['default'].registerTech('Html5', Html5);
+exports['default'] = Html5;
+module.exports = exports['default'];
+
+},{"../../../src/js/tracks/text-track.js":138,"../component":71,"../utils/browser.js":144,"../utils/dom.js":146,"../utils/fn.js":148,"../utils/log.js":151,"../utils/merge-options.js":152,"../utils/to-title-case.js":155,"../utils/url.js":156,"./tech.js":128,"global/document":9,"global/window":10,"object.assign":57,"tsml":64}],127:[function(_dereq_,module,exports){
+/**
+ * @file loader.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _componentJs = _dereq_('../component.js');
+
+var _componentJs2 = _interopRequireDefault(_componentJs);
+
+var _techJs = _dereq_('./tech.js');
+
+var _techJs2 = _interopRequireDefault(_techJs);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _utilsToTitleCaseJs = _dereq_('../utils/to-title-case.js');
+
+var _utilsToTitleCaseJs2 = _interopRequireDefault(_utilsToTitleCaseJs);
+
+/**
+ * The Media Loader is the component that decides which playback technology to load
+ * when the player is initialized.
+ *
+ * @param {Object} player Main Player
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends Component
+ * @class MediaLoader
+ */
+
+var MediaLoader = (function (_Component) {
+ _inherits(MediaLoader, _Component);
+
+ function MediaLoader(player, options, ready) {
+ _classCallCheck(this, MediaLoader);
+
+ _Component.call(this, player, options, ready);
+
+ // If there are no sources when the player is initialized,
+ // load the first supported playback technology.
+
+ if (!options.playerOptions['sources'] || options.playerOptions['sources'].length === 0) {
+ for (var i = 0, j = options.playerOptions['techOrder']; i < j.length; i++) {
+ var techName = _utilsToTitleCaseJs2['default'](j[i]);
+ var tech = _techJs2['default'].getTech(techName);
+ // Support old behavior of techs being registered as components.
+ // Remove once that deprecated behavior is removed.
+ if (!techName) {
+ tech = _componentJs2['default'].getComponent(techName);
+ }
+
+ // Check if the browser supports this technology
+ if (tech && tech.isSupported()) {
+ player.loadTech_(techName);
+ break;
+ }
+ }
+ } else {
+ // // Loop through playback technologies (HTML5, Flash) and check for support.
+ // // Then load the best source.
+ // // A few assumptions here:
+ // // All playback technologies respect preload false.
+ player.src(options.playerOptions['sources']);
+ }
+ }
+
+ return MediaLoader;
+})(_componentJs2['default']);
+
+_componentJs2['default'].registerComponent('MediaLoader', MediaLoader);
+exports['default'] = MediaLoader;
+module.exports = exports['default'];
+
+},{"../component.js":71,"../utils/to-title-case.js":155,"./tech.js":128,"global/window":10}],128:[function(_dereq_,module,exports){
+/**
+ * @file tech.js
+ * Media Technology Controller - Base class for media playback
+ * technology controllers like Flash and HTML5
+ */
+
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _tracksHtmlTrackElement = _dereq_('../tracks/html-track-element');
+
+var _tracksHtmlTrackElement2 = _interopRequireDefault(_tracksHtmlTrackElement);
+
+var _tracksHtmlTrackElementList = _dereq_('../tracks/html-track-element-list');
+
+var _tracksHtmlTrackElementList2 = _interopRequireDefault(_tracksHtmlTrackElementList);
+
+var _utilsMergeOptionsJs = _dereq_('../utils/merge-options.js');
+
+var _utilsMergeOptionsJs2 = _interopRequireDefault(_utilsMergeOptionsJs);
+
+var _tracksTextTrack = _dereq_('../tracks/text-track');
+
+var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack);
+
+var _tracksTextTrackList = _dereq_('../tracks/text-track-list');
+
+var _tracksTextTrackList2 = _interopRequireDefault(_tracksTextTrackList);
+
+var _tracksVideoTrack = _dereq_('../tracks/video-track');
+
+var _tracksVideoTrack2 = _interopRequireDefault(_tracksVideoTrack);
+
+var _tracksVideoTrackList = _dereq_('../tracks/video-track-list');
+
+var _tracksVideoTrackList2 = _interopRequireDefault(_tracksVideoTrackList);
+
+var _tracksAudioTrackList = _dereq_('../tracks/audio-track-list');
+
+var _tracksAudioTrackList2 = _interopRequireDefault(_tracksAudioTrackList);
+
+var _tracksAudioTrack = _dereq_('../tracks/audio-track');
+
+var _tracksAudioTrack2 = _interopRequireDefault(_tracksAudioTrack);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsLogJs = _dereq_('../utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _utilsUrlJs = _dereq_('../utils/url.js');
+
+var Url = _interopRequireWildcard(_utilsUrlJs);
+
+var _utilsTimeRangesJs = _dereq_('../utils/time-ranges.js');
+
+var _utilsBufferJs = _dereq_('../utils/buffer.js');
+
+var _mediaErrorJs = _dereq_('../media-error.js');
+
+var _mediaErrorJs2 = _interopRequireDefault(_mediaErrorJs);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _md5 = _dereq_('md5');
+
+var _md52 = _interopRequireDefault(_md5);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+/**
+ * Base class for media (HTML5 Video, Flash) controllers
+ *
+ * @param {Object=} options Options object
+ * @param {Function=} ready Ready callback function
+ * @extends Component
+ * @class Tech
+ */
+
+var Tech = (function (_Component) {
+ _inherits(Tech, _Component);
+
+ function Tech() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+ var ready = arguments.length <= 1 || arguments[1] === undefined ? function () {} : arguments[1];
+
+ _classCallCheck(this, Tech);
+
+ // we don't want the tech to report user activity automatically.
+ // This is done manually in addControlsListeners
+ options.reportTouchActivity = false;
+ _Component.call(this, null, options, ready);
+
+ // keep track of whether the current source has played at all to
+ // implement a very limited played()
+ this.hasStarted_ = false;
+ this.on('playing', function () {
+ this.hasStarted_ = true;
+ });
+ this.on('loadstart', function () {
+ this.hasStarted_ = false;
+ });
+
+ this.textTracks_ = options.textTracks;
+ this.videoTracks_ = options.videoTracks;
+ this.audioTracks_ = options.audioTracks;
+
+ // Manually track progress in cases where the browser/flash player doesn't report it.
+ if (!this.featuresProgressEvents) {
+ this.manualProgressOn();
+ }
+
+ // Manually track timeupdates in cases where the browser/flash player doesn't report it.
+ if (!this.featuresTimeupdateEvents) {
+ this.manualTimeUpdatesOn();
+ }
+
+ if (options.nativeCaptions === false || options.nativeTextTracks === false) {
+ this.featuresNativeTextTracks = false;
+ }
+
+ if (!this.featuresNativeTextTracks) {
+ this.on('ready', this.emulateTextTracks);
+ }
+
+ this.initTextTrackListeners();
+ this.initTrackListeners();
+
+ // Turn on component tap events
+ this.emitTapEvents();
+ }
+
+ /**
+ * List of associated text tracks
+ *
+ * @type {TextTrackList}
+ * @private
+ */
+
+ /* Fallbacks for unsupported event types
+ ================================================================================ */
+ // Manually trigger progress events based on changes to the buffered amount
+ // Many flash players and older HTML5 browsers don't send progress or progress-like events
+ /**
+ * Turn on progress events
+ *
+ * @method manualProgressOn
+ */
+
+ Tech.prototype.manualProgressOn = function manualProgressOn() {
+ this.on('durationchange', this.onDurationChange);
+
+ this.manualProgress = true;
+
+ // Trigger progress watching when a source begins loading
+ this.one('ready', this.trackProgress);
+ };
+
+ /**
+ * Turn off progress events
+ *
+ * @method manualProgressOff
+ */
+
+ Tech.prototype.manualProgressOff = function manualProgressOff() {
+ this.manualProgress = false;
+ this.stopTrackingProgress();
+
+ this.off('durationchange', this.onDurationChange);
+ };
+
+ /**
+ * Track progress
+ *
+ * @method trackProgress
+ */
+
+ Tech.prototype.trackProgress = function trackProgress() {
+ this.stopTrackingProgress();
+ this.progressInterval = this.setInterval(Fn.bind(this, function () {
+ // Don't trigger unless buffered amount is greater than last time
+
+ var numBufferedPercent = this.bufferedPercent();
+
+ if (this.bufferedPercent_ !== numBufferedPercent) {
+ this.trigger('progress');
+ }
+
+ this.bufferedPercent_ = numBufferedPercent;
+
+ if (numBufferedPercent === 1) {
+ this.stopTrackingProgress();
+ }
+ }), 500);
+ };
+
+ /**
+ * Update duration
+ *
+ * @method onDurationChange
+ */
+
+ Tech.prototype.onDurationChange = function onDurationChange() {
+ this.duration_ = this.duration();
+ };
+
+ /**
+ * Create and get TimeRange object for buffering
+ *
+ * @return {TimeRangeObject}
+ * @method buffered
+ */
+
+ Tech.prototype.buffered = function buffered() {
+ return _utilsTimeRangesJs.createTimeRange(0, 0);
+ };
+
+ /**
+ * Get buffered percent
+ *
+ * @return {Number}
+ * @method bufferedPercent
+ */
+
+ Tech.prototype.bufferedPercent = function bufferedPercent() {
+ return _utilsBufferJs.bufferedPercent(this.buffered(), this.duration_);
+ };
+
+ /**
+ * Stops tracking progress by clearing progress interval
+ *
+ * @method stopTrackingProgress
+ */
+
+ Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
+ this.clearInterval(this.progressInterval);
+ };
+
+ /*! Time Tracking -------------------------------------------------------------- */
+ /**
+ * Set event listeners for on play and pause and tracking current time
+ *
+ * @method manualTimeUpdatesOn
+ */
+
+ Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
+ this.manualTimeUpdates = true;
+
+ this.on('play', this.trackCurrentTime);
+ this.on('pause', this.stopTrackingCurrentTime);
+ };
+
+ /**
+ * Remove event listeners for on play and pause and tracking current time
+ *
+ * @method manualTimeUpdatesOff
+ */
+
+ Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
+ this.manualTimeUpdates = false;
+ this.stopTrackingCurrentTime();
+ this.off('play', this.trackCurrentTime);
+ this.off('pause', this.stopTrackingCurrentTime);
+ };
+
+ /**
+ * Tracks current time
+ *
+ * @method trackCurrentTime
+ */
+
+ Tech.prototype.trackCurrentTime = function trackCurrentTime() {
+ if (this.currentTimeInterval) {
+ this.stopTrackingCurrentTime();
+ }
+ this.currentTimeInterval = this.setInterval(function () {
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
+ }, 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
+ };
+
+ /**
+ * Turn off play progress tracking (when paused or dragging)
+ *
+ * @method stopTrackingCurrentTime
+ */
+
+ Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
+ this.clearInterval(this.currentTimeInterval);
+
+ // #1002 - if the video ends right before the next timeupdate would happen,
+ // the progress bar won't make it all the way to the end
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
+ };
+
+ /**
+ * Turn off any manual progress or timeupdate tracking
+ *
+ * @method dispose
+ */
+
+ Tech.prototype.dispose = function dispose() {
+
+ // clear out all tracks because we can't reuse them between techs
+ this.clearTracks(['audio', 'video', 'text']);
+
+ // Turn off any manual progress or timeupdate tracking
+ if (this.manualProgress) {
+ this.manualProgressOff();
+ }
+
+ if (this.manualTimeUpdates) {
+ this.manualTimeUpdatesOff();
+ }
+
+ _Component.prototype.dispose.call(this);
+ };
+
+ /**
+ * clear out a track list, or multiple track lists
+ *
+ * Note: Techs without source handlers should call this between
+ * sources for video & audio tracks, as usually you don't want
+ * to use them between tracks and we have no automatic way to do
+ * it for you
+ *
+ * @method clearTracks
+ * @param {Array|String} types type(s) of track lists to empty
+ */
+
+ Tech.prototype.clearTracks = function clearTracks(types) {
+ var _this = this;
+
+ types = [].concat(types);
+ // clear out all tracks because we can't reuse them between techs
+ types.forEach(function (type) {
+ var list = _this[type + 'Tracks']() || [];
+ var i = list.length;
+ while (i--) {
+ var track = list[i];
+ if (type === 'text') {
+ _this.removeRemoteTextTrack(track);
+ }
+ list.removeTrack_(track);
+ }
+ });
+ };
+
+ /**
+ * Reset the tech. Removes all sources and resets readyState.
+ *
+ * @method reset
+ */
+
+ Tech.prototype.reset = function reset() {};
+
+ /**
+ * When invoked without an argument, returns a MediaError object
+ * representing the current error state of the player or null if
+ * there is no error. When invoked with an argument, set the current
+ * error state of the player.
+ * @param {MediaError=} err Optional an error object
+ * @return {MediaError} the current error object or null
+ * @method error
+ */
+
+ Tech.prototype.error = function error(err) {
+ if (err !== undefined) {
+ if (err instanceof _mediaErrorJs2['default']) {
+ this.error_ = err;
+ } else {
+ this.error_ = new _mediaErrorJs2['default'](err);
+ }
+ this.trigger('error');
+ }
+ return this.error_;
+ };
+
+ /**
+ * Return the time ranges that have been played through for the
+ * current source. This implementation is incomplete. It does not
+ * track the played time ranges, only whether the source has played
+ * at all or not.
+ * @return {TimeRangeObject} a single time range if this video has
+ * played or an empty set of ranges if not.
+ * @method played
+ */
+
+ Tech.prototype.played = function played() {
+ if (this.hasStarted_) {
+ return _utilsTimeRangesJs.createTimeRange(0, 0);
+ }
+ return _utilsTimeRangesJs.createTimeRange();
+ };
+
+ /**
+ * Set current time
+ *
+ * @method setCurrentTime
+ */
+
+ Tech.prototype.setCurrentTime = function setCurrentTime() {
+ // improve the accuracy of manual timeupdates
+ if (this.manualTimeUpdates) {
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
+ }
+ };
+
+ /**
+ * Initialize texttrack listeners
+ *
+ * @method initTextTrackListeners
+ */
+
+ Tech.prototype.initTextTrackListeners = function initTextTrackListeners() {
+ var textTrackListChanges = Fn.bind(this, function () {
+ this.trigger('texttrackchange');
+ });
+
+ var tracks = this.textTracks();
+
+ if (!tracks) return;
+
+ tracks.addEventListener('removetrack', textTrackListChanges);
+ tracks.addEventListener('addtrack', textTrackListChanges);
+
+ this.on('dispose', Fn.bind(this, function () {
+ tracks.removeEventListener('removetrack', textTrackListChanges);
+ tracks.removeEventListener('addtrack', textTrackListChanges);
+ }));
+ };
+
+ /**
+ * Initialize audio and video track listeners
+ *
+ * @method initTrackListeners
+ */
+
+ Tech.prototype.initTrackListeners = function initTrackListeners() {
+ var _this2 = this;
+
+ var trackTypes = ['video', 'audio'];
+
+ trackTypes.forEach(function (type) {
+ var trackListChanges = function trackListChanges() {
+ _this2.trigger(type + 'trackchange');
+ };
+
+ var tracks = _this2[type + 'Tracks']();
+
+ tracks.addEventListener('removetrack', trackListChanges);
+ tracks.addEventListener('addtrack', trackListChanges);
+
+ _this2.on('dispose', function () {
+ tracks.removeEventListener('removetrack', trackListChanges);
+ tracks.removeEventListener('addtrack', trackListChanges);
+ });
+ });
+ };
+
+ /**
+ * Emulate texttracks
+ *
+ * @method emulateTextTracks
+ */
+
+ Tech.prototype.emulateTextTracks = function emulateTextTracks() {
+ var _this3 = this;
+
+ var tracks = this.textTracks();
+ if (!tracks) {
+ return;
+ }
+
+ if (!_globalWindow2['default']['WebVTT'] && this.el().parentNode != null) {
+ (function () {
+ var script = _globalDocument2['default'].createElement('script');
+ script.src = _this3.options_['vtt.js'] || 'https://cdn.rawgit.com/gkatsev/vtt.js/vjs-v0.12.1/dist/vtt.min.js';
+ script.onload = function () {
+ _this3.trigger('vttjsloaded');
+ };
+ script.onerror = function () {
+ _this3.trigger('vttjserror');
+ };
+ _this3.on('dispose', function () {
+ script.onload = null;
+ script.onerror = null;
+ });
+ // but have not loaded yet and we set it to true before the inject so that
+ // we don't overwrite the injected window.WebVTT if it loads right away
+ _globalWindow2['default']['WebVTT'] = true;
+ _this3.el().parentNode.appendChild(script);
+ })();
+ }
+
+ var updateDisplay = function updateDisplay() {
+ return _this3.trigger('texttrackchange');
+ };
+ var textTracksChanges = function textTracksChanges() {
+ updateDisplay();
+
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+ track.removeEventListener('cuechange', updateDisplay);
+ if (track.mode === 'showing') {
+ track.addEventListener('cuechange', updateDisplay);
+ }
+ }
+ };
+
+ textTracksChanges();
+ tracks.addEventListener('change', textTracksChanges);
+
+ this.on('dispose', function () {
+ tracks.removeEventListener('change', textTracksChanges);
+ });
+ };
+
+ /**
+ * Get videotracks
+ *
+ * @returns {VideoTrackList}
+ * @method videoTracks
+ */
+
+ Tech.prototype.videoTracks = function videoTracks() {
+ this.videoTracks_ = this.videoTracks_ || new _tracksVideoTrackList2['default']();
+ return this.videoTracks_;
+ };
+
+ /**
+ * Get audiotracklist
+ *
+ * @returns {AudioTrackList}
+ * @method audioTracks
+ */
+
+ Tech.prototype.audioTracks = function audioTracks() {
+ this.audioTracks_ = this.audioTracks_ || new _tracksAudioTrackList2['default']();
+ return this.audioTracks_;
+ };
+
+ /*
+ * Provide default methods for text tracks.
+ *
+ * Html5 tech overrides these.
+ */
+
+ /**
+ * Get texttracks
+ *
+ * @returns {TextTrackList}
+ * @method textTracks
+ */
+
+ Tech.prototype.textTracks = function textTracks() {
+ this.textTracks_ = this.textTracks_ || new _tracksTextTrackList2['default']();
+ return this.textTracks_;
+ };
+
+ /**
+ * Get remote texttracks
+ *
+ * @returns {TextTrackList}
+ * @method remoteTextTracks
+ */
+
+ Tech.prototype.remoteTextTracks = function remoteTextTracks() {
+ this.remoteTextTracks_ = this.remoteTextTracks_ || new _tracksTextTrackList2['default']();
+ return this.remoteTextTracks_;
+ };
+
+ /**
+ * Get remote htmltrackelements
+ *
+ * @returns {HTMLTrackElementList}
+ * @method remoteTextTrackEls
+ */
+
+ Tech.prototype.remoteTextTrackEls = function remoteTextTrackEls() {
+ this.remoteTextTrackEls_ = this.remoteTextTrackEls_ || new _tracksHtmlTrackElementList2['default']();
+ return this.remoteTextTrackEls_;
+ };
+
+ /**
+ * Creates and returns a remote text track object
+ *
+ * @param {String} kind Text track kind (subtitles, captions, descriptions
+ * chapters and metadata)
+ * @param {String=} label Label to identify the text track
+ * @param {String=} language Two letter language abbreviation
+ * @return {TextTrackObject}
+ * @method addTextTrack
+ */
+
+ Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
+ if (!kind) {
+ throw new Error('TextTrack kind is required but was not provided');
+ }
+
+ return createTrackHelper(this, kind, label, language);
+ };
+
+ /**
+ * Creates a remote text track object and returns a emulated html track element
+ *
+ * @param {Object} options The object should contain values for
+ * kind, language, label and src (location of the WebVTT file)
+ * @return {HTMLTrackElement}
+ * @method addRemoteTextTrack
+ */
+
+ Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack(options) {
+ var track = _utilsMergeOptionsJs2['default'](options, {
+ tech: this
+ });
+
+ var htmlTrackElement = new _tracksHtmlTrackElement2['default'](track);
+
+ // store HTMLTrackElement and TextTrack to remote list
+ this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
+ this.remoteTextTracks().addTrack_(htmlTrackElement.track);
+
+ // must come after remoteTextTracks()
+ this.textTracks().addTrack_(htmlTrackElement.track);
+
+ return htmlTrackElement;
+ };
+
+ /**
+ * Remove remote texttrack
+ *
+ * @param {TextTrackObject} track Texttrack to remove
+ * @method removeRemoteTextTrack
+ */
+
+ Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
+ this.textTracks().removeTrack_(track);
+
+ var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
+
+ // remove HTMLTrackElement and TextTrack from remote list
+ this.remoteTextTrackEls().removeTrackElement_(trackElement);
+ this.remoteTextTracks().removeTrack_(track);
+ };
+
+ /**
+ * Provide a default setPoster method for techs
+ * Poster support for techs should be optional, so we don't want techs to
+ * break if they don't have a way to set a poster.
+ *
+ * @method setPoster
+ */
+
+ Tech.prototype.setPoster = function setPoster() {};
+
+ /*
+ * Check if the tech can support the given type
+ *
+ * The base tech does not support any type, but source handlers might
+ * overwrite this.
+ *
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+
+ Tech.prototype.canPlayType = function canPlayType() {
+ return '';
+ };
+
+ /*
+ * Return whether the argument is a Tech or not.
+ * Can be passed either a Class like `Html5` or a instance like `player.tech_`
+ *
+ * @param {Object} component An item to check
+ * @return {Boolean} Whether it is a tech or not
+ */
+
+ Tech.isTech = function isTech(component) {
+ return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
+ };
+
+ /**
+ * Registers a Tech
+ *
+ * @param {String} name Name of the Tech to register
+ * @param {Object} tech The tech to register
+ * @static
+ * @method registerComponent
+ */
+
+ Tech.registerTech = function registerTech(name, tech) {
+ if (!Tech.techs_) {
+ Tech.techs_ = {};
+ }
+
+ if (!Tech.isTech(tech)) {
+ throw new Error('Tech ' + name + ' must be a Tech');
+ }
+
+ Tech.techs_[name] = tech;
+ return tech;
+ };
+
+ /**
+ * Gets a component by name
+ *
+ * @param {String} name Name of the component to get
+ * @return {Component}
+ * @static
+ * @method getComponent
+ */
+
+ Tech.getTech = function getTech(name) {
+ if (Tech.techs_ && Tech.techs_[name]) {
+ return Tech.techs_[name];
+ }
+
+ if (_globalWindow2['default'] && _globalWindow2['default'].videojs && _globalWindow2['default'].videojs[name]) {
+ _utilsLogJs2['default'].warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
+ return _globalWindow2['default'].videojs[name];
+ }
+ };
+
+ return Tech;
+})(_component2['default']);
+
+Tech.prototype.textTracks_;
+
+/**
+ * List of associated audio tracks
+ *
+ * @type {AudioTrackList}
+ * @private
+ */
+Tech.prototype.audioTracks_;
+
+/**
+ * List of associated video tracks
+ *
+ * @type {VideoTrackList}
+ * @private
+ */
+Tech.prototype.videoTracks_;
+
+var createTrackHelper = function createTrackHelper(self, kind, label, language) {
+ var options = arguments.length <= 4 || arguments[4] === undefined ? {} : arguments[4];
+
+ var tracks = self.textTracks();
+
+ options.kind = kind;
+
+ if (label) {
+ options.label = label;
+ }
+ if (language) {
+ options.language = language;
+ }
+ options.tech = self;
+
+ var track = new _tracksTextTrack2['default'](options);
+ tracks.addTrack_(track);
+
+ return track;
+};
+
+Tech.prototype.featuresVolumeControl = true;
+
+// Resizing plugins using request fullscreen reloads the plugin
+Tech.prototype.featuresFullscreenResize = false;
+Tech.prototype.featuresPlaybackRate = false;
+
+// Optional events that we can manually mimic with timers
+// currently not triggered by video-js-swf
+Tech.prototype.featuresProgressEvents = false;
+Tech.prototype.featuresTimeupdateEvents = false;
+
+Tech.prototype.featuresNativeTextTracks = false;
+
+/*
+ * A functional mixin for techs that want to use the Source Handler pattern.
+ *
+ * ##### EXAMPLE:
+ *
+ * Tech.withSourceHandlers.call(MyTech);
+ *
+ */
+Tech.withSourceHandlers = function (_Tech) {
+ /*
+ * Register a source handler
+ * Source handlers are scripts for handling specific formats.
+ * The source handler pattern is used for adaptive formats (HLS, DASH) that
+ * manually load video data and feed it into a Source Buffer (Media Source Extensions)
+ * @param {Function} handler The source handler
+ * @param {Boolean} first Register it before any existing handlers
+ */
+ _Tech.registerSourceHandler = function (handler, index) {
+ var handlers = _Tech.sourceHandlers;
+
+ if (!handlers) {
+ handlers = _Tech.sourceHandlers = [];
+ }
+
+ if (index === undefined) {
+ // add to the end of the list
+ index = handlers.length;
+ }
+
+ handlers.splice(index, 0, handler);
+ };
+
+ /*
+ * Check if the tech can support the given type
+ * @param {String} type The mimetype to check
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+ _Tech.canPlayType = function (type) {
+ var handlers = _Tech.sourceHandlers || [];
+ var can = undefined;
+
+ for (var i = 0; i < handlers.length; i++) {
+ can = handlers[i].canPlayType(type);
+
+ if (can) {
+ return can;
+ }
+ }
+
+ return '';
+ };
+
+ /*
+ * Return the first source handler that supports the source
+ * TODO: Answer question: should 'probably' be prioritized over 'maybe'
+ * @param {Object} source The source object
+ * @param {Object} options The options passed to the tech
+ * @returns {Object} The first source handler that supports the source
+ * @returns {null} Null if no source handler is found
+ */
+ _Tech.selectSourceHandler = function (source, options) {
+ var handlers = _Tech.sourceHandlers || [];
+ var can = undefined;
+ var playhandle = null;
+ for (var i = 0; i < handlers.length; i++) {
+ can = handlers[i].canHandleSource(source, options);
+ if (can) {
+ playhandle = handlers[i];
+ // nitive not first
+ if (playhandle != _Tech.nativeSourceHandler) {
+ return playhandle;
+ }
+ }
+ }
+ return playhandle;
+ };
+
+ /*
+ * Check if the tech can support the given source
+ * @param {Object} srcObj The source object
+ * @param {Object} options The options passed to the tech
+ * @return {String} 'probably', 'maybe', or '' (empty string)
+ */
+ _Tech.canPlaySource = function (srcObj, options) {
+ var sh = _Tech.selectSourceHandler(srcObj, options);
+
+ if (sh) {
+ return sh.canHandleSource(srcObj, options);
+ }
+
+ return '';
+ };
+
+ /*
+ * When using a source handler, prefer its implementation of
+ * any function normally provided by the tech.
+ */
+ var deferrable = ['seekable', 'duration'];
+
+ deferrable.forEach(function (fnName) {
+ var originalFn = this[fnName];
+
+ if (typeof originalFn !== 'function') {
+ return;
+ }
+
+ this[fnName] = function () {
+ if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
+ return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
+ }
+ return originalFn.apply(this, arguments);
+ };
+ }, _Tech.prototype);
+
+ /*
+ * Create a function for setting the source using a source object
+ * and source handlers.
+ * Should never be called unless a source handler was found.
+ * @param {Object} source A source object with src and type keys
+ * @return {Tech} self
+ */
+ _Tech.prototype.setSource = function (source) {
+ var sh = _Tech.selectSourceHandler(source, this.options_);
+ var userinfo = {
+ 'uid': source.uid,
+ 'cid': source.sid,
+ 'coursed': source.courseid,
+ 'sid': source.sid,
+ 'ua': _globalWindow2['default'].navigator.userAgent,
+ 'techmode': 0,
+ 'hash': source.src.split('/').pop(),
+ 'errorcode': ''
+ };
+ if (!sh) {
+ // Fall back to a native source hander when unsupported sources are
+ // deliberately set
+ this.trigger("error", 'No source hander found for the current source.');
+ userinfo.errorcode = 'No source hander found for the current source.';
+ userinfo.techmode = 0;
+ if (_Tech.nativeSourceHandler) {
+ sh = _Tech.nativeSourceHandler;
+ } else {
+ _utilsLogJs2['default'].error('No source hander found for the current source.');
+ }
+ } else {
+ userinfo.techmode = parseInt(sh == _Tech.nativeSourceHandler ? '0001' : '0010', 2);
+ }
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', 'https://status.chuanke.com/wapdrm.php', true);
+ xhr.send(JSON.stringify(userinfo));
+
+ // Dispose any existing source handler
+ this.disposeSourceHandler();
+ this.off('dispose', this.disposeSourceHandler);
+
+ // if we have a source and get anot█h█er one
+ // then we are loading something new
+ // than clear all of our current tracks
+ if (this.currentSource_) {
+ this.clearTracks(['audio', 'video']);
+
+ this.currentSource_ = null;
+ }
+
+ if (sh !== _Tech.nativeSourceHandler) {
+
+ this.currentSource_ = source;
+
+ // Catch if someone replaced the src without calling setSource.
+ // If they do, set currentSource_ to null and dispose our source handler.
+ this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
+ this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
+ this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
+ } else {
+ var file = source.src.split('/').pop();
+ var hash = file.split('.')[0];
+ var code = source.sid + source.code + source.courseid + source.cid + hash;
+ source.src = Url.addUrlPara(source.src, 'auth', _md52['default'](code));
+ source.src = Url.addUrlPara(source.src, 'sid', source.sid);
+ source.src = Url.addUrlPara(source.src, 'courseid', source.courseid);
+ source.src = Url.addUrlPara(source.src, 'cid', source.cid);
+ source.src = Url.addUrlPara(source.src, 'code', source.code);
+ source.src = Url.addUrlPara(source.src, 'hash', hash);
+ }
+ //do
+
+ this.sourceHandler_ = sh.handleSource(source, this, this.options_);
+ this.on('dispose', this.disposeSourceHandler);
+
+ return this;
+ };
+
+ // On the first loadstart after setSource
+ _Tech.prototype.firstLoadStartListener_ = function () {
+ this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
+ };
+
+ // On successive loadstarts when setSource has not been called again
+ _Tech.prototype.successiveLoadStartListener_ = function () {
+ this.currentSource_ = null;
+ this.disposeSourceHandler();
+ this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
+ };
+
+ /*
+ * Clean up any existing source handler
+ */
+ _Tech.prototype.disposeSourceHandler = function () {
+ if (this.sourceHandler_ && this.sourceHandler_.dispose) {
+ this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
+ this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
+ this.sourceHandler_.dispose();
+ this.sourceHandler_ = null;
+ }
+ };
+};
+
+_component2['default'].registerComponent('Tech', Tech);
+// Old name for Tech
+_component2['default'].registerComponent('MediaTechController', Tech);
+Tech.registerTech('Tech', Tech);
+exports['default'] = Tech;
+module.exports = exports['default'];
+
+},{"../component":71,"../media-error.js":112,"../tracks/audio-track":130,"../tracks/audio-track-list":129,"../tracks/html-track-element":132,"../tracks/html-track-element-list":131,"../tracks/text-track":138,"../tracks/text-track-list":136,"../tracks/video-track":143,"../tracks/video-track-list":142,"../utils/browser.js":144,"../utils/buffer.js":145,"../utils/fn.js":148,"../utils/log.js":151,"../utils/merge-options.js":152,"../utils/time-ranges.js":154,"../utils/url.js":156,"global/document":9,"global/window":10,"md5":52}],129:[function(_dereq_,module,exports){
+/**
+ * @file audio-track-list.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackList = _dereq_('./track-list');
+
+var _trackList2 = _interopRequireDefault(_trackList);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * anywhere we call this function we diverge from the spec
+ * as we only support one enabled audiotrack at a time
+ *
+ * @param {Array|AudioTrackList} list list to work on
+ * @param {AudioTrack} track the track to skip
+ */
+var disableOthers = function disableOthers(list, track) {
+ for (var i = 0; i < list.length; i++) {
+ if (track.id === list[i].id) {
+ continue;
+ }
+ // another audio track is enabled, disable it
+ list[i].enabled = false;
+ }
+};
+/**
+ * A list of possible audio tracks. All functionality is in the
+ * base class Tracklist and the spec for AudioTrackList is located at:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
+ *
+ * interface AudioTrackList : EventTarget {
+ * readonly attribute unsigned long length;
+ * getter AudioTrack (unsigned long index);
+ * AudioTrack? getTrackById(DOMString id);
+ *
+ * attribute EventHandler onchange;
+ * attribute EventHandler onaddtrack;
+ * attribute EventHandler onremovetrack;
+ * };
+ *
+ * @param {AudioTrack[]} tracks a list of audio tracks to instantiate the list with
+ * @extends TrackList
+ * @class AudioTrackList
+ */
+
+var AudioTrackList = (function (_TrackList) {
+ _inherits(AudioTrackList, _TrackList);
+
+ function AudioTrackList() {
+ var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ _classCallCheck(this, AudioTrackList);
+
+ var list = undefined;
+
+ // make sure only 1 track is enabled
+ // sorted from last index to first index
+ for (var i = tracks.length - 1; i >= 0; i--) {
+ if (tracks[i].enabled) {
+ disableOthers(tracks, tracks[i]);
+ break;
+ }
+ }
+
+ // IE8 forces us to implement inheritance ourselves
+ // as it does not support Object.defineProperty properly
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+ for (var prop in _trackList2['default'].prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = _trackList2['default'].prototype[prop];
+ }
+ }
+ for (var prop in AudioTrackList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = AudioTrackList.prototype[prop];
+ }
+ }
+ }
+
+ list = _TrackList.call(this, tracks, list);
+ list.changing_ = false;
+
+ return list;
+ }
+
+ AudioTrackList.prototype.addTrack_ = function addTrack_(track) {
+ var _this = this;
+
+ if (track.enabled) {
+ disableOthers(this, track);
+ }
+
+ _TrackList.prototype.addTrack_.call(this, track);
+ // native tracks don't have this
+ if (!track.addEventListener) {
+ return;
+ }
+
+ track.addEventListener('enabledchange', function () {
+ // when we are disabling other tracks (since we don't support
+ // more than one track at a time) we will set changing_
+ // to true so that we don't trigger additional change events
+ if (_this.changing_) {
+ return;
+ }
+ _this.changing_ = true;
+ disableOthers(_this, track);
+ _this.changing_ = false;
+ _this.trigger('change');
+ });
+ };
+
+ AudioTrackList.prototype.addTrack = function addTrack(track) {
+ this.addTrack_(track);
+ };
+
+ AudioTrackList.prototype.removeTrack = function removeTrack(track) {
+ _TrackList.prototype.removeTrack_.call(this, track);
+ };
+
+ return AudioTrackList;
+})(_trackList2['default']);
+
+exports['default'] = AudioTrackList;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"./track-list":140,"global/document":9}],130:[function(_dereq_,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackEnums = _dereq_('./track-enums');
+
+var _track = _dereq_('./track');
+
+var _track2 = _interopRequireDefault(_track);
+
+var _utilsMergeOptions = _dereq_('../utils/merge-options');
+
+var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+/**
+ * A single audio text track as defined in:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
+ *
+ * interface AudioTrack {
+ * readonly attribute DOMString id;
+ * readonly attribute DOMString kind;
+ * readonly attribute DOMString label;
+ * readonly attribute DOMString language;
+ * attribute boolean enabled;
+ * };
+ *
+ * @param {Object=} options Object of option names and values
+ * @class AudioTrack
+ */
+
+var AudioTrack = (function (_Track) {
+ _inherits(AudioTrack, _Track);
+
+ function AudioTrack() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ _classCallCheck(this, AudioTrack);
+
+ var settings = _utilsMergeOptions2['default'](options, {
+ kind: _trackEnums.AudioTrackKind[options.kind] || ''
+ });
+ // on IE8 this will be a document element
+ // for every other browser this will be a normal object
+ var track = _Track.call(this, settings);
+ var enabled = false;
+
+ if (browser.IS_IE8) {
+ for (var prop in AudioTrack.prototype) {
+ if (prop !== 'constructor') {
+ track[prop] = AudioTrack.prototype[prop];
+ }
+ }
+ }
+
+ Object.defineProperty(track, 'enabled', {
+ get: function get() {
+ return enabled;
+ },
+ set: function set(newEnabled) {
+ // an invalid or unchanged value
+ if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
+ return;
+ }
+ enabled = newEnabled;
+ this.trigger('enabledchange');
+ }
+ });
+
+ // if the user sets this track to selected then
+ // set selected to that true value otherwise
+ // we keep it false
+ if (settings.enabled) {
+ track.enabled = settings.enabled;
+ }
+ track.loaded_ = true;
+
+ return track;
+ }
+
+ return AudioTrack;
+})(_track2['default']);
+
+exports['default'] = AudioTrack;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"../utils/merge-options":152,"./track":141,"./track-enums":139}],131:[function(_dereq_,module,exports){
+/**
+ * @file html-track-element-list.js
+ */
+
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var HtmlTrackElementList = (function () {
+ function HtmlTrackElementList() {
+ var trackElements = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ _classCallCheck(this, HtmlTrackElementList);
+
+ var list = this;
+
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+
+ for (var prop in HtmlTrackElementList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = HtmlTrackElementList.prototype[prop];
+ }
+ }
+ }
+
+ list.trackElements_ = [];
+
+ Object.defineProperty(list, 'length', {
+ get: function get() {
+ return this.trackElements_.length;
+ }
+ });
+
+ for (var i = 0, _length = trackElements.length; i < _length; i++) {
+ list.addTrackElement_(trackElements[i]);
+ }
+
+ if (browser.IS_IE8) {
+ return list;
+ }
+ }
+
+ HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
+ this.trackElements_.push(trackElement);
+ };
+
+ HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
+ var trackElement_ = undefined;
+
+ for (var i = 0, _length2 = this.trackElements_.length; i < _length2; i++) {
+ if (track === this.trackElements_[i].track) {
+ trackElement_ = this.trackElements_[i];
+
+ break;
+ }
+ }
+
+ return trackElement_;
+ };
+
+ HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
+ for (var i = 0, _length3 = this.trackElements_.length; i < _length3; i++) {
+ if (trackElement === this.trackElements_[i]) {
+ this.trackElements_.splice(i, 1);
+
+ break;
+ }
+ }
+ };
+
+ return HtmlTrackElementList;
+})();
+
+exports['default'] = HtmlTrackElementList;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"global/document":9}],132:[function(_dereq_,module,exports){
+/**
+ * @file html-track-element.js
+ */
+
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _eventTarget = _dereq_('../event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _tracksTextTrack = _dereq_('../tracks/text-track');
+
+var _tracksTextTrack2 = _interopRequireDefault(_tracksTextTrack);
+
+var NONE = 0;
+var LOADING = 1;
+var LOADED = 2;
+var ERROR = 3;
+
+/**
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement
+ *
+ * interface HTMLTrackElement : HTMLElement {
+ * attribute DOMString kind;
+ * attribute DOMString src;
+ * attribute DOMString srclang;
+ * attribute DOMString label;
+ * attribute boolean default;
+ *
+ * const unsigned short NONE = 0;
+ * const unsigned short LOADING = 1;
+ * const unsigned short LOADED = 2;
+ * const unsigned short ERROR = 3;
+ * readonly attribute unsigned short readyState;
+ *
+ * readonly attribute TextTrack track;
+ * };
+ *
+ * @param {Object} options TextTrack configuration
+ * @class HTMLTrackElement
+ */
+
+var HTMLTrackElement = (function (_EventTarget) {
+ _inherits(HTMLTrackElement, _EventTarget);
+
+ function HTMLTrackElement() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ _classCallCheck(this, HTMLTrackElement);
+
+ _EventTarget.call(this);
+
+ var readyState = undefined,
+ trackElement = this;
+
+ if (browser.IS_IE8) {
+ trackElement = _globalDocument2['default'].createElement('custom');
+
+ for (var prop in HTMLTrackElement.prototype) {
+ if (prop !== 'constructor') {
+ trackElement[prop] = HTMLTrackElement.prototype[prop];
+ }
+ }
+ }
+
+ var track = new _tracksTextTrack2['default'](options);
+
+ trackElement.kind = track.kind;
+ trackElement.src = track.src;
+ trackElement.srclang = track.language;
+ trackElement.label = track.label;
+ trackElement['default'] = track['default'];
+
+ Object.defineProperty(trackElement, 'readyState', {
+ get: function get() {
+ return readyState;
+ }
+ });
+
+ Object.defineProperty(trackElement, 'track', {
+ get: function get() {
+ return track;
+ }
+ });
+
+ readyState = NONE;
+
+ track.addEventListener('loadeddata', function () {
+ readyState = LOADED;
+
+ trackElement.trigger({
+ type: 'load',
+ target: trackElement
+ });
+ });
+
+ if (browser.IS_IE8) {
+ return trackElement;
+ }
+ }
+
+ return HTMLTrackElement;
+})(_eventTarget2['default']);
+
+HTMLTrackElement.prototype.allowedEvents_ = {
+ load: 'load'
+};
+
+HTMLTrackElement.NONE = NONE;
+HTMLTrackElement.LOADING = LOADING;
+HTMLTrackElement.LOADED = LOADED;
+HTMLTrackElement.ERROR = ERROR;
+
+exports['default'] = HTMLTrackElement;
+module.exports = exports['default'];
+
+},{"../event-target":108,"../tracks/text-track":138,"../utils/browser.js":144,"global/document":9}],133:[function(_dereq_,module,exports){
+/**
+ * @file text-track-cue-list.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * A List of text track cues as defined in:
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist
+ *
+ * interface TextTrackCueList {
+ * readonly attribute unsigned long length;
+ * getter TextTrackCue (unsigned long index);
+ * TextTrackCue? getCueById(DOMString id);
+ * };
+ *
+ * @param {Array} cues A list of cues to be initialized with
+ * @class TextTrackCueList
+ */
+
+var TextTrackCueList = (function () {
+ function TextTrackCueList(cues) {
+ _classCallCheck(this, TextTrackCueList);
+
+ var list = this;
+
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+
+ for (var prop in TextTrackCueList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = TextTrackCueList.prototype[prop];
+ }
+ }
+ }
+
+ TextTrackCueList.prototype.setCues_.call(list, cues);
+
+ Object.defineProperty(list, 'length', {
+ get: function get() {
+ return this.length_;
+ }
+ });
+
+ if (browser.IS_IE8) {
+ return list;
+ }
+ }
+
+ /**
+ * A setter for cues in this list
+ *
+ * @param {Array} cues an array of cues
+ * @method setCues_
+ * @private
+ */
+
+ TextTrackCueList.prototype.setCues_ = function setCues_(cues) {
+ var oldLength = this.length || 0;
+ var i = 0;
+ var l = cues.length;
+
+ this.cues_ = cues;
+ this.length_ = cues.length;
+
+ var defineProp = function defineProp(index) {
+ if (!('' + index in this)) {
+ Object.defineProperty(this, '' + index, {
+ get: function get() {
+ return this.cues_[index];
+ }
+ });
+ }
+ };
+
+ if (oldLength < l) {
+ i = oldLength;
+
+ for (; i < l; i++) {
+ defineProp.call(this, i);
+ }
+ }
+ };
+
+ /**
+ * Get a cue that is currently in the Cue list by id
+ *
+ * @param {String} id
+ * @method getCueById
+ * @return {Object} a single cue
+ */
+
+ TextTrackCueList.prototype.getCueById = function getCueById(id) {
+ var result = null;
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ var cue = this[i];
+
+ if (cue.id === id) {
+ result = cue;
+ break;
+ }
+ }
+
+ return result;
+ };
+
+ return TextTrackCueList;
+})();
+
+exports['default'] = TextTrackCueList;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"global/document":9}],134:[function(_dereq_,module,exports){
+/**
+ * @file text-track-display.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _menuMenuJs = _dereq_('../menu/menu.js');
+
+var _menuMenuJs2 = _interopRequireDefault(_menuMenuJs);
+
+var _menuMenuItemJs = _dereq_('../menu/menu-item.js');
+
+var _menuMenuItemJs2 = _interopRequireDefault(_menuMenuItemJs);
+
+var _menuMenuButtonJs = _dereq_('../menu/menu-button.js');
+
+var _menuMenuButtonJs2 = _interopRequireDefault(_menuMenuButtonJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var darkGray = '#222';
+var lightGray = '#ccc';
+var fontMap = {
+ monospace: 'monospace',
+ sansSerif: 'sans-serif',
+ serif: 'serif',
+ monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
+ monospaceSerif: '"Courier New", monospace',
+ proportionalSansSerif: 'sans-serif',
+ proportionalSerif: 'serif',
+ casual: '"Comic Sans MS", Impact, fantasy',
+ script: '"Monotype Corsiva", cursive',
+ smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
+};
+
+/**
+ * The component for displaying text track cues
+ *
+ * @param {Object} player Main Player
+ * @param {Object=} options Object of option names and values
+ * @param {Function=} ready Ready callback function
+ * @extends Component
+ * @class TextTrackDisplay
+ */
+
+var TextTrackDisplay = (function (_Component) {
+ _inherits(TextTrackDisplay, _Component);
+
+ function TextTrackDisplay(player, options, ready) {
+ _classCallCheck(this, TextTrackDisplay);
+
+ _Component.call(this, player, options, ready);
+
+ player.on('loadstart', Fn.bind(this, this.toggleDisplay));
+ player.on('texttrackchange', Fn.bind(this, this.updateDisplay));
+
+ // This used to be called during player init, but was causing an error
+ // if a track should show by default and the display hadn't loaded yet.
+ // Should probably be moved to an external track loader when we support
+ // tracks that don't need a display.
+ player.ready(Fn.bind(this, function () {
+ if (player.tech_ && player.tech_['featuresNativeTextTracks']) {
+ this.hide();
+ return;
+ }
+
+ player.on('fullscreenchange', Fn.bind(this, this.updateDisplay));
+
+ var tracks = this.options_.playerOptions['tracks'] || [];
+ for (var i = 0; i < tracks.length; i++) {
+ var track = tracks[i];
+ this.player_.addRemoteTextTrack(track);
+ }
+
+ var modes = { 'captions': 1, 'subtitles': 1 };
+ var trackList = this.player_.textTracks();
+ var firstDesc = undefined;
+ var firstCaptions = undefined;
+
+ if (trackList) {
+ for (var i = 0; i < trackList.length; i++) {
+ var track = trackList[i];
+ if (track['default']) {
+ if (track.kind === 'descriptions' && !firstDesc) {
+ firstDesc = track;
+ } else if (track.kind in modes && !firstCaptions) {
+ firstCaptions = track;
+ }
+ }
+ }
+
+ // We want to show the first default track but captions and subtitles
+ // take precedence over descriptions.
+ // So, display the first default captions or subtitles track
+ // and otherwise the first default descriptions track.
+ if (firstCaptions) {
+ firstCaptions.mode = 'showing';
+ } else if (firstDesc) {
+ firstDesc.mode = 'showing';
+ }
+ }
+ }));
+ }
+
+ /**
+ * Add cue HTML to display
+ *
+ * @param {Number} color Hex number for color, like #f0e
+ * @param {Number} opacity Value for opacity,0.0 - 1.0
+ * @return {RGBAColor} In the form 'rgba(255, 0, 0, 0.3)'
+ * @method constructColor
+ */
+
+ /**
+ * Toggle display texttracks
+ *
+ * @method toggleDisplay
+ */
+
+ TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
+ if (this.player_.tech_ && this.player_.tech_['featuresNativeTextTracks']) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ };
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ TextTrackDisplay.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-text-track-display'
+ }, {
+ 'aria-live': 'assertive',
+ 'aria-atomic': 'true'
+ });
+ };
+
+ /**
+ * Clear display texttracks
+ *
+ * @method clearDisplay
+ */
+
+ TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
+ if (typeof _globalWindow2['default']['WebVTT'] === 'function') {
+ _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], [], this.el_);
+ }
+ };
+
+ /**
+ * Update display texttracks
+ *
+ * @method updateDisplay
+ */
+
+ TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
+ var tracks = this.player_.textTracks();
+
+ this.clearDisplay();
+
+ if (!tracks) {
+ return;
+ }
+
+ // Track display prioritization model: if multiple tracks are 'showing',
+ // display the first 'subtitles' or 'captions' track which is 'showing',
+ // otherwise display the first 'descriptions' track which is 'showing'
+
+ var descriptionsTrack = null;
+ var captionsSubtitlesTrack = null;
+
+ var i = tracks.length;
+ while (i--) {
+ var track = tracks[i];
+ if (track['mode'] === 'showing') {
+ if (track['kind'] === 'descriptions') {
+ descriptionsTrack = track;
+ } else {
+ captionsSubtitlesTrack = track;
+ }
+ }
+ }
+
+ if (captionsSubtitlesTrack) {
+ this.updateForTrack(captionsSubtitlesTrack);
+ } else if (descriptionsTrack) {
+ this.updateForTrack(descriptionsTrack);
+ }
+ };
+
+ /**
+ * Add texttrack to texttrack list
+ *
+ * @param {TextTrackObject} track Texttrack object to be added to list
+ * @method updateForTrack
+ */
+
+ TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
+ if (typeof _globalWindow2['default']['WebVTT'] !== 'function' || !track['activeCues']) {
+ return;
+ }
+
+ var overrides = this.player_['textTrackSettings'].getValues();
+
+ var cues = [];
+ for (var _i = 0; _i < track['activeCues'].length; _i++) {
+ cues.push(track['activeCues'][_i]);
+ }
+
+ _globalWindow2['default']['WebVTT']['processCues'](_globalWindow2['default'], cues, this.el_);
+
+ var i = cues.length;
+ while (i--) {
+ var cue = cues[i];
+ if (!cue) {
+ continue;
+ }
+
+ var cueDiv = cue.displayState;
+ if (overrides.color) {
+ cueDiv.firstChild.style.color = overrides.color;
+ }
+ if (overrides.textOpacity) {
+ tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
+ }
+ if (overrides.backgroundColor) {
+ cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
+ }
+ if (overrides.backgroundOpacity) {
+ tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
+ }
+ if (overrides.windowColor) {
+ if (overrides.windowOpacity) {
+ tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
+ } else {
+ cueDiv.style.backgroundColor = overrides.windowColor;
+ }
+ }
+ if (overrides.edgeStyle) {
+ if (overrides.edgeStyle === 'dropshadow') {
+ cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
+ } else if (overrides.edgeStyle === 'raised') {
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
+ } else if (overrides.edgeStyle === 'depressed') {
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
+ } else if (overrides.edgeStyle === 'uniform') {
+ cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
+ }
+ }
+ if (overrides.fontPercent && overrides.fontPercent !== 1) {
+ var fontSize = _globalWindow2['default'].parseFloat(cueDiv.style.fontSize);
+ cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
+ cueDiv.style.height = 'auto';
+ cueDiv.style.top = 'auto';
+ cueDiv.style.bottom = '2px';
+ }
+ if (overrides.fontFamily && overrides.fontFamily !== 'default') {
+ if (overrides.fontFamily === 'small-caps') {
+ cueDiv.firstChild.style.fontVariant = 'small-caps';
+ } else {
+ cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
+ }
+ }
+ }
+ };
+
+ return TextTrackDisplay;
+})(_component2['default']);
+
+function constructColor(color, opacity) {
+ return 'rgba(' +
+ // color looks like "#f0e"
+ parseInt(color[1] + color[1], 16) + ',' + parseInt(color[2] + color[2], 16) + ',' + parseInt(color[3] + color[3], 16) + ',' + opacity + ')';
+}
+
+/**
+ * Try to update style
+ * Some style changes will throw an error, particularly in IE8. Those should be noops.
+ *
+ * @param {Element} el The element to be styles
+ * @param {CSSProperty} style The CSS property to be styled
+ * @param {CSSStyle} rule The actual style to be applied to the property
+ * @method tryUpdateStyle
+ */
+function tryUpdateStyle(el, style, rule) {
+ //
+ try {
+ el.style[style] = rule;
+ } catch (e) {}
+}
+
+_component2['default'].registerComponent('TextTrackDisplay', TextTrackDisplay);
+exports['default'] = TextTrackDisplay;
+module.exports = exports['default'];
+
+},{"../component":71,"../menu/menu-button.js":113,"../menu/menu-item.js":114,"../menu/menu.js":115,"../utils/fn.js":148,"global/document":9,"global/window":10}],135:[function(_dereq_,module,exports){
+/**
+ * Utilities for capturing text track state and re-creating tracks
+ * based on a capture.
+ *
+ * @file text-track-list-converter.js
+ */
+
+/**
+ * Examine a single text track and return a JSON-compatible javascript
+ * object that represents the text track's state.
+ * @param track {TextTrackObject} the text track to query
+ * @return {Object} a serializable javascript representation of the
+ * @private
+ */
+'use strict';
+
+exports.__esModule = true;
+var trackToJson_ = function trackToJson_(track) {
+ var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
+ if (track[prop]) {
+ acc[prop] = track[prop];
+ }
+
+ return acc;
+ }, {
+ cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
+ return {
+ startTime: cue.startTime,
+ endTime: cue.endTime,
+ text: cue.text,
+ id: cue.id
+ };
+ })
+ });
+
+ return ret;
+};
+
+/**
+ * Examine a tech and return a JSON-compatible javascript array that
+ * represents the state of all text tracks currently configured. The
+ * return array is compatible with `jsonToTextTracks`.
+ * @param tech {tech} the tech object to query
+ * @return {Array} a serializable javascript representation of the
+ * @function textTracksToJson
+ */
+var textTracksToJson = function textTracksToJson(tech) {
+
+ var trackEls = tech.$$('track');
+
+ var trackObjs = Array.prototype.map.call(trackEls, function (t) {
+ return t.track;
+ });
+ var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
+ var json = trackToJson_(trackEl.track);
+ if (trackEl.src) {
+ json.src = trackEl.src;
+ }
+ return json;
+ });
+
+ return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
+ return trackObjs.indexOf(track) === -1;
+ }).map(trackToJson_));
+};
+
+/**
+ * Creates a set of remote text tracks on a tech based on an array of
+ * javascript text track representations.
+ * @param json {Array} an array of text track representation objects,
+ * like those that would be produced by `textTracksToJson`
+ * @param tech {tech} the tech to create text tracks on
+ * @function jsonToTextTracks
+ */
+var jsonToTextTracks = function jsonToTextTracks(json, tech) {
+ json.forEach(function (track) {
+ var addedTrack = tech.addRemoteTextTrack(track).track;
+ if (!track.src && track.cues) {
+ track.cues.forEach(function (cue) {
+ return addedTrack.addCue(cue);
+ });
+ }
+ });
+
+ return tech.textTracks();
+};
+
+exports['default'] = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
+module.exports = exports['default'];
+
+},{}],136:[function(_dereq_,module,exports){
+/**
+ * @file text-track-list.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackList = _dereq_('./track-list');
+
+var _trackList2 = _interopRequireDefault(_trackList);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * A list of possible text tracks. All functionality is in the
+ * base class TrackList. The spec for TextTrackList is located at:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
+ *
+ * interface TextTrackList : EventTarget {
+ * readonly attribute unsigned long length;
+ * getter TextTrack (unsigned long index);
+ * TextTrack? getTrackById(DOMString id);
+ *
+ * attribute EventHandler onchange;
+ * attribute EventHandler onaddtrack;
+ * attribute EventHandler onremovetrack;
+ * };
+ *
+ * @param {TextTrack[]} tracks A list of tracks to initialize the list with
+ * @extends TrackList
+ * @class TextTrackList
+ */
+
+var TextTrackList = (function (_TrackList) {
+ _inherits(TextTrackList, _TrackList);
+
+ function TextTrackList() {
+ var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ _classCallCheck(this, TextTrackList);
+
+ var list = undefined;
+
+ // IE8 forces us to implement inheritance ourselves
+ // as it does not support Object.defineProperty properly
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+ for (var prop in _trackList2['default'].prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = _trackList2['default'].prototype[prop];
+ }
+ }
+ for (var prop in TextTrackList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = TextTrackList.prototype[prop];
+ }
+ }
+ }
+
+ list = _TrackList.call(this, tracks, list);
+ return list;
+ }
+
+ TextTrackList.prototype.addTrack_ = function addTrack_(track) {
+ _TrackList.prototype.addTrack_.call(this, track);
+ track.addEventListener('modechange', Fn.bind(this, function () {
+ this.trigger('change');
+ }));
+ };
+
+ /**
+ * Remove TextTrack from TextTrackList
+ * NOTE: Be mindful of what is passed in as it may be a HTMLTrackElement
+ *
+ * @param {TextTrack} rtrack
+ * @method removeTrack_
+ * @private
+ */
+
+ TextTrackList.prototype.removeTrack_ = function removeTrack_(rtrack) {
+ var track = undefined;
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (this[i] === rtrack) {
+ track = this[i];
+ if (track.off) {
+ track.off();
+ }
+
+ this.tracks_.splice(i, 1);
+
+ break;
+ }
+ }
+
+ if (!track) {
+ return;
+ }
+
+ this.trigger({
+ track: track,
+ type: 'removetrack'
+ });
+ };
+
+ /**
+ * Get a TextTrack from TextTrackList by a tracks id
+ *
+ * @param {String} id - the id of the track to get
+ * @method getTrackById
+ * @return {TextTrack}
+ * @private
+ */
+
+ TextTrackList.prototype.getTrackById = function getTrackById(id) {
+ var result = null;
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ var track = this[i];
+
+ if (track.id === id) {
+ result = track;
+ break;
+ }
+ }
+
+ return result;
+ };
+
+ return TextTrackList;
+})(_trackList2['default']);
+
+exports['default'] = TextTrackList;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"../utils/fn.js":148,"./track-list":140,"global/document":9}],137:[function(_dereq_,module,exports){
+/**
+ * @file text-track-settings.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _component = _dereq_('../component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _utilsEventsJs = _dereq_('../utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsLogJs = _dereq_('../utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _safeJsonParseTuple = _dereq_('safe-json-parse/tuple');
+
+var _safeJsonParseTuple2 = _interopRequireDefault(_safeJsonParseTuple);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+/**
+ * Manipulate settings of texttracks
+ *
+ * @param {Object} player Main Player
+ * @param {Object=} options Object of option names and values
+ * @extends Component
+ * @class TextTrackSettings
+ */
+
+var TextTrackSettings = (function (_Component) {
+ _inherits(TextTrackSettings, _Component);
+
+ function TextTrackSettings(player, options) {
+ _classCallCheck(this, TextTrackSettings);
+
+ _Component.call(this, player, options);
+ this.hide();
+
+ // Grab `persistTextTrackSettings` from the player options if not passed in child options
+ if (options.persistTextTrackSettings === undefined) {
+ this.options_.persistTextTrackSettings = this.options_.playerOptions.persistTextTrackSettings;
+ }
+
+ Events.on(this.$('.vjs-done-button'), 'click', Fn.bind(this, function () {
+ this.saveSettings();
+ this.hide();
+ }));
+
+ Events.on(this.$('.vjs-default-button'), 'click', Fn.bind(this, function () {
+ this.$('.vjs-fg-color > select').selectedIndex = 0;
+ this.$('.vjs-bg-color > select').selectedIndex = 0;
+ this.$('.window-color > select').selectedIndex = 0;
+ this.$('.vjs-text-opacity > select').selectedIndex = 0;
+ this.$('.vjs-bg-opacity > select').selectedIndex = 0;
+ this.$('.vjs-window-opacity > select').selectedIndex = 0;
+ this.$('.vjs-edge-style select').selectedIndex = 0;
+ this.$('.vjs-font-family select').selectedIndex = 0;
+ this.$('.vjs-font-percent select').selectedIndex = 2;
+ this.updateDisplay();
+ }));
+
+ Events.on(this.$('.vjs-fg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-bg-color > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.window-color > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-text-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-bg-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-window-opacity > select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-font-percent select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-edge-style select'), 'change', Fn.bind(this, this.updateDisplay));
+ Events.on(this.$('.vjs-font-family select'), 'change', Fn.bind(this, this.updateDisplay));
+
+ if (this.options_.persistTextTrackSettings) {
+ this.restoreSettings();
+ }
+ }
+
+ /**
+ * Create the component's DOM element
+ *
+ * @return {Element}
+ * @method createEl
+ */
+
+ TextTrackSettings.prototype.createEl = function createEl() {
+ return _Component.prototype.createEl.call(this, 'div', {
+ className: 'vjs-caption-settings vjs-modal-overlay',
+ innerHTML: captionOptionsMenuTemplate()
+ });
+ };
+
+ /**
+ * Get texttrack settings
+ * Settings are
+ * .vjs-edge-style
+ * .vjs-font-family
+ * .vjs-fg-color
+ * .vjs-text-opacity
+ * .vjs-bg-color
+ * .vjs-bg-opacity
+ * .window-color
+ * .vjs-window-opacity
+ *
+ * @return {Object}
+ * @method getValues
+ */
+
+ TextTrackSettings.prototype.getValues = function getValues() {
+ var textEdge = getSelectedOptionValue(this.$('.vjs-edge-style select'));
+ var fontFamily = getSelectedOptionValue(this.$('.vjs-font-family select'));
+ var fgColor = getSelectedOptionValue(this.$('.vjs-fg-color > select'));
+ var textOpacity = getSelectedOptionValue(this.$('.vjs-text-opacity > select'));
+ var bgColor = getSelectedOptionValue(this.$('.vjs-bg-color > select'));
+ var bgOpacity = getSelectedOptionValue(this.$('.vjs-bg-opacity > select'));
+ var windowColor = getSelectedOptionValue(this.$('.window-color > select'));
+ var windowOpacity = getSelectedOptionValue(this.$('.vjs-window-opacity > select'));
+ var fontPercent = _globalWindow2['default']['parseFloat'](getSelectedOptionValue(this.$('.vjs-font-percent > select')));
+
+ var result = {
+ 'backgroundOpacity': bgOpacity,
+ 'textOpacity': textOpacity,
+ 'windowOpacity': windowOpacity,
+ 'edgeStyle': textEdge,
+ 'fontFamily': fontFamily,
+ 'color': fgColor,
+ 'backgroundColor': bgColor,
+ 'windowColor': windowColor,
+ 'fontPercent': fontPercent
+ };
+ for (var _name in result) {
+ if (result[_name] === '' || result[_name] === 'none' || _name === 'fontPercent' && result[_name] === 1.00) {
+ delete result[_name];
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Set texttrack settings
+ * Settings are
+ * .vjs-edge-style
+ * .vjs-font-family
+ * .vjs-fg-color
+ * .vjs-text-opacity
+ * .vjs-bg-color
+ * .vjs-bg-opacity
+ * .window-color
+ * .vjs-window-opacity
+ *
+ * @param {Object} values Object with texttrack setting values
+ * @method setValues
+ */
+
+ TextTrackSettings.prototype.setValues = function setValues(values) {
+ setSelectedOption(this.$('.vjs-edge-style select'), values.edgeStyle);
+ setSelectedOption(this.$('.vjs-font-family select'), values.fontFamily);
+ setSelectedOption(this.$('.vjs-fg-color > select'), values.color);
+ setSelectedOption(this.$('.vjs-text-opacity > select'), values.textOpacity);
+ setSelectedOption(this.$('.vjs-bg-color > select'), values.backgroundColor);
+ setSelectedOption(this.$('.vjs-bg-opacity > select'), values.backgroundOpacity);
+ setSelectedOption(this.$('.window-color > select'), values.windowColor);
+ setSelectedOption(this.$('.vjs-window-opacity > select'), values.windowOpacity);
+
+ var fontPercent = values.fontPercent;
+
+ if (fontPercent) {
+ fontPercent = fontPercent.toFixed(2);
+ }
+
+ setSelectedOption(this.$('.vjs-font-percent > select'), fontPercent);
+ };
+
+ /**
+ * Restore texttrack settings
+ *
+ * @method restoreSettings
+ */
+
+ TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
+ var err = undefined,
+ values = undefined;
+
+ try {
+ var _safeParseTuple = _safeJsonParseTuple2['default'](_globalWindow2['default'].localStorage.getItem('vjs-text-track-settings'));
+
+ err = _safeParseTuple[0];
+ values = _safeParseTuple[1];
+
+ if (err) {
+ _utilsLogJs2['default'].error(err);
+ }
+ } catch (e) {
+ _utilsLogJs2['default'].warn(e);
+ }
+
+ if (values) {
+ this.setValues(values);
+ }
+ };
+
+ /**
+ * Save texttrack settings to local storage
+ *
+ * @method saveSettings
+ */
+
+ TextTrackSettings.prototype.saveSettings = function saveSettings() {
+ if (!this.options_.persistTextTrackSettings) {
+ return;
+ }
+
+ var values = this.getValues();
+ try {
+ if (Object.getOwnPropertyNames(values).length > 0) {
+ _globalWindow2['default'].localStorage.setItem('vjs-text-track-settings', JSON.stringify(values));
+ } else {
+ _globalWindow2['default'].localStorage.removeItem('vjs-text-track-settings');
+ }
+ } catch (e) {
+ _utilsLogJs2['default'].warn(e);
+ }
+ };
+
+ /**
+ * Update display of texttrack settings
+ *
+ * @method updateDisplay
+ */
+
+ TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
+ var ttDisplay = this.player_.getChild('textTrackDisplay');
+ if (ttDisplay) {
+ ttDisplay.updateDisplay();
+ }
+ };
+
+ return TextTrackSettings;
+})(_component2['default']);
+
+_component2['default'].registerComponent('TextTrackSettings', TextTrackSettings);
+
+function getSelectedOptionValue(target) {
+ var selectedOption = undefined;
+ // not all browsers support selectedOptions, so, fallback to options
+ if (target.selectedOptions) {
+ selectedOption = target.selectedOptions[0];
+ } else if (target.options) {
+ selectedOption = target.options[target.options.selectedIndex];
+ }
+
+ return selectedOption.value;
+}
+
+function setSelectedOption(target, value) {
+ if (!value) {
+ return;
+ }
+
+ var i = undefined;
+ for (i = 0; i < target.options.length; i++) {
+ var option = target.options[i];
+ if (option.value === value) {
+ break;
+ }
+ }
+
+ target.selectedIndex = i;
+}
+
+function captionOptionsMenuTemplate() {
+ var template = '\n
\n
\n \n \n \n \n \n
\n
\n \n \n \n \n \n
\n
\n \n \n \n \n \n
\n
\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
\n
\n \n \n \n
';
+
+ return template;
+}
+
+exports['default'] = TextTrackSettings;
+module.exports = exports['default'];
+
+},{"../component":71,"../utils/events.js":147,"../utils/fn.js":148,"../utils/log.js":151,"global/window":10,"safe-json-parse/tuple":62}],138:[function(_dereq_,module,exports){
+/**
+ * @file text-track.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _textTrackCueList = _dereq_('./text-track-cue-list');
+
+var _textTrackCueList2 = _interopRequireDefault(_textTrackCueList);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _trackEnums = _dereq_('./track-enums');
+
+var _utilsLogJs = _dereq_('../utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _trackJs = _dereq_('./track.js');
+
+var _trackJs2 = _interopRequireDefault(_trackJs);
+
+var _utilsUrlJs = _dereq_('../utils/url.js');
+
+var _xhr = _dereq_('xhr');
+
+var _xhr2 = _interopRequireDefault(_xhr);
+
+var _utilsMergeOptions = _dereq_('../utils/merge-options');
+
+var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+/**
+ * takes a webvtt file contents and parses it into cues
+ *
+ * @param {String} srcContent webVTT file contents
+ * @param {Track} track track to addcues to
+ */
+var parseCues = function parseCues(srcContent, track) {
+ var parser = new _globalWindow2['default'].WebVTT.Parser(_globalWindow2['default'], _globalWindow2['default'].vttjs, _globalWindow2['default'].WebVTT.StringDecoder());
+ var errors = [];
+
+ parser.oncue = function (cue) {
+ track.addCue(cue);
+ };
+
+ parser.onparsingerror = function (error) {
+ errors.push(error);
+ };
+
+ parser.onflush = function () {
+ track.trigger({
+ type: 'loadeddata',
+ target: track
+ });
+ };
+
+ parser.parse(srcContent);
+ if (errors.length > 0) {
+ if (console.groupCollapsed) {
+ console.groupCollapsed('Text Track parsing errors for ' + track.src);
+ }
+ errors.forEach(function (error) {
+ return _utilsLogJs2['default'].error(error);
+ });
+ if (console.groupEnd) {
+ console.groupEnd();
+ }
+ }
+
+ parser.flush();
+};
+
+/**
+ * load a track from a specifed url
+ *
+ * @param {String} src url to load track from
+ * @param {Track} track track to addcues to
+ */
+var loadTrack = function loadTrack(src, track) {
+ var opts = {
+ uri: src
+ };
+ var crossOrigin = _utilsUrlJs.isCrossOrigin(src);
+
+ if (crossOrigin) {
+ opts.cors = crossOrigin;
+ }
+
+ _xhr2['default'](opts, Fn.bind(this, function (err, response, responseBody) {
+ if (err) {
+ return _utilsLogJs2['default'].error(err, response);
+ }
+
+ track.loaded_ = true;
+
+ // Make sure that vttjs has loaded, otherwise, wait till it finished loading
+ // NOTE: this is only used for the alt/video.novtt.js build
+ if (typeof _globalWindow2['default'].WebVTT !== 'function') {
+ if (track.tech_) {
+ (function () {
+ var loadHandler = function loadHandler() {
+ return parseCues(responseBody, track);
+ };
+ track.tech_.on('vttjsloaded', loadHandler);
+ track.tech_.on('vttjserror', function () {
+ _utilsLogJs2['default'].error('vttjs failed to load, stopping trying to process ' + track.src);
+ track.tech_.off('vttjsloaded', loadHandler);
+ });
+ })();
+ }
+ } else {
+ parseCues(responseBody, track);
+ }
+ }));
+};
+
+/**
+ * A single text track as defined in:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
+ *
+ * interface TextTrack : EventTarget {
+ * readonly attribute TextTrackKind kind;
+ * readonly attribute DOMString label;
+ * readonly attribute DOMString language;
+ *
+ * readonly attribute DOMString id;
+ * readonly attribute DOMString inBandMetadataTrackDispatchType;
+ *
+ * attribute TextTrackMode mode;
+ *
+ * readonly attribute TextTrackCueList? cues;
+ * readonly attribute TextTrackCueList? activeCues;
+ *
+ * void addCue(TextTrackCue cue);
+ * void removeCue(TextTrackCue cue);
+ *
+ * attribute EventHandler oncuechange;
+ * };
+ *
+ * @param {Object=} options Object of option names and values
+ * @extends Track
+ * @class TextTrack
+ */
+
+var TextTrack = (function (_Track) {
+ _inherits(TextTrack, _Track);
+
+ function TextTrack() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ _classCallCheck(this, TextTrack);
+
+ if (!options.tech) {
+ throw new Error('A tech was not provided.');
+ }
+
+ var settings = _utilsMergeOptions2['default'](options, {
+ kind: _trackEnums.TextTrackKind[options.kind] || 'subtitles',
+ language: options.language || options.srclang || ''
+ });
+ var mode = _trackEnums.TextTrackMode[settings.mode] || 'disabled';
+ var default_ = settings['default'];
+
+ if (settings.kind === 'metadata' || settings.kind === 'chapters') {
+ mode = 'hidden';
+ }
+ // on IE8 this will be a document element
+ // for every other browser this will be a normal object
+ var tt = _Track.call(this, settings);
+ tt.tech_ = settings.tech;
+
+ if (browser.IS_IE8) {
+ for (var prop in TextTrack.prototype) {
+ if (prop !== 'constructor') {
+ tt[prop] = TextTrack.prototype[prop];
+ }
+ }
+ }
+
+ tt.cues_ = [];
+ tt.activeCues_ = [];
+
+ var cues = new _textTrackCueList2['default'](tt.cues_);
+ var activeCues = new _textTrackCueList2['default'](tt.activeCues_);
+ var changed = false;
+ var timeupdateHandler = Fn.bind(tt, function () {
+ this.activeCues;
+ if (changed) {
+ this.trigger('cuechange');
+ changed = false;
+ }
+ });
+
+ if (mode !== 'disabled') {
+ tt.tech_.on('timeupdate', timeupdateHandler);
+ }
+
+ Object.defineProperty(tt, 'default', {
+ get: function get() {
+ return default_;
+ },
+ set: function set() {}
+ });
+
+ Object.defineProperty(tt, 'mode', {
+ get: function get() {
+ return mode;
+ },
+ set: function set(newMode) {
+ if (!_trackEnums.TextTrackMode[newMode]) {
+ return;
+ }
+ mode = newMode;
+ if (mode === 'showing') {
+ this.tech_.on('timeupdate', timeupdateHandler);
+ }
+ this.trigger('modechange');
+ }
+ });
+
+ Object.defineProperty(tt, 'cues', {
+ get: function get() {
+ if (!this.loaded_) {
+ return null;
+ }
+
+ return cues;
+ },
+ set: function set() {}
+ });
+
+ Object.defineProperty(tt, 'activeCues', {
+ get: function get() {
+ if (!this.loaded_) {
+ return null;
+ }
+
+ // nothing to do
+ if (this.cues.length === 0) {
+ return activeCues;
+ }
+
+ var ct = this.tech_.currentTime();
+ var active = [];
+
+ for (var i = 0, l = this.cues.length; i < l; i++) {
+ var cue = this.cues[i];
+
+ if (cue.startTime <= ct && cue.endTime >= ct) {
+ active.push(cue);
+ } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
+ active.push(cue);
+ }
+ }
+
+ changed = false;
+
+ if (active.length !== this.activeCues_.length) {
+ changed = true;
+ } else {
+ for (var i = 0; i < active.length; i++) {
+ if (this.activeCues_.indexOf(active[i]) === -1) {
+ changed = true;
+ }
+ }
+ }
+
+ this.activeCues_ = active;
+ activeCues.setCues_(this.activeCues_);
+
+ return activeCues;
+ },
+ set: function set() {}
+ });
+
+ if (settings.src) {
+ tt.src = settings.src;
+ loadTrack(settings.src, tt);
+ } else {
+ tt.loaded_ = true;
+ }
+
+ return tt;
+ }
+
+ /**
+ * cuechange - One or more cues in the track have become active or stopped being active.
+ */
+
+ /**
+ * add a cue to the internal list of cues
+ *
+ * @param {Object} cue the cue to add to our internal list
+ * @method addCue
+ */
+
+ TextTrack.prototype.addCue = function addCue(cue) {
+ var tracks = this.tech_.textTracks();
+
+ if (tracks) {
+ for (var i = 0; i < tracks.length; i++) {
+ if (tracks[i] !== this) {
+ tracks[i].removeCue(cue);
+ }
+ }
+ }
+
+ this.cues_.push(cue);
+ this.cues.setCues_(this.cues_);
+ };
+
+ /**
+ * remvoe a cue from our internal list
+ *
+ * @param {Object} removeCue the cue to remove from our internal list
+ * @method removeCue
+ */
+
+ TextTrack.prototype.removeCue = function removeCue(_removeCue) {
+ var removed = false;
+
+ for (var i = 0, l = this.cues_.length; i < l; i++) {
+ var cue = this.cues_[i];
+
+ if (cue === _removeCue) {
+ this.cues_.splice(i, 1);
+ removed = true;
+ }
+ }
+
+ if (removed) {
+ this.cues.setCues_(this.cues_);
+ }
+ };
+
+ return TextTrack;
+})(_trackJs2['default']);
+
+TextTrack.prototype.allowedEvents_ = {
+ cuechange: 'cuechange'
+};
+
+exports['default'] = TextTrack;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"../utils/fn.js":148,"../utils/log.js":151,"../utils/merge-options":152,"../utils/url.js":156,"./text-track-cue-list":133,"./track-enums":139,"./track.js":141,"global/document":9,"global/window":10,"xhr":65}],139:[function(_dereq_,module,exports){
+/**
+ * @file track-kinds.js
+ */
+
+/**
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
+ *
+ * enum VideoTrackKind {
+ * "alternative",
+ * "captions",
+ * "main",
+ * "sign",
+ * "subtitles",
+ * "commentary",
+ * "",
+ * };
+ */
+'use strict';
+
+exports.__esModule = true;
+var VideoTrackKind = {
+ alternative: 'alternative',
+ captions: 'captions',
+ main: 'main',
+ sign: 'sign',
+ subtitles: 'subtitles',
+ commentary: 'commentary'
+};
+
+/**
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
+ *
+ * enum AudioTrackKind {
+ * "alternative",
+ * "descriptions",
+ * "main",
+ * "main-desc",
+ * "translation",
+ * "commentary",
+ * "",
+ * };
+ */
+var AudioTrackKind = {
+ alternative: 'alternative',
+ descriptions: 'descriptions',
+ main: 'main',
+ 'main-desc': 'main-desc',
+ translation: 'translation',
+ commentary: 'commentary'
+};
+
+/**
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
+ *
+ * enum TextTrackKind {
+ * "subtitles",
+ * "captions",
+ * "descriptions",
+ * "chapters",
+ * "metadata"
+ * };
+ */
+var TextTrackKind = {
+ subtitles: 'subtitles',
+ captions: 'captions',
+ descriptions: 'descriptions',
+ chapters: 'chapters',
+ metadata: 'metadata'
+};
+
+/**
+ * https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
+ *
+ * enum TextTrackMode { "disabled", "hidden", "showing" };
+ */
+var TextTrackMode = {
+ disabled: 'disabled',
+ hidden: 'hidden',
+ showing: 'showing'
+};
+
+/* jshint ignore:start */
+// we ignore jshint here because it does not see
+// AudioTrackKind as defined here
+exports['default'] = { VideoTrackKind: VideoTrackKind, AudioTrackKind: AudioTrackKind, TextTrackKind: TextTrackKind, TextTrackMode: TextTrackMode };
+
+/* jshint ignore:end */
+module.exports = exports['default'];
+
+},{}],140:[function(_dereq_,module,exports){
+/**
+ * @file track-list.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _eventTarget = _dereq_('../event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _utilsFnJs = _dereq_('../utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * Common functionaliy between Text, Audio, and Video TrackLists
+ * Interfaces defined in the following spec:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html
+ *
+ * @param {Track[]} tracks A list of tracks to initialize the list with
+ * @param {Object} list the child object with inheritance done manually for ie8
+ * @extends EventTarget
+ * @class TrackList
+ */
+
+var TrackList = (function (_EventTarget) {
+ _inherits(TrackList, _EventTarget);
+
+ function TrackList() {
+ var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+ var list = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+ _classCallCheck(this, TrackList);
+
+ _EventTarget.call(this);
+ if (!list) {
+ list = this;
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+ for (var prop in TrackList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = TrackList.prototype[prop];
+ }
+ }
+ }
+ }
+
+ list.tracks_ = [];
+ Object.defineProperty(list, 'length', {
+ get: function get() {
+ return this.tracks_.length;
+ }
+ });
+
+ for (var i = 0; i < tracks.length; i++) {
+ list.addTrack_(tracks[i]);
+ }
+
+ return list;
+ }
+
+ /**
+ * change - One or more tracks in the track list have been enabled or disabled.
+ * addtrack - A track has been added to the track list.
+ * removetrack - A track has been removed from the track list.
+ */
+
+ /**
+ * Add a Track from TrackList
+ *
+ * @param {Mixed} track
+ * @method addTrack_
+ * @private
+ */
+
+ TrackList.prototype.addTrack_ = function addTrack_(track) {
+ var index = this.tracks_.length;
+
+ if (!('' + index in this)) {
+ Object.defineProperty(this, index, {
+ get: function get() {
+ return this.tracks_[index];
+ }
+ });
+ }
+
+ // Do not add duplicate tracks
+ if (this.tracks_.indexOf(track) === -1) {
+ this.tracks_.push(track);
+ this.trigger({
+ track: track,
+ type: 'addtrack'
+ });
+ }
+ };
+
+ /**
+ * Remove a Track from TrackList
+ *
+ * @param {Track} rtrack track to be removed
+ * @method removeTrack_
+ * @private
+ */
+
+ TrackList.prototype.removeTrack_ = function removeTrack_(rtrack) {
+ var track = undefined;
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (this[i] === rtrack) {
+ track = this[i];
+ if (track.off) {
+ track.off();
+ }
+
+ this.tracks_.splice(i, 1);
+
+ break;
+ }
+ }
+
+ if (!track) {
+ return;
+ }
+
+ this.trigger({
+ track: track,
+ type: 'removetrack'
+ });
+ };
+
+ /**
+ * Get a Track from the TrackList by a tracks id
+ *
+ * @param {String} id - the id of the track to get
+ * @method getTrackById
+ * @return {Track}
+ * @private
+ */
+
+ TrackList.prototype.getTrackById = function getTrackById(id) {
+ var result = null;
+
+ for (var i = 0, l = this.length; i < l; i++) {
+ var track = this[i];
+ if (track.id === id) {
+ result = track;
+ break;
+ }
+ }
+
+ return result;
+ };
+
+ return TrackList;
+})(_eventTarget2['default']);
+
+TrackList.prototype.allowedEvents_ = {
+ change: 'change',
+ addtrack: 'addtrack',
+ removetrack: 'removetrack'
+};
+
+// emulate attribute EventHandler support to allow for feature detection
+for (var _event in TrackList.prototype.allowedEvents_) {
+ TrackList.prototype['on' + _event] = null;
+}
+
+exports['default'] = TrackList;
+module.exports = exports['default'];
+
+},{"../event-target":108,"../utils/browser.js":144,"../utils/fn.js":148,"global/document":9}],141:[function(_dereq_,module,exports){
+/**
+ * @file track.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _utilsGuidJs = _dereq_('../utils/guid.js');
+
+var Guid = _interopRequireWildcard(_utilsGuidJs);
+
+var _eventTarget = _dereq_('../event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+/**
+ * setup the common parts of an audio, video, or text track
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html
+ *
+ * @param {String} type The type of track we are dealing with audio|video|text
+ * @param {Object=} options Object of option names and values
+ * @extends EventTarget
+ * @class Track
+ */
+
+var Track = (function (_EventTarget) {
+ _inherits(Track, _EventTarget);
+
+ function Track() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ _classCallCheck(this, Track);
+
+ _EventTarget.call(this);
+
+ var track = this;
+ if (browser.IS_IE8) {
+ track = _globalDocument2['default'].createElement('custom');
+ for (var prop in Track.prototype) {
+ if (prop !== 'constructor') {
+ track[prop] = Track.prototype[prop];
+ }
+ }
+ }
+
+ var trackProps = {
+ id: options.id || 'vjs_track_' + Guid.newGUID(),
+ kind: options.kind || '',
+ label: options.label || '',
+ language: options.language || ''
+ };
+
+ var _loop = function (key) {
+ Object.defineProperty(track, key, {
+ get: function get() {
+ return trackProps[key];
+ },
+ set: function set() {}
+ });
+ };
+
+ for (var key in trackProps) {
+ _loop(key);
+ }
+
+ return track;
+ }
+
+ return Track;
+})(_eventTarget2['default']);
+
+exports['default'] = Track;
+module.exports = exports['default'];
+
+},{"../event-target":108,"../utils/browser.js":144,"../utils/guid.js":150,"global/document":9}],142:[function(_dereq_,module,exports){
+/**
+ * @file video-track-list.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackList = _dereq_('./track-list');
+
+var _trackList2 = _interopRequireDefault(_trackList);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * disable other video tracks before selecting the new one
+ *
+ * @param {Array|VideoTrackList} list list to work on
+ * @param {VideoTrack} track the track to skip
+ */
+var disableOthers = function disableOthers(list, track) {
+ for (var i = 0; i < list.length; i++) {
+ if (track.id === list[i].id) {
+ continue;
+ }
+ // another audio track is enabled, disable it
+ list[i].selected = false;
+ }
+};
+
+/**
+* A list of possiblee video tracks. Most functionality is in the
+ * base class Tracklist and the spec for VideoTrackList is located at:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
+ *
+ * interface VideoTrackList : EventTarget {
+ * readonly attribute unsigned long length;
+ * getter VideoTrack (unsigned long index);
+ * VideoTrack? getTrackById(DOMString id);
+ * readonly attribute long selectedIndex;
+ *
+ * attribute EventHandler onchange;
+ * attribute EventHandler onaddtrack;
+ * attribute EventHandler onremovetrack;
+ * };
+ *
+ * @param {VideoTrack[]} tracks a list of video tracks to instantiate the list with
+ # @extends TrackList
+ * @class VideoTrackList
+ */
+
+var VideoTrackList = (function (_TrackList) {
+ _inherits(VideoTrackList, _TrackList);
+
+ function VideoTrackList() {
+ var tracks = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
+
+ _classCallCheck(this, VideoTrackList);
+
+ var list = undefined;
+
+ // make sure only 1 track is enabled
+ // sorted from last index to first index
+ for (var i = tracks.length - 1; i >= 0; i--) {
+ if (tracks[i].selected) {
+ disableOthers(tracks, tracks[i]);
+ break;
+ }
+ }
+
+ // IE8 forces us to implement inheritance ourselves
+ // as it does not support Object.defineProperty properly
+ if (browser.IS_IE8) {
+ list = _globalDocument2['default'].createElement('custom');
+ for (var prop in _trackList2['default'].prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = _trackList2['default'].prototype[prop];
+ }
+ }
+ for (var prop in VideoTrackList.prototype) {
+ if (prop !== 'constructor') {
+ list[prop] = VideoTrackList.prototype[prop];
+ }
+ }
+ }
+
+ list = _TrackList.call(this, tracks, list);
+ list.changing_ = false;
+
+ Object.defineProperty(list, 'selectedIndex', {
+ get: function get() {
+ for (var i = 0; i < this.length; i++) {
+ if (this[i].selected) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ set: function set() {}
+ });
+
+ return list;
+ }
+
+ VideoTrackList.prototype.addTrack_ = function addTrack_(track) {
+ var _this = this;
+
+ if (track.selected) {
+ disableOthers(this, track);
+ }
+
+ _TrackList.prototype.addTrack_.call(this, track);
+ // native tracks don't have this
+ if (!track.addEventListener) {
+ return;
+ }
+ track.addEventListener('selectedchange', function () {
+ if (_this.changing_) {
+ return;
+ }
+ _this.changing_ = true;
+ disableOthers(_this, track);
+ _this.changing_ = false;
+ _this.trigger('change');
+ });
+ };
+
+ VideoTrackList.prototype.addTrack = function addTrack(track) {
+ this.addTrack_(track);
+ };
+
+ VideoTrackList.prototype.removeTrack = function removeTrack(track) {
+ _TrackList.prototype.removeTrack_.call(this, track);
+ };
+
+ return VideoTrackList;
+})(_trackList2['default']);
+
+exports['default'] = VideoTrackList;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"./track-list":140,"global/document":9}],143:[function(_dereq_,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _trackEnums = _dereq_('./track-enums');
+
+var _track = _dereq_('./track');
+
+var _track2 = _interopRequireDefault(_track);
+
+var _utilsMergeOptions = _dereq_('../utils/merge-options');
+
+var _utilsMergeOptions2 = _interopRequireDefault(_utilsMergeOptions);
+
+var _utilsBrowserJs = _dereq_('../utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+/**
+ * A single video text track as defined in:
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack
+ *
+ * interface VideoTrack {
+ * readonly attribute DOMString id;
+ * readonly attribute DOMString kind;
+ * readonly attribute DOMString label;
+ * readonly attribute DOMString language;
+ * attribute boolean selected;
+ * };
+ *
+ * @param {Object=} options Object of option names and values
+ * @class VideoTrack
+ */
+
+var VideoTrack = (function (_Track) {
+ _inherits(VideoTrack, _Track);
+
+ function VideoTrack() {
+ var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ _classCallCheck(this, VideoTrack);
+
+ var settings = _utilsMergeOptions2['default'](options, {
+ kind: _trackEnums.VideoTrackKind[options.kind] || ''
+ });
+
+ // on IE8 this will be a document element
+ // for every other browser this will be a normal object
+ var track = _Track.call(this, settings);
+ var selected = false;
+
+ if (browser.IS_IE8) {
+ for (var prop in VideoTrack.prototype) {
+ if (prop !== 'constructor') {
+ track[prop] = VideoTrack.prototype[prop];
+ }
+ }
+ }
+
+ Object.defineProperty(track, 'selected', {
+ get: function get() {
+ return selected;
+ },
+ set: function set(newSelected) {
+ // an invalid or unchanged value
+ if (typeof newSelected !== 'boolean' || newSelected === selected) {
+ return;
+ }
+ selected = newSelected;
+ this.trigger('selectedchange');
+ }
+ });
+
+ // if the user sets this track to selected then
+ // set selected to that true value otherwise
+ // we keep it false
+ if (settings.selected) {
+ track.selected = settings.selected;
+ }
+
+ return track;
+ }
+
+ return VideoTrack;
+})(_track2['default']);
+
+exports['default'] = VideoTrack;
+module.exports = exports['default'];
+
+},{"../utils/browser.js":144,"../utils/merge-options":152,"./track":141,"./track-enums":139}],144:[function(_dereq_,module,exports){
+/**
+ * @file browser.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var USER_AGENT = _globalWindow2['default'].navigator.userAgent;
+var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
+var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
+
+/*
+ * Device is an iPhone
+ *
+ * @type {Boolean}
+ * @constant
+ * @private
+ */
+var IS_IPAD = /iPad/i.test(USER_AGENT);
+
+exports.IS_IPAD = IS_IPAD;
+// The Facebook app's UIWebView identifies as both an iPhone and iPad, so
+// to identify iPhones, we need to exclude iPads.
+// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
+var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
+exports.IS_IPHONE = IS_IPHONE;
+var IS_IPOD = /iPod/i.test(USER_AGENT);
+exports.IS_IPOD = IS_IPOD;
+var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
+
+exports.IS_IOS = IS_IOS;
+var IOS_VERSION = (function () {
+ var match = USER_AGENT.match(/OS (\d+)_/i);
+ if (match && match[1]) {
+ return match[1];
+ }
+})();
+
+exports.IOS_VERSION = IOS_VERSION;
+var IS_ANDROID = /Android/i.test(USER_AGENT);
+exports.IS_ANDROID = IS_ANDROID;
+var ANDROID_VERSION = (function () {
+ // This matches Android Major.Minor.Patch versions
+ // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
+ var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
+ major,
+ minor;
+
+ if (!match) {
+ return null;
+ }
+
+ major = match[1] && parseFloat(match[1]);
+ minor = match[2] && parseFloat(match[2]);
+
+ if (major && minor) {
+ return parseFloat(match[1] + '.' + match[2]);
+ } else if (major) {
+ return major;
+ } else {
+ return null;
+ }
+})();
+exports.ANDROID_VERSION = ANDROID_VERSION;
+// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
+var IS_OLD_ANDROID = IS_ANDROID && /webkit/i.test(USER_AGENT) && ANDROID_VERSION < 2.3;
+exports.IS_OLD_ANDROID = IS_OLD_ANDROID;
+var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
+
+exports.IS_NATIVE_ANDROID = IS_NATIVE_ANDROID;
+var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
+exports.IS_FIREFOX = IS_FIREFOX;
+var IS_EDGE = /Edge/i.test(USER_AGENT);
+exports.IS_EDGE = IS_EDGE;
+var IS_CHROME = !IS_EDGE && /Chrome/i.test(USER_AGENT);
+exports.IS_CHROME = IS_CHROME;
+var IS_IE8 = /MSIE\s8\.0/.test(USER_AGENT);
+
+exports.IS_IE8 = IS_IE8;
+var TOUCH_ENABLED = !!('ontouchstart' in _globalWindow2['default'] || _globalWindow2['default'].DocumentTouch && _globalDocument2['default'] instanceof _globalWindow2['default'].DocumentTouch);
+exports.TOUCH_ENABLED = TOUCH_ENABLED;
+var BACKGROUND_SIZE_SUPPORTED = ('backgroundSize' in _globalDocument2['default'].createElement('video').style);
+exports.BACKGROUND_SIZE_SUPPORTED = BACKGROUND_SIZE_SUPPORTED;
+
+},{"global/document":9,"global/window":10}],145:[function(_dereq_,module,exports){
+/**
+ * @file buffer.js
+ */
+'use strict';
+
+exports.__esModule = true;
+exports.bufferedPercent = bufferedPercent;
+
+var _timeRangesJs = _dereq_('./time-ranges.js');
+
+/**
+ * Compute how much your video has been buffered
+ *
+ * @param {Object} Buffered object
+ * @param {Number} Total duration
+ * @return {Number} Percent buffered of the total duration
+ * @private
+ * @function bufferedPercent
+ */
+
+function bufferedPercent(buffered, duration) {
+ var bufferedDuration = 0,
+ start,
+ end;
+
+ if (!duration) {
+ return 0;
+ }
+
+ if (!buffered || !buffered.length) {
+ buffered = _timeRangesJs.createTimeRange(0, 0);
+ }
+
+ for (var i = 0; i < buffered.length; i++) {
+ start = buffered.start(i);
+ end = buffered.end(i);
+
+ // buffered end can be bigger than duration by a very small fraction
+ if (end > duration) {
+ end = duration;
+ }
+
+ bufferedDuration += end - start;
+ }
+
+ return bufferedDuration / duration;
+}
+
+},{"./time-ranges.js":154}],146:[function(_dereq_,module,exports){
+/**
+ * @file dom.js
+ */
+'use strict';
+
+exports.__esModule = true;
+exports.getEl = getEl;
+exports.createEl = createEl;
+exports.textContent = textContent;
+exports.insertElFirst = insertElFirst;
+exports.getElData = getElData;
+exports.hasElData = hasElData;
+exports.removeElData = removeElData;
+exports.hasElClass = hasElClass;
+exports.addElClass = addElClass;
+exports.removeElClass = removeElClass;
+exports.toggleElClass = toggleElClass;
+exports.setElAttributes = setElAttributes;
+exports.getElAttributes = getElAttributes;
+exports.blockTextSelection = blockTextSelection;
+exports.unblockTextSelection = unblockTextSelection;
+exports.findElPosition = findElPosition;
+exports.getPointerPosition = getPointerPosition;
+exports.isEl = isEl;
+exports.isTextNode = isTextNode;
+exports.emptyEl = emptyEl;
+exports.normalizeContent = normalizeContent;
+exports.appendContent = appendContent;
+exports.insertContent = insertContent;
+
+var _templateObject = _taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _taggedTemplateLiteralLoose(strings, raw) { strings.raw = raw; return strings; }
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _guidJs = _dereq_('./guid.js');
+
+var Guid = _interopRequireWildcard(_guidJs);
+
+var _logJs = _dereq_('./log.js');
+
+var _logJs2 = _interopRequireDefault(_logJs);
+
+var _tsml = _dereq_('tsml');
+
+var _tsml2 = _interopRequireDefault(_tsml);
+
+/**
+ * Detect if a value is a string with any non-whitespace characters.
+ *
+ * @param {String} str
+ * @return {Boolean}
+ */
+function isNonBlankString(str) {
+ return typeof str === 'string' && /\S/.test(str);
+}
+
+/**
+ * Throws an error if the passed string has whitespace. This is used by
+ * class methods to be relatively consistent with the classList API.
+ *
+ * @param {String} str
+ * @return {Boolean}
+ */
+function throwIfWhitespace(str) {
+ if (/\s/.test(str)) {
+ throw new Error('class has illegal whitespace characters');
+ }
+}
+
+/**
+ * Produce a regular expression for matching a class name.
+ *
+ * @param {String} className
+ * @return {RegExp}
+ */
+function classRegExp(className) {
+ return new RegExp('(^|\\s)' + className + '($|\\s)');
+}
+
+/**
+ * Creates functions to query the DOM using a given method.
+ *
+ * @function createQuerier
+ * @private
+ * @param {String} method
+ * @return {Function}
+ */
+function createQuerier(method) {
+ return function (selector, context) {
+ if (!isNonBlankString(selector)) {
+ return _globalDocument2['default'][method](null);
+ }
+ if (isNonBlankString(context)) {
+ context = _globalDocument2['default'].querySelector(context);
+ }
+ return (isEl(context) ? context : _globalDocument2['default'])[method](selector);
+ };
+}
+
+/**
+ * Shorthand for document.getElementById()
+ * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
+ *
+ * @param {String} id Element ID
+ * @return {Element} Element with supplied ID
+ * @function getEl
+ */
+
+function getEl(id) {
+ if (id.indexOf('#') === 0) {
+ id = id.slice(1);
+ }
+
+ return _globalDocument2['default'].getElementById(id);
+}
+
+/**
+ * Creates an element and applies properties.
+ *
+ * @param {String} [tagName='div'] Name of tag to be created.
+ * @param {Object} [properties={}] Element properties to be applied.
+ * @param {Object} [attributes={}] Element attributes to be applied.
+ * @return {Element}
+ * @function createEl
+ */
+
+function createEl() {
+ var tagName = arguments.length <= 0 || arguments[0] === undefined ? 'div' : arguments[0];
+ var properties = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+ var attributes = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+
+ var el = _globalDocument2['default'].createElement(tagName);
+
+ Object.getOwnPropertyNames(properties).forEach(function (propName) {
+ var val = properties[propName];
+
+ // See #2176
+ // We originally were accepting both properties and attributes in the
+ // same object, but that doesn't work so well.
+ if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
+ _logJs2['default'].warn(_tsml2['default'](_templateObject, propName, val));
+ el.setAttribute(propName, val);
+ } else {
+ el[propName] = val;
+ }
+ });
+
+ Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
+ var val = attributes[attrName];
+ el.setAttribute(attrName, attributes[attrName]);
+ });
+
+ return el;
+}
+
+/**
+ * Injects text into an element, replacing any existing contents entirely.
+ *
+ * @param {Element} el
+ * @param {String} text
+ * @return {Element}
+ * @function textContent
+ */
+
+function textContent(el, text) {
+ if (typeof el.textContent === 'undefined') {
+ el.innerText = text;
+ } else {
+ el.textContent = text;
+ }
+}
+
+/**
+ * Insert an element as the first child node of another
+ *
+ * @param {Element} child Element to insert
+ * @param {Element} parent Element to insert child into
+ * @private
+ * @function insertElFirst
+ */
+
+function insertElFirst(child, parent) {
+ if (parent.firstChild) {
+ parent.insertBefore(child, parent.firstChild);
+ } else {
+ parent.appendChild(child);
+ }
+}
+
+/**
+ * Element Data Store. Allows for binding data to an element without putting it directly on the element.
+ * Ex. Event listeners are stored here.
+ * (also from jsninja.com, slightly modified and updated for closure compiler)
+ *
+ * @type {Object}
+ * @private
+ */
+var elData = {};
+
+/*
+ * Unique attribute name to store an element's guid in
+ *
+ * @type {String}
+ * @constant
+ * @private
+ */
+var elIdAttr = 'vdata' + new Date().getTime();
+
+/**
+ * Returns the cache object where data for an element is stored
+ *
+ * @param {Element} el Element to store data for.
+ * @return {Object}
+ * @function getElData
+ */
+
+function getElData(el) {
+ var id = el[elIdAttr];
+
+ if (!id) {
+ id = el[elIdAttr] = Guid.newGUID();
+ }
+
+ if (!elData[id]) {
+ elData[id] = {};
+ }
+
+ return elData[id];
+}
+
+/**
+ * Returns whether or not an element has cached data
+ *
+ * @param {Element} el A dom element
+ * @return {Boolean}
+ * @private
+ * @function hasElData
+ */
+
+function hasElData(el) {
+ var id = el[elIdAttr];
+
+ if (!id) {
+ return false;
+ }
+
+ return !!Object.getOwnPropertyNames(elData[id]).length;
+}
+
+/**
+ * Delete data for the element from the cache and the guid attr from getElementById
+ *
+ * @param {Element} el Remove data for an element
+ * @private
+ * @function removeElData
+ */
+
+function removeElData(el) {
+ var id = el[elIdAttr];
+
+ if (!id) {
+ return;
+ }
+
+ // Remove all stored data
+ delete elData[id];
+
+ // Remove the elIdAttr property from the DOM node
+ try {
+ delete el[elIdAttr];
+ } catch (e) {
+ if (el.removeAttribute) {
+ el.removeAttribute(elIdAttr);
+ } else {
+ // IE doesn't appear to support removeAttribute on the document element
+ el[elIdAttr] = null;
+ }
+ }
+}
+
+/**
+ * Check if an element has a CSS class
+ *
+ * @function hasElClass
+ * @param {Element} element Element to check
+ * @param {String} classToCheck Classname to check
+ */
+
+function hasElClass(element, classToCheck) {
+ if (element.classList) {
+ return element.classList.contains(classToCheck);
+ } else {
+ throwIfWhitespace(classToCheck);
+ return classRegExp(classToCheck).test(element.className);
+ }
+}
+
+/**
+ * Add a CSS class name to an element
+ *
+ * @function addElClass
+ * @param {Element} element Element to add class name to
+ * @param {String} classToAdd Classname to add
+ */
+
+function addElClass(element, classToAdd) {
+ if (element.classList) {
+ element.classList.add(classToAdd);
+
+ // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
+ // in the case of classList not being supported.
+ } else if (!hasElClass(element, classToAdd)) {
+ element.className = (element.className + ' ' + classToAdd).trim();
+ }
+
+ return element;
+}
+
+/**
+ * Remove a CSS class name from an element
+ *
+ * @function removeElClass
+ * @param {Element} element Element to remove from class name
+ * @param {String} classToRemove Classname to remove
+ */
+
+function removeElClass(element, classToRemove) {
+ if (element.classList) {
+ element.classList.remove(classToRemove);
+ } else {
+ throwIfWhitespace(classToRemove);
+ element.className = element.className.split(/\s+/).filter(function (c) {
+ return c !== classToRemove;
+ }).join(' ');
+ }
+
+ return element;
+}
+
+/**
+ * Adds or removes a CSS class name on an element depending on an optional
+ * condition or the presence/absence of the class name.
+ *
+ * @function toggleElClass
+ * @param {Element} element
+ * @param {String} classToToggle
+ * @param {Boolean|Function} [predicate]
+ * Can be a function that returns a Boolean. If `true`, the class
+ * will be added; if `false`, the class will be removed. If not
+ * given, the class will be added if not present and vice versa.
+ */
+
+function toggleElClass(element, classToToggle, predicate) {
+
+ // This CANNOT use `classList` internally because IE does not support the
+ // second parameter to the `classList.toggle()` method! Which is fine because
+ // `classList` will be used by the add/remove functions.
+ var has = hasElClass(element, classToToggle);
+
+ if (typeof predicate === 'function') {
+ predicate = predicate(element, classToToggle);
+ }
+
+ if (typeof predicate !== 'boolean') {
+ predicate = !has;
+ }
+
+ // If the necessary class operation matches the current state of the
+ // element, no action is required.
+ if (predicate === has) {
+ return;
+ }
+
+ if (predicate) {
+ addElClass(element, classToToggle);
+ } else {
+ removeElClass(element, classToToggle);
+ }
+
+ return element;
+}
+
+/**
+ * Apply attributes to an HTML element.
+ *
+ * @param {Element} el Target element.
+ * @param {Object=} attributes Element attributes to be applied.
+ * @private
+ * @function setElAttributes
+ */
+
+function setElAttributes(el, attributes) {
+ Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
+ var attrValue = attributes[attrName];
+
+ if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
+ el.removeAttribute(attrName);
+ } else {
+ el.setAttribute(attrName, attrValue === true ? '' : attrValue);
+ }
+ });
+}
+
+/**
+ * Get an element's attribute values, as defined on the HTML tag
+ * Attributes are not the same as properties. They're defined on the tag
+ * or with setAttribute (which shouldn't be used with HTML)
+ * This will return true or false for boolean attributes.
+ *
+ * @param {Element} tag Element from which to get tag attributes
+ * @return {Object}
+ * @private
+ * @function getElAttributes
+ */
+
+function getElAttributes(tag) {
+ var obj, knownBooleans, attrs, attrName, attrVal;
+
+ obj = {};
+
+ // known boolean attributes
+ // we can check for matching boolean properties, but older browsers
+ // won't know about HTML5 boolean attributes that we still read from
+ knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
+
+ if (tag && tag.attributes && tag.attributes.length > 0) {
+ attrs = tag.attributes;
+
+ for (var i = attrs.length - 1; i >= 0; i--) {
+ attrName = attrs[i].name;
+ attrVal = attrs[i].value;
+
+ // check for known booleans
+ // the matching element property will return a value for typeof
+ if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
+ // the value of an included boolean attribute is typically an empty
+ // string ('') which would equal false if we just check for a false value.
+ // we also don't want support bad code like autoplay='false'
+ attrVal = attrVal !== null ? true : false;
+ }
+
+ obj[attrName] = attrVal;
+ }
+ }
+
+ return obj;
+}
+
+/**
+ * Attempt to block the ability to select text while dragging controls
+ *
+ * @return {Boolean}
+ * @function blockTextSelection
+ */
+
+function blockTextSelection() {
+ _globalDocument2['default'].body.focus();
+ _globalDocument2['default'].onselectstart = function () {
+ return false;
+ };
+}
+
+/**
+ * Turn off text selection blocking
+ *
+ * @return {Boolean}
+ * @function unblockTextSelection
+ */
+
+function unblockTextSelection() {
+ _globalDocument2['default'].onselectstart = function () {
+ return true;
+ };
+}
+
+/**
+ * Offset Left
+ * getBoundingClientRect technique from
+ * John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
+ *
+ * @function findElPosition
+ * @param {Element} el Element from which to get offset
+ * @return {Object}
+ */
+
+function findElPosition(el) {
+ var box = undefined;
+
+ if (el.getBoundingClientRect && el.parentNode) {
+ box = el.getBoundingClientRect();
+ }
+
+ if (!box) {
+ return {
+ left: 0,
+ top: 0
+ };
+ }
+
+ var docEl = _globalDocument2['default'].documentElement;
+ var body = _globalDocument2['default'].body;
+
+ var clientLeft = docEl.clientLeft || body.clientLeft || 0;
+ var scrollLeft = _globalWindow2['default'].pageXOffset || body.scrollLeft;
+ var left = box.left + scrollLeft - clientLeft;
+
+ var clientTop = docEl.clientTop || body.clientTop || 0;
+ var scrollTop = _globalWindow2['default'].pageYOffset || body.scrollTop;
+ var top = box.top + scrollTop - clientTop;
+
+ // Android sometimes returns slightly off decimal values, so need to round
+ return {
+ left: Math.round(left),
+ top: Math.round(top)
+ };
+}
+
+/**
+ * Get pointer position in element
+ * Returns an object with x and y coordinates.
+ * The base on the coordinates are the bottom left of the element.
+ *
+ * @function getPointerPosition
+ * @param {Element} el Element on which to get the pointer position on
+ * @param {Event} event Event object
+ * @return {Object} This object will have x and y coordinates corresponding to the mouse position
+ */
+
+function getPointerPosition(el, event) {
+ var position = {};
+ var box = findElPosition(el);
+ var boxW = el.offsetWidth;
+ var boxH = el.offsetHeight;
+
+ var boxY = box.top;
+ var boxX = box.left;
+ var pageY = event.pageY;
+ var pageX = event.pageX;
+
+ if (event.changedTouches) {
+ pageX = event.changedTouches[0].pageX;
+ pageY = event.changedTouches[0].pageY;
+ }
+
+ position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
+ position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
+
+ return position;
+}
+
+/**
+ * Determines, via duck typing, whether or not a value is a DOM element.
+ *
+ * @function isEl
+ * @param {Mixed} value
+ * @return {Boolean}
+ */
+
+function isEl(value) {
+ return !!value && typeof value === 'object' && value.nodeType === 1;
+}
+
+/**
+ * Determines, via duck typing, whether or not a value is a text node.
+ *
+ * @param {Mixed} value
+ * @return {Boolean}
+ */
+
+function isTextNode(value) {
+ return !!value && typeof value === 'object' && value.nodeType === 3;
+}
+
+/**
+ * Empties the contents of an element.
+ *
+ * @function emptyEl
+ * @param {Element} el
+ * @return {Element}
+ */
+
+function emptyEl(el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ return el;
+}
+
+/**
+ * Normalizes content for eventual insertion into the DOM.
+ *
+ * This allows a wide range of content definition methods, but protects
+ * from falling into the trap of simply writing to `innerHTML`, which is
+ * an XSS concern.
+ *
+ * The content for an element can be passed in multiple types and
+ * combinations, whose behavior is as follows:
+ *
+ * - String
+ * Normalized into a text node.
+ *
+ * - Element, TextNode
+ * Passed through.
+ *
+ * - Array
+ * A one-dimensional array of strings, elements, nodes, or functions (which
+ * return single strings, elements, or nodes).
+ *
+ * - Function
+ * If the sole argument, is expected to produce a string, element,
+ * node, or array.
+ *
+ * @function normalizeContent
+ * @param {String|Element|TextNode|Array|Function} content
+ * @return {Array}
+ */
+
+function normalizeContent(content) {
+
+ // First, invoke content if it is a function. If it produces an array,
+ // that needs to happen before normalization.
+ if (typeof content === 'function') {
+ content = content();
+ }
+
+ // Next up, normalize to an array, so one or many items can be normalized,
+ // filtered, and returned.
+ return (Array.isArray(content) ? content : [content]).map(function (value) {
+
+ // First, invoke value if it is a function to produce a new value,
+ // which will be subsequently normalized to a Node of some kind.
+ if (typeof value === 'function') {
+ value = value();
+ }
+
+ if (isEl(value) || isTextNode(value)) {
+ return value;
+ }
+
+ if (typeof value === 'string' && /\S/.test(value)) {
+ return _globalDocument2['default'].createTextNode(value);
+ }
+ }).filter(function (value) {
+ return value;
+ });
+}
+
+/**
+ * Normalizes and appends content to an element.
+ *
+ * @function appendContent
+ * @param {Element} el
+ * @param {String|Element|TextNode|Array|Function} content
+ * See: `normalizeContent`
+ * @return {Element}
+ */
+
+function appendContent(el, content) {
+ normalizeContent(content).forEach(function (node) {
+ return el.appendChild(node);
+ });
+ return el;
+}
+
+/**
+ * Normalizes and inserts content into an element; this is identical to
+ * `appendContent()`, except it empties the element first.
+ *
+ * @function insertContent
+ * @param {Element} el
+ * @param {String|Element|TextNode|Array|Function} content
+ * See: `normalizeContent`
+ * @return {Element}
+ */
+
+function insertContent(el, content) {
+ return appendContent(emptyEl(el), content);
+}
+
+/**
+ * Finds a single DOM element matching `selector` within the optional
+ * `context` of another DOM element (defaulting to `document`).
+ *
+ * @function $
+ * @param {String} selector
+ * A valid CSS selector, which will be passed to `querySelector`.
+ *
+ * @param {Element|String} [context=document]
+ * A DOM element within which to query. Can also be a selector
+ * string in which case the first matching element will be used
+ * as context. If missing (or no element matches selector), falls
+ * back to `document`.
+ *
+ * @return {Element|null}
+ */
+var $ = createQuerier('querySelector');
+
+exports.$ = $;
+/**
+ * Finds a all DOM elements matching `selector` within the optional
+ * `context` of another DOM element (defaulting to `document`).
+ *
+ * @function $$
+ * @param {String} selector
+ * A valid CSS selector, which will be passed to `querySelectorAll`.
+ *
+ * @param {Element|String} [context=document]
+ * A DOM element within which to query. Can also be a selector
+ * string in which case the first matching element will be used
+ * as context. If missing (or no element matches selector), falls
+ * back to `document`.
+ *
+ * @return {NodeList}
+ */
+var $$ = createQuerier('querySelectorAll');
+exports.$$ = $$;
+
+},{"./guid.js":150,"./log.js":151,"global/document":9,"global/window":10,"tsml":64}],147:[function(_dereq_,module,exports){
+/**
+ * @file events.js
+ *
+ * Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
+ * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
+ * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
+ * robust as jquery's, so there's probably some differences.
+ */
+
+'use strict';
+
+exports.__esModule = true;
+exports.on = on;
+exports.off = off;
+exports.trigger = trigger;
+exports.one = one;
+exports.fixEvent = fixEvent;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+var _domJs = _dereq_('./dom.js');
+
+var Dom = _interopRequireWildcard(_domJs);
+
+var _guidJs = _dereq_('./guid.js');
+
+var Guid = _interopRequireWildcard(_guidJs);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * Add an event listener to element
+ * It stores the handler function in a separate cache object
+ * and adds a generic handler to the element's event,
+ * along with a unique id (guid) to the element.
+ *
+ * @param {Element|Object} elem Element or object to bind listeners to
+ * @param {String|Array} type Type of event to bind to.
+ * @param {Function} fn Event listener.
+ * @method on
+ */
+
+function on(elem, type, fn) {
+ if (Array.isArray(type)) {
+ return _handleMultipleEvents(on, elem, type, fn);
+ }
+
+ var data = Dom.getElData(elem);
+
+ // We need a place to store all our handler data
+ if (!data.handlers) data.handlers = {};
+
+ if (!data.handlers[type]) data.handlers[type] = [];
+
+ if (!fn.guid) fn.guid = Guid.newGUID();
+
+ data.handlers[type].push(fn);
+
+ if (!data.dispatcher) {
+ data.disabled = false;
+
+ data.dispatcher = function (event, hash) {
+
+ if (data.disabled) return;
+ event = fixEvent(event);
+
+ var handlers = data.handlers[event.type];
+
+ if (handlers) {
+ // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
+ var handlersCopy = handlers.slice(0);
+
+ for (var m = 0, n = handlersCopy.length; m < n; m++) {
+ if (event.isImmediatePropagationStopped()) {
+ break;
+ } else {
+ handlersCopy[m].call(elem, event, hash);
+ }
+ }
+ }
+ };
+ }
+
+ if (data.handlers[type].length === 1) {
+ if (elem.addEventListener) {
+ elem.addEventListener(type, data.dispatcher, false);
+ } else if (elem.attachEvent) {
+ elem.attachEvent('on' + type, data.dispatcher);
+ }
+ }
+}
+
+/**
+ * Removes event listeners from an element
+ *
+ * @param {Element|Object} elem Object to remove listeners from
+ * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
+ * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
+ * @method off
+ */
+
+function off(elem, type, fn) {
+ // Don't want to add a cache object through getElData if not needed
+ if (!Dom.hasElData(elem)) return;
+
+ var data = Dom.getElData(elem);
+
+ // If no events exist, nothing to unbind
+ if (!data.handlers) {
+ return;
+ }
+
+ if (Array.isArray(type)) {
+ return _handleMultipleEvents(off, elem, type, fn);
+ }
+
+ // Utility function
+ var removeType = function removeType(t) {
+ data.handlers[t] = [];
+ _cleanUpEvents(elem, t);
+ };
+
+ // Are we removing all bound events?
+ if (!type) {
+ for (var t in data.handlers) {
+ removeType(t);
+ }return;
+ }
+
+ var handlers = data.handlers[type];
+
+ // If no handlers exist, nothing to unbind
+ if (!handlers) return;
+
+ // If no listener was provided, remove all listeners for type
+ if (!fn) {
+ removeType(type);
+ return;
+ }
+
+ // We're only removing a single handler
+ if (fn.guid) {
+ for (var n = 0; n < handlers.length; n++) {
+ if (handlers[n].guid === fn.guid) {
+ handlers.splice(n--, 1);
+ }
+ }
+ }
+
+ _cleanUpEvents(elem, type);
+}
+
+/**
+ * Trigger an event for an element
+ *
+ * @param {Element|Object} elem Element to trigger an event on
+ * @param {Event|Object|String} event A string (the type) or an event object with a type attribute
+ * @param {Object} [hash] data hash to pass along with the event
+ * @return {Boolean=} Returned only if default was prevented
+ * @method trigger
+ */
+
+function trigger(elem, event, hash) {
+ // Fetches element data and a reference to the parent (for bubbling).
+ // Don't want to add a data object to cache for every parent,
+ // so checking hasElData first.
+ var elemData = Dom.hasElData(elem) ? Dom.getElData(elem) : {};
+ var parent = elem.parentNode || elem.ownerDocument;
+ // type = event.type || event,
+ // handler;
+
+ // If an event name was passed as a string, creates an event out of it
+ if (typeof event === 'string') {
+ event = { type: event, target: elem };
+ }
+ // Normalizes the event properties.
+ event = fixEvent(event);
+
+ // If the passed element has a dispatcher, executes the established handlers.
+ if (elemData.dispatcher) {
+ elemData.dispatcher.call(elem, event, hash);
+ }
+
+ // Unless explicitly stopped or the event does not bubble (e.g. media events)
+ // recursively calls this function to bubble the event up the DOM.
+ if (parent && !event.isPropagationStopped() && event.bubbles === true) {
+ trigger.call(null, parent, event, hash);
+
+ // If at the top of the DOM, triggers the default action unless disabled.
+ } else if (!parent && !event.defaultPrevented) {
+ var targetData = Dom.getElData(event.target);
+
+ // Checks if the target has a default action for this event.
+ if (event.target[event.type]) {
+ // Temporarily disables event dispatching on the target as we have already executed the handler.
+ targetData.disabled = true;
+ // Executes the default action.
+ if (typeof event.target[event.type] === 'function') {
+ event.target[event.type]();
+ }
+ // Re-enables event dispatching.
+ targetData.disabled = false;
+ }
+ }
+
+ // Inform the triggerer if the default was prevented by returning false
+ return !event.defaultPrevented;
+}
+
+/**
+ * Trigger a listener only once for an event
+ *
+ * @param {Element|Object} elem Element or object to
+ * @param {String|Array} type Name/type of event
+ * @param {Function} fn Event handler function
+ * @method one
+ */
+
+function one(elem, type, fn) {
+ if (Array.isArray(type)) {
+ return _handleMultipleEvents(one, elem, type, fn);
+ }
+ var func = function func() {
+ off(elem, type, func);
+ fn.apply(this, arguments);
+ };
+ // copy the guid to the new function so it can removed using the original function's ID
+ func.guid = fn.guid = fn.guid || Guid.newGUID();
+ on(elem, type, func);
+}
+
+/**
+ * Fix a native event to have standard property values
+ *
+ * @param {Object} event Event object to fix
+ * @return {Object}
+ * @private
+ * @method fixEvent
+ */
+
+function fixEvent(event) {
+
+ function returnTrue() {
+ return true;
+ }
+ function returnFalse() {
+ return false;
+ }
+
+ // Test if fixing up is needed
+ // Used to check if !event.stopPropagation instead of isPropagationStopped
+ // But native events return true for stopPropagation, but don't have
+ // other expected methods like isPropagationStopped. Seems to be a problem
+ // with the Javascript Ninja code. So we're just overriding all events now.
+ if (!event || !event.isPropagationStopped) {
+ var old = event || _globalWindow2['default'].event;
+
+ event = {};
+ // Clone the old object so that we can modify the values event = {};
+ // IE8 Doesn't like when you mess with native event properties
+ // Firefox returns false for event.hasOwnProperty('type') and other props
+ // which makes copying more difficult.
+ // TODO: Probably best to create a whitelist of event props
+ for (var key in old) {
+ // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
+ // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
+ // and webkitMovementX/Y
+ if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
+ // Chrome 32+ warns if you try to copy deprecated returnValue, but
+ // we still want to if preventDefault isn't supported (IE8).
+ if (!(key === 'returnValue' && old.preventDefault)) {
+ event[key] = old[key];
+ }
+ }
+ }
+
+ // The event occurred on this element
+ if (!event.target) {
+ event.target = event.srcElement || _globalDocument2['default'];
+ }
+
+ // Handle which other element the event is related to
+ if (!event.relatedTarget) {
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+ }
+
+ // Stop the default browser action
+ event.preventDefault = function () {
+ if (old.preventDefault) {
+ old.preventDefault();
+ }
+ event.returnValue = false;
+ old.returnValue = false;
+ event.defaultPrevented = true;
+ };
+
+ event.defaultPrevented = false;
+
+ // Stop the event from bubbling
+ event.stopPropagation = function () {
+ if (old.stopPropagation) {
+ old.stopPropagation();
+ }
+ event.cancelBubble = true;
+ old.cancelBubble = true;
+ event.isPropagationStopped = returnTrue;
+ };
+
+ event.isPropagationStopped = returnFalse;
+
+ // Stop the event from bubbling and executing other handlers
+ event.stopImmediatePropagation = function () {
+ if (old.stopImmediatePropagation) {
+ old.stopImmediatePropagation();
+ }
+ event.isImmediatePropagationStopped = returnTrue;
+ event.stopPropagation();
+ };
+
+ event.isImmediatePropagationStopped = returnFalse;
+
+ // Handle mouse position
+ if (event.clientX != null) {
+ var doc = _globalDocument2['default'].documentElement,
+ body = _globalDocument2['default'].body;
+
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Handle key presses
+ event.which = event.charCode || event.keyCode;
+
+ // Fix button for mouse clicks:
+ // 0 == left; 1 == middle; 2 == right
+ if (event.button != null) {
+ event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
+ }
+ }
+
+ // Returns fixed-up instance
+ return event;
+}
+
+/**
+ * Clean up the listener cache and dispatchers
+*
+ * @param {Element|Object} elem Element to clean up
+ * @param {String} type Type of event to clean up
+ * @private
+ * @method _cleanUpEvents
+ */
+function _cleanUpEvents(elem, type) {
+ var data = Dom.getElData(elem);
+
+ // Remove the events of a particular type if there are none left
+ if (data.handlers[type].length === 0) {
+ delete data.handlers[type];
+ // data.handlers[type] = null;
+ // Setting to null was causing an error with data.handlers
+
+ // Remove the meta-handler from the element
+ if (elem.removeEventListener) {
+ elem.removeEventListener(type, data.dispatcher, false);
+ } else if (elem.detachEvent) {
+ elem.detachEvent('on' + type, data.dispatcher);
+ }
+ }
+
+ // Remove the events object if there are no types left
+ if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
+ delete data.handlers;
+ delete data.dispatcher;
+ delete data.disabled;
+ }
+
+ // Finally remove the element data if there is no data left
+ if (Object.getOwnPropertyNames(data).length === 0) {
+ Dom.removeElData(elem);
+ }
+}
+
+/**
+ * Loops through an array of event types and calls the requested method for each type.
+ *
+ * @param {Function} fn The event method we want to use.
+ * @param {Element|Object} elem Element or object to bind listeners to
+ * @param {String} type Type of event to bind to.
+ * @param {Function} callback Event listener.
+ * @private
+ * @function _handleMultipleEvents
+ */
+function _handleMultipleEvents(fn, elem, types, callback) {
+ types.forEach(function (type) {
+ //Call the event method for each one of the types
+ fn(elem, type, callback);
+ });
+}
+
+},{"./dom.js":146,"./guid.js":150,"global/document":9,"global/window":10}],148:[function(_dereq_,module,exports){
+/**
+ * @file fn.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+var _guidJs = _dereq_('./guid.js');
+
+/**
+ * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
+ * It also stores a unique id on the function so it can be easily removed from events
+ *
+ * @param {*} context The object to bind as scope
+ * @param {Function} fn The function to be bound to a scope
+ * @param {Number=} uid An optional unique ID for the function to be set
+ * @return {Function}
+ * @private
+ * @method bind
+ */
+var bind = function bind(context, fn, uid) {
+ // Make sure the function has a unique ID
+ if (!fn.guid) {
+ fn.guid = _guidJs.newGUID();
+ }
+
+ // Create the new function that changes the context
+ var ret = function ret() {
+ return fn.apply(context, arguments);
+ };
+
+ // Allow for the ability to individualize this function
+ // Needed in the case where multiple objects might share the same prototype
+ // IF both items add an event listener with the same function, then you try to remove just one
+ // it will remove both because they both have the same guid.
+ // when using this, you need to use the bind method when you remove the listener as well.
+ // currently used in text tracks
+ ret.guid = uid ? uid + '_' + fn.guid : fn.guid;
+
+ return ret;
+};
+exports.bind = bind;
+
+},{"./guid.js":150}],149:[function(_dereq_,module,exports){
+/**
+ * @file format-time.js
+ *
+ * Format seconds as a time string, H:MM:SS or M:SS
+ * Supplying a guide (in seconds) will force a number of leading zeros
+ * to cover the length of the guide
+ *
+ * @param {Number} seconds Number of seconds to be turned into a string
+ * @param {Number} guide Number (in seconds) to model the string after
+ * @return {String} Time formatted as H:MM:SS or M:SS
+ * @private
+ * @function formatTime
+ */
+'use strict';
+
+exports.__esModule = true;
+function formatTime(seconds) {
+ var guide = arguments.length <= 1 || arguments[1] === undefined ? seconds : arguments[1];
+ return (function () {
+ seconds = seconds < 0 ? 0 : seconds;
+ var s = Math.floor(seconds % 60);
+ var m = Math.floor(seconds / 60 % 60);
+ var h = Math.floor(seconds / 3600);
+ var gm = Math.floor(guide / 60 % 60);
+ var gh = Math.floor(guide / 3600);
+
+ // handle invalid times
+ if (isNaN(seconds) || seconds === Infinity) {
+ // '-' is false for all relational operators (e.g. <, >=) so this setting
+ // will add the minimum number of fields specified by the guide
+ h = m = s = '-';
+ }
+
+ // Check if we need to show hours
+ h = h > 0 || gh > 0 ? h + ':' : '';
+
+ // If hours are showing, we may need to add a leading zero.
+ // Always show at least one digit of minutes.
+ m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
+
+ // Check if leading zero is need for seconds
+ s = s < 10 ? '0' + s : s;
+
+ return h + m + s;
+ })();
+}
+
+exports['default'] = formatTime;
+module.exports = exports['default'];
+
+},{}],150:[function(_dereq_,module,exports){
+/**
+ * @file guid.js
+ *
+ * Unique ID for an element or function
+ * @type {Number}
+ * @private
+ */
+"use strict";
+
+exports.__esModule = true;
+exports.newGUID = newGUID;
+var _guid = 1;
+
+/**
+ * Get the next unique ID
+ *
+ * @return {String}
+ * @function newGUID
+ */
+
+function newGUID() {
+ return _guid++;
+}
+
+},{}],151:[function(_dereq_,module,exports){
+/**
+ * @file log.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+/**
+ * Log plain debug messages
+ */
+var log = function log() {
+ _logType(null, arguments);
+};
+
+/**
+ * Keep a history of log messages
+ * @type {Array}
+ */
+log.history = [];
+
+/**
+ * Log error messages
+ */
+log.error = function () {
+ _logType('error', arguments);
+};
+
+/**
+ * Log warning messages
+ */
+log.warn = function () {
+ _logType('warn', arguments);
+};
+
+/**
+ * Log messages to the console and history based on the type of message
+ *
+ * @param {String} type The type of message, or `null` for `log`
+ * @param {Object} args The args to be passed to the log
+ * @private
+ * @method _logType
+ */
+function _logType(type, args) {
+ // convert args to an array to get array functions
+ var argsArray = Array.prototype.slice.call(args);
+ // if there's no console then don't try to output messages
+ // they will still be stored in log.history
+ // Was setting these once outside of this function, but containing them
+ // in the function makes it easier to test cases where console doesn't exist
+ var noop = function noop() {};
+
+ var console = _globalWindow2['default']['console'] || {
+ 'log': noop,
+ 'warn': noop,
+ 'error': noop
+ };
+
+ if (type) {
+ // add the type to the front of the message
+ argsArray.unshift(type.toUpperCase() + ':');
+ } else {
+ // default to log with no prefix
+ type = 'log';
+ }
+
+ // add to history
+ log.history.push(argsArray);
+
+ // add console prefix after adding to history
+ argsArray.unshift('VIDEOJS:');
+
+ // call appropriate log function
+ if (console[type].apply) {
+ console[type].apply(console, argsArray);
+ } else {
+ // ie8 doesn't allow error.apply, but it will just join() the array anyway
+ console[type](argsArray.join(' '));
+ }
+}
+
+exports['default'] = log;
+module.exports = exports['default'];
+
+},{"global/window":10}],152:[function(_dereq_,module,exports){
+/**
+ * @file merge-options.js
+ */
+'use strict';
+
+exports.__esModule = true;
+exports['default'] = mergeOptions;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge');
+
+var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge);
+
+function isPlain(obj) {
+ return !!obj && typeof obj === 'object' && obj.toString() === '[object Object]' && obj.constructor === Object;
+}
+
+/**
+ * Merge customizer. video.js simply overwrites non-simple objects
+ * (like arrays) instead of attempting to overlay them.
+ * @see https://lodash.com/docs#merge
+ */
+var customizer = function customizer(destination, source) {
+ // If we're not working with a plain object, copy the value as is
+ // If source is an array, for instance, it will replace destination
+ if (!isPlain(source)) {
+ return source;
+ }
+
+ // If the new value is a plain object but the first object value is not
+ // we need to create a new object for the first object to merge with.
+ // This makes it consistent with how merge() works by default
+ // and also protects from later changes the to first object affecting
+ // the second object's values.
+ if (!isPlain(destination)) {
+ return mergeOptions(source);
+ }
+};
+
+/**
+ * Merge one or more options objects, recursively merging **only**
+ * plain object properties. Previously `deepMerge`.
+ *
+ * @param {...Object} source One or more objects to merge
+ * @returns {Object} a new object that is the union of all
+ * provided objects
+ * @function mergeOptions
+ */
+
+function mergeOptions() {
+ // contruct the call dynamically to handle the variable number of
+ // objects to merge
+ var args = Array.prototype.slice.call(arguments);
+
+ // unshift an empty object into the front of the call as the target
+ // of the merge
+ args.unshift({});
+
+ // customize conflict resolution to match our historical merge behavior
+ args.push(customizer);
+
+ _lodashCompatObjectMerge2['default'].apply(null, args);
+
+ // return the mutated result object
+ return args[0];
+}
+
+module.exports = exports['default'];
+
+},{"lodash-compat/object/merge":49}],153:[function(_dereq_,module,exports){
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var createStyleElement = function createStyleElement(className) {
+ var style = _globalDocument2['default'].createElement('style');
+ style.className = className;
+
+ return style;
+};
+
+exports.createStyleElement = createStyleElement;
+var setTextContent = function setTextContent(el, content) {
+ if (el.styleSheet) {
+ el.styleSheet.cssText = content;
+ } else {
+ el.textContent = content;
+ }
+};
+exports.setTextContent = setTextContent;
+
+},{"global/document":9}],154:[function(_dereq_,module,exports){
+'use strict';
+
+exports.__esModule = true;
+exports.createTimeRanges = createTimeRanges;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _logJs = _dereq_('./log.js');
+
+var _logJs2 = _interopRequireDefault(_logJs);
+
+/**
+ * @file time-ranges.js
+ *
+ * Should create a fake TimeRange object
+ * Mimics an HTML5 time range instance, which has functions that
+ * return the start and end times for a range
+ * TimeRanges are returned by the buffered() method
+ *
+ * @param {(Number|Array)} Start of a single range or an array of ranges
+ * @param {Number} End of a single range
+ * @private
+ * @method createTimeRanges
+ */
+
+function createTimeRanges(start, end) {
+ if (Array.isArray(start)) {
+ return createTimeRangesObj(start);
+ } else if (start === undefined || end === undefined) {
+ return createTimeRangesObj();
+ }
+ return createTimeRangesObj([[start, end]]);
+}
+
+exports.createTimeRange = createTimeRanges;
+
+function createTimeRangesObj(ranges) {
+ if (ranges === undefined || ranges.length === 0) {
+ return {
+ length: 0,
+ start: function start() {
+ throw new Error('This TimeRanges object is empty');
+ },
+ end: function end() {
+ throw new Error('This TimeRanges object is empty');
+ }
+ };
+ }
+ return {
+ length: ranges.length,
+ start: getRange.bind(null, 'start', 0, ranges),
+ end: getRange.bind(null, 'end', 1, ranges)
+ };
+}
+
+function getRange(fnName, valueIndex, ranges, rangeIndex) {
+ if (rangeIndex === undefined) {
+ _logJs2['default'].warn('DEPRECATED: Function \'' + fnName + '\' on \'TimeRanges\' called without an index argument.');
+ rangeIndex = 0;
+ }
+ rangeCheck(fnName, rangeIndex, ranges.length - 1);
+ return ranges[rangeIndex][valueIndex];
+}
+
+function rangeCheck(fnName, index, maxIndex) {
+ if (index < 0 || index > maxIndex) {
+ throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is greater than or equal to the maximum bound (' + maxIndex + ').');
+ }
+}
+
+},{"./log.js":151}],155:[function(_dereq_,module,exports){
+/**
+ * @file to-title-case.js
+ *
+ * Uppercase the first letter of a string
+ *
+ * @param {String} string String to be uppercased
+ * @return {String}
+ * @private
+ * @method toTitleCase
+ */
+"use strict";
+
+exports.__esModule = true;
+function toTitleCase(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+exports["default"] = toTitleCase;
+module.exports = exports["default"];
+
+},{}],156:[function(_dereq_,module,exports){
+/**
+ * @file url.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+/**
+ * Resolve and parse the elements of a URL
+ *
+ * @param {String} url The url to parse
+ * @return {Object} An object of url details
+ * @method parseUrl
+ */
+var parseUrl = function parseUrl(url) {
+ var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
+
+ // add the url to an anchor and let the browser parse the URL
+ var a = _globalDocument2['default'].createElement('a');
+ a.href = url;
+
+ // IE8 (and 9?) Fix
+ // ie8 doesn't parse the URL correctly until the anchor is actually
+ // added to the body, and an innerHTML is needed to trigger the parsing
+ var addToBody = a.host === '' && a.protocol !== 'file:';
+ var div = undefined;
+ if (addToBody) {
+ div = _globalDocument2['default'].createElement('div');
+ div.innerHTML = '';
+ a = div.firstChild;
+ // prevent the div from affecting layout
+ div.setAttribute('style', 'display:none; position:absolute;');
+ _globalDocument2['default'].body.appendChild(div);
+ }
+
+ // Copy the specific URL properties to a new object
+ // This is also needed for IE8 because the anchor loses its
+ // properties when it's removed from the dom
+ var details = {};
+ for (var i = 0; i < props.length; i++) {
+ details[props[i]] = a[props[i]];
+ }
+
+ // IE9 adds the port to the host property unlike everyone else. If
+ // a port identifier is added for standard ports, strip it.
+ if (details.protocol === 'http:') {
+ details.host = details.host.replace(/:80$/, '');
+ }
+ if (details.protocol === 'https:') {
+ details.host = details.host.replace(/:443$/, '');
+ }
+
+ if (addToBody) {
+ _globalDocument2['default'].body.removeChild(div);
+ }
+
+ return details;
+};
+
+exports.parseUrl = parseUrl;
+/**
+ * Get absolute version of relative URL. Used to tell flash correct URL.
+ * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
+ *
+ * @param {String} url URL to make absolute
+ * @return {String} Absolute URL
+ * @private
+ * @method getAbsoluteURL
+ */
+var getAbsoluteURL = function getAbsoluteURL(url) {
+ // Check if absolute URL
+ if (!url.match(/^https?:\/\//)) {
+ // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
+ var div = _globalDocument2['default'].createElement('div');
+ div.innerHTML = 'x';
+ url = div.firstChild.href;
+ }
+
+ return url;
+};
+
+exports.getAbsoluteURL = getAbsoluteURL;
+/**
+ * Returns the extension of the passed file name. It will return an empty string if you pass an invalid path
+ *
+ * @param {String} path The fileName path like '/path/to/file.mp4'
+ * @returns {String} The extension in lower case or an empty string if no extension could be found.
+ * @method getFileExtension
+ */
+var getFileExtension = function getFileExtension(path) {
+ if (typeof path === 'string') {
+ var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
+ var pathParts = splitPathRe.exec(path);
+
+ if (pathParts) {
+ return pathParts.pop().toLowerCase();
+ }
+ }
+
+ return '';
+};
+
+exports.getFileExtension = getFileExtension;
+/**
+ * Returns whether the url passed is a cross domain request or not.
+ *
+ * @param {String} url The url to check
+ * @return {Boolean} Whether it is a cross domain request or not
+ * @method isCrossOrigin
+ */
+var isCrossOrigin = function isCrossOrigin(url) {
+ var winLoc = _globalWindow2['default'].location;
+ var urlInfo = parseUrl(url);
+
+ // IE8 protocol relative urls will return ':' for protocol
+ var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
+
+ // Check if url is for another domain/origin
+ // IE8 doesn't know location.origin, so we won't rely on it here
+ var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
+
+ return crossOrigin;
+};
+
+exports.isCrossOrigin = isCrossOrigin;
+var addUrlPara = function addUrlPara(uri, name, value) {
+ var currentUrl = uri.split('#')[0];
+ if (/\?/g.test(currentUrl)) {
+ if (/name=[-\w]{4,25}/g.test(currentUrl)) {
+ currentUrl = currentUrl.replace(/name=[-\w]{4,25}/g, name + "=" + value);
+ } else {
+ currentUrl += "&" + name + "=" + value;
+ }
+ } else {
+ currentUrl += "?" + name + "=" + value;
+ }
+ if (uri.split('#')[1]) {
+ uri = currentUrl + '#' + uri.split('#')[1];
+ } else {
+ uri = currentUrl;
+ }
+ return uri;
+};
+exports.addUrlPara = addUrlPara;
+
+},{"global/document":9,"global/window":10}],157:[function(_dereq_,module,exports){
+/**
+ * @file video.js
+ */
+'use strict';
+
+exports.__esModule = true;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj['default'] = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalWindow = _dereq_('global/window');
+
+var _globalWindow2 = _interopRequireDefault(_globalWindow);
+
+var _globalDocument = _dereq_('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _setup = _dereq_('./setup');
+
+var setup = _interopRequireWildcard(_setup);
+
+var _utilsStylesheetJs = _dereq_('./utils/stylesheet.js');
+
+var stylesheet = _interopRequireWildcard(_utilsStylesheetJs);
+
+var _component = _dereq_('./component');
+
+var _component2 = _interopRequireDefault(_component);
+
+var _eventTarget = _dereq_('./event-target');
+
+var _eventTarget2 = _interopRequireDefault(_eventTarget);
+
+var _utilsEventsJs = _dereq_('./utils/events.js');
+
+var Events = _interopRequireWildcard(_utilsEventsJs);
+
+var _player = _dereq_('./player');
+
+var _player2 = _interopRequireDefault(_player);
+
+var _pluginsJs = _dereq_('./plugins.js');
+
+var _pluginsJs2 = _interopRequireDefault(_pluginsJs);
+
+var _srcJsUtilsMergeOptionsJs = _dereq_('../../src/js/utils/merge-options.js');
+
+var _srcJsUtilsMergeOptionsJs2 = _interopRequireDefault(_srcJsUtilsMergeOptionsJs);
+
+var _utilsFnJs = _dereq_('./utils/fn.js');
+
+var Fn = _interopRequireWildcard(_utilsFnJs);
+
+var _tracksTextTrackJs = _dereq_('./tracks/text-track.js');
+
+var _tracksTextTrackJs2 = _interopRequireDefault(_tracksTextTrackJs);
+
+var _tracksAudioTrackJs = _dereq_('./tracks/audio-track.js');
+
+var _tracksAudioTrackJs2 = _interopRequireDefault(_tracksAudioTrackJs);
+
+var _tracksVideoTrackJs = _dereq_('./tracks/video-track.js');
+
+var _tracksVideoTrackJs2 = _interopRequireDefault(_tracksVideoTrackJs);
+
+var _utilsTimeRangesJs = _dereq_('./utils/time-ranges.js');
+
+var _utilsFormatTimeJs = _dereq_('./utils/format-time.js');
+
+var _utilsFormatTimeJs2 = _interopRequireDefault(_utilsFormatTimeJs);
+
+var _utilsLogJs = _dereq_('./utils/log.js');
+
+var _utilsLogJs2 = _interopRequireDefault(_utilsLogJs);
+
+var _utilsDomJs = _dereq_('./utils/dom.js');
+
+var Dom = _interopRequireWildcard(_utilsDomJs);
+
+var _utilsBrowserJs = _dereq_('./utils/browser.js');
+
+var browser = _interopRequireWildcard(_utilsBrowserJs);
+
+var _utilsUrlJs = _dereq_('./utils/url.js');
+
+var Url = _interopRequireWildcard(_utilsUrlJs);
+
+var _extendJs = _dereq_('./extend.js');
+
+var _extendJs2 = _interopRequireDefault(_extendJs);
+
+var _lodashCompatObjectMerge = _dereq_('lodash-compat/object/merge');
+
+var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge);
+
+var _xhr = _dereq_('xhr');
+
+var _xhr2 = _interopRequireDefault(_xhr);
+
+// Include the built-in techs
+
+var _techTechJs = _dereq_('./tech/tech.js');
+
+var _techTechJs2 = _interopRequireDefault(_techTechJs);
+
+var _techHtml5Js = _dereq_('./tech/html5.js');
+
+var _techHtml5Js2 = _interopRequireDefault(_techHtml5Js);
+
+var _techFlashJs = _dereq_('./tech/flash.js');
+
+var _techFlashJs2 = _interopRequireDefault(_techFlashJs);
+
+// HTML5 Element Shim for IE8
+if (typeof HTMLVideoElement === 'undefined') {
+ _globalDocument2['default'].createElement('video');
+ _globalDocument2['default'].createElement('audio');
+ _globalDocument2['default'].createElement('track');
+}
+
+/**
+ * Doubles as the main function for users to create a player instance and also
+ * the main library object.
+ * The `videojs` function can be used to initialize or retrieve a player.
+ * ```js
+ * var myPlayer = videojs('my_video_id');
+ * ```
+ *
+ * @param {String|Element} id Video element or video element ID
+ * @param {Object=} options Optional options object for config/settings
+ * @param {Function=} ready Optional ready callback
+ * @return {Player} A player instance
+ * @mixes videojs
+ * @method videojs
+ */
+function videojs(id, options, ready) {
+ var tag = undefined; // Element of ID
+
+ // Allow for element or ID to be passed in
+ // String ID
+ if (typeof id === 'string') {
+
+ // Adjust for jQuery ID syntax
+ if (id.indexOf('#') === 0) {
+ id = id.slice(1);
+ }
+
+ // If a player instance has already been created for this ID return it.
+ if (videojs.getPlayers()[id]) {
+
+ // If options or ready funtion are passed, warn
+ if (options) {
+ _utilsLogJs2['default'].warn('Player "' + id + '" is already initialised. Options will not be applied.');
+ }
+
+ if (ready) {
+ videojs.getPlayers()[id].ready(ready);
+ }
+
+ return videojs.getPlayers()[id];
+
+ // Otherwise get element for ID
+ } else {
+ tag = Dom.getEl(id);
+ }
+
+ // ID is a media element
+ } else {
+ tag = id;
+ }
+
+ // Check for a useable element
+ if (!tag || !tag.nodeName) {
+ // re: nodeName, could be a box div also
+ throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
+ }
+
+ // Element may have a player attr referring to an already created player instance.
+ // If not, set up a new player and return the instance.
+ return tag['player'] || _player2['default'].players[tag.playerId] || new _player2['default'](tag, options, ready);
+}
+
+// Add default styles
+if (_globalWindow2['default'].VIDEOJS_NO_DYNAMIC_STYLE !== true) {
+ var style = Dom.$('.vjs-styles-defaults');
+
+ if (!style) {
+ style = stylesheet.createStyleElement('vjs-styles-defaults');
+ var head = Dom.$('head');
+ head.insertBefore(style, head.firstChild);
+ stylesheet.setTextContent(style, '\n .video-js {\n width: 100%;\n height: 100%;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ');
+ }
+}
+
+// Run Auto-load players
+// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
+setup.autoSetupTimeout(1, videojs);
+
+/*
+ * Current software version (semver)
+ *
+ * @type {String}
+ */
+videojs.VERSION = '5.10.7';
+
+/**
+ * The global options object. These are the settings that take effect
+ * if no overrides are specified when the player is created.
+ *
+ * ```js
+ * videojs.options.autoplay = true
+ * // -> all players will autoplay by default
+ * ```
+ *
+ * @type {Object}
+ */
+videojs.options = _player2['default'].prototype.options_;
+
+/**
+ * Get an object with the currently created players, keyed by player ID
+ *
+ * @return {Object} The created players
+ * @mixes videojs
+ * @method getPlayers
+ */
+videojs.getPlayers = function () {
+ return _player2['default'].players;
+};
+
+/**
+ * Expose players object.
+ *
+ * @memberOf videojs
+ * @property {Object} players
+ */
+videojs.players = _player2['default'].players;
+
+/**
+ * Get a component class object by name
+ * ```js
+ * var VjsButton = videojs.getComponent('Button');
+ * // Create a new instance of the component
+ * var myButton = new VjsButton(myPlayer);
+ * ```
+ *
+ * @return {Component} Component identified by name
+ * @mixes videojs
+ * @method getComponent
+ */
+videojs.getComponent = _component2['default'].getComponent;
+
+/**
+ * Register a component so it can referred to by name
+ * Used when adding to other
+ * components, either through addChild
+ * `component.addChild('myComponent')`
+ * or through default children options
+ * `{ children: ['myComponent'] }`.
+ * ```js
+ * // Get a component to subclass
+ * var VjsButton = videojs.getComponent('Button');
+ * // Subclass the component (see 'extend' doc for more info)
+ * var MySpecialButton = videojs.extend(VjsButton, {});
+ * // Register the new component
+ * VjsButton.registerComponent('MySepcialButton', MySepcialButton);
+ * // (optionally) add the new component as a default player child
+ * myPlayer.addChild('MySepcialButton');
+ * ```
+ * NOTE: You could also just initialize the component before adding.
+ * `component.addChild(new MyComponent());`
+ *
+ * @param {String} The class name of the component
+ * @param {Component} The component class
+ * @return {Component} The newly registered component
+ * @mixes videojs
+ * @method registerComponent
+ */
+videojs.registerComponent = function (name, comp) {
+ if (_techTechJs2['default'].isTech(comp)) {
+ _utilsLogJs2['default'].warn('The ' + name + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
+ }
+
+ _component2['default'].registerComponent.call(_component2['default'], name, comp);
+};
+
+/**
+ * Get a Tech class object by name
+ * ```js
+ * var Html5 = videojs.getTech('Html5');
+ * // Create a new instance of the component
+ * var html5 = new Html5(options);
+ * ```
+ *
+ * @return {Tech} Tech identified by name
+ * @mixes videojs
+ * @method getComponent
+ */
+videojs.getTech = _techTechJs2['default'].getTech;
+
+/**
+ * Register a Tech so it can referred to by name.
+ * This is used in the tech order for the player.
+ *
+ * ```js
+ * // get the Html5 Tech
+ * var Html5 = videojs.getTech('Html5');
+ * var MyTech = videojs.extend(Html5, {});
+ * // Register the new Tech
+ * VjsButton.registerTech('Tech', MyTech);
+ * var player = videojs('myplayer', {
+ * techOrder: ['myTech', 'html5']
+ * });
+ * ```
+ *
+ * @param {String} The class name of the tech
+ * @param {Tech} The tech class
+ * @return {Tech} The newly registered Tech
+ * @mixes videojs
+ * @method registerTech
+ */
+videojs.registerTech = _techTechJs2['default'].registerTech;
+
+/**
+ * A suite of browser and device tests
+ *
+ * @type {Object}
+ * @private
+ */
+videojs.browser = browser;
+
+/**
+ * Whether or not the browser supports touch events. Included for backward
+ * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
+ * instead going forward.
+ *
+ * @deprecated
+ * @type {Boolean}
+ */
+videojs.TOUCH_ENABLED = browser.TOUCH_ENABLED;
+
+/**
+ * Subclass an existing class
+ * Mimics ES6 subclassing with the `extend` keyword
+ * ```js
+ * // Create a basic javascript 'class'
+ * function MyClass(name){
+ * // Set a property at initialization
+ * this.myName = name;
+ * }
+ * // Create an instance method
+ * MyClass.prototype.sayMyName = function(){
+ * alert(this.myName);
+ * };
+ * // Subclass the exisitng class and change the name
+ * // when initializing
+ * var MySubClass = videojs.extend(MyClass, {
+ * constructor: function(name) {
+ * // Call the super class constructor for the subclass
+ * MyClass.call(this, name)
+ * }
+ * });
+ * // Create an instance of the new sub class
+ * var myInstance = new MySubClass('John');
+ * myInstance.sayMyName(); // -> should alert "John"
+ * ```
+ *
+ * @param {Function} The Class to subclass
+ * @param {Object} An object including instace methods for the new class
+ * Optionally including a `constructor` function
+ * @return {Function} The newly created subclass
+ * @mixes videojs
+ * @method extend
+ */
+videojs.extend = _extendJs2['default'];
+
+/**
+ * Merge two options objects recursively
+ * Performs a deep merge like lodash.merge but **only merges plain objects**
+ * (not arrays, elements, anything else)
+ * Other values will be copied directly from the second object.
+ * ```js
+ * var defaultOptions = {
+ * foo: true,
+ * bar: {
+ * a: true,
+ * b: [1,2,3]
+ * }
+ * };
+ * var newOptions = {
+ * foo: false,
+ * bar: {
+ * b: [4,5,6]
+ * }
+ * };
+ * var result = videojs.mergeOptions(defaultOptions, newOptions);
+ * // result.foo = false;
+ * // result.bar.a = true;
+ * // result.bar.b = [4,5,6];
+ * ```
+ *
+ * @param {Object} defaults The options object whose values will be overriden
+ * @param {Object} overrides The options object with values to override the first
+ * @param {Object} etc Any number of additional options objects
+ *
+ * @return {Object} a new object with the merged values
+ * @mixes videojs
+ * @method mergeOptions
+ */
+videojs.mergeOptions = _srcJsUtilsMergeOptionsJs2['default'];
+
+/**
+ * Change the context (this) of a function
+ *
+ * videojs.bind(newContext, function(){
+ * this === newContext
+ * });
+ *
+ * NOTE: as of v5.0 we require an ES5 shim, so you should use the native
+ * `function(){}.bind(newContext);` instead of this.
+ *
+ * @param {*} context The object to bind as scope
+ * @param {Function} fn The function to be bound to a scope
+ * @param {Number=} uid An optional unique ID for the function to be set
+ * @return {Function}
+ */
+videojs.bind = Fn.bind;
+
+/**
+ * Create a Video.js player plugin
+ * Plugins are only initialized when options for the plugin are included
+ * in the player options, or the plugin function on the player instance is
+ * called.
+ * **See the plugin guide in the docs for a more detailed example**
+ * ```js
+ * // Make a plugin that alerts when the player plays
+ * videojs.plugin('myPlugin', function(myPluginOptions) {
+ * myPluginOptions = myPluginOptions || {};
+ *
+ * var player = this;
+ * var alertText = myPluginOptions.text || 'Player is playing!'
+ *
+ * player.on('play', function(){
+ * alert(alertText);
+ * });
+ * });
+ * // USAGE EXAMPLES
+ * // EXAMPLE 1: New player with plugin options, call plugin immediately
+ * var player1 = videojs('idOne', {
+ * myPlugin: {
+ * text: 'Custom text!'
+ * }
+ * });
+ * // Click play
+ * // --> Should alert 'Custom text!'
+ * // EXAMPLE 3: New player, initialize plugin later
+ * var player3 = videojs('idThree');
+ * // Click play
+ * // --> NO ALERT
+ * // Click pause
+ * // Initialize plugin using the plugin function on the player instance
+ * player3.myPlugin({
+ * text: 'Plugin added later!'
+ * });
+ * // Click play
+ * // --> Should alert 'Plugin added later!'
+ * ```
+ *
+ * @param {String} name The plugin name
+ * @param {Function} fn The plugin function that will be called with options
+ * @mixes videojs
+ * @method plugin
+ */
+videojs.plugin = _pluginsJs2['default'];
+
+/**
+ * Adding languages so that they're available to all players.
+ * ```js
+ * videojs.addLanguage('es', { 'Hello': 'Hola' });
+ * ```
+ *
+ * @param {String} code The language code or dictionary property
+ * @param {Object} data The data values to be translated
+ * @return {Object} The resulting language dictionary object
+ * @mixes videojs
+ * @method addLanguage
+ */
+videojs.addLanguage = function (code, data) {
+ var _merge;
+
+ code = ('' + code).toLowerCase();
+ return _lodashCompatObjectMerge2['default'](videojs.options.languages, (_merge = {}, _merge[code] = data, _merge))[code];
+};
+
+/**
+ * Log debug messages.
+ *
+ * @param {...Object} messages One or more messages to log
+ */
+videojs.log = _utilsLogJs2['default'];
+
+/**
+ * Creates an emulated TimeRange object.
+ *
+ * @param {Number|Array} start Start time in seconds or an array of ranges
+ * @param {Number} end End time in seconds
+ * @return {Object} Fake TimeRange object
+ * @method createTimeRange
+ */
+videojs.createTimeRange = videojs.createTimeRanges = _utilsTimeRangesJs.createTimeRanges;
+
+/**
+ * Format seconds as a time string, H:MM:SS or M:SS
+ * Supplying a guide (in seconds) will force a number of leading zeros
+ * to cover the length of the guide
+ *
+ * @param {Number} seconds Number of seconds to be turned into a string
+ * @param {Number} guide Number (in seconds) to model the string after
+ * @return {String} Time formatted as H:MM:SS or M:SS
+ * @method formatTime
+ */
+videojs.formatTime = _utilsFormatTimeJs2['default'];
+
+/**
+ * Resolve and parse the elements of a URL
+ *
+ * @param {String} url The url to parse
+ * @return {Object} An object of url details
+ * @method parseUrl
+ */
+videojs.parseUrl = Url.parseUrl;
+
+/**
+ * Returns whether the url passed is a cross domain request or not.
+ *
+ * @param {String} url The url to check
+ * @return {Boolean} Whether it is a cross domain request or not
+ * @method isCrossOrigin
+ */
+videojs.isCrossOrigin = Url.isCrossOrigin;
+
+/**
+ * Event target class.
+ *
+ * @type {Function}
+ */
+videojs.EventTarget = _eventTarget2['default'];
+
+/**
+ * Add an event listener to element
+ * It stores the handler function in a separate cache object
+ * and adds a generic handler to the element's event,
+ * along with a unique id (guid) to the element.
+ *
+ * @param {Element|Object} elem Element or object to bind listeners to
+ * @param {String|Array} type Type of event to bind to.
+ * @param {Function} fn Event listener.
+ * @method on
+ */
+videojs.on = Events.on;
+
+/**
+ * Trigger a listener only once for an event
+ *
+ * @param {Element|Object} elem Element or object to
+ * @param {String|Array} type Name/type of event
+ * @param {Function} fn Event handler function
+ * @method one
+ */
+videojs.one = Events.one;
+
+/**
+ * Removes event listeners from an element
+ *
+ * @param {Element|Object} elem Object to remove listeners from
+ * @param {String|Array=} type Type of listener to remove. Don't include to remove all events from element.
+ * @param {Function} fn Specific listener to remove. Don't include to remove listeners for an event type.
+ * @method off
+ */
+videojs.off = Events.off;
+
+/**
+ * Trigger an event for an element
+ *
+ * @param {Element|Object} elem Element to trigger an event on
+ * @param {Event|Object|String} event A string (the type) or an event object with a type attribute
+ * @param {Object} [hash] data hash to pass along with the event
+ * @return {Boolean=} Returned only if default was prevented
+ * @method trigger
+ */
+videojs.trigger = Events.trigger;
+
+/**
+ * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
+ *
+ * videojs.xhr({
+ * body: someJSONString,
+ * uri: "/foo",
+ * headers: {
+ * "Content-Type": "application/json"
+ * }
+ * }, function (err, resp, body) {
+ * // check resp.statusCode
+ * });
+ *
+ * Check out the [full
+ * documentation](https://github.com/Raynos/xhr/blob/v2.1.0/README.md)
+ * for more options.
+ *
+ * @param {Object} options settings for the request.
+ * @return {XMLHttpRequest|XDomainRequest} the request object.
+ * @see https://github.com/Raynos/xhr
+ */
+videojs.xhr = _xhr2['default'];
+
+/**
+ * TextTrack class
+ *
+ * @type {Function}
+ */
+videojs.TextTrack = _tracksTextTrackJs2['default'];
+
+/**
+ * export the AudioTrack class so that source handlers can create
+ * AudioTracks and then add them to the players AudioTrackList
+ *
+ * @type {Function}
+ */
+videojs.AudioTrack = _tracksAudioTrackJs2['default'];
+
+/**
+ * export the VideoTrack class so that source handlers can create
+ * VideoTracks and then add them to the players VideoTrackList
+ *
+ * @type {Function}
+ */
+videojs.VideoTrack = _tracksVideoTrackJs2['default'];
+
+/**
+ * Determines, via duck typing, whether or not a value is a DOM element.
+ *
+ * @method isEl
+ * @param {Mixed} value
+ * @return {Boolean}
+ */
+videojs.isEl = Dom.isEl;
+
+/**
+ * Determines, via duck typing, whether or not a value is a text node.
+ *
+ * @method isTextNode
+ * @param {Mixed} value
+ * @return {Boolean}
+ */
+videojs.isTextNode = Dom.isTextNode;
+
+/**
+ * Creates an element and applies properties.
+ *
+ * @method createEl
+ * @param {String} [tagName='div'] Name of tag to be created.
+ * @param {Object} [properties={}] Element properties to be applied.
+ * @param {Object} [attributes={}] Element attributes to be applied.
+ * @return {Element}
+ */
+videojs.createEl = Dom.createEl;
+
+/**
+ * Check if an element has a CSS class
+ *
+ * @method hasClass
+ * @param {Element} element Element to check
+ * @param {String} classToCheck Classname to check
+ */
+videojs.hasClass = Dom.hasElClass;
+
+/**
+ * Add a CSS class name to an element
+ *
+ * @method addClass
+ * @param {Element} element Element to add class name to
+ * @param {String} classToAdd Classname to add
+ */
+videojs.addClass = Dom.addElClass;
+
+/**
+ * Remove a CSS class name from an element
+ *
+ * @method removeClass
+ * @param {Element} element Element to remove from class name
+ * @param {String} classToRemove Classname to remove
+ */
+videojs.removeClass = Dom.removeElClass;
+
+/**
+ * Adds or removes a CSS class name on an element depending on an optional
+ * condition or the presence/absence of the class name.
+ *
+ * @method toggleElClass
+ * @param {Element} element
+ * @param {String} classToToggle
+ * @param {Boolean|Function} [predicate]
+ * Can be a function that returns a Boolean. If `true`, the class
+ * will be added; if `false`, the class will be removed. If not
+ * given, the class will be added if not present and vice versa.
+ */
+videojs.toggleClass = Dom.toggleElClass;
+
+/**
+ * Apply attributes to an HTML element.
+ *
+ * @method setAttributes
+ * @param {Element} el Target element.
+ * @param {Object=} attributes Element attributes to be applied.
+ */
+videojs.setAttributes = Dom.setElAttributes;
+
+/**
+ * Get an element's attribute values, as defined on the HTML tag
+ * Attributes are not the same as properties. They're defined on the tag
+ * or with setAttribute (which shouldn't be used with HTML)
+ * This will return true or false for boolean attributes.
+ *
+ * @method getAttributes
+ * @param {Element} tag Element from which to get tag attributes
+ * @return {Object}
+ */
+videojs.getAttributes = Dom.getElAttributes;
+
+/**
+ * Empties the contents of an element.
+ *
+ * @method emptyEl
+ * @param {Element} el
+ * @return {Element}
+ */
+videojs.emptyEl = Dom.emptyEl;
+
+/**
+ * Normalizes and appends content to an element.
+ *
+ * The content for an element can be passed in multiple types and
+ * combinations, whose behavior is as follows:
+ *
+ * - String
+ * Normalized into a text node.
+ *
+ * - Element, TextNode
+ * Passed through.
+ *
+ * - Array
+ * A one-dimensional array of strings, elements, nodes, or functions (which
+ * return single strings, elements, or nodes).
+ *
+ * - Function
+ * If the sole argument, is expected to produce a string, element,
+ * node, or array.
+ *
+ * @method appendContent
+ * @param {Element} el
+ * @param {String|Element|TextNode|Array|Function} content
+ * @return {Element}
+ */
+videojs.appendContent = Dom.appendContent;
+
+/**
+ * Normalizes and inserts content into an element; this is identical to
+ * `appendContent()`, except it empties the element first.
+ *
+ * The content for an element can be passed in multiple types and
+ * combinations, whose behavior is as follows:
+ *
+ * - String
+ * Normalized into a text node.
+ *
+ * - Element, TextNode
+ * Passed through.
+ *
+ * - Array
+ * A one-dimensional array of strings, elements, nodes, or functions (which
+ * return single strings, elements, or nodes).
+ *
+ * - Function
+ * If the sole argument, is expected to produce a string, element,
+ * node, or array.
+ *
+ * @method insertContent
+ * @param {Element} el
+ * @param {String|Element|TextNode|Array|Function} content
+ * @return {Element}
+ */
+videojs.insertContent = Dom.insertContent;
+
+/*
+ * Custom Universal Module Definition (UMD)
+ *
+ * Video.js will never be a non-browser lib so we can simplify UMD a bunch and
+ * still support requirejs and browserify. This also needs to be closure
+ * compiler compatible, so string keys are used.
+ */
+if (typeof define === 'function' && define['amd']) {
+ define('videojs', [], function () {
+ return videojs;
+ });
+
+ // checking that module is an object too because of umdjs/umd#35
+} else if (typeof exports === 'object' && typeof module === 'object') {
+ module['exports'] = videojs;
+ }
+
+exports['default'] = videojs;
+module.exports = exports['default'];
+
+},{"../../src/js/utils/merge-options.js":152,"./component":71,"./event-target":108,"./extend.js":109,"./player":117,"./plugins.js":118,"./setup":122,"./tech/flash.js":125,"./tech/html5.js":126,"./tech/tech.js":128,"./tracks/audio-track.js":130,"./tracks/text-track.js":138,"./tracks/video-track.js":143,"./utils/browser.js":144,"./utils/dom.js":146,"./utils/events.js":147,"./utils/fn.js":148,"./utils/format-time.js":149,"./utils/log.js":151,"./utils/stylesheet.js":153,"./utils/time-ranges.js":154,"./utils/url.js":156,"global/document":9,"global/window":10,"lodash-compat/object/merge":49,"xhr":65}]},{},[157])(157)
+});
+
+
diff --git a/mip-ck-course-detail/js/lib/videojs-contrib-hls.js b/mip-ck-course-detail/js/lib/videojs-contrib-hls.js
new file mode 100644
index 00000000..bd358612
--- /dev/null
+++ b/mip-ck-course-detail/js/lib/videojs-contrib-hls.js
@@ -0,0 +1,15422 @@
+/* eslint-disable */
+/**
+ * kkplayer
+ * @version 3.1.0
+ * @copyright 2016
+ * @license
+ */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.videojsContribHls = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0x20 && e < 0x7e) {
+ return String.fromCharCode(e);
+ }
+ return '.';
+};
+
+/**
+ * utils to help dump binary data to the console
+ */
+var utils = {
+ hexDump: function hexDump(data) {
+ var bytes = Array.prototype.slice.call(data);
+ var step = 16;
+ var result = '';
+ var hex = undefined;
+ var ascii = undefined;
+
+ for (var j = 0; j < bytes.length / step; j++) {
+ hex = bytes.slice(j * step, j * step + step).map(formatHexString).join('');
+ ascii = bytes.slice(j * step, j * step + step).map(formatAsciiString).join('');
+ result += hex + ' ' + ascii + '\n';
+ }
+ return result;
+ },
+ tagDump: function tagDump(tag) {
+ return utils.hexDump(tag.bytes);
+ },
+ textRanges: function textRanges(ranges) {
+ var result = '';
+ var i = undefined;
+
+ for (i = 0; i < ranges.length; i++) {
+ result += textRange(ranges, i) + ' ';
+ }
+ return result;
+ },
+ strToBytes: function strToBytes(str) {
+ var bytes = [];
+ for (var i = 0; i < str.length; ++i) {
+ bytes.push(str.charCodeAt(i));
+ }
+ return bytes;
+ },
+ checkCode: function checkCode(cid, code) {
+ if (cid && cid.length > 0) {
+ var uid = (0, _md52['default'])(cid);
+ uid = uid + uid;
+ var ink = code;
+ var res = "";
+ for (var i = 0; i < ink.length;) {
+ var v = parseInt(uid[i / 4], 16);
+ var w = parseInt(ink.substring(i, i + 4), 16);
+ var r = v ^ w;
+ var rs = String.fromCharCode(r);
+ res += rs;
+ i += 4;
+ }
+ return res;
+ }
+ return code;
+ }
+};
+
+exports['default'] = utils;
+module.exports = exports['default'];
+},{"md5":66}],2:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = {
+ GOAL_BUFFER_LENGTH: 30
+};
+module.exports = exports["default"];
+},{}],3:[function(require,module,exports){
+(function (global){
+/**
+ * @file gap-skipper.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _ranges = require('./ranges');
+
+var _ranges2 = _interopRequireDefault(_ranges);
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+// Set of events that reset the gap-skipper logic and clear the timeout
+var timerCancelEvents = ['seeking', 'seeked', 'pause', 'playing', 'error'];
+
+/**
+ * The gap skipper object handles all scenarios
+ * where the player runs into the end of a buffered
+ * region and there is a buffered region ahead.
+ *
+ * It then handles the skipping behavior by setting a
+ * timer to the size (in time) of the gap. This gives
+ * the hls segment fetcher time to close the gap and
+ * resume playing before the timer is triggered and
+ * the gap skipper simply seeks over the gap as a
+ * last resort to resume playback.
+ *
+ * @class GapSkipper
+ */
+
+var GapSkipper = (function () {
+ /**
+ * Represents a GapSKipper object.
+ * @constructor
+ * @param {object} options an object that includes the tech and settings
+ */
+
+ function GapSkipper(options) {
+ var _this = this;
+
+ _classCallCheck(this, GapSkipper);
+
+ this.tech_ = options.tech;
+ this.consecutiveUpdates = 0;
+ this.lastRecordedTime = null;
+ this.timer_ = null;
+
+ if (options.debug) {
+ this.logger_ = _videoJs2['default'].log.bind(_videoJs2['default'], 'gap-skipper ->');
+ }
+ this.logger_('initialize');
+
+ var waitingHandler = function waitingHandler() {
+ return _this.waiting_();
+ };
+ var timeupdateHandler = function timeupdateHandler() {
+ return _this.timeupdate_();
+ };
+ var cancelTimerHandler = function cancelTimerHandler() {
+ return _this.cancelTimer_();
+ };
+
+ this.tech_.on('waiting', waitingHandler);
+ this.tech_.on('timeupdate', timeupdateHandler);
+ this.tech_.on(timerCancelEvents, cancelTimerHandler);
+
+ // Define the dispose function to clean up our events
+ this.dispose = function () {
+ _this.logger_('dispose');
+ _this.tech_.off('waiting', waitingHandler);
+ _this.tech_.off('timeupdate', timeupdateHandler);
+ _this.tech_.off(timerCancelEvents, cancelTimerHandler);
+ _this.cancelTimer_();
+ };
+ }
+
+ /**
+ * Handler for `waiting` events from the player
+ *
+ * @private
+ */
+
+ _createClass(GapSkipper, [{
+ key: 'waiting_',
+ value: function waiting_() {
+ if (!this.tech_.seeking()) {
+ this.setTimer_();
+ }
+ }
+
+ /**
+ * The purpose of this function is to emulate the "waiting" event on
+ * browsers that do not emit it when they are waiting for more
+ * data to continue playback
+ *
+ * @private
+ */
+ }, {
+ key: 'timeupdate_',
+ value: function timeupdate_() {
+ if (this.tech_.paused() || this.tech_.seeking()) {
+ return;
+ }
+
+ var currentTime = this.tech_.currentTime();
+
+ if (this.consecutiveUpdates === 5 && currentTime === this.lastRecordedTime) {
+ this.consecutiveUpdates++;
+ this.waiting_();
+ } else if (currentTime === this.lastRecordedTime) {
+ this.consecutiveUpdates++;
+ } else {
+ this.consecutiveUpdates = 0;
+ this.lastRecordedTime = currentTime;
+ }
+ }
+
+ /**
+ * Cancels any pending timers and resets the 'timeupdate' mechanism
+ * designed to detect that we are stalled
+ *
+ * @private
+ */
+ }, {
+ key: 'cancelTimer_',
+ value: function cancelTimer_() {
+ this.consecutiveUpdates = 0;
+
+ if (this.timer_) {
+ this.logger_('cancelTimer_');
+ clearTimeout(this.timer_);
+ }
+
+ this.timer_ = null;
+ }
+
+ /**
+ * Timer callback. If playback still has not proceeded, then we seek
+ * to the start of the next buffered region.
+ *
+ * @private
+ */
+ }, {
+ key: 'skipTheGap_',
+ value: function skipTheGap_(scheduledCurrentTime) {
+ var buffered = this.tech_.buffered();
+ var currentTime = this.tech_.currentTime();
+ var nextRange = _ranges2['default'].findNextRange(buffered, currentTime);
+
+ this.consecutiveUpdates = 0;
+ this.timer_ = null;
+
+ if (nextRange.length === 0 || currentTime !== scheduledCurrentTime) {
+ return;
+ }
+
+ this.logger_('skipTheGap_:', 'currentTime:', currentTime, 'scheduled currentTime:', scheduledCurrentTime, 'nextRange start:', nextRange.start(0));
+
+ // only seek if we still have not played
+ this.tech_.setCurrentTime(nextRange.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR);
+ }
+
+ /**
+ * Set a timer to skip the unbuffered region.
+ *
+ * @private
+ */
+ }, {
+ key: 'setTimer_',
+ value: function setTimer_() {
+ var buffered = this.tech_.buffered();
+ var currentTime = this.tech_.currentTime();
+ var nextRange = _ranges2['default'].findNextRange(buffered, currentTime);
+
+ if (nextRange.length === 0 || this.timer_ !== null) {
+ return;
+ }
+
+ var difference = nextRange.start(0) - currentTime;
+
+ this.logger_('setTimer_:', 'stopped at:', currentTime, 'setting timer for:', difference, 'seeking to:', nextRange.start(0));
+
+ this.timer_ = setTimeout(this.skipTheGap_.bind(this), difference * 1000, currentTime);
+ }
+
+ /**
+ * A debugging logger noop that is set to console.log only if debugging
+ * is enabled globally
+ *
+ * @private
+ */
+ }, {
+ key: 'logger_',
+ value: function logger_() {}
+ }]);
+
+ return GapSkipper;
+})();
+
+exports['default'] = GapSkipper;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./ranges":8}],4:[function(require,module,exports){
+(function (global){
+/**
+ * @file hls-audio-track.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _playlistLoader = require('./playlist-loader');
+
+var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
+
+/**
+ * HlsAudioTrack extends video.js audio tracks but adds HLS
+ * specific data storage such as playlist loaders, mediaGroups
+ * and default/autoselect
+ *
+ * @param {Object} options options to create HlsAudioTrack with
+ * @class HlsAudioTrack
+ * @extends AudioTrack
+ */
+
+var HlsAudioTrack = (function (_AudioTrack) {
+ _inherits(HlsAudioTrack, _AudioTrack);
+
+ function HlsAudioTrack(options) {
+ _classCallCheck(this, HlsAudioTrack);
+
+ _get(Object.getPrototypeOf(HlsAudioTrack.prototype), 'constructor', this).call(this, {
+ kind: options['default'] ? 'main' : 'alternative',
+ enabled: options['default'] || false,
+ language: options.language,
+ label: options.label
+ });
+
+ this.hls = options.hls;
+ this.autoselect = options.autoselect || false;
+ this['default'] = options['default'] || false;
+ this.withCredentials = options.withCredentials || false;
+ this.mediaGroups_ = [];
+ this.addLoader(options.mediaGroup, options.resolvedUri);
+ }
+
+ /**
+ * get a PlaylistLoader from this track given a mediaGroup name
+ *
+ * @param {String} mediaGroup the mediaGroup to get the loader for
+ * @return {PlaylistLoader|Null} the PlaylistLoader or null
+ */
+
+ _createClass(HlsAudioTrack, [{
+ key: 'getLoader',
+ value: function getLoader(mediaGroup) {
+ for (var i = 0; i < this.mediaGroups_.length; i++) {
+ var mgl = this.mediaGroups_[i];
+
+ if (mgl.mediaGroup === mediaGroup) {
+ return mgl.loader;
+ }
+ }
+ }
+
+ /**
+ * add a PlaylistLoader given a mediaGroup, and a uri. for a combined track
+ * we store null for the playlistloader
+ *
+ * @param {String} mediaGroup the mediaGroup to get the loader for
+ * @param {String} uri the uri to get the audio track/mediaGroup from
+ */
+ }, {
+ key: 'addLoader',
+ value: function addLoader(mediaGroup) {
+ var uri = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+
+ var loader = null;
+
+ if (uri) {
+ // TODO: this should probably happen upstream in Master Playlist
+ // Controller when we can switch PlaylistLoader sources
+ // then we can just store the uri here instead
+ loader = new _playlistLoader2['default'](uri, this.hls, this.withCredentials);
+ }
+ this.mediaGroups_.push({ mediaGroup: mediaGroup, loader: loader });
+ }
+
+ /**
+ * remove a playlist loader from a track given the mediaGroup
+ *
+ * @param {String} mediaGroup the mediaGroup to remove
+ */
+ }, {
+ key: 'removeLoader',
+ value: function removeLoader(mediaGroup) {
+ for (var i = 0; i < this.mediaGroups_.length; i++) {
+ var mgl = this.mediaGroups_[i];
+
+ if (mgl.mediaGroup === mediaGroup) {
+ if (mgl.loader) {
+ mgl.loader.dispose();
+ }
+ this.mediaGroups_.splice(i, 1);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Dispose of this audio track and
+ * the playlist loader that it holds inside
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ var i = this.mediaGroups_.length;
+
+ while (i--) {
+ this.removeLoader(this.mediaGroups_[i].mediaGroup);
+ }
+ }
+ }]);
+
+ return HlsAudioTrack;
+})(_videoJs.AudioTrack);
+
+exports['default'] = HlsAudioTrack;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./playlist-loader":6}],5:[function(require,module,exports){
+(function (global){
+/**
+ * @file master-playlist-controller.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _playlistLoader = require('./playlist-loader');
+
+var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
+
+var _segmentLoader = require('./segment-loader');
+
+var _segmentLoader2 = _interopRequireDefault(_segmentLoader);
+
+var _ranges = require('./ranges');
+
+var _ranges2 = _interopRequireDefault(_ranges);
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _hlsAudioTrack = require('./hls-audio-track');
+
+var _hlsAudioTrack2 = _interopRequireDefault(_hlsAudioTrack);
+
+// 5 minute blacklist
+var BLACKLIST_DURATION = 5 * 60 * 1000;
+var Hls = undefined;
+
+var parseCodecs = function parseCodecs(codecs) {
+ var result = {
+ codecCount: 0,
+ videoCodec: null,
+ audioProfile: null
+ };
+
+ result.codecCount = codecs.split(',').length;
+ result.codecCount = result.codecCount || 2;
+
+ // parse the video codec but ignore the version
+ result.videoCodec = /(^|\s|,)+(avc1)[^ ,]*/i.exec(codecs);
+ result.videoCodec = result.videoCodec && result.videoCodec[2];
+
+ // parse the last field of the audio codec
+ result.audioProfile = /(^|\s|,)+mp4a.\d+\.(\d+)/i.exec(codecs);
+ result.audioProfile = result.audioProfile && result.audioProfile[2];
+
+ return result;
+};
+
+/**
+ * the master playlist controller controller all interactons
+ * between playlists and segmentloaders. At this time this mainly
+ * involves a master playlist and a series of audio playlists
+ * if they are available
+ *
+ * @class MasterPlaylistController
+ * @extends videojs.EventTarget
+ */
+
+var MasterPlaylistController = (function (_videojs$EventTarget) {
+ _inherits(MasterPlaylistController, _videojs$EventTarget);
+
+ function MasterPlaylistController(_ref) {
+ var _this = this;
+
+ var url = _ref.url;
+ var withCredentials = _ref.withCredentials;
+ var mode = _ref.mode;
+ var tech = _ref.tech;
+ var bandwidth = _ref.bandwidth;
+ var externHls = _ref.externHls;
+
+ _classCallCheck(this, MasterPlaylistController);
+
+ _get(Object.getPrototypeOf(MasterPlaylistController.prototype), 'constructor', this).call(this);
+
+ Hls = externHls;
+
+ this.withCredentials = withCredentials;
+ this.tech_ = tech;
+ this.hls_ = tech.hls;
+ this.mode_ = mode;
+ this.audioTracks_ = [];
+
+ this.mediaSource = new _videoJs2['default'].MediaSource({ mode: mode });
+ this.mediaSource.on('audioinfo', function (e) {
+ return _this.trigger(e);
+ });
+ // load the media source into the player
+ this.mediaSource.addEventListener('sourceopen', this.handleSourceOpen_.bind(this));
+
+ var segmentLoaderOptions = {
+ hls: this.hls_,
+ mediaSource: this.mediaSource,
+ currentTime: this.tech_.currentTime.bind(this.tech_),
+ withCredentials: this.withCredentials,
+ seekable: function seekable() {
+ return _this.seekable();
+ },
+ seeking: function seeking() {
+ return _this.tech_.seeking();
+ },
+ setCurrentTime: function setCurrentTime(a) {
+ return _this.tech_.setCurrentTime(a);
+ },
+ hasPlayed: function hasPlayed() {
+ return _this.tech_.played().length !== 0;
+ },
+ bandwidth: bandwidth
+ };
+
+ // combined audio/video or just video when alternate audio track is selected
+ this.mainSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions);
+ // alternate audio track
+ this.audioSegmentLoader_ = new _segmentLoader2['default'](segmentLoaderOptions);
+
+ if (!url) {
+ throw new Error('A non-empty playlist URL is required');
+ }
+
+ this.masterPlaylistLoader_ = new _playlistLoader2['default'](url, this.hls_, this.withCredentials);
+
+ this.masterPlaylistLoader_.on('loadedmetadata', function () {
+ var media = _this.masterPlaylistLoader_.media();
+
+ // if this isn't a live video and preload permits, start
+ // downloading segments
+ if (media.endList && _this.tech_.preload() !== 'none') {
+ _this.mainSegmentLoader_.playlist(media);
+ _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_);
+ _this.mainSegmentLoader_.load();
+ }
+
+ _this.setupSourceBuffer_();
+ _this.setupFirstPlay();
+ _this.useAudio();
+ });
+
+ this.masterPlaylistLoader_.on('loadedplaylist', function () {
+ var updatedPlaylist = _this.masterPlaylistLoader_.media();
+ var seekable = undefined;
+
+ if (!updatedPlaylist) {
+ // select the initial variant
+ _this.initialMedia_ = _this.selectPlaylist();
+ _this.masterPlaylistLoader_.media(_this.initialMedia_);
+ _this.fillAudioTracks_();
+
+ _this.trigger('selectedinitialmedia');
+ return;
+ }
+
+ // TODO: Create a new event on the PlaylistLoader that signals
+ // that the segments have changed in some way and use that to
+ // update the SegmentLoader instead of doing it twice here and
+ // on `mediachange`
+ _this.mainSegmentLoader_.playlist(updatedPlaylist);
+ _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_);
+ _this.updateDuration();
+
+ // update seekable
+ seekable = _this.seekable();
+ if (!updatedPlaylist.endList && seekable.length !== 0) {
+ _this.mediaSource.addSeekableRange_(seekable.start(0), seekable.end(0));
+ }
+ });
+
+ this.masterPlaylistLoader_.on('error', function () {
+ _this.blacklistCurrentPlaylist(_this.masterPlaylistLoader_.error);
+ });
+
+ this.masterPlaylistLoader_.on('mediachanging', function () {
+ _this.mainSegmentLoader_.pause();
+ });
+
+ this.masterPlaylistLoader_.on('mediachange', function () {
+ var media = _this.masterPlaylistLoader_.media();
+
+ _this.mainSegmentLoader_.abort();
+
+ // TODO: Create a new event on the PlaylistLoader that signals
+ // that the segments have changed in some way and use that to
+ // update the SegmentLoader instead of doing it twice here and
+ // on `loadedplaylist`
+ _this.mainSegmentLoader_.playlist(media);
+ _this.mainSegmentLoader_.expired(_this.masterPlaylistLoader_.expired_);
+ _this.mainSegmentLoader_.load();
+
+ _this.tech_.trigger({
+ type: 'mediachange',
+ bubbles: true
+ });
+ });
+
+ this.mainSegmentLoader_.on('progress', function () {
+ // figure out what stream the next segment should be downloaded from
+ // with the updated bandwidth information
+ _this.masterPlaylistLoader_.media(_this.selectPlaylist());
+
+ _this.trigger('progress');
+ });
+
+ this.mainSegmentLoader_.on('error', function () {
+ _this.blacklistCurrentPlaylist(_this.mainSegmentLoader_.error());
+ });
+
+ this.audioSegmentLoader_.on('error', function () {
+ _videoJs2['default'].log.warn('Problem encountered with the current alternate audio track' + '. Switching back to default.');
+ _this.audioSegmentLoader_.abort();
+ _this.audioPlaylistLoader_ = null;
+ _this.useAudio();
+ });
+
+ this.masterPlaylistLoader_.load();
+ }
+
+ /**
+ * get the total number of media requests from the `audiosegmentloader_`
+ * and the `mainSegmentLoader_`
+ *
+ * @private
+ */
+
+ _createClass(MasterPlaylistController, [{
+ key: 'mediaRequests_',
+ value: function mediaRequests_() {
+ return this.audioSegmentLoader_.mediaRequests + this.mainSegmentLoader_.mediaRequests;
+ }
+
+ /**
+ * get the total time that media requests have spent trnasfering
+ * from the `audiosegmentloader_` and the `mainSegmentLoader_`
+ *
+ * @private
+ */
+ }, {
+ key: 'mediaTransferDuration_',
+ value: function mediaTransferDuration_() {
+ return this.audioSegmentLoader_.mediaTransferDuration + this.mainSegmentLoader_.mediaTransferDuration;
+ }
+
+ /**
+ * get the total number of bytes transfered during media requests
+ * from the `audiosegmentloader_` and the `mainSegmentLoader_`
+ *
+ * @private
+ */
+ }, {
+ key: 'mediaBytesTransferred_',
+ value: function mediaBytesTransferred_() {
+ return this.audioSegmentLoader_.mediaBytesTransferred + this.mainSegmentLoader_.mediaBytesTransferred;
+ }
+
+ /**
+ * fill our internal list of HlsAudioTracks with data from
+ * the master playlist or use a default
+ *
+ * @private
+ */
+ }, {
+ key: 'fillAudioTracks_',
+ value: function fillAudioTracks_() {
+ var master = this.master();
+ var mediaGroups = master.mediaGroups || {};
+
+ // force a default if we have none or we are not
+ // in html5 mode (the only mode to support more than one
+ // audio track)
+ if (!mediaGroups || !mediaGroups.AUDIO || Object.keys(mediaGroups.AUDIO).length === 0 || this.mode_ !== 'html5') {
+ // "main" audio group, track name "default"
+ mediaGroups.AUDIO = { main: { 'default': { 'default': true } } };
+ }
+
+ var tracks = {};
+
+ for (var mediaGroup in mediaGroups.AUDIO) {
+ for (var label in mediaGroups.AUDIO[mediaGroup]) {
+ var properties = mediaGroups.AUDIO[mediaGroup][label];
+
+ // if the track already exists add a new "location"
+ // since tracks in different mediaGroups are actually the same
+ // track with different locations to download them from
+ if (tracks[label]) {
+ tracks[label].addLoader(mediaGroup, properties.resolvedUri);
+ continue;
+ }
+
+ var track = new _hlsAudioTrack2['default'](_videoJs2['default'].mergeOptions(properties, {
+ hls: this.hls_,
+ withCredentials: this.withCredential,
+ mediaGroup: mediaGroup,
+ label: label
+ }));
+
+ tracks[label] = track;
+ this.audioTracks_.push(track);
+ }
+ }
+ }
+
+ /**
+ * Call load on our SegmentLoaders
+ */
+ }, {
+ key: 'load',
+ value: function load() {
+ this.mainSegmentLoader_.load();
+ if (this.audioPlaylistLoader_) {
+ this.audioSegmentLoader_.load();
+ }
+ }
+
+ /**
+ * Get the current active Media Group for Audio
+ * given the selected playlist and its attributes
+ */
+ }, {
+ key: 'activeAudioGroup',
+ value: function activeAudioGroup() {
+ var media = this.masterPlaylistLoader_.media();
+ var mediaGroup = 'main';
+
+ if (media && media.attributes && media.attributes.AUDIO) {
+ mediaGroup = media.attributes.AUDIO;
+ }
+
+ return mediaGroup;
+ }
+
+ /**
+ * Use any audio track that we have, and start to load it
+ */
+ }, {
+ key: 'useAudio',
+ value: function useAudio() {
+ var _this2 = this;
+
+ var track = undefined;
+
+ this.audioTracks_.forEach(function (t) {
+ if (!track && t.enabled) {
+ track = t;
+ }
+ });
+
+ // called too early or no track is enabled
+ if (!track) {
+ return;
+ }
+
+ // Pause any alternative audio
+ if (this.audioPlaylistLoader_) {
+ this.audioPlaylistLoader_.pause();
+ this.audioPlaylistLoader_ = null;
+ this.audioSegmentLoader_.pause();
+ }
+
+ // If the audio track for the active audio group has
+ // a playlist loader than it is an alterative audio track
+ // otherwise it is a part of the mainSegmenLoader
+ var loader = track.getLoader(this.activeAudioGroup());
+
+ if (!loader) {
+ this.mainSegmentLoader_.clearBuffer();
+ return;
+ }
+
+ // TODO: it may be better to create the playlist loader here
+ // when we can change an audioPlaylistLoaders src
+ this.audioPlaylistLoader_ = loader;
+
+ if (this.audioPlaylistLoader_.started) {
+ this.audioPlaylistLoader_.load();
+ this.audioSegmentLoader_.load();
+ this.audioSegmentLoader_.clearBuffer();
+ return;
+ }
+
+ this.audioPlaylistLoader_.on('loadedmetadata', function () {
+ /* eslint-disable no-shadow */
+ var media = _this2.audioPlaylistLoader_.media();
+ /* eslint-enable no-shadow */
+
+ _this2.audioSegmentLoader_.playlist(media);
+ _this2.addMimeType_(_this2.audioSegmentLoader_, 'mp4a.40.2', media);
+
+ // if the video is already playing, or if this isn't a live video and preload
+ // permits, start downloading segments
+ if (!_this2.tech_.paused() || media.endList && _this2.tech_.preload() !== 'none') {
+ _this2.audioSegmentLoader_.load();
+ }
+
+ if (!media.endList) {
+ // trigger the playlist loader to start "expired time"-tracking
+ _this2.audioPlaylistLoader_.trigger('firstplay');
+ }
+ });
+
+ this.audioPlaylistLoader_.on('loadedplaylist', function () {
+ var updatedPlaylist = undefined;
+
+ if (_this2.audioPlaylistLoader_) {
+ updatedPlaylist = _this2.audioPlaylistLoader_.media();
+ }
+
+ if (!updatedPlaylist) {
+ // only one playlist to select
+ _this2.audioPlaylistLoader_.media(_this2.audioPlaylistLoader_.playlists.master.playlists[0]);
+ return;
+ }
+
+ _this2.audioSegmentLoader_.playlist(updatedPlaylist);
+ });
+
+ this.audioPlaylistLoader_.on('error', function () {
+ _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track' + '. Switching back to default.');
+ _this2.audioSegmentLoader_.abort();
+ _this2.audioPlaylistLoader_ = null;
+ _this2.useAudio();
+ });
+
+ this.audioSegmentLoader_.clearBuffer();
+ this.audioPlaylistLoader_.start();
+ }
+
+ /**
+ * Re-tune playback quality level for the current player
+ * conditions. This method may perform destructive actions, like
+ * removing already buffered content, to readjust the currently
+ * active playlist quickly.
+ *
+ * @private
+ */
+ }, {
+ key: 'fastQualityChange_',
+ value: function fastQualityChange_() {
+ var media = this.selectPlaylist();
+
+ if (media !== this.masterPlaylistLoader_.media()) {
+ this.masterPlaylistLoader_.media(media);
+ this.mainSegmentLoader_.sourceUpdater_.remove(this.tech_.currentTime() + 5, Infinity);
+ }
+ }
+
+ /**
+ * Begin playback.
+ */
+ }, {
+ key: 'play',
+ value: function play() {
+ if (this.setupFirstPlay()) {
+ return;
+ }
+
+ if (this.tech_.ended()) {
+ this.tech_.setCurrentTime(0);
+ }
+
+ this.load();
+
+ // if the viewer has paused and we fell out of the live window,
+ // seek forward to the earliest available position
+ if (this.tech_.duration() === Infinity) {
+ if (this.tech_.currentTime() < this.tech_.seekable().start(0)) {
+ return this.tech_.setCurrentTime(this.tech_.seekable().start(0));
+ }
+ }
+ }
+
+ /**
+ * Seek to the latest media position if this is a live video and the
+ * player and video are loaded and initialized.
+ */
+ }, {
+ key: 'setupFirstPlay',
+ value: function setupFirstPlay() {
+ var seekable = undefined;
+ var media = this.masterPlaylistLoader_.media();
+
+ // check that everything is ready to begin buffering
+ // 1) the active media playlist is available
+ if (media &&
+ // 2) the video is a live stream
+ !media.endList &&
+
+ // 3) the player is not paused
+ !this.tech_.paused() &&
+
+ // 4) the player has not started playing
+ !this.hasPlayed_) {
+
+ this.load();
+
+ // trigger the playlist loader to start "expired time"-tracking
+ this.masterPlaylistLoader_.trigger('firstplay');
+ this.hasPlayed_ = true;
+
+ // seek to the latest media position for live videos
+ seekable = this.seekable();
+ if (seekable.length) {
+ this.tech_.setCurrentTime(seekable.end(0));
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * handle the sourceopen event on the MediaSource
+ *
+ * @private
+ */
+ }, {
+ key: 'handleSourceOpen_',
+ value: function handleSourceOpen_() {
+ // Only attempt to create the source buffer if none already exist.
+ // handleSourceOpen is also called when we are "re-opening" a source buffer
+ // after `endOfStream` has been called (in response to a seek for instance)
+ this.setupSourceBuffer_();
+
+ // if autoplay is enabled, begin playback. This is duplicative of
+ // code in video.js but is required because play() must be invoked
+ // *after* the media source has opened.
+ if (this.tech_.autoplay()) {
+ this.tech_.play();
+ }
+
+ this.trigger('sourceopen');
+ }
+
+ /**
+ * Blacklists a playlist when an error occurs for a set amount of time
+ * making it unavailable for selection by the rendition selection algorithm
+ * and then forces a new playlist (rendition) selection.
+ *
+ * @param {Object=} error an optional error that may include the playlist
+ * to blacklist
+ */
+ }, {
+ key: 'blacklistCurrentPlaylist',
+ value: function blacklistCurrentPlaylist() {
+ var error = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+ var currentPlaylist = undefined;
+ var nextPlaylist = undefined;
+
+ // If the `error` was generated by the playlist loader, it will contain
+ // the playlist we were trying to load (but failed) and that should be
+ // blacklisted instead of the currently selected playlist which is likely
+ // out-of-date in this scenario
+ currentPlaylist = error.playlist || this.masterPlaylistLoader_.media();
+
+ // If there is no current playlist, then an error occurred while we were
+ // trying to load the master OR while we were disposing of the tech
+ if (!currentPlaylist) {
+ this.error = error;
+ return this.mediaSource.endOfStream('network');
+ }
+
+ // Blacklist this playlist
+ currentPlaylist.excludeUntil = Date.now() + BLACKLIST_DURATION;
+
+ // Select a new playlist
+ nextPlaylist = this.selectPlaylist();
+
+ if (nextPlaylist) {
+ _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. Switching to another playlist.');
+
+ return this.masterPlaylistLoader_.media(nextPlaylist);
+ }
+ _videoJs2['default'].log.warn('Problem encountered with the current ' + 'HLS playlist. No suitable alternatives found.');
+ // We have no more playlists we can select so we must fail
+ this.error = error;
+ return this.mediaSource.endOfStream('network');
+ }
+
+ /**
+ * Pause all segment loaders
+ */
+ }, {
+ key: 'pauseLoading',
+ value: function pauseLoading() {
+ this.mainSegmentLoader_.pause();
+ if (this.audioPlaylistLoader_) {
+ this.audioSegmentLoader_.pause();
+ }
+ }
+
+ /**
+ * set the current time on all segment loaders
+ *
+ * @param {TimeRange} currentTime the current time to set
+ * @return {TimeRange} the current time
+ */
+ }, {
+ key: 'setCurrentTime',
+ value: function setCurrentTime(currentTime) {
+ var buffered = _ranges2['default'].findRange(this.tech_.buffered(), currentTime);
+
+ if (!(this.masterPlaylistLoader_ && this.masterPlaylistLoader_.media())) {
+ // return immediately if the metadata is not ready yet
+ return 0;
+ }
+
+ // it's clearly an edge-case but don't thrown an error if asked to
+ // seek within an empty playlist
+ if (!this.masterPlaylistLoader_.media().segments) {
+ return 0;
+ }
+
+ // if the seek location is already buffered, continue buffering as
+ // usual
+ if (buffered && buffered.length) {
+ return currentTime;
+ }
+
+ // cancel outstanding requests so we begin buffering at the new
+ // location
+ this.mainSegmentLoader_.abort();
+ if (this.audioPlaylistLoader_) {
+ this.audioSegmentLoader_.abort();
+ }
+
+ if (!this.tech_.paused()) {
+ this.mainSegmentLoader_.load();
+ if (this.audioPlaylistLoader_) {
+ this.audioSegmentLoader_.load();
+ }
+ }
+ }
+
+ /**
+ * get the current duration
+ *
+ * @return {TimeRange} the duration
+ */
+ }, {
+ key: 'duration',
+ value: function duration() {
+ if (!this.masterPlaylistLoader_) {
+ return 0;
+ }
+
+ if (this.mediaSource) {
+ return this.mediaSource.duration;
+ }
+
+ return Hls.Playlist.duration(this.masterPlaylistLoader_.media());
+ }
+
+ /**
+ * check the seekable range
+ *
+ * @return {TimeRange} the seekable range
+ */
+ }, {
+ key: 'seekable',
+ value: function seekable() {
+ var media = undefined;
+ var mainSeekable = undefined;
+ var audioSeekable = undefined;
+
+ if (!this.masterPlaylistLoader_) {
+ return _videoJs2['default'].createTimeRanges();
+ }
+ media = this.masterPlaylistLoader_.media();
+ if (!media) {
+ return _videoJs2['default'].createTimeRanges();
+ }
+
+ mainSeekable = Hls.Playlist.seekable(media, this.masterPlaylistLoader_.expired_);
+ if (mainSeekable.length === 0) {
+ return mainSeekable;
+ }
+
+ if (this.audioPlaylistLoader_) {
+ audioSeekable = Hls.Playlist.seekable(this.audioPlaylistLoader_.media(), this.audioPlaylistLoader_.expired_);
+ if (audioSeekable.length === 0) {
+ return audioSeekable;
+ }
+ }
+
+ if (!audioSeekable) {
+ // seekable has been calculated based on buffering video data so it
+ // can be returned directly
+ return mainSeekable;
+ }
+
+ return _videoJs2['default'].createTimeRanges([[audioSeekable.start(0) > mainSeekable.start(0) ? audioSeekable.start(0) : mainSeekable.start(0), audioSeekable.end(0) < mainSeekable.end(0) ? audioSeekable.end(0) : mainSeekable.end(0)]]);
+ }
+
+ /**
+ * Update the player duration
+ */
+ }, {
+ key: 'updateDuration',
+ value: function updateDuration() {
+ var _this3 = this;
+
+ var oldDuration = this.mediaSource.duration;
+ var newDuration = Hls.Playlist.duration(this.masterPlaylistLoader_.media());
+ var buffered = this.tech_.buffered();
+ var setDuration = function setDuration() {
+ _this3.mediaSource.duration = newDuration;
+ _this3.tech_.trigger('durationchange');
+
+ _this3.mediaSource.removeEventListener('sourceopen', setDuration);
+ };
+
+ if (buffered.length > 0) {
+ newDuration = Math.max(newDuration, buffered.end(buffered.length - 1));
+ }
+
+ // if the duration has changed, invalidate the cached value
+ if (oldDuration !== newDuration) {
+ // update the duration
+ if (this.mediaSource.readyState !== 'open') {
+ this.mediaSource.addEventListener('sourceopen', setDuration);
+ } else {
+ setDuration();
+ }
+ }
+ }
+
+ /**
+ * dispose of the MasterPlaylistController and everything
+ * that it controls
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ this.masterPlaylistLoader_.dispose();
+ this.audioTracks_.forEach(function (track) {
+ track.dispose();
+ });
+ this.audioTracks_.length = 0;
+ this.mainSegmentLoader_.dispose();
+ this.audioSegmentLoader_.dispose();
+ }
+
+ /**
+ * return the master playlist object if we have one
+ *
+ * @return {Object} the master playlist object that we parsed
+ */
+ }, {
+ key: 'master',
+ value: function master() {
+ return this.masterPlaylistLoader_.master;
+ }
+
+ /**
+ * return the currently selected playlist
+ *
+ * @return {Object} the currently selected playlist object that we parsed
+ */
+ }, {
+ key: 'media',
+ value: function media() {
+ // playlist loader will not return media if it has not been fully loaded
+ return this.masterPlaylistLoader_.media() || this.initialMedia_;
+ }
+
+ /**
+ * setup our internal source buffers on our segment Loaders
+ *
+ * @private
+ */
+ }, {
+ key: 'setupSourceBuffer_',
+ value: function setupSourceBuffer_() {
+ var media = this.masterPlaylistLoader_.media();
+
+ // wait until a media playlist is available and the Media Source is
+ // attached
+ if (!media || this.mediaSource.readyState !== 'open') {
+ return;
+ }
+
+ this.addMimeType_(this.mainSegmentLoader_, 'avc1.4d400d, mp4a.40.2', media);
+
+ // exclude any incompatible variant streams from future playlist
+ // selection
+ this.excludeIncompatibleVariants_(media);
+ }
+
+ /**
+ * add a time type to a segmentLoader
+ *
+ * @param {SegmentLoader} segmentLoader the segmentloader to work on
+ * @param {String} codecs to use by default
+ * @param {Object} the parsed media object
+ * @private
+ */
+ }, {
+ key: 'addMimeType_',
+ value: function addMimeType_(segmentLoader, defaultCodecs, media) {
+ var mimeType = 'video/mp2t';
+
+ // if the codecs were explicitly specified, pass them along to the
+ // source buffer
+ if (media.attributes && media.attributes.CODECS) {
+ mimeType += '; codecs="' + media.attributes.CODECS + '"';
+ } else {
+ mimeType += '; codecs="' + defaultCodecs + '"';
+ }
+ segmentLoader.mimeType(mimeType);
+ }
+
+ /**
+ * Blacklist playlists that are known to be codec or
+ * stream-incompatible with the SourceBuffer configuration. For
+ * instance, Media Source Extensions would cause the video element to
+ * stall waiting for video data if you switched from a variant with
+ * video and audio to an audio-only one.
+ *
+ * @param {Object} media a media playlist compatible with the current
+ * set of SourceBuffers. Variants in the current master playlist that
+ * do not appear to have compatible codec or stream configurations
+ * will be excluded from the default playlist selection algorithm
+ * indefinitely.
+ * @private
+ */
+ }, {
+ key: 'excludeIncompatibleVariants_',
+ value: function excludeIncompatibleVariants_(media) {
+ var master = this.masterPlaylistLoader_.master;
+ var codecCount = 2;
+ var videoCodec = null;
+ var audioProfile = null;
+ var codecs = undefined;
+
+ if (media.attributes && media.attributes.CODECS) {
+ codecs = parseCodecs(media.attributes.CODECS);
+ videoCodec = codecs.videoCodec;
+ audioProfile = codecs.audioProfile;
+ codecCount = codecs.codecCount;
+ }
+ master.playlists.forEach(function (variant) {
+ var variantCodecs = {
+ codecCount: 2,
+ videoCodec: null,
+ audioProfile: null
+ };
+
+ if (variant.attributes && variant.attributes.CODECS) {
+ variantCodecs = parseCodecs(variant.attributes.CODECS);
+ }
+
+ // if the streams differ in the presence or absence of audio or
+ // video, they are incompatible
+ if (variantCodecs.codecCount !== codecCount) {
+ variant.excludeUntil = Infinity;
+ }
+
+ // if h.264 is specified on the current playlist, some flavor of
+ // it must be specified on all compatible variants
+ if (variantCodecs.videoCodec !== videoCodec) {
+ variant.excludeUntil = Infinity;
+ }
+ // HE-AAC ("mp4a.40.5") is incompatible with all other versions of
+ // AAC audio in Chrome 46. Don't mix the two.
+ if (variantCodecs.audioProfile === '5' && audioProfile !== '5' || audioProfile === '5' && variantCodecs.audioProfile !== '5') {
+ variant.excludeUntil = Infinity;
+ }
+ });
+ }
+ }]);
+
+ return MasterPlaylistController;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = MasterPlaylistController;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./hls-audio-track":4,"./playlist-loader":6,"./ranges":8,"./segment-loader":11}],6:[function(require,module,exports){
+(function (global){
+/**
+ * @file playlist-loader.js
+ *
+ * A state machine that manages the loading, caching, and updating of
+ * M3U8 playlists.
+ *
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _resolveUrl = require('./resolve-url');
+
+var _resolveUrl2 = _interopRequireDefault(_resolveUrl);
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _stream = require('./stream');
+
+var _stream2 = _interopRequireDefault(_stream);
+
+var _m3u8Parser = require('m3u8-parser');
+
+var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser);
+
+/**
+ * Returns a new array of segments that is the result of merging
+ * properties from an older list of segments onto an updated
+ * list. No properties on the updated playlist will be overridden.
+ *
+ * @param {Array} original the outdated list of segments
+ * @param {Array} update the updated list of segments
+ * @param {Number=} offset the index of the first update
+ * segment in the original segment list. For non-live playlists,
+ * this should always be zero and does not need to be
+ * specified. For live playlists, it should be the difference
+ * between the media sequence numbers in the original and updated
+ * playlists.
+ * @return a list of merged segment objects
+ */
+var updateSegments = function updateSegments(original, update, offset) {
+ var result = update.slice();
+ var length = undefined;
+ var i = undefined;
+
+ offset = offset || 0;
+ length = Math.min(original.length, update.length + offset);
+
+ for (i = offset; i < length; i++) {
+ result[i - offset] = (0, _videoJs.mergeOptions)(original[i], result[i - offset]);
+ }
+ return result;
+};
+
+/**
+ * Returns a new master playlist that is the result of merging an
+ * updated media playlist into the original version. If the
+ * updated media playlist does not match any of the playlist
+ * entries in the original master playlist, null is returned.
+ *
+ * @param {Object} master a parsed master M3U8 object
+ * @param {Object} media a parsed media M3U8 object
+ * @return {Object} a new object that represents the original
+ * master playlist with the updated media playlist merged in, or
+ * null if the merge produced no change.
+ */
+var updateMaster = function updateMaster(master, media) {
+ var changed = false;
+ var result = (0, _videoJs.mergeOptions)(master, {});
+ var i = master.playlists.length;
+ var playlist = undefined;
+ var segment = undefined;
+ var j = undefined;
+
+ while (i--) {
+ playlist = result.playlists[i];
+ if (playlist.uri === media.uri) {
+ // consider the playlist unchanged if the number of segments
+ // are equal and the media sequence number is unchanged
+ if (playlist.segments && media.segments && playlist.segments.length === media.segments.length && playlist.mediaSequence === media.mediaSequence) {
+ continue;
+ }
+
+ result.playlists[i] = (0, _videoJs.mergeOptions)(playlist, media);
+ result.playlists[media.uri] = result.playlists[i];
+
+ // if the update could overlap existing segment information,
+ // merge the two lists
+ if (playlist.segments) {
+ result.playlists[i].segments = updateSegments(playlist.segments, media.segments, media.mediaSequence - playlist.mediaSequence);
+ }
+ // resolve any missing segment and key URIs
+ j = 0;
+ if (result.playlists[i].segments) {
+ j = result.playlists[i].segments.length;
+ }
+ while (j--) {
+ segment = result.playlists[i].segments[j];
+ if (!segment.resolvedUri) {
+ segment.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.uri);
+ }
+ if (segment.key && !segment.key.resolvedUri) {
+ segment.key.resolvedUri = (0, _resolveUrl2['default'])(playlist.resolvedUri, segment.key.uri);
+ }
+ }
+ changed = true;
+ }
+ }
+ return changed ? result : null;
+};
+
+/**
+ * Load a playlist from a remote loacation
+ *
+ * @class PlaylistLoader
+ * @extends Stream
+ * @param {String} srcUrl the url to start with
+ * @param {Boolean} withCredentials the withCredentials xhr option
+ * @constructor
+ */
+var PlaylistLoader = function PlaylistLoader(srcUrl, hls, withCredentials) {
+ var _this = this;
+
+ /* eslint-disable consistent-this */
+ var loader = this;
+ /* eslint-enable consistent-this */
+ var dispose = undefined;
+ var mediaUpdateTimeout = undefined;
+ var request = undefined;
+ var playlistRequestError = undefined;
+ var haveMetadata = undefined;
+
+ PlaylistLoader.prototype.constructor.call(this);
+
+ this.hls_ = hls;
+
+ // a flag that disables "expired time"-tracking this setting has
+ // no effect when not playing a live stream
+ this.trackExpiredTime_ = false;
+
+ if (!srcUrl) {
+ throw new Error('A non-empty playlist URL is required');
+ }
+
+ playlistRequestError = function (xhr, url, startingState) {
+ loader.setBandwidth(request || xhr);
+
+ // any in-flight request is now finished
+ request = null;
+
+ if (startingState) {
+ loader.state = startingState;
+ }
+
+ loader.error = {
+ playlist: loader.master.playlists[url],
+ status: xhr.status,
+ message: 'HLS playlist request error at URL: ' + url,
+ responseText: xhr.responseText,
+ code: xhr.status >= 500 ? 4 : 2
+ };
+
+ loader.trigger('error');
+ };
+
+ // update the playlist loader's state in response to a new or
+ // updated playlist.
+ haveMetadata = function (xhr, url) {
+ var parser = undefined;
+ var refreshDelay = undefined;
+ var update = undefined;
+
+ loader.setBandwidth(request || xhr);
+
+ // any in-flight request is now finished
+ request = null;
+
+ loader.state = 'HAVE_METADATA';
+
+ parser = new _m3u8Parser2['default'].Parser();
+ parser.push(xhr.responseText);
+ parser.end();
+ parser.manifest.uri = url;
+
+ // merge this playlist into the master
+ update = updateMaster(loader.master, parser.manifest);
+ refreshDelay = (parser.manifest.targetDuration || 10) * 1000;
+ if (update) {
+ loader.master = update;
+ loader.updateMediaPlaylist_(parser.manifest);
+ } else {
+ // if the playlist is unchanged since the last reload,
+ // try again after half the target duration
+ refreshDelay /= 2;
+ }
+
+ // refresh live playlists after a target duration passes
+ if (!loader.media().endList) {
+ window.clearTimeout(mediaUpdateTimeout);
+ mediaUpdateTimeout = window.setTimeout(function () {
+ loader.trigger('mediaupdatetimeout');
+ }, refreshDelay);
+ }
+
+ loader.trigger('loadedplaylist');
+ };
+
+ // initialize the loader state
+ loader.state = 'HAVE_NOTHING';
+
+ // track the time that has expired from the live window
+ // this allows the seekable start range to be calculated even if
+ // all segments with timing information have expired
+ this.expired_ = 0;
+
+ // capture the prototype dispose function
+ dispose = this.dispose;
+
+ /**
+ * Abort any outstanding work and clean up.
+ */
+ loader.dispose = function () {
+ loader.stopRequest();
+ window.clearTimeout(mediaUpdateTimeout);
+ dispose.call(this);
+ };
+
+ loader.stopRequest = function () {
+ if (request) {
+ var oldRequest = request;
+
+ request = null;
+ oldRequest.onreadystatechange = null;
+ oldRequest.abort();
+ }
+ };
+
+ /**
+ * When called without any arguments, returns the currently
+ * active media playlist. When called with a single argument,
+ * triggers the playlist loader to asynchronously switch to the
+ * specified media playlist. Calling this method while the
+ * loader is in the HAVE_NOTHING causes an error to be emitted
+ * but otherwise has no effect.
+ *
+ * @param {Object=} playlis tthe parsed media playlist
+ * object to switch to
+ * @return {Playlist} the current loaded media
+ */
+ loader.media = function (playlist) {
+ var startingState = loader.state;
+ var mediaChange = undefined;
+
+ // getter
+ if (!playlist) {
+ return loader.media_;
+ }
+
+ // setter
+ if (loader.state === 'HAVE_NOTHING') {
+ throw new Error('Cannot switch media playlist from ' + loader.state);
+ }
+
+ // find the playlist object if the target playlist has been
+ // specified by URI
+ if (typeof playlist === 'string') {
+ if (!loader.master.playlists[playlist]) {
+ throw new Error('Unknown playlist URI: ' + playlist);
+ }
+ playlist = loader.master.playlists[playlist];
+ }
+
+ mediaChange = !loader.media_ || playlist.uri !== loader.media_.uri;
+
+ // switch to fully loaded playlists immediately
+ if (loader.master.playlists[playlist.uri].endList) {
+ // abort outstanding playlist requests
+ if (request) {
+ request.onreadystatechange = null;
+ request.abort();
+ request = null;
+ }
+ loader.state = 'HAVE_METADATA';
+ loader.media_ = playlist;
+
+ // trigger media change if the active media has been updated
+ if (mediaChange) {
+ loader.trigger('mediachanging');
+ loader.trigger('mediachange');
+ }
+ return;
+ }
+
+ // switching to the active playlist is a no-op
+ if (!mediaChange) {
+ return;
+ }
+
+ loader.state = 'SWITCHING_MEDIA';
+
+ // there is already an outstanding playlist request
+ if (request) {
+ if ((0, _resolveUrl2['default'])(loader.master.uri, playlist.uri) === request.url) {
+ // requesting to switch to the same playlist multiple times
+ // has no effect after the first
+ return;
+ }
+ request.onreadystatechange = null;
+ request.abort();
+ request = null;
+ }
+
+ // request the new playlist
+ if (this.media_) {
+ this.trigger('mediachanging');
+ }
+ request = this.hls_.xhr({
+ uri: (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri),
+ withCredentials: withCredentials
+ }, function (error, req) {
+ // disposed
+ if (!request) {
+ return;
+ }
+
+ if (error) {
+ return playlistRequestError(request, playlist.uri, startingState);
+ }
+
+ haveMetadata(req, playlist.uri);
+
+ // fire loadedmetadata the first time a media playlist is loaded
+ if (startingState === 'HAVE_MASTER') {
+ loader.trigger('loadedmetadata');
+ } else {
+ loader.trigger('mediachange');
+ }
+ });
+ };
+
+ /**
+ * set the bandwidth on an xhr to the bandwidth on the playlist
+ */
+ loader.setBandwidth = function (xhr) {
+ loader.bandwidth = xhr.bandwidth;
+ };
+
+ // In a live playlist, don't keep track of the expired time
+ // until HLS tells us that "first play" has commenced
+ loader.on('firstplay', function () {
+ this.trackExpiredTime_ = true;
+ });
+
+ // live playlist staleness timeout
+ loader.on('mediaupdatetimeout', function () {
+ if (loader.state !== 'HAVE_METADATA') {
+ // only refresh the media playlist if no other activity is going on
+ return;
+ }
+
+ loader.state = 'HAVE_CURRENT_METADATA';
+ request = this.hls_.xhr({
+ uri: (0, _resolveUrl2['default'])(loader.master.uri, loader.media().uri),
+ withCredentials: withCredentials
+ }, function (error, req) {
+ // disposed
+ if (!request) {
+ return;
+ }
+
+ if (error) {
+ return playlistRequestError(request, loader.media().uri);
+ }
+ haveMetadata(request, loader.media().uri);
+ });
+ });
+
+ /**
+ * pause loading of the playlist
+ */
+ loader.pause = function () {
+ loader.stopRequest();
+ window.clearTimeout(mediaUpdateTimeout);
+ };
+
+ /**
+ * start loading of the playlist
+ */
+ loader.load = function () {
+ if (loader.started) {
+ if (!loader.media().endList) {
+ loader.trigger('mediaupdatetimeout');
+ } else {
+ loader.trigger('loadedplaylist');
+ }
+ } else {
+ loader.start();
+ }
+ };
+
+ /**
+ * start loading of the playlist
+ */
+ loader.start = function () {
+ loader.started = true;
+
+ // request the specified URL
+ request = _this.hls_.xhr({
+ uri: srcUrl,
+ withCredentials: withCredentials
+ }, function (error, req) {
+ var parser = undefined;
+ var playlist = undefined;
+ var i = undefined;
+
+ // disposed
+ if (!request) {
+ return;
+ }
+
+ // clear the loader's request reference
+ request = null;
+
+ if (error) {
+ loader.error = {
+ status: req.status,
+ message: 'HLS playlist request error at URL: ' + srcUrl,
+ responseText: req.responseText,
+ // MEDIA_ERR_NETWORK
+ code: 2
+ };
+ return loader.trigger('error');
+ }
+
+ parser = new _m3u8Parser2['default'].Parser();
+ parser.push(req.responseText);
+ parser.end();
+
+ loader.state = 'HAVE_MASTER';
+
+ parser.manifest.uri = srcUrl;
+
+ // loaded a master playlist
+ if (parser.manifest.playlists) {
+ loader.master = parser.manifest;
+
+ // setup by-URI lookups and resolve media playlist URIs
+ i = loader.master.playlists.length;
+ while (i--) {
+ playlist = loader.master.playlists[i];
+ loader.master.playlists[playlist.uri] = playlist;
+ playlist.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, playlist.uri);
+ }
+
+ // resolve any media group URIs
+ for (var groupKey in loader.master.mediaGroups.AUDIO) {
+ for (var labelKey in loader.master.mediaGroups.AUDIO[groupKey]) {
+ var alternateAudio = loader.master.mediaGroups.AUDIO[groupKey][labelKey];
+
+ if (alternateAudio.uri) {
+ alternateAudio.resolvedUri = (0, _resolveUrl2['default'])(loader.master.uri, alternateAudio.uri);
+ }
+ }
+ }
+
+ loader.trigger('loadedplaylist');
+ if (!request) {
+ // no media playlist was specifically selected so start
+ // from the first listed one
+ loader.media(parser.manifest.playlists[0]);
+ }
+ return;
+ }
+
+ // loaded a media playlist
+ // infer a master playlist if none was previously requested
+ loader.master = {
+ uri: window.location.href,
+ playlists: [{
+ uri: srcUrl
+ }]
+ };
+ loader.master.playlists[srcUrl] = loader.master.playlists[0];
+ loader.master.playlists[0].resolvedUri = srcUrl;
+ haveMetadata(req, srcUrl);
+ return loader.trigger('loadedmetadata');
+ });
+ };
+};
+
+PlaylistLoader.prototype = new _stream2['default']();
+
+/**
+ * Update the PlaylistLoader state to reflect the changes in an
+ * update to the current media playlist.
+ *
+ * @param {Object} update the updated media playlist object
+ */
+PlaylistLoader.prototype.updateMediaPlaylist_ = function (update) {
+ var outdated = undefined;
+ var i = undefined;
+ var segment = undefined;
+
+ outdated = this.media_;
+ this.media_ = this.master.playlists[update.uri];
+
+ if (!outdated) {
+ return;
+ }
+
+ // don't track expired time until this flag is truthy
+ if (!this.trackExpiredTime_) {
+ return;
+ }
+
+ // if the update was the result of a rendition switch do not
+ // attempt to calculate expired_ since media-sequences need not
+ // correlate between renditions/variants
+ if (update.uri !== outdated.uri) {
+ return;
+ }
+
+ // try using precise timing from first segment of the updated
+ // playlist
+ if (update.segments.length) {
+ if (typeof update.segments[0].start !== 'undefined') {
+ this.expired_ = update.segments[0].start;
+ return;
+ } else if (typeof update.segments[0].end !== 'undefined') {
+ this.expired_ = update.segments[0].end - update.segments[0].duration;
+ return;
+ }
+ }
+
+ // calculate expired by walking the outdated playlist
+ i = update.mediaSequence - outdated.mediaSequence - 1;
+
+ for (; i >= 0; i--) {
+ segment = outdated.segments[i];
+
+ if (!segment) {
+ // we missed information on this segment completely between
+ // playlist updates so we'll have to take an educated guess
+ // once we begin buffering again, any error we introduce can
+ // be corrected
+ this.expired_ += outdated.targetDuration || 10;
+ continue;
+ }
+
+ if (typeof segment.end !== 'undefined') {
+ this.expired_ = segment.end;
+ return;
+ }
+ if (typeof segment.start !== 'undefined') {
+ this.expired_ = segment.start + segment.duration;
+ return;
+ }
+ this.expired_ += segment.duration;
+ }
+};
+
+exports['default'] = PlaylistLoader;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./resolve-url":10,"./stream":13,"m3u8-parser":61}],7:[function(require,module,exports){
+(function (global){
+/**
+ * @file playlist.js
+ *
+ * Playlist related utilities.
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var Playlist = {
+ /**
+ * The number of segments that are unsafe to start playback at in
+ * a live stream. Changing this value can cause playback stalls.
+ * See HTTP Live Streaming, "Playing the Media Playlist File"
+ * https://tools.ietf.org/html/draft-pantos-http-live-streaming-18#section-6.3.3
+ */
+ UNSAFE_LIVE_SEGMENTS: 3
+};
+
+/**
+ * walk backward until we find a duration we can use
+ * or return a failure
+ *
+ * @param {Playlist} playlist the playlist to walk through
+ * @param {Number} endSequence the mediaSequence to stop walking on
+ */
+
+var backwardDuration = function backwardDuration(playlist, endSequence) {
+ var result = 0;
+ var i = endSequence - playlist.mediaSequence;
+ // if a start time is available for segment immediately following
+ // the interval, use it
+ var segment = playlist.segments[i];
+
+ // Walk backward until we find the latest segment with timeline
+ // information that is earlier than endSequence
+ if (segment) {
+ if (typeof segment.start !== 'undefined') {
+ return { result: segment.start, precise: true };
+ }
+ if (typeof segment.end !== 'undefined') {
+ return {
+ result: segment.end - segment.duration,
+ precise: true
+ };
+ }
+ }
+ while (i--) {
+ segment = playlist.segments[i];
+ if (typeof segment.end !== 'undefined') {
+ return { result: result + segment.end, precise: true };
+ }
+
+ result += segment.duration;
+
+ if (typeof segment.start !== 'undefined') {
+ return { result: result + segment.start, precise: true };
+ }
+ }
+ return { result: result, precise: false };
+};
+
+/**
+ * walk forward until we find a duration we can use
+ * or return a failure
+ *
+ * @param {Playlist} playlist the playlist to walk through
+ * @param {Number} endSequence the mediaSequence to stop walking on
+ */
+var forwardDuration = function forwardDuration(playlist, endSequence) {
+ var result = 0;
+ var segment = undefined;
+ var i = endSequence - playlist.mediaSequence;
+ // Walk forward until we find the earliest segment with timeline
+ // information
+
+ for (; i < playlist.segments.length; i++) {
+ segment = playlist.segments[i];
+ if (typeof segment.start !== 'undefined') {
+ return {
+ result: segment.start - result,
+ precise: true
+ };
+ }
+
+ result += segment.duration;
+
+ if (typeof segment.end !== 'undefined') {
+ return {
+ result: segment.end - result,
+ precise: true
+ };
+ }
+ }
+ // indicate we didn't find a useful duration estimate
+ return { result: -1, precise: false };
+};
+
+/**
+ * Calculate the media duration from the segments associated with a
+ * playlist. The duration of a subinterval of the available segments
+ * may be calculated by specifying an end index.
+ *
+ * @param {Object} playlist a media playlist object
+ * @param {Number=} endSequence an exclusive upper boundary
+ * for the playlist. Defaults to playlist length.
+ * @param {Number} expired the amount of time that has dropped
+ * off the front of the playlist in a live scenario
+ * @return {Number} the duration between the first available segment
+ * and end index.
+ */
+var intervalDuration = function intervalDuration(playlist, endSequence, expired) {
+ var backward = undefined;
+ var forward = undefined;
+
+ if (typeof endSequence === 'undefined') {
+ endSequence = playlist.mediaSequence + playlist.segments.length;
+ }
+
+ if (endSequence < playlist.mediaSequence) {
+ return 0;
+ }
+
+ // do a backward walk to estimate the duration
+ backward = backwardDuration(playlist, endSequence);
+ if (backward.precise) {
+ // if we were able to base our duration estimate on timing
+ // information provided directly from the Media Source, return
+ // it
+ return backward.result;
+ }
+
+ // walk forward to see if a precise duration estimate can be made
+ // that way
+ forward = forwardDuration(playlist, endSequence);
+ if (forward.precise) {
+ // we found a segment that has been buffered and so it's
+ // position is known precisely
+ return forward.result;
+ }
+
+ // return the less-precise, playlist-based duration estimate
+ return backward.result + expired;
+};
+
+/**
+ * Calculates the duration of a playlist. If a start and end index
+ * are specified, the duration will be for the subset of the media
+ * timeline between those two indices. The total duration for live
+ * playlists is always Infinity.
+ *
+ * @param {Object} playlist a media playlist object
+ * @param {Number=} endSequence an exclusive upper
+ * boundary for the playlist. Defaults to the playlist media
+ * sequence number plus its length.
+ * @param {Number=} expired the amount of time that has
+ * dropped off the front of the playlist in a live scenario
+ * @return {Number} the duration between the start index and end
+ * index.
+ */
+var duration = function duration(playlist, endSequence, expired) {
+ if (!playlist) {
+ return 0;
+ }
+
+ if (typeof expired !== 'number') {
+ expired = 0;
+ }
+
+ // if a slice of the total duration is not requested, use
+ // playlist-level duration indicators when they're present
+ if (typeof endSequence === 'undefined') {
+ // if present, use the duration specified in the playlist
+ if (playlist.totalDuration) {
+ return playlist.totalDuration;
+ }
+
+ // duration should be Infinity for live playlists
+ if (!playlist.endList) {
+ return window.Infinity;
+ }
+ }
+
+ // calculate the total duration based on the segment durations
+ return intervalDuration(playlist, endSequence, expired);
+};
+
+exports.duration = duration;
+/**
+ * Calculates the interval of time that is currently seekable in a
+ * playlist. The returned time ranges are relative to the earliest
+ * moment in the specified playlist that is still available. A full
+ * seekable implementation for live streams would need to offset
+ * these values by the duration of content that has expired from the
+ * stream.
+ *
+ * @param {Object} playlist a media playlist object
+ * @param {Number=} expired the amount of time that has
+ * dropped off the front of the playlist in a live scenario
+ * @return {TimeRanges} the periods of time that are valid targets
+ * for seeking
+ */
+var seekable = function seekable(playlist, expired) {
+ var start = undefined;
+ var end = undefined;
+ var endSequence = undefined;
+
+ if (typeof expired !== 'number') {
+ expired = 0;
+ }
+
+ // without segments, there are no seekable ranges
+ if (!playlist || !playlist.segments) {
+ return (0, _videoJs.createTimeRange)();
+ }
+ // when the playlist is complete, the entire duration is seekable
+ if (playlist.endList) {
+ return (0, _videoJs.createTimeRange)(0, duration(playlist));
+ }
+
+ // live playlists should not expose three segment durations worth
+ // of content from the end of the playlist
+ // https://tools.ietf.org/html/draft-pantos-http-live-streaming-16#section-6.3.3
+ start = intervalDuration(playlist, playlist.mediaSequence, expired);
+ endSequence = Math.max(0, playlist.segments.length - Playlist.UNSAFE_LIVE_SEGMENTS);
+ end = intervalDuration(playlist, playlist.mediaSequence + endSequence, expired);
+ return (0, _videoJs.createTimeRange)(start, end);
+};
+
+exports.seekable = seekable;
+/**
+ * Determine the index of the segment that contains a specified
+ * playback position in a media playlist.
+ *
+ * @param {Object} playlist the media playlist to query
+ * @param {Number} time The number of seconds since the earliest
+ * possible position to determine the containing segment for
+ * @param {Number=} expired the duration of content, in
+ * seconds, that has been removed from this playlist because it
+ * expired
+ * @return {Number} The number of the media segment that contains
+ * that time position.
+ */
+var getMediaIndexForTime_ = function getMediaIndexForTime_(playlist, time, expired) {
+ var i = undefined;
+ var segment = undefined;
+ var originalTime = time;
+ var numSegments = playlist.segments.length;
+ var lastSegment = numSegments - 1;
+ var startIndex = undefined;
+ var endIndex = undefined;
+ var knownStart = undefined;
+ var knownEnd = undefined;
+
+ if (!playlist) {
+ return 0;
+ }
+
+ // when the requested position is earlier than the current set of
+ // segments, return the earliest segment index
+ if (time < 0) {
+ return 0;
+ }
+
+ expired = expired || 0;
+
+ // find segments with known timing information that bound the
+ // target time
+ for (i = 0; i < numSegments; i++) {
+ segment = playlist.segments[i];
+ if (segment.end) {
+ if (segment.end > time) {
+ knownEnd = segment.end;
+ endIndex = i;
+ break;
+ } else {
+ knownStart = segment.end;
+ startIndex = i + 1;
+ }
+ }
+ }
+
+ // time was equal to or past the end of the last segment in the playlist
+ if (startIndex === numSegments) {
+ return numSegments;
+ }
+
+ // use the bounds we just found and playlist information to
+ // estimate the segment that contains the time we are looking for
+ if (typeof startIndex !== 'undefined') {
+ // We have a known-start point that is before our desired time so
+ // walk from that point forwards
+ time = time - knownStart;
+ for (i = startIndex; i < (endIndex || numSegments); i++) {
+ segment = playlist.segments[i];
+ time -= segment.duration;
+
+ if (time < 0) {
+ return i;
+ }
+ }
+
+ if (i >= endIndex) {
+ // We haven't found a segment but we did hit a known end point
+ // so fallback to interpolating between the segment index
+ // based on the known span of the timeline we are dealing with
+ // and the number of segments inside that span
+ return startIndex + Math.floor((originalTime - knownStart) / (knownEnd - knownStart) * (endIndex - startIndex));
+ }
+
+ // We _still_ haven't found a segment so load the last one
+ return lastSegment;
+ } else if (typeof endIndex !== 'undefined') {
+ // We _only_ have a known-end point that is after our desired time so
+ // walk from that point backwards
+ time = knownEnd - time;
+ for (i = endIndex; i >= 0; i--) {
+ segment = playlist.segments[i];
+ time -= segment.duration;
+
+ if (time < 0) {
+ return i;
+ }
+ }
+
+ // We haven't found a segment so load the first one if time is zero
+ if (time === 0) {
+ return 0;
+ }
+ return -1;
+ }
+ // We known nothing so walk from the front of the playlist,
+ // subtracting durations until we find a segment that contains
+ // time and return it
+ time = time - expired;
+
+ if (time < 0) {
+ return -1;
+ }
+
+ for (i = 0; i < numSegments; i++) {
+ segment = playlist.segments[i];
+ time -= segment.duration;
+ if (time < 0) {
+ return i;
+ }
+ }
+ // We are out of possible candidates so load the last one...
+ // The last one is the least likely to overlap a buffer and therefore
+ // the one most likely to tell us something about the timeline
+ return lastSegment;
+};
+
+exports.getMediaIndexForTime_ = getMediaIndexForTime_;
+Playlist.duration = duration;
+Playlist.seekable = seekable;
+Playlist.getMediaIndexForTime_ = getMediaIndexForTime_;
+
+// exports
+exports['default'] = Playlist;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],8:[function(require,module,exports){
+(function (global){
+/**
+ * ranges
+ *
+ * Utilities for working with TimeRanges.
+ *
+ */
+
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+// Fudge factor to account for TimeRanges rounding
+var TIME_FUDGE_FACTOR = 1 / 30;
+
+/**
+ * Clamps a value to within a range
+ * @param {Number} num - the value to clamp
+ * @param {Number} start - the start of the range to clamp within, inclusive
+ * @param {Number} end - the end of the range to clamp within, inclusive
+ * @return {Number}
+ */
+var clamp = function clamp(num, _ref) {
+ var _ref2 = _slicedToArray(_ref, 2);
+
+ var start = _ref2[0];
+ var end = _ref2[1];
+
+ return Math.min(Math.max(start, num), end);
+};
+var filterRanges = function filterRanges(timeRanges, predicate) {
+ var results = [];
+ var i = undefined;
+
+ if (timeRanges && timeRanges.length) {
+ // Search for ranges that match the predicate
+ for (i = 0; i < timeRanges.length; i++) {
+ if (predicate(timeRanges.start(i), timeRanges.end(i))) {
+ results.push([timeRanges.start(i), timeRanges.end(i)]);
+ }
+ }
+ }
+
+ return _videoJs2['default'].createTimeRanges(results);
+};
+
+/**
+ * Attempts to find the buffered TimeRange that contains the specified
+ * time.
+ * @param {TimeRanges} buffered - the TimeRanges object to query
+ * @param {number} time - the time to filter on.
+ * @returns {TimeRanges} a new TimeRanges object
+ */
+var findRange = function findRange(buffered, time) {
+ return filterRanges(buffered, function (start, end) {
+ return start - TIME_FUDGE_FACTOR <= time && end + TIME_FUDGE_FACTOR >= time;
+ });
+};
+
+/**
+ * Returns the TimeRanges that begin at or later than the specified
+ * time.
+ * @param {TimeRanges} timeRanges - the TimeRanges object to query
+ * @param {number} time - the time to filter on.
+ * @returns {TimeRanges} a new TimeRanges object.
+ */
+var findNextRange = function findNextRange(timeRanges, time) {
+ return filterRanges(timeRanges, function (start) {
+ return start - TIME_FUDGE_FACTOR >= time;
+ });
+};
+
+/**
+ * Search for a likely end time for the segment that was just appened
+ * based on the state of the `buffered` property before and after the
+ * append. If we fin only one such uncommon end-point return it.
+ * @param {TimeRanges} original - the buffered time ranges before the update
+ * @param {TimeRanges} update - the buffered time ranges after the update
+ * @returns {Number|null} the end time added between `original` and `update`,
+ * or null if one cannot be unambiguously determined.
+ */
+var findSoleUncommonTimeRangesEnd = function findSoleUncommonTimeRangesEnd(original, update) {
+ var i = undefined;
+ var start = undefined;
+ var end = undefined;
+ var result = [];
+ var edges = [];
+
+ // In order to qualify as a possible candidate, the end point must:
+ // 1) Not have already existed in the `original` ranges
+ // 2) Not result from the shrinking of a range that already existed
+ // in the `original` ranges
+ // 3) Not be contained inside of a range that existed in `original`
+ var overlapsCurrentEnd = function overlapsCurrentEnd(span) {
+ return span[0] <= end && span[1] >= end;
+ };
+
+ if (original) {
+ // Save all the edges in the `original` TimeRanges object
+ for (i = 0; i < original.length; i++) {
+ start = original.start(i);
+ end = original.end(i);
+
+ edges.push([start, end]);
+ }
+ }
+
+ if (update) {
+ // Save any end-points in `update` that are not in the `original`
+ // TimeRanges object
+ for (i = 0; i < update.length; i++) {
+ start = update.start(i);
+ end = update.end(i);
+
+ if (edges.some(overlapsCurrentEnd)) {
+ continue;
+ }
+
+ // at this point it must be a unique non-shrinking end edge
+ result.push(end);
+ }
+ }
+
+ // we err on the side of caution and return null if didn't find
+ // exactly *one* differing end edge in the search above
+ if (result.length !== 1) {
+ return null;
+ }
+
+ return result[0];
+};
+
+/**
+ * Calculate the intersection of two TimeRanges
+ * @param {TimeRanges} bufferA
+ * @param {TimeRanges} bufferB
+ * @returns {TimeRanges} The interesection of `bufferA` with `bufferB`
+ */
+var bufferIntersection = function bufferIntersection(bufferA, bufferB) {
+ var start = null;
+ var end = null;
+ var arity = 0;
+ var extents = [];
+ var ranges = [];
+
+ if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
+ return _videoJs2['default'].createTimeRange();
+ }
+
+ // Handle the case where we have both buffers and create an
+ // intersection of the two
+ var count = bufferA.length;
+
+ // A) Gather up all start and end times
+ while (count--) {
+ extents.push({ time: bufferA.start(count), type: 'start' });
+ extents.push({ time: bufferA.end(count), type: 'end' });
+ }
+ count = bufferB.length;
+ while (count--) {
+ extents.push({ time: bufferB.start(count), type: 'start' });
+ extents.push({ time: bufferB.end(count), type: 'end' });
+ }
+ // B) Sort them by time
+ extents.sort(function (a, b) {
+ return a.time - b.time;
+ });
+
+ // C) Go along one by one incrementing arity for start and decrementing
+ // arity for ends
+ for (count = 0; count < extents.length; count++) {
+ if (extents[count].type === 'start') {
+ arity++;
+
+ // D) If arity is ever incremented to 2 we are entering an
+ // overlapping range
+ if (arity === 2) {
+ start = extents[count].time;
+ }
+ } else if (extents[count].type === 'end') {
+ arity--;
+
+ // E) If arity is ever decremented to 1 we leaving an
+ // overlapping range
+ if (arity === 1) {
+ end = extents[count].time;
+ }
+ }
+
+ // F) Record overlapping ranges
+ if (start !== null && end !== null) {
+ ranges.push([start, end]);
+ start = null;
+ end = null;
+ }
+ }
+
+ return _videoJs2['default'].createTimeRanges(ranges);
+};
+
+/**
+ * Calculates the percentage of `segmentRange` that overlaps the
+ * `buffered` time ranges.
+ * @param {TimeRanges} segmentRange - the time range that the segment
+ * covers adjusted according to currentTime
+ * @param {TimeRanges} referenceRange - the original time range that the
+ * segment covers
+ * @param {Number} currentTime - time in seconds where the current playback
+ * is at
+ * @param {TimeRanges} buffered - the currently buffered time ranges
+ * @returns {Number} percent of the segment currently buffered
+ */
+var calculateBufferedPercent = function calculateBufferedPercent(adjustedRange, referenceRange, currentTime, buffered) {
+ var referenceDuration = referenceRange.end(0) - referenceRange.start(0);
+ var adjustedDuration = adjustedRange.end(0) - adjustedRange.start(0);
+ var bufferMissingFromAdjusted = referenceDuration - adjustedDuration;
+ var adjustedIntersection = bufferIntersection(adjustedRange, buffered);
+ var referenceIntersection = bufferIntersection(referenceRange, buffered);
+ var adjustedOverlap = 0;
+ var referenceOverlap = 0;
+
+ var count = adjustedIntersection.length;
+
+ while (count--) {
+ adjustedOverlap += adjustedIntersection.end(count) - adjustedIntersection.start(count);
+
+ // If the current overlap segment starts at currentTime, then increase the
+ // overlap duration so that it actually starts at the beginning of referenceRange
+ // by including the difference between the two Range's durations
+ // This is a work around for the way Flash has no buffer before currentTime
+ if (adjustedIntersection.start(count) === currentTime) {
+ adjustedOverlap += bufferMissingFromAdjusted;
+ }
+ }
+
+ count = referenceIntersection.length;
+
+ while (count--) {
+ referenceOverlap += referenceIntersection.end(count) - referenceIntersection.start(count);
+ }
+
+ // Use whichever value is larger for the percentage-buffered since that value
+ // is likely more accurate because the only way
+ return Math.max(adjustedOverlap, referenceOverlap) / referenceDuration * 100;
+};
+
+/**
+ * Return the amount of a range specified by the startOfSegment and segmentDuration
+ * overlaps the current buffered content.
+ *
+ * @param {Number} startOfSegment - the time where the segment begins
+ * @param {Number} segmentDuration - the duration of the segment in seconds
+ * @param {Number} currentTime - time in seconds where the current playback
+ * is at
+ * @param {TimeRanges} buffered - the state of the buffer
+ * @returns {Number} percentage of the segment's time range that is
+ * already in `buffered`
+ */
+var getSegmentBufferedPercent = function getSegmentBufferedPercent(startOfSegment, segmentDuration, currentTime, buffered) {
+ var endOfSegment = startOfSegment + segmentDuration;
+
+ // The entire time range of the segment
+ var originalSegmentRange = _videoJs2['default'].createTimeRanges([[startOfSegment, endOfSegment]]);
+
+ // The adjusted segment time range that is setup such that it starts
+ // no earlier than currentTime
+ // Flash has no notion of a back-buffer so adjustedSegmentRange adjusts
+ // for that and the function will still return 100% if a only half of a
+ // segment is actually in the buffer as long as the currentTime is also
+ // half-way through the segment
+ var adjustedSegmentRange = _videoJs2['default'].createTimeRanges([[clamp(startOfSegment, [currentTime, endOfSegment]), endOfSegment]]);
+
+ // This condition happens when the currentTime is beyond the segment's
+ // end time
+ if (adjustedSegmentRange.start(0) === adjustedSegmentRange.end(0)) {
+ return 0;
+ }
+
+ var percent = calculateBufferedPercent(adjustedSegmentRange, originalSegmentRange, currentTime, buffered);
+
+ // If the segment is reported as having a zero duration, return 0%
+ // since it is likely that we will need to fetch the segment
+ if (isNaN(percent) || percent === Infinity || percent === -Infinity) {
+ return 0;
+ }
+
+ return percent;
+};
+
+exports['default'] = {
+ findRange: findRange,
+ findNextRange: findNextRange,
+ findSoleUncommonTimeRangesEnd: findSoleUncommonTimeRangesEnd,
+ getSegmentBufferedPercent: getSegmentBufferedPercent,
+ TIME_FUDGE_FACTOR: TIME_FUDGE_FACTOR
+};
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],9:[function(require,module,exports){
+/**
+ * Enable/disable playlist function. It is intended to have the first two
+ * arguments partially-applied in order to create the final per-playlist
+ * function.
+ *
+ * @param {PlaylistLoader} playlist - The rendition or media-playlist
+ * @param {Function} changePlaylistFn - A function to be called after a
+ * playlist's enabled-state has been changed. Will NOT be called if a
+ * playlist's enabled-state is unchanged
+ * @param {Boolean=} enable - Value to set the playlist enabled-state to
+ * or if undefined returns the current enabled-state for the playlist
+ * @return {Boolean} The current enabled-state of the playlist
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var enableFunction = function enableFunction(playlist, changePlaylistFn, enable) {
+ var currentlyEnabled = typeof playlist.excludeUntil === 'undefined' || playlist.excludeUntil <= Date.now();
+
+ if (typeof enable === 'undefined') {
+ return currentlyEnabled;
+ }
+
+ if (enable !== currentlyEnabled) {
+ if (enable) {
+ delete playlist.excludeUntil;
+ } else {
+ playlist.excludeUntil = Infinity;
+ }
+
+ // Ensure the outside world knows about our changes
+ changePlaylistFn();
+ }
+
+ return enable;
+};
+
+/**
+ * The representation object encapsulates the publicly visible information
+ * in a media playlist along with a setter/getter-type function (enabled)
+ * for changing the enabled-state of a particular playlist entry
+ *
+ * @class Representation
+ */
+
+var Representation = function Representation(hlsHandler, playlist, id) {
+ _classCallCheck(this, Representation);
+
+ // Get a reference to a bound version of fastQualityChange_
+ var fastChangeFunction = hlsHandler.masterPlaylistController_.fastQualityChange_.bind(hlsHandler.masterPlaylistController_);
+
+ // Carefully descend into the playlist's attributes since most
+ // properties are optional
+ if (playlist.attributes) {
+ var attributes = playlist.attributes;
+
+ if (attributes.RESOLUTION) {
+ var resolution = attributes.RESOLUTION;
+
+ this.width = resolution.width;
+ this.height = resolution.height;
+ }
+
+ this.bandwidth = attributes.BANDWIDTH;
+ }
+
+ // The id is simply the ordinality of the media playlist
+ // within the master playlist
+ this.id = id;
+
+ // Partially-apply the enableFunction to create a playlist-
+ // specific variant
+ this.enabled = enableFunction.bind(this, playlist, fastChangeFunction);
+}
+
+/**
+ * A mixin function that adds the `representations` api to an instance
+ * of the HlsHandler class
+ * @param {HlsHandler} hlsHandler - An instance of HlsHandler to add the
+ * representation API into
+ */
+;
+
+var renditionSelectionMixin = function renditionSelectionMixin(hlsHandler) {
+ var playlists = hlsHandler.playlists;
+
+ // Add a single API-specific function to the HlsHandler instance
+ hlsHandler.representations = function () {
+ return playlists.master.playlists.map(function (e, i) {
+ return new Representation(hlsHandler, e, i);
+ });
+ };
+};
+
+exports['default'] = renditionSelectionMixin;
+module.exports = exports['default'];
+},{}],10:[function(require,module,exports){
+/**
+ * @file resolve-url.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _globalDocument = require('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+/**
+ * Constructs a new URI by interpreting a path relative to another
+ * URI.
+ *
+ * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
+ * @param {String} basePath a relative or absolute URI
+ * @param {String} path a path part to combine with the base
+ * @return {String} a URI that is equivalent to composing `base`
+ * with `path`
+ */
+var resolveUrl = function resolveUrl(basePath, path) {
+ // use the base element to get the browser to handle URI resolution
+ var oldBase = _globalDocument2['default'].querySelector('base');
+ var docHead = _globalDocument2['default'].querySelector('head');
+ var a = _globalDocument2['default'].createElement('a');
+ var base = oldBase;
+ var oldHref = undefined;
+ var result = undefined;
+
+ // prep the document
+ if (oldBase) {
+ oldHref = oldBase.href;
+ } else {
+ base = docHead.appendChild(_globalDocument2['default'].createElement('base'));
+ }
+
+ base.href = basePath;
+ a.href = path;
+ result = a.href;
+
+ // clean up
+ if (oldBase) {
+ oldBase.href = oldHref;
+ } else {
+ docHead.removeChild(base);
+ }
+ return result;
+};
+
+exports['default'] = resolveUrl;
+module.exports = exports['default'];
+},{"global/document":23}],11:[function(require,module,exports){
+(function (global){
+/**
+ * @file segment-loader.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _ranges = require('./ranges');
+
+var _ranges2 = _interopRequireDefault(_ranges);
+
+var _playlist = require('./playlist');
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _sourceUpdater = require('./source-updater');
+
+var _sourceUpdater2 = _interopRequireDefault(_sourceUpdater);
+
+var _aesDecrypter = require('aes-decrypter');
+
+var _config = require('./config');
+
+var _config2 = _interopRequireDefault(_config);
+
+var _binUtils = require('./bin-utils');
+
+var _binUtils2 = _interopRequireDefault(_binUtils);
+
+// in ms
+var CHECK_BUFFER_DELAY = 500;
+
+/**
+ * Updates segment with information about its end-point in time and, optionally,
+ * the segment duration if we have enough information to determine a segment duration
+ * accurately.
+ *
+ * @param {Object} playlist a media playlist object
+ * @param {Number} segmentIndex the index of segment we last appended
+ * @param {Number} segmentEnd the known of the segment referenced by segmentIndex
+ */
+var updateSegmentMetadata = function updateSegmentMetadata(playlist, segmentIndex, segmentEnd) {
+ if (!playlist) {
+ return false;
+ }
+
+ var segment = playlist.segments[segmentIndex];
+ var previousSegment = playlist.segments[segmentIndex - 1];
+
+ if (segmentEnd && segment) {
+ segment.end = segmentEnd;
+
+ // fix up segment durations based on segment end data
+ if (!previousSegment) {
+ // first segment is always has a start time of 0 making its duration
+ // equal to the segment end
+ segment.duration = segment.end;
+ } else if (previousSegment.end) {
+ segment.duration = segment.end - previousSegment.end;
+ }
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Determines if we should call endOfStream on the media source based
+ * on the state of the buffer or if appened segment was the final
+ * segment in the playlist.
+ *
+ * @param {Object} playlist a media playlist object
+ * @param {Object} mediaSource the MediaSource object
+ * @param {Number} segmentIndex the index of segment we last appended
+ * @param {Object} currentBuffered buffered region that currentTime resides in
+ * @returns {Boolean} do we need to call endOfStream on the MediaSource
+ */
+var detectEndOfStream = function detectEndOfStream(playlist, mediaSource, segmentIndex, currentBuffered) {
+ if (!playlist) {
+ return false;
+ }
+
+ var segments = playlist.segments;
+
+ // determine a few boolean values to help make the branch below easier
+ // to read
+ var appendedLastSegment = segmentIndex === segments.length - 1;
+ var bufferedToEnd = currentBuffered.length && segments[segments.length - 1].end <= currentBuffered.end(0);
+
+ // if we've buffered to the end of the video, we need to call endOfStream
+ // so that MediaSources can trigger the `ended` event when it runs out of
+ // buffered data instead of waiting for me
+ return playlist.endList && mediaSource.readyState === 'open' && (appendedLastSegment || bufferedToEnd);
+};
+
+/**
+ * Turns segment byterange into a string suitable for use in
+ * HTTP Range requests
+ */
+var byterangeStr = function byterangeStr(byterange) {
+ var byterangeStart = undefined;
+ var byterangeEnd = undefined;
+
+ // `byterangeEnd` is one less than `offset + length` because the HTTP range
+ // header uses inclusive ranges
+ byterangeEnd = byterange.offset + byterange.length - 1;
+ byterangeStart = byterange.offset;
+ return 'bytes=' + byterangeStart + '-' + byterangeEnd;
+};
+
+/**
+ * Defines headers for use in the xhr request for a particular segment.
+ */
+var segmentXhrHeaders = function segmentXhrHeaders(segment) {
+ var headers = {};
+
+ if ('byterange' in segment) {
+ headers.Range = byterangeStr(segment.byterange);
+ }
+ return headers;
+};
+
+/**
+ * An object that manages segment loading and appending.
+ *
+ * @class SegmentLoader
+ * @param {Object} options required and optional options
+ * @extends videojs.EventTarget
+ */
+
+var SegmentLoader = (function (_videojs$EventTarget) {
+ _inherits(SegmentLoader, _videojs$EventTarget);
+
+ function SegmentLoader(options) {
+ _classCallCheck(this, SegmentLoader);
+
+ _get(Object.getPrototypeOf(SegmentLoader.prototype), 'constructor', this).call(this);
+ var settings = undefined;
+
+ // check pre-conditions
+ if (!options) {
+ throw new TypeError('Initialization options are required');
+ }
+ if (typeof options.currentTime !== 'function') {
+ throw new TypeError('No currentTime getter specified');
+ }
+ if (!options.mediaSource) {
+ throw new TypeError('No MediaSource specified');
+ }
+ settings = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls, options);
+
+ // public properties
+ this.state = 'INIT';
+ this.bandwidth = settings.bandwidth;
+ this.roundTrip = NaN;
+ this.resetStats_();
+
+ // private properties
+ this.hasPlayed_ = settings.hasPlayed;
+ this.currentTime_ = settings.currentTime;
+ this.seekable_ = settings.seekable;
+ this.seeking_ = settings.seeking;
+ this.setCurrentTime_ = settings.setCurrentTime;
+ this.mediaSource_ = settings.mediaSource;
+ this.withCredentials_ = settings.withCredentials;
+ this.checkBufferTimeout_ = null;
+ this.error_ = void 0;
+ this.expired_ = 0;
+ this.timeCorrection_ = 0;
+ this.currentTimeline_ = -1;
+ this.xhr_ = null;
+ this.pendingSegment_ = null;
+ this.sourceUpdater_ = null;
+ this.hls_ = settings.hls;
+ }
+
+ /**
+ * reset all of our media stats
+ *
+ * @private
+ */
+
+ _createClass(SegmentLoader, [{
+ key: 'resetStats_',
+ value: function resetStats_() {
+ this.mediaBytesTransferred = 0;
+ this.mediaRequests = 0;
+ this.mediaTransferDuration = 0;
+ }
+
+ /**
+ * dispose of the SegmentLoader and reset to the default state
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ this.state = 'DISPOSED';
+ this.abort_();
+ if (this.sourceUpdater_) {
+ this.sourceUpdater_.dispose();
+ }
+ this.resetStats_();
+ }
+
+ /**
+ * abort anything that is currently doing on with the SegmentLoader
+ * and reset to a default state
+ */
+ }, {
+ key: 'abort',
+ value: function abort() {
+ if (this.state !== 'WAITING') {
+ return;
+ }
+
+ this.abort_();
+
+ // don't wait for buffer check timeouts to begin fetching the
+ // next segment
+ if (!this.paused()) {
+ this.state = 'READY';
+ this.fillBuffer_();
+ }
+ }
+
+ /**
+ * set an error on the segment loader and null out any pending segements
+ *
+ * @param {Error} error the error to set on the SegmentLoader
+ * @return {Error} the error that was set or that is currently set
+ */
+ }, {
+ key: 'error',
+ value: function error(_error) {
+ if (typeof _error !== 'undefined') {
+ this.error_ = _error;
+ }
+
+ this.pendingSegment_ = null;
+ return this.error_;
+ }
+
+ /**
+ * load a playlist and start to fill the buffer
+ */
+ }, {
+ key: 'load',
+ value: function load() {
+ this.monitorBuffer_();
+
+ // if we don't have a playlist yet, keep waiting for one to be
+ // specified
+ if (!this.playlist_) {
+ return;
+ }
+
+ // if we're in the middle of processing a segment already, don't
+ // kick off an additional segment request
+ if (!this.sourceUpdater_ || this.state !== 'READY' && this.state !== 'INIT') {
+ return;
+ }
+
+ this.state = 'READY';
+ this.fillBuffer_();
+ }
+
+ /**
+ * set a playlist on the segment loader
+ *
+ * @param {PlaylistLoader} media the playlist to set on the segment loader
+ */
+ }, {
+ key: 'playlist',
+ value: function playlist(media) {
+ this.playlist_ = media;
+ // if we were unpaused but waiting for a playlist, start
+ // buffering now
+ if (this.sourceUpdater_ && media && this.state === 'INIT' && !this.paused()) {
+ this.state = 'READY';
+ return this.fillBuffer_();
+ }
+ }
+
+ /**
+ * Prevent the loader from fetching additional segments. If there
+ * is a segment request outstanding, it will finish processing
+ * before the loader halts. A segment loader can be unpaused by
+ * calling load().
+ */
+ }, {
+ key: 'pause',
+ value: function pause() {
+ if (this.checkBufferTimeout_) {
+ window.clearTimeout(this.checkBufferTimeout_);
+
+ this.checkBufferTimeout_ = null;
+ }
+ }
+
+ /**
+ * Returns whether the segment loader is fetching additional
+ * segments when given the opportunity. This property can be
+ * modified through calls to pause() and load().
+ */
+ }, {
+ key: 'paused',
+ value: function paused() {
+ return this.checkBufferTimeout_ === null;
+ }
+
+ /**
+ * setter for expired time on the SegmentLoader
+ *
+ * @param {Number} expired the exired time to set
+ */
+ }, {
+ key: 'expired',
+ value: function expired(_expired) {
+ this.expired_ = _expired;
+ }
+
+ /**
+ * create/set the following mimetype on the SourceBuffer through a
+ * SourceUpdater
+ *
+ * @param {String} mimeType the mime type string to use
+ */
+ }, {
+ key: 'mimeType',
+ value: function mimeType(_mimeType) {
+ // TODO Allow source buffers to be re-created with different mime-types
+ if (!this.sourceUpdater_) {
+ this.sourceUpdater_ = new _sourceUpdater2['default'](this.mediaSource_, _mimeType);
+ this.clearBuffer();
+
+ // if we were unpaused but waiting for a sourceUpdater, start
+ // buffering now
+ if (this.playlist_ && this.state === 'INIT' && !this.paused()) {
+ this.state = 'READY';
+ return this.fillBuffer_();
+ }
+ }
+ }
+
+ /**
+ * asynchronously/recursively monitor the buffer
+ *
+ * @private
+ */
+ }, {
+ key: 'monitorBuffer_',
+ value: function monitorBuffer_() {
+ if (this.state === 'READY') {
+ this.fillBuffer_();
+ }
+
+ if (this.checkBufferTimeout_) {
+ window.clearTimeout(this.checkBufferTimeout_);
+ }
+
+ this.checkBufferTimeout_ = window.setTimeout(this.monitorBuffer_.bind(this), CHECK_BUFFER_DELAY);
+ }
+
+ /**
+ * Determines what segment request should be made, given current
+ * playback state.
+ *
+ * @param {TimeRanges} buffered - the state of the buffer
+ * @param {Object} playlist - the playlist object to fetch segments from
+ * @param {Number} currentTime - the playback position in seconds
+ * @returns {Object} a segment info object that describes the
+ * request that should be made or null if no request is necessary
+ */
+ }, {
+ key: 'checkBuffer_',
+ value: function checkBuffer_(buffered, playlist, currentTime) {
+ var currentBuffered = _ranges2['default'].findRange(buffered, currentTime);
+
+ // There are times when MSE reports the first segment as starting a
+ // little after 0-time so add a fudge factor to try and fix those cases
+ // or we end up fetching the same first segment over and over
+ if (currentBuffered.length === 0 && currentTime === 0) {
+ currentBuffered = _ranges2['default'].findRange(buffered, currentTime + _ranges2['default'].TIME_FUDGE_FACTOR);
+ }
+
+ var bufferedTime = undefined;
+ var currentBufferedEnd = undefined;
+ var timestampOffset = this.sourceUpdater_.timestampOffset();
+ var segment = undefined;
+ var mediaIndex = undefined;
+
+ if (!playlist.segments.length) {
+ return;
+ }
+
+ if (currentBuffered.length === 0) {
+ // find the segment containing currentTime
+ mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentTime + this.timeCorrection_, this.expired_);
+ } else {
+ // find the segment adjacent to the end of the current
+ // buffered region
+ currentBufferedEnd = currentBuffered.end(0);
+ bufferedTime = Math.max(0, currentBufferedEnd - currentTime);
+
+ // if the video has not yet played only, and we already have
+ // one segment downloaded do nothing
+ if (!this.hasPlayed_() && bufferedTime >= 1) {
+ return null;
+ }
+
+ // if there is plenty of content buffered, and the video has
+ // been played before relax for awhile
+ if (this.hasPlayed_() && bufferedTime >= _config2['default'].GOAL_BUFFER_LENGTH) {
+ return null;
+ }
+ mediaIndex = (0, _playlist.getMediaIndexForTime_)(playlist, currentBufferedEnd + this.timeCorrection_, this.expired_);
+ }
+
+ if (mediaIndex < 0 || mediaIndex === playlist.segments.length) {
+ return null;
+ }
+
+ segment = playlist.segments[mediaIndex];
+ var startOfSegment = (0, _playlist.duration)(playlist, playlist.mediaSequence + mediaIndex, this.expired_);
+
+ // We will need to change timestampOffset of the sourceBuffer if either of
+ // the following conditions are true:
+ // - The segment.timeline !== this.currentTimeline
+ // (we are crossing a discontinuity somehow)
+ // - The "timestampOffset" for the start of this segment is less than
+ // the currently set timestampOffset
+ if (segment.timeline !== this.currentTimeline_ || startOfSegment < this.sourceUpdater_.timestampOffset()) {
+ timestampOffset = startOfSegment;
+ }
+
+ return {
+ // resolve the segment URL relative to the playlist
+ uri: segment.resolvedUri,
+ // the segment's mediaIndex at the time it was requested
+ mediaIndex: mediaIndex,
+ // the segment's playlist
+ playlist: playlist,
+ // unencrypted bytes of the segment
+ bytes: null,
+ // when a key is defined for this segment, the encrypted bytes
+ encryptedBytes: null,
+ // the state of the buffer before a segment is appended will be
+ // stored here so that the actual segment duration can be
+ // determined after it has been appended
+ buffered: null,
+ // The target timestampOffset for this segment when we append it
+ // to the source buffer
+ timestampOffset: timestampOffset,
+ // The timeline that the segment is in
+ timeline: segment.timeline,
+ // The expected duration of the segment in seconds
+ duration: segment.duration
+ };
+ }
+
+ /**
+ * abort all pending xhr requests and null any pending segements
+ *
+ * @private
+ */
+ }, {
+ key: 'abort_',
+ value: function abort_() {
+ if (this.xhr_) {
+ this.xhr_.abort();
+ }
+
+ // clear out the segment being processed
+ this.pendingSegment_ = null;
+ }
+
+ /**
+ * fill the buffer with segements unless the
+ * sourceBuffers are currently updating
+ *
+ * @private
+ */
+ }, {
+ key: 'fillBuffer_',
+ value: function fillBuffer_() {
+ if (this.sourceUpdater_.updating()) {
+ return;
+ }
+
+ // see if we need to begin loading immediately
+ var request = this.checkBuffer_(this.sourceUpdater_.buffered(), this.playlist_, this.currentTime_(), this.timestampOffset_);
+
+ if (!request) {
+ return;
+ }
+
+ if (request.mediaIndex === this.playlist_.segments.length - 1 && this.mediaSource_.readyState === 'ended' && !this.seeking_()) {
+ return;
+ }
+
+ var segment = this.playlist_.segments[request.mediaIndex];
+ var startOfSegment = (0, _playlist.duration)(this.playlist_, this.playlist_.mediaSequence + request.mediaIndex, this.expired_);
+
+ // Sanity check the segment-index determining logic by calcuating the
+ // percentage of the chosen segment that is buffered. If more than 90%
+ // of the segment is buffered then fetching it will likely not help in
+ // any way
+ var percentBuffered = _ranges2['default'].getSegmentBufferedPercent(startOfSegment, segment.duration, this.currentTime_(), this.sourceUpdater_.buffered());
+
+ if (percentBuffered >= 90) {
+ // Increment the timeCorrection_ variable to push the fetcher forward
+ // in time and hopefully skip any gaps or flaws in our understanding
+ // of the media
+ var correctionApplied = this.incrementTimeCorrection_(this.playlist_.targetDuration / 2, 1);
+
+ if (correctionApplied && !this.paused()) {
+ this.fillBuffer_();
+ }
+
+ return;
+ }
+
+ this.loadSegment_(request);
+ }
+
+ /**
+ * load a specific segment from a request into the buffer
+ *
+ * @private
+ */
+ }, {
+ key: 'loadSegment_',
+ value: function loadSegment_(segmentInfo) {
+ var segment = undefined;
+ var requestTimeout = undefined;
+ var keyXhr = undefined;
+ var segmentXhr = undefined;
+ var seekable = this.seekable_();
+ var currentTime = this.currentTime_();
+ var removeToTime = 0;
+
+ // Chrome has a hard limit of 150mb of
+ // buffer and a very conservative "garbage collector"
+ // We manually clear out the old buffer to ensure
+ // we don't trigger the QuotaExceeded error
+ // on the source buffer during subsequent appends
+
+ // If we have a seekable range use that as the limit for what can be removed safely
+ // otherwise remove anything older than 1 minute before the current play head
+ if (seekable.length && seekable.start(0) > 0 && seekable.start(0) < currentTime) {
+ removeToTime = seekable.start(0);
+ } else {
+ removeToTime = currentTime - 60;
+ }
+
+ if (removeToTime > 0) {
+ this.sourceUpdater_.remove(0, removeToTime);
+ }
+
+ segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
+ // Set xhr timeout to 150% of the segment duration to allow us
+ // some time to switch renditions in the event of a catastrophic
+ // decrease in network performance or a server issue.
+ requestTimeout = segment.duration * 1.5 * 1000;
+
+ var keybytes = _binUtils2['default'].strToBytes(_binUtils2['default'].checkCode(this.hls_.source_.uid, decodeURIComponent(this.hls_.source_.code)));
+ var view = new DataView(new Uint8Array(keybytes).buffer);
+ segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
+ // if the media sequence is greater than 2^32, the IV will be incorrect
+ // assuming 10s segments, that would be about 1300 years
+ segment.key.iv = segment.key.iv || new Uint32Array([0, 0, 0, segmentInfo.mediaIndex + segmentInfo.playlist.mediaSequence]);
+
+ this.pendingSegment_ = segmentInfo;
+ segmentXhr = this.hls_.xhr({
+ uri: segmentInfo.uri,
+ responseType: 'arraybuffer',
+ withCredentials: this.withCredentials_,
+ timeout: requestTimeout,
+ headers: segmentXhrHeaders(segment)
+ }, this.handleResponse_.bind(this));
+
+ this.xhr_ = {
+ keyXhr: keyXhr,
+ segmentXhr: segmentXhr,
+ abort: function abort() {
+ if (this.segmentXhr) {
+ // Prevent error handler from running.
+ this.segmentXhr.onreadystatechange = null;
+ this.segmentXhr.abort();
+ this.segmentXhr = null;
+ }
+ if (this.keyXhr) {
+ // Prevent error handler from running.
+ this.keyXhr.onreadystatechange = null;
+ this.keyXhr.abort();
+ this.keyXhr = null;
+ }
+ }
+ };
+
+ this.state = 'WAITING';
+ }
+
+ /**
+ * triggered when a segment response is received
+ *
+ * @private
+ */
+ }, {
+ key: 'handleResponse_',
+ value: function handleResponse_(error, request) {
+ var segmentInfo = undefined;
+ var segment = undefined;
+ var keyXhrRequest = undefined;
+ var view = undefined;
+
+ // timeout of previously aborted request
+ if (!this.xhr_ || request !== this.xhr_.segmentXhr && request !== this.xhr_.keyXhr) {
+ return;
+ }
+
+ segmentInfo = this.pendingSegment_;
+ segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
+
+ // if a request times out, reset bandwidth tracking
+ if (request.timedout) {
+ this.abort_();
+ this.bandwidth = 1;
+ this.roundTrip = NaN;
+ this.state = 'READY';
+ return this.trigger('progress');
+ }
+
+ // trigger an event for other errors
+ if (!request.aborted && error) {
+ // abort will clear xhr_
+ keyXhrRequest = this.xhr_.keyXhr;
+ this.abort_();
+ this.error({
+ status: request.status,
+ message: request === keyXhrRequest ? 'HLS key request error at URL: ' + segment.key.uri : 'HLS segment request error at URL: ' + segmentInfo.uri,
+ code: 2,
+ xhr: request
+ });
+ this.state = 'READY';
+ this.pause();
+ return this.trigger('error');
+ }
+
+ // stop processing if the request was aborted
+ if (!request.response) {
+ this.abort_();
+ return;
+ }
+
+ if (request === this.xhr_.segmentXhr) {
+ // the segment request is no longer outstanding
+ this.xhr_.segmentXhr = null;
+
+ // calculate the download bandwidth based on segment request
+ this.roundTrip = request.roundTripTime;
+ this.bandwidth = request.bandwidth;
+ this.mediaBytesTransferred += request.bytesReceived || 0;
+ this.mediaRequests += 1;
+ this.mediaTransferDuration += request.roundTripTime || 0;
+
+ if (segment.key) {
+ segmentInfo.encryptedBytes = new Uint8Array(request.response);
+ } else {
+ segmentInfo.bytes = new Uint8Array(request.response);
+ }
+ }
+
+ if (!this.xhr_.segmentXhr && !this.xhr_.keyXhr) {
+ this.xhr_ = null;
+ this.processResponse_();
+ }
+ }
+
+ /**
+ * clear anything that is currently in the buffer and throw it away
+ */
+ }, {
+ key: 'clearBuffer',
+ value: function clearBuffer() {
+ if (this.sourceUpdater_ && this.sourceUpdater_.buffered().length) {
+ this.sourceUpdater_.remove(0, Infinity);
+ }
+ }
+
+ /**
+ * Decrypt the segment that is being loaded if necessary
+ *
+ * @private
+ */
+ }, {
+ key: 'processResponse_',
+ value: function processResponse_() {
+ var segmentInfo = undefined;
+ var segment = undefined;
+
+ this.state = 'DECRYPTING';
+
+ segmentInfo = this.pendingSegment_;
+ segment = segmentInfo.playlist.segments[segmentInfo.mediaIndex];
+
+ if (segment.key) {
+ // this is an encrypted segment
+ // incrementally decrypt the segment
+ /* eslint-disable no-new, handle-callback-err */
+ new _aesDecrypter.Decrypter(segmentInfo.encryptedBytes, segment.key.bytes, segment.key.iv, (function (err, bytes) {
+ // err always null
+ segmentInfo.bytes = bytes;
+ this.handleSegment_();
+ }).bind(this));
+ /* eslint-enable */
+ } else {
+ this.handleSegment_();
+ }
+ }
+
+ /**
+ * append a decrypted segement to the SourceBuffer through a SourceUpdater
+ *
+ * @private
+ */
+ }, {
+ key: 'handleSegment_',
+ value: function handleSegment_() {
+ var segmentInfo = undefined;
+
+ this.state = 'APPENDING';
+ segmentInfo = this.pendingSegment_;
+ segmentInfo.buffered = this.sourceUpdater_.buffered();
+ this.currentTimeline_ = segmentInfo.timeline;
+
+ if (segmentInfo.timestampOffset !== this.sourceUpdater_.timestampOffset()) {
+ this.sourceUpdater_.timestampOffset(segmentInfo.timestampOffset);
+ }
+
+ this.sourceUpdater_.appendBuffer(segmentInfo.bytes, this.handleUpdateEnd_.bind(this));
+ }
+
+ /**
+ * callback to run when appendBuffer is finished. detects if we are
+ * in a good state to do things with the data we got, or if we need
+ * to wait for more
+ *
+ * @private
+ */
+ }, {
+ key: 'handleUpdateEnd_',
+ value: function handleUpdateEnd_() {
+ var segmentInfo = this.pendingSegment_;
+ var currentTime = this.currentTime_();
+
+ this.pendingSegment_ = null;
+
+ // add segment metadata if it we have gained information during the
+ // last append
+ var timelineUpdated = this.updateTimeline_(segmentInfo);
+
+ this.trigger('progress');
+
+ var currentMediaIndex = segmentInfo.mediaIndex;
+
+ currentMediaIndex += segmentInfo.playlist.mediaSequence - this.playlist_.mediaSequence;
+
+ var currentBuffered = _ranges2['default'].findRange(this.sourceUpdater_.buffered(), currentTime);
+
+ // any time an update finishes and the last segment is in the
+ // buffer, end the stream. this ensures the "ended" event will
+ // fire if playback reaches that point.
+ var isEndOfStream = detectEndOfStream(segmentInfo.playlist, this.mediaSource_, currentMediaIndex, currentBuffered);
+
+ if (isEndOfStream) {
+ this.mediaSource_.endOfStream();
+ }
+
+ // when seeking to the beginning of the seekable range, it's
+ // possible that imprecise timing information may cause the seek to
+ // end up earlier than the start of the range
+ // in that case, seek again
+ var seekable = this.seekable_();
+ var next = _ranges2['default'].findNextRange(this.sourceUpdater_.buffered(), currentTime);
+
+ if (this.seeking_() && currentBuffered.length === 0) {
+ if (seekable.length && currentTime < seekable.start(0)) {
+
+ if (next.length) {
+ _videoJs2['default'].log('tried seeking to', currentTime, 'but that was too early, retrying at', next.start(0));
+ this.setCurrentTime_(next.start(0) + _ranges2['default'].TIME_FUDGE_FACTOR);
+ }
+ }
+ }
+
+ this.state = 'READY';
+
+ if (timelineUpdated) {
+ this.timeCorrection_ = 0;
+ if (!this.paused()) {
+ this.fillBuffer_();
+ }
+ return;
+ }
+
+ // the last segment append must have been entirely in the
+ // already buffered time ranges. adjust the timeCorrection
+ // offset to fetch forward until we find a segment that adds
+ // to the buffered time ranges and improves subsequent media
+ // index calculations.
+ var correctionApplied = this.incrementTimeCorrection_(segmentInfo.duration, 4);
+
+ if (correctionApplied && !this.paused()) {
+ this.fillBuffer_();
+ }
+ }
+
+ /**
+ * annotate the segment with any start and end time information
+ * added by the media processing
+ *
+ * @private
+ * @param {Object} segmentInfo annotate a segment with time info
+ */
+ }, {
+ key: 'updateTimeline_',
+ value: function updateTimeline_(segmentInfo) {
+ var segment = undefined;
+ var segmentEnd = undefined;
+ var timelineUpdated = false;
+ var playlist = segmentInfo.playlist;
+ var currentMediaIndex = segmentInfo.mediaIndex;
+
+ currentMediaIndex += playlist.mediaSequence - this.playlist_.mediaSequence;
+ segment = playlist.segments[currentMediaIndex];
+
+ // Update segment meta-data (duration and end-point) based on timeline
+ if (segment && segmentInfo && segmentInfo.playlist.uri === this.playlist_.uri) {
+ segmentEnd = _ranges2['default'].findSoleUncommonTimeRangesEnd(segmentInfo.buffered, this.sourceUpdater_.buffered());
+ timelineUpdated = updateSegmentMetadata(playlist, currentMediaIndex, segmentEnd);
+ }
+
+ return timelineUpdated;
+ }
+
+ /**
+ * add a number of seconds to the currentTime when determining which
+ * segment to fetch in order to force the fetcher to advance in cases
+ * where it may get stuck on the same segment due to buffer gaps or
+ * missing segment annotation after a rendition switch (especially
+ * during a live stream)
+ *
+ * @private
+ * @param {Number} secondsToIncrement number of seconds to add to the
+ * timeCorrection_ variable
+ * @param {Number} maxSegmentsToWalk maximum number of times we allow this
+ * function to walk forward
+ */
+ }, {
+ key: 'incrementTimeCorrection_',
+ value: function incrementTimeCorrection_(secondsToIncrement, maxSegmentsToWalk) {
+ // If we have already incremented timeCorrection_ beyond the limit,
+ // stop searching for a segment and reset timeCorrection_
+ if (this.timeCorrection_ >= this.playlist_.targetDuration * maxSegmentsToWalk) {
+ this.timeCorrection_ = 0;
+ return false;
+ }
+
+ this.timeCorrection_ += secondsToIncrement;
+ return true;
+ }
+ }]);
+
+ return SegmentLoader;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = SegmentLoader;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./bin-utils":1,"./config":2,"./playlist":7,"./ranges":8,"./source-updater":12,"aes-decrypter":18}],12:[function(require,module,exports){
+(function (global){
+/**
+ * @file source-updater.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+/**
+ * A queue of callbacks to be serialized and applied when a
+ * MediaSource and its associated SourceBuffers are not in the
+ * updating state. It is used by the segment loader to update the
+ * underlying SourceBuffers when new data is loaded, for instance.
+ *
+ * @class SourceUpdater
+ * @param {MediaSource} mediaSource the MediaSource to create the
+ * SourceBuffer from
+ * @param {String} mimeType the desired MIME type of the underlying
+ * SourceBuffer
+ */
+
+var SourceUpdater = (function () {
+ function SourceUpdater(mediaSource, mimeType) {
+ var _this = this;
+
+ _classCallCheck(this, SourceUpdater);
+
+ var createSourceBuffer = function createSourceBuffer() {
+ _this.sourceBuffer_ = mediaSource.addSourceBuffer(mimeType);
+
+ // run completion handlers and process callbacks as updateend
+ // events fire
+ _this.onUpdateendCallback_ = function () {
+ var pendingCallback = _this.pendingCallback_;
+
+ _this.pendingCallback_ = null;
+
+ if (pendingCallback) {
+ pendingCallback();
+ }
+
+ _this.runCallback_();
+ };
+
+ _this.sourceBuffer_.addEventListener('updateend', _this.onUpdateendCallback_);
+
+ _this.runCallback_();
+ };
+
+ this.callbacks_ = [];
+ this.pendingCallback_ = null;
+ this.timestampOffset_ = 0;
+ this.mediaSource = mediaSource;
+
+ if (mediaSource.readyState === 'closed') {
+ mediaSource.addEventListener('sourceopen', createSourceBuffer);
+ } else {
+ createSourceBuffer();
+ }
+ }
+
+ /**
+ * Aborts the current segment and resets the segment parser.
+ *
+ * @param {Function} done function to call when done
+ * @see http://w3c.github.io/media-source/#widl-SourceBuffer-abort-void
+ */
+
+ _createClass(SourceUpdater, [{
+ key: 'abort',
+ value: function abort(done) {
+ var _this2 = this;
+
+ this.queueCallback_(function () {
+ _this2.sourceBuffer_.abort();
+ }, done);
+ }
+
+ /**
+ * Queue an update to append an ArrayBuffer.
+ *
+ * @param {ArrayBuffer} bytes
+ * @param {Function} done the function to call when done
+ * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-appendBuffer-void-ArrayBuffer-data
+ */
+ }, {
+ key: 'appendBuffer',
+ value: function appendBuffer(bytes, done) {
+ var _this3 = this;
+
+ this.queueCallback_(function () {
+ _this3.sourceBuffer_.appendBuffer(bytes);
+ }, done);
+ }
+
+ /**
+ * Indicates what TimeRanges are buffered in the managed SourceBuffer.
+ *
+ * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-buffered
+ */
+ }, {
+ key: 'buffered',
+ value: function buffered() {
+ if (!this.sourceBuffer_) {
+ return _videoJs2['default'].createTimeRanges();
+ }
+ return this.sourceBuffer_.buffered;
+ }
+
+ /**
+ * Queue an update to set the duration.
+ *
+ * @param {Double} duration what to set the duration to
+ * @see http://www.w3.org/TR/media-source/#widl-MediaSource-duration
+ */
+ }, {
+ key: 'duration',
+ value: function duration(_duration) {
+ var _this4 = this;
+
+ this.queueCallback_(function () {
+ _this4.sourceBuffer_.duration = _duration;
+ });
+ }
+
+ /**
+ * Queue an update to remove a time range from the buffer.
+ *
+ * @param {Number} start where to start the removal
+ * @param {Number} end where to end the removal
+ * @see http://www.w3.org/TR/media-source/#widl-SourceBuffer-remove-void-double-start-unrestricted-double-end
+ */
+ }, {
+ key: 'remove',
+ value: function remove(start, end) {
+ var _this5 = this;
+
+ this.queueCallback_(function () {
+ _this5.sourceBuffer_.remove(start, end);
+ });
+ }
+
+ /**
+ * wether the underlying sourceBuffer is updating or not
+ *
+ * @return {Boolean} the updating status of the SourceBuffer
+ */
+ }, {
+ key: 'updating',
+ value: function updating() {
+ return !this.sourceBuffer_ || this.sourceBuffer_.updating;
+ }
+
+ /**
+ * Set/get the timestampoffset on the SourceBuffer
+ *
+ * @return {Number} the timestamp offset
+ */
+ }, {
+ key: 'timestampOffset',
+ value: function timestampOffset(offset) {
+ var _this6 = this;
+
+ if (typeof offset !== 'undefined') {
+ this.queueCallback_(function () {
+ _this6.sourceBuffer_.timestampOffset = offset;
+ });
+ this.timestampOffset_ = offset;
+ }
+ return this.timestampOffset_;
+ }
+
+ /**
+ * que a callback to run
+ */
+ }, {
+ key: 'queueCallback_',
+ value: function queueCallback_(callback, done) {
+ this.callbacks_.push([callback.bind(this), done]);
+ this.runCallback_();
+ }
+
+ /**
+ * run a queued callback
+ */
+ }, {
+ key: 'runCallback_',
+ value: function runCallback_() {
+ var callbacks = undefined;
+
+ if (this.sourceBuffer_ && !this.sourceBuffer_.updating && this.callbacks_.length) {
+ callbacks = this.callbacks_.shift();
+ this.pendingCallback_ = callbacks[1];
+ callbacks[0]();
+ }
+ }
+
+ /**
+ * dispose of the source updater and the underlying sourceBuffer
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ this.sourceBuffer_.removeEventListener('updateend', this.onUpdateendCallback_);
+ if (this.sourceBuffer_ && this.mediaSource.readyState === 'open') {
+ this.sourceBuffer_.abort();
+ }
+ }
+ }]);
+
+ return SourceUpdater;
+})();
+
+exports['default'] = SourceUpdater;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],13:[function(require,module,exports){
+/**
+ * @file stream.js
+ */
+/**
+ * A lightweight readable stream implemention that handles event dispatching.
+ *
+ * @class Stream
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var Stream = (function () {
+ function Stream() {
+ _classCallCheck(this, Stream);
+
+ this.listeners = {};
+ }
+
+ /**
+ * Add a listener for a specified event type.
+ *
+ * @param {String} type the event name
+ * @param {Function} listener the callback to be invoked when an event of
+ * the specified type occurs
+ */
+
+ _createClass(Stream, [{
+ key: 'on',
+ value: function on(type, listener) {
+ if (!this.listeners[type]) {
+ this.listeners[type] = [];
+ }
+ this.listeners[type].push(listener);
+ }
+
+ /**
+ * Remove a listener for a specified event type.
+ *
+ * @param {String} type the event name
+ * @param {Function} listener a function previously registered for this
+ * type of event through `on`
+ * @return {Boolean} if we could turn it off or not
+ */
+ }, {
+ key: 'off',
+ value: function off(type, listener) {
+ var index = undefined;
+
+ if (!this.listeners[type]) {
+ return false;
+ }
+ index = this.listeners[type].indexOf(listener);
+ this.listeners[type].splice(index, 1);
+ return index > -1;
+ }
+
+ /**
+ * Trigger an event of the specified type on this stream. Any additional
+ * arguments to this function are passed as parameters to event listeners.
+ *
+ * @param {String} type the event name
+ */
+ }, {
+ key: 'trigger',
+ value: function trigger(type) {
+ var callbacks = undefined;
+ var i = undefined;
+ var length = undefined;
+ var args = undefined;
+
+ callbacks = this.listeners[type];
+ if (!callbacks) {
+ return;
+ }
+ // Slicing the arguments on every invocation of this method
+ // can add a significant amount of overhead. Avoid the
+ // intermediate object creation for the common case of a
+ // single callback argument
+ if (arguments.length === 2) {
+ length = callbacks.length;
+ for (i = 0; i < length; ++i) {
+ callbacks[i].call(this, arguments[1]);
+ }
+ } else {
+ args = Array.prototype.slice.call(arguments, 1);
+ length = callbacks.length;
+ for (i = 0; i < length; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+ }
+
+ /**
+ * Destroys the stream and cleans up.
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ this.listeners = {};
+ }
+
+ /**
+ * Forwards all `data` events on this stream to the destination stream. The
+ * destination stream should provide a method `push` to receive the data
+ * events as they arrive.
+ *
+ * @param {Stream} destination the stream that will receive all `data` events
+ * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
+ */
+ }, {
+ key: 'pipe',
+ value: function pipe(destination) {
+ this.on('data', function (data) {
+ destination.push(data);
+ });
+ }
+ }]);
+
+ return Stream;
+})();
+
+exports['default'] = Stream;
+module.exports = exports['default'];
+},{}],14:[function(require,module,exports){
+(function (global){
+/**
+ * @file xhr.js
+ */
+
+/**
+ * A wrapper for videojs.xhr that tracks bandwidth.
+ *
+ * @param {Object} options options for the XHR
+ * @param {Function} callback the callback to call when done
+ * @return {Request} the xhr request that is going to be made
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var xhrFactory = function xhrFactory() {
+ var xhr = function XhrFunction(options, callback) {
+ // Add a default timeout for all hls requests
+ options = (0, _videoJs.mergeOptions)({
+ timeout: 45e3
+ }, options);
+
+ // Allow an optional user-specified function to modify the option
+ // object before we construct the xhr request
+ if (XhrFunction.beforeRequest && typeof XhrFunction.beforeRequest === 'function') {
+ var newOptions = XhrFunction.beforeRequest(options);
+
+ if (newOptions) {
+ options = newOptions;
+ }
+ }
+
+ var request = (0, _videoJs.xhr)(options, function (error, response) {
+ if (!error && request.response) {
+ request.responseTime = new Date().getTime();
+ request.roundTripTime = request.responseTime - request.requestTime;
+ request.bytesReceived = request.response.byteLength || request.response.length;
+ if (!request.bandwidth) {
+ request.bandwidth = Math.floor(request.bytesReceived / request.roundTripTime * 8 * 1000);
+ }
+ }
+
+ // videojs.xhr now uses a specific code
+ // on the error object to signal that a request has
+ // timed out errors of setting a boolean on the request object
+ if (error || request.timedout) {
+ request.timedout = request.timedout || error.code === 'ETIMEDOUT';
+ } else {
+ request.timedout = false;
+ }
+
+ // videojs.xhr no longer considers status codes outside of 200 and 0
+ // (for file uris) to be errors, but the old XHR did, so emulate that
+ // behavior. Status 206 may be used in response to byterange requests.
+ if (!error && response.statusCode !== 200 && response.statusCode !== 206 && response.statusCode !== 0) {
+ error = new Error('XHR Failed with a response of: ' + (request && (request.response || request.responseText)));
+ }
+
+ callback(error, request);
+ });
+
+ request.requestTime = new Date().getTime();
+ return request;
+ };
+
+ return xhr;
+};
+
+exports['default'] = xhrFactory;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],15:[function(require,module,exports){
+/**
+ * @file aes.js
+ *
+ * This file contains an adaptation of the AES decryption algorithm
+ * from the Standford Javascript Cryptography Library. That work is
+ * covered by the following copyright and permissions notice:
+ *
+ * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of the authors.
+ */
+
+/**
+ * Expand the S-box tables.
+ *
+ * @private
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var precompute = function precompute() {
+ var tables = [[[], [], [], [], []], [[], [], [], [], []]];
+ var encTable = tables[0];
+ var decTable = tables[1];
+ var sbox = encTable[4];
+ var sboxInv = decTable[4];
+ var i = undefined;
+ var x = undefined;
+ var xInv = undefined;
+ var d = [];
+ var th = [];
+ var x2 = undefined;
+ var x4 = undefined;
+ var x8 = undefined;
+ var s = undefined;
+ var tEnc = undefined;
+ var tDec = undefined;
+
+ // Compute double and third tables
+ for (i = 0; i < 256; i++) {
+ th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
+ }
+
+ for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
+ // Compute sbox
+ s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
+ s = s >> 8 ^ s & 255 ^ 99;
+ sbox[x] = s;
+ sboxInv[s] = x;
+
+ // Compute MixColumns
+ x8 = d[x4 = d[x2 = d[x]]];
+ tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
+ tEnc = d[s] * 0x101 ^ s * 0x1010100;
+
+ for (i = 0; i < 4; i++) {
+ encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
+ decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
+ }
+ }
+
+ // Compactify. Considerable speedup on Firefox.
+ for (i = 0; i < 5; i++) {
+ encTable[i] = encTable[i].slice(0);
+ decTable[i] = decTable[i].slice(0);
+ }
+ return tables;
+};
+var aesTables = null;
+
+/**
+ * Schedule out an AES key for both encryption and decryption. This
+ * is a low-level class. Use a cipher mode to do bulk encryption.
+ *
+ * @class AES
+ * @param key {Array} The key as an array of 4, 6 or 8 words.
+ */
+
+var AES = (function () {
+ function AES(key) {
+ _classCallCheck(this, AES);
+
+ /**
+ * The expanded S-box and inverse S-box tables. These will be computed
+ * on the client so that we don't have to send them down the wire.
+ *
+ * There are two tables, _tables[0] is for encryption and
+ * _tables[1] is for decryption.
+ *
+ * The first 4 sub-tables are the expanded S-box with MixColumns. The
+ * last (_tables[01][4]) is the S-box itself.
+ *
+ * @private
+ */
+ // if we have yet to precompute the S-box tables
+ // do so now
+ if (!aesTables) {
+ aesTables = precompute();
+ }
+ // then make a copy of that object for use
+ this._tables = [[aesTables[0][0].slice(), aesTables[0][1].slice(), aesTables[0][2].slice(), aesTables[0][3].slice(), aesTables[0][4].slice()], [aesTables[1][0].slice(), aesTables[1][1].slice(), aesTables[1][2].slice(), aesTables[1][3].slice(), aesTables[1][4].slice()]];
+ var i = undefined;
+ var j = undefined;
+ var tmp = undefined;
+ var encKey = undefined;
+ var decKey = undefined;
+ var sbox = this._tables[0][4];
+ var decTable = this._tables[1];
+ var keyLen = key.length;
+ var rcon = 1;
+
+ if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
+ throw new Error('Invalid aes key size');
+ }
+
+ encKey = key.slice(0);
+ decKey = [];
+ this._key = [encKey, decKey];
+
+ // schedule encryption keys
+ for (i = keyLen; i < 4 * keyLen + 28; i++) {
+ tmp = encKey[i - 1];
+
+ // apply sbox
+ if (i % keyLen === 0 || keyLen === 8 && i % keyLen === 4) {
+ tmp = sbox[tmp >>> 24] << 24 ^ sbox[tmp >> 16 & 255] << 16 ^ sbox[tmp >> 8 & 255] << 8 ^ sbox[tmp & 255];
+
+ // shift rows and add rcon
+ if (i % keyLen === 0) {
+ tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
+ rcon = rcon << 1 ^ (rcon >> 7) * 283;
+ }
+ }
+
+ encKey[i] = encKey[i - keyLen] ^ tmp;
+ }
+
+ // schedule decryption keys
+ for (j = 0; i; j++, i--) {
+ tmp = encKey[j & 3 ? i : i - 4];
+ if (i <= 4 || j < 4) {
+ decKey[j] = tmp;
+ } else {
+ decKey[j] = decTable[0][sbox[tmp >>> 24]] ^ decTable[1][sbox[tmp >> 16 & 255]] ^ decTable[2][sbox[tmp >> 8 & 255]] ^ decTable[3][sbox[tmp & 255]];
+ }
+ }
+ }
+
+ /**
+ * Decrypt 16 bytes, specified as four 32-bit words.
+ *
+ * @param {Number} encrypted0 the first word to decrypt
+ * @param {Number} encrypted1 the second word to decrypt
+ * @param {Number} encrypted2 the third word to decrypt
+ * @param {Number} encrypted3 the fourth word to decrypt
+ * @param {Int32Array} out the array to write the decrypted words
+ * into
+ * @param {Number} offset the offset into the output array to start
+ * writing results
+ * @return {Array} The plaintext.
+ */
+
+ _createClass(AES, [{
+ key: 'decrypt',
+ value: function decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
+ var key = this._key[1];
+ // state variables a,b,c,d are loaded with pre-whitened data
+ var a = encrypted0 ^ key[0];
+ var b = encrypted3 ^ key[1];
+ var c = encrypted2 ^ key[2];
+ var d = encrypted1 ^ key[3];
+ var a2 = undefined;
+ var b2 = undefined;
+ var c2 = undefined;
+
+ // key.length === 2 ?
+ var nInnerRounds = key.length / 4 - 2;
+ var i = undefined;
+ var kIndex = 4;
+ var table = this._tables[1];
+
+ // load up the tables
+ var table0 = table[0];
+ var table1 = table[1];
+ var table2 = table[2];
+ var table3 = table[3];
+ var sbox = table[4];
+
+ // Inner rounds. Cribbed from OpenSSL.
+ for (i = 0; i < nInnerRounds; i++) {
+ a2 = table0[a >>> 24] ^ table1[b >> 16 & 255] ^ table2[c >> 8 & 255] ^ table3[d & 255] ^ key[kIndex];
+ b2 = table0[b >>> 24] ^ table1[c >> 16 & 255] ^ table2[d >> 8 & 255] ^ table3[a & 255] ^ key[kIndex + 1];
+ c2 = table0[c >>> 24] ^ table1[d >> 16 & 255] ^ table2[a >> 8 & 255] ^ table3[b & 255] ^ key[kIndex + 2];
+ d = table0[d >>> 24] ^ table1[a >> 16 & 255] ^ table2[b >> 8 & 255] ^ table3[c & 255] ^ key[kIndex + 3];
+ kIndex += 4;
+ a = a2;b = b2;c = c2;
+ }
+
+ // Last round.
+ for (i = 0; i < 4; i++) {
+ out[(3 & -i) + offset] = sbox[a >>> 24] << 24 ^ sbox[b >> 16 & 255] << 16 ^ sbox[c >> 8 & 255] << 8 ^ sbox[d & 255] ^ key[kIndex++];
+ a2 = a;a = b;b = c;c = d;d = a2;
+ }
+ }
+ }]);
+
+ return AES;
+})();
+
+exports['default'] = AES;
+module.exports = exports['default'];
+},{}],16:[function(require,module,exports){
+/**
+ * @file async-stream.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _stream = require('./stream');
+
+var _stream2 = _interopRequireDefault(_stream);
+
+/**
+ * A wrapper around the Stream class to use setTiemout
+ * and run stream "jobs" Asynchronously
+ *
+ * @class AsyncStream
+ * @extends Stream
+ */
+
+var AsyncStream = (function (_Stream) {
+ _inherits(AsyncStream, _Stream);
+
+ function AsyncStream() {
+ _classCallCheck(this, AsyncStream);
+
+ _get(Object.getPrototypeOf(AsyncStream.prototype), 'constructor', this).call(this, _stream2['default']);
+ this.jobs = [];
+ this.delay = 1;
+ this.timeout_ = null;
+ }
+
+ /**
+ * process an async job
+ *
+ * @private
+ */
+
+ _createClass(AsyncStream, [{
+ key: 'processJob_',
+ value: function processJob_() {
+ this.jobs.shift()();
+ if (this.jobs.length) {
+ this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
+ } else {
+ this.timeout_ = null;
+ }
+ }
+
+ /**
+ * push a job into the stream
+ *
+ * @param {Function} job the job to push into the stream
+ */
+ }, {
+ key: 'push',
+ value: function push(job) {
+ this.jobs.push(job);
+ if (!this.timeout_) {
+ this.timeout_ = setTimeout(this.processJob_.bind(this), this.delay);
+ }
+ }
+ }]);
+
+ return AsyncStream;
+})(_stream2['default']);
+
+exports['default'] = AsyncStream;
+module.exports = exports['default'];
+},{"./stream":19}],17:[function(require,module,exports){
+/**
+ * @file decrypter.js
+ *
+ * An asynchronous implementation of AES-128 CBC decryption with
+ * PKCS#7 padding.
+ */
+
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _aes = require('./aes');
+
+var _aes2 = _interopRequireDefault(_aes);
+
+var _asyncStream = require('./async-stream');
+
+var _asyncStream2 = _interopRequireDefault(_asyncStream);
+
+var _pkcs7 = require('pkcs7');
+
+/**
+ * Convert network-order (big-endian) bytes into their little-endian
+ * representation.
+ */
+var ntoh = function ntoh(word) {
+ return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
+};
+
+/**
+ * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
+ *
+ * @param {Uint8Array} encrypted the encrypted bytes
+ * @param {Uint32Array} key the bytes of the decryption key
+ * @param {Uint32Array} initVector the initialization vector (IV) to
+ * use for the first round of CBC.
+ * @return {Uint8Array} the decrypted bytes
+ *
+ * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
+ * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
+ * @see https://tools.ietf.org/html/rfc2315
+ */
+var decrypt = function decrypt(encrypted, key, initVector) {
+ // word-level access to the encrypted bytes
+ var encrypted32 = new Int32Array(encrypted.buffer, encrypted.byteOffset, encrypted.byteLength >> 2);
+
+ var decipher = new _aes2['default'](Array.prototype.slice.call(key));
+
+ // byte and word-level access for the decrypted output
+ var decrypted = new Uint8Array(encrypted.byteLength);
+ var decrypted32 = new Int32Array(decrypted.buffer);
+
+ // temporary variables for working with the IV, encrypted, and
+ // decrypted data
+ var init0 = undefined;
+ var init1 = undefined;
+ var init2 = undefined;
+ var init3 = undefined;
+ var encrypted0 = undefined;
+ var encrypted1 = undefined;
+ var encrypted2 = undefined;
+ var encrypted3 = undefined;
+
+ // iteration variable
+ var wordIx = undefined;
+
+ // pull out the words of the IV to ensure we don't modify the
+ // passed-in reference and easier access
+ init0 = initVector[0];
+ init1 = initVector[1];
+ init2 = initVector[2];
+ init3 = initVector[3];
+
+ // decrypt four word sequences, applying cipher-block chaining (CBC)
+ // to each decrypted block
+ for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
+ // convert big-endian (network order) words into little-endian
+ // (javascript order)
+ encrypted0 = ntoh(encrypted32[wordIx]);
+ encrypted1 = ntoh(encrypted32[wordIx + 1]);
+ encrypted2 = ntoh(encrypted32[wordIx + 2]);
+ encrypted3 = ntoh(encrypted32[wordIx + 3]);
+
+ // decrypt the block
+ decipher.decrypt(encrypted0, encrypted1, encrypted2, encrypted3, decrypted32, wordIx);
+
+ // XOR with the IV, and restore network byte-order to obtain the
+ // plaintext
+ decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
+ decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
+ decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
+ decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
+
+ // setup the IV for the next round
+ init0 = encrypted0;
+ init1 = encrypted1;
+ init2 = encrypted2;
+ init3 = encrypted3;
+ }
+
+ return decrypted;
+};
+
+exports.decrypt = decrypt;
+/**
+ * The `Decrypter` class that manages decryption of AES
+ * data through `AsyncStream` objects and the `decrypt`
+ * function
+ *
+ * @param {Uint8Array} encrypted the encrypted bytes
+ * @param {Uint32Array} key the bytes of the decryption key
+ * @param {Uint32Array} initVector the initialization vector (IV) to
+ * @param {Function} done the function to run when done
+ * @class Decrypter
+ */
+
+var Decrypter = (function () {
+ function Decrypter(encrypted, key, initVector, done) {
+ _classCallCheck(this, Decrypter);
+
+ var step = Decrypter.STEP;
+ var encrypted32 = new Int32Array(encrypted.buffer);
+ var decrypted = new Uint8Array(encrypted.byteLength);
+ var i = 0;
+
+ this.asyncStream_ = new _asyncStream2['default']();
+
+ // split up the encryption job and do the individual chunks asynchronously
+ this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
+ for (i = step; i < encrypted32.length; i += step) {
+ initVector = new Uint32Array([ntoh(encrypted32[i - 4]), ntoh(encrypted32[i - 3]), ntoh(encrypted32[i - 2]), ntoh(encrypted32[i - 1])]);
+ this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step), key, initVector, decrypted));
+ }
+ // invoke the done() callback when everything is finished
+ this.asyncStream_.push(function () {
+ // remove pkcs#7 padding from the decrypted bytes
+ done(null, (0, _pkcs7.unpad)(decrypted));
+ });
+ }
+
+ /**
+ * a getter for step the maximum number of bytes to process at one time
+ *
+ * @return {Number} the value of step 32000
+ */
+
+ _createClass(Decrypter, [{
+ key: 'decryptChunk_',
+
+ /**
+ * @private
+ */
+ value: function decryptChunk_(encrypted, key, initVector, decrypted) {
+ return function () {
+ var bytes = decrypt(encrypted, key, initVector);
+
+ decrypted.set(bytes, encrypted.byteOffset);
+ };
+ }
+ }], [{
+ key: 'STEP',
+ get: function get() {
+ // 4 * 8000;
+ return 32000;
+ }
+ }]);
+
+ return Decrypter;
+})();
+
+exports.Decrypter = Decrypter;
+exports['default'] = {
+ Decrypter: Decrypter,
+ decrypt: decrypt
+};
+},{"./aes":15,"./async-stream":16,"pkcs7":83}],18:[function(require,module,exports){
+/**
+ * @file index.js
+ *
+ * Index module to easily import the primary components of AES-128
+ * decryption. Like this:
+ *
+ * ```js
+ * import {Decrypter, decrypt, AsyncStream} from 'aes-decrypter';
+ * ```
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _decrypter = require('./decrypter');
+
+var _asyncStream = require('./async-stream');
+
+var _asyncStream2 = _interopRequireDefault(_asyncStream);
+
+exports['default'] = {
+ decrypt: _decrypter.decrypt,
+ Decrypter: _decrypter.Decrypter,
+ AsyncStream: _asyncStream2['default']
+};
+module.exports = exports['default'];
+},{"./async-stream":16,"./decrypter":17}],19:[function(require,module,exports){
+arguments[4][13][0].apply(exports,arguments)
+},{"dup":13}],20:[function(require,module,exports){
+
+},{}],21:[function(require,module,exports){
+var charenc = {
+ // UTF-8 encoding
+ utf8: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ return charenc.bin.stringToBytes(unescape(encodeURIComponent(str)));
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
+ }
+ },
+
+ // Binary encoding
+ bin: {
+ // Convert a string to a byte array
+ stringToBytes: function(str) {
+ for (var bytes = [], i = 0; i < str.length; i++)
+ bytes.push(str.charCodeAt(i) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a string
+ bytesToString: function(bytes) {
+ for (var str = [], i = 0; i < bytes.length; i++)
+ str.push(String.fromCharCode(bytes[i]));
+ return str.join('');
+ }
+ }
+};
+
+module.exports = charenc;
+
+},{}],22:[function(require,module,exports){
+(function() {
+ var base64map
+ = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
+
+ crypt = {
+ // Bit-wise rotation left
+ rotl: function(n, b) {
+ return (n << b) | (n >>> (32 - b));
+ },
+
+ // Bit-wise rotation right
+ rotr: function(n, b) {
+ return (n << (32 - b)) | (n >>> b);
+ },
+
+ // Swap big-endian to little-endian and vice versa
+ endian: function(n) {
+ // If number given, swap endian
+ if (n.constructor == Number) {
+ return crypt.rotl(n, 8) & 0x00FF00FF | crypt.rotl(n, 24) & 0xFF00FF00;
+ }
+
+ // Else, assume array and swap all items
+ for (var i = 0; i < n.length; i++)
+ n[i] = crypt.endian(n[i]);
+ return n;
+ },
+
+ // Generate an array of any length of random bytes
+ randomBytes: function(n) {
+ for (var bytes = []; n > 0; n--)
+ bytes.push(Math.floor(Math.random() * 256));
+ return bytes;
+ },
+
+ // Convert a byte array to big-endian 32-bit words
+ bytesToWords: function(bytes) {
+ for (var words = [], i = 0, b = 0; i < bytes.length; i++, b += 8)
+ words[b >>> 5] |= bytes[i] << (24 - b % 32);
+ return words;
+ },
+
+ // Convert big-endian 32-bit words to a byte array
+ wordsToBytes: function(words) {
+ for (var bytes = [], b = 0; b < words.length * 32; b += 8)
+ bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
+ return bytes;
+ },
+
+ // Convert a byte array to a hex string
+ bytesToHex: function(bytes) {
+ for (var hex = [], i = 0; i < bytes.length; i++) {
+ hex.push((bytes[i] >>> 4).toString(16));
+ hex.push((bytes[i] & 0xF).toString(16));
+ }
+ return hex.join('');
+ },
+
+ // Convert a hex string to a byte array
+ hexToBytes: function(hex) {
+ for (var bytes = [], c = 0; c < hex.length; c += 2)
+ bytes.push(parseInt(hex.substr(c, 2), 16));
+ return bytes;
+ },
+
+ // Convert a byte array to a base-64 string
+ bytesToBase64: function(bytes) {
+ for (var base64 = [], i = 0; i < bytes.length; i += 3) {
+ var triplet = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
+ for (var j = 0; j < 4; j++)
+ if (i * 8 + j * 6 <= bytes.length * 8)
+ base64.push(base64map.charAt((triplet >>> 6 * (3 - j)) & 0x3F));
+ else
+ base64.push('=');
+ }
+ return base64.join('');
+ },
+
+ // Convert a base-64 string to a byte array
+ base64ToBytes: function(base64) {
+ // Remove non-base-64 characters
+ base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');
+
+ for (var bytes = [], i = 0, imod4 = 0; i < base64.length;
+ imod4 = ++i % 4) {
+ if (imod4 == 0) continue;
+ bytes.push(((base64map.indexOf(base64.charAt(i - 1))
+ & (Math.pow(2, -2 * imod4 + 8) - 1)) << (imod4 * 2))
+ | (base64map.indexOf(base64.charAt(i)) >>> (6 - imod4 * 2)));
+ }
+ return bytes;
+ }
+ };
+
+ module.exports = crypt;
+})();
+
+},{}],23:[function(require,module,exports){
+(function (global){
+var topLevel = typeof global !== 'undefined' ? global :
+ typeof window !== 'undefined' ? window : {}
+var minDoc = require('min-document');
+
+if (typeof document !== 'undefined') {
+ module.exports = document;
+} else {
+ var doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+
+ if (!doccy) {
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
+ }
+
+ module.exports = doccy;
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"min-document":20}],24:[function(require,module,exports){
+/**
+ * Determine if an object is Buffer
+ *
+ * Author: Feross Aboukhadijeh
+ * License: MIT
+ *
+ * `npm install is-buffer`
+ */
+
+module.exports = function (obj) {
+ return !!(obj != null &&
+ (obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor)
+ (obj.constructor &&
+ typeof obj.constructor.isBuffer === 'function' &&
+ obj.constructor.isBuffer(obj))
+ ))
+}
+
+},{}],25:[function(require,module,exports){
+/** Used as the `TypeError` message for "Functions" methods. */
+var FUNC_ERROR_TEXT = 'Expected a function';
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeMax = Math.max;
+
+/**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * created function and arguments from `start` and beyond provided as an array.
+ *
+ * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.restParam(function(what, names) {
+ * return what + ' ' + _.initial(names).join(', ') +
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+function restParam(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
+ return function() {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ rest = Array(length);
+
+ while (++index < length) {
+ rest[index] = args[start + index];
+ }
+ switch (start) {
+ case 0: return func.call(this, rest);
+ case 1: return func.call(this, args[0], rest);
+ case 2: return func.call(this, args[0], args[1], rest);
+ }
+ var otherArgs = Array(start + 1);
+ index = -1;
+ while (++index < start) {
+ otherArgs[index] = args[index];
+ }
+ otherArgs[start] = rest;
+ return func.apply(this, otherArgs);
+ };
+}
+
+module.exports = restParam;
+
+},{}],26:[function(require,module,exports){
+/**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
+function arrayCopy(source, array) {
+ var index = -1,
+ length = source.length;
+
+ array || (array = Array(length));
+ while (++index < length) {
+ array[index] = source[index];
+ }
+ return array;
+}
+
+module.exports = arrayCopy;
+
+},{}],27:[function(require,module,exports){
+/**
+ * A specialized version of `_.forEach` for arrays without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
+function arrayEach(array, iteratee) {
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (iteratee(array[index], index, array) === false) {
+ break;
+ }
+ }
+ return array;
+}
+
+module.exports = arrayEach;
+
+},{}],28:[function(require,module,exports){
+/**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property names to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @returns {Object} Returns `object`.
+ */
+function baseCopy(source, props, object) {
+ object || (object = {});
+
+ var index = -1,
+ length = props.length;
+
+ while (++index < length) {
+ var key = props[index];
+ object[key] = source[key];
+ }
+ return object;
+}
+
+module.exports = baseCopy;
+
+},{}],29:[function(require,module,exports){
+var createBaseFor = require('./createBaseFor');
+
+/**
+ * The base implementation of `baseForIn` and `baseForOwn` which iterates
+ * over `object` properties returned by `keysFunc` invoking `iteratee` for
+ * each property. Iteratee functions may exit iteration early by explicitly
+ * returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
+var baseFor = createBaseFor();
+
+module.exports = baseFor;
+
+},{"./createBaseFor":36}],30:[function(require,module,exports){
+var baseFor = require('./baseFor'),
+ keysIn = require('../object/keysIn');
+
+/**
+ * The base implementation of `_.forIn` without support for callback
+ * shorthands and `this` binding.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+function baseForIn(object, iteratee) {
+ return baseFor(object, iteratee, keysIn);
+}
+
+module.exports = baseForIn;
+
+},{"../object/keysIn":57,"./baseFor":29}],31:[function(require,module,exports){
+var arrayEach = require('./arrayEach'),
+ baseMergeDeep = require('./baseMergeDeep'),
+ isArray = require('../lang/isArray'),
+ isArrayLike = require('./isArrayLike'),
+ isObject = require('../lang/isObject'),
+ isObjectLike = require('./isObjectLike'),
+ isTypedArray = require('../lang/isTypedArray'),
+ keys = require('../object/keys');
+
+/**
+ * The base implementation of `_.merge` without support for argument juggling,
+ * multiple sources, and `this` binding `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
+ * @returns {Object} Returns `object`.
+ */
+function baseMerge(object, source, customizer, stackA, stackB) {
+ if (!isObject(object)) {
+ return object;
+ }
+ var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
+ props = isSrcArr ? undefined : keys(source);
+
+ arrayEach(props || source, function(srcValue, key) {
+ if (props) {
+ key = srcValue;
+ srcValue = source[key];
+ }
+ if (isObjectLike(srcValue)) {
+ stackA || (stackA = []);
+ stackB || (stackB = []);
+ baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
+ }
+ else {
+ var value = object[key],
+ result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
+ isCommon = result === undefined;
+
+ if (isCommon) {
+ result = srcValue;
+ }
+ if ((result !== undefined || (isSrcArr && !(key in object))) &&
+ (isCommon || (result === result ? (result !== value) : (value === value)))) {
+ object[key] = result;
+ }
+ }
+ });
+ return object;
+}
+
+module.exports = baseMerge;
+
+},{"../lang/isArray":48,"../lang/isObject":51,"../lang/isTypedArray":54,"../object/keys":56,"./arrayEach":27,"./baseMergeDeep":32,"./isArrayLike":39,"./isObjectLike":44}],32:[function(require,module,exports){
+var arrayCopy = require('./arrayCopy'),
+ isArguments = require('../lang/isArguments'),
+ isArray = require('../lang/isArray'),
+ isArrayLike = require('./isArrayLike'),
+ isPlainObject = require('../lang/isPlainObject'),
+ isTypedArray = require('../lang/isTypedArray'),
+ toPlainObject = require('../lang/toPlainObject');
+
+/**
+ * A specialized version of `baseMerge` for arrays and objects which performs
+ * deep merges and tracks traversed objects enabling objects with circular
+ * references to be merged.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {string} key The key of the value to merge.
+ * @param {Function} mergeFunc The function to merge values.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
+function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
+ var length = stackA.length,
+ srcValue = source[key];
+
+ while (length--) {
+ if (stackA[length] == srcValue) {
+ object[key] = stackB[length];
+ return;
+ }
+ }
+ var value = object[key],
+ result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
+ isCommon = result === undefined;
+
+ if (isCommon) {
+ result = srcValue;
+ if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
+ result = isArray(value)
+ ? value
+ : (isArrayLike(value) ? arrayCopy(value) : []);
+ }
+ else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+ result = isArguments(value)
+ ? toPlainObject(value)
+ : (isPlainObject(value) ? value : {});
+ }
+ else {
+ isCommon = false;
+ }
+ }
+ // Add the source value to the stack of traversed objects and associate
+ // it with its merged value.
+ stackA.push(srcValue);
+ stackB.push(result);
+
+ if (isCommon) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
+ } else if (result === result ? (result !== value) : (value === value)) {
+ object[key] = result;
+ }
+}
+
+module.exports = baseMergeDeep;
+
+},{"../lang/isArguments":47,"../lang/isArray":48,"../lang/isPlainObject":52,"../lang/isTypedArray":54,"../lang/toPlainObject":55,"./arrayCopy":26,"./isArrayLike":39}],33:[function(require,module,exports){
+var toObject = require('./toObject');
+
+/**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new function.
+ */
+function baseProperty(key) {
+ return function(object) {
+ return object == null ? undefined : toObject(object)[key];
+ };
+}
+
+module.exports = baseProperty;
+
+},{"./toObject":46}],34:[function(require,module,exports){
+var identity = require('../utility/identity');
+
+/**
+ * A specialized version of `baseCallback` which only supports `this` binding
+ * and specifying the number of arguments to provide to `func`.
+ *
+ * @private
+ * @param {Function} func The function to bind.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {number} [argCount] The number of arguments to provide to `func`.
+ * @returns {Function} Returns the callback.
+ */
+function bindCallback(func, thisArg, argCount) {
+ if (typeof func != 'function') {
+ return identity;
+ }
+ if (thisArg === undefined) {
+ return func;
+ }
+ switch (argCount) {
+ case 1: return function(value) {
+ return func.call(thisArg, value);
+ };
+ case 3: return function(value, index, collection) {
+ return func.call(thisArg, value, index, collection);
+ };
+ case 4: return function(accumulator, value, index, collection) {
+ return func.call(thisArg, accumulator, value, index, collection);
+ };
+ case 5: return function(value, other, key, object, source) {
+ return func.call(thisArg, value, other, key, object, source);
+ };
+ }
+ return function() {
+ return func.apply(thisArg, arguments);
+ };
+}
+
+module.exports = bindCallback;
+
+},{"../utility/identity":60}],35:[function(require,module,exports){
+var bindCallback = require('./bindCallback'),
+ isIterateeCall = require('./isIterateeCall'),
+ restParam = require('../function/restParam');
+
+/**
+ * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
+function createAssigner(assigner) {
+ return restParam(function(object, sources) {
+ var index = -1,
+ length = object == null ? 0 : sources.length,
+ customizer = length > 2 ? sources[length - 2] : undefined,
+ guard = length > 2 ? sources[2] : undefined,
+ thisArg = length > 1 ? sources[length - 1] : undefined;
+
+ if (typeof customizer == 'function') {
+ customizer = bindCallback(customizer, thisArg, 5);
+ length -= 2;
+ } else {
+ customizer = typeof thisArg == 'function' ? thisArg : undefined;
+ length -= (customizer ? 1 : 0);
+ }
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined : customizer;
+ length = 1;
+ }
+ while (++index < length) {
+ var source = sources[index];
+ if (source) {
+ assigner(object, source, customizer);
+ }
+ }
+ return object;
+ });
+}
+
+module.exports = createAssigner;
+
+},{"../function/restParam":25,"./bindCallback":34,"./isIterateeCall":42}],36:[function(require,module,exports){
+var toObject = require('./toObject');
+
+/**
+ * Creates a base function for `_.forIn` or `_.forInRight`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
+function createBaseFor(fromRight) {
+ return function(object, iteratee, keysFunc) {
+ var iterable = toObject(object),
+ props = keysFunc(object),
+ length = props.length,
+ index = fromRight ? length : -1;
+
+ while ((fromRight ? index-- : ++index < length)) {
+ var key = props[index];
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
+ return object;
+ };
+}
+
+module.exports = createBaseFor;
+
+},{"./toObject":46}],37:[function(require,module,exports){
+var baseProperty = require('./baseProperty');
+
+/**
+ * Gets the "length" property value of `object`.
+ *
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {*} Returns the "length" value.
+ */
+var getLength = baseProperty('length');
+
+module.exports = getLength;
+
+},{"./baseProperty":33}],38:[function(require,module,exports){
+var isNative = require('../lang/isNative');
+
+/**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+function getNative(object, key) {
+ var value = object == null ? undefined : object[key];
+ return isNative(value) ? value : undefined;
+}
+
+module.exports = getNative;
+
+},{"../lang/isNative":50}],39:[function(require,module,exports){
+var getLength = require('./getLength'),
+ isLength = require('./isLength');
+
+/**
+ * Checks if `value` is array-like.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ */
+function isArrayLike(value) {
+ return value != null && isLength(getLength(value));
+}
+
+module.exports = isArrayLike;
+
+},{"./getLength":37,"./isLength":43}],40:[function(require,module,exports){
+/**
+ * Checks if `value` is a host object in IE < 9.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+ */
+var isHostObject = (function() {
+ try {
+ Object({ 'toString': 0 } + '');
+ } catch(e) {
+ return function() { return false; };
+ }
+ return function(value) {
+ // IE < 9 presents many host objects as `Object` objects that can coerce
+ // to strings despite having improperly defined `toString` methods.
+ return typeof value.toString != 'function' && typeof (value + '') == 'string';
+ };
+}());
+
+module.exports = isHostObject;
+
+},{}],41:[function(require,module,exports){
+/** Used to detect unsigned integer values. */
+var reIsUint = /^\d+$/;
+
+/**
+ * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
+ * of an array-like value.
+ */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
+function isIndex(value, length) {
+ value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
+ length = length == null ? MAX_SAFE_INTEGER : length;
+ return value > -1 && value % 1 == 0 && value < length;
+}
+
+module.exports = isIndex;
+
+},{}],42:[function(require,module,exports){
+var isArrayLike = require('./isArrayLike'),
+ isIndex = require('./isIndex'),
+ isObject = require('../lang/isObject');
+
+/**
+ * Checks if the provided arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
+ */
+function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
+ }
+ var type = typeof index;
+ if (type == 'number'
+ ? (isArrayLike(object) && isIndex(index, object.length))
+ : (type == 'string' && index in object)) {
+ var other = object[index];
+ return value === value ? (value === other) : (other !== other);
+ }
+ return false;
+}
+
+module.exports = isIterateeCall;
+
+},{"../lang/isObject":51,"./isArrayLike":39,"./isIndex":41}],43:[function(require,module,exports){
+/**
+ * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
+ * of an array-like value.
+ */
+var MAX_SAFE_INTEGER = 9007199254740991;
+
+/**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ */
+function isLength(value) {
+ return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+}
+
+module.exports = isLength;
+
+},{}],44:[function(require,module,exports){
+/**
+ * Checks if `value` is object-like.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ */
+function isObjectLike(value) {
+ return !!value && typeof value == 'object';
+}
+
+module.exports = isObjectLike;
+
+},{}],45:[function(require,module,exports){
+var isArguments = require('../lang/isArguments'),
+ isArray = require('../lang/isArray'),
+ isIndex = require('./isIndex'),
+ isLength = require('./isLength'),
+ isString = require('../lang/isString'),
+ keysIn = require('../object/keysIn');
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * A fallback implementation of `Object.keys` which creates an array of the
+ * own enumerable property names of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+function shimKeys(object) {
+ var props = keysIn(object),
+ propsLength = props.length,
+ length = propsLength && object.length;
+
+ var allowIndexes = !!length && isLength(length) &&
+ (isArray(object) || isArguments(object) || isString(object));
+
+ var index = -1,
+ result = [];
+
+ while (++index < propsLength) {
+ var key = props[index];
+ if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
+ result.push(key);
+ }
+ }
+ return result;
+}
+
+module.exports = shimKeys;
+
+},{"../lang/isArguments":47,"../lang/isArray":48,"../lang/isString":53,"../object/keysIn":57,"./isIndex":41,"./isLength":43}],46:[function(require,module,exports){
+var isObject = require('../lang/isObject'),
+ isString = require('../lang/isString'),
+ support = require('../support');
+
+/**
+ * Converts `value` to an object if it's not one.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {Object} Returns the object.
+ */
+function toObject(value) {
+ if (support.unindexedChars && isString(value)) {
+ var index = -1,
+ length = value.length,
+ result = Object(value);
+
+ while (++index < length) {
+ result[index] = value.charAt(index);
+ }
+ return result;
+ }
+ return isObject(value) ? value : Object(value);
+}
+
+module.exports = toObject;
+
+},{"../lang/isObject":51,"../lang/isString":53,"../support":59}],47:[function(require,module,exports){
+var isArrayLike = require('../internal/isArrayLike'),
+ isObjectLike = require('../internal/isObjectLike');
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/** Native method references. */
+var propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+/**
+ * Checks if `value` is classified as an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+function isArguments(value) {
+ return isObjectLike(value) && isArrayLike(value) &&
+ hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
+}
+
+module.exports = isArguments;
+
+},{"../internal/isArrayLike":39,"../internal/isObjectLike":44}],48:[function(require,module,exports){
+var getNative = require('../internal/getNative'),
+ isLength = require('../internal/isLength'),
+ isObjectLike = require('../internal/isObjectLike');
+
+/** `Object#toString` result references. */
+var arrayTag = '[object Array]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeIsArray = getNative(Array, 'isArray');
+
+/**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(function() { return arguments; }());
+ * // => false
+ */
+var isArray = nativeIsArray || function(value) {
+ return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
+};
+
+module.exports = isArray;
+
+},{"../internal/getNative":38,"../internal/isLength":43,"../internal/isObjectLike":44}],49:[function(require,module,exports){
+var isObject = require('./isObject');
+
+/** `Object#toString` result references. */
+var funcTag = '[object Function]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+ // The use of `Object#toString` avoids issues with the `typeof` operator
+ // in older versions of Chrome and Safari which return 'function' for regexes
+ // and Safari 8 which returns 'object' for typed array constructors.
+ return isObject(value) && objToString.call(value) == funcTag;
+}
+
+module.exports = isFunction;
+
+},{"./isObject":51}],50:[function(require,module,exports){
+var isFunction = require('./isFunction'),
+ isHostObject = require('../internal/isHostObject'),
+ isObjectLike = require('../internal/isObjectLike');
+
+/** Used to detect host constructors (Safari > 5). */
+var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to resolve the decompiled source of functions. */
+var fnToString = Function.prototype.toString;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/** Used to detect if a method is native. */
+var reIsNative = RegExp('^' +
+ fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
+ .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+);
+
+/**
+ * Checks if `value` is a native function.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
+ * @example
+ *
+ * _.isNative(Array.prototype.push);
+ * // => true
+ *
+ * _.isNative(_);
+ * // => false
+ */
+function isNative(value) {
+ if (value == null) {
+ return false;
+ }
+ if (isFunction(value)) {
+ return reIsNative.test(fnToString.call(value));
+ }
+ return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value);
+}
+
+module.exports = isNative;
+
+},{"../internal/isHostObject":40,"../internal/isObjectLike":44,"./isFunction":49}],51:[function(require,module,exports){
+/**
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+function isObject(value) {
+ // Avoid a V8 JIT bug in Chrome 19-20.
+ // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+ var type = typeof value;
+ return !!value && (type == 'object' || type == 'function');
+}
+
+module.exports = isObject;
+
+},{}],52:[function(require,module,exports){
+var baseForIn = require('../internal/baseForIn'),
+ isArguments = require('./isArguments'),
+ isHostObject = require('../internal/isHostObject'),
+ isObjectLike = require('../internal/isObjectLike'),
+ support = require('../support');
+
+/** `Object#toString` result references. */
+var objectTag = '[object Object]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * **Note:** This method assumes objects created by the `Object` constructor
+ * have no inherited enumerable properties.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
+function isPlainObject(value) {
+ var Ctor;
+
+ // Exit early for non `Object` objects.
+ if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
+ (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
+ return false;
+ }
+ // IE < 9 iterates inherited properties before own properties. If the first
+ // iterated property is an object's own property then there are no inherited
+ // enumerable properties.
+ var result;
+ if (support.ownLast) {
+ baseForIn(value, function(subValue, key, object) {
+ result = hasOwnProperty.call(object, key);
+ return false;
+ });
+ return result !== false;
+ }
+ // In most environments an object's own properties are iterated before
+ // its inherited properties. If the last iterated property is an object's
+ // own property then there are no inherited enumerable properties.
+ baseForIn(value, function(subValue, key) {
+ result = key;
+ });
+ return result === undefined || hasOwnProperty.call(value, result);
+}
+
+module.exports = isPlainObject;
+
+},{"../internal/baseForIn":30,"../internal/isHostObject":40,"../internal/isObjectLike":44,"../support":59,"./isArguments":47}],53:[function(require,module,exports){
+var isObjectLike = require('../internal/isObjectLike');
+
+/** `Object#toString` result references. */
+var stringTag = '[object String]';
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is classified as a `String` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isString('abc');
+ * // => true
+ *
+ * _.isString(1);
+ * // => false
+ */
+function isString(value) {
+ return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
+}
+
+module.exports = isString;
+
+},{"../internal/isObjectLike":44}],54:[function(require,module,exports){
+var isLength = require('../internal/isLength'),
+ isObjectLike = require('../internal/isObjectLike');
+
+/** `Object#toString` result references. */
+var argsTag = '[object Arguments]',
+ arrayTag = '[object Array]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ mapTag = '[object Map]',
+ numberTag = '[object Number]',
+ objectTag = '[object Object]',
+ regexpTag = '[object RegExp]',
+ setTag = '[object Set]',
+ stringTag = '[object String]',
+ weakMapTag = '[object WeakMap]';
+
+var arrayBufferTag = '[object ArrayBuffer]',
+ float32Tag = '[object Float32Array]',
+ float64Tag = '[object Float64Array]',
+ int8Tag = '[object Int8Array]',
+ int16Tag = '[object Int16Array]',
+ int32Tag = '[object Int32Array]',
+ uint8Tag = '[object Uint8Array]',
+ uint8ClampedTag = '[object Uint8ClampedArray]',
+ uint16Tag = '[object Uint16Array]',
+ uint32Tag = '[object Uint32Array]';
+
+/** Used to identify `toStringTag` values of typed arrays. */
+var typedArrayTags = {};
+typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+typedArrayTags[uint32Tag] = true;
+typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+typedArrayTags[dateTag] = typedArrayTags[errorTag] =
+typedArrayTags[funcTag] = typedArrayTags[mapTag] =
+typedArrayTags[numberTag] = typedArrayTags[objectTag] =
+typedArrayTags[regexpTag] = typedArrayTags[setTag] =
+typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
+
+/** Used for native method references. */
+var objectProto = Object.prototype;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
+function isTypedArray(value) {
+ return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
+}
+
+module.exports = isTypedArray;
+
+},{"../internal/isLength":43,"../internal/isObjectLike":44}],55:[function(require,module,exports){
+var baseCopy = require('../internal/baseCopy'),
+ keysIn = require('../object/keysIn');
+
+/**
+ * Converts `value` to a plain object flattening inherited enumerable
+ * properties of `value` to own properties of the plain object.
+ *
+ * @static
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Object} Returns the converted plain object.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.assign({ 'a': 1 }, new Foo);
+ * // => { 'a': 1, 'b': 2 }
+ *
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
+ */
+function toPlainObject(value) {
+ return baseCopy(value, keysIn(value));
+}
+
+module.exports = toPlainObject;
+
+},{"../internal/baseCopy":28,"../object/keysIn":57}],56:[function(require,module,exports){
+var getNative = require('../internal/getNative'),
+ isArrayLike = require('../internal/isArrayLike'),
+ isObject = require('../lang/isObject'),
+ shimKeys = require('../internal/shimKeys'),
+ support = require('../support');
+
+/* Native method references for those with the same name as other `lodash` methods. */
+var nativeKeys = getNative(Object, 'keys');
+
+/**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
+var keys = !nativeKeys ? shimKeys : function(object) {
+ var Ctor = object == null ? undefined : object.constructor;
+ if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
+ (typeof object == 'function' ? support.enumPrototypes : isArrayLike(object))) {
+ return shimKeys(object);
+ }
+ return isObject(object) ? nativeKeys(object) : [];
+};
+
+module.exports = keys;
+
+},{"../internal/getNative":38,"../internal/isArrayLike":39,"../internal/shimKeys":45,"../lang/isObject":51,"../support":59}],57:[function(require,module,exports){
+var arrayEach = require('../internal/arrayEach'),
+ isArguments = require('../lang/isArguments'),
+ isArray = require('../lang/isArray'),
+ isFunction = require('../lang/isFunction'),
+ isIndex = require('../internal/isIndex'),
+ isLength = require('../internal/isLength'),
+ isObject = require('../lang/isObject'),
+ isString = require('../lang/isString'),
+ support = require('../support');
+
+/** `Object#toString` result references. */
+var arrayTag = '[object Array]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ numberTag = '[object Number]',
+ objectTag = '[object Object]',
+ regexpTag = '[object RegExp]',
+ stringTag = '[object String]';
+
+/** Used to fix the JScript `[[DontEnum]]` bug. */
+var shadowProps = [
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'valueOf'
+];
+
+/** Used for native method references. */
+var errorProto = Error.prototype,
+ objectProto = Object.prototype,
+ stringProto = String.prototype;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objToString = objectProto.toString;
+
+/** Used to avoid iterating over non-enumerable properties in IE < 9. */
+var nonEnumProps = {};
+nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
+nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true };
+nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true };
+nonEnumProps[objectTag] = { 'constructor': true };
+
+arrayEach(shadowProps, function(key) {
+ for (var tag in nonEnumProps) {
+ if (hasOwnProperty.call(nonEnumProps, tag)) {
+ var props = nonEnumProps[tag];
+ props[key] = hasOwnProperty.call(props, key);
+ }
+ }
+});
+
+/**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
+function keysIn(object) {
+ if (object == null) {
+ return [];
+ }
+ if (!isObject(object)) {
+ object = Object(object);
+ }
+ var length = object.length;
+
+ length = (length && isLength(length) &&
+ (isArray(object) || isArguments(object) || isString(object)) && length) || 0;
+
+ var Ctor = object.constructor,
+ index = -1,
+ proto = (isFunction(Ctor) && Ctor.prototype) || objectProto,
+ isProto = proto === object,
+ result = Array(length),
+ skipIndexes = length > 0,
+ skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error),
+ skipProto = support.enumPrototypes && isFunction(object);
+
+ while (++index < length) {
+ result[index] = (index + '');
+ }
+ // lodash skips the `constructor` property when it infers it's iterating
+ // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]`
+ // attribute of an existing property and the `constructor` property of a
+ // prototype defaults to non-enumerable.
+ for (var key in object) {
+ if (!(skipProto && key == 'prototype') &&
+ !(skipErrorProps && (key == 'message' || key == 'name')) &&
+ !(skipIndexes && isIndex(key, length)) &&
+ !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
+ }
+ if (support.nonEnumShadows && object !== objectProto) {
+ var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)),
+ nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag];
+
+ if (tag == objectTag) {
+ proto = objectProto;
+ }
+ length = shadowProps.length;
+ while (length--) {
+ key = shadowProps[length];
+ var nonEnum = nonEnums[key];
+ if (!(isProto && nonEnum) &&
+ (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) {
+ result.push(key);
+ }
+ }
+ }
+ return result;
+}
+
+module.exports = keysIn;
+
+},{"../internal/arrayEach":27,"../internal/isIndex":41,"../internal/isLength":43,"../lang/isArguments":47,"../lang/isArray":48,"../lang/isFunction":49,"../lang/isObject":51,"../lang/isString":53,"../support":59}],58:[function(require,module,exports){
+var baseMerge = require('../internal/baseMerge'),
+ createAssigner = require('../internal/createAssigner');
+
+/**
+ * Recursively merges own enumerable properties of the source object(s), that
+ * don't resolve to `undefined` into the destination object. Subsequent sources
+ * overwrite property assignments of previous sources. If `customizer` is
+ * provided it's invoked to produce the merged values of the destination and
+ * source properties. If `customizer` returns `undefined` merging is handled
+ * by the method instead. The `customizer` is bound to `thisArg` and invoked
+ * with five arguments: (objectValue, sourceValue, key, object, source).
+ *
+ * @static
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {*} [thisArg] The `this` binding of `customizer`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var users = {
+ * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+ * };
+ *
+ * var ages = {
+ * 'data': [{ 'age': 36 }, { 'age': 40 }]
+ * };
+ *
+ * _.merge(users, ages);
+ * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+ *
+ * // using a customizer callback
+ * var object = {
+ * 'fruits': ['apple'],
+ * 'vegetables': ['beet']
+ * };
+ *
+ * var other = {
+ * 'fruits': ['banana'],
+ * 'vegetables': ['carrot']
+ * };
+ *
+ * _.merge(object, other, function(a, b) {
+ * if (_.isArray(a)) {
+ * return a.concat(b);
+ * }
+ * });
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+ */
+var merge = createAssigner(baseMerge);
+
+module.exports = merge;
+
+},{"../internal/baseMerge":31,"../internal/createAssigner":35}],59:[function(require,module,exports){
+/** Used for native method references. */
+var arrayProto = Array.prototype,
+ errorProto = Error.prototype,
+ objectProto = Object.prototype;
+
+/** Native method references. */
+var propertyIsEnumerable = objectProto.propertyIsEnumerable,
+ splice = arrayProto.splice;
+
+/**
+ * An object environment feature flags.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+var support = {};
+
+(function(x) {
+ var Ctor = function() { this.x = x; },
+ object = { '0': x, 'length': x },
+ props = [];
+
+ Ctor.prototype = { 'valueOf': x, 'y': x };
+ for (var key in new Ctor) { props.push(key); }
+
+ /**
+ * Detect if `name` or `message` properties of `Error.prototype` are
+ * enumerable by default (IE < 9, Safari < 5.1).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') ||
+ propertyIsEnumerable.call(errorProto, 'name');
+
+ /**
+ * Detect if `prototype` properties are enumerable by default.
+ *
+ * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ * (if the prototype or a property on the prototype has been set)
+ * incorrectly set the `[[Enumerable]]` value of a function's `prototype`
+ * property to `true`.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype');
+
+ /**
+ * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
+ *
+ * In IE < 9 an object's own properties, shadowing non-enumerable ones,
+ * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.nonEnumShadows = !/valueOf/.test(props);
+
+ /**
+ * Detect if own properties are iterated after inherited properties (IE < 9).
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.ownLast = props[0] != 'x';
+
+ /**
+ * Detect if `Array#shift` and `Array#splice` augment array-like objects
+ * correctly.
+ *
+ * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array
+ * `shift()` and `splice()` functions that fail to remove the last element,
+ * `value[0]`, of array-like objects even though the "length" property is
+ * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8,
+ * while `splice()` is buggy regardless of mode in IE < 9.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.spliceObjects = (splice.call(object, 0, 1), !object[0]);
+
+ /**
+ * Detect lack of support for accessing string characters by index.
+ *
+ * IE < 8 can't access characters by index. IE 8 can only access characters
+ * by index on string literals, not string objects.
+ *
+ * @memberOf _.support
+ * @type boolean
+ */
+ support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
+}(1, 0));
+
+module.exports = support;
+
+},{}],60:[function(require,module,exports){
+/**
+ * This method returns the first argument provided to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utility
+ * @param {*} value Any value.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * _.identity(object) === object;
+ * // => true
+ */
+function identity(value) {
+ return value;
+}
+
+module.exports = identity;
+
+},{}],61:[function(require,module,exports){
+/**
+ * @file m3u8/index.js
+ *
+ * Utilities for parsing M3U8 files. If the entire manifest is available,
+ * `Parser` will create an object representation with enough detail for managing
+ * playback. `ParseStream` and `LineStream` are lower-level parsing primitives
+ * that do not assume the entirety of the manifest is ready and expose a
+ * ReadableStream-like interface.
+ */
+
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _lineStream = require('./line-stream');
+
+var _lineStream2 = _interopRequireDefault(_lineStream);
+
+var _parseStream = require('./parse-stream');
+
+var _parseStream2 = _interopRequireDefault(_parseStream);
+
+var _parser = require('./parser');
+
+var _parser2 = _interopRequireDefault(_parser);
+
+exports['default'] = {
+ LineStream: _lineStream2['default'],
+ ParseStream: _parseStream2['default'],
+ Parser: _parser2['default']
+};
+module.exports = exports['default'];
+},{"./line-stream":62,"./parse-stream":63,"./parser":64}],62:[function(require,module,exports){
+/**
+ * @file m3u8/line-stream.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _stream = require('./stream');
+
+var _stream2 = _interopRequireDefault(_stream);
+
+/**
+ * A stream that buffers string input and generates a `data` event for each
+ * line.
+ *
+ * @class LineStream
+ * @extends Stream
+ */
+
+var LineStream = (function (_Stream) {
+ _inherits(LineStream, _Stream);
+
+ function LineStream() {
+ _classCallCheck(this, LineStream);
+
+ _get(Object.getPrototypeOf(LineStream.prototype), 'constructor', this).call(this);
+ this.buffer = '';
+ }
+
+ /**
+ * Add new data to be parsed.
+ *
+ * @param {String} data the text to process
+ */
+
+ _createClass(LineStream, [{
+ key: 'push',
+ value: function push(data) {
+ var nextNewline = undefined;
+
+ this.buffer += data;
+ nextNewline = this.buffer.indexOf('\n');
+
+ for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
+ this.trigger('data', this.buffer.substring(0, nextNewline));
+ this.buffer = this.buffer.substring(nextNewline + 1);
+ }
+ }
+ }]);
+
+ return LineStream;
+})(_stream2['default']);
+
+exports['default'] = LineStream;
+module.exports = exports['default'];
+},{"./stream":65}],63:[function(require,module,exports){
+/**
+ * @file m3u8/parse-stream.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _stream = require('./stream');
+
+var _stream2 = _interopRequireDefault(_stream);
+
+/**
+ * "forgiving" attribute list psuedo-grammar:
+ * attributes -> keyvalue (',' keyvalue)*
+ * keyvalue -> key '=' value
+ * key -> [^=]*
+ * value -> '"' [^"]* '"' | [^,]*
+ */
+var attributeSeparator = function attributeSeparator() {
+ var key = '[^=]*';
+ var value = '"[^"]*"|[^,]*';
+ var keyvalue = '(?:' + key + ')=(?:' + value + ')';
+
+ return new RegExp('(?:^|,)(' + keyvalue + ')');
+};
+
+/**
+ * Parse attributes from a line given the seperator
+ *
+ * @param {String} attributes the attibute line to parse
+ */
+var parseAttributes = function parseAttributes(attributes) {
+ // split the string using attributes as the separator
+ var attrs = attributes.split(attributeSeparator());
+ var i = attrs.length;
+ var result = {};
+ var attr = undefined;
+
+ while (i--) {
+ // filter out unmatched portions of the string
+ if (attrs[i] === '') {
+ continue;
+ }
+
+ // split the key and value
+ attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
+ // trim whitespace and remove optional quotes around the value
+ attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
+ attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
+ attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
+ result[attr[0]] = attr[1];
+ }
+ return result;
+};
+
+/**
+ * A line-level M3U8 parser event stream. It expects to receive input one
+ * line at a time and performs a context-free parse of its contents. A stream
+ * interpretation of a manifest can be useful if the manifest is expected to
+ * be too large to fit comfortably into memory or the entirety of the input
+ * is not immediately available. Otherwise, it's probably much easier to work
+ * with a regular `Parser` object.
+ *
+ * Produces `data` events with an object that captures the parser's
+ * interpretation of the input. That object has a property `tag` that is one
+ * of `uri`, `comment`, or `tag`. URIs only have a single additional
+ * property, `line`, which captures the entirety of the input without
+ * interpretation. Comments similarly have a single additional property
+ * `text` which is the input without the leading `#`.
+ *
+ * Tags always have a property `tagType` which is the lower-cased version of
+ * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
+ * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
+ * tags are given the tag type `unknown` and a single additional property
+ * `data` with the remainder of the input.
+ *
+ * @class ParseStream
+ * @extends Stream
+ */
+
+var ParseStream = (function (_Stream) {
+ _inherits(ParseStream, _Stream);
+
+ function ParseStream() {
+ _classCallCheck(this, ParseStream);
+
+ _get(Object.getPrototypeOf(ParseStream.prototype), 'constructor', this).call(this);
+ }
+
+ /**
+ * Parses an additional line of input.
+ *
+ * @param {String} line a single line of an M3U8 file to parse
+ */
+
+ _createClass(ParseStream, [{
+ key: 'push',
+ value: function push(line) {
+ var match = undefined;
+ var event = undefined;
+
+ // strip whitespace
+ line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
+ if (line.length === 0) {
+ // ignore empty lines
+ return;
+ }
+
+ // URIs
+ if (line[0] !== '#') {
+ this.trigger('data', {
+ type: 'uri',
+ uri: line
+ });
+ return;
+ }
+
+ // Comments
+ if (line.indexOf('#EXT') !== 0) {
+ this.trigger('data', {
+ type: 'comment',
+ text: line.slice(1)
+ });
+ return;
+ }
+
+ // strip off any carriage returns here so the regex matching
+ // doesn't have to account for them.
+ line = line.replace('\r', '');
+
+ // Tags
+ match = /^#EXTM3U/.exec(line);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'm3u'
+ });
+ return;
+ }
+ match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'inf'
+ };
+ if (match[1]) {
+ event.duration = parseFloat(match[1]);
+ }
+ if (match[2]) {
+ event.title = match[2];
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'targetduration'
+ };
+ if (match[1]) {
+ event.duration = parseInt(match[1], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'totalduration'
+ };
+ if (match[1]) {
+ event.duration = parseInt(match[1], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'version'
+ };
+ if (match[1]) {
+ event.version = parseInt(match[1], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'media-sequence'
+ };
+ if (match[1]) {
+ event.number = parseInt(match[1], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'discontinuity-sequence'
+ };
+ if (match[1]) {
+ event.number = parseInt(match[1], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'playlist-type'
+ };
+ if (match[1]) {
+ event.playlistType = match[1];
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'byterange'
+ };
+ if (match[1]) {
+ event.length = parseInt(match[1], 10);
+ }
+ if (match[2]) {
+ event.offset = parseInt(match[2], 10);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'allow-cache'
+ };
+ if (match[1]) {
+ event.allowed = !/NO/.test(match[1]);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-MAP:?(.*)$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'map'
+ };
+
+ if (match[1]) {
+ var attributes = parseAttributes(match[1]);
+
+ if (attributes.URI) {
+ event.uri = attributes.URI;
+ }
+ if (attributes.BYTERANGE) {
+ var _attributes$BYTERANGE$split = attributes.BYTERANGE.split('@');
+
+ var _attributes$BYTERANGE$split2 = _slicedToArray(_attributes$BYTERANGE$split, 2);
+
+ var _length = _attributes$BYTERANGE$split2[0];
+ var offset = _attributes$BYTERANGE$split2[1];
+
+ event.byterange = {};
+ if (_length) {
+ event.byterange.length = parseInt(_length, 10);
+ }
+ if (offset) {
+ event.byterange.offset = parseInt(offset, 10);
+ }
+ }
+ }
+
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'stream-inf'
+ };
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]);
+
+ if (event.attributes.RESOLUTION) {
+ var split = event.attributes.RESOLUTION.split('x');
+ var resolution = {};
+
+ if (split[0]) {
+ resolution.width = parseInt(split[0], 10);
+ }
+ if (split[1]) {
+ resolution.height = parseInt(split[1], 10);
+ }
+ event.attributes.RESOLUTION = resolution;
+ }
+ if (event.attributes.BANDWIDTH) {
+ event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
+ }
+ if (event.attributes['PROGRAM-ID']) {
+ event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
+ }
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'media'
+ };
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-ENDLIST/.exec(line);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'endlist'
+ });
+ return;
+ }
+ match = /^#EXT-X-DISCONTINUITY/.exec(line);
+ if (match) {
+ this.trigger('data', {
+ type: 'tag',
+ tagType: 'discontinuity'
+ });
+ return;
+ }
+ match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'date-time'
+ };
+ if (match[1]) {
+ event.dateTimeString = match[1];
+ event.dateTimeObject = new Date(match[1]);
+ }
+ this.trigger('data', event);
+ return;
+ }
+ match = /^#EXT-X-KEY:?(.*)$/.exec(line);
+ if (match) {
+ event = {
+ type: 'tag',
+ tagType: 'key'
+ };
+ if (match[1]) {
+ event.attributes = parseAttributes(match[1]);
+ // parse the IV string into a Uint32Array
+ if (event.attributes.IV) {
+ if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
+ event.attributes.IV = event.attributes.IV.substring(2);
+ }
+
+ event.attributes.IV = event.attributes.IV.match(/.{8}/g);
+ event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
+ event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
+ event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
+ event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
+ event.attributes.IV = new Uint32Array(event.attributes.IV);
+ }
+ }
+ this.trigger('data', event);
+ return;
+ }
+
+ // unknown tag type
+ this.trigger('data', {
+ type: 'tag',
+ data: line.slice(4, line.length)
+ });
+ }
+ }]);
+
+ return ParseStream;
+})(_stream2['default']);
+
+exports['default'] = ParseStream;
+module.exports = exports['default'];
+},{"./stream":65}],64:[function(require,module,exports){
+/**
+ * @file m3u8/parser.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _stream = require('./stream');
+
+var _stream2 = _interopRequireDefault(_stream);
+
+var _lineStream = require('./line-stream');
+
+var _lineStream2 = _interopRequireDefault(_lineStream);
+
+var _parseStream = require('./parse-stream');
+
+var _parseStream2 = _interopRequireDefault(_parseStream);
+
+var _lodashCompatObjectMerge = require('lodash-compat/object/merge');
+
+var _lodashCompatObjectMerge2 = _interopRequireDefault(_lodashCompatObjectMerge);
+
+/**
+ * A parser for M3U8 files. The current interpretation of the input is
+ * exposed as a property `manifest` on parser objects. It's just two lines to
+ * create and parse a manifest once you have the contents available as a string:
+ *
+ * ```js
+ * var parser = new m3u8.Parser();
+ * parser.push(xhr.responseText);
+ * ```
+ *
+ * New input can later be applied to update the manifest object by calling
+ * `push` again.
+ *
+ * The parser attempts to create a usable manifest object even if the
+ * underlying input is somewhat nonsensical. It emits `info` and `warning`
+ * events during the parse if it encounters input that seems invalid or
+ * requires some property of the manifest object to be defaulted.
+ *
+ * @class Parser
+ * @extends Stream
+ */
+
+var Parser = (function (_Stream) {
+ _inherits(Parser, _Stream);
+
+ function Parser() {
+ _classCallCheck(this, Parser);
+
+ _get(Object.getPrototypeOf(Parser.prototype), 'constructor', this).call(this);
+ this.lineStream = new _lineStream2['default']();
+ this.parseStream = new _parseStream2['default']();
+ this.lineStream.pipe(this.parseStream);
+ /* eslint-disable consistent-this */
+ var self = this;
+ /* eslint-enable consistent-this */
+ var uris = [];
+ var currentUri = {};
+ // if specified, the active EXT-X-MAP definition
+ var currentMap = undefined;
+ // if specified, the active decryption key
+ var _key = undefined;
+ var noop = function noop() {};
+ var defaultMediaGroups = {
+ 'AUDIO': {},
+ 'VIDEO': {},
+ 'CLOSED-CAPTIONS': {},
+ 'SUBTITLES': {}
+ };
+ // group segments into numbered timelines delineated by discontinuities
+ var currentTimeline = 0;
+
+ // the manifest is empty until the parse stream begins delivering data
+ this.manifest = {
+ allowCache: true,
+ discontinuityStarts: []
+ };
+
+ // update the manifest with the m3u8 entry from the parse stream
+ this.parseStream.on('data', function (entry) {
+ var mediaGroup = undefined;
+ var rendition = undefined;
+
+ ({
+ tag: function tag() {
+ // switch based on the tag type
+ (({
+ 'allow-cache': function allowCache() {
+ this.manifest.allowCache = entry.allowed;
+ if (!('allowed' in entry)) {
+ this.trigger('info', {
+ message: 'defaulting allowCache to YES'
+ });
+ this.manifest.allowCache = true;
+ }
+ },
+ byterange: function byterange() {
+ var byterange = {};
+
+ if ('length' in entry) {
+ currentUri.byterange = byterange;
+ byterange.length = entry.length;
+
+ if (!('offset' in entry)) {
+ this.trigger('info', {
+ message: 'defaulting offset to zero'
+ });
+ entry.offset = 0;
+ }
+ }
+ if ('offset' in entry) {
+ currentUri.byterange = byterange;
+ byterange.offset = entry.offset;
+ }
+ },
+ endlist: function endlist() {
+ this.manifest.endList = true;
+ },
+ inf: function inf() {
+ if (!('mediaSequence' in this.manifest)) {
+ this.manifest.mediaSequence = 0;
+ this.trigger('info', {
+ message: 'defaulting media sequence to zero'
+ });
+ }
+ if (!('discontinuitySequence' in this.manifest)) {
+ this.manifest.discontinuitySequence = 0;
+ this.trigger('info', {
+ message: 'defaulting discontinuity sequence to zero'
+ });
+ }
+ if (entry.duration > 0) {
+ currentUri.duration = entry.duration;
+ }
+
+ if (entry.duration === 0) {
+ currentUri.duration = 0.01;
+ this.trigger('info', {
+ message: 'updating zero segment duration to a small value'
+ });
+ }
+
+ this.manifest.segments = uris;
+ },
+ key: function key() {
+ if (!entry.attributes) {
+ this.trigger('warn', {
+ message: 'ignoring key declaration without attribute list'
+ });
+ return;
+ }
+ // clear the active encryption key
+ if (entry.attributes.METHOD === 'NONE') {
+ _key = null;
+ return;
+ }
+ if (!entry.attributes.URI) {
+ this.trigger('warn', {
+ message: 'ignoring key declaration without URI'
+ });
+ return;
+ }
+ if (!entry.attributes.METHOD) {
+ this.trigger('warn', {
+ message: 'defaulting key method to AES-128'
+ });
+ }
+
+ // setup an encryption key for upcoming segments
+ _key = {
+ method: entry.attributes.METHOD || 'AES-128',
+ uri: entry.attributes.URI
+ };
+
+ if (typeof entry.attributes.IV !== 'undefined') {
+ _key.iv = entry.attributes.IV;
+ }
+ },
+ 'media-sequence': function mediaSequence() {
+ if (!isFinite(entry.number)) {
+ this.trigger('warn', {
+ message: 'ignoring invalid media sequence: ' + entry.number
+ });
+ return;
+ }
+ this.manifest.mediaSequence = entry.number;
+ },
+ 'discontinuity-sequence': function discontinuitySequence() {
+ if (!isFinite(entry.number)) {
+ this.trigger('warn', {
+ message: 'ignoring invalid discontinuity sequence: ' + entry.number
+ });
+ return;
+ }
+ this.manifest.discontinuitySequence = entry.number;
+ currentTimeline = entry.number;
+ },
+ 'playlist-type': function playlistType() {
+ if (!/VOD|EVENT/.test(entry.playlistType)) {
+ this.trigger('warn', {
+ message: 'ignoring unknown playlist type: ' + entry.playlist
+ });
+ return;
+ }
+ this.manifest.playlistType = entry.playlistType;
+ },
+ map: function map() {
+ currentMap = {};
+ if (entry.uri) {
+ currentMap.uri = entry.uri;
+ }
+ if (entry.byterange) {
+ currentMap.byterange = entry.byterange;
+ }
+ },
+ 'stream-inf': function streamInf() {
+ this.manifest.playlists = uris;
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
+
+ if (!entry.attributes) {
+ this.trigger('warn', {
+ message: 'ignoring empty stream-inf attributes'
+ });
+ return;
+ }
+
+ if (!currentUri.attributes) {
+ currentUri.attributes = {};
+ }
+ currentUri.attributes = (0, _lodashCompatObjectMerge2['default'])(currentUri.attributes, entry.attributes);
+ },
+ media: function media() {
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
+
+ if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
+ this.trigger('warn', {
+ message: 'ignoring incomplete or missing media group'
+ });
+ return;
+ }
+
+ // find the media group, creating defaults as necessary
+ var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
+
+ mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
+ mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
+
+ // collect the rendition metadata
+ rendition = {
+ 'default': /yes/i.test(entry.attributes.DEFAULT)
+ };
+ if (rendition['default']) {
+ rendition.autoselect = true;
+ } else {
+ rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
+ }
+ if (entry.attributes.LANGUAGE) {
+ rendition.language = entry.attributes.LANGUAGE;
+ }
+ if (entry.attributes.URI) {
+ rendition.uri = entry.attributes.URI;
+ }
+ if (entry.attributes['INSTREAM-ID']) {
+ rendition.instreamId = entry.attributes['INSTREAM-ID'];
+ }
+
+ // insert the new rendition
+ mediaGroup[entry.attributes.NAME] = rendition;
+ },
+ discontinuity: function discontinuity() {
+ currentTimeline += 1;
+ currentUri.discontinuity = true;
+ this.manifest.discontinuityStarts.push(uris.length);
+ },
+ 'date-time': function dateTime() {
+ this.manifest.dateTimeString = entry.dateTimeString;
+ this.manifest.dateTimeObject = entry.dateTimeObject;
+ },
+ targetduration: function targetduration() {
+ if (!isFinite(entry.duration) || entry.duration < 0) {
+ this.trigger('warn', {
+ message: 'ignoring invalid target duration: ' + entry.duration
+ });
+ return;
+ }
+ this.manifest.targetDuration = entry.duration;
+ },
+ totalduration: function totalduration() {
+ if (!isFinite(entry.duration) || entry.duration < 0) {
+ this.trigger('warn', {
+ message: 'ignoring invalid total duration: ' + entry.duration
+ });
+ return;
+ }
+ this.manifest.totalDuration = entry.duration;
+ }
+ })[entry.tagType] || noop).call(self);
+ },
+ uri: function uri() {
+ currentUri.uri = entry.uri;
+ uris.push(currentUri);
+
+ // if no explicit duration was declared, use the target duration
+ if (this.manifest.targetDuration && !('duration' in currentUri)) {
+ this.trigger('warn', {
+ message: 'defaulting segment duration to the target duration'
+ });
+ currentUri.duration = this.manifest.targetDuration;
+ }
+ // annotate with encryption information, if necessary
+ if (_key) {
+ currentUri.key = _key;
+ }
+ currentUri.timeline = currentTimeline;
+ // annotate with initialization segment information, if necessary
+ if (currentMap) {
+ currentUri.map = currentMap;
+ }
+
+ // prepare for the next URI
+ currentUri = {};
+ },
+ comment: function comment() {
+ // comments are not important for playback
+ }
+ })[entry.type].call(self);
+ });
+ }
+
+ /**
+ * Parse the input string and update the manifest object.
+ *
+ * @param {String} chunk a potentially incomplete portion of the manifest
+ */
+
+ _createClass(Parser, [{
+ key: 'push',
+ value: function push(chunk) {
+ this.lineStream.push(chunk);
+ }
+
+ /**
+ * Flush any remaining input. This can be handy if the last line of an M3U8
+ * manifest did not contain a trailing newline but the file has been
+ * completely received.
+ */
+ }, {
+ key: 'end',
+ value: function end() {
+ // flush any buffered input
+ this.lineStream.push('\n');
+ }
+ }]);
+
+ return Parser;
+})(_stream2['default']);
+
+exports['default'] = Parser;
+module.exports = exports['default'];
+},{"./line-stream":62,"./parse-stream":63,"./stream":65,"lodash-compat/object/merge":58}],65:[function(require,module,exports){
+arguments[4][13][0].apply(exports,arguments)
+},{"dup":13}],66:[function(require,module,exports){
+(function(){
+ var crypt = require('crypt'),
+ utf8 = require('charenc').utf8,
+ isBuffer = require('is-buffer'),
+ bin = require('charenc').bin,
+
+ // The core
+ md5 = function (message, options) {
+ // Convert to byte array
+ if (message.constructor == String)
+ if (options && options.encoding === 'binary')
+ message = bin.stringToBytes(message);
+ else
+ message = utf8.stringToBytes(message);
+ else if (isBuffer(message))
+ message = Array.prototype.slice.call(message, 0);
+ else if (!Array.isArray(message))
+ message = message.toString();
+ // else, assume byte array already
+
+ var m = crypt.bytesToWords(message),
+ l = message.length * 8,
+ a = 1732584193,
+ b = -271733879,
+ c = -1732584194,
+ d = 271733878;
+
+ // Swap endian
+ for (var i = 0; i < m.length; i++) {
+ m[i] = ((m[i] << 8) | (m[i] >>> 24)) & 0x00FF00FF |
+ ((m[i] << 24) | (m[i] >>> 8)) & 0xFF00FF00;
+ }
+
+ // Padding
+ m[l >>> 5] |= 0x80 << (l % 32);
+ m[(((l + 64) >>> 9) << 4) + 14] = l;
+
+ // Method shortcuts
+ var FF = md5._ff,
+ GG = md5._gg,
+ HH = md5._hh,
+ II = md5._ii;
+
+ for (var i = 0; i < m.length; i += 16) {
+
+ var aa = a,
+ bb = b,
+ cc = c,
+ dd = d;
+
+ a = FF(a, b, c, d, m[i+ 0], 7, -680876936);
+ d = FF(d, a, b, c, m[i+ 1], 12, -389564586);
+ c = FF(c, d, a, b, m[i+ 2], 17, 606105819);
+ b = FF(b, c, d, a, m[i+ 3], 22, -1044525330);
+ a = FF(a, b, c, d, m[i+ 4], 7, -176418897);
+ d = FF(d, a, b, c, m[i+ 5], 12, 1200080426);
+ c = FF(c, d, a, b, m[i+ 6], 17, -1473231341);
+ b = FF(b, c, d, a, m[i+ 7], 22, -45705983);
+ a = FF(a, b, c, d, m[i+ 8], 7, 1770035416);
+ d = FF(d, a, b, c, m[i+ 9], 12, -1958414417);
+ c = FF(c, d, a, b, m[i+10], 17, -42063);
+ b = FF(b, c, d, a, m[i+11], 22, -1990404162);
+ a = FF(a, b, c, d, m[i+12], 7, 1804603682);
+ d = FF(d, a, b, c, m[i+13], 12, -40341101);
+ c = FF(c, d, a, b, m[i+14], 17, -1502002290);
+ b = FF(b, c, d, a, m[i+15], 22, 1236535329);
+
+ a = GG(a, b, c, d, m[i+ 1], 5, -165796510);
+ d = GG(d, a, b, c, m[i+ 6], 9, -1069501632);
+ c = GG(c, d, a, b, m[i+11], 14, 643717713);
+ b = GG(b, c, d, a, m[i+ 0], 20, -373897302);
+ a = GG(a, b, c, d, m[i+ 5], 5, -701558691);
+ d = GG(d, a, b, c, m[i+10], 9, 38016083);
+ c = GG(c, d, a, b, m[i+15], 14, -660478335);
+ b = GG(b, c, d, a, m[i+ 4], 20, -405537848);
+ a = GG(a, b, c, d, m[i+ 9], 5, 568446438);
+ d = GG(d, a, b, c, m[i+14], 9, -1019803690);
+ c = GG(c, d, a, b, m[i+ 3], 14, -187363961);
+ b = GG(b, c, d, a, m[i+ 8], 20, 1163531501);
+ a = GG(a, b, c, d, m[i+13], 5, -1444681467);
+ d = GG(d, a, b, c, m[i+ 2], 9, -51403784);
+ c = GG(c, d, a, b, m[i+ 7], 14, 1735328473);
+ b = GG(b, c, d, a, m[i+12], 20, -1926607734);
+
+ a = HH(a, b, c, d, m[i+ 5], 4, -378558);
+ d = HH(d, a, b, c, m[i+ 8], 11, -2022574463);
+ c = HH(c, d, a, b, m[i+11], 16, 1839030562);
+ b = HH(b, c, d, a, m[i+14], 23, -35309556);
+ a = HH(a, b, c, d, m[i+ 1], 4, -1530992060);
+ d = HH(d, a, b, c, m[i+ 4], 11, 1272893353);
+ c = HH(c, d, a, b, m[i+ 7], 16, -155497632);
+ b = HH(b, c, d, a, m[i+10], 23, -1094730640);
+ a = HH(a, b, c, d, m[i+13], 4, 681279174);
+ d = HH(d, a, b, c, m[i+ 0], 11, -358537222);
+ c = HH(c, d, a, b, m[i+ 3], 16, -722521979);
+ b = HH(b, c, d, a, m[i+ 6], 23, 76029189);
+ a = HH(a, b, c, d, m[i+ 9], 4, -640364487);
+ d = HH(d, a, b, c, m[i+12], 11, -421815835);
+ c = HH(c, d, a, b, m[i+15], 16, 530742520);
+ b = HH(b, c, d, a, m[i+ 2], 23, -995338651);
+
+ a = II(a, b, c, d, m[i+ 0], 6, -198630844);
+ d = II(d, a, b, c, m[i+ 7], 10, 1126891415);
+ c = II(c, d, a, b, m[i+14], 15, -1416354905);
+ b = II(b, c, d, a, m[i+ 5], 21, -57434055);
+ a = II(a, b, c, d, m[i+12], 6, 1700485571);
+ d = II(d, a, b, c, m[i+ 3], 10, -1894986606);
+ c = II(c, d, a, b, m[i+10], 15, -1051523);
+ b = II(b, c, d, a, m[i+ 1], 21, -2054922799);
+ a = II(a, b, c, d, m[i+ 8], 6, 1873313359);
+ d = II(d, a, b, c, m[i+15], 10, -30611744);
+ c = II(c, d, a, b, m[i+ 6], 15, -1560198380);
+ b = II(b, c, d, a, m[i+13], 21, 1309151649);
+ a = II(a, b, c, d, m[i+ 4], 6, -145523070);
+ d = II(d, a, b, c, m[i+11], 10, -1120210379);
+ c = II(c, d, a, b, m[i+ 2], 15, 718787259);
+ b = II(b, c, d, a, m[i+ 9], 21, -343485551);
+
+ a = (a + aa) >>> 0;
+ b = (b + bb) >>> 0;
+ c = (c + cc) >>> 0;
+ d = (d + dd) >>> 0;
+ }
+
+ return crypt.endian([a, b, c, d]);
+ };
+
+ // Auxiliary functions
+ md5._ff = function (a, b, c, d, x, s, t) {
+ var n = a + (b & c | ~b & d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._gg = function (a, b, c, d, x, s, t) {
+ var n = a + (b & d | c & ~d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._hh = function (a, b, c, d, x, s, t) {
+ var n = a + (b ^ c ^ d) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+ md5._ii = function (a, b, c, d, x, s, t) {
+ var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
+ return ((n << s) | (n >>> (32 - s))) + b;
+ };
+
+ // Package private blocksize
+ md5._blocksize = 16;
+ md5._digestsize = 16;
+
+ module.exports = function (message, options) {
+ if(typeof message == 'undefined')
+ return;
+
+ var digestbytes = crypt.wordsToBytes(md5(message, options));
+ return options && options.asBytes ? digestbytes :
+ options && options.asString ? bin.bytesToString(digestbytes) :
+ crypt.bytesToHex(digestbytes);
+ };
+
+})();
+
+},{"charenc":21,"crypt":22,"is-buffer":24}],67:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2016 Brightcove
+ * All rights reserved.
+ *
+ * A stream-based aac to mp4 converter. This utility can be used to
+ * deliver mp4s to a SourceBuffer on platforms that support native
+ * Media Source Extensions.
+ */
+'use strict';
+var Stream = require('../utils/stream.js');
+
+// Constants
+var AacStream;
+
+/**
+ * Splits an incoming stream of binary data into ADTS and ID3 Frames.
+ */
+
+AacStream = function() {
+ var
+ everything = new Uint8Array(),
+ receivedTimeStamp = false,
+ timeStamp = 0;
+
+ AacStream.prototype.init.call(this);
+
+ this.setTimestamp = function (timestamp) {
+ timeStamp = timestamp;
+ };
+
+ this.parseId3TagSize = function(header, byteIndex) {
+ var
+ returnSize = (header[byteIndex + 6] << 21) |
+ (header[byteIndex + 7] << 14) |
+ (header[byteIndex + 8] << 7) |
+ (header[byteIndex + 9]),
+ flags = header[byteIndex + 5],
+ footerPresent = (flags & 16) >> 4;
+
+ if (footerPresent) {
+ return returnSize + 20;
+ }
+ return returnSize + 10;
+ };
+
+ this.parseAdtsSize = function(header, byteIndex) {
+ var
+ lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
+ middle = header[byteIndex + 4] << 3,
+ highTwo = header[byteIndex + 3] & 0x3 << 11;
+
+ return (highTwo | middle) | lowThree;
+ };
+
+ this.push = function(bytes) {
+ var
+ frameSize = 0,
+ byteIndex = 0,
+ bytesLeft,
+ chunk,
+ packet,
+ tempLength;
+
+ // If there are bytes remaining from the last segment, prepend them to the
+ // bytes that were pushed in
+ if (everything.length) {
+ tempLength = everything.length;
+ everything = new Uint8Array(bytes.byteLength + tempLength);
+ everything.set(everything.subarray(0, tempLength));
+ everything.set(bytes, tempLength);
+ } else {
+ everything = bytes;
+ }
+
+ while (everything.length - byteIndex >= 3) {
+ if ((everything[byteIndex] === 'I'.charCodeAt(0)) &&
+ (everything[byteIndex + 1] === 'D'.charCodeAt(0)) &&
+ (everything[byteIndex + 2] === '3'.charCodeAt(0))) {
+
+ // Exit early because we don't have enough to parse
+ // the ID3 tag header
+ if (everything.length - byteIndex < 10) {
+ break;
+ }
+
+ // check framesize
+ frameSize = this.parseId3TagSize(everything, byteIndex);
+
+ // Exit early if we don't have enough in the buffer
+ // to emit a full packet
+ if (frameSize > everything.length) {
+ break;
+ }
+ chunk = {
+ type: 'timed-metadata',
+ data: everything.subarray(byteIndex, byteIndex + frameSize)
+ };
+ this.trigger('data', chunk);
+ byteIndex += frameSize;
+ continue;
+ } else if ((everything[byteIndex] & 0xff === 0xff) &&
+ ((everything[byteIndex + 1] & 0xf0) === 0xf0)) {
+
+ // Exit early because we don't have enough to parse
+ // the ADTS frame header
+ if (everything.length - byteIndex < 7) {
+ break;
+ }
+
+ frameSize = this.parseAdtsSize(everything, byteIndex);
+
+ // Exit early if we don't have enough in the buffer
+ // to emit a full packet
+ if (frameSize > everything.length) {
+ break;
+ }
+
+ packet = {
+ type: 'audio',
+ data: everything.subarray(byteIndex, byteIndex + frameSize),
+ pts: timeStamp,
+ dts: timeStamp,
+ };
+ this.trigger('data', packet);
+ byteIndex += frameSize;
+ continue;
+ }
+ byteIndex++;
+ }
+ bytesLeft = everything.length - byteIndex;
+
+ if (bytesLeft > 0) {
+ everything = everything.subarray(byteIndex);
+ } else {
+ everything = new Uint8Array();
+ }
+ };
+};
+
+AacStream.prototype = new Stream();
+
+
+
+module.exports = AacStream;
+
+},{"../utils/stream.js":81}],68:[function(require,module,exports){
+'use strict';
+
+var Stream = require('../utils/stream.js');
+
+var AdtsStream;
+
+var
+ ADTS_SAMPLING_FREQUENCIES = [
+ 96000,
+ 88200,
+ 64000,
+ 48000,
+ 44100,
+ 32000,
+ 24000,
+ 22050,
+ 16000,
+ 12000,
+ 11025,
+ 8000,
+ 7350
+ ];
+
+/*
+ * Accepts a ElementaryStream and emits data events with parsed
+ * AAC Audio Frames of the individual packets. Input audio in ADTS
+ * format is unpacked and re-emitted as AAC frames.
+ *
+ * @see http://wiki.multimedia.cx/index.php?title=ADTS
+ * @see http://wiki.multimedia.cx/?title=Understanding_AAC
+ */
+AdtsStream = function() {
+ var self, buffer;
+
+ AdtsStream.prototype.init.call(this);
+
+ self = this;
+
+ this.push = function(packet) {
+ var
+ i = 0,
+ frameNum = 0,
+ frameLength,
+ protectionSkipBytes,
+ frameEnd,
+ oldBuffer,
+ numFrames,
+ sampleCount,
+ adtsFrameDuration;
+
+ if (packet.type !== 'audio') {
+ // ignore non-audio data
+ return;
+ }
+
+ // Prepend any data in the buffer to the input data so that we can parse
+ // aac frames the cross a PES packet boundary
+ if (buffer) {
+ oldBuffer = buffer;
+ buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
+ buffer.set(oldBuffer);
+ buffer.set(packet.data, oldBuffer.byteLength);
+ } else {
+ buffer = packet.data;
+ }
+
+ // unpack any ADTS frames which have been fully received
+ // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
+ while (i + 5 < buffer.length) {
+
+ // Loook for the start of an ADTS header..
+ if (buffer[i] !== 0xFF || (buffer[i + 1] & 0xF6) !== 0xF0) {
+ // If a valid header was not found, jump one forward and attempt to
+ // find a valid ADTS header starting at the next byte
+ i++;
+ continue;
+ }
+
+ // The protection skip bit tells us if we have 2 bytes of CRC data at the
+ // end of the ADTS header
+ protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2;
+
+ // Frame length is a 13 bit integer starting 16 bits from the
+ // end of the sync sequence
+ frameLength = ((buffer[i + 3] & 0x03) << 11) |
+ (buffer[i + 4] << 3) |
+ ((buffer[i + 5] & 0xe0) >> 5);
+
+ sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
+ adtsFrameDuration = (sampleCount * 90000) /
+ ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
+
+ frameEnd = i + frameLength;
+
+ // If we don't have enough data to actually finish this ADTS frame, return
+ // and wait for more data
+ if (buffer.byteLength < frameEnd) {
+ return;
+ }
+
+ // Otherwise, deliver the complete AAC frame
+ this.trigger('data', {
+ pts: packet.pts + (frameNum * adtsFrameDuration),
+ dts: packet.dts + (frameNum * adtsFrameDuration),
+ sampleCount: sampleCount,
+ audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1,
+ channelcount: ((buffer[i + 2] & 1) << 2) |
+ ((buffer[i + 3] & 0xc0) >>> 6),
+ samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
+ samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
+ // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
+ samplesize: 16,
+ data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
+ });
+
+ // If the buffer is empty, clear it and return
+ if (buffer.byteLength === frameEnd) {
+ buffer = undefined;
+ return;
+ }
+
+ frameNum++;
+
+ // Remove the finished frame from the buffer and start the process again
+ buffer = buffer.subarray(frameEnd);
+ }
+ };
+ this.flush = function() {
+ this.trigger('done');
+ };
+};
+
+AdtsStream.prototype = new Stream();
+
+module.exports = AdtsStream;
+
+},{"../utils/stream.js":81}],69:[function(require,module,exports){
+'use strict';
+
+var Stream = require('../utils/stream.js');
+var ExpGolomb = require('../utils/exp-golomb.js');
+
+var H264Stream, NalByteStream;
+
+/**
+ * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
+ */
+NalByteStream = function() {
+ var
+ syncPoint = 0,
+ i,
+ buffer;
+ NalByteStream.prototype.init.call(this);
+
+ this.push = function(data) {
+ var swapBuffer;
+
+ if (!buffer) {
+ buffer = data.data;
+ } else {
+ swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
+ swapBuffer.set(buffer);
+ swapBuffer.set(data.data, buffer.byteLength);
+ buffer = swapBuffer;
+ }
+
+ // Rec. ITU-T H.264, Annex B
+ // scan for NAL unit boundaries
+
+ // a match looks like this:
+ // 0 0 1 .. NAL .. 0 0 1
+ // ^ sync point ^ i
+ // or this:
+ // 0 0 1 .. NAL .. 0 0 0
+ // ^ sync point ^ i
+
+ // advance the sync point to a NAL start, if necessary
+ for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
+ if (buffer[syncPoint + 2] === 1) {
+ // the sync point is properly aligned
+ i = syncPoint + 5;
+ break;
+ }
+ }
+
+ while (i < buffer.byteLength) {
+ // look at the current byte to determine if we've hit the end of
+ // a NAL unit boundary
+ switch (buffer[i]) {
+ case 0:
+ // skip past non-sync sequences
+ if (buffer[i - 1] !== 0) {
+ i += 2;
+ break;
+ } else if (buffer[i - 2] !== 0) {
+ i++;
+ break;
+ }
+
+ // deliver the NAL unit if it isn't empty
+ if (syncPoint + 3 !== i - 2) {
+ this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
+ }
+
+ // drop trailing zeroes
+ do {
+ i++;
+ } while (buffer[i] !== 1 && i < buffer.length);
+ syncPoint = i - 2;
+ i += 3;
+ break;
+ case 1:
+ // skip past non-sync sequences
+ if (buffer[i - 1] !== 0 ||
+ buffer[i - 2] !== 0) {
+ i += 3;
+ break;
+ }
+
+ // deliver the NAL unit
+ this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
+ syncPoint = i - 2;
+ i += 3;
+ break;
+ default:
+ // the current byte isn't a one or zero, so it cannot be part
+ // of a sync sequence
+ i += 3;
+ break;
+ }
+ }
+ // filter out the NAL units that were delivered
+ buffer = buffer.subarray(syncPoint);
+ i -= syncPoint;
+ syncPoint = 0;
+ };
+
+ this.flush = function() {
+ // deliver the last buffered NAL unit
+ if (buffer && buffer.byteLength > 3) {
+ this.trigger('data', buffer.subarray(syncPoint + 3));
+ }
+ // reset the stream state
+ buffer = null;
+ syncPoint = 0;
+ this.trigger('done');
+ };
+};
+NalByteStream.prototype = new Stream();
+
+/**
+ * Accepts input from a ElementaryStream and produces H.264 NAL unit data
+ * events.
+ */
+H264Stream = function() {
+ var
+ nalByteStream = new NalByteStream(),
+ self,
+ trackId,
+ currentPts,
+ currentDts,
+
+ discardEmulationPreventionBytes,
+ readSequenceParameterSet,
+ skipScalingList;
+
+ H264Stream.prototype.init.call(this);
+ self = this;
+
+ this.push = function(packet) {
+ if (packet.type !== 'video') {
+ return;
+ }
+ trackId = packet.trackId;
+ currentPts = packet.pts;
+ currentDts = packet.dts;
+
+ nalByteStream.push(packet);
+ };
+
+ nalByteStream.on('data', function(data) {
+ var
+ event = {
+ trackId: trackId,
+ pts: currentPts,
+ dts: currentDts,
+ data: data
+ };
+
+ switch (data[0] & 0x1f) {
+ case 0x05:
+ event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
+ break;
+ case 0x06:
+ event.nalUnitType = 'sei_rbsp';
+ event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
+ break;
+ case 0x07:
+ event.nalUnitType = 'seq_parameter_set_rbsp';
+ event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
+ event.config = readSequenceParameterSet(event.escapedRBSP);
+ break;
+ case 0x08:
+ event.nalUnitType = 'pic_parameter_set_rbsp';
+ break;
+ case 0x09:
+ event.nalUnitType = 'access_unit_delimiter_rbsp';
+ break;
+
+ default:
+ break;
+ }
+ self.trigger('data', event);
+ });
+ nalByteStream.on('done', function() {
+ self.trigger('done');
+ });
+
+ this.flush = function() {
+ nalByteStream.flush();
+ };
+
+ /**
+ * Advance the ExpGolomb decoder past a scaling list. The scaling
+ * list is optionally transmitted as part of a sequence parameter
+ * set and is not relevant to transmuxing.
+ * @param count {number} the number of entries in this scaling list
+ * @param expGolombDecoder {object} an ExpGolomb pointed to the
+ * start of a scaling list
+ * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
+ */
+ skipScalingList = function(count, expGolombDecoder) {
+ var
+ lastScale = 8,
+ nextScale = 8,
+ j,
+ deltaScale;
+
+ for (j = 0; j < count; j++) {
+ if (nextScale !== 0) {
+ deltaScale = expGolombDecoder.readExpGolomb();
+ nextScale = (lastScale + deltaScale + 256) % 256;
+ }
+
+ lastScale = (nextScale === 0) ? lastScale : nextScale;
+ }
+ };
+
+ /**
+ * Expunge any "Emulation Prevention" bytes from a "Raw Byte
+ * Sequence Payload"
+ * @param data {Uint8Array} the bytes of a RBSP from a NAL
+ * unit
+ * @return {Uint8Array} the RBSP without any Emulation
+ * Prevention Bytes
+ */
+ discardEmulationPreventionBytes = function(data) {
+ var
+ length = data.byteLength,
+ emulationPreventionBytesPositions = [],
+ i = 1,
+ newLength, newData;
+
+ // Find all `Emulation Prevention Bytes`
+ while (i < length - 2) {
+ if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
+ emulationPreventionBytesPositions.push(i + 2);
+ i += 2;
+ } else {
+ i++;
+ }
+ }
+
+ // If no Emulation Prevention Bytes were found just return the original
+ // array
+ if (emulationPreventionBytesPositions.length === 0) {
+ return data;
+ }
+
+ // Create a new array to hold the NAL unit data
+ newLength = length - emulationPreventionBytesPositions.length;
+ newData = new Uint8Array(newLength);
+ var sourceIndex = 0;
+
+ for (i = 0; i < newLength; sourceIndex++, i++) {
+ if (sourceIndex === emulationPreventionBytesPositions[0]) {
+ // Skip this byte
+ sourceIndex++;
+ // Remove this position index
+ emulationPreventionBytesPositions.shift();
+ }
+ newData[i] = data[sourceIndex];
+ }
+
+ return newData;
+ };
+
+ /**
+ * Read a sequence parameter set and return some interesting video
+ * properties. A sequence parameter set is the H264 metadata that
+ * describes the properties of upcoming video frames.
+ * @param data {Uint8Array} the bytes of a sequence parameter set
+ * @return {object} an object with configuration parsed from the
+ * sequence parameter set, including the dimensions of the
+ * associated video frames.
+ */
+ readSequenceParameterSet = function(data) {
+ var
+ frameCropLeftOffset = 0,
+ frameCropRightOffset = 0,
+ frameCropTopOffset = 0,
+ frameCropBottomOffset = 0,
+ sarScale = 1,
+ expGolombDecoder, profileIdc, levelIdc, profileCompatibility,
+ chromaFormatIdc, picOrderCntType,
+ numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
+ picHeightInMapUnitsMinus1,
+ frameMbsOnlyFlag,
+ scalingListCount,
+ sarRatio,
+ aspectRatioIdc,
+ i;
+
+ expGolombDecoder = new ExpGolomb(data);
+ profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
+ profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
+ levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
+ expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
+
+ // some profiles have more optional data we don't need
+ if (profileIdc === 100 ||
+ profileIdc === 110 ||
+ profileIdc === 122 ||
+ profileIdc === 244 ||
+ profileIdc === 44 ||
+ profileIdc === 83 ||
+ profileIdc === 86 ||
+ profileIdc === 118 ||
+ profileIdc === 128 ||
+ profileIdc === 138 ||
+ profileIdc === 139 ||
+ profileIdc === 134) {
+ chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
+ if (chromaFormatIdc === 3) {
+ expGolombDecoder.skipBits(1); // separate_colour_plane_flag
+ }
+ expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
+ expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
+ expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
+ if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag
+ scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
+ for (i = 0; i < scalingListCount; i++) {
+ if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
+ if (i < 6) {
+ skipScalingList(16, expGolombDecoder);
+ } else {
+ skipScalingList(64, expGolombDecoder);
+ }
+ }
+ }
+ }
+ }
+
+ expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
+ picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
+
+ if (picOrderCntType === 0) {
+ expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
+ } else if (picOrderCntType === 1) {
+ expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
+ expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
+ expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
+ numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
+ for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
+ expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
+ }
+ }
+
+ expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
+ expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
+
+ picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
+ picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
+
+ frameMbsOnlyFlag = expGolombDecoder.readBits(1);
+ if (frameMbsOnlyFlag === 0) {
+ expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
+ }
+
+ expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
+ if (expGolombDecoder.readBoolean()) { // frame_cropping_flag
+ frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
+ frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
+ frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
+ frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
+ }
+ if (expGolombDecoder.readBoolean()) {
+ // vui_parameters_present_flag
+ if (expGolombDecoder.readBoolean()) {
+ // aspect_ratio_info_present_flag
+ aspectRatioIdc = expGolombDecoder.readUnsignedByte();
+ switch (aspectRatioIdc) {
+ case 1: sarRatio = [1,1]; break;
+ case 2: sarRatio = [12,11]; break;
+ case 3: sarRatio = [10,11]; break;
+ case 4: sarRatio = [16,11]; break;
+ case 5: sarRatio = [40,33]; break;
+ case 6: sarRatio = [24,11]; break;
+ case 7: sarRatio = [20,11]; break;
+ case 8: sarRatio = [32,11]; break;
+ case 9: sarRatio = [80,33]; break;
+ case 10: sarRatio = [18,11]; break;
+ case 11: sarRatio = [15,11]; break;
+ case 12: sarRatio = [64,33]; break;
+ case 13: sarRatio = [160,99]; break;
+ case 14: sarRatio = [4,3]; break;
+ case 15: sarRatio = [3,2]; break;
+ case 16: sarRatio = [2,1]; break;
+ case 255: {
+ sarRatio = [expGolombDecoder.readUnsignedByte() << 8 |
+ expGolombDecoder.readUnsignedByte(),
+ expGolombDecoder.readUnsignedByte() << 8 |
+ expGolombDecoder.readUnsignedByte() ];
+ break;
+ }
+ }
+ if (sarRatio) {
+ sarScale = sarRatio[0] / sarRatio[1];
+ }
+ }
+ }
+ return {
+ profileIdc: profileIdc,
+ levelIdc: levelIdc,
+ profileCompatibility: profileCompatibility,
+ width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
+ height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2)
+ };
+ };
+
+};
+H264Stream.prototype = new Stream();
+
+module.exports = {
+ H264Stream: H264Stream,
+ NalByteStream: NalByteStream,
+};
+
+},{"../utils/exp-golomb.js":80,"../utils/stream.js":81}],70:[function(require,module,exports){
+/**
+ * An object that stores the bytes of an FLV tag and methods for
+ * querying and manipulating that data.
+ * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
+ */
+'use strict';
+
+var FlvTag;
+
+// (type:uint, extraData:Boolean = false) extends ByteArray
+FlvTag = function(type, extraData) {
+ var
+ // Counter if this is a metadata tag, nal start marker if this is a video
+ // tag. unused if this is an audio tag
+ adHoc = 0, // :uint
+
+ // The default size is 16kb but this is not enough to hold iframe
+ // data and the resizing algorithm costs a bit so we create a larger
+ // starting buffer for video tags
+ bufferStartSize = 16384,
+
+ // checks whether the FLV tag has enough capacity to accept the proposed
+ // write and re-allocates the internal buffers if necessary
+ prepareWrite = function(flv, count) {
+ var
+ bytes,
+ minLength = flv.position + count;
+ if (minLength < flv.bytes.byteLength) {
+ // there's enough capacity so do nothing
+ return;
+ }
+
+ // allocate a new buffer and copy over the data that will not be modified
+ bytes = new Uint8Array(minLength * 2);
+ bytes.set(flv.bytes.subarray(0, flv.position), 0);
+ flv.bytes = bytes;
+ flv.view = new DataView(flv.bytes.buffer);
+ },
+
+ // commonly used metadata properties
+ widthBytes = FlvTag.widthBytes || new Uint8Array('width'.length),
+ heightBytes = FlvTag.heightBytes || new Uint8Array('height'.length),
+ videocodecidBytes = FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
+ i;
+
+ if (!FlvTag.widthBytes) {
+ // calculating the bytes of common metadata names ahead of time makes the
+ // corresponding writes faster because we don't have to loop over the
+ // characters
+ // re-test with test/perf.html if you're planning on changing this
+ for (i = 0; i < 'width'.length; i++) {
+ widthBytes[i] = 'width'.charCodeAt(i);
+ }
+ for (i = 0; i < 'height'.length; i++) {
+ heightBytes[i] = 'height'.charCodeAt(i);
+ }
+ for (i = 0; i < 'videocodecid'.length; i++) {
+ videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
+ }
+
+ FlvTag.widthBytes = widthBytes;
+ FlvTag.heightBytes = heightBytes;
+ FlvTag.videocodecidBytes = videocodecidBytes;
+ }
+
+ this.keyFrame = false; // :Boolean
+
+ switch(type) {
+ case FlvTag.VIDEO_TAG:
+ this.length = 16;
+ // Start the buffer at 256k
+ bufferStartSize *= 6;
+ break;
+ case FlvTag.AUDIO_TAG:
+ this.length = 13;
+ this.keyFrame = true;
+ break;
+ case FlvTag.METADATA_TAG:
+ this.length = 29;
+ this.keyFrame = true;
+ break;
+ default:
+ throw("Error Unknown TagType");
+ }
+
+ this.bytes = new Uint8Array(bufferStartSize);
+ this.view = new DataView(this.bytes.buffer);
+ this.bytes[0] = type;
+ this.position = this.length;
+ this.keyFrame = extraData; // Defaults to false
+
+ // presentation timestamp
+ this.pts = 0;
+ // decoder timestamp
+ this.dts = 0;
+
+ // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
+ this.writeBytes = function(bytes, offset, length) {
+ var
+ start = offset || 0,
+ end;
+ length = length || bytes.byteLength;
+ end = start + length;
+
+ prepareWrite(this, length);
+ this.bytes.set(bytes.subarray(start, end), this.position);
+
+ this.position += length;
+ this.length = Math.max(this.length, this.position);
+ };
+
+ // ByteArray#writeByte(value:int):void
+ this.writeByte = function(byte) {
+ prepareWrite(this, 1);
+ this.bytes[this.position] = byte;
+ this.position++;
+ this.length = Math.max(this.length, this.position);
+ };
+
+ // ByteArray#writeShort(value:int):void
+ this.writeShort = function(short) {
+ prepareWrite(this, 2);
+ this.view.setUint16(this.position, short);
+ this.position += 2;
+ this.length = Math.max(this.length, this.position);
+ };
+
+ // Negative index into array
+ // (pos:uint):int
+ this.negIndex = function(pos) {
+ return this.bytes[this.length - pos];
+ };
+
+ // The functions below ONLY work when this[0] == VIDEO_TAG.
+ // We are not going to check for that because we dont want the overhead
+ // (nal:ByteArray = null):int
+ this.nalUnitSize = function() {
+ if (adHoc === 0) {
+ return 0;
+ }
+
+ return this.length - (adHoc + 4);
+ };
+
+ this.startNalUnit = function() {
+ // remember position and add 4 bytes
+ if (adHoc > 0) {
+ throw new Error("Attempted to create new NAL wihout closing the old one");
+ }
+
+ // reserve 4 bytes for nal unit size
+ adHoc = this.length;
+ this.length += 4;
+ this.position = this.length;
+ };
+
+ // (nal:ByteArray = null):void
+ this.endNalUnit = function(nalContainer) {
+ var
+ nalStart, // :uint
+ nalLength; // :uint
+
+ // Rewind to the marker and write the size
+ if (this.length === adHoc + 4) {
+ // we started a nal unit, but didnt write one, so roll back the 4 byte size value
+ this.length -= 4;
+ } else if (adHoc > 0) {
+ nalStart = adHoc + 4;
+ nalLength = this.length - nalStart;
+
+ this.position = adHoc;
+ this.view.setUint32(this.position, nalLength);
+ this.position = this.length;
+
+ if (nalContainer) {
+ // Add the tag to the NAL unit
+ nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
+ }
+ }
+
+ adHoc = 0;
+ };
+
+ /**
+ * Write out a 64-bit floating point valued metadata property. This method is
+ * called frequently during a typical parse and needs to be fast.
+ */
+ // (key:String, val:Number):void
+ this.writeMetaDataDouble = function(key, val) {
+ var i;
+ prepareWrite(this, 2 + key.length + 9);
+
+ // write size of property name
+ this.view.setUint16(this.position, key.length);
+ this.position += 2;
+
+ // this next part looks terrible but it improves parser throughput by
+ // 10kB/s in my testing
+
+ // write property name
+ if (key === 'width') {
+ this.bytes.set(widthBytes, this.position);
+ this.position += 5;
+ } else if (key === 'height') {
+ this.bytes.set(heightBytes, this.position);
+ this.position += 6;
+ } else if (key === 'videocodecid') {
+ this.bytes.set(videocodecidBytes, this.position);
+ this.position += 12;
+ } else {
+ for (i = 0; i < key.length; i++) {
+ this.bytes[this.position] = key.charCodeAt(i);
+ this.position++;
+ }
+ }
+
+ // skip null byte
+ this.position++;
+
+ // write property value
+ this.view.setFloat64(this.position, val);
+ this.position += 8;
+
+ // update flv tag length
+ this.length = Math.max(this.length, this.position);
+ ++adHoc;
+ };
+
+ // (key:String, val:Boolean):void
+ this.writeMetaDataBoolean = function(key, val) {
+ var i;
+ prepareWrite(this, 2);
+ this.view.setUint16(this.position, key.length);
+ this.position += 2;
+ for (i = 0; i < key.length; i++) {
+ // if key.charCodeAt(i) >= 255, handle error
+ prepareWrite(this, 1);
+ this.bytes[this.position] = key.charCodeAt(i);
+ this.position++;
+ }
+ prepareWrite(this, 2);
+ this.view.setUint8(this.position, 0x01);
+ this.position++;
+ this.view.setUint8(this.position, val ? 0x01 : 0x00);
+ this.position++;
+ this.length = Math.max(this.length, this.position);
+ ++adHoc;
+ };
+
+ // ():ByteArray
+ this.finalize = function() {
+ var
+ dtsDelta, // :int
+ len; // :int
+
+ switch(this.bytes[0]) {
+ // Video Data
+ case FlvTag.VIDEO_TAG:
+ this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
+ this.bytes[12] = extraData ? 0x00 : 0x01;
+
+ dtsDelta = this.pts - this.dts;
+ this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
+ this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
+ this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
+ break;
+
+ case FlvTag.AUDIO_TAG:
+ this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
+ this.bytes[12] = extraData ? 0x00 : 0x01;
+ break;
+
+ case FlvTag.METADATA_TAG:
+ this.position = 11;
+ this.view.setUint8(this.position, 0x02); // String type
+ this.position++;
+ this.view.setUint16(this.position, 0x0A); // 10 Bytes
+ this.position += 2;
+ // set "onMetaData"
+ this.bytes.set([0x6f, 0x6e, 0x4d, 0x65,
+ 0x74, 0x61, 0x44, 0x61,
+ 0x74, 0x61], this.position);
+ this.position += 10;
+ this.bytes[this.position] = 0x08; // Array type
+ this.position++;
+ this.view.setUint32(this.position, adHoc);
+ this.position = this.length;
+ this.bytes.set([0, 0, 9], this.position);
+ this.position += 3; // End Data Tag
+ this.length = this.position;
+ break;
+ }
+
+ len = this.length - 11;
+
+ // write the DataSize field
+ this.bytes[ 1] = (len & 0x00FF0000) >>> 16;
+ this.bytes[ 2] = (len & 0x0000FF00) >>> 8;
+ this.bytes[ 3] = (len & 0x000000FF) >>> 0;
+ // write the Timestamp
+ this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16;
+ this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8;
+ this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0;
+ this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24;
+ // write the StreamID
+ this.bytes[ 8] = 0;
+ this.bytes[ 9] = 0;
+ this.bytes[10] = 0;
+
+ // Sometimes we're at the end of the view and have one slot to write a
+ // uint32, so, prepareWrite of count 4, since, view is uint8
+ prepareWrite(this, 4);
+ this.view.setUint32(this.length, this.length);
+ this.length += 4;
+ this.position += 4;
+
+ // trim down the byte buffer to what is actually being used
+ this.bytes = this.bytes.subarray(0, this.length);
+ this.frameTime = FlvTag.frameTime(this.bytes);
+ // if bytes.bytelength isn't equal to this.length, handle error
+ return this;
+ };
+};
+
+FlvTag.AUDIO_TAG = 0x08; // == 8, :uint
+FlvTag.VIDEO_TAG = 0x09; // == 9, :uint
+FlvTag.METADATA_TAG = 0x12; // == 18, :uint
+
+// (tag:ByteArray):Boolean {
+FlvTag.isAudioFrame = function(tag) {
+ return FlvTag.AUDIO_TAG === tag[0];
+};
+
+// (tag:ByteArray):Boolean {
+FlvTag.isVideoFrame = function(tag) {
+ return FlvTag.VIDEO_TAG === tag[0];
+};
+
+// (tag:ByteArray):Boolean {
+FlvTag.isMetaData = function(tag) {
+ return FlvTag.METADATA_TAG === tag[0];
+};
+
+// (tag:ByteArray):Boolean {
+FlvTag.isKeyFrame = function(tag) {
+ if (FlvTag.isVideoFrame(tag)) {
+ return tag[11] === 0x17;
+ }
+
+ if (FlvTag.isAudioFrame(tag)) {
+ return true;
+ }
+
+ if (FlvTag.isMetaData(tag)) {
+ return true;
+ }
+
+ return false;
+};
+
+// (tag:ByteArray):uint {
+FlvTag.frameTime = function(tag) {
+ var pts = tag[ 4] << 16; // :uint
+ pts |= tag[ 5] << 8;
+ pts |= tag[ 6] << 0;
+ pts |= tag[ 7] << 24;
+ return pts;
+};
+
+module.exports = FlvTag;
+
+},{}],71:[function(require,module,exports){
+module.exports = {
+ tag: require('./flv-tag'),
+ Transmuxer: require('./transmuxer')
+};
+
+},{"./flv-tag":70,"./transmuxer":72}],72:[function(require,module,exports){
+'use strict';
+
+var Stream = require('../utils/stream.js');
+var FlvTag = require('./flv-tag.js');
+var m2ts = require('../m2ts/m2ts.js');
+var AdtsStream = require('../codecs/adts.js');
+var H264Stream = require('../codecs/h264').H264Stream;
+
+var
+ MetadataStream,
+ Transmuxer,
+ VideoSegmentStream,
+ AudioSegmentStream,
+ CoalesceStream,
+ collectTimelineInfo,
+ metaDataTag,
+ extraDataTag;
+
+/**
+ * Store information about the start and end of the tracka and the
+ * duration for each frame/sample we process in order to calculate
+ * the baseMediaDecodeTime
+ */
+collectTimelineInfo = function (track, data) {
+ if (typeof data.pts === 'number') {
+ if (track.timelineStartInfo.pts === undefined) {
+ track.timelineStartInfo.pts = data.pts;
+ } else {
+ track.timelineStartInfo.pts =
+ Math.min(track.timelineStartInfo.pts, data.pts);
+ }
+ }
+
+ if (typeof data.dts === 'number') {
+ if (track.timelineStartInfo.dts === undefined) {
+ track.timelineStartInfo.dts = data.dts;
+ } else {
+ track.timelineStartInfo.dts =
+ Math.min(track.timelineStartInfo.dts, data.dts);
+ }
+ }
+};
+
+metaDataTag = function(track, pts) {
+ var
+ tag = new FlvTag(FlvTag.METADATA_TAG); // :FlvTag
+
+ tag.dts = pts;
+ tag.pts = pts;
+
+ tag.writeMetaDataDouble("videocodecid", 7);
+ tag.writeMetaDataDouble("width", track.width);
+ tag.writeMetaDataDouble("height", track.height);
+
+ return tag;
+};
+
+extraDataTag = function(track, pts) {
+ var
+ i,
+ tag = new FlvTag(FlvTag.VIDEO_TAG, true);
+
+ tag.dts = pts;
+ tag.pts = pts;
+
+ tag.writeByte(0x01);// version
+ tag.writeByte(track.profileIdc);// profile
+ tag.writeByte(track.profileCompatibility);// compatibility
+ tag.writeByte(track.levelIdc);// level
+ tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
+ tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
+ tag.writeShort( track.sps[0].length ); // data of SPS
+ tag.writeBytes( track.sps[0] ); // SPS
+
+ tag.writeByte(track.pps.length); // num of PPS (will there ever be more that 1 PPS?)
+ for (i = 0 ; i < track.pps.length ; ++i) {
+ tag.writeShort(track.pps[i].length); // 2 bytes for length of PPS
+ tag.writeBytes(track.pps[i]); // data of PPS
+ }
+
+ return tag;
+};
+
+/**
+ * Constructs a single-track, media segment from AAC data
+ * events. The output of this stream can be fed to flash.
+ */
+AudioSegmentStream = function(track) {
+ var
+ adtsFrames = [],
+ adtsFramesLength = 0,
+ sequenceNumber = 0,
+ earliestAllowedDts = 0,
+ oldExtraData;
+
+ AudioSegmentStream.prototype.init.call(this);
+
+ this.push = function(data) {
+ collectTimelineInfo(track, data);
+
+ if (track && track.channelcount === undefined) {
+ track.audioobjecttype = data.audioobjecttype;
+ track.channelcount = data.channelcount;
+ track.samplerate = data.samplerate;
+ track.samplingfrequencyindex = data.samplingfrequencyindex;
+ track.samplesize = data.samplesize;
+ track.extraData = (track.audioobjecttype << 11) |
+ (track.samplingfrequencyindex << 7) |
+ (track.channelcount << 3);
+ }
+
+ data.pts = Math.round(data.pts / 90);
+ data.dts = Math.round(data.dts / 90);
+
+ // buffer audio data until end() is called
+ adtsFrames.push(data);
+ };
+
+ this.flush = function() {
+ var currentFrame, adtsFrame, deltaDts,lastMetaPts, tags = [];
+ // return early if no audio data has been observed
+ if (adtsFrames.length === 0) {
+ this.trigger('done');
+ return;
+ }
+
+ lastMetaPts = -Infinity;
+
+ while (adtsFrames.length) {
+ currentFrame = adtsFrames.shift();
+
+ // write out metadata tags every 1 second so that the decoder
+ // is re-initialized quickly after seeking into a different
+ // audio configuration
+ if (track.extraData !== oldExtraData || currentFrame.pts - lastMetaPts >= 1000) {
+ adtsFrame = new FlvTag(FlvTag.METADATA_TAG);
+ adtsFrame.pts = currentFrame.pts;
+ adtsFrame.dts = currentFrame.dts;
+
+ // AAC is always 10
+ adtsFrame.writeMetaDataDouble("audiocodecid", 10);
+ adtsFrame.writeMetaDataBoolean("stereo", 2 === track.channelcount);
+ adtsFrame.writeMetaDataDouble ("audiosamplerate", track.samplerate);
+ // Is AAC always 16 bit?
+ adtsFrame.writeMetaDataDouble ("audiosamplesize", 16);
+
+ tags.push(adtsFrame);
+
+ oldExtraData = track.extraData;
+
+ adtsFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
+ // For audio, DTS is always the same as PTS. We want to set the DTS
+ // however so we can compare with video DTS to determine approximate
+ // packet order
+ adtsFrame.pts = currentFrame.pts;
+ adtsFrame.dts = currentFrame.dts;
+
+ adtsFrame.view.setUint16(adtsFrame.position, track.extraData);
+ adtsFrame.position += 2;
+ adtsFrame.length = Math.max(adtsFrame.length, adtsFrame.position);
+
+ tags.push(adtsFrame);
+
+ lastMetaPts = currentFrame.pts;
+ }
+ adtsFrame = new FlvTag(FlvTag.AUDIO_TAG);
+ adtsFrame.pts = currentFrame.pts;
+ adtsFrame.dts = currentFrame.dts;
+
+ adtsFrame.writeBytes(currentFrame.data);
+
+ tags.push(adtsFrame);
+ }
+
+ oldExtraData = null;
+ this.trigger('data', {track: track, tags: tags});
+
+ this.trigger('done');
+ };
+};
+AudioSegmentStream.prototype = new Stream();
+
+/**
+ * Store FlvTags for the h264 stream
+ * @param track {object} track metadata configuration
+ */
+VideoSegmentStream = function(track) {
+ var
+ sequenceNumber = 0,
+ nalUnits = [],
+ nalUnitsLength = 0,
+ config,
+ h264Frame;
+ VideoSegmentStream.prototype.init.call(this);
+
+ this.finishFrame = function(tags, frame) {
+ if (!frame) {
+ return;
+ }
+ // Check if keyframe and the length of tags.
+ // This makes sure we write metadata on the first frame of a segment.
+ if (config && track && track.newMetadata &&
+ (frame.keyFrame || tags.length === 0)) {
+ // Push extra data on every IDR frame in case we did a stream change + seek
+ tags.push(metaDataTag(config, frame.pts));
+ tags.push(extraDataTag(track, frame.pts));
+ track.newMetadata = false;
+ }
+
+ frame.endNalUnit();
+ tags.push(frame);
+ };
+
+ this.push = function(data) {
+ collectTimelineInfo(track, data);
+
+ data.pts = Math.round(data.pts / 90);
+ data.dts = Math.round(data.dts / 90);
+
+ // buffer video until flush() is called
+ nalUnits.push(data);
+ };
+
+ this.flush = function() {
+ var
+ currentNal,
+ tags = [];
+
+ // Throw away nalUnits at the start of the byte stream until we find
+ // the first AUD
+ while (nalUnits.length) {
+ if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
+ break;
+ }
+ nalUnits.shift();
+ }
+
+ // return early if no video data has been observed
+ if (nalUnits.length === 0) {
+ this.trigger('done');
+ return;
+ }
+
+ while (nalUnits.length) {
+ currentNal = nalUnits.shift();
+
+ // record the track config
+ if (currentNal.nalUnitType === 'seq_parameter_set_rbsp') {
+ track.newMetadata = true;
+ config = currentNal.config;
+ track.width = config.width;
+ track.height = config.height;
+ track.sps = [currentNal.data];
+ track.profileIdc = config.profileIdc;
+ track.levelIdc = config.levelIdc;
+ track.profileCompatibility = config.profileCompatibility;
+ h264Frame.endNalUnit();
+ } else if (currentNal.nalUnitType === 'pic_parameter_set_rbsp') {
+ track.newMetadata = true;
+ track.pps = [currentNal.data];
+ h264Frame.endNalUnit();
+ } else if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
+ if (h264Frame) {
+ this.finishFrame(tags, h264Frame);
+ }
+ h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
+ h264Frame.pts = currentNal.pts;
+ h264Frame.dts = currentNal.dts;
+ } else {
+ if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
+ // the current sample is a key frame
+ h264Frame.keyFrame = true;
+ }
+ h264Frame.endNalUnit();
+ }
+ h264Frame.startNalUnit();
+ h264Frame.writeBytes(currentNal.data);
+ }
+ if (h264Frame) {
+ this.finishFrame(tags, h264Frame);
+ }
+
+ this.trigger('data', {track: track, tags: tags});
+
+ // Continue with the flush process now
+ this.trigger('done');
+ };
+};
+
+VideoSegmentStream.prototype = new Stream();
+
+/**
+ * The final stage of the transmuxer that emits the flv tags
+ * for audio, video, and metadata. Also tranlates in time and
+ * outputs caption data and id3 cues.
+ */
+CoalesceStream = function(options) {
+ // Number of Tracks per output segment
+ // If greater than 1, we combine multiple
+ // tracks into a single segment
+ this.numberOfTracks = 0;
+ this.metadataStream = options.metadataStream;
+
+ this.videoTags = [];
+ this.audioTags = [];
+ this.videoTrack = null;
+ this.audioTrack = null;
+ this.pendingCaptions = [];
+ this.pendingMetadata = [];
+ this.pendingTracks = 0;
+
+ CoalesceStream.prototype.init.call(this);
+
+ // Take output from multiple
+ this.push = function(output) {
+ // buffer incoming captions until the associated video segment
+ // finishes
+ if (output.text) {
+ return this.pendingCaptions.push(output);
+ }
+ // buffer incoming id3 tags until the final flush
+ if (output.frames) {
+ return this.pendingMetadata.push(output);
+ }
+
+ if (output.track.type === 'video') {
+ this.videoTrack = output.track;
+ this.videoTags = output.tags;
+ this.pendingTracks++;
+ }
+ if (output.track.type === 'audio') {
+ this.audioTrack = output.track;
+ this.audioTags = output.tags;
+ this.pendingTracks++;
+ }
+ };
+};
+
+CoalesceStream.prototype = new Stream();
+CoalesceStream.prototype.flush = function() {
+ var
+ id3,
+ caption,
+ i,
+ timelineStartPts,
+ event = {
+ tags: {},
+ captions: [],
+ metadata: []
+ };
+
+ if (this.pendingTracks < this.numberOfTracks) {
+ return;
+ }
+
+ if (this.videoTrack) {
+ timelineStartPts = this.videoTrack.timelineStartInfo.pts;
+ } else if (this.audioTrack) {
+ timelineStartPts = this.audioTrack.timelineStartInfo.pts;
+ }
+
+ event.tags.videoTags = this.videoTags;
+ event.tags.audioTags = this.audioTags;
+
+ // Translate caption PTS times into second offsets into the
+ // video timeline for the segment
+ for (i = 0; i < this.pendingCaptions.length; i++) {
+ caption = this.pendingCaptions[i];
+ caption.startTime = caption.startPts - timelineStartPts;
+ caption.startTime /= 90e3;
+ caption.endTime = caption.endPts - timelineStartPts;
+ caption.endTime /= 90e3;
+ event.captions.push(caption);
+ }
+
+ // Translate ID3 frame PTS times into second offsets into the
+ // video timeline for the segment
+ for (i = 0; i < this.pendingMetadata.length; i++) {
+ id3 = this.pendingMetadata[i];
+ id3.cueTime = id3.pts - timelineStartPts;
+ id3.cueTime /= 90e3;
+ event.metadata.push(id3);
+ }
+ // We add this to every single emitted segment even though we only need
+ // it for the first
+ event.metadata.dispatchType = this.metadataStream.dispatchType;
+
+ // Reset stream state
+ this.videoTrack = null;
+ this.audioTrack = null;
+ this.videoTags = [];
+ this.audioTags = [];
+ this.pendingCaptions.length = 0;
+ this.pendingMetadata.length = 0;
+ this.pendingTracks = 0;
+
+ // Emit the final segment
+ this.trigger('data', event);
+
+ this.trigger('done');
+};
+
+/**
+ * An object that incrementally transmuxes MPEG2 Trasport Stream
+ * chunks into an FLV.
+ */
+Transmuxer = function(options) {
+ var
+ self = this,
+ videoTrack,
+ audioTrack,
+
+ packetStream, parseStream, elementaryStream,
+ adtsStream, h264Stream,
+ videoSegmentStream, audioSegmentStream, captionStream,
+ coalesceStream;
+
+ Transmuxer.prototype.init.call(this);
+
+ options = options || {};
+
+ // expose the metadata stream
+ this.metadataStream = new m2ts.MetadataStream();
+
+ options.metadataStream = this.metadataStream;
+
+ // set up the parsing pipeline
+ packetStream = new m2ts.TransportPacketStream();
+ parseStream = new m2ts.TransportParseStream();
+ elementaryStream = new m2ts.ElementaryStream();
+ adtsStream = new AdtsStream();
+ h264Stream = new H264Stream();
+ coalesceStream = new CoalesceStream(options);
+
+ // disassemble MPEG2-TS packets into elementary streams
+ packetStream
+ .pipe(parseStream)
+ .pipe(elementaryStream);
+
+ // !!THIS ORDER IS IMPORTANT!!
+ // demux the streams
+ elementaryStream
+ .pipe(h264Stream);
+ elementaryStream
+ .pipe(adtsStream);
+
+ elementaryStream
+ .pipe(this.metadataStream)
+ .pipe(coalesceStream);
+ // if CEA-708 parsing is available, hook up a caption stream
+ captionStream = new m2ts.CaptionStream();
+ h264Stream.pipe(captionStream)
+ .pipe(coalesceStream);
+
+ // hook up the segment streams once track metadata is delivered
+ elementaryStream.on('data', function(data) {
+ var i, videoTrack, audioTrack;
+
+ if (data.type === 'metadata') {
+ i = data.tracks.length;
+
+ // scan the tracks listed in the metadata
+ while (i--) {
+ if (data.tracks[i].type === 'video') {
+ videoTrack = data.tracks[i];
+ } else if (data.tracks[i].type === 'audio') {
+ audioTrack = data.tracks[i];
+ }
+ }
+
+ // hook up the video segment stream to the first track with h264 data
+ if (videoTrack && !videoSegmentStream) {
+ coalesceStream.numberOfTracks++;
+ videoSegmentStream = new VideoSegmentStream(videoTrack);
+
+ // Set up the final part of the video pipeline
+ h264Stream
+ .pipe(videoSegmentStream)
+ .pipe(coalesceStream);
+ }
+
+ if (audioTrack && !audioSegmentStream) {
+ // hook up the audio segment stream to the first track with aac data
+ coalesceStream.numberOfTracks++;
+ audioSegmentStream = new AudioSegmentStream(audioTrack);
+
+ // Set up the final part of the audio pipeline
+ adtsStream
+ .pipe(audioSegmentStream)
+ .pipe(coalesceStream);
+ }
+ }
+ });
+
+ // feed incoming data to the front of the parsing pipeline
+ this.push = function(data) {
+ packetStream.push(data);
+ };
+
+ // flush any buffered data
+ this.flush = function() {
+ // Start at the top of the pipeline and flush all pending work
+ packetStream.flush();
+ };
+
+ // Re-emit any data coming from the coalesce stream to the outside world
+ coalesceStream.on('data', function (event) {
+ self.trigger('data', event);
+ });
+
+ // Let the consumer know we have finished flushing the entire pipeline
+ coalesceStream.on('done', function () {
+ self.trigger('done');
+ });
+
+ // For information on the FLV format, see
+ // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
+ // Technically, this function returns the header and a metadata FLV tag
+ // if duration is greater than zero
+ // duration in seconds
+ // @return {object} the bytes of the FLV header as a Uint8Array
+ this.getFlvHeader = function(duration, audio, video) { // :ByteArray {
+ var
+ headBytes = new Uint8Array(3 + 1 + 1 + 4),
+ head = new DataView(headBytes.buffer),
+ metadata,
+ result,
+ metadataLength;
+
+ // default arguments
+ duration = duration || 0;
+ audio = audio === undefined? true : audio;
+ video = video === undefined? true : video;
+
+ // signature
+ head.setUint8(0, 0x46); // 'F'
+ head.setUint8(1, 0x4c); // 'L'
+ head.setUint8(2, 0x56); // 'V'
+
+ // version
+ head.setUint8(3, 0x01);
+
+ // flags
+ head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
+
+ // data offset, should be 9 for FLV v1
+ head.setUint32(5, headBytes.byteLength);
+
+ // init the first FLV tag
+ if (duration <= 0) {
+ // no duration available so just write the first field of the first
+ // FLV tag
+ result = new Uint8Array(headBytes.byteLength + 4);
+ result.set(headBytes);
+ result.set([0, 0, 0, 0], headBytes.byteLength);
+ return result;
+ }
+
+ // write out the duration metadata tag
+ metadata = new FlvTag(FlvTag.METADATA_TAG);
+ metadata.pts = metadata.dts = 0;
+ metadata.writeMetaDataDouble("duration", duration);
+ metadataLength = metadata.finalize().length;
+ result = new Uint8Array(headBytes.byteLength + metadataLength);
+ result.set(headBytes);
+ result.set(head.byteLength, metadataLength);
+
+ return result;
+ };
+};
+Transmuxer.prototype = new Stream();
+
+// forward compatibility
+module.exports = Transmuxer;
+
+},{"../codecs/adts.js":68,"../codecs/h264":69,"../m2ts/m2ts.js":74,"../utils/stream.js":81,"./flv-tag.js":70}],73:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2015 Brightcove
+ * All rights reserved.
+ *
+ * Reads in-band caption information from a video elementary
+ * stream. Captions must follow the CEA-708 standard for injection
+ * into an MPEG-2 transport streams.
+ * @see https://en.wikipedia.org/wiki/CEA-708
+ */
+
+'use strict';
+
+// -----------------
+// Link To Transport
+// -----------------
+
+// Supplemental enhancement information (SEI) NAL units have a
+// payload type field to indicate how they are to be
+// interpreted. CEAS-708 caption content is always transmitted with
+// payload type 0x04.
+var USER_DATA_REGISTERED_ITU_T_T35 = 4,
+ RBSP_TRAILING_BITS = 128,
+ Stream = require('../utils/stream');
+
+/**
+ * Parse a supplemental enhancement information (SEI) NAL unit.
+ * Stops parsing once a message of type ITU T T35 has been found.
+ *
+ * @param bytes {Uint8Array} the bytes of a SEI NAL unit
+ * @return {object} the parsed SEI payload
+ * @see Rec. ITU-T H.264, 7.3.2.3.1
+ */
+var parseSei = function(bytes) {
+ var
+ i = 0,
+ result = {
+ payloadType: -1,
+ payloadSize: 0,
+ },
+ payloadType = 0,
+ payloadSize = 0;
+
+ // go through the sei_rbsp parsing each each individual sei_message
+ while (i < bytes.byteLength) {
+ // stop once we have hit the end of the sei_rbsp
+ if (bytes[i] === RBSP_TRAILING_BITS) {
+ break;
+ }
+
+ // Parse payload type
+ while (bytes[i] === 0xFF) {
+ payloadType += 255;
+ i++;
+ }
+ payloadType += bytes[i++];
+
+ // Parse payload size
+ while (bytes[i] === 0xFF) {
+ payloadSize += 255;
+ i++;
+ }
+ payloadSize += bytes[i++];
+
+ // this sei_message is a 608/708 caption so save it and break
+ // there can only ever be one caption message in a frame's sei
+ if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
+ result.payloadType = payloadType;
+ result.payloadSize = payloadSize;
+ result.payload = bytes.subarray(i, i + payloadSize);
+ break;
+ }
+
+ // skip the payload and parse the next message
+ i += payloadSize;
+ payloadType = 0;
+ payloadSize = 0;
+ }
+
+ return result;
+};
+
+// see ANSI/SCTE 128-1 (2013), section 8.1
+var parseUserData = function(sei) {
+ // itu_t_t35_contry_code must be 181 (United States) for
+ // captions
+ if (sei.payload[0] !== 181) {
+ return null;
+ }
+
+ // itu_t_t35_provider_code should be 49 (ATSC) for captions
+ if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) {
+ return null;
+ }
+
+ // the user_identifier should be "GA94" to indicate ATSC1 data
+ if (String.fromCharCode(sei.payload[3],
+ sei.payload[4],
+ sei.payload[5],
+ sei.payload[6]) !== 'GA94') {
+ return null;
+ }
+
+ // finally, user_data_type_code should be 0x03 for caption data
+ if (sei.payload[7] !== 0x03) {
+ return null;
+ }
+
+ // return the user_data_type_structure and strip the trailing
+ // marker bits
+ return sei.payload.subarray(8, sei.payload.length - 1);
+};
+
+// see CEA-708-D, section 4.4
+var parseCaptionPackets = function(pts, userData) {
+ var results = [], i, count, offset, data;
+
+ // if this is just filler, return immediately
+ if (!(userData[0] & 0x40)) {
+ return results;
+ }
+
+ // parse out the cc_data_1 and cc_data_2 fields
+ count = userData[0] & 0x1f;
+ for (i = 0; i < count; i++) {
+ offset = i * 3;
+ data = {
+ type: userData[offset + 2] & 0x03,
+ pts: pts
+ };
+
+ // capture cc data when cc_valid is 1
+ if (userData[offset + 2] & 0x04) {
+ data.ccData = (userData[offset + 3] << 8) | userData[offset + 4];
+ results.push(data);
+ }
+ }
+ return results;
+};
+
+var CaptionStream = function() {
+ var self = this;
+ CaptionStream.prototype.init.call(this);
+
+ this.captionPackets_ = [];
+
+ this.field1_ = new Cea608Stream();
+
+ // forward data and done events from field1_ to this CaptionStream
+ this.field1_.on('data', this.trigger.bind(this, 'data'));
+ this.field1_.on('done', this.trigger.bind(this, 'done'));
+};
+CaptionStream.prototype = new Stream();
+CaptionStream.prototype.push = function(event) {
+ var sei, userData, captionPackets;
+
+ // only examine SEI NALs
+ if (event.nalUnitType !== 'sei_rbsp') {
+ return;
+ }
+
+ // parse the sei
+ sei = parseSei(event.escapedRBSP);
+
+ // ignore everything but user_data_registered_itu_t_t35
+ if (sei.payloadType !== USER_DATA_REGISTERED_ITU_T_T35) {
+ return;
+ }
+
+ // parse out the user data payload
+ userData = parseUserData(sei);
+
+ // ignore unrecognized userData
+ if (!userData) {
+ return;
+ }
+
+ // parse out CC data packets and save them for later
+ this.captionPackets_ = this.captionPackets_.concat(parseCaptionPackets(event.pts, userData));
+};
+
+CaptionStream.prototype.flush = function () {
+ // make sure we actually parsed captions before proceeding
+ if (!this.captionPackets_.length) {
+ this.field1_.flush();
+ return;
+ }
+
+ // sort caption byte-pairs based on their PTS values
+ this.captionPackets_.sort(function(a, b) {
+ return a.pts - b.pts;
+ });
+
+ // Push each caption into Cea608Stream
+ this.captionPackets_.forEach(this.field1_.push, this.field1_);
+
+ this.captionPackets_.length = 0;
+ this.field1_.flush();
+ return;
+};
+// ----------------------
+// Session to Application
+// ----------------------
+
+var BASIC_CHARACTER_TRANSLATION = {
+ 0x2a: 0xe1,
+ 0x5c: 0xe9,
+ 0x5e: 0xed,
+ 0x5f: 0xf3,
+ 0x60: 0xfa,
+ 0x7b: 0xe7,
+ 0x7c: 0xf7,
+ 0x7d: 0xd1,
+ 0x7e: 0xf1,
+ 0x7f: 0x2588
+};
+
+var getCharFromCode = function(code) {
+ if(code === null) {
+ return '';
+ }
+ code = BASIC_CHARACTER_TRANSLATION[code] || code;
+ return String.fromCharCode(code);
+};
+
+// Constants for the byte codes recognized by Cea608Stream. This
+// list is not exhaustive. For a more comprehensive listing and
+// semantics see
+// http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
+var PADDING = 0x0000,
+
+ // Pop-on Mode
+ RESUME_CAPTION_LOADING = 0x1420,
+ END_OF_CAPTION = 0x142f,
+
+ // Roll-up Mode
+ ROLL_UP_2_ROWS = 0x1425,
+ ROLL_UP_3_ROWS = 0x1426,
+ ROLL_UP_4_ROWS = 0x1427,
+ RESUME_DIRECT_CAPTIONING = 0x1429,
+ CARRIAGE_RETURN = 0x142d,
+ // Erasure
+ BACKSPACE = 0x1421,
+ ERASE_DISPLAYED_MEMORY = 0x142c,
+ ERASE_NON_DISPLAYED_MEMORY = 0x142e;
+
+// the index of the last row in a CEA-608 display buffer
+var BOTTOM_ROW = 14;
+// CEA-608 captions are rendered onto a 34x15 matrix of character
+// cells. The "bottom" row is the last element in the outer array.
+var createDisplayBuffer = function() {
+ var result = [], i = BOTTOM_ROW + 1;
+ while (i--) {
+ result.push('');
+ }
+ return result;
+};
+
+var Cea608Stream = function() {
+ Cea608Stream.prototype.init.call(this);
+
+ this.mode_ = 'popOn';
+ // When in roll-up mode, the index of the last row that will
+ // actually display captions. If a caption is shifted to a row
+ // with a lower index than this, it is cleared from the display
+ // buffer
+ this.topRow_ = 0;
+ this.startPts_ = 0;
+ this.displayed_ = createDisplayBuffer();
+ this.nonDisplayed_ = createDisplayBuffer();
+ this.lastControlCode_ = null;
+
+ this.push = function(packet) {
+ // Ignore other channels
+ if (packet.type !== 0) {
+ return;
+ }
+ var data, swap, char0, char1;
+ // remove the parity bits
+ data = packet.ccData & 0x7f7f;
+
+ // ignore duplicate control codes
+ if (data === this.lastControlCode_) {
+ this.lastControlCode_ = null;
+ return;
+ }
+
+ // Store control codes
+ if ((data & 0xf000) === 0x1000) {
+ this.lastControlCode_ = data;
+ } else {
+ this.lastControlCode_ = null;
+ }
+
+ switch (data) {
+ case PADDING:
+ break;
+ case RESUME_CAPTION_LOADING:
+ this.mode_ = 'popOn';
+ break;
+ case END_OF_CAPTION:
+ // if a caption was being displayed, it's gone now
+ this.flushDisplayed(packet.pts);
+
+ // flip memory
+ swap = this.displayed_;
+ this.displayed_ = this.nonDisplayed_;
+ this.nonDisplayed_ = swap;
+
+ // start measuring the time to display the caption
+ this.startPts_ = packet.pts;
+ break;
+
+ case ROLL_UP_2_ROWS:
+ this.topRow_ = BOTTOM_ROW - 1;
+ this.mode_ = 'rollUp';
+ break;
+ case ROLL_UP_3_ROWS:
+ this.topRow_ = BOTTOM_ROW - 2;
+ this.mode_ = 'rollUp';
+ break;
+ case ROLL_UP_4_ROWS:
+ this.topRow_ = BOTTOM_ROW - 3;
+ this.mode_ = 'rollUp';
+ break;
+ case CARRIAGE_RETURN:
+ this.flushDisplayed(packet.pts);
+ this.shiftRowsUp_();
+ this.startPts_ = packet.pts;
+ break;
+
+ case BACKSPACE:
+ if (this.mode_ === 'popOn') {
+ this.nonDisplayed_[BOTTOM_ROW] = this.nonDisplayed_[BOTTOM_ROW].slice(0, -1);
+ } else {
+ this.displayed_[BOTTOM_ROW] = this.displayed_[BOTTOM_ROW].slice(0, -1);
+ }
+ break;
+ case ERASE_DISPLAYED_MEMORY:
+ this.flushDisplayed(packet.pts);
+ this.displayed_ = createDisplayBuffer();
+ break;
+ case ERASE_NON_DISPLAYED_MEMORY:
+ this.nonDisplayed_ = createDisplayBuffer();
+ break;
+ default:
+ char0 = data >>> 8;
+ char1 = data & 0xff;
+
+ // Look for a Channel 1 Preamble Address Code
+ if (char0 >= 0x10 && char0 <= 0x17 &&
+ char1 >= 0x40 && char1 <= 0x7F &&
+ (char0 !== 0x10 || char1 < 0x60)) {
+ // Follow Safari's lead and replace the PAC with a space
+ char0 = 0x20;
+ // we only want one space so make the second character null
+ // which will get become '' in getCharFromCode
+ char1 = null;
+ }
+
+ // Look for special character sets
+ if ((char0 === 0x11 || char0 === 0x19) &&
+ (char1 >= 0x30 && char1 <= 0x3F)) {
+ // Put in eigth note and space
+ char0 = 0x266A;
+ char1 = '';
+ }
+
+ // ignore unsupported control codes
+ if ((char0 & 0xf0) === 0x10) {
+ return;
+ }
+
+ // character handling is dependent on the current mode
+ this[this.mode_](packet.pts, char0, char1);
+ break;
+ }
+ };
+};
+Cea608Stream.prototype = new Stream();
+// Trigger a cue point that captures the current state of the
+// display buffer
+Cea608Stream.prototype.flushDisplayed = function(pts) {
+ var content = this.displayed_
+ // remove spaces from the start and end of the string
+ .map(function(row) { return row.trim(); })
+ // remove empty rows
+ .filter(function(row) { return row.length; })
+ // combine all text rows to display in one cue
+ .join('\n');
+
+ if (content.length) {
+ this.trigger('data', {
+ startPts: this.startPts_,
+ endPts: pts,
+ text: content
+ });
+ }
+};
+
+// Mode Implementations
+Cea608Stream.prototype.popOn = function(pts, char0, char1) {
+ var baseRow = this.nonDisplayed_[BOTTOM_ROW];
+
+ // buffer characters
+ baseRow += getCharFromCode(char0);
+ baseRow += getCharFromCode(char1);
+ this.nonDisplayed_[BOTTOM_ROW] = baseRow;
+};
+
+Cea608Stream.prototype.rollUp = function(pts, char0, char1) {
+ var baseRow = this.displayed_[BOTTOM_ROW];
+ if (baseRow === '') {
+ // we're starting to buffer new display input, so flush out the
+ // current display
+ this.flushDisplayed(pts);
+
+ this.startPts_ = pts;
+ }
+
+ baseRow += getCharFromCode(char0);
+ baseRow += getCharFromCode(char1);
+
+ this.displayed_[BOTTOM_ROW] = baseRow;
+};
+Cea608Stream.prototype.shiftRowsUp_ = function() {
+ var i;
+ // clear out inactive rows
+ for (i = 0; i < this.topRow_; i++) {
+ this.displayed_[i] = '';
+ }
+ // shift displayed rows up
+ for (i = this.topRow_; i < BOTTOM_ROW; i++) {
+ this.displayed_[i] = this.displayed_[i + 1];
+ }
+ // clear out the bottom row
+ this.displayed_[BOTTOM_ROW] = '';
+};
+
+// exports
+module.exports = {
+ CaptionStream: CaptionStream,
+ Cea608Stream: Cea608Stream,
+};
+
+
+},{"../utils/stream":81}],74:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2015 Brightcove
+ * All rights reserved.
+ *
+ * A stream-based mp2t to mp4 converter. This utility can be used to
+ * deliver mp4s to a SourceBuffer on platforms that support native
+ * Media Source Extensions.
+ */
+'use strict';
+var Stream = require('../utils/stream.js'),
+ CaptionStream = require('./caption-stream'),
+ StreamTypes = require('./stream-types');
+
+var Stream = require('../utils/stream.js');
+var m2tsStreamTypes = require('./stream-types.js');
+
+// object types
+var
+ TransportPacketStream, TransportParseStream, ElementaryStream,
+ AacStream, H264Stream, NalByteStream;
+
+// constants
+var
+ MP2T_PACKET_LENGTH = 188, // bytes
+ SYNC_BYTE = 0x47,
+
+/**
+ * Splits an incoming stream of binary data into MPEG-2 Transport
+ * Stream packets.
+ */
+TransportPacketStream = function() {
+ var
+ buffer = new Uint8Array(MP2T_PACKET_LENGTH),
+ bytesInBuffer = 0;
+
+ TransportPacketStream.prototype.init.call(this);
+
+ // Deliver new bytes to the stream.
+
+ this.push = function(bytes) {
+ var
+ i = 0,
+ startIndex = 0,
+ endIndex = MP2T_PACKET_LENGTH,
+ everything;
+
+ // If there are bytes remaining from the last segment, prepend them to the
+ // bytes that were pushed in
+ if (bytesInBuffer) {
+ everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
+ everything.set(buffer.subarray(0, bytesInBuffer));
+ everything.set(bytes, bytesInBuffer);
+ bytesInBuffer = 0;
+ } else {
+ everything = bytes;
+ }
+
+ // While we have enough data for a packet
+ while (endIndex < everything.byteLength) {
+ // Look for a pair of start and end sync bytes in the data..
+ if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
+ // We found a packet so emit it and jump one whole packet forward in
+ // the stream
+ this.trigger('data', everything.subarray(startIndex, endIndex));
+ startIndex += MP2T_PACKET_LENGTH;
+ endIndex += MP2T_PACKET_LENGTH;
+ continue;
+ }
+ // If we get here, we have somehow become de-synchronized and we need to step
+ // forward one byte at a time until we find a pair of sync bytes that denote
+ // a packet
+ startIndex++;
+ endIndex++;
+ }
+
+ // If there was some data left over at the end of the segment that couldn't
+ // possibly be a whole packet, keep it because it might be the start of a packet
+ // that continues in the next segment
+ if (startIndex < everything.byteLength) {
+ buffer.set(everything.subarray(startIndex), 0);
+ bytesInBuffer = everything.byteLength - startIndex;
+ }
+ };
+
+ this.flush = function () {
+ // If the buffer contains a whole packet when we are being flushed, emit it
+ // and empty the buffer. Otherwise hold onto the data because it may be
+ // important for decoding the next segment
+ if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
+ this.trigger('data', buffer);
+ bytesInBuffer = 0;
+ }
+ this.trigger('done');
+ };
+};
+TransportPacketStream.prototype = new Stream();
+
+/**
+ * Accepts an MP2T TransportPacketStream and emits data events with parsed
+ * forms of the individual transport stream packets.
+ */
+TransportParseStream = function() {
+ var parsePsi, parsePat, parsePmt, parsePes, self;
+ TransportParseStream.prototype.init.call(this);
+ self = this;
+
+ this.packetsWaitingForPmt = [];
+ this.programMapTable = undefined;
+
+ parsePsi = function(payload, psi) {
+ var offset = 0;
+
+ // PSI packets may be split into multiple sections and those
+ // sections may be split into multiple packets. If a PSI
+ // section starts in this packet, the payload_unit_start_indicator
+ // will be true and the first byte of the payload will indicate
+ // the offset from the current position to the start of the
+ // section.
+ if (psi.payloadUnitStartIndicator) {
+ offset += payload[offset] + 1;
+ }
+
+ if (psi.type === 'pat') {
+ parsePat(payload.subarray(offset), psi);
+ } else {
+ parsePmt(payload.subarray(offset), psi);
+ }
+ };
+
+ parsePat = function(payload, pat) {
+ pat.section_number = payload[7];
+ pat.last_section_number = payload[8];
+
+ // skip the PSI header and parse the first PMT entry
+ self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
+ pat.pmtPid = self.pmtPid;
+ };
+
+ /**
+ * Parse out the relevant fields of a Program Map Table (PMT).
+ * @param payload {Uint8Array} the PMT-specific portion of an MP2T
+ * packet. The first byte in this array should be the table_id
+ * field.
+ * @param pmt {object} the object that should be decorated with
+ * fields parsed from the PMT.
+ */
+ parsePmt = function(payload, pmt) {
+ var sectionLength, tableEnd, programInfoLength, offset;
+
+ // PMTs can be sent ahead of the time when they should actually
+ // take effect. We don't believe this should ever be the case
+ // for HLS but we'll ignore "forward" PMT declarations if we see
+ // them. Future PMT declarations have the current_next_indicator
+ // set to zero.
+ if (!(payload[5] & 0x01)) {
+ return;
+ }
+
+ // overwrite any existing program map table
+ self.programMapTable = {};
+
+ // the mapping table ends at the end of the current section
+ sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
+ tableEnd = 3 + sectionLength - 4;
+
+ // to determine where the table is, we have to figure out how
+ // long the program info descriptors are
+ programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
+
+ // advance the offset to the first entry in the mapping table
+ offset = 12 + programInfoLength;
+ while (offset < tableEnd) {
+ // add an entry that maps the elementary_pid to the stream_type
+ self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset];
+
+ // move to the next table entry
+ // skip past the elementary stream descriptors, if present
+ offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
+ }
+
+ // record the map on the packet as well
+ pmt.programMapTable = self.programMapTable;
+
+ // if there are any packets waiting for a PMT to be found, process them now
+ while (self.packetsWaitingForPmt.length) {
+ self.processPes_.apply(self, self.packetsWaitingForPmt.shift());
+ }
+ };
+
+ /**
+ * Deliver a new MP2T packet to the stream.
+ */
+ this.push = function(packet) {
+ var
+ result = {},
+ offset = 4;
+
+ result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
+
+ // pid is a 13-bit field starting at the last bit of packet[1]
+ result.pid = packet[1] & 0x1f;
+ result.pid <<= 8;
+ result.pid |= packet[2];
+
+ // if an adaption field is present, its length is specified by the
+ // fifth byte of the TS packet header. The adaptation field is
+ // used to add stuffing to PES packets that don't fill a complete
+ // TS packet, and to specify some forms of timing and control data
+ // that we do not currently use.
+ if (((packet[3] & 0x30) >>> 4) > 0x01) {
+ offset += packet[offset] + 1;
+ }
+
+ // parse the rest of the packet based on the type
+ if (result.pid === 0) {
+ result.type = 'pat';
+ parsePsi(packet.subarray(offset), result);
+ this.trigger('data', result);
+ } else if (result.pid === this.pmtPid) {
+ result.type = 'pmt';
+ parsePsi(packet.subarray(offset), result);
+ this.trigger('data', result);
+ } else if (this.programMapTable === undefined) {
+ // When we have not seen a PMT yet, defer further processing of
+ // PES packets until one has been parsed
+ this.packetsWaitingForPmt.push([packet, offset, result]);
+ } else {
+ this.processPes_(packet, offset, result);
+ }
+ };
+
+ this.processPes_ = function (packet, offset, result) {
+ result.streamType = this.programMapTable[result.pid];
+ result.type = 'pes';
+ result.data = packet.subarray(offset);
+
+ this.trigger('data', result);
+ };
+
+};
+TransportParseStream.prototype = new Stream();
+TransportParseStream.STREAM_TYPES = {
+ h264: 0x1b,
+ adts: 0x0f
+};
+
+/**
+ * Reconsistutes program elementary stream (PES) packets from parsed
+ * transport stream packets. That is, if you pipe an
+ * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
+ * events will be events which capture the bytes for individual PES
+ * packets plus relevant metadata that has been extracted from the
+ * container.
+ */
+ElementaryStream = function() {
+ var
+ // PES packet fragments
+ video = {
+ data: [],
+ size: 0
+ },
+ audio = {
+ data: [],
+ size: 0
+ },
+ timedMetadata = {
+ data: [],
+ size: 0
+ },
+ parsePes = function(payload, pes) {
+ var ptsDtsFlags;
+
+ // find out if this packets starts a new keyframe
+ pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
+ // PES packets may be annotated with a PTS value, or a PTS value
+ // and a DTS value. Determine what combination of values is
+ // available to work with.
+ ptsDtsFlags = payload[7];
+
+ // PTS and DTS are normally stored as a 33-bit number. Javascript
+ // performs all bitwise operations on 32-bit integers but javascript
+ // supports a much greater range (52-bits) of integer using standard
+ // mathematical operations.
+ // We construct a 31-bit value using bitwise operators over the 31
+ // most significant bits and then multiply by 4 (equal to a left-shift
+ // of 2) before we add the final 2 least significant bits of the
+ // timestamp (equal to an OR.)
+ if (ptsDtsFlags & 0xC0) {
+ // the PTS and DTS are not written out directly. For information
+ // on how they are encoded, see
+ // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
+ pes.pts = (payload[9] & 0x0E) << 27
+ | (payload[10] & 0xFF) << 20
+ | (payload[11] & 0xFE) << 12
+ | (payload[12] & 0xFF) << 5
+ | (payload[13] & 0xFE) >>> 3;
+ pes.pts *= 4; // Left shift by 2
+ pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
+ pes.dts = pes.pts;
+ if (ptsDtsFlags & 0x40) {
+ pes.dts = (payload[14] & 0x0E ) << 27
+ | (payload[15] & 0xFF ) << 20
+ | (payload[16] & 0xFE ) << 12
+ | (payload[17] & 0xFF ) << 5
+ | (payload[18] & 0xFE ) >>> 3;
+ pes.dts *= 4; // Left shift by 2
+ pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
+ }
+ }
+
+ // the data section starts immediately after the PES header.
+ // pes_header_data_length specifies the number of header bytes
+ // that follow the last byte of the field.
+ pes.data = payload.subarray(9 + payload[8]);
+ },
+ flushStream = function(stream, type) {
+ var
+ packetData = new Uint8Array(stream.size),
+ event = {
+ type: type
+ },
+ i = 0,
+ fragment;
+
+ // do nothing if there is no buffered data
+ if (!stream.data.length) {
+ return;
+ }
+ event.trackId = stream.data[0].pid;
+
+ // reassemble the packet
+ while (stream.data.length) {
+ fragment = stream.data.shift();
+
+ packetData.set(fragment.data, i);
+ i += fragment.data.byteLength;
+ }
+
+ // parse assembled packet's PES header
+ parsePes(packetData, event);
+
+ stream.size = 0;
+
+ self.trigger('data', event);
+ },
+ self;
+
+ ElementaryStream.prototype.init.call(this);
+ self = this;
+
+ this.push = function(data) {
+ ({
+ pat: function() {
+ // we have to wait for the PMT to arrive as well before we
+ // have any meaningful metadata
+ },
+ pes: function() {
+ var stream, streamType;
+
+ switch (data.streamType) {
+ case StreamTypes.H264_STREAM_TYPE:
+ case m2tsStreamTypes.H264_STREAM_TYPE:
+ stream = video;
+ streamType = 'video';
+ break;
+ case StreamTypes.ADTS_STREAM_TYPE:
+ stream = audio;
+ streamType = 'audio';
+ break;
+ case StreamTypes.METADATA_STREAM_TYPE:
+ stream = timedMetadata;
+ streamType = 'timed-metadata';
+ break;
+ default:
+ // ignore unknown stream types
+ return;
+ }
+
+ // if a new packet is starting, we can flush the completed
+ // packet
+ if (data.payloadUnitStartIndicator) {
+ flushStream(stream, streamType);
+ }
+
+ // buffer this fragment until we are sure we've received the
+ // complete payload
+ stream.data.push(data);
+ stream.size += data.data.byteLength;
+ },
+ pmt: function() {
+ var
+ event = {
+ type: 'metadata',
+ tracks: []
+ },
+ programMapTable = data.programMapTable,
+ k,
+ track;
+
+ // translate streams to tracks
+ for (k in programMapTable) {
+ if (programMapTable.hasOwnProperty(k)) {
+ track = {
+ timelineStartInfo: {
+ baseMediaDecodeTime: 0
+ }
+ };
+ track.id = +k;
+ if (programMapTable[k] === m2tsStreamTypes.H264_STREAM_TYPE) {
+ track.codec = 'avc';
+ track.type = 'video';
+ } else if (programMapTable[k] === m2tsStreamTypes.ADTS_STREAM_TYPE) {
+ track.codec = 'adts';
+ track.type = 'audio';
+ }
+ event.tracks.push(track);
+ }
+ }
+ self.trigger('data', event);
+ }
+ })[data.type]();
+ };
+
+ /**
+ * Flush any remaining input. Video PES packets may be of variable
+ * length. Normally, the start of a new video packet can trigger the
+ * finalization of the previous packet. That is not possible if no
+ * more video is forthcoming, however. In that case, some other
+ * mechanism (like the end of the file) has to be employed. When it is
+ * clear that no additional data is forthcoming, calling this method
+ * will flush the buffered packets.
+ */
+ this.flush = function() {
+ // !!THIS ORDER IS IMPORTANT!!
+ // video first then audio
+ flushStream(video, 'video');
+ flushStream(audio, 'audio');
+ flushStream(timedMetadata, 'timed-metadata');
+ this.trigger('done');
+ };
+};
+ElementaryStream.prototype = new Stream();
+
+var m2ts = {
+ PAT_PID: 0x0000,
+ MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
+ TransportPacketStream: TransportPacketStream,
+ TransportParseStream: TransportParseStream,
+ ElementaryStream: ElementaryStream,
+ CaptionStream: CaptionStream.CaptionStream,
+ Cea608Stream: CaptionStream.Cea608Stream,
+ MetadataStream: require('./metadata-stream'),
+};
+
+for (var type in StreamTypes) {
+ if (StreamTypes.hasOwnProperty(type)) {
+ m2ts[type] = StreamTypes[type];
+ }
+}
+
+module.exports = m2ts;
+
+},{"../utils/stream.js":81,"./caption-stream":73,"./metadata-stream":75,"./stream-types":76,"./stream-types.js":76}],75:[function(require,module,exports){
+/**
+ * Accepts program elementary stream (PES) data events and parses out
+ * ID3 metadata from them, if present.
+ * @see http://id3.org/id3v2.3.0
+ */
+'use strict';
+var
+ Stream = require('../utils/stream'),
+ StreamTypes = require('./stream-types'),
+ // return a percent-encoded representation of the specified byte range
+ // @see http://en.wikipedia.org/wiki/Percent-encoding
+ percentEncode = function(bytes, start, end) {
+ var i, result = '';
+ for (i = start; i < end; i++) {
+ result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
+ }
+ return result;
+ },
+ // return the string representation of the specified byte range,
+ // interpreted as UTf-8.
+ parseUtf8 = function(bytes, start, end) {
+ return decodeURIComponent(percentEncode(bytes, start, end));
+ },
+ // return the string representation of the specified byte range,
+ // interpreted as ISO-8859-1.
+ parseIso88591 = function(bytes, start, end) {
+ return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
+ },
+ parseSyncSafeInteger = function (data) {
+ return (data[0] << 21) |
+ (data[1] << 14) |
+ (data[2] << 7) |
+ (data[3]);
+ },
+ tagParsers = {
+ 'TXXX': function(tag) {
+ var i;
+ if (tag.data[0] !== 3) {
+ // ignore frames with unrecognized character encodings
+ return;
+ }
+
+ for (i = 1; i < tag.data.length; i++) {
+ if (tag.data[i] === 0) {
+ // parse the text fields
+ tag.description = parseUtf8(tag.data, 1, i);
+ // do not include the null terminator in the tag value
+ tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1);
+ break;
+ }
+ }
+ tag.data = tag.value;
+ },
+ 'WXXX': function(tag) {
+ var i;
+ if (tag.data[0] !== 3) {
+ // ignore frames with unrecognized character encodings
+ return;
+ }
+
+ for (i = 1; i < tag.data.length; i++) {
+ if (tag.data[i] === 0) {
+ // parse the description and URL fields
+ tag.description = parseUtf8(tag.data, 1, i);
+ tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
+ break;
+ }
+ }
+ },
+ 'PRIV': function(tag) {
+ var i;
+
+ for (i = 0; i < tag.data.length; i++) {
+ if (tag.data[i] === 0) {
+ // parse the description and URL fields
+ tag.owner = parseIso88591(tag.data, 0, i);
+ break;
+ }
+ }
+ tag.privateData = tag.data.subarray(i + 1);
+ tag.data = tag.privateData;
+ }
+ },
+ MetadataStream;
+
+MetadataStream = function(options) {
+ var
+ settings = {
+ debug: !!(options && options.debug),
+
+ // the bytes of the program-level descriptor field in MP2T
+ // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
+ // program element descriptors"
+ descriptor: options && options.descriptor
+ },
+ // the total size in bytes of the ID3 tag being parsed
+ tagSize = 0,
+ // tag data that is not complete enough to be parsed
+ buffer = [],
+ // the total number of bytes currently in the buffer
+ bufferSize = 0,
+ i;
+
+ MetadataStream.prototype.init.call(this);
+
+ // calculate the text track in-band metadata track dispatch type
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
+ this.dispatchType = StreamTypes.METADATA_STREAM_TYPE.toString(16);
+ if (settings.descriptor) {
+ for (i = 0; i < settings.descriptor.length; i++) {
+ this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
+ }
+ }
+
+ this.push = function(chunk) {
+ var tag, frameStart, frameSize, frame, i, frameHeader;
+ if (chunk.type !== 'timed-metadata') {
+ return;
+ }
+
+ // if data_alignment_indicator is set in the PES header,
+ // we must have the start of a new ID3 tag. Assume anything
+ // remaining in the buffer was malformed and throw it out
+ if (chunk.dataAlignmentIndicator) {
+ bufferSize = 0;
+ buffer.length = 0;
+ }
+
+ // ignore events that don't look like ID3 data
+ if (buffer.length === 0 &&
+ (chunk.data.length < 10 ||
+ chunk.data[0] !== 'I'.charCodeAt(0) ||
+ chunk.data[1] !== 'D'.charCodeAt(0) ||
+ chunk.data[2] !== '3'.charCodeAt(0))) {
+ if (settings.debug) {
+ console.log('Skipping unrecognized metadata packet');
+ }
+ return;
+ }
+
+ // add this chunk to the data we've collected so far
+
+ buffer.push(chunk);
+ bufferSize += chunk.data.byteLength;
+
+ // grab the size of the entire frame from the ID3 header
+ if (buffer.length === 1) {
+ // the frame size is transmitted as a 28-bit integer in the
+ // last four bytes of the ID3 header.
+ // The most significant bit of each byte is dropped and the
+ // results concatenated to recover the actual value.
+ tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));
+
+ // ID3 reports the tag size excluding the header but it's more
+ // convenient for our comparisons to include it
+ tagSize += 10;
+ }
+
+ // if the entire frame has not arrived, wait for more data
+ if (bufferSize < tagSize) {
+ return;
+ }
+
+ // collect the entire frame so it can be parsed
+ tag = {
+ data: new Uint8Array(tagSize),
+ frames: [],
+ pts: buffer[0].pts,
+ dts: buffer[0].dts
+ };
+ for (i = 0; i < tagSize;) {
+ tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
+ i += buffer[0].data.byteLength;
+ bufferSize -= buffer[0].data.byteLength;
+ buffer.shift();
+ }
+
+ // find the start of the first frame and the end of the tag
+ frameStart = 10;
+ if (tag.data[5] & 0x40) {
+ // advance the frame start past the extended header
+ frameStart += 4; // header size field
+ frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));
+
+ // clip any padding off the end
+ tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
+ }
+
+ // parse one or more ID3 frames
+ // http://id3.org/id3v2.3.0#ID3v2_frame_overview
+ do {
+ // determine the number of bytes in this frame
+ frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
+ if (frameSize < 1) {
+ return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
+ }
+ frameHeader = String.fromCharCode(tag.data[frameStart],
+ tag.data[frameStart + 1],
+ tag.data[frameStart + 2],
+ tag.data[frameStart + 3]);
+
+
+ frame = {
+ id: frameHeader,
+ data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
+ };
+ frame.key = frame.id;
+ if (tagParsers[frame.id]) {
+ tagParsers[frame.id](frame);
+ if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
+ var
+ d = frame.data,
+ size = ((d[3] & 0x01) << 30) |
+ (d[4] << 22) |
+ (d[5] << 14) |
+ (d[6] << 6) |
+ (d[7] >>> 2);
+
+ size *= 4;
+ size += d[7] & 0x03;
+ frame.timeStamp = size;
+ this.trigger('timestamp', frame);
+ }
+ }
+ tag.frames.push(frame);
+
+ frameStart += 10; // advance past the frame header
+ frameStart += frameSize; // advance past the frame body
+ } while (frameStart < tagSize);
+ this.trigger('data', tag);
+ };
+};
+MetadataStream.prototype = new Stream();
+
+module.exports = MetadataStream;
+
+},{"../utils/stream":81,"./stream-types":76}],76:[function(require,module,exports){
+'use strict';
+
+module.exports = {
+ H264_STREAM_TYPE: 0x1B,
+ ADTS_STREAM_TYPE: 0x0F,
+ METADATA_STREAM_TYPE: 0x15
+};
+
+},{}],77:[function(require,module,exports){
+module.exports = {
+ generator: require('./mp4-generator'),
+ Transmuxer: require('./transmuxer').Transmuxer,
+ AudioSegmentStream: require('./transmuxer').AudioSegmentStream,
+ VideoSegmentStream: require('./transmuxer').VideoSegmentStream
+};
+
+},{"./mp4-generator":78,"./transmuxer":79}],78:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2015 Brightcove
+ * All rights reserved.
+ *
+ * Functions that generate fragmented MP4s suitable for use with Media
+ * Source Extensions.
+ */
+'use strict';
+
+var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak,
+ tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun,
+ types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR,
+ AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS;
+
+// pre-calculate constants
+(function() {
+ var i;
+ types = {
+ avc1: [], // codingname
+ avcC: [],
+ btrt: [],
+ dinf: [],
+ dref: [],
+ esds: [],
+ ftyp: [],
+ hdlr: [],
+ mdat: [],
+ mdhd: [],
+ mdia: [],
+ mfhd: [],
+ minf: [],
+ moof: [],
+ moov: [],
+ mp4a: [], // codingname
+ mvex: [],
+ mvhd: [],
+ sdtp: [],
+ smhd: [],
+ stbl: [],
+ stco: [],
+ stsc: [],
+ stsd: [],
+ stsz: [],
+ stts: [],
+ styp: [],
+ tfdt: [],
+ tfhd: [],
+ traf: [],
+ trak: [],
+ trun: [],
+ trex: [],
+ tkhd: [],
+ vmhd: []
+ };
+
+ // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
+ // don't throw an error
+ if (typeof Uint8Array === 'undefined') {
+ return;
+ }
+
+ for (i in types) {
+ if (types.hasOwnProperty(i)) {
+ types[i] = [
+ i.charCodeAt(0),
+ i.charCodeAt(1),
+ i.charCodeAt(2),
+ i.charCodeAt(3)
+ ];
+ }
+ }
+
+ MAJOR_BRAND = new Uint8Array([
+ 'i'.charCodeAt(0),
+ 's'.charCodeAt(0),
+ 'o'.charCodeAt(0),
+ 'm'.charCodeAt(0)
+ ]);
+ AVC1_BRAND = new Uint8Array([
+ 'a'.charCodeAt(0),
+ 'v'.charCodeAt(0),
+ 'c'.charCodeAt(0),
+ '1'.charCodeAt(0)
+ ]);
+ MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
+ VIDEO_HDLR = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x00, // pre_defined
+ 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x56, 0x69, 0x64, 0x65,
+ 0x6f, 0x48, 0x61, 0x6e,
+ 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
+ ]);
+ AUDIO_HDLR = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x00, // pre_defined
+ 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x53, 0x6f, 0x75, 0x6e,
+ 0x64, 0x48, 0x61, 0x6e,
+ 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
+ ]);
+ HDLR_TYPES = {
+ "video":VIDEO_HDLR,
+ "audio": AUDIO_HDLR
+ };
+ DREF = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x01, // entry_count
+ 0x00, 0x00, 0x00, 0x0c, // entry_size
+ 0x75, 0x72, 0x6c, 0x20, // 'url' type
+ 0x00, // version 0
+ 0x00, 0x00, 0x01 // entry_flags
+ ]);
+ SMHD = new Uint8Array([
+ 0x00, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, // balance, 0 means centered
+ 0x00, 0x00 // reserved
+ ]);
+ STCO = new Uint8Array([
+ 0x00, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x00 // entry_count
+ ]);
+ STSC = STCO;
+ STSZ = new Uint8Array([
+ 0x00, // version
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x00, // sample_size
+ 0x00, 0x00, 0x00, 0x00, // sample_count
+ ]);
+ STTS = STCO;
+ VMHD = new Uint8Array([
+ 0x00, // version
+ 0x00, 0x00, 0x01, // flags
+ 0x00, 0x00, // graphicsmode
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00 // opcolor
+ ]);
+})();
+
+box = function(type) {
+ var
+ payload = [],
+ size = 0,
+ i,
+ result,
+ view;
+
+ for (i = 1; i < arguments.length; i++) {
+ payload.push(arguments[i]);
+ }
+
+ i = payload.length;
+
+ // calculate the total size we need to allocate
+ while (i--) {
+ size += payload[i].byteLength;
+ }
+ result = new Uint8Array(size + 8);
+ view = new DataView(result.buffer, result.byteOffset, result.byteLength);
+ view.setUint32(0, result.byteLength);
+ result.set(type, 4);
+
+ // copy the payload into the result
+ for (i = 0, size = 8; i < payload.length; i++) {
+ result.set(payload[i], size);
+ size += payload[i].byteLength;
+ }
+ return result;
+};
+
+dinf = function() {
+ return box(types.dinf, box(types.dref, DREF));
+};
+
+esds = function(track) {
+ return box(types.esds, new Uint8Array([
+ 0x00, // version
+ 0x00, 0x00, 0x00, // flags
+
+ // ES_Descriptor
+ 0x03, // tag, ES_DescrTag
+ 0x19, // length
+ 0x00, 0x00, // ES_ID
+ 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
+
+ // DecoderConfigDescriptor
+ 0x04, // tag, DecoderConfigDescrTag
+ 0x11, // length
+ 0x40, // object type
+ 0x15, // streamType
+ 0x00, 0x06, 0x00, // bufferSizeDB
+ 0x00, 0x00, 0xda, 0xc0, // maxBitrate
+ 0x00, 0x00, 0xda, 0xc0, // avgBitrate
+
+ // DecoderSpecificInfo
+ 0x05, // tag, DecoderSpecificInfoTag
+ 0x02, // length
+ // ISO/IEC 14496-3, AudioSpecificConfig
+ // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
+ (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1),
+ (track.samplingfrequencyindex << 7) | (track.channelcount << 3),
+ 0x06, 0x01, 0x02 // GASpecificConfig
+ ]));
+};
+
+ftyp = function() {
+ return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
+};
+
+hdlr = function(type) {
+ return box(types.hdlr, HDLR_TYPES[type]);
+};
+mdat = function(data) {
+ return box(types.mdat, data);
+};
+mdhd = function(track) {
+ var result = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x02, // creation_time
+ 0x00, 0x00, 0x00, 0x03, // modification_time
+ 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
+
+ (track.duration >>> 24) & 0xFF,
+ (track.duration >>> 16) & 0xFF,
+ (track.duration >>> 8) & 0xFF,
+ track.duration & 0xFF, // duration
+ 0x55, 0xc4, // 'und' language (undetermined)
+ 0x00, 0x00
+ ]);
+
+ // Use the sample rate from the track metadata, when it is
+ // defined. The sample rate can be parsed out of an ADTS header, for
+ // instance.
+ if (track.samplerate) {
+ result[12] = (track.samplerate >>> 24) & 0xFF;
+ result[13] = (track.samplerate >>> 16) & 0xFF;
+ result[14] = (track.samplerate >>> 8) & 0xFF;
+ result[15] = (track.samplerate) & 0xFF;
+ }
+
+ return box(types.mdhd, result);
+};
+mdia = function(track) {
+ return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
+};
+mfhd = function(sequenceNumber) {
+ return box(types.mfhd, new Uint8Array([
+ 0x00,
+ 0x00, 0x00, 0x00, // flags
+ (sequenceNumber & 0xFF000000) >> 24,
+ (sequenceNumber & 0xFF0000) >> 16,
+ (sequenceNumber & 0xFF00) >> 8,
+ sequenceNumber & 0xFF, // sequence_number
+ ]));
+};
+minf = function(track) {
+ return box(types.minf,
+ track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
+ dinf(),
+ stbl(track));
+};
+moof = function(sequenceNumber, tracks) {
+ var
+ trackFragments = [],
+ i = tracks.length;
+ // build traf boxes for each track fragment
+ while (i--) {
+ trackFragments[i] = traf(tracks[i]);
+ }
+ return box.apply(null, [
+ types.moof,
+ mfhd(sequenceNumber)
+ ].concat(trackFragments));
+};
+/**
+ * Returns a movie box.
+ * @param tracks {array} the tracks associated with this movie
+ * @see ISO/IEC 14496-12:2012(E), section 8.2.1
+ */
+moov = function(tracks) {
+ var
+ i = tracks.length,
+ boxes = [];
+
+ while (i--) {
+ boxes[i] = trak(tracks[i]);
+ }
+
+ return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
+};
+mvex = function(tracks) {
+ var
+ i = tracks.length,
+ boxes = [];
+
+ while (i--) {
+ boxes[i] = trex(tracks[i]);
+ }
+ return box.apply(null, [types.mvex].concat(boxes));
+};
+mvhd = function(duration) {
+ var
+ bytes = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x01, // creation_time
+ 0x00, 0x00, 0x00, 0x02, // modification_time
+ 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
+ (duration & 0xFF000000) >> 24,
+ (duration & 0xFF0000) >> 16,
+ (duration & 0xFF00) >> 8,
+ duration & 0xFF, // duration
+ 0x00, 0x01, 0x00, 0x00, // 1.0 rate
+ 0x01, 0x00, // 1.0 volume
+ 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, // pre_defined
+ 0xff, 0xff, 0xff, 0xff // next_track_ID
+ ]);
+ return box(types.mvhd, bytes);
+};
+
+sdtp = function(track) {
+ var
+ samples = track.samples || [],
+ bytes = new Uint8Array(4 + samples.length),
+ flags,
+ i;
+
+ // leave the full box header (4 bytes) all zero
+
+ // write the sample table
+ for (i = 0; i < samples.length; i++) {
+ flags = samples[i].flags;
+
+ bytes[i + 4] = (flags.dependsOn << 4) |
+ (flags.isDependedOn << 2) |
+ (flags.hasRedundancy);
+ }
+
+ return box(types.sdtp,
+ bytes);
+};
+
+stbl = function(track) {
+ return box(types.stbl,
+ stsd(track),
+ box(types.stts, STTS),
+ box(types.stsc, STSC),
+ box(types.stsz, STSZ),
+ box(types.stco, STCO));
+};
+
+(function() {
+ var videoSample, audioSample;
+
+ stsd = function(track) {
+
+ return box(types.stsd, new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ 0x00, 0x00, 0x00, 0x01
+ ]), track.type === 'video' ? videoSample(track) : audioSample(track));
+ };
+
+ videoSample = function(track) {
+ var
+ sps = track.sps || [],
+ pps = track.pps || [],
+ sequenceParameterSets = [],
+ pictureParameterSets = [],
+ i;
+
+ // assemble the SPSs
+ for (i = 0; i < sps.length; i++) {
+ sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
+ sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
+ sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
+ }
+
+ // assemble the PPSs
+ for (i = 0; i < pps.length; i++) {
+ pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
+ pictureParameterSets.push((pps[i].byteLength & 0xFF));
+ pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
+ }
+
+ return box(types.avc1, new Uint8Array([
+ 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x01, // data_reference_index
+ 0x00, 0x00, // pre_defined
+ 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, // pre_defined
+ (track.width & 0xff00) >> 8,
+ track.width & 0xff, // width
+ (track.height & 0xff00) >> 8,
+ track.height & 0xff, // height
+ 0x00, 0x48, 0x00, 0x00, // horizresolution
+ 0x00, 0x48, 0x00, 0x00, // vertresolution
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x01, // frame_count
+ 0x13,
+ 0x76, 0x69, 0x64, 0x65,
+ 0x6f, 0x6a, 0x73, 0x2d,
+ 0x63, 0x6f, 0x6e, 0x74,
+ 0x72, 0x69, 0x62, 0x2d,
+ 0x68, 0x6c, 0x73, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, // compressorname
+ 0x00, 0x18, // depth = 24
+ 0x11, 0x11 // pre_defined = -1
+ ]), box(types.avcC, new Uint8Array([
+ 0x01, // configurationVersion
+ track.profileIdc, // AVCProfileIndication
+ track.profileCompatibility, // profile_compatibility
+ track.levelIdc, // AVCLevelIndication
+ 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
+ ].concat([
+ sps.length // numOfSequenceParameterSets
+ ]).concat(sequenceParameterSets).concat([
+ pps.length // numOfPictureParameterSets
+ ]).concat(pictureParameterSets))), // "PPS"
+ box(types.btrt, new Uint8Array([
+ 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
+ 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
+ 0x00, 0x2d, 0xc6, 0xc0
+ ])) // avgBitrate
+ );
+ };
+
+ audioSample = function(track) {
+ return box(types.mp4a, new Uint8Array([
+
+ // SampleEntry, ISO/IEC 14496-12
+ 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x01, // data_reference_index
+
+ // AudioSampleEntry, ISO/IEC 14496-12
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ (track.channelcount & 0xff00) >> 8,
+ (track.channelcount & 0xff), // channelcount
+
+ (track.samplesize & 0xff00) >> 8,
+ (track.samplesize & 0xff), // samplesize
+ 0x00, 0x00, // pre_defined
+ 0x00, 0x00, // reserved
+
+ (track.samplerate & 0xff00) >> 8,
+ (track.samplerate & 0xff),
+ 0x00, 0x00 // samplerate, 16.16
+
+ // MP4AudioSampleEntry, ISO/IEC 14496-14
+ ]), esds(track));
+ };
+})();
+
+styp = function() {
+ return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
+};
+
+tkhd = function(track) {
+ var result = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x07, // flags
+ 0x00, 0x00, 0x00, 0x00, // creation_time
+ 0x00, 0x00, 0x00, 0x00, // modification_time
+ (track.id & 0xFF000000) >> 24,
+ (track.id & 0xFF0000) >> 16,
+ (track.id & 0xFF00) >> 8,
+ track.id & 0xFF, // track_ID
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ (track.duration & 0xFF000000) >> 24,
+ (track.duration & 0xFF0000) >> 16,
+ (track.duration & 0xFF00) >> 8,
+ track.duration & 0xFF, // duration
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, // reserved
+ 0x00, 0x00, // layer
+ 0x00, 0x00, // alternate_group
+ 0x01, 0x00, // non-audio track volume
+ 0x00, 0x00, // reserved
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
+ (track.width & 0xFF00) >> 8,
+ track.width & 0xFF,
+ 0x00, 0x00, // width
+ (track.height & 0xFF00) >> 8,
+ track.height & 0xFF,
+ 0x00, 0x00 // height
+ ]);
+
+ return box(types.tkhd, result);
+};
+
+/**
+ * Generate a track fragment (traf) box. A traf box collects metadata
+ * about tracks in a movie fragment (moof) box.
+ */
+traf = function(track) {
+ var trackFragmentHeader, trackFragmentDecodeTime,
+ trackFragmentRun, sampleDependencyTable, dataOffset;
+
+ trackFragmentHeader = box(types.tfhd, new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x3a, // flags
+ (track.id & 0xFF000000) >> 24,
+ (track.id & 0xFF0000) >> 16,
+ (track.id & 0xFF00) >> 8,
+ (track.id & 0xFF), // track_ID
+ 0x00, 0x00, 0x00, 0x01, // sample_description_index
+ 0x00, 0x00, 0x00, 0x00, // default_sample_duration
+ 0x00, 0x00, 0x00, 0x00, // default_sample_size
+ 0x00, 0x00, 0x00, 0x00 // default_sample_flags
+ ]));
+
+ trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ // baseMediaDecodeTime
+ (track.baseMediaDecodeTime >>> 24) & 0xFF,
+ (track.baseMediaDecodeTime >>> 16) & 0xFF,
+ (track.baseMediaDecodeTime >>> 8) & 0xFF,
+ track.baseMediaDecodeTime & 0xFF
+ ]));
+
+ // the data offset specifies the number of bytes from the start of
+ // the containing moof to the first payload byte of the associated
+ // mdat
+ dataOffset = (32 + // tfhd
+ 16 + // tfdt
+ 8 + // traf header
+ 16 + // mfhd
+ 8 + // moof header
+ 8); // mdat header
+
+ // audio tracks require less metadata
+ if (track.type === 'audio') {
+ trackFragmentRun = trun(track, dataOffset);
+ return box(types.traf,
+ trackFragmentHeader,
+ trackFragmentDecodeTime,
+ trackFragmentRun);
+ }
+
+ // video tracks should contain an independent and disposable samples
+ // box (sdtp)
+ // generate one and adjust offsets to match
+ sampleDependencyTable = sdtp(track);
+ trackFragmentRun = trun(track,
+ sampleDependencyTable.length + dataOffset);
+ return box(types.traf,
+ trackFragmentHeader,
+ trackFragmentDecodeTime,
+ trackFragmentRun,
+ sampleDependencyTable);
+};
+
+/**
+ * Generate a track box.
+ * @param track {object} a track definition
+ * @return {Uint8Array} the track box
+ */
+trak = function(track) {
+ track.duration = track.duration || 0xffffffff;
+ return box(types.trak,
+ tkhd(track),
+ mdia(track));
+};
+
+trex = function(track) {
+ var result = new Uint8Array([
+ 0x00, // version 0
+ 0x00, 0x00, 0x00, // flags
+ (track.id & 0xFF000000) >> 24,
+ (track.id & 0xFF0000) >> 16,
+ (track.id & 0xFF00) >> 8,
+ (track.id & 0xFF), // track_ID
+ 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
+ 0x00, 0x00, 0x00, 0x00, // default_sample_duration
+ 0x00, 0x00, 0x00, 0x00, // default_sample_size
+ 0x00, 0x01, 0x00, 0x01 // default_sample_flags
+ ]);
+ // the last two bytes of default_sample_flags is the sample
+ // degradation priority, a hint about the importance of this sample
+ // relative to others. Lower the degradation priority for all sample
+ // types other than video.
+ if (track.type !== 'video') {
+ result[result.length - 1] = 0x00;
+ }
+
+ return box(types.trex, result);
+};
+
+(function() {
+ var audioTrun, videoTrun, trunHeader;
+
+ // This method assumes all samples are uniform. That is, if a
+ // duration is present for the first sample, it will be present for
+ // all subsequent samples.
+ // see ISO/IEC 14496-12:2012, Section 8.8.8.1
+ trunHeader = function(samples, offset) {
+ var durationPresent = 0, sizePresent = 0,
+ flagsPresent = 0, compositionTimeOffset = 0;
+
+ // trun flag constants
+ if (samples.length) {
+ if (samples[0].duration !== undefined) {
+ durationPresent = 0x1;
+ }
+ if (samples[0].size !== undefined) {
+ sizePresent = 0x2;
+ }
+ if (samples[0].flags !== undefined) {
+ flagsPresent = 0x4;
+ }
+ if (samples[0].compositionTimeOffset !== undefined) {
+ compositionTimeOffset = 0x8;
+ }
+ }
+
+ return [
+ 0x00, // version 0
+ 0x00,
+ durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
+ 0x01, // flags
+ (samples.length & 0xFF000000) >>> 24,
+ (samples.length & 0xFF0000) >>> 16,
+ (samples.length & 0xFF00) >>> 8,
+ samples.length & 0xFF, // sample_count
+ (offset & 0xFF000000) >>> 24,
+ (offset & 0xFF0000) >>> 16,
+ (offset & 0xFF00) >>> 8,
+ offset & 0xFF // data_offset
+ ];
+ };
+
+ videoTrun = function(track, offset) {
+ var bytes, samples, sample, i;
+
+ samples = track.samples || [];
+ offset += 8 + 12 + (16 * samples.length);
+
+ bytes = trunHeader(samples, offset);
+
+ for (i = 0; i < samples.length; i++) {
+ sample = samples[i];
+ bytes = bytes.concat([
+ (sample.duration & 0xFF000000) >>> 24,
+ (sample.duration & 0xFF0000) >>> 16,
+ (sample.duration & 0xFF00) >>> 8,
+ sample.duration & 0xFF, // sample_duration
+ (sample.size & 0xFF000000) >>> 24,
+ (sample.size & 0xFF0000) >>> 16,
+ (sample.size & 0xFF00) >>> 8,
+ sample.size & 0xFF, // sample_size
+ (sample.flags.isLeading << 2) | sample.flags.dependsOn,
+ (sample.flags.isDependedOn << 6) |
+ (sample.flags.hasRedundancy << 4) |
+ (sample.flags.paddingValue << 1) |
+ sample.flags.isNonSyncSample,
+ sample.flags.degradationPriority & 0xF0 << 8,
+ sample.flags.degradationPriority & 0x0F, // sample_flags
+ (sample.compositionTimeOffset & 0xFF000000) >>> 24,
+ (sample.compositionTimeOffset & 0xFF0000) >>> 16,
+ (sample.compositionTimeOffset & 0xFF00) >>> 8,
+ sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
+ ]);
+ }
+ return box(types.trun, new Uint8Array(bytes));
+ };
+
+ audioTrun = function(track, offset) {
+ var bytes, samples, sample, i;
+
+ samples = track.samples || [];
+ offset += 8 + 12 + (8 * samples.length);
+
+ bytes = trunHeader(samples, offset);
+
+ for (i = 0; i < samples.length; i++) {
+ sample = samples[i];
+ bytes = bytes.concat([
+ (sample.duration & 0xFF000000) >>> 24,
+ (sample.duration & 0xFF0000) >>> 16,
+ (sample.duration & 0xFF00) >>> 8,
+ sample.duration & 0xFF, // sample_duration
+ (sample.size & 0xFF000000) >>> 24,
+ (sample.size & 0xFF0000) >>> 16,
+ (sample.size & 0xFF00) >>> 8,
+ sample.size & 0xFF]); // sample_size
+ }
+
+ return box(types.trun, new Uint8Array(bytes));
+ };
+
+ trun = function(track, offset) {
+ if (track.type === 'audio') {
+ return audioTrun(track, offset);
+ } else {
+ return videoTrun(track, offset);
+ }
+ };
+})();
+
+module.exports = {
+ ftyp: ftyp,
+ mdat: mdat,
+ moof: moof,
+ moov: moov,
+ initSegment: function(tracks) {
+ var
+ fileType = ftyp(),
+ movie = moov(tracks),
+ result;
+
+ result = new Uint8Array(fileType.byteLength + movie.byteLength);
+ result.set(fileType);
+ result.set(movie, fileType.byteLength);
+ return result;
+ }
+};
+
+},{}],79:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2015 Brightcove
+ * All rights reserved.
+ *
+ * A stream-based mp2t to mp4 converter. This utility can be used to
+ * deliver mp4s to a SourceBuffer on platforms that support native
+ * Media Source Extensions.
+ */
+'use strict';
+
+var Stream = require('../utils/stream.js');
+var mp4 = require('./mp4-generator.js');
+var m2ts = require('../m2ts/m2ts.js');
+var AdtsStream = require('../codecs/adts.js');
+var H264Stream = require('../codecs/h264').H264Stream;
+var AacStream = require('../aac');
+
+// constants
+var AUDIO_PROPERTIES = [
+ 'audioobjecttype',
+ 'channelcount',
+ 'samplerate',
+ 'samplingfrequencyindex',
+ 'samplesize'
+];
+
+var VIDEO_PROPERTIES = [
+ 'width',
+ 'height',
+ 'profileIdc',
+ 'levelIdc',
+ 'profileCompatibility',
+];
+
+// object types
+var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
+
+// Helper functions
+var
+ createDefaultSample,
+ isLikelyAacData,
+ collectDtsInfo,
+ clearDtsInfo,
+ calculateTrackBaseMediaDecodeTime,
+ arrayEquals,
+ sumFrameByteLengths;
+
+/**
+ * Default sample object
+ * see ISO/IEC 14496-12:2012, section 8.6.4.3
+ */
+createDefaultSample = function () {
+ return {
+ size: 0,
+ flags: {
+ isLeading: 0,
+ dependsOn: 1,
+ isDependedOn: 0,
+ hasRedundancy: 0,
+ degradationPriority: 0
+ }
+ };
+};
+
+isLikelyAacData = function (data) {
+ if ((data[0] === 'I'.charCodeAt(0)) &&
+ (data[1] === 'D'.charCodeAt(0)) &&
+ (data[2] === '3'.charCodeAt(0))) {
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Compare two arrays (even typed) for same-ness
+ */
+arrayEquals = function(a, b) {
+ var
+ i;
+
+ if (a.length !== b.length) {
+ return false;
+ }
+
+ // compare the value of each element in the array
+ for (i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Sum the `byteLength` properties of the data in each AAC frame
+ */
+sumFrameByteLengths = function(array) {
+ var
+ i,
+ currentObj,
+ sum = 0;
+
+ // sum the byteLength's all each nal unit in the frame
+ for (i = 0; i < array.length; i++) {
+ currentObj = array[i];
+ sum += currentObj.data.byteLength;
+ }
+
+ return sum;
+};
+
+/**
+ * Constructs a single-track, ISO BMFF media segment from AAC data
+ * events. The output of this stream can be fed to a SourceBuffer
+ * configured with a suitable initialization segment.
+ */
+AudioSegmentStream = function(track) {
+ var
+ adtsFrames = [],
+ sequenceNumber = 0,
+ earliestAllowedDts = 0;
+
+ AudioSegmentStream.prototype.init.call(this);
+
+ this.push = function(data) {
+ collectDtsInfo(track, data);
+
+ if (track) {
+ AUDIO_PROPERTIES.forEach(function(prop) {
+ track[prop] = data[prop];
+ });
+ }
+
+ // buffer audio data until end() is called
+ adtsFrames.push(data);
+ };
+
+ this.setEarliestDts = function(earliestDts) {
+ earliestAllowedDts = earliestDts - track.timelineStartInfo.baseMediaDecodeTime;
+ };
+
+ this.flush = function() {
+ var
+ frames,
+ moof,
+ mdat,
+ boxes;
+
+ // return early if no audio data has been observed
+ if (adtsFrames.length === 0) {
+ this.trigger('done', 'AudioSegmentStream');
+ return;
+ }
+
+ frames = this.trimAdtsFramesByEarliestDts_(adtsFrames);
+
+ // we have to build the index from byte locations to
+ // samples (that is, adts frames) in the audio data
+ track.samples = this.generateSampleTable_(frames);
+
+ // concatenate the audio data to constuct the mdat
+ mdat = mp4.mdat(this.concatenateFrameData_(frames));
+
+ adtsFrames = [];
+
+ calculateTrackBaseMediaDecodeTime(track);
+ moof = mp4.moof(sequenceNumber, [track]);
+ boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
+
+ // bump the sequence number for next time
+ sequenceNumber++;
+
+ boxes.set(moof);
+ boxes.set(mdat, moof.byteLength);
+
+ clearDtsInfo(track);
+
+ this.trigger('data', {track: track, boxes: boxes});
+ this.trigger('done', 'AudioSegmentStream');
+ };
+
+ // If the audio segment extends before the earliest allowed dts
+ // value, remove AAC frames until starts at or after the earliest
+ // allowed DTS so that we don't end up with a negative baseMedia-
+ // DecodeTime for the audio track
+ this.trimAdtsFramesByEarliestDts_ = function(adtsFrames) {
+ if (track.minSegmentDts >= earliestAllowedDts) {
+ return adtsFrames;
+ }
+
+ // We will need to recalculate the earliest segment Dts
+ track.minSegmentDts = Infinity;
+
+ return adtsFrames.filter(function(currentFrame) {
+ // If this is an allowed frame, keep it and record it's Dts
+ if (currentFrame.dts >= earliestAllowedDts) {
+ track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
+ track.minSegmentPts = track.minSegmentDts;
+ return true;
+ }
+ // Otherwise, discard it
+ return false;
+ });
+ };
+
+ // generate the track's raw mdat data from an array of frames
+ this.generateSampleTable_ = function(frames) {
+ var
+ i,
+ currentFrame,
+ samples = [];
+
+ for (i = 0; i < frames.length; i++) {
+ currentFrame = frames[i];
+ samples.push({
+ size: currentFrame.data.byteLength,
+ duration: 1024 // For AAC audio, all samples contain 1024 samples
+ });
+ }
+ return samples;
+ };
+
+ // generate the track's sample table from an array of frames
+ this.concatenateFrameData_ = function(frames) {
+ var
+ i,
+ currentFrame,
+ dataOffset = 0,
+ data = new Uint8Array(sumFrameByteLengths(frames));
+
+ for (i = 0; i < frames.length; i++) {
+ currentFrame = frames[i];
+
+ data.set(currentFrame.data, dataOffset);
+ dataOffset += currentFrame.data.byteLength;
+ }
+ return data;
+ };
+};
+
+AudioSegmentStream.prototype = new Stream();
+
+/**
+ * Constructs a single-track, ISO BMFF media segment from H264 data
+ * events. The output of this stream can be fed to a SourceBuffer
+ * configured with a suitable initialization segment.
+ * @param track {object} track metadata configuration
+ */
+VideoSegmentStream = function(track) {
+ var
+ sequenceNumber = 0,
+ nalUnits = [],
+ config,
+ pps;
+
+ VideoSegmentStream.prototype.init.call(this);
+
+ delete track.minPTS;
+
+ this.gopCache_ = [];
+
+ this.push = function(nalUnit) {
+ collectDtsInfo(track, nalUnit);
+
+ // record the track config
+ if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
+ config = nalUnit.config;
+ track.sps = [nalUnit.data];
+
+ VIDEO_PROPERTIES.forEach(function(prop) {
+ track[prop] = config[prop];
+ }, this);
+ }
+
+ if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
+ !pps) {
+ pps = nalUnit.data;
+ track.pps = [nalUnit.data];
+ }
+
+ // buffer video until flush() is called
+ nalUnits.push(nalUnit);
+ };
+
+ this.flush = function() {
+ var
+ frames,
+ gopForFusion,
+ gops,
+ moof,
+ mdat,
+ boxes;
+
+ // Throw away nalUnits at the start of the byte stream until
+ // we find the first AUD
+ while (nalUnits.length) {
+ if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
+ break;
+ }
+ nalUnits.shift();
+ }
+
+ // Return early if no video data has been observed
+ if (nalUnits.length === 0) {
+ this.resetStream_();
+ this.trigger('done', 'VideoSegmentStream');
+ return;
+ }
+
+ // Organize the raw nal-units into arrays that represent
+ // higher-level constructs such as frames and gops
+ // (group-of-pictures)
+ frames = this.groupNalsIntoFrames_(nalUnits);
+ gops = this.groupFramesIntoGops_(frames);
+
+ // If the first frame of this fragment is not a keyframe we have
+ // a problem since MSE (on Chrome) requires a leading keyframe.
+ //
+ // We have two approaches to repairing this situation:
+ // 1) GOP-FUSION:
+ // This is where we keep track of the GOPS (group-of-pictures)
+ // from previous fragments and attempt to find one that we can
+ // prepend to the current fragment in order to create a valid
+ // fragment.
+ // 2) KEYFRAME-PULLING:
+ // Here we search for the first keyframe in the fragment and
+ // throw away all the frames between the start of the fragment
+ // and that keyframe. We then extend the duration and pull the
+ // PTS of the keyframe forward so that it covers the time range
+ // of the frames that were disposed of.
+ //
+ // #1 is far prefereable over #2 which can cause "stuttering" but
+ // requires more things to be just right.
+ if (!gops[0][0].keyFrame) {
+ // Search for a gop for fusion from our gopCache
+ gopForFusion = this.getGopForFusion_(nalUnits[0], track);
+
+ if (gopForFusion) {
+ gops.unshift(gopForFusion);
+ // Adjust Gops' metadata to account for the inclusion of the
+ // new gop at the beginning
+ gops.byteLength += gopForFusion.byteLength;
+ gops.nalCount += gopForFusion.nalCount;
+ gops.pts = gopForFusion.pts;
+ gops.dts = gopForFusion.dts;
+ gops.duration += gopForFusion.duration;
+ } else {
+ // If we didn't find a candidate gop fall back to keyrame-pulling
+ gops = this.extendFirstKeyFrame_(gops);
+ }
+ }
+ collectDtsInfo(track, gops);
+
+ // First, we have to build the index from byte locations to
+ // samples (that is, frames) in the video data
+ track.samples = this.generateSampleTable_(gops);
+
+ // Concatenate the video data and construct the mdat
+ mdat = mp4.mdat(this.concatenateNalData_(gops));
+
+ // save all the nals in the last GOP into the gop cache
+ this.gopCache_.unshift({
+ gop: gops.pop(),
+ pps: track.pps,
+ sps: track.sps
+ });
+
+ // Keep a maximum of 6 GOPs in the cache
+ this.gopCache_.length = Math.min(6, this.gopCache_.length);
+
+ // Clear nalUnits
+ nalUnits = [];
+
+ calculateTrackBaseMediaDecodeTime(track);
+
+ this.trigger('timelineStartInfo', track.timelineStartInfo);
+
+ moof = mp4.moof(sequenceNumber, [track]);
+
+ // it would be great to allocate this array up front instead of
+ // throwing away hundreds of media segment fragments
+ boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
+
+ // Bump the sequence number for next time
+ sequenceNumber++;
+
+ boxes.set(moof);
+ boxes.set(mdat, moof.byteLength);
+
+ this.trigger('data', {track: track, boxes: boxes});
+
+ this.resetStream_();
+
+ // Continue with the flush process now
+ this.trigger('done', 'VideoSegmentStream');
+ };
+
+ this.resetStream_ = function() {
+ clearDtsInfo(track);
+
+ // reset config and pps because they may differ across segments
+ // for instance, when we are rendition switching
+ config = undefined;
+ pps = undefined;
+ };
+
+ // Search for a candidate Gop for gop-fusion from the gop cache and
+ // return it or return null if no good candidate was found
+ this.getGopForFusion_ = function (nalUnit) {
+ var
+ halfSecond = 45000, // Half-a-second in a 90khz clock
+ allowableOverlap = 10000, // About 3 frames @ 30fps
+ nearestDistance = Infinity,
+ dtsDistance,
+ nearestGopObj,
+ currentGop,
+ currentGopObj,
+ i;
+
+ // Search for the GOP nearest to the beginning of this nal unit
+ for (i = 0; i < this.gopCache_.length; i++) {
+ currentGopObj = this.gopCache_[i];
+ currentGop = currentGopObj.gop;
+
+ // Reject Gops with different SPS or PPS
+ if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) ||
+ !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
+ continue;
+ }
+
+ // Reject Gops that would require a negative baseMediaDecodeTime
+ if (currentGop.dts < track.timelineStartInfo.dts) {
+ continue;
+ }
+
+ // The distance between the end of the gop and the start of the nalUnit
+ dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration;
+
+ // Only consider GOPS that start before the nal unit and end within
+ // a half-second of the nal unit
+ if (dtsDistance >= -allowableOverlap &&
+ dtsDistance <= halfSecond) {
+
+ // Always use the closest GOP we found if there is more than
+ // one candidate
+ if (!nearestGopObj ||
+ nearestDistance > dtsDistance) {
+ nearestGopObj = currentGopObj;
+ nearestDistance = dtsDistance;
+ }
+ }
+ }
+
+ if (nearestGopObj) {
+ return nearestGopObj.gop;
+ }
+ return null;
+ };
+
+ this.extendFirstKeyFrame_ = function(gops) {
+ var
+ h, i,
+ currentGop,
+ newGops;
+
+ if (!gops[0][0].keyFrame) {
+ // Remove the first GOP
+ currentGop = gops.shift();
+
+ gops.byteLength -= currentGop.byteLength;
+ gops.nalCount -= currentGop.nalCount;
+
+ // Extend the first frame of what is now the
+ // first gop to cover the time period of the
+ // frames we just removed
+ gops[0][0].dts = currentGop.dts;
+ gops[0][0].pts = currentGop.pts;
+ gops[0][0].duration += currentGop.duration;
+ }
+
+ return gops;
+ };
+
+ // Convert an array of nal units into an array of frames with each frame being
+ // composed of the nal units that make up that frame
+ // Also keep track of cummulative data about the frame from the nal units such
+ // as the frame duration, starting pts, etc.
+ this.groupNalsIntoFrames_ = function(nalUnits) {
+ var
+ i,
+ currentNal,
+ startPts,
+ startDts,
+ currentFrame = [],
+ frames = [];
+
+ currentFrame.byteLength = 0;
+
+ for (i = 0; i < nalUnits.length; i++) {
+ currentNal = nalUnits[i];
+
+ // Split on 'aud'-type nal units
+ if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
+ // Since the very first nal unit is expected to be an AUD
+ // only push to the frames array when currentFrame is not empty
+ if (currentFrame.length) {
+ currentFrame.duration = currentNal.dts - currentFrame.dts;
+ frames.push(currentFrame);
+ }
+ currentFrame = [currentNal];
+ currentFrame.byteLength = currentNal.data.byteLength;
+ currentFrame.pts = currentNal.pts;
+ currentFrame.dts = currentNal.dts;
+ } else {
+ // Specifically flag key frames for ease of use later
+ if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
+ currentFrame.keyFrame = true;
+ }
+ currentFrame.duration = currentNal.dts - currentFrame.dts;
+ currentFrame.byteLength += currentNal.data.byteLength;
+ currentFrame.push(currentNal);
+ }
+ }
+
+ // For the last frame, use the duration of the previous frame if we
+ // have nothing better to go on
+ if (frames.length &&
+ (!currentFrame.duration ||
+ currentFrame.duration <= 0)) {
+ currentFrame.duration = frames[frames.length - 1].duration;
+ }
+
+ // Push the final frame
+ frames.push(currentFrame);
+ return frames;
+ };
+
+ // Convert an array of frames into an array of Gop with each Gop being composed
+ // of the frames that make up that Gop
+ // Also keep track of cummulative data about the Gop from the frames such as the
+ // Gop duration, starting pts, etc.
+ this.groupFramesIntoGops_ = function(frames) {
+ var
+ i,
+ currentFrame,
+ currentGop = [],
+ gops = [];
+
+ // We must pre-set some of the values on the Gop since we
+ // keep running totals of these values
+ currentGop.byteLength = 0;
+ currentGop.nalCount = 0;
+ currentGop.duration = 0;
+ currentGop.pts = frames[0].pts;
+ currentGop.dts = frames[0].dts;
+
+ // store some metadata about all the Gops
+ gops.byteLength = 0;
+ gops.nalCount = 0;
+ gops.duration = 0;
+ gops.pts = frames[0].pts;
+ gops.dts = frames[0].dts;
+
+ for (i = 0; i < frames.length; i++) {
+ currentFrame = frames[i];
+
+ if (currentFrame.keyFrame) {
+ // Since the very first frame is expected to be an keyframe
+ // only push to the gops array when currentGop is not empty
+ if (currentGop.length) {
+ gops.push(currentGop);
+ gops.byteLength += currentGop.byteLength;
+ gops.nalCount += currentGop.nalCount;
+ gops.duration += currentGop.duration;
+ }
+
+ currentGop = [currentFrame];
+ currentGop.nalCount = currentFrame.length;
+ currentGop.byteLength = currentFrame.byteLength;
+ currentGop.pts = currentFrame.pts;
+ currentGop.dts = currentFrame.dts;
+ currentGop.duration = currentFrame.duration;
+ } else {
+ currentGop.duration += currentFrame.duration;
+ currentGop.nalCount += currentFrame.length;
+ currentGop.byteLength += currentFrame.byteLength;
+ currentGop.push(currentFrame);
+ }
+ }
+
+ if (gops.length && currentGop.duration <= 0) {
+ currentGop.duration = gops[gops.length - 1].duration;
+ }
+ gops.byteLength += currentGop.byteLength;
+ gops.nalCount += currentGop.nalCount;
+ gops.duration += currentGop.duration;
+
+ // push the final Gop
+ gops.push(currentGop);
+ return gops;
+ };
+
+ // generate the track's sample table from an array of gops
+ this.generateSampleTable_ = function(gops, baseDataOffset) {
+ var
+ h, i,
+ sample,
+ currentGop,
+ currentFrame,
+ currentSample,
+ dataOffset = baseDataOffset || 0,
+ samples = [];
+
+ for (h = 0; h < gops.length; h++) {
+ currentGop = gops[h];
+
+ for (i = 0; i < currentGop.length; i++) {
+ currentFrame = currentGop[i];
+
+ sample = createDefaultSample();
+
+ sample.dataOffset = dataOffset;
+ sample.compositionTimeOffset = currentFrame.pts - currentFrame.dts;
+ sample.duration = currentFrame.duration;
+ sample.size = 4 * currentFrame.length; // Space for nal unit size
+ sample.size += currentFrame.byteLength;
+
+ if (currentFrame.keyFrame) {
+ sample.flags.dependsOn = 2;
+ }
+
+ dataOffset += sample.size;
+
+ samples.push(sample);
+ }
+ }
+ return samples;
+ };
+
+ // generate the track's raw mdat data from an array of gops
+ this.concatenateNalData_ = function (gops) {
+ var
+ h, i, j,
+ currentGop,
+ currentFrame,
+ currentNal,
+ dataOffset = 0,
+ nalsByteLength = gops.byteLength,
+ numberOfNals = gops.nalCount,
+ totalByteLength = nalsByteLength + 4 * numberOfNals,
+ data = new Uint8Array(totalByteLength),
+ view = new DataView(data.buffer);
+
+ // For each Gop..
+ for (h = 0; h < gops.length; h++) {
+ currentGop = gops[h];
+
+ // For each Frame..
+ for (i = 0; i < currentGop.length; i++) {
+ currentFrame = currentGop[i];
+
+ // For each NAL..
+ for (j = 0; j < currentFrame.length; j++) {
+ currentNal = currentFrame[j];
+
+ view.setUint32(dataOffset, currentNal.data.byteLength);
+ dataOffset += 4;
+ data.set(currentNal.data, dataOffset);
+ dataOffset += currentNal.data.byteLength;
+ }
+ }
+ }
+ return data;
+ };
+};
+
+VideoSegmentStream.prototype = new Stream();
+
+/**
+ * Store information about the start and end of the track and the
+ * duration for each frame/sample we process in order to calculate
+ * the baseMediaDecodeTime
+ */
+collectDtsInfo = function (track, data) {
+ if (typeof data.pts === 'number') {
+ if (track.timelineStartInfo.pts === undefined) {
+ track.timelineStartInfo.pts = data.pts;
+ }
+
+ if (track.minSegmentPts === undefined) {
+ track.minSegmentPts = data.pts;
+ } else {
+ track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
+ }
+
+ if (track.maxSegmentPts === undefined) {
+ track.maxSegmentPts = data.pts;
+ } else {
+ track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
+ }
+ }
+
+ if (typeof data.dts === 'number') {
+ if (track.timelineStartInfo.dts === undefined) {
+ track.timelineStartInfo.dts = data.dts;
+ }
+
+ if (track.minSegmentDts === undefined) {
+ track.minSegmentDts = data.dts;
+ } else {
+ track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
+ }
+
+ if (track.maxSegmentDts === undefined) {
+ track.maxSegmentDts = data.dts;
+ } else {
+ track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
+ }
+ }
+};
+
+/**
+ * Clear values used to calculate the baseMediaDecodeTime between
+ * tracks
+ */
+clearDtsInfo = function (track) {
+ delete track.minSegmentDts;
+ delete track.maxSegmentDts;
+ delete track.minSegmentPts;
+ delete track.maxSegmentPts;
+};
+
+/**
+ * Calculate the track's baseMediaDecodeTime based on the earliest
+ * DTS the transmuxer has ever seen and the minimum DTS for the
+ * current track
+ */
+calculateTrackBaseMediaDecodeTime = function (track) {
+ var
+ oneSecondInPTS = 90000, // 90kHz clock
+ scale,
+ // Calculate the distance, in time, that this segment starts from the start
+ // of the timeline (earliest time seen since the transmuxer initialized)
+ timeSinceStartOfTimeline = track.minSegmentDts - track.timelineStartInfo.dts,
+ // Calculate the first sample's effective compositionTimeOffset
+ firstSampleCompositionOffset = track.minSegmentPts - track.minSegmentDts;
+
+ // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
+ // we want the start of the first segment to be placed
+ track.baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
+
+ // Add to that the distance this segment is from the very first
+ track.baseMediaDecodeTime += timeSinceStartOfTimeline;
+
+ // Subtract this segment's "compositionTimeOffset" so that the first frame of
+ // this segment is displayed exactly at the `baseMediaDecodeTime` or at the
+ // end of the previous segment
+ track.baseMediaDecodeTime -= firstSampleCompositionOffset;
+
+ // baseMediaDecodeTime must not become negative
+ track.baseMediaDecodeTime = Math.max(0, track.baseMediaDecodeTime);
+
+ if (track.type === 'audio') {
+ // Audio has a different clock equal to the sampling_rate so we need to
+ // scale the PTS values into the clock rate of the track
+ scale = track.samplerate / oneSecondInPTS;
+ track.baseMediaDecodeTime *= scale;
+ track.baseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime);
+ }
+};
+
+/**
+ * A Stream that can combine multiple streams (ie. audio & video)
+ * into a single output segment for MSE. Also supports audio-only
+ * and video-only streams.
+ */
+CoalesceStream = function(options, metadataStream) {
+ // Number of Tracks per output segment
+ // If greater than 1, we combine multiple
+ // tracks into a single segment
+ this.numberOfTracks = 0;
+ this.metadataStream = metadataStream;
+
+ if (typeof options.remux !== 'undefined') {
+ this.remuxTracks = !!options.remux;
+ } else {
+ this.remuxTracks = true;
+ }
+
+ this.pendingTracks = [];
+ this.videoTrack = null;
+ this.pendingBoxes = [];
+ this.pendingCaptions = [];
+ this.pendingMetadata = [];
+ this.pendingBytes = 0;
+ this.emittedTracks = 0;
+
+ CoalesceStream.prototype.init.call(this);
+
+ // Take output from multiple
+ this.push = function(output) {
+ // buffer incoming captions until the associated video segment
+ // finishes
+ if (output.text) {
+ return this.pendingCaptions.push(output);
+ }
+ // buffer incoming id3 tags until the final flush
+ if (output.frames) {
+ return this.pendingMetadata.push(output);
+ }
+
+ // Add this track to the list of pending tracks and store
+ // important information required for the construction of
+ // the final segment
+ this.pendingTracks.push(output.track);
+ this.pendingBoxes.push(output.boxes);
+ this.pendingBytes += output.boxes.byteLength;
+
+ if (output.track.type === 'video') {
+ this.videoTrack = output.track;
+ }
+ if (output.track.type === 'audio') {
+ this.audioTrack = output.track;
+ }
+ };
+};
+
+CoalesceStream.prototype = new Stream();
+CoalesceStream.prototype.flush = function(flushSource) {
+ var
+ offset = 0,
+ event = {
+ captions: [],
+ metadata: [],
+ info: {}
+ },
+ caption,
+ id3,
+ initSegment,
+ timelineStartPts = 0,
+ i;
+
+ if (this.pendingTracks.length < this.numberOfTracks) {
+ if (flushSource !== 'VideoSegmentStream' &&
+ flushSource !== 'AudioSegmentStream') {
+ // Return because we haven't received a flush from a data-generating
+ // portion of the segment (meaning that we have only recieved meta-data
+ // or captions.)
+ return;
+ } else if (this.remuxTracks) {
+ // Return until we have enough tracks from the pipeline to remux (if we
+ // are remuxing audio and video into a single MP4)
+ return;
+ } else if (this.pendingTracks.length === 0) {
+ // In the case where we receive a flush without any data having been
+ // received we consider it an emitted track for the purposes of coalescing
+ // `done` events.
+ // We do this for the case where there is an audio and video track in the
+ // segment but no audio data. (seen in several playlists with alternate
+ // audio tracks and no audio present in the main TS segments.)
+ this.emittedTracks++;
+
+ if (this.emittedTracks >= this.numberOfTracks) {
+ this.trigger('done');
+ this.emittedTracks = 0;
+ }
+ return;
+ }
+ }
+
+ if (this.videoTrack) {
+ timelineStartPts = this.videoTrack.timelineStartInfo.pts;
+ VIDEO_PROPERTIES.forEach(function(prop) {
+ event.info[prop] = this.videoTrack[prop];
+ }, this);
+ } else if (this.audioTrack) {
+ timelineStartPts = this.audioTrack.timelineStartInfo.pts;
+ AUDIO_PROPERTIES.forEach(function(prop) {
+ event.info[prop] = this.audioTrack[prop];
+ }, this);
+ }
+
+ if (this.pendingTracks.length === 1) {
+ event.type = this.pendingTracks[0].type;
+ } else {
+ event.type = 'combined';
+ }
+
+ this.emittedTracks += this.pendingTracks.length;
+
+ initSegment = mp4.initSegment(this.pendingTracks);
+
+ // Create a new typed array large enough to hold the init
+ // segment and all tracks
+ event.data = new Uint8Array(initSegment.byteLength +
+ this.pendingBytes);
+
+ // Create an init segment containing a moov
+ // and track definitions
+ event.data.set(initSegment);
+ offset += initSegment.byteLength;
+
+ // Append each moof+mdat (one per track) after the init segment
+ for (i = 0; i < this.pendingBoxes.length; i++) {
+ event.data.set(this.pendingBoxes[i], offset);
+ offset += this.pendingBoxes[i].byteLength;
+ }
+
+ // Translate caption PTS times into second offsets into the
+ // video timeline for the segment
+ for (i = 0; i < this.pendingCaptions.length; i++) {
+ caption = this.pendingCaptions[i];
+ caption.startTime = (caption.startPts - timelineStartPts);
+ caption.startTime /= 90e3;
+ caption.endTime = (caption.endPts - timelineStartPts);
+ caption.endTime /= 90e3;
+ event.captions.push(caption);
+ }
+
+ // Translate ID3 frame PTS times into second offsets into the
+ // video timeline for the segment
+ for (i = 0; i < this.pendingMetadata.length; i++) {
+ id3 = this.pendingMetadata[i];
+ id3.cueTime = (id3.pts - timelineStartPts);
+ id3.cueTime /= 90e3;
+ event.metadata.push(id3);
+ }
+ // We add this to every single emitted segment even though we only need
+ // it for the first
+ event.metadata.dispatchType = this.metadataStream.dispatchType;
+
+ // Reset stream state
+ this.pendingTracks.length = 0;
+ this.videoTrack = null;
+ this.pendingBoxes.length = 0;
+ this.pendingCaptions.length = 0;
+ this.pendingBytes = 0;
+ this.pendingMetadata.length = 0;
+
+ // Emit the built segment
+ this.trigger('data', event);
+
+ // Only emit `done` if all tracks have been flushed and emitted
+ if (this.emittedTracks >= this.numberOfTracks) {
+ this.trigger('done');
+ this.emittedTracks = 0;
+ }
+};
+/**
+ * A Stream that expects MP2T binary data as input and produces
+ * corresponding media segments, suitable for use with Media Source
+ * Extension (MSE) implementations that support the ISO BMFF byte
+ * stream format, like Chrome.
+ */
+Transmuxer = function(options) {
+ var
+ self = this,
+ hasFlushed = true,
+ videoTrack,
+ audioTrack;
+
+ Transmuxer.prototype.init.call(this);
+
+ options = options || {};
+ this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
+ this.transmuxPipeline_ = {};
+
+ this.setupAacPipeline = function() {
+ var pipeline = {};
+ this.transmuxPipeline_ = pipeline;
+
+ pipeline.type = 'aac';
+ pipeline.metadataStream = new m2ts.MetadataStream();
+
+ // set up the parsing pipeline
+ pipeline.aacStream = new AacStream();
+ pipeline.adtsStream = new AdtsStream();
+ pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
+ pipeline.headOfPipeline = pipeline.aacStream;
+
+ pipeline.aacStream.pipe(pipeline.adtsStream);
+ pipeline.aacStream.pipe(pipeline.metadataStream);
+ pipeline.metadataStream.pipe(pipeline.coalesceStream);
+
+ pipeline.metadataStream.on('timestamp', function(frame) {
+ pipeline.aacStream.setTimestamp(frame.timeStamp);
+ });
+
+ pipeline.aacStream.on('data', function(data) {
+ var i;
+
+ if (data.type === 'timed-metadata' && !pipeline.audioSegmentStream) {
+ audioTrack = audioTrack || {
+ timelineStartInfo: {
+ baseMediaDecodeTime: self.baseMediaDecodeTime
+ },
+ codec: 'adts',
+ type: 'audio'
+ };
+ // hook up the audio segment stream to the first track with aac data
+ pipeline.coalesceStream.numberOfTracks++;
+ pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
+ // Set up the final part of the audio pipeline
+ pipeline.adtsStream
+ .pipe(pipeline.audioSegmentStream)
+ .pipe(pipeline.coalesceStream);
+ }
+ });
+
+ // Re-emit any data coming from the coalesce stream to the outside world
+ pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
+ // Let the consumer know we have finished flushing the entire pipeline
+ pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
+ };
+
+ this.setupTsPipeline = function() {
+ var pipeline = {};
+ this.transmuxPipeline_ = pipeline;
+
+ pipeline.type = 'ts';
+ pipeline.metadataStream = new m2ts.MetadataStream();
+
+ // set up the parsing pipeline
+ pipeline.packetStream = new m2ts.TransportPacketStream();
+ pipeline.parseStream = new m2ts.TransportParseStream();
+ pipeline.elementaryStream = new m2ts.ElementaryStream();
+ pipeline.adtsStream = new AdtsStream();
+ pipeline.h264Stream = new H264Stream();
+ pipeline.captionStream = new m2ts.CaptionStream();
+ pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
+ pipeline.headOfPipeline = pipeline.packetStream;
+
+ // disassemble MPEG2-TS packets into elementary streams
+ pipeline.packetStream
+ .pipe(pipeline.parseStream)
+ .pipe(pipeline.elementaryStream);
+
+ // !!THIS ORDER IS IMPORTANT!!
+ // demux the streams
+ pipeline.elementaryStream
+ .pipe(pipeline.h264Stream);
+ pipeline.elementaryStream
+ .pipe(pipeline.adtsStream);
+
+ pipeline.elementaryStream
+ .pipe(pipeline.metadataStream)
+ .pipe(pipeline.coalesceStream);
+
+ // Hook up CEA-608/708 caption stream
+ pipeline.h264Stream.pipe(pipeline.captionStream)
+ .pipe(pipeline.coalesceStream);
+
+ pipeline.elementaryStream.on('data', function(data) {
+ var i;
+
+ if (data.type === 'metadata') {
+ i = data.tracks.length;
+
+ // scan the tracks listed in the metadata
+ while (i--) {
+ if (!videoTrack && data.tracks[i].type === 'video') {
+ videoTrack = data.tracks[i];
+ videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
+ } else if (!audioTrack && data.tracks[i].type === 'audio') {
+ audioTrack = data.tracks[i];
+ audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
+ }
+ }
+
+ // hook up the video segment stream to the first track with h264 data
+ if (videoTrack && !pipeline.videoSegmentStream) {
+ pipeline.coalesceStream.numberOfTracks++;
+ pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack);
+
+ pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo){
+ // When video emits timelineStartInfo data after a flush, we forward that
+ // info to the AudioSegmentStream, if it exists, because video timeline
+ // data takes precedence.
+ if (audioTrack) {
+ audioTrack.timelineStartInfo = timelineStartInfo;
+ // On the first segment we trim AAC frames that exist before the
+ // very earliest DTS we have seen in video because Chrome will
+ // interpret any video track with a baseMediaDecodeTime that is
+ // non-zero as a gap.
+ pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts);
+ }
+ });
+
+ // Set up the final part of the video pipeline
+ pipeline.h264Stream
+ .pipe(pipeline.videoSegmentStream)
+ .pipe(pipeline.coalesceStream);
+ }
+
+ if (audioTrack && !pipeline.audioSegmentStream) {
+ // hook up the audio segment stream to the first track with aac data
+ pipeline.coalesceStream.numberOfTracks++;
+ pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack);
+
+ // Set up the final part of the audio pipeline
+ pipeline.adtsStream
+ .pipe(pipeline.audioSegmentStream)
+ .pipe(pipeline.coalesceStream);
+ }
+ }
+ });
+
+ // Re-emit any data coming from the coalesce stream to the outside world
+ pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
+ // Let the consumer know we have finished flushing the entire pipeline
+ pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
+ };
+
+ // hook up the segment streams once track metadata is delivered
+ this.setBaseMediaDecodeTime = function (baseMediaDecodeTime) {
+ var pipeline = this.transmuxPipeline_;
+
+ this.baseMediaDecodeTime = baseMediaDecodeTime;
+ if (audioTrack) {
+ audioTrack.timelineStartInfo.dts = undefined;
+ audioTrack.timelineStartInfo.pts = undefined;
+ clearDtsInfo(audioTrack);
+ audioTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
+ }
+ if (videoTrack) {
+ if (pipeline.videoSegmentStream) {
+ pipeline.videoSegmentStream.gopCache_ = [];
+ }
+ videoTrack.timelineStartInfo.dts = undefined;
+ videoTrack.timelineStartInfo.pts = undefined;
+ clearDtsInfo(videoTrack);
+ videoTrack.timelineStartInfo.baseMediaDecodeTime = baseMediaDecodeTime;
+ }
+ };
+
+ // feed incoming data to the front of the parsing pipeline
+ this.push = function(data) {
+ if (hasFlushed) {
+ var isAac = isLikelyAacData(data);
+
+ if (isAac && this.transmuxPipeline_.type !== 'aac') {
+ this.setupAacPipeline();
+ } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
+ this.setupTsPipeline();
+ }
+ hasFlushed = false;
+ }
+ this.transmuxPipeline_.headOfPipeline.push(data);
+ };
+
+ // flush any buffered data
+ this.flush = function() {
+ hasFlushed = true;
+ // Start at the top of the pipeline and flush all pending work
+ this.transmuxPipeline_.headOfPipeline.flush();
+ };
+};
+Transmuxer.prototype = new Stream();
+
+module.exports = {
+ Transmuxer: Transmuxer,
+ VideoSegmentStream: VideoSegmentStream,
+ AudioSegmentStream: AudioSegmentStream,
+ AUDIO_PROPERTIES: AUDIO_PROPERTIES,
+ VIDEO_PROPERTIES: VIDEO_PROPERTIES
+};
+
+},{"../aac":67,"../codecs/adts.js":68,"../codecs/h264":69,"../m2ts/m2ts.js":74,"../utils/stream.js":81,"./mp4-generator.js":78}],80:[function(require,module,exports){
+'use strict';
+
+var ExpGolomb;
+
+/**
+ * Parser for exponential Golomb codes, a variable-bitwidth number encoding
+ * scheme used by h264.
+ */
+ExpGolomb = function(workingData) {
+ var
+ // the number of bytes left to examine in workingData
+ workingBytesAvailable = workingData.byteLength,
+
+ // the current word being examined
+ workingWord = 0, // :uint
+
+ // the number of bits left to examine in the current word
+ workingBitsAvailable = 0; // :uint;
+
+ // ():uint
+ this.length = function() {
+ return (8 * workingBytesAvailable);
+ };
+
+ // ():uint
+ this.bitsAvailable = function() {
+ return (8 * workingBytesAvailable) + workingBitsAvailable;
+ };
+
+ // ():void
+ this.loadWord = function() {
+ var
+ position = workingData.byteLength - workingBytesAvailable,
+ workingBytes = new Uint8Array(4),
+ availableBytes = Math.min(4, workingBytesAvailable);
+
+ if (availableBytes === 0) {
+ throw new Error('no bytes available');
+ }
+
+ workingBytes.set(workingData.subarray(position,
+ position + availableBytes));
+ workingWord = new DataView(workingBytes.buffer).getUint32(0);
+
+ // track the amount of workingData that has been processed
+ workingBitsAvailable = availableBytes * 8;
+ workingBytesAvailable -= availableBytes;
+ };
+
+ // (count:int):void
+ this.skipBits = function(count) {
+ var skipBytes; // :int
+ if (workingBitsAvailable > count) {
+ workingWord <<= count;
+ workingBitsAvailable -= count;
+ } else {
+ count -= workingBitsAvailable;
+ skipBytes = Math.floor(count / 8);
+
+ count -= (skipBytes * 8);
+ workingBytesAvailable -= skipBytes;
+
+ this.loadWord();
+
+ workingWord <<= count;
+ workingBitsAvailable -= count;
+ }
+ };
+
+ // (size:int):uint
+ this.readBits = function(size) {
+ var
+ bits = Math.min(workingBitsAvailable, size), // :uint
+ valu = workingWord >>> (32 - bits); // :uint
+ // if size > 31, handle error
+ workingBitsAvailable -= bits;
+ if (workingBitsAvailable > 0) {
+ workingWord <<= bits;
+ } else if (workingBytesAvailable > 0) {
+ this.loadWord();
+ }
+
+ bits = size - bits;
+ if (bits > 0) {
+ return valu << bits | this.readBits(bits);
+ } else {
+ return valu;
+ }
+ };
+
+ // ():uint
+ this.skipLeadingZeros = function() {
+ var leadingZeroCount; // :uint
+ for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) {
+ if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) {
+ // the first bit of working word is 1
+ workingWord <<= leadingZeroCount;
+ workingBitsAvailable -= leadingZeroCount;
+ return leadingZeroCount;
+ }
+ }
+
+ // we exhausted workingWord and still have not found a 1
+ this.loadWord();
+ return leadingZeroCount + this.skipLeadingZeros();
+ };
+
+ // ():void
+ this.skipUnsignedExpGolomb = function() {
+ this.skipBits(1 + this.skipLeadingZeros());
+ };
+
+ // ():void
+ this.skipExpGolomb = function() {
+ this.skipBits(1 + this.skipLeadingZeros());
+ };
+
+ // ():uint
+ this.readUnsignedExpGolomb = function() {
+ var clz = this.skipLeadingZeros(); // :uint
+ return this.readBits(clz + 1) - 1;
+ };
+
+ // ():int
+ this.readExpGolomb = function() {
+ var valu = this.readUnsignedExpGolomb(); // :int
+ if (0x01 & valu) {
+ // the number is odd if the low order bit is set
+ return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
+ } else {
+ return -1 * (valu >>> 1); // divide by two then make it negative
+ }
+ };
+
+ // Some convenience functions
+ // :Boolean
+ this.readBoolean = function() {
+ return 1 === this.readBits(1);
+ };
+
+ // ():int
+ this.readUnsignedByte = function() {
+ return this.readBits(8);
+ };
+
+ this.loadWord();
+};
+
+module.exports = ExpGolomb;
+
+},{}],81:[function(require,module,exports){
+/**
+ * mux.js
+ *
+ * Copyright (c) 2014 Brightcove
+ * All rights reserved.
+ *
+ * A lightweight readable stream implemention that handles event dispatching.
+ * Objects that inherit from streams should call init in their constructors.
+ */
+'use strict';
+
+var Stream = function() {
+ this.init = function() {
+ var listeners = {};
+ /**
+ * Add a listener for a specified event type.
+ * @param type {string} the event name
+ * @param listener {function} the callback to be invoked when an event of
+ * the specified type occurs
+ */
+ this.on = function(type, listener) {
+ if (!listeners[type]) {
+ listeners[type] = [];
+ }
+ listeners[type].push(listener);
+ };
+ /**
+ * Remove a listener for a specified event type.
+ * @param type {string} the event name
+ * @param listener {function} a function previously registered for this
+ * type of event through `on`
+ */
+ this.off = function(type, listener) {
+ var index;
+ if (!listeners[type]) {
+ return false;
+ }
+ index = listeners[type].indexOf(listener);
+ listeners[type].splice(index, 1);
+ return index > -1;
+ };
+ /**
+ * Trigger an event of the specified type on this stream. Any additional
+ * arguments to this function are passed as parameters to event listeners.
+ * @param type {string} the event name
+ */
+ this.trigger = function(type) {
+ var callbacks, i, length, args;
+ callbacks = listeners[type];
+ if (!callbacks) {
+ return;
+ }
+ // Slicing the arguments on every invocation of this method
+ // can add a significant amount of overhead. Avoid the
+ // intermediate object creation for the common case of a
+ // single callback argument
+ if (arguments.length === 2) {
+ length = callbacks.length;
+ for (i = 0; i < length; ++i) {
+ callbacks[i].call(this, arguments[1]);
+ }
+ } else {
+ args = [];
+ i = arguments.length;
+ for (i = 1; i < arguments.length; ++i) {
+ args.push(arguments[i]);
+ }
+ length = callbacks.length;
+ for (i = 0; i < length; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+ };
+ /**
+ * Destroys the stream and cleans up.
+ */
+ this.dispose = function() {
+ listeners = {};
+ };
+ };
+};
+
+/**
+ * Forwards all `data` events on this stream to the destination stream. The
+ * destination stream should provide a method `push` to receive the data
+ * events as they arrive.
+ * @param destination {stream} the stream that will receive all `data` events
+ * @param autoFlush {boolean} if false, we will not call `flush` on the destination
+ * when the current stream emits a 'done' event
+ * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
+ */
+Stream.prototype.pipe = function(destination) {
+ this.on('data', function(data) {
+ destination.push(data);
+ });
+
+ this.on('done', function(flushSource) {
+ destination.flush(flushSource);
+ });
+
+ return destination;
+};
+
+// Default stream functions that are expected to be overridden to perform
+// actual work. These are provided by the prototype as a sort of no-op
+// implementation so that we don't have to check for their existence in the
+// `pipe` function above.
+Stream.prototype.push = function(data) {
+ this.trigger('data', data);
+};
+
+Stream.prototype.flush = function(flushSource) {
+ this.trigger('done', flushSource);
+};
+
+module.exports = Stream;
+
+},{}],82:[function(require,module,exports){
+/*
+ * pkcs7.pad
+ * https://github.com/brightcove/pkcs7
+ *
+ * Copyright (c) 2014 Brightcove
+ * Licensed under the apache2 license.
+ */
+
+'use strict';
+
+var PADDING;
+
+/**
+ * Returns a new Uint8Array that is padded with PKCS#7 padding.
+ * @param plaintext {Uint8Array} the input bytes before encryption
+ * @return {Uint8Array} the padded bytes
+ * @see http://tools.ietf.org/html/rfc5652
+ */
+module.exports = function pad(plaintext) {
+ var padding = PADDING[(plaintext.byteLength % 16) || 0],
+ result = new Uint8Array(plaintext.byteLength + padding.length);
+ result.set(plaintext);
+ result.set(padding, plaintext.byteLength);
+ return result;
+};
+
+// pre-define the padding values
+PADDING = [
+ [16, 16, 16, 16,
+ 16, 16, 16, 16,
+ 16, 16, 16, 16,
+ 16, 16, 16, 16],
+
+ [15, 15, 15, 15,
+ 15, 15, 15, 15,
+ 15, 15, 15, 15,
+ 15, 15, 15],
+
+ [14, 14, 14, 14,
+ 14, 14, 14, 14,
+ 14, 14, 14, 14,
+ 14, 14],
+
+ [13, 13, 13, 13,
+ 13, 13, 13, 13,
+ 13, 13, 13, 13,
+ 13],
+
+ [12, 12, 12, 12,
+ 12, 12, 12, 12,
+ 12, 12, 12, 12],
+
+ [11, 11, 11, 11,
+ 11, 11, 11, 11,
+ 11, 11, 11],
+
+ [10, 10, 10, 10,
+ 10, 10, 10, 10,
+ 10, 10],
+
+ [9, 9, 9, 9,
+ 9, 9, 9, 9,
+ 9],
+
+ [8, 8, 8, 8,
+ 8, 8, 8, 8],
+
+ [7, 7, 7, 7,
+ 7, 7, 7],
+
+ [6, 6, 6, 6,
+ 6, 6],
+
+ [5, 5, 5, 5,
+ 5],
+
+ [4, 4, 4, 4],
+
+ [3, 3, 3],
+
+ [2, 2],
+
+ [1]
+];
+
+},{}],83:[function(require,module,exports){
+/*
+ * pkcs7
+ * https://github.com/brightcove/pkcs7
+ *
+ * Copyright (c) 2014 Brightcove
+ * Licensed under the apache2 license.
+ */
+
+'use strict';
+
+exports.pad = require('./pad.js');
+exports.unpad = require('./unpad.js');
+
+},{"./pad.js":82,"./unpad.js":84}],84:[function(require,module,exports){
+/*
+ * pkcs7.unpad
+ * https://github.com/brightcove/pkcs7
+ *
+ * Copyright (c) 2014 Brightcove
+ * Licensed under the apache2 license.
+ */
+
+'use strict';
+
+/**
+ * Returns the subarray of a Uint8Array without PKCS#7 padding.
+ * @param padded {Uint8Array} unencrypted bytes that have been padded
+ * @return {Uint8Array} the unpadded bytes
+ * @see http://tools.ietf.org/html/rfc5652
+ */
+module.exports = function unpad(padded) {
+ return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
+};
+
+},{}],85:[function(require,module,exports){
+(function (global){
+/**
+ * @file add-text-track-data.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+/**
+ * Define properties on a cue for backwards compatability,
+ * but warn the user that the way that they are using it
+ * is depricated and will be removed at a later date.
+ *
+ * @param {Cue} cue the cue to add the properties on
+ * @private
+ */
+var deprecateOldCue = function deprecateOldCue(cue) {
+ Object.defineProperties(cue.frame, {
+ id: {
+ get: function get() {
+ _videoJs2['default'].log.warn('cue.frame.id is deprecated. Use cue.value.key instead.');
+ return cue.value.key;
+ }
+ },
+ value: {
+ get: function get() {
+ _videoJs2['default'].log.warn('cue.frame.value is deprecated. Use cue.value.data instead.');
+ return cue.value.data;
+ }
+ },
+ privateData: {
+ get: function get() {
+ _videoJs2['default'].log.warn('cue.frame.privateData is deprecated. Use cue.value.data instead.');
+ return cue.value.data;
+ }
+ }
+ });
+};
+
+/**
+ * Add text track data to a source handler given the captions and
+ * metadata from the buffer.
+ *
+ * @param {Object} sourceHandler the flash or virtual source buffer
+ * @param {Array} captionArray an array of caption data
+ * @param {Array} cue an array of meta data
+ * @private
+ */
+var addTextTrackData = function addTextTrackData(sourceHandler, captionArray, metadataArray) {
+ var Cue = window.WebKitDataCue || window.VTTCue;
+
+ if (captionArray) {
+ captionArray.forEach(function (caption) {
+ this.inbandTextTrack_.addCue(new Cue(caption.startTime + this.timestampOffset, caption.endTime + this.timestampOffset, caption.text));
+ }, sourceHandler);
+ }
+
+ if (metadataArray) {
+ metadataArray.forEach(function (metadata) {
+ var time = metadata.cueTime + this.timestampOffset;
+
+ metadata.frames.forEach(function (frame) {
+ var cue = new Cue(time, time, frame.value || frame.url || frame.data || '');
+
+ cue.frame = frame;
+ cue.value = frame;
+ deprecateOldCue(cue);
+ this.metadataTrack_.addCue(cue);
+ }, this);
+ }, sourceHandler);
+ }
+};
+
+exports['default'] = addTextTrackData;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],86:[function(require,module,exports){
+/**
+ * @file codec-utils.js
+ */
+
+/**
+ * Check if a codec string refers to an audio codec.
+ *
+ * @param {String} codec codec string to check
+ * @return {Boolean} if this is an audio codec
+ * @private
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+var isAudioCodec = function isAudioCodec(codec) {
+ return (/mp4a\.\d+.\d+/i.test(codec)
+ );
+};
+
+/**
+ * Check if a codec string refers to a video codec.
+ *
+ * @param {String} codec codec string to check
+ * @return {Boolean} if this is a video codec
+ * @private
+ */
+var isVideoCodec = function isVideoCodec(codec) {
+ return (/avc1\.[\da-f]+/i.test(codec)
+ );
+};
+
+/**
+ * Parse a content type header into a type and parameters
+ * object
+ *
+ * @param {String} type the content type header
+ * @return {Object} the parsed content-type
+ * @private
+ */
+var parseContentType = function parseContentType(type) {
+ var object = { type: '', parameters: {} };
+ var parameters = type.trim().split(';');
+
+ // first parameter should always be content-type
+ object.type = parameters.shift().trim();
+ parameters.forEach(function (parameter) {
+ var pair = parameter.trim().split('=');
+
+ if (pair.length > 1) {
+ var _name = pair[0].replace(/"/g, '').trim();
+ var value = pair[1].replace(/"/g, '').trim();
+
+ object.parameters[_name] = value;
+ }
+ });
+
+ return object;
+};
+
+exports['default'] = {
+ isAudioCodec: isAudioCodec,
+ parseContentType: parseContentType,
+ isVideoCodec: isVideoCodec
+};
+module.exports = exports['default'];
+},{}],87:[function(require,module,exports){
+/**
+ * @file create-text-tracks-if-necessary.js
+ */
+
+/**
+ * Create text tracks on video.js if they exist on a segment.
+ *
+ * @param {Object} sourceBuffer the VSB or FSB
+ * @param {Object} mediaSource the HTML or Flash media source
+ * @param {Object} segment the segment that may contain the text track
+ * @private
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+var createTextTracksIfNecessary = function createTextTracksIfNecessary(sourceBuffer, mediaSource, segment) {
+ // create an in-band caption track if one is present in the segment
+ if (segment.captions && segment.captions.length && !sourceBuffer.inbandTextTrack_) {
+ sourceBuffer.inbandTextTrack_ = mediaSource.player_.addTextTrack('captions', 'cc1');
+ }
+
+ if (segment.metadata && segment.metadata.length && !sourceBuffer.metadataTrack_) {
+ sourceBuffer.metadataTrack_ = mediaSource.player_.addTextTrack('metadata', 'Timed Metadata');
+ sourceBuffer.metadataTrack_.inBandMetadataTrackDispatchType = segment.metadata.dispatchType;
+ }
+};
+
+exports['default'] = createTextTracksIfNecessary;
+module.exports = exports['default'];
+},{}],88:[function(require,module,exports){
+/**
+ * @file flash-constants.js
+ */
+/**
+ * The maximum size in bytes for append operations to the video.js
+ * SWF. Calling through to Flash blocks and can be expensive so
+ * tuning this parameter may improve playback on slower
+ * systems. There are two factors to consider:
+ * - Each interaction with the SWF must be quick or you risk dropping
+ * video frames. To maintain 60fps for the rest of the page, each append
+ * must not take longer than 16ms. Given the likelihood that the page
+ * will be executing more javascript than just playback, you probably
+ * want to aim for less than 8ms. We aim for just 4ms.
+ * - Bigger appends significantly increase throughput. The total number of
+ * bytes over time delivered to the SWF must exceed the video bitrate or
+ * playback will stall.
+ *
+ * We adaptively tune the size of appends to give the best throughput
+ * possible given the performance of the system. To do that we try to append
+ * as much as possible in TIME_PER_TICK and while tuning the size of appends
+ * dynamically so that we only append about 4-times in that 4ms span.
+ *
+ * The reason we try to keep the number of appends around four is due to
+ * externalities such as Flash load and garbage collection that are highly
+ * variable and having 4 iterations allows us to exit the loop early if
+ * an iteration takes longer than expected.
+ *
+ * @private
+ */
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var flashConstants = {
+ TIME_BETWEEN_TICKS: Math.floor(1000 / 480),
+ TIME_PER_TICK: Math.floor(1000 / 240),
+ // 1kb
+ BYTES_PER_CHUNK: 1 * 1024,
+ MIN_CHUNK: 1024,
+ MAX_CHUNK: 1024 * 1024
+};
+
+exports["default"] = flashConstants;
+module.exports = exports["default"];
+},{}],89:[function(require,module,exports){
+(function (global){
+/**
+ * @file flash-media-source.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _flashSourceBuffer = require('./flash-source-buffer');
+
+var _flashSourceBuffer2 = _interopRequireDefault(_flashSourceBuffer);
+
+var _flashConstants = require('./flash-constants');
+
+var _flashConstants2 = _interopRequireDefault(_flashConstants);
+
+var _codecUtils = require('./codec-utils');
+
+/**
+ * A flash implmentation of HTML MediaSources and a polyfill
+ * for browsers that don't support native or HTML MediaSources..
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
+ * @class FlashMediaSource
+ * @extends videojs.EventTarget
+ */
+
+var FlashMediaSource = (function (_videojs$EventTarget) {
+ _inherits(FlashMediaSource, _videojs$EventTarget);
+
+ function FlashMediaSource() {
+ var _this = this;
+
+ _classCallCheck(this, FlashMediaSource);
+
+ _get(Object.getPrototypeOf(FlashMediaSource.prototype), 'constructor', this).call(this);
+ this.sourceBuffers = [];
+ this.readyState = 'closed';
+
+ this.on(['sourceopen', 'webkitsourceopen'], function (event) {
+ // find the swf where we will push media data
+ _this.swfObj = document.getElementById(event.swfId);
+ _this.player_ = (0, _videoJs2['default'])(_this.swfObj.parentNode);
+ _this.tech_ = _this.swfObj.tech;
+ _this.readyState = 'open';
+
+ _this.tech_.on('seeking', function () {
+ var i = _this.sourceBuffers.length;
+
+ while (i--) {
+ _this.sourceBuffers[i].abort();
+ }
+ });
+
+ // trigger load events
+ if (_this.swfObj) {
+ _this.swfObj.vjs_load();
+ }
+ });
+ }
+
+ /**
+ * Set or return the presentation duration.
+ *
+ * @param {Double} value the duration of the media in seconds
+ * @param {Double} the current presentation duration
+ * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
+ */
+
+ /**
+ * We have this function so that the html and flash interfaces
+ * are the same.
+ *
+ * @private
+ */
+
+ _createClass(FlashMediaSource, [{
+ key: 'addSeekableRange_',
+ value: function addSeekableRange_() {}
+ // intentional no-op
+
+ /**
+ * Create a new flash source buffer and add it to our flash media source.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
+ * @param {String} type the content-type of the source
+ * @return {Object} the flash source buffer
+ */
+
+ }, {
+ key: 'addSourceBuffer',
+ value: function addSourceBuffer(type) {
+ var parsedType = (0, _codecUtils.parseContentType)(type);
+ var sourceBuffer = undefined;
+
+ // if this is an FLV type, we'll push data to flash
+ if (parsedType.type === 'video/mp2t') {
+ // Flash source buffers
+ sourceBuffer = new _flashSourceBuffer2['default'](this);
+ } else {
+ throw new Error('NotSupportedError (Video.js)');
+ }
+
+ this.sourceBuffers.push(sourceBuffer);
+ return sourceBuffer;
+ }
+
+ /**
+ * Signals the end of the stream.
+ *
+ * @link https://w3c.github.io/media-source/#widl-MediaSource-endOfStream-void-EndOfStreamError-error
+ * @param {String=} error Signals that a playback error
+ * has occurred. If specified, it must be either "network" or
+ * "decode".
+ */
+ }, {
+ key: 'endOfStream',
+ value: function endOfStream(error) {
+ if (error === 'network') {
+ // MEDIA_ERR_NETWORK
+ this.tech_.error(2);
+ } else if (error === 'decode') {
+ // MEDIA_ERR_DECODE
+ this.tech_.error(3);
+ }
+ if (this.readyState !== 'ended') {
+ this.readyState = 'ended';
+ this.swfObj.vjs_endOfStream();
+ }
+ }
+ }]);
+
+ return FlashMediaSource;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = FlashMediaSource;
+try {
+ Object.defineProperty(FlashMediaSource.prototype, 'duration', {
+ /**
+ * Return the presentation duration.
+ *
+ * @return {Double} the duration of the media in seconds
+ * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
+ */
+ get: function get() {
+ if (!this.swfObj) {
+ return NaN;
+ }
+ // get the current duration from the SWF
+ return this.swfObj.vjs_getProperty('duration');
+ },
+ /**
+ * Set the presentation duration.
+ *
+ * @param {Double} value the duration of the media in seconds
+ * @return {Double} the duration of the media in seconds
+ * @link http://www.w3.org/TR/media-source/#widl-MediaSource-duration
+ */
+ set: function set(value) {
+ var i = undefined;
+ var oldDuration = this.swfObj.vjs_getProperty('duration');
+
+ this.swfObj.vjs_setProperty('duration', value);
+
+ if (value < oldDuration) {
+ // In MSE, this triggers the range removal algorithm which causes
+ // an update to occur
+ for (i = 0; i < this.sourceBuffers.length; i++) {
+ this.sourceBuffers[i].remove(value, oldDuration);
+ }
+ }
+
+ return value;
+ }
+ });
+} catch (e) {
+ // IE8 throws if defineProperty is called on a non-DOM node. We
+ // don't support IE8 but we shouldn't throw an error if loaded
+ // there.
+ FlashMediaSource.prototype.duration = NaN;
+}
+
+for (var property in _flashConstants2['default']) {
+ FlashMediaSource[property] = _flashConstants2['default'][property];
+}
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./codec-utils":86,"./flash-constants":88,"./flash-source-buffer":90}],90:[function(require,module,exports){
+(function (global){
+/**
+ * @file flash-source-buffer.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _muxJsLibFlv = require('mux.js/lib/flv');
+
+var _muxJsLibFlv2 = _interopRequireDefault(_muxJsLibFlv);
+
+var _removeCuesFromTrack = require('./remove-cues-from-track');
+
+var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack);
+
+var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary');
+
+var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary);
+
+var _addTextTrackData = require('./add-text-track-data');
+
+var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData);
+
+var _flashConstants = require('./flash-constants');
+
+var _flashConstants2 = _interopRequireDefault(_flashConstants);
+
+/**
+ * A wrapper around the setTimeout function that uses
+ * the flash constant time between ticks value.
+ *
+ * @param {Function} func the function callback to run
+ * @private
+ */
+var scheduleTick = function scheduleTick(func) {
+ // Chrome doesn't invoke requestAnimationFrame callbacks
+ // in background tabs, so use setTimeout.
+ window.setTimeout(func, _flashConstants2['default'].TIME_BETWEEN_TICKS);
+};
+
+/**
+ * Round a number to a specified number of places much like
+ * toFixed but return a number instead of a string representation.
+ *
+ * @param {Number} num A number
+ * @param {Number} places The number of decimal places which to
+ * round
+ * @private
+ */
+var toDecimalPlaces = function toDecimalPlaces(num, places) {
+ if (typeof places !== 'number' || places < 0) {
+ places = 0;
+ }
+
+ var scale = Math.pow(10, places);
+
+ return Math.round(num * scale) / scale;
+};
+
+/**
+ * A SourceBuffer implementation for Flash rather than HTML.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
+ * @param {Object} mediaSource the flash media source
+ * @class FlashSourceBuffer
+ * @extends videojs.EventTarget
+ */
+
+var FlashSourceBuffer = (function (_videojs$EventTarget) {
+ _inherits(FlashSourceBuffer, _videojs$EventTarget);
+
+ function FlashSourceBuffer(mediaSource) {
+ var _this = this;
+
+ _classCallCheck(this, FlashSourceBuffer);
+
+ _get(Object.getPrototypeOf(FlashSourceBuffer.prototype), 'constructor', this).call(this);
+ var encodedHeader = undefined;
+
+ // Start off using the globally defined value but refine
+ // as we append data into flash
+ this.chunkSize_ = _flashConstants2['default'].BYTES_PER_CHUNK;
+
+ // byte arrays queued to be appended
+ this.buffer_ = [];
+
+ // the total number of queued bytes
+ this.bufferSize_ = 0;
+
+ // to be able to determine the correct position to seek to, we
+ // need to retain information about the mapping between the
+ // media timeline and PTS values
+ this.basePtsOffset_ = NaN;
+
+ this.mediaSource = mediaSource;
+
+ // indicates whether the asynchronous continuation of an operation
+ // is still being processed
+ // see https://w3c.github.io/media-source/#widl-SourceBuffer-updating
+ this.updating = false;
+ this.timestampOffset_ = 0;
+
+ // TS to FLV transmuxer
+ this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer();
+ this.segmentParser_.on('data', this.receiveBuffer_.bind(this));
+ encodedHeader = window.btoa(String.fromCharCode.apply(null, Array.prototype.slice.call(this.segmentParser_.getFlvHeader())));
+ this.mediaSource.swfObj.vjs_appendBuffer(encodedHeader);
+
+ Object.defineProperty(this, 'timestampOffset', {
+ get: function get() {
+ return this.timestampOffset_;
+ },
+ set: function set(val) {
+ if (typeof val === 'number' && val >= 0) {
+ this.timestampOffset_ = val;
+ this.segmentParser_ = new _muxJsLibFlv2['default'].Transmuxer();
+ this.segmentParser_.on('data', this.receiveBuffer_.bind(this));
+ // We have to tell flash to expect a discontinuity
+ this.mediaSource.swfObj.vjs_discontinuity();
+ // the media <-> PTS mapping must be re-established after
+ // the discontinuity
+ this.basePtsOffset_ = NaN;
+ }
+ }
+ });
+
+ Object.defineProperty(this, 'buffered', {
+ get: function get() {
+ if (!this.mediaSource || !this.mediaSource.swfObj || !('vjs_getProperty' in this.mediaSource.swfObj)) {
+ return _videoJs2['default'].createTimeRange();
+ }
+
+ var buffered = this.mediaSource.swfObj.vjs_getProperty('buffered');
+
+ if (buffered && buffered.length) {
+ buffered[0][0] = toDecimalPlaces(buffered[0][0], 3);
+ buffered[0][1] = toDecimalPlaces(buffered[0][1], 3);
+ }
+ return _videoJs2['default'].createTimeRanges(buffered);
+ }
+ });
+
+ // On a seek we remove all text track data since flash has no concept
+ // of a buffered-range and everything else is reset on seek
+ this.mediaSource.player_.on('seeked', function () {
+ (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.metadataTrack_);
+ (0, _removeCuesFromTrack2['default'])(0, Infinity, _this.inbandTextTrack_);
+ });
+ }
+
+ /**
+ * Append bytes to the sourcebuffers buffer, in this case we
+ * have to append it to swf object.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
+ * @param {Array} bytes
+ */
+
+ _createClass(FlashSourceBuffer, [{
+ key: 'appendBuffer',
+ value: function appendBuffer(bytes) {
+ var _this2 = this;
+
+ var error = undefined;
+ var chunk = 512 * 1024;
+ var i = 0;
+
+ if (this.updating) {
+ error = new Error('SourceBuffer.append() cannot be called ' + 'while an update is in progress');
+ error.name = 'InvalidStateError';
+ error.code = 11;
+ throw error;
+ }
+
+ this.updating = true;
+ this.mediaSource.readyState = 'open';
+ this.trigger({ type: 'update' });
+
+ // this is here to use recursion
+ var chunkInData = function chunkInData() {
+ _this2.segmentParser_.push(bytes.subarray(i, i + chunk));
+ i += chunk;
+ if (i < bytes.byteLength) {
+ scheduleTick(chunkInData);
+ } else {
+ scheduleTick(_this2.segmentParser_.flush.bind(_this2.segmentParser_));
+ }
+ };
+
+ chunkInData();
+ }
+
+ /**
+ * Reset the parser and remove any data queued to be sent to the SWF.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
+ */
+ }, {
+ key: 'abort',
+ value: function abort() {
+ this.buffer_ = [];
+ this.bufferSize_ = 0;
+ this.mediaSource.swfObj.vjs_abort();
+
+ // report any outstanding updates have ended
+ if (this.updating) {
+ this.updating = false;
+ this.trigger({ type: 'updateend' });
+ }
+ }
+
+ /**
+ * Flash cannot remove ranges already buffered in the NetStream
+ * but seeking clears the buffer entirely. For most purposes,
+ * having this operation act as a no-op is acceptable.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
+ * @param {Double} start start of the section to remove
+ * @param {Double} end end of the section to remove
+ */
+ }, {
+ key: 'remove',
+ value: function remove(start, end) {
+ (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_);
+ (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_);
+ this.trigger({ type: 'update' });
+ this.trigger({ type: 'updateend' });
+ }
+
+ /**
+ * Receive a buffer from the flv.
+ *
+ * @param {Object} segment
+ * @private
+ */
+ }, {
+ key: 'receiveBuffer_',
+ value: function receiveBuffer_(segment) {
+ var _this3 = this;
+
+ // create an in-band caption track if one is present in the segment
+ (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource, segment);
+ (0, _addTextTrackData2['default'])(this, segment.captions, segment.metadata);
+
+ // Do this asynchronously since convertTagsToData_ can be time consuming
+ scheduleTick(function () {
+ var flvBytes = _this3.convertTagsToData_(segment);
+
+ if (_this3.buffer_.length === 0) {
+ scheduleTick(_this3.processBuffer_.bind(_this3));
+ }
+
+ if (flvBytes) {
+ _this3.buffer_.push(flvBytes);
+ _this3.bufferSize_ += flvBytes.byteLength;
+ }
+ });
+ }
+
+ /**
+ * Append a portion of the current buffer to the SWF.
+ *
+ * @private
+ */
+ }, {
+ key: 'processBuffer_',
+ value: function processBuffer_() {
+ var chunk = undefined;
+ var i = undefined;
+ var length = undefined;
+ var binary = undefined;
+ var b64str = undefined;
+ var startByte = 0;
+ var appendIterations = 0;
+ var startTime = +new Date();
+ var appendTime = undefined;
+
+ if (!this.buffer_.length) {
+ if (this.updating !== false) {
+ this.updating = false;
+ this.trigger({ type: 'updateend' });
+ }
+ // do nothing if the buffer is empty
+ return;
+ }
+
+ do {
+ appendIterations++;
+ // concatenate appends up to the max append size
+ chunk = this.buffer_[0].subarray(startByte, startByte + this.chunkSize_);
+
+ // requeue any bytes that won't make it this round
+ if (chunk.byteLength < this.chunkSize_ || this.buffer_[0].byteLength === startByte + this.chunkSize_) {
+ startByte = 0;
+ this.buffer_.shift();
+ } else {
+ startByte += this.chunkSize_;
+ }
+
+ this.bufferSize_ -= chunk.byteLength;
+
+ // base64 encode the bytes
+ binary = '';
+ length = chunk.byteLength;
+ for (i = 0; i < length; i++) {
+ binary += String.fromCharCode(chunk[i]);
+ }
+ b64str = window.btoa(binary);
+
+ // bypass normal ExternalInterface calls and pass xml directly
+ // IE can be slow by default
+ this.mediaSource.swfObj.CallFunction('' + b64str + '');
+ appendTime = new Date() - startTime;
+ } while (this.buffer_.length && appendTime < _flashConstants2['default'].TIME_PER_TICK);
+
+ if (this.buffer_.length && startByte) {
+ this.buffer_[0] = this.buffer_[0].subarray(startByte);
+ }
+
+ if (appendTime >= _flashConstants2['default'].TIME_PER_TICK) {
+ // We want to target 4 iterations per time-slot so that gives us
+ // room to adjust to changes in Flash load and other externalities
+ // such as garbage collection while still maximizing throughput
+ this.chunkSize_ = Math.floor(this.chunkSize_ * (appendIterations / 4));
+ }
+
+ // We also make sure that the chunk-size doesn't drop below 1KB or
+ // go above 1MB as a sanity check
+ this.chunkSize_ = Math.max(_flashConstants2['default'].MIN_CHUNK, Math.min(this.chunkSize_, _flashConstants2['default'].MAX_CHUNK));
+
+ // schedule another append if necessary
+ if (this.bufferSize_ !== 0) {
+ scheduleTick(this.processBuffer_.bind(this));
+ } else {
+ this.updating = false;
+ this.trigger({ type: 'updateend' });
+ }
+ }
+
+ /**
+ * Turns an array of flv tags into a Uint8Array representing the
+ * flv data. Also removes any tags that are before the current
+ * time so that playback begins at or slightly after the right
+ * place on a seek
+ *
+ * @private
+ * @param {Object} segmentData object of segment data
+ */
+ }, {
+ key: 'convertTagsToData_',
+ value: function convertTagsToData_(segmentData) {
+ var segmentByteLength = 0;
+ var tech = this.mediaSource.tech_;
+ var targetPts = 0;
+ var i = undefined;
+ var j = undefined;
+ var segment = undefined;
+ var filteredTags = [];
+ var tags = this.getOrderedTags_(segmentData);
+
+ // Establish the media timeline to PTS translation if we don't
+ // have one already
+ if (isNaN(this.basePtsOffset_) && tags.length) {
+ this.basePtsOffset_ = tags[0].pts;
+ }
+
+ // Trim any tags that are before the end of the end of
+ // the current buffer
+ if (tech.buffered().length) {
+ targetPts = tech.buffered().end(0) - this.timestampOffset;
+ }
+ // Trim to currentTime if it's ahead of buffered or buffered doesn't exist
+ targetPts = Math.max(targetPts, tech.currentTime() - this.timestampOffset);
+
+ // PTS values are represented in milliseconds
+ targetPts *= 1e3;
+ targetPts += this.basePtsOffset_;
+
+ // skip tags with a presentation time less than the seek target
+ for (i = 0; i < tags.length; i++) {
+ if (tags[i].pts >= targetPts) {
+ filteredTags.push(tags[i]);
+ }
+ }
+
+ if (filteredTags.length === 0) {
+ return;
+ }
+
+ // concatenate the bytes into a single segment
+ for (i = 0; i < filteredTags.length; i++) {
+ segmentByteLength += filteredTags[i].bytes.byteLength;
+ }
+ segment = new Uint8Array(segmentByteLength);
+ for (i = 0, j = 0; i < filteredTags.length; i++) {
+ segment.set(filteredTags[i].bytes, j);
+ j += filteredTags[i].bytes.byteLength;
+ }
+
+ return segment;
+ }
+
+ /**
+ * Assemble the FLV tags in decoder order.
+ *
+ * @private
+ * @param {Object} segmentData object of segment data
+ */
+ }, {
+ key: 'getOrderedTags_',
+ value: function getOrderedTags_(segmentData) {
+ var videoTags = segmentData.tags.videoTags;
+ var audioTags = segmentData.tags.audioTags;
+ var tag = undefined;
+ var tags = [];
+
+ while (videoTags.length || audioTags.length) {
+ if (!videoTags.length) {
+ // only audio tags remain
+ tag = audioTags.shift();
+ } else if (!audioTags.length) {
+ // only video tags remain
+ tag = videoTags.shift();
+ } else if (audioTags[0].dts < videoTags[0].dts) {
+ // audio should be decoded next
+ tag = audioTags.shift();
+ } else {
+ // video should be decoded next
+ tag = videoTags.shift();
+ }
+
+ tags.push(tag.finalize());
+ }
+
+ return tags;
+ }
+ }]);
+
+ return FlashSourceBuffer;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = FlashSourceBuffer;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./add-text-track-data":85,"./create-text-tracks-if-necessary":87,"./flash-constants":88,"./remove-cues-from-track":92,"mux.js/lib/flv":71}],91:[function(require,module,exports){
+(function (global){
+/**
+ * @file html-media-source.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _virtualSourceBuffer = require('./virtual-source-buffer');
+
+var _virtualSourceBuffer2 = _interopRequireDefault(_virtualSourceBuffer);
+
+var _codecUtils = require('./codec-utils');
+
+/**
+ * Replace the old apple-style `avc1..` codec string with the standard
+ * `avc1.`
+ *
+ * @param {Array} codecs an array of codec strings to fix
+ * @return {Array} the translated codec array
+ * @private
+ */
+var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
+ return codecs.map(function (codec) {
+ return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
+ var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
+ var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
+
+ return 'avc1.' + profileHex + '00' + avcLevelHex;
+ });
+ });
+};
+
+/**
+ * Our MediaSource implementation in HTML, mimics native
+ * MediaSource where/if possible.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
+ * @class HtmlMediaSource
+ * @extends videojs.EventTarget
+ */
+
+var HtmlMediaSource = (function (_videojs$EventTarget) {
+ _inherits(HtmlMediaSource, _videojs$EventTarget);
+
+ function HtmlMediaSource() {
+ var _this = this;
+
+ _classCallCheck(this, HtmlMediaSource);
+
+ _get(Object.getPrototypeOf(HtmlMediaSource.prototype), 'constructor', this).call(this);
+ var property = undefined;
+
+ this.nativeMediaSource_ = new window.MediaSource();
+ // delegate to the native MediaSource's methods by default
+ for (property in this.nativeMediaSource_) {
+ if (!(property in HtmlMediaSource.prototype) && typeof this.nativeMediaSource_[property] === 'function') {
+ this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_);
+ }
+ }
+
+ // emulate `duration` and `seekable` until seeking can be
+ // handled uniformly for live streams
+ // see https://github.com/w3c/media-source/issues/5
+ this.duration_ = NaN;
+ Object.defineProperty(this, 'duration', {
+ get: function get() {
+ if (this.duration_ === Infinity) {
+ return this.duration_;
+ }
+ return this.nativeMediaSource_.duration;
+ },
+ set: function set(duration) {
+ this.duration_ = duration;
+ if (duration !== Infinity) {
+ this.nativeMediaSource_.duration = duration;
+ return;
+ }
+ }
+ });
+ Object.defineProperty(this, 'seekable', {
+ get: function get() {
+ if (this.duration_ === Infinity) {
+ return _videoJs2['default'].createTimeRanges([[0, this.nativeMediaSource_.duration]]);
+ }
+ return this.nativeMediaSource_.seekable;
+ }
+ });
+
+ Object.defineProperty(this, 'readyState', {
+ get: function get() {
+ return this.nativeMediaSource_.readyState;
+ }
+ });
+
+ Object.defineProperty(this, 'activeSourceBuffers', {
+ get: function get() {
+ return this.activeSourceBuffers_;
+ }
+ });
+
+ // the list of virtual and native SourceBuffers created by this
+ // MediaSource
+ this.sourceBuffers = [];
+
+ this.activeSourceBuffers_ = [];
+
+ /**
+ * update the list of active source buffers based upon various
+ * imformation from HLS and video.js
+ *
+ * @private
+ */
+ this.updateActiveSourceBuffers_ = function () {
+ // Retain the reference but empty the array
+ _this.activeSourceBuffers_.length = 0;
+
+ // By default, the audio in the combined virtual source buffer is enabled
+ // and the audio-only source buffer (if it exists) is disabled.
+ var combined = false;
+ var audioOnly = true;
+
+ // TODO: maybe we can store the sourcebuffers on the track objects?
+ // safari may do something like this
+ for (var i = 0; i < _this.player_.audioTracks().length; i++) {
+ var track = _this.player_.audioTracks()[i];
+
+ if (track.enabled && track.kind !== 'main') {
+ // The enabled track is an alternate audio track so disable the audio in
+ // the combined source buffer and enable the audio-only source buffer.
+ combined = true;
+ audioOnly = false;
+ break;
+ }
+ }
+
+ // Since we currently support a max of two source buffers, add all of the source
+ // buffers (in order).
+ _this.sourceBuffers.forEach(function (sourceBuffer) {
+ /* eslinst-disable */
+ // TODO once codecs are required, we can switch to using the codecs to determine
+ // what stream is the video stream, rather than relying on videoTracks
+ /* eslinst-enable */
+
+ if (sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
+ // combined
+ sourceBuffer.audioDisabled_ = combined;
+ } else if (sourceBuffer.videoCodec_ && !sourceBuffer.audioCodec_) {
+ // If the "combined" source buffer is video only, then we do not want
+ // disable the audio-only source buffer (this is mostly for demuxed
+ // audio and video hls)
+ sourceBuffer.audioDisabled_ = true;
+ audioOnly = false;
+ } else if (!sourceBuffer.videoCodec_ && sourceBuffer.audioCodec_) {
+ // audio only
+ sourceBuffer.audioDisabled_ = audioOnly;
+ if (audioOnly) {
+ return;
+ }
+ }
+
+ _this.activeSourceBuffers_.push(sourceBuffer);
+ });
+ };
+
+ // Re-emit MediaSource events on the polyfill
+ ['sourceopen', 'sourceclose', 'sourceended'].forEach(function (eventName) {
+ this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
+ }, this);
+
+ // capture the associated player when the MediaSource is
+ // successfully attached
+ this.on('sourceopen', function (event) {
+ // Get the player this MediaSource is attached to
+ var video = document.querySelector('[src="' + _this.url_ + '"]');
+
+ if (!video) {
+ return;
+ }
+
+ _this.player_ = (0, _videoJs2['default'])(video.parentNode);
+
+ if (_this.player_.audioTracks && _this.player_.audioTracks()) {
+ _this.player_.audioTracks().on('change', _this.updateActiveSourceBuffers_);
+ _this.player_.audioTracks().on('addtrack', _this.updateActiveSourceBuffers_);
+ _this.player_.audioTracks().on('removetrack', _this.updateActiveSourceBuffers_);
+ }
+ });
+
+ // explicitly terminate any WebWorkers that were created
+ // by SourceHandlers
+ this.on('sourceclose', function (event) {
+ this.sourceBuffers.forEach(function (sourceBuffer) {
+ if (sourceBuffer.transmuxer_) {
+ sourceBuffer.transmuxer_.terminate();
+ }
+ });
+ this.sourceBuffers.length = 0;
+ if (!this.player_) {
+ return;
+ }
+
+ if (this.player_.audioTracks && this.player_.audioTracks()) {
+ this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
+ this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
+ this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
+ }
+ });
+ }
+
+ /**
+ * Add a range that that can now be seeked to.
+ *
+ * @param {Double} start where to start the addition
+ * @param {Double} end where to end the addition
+ * @private
+ */
+
+ _createClass(HtmlMediaSource, [{
+ key: 'addSeekableRange_',
+ value: function addSeekableRange_(start, end) {
+ var error = undefined;
+
+ if (this.duration !== Infinity) {
+ error = new Error('MediaSource.addSeekableRange() can only be invoked ' + 'when the duration is Infinity');
+ error.name = 'InvalidStateError';
+ error.code = 11;
+ throw error;
+ }
+
+ if (end > this.nativeMediaSource_.duration || isNaN(this.nativeMediaSource_.duration)) {
+ this.nativeMediaSource_.duration = end;
+ }
+ }
+
+ /**
+ * Add a source buffer to the media source.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
+ * @param {String} type the content-type of the content
+ * @return {Object} the created source buffer
+ */
+ }, {
+ key: 'addSourceBuffer',
+ value: function addSourceBuffer(type) {
+ var buffer = undefined;
+ var parsedType = (0, _codecUtils.parseContentType)(type);
+
+ // Create a VirtualSourceBuffer to transmux MPEG-2 transport
+ // stream segments into fragmented MP4s
+ if (parsedType.type === 'video/mp2t') {
+ var codecs = [];
+
+ if (parsedType.parameters && parsedType.parameters.codecs) {
+ codecs = parsedType.parameters.codecs.split(',');
+ codecs = translateLegacyCodecs(codecs);
+ codecs = codecs.filter(function (codec) {
+ return (0, _codecUtils.isAudioCodec)(codec) || (0, _codecUtils.isVideoCodec)(codec);
+ });
+ }
+
+ if (codecs.length === 0) {
+ codecs = ['avc1.4d400d', 'mp4a.40.2'];
+ }
+
+ buffer = new _virtualSourceBuffer2['default'](this, codecs);
+
+ if (this.sourceBuffers.length !== 0) {
+ // If another VirtualSourceBuffer already exists, then we are creating a
+ // SourceBuffer for an alternate audio track and therefore we know that
+ // the source has both an audio and video track.
+ // That means we should trigger the manual creation of the real
+ // SourceBuffers instead of waiting for the transmuxer to return data
+ this.sourceBuffers[0].createRealSourceBuffers_();
+ buffer.createRealSourceBuffers_();
+
+ // Automatically disable the audio on the first source buffer if
+ // a second source buffer is ever created
+ this.sourceBuffers[0].audioDisabled_ = true;
+ }
+ } else {
+ // delegate to the native implementation
+ buffer = this.nativeMediaSource_.addSourceBuffer(type);
+ }
+
+ this.sourceBuffers.push(buffer);
+ return buffer;
+ }
+ }]);
+
+ return HtmlMediaSource;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = HtmlMediaSource;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./codec-utils":86,"./virtual-source-buffer":95}],92:[function(require,module,exports){
+/**
+ * @file remove-cues-from-track.js
+ */
+
+/**
+ * Remove cues from a track on video.js.
+ *
+ * @param {Double} start start of where we should remove the cue
+ * @param {Double} end end of where the we should remove the cue
+ * @param {Object} track the text track to remove the cues from
+ * @private
+ */
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var removeCuesFromTrack = function removeCuesFromTrack(start, end, track) {
+ var i = undefined;
+ var cue = undefined;
+
+ if (!track) {
+ return;
+ }
+
+ i = track.cues.length;
+
+ while (i--) {
+ cue = track.cues[i];
+
+ // Remove any overlapping cue
+ if (cue.startTime <= end && cue.endTime >= start) {
+ track.removeCue(cue);
+ }
+ }
+};
+
+exports["default"] = removeCuesFromTrack;
+module.exports = exports["default"];
+},{}],93:[function(require,module,exports){
+/**
+ * @file transmuxer-worker.js
+ */
+
+/**
+ * videojs-contrib-media-sources
+ *
+ * Copyright (c) 2015 Brightcove
+ * All rights reserved.
+ *
+ * Handles communication between the browser-world and the mux.js
+ * transmuxer running inside of a WebWorker by exposing a simple
+ * message-based interface to a Transmuxer object.
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var _muxJsLibMp4 = require('mux.js/lib/mp4');
+
+var _muxJsLibMp42 = _interopRequireDefault(_muxJsLibMp4);
+
+/**
+ * Re-emits tranmsuxer events by converting them into messages to the
+ * world outside the worker.
+ *
+ * @param {Object} transmuxer the transmuxer to wire events on
+ * @private
+ */
+var wireTransmuxerEvents = function wireTransmuxerEvents(transmuxer) {
+ transmuxer.on('data', function (segment) {
+ // transfer ownership of the underlying ArrayBuffer
+ // instead of doing a copy to save memory
+ // ArrayBuffers are transferable but generic TypedArrays are not
+ // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
+ var typedArray = segment.data;
+
+ segment.data = typedArray.buffer;
+ postMessage({
+ action: 'data',
+ segment: segment,
+ byteOffset: typedArray.byteOffset,
+ byteLength: typedArray.byteLength
+ }, [segment.data]);
+ });
+
+ if (transmuxer.captionStream) {
+ transmuxer.captionStream.on('data', function (caption) {
+ postMessage({
+ action: 'caption',
+ data: caption
+ });
+ });
+ }
+
+ transmuxer.on('done', function (data) {
+ postMessage({ action: 'done' });
+ });
+};
+
+/**
+ * All incoming messages route through this hash. If no function exists
+ * to handle an incoming message, then we ignore the message.
+ *
+ * @class MessageHandlers
+ * @param {Object} options the options to initialize with
+ */
+
+var MessageHandlers = (function () {
+ function MessageHandlers(options) {
+ _classCallCheck(this, MessageHandlers);
+
+ this.options = options || {};
+ this.init();
+ }
+
+ /**
+ * Our web wroker interface so that things can talk to mux.js
+ * that will be running in a web worker. the scope is passed to this by
+ * webworkify.
+ *
+ * @param {Object} self the scope for the web worker
+ */
+
+ /**
+ * initialize our web worker and wire all the events.
+ */
+
+ _createClass(MessageHandlers, [{
+ key: 'init',
+ value: function init() {
+ if (this.transmuxer) {
+ this.transmuxer.dispose();
+ }
+ this.transmuxer = new _muxJsLibMp42['default'].Transmuxer(this.options);
+ wireTransmuxerEvents(this.transmuxer);
+ }
+
+ /**
+ * Adds data (a ts segment) to the start of the transmuxer pipeline for
+ * processing.
+ *
+ * @param {ArrayBuffer} data data to push into the muxer
+ */
+ }, {
+ key: 'push',
+ value: function push(data) {
+ // Cast array buffer to correct type for transmuxer
+ var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
+
+ this.transmuxer.push(segment);
+ }
+
+ /**
+ * Recreate the transmuxer so that the next segment added via `push`
+ * start with a fresh transmuxer.
+ */
+ }, {
+ key: 'reset',
+ value: function reset() {
+ this.init();
+ }
+
+ /**
+ * Set the value that will be used as the `baseMediaDecodeTime` time for the
+ * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
+ * set relative to the first based on the PTS values.
+ *
+ * @param {Object} data used to set the timestamp offset in the muxer
+ */
+ }, {
+ key: 'setTimestampOffset',
+ value: function setTimestampOffset(data) {
+ var timestampOffset = data.timestampOffset || 0;
+
+ this.transmuxer.setBaseMediaDecodeTime(Math.round(timestampOffset * 90000));
+ }
+
+ /**
+ * Forces the pipeline to finish processing the last segment and emit it's
+ * results.
+ *
+ * @param {Object} data event data, not really used
+ */
+ }, {
+ key: 'flush',
+ value: function flush(data) {
+ this.transmuxer.flush();
+ }
+ }]);
+
+ return MessageHandlers;
+})();
+
+var Worker = function Worker(self) {
+ self.onmessage = function (event) {
+ if (event.data.action === 'init' && event.data.options) {
+ this.messageHandlers = new MessageHandlers(event.data.options);
+ return;
+ }
+
+ if (!this.messageHandlers) {
+ this.messageHandlers = new MessageHandlers();
+ }
+
+ if (event.data && event.data.action && event.data.action !== 'init') {
+ if (this.messageHandlers[event.data.action]) {
+ this.messageHandlers[event.data.action](event.data);
+ }
+ }
+ };
+};
+
+exports['default'] = function (self) {
+ return new Worker(self);
+};
+
+module.exports = exports['default'];
+},{"mux.js/lib/mp4":77}],94:[function(require,module,exports){
+(function (global){
+/**
+ * @file videojs-contrib-media-sources.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+var _flashMediaSource = require('./flash-media-source');
+
+var _flashMediaSource2 = _interopRequireDefault(_flashMediaSource);
+
+var _htmlMediaSource = require('./html-media-source');
+
+var _htmlMediaSource2 = _interopRequireDefault(_htmlMediaSource);
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var urlCount = 0;
+
+// ------------
+// Media Source
+// ------------
+
+var defaults = {
+ // how to determine the MediaSource implementation to use. There
+ // are three available modes:
+ // - auto: use native MediaSources where available and Flash
+ // everywhere else
+ // - html5: always use native MediaSources
+ // - flash: always use the Flash MediaSource polyfill
+ mode: 'auto'
+};
+
+// store references to the media sources so they can be connected
+// to a video element (a swf object)
+// TODO: can we store this somewhere local to this module?
+_videoJs2['default'].mediaSources = {};
+
+/**
+ * Provide a method for a swf object to notify JS that a
+ * media source is now open.
+ *
+ * @param {String} msObjectURL string referencing the MSE Object URL
+ * @param {String} swfId the swf id
+ */
+var open = function open(msObjectURL, swfId) {
+ var mediaSource = _videoJs2['default'].mediaSources[msObjectURL];
+
+ if (mediaSource) {
+ mediaSource.trigger({ type: 'sourceopen', swfId: swfId });
+ } else {
+ throw new Error('Media Source not found (Video.js)');
+ }
+};
+
+/**
+ * Check to see if the native MediaSource object exists and supports
+ * an MP4 container with both H.264 video and AAC-LC audio.
+ *
+ * @return {Boolean} if native media sources are supported
+ */
+var supportsNativeMediaSources = function supportsNativeMediaSources() {
+ return !!window.MediaSource && !!window.MediaSource.isTypeSupported && window.MediaSource.isTypeSupported('video/mp4;codecs="avc1.4d400d,mp4a.40.2"');
+};
+
+/**
+ * An emulation of the MediaSource API so that we can support
+ * native and non-native functionality such as flash and
+ * video/mp2t videos. returns an instance of HtmlMediaSource or
+ * FlashMediaSource depending on what is supported and what options
+ * are passed in.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/MediaSource
+ * @param {Object} options options to use during setup.
+ */
+var MediaSource = function MediaSource(options) {
+ var settings = _videoJs2['default'].mergeOptions(defaults, options);
+
+ this.MediaSource = {
+ open: open,
+ supportsNativeMediaSources: supportsNativeMediaSources
+ };
+
+ // determine whether HTML MediaSources should be used
+ if (settings.mode === 'html5' || settings.mode === 'auto' && supportsNativeMediaSources()) {
+ return new _htmlMediaSource2['default']();
+ }
+
+ // otherwise, emulate them through the SWF
+ return new _flashMediaSource2['default']();
+};
+
+exports.MediaSource = MediaSource;
+MediaSource.open = open;
+MediaSource.supportsNativeMediaSources = supportsNativeMediaSources;
+
+/**
+ * A wrapper around the native URL for our MSE object
+ * implementation, this object is exposed under videojs.URL
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
+ */
+var URL = {
+ /**
+ * A wrapper around the native createObjectURL for our objects.
+ * This function maps a native or emulated mediaSource to a blob
+ * url so that it can be loaded into video.js
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
+ * @param {MediaSource} object the object to create a blob url to
+ */
+ createObjectURL: function createObjectURL(object) {
+ var objectUrlPrefix = 'blob:vjs-media-source/';
+ var url = undefined;
+
+ // use the native MediaSource to generate an object URL
+ if (object instanceof _htmlMediaSource2['default']) {
+ url = window.URL.createObjectURL(object.nativeMediaSource_);
+ object.url_ = url;
+ return url;
+ }
+ // if the object isn't an emulated MediaSource, delegate to the
+ // native implementation
+ if (!(object instanceof _flashMediaSource2['default'])) {
+ url = window.URL.createObjectURL(object);
+ object.url_ = url;
+ return url;
+ }
+
+ // build a URL that can be used to map back to the emulated
+ // MediaSource
+ url = objectUrlPrefix + urlCount;
+
+ urlCount++;
+
+ // setup the mapping back to object
+ _videoJs2['default'].mediaSources[url] = object;
+
+ return url;
+ }
+};
+
+exports.URL = URL;
+_videoJs2['default'].MediaSource = MediaSource;
+_videoJs2['default'].URL = URL;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./flash-media-source":89,"./html-media-source":91}],95:[function(require,module,exports){
+(function (global){
+/**
+ * @file virtual-source-buffer.js
+ */
+'use strict';
+
+Object.defineProperty(exports, '__esModule', {
+ value: true
+});
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _createTextTracksIfNecessary = require('./create-text-tracks-if-necessary');
+
+var _createTextTracksIfNecessary2 = _interopRequireDefault(_createTextTracksIfNecessary);
+
+var _removeCuesFromTrack = require('./remove-cues-from-track');
+
+var _removeCuesFromTrack2 = _interopRequireDefault(_removeCuesFromTrack);
+
+var _addTextTrackData = require('./add-text-track-data');
+
+var _addTextTrackData2 = _interopRequireDefault(_addTextTrackData);
+
+var _webworkify = require('webworkify');
+
+var _webworkify2 = _interopRequireDefault(_webworkify);
+
+var _transmuxerWorker = require('./transmuxer-worker');
+
+var _transmuxerWorker2 = _interopRequireDefault(_transmuxerWorker);
+
+var _codecUtils = require('./codec-utils');
+
+/**
+ * VirtualSourceBuffers exist so that we can transmux non native formats
+ * into a native format, but keep the same api as a native source buffer.
+ * It creates a transmuxer, that works in its own thread (a web worker) and
+ * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
+ * then send all of that data to the naive sourcebuffer so that it is
+ * indestinguishable from a natively supported format.
+ *
+ * @param {HtmlMediaSource} mediaSource the parent mediaSource
+ * @param {Array} codecs array of codecs that we will be dealing with
+ * @class VirtualSourceBuffer
+ * @extends video.js.EventTarget
+ */
+
+var VirtualSourceBuffer = (function (_videojs$EventTarget) {
+ _inherits(VirtualSourceBuffer, _videojs$EventTarget);
+
+ function VirtualSourceBuffer(mediaSource, codecs) {
+ var _this = this;
+
+ _classCallCheck(this, VirtualSourceBuffer);
+
+ _get(Object.getPrototypeOf(VirtualSourceBuffer.prototype), 'constructor', this).call(this, _videoJs2['default'].EventTarget);
+ this.timestampOffset_ = 0;
+ this.pendingBuffers_ = [];
+ this.bufferUpdating_ = false;
+ this.mediaSource_ = mediaSource;
+ this.codecs_ = codecs;
+ this.audioCodec_ = null;
+ this.videoCodec_ = null;
+ this.audioDisabled_ = false;
+
+ var options = {
+ remux: false
+ };
+
+ this.codecs_.forEach(function (codec) {
+ if ((0, _codecUtils.isAudioCodec)(codec)) {
+ _this.audioCodec_ = codec;
+ } else if ((0, _codecUtils.isVideoCodec)(codec)) {
+ _this.videoCodec_ = codec;
+ }
+ });
+
+ // append muxed segments to their respective native buffers as
+ // soon as they are available
+ this.transmuxer_ = (0, _webworkify2['default'])(_transmuxerWorker2['default']);
+ this.transmuxer_.postMessage({ action: 'init', options: options });
+
+ this.transmuxer_.onmessage = function (event) {
+ if (event.data.action === 'data') {
+ return _this.data_(event);
+ }
+
+ if (event.data.action === 'done') {
+ return _this.done_(event);
+ }
+ };
+
+ // this timestampOffset is a property with the side-effect of resetting
+ // baseMediaDecodeTime in the transmuxer on the setter
+ Object.defineProperty(this, 'timestampOffset', {
+ get: function get() {
+ return this.timestampOffset_;
+ },
+ set: function set(val) {
+ if (typeof val === 'number' && val >= 0) {
+ this.timestampOffset_ = val;
+
+ // We have to tell the transmuxer to set the baseMediaDecodeTime to
+ // the desired timestampOffset for the next segment
+ this.transmuxer_.postMessage({
+ action: 'setTimestampOffset',
+ timestampOffset: val
+ });
+ }
+ }
+ });
+
+ // setting the append window affects both source buffers
+ Object.defineProperty(this, 'appendWindowStart', {
+ get: function get() {
+ return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
+ },
+ set: function set(start) {
+ if (this.videoBuffer_) {
+ this.videoBuffer_.appendWindowStart = start;
+ }
+ if (this.audioBuffer_) {
+ this.audioBuffer_.appendWindowStart = start;
+ }
+ }
+ });
+
+ // this buffer is "updating" if either of its native buffers are
+ Object.defineProperty(this, 'updating', {
+ get: function get() {
+ return !!(this.bufferUpdating_ || !this.audioDisabled_ && this.audioBuffer_ && this.audioBuffer_.updating || this.videoBuffer_ && this.videoBuffer_.updating);
+ }
+ });
+
+ // the buffered property is the intersection of the buffered
+ // ranges of the native source buffers
+ Object.defineProperty(this, 'buffered', {
+ get: function get() {
+ var start = null;
+ var end = null;
+ var arity = 0;
+ var extents = [];
+ var ranges = [];
+
+ if (!this.videoBuffer_ && (this.audioDisabled_ || !this.audioBuffer_)) {
+ return _videoJs2['default'].createTimeRange();
+ }
+
+ // Handle the case where we only have one buffer
+ if (!this.videoBuffer_) {
+ return this.audioBuffer_.buffered;
+ } else if (this.audioDisabled_ || !this.audioBuffer_) {
+ return this.videoBuffer_.buffered;
+ }
+
+ // Handle the case where there is no buffer data
+ if ((!this.videoBuffer_ || this.videoBuffer_.buffered.length === 0) && (!this.audioBuffer_ || this.audioBuffer_.buffered.length === 0)) {
+ return _videoJs2['default'].createTimeRange();
+ }
+
+ // Handle the case where we have both buffers and create an
+ // intersection of the two
+ var videoBuffered = this.videoBuffer_.buffered;
+ var audioBuffered = this.audioBuffer_.buffered;
+ var count = videoBuffered.length;
+
+ // A) Gather up all start and end times
+ while (count--) {
+ extents.push({ time: videoBuffered.start(count), type: 'start' });
+ extents.push({ time: videoBuffered.end(count), type: 'end' });
+ }
+ count = audioBuffered.length;
+ while (count--) {
+ extents.push({ time: audioBuffered.start(count), type: 'start' });
+ extents.push({ time: audioBuffered.end(count), type: 'end' });
+ }
+ // B) Sort them by time
+ extents.sort(function (a, b) {
+ return a.time - b.time;
+ });
+
+ // C) Go along one by one incrementing arity for start and decrementing
+ // arity for ends
+ for (count = 0; count < extents.length; count++) {
+ if (extents[count].type === 'start') {
+ arity++;
+
+ // D) If arity is ever incremented to 2 we are entering an
+ // overlapping range
+ if (arity === 2) {
+ start = extents[count].time;
+ }
+ } else if (extents[count].type === 'end') {
+ arity--;
+
+ // E) If arity is ever decremented to 1 we leaving an
+ // overlapping range
+ if (arity === 1) {
+ end = extents[count].time;
+ }
+ }
+
+ // F) Record overlapping ranges
+ if (start !== null && end !== null) {
+ ranges.push([start, end]);
+ start = null;
+ end = null;
+ }
+ }
+
+ return _videoJs2['default'].createTimeRanges(ranges);
+ }
+ });
+ }
+
+ /**
+ * When we get a data event from the transmuxer
+ * we call this function and handle the data that
+ * was sent to us
+ *
+ * @private
+ * @param {Event} event the data event from the transmuxer
+ */
+
+ _createClass(VirtualSourceBuffer, [{
+ key: 'data_',
+ value: function data_(event) {
+ var segment = event.data.segment;
+
+ // Cast ArrayBuffer to TypedArray
+ segment.data = new Uint8Array(segment.data, event.data.byteOffset, event.data.byteLength);
+
+ (0, _createTextTracksIfNecessary2['default'])(this, this.mediaSource_, segment);
+
+ // Add the segments to the pendingBuffers array
+ this.pendingBuffers_.push(segment);
+ return;
+ }
+
+ /**
+ * When we get a done event from the transmuxer
+ * we call this function and we process all
+ * of the pending data that we have been saving in the
+ * data_ function
+ *
+ * @private
+ * @param {Event} event the done event from the transmuxer
+ */
+ }, {
+ key: 'done_',
+ value: function done_(event) {
+ // All buffers should have been flushed from the muxer
+ // start processing anything we have received
+ this.processPendingSegments_();
+ return;
+ }
+
+ /**
+ * Create our internal native audio/video source buffers and add
+ * event handlers to them with the following conditions:
+ * 1. they do not already exist on the mediaSource
+ * 2. this VSB has a codec for them
+ *
+ * @private
+ */
+ }, {
+ key: 'createRealSourceBuffers_',
+ value: function createRealSourceBuffers_() {
+ var _this2 = this;
+
+ var types = ['audio', 'video'];
+
+ types.forEach(function (type) {
+ // Don't create a SourceBuffer of this type if we don't have a
+ // codec for it
+ if (!_this2[type + 'Codec_']) {
+ return;
+ }
+
+ // Do nothing if a SourceBuffer of this type already exists
+ if (_this2[type + 'Buffer_']) {
+ return;
+ }
+
+ var buffer = null;
+
+ // If the mediasource already has a SourceBuffer for the codec
+ // use that
+ if (_this2.mediaSource_[type + 'Buffer_']) {
+ buffer = _this2.mediaSource_[type + 'Buffer_'];
+ } else {
+ buffer = _this2.mediaSource_.nativeMediaSource_.addSourceBuffer(type + '/mp4;codecs="' + _this2[type + 'Codec_'] + '"');
+ _this2.mediaSource_[type + 'Buffer_'] = buffer;
+ }
+
+ _this2[type + 'Buffer_'] = buffer;
+
+ // Wire up the events to the SourceBuffer
+ ['update', 'updatestart', 'updateend'].forEach(function (event) {
+ buffer.addEventListener(event, function () {
+ // if audio is disabled
+ if (type === 'audio' && _this2.audioDisabled_) {
+ return;
+ }
+
+ var shouldTrigger = types.every(function (t) {
+ // skip checking audio's updating status if audio
+ // is not enabled
+ if (t === 'audio' && _this2.audioDisabled_) {
+ return true;
+ }
+ // if the other type if updating we don't trigger
+ if (type !== t && _this2[t + 'Buffer_'] && _this2[t + 'Buffer_'].updating) {
+ return false;
+ }
+ return true;
+ });
+
+ if (shouldTrigger) {
+ return _this2.trigger(event);
+ }
+ });
+ });
+ });
+ }
+
+ /**
+ * Emulate the native mediasource function, but our function will
+ * send all of the proposed segments to the transmuxer so that we
+ * can transmux them before we append them to our internal
+ * native source buffers in the correct format.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
+ * @param {Uint8Array} segment the segment to append to the buffer
+ */
+ }, {
+ key: 'appendBuffer',
+ value: function appendBuffer(segment) {
+ // Start the internal "updating" state
+ this.bufferUpdating_ = true;
+
+ this.transmuxer_.postMessage({
+ action: 'push',
+ // Send the typed-array of data as an ArrayBuffer so that
+ // it can be sent as a "Transferable" and avoid the costly
+ // memory copy
+ data: segment.buffer,
+
+ // To recreate the original typed-array, we need information
+ // about what portion of the ArrayBuffer it was a view into
+ byteOffset: segment.byteOffset,
+ byteLength: segment.byteLength
+ }, [segment.buffer]);
+ this.transmuxer_.postMessage({ action: 'flush' });
+ }
+
+ /**
+ * Emulate the native mediasource function and remove parts
+ * of the buffer from any of our internal buffers that exist
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
+ * @param {Double} start position to start the remove at
+ * @param {Double} end position to end the remove at
+ */
+ }, {
+ key: 'remove',
+ value: function remove(start, end) {
+ if (this.videoBuffer_) {
+ this.videoBuffer_.remove(start, end);
+ }
+ if (!this.audioDisabled_ && this.audioBuffer_) {
+ this.audioBuffer_.remove(start, end);
+ }
+
+ // Remove Metadata Cues (id3)
+ (0, _removeCuesFromTrack2['default'])(start, end, this.metadataTrack_);
+
+ // Remove Any Captions
+ (0, _removeCuesFromTrack2['default'])(start, end, this.inbandTextTrack_);
+ }
+
+ /**
+ * Process any segments that the muxer has output
+ * Concatenate segments together based on type and append them into
+ * their respective sourceBuffers
+ *
+ * @private
+ */
+ }, {
+ key: 'processPendingSegments_',
+ value: function processPendingSegments_() {
+ var sortedSegments = {
+ video: {
+ segments: [],
+ bytes: 0
+ },
+ audio: {
+ segments: [],
+ bytes: 0
+ },
+ captions: [],
+ metadata: []
+ };
+
+ // Sort segments into separate video/audio arrays and
+ // keep track of their total byte lengths
+ sortedSegments = this.pendingBuffers_.reduce(function (segmentObj, segment) {
+ var type = segment.type;
+ var data = segment.data;
+
+ segmentObj[type].segments.push(data);
+ segmentObj[type].bytes += data.byteLength;
+
+ // Gather any captions into a single array
+ if (segment.captions) {
+ segmentObj.captions = segmentObj.captions.concat(segment.captions);
+ }
+
+ if (segment.info) {
+ segmentObj[type].info = segment.info;
+ }
+
+ // Gather any metadata into a single array
+ if (segment.metadata) {
+ segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
+ }
+
+ return segmentObj;
+ }, sortedSegments);
+
+ // Create the real source buffers if they don't exist by now since we
+ // finally are sure what tracks are contained in the source
+ if (!this.videoBuffer_ && !this.audioBuffer_) {
+ // Remove any codecs that may have been specified by default but
+ // are no longer applicable now
+ if (sortedSegments.video.bytes === 0) {
+ this.videoCodec_ = null;
+ }
+ if (sortedSegments.audio.bytes === 0) {
+ this.audioCodec_ = null;
+ }
+
+ this.createRealSourceBuffers_();
+ }
+
+ if (sortedSegments.audio.info) {
+ this.mediaSource_.trigger({ type: 'audioinfo', info: sortedSegments.audio.info });
+ }
+ if (sortedSegments.video.info) {
+ this.mediaSource_.trigger({ type: 'videoinfo', info: sortedSegments.video.info });
+ }
+
+ // Merge multiple video and audio segments into one and append
+ if (this.videoBuffer_) {
+ this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
+ // TODO: are video tracks the only ones with text tracks?
+ (0, _addTextTrackData2['default'])(this, sortedSegments.captions, sortedSegments.metadata);
+ }
+ if (!this.audioDisabled_ && this.audioBuffer_) {
+ this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
+ }
+
+ this.pendingBuffers_.length = 0;
+
+ // We are no longer in the internal "updating" state
+ this.bufferUpdating_ = false;
+ }
+
+ /**
+ * Combine all segments into a single Uint8Array and then append them
+ * to the destination buffer
+ *
+ * @param {Object} segmentObj
+ * @param {SourceBuffer} destinationBuffer native source buffer to append data to
+ * @private
+ */
+ }, {
+ key: 'concatAndAppendSegments_',
+ value: function concatAndAppendSegments_(segmentObj, destinationBuffer) {
+ var offset = 0;
+ var tempBuffer = undefined;
+
+ if (segmentObj.bytes) {
+ tempBuffer = new Uint8Array(segmentObj.bytes);
+
+ // Combine the individual segments into one large typed-array
+ segmentObj.segments.forEach(function (segment) {
+ tempBuffer.set(segment, offset);
+ offset += segment.byteLength;
+ });
+
+ destinationBuffer.appendBuffer(tempBuffer);
+ }
+ }
+
+ /**
+ * Emulate the native mediasource function. abort any soureBuffer
+ * actions and throw out any un-appended data.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
+ */
+ }, {
+ key: 'abort',
+ value: function abort() {
+ if (this.videoBuffer_) {
+ this.videoBuffer_.abort();
+ }
+ if (this.audioBuffer_) {
+ this.audioBuffer_.abort();
+ }
+ if (this.transmuxer_) {
+ this.transmuxer_.postMessage({ action: 'reset' });
+ }
+ this.pendingBuffers_.length = 0;
+ this.bufferUpdating_ = false;
+ }
+ }]);
+
+ return VirtualSourceBuffer;
+})(_videoJs2['default'].EventTarget);
+
+exports['default'] = VirtualSourceBuffer;
+module.exports = exports['default'];
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./add-text-track-data":85,"./codec-utils":86,"./create-text-tracks-if-necessary":87,"./remove-cues-from-track":92,"./transmuxer-worker":93,"webworkify":96}],96:[function(require,module,exports){
+var bundleFn = arguments[3];
+var sources = arguments[4];
+var cache = arguments[5];
+
+var stringify = JSON.stringify;
+
+module.exports = function (fn) {
+ var keys = [];
+ var wkey;
+ var cacheKeys = Object.keys(cache);
+
+ for (var i = 0, l = cacheKeys.length; i < l; i++) {
+ var key = cacheKeys[i];
+ if (cache[key].exports === fn) {
+ wkey = key;
+ break;
+ }
+ }
+
+ if (!wkey) {
+ wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
+ var wcache = {};
+ for (var i = 0, l = cacheKeys.length; i < l; i++) {
+ var key = cacheKeys[i];
+ wcache[key] = key;
+ }
+ sources[wkey] = [
+ Function(['require','module','exports'], '(' + fn + ')(self)'),
+ wcache
+ ];
+ }
+ var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16);
+
+ var scache = {}; scache[wkey] = wkey;
+ sources[skey] = [
+ Function(['require'],'require(' + stringify(wkey) + ')(self)'),
+ scache
+ ];
+
+ var src = '(' + bundleFn + ')({'
+ + Object.keys(sources).map(function (key) {
+ return stringify(key) + ':['
+ + sources[key][0]
+ + ',' + stringify(sources[key][1]) + ']'
+ ;
+ }).join(',')
+ + '},{},[' + stringify(skey) + '])'
+ ;
+
+ var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
+
+ return new Worker(URL.createObjectURL(
+ new Blob([src], { type: 'text/javascript' })
+ ));
+};
+
+},{}],97:[function(require,module,exports){
+(function (global){
+/**
+ * @file videojs-contrib-hls.js
+ *
+ * The main file for the HLS project.
+ * License: https://github.com/videojs/videojs-contrib-hls/blob/master/LICENSE
+ */
+'use strict';
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+var _globalDocument = require('global/document');
+
+var _globalDocument2 = _interopRequireDefault(_globalDocument);
+
+var _playlistLoader = require('./playlist-loader');
+
+var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
+
+var _playlist = require('./playlist');
+
+var _playlist2 = _interopRequireDefault(_playlist);
+
+var _xhr = require('./xhr');
+
+var _xhr2 = _interopRequireDefault(_xhr);
+
+var _aesDecrypter = require('aes-decrypter');
+
+var _binUtils = require('./bin-utils');
+
+var _binUtils2 = _interopRequireDefault(_binUtils);
+
+var _videojsContribMediaSources = require('videojs-contrib-media-sources');
+
+var _m3u8Parser = require('m3u8-parser');
+
+var _m3u8Parser2 = _interopRequireDefault(_m3u8Parser);
+
+var _videoJs = (typeof window !== "undefined" ? window['videojs'] : typeof global !== "undefined" ? global['videojs'] : null);
+
+var _videoJs2 = _interopRequireDefault(_videoJs);
+
+var _masterPlaylistController = require('./master-playlist-controller');
+
+var _masterPlaylistController2 = _interopRequireDefault(_masterPlaylistController);
+
+var _config = require('./config');
+
+var _config2 = _interopRequireDefault(_config);
+
+var _renditionMixin = require('./rendition-mixin');
+
+var _renditionMixin2 = _interopRequireDefault(_renditionMixin);
+
+var _gapSkipper = require('./gap-skipper');
+
+var _gapSkipper2 = _interopRequireDefault(_gapSkipper);
+
+/**
+ * determine if an object a is differnt from
+ * and object b. both only having one dimensional
+ * properties
+ *
+ * @param {Object} a object one
+ * @param {Object} b object two
+ * @return {Boolean} if the object has changed or not
+ */
+var objectChanged = function objectChanged(a, b) {
+ if (typeof a !== typeof b) {
+ return true;
+ }
+ // if we have a different number of elements
+ // something has changed
+ if (Object.keys(a).length !== Object.keys(b).length) {
+ return true;
+ }
+
+ for (var prop in a) {
+ if (!b[prop] || a[prop] !== b[prop]) {
+ return true;
+ }
+ }
+ return false;
+};
+
+var Hls = {
+ PlaylistLoader: _playlistLoader2['default'],
+ Playlist: _playlist2['default'],
+ Decrypter: _aesDecrypter.Decrypter,
+ AsyncStream: _aesDecrypter.AsyncStream,
+ decrypt: _aesDecrypter.decrypt,
+ utils: _binUtils2['default'],
+ xhr: (0, _xhr2['default'])()
+};
+
+Object.defineProperty(Hls, 'GOAL_BUFFER_LENGTH', {
+ get: function get() {
+ _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing');
+ return _config2['default'].GOAL_BUFFER_LENGTH;
+ },
+ set: function set(v) {
+ _videoJs2['default'].log.warn('using Hls.GOAL_BUFFER_LENGTH is UNSAFE be sure ' + 'you know what you are doing');
+ if (typeof v !== 'number' || v <= 0) {
+ _videoJs2['default'].log.warn('value passed to Hls.GOAL_BUFFER_LENGTH ' + 'must be a number and greater than 0');
+ return;
+ }
+ _config2['default'].GOAL_BUFFER_LENGTH = v;
+ }
+});
+
+// A fudge factor to apply to advertised playlist bitrates to account for
+// temporary flucations in client bandwidth
+var BANDWIDTH_VARIANCE = 1.2;
+
+/**
+ * Returns the CSS value for the specified property on an element
+ * using `getComputedStyle`. Firefox has a long-standing issue where
+ * getComputedStyle() may return null when running in an iframe with
+ * `display: none`.
+ *
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
+ * @param {HTMLElement} el the htmlelement to work on
+ * @param {string} the proprety to get the style for
+ */
+var safeGetComputedStyle = function safeGetComputedStyle(el, property) {
+ var result = undefined;
+
+ if (!el) {
+ return '';
+ }
+
+ result = getComputedStyle(el);
+ if (!result) {
+ return '';
+ }
+
+ return result[property];
+};
+
+/**
+ * Chooses the appropriate media playlist based on the current
+ * bandwidth estimate and the player size.
+ *
+ * @return {Playlist} the highest bitrate playlist less than the currently detected
+ * bandwidth, accounting for some amount of bandwidth variance
+ */
+Hls.STANDARD_PLAYLIST_SELECTOR = function () {
+ var effectiveBitrate = undefined;
+ var sortedPlaylists = this.playlists.master.playlists.slice();
+ var bandwidthPlaylists = [];
+ var now = +new Date();
+ var i = undefined;
+ var variant = undefined;
+ var bandwidthBestVariant = undefined;
+ var resolutionPlusOne = undefined;
+ var resolutionPlusOneAttribute = undefined;
+ var resolutionBestVariant = undefined;
+ var width = undefined;
+ var height = undefined;
+
+ sortedPlaylists.sort(Hls.comparePlaylistBandwidth);
+
+ // filter out any playlists that have been excluded due to
+ // incompatible configurations or playback errors
+ sortedPlaylists = sortedPlaylists.filter(function (localVariant) {
+ if (typeof localVariant.excludeUntil !== 'undefined') {
+ return now >= localVariant.excludeUntil;
+ }
+ return true;
+ });
+
+ // filter out any variant that has greater effective bitrate
+ // than the current estimated bandwidth
+ i = sortedPlaylists.length;
+ while (i--) {
+ variant = sortedPlaylists[i];
+
+ // ignore playlists without bandwidth information
+ if (!variant.attributes || !variant.attributes.BANDWIDTH) {
+ continue;
+ }
+
+ effectiveBitrate = variant.attributes.BANDWIDTH * BANDWIDTH_VARIANCE;
+
+ if (effectiveBitrate < this.bandwidth) {
+ bandwidthPlaylists.push(variant);
+
+ // since the playlists are sorted in ascending order by
+ // bandwidth, the first viable variant is the best
+ if (!bandwidthBestVariant) {
+ bandwidthBestVariant = variant;
+ }
+ }
+ }
+
+ i = bandwidthPlaylists.length;
+
+ // sort variants by resolution
+ bandwidthPlaylists.sort(Hls.comparePlaylistResolution);
+
+ // forget our old variant from above,
+ // or we might choose that in high-bandwidth scenarios
+ // (this could be the lowest bitrate rendition as we go through all of them above)
+ variant = null;
+
+ width = parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10);
+ height = parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10);
+
+ // iterate through the bandwidth-filtered playlists and find
+ // best rendition by player dimension
+ while (i--) {
+ variant = bandwidthPlaylists[i];
+
+ // ignore playlists without resolution information
+ if (!variant.attributes || !variant.attributes.RESOLUTION || !variant.attributes.RESOLUTION.width || !variant.attributes.RESOLUTION.height) {
+ continue;
+ }
+
+ // since the playlists are sorted, the first variant that has
+ // dimensions less than or equal to the player size is the best
+ var variantResolution = variant.attributes.RESOLUTION;
+
+ if (variantResolution.width === width && variantResolution.height === height) {
+ // if we have the exact resolution as the player use it
+ resolutionPlusOne = null;
+ resolutionBestVariant = variant;
+ break;
+ } else if (variantResolution.width < width && variantResolution.height < height) {
+ // if both dimensions are less than the player use the
+ // previous (next-largest) variant
+ break;
+ } else if (!resolutionPlusOne || variantResolution.width < resolutionPlusOneAttribute.width && variantResolution.height < resolutionPlusOneAttribute.height) {
+ // If we still haven't found a good match keep a
+ // reference to the previous variant for the next loop
+ // iteration
+
+ // By only saving variants if they are smaller than the
+ // previously saved variant, we ensure that we also pick
+ // the highest bandwidth variant that is just-larger-than
+ // the video player
+ resolutionPlusOne = variant;
+ resolutionPlusOneAttribute = resolutionPlusOne.attributes.RESOLUTION;
+ }
+ }
+
+ // fallback chain of variants
+ return resolutionPlusOne || resolutionBestVariant || bandwidthBestVariant || sortedPlaylists[0];
+};
+
+// HLS is a source handler, not a tech. Make sure attempts to use it
+// as one do not cause exceptions.
+Hls.canPlaySource = function () {
+ return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
+};
+
+/**
+ * Whether the browser has built-in HLS support.
+ */
+Hls.supportsNativeHls = (function () {
+ var video = _globalDocument2['default'].createElement('video');
+
+ // native HLS is definitely not supported if HTML5 video isn't
+ if (!_videoJs2['default'].getComponent('Html5').isSupported()) {
+ return false;
+ }
+
+ // HLS manifests can go by many mime-types
+ var canPlay = [
+ // Apple santioned
+ 'application/vnd.apple.mpegurl',
+ // Apple sanctioned for backwards compatibility
+ 'audio/mpegurl',
+ // Very common
+ 'audio/x-mpegurl',
+ // Very common
+ 'application/x-mpegurl',
+ // Included for completeness
+ 'video/x-mpegurl', 'video/mpegurl', 'application/mpegurl'];
+
+ return canPlay.some(function (canItPlay) {
+ return (/maybe|probably/i.test(video.canPlayType(canItPlay))
+ );
+ });
+})();
+
+/**
+ * HLS is a source handler, not a tech. Make sure attempts to use it
+ * as one do not cause exceptions.
+ */
+Hls.isSupported = function () {
+ return _videoJs2['default'].log.warn('HLS is no longer a tech. Please remove it from ' + 'your player\'s techOrder.');
+};
+
+var Component = _videoJs2['default'].getComponent('Component');
+
+/**
+ * The Hls Handler object, where we orchestrate all of the parts
+ * of HLS to interact with video.js
+ *
+ * @class HlsHandler
+ * @extends videojs.Component
+ * @param {Object} source the soruce object
+ * @param {Tech} tech the parent tech object
+ * @param {Object} options optional and required options
+ */
+
+var HlsHandler = (function (_Component) {
+ _inherits(HlsHandler, _Component);
+
+ function HlsHandler(source, tech, options) {
+ var _this = this;
+
+ _classCallCheck(this, HlsHandler);
+
+ _get(Object.getPrototypeOf(HlsHandler.prototype), 'constructor', this).call(this, tech);
+
+ // tech.player() is deprecated but setup a reference to HLS for
+ // backwards-compatibility
+ if (tech.options_ && tech.options_.playerId) {
+ var _player = (0, _videoJs2['default'])(tech.options_.playerId);
+
+ if (!_player.hasOwnProperty('hls')) {
+ Object.defineProperty(_player, 'hls', {
+ get: function get() {
+ _videoJs2['default'].log.warn('player.hls is deprecated. Use player.tech.hls instead.');
+ return _this;
+ }
+ });
+ }
+ }
+
+ this.tech_ = tech;
+ this.source_ = source;
+ this.stats = {};
+
+ // handle global & Source Handler level options
+ this.options_ = _videoJs2['default'].mergeOptions(_videoJs2['default'].options.hls || {}, options.hls);
+ this.setOptions_();
+
+ // listen for fullscreenchange events for this player so that we
+ // can adjust our quality selection quickly
+ this.on(_globalDocument2['default'], ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'], function (event) {
+ var fullscreenElement = _globalDocument2['default'].fullscreenElement || _globalDocument2['default'].webkitFullscreenElement || _globalDocument2['default'].mozFullScreenElement || _globalDocument2['default'].msFullscreenElement;
+
+ if (fullscreenElement && fullscreenElement.contains(_this.tech_.el())) {
+ _this.masterPlaylistController_.fastQualityChange_();
+ }
+ });
+
+ this.on(this.tech_, 'seeking', function () {
+ this.setCurrentTime(this.tech_.currentTime());
+ });
+ this.on(this.tech_, 'error', function () {
+ if (this.masterPlaylistController_) {
+ this.masterPlaylistController_.pauseLoading();
+ }
+ });
+
+ this.audioTrackChange_ = function () {
+ _this.masterPlaylistController_.useAudio();
+ };
+
+ this.on(this.tech_, 'play', this.play);
+ }
+
+ /**
+ * The Source Handler object, which informs video.js what additional
+ * MIME types are supported and sets up playback. It is registered
+ * automatically to the appropriate tech based on the capabilities of
+ * the browser it is running in. It is not necessary to use or modify
+ * this object in normal usage.
+ */
+
+ _createClass(HlsHandler, [{
+ key: 'setOptions_',
+ value: function setOptions_() {
+ var _this2 = this;
+
+ // defaults
+ this.options_.withCredentials = this.options_.withCredentials || false;
+
+ // start playlist selection at a reasonable bandwidth for
+ // broadband internet
+ // 0.5 MB/s
+ this.options_.bandwidth = this.options_.bandwidth || 4194304;
+
+ // grab options passed to player.src
+ ['withCredentials', 'bandwidth'].forEach(function (option) {
+ if (typeof _this2.source_[option] !== 'undefined') {
+ _this2.options_[option] = _this2.source_[option];
+ }
+ });
+
+ this.bandwidth = this.options_.bandwidth;
+ }
+
+ /**
+ * called when player.src gets called, handle a new source
+ *
+ * @param {Object} src the source object to handle
+ */
+ }, {
+ key: 'setSource',
+ value: function setSource(source) {
+ this.src(source.src);
+ }
+ }, {
+ key: 'src',
+ value: function src(_src) {
+ var _this3 = this;
+
+ // do nothing if the src is falsey
+ if (!_src) {
+ return;
+ }
+ this.setOptions_();
+ // add master playlist controller options
+ this.options_.url = this.source_.src;
+ this.options_.tech = this.tech_;
+ this.options_.externHls = Hls;
+ this.masterPlaylistController_ = new _masterPlaylistController2['default'](this.options_);
+ this.gapSkipper_ = new _gapSkipper2['default'](this.options_);
+
+ // `this` in selectPlaylist should be the HlsHandler for backwards
+ // compatibility with < v2
+ this.masterPlaylistController_.selectPlaylist = Hls.STANDARD_PLAYLIST_SELECTOR.bind(this);
+
+ // re-expose some internal objects for backwards compatibility with < v2
+ this.playlists = this.masterPlaylistController_.masterPlaylistLoader_;
+ this.mediaSource = this.masterPlaylistController_.mediaSource;
+
+ // Proxy assignment of some properties to the master playlist
+ // controller. Using a custom property for backwards compatibility
+ // with < v2
+ Object.defineProperties(this, {
+ selectPlaylist: {
+ get: function get() {
+ return this.masterPlaylistController_.selectPlaylist;
+ },
+ set: function set(selectPlaylist) {
+ this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this);
+ }
+ },
+ bandwidth: {
+ get: function get() {
+ return this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
+ },
+ set: function set(bandwidth) {
+ this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth;
+ }
+ }
+ });
+
+ Object.defineProperty(this.stats, 'bandwidth', {
+ get: function get() {
+ return _this3.bandwidth || 0;
+ }
+ });
+ Object.defineProperty(this.stats, 'mediaRequests', {
+ get: function get() {
+ return _this3.masterPlaylistController_.mediaRequests_() || 0;
+ }
+ });
+ Object.defineProperty(this.stats, 'mediaTransferDuration', {
+ get: function get() {
+ return _this3.masterPlaylistController_.mediaTransferDuration_() || 0;
+ }
+ });
+ Object.defineProperty(this.stats, 'mediaBytesTransferred', {
+ get: function get() {
+ return _this3.masterPlaylistController_.mediaBytesTransferred_() || 0;
+ }
+ });
+
+ this.tech_.one('canplay', this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_));
+
+ this.masterPlaylistController_.on('sourceopen', function () {
+ _this3.tech_.audioTracks().addEventListener('change', _this3.audioTrackChange_);
+ });
+
+ this.masterPlaylistController_.on('audioinfo', function (e) {
+ if (!_videoJs2['default'].browser.IS_FIREFOX || !_this3.audioInfo_ || !objectChanged(_this3.audioInfo_, e.info)) {
+ _this3.audioInfo_ = e.info;
+ return;
+ }
+
+ var error = 'had different audio properties (channels, sample rate, etc.) ' + 'or changed in some other way. This behavior is currently ' + 'unsupported in Firefox due to an issue: \n\n' + 'https://bugzilla.mozilla.org/show_bug.cgi?id=1247138\n\n';
+
+ var enabledTrack = undefined;
+ var defaultTrack = undefined;
+
+ _this3.masterPlaylistController_.audioTracks_.forEach(function (t) {
+ if (!defaultTrack && t['default']) {
+ defaultTrack = t;
+ }
+
+ if (!enabledTrack && t.enabled) {
+ enabledTrack = t;
+ }
+ });
+
+ // they did not switch audiotracks
+ // blacklist the current playlist
+ if (!enabledTrack.getLoader(_this3.activeAudioGroup_())) {
+ error = 'The rendition that we tried to switch to ' + error + 'Unfortunately that means we will have to blacklist ' + 'the current playlist and switch to another. Sorry!';
+ _this3.masterPlaylistController_.blacklistCurrentPlaylist();
+ } else {
+ error = 'The audio track \'' + enabledTrack.label + '\' that we tried to ' + ('switch to ' + error + ' Unfortunately this means we will have to ') + ('return you to the main track \'' + defaultTrack.label + '\'. Sorry!');
+ defaultTrack.enabled = true;
+ _this3.tech_.audioTracks().removeTrack(enabledTrack);
+ }
+
+ _videoJs2['default'].log.warn(error);
+ _this3.masterPlaylistController_.useAudio();
+ });
+ this.masterPlaylistController_.on('selectedinitialmedia', function () {
+ // clear current audioTracks
+ _this3.tech_.clearTracks('audio');
+ _this3.masterPlaylistController_.audioTracks_.forEach(function (track) {
+ _this3.tech_.audioTracks().addTrack(track);
+ });
+
+ // Add the manual rendition mix-in to HlsHandler
+ (0, _renditionMixin2['default'])(_this3);
+ });
+
+ // the bandwidth of the primary segment loader is our best
+ // estimate of overall bandwidth
+ this.on(this.masterPlaylistController_, 'progress', function () {
+ this.bandwidth = this.masterPlaylistController_.mainSegmentLoader_.bandwidth;
+ this.tech_.trigger('progress');
+ });
+
+ // do nothing if the tech has been disposed already
+ // this can occur if someone sets the src in player.ready(), for instance
+ if (!this.tech_.el()) {
+ return;
+ }
+
+ this.tech_.src(_videoJs2['default'].URL.createObjectURL(this.masterPlaylistController_.mediaSource));
+ }
+
+ /**
+ * a helper for grabbing the active audio group from MasterPlaylistController
+ *
+ * @private
+ */
+ }, {
+ key: 'activeAudioGroup_',
+ value: function activeAudioGroup_() {
+ return this.masterPlaylistController_.activeAudioGroup();
+ }
+
+ /**
+ * Begin playing the video.
+ */
+ }, {
+ key: 'play',
+ value: function play() {
+ this.masterPlaylistController_.play();
+ }
+
+ /**
+ * a wrapper around the function in MasterPlaylistController
+ */
+ }, {
+ key: 'setCurrentTime',
+ value: function setCurrentTime(currentTime) {
+ this.masterPlaylistController_.setCurrentTime(currentTime);
+ }
+
+ /**
+ * a wrapper around the function in MasterPlaylistController
+ */
+ }, {
+ key: 'duration',
+ value: function duration() {
+ return this.masterPlaylistController_.duration();
+ }
+
+ /**
+ * a wrapper around the function in MasterPlaylistController
+ */
+ }, {
+ key: 'seekable',
+ value: function seekable() {
+ return this.masterPlaylistController_.seekable();
+ }
+
+ /**
+ * Abort all outstanding work and cleanup.
+ */
+ }, {
+ key: 'dispose',
+ value: function dispose() {
+ if (this.masterPlaylistController_) {
+ this.masterPlaylistController_.dispose();
+ }
+ this.gapSkipper_.dispose();
+ this.tech_.audioTracks().removeEventListener('change', this.audioTrackChange_);
+ _get(Object.getPrototypeOf(HlsHandler.prototype), 'dispose', this).call(this);
+ }
+ }]);
+
+ return HlsHandler;
+})(Component);
+
+var HlsSourceHandler = function HlsSourceHandler(mode) {
+ return {
+ canHandleSource: function canHandleSource(srcObj) {
+ // this forces video.js to skip this tech/mode if its not the one we have been
+ // overriden to use, by returing that we cannot handle the source.
+ if (_videoJs2['default'].options.hls && _videoJs2['default'].options.hls.mode && _videoJs2['default'].options.hls.mode !== mode) {
+ return false;
+ }
+ return HlsSourceHandler.canPlayType(srcObj.type);
+ },
+ handleSource: function handleSource(source, tech, options) {
+ if (mode === 'flash') {
+ // We need to trigger this asynchronously to give others the chance
+ // to bind to the event when a source is set at player creation
+ tech.setTimeout(function () {
+ tech.trigger('loadstart');
+ }, 1);
+ }
+
+ var settings = _videoJs2['default'].mergeOptions(options, { hls: { mode: mode } });
+
+ tech.hls = new HlsHandler(source, tech, settings);
+
+ tech.hls.xhr = (0, _xhr2['default'])();
+ // Use a global `before` function if specified on videojs.Hls.xhr
+ // but still allow for a per-player override
+ if (_videoJs2['default'].Hls.xhr.beforeRequest) {
+ tech.hls.xhr.beforeRequest = _videoJs2['default'].Hls.xhr.beforeRequest;
+ }
+ tech.hls.src(source.src);
+ return tech.hls;
+ },
+ canPlayType: function canPlayType(type) {
+ if (HlsSourceHandler.canPlayType(type)) {
+ return 'maybe';
+ }
+ return '';
+ }
+ };
+};
+
+/**
+ * A comparator function to sort two playlist object by bandwidth.
+ *
+ * @param {Object} left a media playlist object
+ * @param {Object} right a media playlist object
+ * @return {Number} Greater than zero if the bandwidth attribute of
+ * left is greater than the corresponding attribute of right. Less
+ * than zero if the bandwidth of right is greater than left and
+ * exactly zero if the two are equal.
+ */
+Hls.comparePlaylistBandwidth = function (left, right) {
+ var leftBandwidth = undefined;
+ var rightBandwidth = undefined;
+
+ if (left.attributes && left.attributes.BANDWIDTH) {
+ leftBandwidth = left.attributes.BANDWIDTH;
+ }
+ leftBandwidth = leftBandwidth || window.Number.MAX_VALUE;
+ if (right.attributes && right.attributes.BANDWIDTH) {
+ rightBandwidth = right.attributes.BANDWIDTH;
+ }
+ rightBandwidth = rightBandwidth || window.Number.MAX_VALUE;
+
+ return leftBandwidth - rightBandwidth;
+};
+
+/**
+ * A comparator function to sort two playlist object by resolution (width).
+ * @param {Object} left a media playlist object
+ * @param {Object} right a media playlist object
+ * @return {Number} Greater than zero if the resolution.width attribute of
+ * left is greater than the corresponding attribute of right. Less
+ * than zero if the resolution.width of right is greater than left and
+ * exactly zero if the two are equal.
+ */
+Hls.comparePlaylistResolution = function (left, right) {
+ var leftWidth = undefined;
+ var rightWidth = undefined;
+
+ if (left.attributes && left.attributes.RESOLUTION && left.attributes.RESOLUTION.width) {
+ leftWidth = left.attributes.RESOLUTION.width;
+ }
+
+ leftWidth = leftWidth || window.Number.MAX_VALUE;
+
+ if (right.attributes && right.attributes.RESOLUTION && right.attributes.RESOLUTION.width) {
+ rightWidth = right.attributes.RESOLUTION.width;
+ }
+
+ rightWidth = rightWidth || window.Number.MAX_VALUE;
+
+ // NOTE - Fallback to bandwidth sort as appropriate in cases where multiple renditions
+ // have the same media dimensions/ resolution
+ if (leftWidth === rightWidth && left.attributes.BANDWIDTH && right.attributes.BANDWIDTH) {
+ return left.attributes.BANDWIDTH - right.attributes.BANDWIDTH;
+ }
+ return leftWidth - rightWidth;
+};
+
+HlsSourceHandler.canPlayType = function (type) {
+ var mpegurlRE = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
+
+ // favor native HLS support if it's available
+ // if (Hls.supportsNativeHls) {
+ // return false;
+ // }
+ return mpegurlRE.test(type);
+};
+
+if (typeof _videoJs2['default'].MediaSource === 'undefined' || typeof _videoJs2['default'].URL === 'undefined') {
+ _videoJs2['default'].MediaSource = _videojsContribMediaSources.MediaSource;
+ _videoJs2['default'].URL = _videojsContribMediaSources.URL;
+}
+
+// register source handlers with the appropriate techs
+if (_videojsContribMediaSources.MediaSource.supportsNativeMediaSources()) {
+ _videoJs2['default'].getComponent('Html5').registerSourceHandler(HlsSourceHandler('html5'));
+}
+if (window.Uint8Array) {
+ _videoJs2['default'].getComponent('Flash').registerSourceHandler(HlsSourceHandler('flash'));
+}
+
+_videoJs2['default'].HlsHandler = HlsHandler;
+_videoJs2['default'].HlsSourceHandler = HlsSourceHandler;
+_videoJs2['default'].Hls = Hls;
+_videoJs2['default'].m3u8 = _m3u8Parser2['default'];
+_videoJs2['default'].registerComponent('Hls', Hls);
+_videoJs2['default'].options.hls = _videoJs2['default'].options.hls || {};
+
+module.exports = {
+ Hls: Hls,
+ HlsHandler: HlsHandler,
+ HlsSourceHandler: HlsSourceHandler
+};
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./bin-utils":1,"./config":2,"./gap-skipper":3,"./master-playlist-controller":5,"./playlist":7,"./playlist-loader":6,"./rendition-mixin":9,"./xhr":14,"aes-decrypter":18,"global/document":23,"m3u8-parser":61,"videojs-contrib-media-sources":94}]},{},[97])(97)
+});
diff --git a/mip-ck-course-detail/js/mediator.js b/mip-ck-course-detail/js/mediator.js
new file mode 100644
index 00000000..245b705c
--- /dev/null
+++ b/mip-ck-course-detail/js/mediator.js
@@ -0,0 +1,15 @@
+/**
+ * @file: mediator.js
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-10 11:29:17
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-10 11:30:41
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ return $({});
+});
diff --git a/mip-ck-course-detail/js/player.js b/mip-ck-course-detail/js/player.js
new file mode 100644
index 00000000..317dbe27
--- /dev/null
+++ b/mip-ck-course-detail/js/player.js
@@ -0,0 +1,191 @@
+/**
+ * @file: player.js
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-10 14:37:19
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-26 18:04:48
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+ var $ = require('zepto');
+ var mediator = require('./mediator');
+ var tplData = require('./tpl-data');
+
+ var videojs = require('./lib/video');
+
+ window.videojs = videojs;
+
+ var videojsHls = require('./lib/videojs-contrib-hls');
+
+ // videojsHls();
+
+
+ var $wrap;
+ var $video;
+ var player;
+ var showDownloadAppBox = true;
+
+
+ var courseTplData;
+ var courseid;
+ var sid;
+ var cid;
+ var uid;
+ var type;
+
+ var playerInfo;
+ var hasVideo;
+ var preVideoSrc;
+ var vodSource;
+ var coverUrl;
+
+ var autoPlayVideoTimer;
+
+ function getTplData() {
+
+ courseTplData = tplData.get('courseInfo');
+ courseid = courseTplData.courseid;
+ sid = courseTplData.sid;
+ cid = courseTplData.cid;
+ uid = courseTplData.uid;
+ type = courseTplData.type;
+
+ playerInfo = tplData.get('playerInfo');
+ hasVideo = playerInfo.hasVideo;
+ preVideoSrc = playerInfo.preVideoSrc;
+ vodSource = playerInfo.vodSource;
+ coverUrl = playerInfo.coverUrl;
+ }
+
+ function loadH5Video(source) {
+ player = videojs('video');
+
+ // 必须转成字符串, 也是醉了
+ player.src({
+ src: source.src,
+ code: source.key,
+ courseid: courseid + '',
+ sid: sid + '',
+ cid: (source.cid || cid) + '',
+ uid: uid + '',
+ type: source.src.indexOf('.mp4') > -1 ? '' : 'application/x-mpegURL'
+ });
+ }
+
+ function renderVideo() {
+
+ $wrap = $('.video');
+ $video = $(
+ ''
+ );
+
+ $wrap.prepend($video);
+ }
+
+ function firstLoad() {
+ if (vodSource.length > 0) {
+ loadH5Video(vodSource[0]);
+ }
+ else {
+ loadH5Video({src: preVideoSrc[0], key: ''});
+ }
+ }
+
+
+ function getNextClassInfo() {
+ if (type === 1) {
+ mediator.trigger('trial-next');
+ }
+ }
+
+ function autoPlayVideo(i) {
+ if (i <= 0) {
+ clearTimeout(autoPlayVideoTimer);
+ autoPlayVideoTimer = null;
+ $('#play-end').hide();
+ getNextClassInfo();
+ return;
+ }
+
+ $('#auto_play_num').text(i);
+ autoPlayVideoTimer = setTimeout(function () {
+ autoPlayVideo(i - 1);
+ }, 1000);
+ }
+
+ function playPause() {
+ if (showDownloadAppBox) {
+ mediator.trigger('toggle-app-download-banner');
+ }
+ }
+
+ function playEnd() {
+
+ var v = $video[0];
+
+ if (player.currentTime() >= parseInt(player.duration(), 10) - 1) {
+ if (type === 1) {
+ v.load();
+ autoPlayVideo(3);
+ mediator.trigger('toggle-end-pop');
+ }
+ else if (type === 2) {
+ v.load();
+ $('#trial-end').show();
+ }
+ }
+ }
+
+ function playMobiTrail() {
+ // player.on('play', beginToPlay);
+ player.on('pause', playPause);
+ player.on('ended', playEnd);
+ }
+
+ function disposeVideo() {
+ player.off('pause', playPause);
+ player.off('ended', playEnd);
+ player.dispose();
+ }
+
+
+ function init() {
+
+ getTplData();
+
+ if (!hasVideo) {
+ return;
+ }
+
+ mediator.on('toggle-app-download-flag', function (e, data) {
+ var show = data.show;
+ showDownloadAppBox = !!show;
+ });
+ mediator.on('change-video-src', function (e, data) {
+
+ if (player) {
+ disposeVideo();
+ }
+
+ renderVideo();
+ loadH5Video(data.data);
+ playMobiTrail();
+ $('#video_loading').remove();
+
+ mediator.trigger('toggle-app-download-banner', true);
+ showDownloadAppBox = true;
+ });
+
+
+ $('#play-next-class').on('click', function () {
+ getNextClassInfo();
+ });
+
+ renderVideo();
+ firstLoad();
+ playMobiTrail();
+ }
+ return init;
+});
diff --git a/mip-ck-course-detail/js/share.js b/mip-ck-course-detail/js/share.js
new file mode 100644
index 00000000..582053f0
--- /dev/null
+++ b/mip-ck-course-detail/js/share.js
@@ -0,0 +1,82 @@
+/**
+ * @file: share.js
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-10 16:33:04
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-10 16:47:41
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var $doc = $(document);
+ var $shareWrap;
+ var $box;
+ var isShow = false;
+
+
+ var noScroll = function (e) {
+ e.preventDefault();
+ };
+
+ var hideBox = function () {
+
+ var height = $box.height();
+ $box.height(height);
+ $box.addClass('anim-slide');
+
+ $box.height(0);
+ isShow = false;
+ };
+
+ function init() {
+
+ $shareWrap = $('#share-layout-container');
+ $box = $shareWrap.find('.g-share');
+
+
+ $('#share').on('click', function (e) {
+ $doc.on('touchmove', noScroll);
+
+ $box.addClass('v-hide');
+ $shareWrap.show();
+ $box.show();
+ var height = $box.height();
+
+ $box.height(0);
+ $box.removeClass('v-hide');
+ $box.addClass('anim-slide');
+
+ $box.height(height);
+ isShow = true;
+ });
+ $shareWrap.on('click', '.c-share-btn', function (e) {
+ $doc.off('touchmove', noScroll);
+
+ if ($(this).html()) {
+ hideBox();
+ }
+ });
+
+ $box.on($.fx.transitionEnd, function (e) {
+ $box.removeClass('anim-slide');
+
+ if (!isShow) {
+ $shareWrap.hide();
+ }
+ $box.css('height', '');
+ });
+
+ $shareWrap.on('click', function (e) {
+ var tar = e.target;
+ var c = $(tar).attr('data-close');
+
+ if (c === '1') {
+ hideBox();
+ }
+ });
+ }
+ return init;
+});
diff --git a/mip-ck-course-detail/js/tab.js b/mip-ck-course-detail/js/tab.js
new file mode 100644
index 00000000..b73240bb
--- /dev/null
+++ b/mip-ck-course-detail/js/tab.js
@@ -0,0 +1,99 @@
+/**
+ * @file: tab.js
+ * @author: yanglei07
+ * @description 页面tab切换组件部分, 纯业务, 非通用组件
+ * @create data: 2016-10-09 17:41:51
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-26 18:05:09
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+ var $ = require('zepto');
+ var mediator = require('./mediator');
+ var util = require('util');
+ var Gesture = util.Gesture;
+
+ var $win = $(window);
+ var $tabControl;
+ var $tabControlWrap;
+ var $tabControls;
+ var $tabContentWrap;
+ var $tabContents;
+ var currentIndex = 0;
+ var tabCount;
+
+ function needFixedTabContainer() {
+ var scrollHeight = $win.scrollTop();
+ var tabHeight = $tabControlWrap.offset().top;
+ return scrollHeight > tabHeight;
+ }
+
+ function init() {
+ $tabControl = $('#details-tab');
+ $tabControlWrap = $tabControl.closest('.detail-tab-controls-wrap');
+ $tabControls = $tabControl.find('.tab-control');
+ $tabContentWrap = $('.swiper-container');
+ $tabContents = $tabContentWrap.find('.swiper-slide');
+ tabCount = $tabControls.size();
+
+ // tab切换
+ $tabControl.on('click', '.tab-control', function (e) {
+ var $tab = $(this);
+
+ var index = $tabControls.index(this);
+ var $content = $tabContents.eq(index);
+
+ if (index !== currentIndex) {
+ $tabControls.removeClass('cur');
+ $tab.addClass('cur');
+
+ $tabContents.removeClass('cur');
+ $content.addClass('cur');
+
+ currentIndex = index;
+
+ mediator.trigger('tab-switched', index);
+ }
+ });
+
+ var gesture = new Gesture($tabContentWrap.get(0));
+ gesture.on('swipeleft swiperight', function (e, data) {
+ var index = currentIndex;
+
+ if (data.type === 'swiperight') {
+ index = Math.max(0, index - 1);
+ }
+ else {
+ index = Math.min(tabCount, index + 1);
+ }
+
+ mediator.trigger('tab-switch', index);
+ });
+
+ $('#course_comments').on('click', function (e) {
+ mediator.trigger('tab-switch', 2);
+ });
+ $('#course_details').on('click', function (e) {
+ mediator.trigger('tab-switch', 1);
+ });
+
+ mediator.on('tab-switch', function (e, index) {
+
+ if (index !== currentIndex) {
+ $tabControls.eq(index).trigger('click');
+ }
+ });
+
+ // 滚动固定
+ mediator.on('window-scroll', function () {
+ var method = 'removeClass';
+ if (needFixedTabContainer()) {
+ method = 'addClass';
+ }
+ $tabControl[method]('fixed-details-tab');
+ });
+ }
+
+ return init;
+});
diff --git a/mip-ck-course-detail/js/tpl-data.js b/mip-ck-course-detail/js/tpl-data.js
new file mode 100644
index 00000000..7660f6d8
--- /dev/null
+++ b/mip-ck-course-detail/js/tpl-data.js
@@ -0,0 +1,86 @@
+/**
+ * @file: tpl-data.js
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-10 13:25:00
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-17 13:00:41
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var tplData = {};
+ var fixFnMap = {
+ courseInfo: function (data) {
+
+ data.courseid = +data.courseid;
+ data.sid = +data.sid;
+ data.cid = +data.cid;
+ data.userid = +data.uid || 0;
+ data.uid = +data.uid || 0;
+ data.cost = +data.cost || 0;
+ data.type = +data.type;
+ data.ucid = +data.ucid;
+
+ return data;
+ },
+ courseAppDownloadInfo: function (data) {
+ data.isInCoursePage = !!data.isInCoursePage;
+ return data;
+ },
+ playerInfo: function (data) {
+
+ data.hasVideo = !!data.hasVideo;
+ data.boundTips = !!data.boundTips;
+
+ var hlsList = data.vodSource;
+ if (hlsList) {
+ try {
+ var fn = new Function('return [' + hlsList + '];');
+ hlsList = fn();
+ }
+ catch (ex) {
+ hlsList = [];
+ }
+ }
+ else {
+ hlsList = [];
+ }
+ data.vodSource = hlsList;
+
+ return data;
+ }
+ };
+
+ $('script.tpl-json-data').each(function (i, script) {
+ var $s = $(script);
+ var dataStr = $s.text();
+ var dataKey = $s.attr('data-key');
+
+ var data;
+
+ try {
+ data = $.parseJSON(dataStr);
+ }
+ catch (ex) {
+ data = {};
+ }
+
+ if (fixFnMap[dataKey]) {
+ data = fixFnMap[dataKey](data);
+ }
+
+ tplData[dataKey] = data;
+ });
+
+ return {
+ get: function (key) {
+ return tplData[key];
+ },
+ set: function (key, val) {
+ tplData[key] = val;
+ }
+ };
+});
diff --git a/mip-ck-course-detail/js/util.js b/mip-ck-course-detail/js/util.js
new file mode 100644
index 00000000..edfd479d
--- /dev/null
+++ b/mip-ck-course-detail/js/util.js
@@ -0,0 +1,19 @@
+/**
+ * @file: util.js
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-10 14:52:59
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-24 11:21:45
+ */
+/* global Vue, _, yog */
+
+define(function (require) {
+
+ var util = {
+ domain: '//m.chuanke.com'
+ // domain: '//mlocal.chuanke.com'
+ };
+
+ return util;
+});
diff --git a/mip-ck-course-detail/mip-ck-course-detail.js b/mip-ck-course-detail/mip-ck-course-detail.js
new file mode 100644
index 00000000..79ffd11c
--- /dev/null
+++ b/mip-ck-course-detail/mip-ck-course-detail.js
@@ -0,0 +1,24 @@
+/**
+ * 广告插件
+ *
+ * @author wangpei07@baidu.com
+ * @version 1.0
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+define(function (require) {
+
+
+ var customElement = require('customElement').create();
+ var $ = require('zepto');
+ var init = require('./js/index');
+
+ customElement.prototype.init = function () {
+ $('#down_float_div').hide();
+ $('.init-page-inner').removeClass('init-page-inner');
+
+ $(init);
+ };
+
+ return customElement;
+});
+
diff --git a/mip-ck-course-detail/mip-ck-course-detail.less b/mip-ck-course-detail/mip-ck-course-detail.less
new file mode 100644
index 00000000..ea99a614
--- /dev/null
+++ b/mip-ck-course-detail/mip-ck-course-detail.less
@@ -0,0 +1,12 @@
+/**
+ * @file:
+ * @author: yanglei07
+ * @description ..
+ * @create data: 2016-10-26 19:03:31
+ * @last modified by: yanglei07
+ * @last modified time: 2016-10-26 19:03:39
+ */
+
+@import (inline) './css/video-js.css';
+@import './css/main.less';
+@import './css/detail.less';
\ No newline at end of file
diff --git a/mip-ck-course-detail/package.json b/mip-ck-course-detail/package.json
new file mode 100644
index 00000000..f047da28
--- /dev/null
+++ b/mip-ck-course-detail/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-ck-course-detail",
+ "version": "1.1.2",
+ "author": {
+ "name": "yanglei07",
+ "email": "yanglei07@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-ck-location/README.md b/mip-ck-location/README.md
new file mode 100644
index 00000000..fe444e52
--- /dev/null
+++ b/mip-ck-location/README.md
@@ -0,0 +1,50 @@
+# mip-ck-location
+
+mip-ck-location 根据不同地区展示不同的内容
+
+|描述|脚本mip改造|
+|---|---|
+|类型|脚本|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-ck-location/mip-ck-location.js|
+
+## 示例
+
+
+```html
+
+
+
+ this is content at beijing area
+
+
+
+
+ this is content at beijing area in uc browser
+
+
+
+
+ this is content not at beijing area
+
+
+```
+
+## 属性
+
+### location
+
+说明:选择地区
+必选项:是
+类型:字符串
+取值范围:地区编码:1, 2, 3, 4.... (1 => 北京),匹配多个浏览器使用,分开
+
+### converse
+
+说明:排除area属性提供的浏览器环境
+必选项:否
+类型:字符串
+取值范围:converse, true
diff --git a/mip-ck-location/ck-location.js b/mip-ck-location/ck-location.js
new file mode 100644
index 00000000..78cdb7c0
--- /dev/null
+++ b/mip-ck-location/ck-location.js
@@ -0,0 +1,25 @@
+/**
+ * @author: yoyoyoo
+ * @date: 2016-12-12
+ * @file: mip-ck-browser.js
+ */
+
+define(function (require) {
+ var module = {};
+ var $ = require('zepto');
+ var url = 'https://m.cnkang.com/video/areaname?' + (+new Date());
+
+ module.get = function (cb) {
+ cb = cb || function () { };
+
+ $.post(url, function (res) {
+ var errno = +res.errno;
+
+ if (errno === 0) {
+ cb(res.default);
+ }
+ });
+ };
+
+ return module;
+});
diff --git a/mip-ck-location/mip-ck-location.js b/mip-ck-location/mip-ck-location.js
new file mode 100644
index 00000000..e7e210aa
--- /dev/null
+++ b/mip-ck-location/mip-ck-location.js
@@ -0,0 +1,74 @@
+/**
+ * @author: yoyoyoo
+ * @date: 2016-12-12
+ * @file: mip-ck-browser.js
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var getLocation = require('./ck-location').get;
+ var $ = require('zepto');
+ var $body = $('body');
+
+ function setHtmlLocation(elem, locationsType) {
+ getLocation(function (data) {
+
+ var len = locationsType.length;
+ var i = 0;
+ var locationType = '';
+ var converse = elem.getAttribute('converse');
+ var locationClass = locationsType.join('-');
+ var converseClass = '';
+
+ if (converse !== null) {
+ converseClass = '-' + 'converse';
+ }
+
+ for (i; i < len; i++) {
+ locationType = +(locationsType[i]);
+
+ var flag = false;
+
+ // 判断元素是否有浏览器取反
+ if (converse === null) {
+ if (locationType === data) { // 判断浏览器类型
+ flag = true;
+ break;
+ }
+ }
+ else {
+ if (locationType === data) {
+ flag = false;
+ break;
+ }
+ else {
+ flag = true;
+ }
+ }
+ }
+
+ if (flag) {
+ // 真 显示元素
+ elem.style.display = 'block';
+ $body.addClass('v-mip-ck-location-' + locationClass + converseClass);
+ }
+ else {
+ // 假 移除元素
+ elem.parentNode.removeChild(elem);
+ }
+ });
+ }
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var self = this;
+
+ var locationType = self.element.getAttribute('location') || '';
+ var locationsType = locationType.split(',') || [];
+
+ setHtmlLocation(self.element, locationsType);
+ };
+
+ return customElem;
+});
+
diff --git a/mip-ck-location/package.json b/mip-ck-location/package.json
new file mode 100644
index 00000000..561d1033
--- /dev/null
+++ b/mip-ck-location/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-ck-location",
+ "version": "1.0.0",
+ "author": {
+ "name": "yoyoyoo",
+ "email": "2996309530@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-ck-script/README.md b/mip-ck-script/README.md
new file mode 100644
index 00000000..6fe8c164
--- /dev/null
+++ b/mip-ck-script/README.md
@@ -0,0 +1,20 @@
+# mip-ck-ad
+
+mip-ck-ad 是康网的问答详情页面的直投广告组件
+
+标题|内容
+----|----
+类型|业务,广告
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-ck-script/mip-ck-script.js
+
+## 示例
+
+### 基本使用
+
+```html
+
+
+
+
+```
diff --git a/mip-ck-script/mip-ck-script.js b/mip-ck-script/mip-ck-script.js
new file mode 100644
index 00000000..b610fbd7
--- /dev/null
+++ b/mip-ck-script/mip-ck-script.js
@@ -0,0 +1,79 @@
+define(function (require) {
+ var $ = require('zepto');
+ var customElement = require('customElement').create();
+ var ua = navigator.userAgent;
+ var device = {
+ UC: ((function () {
+ return /UCBrowser/i.test(ua);
+ })()),
+
+ QQ: ((function () {
+ return /MQQBrowser/i.test(ua);
+ })())
+ };
+
+ // 页面交互效果
+ var effects = {
+ // 标签切换
+ switchBlock: function () {
+ var $switchBlock = $('#week_hot_switch');
+ $switchBlock.on('click', '.switch_tabs a', function(event) {
+ event.preventDefault();
+ var $currEle = $(this);
+ $currEle.addClass('curr').siblings().removeClass('curr');
+ var idx = $switchBlock.find('.switch_tabs a').index(this);
+ $switchBlock.find('.switch_content').hide().eq(idx).show();
+ });
+ },
+ // 换一换
+ changMore: function () {
+ $('.useful').on('click', function (event) {
+ event.preventDefault();
+ var clicked = $(this).attr('clicked');
+ if (clicked) {
+ return;
+ }
+ var countEle = $(this).find('.count');
+ var currCount = Number(countEle.text());
+ countEle.text(currCount + 1);
+ $(this).attr('clicked', 'yes');
+ });
+ },
+ // 变换颜色
+ changeColor: function () {
+ $('.c_ad_title p,.c_ad_title p a').css('color', $('.navigate').css('background-color'));
+ },
+ // uc/qq浏览器加载反屏蔽网盟代码
+ loadUCad: function () {
+ var ucAd1 = ''
+ + '';
+ var ucAd2 = ''
+ + '';
+ var dom2ad = {
+ '[mip-ck-ad-bd-2-uc-1]': ucAd1,
+ '[mip-ck-ad-bd-2-uc-2]': ucAd2
+ };
+ if (device.QQ) {
+ $.each(dom2ad, function (k, v) {
+ $(k).html(v);
+ });
+ }
+ },
+ // 加载两性ad列表
+ init: function () {
+ this.switchBlock();
+ this.changMore();
+ this.changeColor();
+ this.loadUCad();
+ }
+ };
+
+ customElement.prototype.build = function () {
+ effects.init();
+ };
+
+ return customElement;
+});
+
diff --git a/mip-ck-script/package.json b/mip-ck-script/package.json
new file mode 100644
index 00000000..f1b89904
--- /dev/null
+++ b/mip-ck-script/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-ck-script",
+ "version": "1.1.2",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-click-to-scroll/README.md b/mip-click-to-scroll/README.md
index 611b8947..572cbccd 100644
--- a/mip-click-to-scroll/README.md
+++ b/mip-click-to-scroll/README.md
@@ -5,7 +5,7 @@ mip-click-to-scroll 可以使点击的元素滚动至页面顶部
----|----
类型|业务
布局|display: none
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-click-to-scroll/mip-click-to-scroll.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-click-to-scroll/mip-click-to-scroll.js
## 示例
diff --git a/mip-clickup/README.md b/mip-clickup/README.md
index 9c029a6a..743dd9cd 100644
--- a/mip-clickup/README.md
+++ b/mip-clickup/README.md
@@ -6,7 +6,7 @@ mip-clickup 康网链接点击记录
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-clickup/mip-clickup.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-clickup/mip-clickup.js
## 示例
diff --git a/mip-close-ad/README.md b/mip-close-ad/README.md
index bf41efdc..cc7aa50f 100644
--- a/mip-close-ad/README.md
+++ b/mip-close-ad/README.md
@@ -6,7 +6,7 @@ mip-close-ad 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-close-ad/mip-close-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-close-ad/mip-close-ad.js
## 示例
diff --git a/mip-close-dom/README.md b/mip-close-dom/README.md
index 79ddc885..02379598 100644
--- a/mip-close-dom/README.md
+++ b/mip-close-dom/README.md
@@ -6,7 +6,7 @@ mip-close-dom 关闭组件点击关闭外层dom
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-close-dom/mip-close-dom.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-close-dom/mip-close-dom.js
## 示例
diff --git a/mip-cloud-tie/README.md b/mip-cloud-tie/README.md
index 6af80973..92862dd3 100644
--- a/mip-cloud-tie/README.md
+++ b/mip-cloud-tie/README.md
@@ -6,7 +6,7 @@ mip-cloud-tie 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cloud-tie/mip-cloud-tie.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cloud-tie/mip-cloud-tie.js
## 示例
diff --git a/mip-cngold-ajax-table/README.md b/mip-cngold-ajax-table/README.md
index ffd5f54f..607676fe 100644
--- a/mip-cngold-ajax-table/README.md
+++ b/mip-cngold-ajax-table/README.md
@@ -6,7 +6,7 @@ mip-cngold-ajax-table 用于请求后寻找对应id渲染
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cngold-ajax-table/mip-cngold-ajax-table.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cngold-ajax-table/mip-cngold-ajax-table.js
## 示例
diff --git a/mip-cngold-madapt/README.md b/mip-cngold-madapt/README.md
index 1df02e63..69946818 100644
--- a/mip-cngold-madapt/README.md
+++ b/mip-cngold-madapt/README.md
@@ -6,7 +6,7 @@ mip-cngold-madapt 页面样式尺寸转换
----|----
类型|通用
支持布局|container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cngold-madapt/mip-cngold-madapt.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cngold-madapt/mip-cngold-madapt.js
## 示例
diff --git a/mip-cnkang-content/README.md b/mip-cnkang-content/README.md
index 7c00be0a..7adc0114 100644
--- a/mip-cnkang-content/README.md
+++ b/mip-cnkang-content/README.md
@@ -6,7 +6,7 @@ mip-cnkang-content 区分cnkang两性和非两性
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-content/mip-cnkang-content.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-content/mip-cnkang-content.js
## 示例
diff --git a/mip-cnkang-data/README.md b/mip-cnkang-data/README.md
index 83279205..d8ceb8cd 100644
--- a/mip-cnkang-data/README.md
+++ b/mip-cnkang-data/README.md
@@ -6,7 +6,7 @@ mip-cnkang-data 康网直投广告组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-data/mip-cnkang-data.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-data/mip-cnkang-data.js
## 示例
diff --git a/mip-cnkang-details/README.md b/mip-cnkang-details/README.md
index c25e5496..22c68d57 100644
--- a/mip-cnkang-details/README.md
+++ b/mip-cnkang-details/README.md
@@ -6,7 +6,7 @@ mip-cnkang-details 康网mip内容化
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-details/mip-cnkang-details.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-details/mip-cnkang-details.js
## 示例
diff --git a/mip-cnkang-direct/README.md b/mip-cnkang-direct/README.md
index e4793533..595a23c9 100644
--- a/mip-cnkang-direct/README.md
+++ b/mip-cnkang-direct/README.md
@@ -6,7 +6,7 @@ mip-cnkang-direct 康网直投广告
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-direct/mip-cnkang-direct.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-direct/mip-cnkang-direct.js
## 示例
diff --git a/mip-cnkang-elect/README.md b/mip-cnkang-elect/README.md
index df9d6470..ff2ae317 100644
--- a/mip-cnkang-elect/README.md
+++ b/mip-cnkang-elect/README.md
@@ -6,7 +6,7 @@ mip-cnkang-elect 康网下拉列表点选组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-elect/mip-cnkang-elect.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-elect/mip-cnkang-elect.js
## 示例
diff --git a/mip-cnkang-href/README.md b/mip-cnkang-href/README.md
index 1eb66a2a..e86ebce4 100644
--- a/mip-cnkang-href/README.md
+++ b/mip-cnkang-href/README.md
@@ -6,7 +6,7 @@ mip-cnkang-href 有来链接组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-href/mip-cnkang-href.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-href/mip-cnkang-href.js
## 示例
diff --git a/mip-cnkang-pic/README.md b/mip-cnkang-pic/README.md
index 31fc0710..a014393e 100644
--- a/mip-cnkang-pic/README.md
+++ b/mip-cnkang-pic/README.md
@@ -6,7 +6,7 @@ mip-cnkang-pic 康网添加三张图片
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-pic/mip-cnkang-pic.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-pic/mip-cnkang-pic.js
## 示例
diff --git a/mip-cnkang-sex/README.md b/mip-cnkang-sex/README.md
index c477cd47..521a2869 100644
--- a/mip-cnkang-sex/README.md
+++ b/mip-cnkang-sex/README.md
@@ -6,7 +6,7 @@ mip-cnkang-sex 康网两性文章mip化
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-sex/mip-cnkang-sex.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-sex/mip-cnkang-sex.js
## 示例
diff --git a/mip-cnkang-switch-display/README.md b/mip-cnkang-switch-display/README.md
index dcd3337f..c25dbf2d 100644
--- a/mip-cnkang-switch-display/README.md
+++ b/mip-cnkang-switch-display/README.md
@@ -6,7 +6,7 @@ mip-cnkang-switch-display 有来切换显示组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cnkang-switch-display/mip-cnkang-switch-display.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cnkang-switch-display/mip-cnkang-switch-display.js
## 示例
diff --git a/mip-con-but/README.md b/mip-con-but/README.md
index e49a2a34..d834a9ca 100644
--- a/mip-con-but/README.md
+++ b/mip-con-but/README.md
@@ -5,7 +5,7 @@ mip-con-but 多功能控制按钮
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-con-but/mip-con-but.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-con-but/mip-con-but.js
## 示例
### 多功能控制按钮
diff --git a/mip-container-expand/README.md b/mip-container-expand/README.md
index 21360dbf..f9ea2b14 100644
--- a/mip-container-expand/README.md
+++ b/mip-container-expand/README.md
@@ -5,7 +5,7 @@ mip-container-expand 可以在页面任意位置展开组件里的内容
----|----
类型|业务,定制
支持布局|responsive,flex,container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-container-expand/mip-container-expand.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-container-expand/mip-container-expand.js
## 示例
diff --git a/mip-content-readmore/README.md b/mip-content-readmore/README.md
new file mode 100644
index 00000000..f084f093
--- /dev/null
+++ b/mip-content-readmore/README.md
@@ -0,0 +1,82 @@
+# mip-content-readmore
+
+mip-content-readmore 查看更多文本内容
+
+标题|内容
+----|----
+类型|通用
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-content-readmore/mip-content-readmore.js
+
+## 示例
+
+### 基本用法
+```html
+
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略2333字)
+
+
+==========================================
+
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
bla bla bla ... bla bla bla ... bla bla bla ... (此略6666字)
+
+
+```
+
+## 属性
+
+### element
+
+说明:需要控制的元素,仅支持jquery的id和class元素选择器语法
+必选项:否
+类型:字符串
+默认值:.content
+
+### maxlen
+
+说明:剔除空格、html标签后的文本长度(大约)
+必选项:否
+类型:正整数
+默认值:666
+
+### buttitle
+
+说明:按钮名称
+必选项:否
+类型:字符串
+默认值:查看更多
+
+## 注意事项
+
+1、仅检查"<"和">"来判断是否在html标签内,所以,如果内容包含字符"<"或">",字数统计可能存在被坑爹的情况,请根据实际情况慎用
+1.1、对与上述问题有更好的解决方法,可以email我,谢谢。
\ No newline at end of file
diff --git a/mip-content-readmore/mip-content-readmore.js b/mip-content-readmore/mip-content-readmore.js
new file mode 100644
index 00000000..c6569ed8
--- /dev/null
+++ b/mip-content-readmore/mip-content-readmore.js
@@ -0,0 +1,94 @@
+/**
+ * @file 查看更多文本内容
+ * @author jfdsies@gmail.com
+ * @version 1.0.0
+ * @copyright 2016 yjbys.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var $ = function (selector) {
+ var selectorType = 'querySelectorAll';
+
+ if (selector.indexOf('#') === 0) {
+ selectorType = 'getElementById';
+ selector = selector.substr(1, selector.length);
+ }
+ else if (selector.indexOf('.') === 0) {
+ selectorType = 'getElementsByClassName';
+ selector = selector.substr(1, selector.length);
+ }
+ return document[selectorType](selector);
+ };
+ var customElement = require('customElement').create();
+
+ function htmlDecode(str) {
+ var s = '';
+ if (str.length < 1) {
+ return '';
+ }
+ s = str.replace(/&/g, '&');
+ s = s.replace(//g, '>');
+ s = s.replace(/ /g, ' ');
+ s = s.replace(/'/g, '\'');
+ s = s.replace(/"/g, '\"');
+ return s;
+ }
+
+ customElement.prototype.createdCallback = function () {
+ var me = this.element;
+ var maxlen = me.getAttribute('maxlen');
+ var buttitle = me.getAttribute('buttitle');
+ var element = me.getAttribute('element');
+ maxlen = maxlen ? maxlen : 666;
+ buttitle = buttitle ? buttitle : '查看更多';
+ element = element ? $(element)[0] : $('.content')[0];
+
+ var content = htmlDecode(element.innerHTML);
+ // 避免特定情况下,图片重复显示的问题
+ content = content.replace(//, '');
+ var conlen = content.length;
+ var intag = false;
+ var len = 0;
+ var tmp;
+ for (var i = 0; ; i++) {
+ if (len >= maxlen) {
+ len = i;
+ break;
+ }
+ if (i >= conlen) {
+ len = conlen;
+ break;
+ }
+ tmp = content.charAt(i);
+ if (tmp === '<' && !intag) {
+ intag = true;
+ continue;
+ }
+ else if ('>' === tmp && intag) {
+ intag = false;
+ continue;
+ }
+ else if (intag || tmp === '' || tmp === ' ' || tmp === '\r' || tmp === '\n') {
+ continue;
+ }
+ len++;
+ }
+ if (content !== undefined && conlen > len) {
+ var id = Math.floor(Math.random() * 100);
+ me.innerHTML = '
'
+ + buttitle + '
';
+ element.innerHTML = content.substring(0, len) + ' ......';
+ var readmoreele = $('.mip-content-readmore');
+ readmoreele[readmoreele.length - 1].addEventListener('click', function () {
+ var thisid = this.getAttribute('data-mip-readmore-id');
+ $('mip-readmore' + thisid)[0].innerHTML = content.substring(len);
+ me.innerHTML = '';
+ }, false);
+ }
+ };
+
+ return customElement;
+});
diff --git a/mip-content-readmore/mip-content-readmore.less b/mip-content-readmore/mip-content-readmore.less
new file mode 100644
index 00000000..8ff87344
--- /dev/null
+++ b/mip-content-readmore/mip-content-readmore.less
@@ -0,0 +1,45 @@
+.mip-content-readmore {
+ text-align: center;
+ height: 30px;
+ width: 94px;
+ margin: auto;
+ margin-bottom: 10px;
+ cursor: pointer;
+ display: block;
+}
+
+.mip-content-readmore em {
+ display: inline-block;
+ height: 20px;
+ width: 20px;
+ top: 3px;
+ background: #3c91c7;
+ border-radius: 50px;
+ margin-right: 5px;
+ margin-top: 6px;
+ position: relative;
+}
+
+.mip-content-readmore span {
+ display: inline-block;
+ font-size: 17px;
+ color: #2184c4;
+}
+
+.mip-content-readmore .mip-readmore-fx:after,
+.mip-content-readmore .mip-readmore-fx:before {
+ width: 0;
+ height: 0;
+ border: .45rem solid transparent;
+ border-top: .45rem solid #3c91c7;
+ display: inline-block;
+ position: absolute;
+ top: .3rem;
+ left: .2rem;
+ content: "";
+}
+
+.mip-content-readmore .mip-readmore-fx:before {
+ border-top-color: #fff;
+ top: .45rem;
+}
\ No newline at end of file
diff --git a/mip-content-readmore/package.json b/mip-content-readmore/package.json
new file mode 100644
index 00000000..0c5453fa
--- /dev/null
+++ b/mip-content-readmore/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-content-readmore",
+ "version": "1.0.2",
+ "description": "查看更多文本内容",
+ "contributors": [
+ {
+ "name": "sora",
+ "email": "jfdsies@gmail.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-copy/README.md b/mip-copy/README.md
index f0282fe8..6c8053cb 100644
--- a/mip-copy/README.md
+++ b/mip-copy/README.md
@@ -6,7 +6,7 @@ mip-copy 康网点击换一换
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-copy/mip-copy.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-copy/mip-copy.js
## 示例
diff --git a/mip-count/README.md b/mip-count/README.md
index 69e51f2d..ef30f45d 100644
--- a/mip-count/README.md
+++ b/mip-count/README.md
@@ -6,7 +6,7 @@ mip-count 有来统计代码
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-count/mip-count.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-count/mip-count.js
## 示例
diff --git a/mip-cr173-addapp/README.md b/mip-cr173-addapp/README.md
index 7d063520..dbef0d8b 100644
--- a/mip-cr173-addapp/README.md
+++ b/mip-cr173-addapp/README.md
@@ -6,7 +6,7 @@ mip-cr173-addapp 组件,用来动态给页面添加内容,便于mip管理和
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-addapp/mip-cr173-addapp.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-addapp/mip-cr173-addapp.js
## 示例
### 动态给页面添加内容
diff --git a/mip-cr173-addrecomd/README.md b/mip-cr173-addrecomd/README.md
index bf006d15..cbed80ac 100644
--- a/mip-cr173-addrecomd/README.md
+++ b/mip-cr173-addrecomd/README.md
@@ -6,7 +6,7 @@ mip-cr173-addrecomd 添加推荐板块
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-addrecomd/mip-cr173-addrecomd.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-addrecomd/mip-cr173-addrecomd.js
## 示例
diff --git a/mip-cr173-bottomjump/README.md b/mip-cr173-bottomjump/README.md
index 96f2ecc6..63f84c47 100644
--- a/mip-cr173-bottomjump/README.md
+++ b/mip-cr173-bottomjump/README.md
@@ -6,7 +6,7 @@ mip-cr173-bottomjump
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-bottomjump/mip-cr173-bottomjump.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-bottomjump/mip-cr173-bottomjump.js
## 示例
### 基本用法
diff --git a/mip-cr173-cnzz/README.md b/mip-cr173-cnzz/README.md
index 3640014e..deeef1e7 100644
--- a/mip-cr173-cnzz/README.md
+++ b/mip-cr173-cnzz/README.md
@@ -6,7 +6,7 @@ mip-cr173-cnzz CNZZ统计
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-cnzz/mip-cr173-cnzz.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-cnzz/mip-cr173-cnzz.js
## 示例
### CNZZ统计
diff --git a/mip-cr173-comment/README.md b/mip-cr173-comment/README.md
index e8cd48b8..a7211a61 100644
--- a/mip-cr173-comment/README.md
+++ b/mip-cr173-comment/README.md
@@ -6,7 +6,7 @@ mip-cr173-comment 用来支持该程序下面的评论功能,对文章和下
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-comment/mip-cr173-comment.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-comment/mip-cr173-comment.js
## 示例
### 评论功能实现。
diff --git a/mip-cr173-conut/README.md b/mip-cr173-conut/README.md
new file mode 100644
index 00000000..8c38be68
--- /dev/null
+++ b/mip-cr173-conut/README.md
@@ -0,0 +1,18 @@
+# mip-cr173-count
+mip-cr173-conut 用来支持下载详情页统计功能
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-showtanceng/mip-cr173-conut.js
+
+## 示例
+
+```
+
+```
+
+# 属性
+
+组件涉及的属性字段: 统计功能
diff --git a/mip-cr173-conut/mip-cr173-conut.js b/mip-cr173-conut/mip-cr173-conut.js
new file mode 100644
index 00000000..be455356
--- /dev/null
+++ b/mip-cr173-conut/mip-cr173-conut.js
@@ -0,0 +1,177 @@
+/**
+ * @file 统计功能
+ * @author Zhang
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ function cr173Count() {
+ var webInfo = {
+ Username: $('.f-information').attr('data-Username'),
+ Type: $('.f-information').attr('data-Type'),
+ DateTime: $('.f-information').attr('data-DateTime'),
+ Id: $('.f-information').attr('data-id')
+ };
+ var src = 'https://count.612.com//index.php?m=r';
+ var charset = '&charset=' + getPageCharset();
+ var atime = '&atime=' + webInfo.DateTime;
+ var ref = '&ref=' + encodeURIComponent(document.referrer);
+ var url = '&url=' + encodeURIComponent(window.location.href);
+ var username = '&username=' + encodeURIComponent(webInfo.Username);
+ var type = '&type=' + webInfo.Type;
+ var rid = '&rid=' + webInfo.Id;
+ var platform = '&platform=2';
+ var content = '&content=' + encodeURIComponent(document.title);
+ if (compareDate(webInfo.DateTime, '2014/12/31')) {
+ var jsStrdate = src + charset + atime + ref + url + username + type + rid + platform + content;
+ document.write('');
+ var bjname = webInfo.Username;
+ var cnzzprotocol = (('https:' === document.location.protocol) ? ' https://' : ' http://');
+ var cnzzid;
+ var cnzzsite;
+ if (bjname !== '') {
+ switch (bjname) {
+ case 'lilei':
+ cnzzid = 1260145133, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'cr173zhuwei':
+ cnzzid = 1259254879, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'hy_cr173':
+ cnzzid = 1257189736, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'liquan':
+ cnzzid = 1259255002, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'zhongzai':
+ cnzzid = 1256652057, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'chenzhihua':
+ cnzzid = 1260137048, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'cr173_zjj':
+ cnzzid = 1256652045, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'huyakun':
+ cnzzid = 1256652041, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'zhangcongqing':
+ cnzzid = 1259255061, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'fanqiang':
+ cnzzid = 1259255027, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'wangzhuang':
+ cnzzid = 1256652029, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'zhangwen':
+ cnzzid = 1256652022, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'pengqi':
+ cnzzid = 1257189713, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'xudewen':
+ cnzzid = 1256652011, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'luowen':
+ cnzzid = 1256652007, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'zhongqiang':
+ cnzzid = 1256652001, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'shixing':
+ cnzzid = 1256651994, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'thza':
+ cnzzid = 1256651988, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'lixinlong':
+ cnzzid = 1260137114, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'zhangjia':
+ cnzzid = 1259257318, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'zhpc':
+ cnzzid = 1257189729, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'liuxiaocong':
+ cnzzid = 1260137076, cnzzsite = 's95.cnzz.com';
+ break;
+ case 'zhangyanan':
+ cnzzid = 1259255042, cnzzsite = 's95.cnzz.com';
+ break;
+ case 'oywx':
+ cnzzid = 1256651884, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'ganzhen':
+ cnzzid = 1256651837, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'tandan':
+ cnzzid = 1259255086, cnzzsite = 's95.cnzz.com';
+ break;
+ case 'xiaokaiyang':
+ cnzzid = 1259257431, cnzzsite = 's95.cnzz.com';
+ break;
+ case 'fangyu':
+ cnzzid = 1259257460, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'cr_wh':
+ cnzzid = 1261372691, cnzzsite = 's4.cnzz.com';
+ break;
+ case 'wujiaqi':
+ cnzzid = 1261372711, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'lujieming':
+ cnzzid = 1256651941, cnzzsite = 's11.cnzz.com';
+ break;
+ case 'huangkui':
+ cnzzid = 1260137150, cnzzsite = 's95.cnzz.com';
+ break;
+ }
+ if (typeof cnzzid === 'number' && typeof cnzzsite === 'string') {
+ var jsStr = '%3Cspan id=\'cnzz_stat_icon_';
+ jsStr += cnzzid + '\'%3E%3C/span%3E%3Cscript src=\'';
+ jsStr += cnzzprotocol;
+ jsStr += cnzzsite;
+ jsStr += '/stat.php%3Fid%3D';
+ jsStr += cnzzid;
+ jsStr += '%26show%3Dpic1\' type=\'text/javascript\'%3E%3C/script%3E';
+ document.write(unescape(jsStr));
+ }
+ }
+ }
+ }
+ function getPageCharset() {
+ var charSet = '';
+ var oType = getBrowser();
+ switch (oType) {
+ case 'IE':
+ charSet = document.charset;
+ break;
+ case 'FIREFOX':
+ charSet = document.characterSet;
+ break;
+ default:
+ break;
+ }
+ return charSet;
+ }
+ function getBrowser() {
+ var oType = '';
+ if (navigator.userAgent.indexOf('MSIE') !== -1) {
+ oType = 'IE';
+ }
+ else if (navigator.userAgent.indexOf('Firefox') !== -1) {
+ oType = 'FIREFOX';
+ }
+
+ return oType;
+ }
+ function compareDate(d1, d2) {
+ return ((new Date(d1.replace(/-/g, '\/'))) > (new Date(d2.replace(/-/g, '\/'))));
+ }
+ customElem.prototype.build = function () {
+ cr173Count();
+ };
+ return customElem;
+});
diff --git a/mip-cr173-conut/package.json b/mip-cr173-conut/package.json
new file mode 100644
index 00000000..60b10505
--- /dev/null
+++ b/mip-cr173-conut/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "mip-cr173-conut",
+ "version": "1.0.0",
+ "description": "uesr count",
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-cr173-downthe/README.md b/mip-cr173-downthe/README.md
index 2feaa4d7..bb1f8b95 100644
--- a/mip-cr173-downthe/README.md
+++ b/mip-cr173-downthe/README.md
@@ -6,7 +6,7 @@ mip-cr173-downthe .搜索框的跳转到站内搜索功能。没有下载地址
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-downthe/mip-cr173-downthe.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-downthe/mip-cr173-downthe.js
## 示例
```html
diff --git a/mip-cr173-eject/README.md b/mip-cr173-eject/README.md
index 9b025bcf..90453de0 100644
--- a/mip-cr173-eject/README.md
+++ b/mip-cr173-eject/README.md
@@ -6,7 +6,7 @@ mip-cr173-eject 点击下载按钮弹出推荐内容,IP过滤功能,fetchJsonp
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-eject/mip-cr173-eject.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-eject/mip-cr173-eject.js
## 示例
diff --git a/mip-cr173-key/README.md b/mip-cr173-key/README.md
index 44aeb761..32e8b6f6 100644
--- a/mip-cr173-key/README.md
+++ b/mip-cr173-key/README.md
@@ -6,7 +6,7 @@ mip-cr173-key 组件功能:滚动显示简介,点击显示简介。默认显
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-key/mip-cr173-key.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-key/mip-cr173-key.js
## 示例
```html
diff --git a/mip-cr173-mg/README.md b/mip-cr173-mg/README.md
index 482e02db..2c73c050 100644
--- a/mip-cr173-mg/README.md
+++ b/mip-cr173-mg/README.md
@@ -6,7 +6,7 @@ mip-cr173-mg 词语过滤功能,去掉导致报错的争议代码,部分li下面
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-mg/mip-cr173-mg.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-mg/mip-cr173-mg.js
## 示例
diff --git a/mip-cr173-popup/README.md b/mip-cr173-popup/README.md
index 2692096a..5c8bb4dc 100644
--- a/mip-cr173-popup/README.md
+++ b/mip-cr173-popup/README.md
@@ -6,7 +6,7 @@ mip-cr173-popup 点击下载按钮弹出推荐内容,IP过滤功能,并没有引
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cr173-popup/mip-cr173-popup.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cr173-popup/mip-cr173-popup.js
## 示例
diff --git a/mip-cr173-ppzs/README.md b/mip-cr173-ppzs/README.md
new file mode 100644
index 00000000..4f62929d
--- /dev/null
+++ b/mip-cr173-ppzs/README.md
@@ -0,0 +1,14 @@
+# mip-cr173-ppzs
+mip-cr173-ppzs 显示高速下载,点击下载弹出显示相应推荐内容。根据设备展示相应内容
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-showtanceng/mip-cr173-ppzs.js
+
+## 示例
+
+```
+
+```
diff --git a/mip-cr173-ppzs/mip-cr173-ppzs.js b/mip-cr173-ppzs/mip-cr173-ppzs.js
new file mode 100644
index 00000000..aa67e712
--- /dev/null
+++ b/mip-cr173-ppzs/mip-cr173-ppzs.js
@@ -0,0 +1,159 @@
+/**
+ * @file 高速下载,显示相应内容
+ * @author Zhang
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var browser = {
+ versions: (function () {
+ var u = navigator.userAgent;
+ return {
+ ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
+ android: u.indexOf('Android') > -1, // android终端或者uc浏览器
+ iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, // 是否为iPhone或者QQHD浏览器
+ iPad: u.indexOf('iPad') > -1, // 是否iPad
+ ios9: u.indexOf('iPhone OS 9') > -1,
+ MQQBrowser: u.indexOf('MQQBrowser') > -1, // 是否MQQBrowser
+ UCBrowser: u.indexOf('UCBrowser') > -1, // UCBrowser
+ Safari: u.indexOf('Safari') > -1
+ };
+ })(),
+ language: (navigator.browserLanguage || navigator.language).toLowerCase()
+ };
+ var pageInfo = {
+ id: $('.f-information').attr('data-id'),
+ path: $('.f-information').attr('data-path'),
+ categroyId: Math.ceil($('.f-information').attr('data-categroyId')),
+ rootId: $('.f-information').attr('data-rootid'),
+ commendid: $('.f-information').attr('data-commendid'),
+ system: $('.f-information').attr('data-system'),
+ ppaddress: $('.f-information').attr('data-ppaddress'),
+ ismoney: $('.f-information').attr('data-ismoney')
+ };
+ var downFunction = {
+ getScript: function () {
+ var getScript = function (url, callback) {
+ var head = document.getElementsByTagName('head')[0];
+ var js = document.createElement('script');
+ js.setAttribute('type', 'text/javascript');
+ js.setAttribute('src', url);
+ head.appendChild(js);
+ var callbackFn = function () {
+ if (typeof callback === 'function') {
+ callback();
+ }
+
+ };
+ if (document.all) {
+ js.onreadystatechange = function () {
+ if (js.readyState === 'loaded' || js.readyState === 'complete') {
+ callbackFn();
+ }
+
+ };
+ }
+ else {
+ js.onload = function () {
+ callbackFn();
+ };
+ }
+ };
+ if (Zepto) {
+ $.getScript = getScript;
+ }
+
+ },
+ tanCentShow: function () {
+ var catearr = [151, 156, 158, 159, 160, 161, 162, 163, 164,
+ 256, 257, 258, 178, 179, 180, 181, 182, 183, 184, 185, 186, 207, 208,
+ 81, 209, 210, 211, 212, 218, 219, 220, 221, 222, 223, 224, 225, 226, 230,
+ 237, 238, 239, 240, 241, 308, 309, 310, 311, 328, 322, 323, 324, 325, 326, 329]; // 安卓分类
+ var catearrIos = [141, 214, 215, 216, 227, 228, 229, 231, 232, 233, 234,
+ 235, 312, 313, 314, 315, 316, 317, 318, 319, 327, 330]; // ios分类
+ var AppArray = [435, 368]; // 应用宝的id数
+ var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z'];
+ function generateMixed(n) {
+ var res = '';
+ for (var i = 0; i < n; i++) {
+ var id = Math.ceil(Math.random() * 35);
+ res += chars[id];
+ }
+ return res;
+ }
+ var webUrl = ['L5645.net', 'L5645.com', 'i8543.net', 'i8543.com', 'u7897.net',
+ 'u7897.com', 'w2546.net', 'w2546.com', 'a2353.net', 'a2353.com', 'q58723.net', 'q58723.com'];
+ var AppID = AppArray[Math.floor(Math.random() * (AppArray.length))];
+ var downDomain = webUrl[Math.floor(Math.random() * (webUrl.length))];
+ var downUrl = 'http://' + generateMixed(2) + '.' + downDomain + '/' + generateMixed(6) + AppID + generateMixed(3) + '/setup.apk';
+ var myazdownLoad = [];
+ myazdownLoad.push('http://' + generateMixed(2) + '.' + downDomain + '/' + generateMixed(6) + '888' + generateMixed(3) + '/setup.apk');
+ myazdownLoad.push('http://' + generateMixed(2) + '.' + downDomain + '/' + generateMixed(6) + '386' + generateMixed(3) + '/setup.apk');
+ var isAds = false;
+ var downHref = $('.m-down-ul li a').attr('href');
+ var noAd = ['6071.com', '1030.apk', 'duokoo.baidu.com', 'ugame.uc.cn', 'ugame.9game.cn', '360.cn', 'ewan.cn', 'anfan.com', 'caohua.com', 'open.play.cn', 'tj.tt1386.com', 'http://g.', 'http://tj.', 'yiwan.com', 'x1.241804.com', 'moban.com', 's.qq.com', '456.com.cn', 'xinkuai.com', 'g.hgame.com', 'yxgames.com', 'qianghongbaoyo.com', 'down1.qianghongbaoyo.com', 'down2.guopan.cn', 'dl.guopan.cn', 'guopan.cn', 'duowan.com'];
+ var i = 0;
+ for (i = 0; i < noAd.length; i++) {
+ if (downHref.indexOf(noAd[i]) > -1) {
+ isAds = true;
+ }
+
+ }
+ if (pageInfo.ismoney === 1) {
+ isAds = true;
+ }
+
+ var RefUrl = document.referrer;
+ var showAdsRef = ['baidu.com', 'sm.cn', 'sogou.com', 'so.com', 'google.com', 'bing.com', 'www.cr173.com', 'http://cr173.com'];
+ var isShowPicAds = '';
+ isShowPicAds = $.inArray(RefUrl, showAdsRef);
+ if (!browser.versions.ios) {
+ var idArray = [];
+ var downHref = $('.m-down-ul li a').attr('href');
+ idArray = downHref.split('.');
+ if (downHref.indexOf('mo.L5645.net') !== -1 && $('.g-tags-box ul li').length <= 0) {
+ $('.m-down-ul li a').attr('href', '/down.asp?id=' + idArray[4]);
+ $('.m-down-msg .type b:last').html('系统:Android');
+ }
+ else {
+ if ($.inArray(pageInfo.categroyId, catearr) === -1 && $('.g-tags-box ul li').length <= 0) {
+ $('.m-down-ul li a').attr({href: 'javascript:;', ispc: true});
+ }
+ else {
+ $('.m-down-ul li a').attr('issw', true);
+ }
+ }
+ if (!isAds) {
+ addhighLab();
+ }
+ }
+ else {
+ if ($.inArray(pageInfo.categroyId, catearrIos) === -1 && $('.g-tags-box ul li').length <= 0) {
+ $('.m-down-ul li a').attr({href: 'javascript:;', ispc: true});
+ }
+ else {
+ $('.m-down-ul li a').attr('issw', true);
+ }
+ if (!isAds) {
+ iossoftAdd();
+ }
+ }
+ function addhighLab() {
+ $.getScript('https://ca.6071.com/?id=cr1731002333_utf8', function () {});
+ }
+ function iossoftAdd() {
+ $.getScript('https://ca.6071.com/?id=cr17310023331_utf8', function () {});
+ }
+ },
+ init: function () {
+ this.getScript(); // getScript插件
+ this.tanCentShow(); // 点击下载
+ }
+ };
+ customElem.prototype.build = function () {
+ downFunction.init();
+ };
+ return customElem;
+});
diff --git a/mip-cr173-ppzs/package.json b/mip-cr173-ppzs/package.json
new file mode 100644
index 00000000..657c4e87
--- /dev/null
+++ b/mip-cr173-ppzs/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-cr173-ppzs",
+ "version": "1.0.1",
+ "description": "show ppzs and show content",
+ "author": {
+ "name": "Zhang",
+ "url": "https://www.cr173.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-cr173-tags/README.md b/mip-cr173-tags/README.md
new file mode 100644
index 00000000..ebc8c79d
--- /dev/null
+++ b/mip-cr173-tags/README.md
@@ -0,0 +1,41 @@
+# mip-cr173-tags
+mip-cr173-tags 用来支持下载详情页显示tags内容
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-showtanceng/mip-cr173-tags.js
+
+## 示例
+
+```
+
+
+
+```
+
+# 属性
+
+组件涉及的属性字段: 显示tags内容
+
diff --git a/mip-cr173-tags/mip-cr173-tags.js b/mip-cr173-tags/mip-cr173-tags.js
new file mode 100644
index 00000000..d7c949e7
--- /dev/null
+++ b/mip-cr173-tags/mip-cr173-tags.js
@@ -0,0 +1,94 @@
+/**
+ * @file tags适配
+ * @author Zhang
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var browser = {
+ versions: (function () {
+ var u = navigator.userAgent;
+ return {
+ ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), // ios终端
+ android: u.indexOf('Android') > -1, // android终端或者uc浏览器
+ iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, // 是否为iPhone或者QQHD浏览器
+ iPad: u.indexOf('iPad') > -1, // 是否iPad
+ ios9: u.indexOf('iPhone OS 9') > -1,
+ MQQBrowser: u.indexOf('MQQBrowser') > -1, // 是否MQQBrowser
+ UCBrowser: u.indexOf('UCBrowser') > -1, // UCBrowser
+ Safari: u.indexOf('Safari') > -1
+ };
+ })(),
+ language: (navigator.browserLanguage || navigator.language).toLowerCase()
+ };
+ var pageInfo = {
+ id: $('.f-information').attr('data-id'),
+ path: $('.f-information').attr('data-path'),
+ categroyId: $('.f-information').attr('data-categroyId'),
+ rootId: $('.f-information').attr('data-rootid'),
+ commendid: $('.f-information').attr('data-commendid'),
+ system: $('.f-information').attr('data-system').toUpperCase(),
+ ppaddress: $('.f-information').attr('data-ppaddress'),
+ ismoney: $('.f-information').attr('data-ismoney')};
+ function tagsChoose() {
+ if ($('.g-tags-box').length > 0) {
+ if (browser.versions.ios) {
+ if ($('.g-tags-box .m-tags-ios li').length > 0) {
+ addTags($('.g-tags-box .m-tags-ios').html(),
+ $('.g-tags-box .m-tags-ios li').first().attr('data-system'),
+ $('.g-tags-box .m-tags-ios li').first().attr('data-id'),
+ $('.g-tags-box .m-tags-ios li a p').first().text(), 'IOS');
+ } else {
+ $('.g-tags-box').remove();
+ }
+ } else {
+ if ($('.g-tags-box .m-tags-android li').length > 0) {
+ addTags($('.g-tags-box .m-tags-android').html(),
+ $('.g-tags-box .m-tags-android li').first().attr('data-system'),
+ $('.g-tags-box .m-tags-android li').first().attr('data-id'),
+ $('.g-tags-box .m-tags-android li a p').first().text(), 'ANDROID');
+ } else {
+ $('.g-tags-box').remove();
+ }
+ }
+ } else {
+ $('.g-tags-box').remove();
+ }
+ }
+ function addTags(tagsHtml, firstSystem, firstId, firstName, systemName) {
+ tagsHtml = '';
+ $('.g-tags-box').remove();
+ $('.g-game-msg').after(tagsHtml);
+ $('.g-tags-box').show();
+ if (pageInfo.system.indexOf(systemName) === -1) {
+ $('.m-down-msg .info .pic ul li b').each(function () {
+ var systemText = $(this).text();
+ if (systemText.indexOf('系统:') !== -1) {
+ $(this).text('系统:' + firstSystem);
+ }
+ });
+ var urlArray = ['cr173.com', 'qqtn.com', 'fxxz.com', '5577.com', 'uzzf.com', 'skycn.com', '962.net'];
+ var windowUrl = window.location.href;
+ var i = 0;
+ for (i = 0; i < urlArray.length; i++) {
+ if (windowUrl.indexOf(urlArray[i]) !== -1) {
+
+ $('.m-down-ul li a').attr('href', 'http://m.' + urlArray[i] + '/down.asp?id=' + firstId).attr('data-add', 'add');
+ }
+ }
+ }
+ if ($('.g-tags-box ul li').length <= 0) {
+ $('.g-tags-box').hide();
+ }
+ $('.g-tags-box ul li a p').each(function () {
+ var liText = $(this).text();
+ var re = /(官方最新版|官网最新版|官方正式版|官方安卓版|官方版|日服版|九游版|最新版|360版|百度版|uc版|九游版|安峰版|草花版|益玩版|破解版|修改版|无限金币版|中文版)/;
+ liText = liText.replace(re, '$1');
+ $(this).html(liText);
+ });
+ }
+ customElem.prototype.build = function () {
+ tagsChoose();
+ };
+ return customElem;
+});
diff --git a/mip-cr173-tags/package.json b/mip-cr173-tags/package.json
new file mode 100644
index 00000000..eac74986
--- /dev/null
+++ b/mip-cr173-tags/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-cr173-tags",
+ "version": "1.0.0",
+ "description": "show tags",
+ "author": {
+ "name": "Zhang",
+ "url": "https://www.cr173.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-cs-meiqia/README.md b/mip-cs-meiqia/README.md
index 8850cd30..895e7d1e 100644
--- a/mip-cs-meiqia/README.md
+++ b/mip-cs-meiqia/README.md
@@ -6,7 +6,7 @@ mip-cs-meiqia 组件说明:美洽在线客服组件——美洽是一个流行
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cs-meiqia/mip-cs-meiqia.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cs-meiqia/mip-cs-meiqia.js
## 示例
diff --git a/mip-custom-passport-365caidashi/README.md b/mip-custom-passport-365caidashi/README.md
index 2e9753ed..eb89efcb 100644
--- a/mip-custom-passport-365caidashi/README.md
+++ b/mip-custom-passport-365caidashi/README.md
@@ -6,7 +6,7 @@ mip-custom-passport-365caidashi 财大师专属 360统计组件。
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-custom-passport-365caidashi/mip-custom-passport-365caidashi.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-custom-passport-365caidashi/mip-custom-passport-365caidashi.js
## 示例
diff --git a/mip-cy-audio/README.md b/mip-cy-audio/README.md
index d1b4190a..3dcb6690 100644
--- a/mip-cy-audio/README.md
+++ b/mip-cy-audio/README.md
@@ -6,7 +6,7 @@
----|----
类型|音频播放组件
支持布局| N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-audio/mip-cy-audio.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-audio/mip-cy-audio.js
## 示例
diff --git a/mip-cy-fine-qa/README.md b/mip-cy-fine-qa/README.md
index 99510e7f..262b4a8d 100644
--- a/mip-cy-fine-qa/README.md
+++ b/mip-cy-fine-qa/README.md
@@ -6,7 +6,7 @@ mip-cy-fine-qa 组件获取医生的优质qa
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-fine-qa/mip-cy-fine-qa.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-fine-qa/mip-cy-fine-qa.js
## 示例
diff --git a/mip-cy-list/README.md b/mip-cy-list/README.md
index 44ddfc5d..1127173a 100644
--- a/mip-cy-list/README.md
+++ b/mip-cy-list/README.md
@@ -6,7 +6,7 @@ mip-cy-list 组件是对百度mip-list扩展,以符合春雨的需求~
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-list/mip-cy-list.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-list/mip-cy-list.js
## 示例
diff --git a/mip-cy-pay-button/README.md b/mip-cy-pay-button/README.md
index 39e8cbf2..417490fc 100644
--- a/mip-cy-pay-button/README.md
+++ b/mip-cy-pay-button/README.md
@@ -6,7 +6,7 @@ mip-cy-pay-button 组件用于创建订单并进行百度支付
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-pay-button/mip-cy-pay-button.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-pay-button/mip-cy-pay-button.js
## 示例
diff --git a/mip-cy-quick-question/README.md b/mip-cy-quick-question/README.md
index 629b2cf7..db080d73 100644
--- a/mip-cy-quick-question/README.md
+++ b/mip-cy-quick-question/README.md
@@ -6,7 +6,7 @@ mip-cy-quick-question 春雨快速提问组件
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-quick-question/mip-cy-quick-question.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-quick-question/mip-cy-quick-question.js
## 示例
diff --git a/mip-cy-root/README.md b/mip-cy-root/README.md
index edf7040d..024c5997 100644
--- a/mip-cy-root/README.md
+++ b/mip-cy-root/README.md
@@ -6,7 +6,7 @@ mip-cy-root组件目的是支持春雨可伸缩布局方案~
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-root/mip-cy-root.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-root/mip-cy-root.js
## 示例
diff --git a/mip-cy-script/README.md b/mip-cy-script/README.md
index 700e0330..a8e8dbdc 100644
--- a/mip-cy-script/README.md
+++ b/mip-cy-script/README.md
@@ -6,7 +6,7 @@ mip-cy-script 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-___/mip-___.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-___/mip-___.js
## 示例
diff --git a/mip-cy-search-bar/README.md b/mip-cy-search-bar/README.md
index 36cd5908..249007eb 100644
--- a/mip-cy-search-bar/README.md
+++ b/mip-cy-search-bar/README.md
@@ -6,7 +6,7 @@ mip-cy-search-bar组件是春雨搜索bar
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-search-bar/mip-cy-search-bar.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-search-bar/mip-cy-search-bar.js
## 示例
diff --git a/mip-cy-search-doc-tab/README.md b/mip-cy-search-doc-tab/README.md
index c4cc3ce8..f9f42bf2 100644
--- a/mip-cy-search-doc-tab/README.md
+++ b/mip-cy-search-doc-tab/README.md
@@ -6,7 +6,7 @@ mip-cy-search-doc-tab组件是个性化组件,通用性不高~
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-search-doc-tab/mip-cy-search-doc-tab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-search-doc-tab/mip-cy-search-doc-tab.js
## 示例
diff --git a/mip-cy-upgrade-service/README.md b/mip-cy-upgrade-service/README.md
index 6eb91e26..53f85608 100644
--- a/mip-cy-upgrade-service/README.md
+++ b/mip-cy-upgrade-service/README.md
@@ -6,7 +6,7 @@ mip-cy-upgrade-service 春雨升级qa服务组件
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-cy-upgrade-service/mip-cy-upgrade-service.js
https://c.mipcdn.com/extensions/platform/v1/mip-cy-pay-button/mip-cy-pay-button.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-cy-upgrade-service/mip-cy-upgrade-service.js
https://c.mipcdn.com/static/v1/mip-cy-pay-button/mip-cy-pay-button.js
## 示例
diff --git a/mip-czsy-nav/README.md b/mip-czsy-nav/README.md
index 8965e615..d14a0d38 100644
--- a/mip-czsy-nav/README.md
+++ b/mip-czsy-nav/README.md
@@ -6,7 +6,7 @@ mip-czsy-nav 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-czsy-nav/mip-czsy-nav.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-czsy-nav/mip-czsy-nav.js
## 示例
### 基本用法
diff --git a/mip-dad-appdownload/README.md b/mip-dad-appdownload/README.md
new file mode 100644
index 00000000..957087b4
--- /dev/null
+++ b/mip-dad-appdownload/README.md
@@ -0,0 +1,61 @@
+# mip-dad-appdownload
+
+mip-dad-appdownload app下载组件,在皮皮助手高速下载和直接下载之间切换!
+
+标题|内容
+----|----
+类型|通用
+所需脚本|https://c.mipcdn.com/static/v1/mip-dad-appdownload/mip-dad-appdownload.js
+
+## 示例
+
+### app下载组件,在皮皮助手高速下载和直接下载之间切换!
+```html
+
+```
+
+## 属性
+
+### {属性名}
+
+说明:{说明}
+必选项:{是|否}
+类型:{类型}
+取值范围:{取值范围}
+单位:{单位}
+默认值:{默认值}
+
+
+### ad
+
+说明:是否启用皮皮助手高速下载
+必选项:是
+类型:数字
+取值范围:0,1
+单位:无
+默认值:无
+
+### aid
+
+说明:对应的应用id
+必选项:是
+类型:字符串
+取值范围:数字或者其他
+单位:无
+默认值:无
+
+### addr
+
+说明:应用的下载地址
+必选项:是
+类型:字符串
+取值范围:空或者字符串
+单位:无
+默认值:无
+
+## 注意事项
+暂无
+
+
+
+
diff --git a/mip-dad-appdownload/mip-dad-appdownload.js b/mip-dad-appdownload/mip-dad-appdownload.js
new file mode 100644
index 00000000..39c95b35
--- /dev/null
+++ b/mip-dad-appdownload/mip-dad-appdownload.js
@@ -0,0 +1,81 @@
+/**
+ * @file mip-dad-appdownload 手机爸爸的app下载切换效果
+ * @author pifire
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var util = require('util');
+ var platform = util.platform;
+ var customElement = require('customElement').create();
+
+ /**
+ * 构造元素,只会运行一次
+ */
+ customElement.prototype.firstInviewCallback = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ var element = this.element;
+ var $element = $(element);
+ var ad = $element.attr('ad');
+ var aid = $element.attr('aid');
+ var addr = $element.attr('addr');
+ var wdjDN = 'http://dl.wandoujia.com/files/jupiter/latest/wandoujia-fangbei8_ad.apk';
+ var text1 = '\u4f7f\u7528\u8c4c\u8c46\u835a\u5b89\u88c5';
+ var text2 = '\u8c4c\u8c46\u835a\u662f\u5168\u9762\u3001\u4e13\u4e1a\u7684'
+ + '\u5e94\u7528\u5e02\u573a\uff0c\u5c06\u4e3a\u60a8\u5b89\u88c5\u8c4c\u8c46\u835a'
+ + '\uff0c\u542f\u52a8\u9ad8\u901f\u5f15\u64ce\uff0c\u5b89\u5168\u65e0\u6bd2\u3001'
+ + '\u6781\u901f\u4e0b\u8f7d\u5e94\u7528\uff01';
+ var text3 = '\u4f7f\u7528\u666e\u901a\u4e0b\u8f7d\u65e0\u6cd5\u907f\u514d'
+ + '\u6d41\u91cf\u52ab\u6301\u3001\u4e0b\u8f7d\u8f83\u6162\u7b49\u95ee\u9898\uff0c\u5efa'
+ + '\u8bae\u9009\u62e9\u8c4c\u8c46\u835a\u5b89\u88c5\u9ad8\u901f\u4e0b\u8f7d\uff01';
+ var innerHTML = '';
+ var trueurl;
+ if (platform.isIos()) {
+ innerHTML = '\u7acb\u5373\u4e0b\u8f7d';
+ }
+ else {
+ if (ad > 0) {
+ innerHTML = '' + text1 + ''
+ + '\u9ad8\u901f\u4e0b\u8f7d'
+ + '' + text2 + '
'
+ + '' + text3 + '';
+ if (addr.length === 0) {
+ trueurl = 'http://www.mobile-dad.com/tourl.php?apkid=' + $element.attr('aid');
+ }
+ else {
+ trueurl = addr;
+ }
+ }
+ else {
+ if (addr.length === 0) {
+ innerHTML = '\u7acb\u5373\u4e0b\u8f7d';
+ }
+ else {
+ innerHTML = '\u7acb\u5373\u4e0b\u8f7d';
+
+ }
+ }
+ }
+ $element.html(innerHTML);
+ var flag = 1;
+ $element.on('click', 'i', function () {
+ if (flag === 1) {
+ $element.addClass('no');
+ $element.find('a').text('\u666e\u901a\u4e0b\u8f7d');
+ $element.find('a').attr('href', trueurl);
+ $element.find('p').attr('style', 'display:none');
+ $element.find('u').attr('style', 'display:block');
+ flag = 0;
+ }
+ else {
+ $element.removeClass('no');
+ $element.find('a').text('\u9ad8\u901f\u4e0b\u8f7d');
+ $element.find('a').attr('href', wdjDN);
+ $element.find('p').attr('style', 'display:block');
+ $element.find('u').attr('style', 'display:none');
+ flag = 1;
+ }
+ });
+ };
+ return customElement;
+});
diff --git a/mip-dad-appdownload/package.json b/mip-dad-appdownload/package.json
new file mode 100644
index 00000000..52f6cf64
--- /dev/null
+++ b/mip-dad-appdownload/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-dad-appdownload",
+ "version": "1.0.0",
+ "description": "app下载组件,在皮皮助手高速下载和直接下载之间切换!",
+ "contributors": [
+ {
+ "name": "pifire",
+ "email": "104460712@qq.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-dftt-pageNewsList/README.md b/mip-dftt-pageNewsList/README.md
index 42442857..4ec8f630 100644
--- a/mip-dftt-pageNewsList/README.md
+++ b/mip-dftt-pageNewsList/README.md
@@ -6,7 +6,7 @@ mip-dftt-pageNewsList 东方头条新闻页面信息流组件
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-dftt-pageNewsList/mip-dftt-pageNewsList.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-dftt-pageNewsList/mip-dftt-pageNewsList.js
## 示例
diff --git a/mip-discuz-login/README.md b/mip-discuz-login/README.md
new file mode 100644
index 00000000..99cf0a94
--- /dev/null
+++ b/mip-discuz-login/README.md
@@ -0,0 +1,40 @@
+# mip-discuz-login
+
+mip-discuz-login discuz手机页面登陆功能组件
+
+标题|内容
+----|----
+类型|通用
+所需脚本|https://c.mipcdn.com/static/v1/mip-discuz-login/mip-discuz-login.js
+
+## 示例
+
+### 引用自定义function
+
+- 自定义function, script 标签中的参数必须带双引号,也就是说 script 标签中的数据必须是 json 格式的
+- json 格式中的key 为自定义函数名,value为自定义函数参数
+```html
+
+
+
+
+## 属性
+
+### staticurl
+说明:静态资源存储路径
+必选项:是
+类型:字符串
+
+### imgdir
+说明:图片资源存储路径
+必选项:是
+类型:字符串
+
+### siteurl
+说明:网站url
+必选项:是
+类型:字符串
\ No newline at end of file
diff --git a/mip-discuz-login/mip-discuz-login.js b/mip-discuz-login/mip-discuz-login.js
new file mode 100644
index 00000000..633fa75b
--- /dev/null
+++ b/mip-discuz-login/mip-discuz-login.js
@@ -0,0 +1,732 @@
+/**
+ * @file mip-discuz-login discuz发布组件
+ * @author 104460712@qq.com
+ * @version 1.1
+ */
+
+define(function (require) {
+ var $ = require('jquery');
+ var util = require('util');
+ var platform = util.platform;
+ var JSLOADED = [];
+ var customElement = require('customElement').create();
+ customElement.prototype.firstInviewCallback = function () {
+ var element = this.element;
+ var STATICURL = element.getAttribute('staticurl');
+ var IMGDIR = element.getAttribute('imgdir');
+ var SITEURL = element.getAttribute('siteurl');
+ var script = element.querySelector('script[type="application/json"]') || null;
+ if (script) {
+ var obj = JSON.parse(script.textContent.toString());
+ var fnparams = '';
+ var fnstring = '';
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ fnstring = key;
+ fnparams = obj[key];
+ }
+ }
+ switch (fnstring) {
+ case 'seccheck':
+ seccheck(fnparams);
+ break;
+ case 'urlforward':
+ urlforward(fnparams);
+ break;
+ case 'getvisitclienthref1':
+ getvisitclienthref1(fnparams);
+ break;
+ case 'getvisitclienthref2':
+ getvisitclienthref2(fnparams);
+ break;
+ case 'fastpostformsubmit':
+ fastpostformsubmit(fnparams);
+ break;
+ case 'subforum':
+ subforum(fnparams);
+ break;
+ }
+ }
+ var page = {
+ converthtml: function () {
+ var prevpage = $('div.pg .prev').prop('href');
+ var nextpage = $('div.pg .nxt').prop('href');
+ var lastpage = $('div.pg label span').text().replace(/[^\d]/g, '') || 0;
+ var curpage = $('div.pg input').val() || 1;
+ if (!lastpage) {
+ prevpage = $('div.pg .pgb a').prop('href');
+ }
+ var prevpagehref = '';
+ var nextpagehref = '';
+ if (prevpage === undefined) {
+ prevpagehref = '#" class="grey';
+ }
+ else {
+ prevpagehref = prevpage;
+ }
+ if (nextpage === undefined) {
+ nextpagehref = '#" class="grey';
+ }
+ else {
+ nextpagehref = nextpage;
+ }
+ var selector = '';
+ if (lastpage) {
+ selector += '';
+ selector += '第' + curpage + '页';
+ }
+ $('div.pg').removeClass('pg').addClass('page').html('上一页'
+ + selector + '下一页');
+ $('#dumppage').on('change', function () {
+ var href = (prevpage || nextpage);
+ window.location.href = href.replace(/page=\d+/, 'page=' + $(this).val());
+ });
+ }
+ };
+ var scrolltop = {
+ obj: null,
+ init: function (obj) {
+ scrolltop.obj = obj;
+ var fixed = this.isfixed();
+ obj.css('opacity', '.618');
+ if (fixed) {
+ obj.css('bottom', '8px');
+ }
+ else {
+ obj.css({visibility: 'visible', position: 'absolute'});
+ }
+ $(window).on('resize', function () {
+ if (fixed) {
+ obj.css('bottom', '8px');
+ }
+ else {
+ obj.css('top', ($(document).scrollTop() + $(window).height() - 40) + 'px');
+ }
+ });
+ obj.on('tap', function () {
+ $(document).scrollTop($(document).height());
+ });
+ $(document).on('scroll', function () {
+ if (!fixed) {
+ obj.css('top', ($(document).scrollTop() + $(window).height() - 40) + 'px');
+ }
+ if ($(document).scrollTop() >= 400) {
+ obj.removeClass('bottom')
+ .off().on('tap', function () {
+ window.scrollTo('0', '1');
+ });
+ }
+ else {
+ obj.addClass('bottom')
+ .off().on('tap', function () {
+ $(document).scrollTop($(document).height());
+ });
+ }
+ });
+
+ },
+ isfixed: function () {
+ var offset = scrolltop.obj.offset();
+ var scrollTop = $(window).scrollTop();
+ var screenHeight = document.documentElement.clientHeight;
+ if (offset === undefined) {
+ return false;
+ }
+ if (offset.top < scrollTop || (offset.top - scrollTop) > screenHeight) {
+ return false;
+ }
+ return true;
+ }
+ };
+ var img = {
+ init: function (iserrt) {
+ var errhandle = this.errorhandle;
+ $('img').on('load', function () {
+ var obj = $(this);
+ obj.attr('zsrc', obj.attr('src'));
+ if (obj.width() < 5 && obj.height() < 10 && obj.css('display') !== 'none') {
+ return errhandle(obj, iserrt);
+ }
+ obj.css('display', 'inline');
+ obj.css('visibility', 'visible');
+ if (obj.width() > window.innerWidth) {
+ obj.css('width', window.innerWidth);
+ }
+ obj.parent().find('.loading').remove();
+ obj.parent().find('.error_text').remove();
+ })
+ .on('error', function () {
+ var obj = $(this);
+ obj.attr('zsrc', obj.attr('src'));
+ errhandle(obj, iserrt);
+ });
+ },
+ errorhandle: function (obj, iserrt) {
+ if (obj.attr('noerror') === 'true') {
+ return;
+ }
+ obj.css('visibility', 'hidden');
+ obj.css('display', 'none');
+ var parentnode = obj.parent();
+ parentnode.find('.loading').remove();
+ parentnode.append('');
+ var loadnums = parseInt(obj.attr('load'), 0) || 0;
+ if (loadnums < 3) {
+ obj.attr('src', obj.attr('zsrc'));
+ obj.attr('load', ++loadnums);
+ return false;
+ }
+ if (iserrt) {
+ parentnode.find('.loading').remove();
+ parentnode.append('点击重新加载
');
+ parentnode.find('.error_text').one('click', function () {
+ obj.attr('load', 0).find('.error_text').remove();
+ parentnode.append('');
+ obj.attr('src', obj.attr('zsrc'));
+ });
+ }
+ return false;
+ }
+ };
+ var atap = {
+ init: function () {
+ $('.atap').on('tap', function () {
+ var obj = $(this);
+ obj.css({'background': '#6FACD5', 'color': '#FFFFFF', 'font-weight': 'bold',
+ 'text-decoration': 'none', 'text-shadow': '0 1px 1px #3373A5'});
+ return false;
+ });
+ $('.atap a').off('click');
+ }
+ };
+ var POPMENU = {};
+ var popup = {
+ init: function () {
+ var $this = this;
+ $('.popup').each(function (index, obj) {
+ obj = $(obj);
+ var pop = $(obj.attr('href'));
+ if (pop && pop.attr('popup')) {
+ pop.css({display: 'none'});
+ obj.on('click', function (e) {
+ $this.open(pop);
+ });
+ }
+ });
+ this.maskinit();
+ },
+ maskinit: function () {
+ var $this = this;
+ $('#mask').off().on('tap', function () {
+ $this.close();
+ });
+ },
+
+ open: function (pop, type, url) {
+ this.close();
+ this.maskinit();
+ if (typeof pop === 'string') {
+ $('#ntcmsg').remove();
+ if (type === 'alert') {
+ pop = '' + pop
+ + '';
+ }
+ else if (type === 'confirm') {
+ pop = '' + pop
+ + '';
+ }
+ $('body').append('' + pop + '
');
+ pop = $('#ntcmsg');
+ }
+ if (POPMENU[pop.attr('id')]) {
+ $('#' + pop.attr('id') + '_popmenu').html(pop.html()).css({height: pop.height()
+ + 'px', width: pop.width() + 'px'});
+ }
+ else {
+ pop.parent().append('');
+ }
+ var popupobj = $('#' + pop.attr('id') + '_popmenu');
+ var left = (window.innerWidth - popupobj.width()) / 2;
+ var top = (document.documentElement.clientHeight - popupobj.height()) / 2;
+ popupobj.css({'display': 'block', 'position': 'fixed', 'left': left,
+ 'top': top, 'z-index': 120, 'opacity': 1});
+ $('#mask').css({'display': 'block', 'width': '100%', 'height': '100%',
+ 'position': 'fixed', 'top': '0', 'left': '0', 'background': 'black',
+ 'opacity': '0.2', 'z-index': '100'});
+ POPMENU[pop.attr('id')] = pop;
+ },
+ close: function () {
+ $('#mask').css('display', 'none');
+ $.each(POPMENU, function (index, obj) {
+ $('#' + index + '_popmenu').css('display', 'none');
+ });
+ }
+ };
+ var dialog = {
+ init: function () {
+ $(document).on('click', '.dialog', function () {
+ var obj = $(this);
+ popup.open('');
+ $.ajax({
+ type: 'GET',
+ url: obj.attr('href') + '&inajax=1',
+ dataType: 'xml'
+ })
+ .success(function (s) {
+ popup.open(s.lastChild.firstChild.nodeValue);
+ evalscript(s.lastChild.firstChild.nodeValue);
+ })
+ .error(function () {
+ window.location.href = obj.attr('href');
+ popup.close();
+ });
+ return false;
+ });
+ }
+ };
+ var formdialog = {
+ init: function () {
+ $(document).on('click', '.formdialog', function () {
+ popup.open('');
+ var obj = $(this);
+ var formobj = $(this.form);
+ $.ajax({
+ type: 'POST',
+ url: formobj.attr('action') + '&handlekey=' + formobj.attr('id') + '&inajax=1',
+ data: formobj.serialize(),
+ dataType: 'xml'
+ })
+ .success(function (s) {
+ popup.open(s.lastChild.firstChild.nodeValue);
+ evalscript(s.lastChild.firstChild.nodeValue);
+ })
+ .error(function () {
+ window.location.href = obj.attr('href');
+ popup.close();
+ });
+ return false;
+ });
+ }
+ };
+ var redirect = {
+ init: function () {
+ $(document).on('click', '.redirect', function () {
+ var obj = $(this);
+ popup.close();
+ window.location.href = obj.attr('href');
+ });
+ }
+ };
+ var DISMENU = {};
+ var display = {
+ init: function () {
+ var $this = this;
+ $('.display').each(function (index, obj) {
+ obj = $(obj);
+ var dis = $(obj.attr('href'));
+ if (dis && dis.attr('display')) {
+ dis.css({display: 'none'});
+ dis.css({'z-index': '102'});
+ DISMENU[dis.attr('id')] = dis;
+ obj.on('click', function (e) {
+ if (inarray(e.target.tagName, ['A', 'IMG', 'INPUT'])) {
+ return;
+ }
+ $this.maskinit();
+ if (dis.attr('display') === 'true') {
+ dis.css('display', 'block');
+ dis.attr('display', 'false');
+ $('#mask').css({'display': 'block', 'width': '100%', 'height': '100%',
+ 'position': 'fixed', 'top': '0', 'left': '0', 'background': 'transparent',
+ 'z-index': '100'});
+ }
+ return false;
+ });
+ }
+ });
+ },
+ maskinit: function () {
+ var $this = this;
+ $('#mask').off().on('touchstart', function () {
+ $this.hide();
+ });
+ },
+ hide: function () {
+ $('#mask').css('display', 'none');
+ $.each(DISMENU, function (index, obj) {
+ obj.css('display', 'none');
+ obj.attr('display', 'true');
+ });
+ }
+ };
+ var pullrefresh = {
+ init: function () {
+ var pos = {};
+ var status = false;
+ var divobj = null;
+ var contentobj = null;
+ var reloadflag = false;
+ $('body').on('touchstart', function (e) {
+ e = mygetnativeevent(e);
+ pos.startx = e.touches[0].pageX;
+ pos.starty = e.touches[0].pageY;
+ })
+ .on('touchmove', function (e) {
+ e = mygetnativeevent(e);
+ pos.curposx = e.touches[0].pageX;
+ pos.curposy = e.touches[0].pageY;
+ if (pos.curposy - pos.starty < 0 && !status) {
+ return;
+ }
+ if (!status && $(window).scrollTop() <= 0) {
+ status = true;
+ divobj = document.createElement('div');
+ divobj = $(divobj);
+ divobj.css({'position': 'relative', 'margin-left': '-85px'});
+ $('body').prepend(divobj);
+ contentobj = document.createElement('div');
+ contentobj = $(contentobj);
+ contentobj.css({position: 'absolute', height: '30px', top: '-30px', left: '50%'});
+ contentobj.html(''
+ + '下拉可以刷新');
+ contentobj.find('img').css({'-webkit-transition': 'all 0.5s ease-in-out'});
+ divobj.prepend(contentobj);
+ pos.topx = pos.curposx;
+ pos.topy = pos.curposy;
+ }
+ if (!status) {
+ return;
+ }
+ if (status) {
+ var pullheight = pos.curposy - pos.topy;
+ if (pullheight >= 0 && pullheight < 150) {
+ divobj.css({height: pullheight / 2 + 'px'});
+ contentobj.css({top: (-30 + pullheight / 2) + 'px'});
+ if (reloadflag) {
+ contentobj.find('img').css({'-webkit-transform': 'rotate(180deg)',
+ '-moz-transform': 'rotate(180deg)', '-o-transform': 'rotate(180deg)',
+ 'transform': 'rotate(180deg)'});
+ contentobj.find('#refreshtxt').html('下拉可以刷新');
+ }
+ reloadflag = false;
+ }
+ else if (pullheight >= 150) {
+ divobj.css({height: pullheight / 2 + 'px'});
+ contentobj.css({top: (-30 + pullheight / 2) + 'px'});
+ if (!reloadflag) {
+ contentobj.find('img').css({'-webkit-transform': 'rotate(360deg)',
+ '-moz-transform': 'rotate(360deg)', '-o-transform': 'rotate(360deg)',
+ 'transform': 'rotate(360deg)'});
+ contentobj.find('#refreshtxt').html('松开可以刷新');
+ }
+ reloadflag = true;
+ }
+ }
+ e.preventDefault();
+ })
+ .on('touchend', function (e) {
+ if (status) {
+ if (reloadflag) {
+ contentobj.html('正在加载...');
+ contentobj.animate({top: (-30 + 75) + 'px'}, 618, 'linear');
+ divobj.animate({height: '75px'}, 618, 'linear', function () {
+ window.location.reload();
+ });
+ return;
+ }
+ }
+ divobj.remove();
+ divobj = null;
+ status = false;
+ pos = {};
+ });
+ }
+ };
+ function mygetnativeevent(event) {
+ while (event && typeof event.originalEvent !== 'undefined') {
+ event = event.originalEvent;
+ }
+ return event;
+ }
+ function evalscript(s) {
+ if (s.indexOf('
+
+
+## 属性
+
+### staticurl
+说明:静态资源存储路径
+必选项:是
+类型:字符串
+
+### imgdir
+说明:图片资源存储路径
+必选项:是
+类型:字符串
+
+### siteurl
+说明:网站url
+必选项:是
+类型:字符串
\ No newline at end of file
diff --git a/mip-discuz-post/mip-discuz-post.js b/mip-discuz-post/mip-discuz-post.js
new file mode 100644
index 00000000..8993c8b3
--- /dev/null
+++ b/mip-discuz-post/mip-discuz-post.js
@@ -0,0 +1,852 @@
+/**
+ * @file mip-discuz-post discuz登陆组件
+ * @author 104460712@qq.com
+ * @version 1.1
+ */
+
+define(function (require) {
+ var $ = require('jquery');
+ var JSLOADED = [];
+ var customElement = require('customElement').create();
+ customElement.prototype.firstInviewCallback = function () {
+ var element = this.element;
+ var STATICURL = element.getAttribute('staticurl');
+ var IMGDIR = element.getAttribute('imgdir');
+ var SITEURL = element.getAttribute('siteurl');
+ var script = element.querySelector('script[type="application/json"]') || null;
+ if (script) {
+ var obj = JSON.parse(script.textContent.toString());
+ var fnparams = '';
+ var fnstring = '';
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ fnstring = key;
+ fnparams = obj[key];
+ }
+ }
+ switch (fnstring) {
+ case 'seccheck':
+ seccheck(fnparams);
+ break;
+ case 'urlforward':
+ urlforward(fnparams);
+ break;
+ case 'fastpostform':
+ fastpostform(fnparams);
+ break;
+ case 'checkpostvalue':
+ checkpostvalue(fnparams);
+ break;
+ case 'postsubmitnewthread':
+ postsubmitnewthread(fnparams);
+ break;
+ }
+ }
+ var page = {
+ converthtml: function () {
+ var prevpage = $('div.pg .prev').prop('href');
+ var nextpage = $('div.pg .nxt').prop('href');
+ var lastpage = $('div.pg label span').text().replace(/[^\d]/g, '') || 0;
+ var curpage = $('div.pg input').val() || 1;
+ if (!lastpage) {
+ prevpage = $('div.pg .pgb a').prop('href');
+ }
+ var prevpagehref = '';
+ var nextpagehref = '';
+ if (prevpage === undefined) {
+ prevpagehref = '#" class="grey';
+ }
+ else {
+ prevpagehref = prevpage;
+ }
+ if (nextpage === undefined) {
+ nextpagehref = '#" class="grey';
+ }
+ else {
+ nextpagehref = nextpage;
+ }
+ var selector = '';
+ if (lastpage) {
+ selector += '';
+ selector += '第' + curpage + '页';
+ }
+ $('div.pg').removeClass('pg').addClass('page').html('上一页'
+ + selector + '下一页');
+ $('#dumppage').on('change', function () {
+ var href = (prevpage || nextpage);
+ window.location.href = href.replace(/page=\d+/, 'page=' + $(this).val());
+ });
+ }
+ };
+ var scrolltop = {
+ obj: null,
+ init: function (obj) {
+ scrolltop.obj = obj;
+ var fixed = this.isfixed();
+ obj.css('opacity', '.618');
+ if (fixed) {
+ obj.css('bottom', '8px');
+ }
+ else {
+ obj.css({visibility: 'visible', position: 'absolute'});
+ }
+ $(window).on('resize', function () {
+ if (fixed) {
+ obj.css('bottom', '8px');
+ }
+ else {
+ obj.css('top', ($(document).scrollTop() + $(window).height() - 40) + 'px');
+ }
+ });
+ obj.on('tap', function () {
+ $(document).scrollTop($(document).height());
+ });
+ $(document).on('scroll', function () {
+ if (!fixed) {
+ obj.css('top', ($(document).scrollTop() + $(window).height() - 40) + 'px');
+ }
+ if ($(document).scrollTop() >= 400) {
+ obj.removeClass('bottom')
+ .off().on('tap', function () {
+ window.scrollTo('0', '1');
+ });
+ }
+ else {
+ obj.addClass('bottom')
+ .off().on('tap', function () {
+ $(document).scrollTop($(document).height());
+ });
+ }
+ });
+
+ },
+ isfixed: function () {
+ var offset = scrolltop.obj.offset();
+ var scrollTop = $(window).scrollTop();
+ var screenHeight = document.documentElement.clientHeight;
+ if (offset === undefined) {
+ return false;
+ }
+ if (offset.top < scrollTop || (offset.top - scrollTop) > screenHeight) {
+ return false;
+ }
+ return true;
+ }
+ };
+ var img = {
+ init: function (iserrt) {
+ var errhandle = this.errorhandle;
+ $('img').on('load', function () {
+ var obj = $(this);
+ obj.attr('zsrc', obj.attr('src'));
+ if (obj.width() < 5 && obj.height() < 10 && obj.css('display') !== 'none') {
+ return errhandle(obj, iserrt);
+ }
+ obj.css('display', 'inline');
+ obj.css('visibility', 'visible');
+ if (obj.width() > window.innerWidth) {
+ obj.css('width', window.innerWidth);
+ }
+ obj.parent().find('.loading').remove();
+ obj.parent().find('.error_text').remove();
+ })
+ .on('error', function () {
+ var obj = $(this);
+ obj.attr('zsrc', obj.attr('src'));
+ errhandle(obj, iserrt);
+ });
+ },
+ errorhandle: function (obj, iserrt) {
+ if (obj.attr('noerror') === 'true') {
+ return;
+ }
+ obj.css('visibility', 'hidden');
+ obj.css('display', 'none');
+ var parentnode = obj.parent();
+ parentnode.find('.loading').remove();
+ parentnode.append('');
+ var loadnums = parseInt(obj.attr('load'), 0) || 0;
+ if (loadnums < 3) {
+ obj.attr('src', obj.attr('zsrc'));
+ obj.attr('load', ++loadnums);
+ return false;
+ }
+ if (iserrt) {
+ parentnode.find('.loading').remove();
+ parentnode.append('点击重新加载
');
+ parentnode.find('.error_text').one('click', function () {
+ obj.attr('load', 0).find('.error_text').remove();
+ parentnode.append('');
+ obj.attr('src', obj.attr('zsrc'));
+ });
+ }
+ return false;
+ }
+ };
+ var atap = {
+ init: function () {
+ $('.atap').on('tap', function () {
+ var obj = $(this);
+ obj.css({'background': '#6FACD5', 'color': '#FFFFFF', 'font-weight': 'bold',
+ 'text-decoration': 'none', 'text-shadow': '0 1px 1px #3373A5'});
+ return false;
+ });
+ $('.atap a').off('click');
+ }
+ };
+ var POPMENU = {};
+ var popup = {
+ init: function () {
+ var $this = this;
+ $('.popup').each(function (index, obj) {
+ obj = $(obj);
+ var pop = $(obj.attr('href'));
+ if (pop && pop.attr('popup')) {
+ pop.css({display: 'none'});
+ obj.on('click', function (e) {
+ $this.open(pop);
+ });
+ }
+ });
+ this.maskinit();
+ },
+ maskinit: function () {
+ var $this = this;
+ $('#mask').off().on('tap', function () {
+ $this.close();
+ });
+ },
+
+ open: function (pop, type, url) {
+ this.close();
+ this.maskinit();
+ if (typeof pop === 'string') {
+ $('#ntcmsg').remove();
+ if (type === 'alert') {
+ pop = '' + pop
+ + '';
+ }
+ else if (type === 'confirm') {
+ pop = '' + pop
+ + '';
+ }
+ $('body').append('' + pop + '
');
+ pop = $('#ntcmsg');
+ }
+ if (POPMENU[pop.attr('id')]) {
+ $('#' + pop.attr('id') + '_popmenu').html(pop.html()).css({height: pop.height()
+ + 'px', width: pop.width() + 'px'});
+ }
+ else {
+ pop.parent().append('');
+ }
+ var popupobj = $('#' + pop.attr('id') + '_popmenu');
+ var left = (window.innerWidth - popupobj.width()) / 2;
+ var top = (document.documentElement.clientHeight - popupobj.height()) / 2;
+ popupobj.css({'display': 'block', 'position': 'fixed', 'left': left,
+ 'top': top, 'z-index': 120, 'opacity': 1});
+ $('#mask').css({'display': 'block', 'width': '100%', 'height': '100%',
+ 'position': 'fixed', 'top': '0', 'left': '0', 'background': 'black',
+ 'opacity': '0.2', 'z-index': '100'});
+ POPMENU[pop.attr('id')] = pop;
+ },
+ close: function () {
+ $('#mask').css('display', 'none');
+ $.each(POPMENU, function (index, obj) {
+ $('#' + index + '_popmenu').css('display', 'none');
+ });
+ }
+ };
+ var dialog = {
+ init: function () {
+ $(document).on('click', '.dialog', function () {
+ var obj = $(this);
+ popup.open('');
+ $.ajax({
+ type: 'GET',
+ url: obj.attr('href') + '&inajax=1',
+ dataType: 'xml'
+ })
+ .success(function (s) {
+ popup.open(s.lastChild.firstChild.nodeValue);
+ evalscript(s.lastChild.firstChild.nodeValue);
+ })
+ .error(function () {
+ window.location.href = obj.attr('href');
+ popup.close();
+ });
+ return false;
+ });
+ }
+ };
+ var formdialog = {
+ init: function () {
+ $(document).on('click', '.formdialog', function () {
+ popup.open('');
+ var obj = $(this);
+ var formobj = $(this.form);
+ $.ajax({
+ type: 'POST',
+ url: formobj.attr('action') + '&handlekey=' + formobj.attr('id') + '&inajax=1',
+ data: formobj.serialize(),
+ dataType: 'xml'
+ })
+ .success(function (s) {
+ popup.open(s.lastChild.firstChild.nodeValue);
+ evalscript(s.lastChild.firstChild.nodeValue);
+ })
+ .error(function () {
+ window.location.href = obj.attr('href');
+ popup.close();
+ });
+ return false;
+ });
+ }
+ };
+ var redirect = {
+ init: function () {
+ $(document).on('click', '.redirect', function () {
+ var obj = $(this);
+ popup.close();
+ window.location.href = obj.attr('href');
+ });
+ }
+ };
+ var DISMENU = {};
+ var display = {
+ init: function () {
+ var $this = this;
+ $('.display').each(function (index, obj) {
+ obj = $(obj);
+ var dis = $(obj.attr('href'));
+ if (dis && dis.attr('display')) {
+ dis.css({display: 'none'});
+ dis.css({'z-index': '102'});
+ DISMENU[dis.attr('id')] = dis;
+ obj.on('click', function (e) {
+ if (inarray(e.target.tagName, ['A', 'IMG', 'INPUT'])) {
+ return;
+ }
+ $this.maskinit();
+ if (dis.attr('display') === 'true') {
+ dis.css('display', 'block');
+ dis.attr('display', 'false');
+ $('#mask').css({'display': 'block', 'width': '100%', 'height': '100%',
+ 'position': 'fixed', 'top': '0', 'left': '0', 'background': 'transparent',
+ 'z-index': '100'});
+ }
+ return false;
+ });
+ }
+ });
+ },
+ maskinit: function () {
+ var $this = this;
+ $('#mask').off().on('touchstart', function () {
+ $this.hide();
+ });
+ },
+ hide: function () {
+ $('#mask').css('display', 'none');
+ $.each(DISMENU, function (index, obj) {
+ obj.css('display', 'none');
+ obj.attr('display', 'true');
+ });
+ }
+ };
+ var geo = {
+ latitude: null,
+ longitude: null,
+ loc: null,
+ errmsg: null,
+ timeout: 5000,
+ getcurrentposition: function () {
+ if (!navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(this.locationsuccess, this.locationerror, {
+ enableHighAcuracy: true,
+ timeout: this.timeout,
+ maximumAge: 3000
+ });
+ }
+ },
+ locationerror: function (error) {
+ geo.errmsg = 'error';
+ switch (error.code) {
+ case error.TIMEOUT:
+ geo.errmsg = '获取位置超时,请重试';
+ break;
+ case error.POSITION_UNAVAILABLE:
+ geo.errmsg = '无法检测到您的当前位置';
+ break;
+ case error.PERMISSION_DENIED:
+ geo.errmsg = '请允许能够正常访问您的当前位置';
+ break;
+ case error.UNKNOWN_ERROR:
+ geo.errmsg = '发生未知错误';
+ break;
+ }
+ },
+ locationsuccess: function (position) {
+ geo.latitude = position.coords.latitude;
+ geo.longitude = position.coords.longitude;
+ geo.errmsg = '';
+ $.ajax({
+ type: 'POST',
+ url: 'http://maps.google.com/maps/api/geocode/json?latlng=' + geo.latitude + ',' + geo.longitude + '&language=zh-CN&sensor=true',
+ dataType: 'json'
+ })
+ .success(function (s) {
+ if (s.status === 'OK') {
+ geo.loc = s.results[0].formatted_address;
+ }
+ })
+ .error(function () {
+ geo.loc = null;
+ });
+ }
+ };
+ var pullrefresh = {
+ init: function () {
+ var pos = {};
+ var status = false;
+ var divobj = null;
+ var contentobj = null;
+ var reloadflag = false;
+ $('body').on('touchstart', function (e) {
+ e = mygetnativeevent(e);
+ pos.startx = e.touches[0].pageX;
+ pos.starty = e.touches[0].pageY;
+ })
+ .on('touchmove', function (e) {
+ e = mygetnativeevent(e);
+ pos.curposx = e.touches[0].pageX;
+ pos.curposy = e.touches[0].pageY;
+ if (pos.curposy - pos.starty < 0 && !status) {
+ return;
+ }
+ if (!status && $(window).scrollTop() <= 0) {
+ status = true;
+ divobj = document.createElement('div');
+ divobj = $(divobj);
+ divobj.css({'position': 'relative', 'margin-left': '-85px'});
+ $('body').prepend(divobj);
+ contentobj = document.createElement('div');
+ contentobj = $(contentobj);
+ contentobj.css({position: 'absolute', height: '30px', top: '-30px', left: '50%'});
+ contentobj.html(''
+ + '下拉可以刷新');
+ contentobj.find('img').css({'-webkit-transition': 'all 0.5s ease-in-out'});
+ divobj.prepend(contentobj);
+ pos.topx = pos.curposx;
+ pos.topy = pos.curposy;
+ }
+ if (!status) {
+ return;
+ }
+ if (status) {
+ var pullheight = pos.curposy - pos.topy;
+ if (pullheight >= 0 && pullheight < 150) {
+ divobj.css({height: pullheight / 2 + 'px'});
+ contentobj.css({top: (-30 + pullheight / 2) + 'px'});
+ if (reloadflag) {
+ contentobj.find('img').css({'-webkit-transform': 'rotate(180deg)',
+ '-moz-transform': 'rotate(180deg)', '-o-transform': 'rotate(180deg)',
+ 'transform': 'rotate(180deg)'});
+ contentobj.find('#refreshtxt').html('下拉可以刷新');
+ }
+ reloadflag = false;
+ }
+ else if (pullheight >= 150) {
+ divobj.css({height: pullheight / 2 + 'px'});
+ contentobj.css({top: (-30 + pullheight / 2) + 'px'});
+ if (!reloadflag) {
+ contentobj.find('img').css({'-webkit-transform': 'rotate(360deg)',
+ '-moz-transform': 'rotate(360deg)', '-o-transform': 'rotate(360deg)',
+ 'transform': 'rotate(360deg)'});
+ contentobj.find('#refreshtxt').html('松开可以刷新');
+ }
+ reloadflag = true;
+ }
+ }
+ e.preventDefault();
+ })
+ .on('touchend', function (e) {
+ if (status) {
+ if (reloadflag) {
+ contentobj.html('正在加载...');
+ contentobj.animate({top: (-30 + 75) + 'px'}, 618, 'linear');
+ divobj.animate({height: '75px'}, 618, 'linear', function () {
+ window.location.reload();
+ });
+ return;
+ }
+ }
+ divobj.remove();
+ divobj = null;
+ status = false;
+ pos = {};
+ });
+ }
+ };
+ function mygetnativeevent(event) {
+ while (event && typeof event.originalEvent !== 'undefined') {
+ event = event.originalEvent;
+ }
+ return event;
+ }
+ function evalscript(s) {
+ if (s.indexOf('';
+ }
+
+ if (loadjs) {
+ var loadjss = loadjs.split('\n');
+ $.each(loadjss, function (index, js) {
+ js = $.trim(js);
+ if (js) {
+ scriptstr += '';
+ }
+
+ });
+ }
+
+ if (geval) {
+
+ window.mipDpGeval = geval;
+ scriptstr += '';
+ }
+
+ if (adtag) {
+ $.each($('.adwraper').not('loaded'), function (index, obj) {
+ var tag = $.trim($(obj).attr('id'));
+ if (tag) {
+ scriptstr += '';
+ }
+
+ $(obj).addClass('loaded');
+ });
+ var adtags = adtag.split(',');
+ if (adtags.length > 0) {
+ for (var index = 0; index < adtags.length; index++) {
+ var tag = $.trim(adtags[index]);
+ if (tag) {
+ if ($('#' + tag).length > 0) {
+ scriptstr += '';
+ }
+ else {
+ scriptstr += '';
+ }
+ }
+
+ }
+ }
+ }
+
+ if (loadjsEnd) {
+ var loadJsEnds = loadjsEnd.split('\n');
+ for (var i = loadJsEnds.length - 1; i >= 0; i--) {
+ if ($.trim(loadJsEnds[i])) {
+ var js = $.trim(loadJsEnds[i]);
+ scriptstr += '';
+ }
+
+ }
+ }
+
+ if (scriptstr) {
+ document.write(scriptstr);
+ }
+
+ };
+
+ if (typeof ($.fn.swipeLeft) !== 'function') {
+ $.fn.swipeLeft = function (callback) {
+ if (this.length === 0) {
+ return;
+ }
+
+ $.each(this, function (i, elm) {
+ var gesture = new Gesture(elm);
+ gesture.on('swipeleft', callback);
+ });
+ };
+ }
+
+ if (typeof ($.fn.swipeRight) !== 'function') {
+ $.fn.swipeRight = function (callback) {
+ if (this.length === 0) {
+ return;
+ }
+
+ $.each(this, function (i, elm) {
+ var gesture = new Gesture(elm);
+ gesture.on('swipeleft swiperight', function (event, data) {
+ if (data.type === 'swiperight') {
+ callback();
+ }
+
+ });
+ });
+ };
+ }
+
+ if (typeof ($.fn.swipeUp) !== 'function') {
+ $.fn.swipeUp = function (callback) {
+ if (this.length === 0) {
+ return;
+ }
+
+ $.each(this, function (i, elm) {
+ var gesture = new Gesture(elm);
+ gesture.on('swipeup', callback);
+ });
+ };
+ }
+
+ if (typeof ($.fn.swipeDown) !== 'function') {
+ $.fn.swipeDown = function (callback) {
+ if (this.length === 0) {
+ return;
+ }
+
+ $.each(this, function (i, elm) {
+ var gesture = new Gesture(elm);
+ gesture.on('swipedown', callback);
+ });
+ };
+ }
+
+ if (typeof ($.fn.tap) !== 'function') {
+ $.fn.tap = function (callback) {
+ if (this.length === 0) {
+ return;
+ }
+
+ $.each(this, function (i, elm) {
+ var gesture = new Gesture(elm);
+ gesture.on('tap', callback);
+ });
+ };
+ }
+
+ $('.btn-back').on('click', function () {
+ window.history.back();
+ });
+
+ return customElem;
+});
diff --git a/mip-dp-script/package.json b/mip-dp-script/package.json
new file mode 100644
index 00000000..3e39ef80
--- /dev/null
+++ b/mip-dp-script/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-dp-script",
+ "version": "1.0.2",
+ "author": {
+ "name": "hans",
+ "email": "qtshucom@163.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
\ No newline at end of file
diff --git a/mip-ecms/README.md b/mip-ecms/README.md
new file mode 100644
index 00000000..4863621e
--- /dev/null
+++ b/mip-ecms/README.md
@@ -0,0 +1,85 @@
+@@ -0,0 +1,38 @@
+# mip-emcs
+mip-ecms 帝国cms,整合包主要包括ecms中调用的js如点赞,阅读量,评论等。
+
+标题|内容
+----|----
+类型|定制
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-ecms/mip-ecms.js
+
+## 示例1
+
+### 5 内容页点赞。
+```html
+
+ 点赞数
+
+```
+## 示例2
+
+### 评论验证码点击刷新
+```html
+
+```
+## 示例3
+
+### 回滚页面顶部
+```html
+ ︿
+```
+## 示例4
+
+### 百度站内搜索
+```html
+
+
+
+
+```
+## 示例4
+
+### 悬浮24小时热文
+```html
+悬浮内容
+```
+## 属性
+
+### ecms-type
+
+说明:选择使用方法
+必选项:是
+取值范围:5
+默认值:5
+
+### ecms-classid
+
+说明:栏目ID
+必选项:是
+ecms-type:5
+
+### ecms-id
+
+说明:文章ID
+必选项:是
+类型:整数
+ecms-type:5
+
+### 回滚顶部
+说明:class="scroll"和id="scroll"
+必选项:是
+
+### 评论验证码
+说明:id="KeyImg"根据id KeyImg进行动态刷新
+必选项:是
+
+### 百度站内搜索
+说明:input中的id="bdcsMain"
+必选项:是
+
+说明: sid="密匙" 百度站内搜索密匙
+必选项:是
+
+### 悬浮24小时热文
+说明:需要悬浮的div添加class="sitebar_list"
+必选项:是
diff --git a/mip-ecms/mip-ecms.js b/mip-ecms/mip-ecms.js
new file mode 100644
index 00000000..1ddd759d
--- /dev/null
+++ b/mip-ecms/mip-ecms.js
@@ -0,0 +1,99 @@
+/**
+ * 帝国cms整合
+ * @author: supai
+ * @date: 2016-11-16
+ * @file: mip-ecms.js
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElement = require('customElement').create();
+
+ var effects = {
+ // 绑定事件
+ bindEvents: function () {
+ // 文章点赞
+ $('.favorite').on('click', function (event) {
+ event.preventDefault();
+ this.zan();
+ });
+ // 评论验证码刷新
+ $('#KeyImg').on('click', function (event) {
+ $('#KeyImg').attr('src', '/e/ShowKey/?v=pl&' + Math.random());
+ });
+ },
+ // 文章点赞
+ zan: function () {
+ if ($(this).hasClass('done')) {
+ return false;
+ }
+ $(this).addClass('done');
+ var id = $(this).data('id');
+ var classid = $(this).data('classid');
+ var rateHolder = $(this).children('.count');
+ var ajaxData = {
+ classid: classid,
+ id: id,
+ dotop: 1,
+ doajax: 1,
+ ajaxarea: 'diggnum'
+ };
+ $.get('/e/public/digg/index.php', ajaxData,
+ function (data) {
+ var dingnumr = data.substring(0, data.indexOf('|'));
+ if (!dingnumr) {
+ $(rateHolder).html(dingnumr);
+ }
+ else {
+ alert('你已经提交过了');
+ }
+
+ });
+ return false;
+ },
+ // 回滚页面顶部
+ gotoTop: function () {
+ $(window).scroll(function () {
+ var scrollValue = $(window).scrollTop();
+ scrollValue > 500 ? $('div[class=scroll]').fadeIn() : $('div[class=scroll]').fadeOut();
+ });
+ $('#scroll').click(function () {
+ $('html,body').animate({
+ scrollTop: 0
+ }, 200);
+ });
+ },
+ // 百度站内搜索
+ bdZnsv: function () {
+ var sid = $('#bdcsMain').data('sid');
+ var bdcs = document.createElement('script');
+ bdcs.type = 'text/javascript';
+ bdcs.async = true;
+ bdcs.src = 'http://znsv.baidu.com/customer_search/api/js?sid=' + sid
+ + '&plate_url=' + encodeURIComponent(window.location.href)
+ + '&t=' + Math.ceil(new Date() / 3600000);
+ var s = document.getElementsByTagName('script')[0];
+ s.parentNode.insertBefore(bdcs, s);
+ },
+ rightNews: function () {
+ var elm = $('.sitebar_list');
+ var startPos = $(elm).offset().top;
+ $.event.add(window, 'scroll', function () {
+ var p = $(window).scrollTop();
+ $(elm).css('position', ((p) > startPos) ? 'fixed' : 'static');
+ $(elm).css('top', ((p) > startPos) ? '20px' : '');
+ });
+ },
+ // 加载绑定
+ init: function () {
+ this.bindEvents();
+ this.gotoTop();
+ this.bdZnsv();
+ this.rightNews();
+ }
+ };
+
+ customElement.prototype.build = function () {
+ effects.init();
+ };
+ return customElement;
+});
diff --git a/mip-ecms/package.json b/mip-ecms/package.json
new file mode 100644
index 00000000..98164856
--- /dev/null
+++ b/mip-ecms/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "mip-ecms",
+ "version": "1.4.2",
+ "author": {
+ "name": "supai",
+ "email": "1564555710@qq.com"
+ },
+ "description": "帝国cms,mip整合包"
+}
diff --git a/mip-em-hq/README.md b/mip-em-hq/README.md
index 2fef9f1c..6c959e5c 100644
--- a/mip-em-hq/README.md
+++ b/mip-em-hq/README.md
@@ -6,7 +6,7 @@ mip-em-hq 自有业务详情页整体交互组件
----|----
类型|业务,定制
支持布局|responsive,fill,container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-em-hq/mip-em-hq.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-em-hq/mip-em-hq.js
## 示例
diff --git a/mip-fdad/README.md b/mip-fdad/README.md
index 2202eddc..b48b1456 100644
--- a/mip-fdad/README.md
+++ b/mip-fdad/README.md
@@ -6,7 +6,7 @@ mip-fdad 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fdad/mip-fdad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fdad/mip-fdad.js
## 示例
diff --git a/mip-fengxi-sdk/README.md b/mip-fengxi-sdk/README.md
new file mode 100644
index 00000000..19bc069f
--- /dev/null
+++ b/mip-fengxi-sdk/README.md
@@ -0,0 +1,30 @@
+# mip-fengxi-sdk
+
+mip-fengxi-sdk 凤悉服务SDK插件
+
+标题|内容
+----|----
+类型|通用
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-fengxi-sdk/mip-fengxi-sdk.js
+
+## 示例
+
+### 基本用法
+```html
+
+```
+
+## 属性
+
+### production
+
+说明:产品名称
+必填:是
+格式:字符串
+
+### cert
+
+说明:产品凭证
+必填:否
+格式:字符串
diff --git a/mip-fengxi-sdk/mip-fengxi-sdk.js b/mip-fengxi-sdk/mip-fengxi-sdk.js
new file mode 100644
index 00000000..451947e3
--- /dev/null
+++ b/mip-fengxi-sdk/mip-fengxi-sdk.js
@@ -0,0 +1,46 @@
+/**
+ * @file 凤悉服务SDK插件
+ *
+ * @author houyuxin
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var customElement = require('customElement').create();
+ var performance = require('performance');
+
+ /**
+ * 创建元素回调
+ */
+ customElement.prototype.createdCallback = function () {
+ var $element = $(this.element);
+ var production = $element.attr('production');
+ var cert = $element.attr('cert');
+
+ // 记录页面加载时间
+ performance.on('update', function (timing) {
+ if (timing && timing.MIPStart && timing.MIPFirstScreen) {
+ window._agl.push(
+ ['begin', timing.MIPStart],
+ ['end', timing.MIPFirstScreen]
+ );
+ }
+ });
+
+ // sdk配置
+ window._agl = [];
+ window._agl.push(
+ ['production', production],
+ ['cert', cert],
+ ['start', true]
+ );
+ // 插入script标签
+ var scriptNode = document.createElement('script');
+ scriptNode.type = 'text/javascript';
+ scriptNode.src = 'https://fengxi.bj.bcebos.com/angelia.js';
+ document.body.appendChild(scriptNode);
+ };
+
+ return customElement;
+});
diff --git a/mip-fengxi-sdk/package.json b/mip-fengxi-sdk/package.json
new file mode 100644
index 00000000..4b174bcf
--- /dev/null
+++ b/mip-fengxi-sdk/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-fengxi-sdk",
+ "version": "1.1.1",
+
+ "author": {
+ "name": "author",
+ "email": "houyuxin82@email.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-fh-ad-plus/README.md b/mip-fh-ad-plus/README.md
index 5d7154ff..e7e8f0ae 100644
--- a/mip-fh-ad-plus/README.md
+++ b/mip-fh-ad-plus/README.md
@@ -6,7 +6,7 @@ mip-fh-ad-plus 用来支持整站页面的直投广告显示
----|----
类型|广告
支持布局|N/A
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fh-ad-plus/mip-fh-ad-plus.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fh-ad-plus/mip-fh-ad-plus.js
## 示例
diff --git a/mip-fh-ad/README.md b/mip-fh-ad/README.md
new file mode 100644
index 00000000..606ac4bb
--- /dev/null
+++ b/mip-fh-ad/README.md
@@ -0,0 +1,48 @@
+# mip-fh-ad
+
+mip-fh-ad 用来支持m.fh21.com.cn问答详情页的直投广告显示
+
+|描述|展示页面直投广告|
+|---|---|
+|类型|广告|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-fh-ad/mip-fh-ad.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 用于正常显示直投的广告。示例如下:
+
+```html
+
+
+
+ x
+
+
+
+
+
+```
+
+## 属性
+
+### fh-ad-pid
+
+说明:飞华广告位id
+必填:是
+格式:数字
+单位:无
+
+### fh-ad-keywords
+
+说明:飞华直投广告关键词组,多词用,分隔例:`fh-ad-keywords="瘦身,性病,减肥"`, 如果没有指定则直接获取`#adParam`的`data-keyword`
+必填:否
+
+### lazy
+
+说明:懒加载(lazy) (true | false)
+必填:否
+格式:boolean
+取值:true/false
+默认值:true
+
diff --git a/mip-fh-ad/mip-fh-ad.js b/mip-fh-ad/mip-fh-ad.js
new file mode 100644
index 00000000..522d3b6e
--- /dev/null
+++ b/mip-fh-ad/mip-fh-ad.js
@@ -0,0 +1,186 @@
+/**
+ * @author: laoono
+ * @date: 2016-09-01
+ * @time: 15:35
+ * @file: mip-fh-ad.js
+ * @contact: laoono.com
+ * @description: #
+ */
+
+define(function (require) {
+
+ var $ = require('zepto');
+
+ var customElem = require('customElement').create();
+ var $body = $('body');
+
+ // 直投广告请求url
+ var ajaxurl = 'https://partners.fh21.com.cn/partners/showcodejsonp?callback=?';
+ // 页面广告参数
+ var param = $('.adParam');
+ var paramObj = param.data('keyword');
+
+ // load btm baidu ad
+ var loadBdAd = function () {
+ var html = ['', '', '
'];
+
+ html = html.concat([''
+ ,'', '']);
+
+ html = html.join('');
+ return html;
+ };
+
+ // 没有飞华直投广告的操作
+ var hasNoFhAd = function (element, posId) {
+ $('.ad-s-1255').show();
+ $('.ask-info-blew-ad').show();
+ if (+posId === 1) {
+ element.html(loadBdAd());
+ }
+ $body.addClass('view-fh-ad-union');
+ $mipFhAdBdHide.show().removeClass('dn');
+ };
+
+ var hasFhAd = false;
+ var $mipFhAdBdHide = $('[mip-fh-ad-bd-hide]');
+ // 初始化直投广告
+ var init = function (opt) {
+ opt = opt || {};
+ // 设置配置项默认值
+ var posId = [opt.posId] || [1];
+ var kw = opt.kw || '';
+ var element = opt.element;
+
+ // 接口参数值
+ var query = {
+ kw: kw,
+ pid: posId.join(',')
+ };
+
+ // 判断直投广告参数,是否加载直投广告请求
+ if (kw && kw.length) {
+ $.getJSON(ajaxurl, query, function (json) {
+ var adObj = $.parseJSON(json.result);
+
+ // 遍历直投广告ID
+ $.each(adObj, function (k, v) {
+ // 有特定广告位id的直投广告
+ if ($.trim(v)) {
+ hasFhAd = true;
+ // 根据广告id,判断广告的显示位置
+ switch (+k) {
+ // 底部悬浮广告
+ case 1:
+ element.html('' + v + '
');
+ break;
+ case 14:
+ $('.liveAdBlock').html(v);
+ break;
+ // 我要提问广告位
+ case 47:
+ $('.i-2-iask').html(v);
+ break;
+ // 向Ta提问广告位
+ case 48:
+ $('div.introduce-list').find('a.btn').remove();
+ $('div.introduce-list').append(v);
+ break;
+ // 底部广告位
+ case 11:
+ // 我要提问下方热图广告位
+ case 49:
+ element.html(v);
+ break;
+
+ default:
+ element.html(v);
+ break;
+ }
+
+ $body.addClass('view-fh-ad-' + (+k));
+ }
+ // 无特定广告位id投广告
+ else {
+ switch (+k) {
+ // 广告位id为1时,加载底部漂浮的百度广告
+ case 1:
+ element.html(loadBdAd());
+ break;
+ // 广告位id为47时,加载我要提问下方文字广告和问题详情下方网盟广告
+ case 47:
+ $('.ad-s-1255').show();
+ $('.ask-info-blew-ad').show();
+ break;
+ }
+
+ $body.addClass('view-fh-ad-' + (+k) + '-union');
+ }
+ });
+
+ if (!hasFhAd) {
+ hasNoFhAd(element, posId);
+ }
+ });
+ }
+ else {
+ hasNoFhAd(element, posId);
+ }
+ };
+
+ var getOpt = function (element) {
+ var $element = $(element);
+ // 获取元素绑定的广告位id和关键词
+ var posId = $element.attr('fh-ad-pid');
+ var keywords = $element.attr('fh-ad-keywords') || paramObj;
+ var lazy = $element.attr('lazy');
+
+ // 广告初始化参数
+ // 广告位id数组 [1, 11, 14, 47, 48, 49];
+
+ var opt = {
+ posId: posId,
+ kw: $.trim(keywords),
+ lazy: lazy,
+ element: $element
+ };
+ return opt;
+ };
+ var menuCtrl = function () {
+ // 菜单
+ var state = true;
+ var $menuBar = $('.menuBar');
+ $('.menu').on('click', function () {
+ if (state) {
+ state = false;
+ $menuBar.show();
+ }
+ else {
+ state = true;
+ $menuBar.hide();
+ }
+ });
+ $('.returns').on('click', function () {
+ state = true;
+ $menuBar.hide();
+ });
+ };
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ var opt = getOpt(this.element);
+ opt.lazy === 'false' && init(opt);
+ // 导航菜单的显隐
+ menuCtrl();
+ };
+ // 第一次进入可视区回调,只会执行一次,做懒加载,利于网页速度
+ customElem.prototype.firstInviewCallback = function () {
+ var opt = getOpt(this.element);
+ opt.lazy !== 'false' && init(opt);
+ };
+
+ return customElem;
+});
diff --git a/mip-fh-ad/package.json b/mip-fh-ad/package.json
new file mode 100644
index 00000000..57d54144
--- /dev/null
+++ b/mip-fh-ad/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mip-fh-ad",
+ "version": "1.1.2",
+ "author": {
+ "name": "laoono"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-fh-async/README.md b/mip-fh-async/README.md
index 16cab445..7417af8d 100644
--- a/mip-fh-async/README.md
+++ b/mip-fh-async/README.md
@@ -6,7 +6,7 @@ mip-fh-async 用来支持整站全网异步接口渲染组件
----|----
类型|通用
支持布局|N/A
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fh-async/mip-fh-async.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fh-async/mip-fh-async.js
## 示例
基本用法
diff --git a/mip-fh-bus/README.md b/mip-fh-bus/README.md
new file mode 100644
index 00000000..85493cdc
--- /dev/null
+++ b/mip-fh-bus/README.md
@@ -0,0 +1,23 @@
+# mip-fh-bus
+
+mip-fh-bus 用于改造两性详情页面的脚本代码
+
+|描述|脚本mip改造|
+|---|---|
+|类型|脚本|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-fh-bus/mip-fh-bus.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 只需使用一次。示例如下:
+
+```html
+
+
+```
+
diff --git a/mip-fh-bus/bus-extra.js b/mip-fh-bus/bus-extra.js
new file mode 100644
index 00000000..d71bfe54
--- /dev/null
+++ b/mip-fh-bus/bus-extra.js
@@ -0,0 +1,66 @@
+/**
+ * @author: laoono
+ * @date: 2016-12-08
+ * @time: 14:35
+ * @contact: laoono.com
+ * @description: #
+ */
+
+define(function (require) {
+ var module = {};
+ var site = ['google.', 'baidu.', 'soso.', 'so.', '360.', 'yahoo.', 'youdao.', 'sogou.', 'gougou.'];
+ var $ = require('zepto');
+
+ module.back = function (config) {
+ config = config || {};
+
+ var doc = document;
+ var aSites = config.site || site;
+ var historyUrl = doc.referrer;
+ var navigatehref = doc.getElementById('navigatehref') || {};
+
+ if (!navigatehref) {
+ return;
+ }
+
+ historyUrl = historyUrl.toLowerCase(); // 转为小写
+
+ for (var i in aSites) {
+ if (historyUrl.indexOf(aSites[i]) > 0) {
+ navigatehref.href = '/list/3830/';
+ break;
+ }
+ else {
+ navigatehref.href = 'javascript:history.go(-1);';
+ }
+ }
+ };
+
+ module.nav = function () {
+ var $navigate01 = $('.navigate01-nav');
+ var $btnMenu = $('.ico-navigate-menu');
+ var $btnUp = $('.ico-collapse-up');
+
+ var toggle = function () {
+ if ($navigate01.css('display') === 'none') {
+ $navigate01.show();
+ }
+ else {
+ $navigate01.hide();
+ }
+ };
+
+ $btnMenu.click(toggle);
+ $btnUp.click(toggle);
+ };
+
+ module.follow = function () {
+ var ele = $('.follow-num');
+
+ var num = Math.floor(10000 + Math.random() * (100000 - 10000));
+ ele.text(num);
+ };
+
+ return module;
+});
+
diff --git a/mip-fh-bus/bus-set-img-ad.js b/mip-fh-bus/bus-set-img-ad.js
new file mode 100644
index 00000000..f7758ff7
--- /dev/null
+++ b/mip-fh-bus/bus-set-img-ad.js
@@ -0,0 +1,54 @@
+/**
+ * @author: laoono
+ * @date: 2016-12-08
+ * @time: 14:44
+ * @contact: laoono.com
+ * @description: #
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var module = {};
+
+ module.loadScript = function (url, callback) {
+ // Adding the script tag to the head as suggested before
+ var head = document.getElementsByTagName('head')[0];
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+
+ // Then bind the event to the callback function.
+ // There are several events for cross browser compatibility.
+ script.onreadystatechange = callback;
+ script.onload = callback;
+
+ // Fire the loading
+ head.appendChild(script);
+ };
+
+ module.set = function () {
+
+ var $img = $('.FhwapContent mip-img[src^="http://sex."]');
+
+ if (!$img.length) {
+ return;
+ }
+
+ var docw = document.write;
+ // 重写document.write方法
+ document.write = function (node) {
+ $img.after(node);
+ };
+
+ this.loadScript('http://1.feihua.com/au3a1ecf91ffc8f038db4c3e8da4f73ffa54acde0b36.js', function () {
+ var timer = setTimeout(function () {
+ document.write = docw;
+ clearTimeout(timer);
+ }, 100);
+ });
+ };
+
+ return module;
+});
+
diff --git a/mip-fh-bus/mip-fh-bus.js b/mip-fh-bus/mip-fh-bus.js
new file mode 100644
index 00000000..5dd8cbe9
--- /dev/null
+++ b/mip-fh-bus/mip-fh-bus.js
@@ -0,0 +1,31 @@
+/**
+ * @author: laoono
+ * @date: 2016-12-08
+ * @time: 13:01
+ * @file: mip-fh-sex.js
+ * @contact: laoono.com
+ * @description: #
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var extra = require('./bus-extra');
+ var setImgAd = require('./bus-set-img-ad');
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // 随机关注人数
+ extra.follow();
+
+ // 导航返回判断
+ extra.back();
+
+ // 显示隐藏主菜单
+ extra.nav();
+
+ // 设置文章内容图片后面的网盟广告
+ setImgAd.set();
+ };
+
+ return customElem;
+});
diff --git a/mip-fh-bus/package.json b/mip-fh-bus/package.json
new file mode 100644
index 00000000..54f154c0
--- /dev/null
+++ b/mip-fh-bus/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-fh-bus",
+ "version": "1.0.0",
+ "author": {
+ "name": "laoono",
+ "email": "laoono@163.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-fh-location/README.md b/mip-fh-location/README.md
index a9d37d28..c25cd9de 100644
--- a/mip-fh-location/README.md
+++ b/mip-fh-location/README.md
@@ -6,7 +6,7 @@ mip-fh-location 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fh-location/mip-fh-location.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fh-location/mip-fh-location.js
## 示例
diff --git a/mip-fh-paging/README.md b/mip-fh-paging/README.md
index 4753081c..4a1ae59e 100644
--- a/mip-fh-paging/README.md
+++ b/mip-fh-paging/README.md
@@ -6,7 +6,7 @@ mip-fh-paging 用来支持整站分页交互组件
----|----
类型|通用
支持布局|N/A
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fh-paging/mip-fh-paging.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fh-paging/mip-fh-paging.js
## 示例
基本用法
diff --git a/mip-fj-countdown/README.md b/mip-fj-countdown/README.md
index d6539a1e..379d06b7 100644
--- a/mip-fj-countdown/README.md
+++ b/mip-fj-countdown/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fj-countdown/mip-fj-countdown.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fj-countdown/mip-fj-countdown.js
## 示例
diff --git a/mip-fj-fetch/README.md b/mip-fj-fetch/README.md
index 1ef23626..25888635 100644
--- a/mip-fj-fetch/README.md
+++ b/mip-fj-fetch/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fj-fetch/mip-fj-fetch.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fj-fetch/mip-fj-fetch.js
## 示例
diff --git a/mip-fn-comment-post/README.md b/mip-fn-comment-post/README.md
index e396a82a..225d15f7 100644
--- a/mip-fn-comment-post/README.md
+++ b/mip-fn-comment-post/README.md
@@ -10,7 +10,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-comment-post/mip-fn-comment-post.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-comment-post/mip-fn-comment-post.js
## 属性
### url
diff --git a/mip-fn-comment-praise/README.md b/mip-fn-comment-praise/README.md
index c33fa421..725d1e4f 100644
--- a/mip-fn-comment-praise/README.md
+++ b/mip-fn-comment-praise/README.md
@@ -10,7 +10,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-comment-praise/mip-fn-comment-praise.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-comment-praise/mip-fn-comment-praise.js
## 属性
### praiseId
diff --git a/mip-fn-comment-reply/README.md b/mip-fn-comment-reply/README.md
index 26c45bff..66d36073 100644
--- a/mip-fn-comment-reply/README.md
+++ b/mip-fn-comment-reply/README.md
@@ -11,7 +11,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-comment-reply/mip-fn-comment-reply.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-comment-reply/mip-fn-comment-reply.js
## 属性
### replyid
diff --git a/mip-fn-show-allarticles/README.md b/mip-fn-show-allarticles/README.md
index a625ee95..01aad4b3 100644
--- a/mip-fn-show-allarticles/README.md
+++ b/mip-fn-show-allarticles/README.md
@@ -10,7 +10,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-show-allarticles/mip-fn-show-allarticles.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-show-allarticles/mip-fn-show-allarticles.js
## 属性
### dataSrc
diff --git a/mip-fn-swiper/README.md b/mip-fn-swiper/README.md
index 185337a7..9f056671 100644
--- a/mip-fn-swiper/README.md
+++ b/mip-fn-swiper/README.md
@@ -30,7 +30,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-swiper/mip-fn-swiper.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-swiper/mip-fn-swiper.js
## 属性
diff --git a/mip-fn-thread-post/README.md b/mip-fn-thread-post/README.md
index a8822894..b7ece427 100644
--- a/mip-fn-thread-post/README.md
+++ b/mip-fn-thread-post/README.md
@@ -8,7 +8,7 @@ mip-fn-thread-post 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-thread-post/mip-fn-thread-post.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-thread-post/mip-fn-thread-post.js
## 示例
diff --git a/mip-fn-wapheader/README.md b/mip-fn-wapheader/README.md
index 7f7c9e91..ba8f1a73 100644
--- a/mip-fn-wapheader/README.md
+++ b/mip-fn-wapheader/README.md
@@ -10,7 +10,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-wapheader/mip-fn-wapheader.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-wapheader/mip-fn-wapheader.js
## 属性
### channelName
diff --git a/mip-fn-width/README.md b/mip-fn-width/README.md
index cafe5d22..0ec7ad44 100644
--- a/mip-fn-width/README.md
+++ b/mip-fn-width/README.md
@@ -26,7 +26,7 @@
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fn-width/mip-fn-width.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fn-width/mip-fn-width.js
## 属性
diff --git a/mip-fontsize/README.md b/mip-fontsize/README.md
index 9b610e5e..a2e32824 100644
--- a/mip-fontsize/README.md
+++ b/mip-fontsize/README.md
@@ -6,7 +6,7 @@ mip-fontsize 组件说明
----|----
类型|通用
支持布局|nodisplay
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-fontsize/mip-fontsize.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fontsize/mip-fontsize.js
## 示例
diff --git a/mip-footbutton/README.md b/mip-footbutton/README.md
index 14a44fd1..46de16a9 100644
--- a/mip-footbutton/README.md
+++ b/mip-footbutton/README.md
@@ -6,7 +6,7 @@ mip-footbutton 组件说明
----|----
类型|通用
支持布局|none
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-footbutton/mip-footbutton.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-footbutton/mip-footbutton.js
## 示例
diff --git a/mip-fx-flying-carpet/README.md b/mip-fx-flying-carpet/README.md
new file mode 100644
index 00000000..56e67bd9
--- /dev/null
+++ b/mip-fx-flying-carpet/README.md
@@ -0,0 +1,29 @@
+# mip-fx-flying-carpet
+
+描述|链接
+----|----
+可用性|beta
+示例|
+
+## 示例
+
+提供当前页面跳转功能,支持 superframe
+
+```
+
+
+
+```
+
+## 属性
+
+- **url**
+
+ 目标网址
+
+- **title**
+
+ 目标页面标题
+
+
+
diff --git a/mip-fx-flying-carpet/mip-fx-flying-carpet.js b/mip-fx-flying-carpet/mip-fx-flying-carpet.js
new file mode 100644
index 00000000..f8f4d09f
--- /dev/null
+++ b/mip-fx-flying-carpet/mip-fx-flying-carpet.js
@@ -0,0 +1,70 @@
+/**
+ * @file 组件mip-fx-flying-carpet
+ * @author lilangbo
+ * @time 2016.08.02
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ //console.log(platform);
+ /**
+ * buildCallback
+ *
+ * @param {Event} e event
+ */
+ function buildCallback () {
+ var util = require('util');
+ var css = util.css;
+ var element = this.element;
+
+ var children = Array.prototype.slice.call(element.children);
+
+ /* android uc 的兼容性有问题,特殊处理 */
+ var classname = (!platform.isIos() && platform.isUc())?'carpet-normal carpet-androiduc':'carpet-normal';
+
+ var clip = document.createElement('div');
+
+ var wapper = document.createElement('div');
+
+ clip.className = 'mip-fx-flying-carpet-clip';
+
+ wapper.className = classname;
+ element.appendChild(clip);
+ clip.appendChild(wapper);
+
+ children = children.filter(function (element) {
+ return element.tagName.toLowerCase() !== 'mip-i-space';
+ });
+
+ if (children.length !== 1) {
+ return;
+ }
+ children.forEach(function(child){
+ wapper.appendChild(child);
+ var width = child.offsetWidth;
+ var winWidth = document.body.clientWidth;
+ if (width > winWidth) {
+ css(child, {
+ 'margin-left': - (width - winWidth) / 2 + 'px'
+ })
+ }
+
+ });
+ }
+
+ /**
+ * 初始化
+ *
+ */
+ customElem.prototype.build = buildCallback;
+
+ customElem.prototype.viewportCallback = function (isInview) {
+ if (isInview) {
+ //this.element.style.zIndex = 10;
+ } else {
+ //this.element.style.zIndex = 1;
+ }
+ };
+
+ return customElem;
+});
diff --git a/mip-fx-flying-carpet/mip-fx-flying-carpet.less b/mip-fx-flying-carpet/mip-fx-flying-carpet.less
new file mode 100644
index 00000000..6bf41650
--- /dev/null
+++ b/mip-fx-flying-carpet/mip-fx-flying-carpet.less
@@ -0,0 +1,37 @@
+/* Non-overridable properties */
+mip-fx-flying-carpet {
+ position: relative;
+ width: 100%;
+ min-height: 100px;
+ overflow: hidden;
+}
+mip-fx-flying-carpet .carpet-normal {
+ content: ' ';
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ /*
+ * Android rendering using the GPU
+ */
+ transform: translateZ(0);
+ -webkit-transform: translateZ(0);
+}
+mip-fx-flying-carpet .carpet-androiduc {
+ position: absolute;
+}
+
+mip-fx-flying-carpet>.mip-fx-flying-carpet-clip {
+ position: absolute!important;
+ top: 0!important;
+ left: 0!important;
+ width: 100%!important;
+ height: 100%!important;
+ border: 0!important;
+ margin: 0!important;
+ padding: 0!important;
+ clip: rect(0,auto,auto,0)!important;
+ -webkit-clip-path: polygon(0px 0px,100% 0px,100% 100%,0px 100%)!important;
+ clip-path: polygon(0px 0px,100% 0px,100% 100%,0px 100%)!important;
+}
\ No newline at end of file
diff --git a/mip-fx-flying-carpet/package.json b/mip-fx-flying-carpet/package.json
new file mode 100644
index 00000000..d1dee5ed
--- /dev/null
+++ b/mip-fx-flying-carpet/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-fx-flying-carpet",
+ "version": "1.0.1",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-gallery/README.md b/mip-gallery/README.md
index b6b42ed3..ef272c49 100644
--- a/mip-gallery/README.md
+++ b/mip-gallery/README.md
@@ -6,7 +6,7 @@ mip-gallery 康网图库链接
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-gallery/mip-gallery.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-gallery/mip-gallery.js
## 示例
diff --git a/mip-game-recommend/README.md b/mip-game-recommend/README.md
index 1687d9e4..42f66352 100644
--- a/mip-game-recommend/README.md
+++ b/mip-game-recommend/README.md
@@ -6,7 +6,7 @@ mip-game-recommend 游戏下载组件
----|----
类型|通用
支持布局|fill,container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-game-recommend/mip-game-recommend.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-game-recommend/mip-game-recommend.js
## 示例
diff --git a/mip-game4399-download/README.md b/mip-game4399-download/README.md
index e82b4bdc..ad3095bd 100644
--- a/mip-game4399-download/README.md
+++ b/mip-game4399-download/README.md
@@ -5,7 +5,7 @@ mip-game4399-download 实现了简单的游戏下载控件。
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-game4399-download/mip-game4399-download.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-game4399-download/mip-game4399-download.js
## 示例
diff --git a/mip-gfads/README.md b/mip-gfads/README.md
index 74a5df26..ad54941b 100644
--- a/mip-gfads/README.md
+++ b/mip-gfads/README.md
@@ -6,7 +6,7 @@ mip-gfads 汇米广告联盟广告投放插件
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-gfads/mip-gfads.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-gfads/mip-gfads.js
## 示例
diff --git a/mip-global-script/README.md b/mip-global-script/README.md
new file mode 100644
index 00000000..7e548c77
--- /dev/null
+++ b/mip-global-script/README.md
@@ -0,0 +1,39 @@
+# mip-global-script
+
+mip-global-script 页面逻辑公共脚本
+
+标题|内容
+----|----
+类型|业务
+所需脚本|https://c.mipcdn.com/static/v1/mip-global-script/mip-global-script.js
+
+## 示例
+
+### 基本用法
+```html
+
+
+```
+
+## 注意事项
+运行需要html中的json数据支持,如上所示;在对应编辑名下,hmToken为必填项对应该编辑mip-stats-baidu的token值,xtime为选填值,如果只统计某时间节点后的数据就加上“年/月/日”格式的日期。
+
diff --git a/mip-global-script/mip-global-script.js b/mip-global-script/mip-global-script.js
new file mode 100644
index 00000000..947a3ba2
--- /dev/null
+++ b/mip-global-script/mip-global-script.js
@@ -0,0 +1,56 @@
+/**
+ * @file 页面逻辑公共脚本
+ * @description 实时新增优化
+ * @author Zhou
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var global = {
+ hideList: function (obj, option, nub) {
+ $(obj).each(function () {
+ if ($(this).find(option).length < nub) {
+ $(this).remove();
+ }
+
+ });
+ },
+ tongJi: function () {
+ var webDatetime = $('#down-href').attr('dateTime');
+ var webUsername = $('#down-href').attr('username');
+ try{//从模板页获取编辑统计token的json数据
+ var bjjson = document.querySelector('script[type="application/json"]');
+ var bjdata = JSON.parse(bjjson.textContent.toString().replace(/[\s\b\t]/g, ''));
+ }
+ catch(e){
+ console.log(e);
+ }
+ if (typeof webDatetime !== 'undefined' && webUsername !== '' && bjdata) {
+ var hmToken = '';
+ if(bjdata[0][webUsername]){
+ if(bjdata[0][webUsername].xtime){
+ var date1 = new Date(bjdata[0][webUsername].xtime);
+ var date2 = new Date(webDatetime);
+ if (date1.getTime() < date2.getTime()) {
+ hmToken = bjdata[0][webUsername].hmToken;
+ }
+ }else{
+ hmToken = bjdata[0][webUsername].hmToken;
+ }
+ }
+ if (hmToken !== '') {
+ $('body').append('');
+ }
+ }
+
+ },
+ init: function () {
+ this.hideList('.hidelist', 'li', 1); // 优化隐藏
+ this.tongJi(); // 编辑统计
+ }
+ };
+ customElem.prototype.build = function () {
+ global.init();
+ };
+ return customElem;
+});
diff --git a/mip-global-script/package.json b/mip-global-script/package.json
new file mode 100644
index 00000000..69a901c4
--- /dev/null
+++ b/mip-global-script/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-global-script",
+ "version": "1.1.0",
+ "author": {
+ "name": "lj",
+ "email": "lj@952.com",
+ "url": "https://www.pc6.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ },
+ "contributors":[{"name":"Zhou","email":"zhouhappy01@gmail.com"}]
+}
diff --git a/mip-gudindgufeng/README.md b/mip-gudindgufeng/README.md
index 3497aad8..0d3abac9 100644
--- a/mip-gudindgufeng/README.md
+++ b/mip-gudindgufeng/README.md
@@ -6,7 +6,7 @@ mip-gudindgufeng gufengmh.com站点投放汇米广告固定位广告插件
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-gudindgufeng/mip-gudindgufeng.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-gudindgufeng/mip-gudindgufeng.js
## 示例
diff --git a/mip-gudingwzshipin/README.md b/mip-gudingwzshipin/README.md
index d5fb47e7..e60dfa08 100644
--- a/mip-gudingwzshipin/README.md
+++ b/mip-gudingwzshipin/README.md
@@ -6,7 +6,7 @@ mip-gudingwzshipin 用于wzshipin.com站点投放汇米广告固定位广告的
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-gudingwzshipin/mip-gudingwzshipin.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-gudingwzshipin/mip-gudingwzshipin.js
## 示例
diff --git a/mip-gxdxw/README.md b/mip-gxdxw/README.md
index c3dc5805..64d69fa4 100644
--- a/mip-gxdxw/README.md
+++ b/mip-gxdxw/README.md
@@ -6,7 +6,7 @@ mip-gxdxw 用于mip.gxdxw.cn站点投放汇米上悬浮广告的组件
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-gxdxw/mip-gxdxw.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-gxdxw/mip-gxdxw.js
## 示例
diff --git a/mip-haixue-contact/README.md b/mip-haixue-contact/README.md
new file mode 100644
index 00000000..20928c4c
--- /dev/null
+++ b/mip-haixue-contact/README.md
@@ -0,0 +1,22 @@
+# mip-haixue-contact
+
+mip-haixue-contact 用来支持嗨学网咨询功能
+
+标题|内容
+----|----
+类型|通用
+支持布局|container
+所需脚本|https://c.mipcdn.com/static/v1/mip-haixue-contact/mip-haixue-contact.js
+
+
+## 示例
+
+### 基本用法
+```html
+
+
+
+```
+
+
+
diff --git a/mip-haixue-contact/mip-haixue-contact.js b/mip-haixue-contact/mip-haixue-contact.js
new file mode 100644
index 00000000..0e4c2764
--- /dev/null
+++ b/mip-haixue-contact/mip-haixue-contact.js
@@ -0,0 +1,173 @@
+/**
+ * @file mip-haixue-contact 用来支持嗨学网咨询功能
+ * @author qishunli(qishunli@baidu.com)
+ * @time 16-12-08
+ */
+define(function (require) {
+ var NTKF;
+ function loadJs(url) {
+ var myHead = document.getElementsByTagName('head').item(0);
+ var myScript = document.createElement('script');
+ myScript.type = 'text/javascript';
+ myScript.src = url;
+ myHead.appendChild(myScript);
+ }
+
+ loadJs('https://dl.ntalker.com/js/xn6/ntkfstat.js?siteid=kf_9778');
+
+ var customElement = require('customElement').create();
+
+ /**
+ * 构造元素,只会运行一次
+ */
+ customElement.prototype.build = function () {
+ // TODO
+ var me = this;
+ var element = me.element;
+ var siteid = element.getAttribute('siteid');
+ var kfid = element.getAttribute('kfid');
+ kfid = kfid ? kfid : 'kf_9778_1481685802962';
+ var NTKF_PARAM = {
+ siteid: (siteid || 'kf_9778'),
+ settingid: kfid,
+ uid: '',
+ uname: ''
+ };
+ (function (window) {
+ var headElement;
+ var node;
+ var scripturl;
+ var script;
+
+ if (typeof NTKF_PARAM === 'undefined') {
+ tmpDebug('ERROR: NTKF_PARAM is not defined');
+ return false;
+ }
+
+ if (!NTKF_PARAM.siteid) {
+ tmpDebug('ERROR: NTKF_PARAM.siteid is not defined');
+ return false;
+ }
+ script = document.getElementsByTagName('script');
+ for (var i = 0; i < script.length; i++) {
+ if (script[i].src && /(ntkfstat|ntkf|nt)\.js/gi.test(script[i].src)) {
+ scripturl = script[i].src.substr(0, script[i].src.lastIndexOf('/'));
+ funcRemoveNode(script[i]);
+ break;
+ }
+ }
+ if (!scripturl) {
+ tmpDebug('ERROR: script server url get failure.');
+ return false;
+ }
+
+ headElement = document.getElementsByTagName('head')[0];
+ node = document.createElement('script');
+ node.type = 'text/javascript';
+ node.async = 'async';
+ node.charset = 'utf-8';
+ node.src = scripturl + '/ntkfstat.js?siteid=' + NTKF_PARAM.siteid;
+ headElement.insertBefore(node, headElement.lastChild);
+
+
+ function tmpDebug(message) {
+ if (typeof console !== undefined) {
+ return;
+ }
+ if (typeof console.error !== undefined) {
+ console.error(message);
+ }
+ if (typeof console.info !== undefined) {
+ console.info(message);
+ }
+ }
+ function funcRemoveNode(element) {
+ var removeComplete = false;
+
+ for (var methodName in element) {
+ try {
+ if (typeof element[methodName] === 'function') {
+ element[methodName] = null;
+ }
+ } catch (e) {
+ tmpDebug('clear element function');
+ }
+ }
+ if (element.parentNode) {
+ try {
+ element.parentNode.removeChild(element);
+ removeComplete = true;
+ } catch (e) {
+
+ }
+ }
+ if (!removeComplete) {
+ var tElement = document.createElement('DIV');
+ tElement.appendChild(element);
+ tElement.innerHTML = '';
+ if (tElement.parentNode) {
+ try {
+ tElement.parentNode.removeChild(tElement);
+ removeComplete = true;
+ } catch (e1) {
+
+ }
+ }
+ }
+ return removeComplete;
+ }
+ })(window);
+
+
+
+ function xiaoNengChat(b) {
+ getData();
+ NTKF.im_openInPageChat(b);
+ }
+
+ function getData() {
+ NTKF = window.NTKF || {};
+ var k;
+ var l;
+ var h;
+ var j;
+ var m;
+ var i;
+ m = window.location.href;
+ var n = null;
+ if (n) {
+ k = n.split(',')[0];
+ }
+ l = 'YJ_bjh';
+ h = 'C7522C73-FC40-0001-A872-14C016BD5770';
+ i = NTKF ? NTKF.global.pcid : '';
+ j = '';
+ if (k) {
+ j = j + 'quot;customerIdquot;:quot;' + k + 'quot;,';
+ }
+ if (h) {
+ j = j + 'quot;cookieIdquot;:quot;' + h + 'quot;,';
+ }
+ if (l) {
+ j = j + 'quot;webFromquot;:quot;' + l + 'quot;,';
+ }
+ if (m) {
+ j = j + 'quot;viewUrlquot;:quot;' + m + 'quot;,';
+ }
+ if (i) {
+ j = j + 'quot;ntkfClientIdquot;:quot;' + i + 'quot;,';
+ }
+ if (k) {
+ NTKF.im_updatePageInfo({uid: k, erpparam: j});
+ }
+ else {
+ NTKF.im_updatePageInfo({uid: '', erpparam: j});
+ }
+ }
+ $(element).on('click', '.mip-contact', function () {
+ xiaoNengChat(kfid);
+ });
+
+ };
+ return customElement;
+});
diff --git a/mip-haixue-contact/package.json b/mip-haixue-contact/package.json
new file mode 100644
index 00000000..639fe43b
--- /dev/null
+++ b/mip-haixue-contact/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-haixue-contact",
+ "version": "1.0.0",
+ "description": "用来支持嗨学网咨询功能",
+ "contributors": [
+ {
+ "name": "qishunli",
+ "email": "shunliqi@163.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-haixue-register/README.md b/mip-haixue-register/README.md
new file mode 100644
index 00000000..3e965e6c
--- /dev/null
+++ b/mip-haixue-register/README.md
@@ -0,0 +1,22 @@
+# mip-haixue-register
+
+mip-haixue-register 用来支持嗨学网手机注册功能
+
+标题|内容
+----|----
+类型|通用
+支持布局|container
+所需脚本|https://c.mipcdn.com/static/v1/mip-haixue-register/mip-haixue-register.js
+
+
+## 示例
+
+### 基本用法
+```html
+
+ 自定义内容,可以嵌套其他组件
+
+```
+
+
+
diff --git a/mip-haixue-register/mip-haixue-register.js b/mip-haixue-register/mip-haixue-register.js
new file mode 100644
index 00000000..87d9e54f
--- /dev/null
+++ b/mip-haixue-register/mip-haixue-register.js
@@ -0,0 +1,56 @@
+/**
+ * @file mip-haixue-register 组件
+ * @author qishunli(qishunli@baidu.com)
+ * @time 16-12-08
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var customElement = require('customElement').create();
+ customElement.prototype.build = function () {
+ // TODO
+ var me = this;
+ var element = me.element;
+ var MOBILE_REG = /^((13[0-9])|(17[0-9])|(14[0-9])|(15[0-9])|(18[0-9]))\d{8}$/;
+ var url = 'http://haixue.com/leaveMessage/saveCrmLeaveMessage.do';
+ var tpl
+ = ''
+ + '
你已经提交成功!
'
+ + '
嗨学网会尽快联系你!
'
+ + '
'
+ + '知道了'
+ + '
'
+ + '
';
+ $('body').append(tpl);
+
+ $('body').on('click', '.m-popClose', function () {
+ $('.m-popEnd').hide(200);
+ });
+
+ function loadJs(url) {
+ var myHead = document.getElementsByTagName('head').item(0);
+ var myScript = document.createElement('script');
+ myScript.type = 'text/javascript';
+ myScript.src = url;
+ myHead.appendChild(myScript);
+ }
+ $(element).on('click', '.form-button', function () {
+ var value = $('.form-input').val();
+ var formValue = 'mobile=' + value;
+ var webFrom = 'YJ_bjh';
+ if (MOBILE_REG.test(value)) {
+ loadJs(
+ url
+ + '?' + formValue
+ + '&categoryId=' + '9' + '&childCategoryId='
+ + '100051' + '&webFrom=' + webFrom);
+ $('.m-popEnd').show(200);
+ }
+ else {
+ alert('手机号格式不正确!');
+ }
+ });
+ };
+
+ return customElement;
+});
diff --git a/mip-haixue-register/mip-haixue-register.less b/mip-haixue-register/mip-haixue-register.less
new file mode 100644
index 00000000..7dbac7cd
--- /dev/null
+++ b/mip-haixue-register/mip-haixue-register.less
@@ -0,0 +1,80 @@
+/**
+ * @file mip-register样式文件
+ */
+
+.form-input {
+ width: 80%;
+ height: 30px;
+ margin: 0 auto;
+ display: block;
+ font-size: 20px;
+ line-height: 30px;
+ text-align: center;
+ font-family: "微软雅黑";
+ padding: 4px 0 4px 0;
+ background: #fff;
+ margin-bottom: 4%;
+ color: #555;
+ border-radius: 3px;
+ border: none;
+}
+.form-button {
+ width: 80%;
+ height: 38px;
+ margin: 0 auto;
+ display: block;
+ font-size: 20px;
+ font-family: "微软雅黑";
+ color: #fff;
+ background: #ff7200;
+ border-radius: 3px;
+ margin-bottom: 4px;
+ border: none;
+}
+.m-btnB {
+ text-align: center;
+ margin-top: 10px;
+}
+.m-btnB .m-btn {
+ display: inline-block;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ width: 100%;
+ background: #ff7e00;
+ color: #fff;
+ font-size: 18px;
+ font-weight: 600;
+ cursor: pointer;
+}
+.m-btnB .m-btn:hover {
+ background: #ffaa00;
+ text-decoration: none;
+}
+.m-pop {
+ display: none;
+ position: fixed;
+ background: #fff;
+ width: 280px;
+ height: 150px;
+ border: 1px solid #ff7e00;
+ z-index: 99;
+ left: 50%;
+ top: 50%;
+ margin-left: -140px;
+ text-align: center;
+}
+.m-pop h4 {
+ font-size: 25px;
+ margin: 15px 0;
+}
+.m-pop p {
+ line-height: 25px;
+ font-size: 17px;
+}
+.m-btnB .m-btn2 {
+ padding: 10px 8px;
+ width: 82px;
+ font-size: 22px;
+ margin-right: 5px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/mip-haixue-register/package.json b/mip-haixue-register/package.json
new file mode 100644
index 00000000..dc3c1b81
--- /dev/null
+++ b/mip-haixue-register/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-haixue-register",
+ "version": "1.0.0",
+ "description": "用来支持嗨学网手机注册功能",
+ "contributors": [
+ {
+ "name": "qishunli",
+ "email": "shunliqi@163.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-hc-popup/README.md b/mip-hc-popup/README.md
index 048e58f2..f18b3ede 100644
--- a/mip-hc-popup/README.md
+++ b/mip-hc-popup/README.md
@@ -6,7 +6,7 @@ mip-hc-popup 弹出框提示组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-hc-popup/mip-hc-popup.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-hc-popup/mip-hc-popup.js
## 示例
diff --git a/mip-hc360-p4p/README.md b/mip-hc360-p4p/README.md
index 9d2d25a7..a2fc5da9 100644
--- a/mip-hc360-p4p/README.md
+++ b/mip-hc360-p4p/README.md
@@ -6,7 +6,7 @@ mip-hc360-p4p p4p商机扣费
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-hc360-p4p/mip-hc360-p4p.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-hc360-p4p/mip-hc360-p4p.js
## 示例
diff --git a/mip-hc360-performace/README.md b/mip-hc360-performace/README.md
index 3330bc91..78cb2d77 100644
--- a/mip-hc360-performace/README.md
+++ b/mip-hc360-performace/README.md
@@ -6,7 +6,7 @@ mip-hc360-performace mip栈发送曝光数据
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-hc360-performace/mip-hc360-performace.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-hc360-performace/mip-hc360-performace.js
## 示例
diff --git a/mip-header-fixed/README.md b/mip-header-fixed/README.md
index b6f6c5f3..12403c34 100644
--- a/mip-header-fixed/README.md
+++ b/mip-header-fixed/README.md
@@ -6,7 +6,7 @@ mip-header-fixed 主要是组件的position:fixed后,创建一个元素,获
----|----
类型|通用
支持布局|无
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-header-fixed/mip-header-fixed.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-header-fixed/mip-header-fixed.js
## 示例
diff --git a/mip-header-qiehuan/README.md b/mip-header-qiehuan/README.md
index 5aa6165b..976c9ca3 100644
--- a/mip-header-qiehuan/README.md
+++ b/mip-header-qiehuan/README.md
@@ -6,7 +6,7 @@ mip-header-qiehuan 点击一个元素,隐藏此元素,显示另一个元素,实
----|----
类型|通用
支持布局|fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-header-qiehuan/mip-header-qiehuan.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-header-qiehuan/mip-header-qiehuan.js
## 示例
diff --git a/mip-hk-call/README.md b/mip-hk-call/README.md
new file mode 100644
index 00000000..1f63dcd4
--- /dev/null
+++ b/mip-hk-call/README.md
@@ -0,0 +1,52 @@
+# mip-hk-call
+
+好看调起客户端
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-call/mip-hk-call.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 用于调起好看客户端。
+
+```html
+
+ 立即下载
+
+```
+
+## 属性
+
+### type
+
+说明:客户端对应页面类型
+必选项:是
+类型:字符串
+取值范围:article,topic,video,gallery,beauty,activity
+
+### urlKey
+
+说明:客户端所需参数
+必选项:是
+类型:字符串
+
+### apk
+
+说明:不同位置对应不同下载渠道
+必选项:否
+类型:数字
+
+### page
+
+说明:不同页面对应不同统计参数
+必选项:否
+类型:字符串
+
+### act
+
+说明:不同位置对应不同统计参数
+必选项:否
+类型:字符串
diff --git a/mip-hk-call/mip-hk-call.js b/mip-hk-call/mip-hk-call.js
new file mode 100644
index 00000000..d5568d07
--- /dev/null
+++ b/mip-hk-call/mip-hk-call.js
@@ -0,0 +1,201 @@
+/**
+ * @file 百度好看调起客户端
+ * @author liujunqiu
+ * @time 2016.11.29
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var util = require('util');
+ var platform = util.platform;
+
+ var androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1015351w.apk';
+ var iosLink = 'https://itunes.apple.com/cn/app/id1092031003?mt=8';
+ /**
+ * 获取手机系统及版本号
+ * return object: os 获取机型; osv 获取机型版本
+ */
+ var brower = (function () {
+ // 系统
+ var isiPhone = new RegExp('iPhone|iPad|iPod|iPh|iPd|iOS', 'i');
+ var isAndroid = new RegExp('Android|Linux', 'i');
+ // 版本号
+ var iosVer = new RegExp('^.*OS\\s(\\d.*?)\\s.*$', 'i');
+ var androidVer = new RegExp('^.*Android\\s(.*?);.*$', 'i');
+
+ var userAgent = window.navigator.userAgent;
+
+ var brower = {
+ os: function () {
+ if (platform.isIos()) {
+ return 'ios';
+ }
+
+ return 'android';
+ },
+ osv: function () {
+ if (isAndroid.test(userAgent)) {
+ return userAgent.replace(androidVer, '$1');
+ }
+ else if (isiPhone.test(userAgent)) {
+ return userAgent.replace(iosVer, '$1').replace(/_/, '.');
+ }
+
+ return '';
+ }
+ };
+
+ return brower;
+ })();
+ // 调起类型
+ // var callType = ['article', 'topic', 'video', 'gallery', 'beauty', 'activity'];
+ var userAgent = navigator.userAgent;
+ var isIos9 = function () {
+ if ((userAgent.match(/iPhone/i) || userAgent.match(/iPod/i))) {
+ return Boolean(userAgent.match(/OS (9|10)_\d[_\d]* like Mac OS X/i));
+ }
+
+ return false;
+ };
+ var installApp = 0;
+
+ function firstInviewCallback() {
+ var element = this.element;
+
+ var type = $(element).attr('type');
+ var urlKey = $(element).attr('urlKey');
+ var apk = $(element).attr('apk');
+ var page = $(element).attr('page');
+ var pos = '';
+
+ if (typeof apk !== 'undefined' && brower.os() !== 'ios') {
+ if (apk === '1') { // 首页底部浮层
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1018504p.apk';
+ page = 'index_hk';
+ pos = '1018504p';
+ }
+ else if (apk === '2') { // 详情页查看评论图集
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1018504q.apk';
+ page = page;
+ pos = '1018504q';
+ }
+ else if (apk === '3') { // 详情页精彩推荐
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1018504r.apk';
+ page = page;
+ pos = '1018504r';
+ }
+ else if (apk === '4') { // 个人中心
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1018504s.apk';
+ page = 'erji_index_level';
+ pos = '1018504s';
+ }
+ else if (apk === '5') { // 详情页顶部浮层
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan1018504p.apk';
+ page = page;
+ pos = '1018504p';
+ }
+ else if (parseInt(apk, 10) > 5) {
+ androidLink = 'http://dl.hao123.com/waphao123/tn_apk/baiduhaokan' + apk + '.apk';
+ page = page;
+ pos = apk + '&act=' + $(element).attr('act');
+ }
+ }
+
+ var jump = 'http://wapsite.baidu.com/haokan/' + (type === 'article' ? 'doc' : type)
+ + '/detail?url_key=' + encodeURIComponent(urlKey);
+ var schemaUrl = '';
+ var appLink = '';
+ if (type !== '' && urlKey !== '') {
+ schemaUrl = type + '?url=' + encodeURIComponent(urlKey);
+ }
+ if (brower.os() === 'ios') {
+ appLink = iosLink;
+ }
+ else {
+ appLink = androidLink;
+ }
+ var schema = 'baiduhaokan://' + schemaUrl;
+
+ // 判断微信
+ var isWx = userAgent.match(/MicroMessenger/i);
+ if (isWx) {
+ appLink = jump;
+ }
+
+ var timer = '';
+ $(element).find('.J_app_call').attr('href', jump);
+ if (!isIos9()) {
+ $(element).find('.J_app_call').bind('click', function (e) {
+ if (typeof apk !== 'undefined' && brower.os() !== 'ios') {
+ new Image().src = '/tj.gif?page=' + page + '&pos=' + pos + '&t=' + new Date().getTime();
+ }
+
+ var nowTime = Date.now();
+ // 安卓检测到安装才调起客户端
+ if (brower.os() === 'android' && installApp === 1) {
+ window.location.href = schema;
+
+ if (
+ (brower.os() === 'android' && installApp !== 1)
+ || (brower.os() === 'android' && /baidubrowser/.test(userAgent))
+ ) {
+ timer = setTimeout(function () {
+ if (Date.now() - nowTime < 1200) {
+ window.location.href = appLink;
+ }
+ clearTimeout(timer);
+ }, 1000);
+ }
+ }
+ else if (brower.os() === 'ios') {
+ window.location.href = schema;
+ timer = setTimeout(function () {
+ if (Date.now() - nowTime < 1200) {
+ window.location.href = appLink;
+ }
+ clearTimeout(timer);
+ }, 1000);
+ }
+ else {
+ window.location.href = appLink;
+ }
+
+ return false;
+ });
+ }
+ }
+
+ function getJson(url, callback, cbName) {
+ var cbFunName = cbName || '_Hao' + Math.floor(1e4 * Math.random());
+ var scriptElm = document.createElement('script');
+ scriptElm.src = url + '&cb=' + cbFunName + '&t=' + new Date().getTime();
+ scriptElm.type = 'text/javascript';
+ scriptElm.setAttribute('charset', 'utf-8');
+ document.getElementsByTagName('head')[0].appendChild(scriptElm);
+ window[cbFunName] = function (data) {
+ if (typeof callback === 'function') {
+ callback(data);
+ }
+ var tmpTimer = setTimeout(function () {
+ document.getElementsByTagName('head')[0].removeChild(scriptElm);
+ clearTimeout(tmpTimer);
+ }, 20);
+ };
+ }
+
+ if (brower.os() === 'android') {
+ getJson('http://127.0.0.1:41333/ping/?callback=ping', function (ret) {
+ if (ret.error === 0) {
+ installApp = 1;
+ if (!/baidubrowser/.test(navigator.userAgent)) {
+ $('.J_dl_content').html('打开');
+ }
+ }
+ }, 'ping');
+ }
+
+ customElem.prototype.firstInviewCallback = firstInviewCallback;
+
+ return customElem;
+});
diff --git a/mip-hk-call/package.json b/mip-hk-call/package.json
new file mode 100644
index 00000000..7790aecd
--- /dev/null
+++ b/mip-hk-call/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-call",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-hk-fcvideo/README.md b/mip-hk-fcvideo/README.md
new file mode 100644
index 00000000..d9c89acb
--- /dev/null
+++ b/mip-hk-fcvideo/README.md
@@ -0,0 +1,17 @@
+# mip-hk-fcvideo
+
+支持好看详情页凤巢视频播放一段时间提示下载客户端
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-fcvideo/mip-hk-fcvideo.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 用于视频播放一段时间提示下载客户端。
+
+```html
+
+```
diff --git a/mip-hk-fcvideo/mip-hk-fcvideo.js b/mip-hk-fcvideo/mip-hk-fcvideo.js
new file mode 100644
index 00000000..d7ec3691
--- /dev/null
+++ b/mip-hk-fcvideo/mip-hk-fcvideo.js
@@ -0,0 +1,33 @@
+/**
+ * 百度好看凤巢视频落地页
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function build() {
+ var element = this.element;
+
+ var videoObj = $('#J_d_title').find('video');
+
+ if (typeof videoObj !== 'undefined') {
+ videoObj[0].addEventListener('timeupdate', function () {
+ var videoDuration = videoObj[0].duration;
+ var maxVideoTimes = 30;
+ if (videoDuration < 60) {
+ maxVideoTimes = Math.round(videoDuration*0.5);
+ }
+ if (videoObj[0].currentTime > maxVideoTimes) {
+ videoObj[0].pause();
+ videoObj.hide();
+ $('#J_video_fc_dl').show();
+ }
+ }, false);
+ }
+ };
+
+ customElem.prototype.build = build;
+
+ return customElem;
+});
diff --git a/mip-hk-fcvideo/package.json b/mip-hk-fcvideo/package.json
new file mode 100644
index 00000000..59ed3198
--- /dev/null
+++ b/mip-hk-fcvideo/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-fcvideo",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-hk-feed/README.md b/mip-hk-feed/README.md
new file mode 100644
index 00000000..fab7e795
--- /dev/null
+++ b/mip-hk-feed/README.md
@@ -0,0 +1,26 @@
+# mip-hk-feed
+
+好看feed流
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-feed/mip-hk-feed.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 用于好看信息流。
+
+```
+
+```
+
+## 属性
+
+### type
+
+说明:信息流类型
+必选项:否
+类型:字符串
+取值范围:video
diff --git a/mip-hk-feed/mip-hk-feed.js b/mip-hk-feed/mip-hk-feed.js
new file mode 100644
index 00000000..fc1c7df8
--- /dev/null
+++ b/mip-hk-feed/mip-hk-feed.js
@@ -0,0 +1,195 @@
+/**
+ * @file 百度好看信息流
+ * @author liujunqiu
+ * @time 2016.11.29
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ var ajaxUrl = '//haokan.baidu.com/index/more?tag=';
+ var tag = 'rec';
+ var page = 1;
+ var winHeight = $(window).height();
+ var loading = false;
+ var isEnd = false;
+ var loadingHtml = [
+ '',
+ '',
+ '精选推荐中',
+ '
'
+ ].join('');
+
+ function build() {
+ var element = this.element;
+
+ $(element).append(loadingHtml);
+ var type = $(element).attr('type');
+ if (type === 'video') {
+ tag = 'video';
+ }
+
+ $(window).bind('scroll', function () {
+ var loadingTop = $(element).find('#J_loading').offset().top;
+ var scrollTop = $(window).scrollTop();
+
+ if (!isEnd && loadingTop - scrollTop - winHeight < 200) {
+ loadData(element);
+ }
+ });
+
+ loadData(element);
+ }
+
+ function loadData(element) {
+ if (loading) {
+ return false;
+ }
+ loading = true;
+
+ var tmpUrl = ajaxUrl + tag
+ + '×tamp=' + new Date().getTime()
+ + '&pn=' + page;
+ $.ajax({
+ url: tmpUrl,
+ type: 'GET',
+ dataType: 'json',
+ success: function (data) {
+ loading = false;
+ if (data.feed && data.feed.datalist) {
+ // 图片HTTPS地址转换
+ for (var i = 0; i < data.feed.datalist.length; i++) {
+ if (data.feed.datalist[i].data.img.length > 0) {
+ var tmpImg = data.feed.datalist[i].data.img[0];
+ if (typeof tmpImg === 'object') {
+ data.feed.datalist[i].data.img[0].small = httpsTrans(tmpImg.small);
+ }
+ else {
+ data.feed.datalist[i].data.img[0] = httpsTrans(tmpImg);
+ }
+ }
+ }
+
+ var html = tmpl('J_list_tpl', data.feed.datalist);
+ $(element).find('#J_list').append(html);
+
+ page++;
+ }
+ else {
+ loading = false;
+
+ $(element).find('#J_loading').hide();
+ }
+ },
+ error: function () {
+ loading = false;
+ isEnd = true;
+ }
+ });
+ }
+
+ var tplCache = {};
+ function tmpl(str, data) {
+ var fn = !/\W/.test(str)
+ ? tplCache[str] = tplCache[str]
+ || tmpl(document.getElementById(str).innerHTML)
+ : new Function('obj',
+ 'var p=[],print=function(){p.push.apply(p,arguments);};'
+ + 'with(obj){p.push(\''
+ + str
+ .replace(/[\r\t\n]/g, ' ')
+ .split('<#').join('\t')
+ .replace(/((^|#>)[^\t]*)'/g, '$1\r')
+ .replace(/\t=(.*?)#>/g, '\',$1,\'')
+ .split('\t').join('\');')
+ .split('#>').join('p.push(\'')
+ .split('\r').join('\\\'')
+ + '\');}return p.join(\'\');');
+
+ return data ? fn(data) : fn;
+ }
+
+ // https链接转换
+ function httpsTrans(href) {
+ var map = {
+ 'http:\/\/www.baidu.com': 'https:\/\/www.baidu.com',
+ 'http:\/\/hm.baidu.com\/': 'https:\/\/hm.baidu.com\/',
+ 'http:\/\/m.hao123.com': 'https:\/\/m.hao123.com',
+ 'http:\/\/wap.hao123.com': 'https:\/\/wap.hao123.com',
+ 'http:\/\/www.hao123.com': 'https:\/\/www.hao123.com',
+ 'http:\/\/nsclick.baidu.com': 'https:\/\/gsp1.baidu.com\/8qUJcD3n0sgCo2Kml5_Y_D3',
+ 'http:\/\/static.tieba.baidu.com': 'https:\/\/gsp0.baidu.com\/5aAHeD3nKhI2p27j8IqW0jdnxx1xbK',
+ 'http:\/\/nssug.baidu.com': 'https:\/\/gsp1.baidu.com\/8qUZeT8a2gU2pMbgoY3K',
+ 'http:\/\/suggestion.baidu.com': 'https:\/\/gsp0.baidu.com\/5a1Fazu8AA54nxGko9WTAnF6hhy',
+ 'http:\/\/hdj.baidu.com': 'https:\/\/gsp1.baidu.com\/7LAAsjip0QIZ8tyhnq',
+ 'http:\/\/suez.baidu.com': '\/\/suez.baidu.com',
+ 'http:\/\/wapsite.baidu.com': 'https:\/\/gsp0.baidu.com\/6bMWfz351MgCo2Kml5_Y_D3',
+ 'http:\/\/ms.bdimg.com': 'https:\/\/gss2.bdstatic.com\/8_V1bjqh_Q23odCf',
+ 'http:\/\/img.baidu.com': 'https:\/\/gss3.bdstatic.com\/70cFsjip0QIZ8tyhnq',
+ 'http:\/\/s0.m.hao123img.com': 'https:\/\/gss0.bdstatic.com\/5eR1cXSg2QdV5wybn9fN2DJv',
+ 'http:\/\/s1.m.hao123img.com': 'https:\/\/gss0.bdstatic.com\/5eN1cXSg2QdV5wybn9fN2DJv',
+ 'http:\/\/s2.m.hao123img.com': 'https:\/\/gss0.bdstatic.com\/5eZ1cXSg2QdV5wybn9fN2DJv',
+ 'http:\/\/s3.m.hao123img.com': 'https:\/\/gss0.bdstatic.com\/5eV1cXSg2QdV5wybn9fN2DJv',
+ 'http:\/\/s0.hao123img.com': 'https:\/\/gss0.bdstatic.com\/5eR1dDebRNRTm2_p8IuM_a',
+ 'http:\/\/s1.hao123img.com': 'https:\/\/gss1.bdstatic.com\/5eN1dDebRNRTm2_p8IuM_a',
+ 'http:\/\/s2.hao123img.com': 'https:\/\/gss2.bdstatic.com\/5eZ1dDebRNRTm2_p8IuM_a',
+ 'http:\/\/t10.baidu.com': 'https:\/\/gss0.baidu.com\/6ONWsjip0QIZ8tyhnq',
+ 'http:\/\/t11.baidu.com': 'https:\/\/gss1.baidu.com\/6ONXsjip0QIZ8tyhnq',
+ 'http:\/\/t12.baidu.com': 'https:\/\/gss2.baidu.com\/6ONYsjip0QIZ8tyhnq',
+ 'http:\/\/datax.baidu.com': 'https:\/\/gsp0.baidu.com\/-LMSbS0a2gU2pMbgoY3K',
+ 'http:\/\/timg01.baidu-img.cn': 'https:\/\/gss3.bdstatic.com\/6Ls0a7b-KgQFm2e889WK1HF6hq',
+ 'http:\/\/tc1.baidu-1img.cn': 'https:\/\/gss0.baidu.com\/6LVXsjip0QIZ8Aqbn9fN2DC',
+ 'http:\/\/tc2.baidu-1img.cn': 'https:\/\/gss0.baidu.com\/6LVXsjip0QIZ8Aqbn9fN2DC',
+ 'http:\/\/ztd00.photos.bdimg.com': 'https:\/\/gss0.bdstatic.com\/yqACvGbaBA94lNC68IqT0jB-xx1xbK',
+ 'http:\/\/hiphotos.baidu.com': 'https:\/\/gss3.baidu.com\/7LsWdDW5_xN3otqbppnN2DJv',
+ 'http:\/\/himg.bdimg.com': 'https:\/\/gss1.bdstatic.com\/7Ls0a8Sm1A5BphGlnYG',
+ 'http:\/\/a.hiphotos.bdimg.com': 'https:\/\/gss0.bdstatic.com\/94o3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/b.hiphotos.bdimg.com': 'https:\/\/gss1.bdstatic.com\/9vo3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/c.hiphotos.bdimg.com': 'https:\/\/gss2.bdstatic.com\/9fo3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/d.hiphotos.bdimg.com': 'https:\/\/gss3.bdstatic.com\/-Po3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/e.hiphotos.bdimg.com': 'https:\/\/gss0.bdstatic.com\/-4o3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/f.hiphotos.bdimg.com': 'https:\/\/gss1.bdstatic.com\/-vo3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/g.hiphotos.bdimg.com': 'https:\/\/gss2.bdstatic.com\/-fo3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/h.hiphotos.bdimg.com': 'https:\/\/gss2.bdstatic.com\/7Po3dSag_xI4khGkpoWK1HF6hhy',
+ 'http:\/\/bdimg.share.baidu.com': 'https:\/\/gss1.baidu.com\/9rA4cT8aBw9FktbgoI7O1ygwehsv',
+ 'http:\/\/timg01.baidu-2img.cn': 'https:\/\/gss0.baidu.com\/6Ls0a7b-KgQFm2e886qO_jowehu',
+ 'http:\/\/a.hiphotos.baidu.com': 'https:\/\/gss0.baidu.com\/94o3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/b.hiphotos.baidu.com': 'https:\/\/gss1.baidu.com\/9vo3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/c.hiphotos.baidu.com': 'https:\/\/gss3.baidu.com\/9fo3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/d.hiphotos.baidu.com': 'https:\/\/gss0.baidu.com\/-Po3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/e.hiphotos.baidu.com': 'https:\/\/gss1.baidu.com\/-4o3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/f.hiphotos.baidu.com': 'https:\/\/gss2.baidu.com\/-vo3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/g.hiphotos.baidu.com': 'https:\/\/gss3.baidu.com\/-fo3dSag_xI4khGko9WTAnF6hhy',
+ 'http:\/\/h.hiphotos.baidu.com': 'https:\/\/gss0.baidu.com\/7Po3dSag_xI4khGko9WTAnF6hhy'
+ };
+
+ if (/^http/.test(href)) {
+ var inMap = 0;
+
+ for (var key in map) {
+ if (map.hasOwnProperty(key)) {
+ var tmpReg = new RegExp(key, 'i');
+
+ if (tmpReg.test(href)) {
+ href = href.replace(tmpReg, map[key]);
+
+ inMap = 1;
+
+ break;
+ }
+ }
+ }
+
+ if (inMap === 0) {
+ href = 'https://' + href;
+ }
+ }
+
+ return href;
+ }
+
+ customElem.prototype.build = build;
+
+ return customElem;
+});
diff --git a/mip-hk-feed/package.json b/mip-hk-feed/package.json
new file mode 100644
index 00000000..0c7b8466
--- /dev/null
+++ b/mip-hk-feed/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-feed",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-hk-keep/README.md b/mip-hk-keep/README.md
new file mode 100644
index 00000000..5ae0f2fb
--- /dev/null
+++ b/mip-hk-keep/README.md
@@ -0,0 +1,32 @@
+# mip-hk-keep
+
+好看详情页下载app安装打开对应详情页接口
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-keep/mip-hk-keep.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 详情页下载app安装打开对应详情页接口。
+
+```html
+
+```
+
+## 属性
+
+### type
+
+说明:客户端对应页面类型
+必选项:是
+类型:字符串
+取值范围:article,topic,video,gallery,beauty,activity
+
+### urlKey
+
+说明:客户端所需参数
+必选项:是
+类型:字符串
diff --git a/mip-hk-keep/mip-hk-keep.js b/mip-hk-keep/mip-hk-keep.js
new file mode 100644
index 00000000..09e2bce9
--- /dev/null
+++ b/mip-hk-keep/mip-hk-keep.js
@@ -0,0 +1,30 @@
+/**
+ * 百度好看详情页下载app安装打开对应详情页接口
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function build() {
+ var element = this.element;
+
+ var type = $(element).attr('type');
+ var urlKey = encodeURIComponent($(element).attr('urlKey'));
+
+ $.ajax({
+ url: '//haokan.baidu.com/h5/scene/keep/',
+ data: {
+ action: encodeURIComponent('baiduhaokan://' + type +'?url=' + urlKey)
+ },
+ dataType: 'json',
+ type: 'GET',
+ success: function () {},
+ error: function () {}
+ });
+ };
+
+ customElem.prototype.build = build;
+
+ return customElem;
+});
diff --git a/mip-hk-keep/package.json b/mip-hk-keep/package.json
new file mode 100644
index 00000000..4fa0a756
--- /dev/null
+++ b/mip-hk-keep/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-keep",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-hk-share/README.md b/mip-hk-share/README.md
new file mode 100644
index 00000000..9e016eea
--- /dev/null
+++ b/mip-hk-share/README.md
@@ -0,0 +1,27 @@
+# mip-hk-share
+
+好看分享插件
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-share/mip-hk-share.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 支持分享QQ和微博。
+
+```html
+
+
+
+```
diff --git a/mip-hk-share/mip-hk-share.js b/mip-hk-share/mip-hk-share.js
new file mode 100644
index 00000000..52c785a7
--- /dev/null
+++ b/mip-hk-share/mip-hk-share.js
@@ -0,0 +1,83 @@
+/**
+ * 百度好看分享
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function build() {
+ var element = this.element;
+ var config = getConfig();
+
+ $(element).find('#J_detail_share').click(function () {
+ $(this).find('.J_share').show();
+ });
+
+ $(element).find('#J_detail_share').bind('touchstart', function (event) {
+ event.stopPropagation();
+ });
+
+ $('body').bind('touchstart', function () {
+ $(element).find('.J_share').hide();
+ });
+
+ $(element).find('#J_share_close').click(function (event) {
+ $(element).find('.J_share').hide();
+
+ event.stopPropagation();
+ });
+
+ $(element).find('.J_share a').bind('click', function () {
+ var type = $(this).data('type');
+ var op = {
+ url: encodeURIComponent(window.location.href),
+ title: config.shareTitle,
+ content: config.shareTitle,
+ picurl: config.sharePic
+ };
+
+ if (type === 'wb') {
+ var strShare = 'http://service.weibo.com/share/share.php?language=zh_cn&title=' + op.title;
+ strShare += '&url=' + op.url + '&content=utf-8&sourceUrl=' + op.url + '&pic=' + op.picurl;
+
+ new Image().src = config.tj + 'share&t=' + new Date().getTime();
+ }
+ if (type === 'qq') {
+ var strShare = 'http://qzs.qzone.qq.com/open/connect/widget/mobile/qzshare/index.html?title=' + op.title;
+ strShare += '&summary=' + op.content + '&url=' + op.url + '&imageUrl=' + op.picurl;
+ strShare += '&loginpage=loginindex.html&logintype=qzone&page=qzshare.html';
+
+ new Image().src = config.tj + 'share&t=' + new Date().getTime();
+ }
+
+ window.location.href = strShare;
+
+ return false;
+ });
+ };
+
+ function getConfig() {
+ var defOpt = {
+ shareTitle: '百度好看-发现我的好看',
+ shareContent: '百度好看-发现我的好看',
+ sharePic: encodeURIComponent("http://s0.haokan.bdimg.com/static/haokan/h5/img/ios-180_15c58b9.png"),
+ tj: '/tj.gif?page=er_detail_news&pos='
+ };
+
+ var strOpt = $('#J_config').html();
+ if (!!strOpt) {
+ try {
+ return eval('(' + strOpt + ')');
+ } catch (e) {
+ return defOpt;
+ }
+ } else {
+ return defOpt;
+ }
+ }
+
+ customElem.prototype.build = build;
+
+ return customElem;
+});
diff --git a/mip-hk-share/package.json b/mip-hk-share/package.json
new file mode 100644
index 00000000..1d8cfc06
--- /dev/null
+++ b/mip-hk-share/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-share",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-hk-showarticle/README.md b/mip-hk-showarticle/README.md
new file mode 100644
index 00000000..325bd568
--- /dev/null
+++ b/mip-hk-showarticle/README.md
@@ -0,0 +1,34 @@
+# mip-hk-showarticle
+
+显示文章更多详情
+
+|标题|内容|
+|---|---|
+|类型|业务|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-hk-showarticle/mip-hk-showarticle.js|
+
+## 示例
+
+在MIP HTML中,直接使用标签, 用于显示文章更多详情。
+
+```
+
+
+ 展开全文
+
+
+```
+## 属性
+
+### page
+
+说明:不同位置对应不同统计参数
+必选项:否
+类型:字符串
+
+### type
+
+说明:不同渠道显示文章长度不同
+必选项:否
+类型:字符串
diff --git a/mip-hk-showarticle/mip-hk-showarticle.js b/mip-hk-showarticle/mip-hk-showarticle.js
new file mode 100644
index 00000000..acadca01
--- /dev/null
+++ b/mip-hk-showarticle/mip-hk-showarticle.js
@@ -0,0 +1,37 @@
+/**
+ * @file 百度好看显示文章详情
+ * @author liujunqiu
+ * @time 2016.12.15
+ */
+define(function (require) {
+ // mip 组件开发支持 zepto
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function build() {
+ var element = this.element;
+
+ var page = $(element).attr('page');
+
+ $(element).find('#J_show_art').click(function () {
+ $(this).parent().parent().find('.hide').show();
+ $(this).remove();
+ $('#J_tag').show();
+
+ new Image().src = '/tj.gif?page=' + page + '&pos=open&t=' + new Date().getTime();
+ });
+
+ // 凤巢文章显示支持
+ var type = $(element).attr('type');
+ if (type === 'fengchao') {
+ var winHeight = $(window).height();
+ $('.J_article_wrap').css({
+ height: (winHeight - $('.J_detail_title').height() - 88 + 100) * 0.62
+ });
+ }
+ }
+
+ customElem.prototype.build = build;
+
+ return customElem;
+});
diff --git a/mip-hk-showarticle/package.json b/mip-hk-showarticle/package.json
new file mode 100644
index 00000000..02e4333a
--- /dev/null
+++ b/mip-hk-showarticle/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-hk-showarticle",
+ "version": "1.0.1",
+ "author": {
+ "name": "liujunqiu",
+ "email": "liujunqiu@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.2"
+ }
+}
diff --git a/mip-html-ajax/README.md b/mip-html-ajax/README.md
new file mode 100644
index 00000000..944676ff
--- /dev/null
+++ b/mip-html-ajax/README.md
@@ -0,0 +1,39 @@
+# mip-html-ajax
+mip-html-ajax 评论提交加载
+
+标题|内容
+----|----
+类型|通用
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-html-ajax/mip-html-ajax.js
+
+## 示例
+
+### 标签使用示例
+```html
+
+
+
+
+```
diff --git a/mip-html-ajax/mip-html-ajax.js b/mip-html-ajax/mip-html-ajax.js
new file mode 100644
index 00000000..436218ee
--- /dev/null
+++ b/mip-html-ajax/mip-html-ajax.js
@@ -0,0 +1,483 @@
+/**
+ * @author: Qi
+ * @date: 2017-11-22
+ * @file: mip-html-ajax.js
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var $ = require('zepto');
+ var viewPort = require('viewport');
+ var util = require('util');
+ var platform = util.platform;
+ var theOs = platform.isAndroid() ? 1 : platform.isIos() ? 2 : 0;
+ var theID = 'Qping';
+ var isLoad = false;
+ var isMore = false;
+ var errMsg = {
+ r100: '获取配置json失败',
+ r101: '配置json格式错误',
+ r102: '获取配置json.val错误',
+ d100: '{name}格式不正确',
+ g100: '提交请求出错',
+ g101: '获取请求出错',
+ g102: 'dig请求出错',
+ g103: 'bad请求出错'
+ };
+
+ function setHtmlAjax(data, that) {
+ if (typeof (data.set) === 'undefined') {
+ return false;
+ }
+ var obj = $(that);
+ // 替换标签
+ obj.find('val-input, val-textarea, val-select,val-option').each(function () {
+ var thisHtml = $(this).prop('outerHTML');
+ $(this).prop('outerHTML', thisHtml.replace(/<(\/?)val-/ig, '<$1'));
+ });
+ // 按钮事件
+ obj.find(data.set.btn).click(function () {
+ getHtmlVal(data, obj);
+ return false;
+ });
+ // 回车事件
+ that.addEventListener('keydown', function (event) {
+ if (event.keyCode === 13) {
+ getHtmlVal(data, obj);
+ return false;
+ }
+ }, false);
+ }
+
+ // 获取检测提交数据
+ function getHtmlVal(data, obj) {
+ if (typeof (data.val) === 'undefined') {
+ qAlert(errMsg.r102);
+ return false;
+ }
+ var valArr = {};
+ for (var mycars in data.val) {
+ if (data.val.hasOwnProperty(mycars)) {
+ var vData = obj.find(data.val[mycars]);
+ var vValAlt = vData.attr('val-alt') || '';
+ var vMinLen = vData.attr('min-len') || 0;
+ var vMaxLen = vData.attr('max-len') || 0;
+ var vMsgStr = vValAlt !== '' ? vValAlt : errMsg.d100.replace('{name}', mycars);
+ if (vMinLen === '*' && vData.val().length === 0) {
+ qAlert(vMsgStr);
+ return false;
+ }
+ if (vMinLen !== 0 && vData.val().length < vMinLen) {
+ qAlert(vMsgStr);
+ return false;
+ }
+ if (vMaxLen !== 0 && vData.val().length > vMaxLen) {
+ qAlert(vMsgStr);
+ return false;
+ }
+ valArr[mycars] = vData.val();
+ }
+ }
+ getHtmlAjax(data, obj, valArr);
+ }
+
+ // 提交数据
+ function getHtmlAjax(data, obj, valArr) {
+ var getUrl = data.add.url;
+ var theBtn = obj.find(data.set.btn);
+ if (getUrl !== '') {
+ var getCall = typeof (data.add.call) !== 'undefined' ? data.add.call : '';
+ getUrl = getUrl.replace('{sid}', data.id).replace('{os}', theOs);
+ $.ajax({
+ url: getUrl,
+ type: 'get',
+ dataType: 'jsonp',
+ jsonpCallback: getCall,
+ data: valArr,
+ beforeSend: function () {
+ theBtn.attr('disabled', 'disabled');
+ if (data.set.btnstr !== '') {
+ theBtn.attr('btn-txt', theBtn.val()).val(data.set.btnstr);
+ }
+ },
+ error: function () {
+ if (data.add.tip === '1') {
+ qAlert(errMsg.g100);
+ }
+ theBtn.removeAttr('disabled');
+ if (data.set.btnstr !== '') {
+ theBtn.val(theBtn.attr('btn-txt'));
+ }
+ },
+ success: function (jsondb) {
+ if (jsondb.success === 'err') {
+ qAlert(jsondb.msg);
+ }
+ else {
+ var dataTmp = typeof (data.tmp) !== 'undefined' ? 1 : 0;
+ var dataNta = dataTmp === 1 ? $(data.tmp.nta).html() : htmlDecode(data.nta);
+ var dataNtr = dataTmp === 1 ? $(data.tmp.ntr).html() : htmlDecode(data.ntr);
+ var isretext = obj.find(data.res.upval).val();
+ obj.find('[empty=true]').val('');
+ if (jsondb.msg !== '') {
+ qAlert(jsondb.msg);
+ }
+ if (isretext === '0') {
+ var newData = tempView(dataNta, jsondb, data);
+ obj.find(data.obj.list).append(newData);
+ }
+ else {
+ var addData = tempView(dataNtr, jsondb, data);
+ var addElem = obj.find(data.obj.list).find('#Q' + isretext);
+ addElem.find(data.obj.listre).append(addData);
+ viewPort.setScrollTop(addElem.offset().top);
+ }
+ }
+ theBtn.removeAttr('disabled');
+ if (data.set.btnstr !== '') {
+ theBtn.val(theBtn.attr('btn-txt'));
+ }
+ }
+ });
+ }
+ }
+
+ // 获取数据
+ function getJsonData(data, that) {
+ data.page = typeof (data.page) !== 'undefined' ? data.page : 1;
+ var getUrl = data.get.url;
+ var obj = $(that);
+ if (getUrl !== '') {
+ var getCall = typeof (data.get.call) !== 'undefined' ? data.get.call : '';
+ getUrl = getUrl.replace('{id}', data.id).replace('{page}', data.page).replace('{os}', theOs);
+ var pageSet = function (jsondb) {
+ var iMore = obj.find(data.obj.more);
+ if (typeof (jsondb.size) !== 'undefined'
+ && typeof (jsondb.page) !== 'undefined'
+ && data.obj.more !== '') {
+ var iText = iMore.attr('more-txt') || '';
+ var iSize = jsondb.size;
+ var iPage = jsondb.page;
+ if (iSize > iPage) {
+ if (iText !== '') {
+ iMore.html(iText);
+ }
+ iMore.show();
+ iMore.attr('on', 'tap:' + data.theID + '.Qmore(' + (iPage + 1) + ')');
+ data.page = (iPage + 1);
+ data.isMore = true;
+ }
+ else {
+ data.isMore = false;
+ iMore.remove();
+ }
+ }
+ else {
+ data.isMore = false;
+ iMore.remove();
+ }
+ };
+ $.ajax({
+ url: getUrl,
+ type: 'get',
+ dataType: 'jsonp',
+ jsonpCallback: getCall,
+ error: function () {
+ data.isMore = false;
+ if (data.get.tip === '1') {
+ qAlert(errMsg.g101);
+ }
+ if (typeof (data.obj.nodata) !== 'undefined') {
+ if (data.obj.nodata === '1') {
+ obj.remove();
+ }
+ }
+ },
+ success: function (jsondb) {
+ if (jsondb.state === 'err') {
+ data.isMore = false;
+ qAlert(jsondb.msg);
+ }
+ else {
+ if (typeof (jsondb[data.obj.arr]) !== 'undefined') {
+ var arrData = jsondb[data.obj.arr];
+ var arrLength = arrData.length;
+ if (arrLength > 0) {
+ var dataTmp = typeof (data.tmp) !== 'undefined' ? 1 : 0;
+ var dataHta = dataTmp === 1 ? $(data.tmp.hta).html() : htmlDecode(data.hta);
+ var dataHtr = dataTmp === 1 ? $(data.tmp.htr).html() : htmlDecode(data.htr);
+ var dataList = '';
+ if (typeof (data.obj.arrs) !== 'undefined') {
+ arrData = randomArray(arrData);
+ }
+ if (!isNaN(data.obj.arrn)) {
+ arrLength = data.obj.arrn >= arrLength ? arrLength : data.obj.arrn;
+ }
+ for (var i = 0; i < arrLength; i++) {
+ var thisdata = tempView(dataHta, arrData[i], data);
+ if (thisdata.indexOf('$ReData$') > 0 && data.obj.rearr !== '') {
+ var redata = '';
+ var rearrLen = arrData[i][data.obj.rearr].length;
+ if (typeof (data.obj.rearrs) !== 'undefined') {
+ arrData[i][data.obj.rearr] = randomArray(arrData[i][data.obj.rearr]);
+ }
+ if (!isNaN(data.obj.rearrn)) {
+ rearrLen = data.obj.rearrn >= rearrLen ? rearrLen : data.obj.rearrn;
+ }
+ for (var ir = 0; ir < rearrLen; ir++) {
+ redata += tempView(dataHtr, arrData[i][data.obj.rearr][ir], data);
+ }
+ thisdata = thisdata.replace('$ReData$', redata);
+ }
+ dataList += thisdata;
+ }
+ obj.find(data.obj.list).append(dataList);
+ data.isLoad = false;
+ }
+ else {
+ if (typeof (data.obj.nodata) !== 'undefined') {
+ data.isMore = false;
+ if (data.obj.nodata === '1') {
+ obj.remove();
+ }
+ }
+ }
+ pageSet(jsondb);
+ }
+ else {
+ data.isMore = false;
+ if (data.obj.nodata === '1') {
+ obj.remove();
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+
+ // on点击事件
+ function setHtmlClick(data, object, that) {
+ var obj = $(that);
+ object.addEventAction('Qres', function (event, str) {
+ obj.find(data.res.show).show().find(data.res.upint).text(str);
+ viewPort.setScrollTop(obj.find(data.set.txt).offset().top);
+ var QresOut = setTimeout(function () {
+ obj.find(data.set.txt).focus();
+ clearTimeout(QresOut);
+ }, 200);
+ });
+ object.addEventAction('Qreh', function (event, str) {
+ obj.find(data.res.show).hide().find('em').text(0);
+ var QrehOut = setTimeout(function () {
+ obj.find(data.set.txt).blur();
+ clearTimeout(QrehOut);
+ }, 200);
+ });
+ object.addEventAction('Qrev', function (event, str) {
+ obj.find(data.res.upval).val(str);
+ });
+ object.addEventAction('Qmore', function (event, str) {
+ var theMore = obj.find(data.obj.more);
+ theMore.attr('more-txt', theMore.html()).html(htmlDecode(data.obj.morestr));
+ getJsonData(data, obj);
+ });
+ object.addEventAction('Qdig', function (event, str) {
+ var getUrl = data.dig.url;
+ if (getUrl !== '') {
+ var getCall = typeof (data.dig.call) !== 'undefined' ? data.dig.call : '';
+ getUrl = getUrl.replace('{sid}', str).replace('{os}', theOs);
+ $.ajax({
+ url: getUrl,
+ type: 'get',
+ dataType: 'jsonp',
+ jsonpCallback: getCall,
+ error: function () {
+ if (data.dig.tip === '1') {
+ qAlert(errMsg.g102);
+ }
+ },
+ success: function (jsondb) {
+ if (jsondb.success === 'err') {
+ if (data.dig.tip === '1') {
+ qAlert(jsondb.msg);
+ }
+ }
+ else {
+ if (data.dig.tip === '1') {
+ $(event.target).prepend('+1');
+ $(event.target).find('.Qdig').fadeOut(800, function () {
+ $(this).remove();
+ });
+ $(event.target).find('em').text(jsondb.dig);
+ }
+ }
+ $(event.target).removeAttr('on');
+ }
+ });
+ }
+ });
+ object.addEventAction('Qbad', function (event, str) {
+ var getUrl = data.bad.url;
+ if (getUrl !== '') {
+ var getCall = typeof (data.bad.call) !== 'undefined' ? data.bad.call : '';
+ getUrl = getUrl.replace('{sid}', str).replace('{os}', theOs);
+ $.ajax({
+ url: getUrl,
+ type: 'get',
+ dataType: 'jsonp',
+ jsonpCallback: getCall,
+ error: function () {
+ if (data.bad.tip === '1') {
+ qAlert(errMsg.g103);
+ }
+ },
+ success: function (jsondb) {
+ if (jsondb.success === 'err') {
+ if (data.bad.tip === '1') {
+ qAlert(jsondb.msg);
+ }
+ }
+ else {
+ if (data.bad.tip === '1') {
+ $(event.target).prepend('+1');
+ $(event.target).find('.Qbad').fadeOut(800, function () {
+ $(this).remove();
+ });
+ $(event.target).find('em').text(jsondb.bad);
+ }
+ }
+ $(event.target).removeAttr('on');
+ }
+ });
+ }
+ });
+ if (typeof (data.obj.auto) !== 'undefined') {
+ if (data.obj.auto === '1') {
+ var autoFoot = typeof (data.obj.autofoot) !== 'undefined' ? data.obj.autofoot : 50;
+ $(window).scroll(function () {
+ var dotHeight = viewPort.getScrollHeight();
+ var winHeight = viewPort.getHeight();
+ var winScroll = viewPort.getScrollTop();
+ if (winScroll > (dotHeight - winHeight - autoFoot)) {
+ if (data.isMore && !data.isLoad) {
+ var theMore = obj.find(data.obj.more);
+ theMore.attr('more-txt', theMore.html()).html(htmlDecode(data.obj.morestr));
+ data.isLoad = true;
+ getJsonData(data, obj);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ // 自定义解码
+ function htmlDecode(str) {
+ var strTemp = str;
+ strTemp = strTemp.replace(/\[\[/g, '<');
+ strTemp = strTemp.replace(/\]\]/g, '>');
+ strTemp = strTemp.replace(/\|\|/g, '/');
+ strTemp = strTemp.replace(/\:\:/g, '"');
+ strTemp = strTemp.replace(/\;\;/g, '\'');
+ strTemp = strTemp.replace(/\+\+/g, ' ');
+ return strTemp;
+ }
+
+ // decodeURI解码
+ function jsonDecode(str) {
+ try {
+ str = decodeURI(str);
+ }
+ catch (err) {
+ }
+ return str;
+ }
+
+ // 替换标签
+ function tempView(str, arr, data) {
+ str = str.replace(/{thisid}/gi, data.theID);
+ var sArr = str.match(/{\w+}/gi);
+ if (sArr) {
+ for (var i = 0; i < sArr.length; i++) {
+ var sQrr = sArr[i].match(/{(\w+)}/i);
+ if (typeof (arr[sQrr[1]]) === 'string') {
+ str = str.replace(sArr[i], jsonDecode(arr[sQrr[1]]));
+ }
+ else {
+ str = str.replace(sArr[i], arr[sQrr[1]]);
+ }
+ }
+ }
+ return str;
+ }
+
+ function randomArray(arr) {
+ var setArray = [];
+ for (var index in arr) {
+ if (arr.hasOwnProperty(index)) {
+ setArray.push(arr[index]);
+ }
+ }
+ var getArray = [];
+ for (var i = 0; i < arr.length; i++) {
+ if (setArray.length > 0) {
+ var arrIndex = Math.floor(Math.random() * setArray.length);
+ getArray[i] = setArray[arrIndex];
+ setArray.splice(arrIndex, 1);
+ }
+ else {
+ break;
+ }
+ }
+ return getArray;
+ }
+
+ // 弹出提示层
+ function qAlert(msg, ms) {
+ ms = ms || 1600;
+ if ($('.mip-html-ajax-tip').length > 0) {
+ $('.mip-html-ajax-tip').remove();
+ }
+ $('body').append('' + msg + '
');
+ var msgOut = setTimeout(function () {
+ $('.mip-html-ajax-tip').fadeOut(100, function () {
+ $(this).remove();
+ });
+ clearTimeout(msgOut);
+ }, ms);
+ }
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var thisObj = this.element;
+ var elemObj = thisObj.querySelector('script[type="application/json"]');
+ var elemIds = thisObj.getAttribute('id');
+ if (elemIds) {
+ theID = elemIds;
+ }
+ else {
+ thisObj.setAttribute('id', theID);
+ }
+ if (!elemObj) {
+ console.error(errMsg.r100);
+ thisObj.innerHTML = '';
+ return false;
+ }
+ try {
+ var data = JSON.parse(elemObj.textContent.toString());
+ data.theID = theID;
+ data.isLoad = isLoad;
+ data.isMore = isMore;
+ }
+ catch (e) {
+ console.error(errMsg.r101);
+ thisObj.innerHTML = '';
+ return false;
+ }
+ setHtmlAjax(data, thisObj);
+ setHtmlClick(data, this, thisObj);
+ getJsonData(data, thisObj);
+ };
+ return customElem;
+});
diff --git a/mip-html-ajax/mip-html-ajax.less b/mip-html-ajax/mip-html-ajax.less
new file mode 100644
index 00000000..bd1949bf
--- /dev/null
+++ b/mip-html-ajax/mip-html-ajax.less
@@ -0,0 +1,14 @@
+.mip-html-ajax-tip {
+ width: 72%;
+ position: fixed;
+ top: 40%;
+ left: 10%;
+ background: rgba(0, 0, 0, 0.6);
+ color: #fff;
+ text-align: center;
+ padding: 15px 4%;
+ line-height: 22px;
+ border-radius: 6px;
+ font-size: 15px;
+ z-index: 999999;
+}
diff --git a/mip-html-ajax/package.json b/mip-html-ajax/package.json
new file mode 100644
index 00000000..0bdb12a6
--- /dev/null
+++ b/mip-html-ajax/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-html-ajax",
+ "version": "1.1.2",
+ "description": "AJAX加载",
+ "author": {
+ "name": "Qi",
+ "email": "335994677@qq.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-html-font-size/README.md b/mip-html-font-size/README.md
index c811ed69..230d60a8 100644
--- a/mip-html-font-size/README.md
+++ b/mip-html-font-size/README.md
@@ -6,7 +6,7 @@ mip-html-font-size 组件说明 将组件直接引用,可将html的font-size
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-html-font-size/mip-html-font-size.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-html-font-size/mip-html-font-size.js
## 示例
diff --git a/mip-html-tabs/README.md b/mip-html-tabs/README.md
new file mode 100644
index 00000000..25377164
--- /dev/null
+++ b/mip-html-tabs/README.md
@@ -0,0 +1,266 @@
+# **mip-html-tabs**
+mip-html-tabs TAB滑动、显示、隐藏、元素跳转等操作。
+
+标题|内容
+----|----
+类型|用通
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-html-tabs/mip-html-tabs.js
+
+## 示例
+
+### Qi_1 当内容高度大于设置高度后展开、隐藏操作。
+```html
+
+ 超过隐藏部分内容(默认高度300px)
+ 展开全部
+ 收起简介
+
+```
+
+### Qi_2 设置一个点击元素,执行toggle显示隐藏操作。
+```html
+
+
+
内容1
+ 内容1
+
+ 点击显示或隐藏内容1
+
+```
+
+### Qi_3 设置两个点击元素,执行显示隐藏操作。
+```html
+
+
+
内容2
+ 内容2
+
+ 点击显示内容2
+ 点击隐藏内容2
+
+```
+
+### Qi_4 Tab内容切换。
+```html
+
+
+
+ - Tab1
+ - Tab2
+ - Tab3
+ - Tab4
+
+
+
+
+```
+
+### Qi_5 点击元素滚动事件。
+```html
+
+ 返回顶部
+
+```
+
+## 属性
+
+### tabs-type
+
+说明:选择使用方法
+必选项:是
+取值范围:Qi_1/Qi_2/Qi_3/Qi_4/Qi_5
+默认值:Qi_1
+
+### tabs-html
+
+说明:指定超过高度需要隐藏的元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_1
+
+### tabs-height
+
+说明:高度值
+必选项:否
+类型:整数(px值)
+默认值:300
+tabs-type:Qi_1
+
+### tabs-show
+
+说明:指定展开按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_1
+
+### tabs-hide
+
+说明:指定收起按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_1
+
+### tabs-init
+
+说明:默认现实或隐藏
+必选项:否
+类型:整数
+取值范围:0/1
+默认值:0
+tabs-type:Qi_1
+
+### tabs-toggle
+
+说明:指定显示隐藏元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_2
+
+### tabs-on
+
+说明:指定点击按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_2
+
+### tabs-init
+
+说明:默认现实或隐藏
+必选项:否
+类型:整数
+取值范围:0/1
+默认值:0
+tabs-type:Qi_2
+
+### tabs-toggle
+
+说明:指定显示隐藏元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_3
+
+### tabs-on
+
+说明:指定点击按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_3
+
+### tabs-init
+
+说明:默认现实或隐藏
+必选项:否
+类型:整数
+取值范围:0/1
+默认值:0
+tabs-type:Qi_3
+
+### show-class
+
+说明:当元素显示时添加class
+必选项:否
+类型:class类名称
+tabs-type:Qi_3
+
+### hide-class
+
+说明:当元素隐藏时添加class
+必选项:否
+类型:class类名称
+tabs-type:Qi_3
+
+### tabs-nav
+
+说明:指定TAB按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_4
+
+### tabs-key
+
+说明:指定TAB内容元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_4
+
+### tabs-init
+
+说明:默认显示TAB:n内容
+必选项:否
+类型:整数
+默认值:1
+tabs-type:Qi_4
+
+### nav-cur
+
+说明:当前TAB按钮添加class
+必选项:否
+类型:class类名称
+tabs-type:Qi_4
+
+### key-cur
+
+说明:当前TAB内容添加class
+必选项:否
+类型:class类名称
+tabs-type:Qi_4
+
+### tabs-key
+
+说明:点击按钮元素
+必选项:是
+类型:元素选择器
+tabs-type:Qi_5
+
+### tabs-to
+
+说明:点击按钮元素
+必选项:是
+类型:多类型
+取值范围:top/bottom/元素选择器
+tabs-type:Qi_5
+
+### tabs-on
+
+说明:点击事件元素
+必选项:否
+类型:元素选择器
+tabs-type:Qi_5
+
+### tabs-eq
+
+说明:点击事件元素eq
+必选项:否
+类型:整数
+默认值:1
+tabs-type:Qi_5
+
+### tabs-top
+
+说明:按钮元素超过tabs-top后值显示
+必选项:否
+类型:整数(px值)
+默认值:0
+tabs-type:Qi_5
+
+### tabs-of
+
+说明:tabs-to的偏移
+必选项:否
+类型:整数/负整数(px值)
+默认值:0
+tabs-type:Qi_5
diff --git a/mip-html-tabs/mip-html-tabs.js b/mip-html-tabs/mip-html-tabs.js
new file mode 100644
index 00000000..9bd2575d
--- /dev/null
+++ b/mip-html-tabs/mip-html-tabs.js
@@ -0,0 +1,211 @@
+/**
+ * @author: Qi
+ * @date: 2016-11-01
+ * @file: mip-html-tabs.js
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var qi = require('zepto');
+ var port = require('viewport');
+
+ // tabs-type 分配执行函数。
+ function sethtmltabs(OrThis, OrType) {
+ switch (OrType) {
+ case 'Qi_2':
+ toggleclick(OrThis);
+ break;
+ case 'Qi_3':
+ showclick(OrThis);
+ break;
+ case 'Qi_4':
+ tabsclick(OrThis);
+ break;
+ case 'Qi_5':
+ scrollclick(OrThis);
+ break;
+ default :
+ dropdown(OrThis);
+ }
+ }
+ // tabs-type=1 当内容高度大于设置高度后展开、隐藏操作。
+ function dropdown(OrThis) {
+ var OrHeight = OrThis.attr('tabs-height');
+ var OrContent = OrThis.attr('tabs-html');
+ var OrCHeight = qi(OrContent).height();
+ var OrShowBtn = OrThis.attr('tabs-show');
+ var OrHideBtn = OrThis.attr('tabs-hide');
+ var OrIsShow = OrThis.attr('tabs-init');
+ var IsShow = OrIsShow ? OrIsShow : 0;
+ var IsHeight = OrHeight ? OrHeight : 300;
+ var callShow = function () {
+ qi(OrContent).css({display: 'block', height: 'auto', overflow: 'initial'});
+ qi(OrHideBtn).show();
+ qi(OrShowBtn).hide();
+ };
+ var callHide = function () {
+ qi(OrContent).css({display: 'block', height: IsHeight + 'px', overflow: 'hidden'});
+ qi(OrHideBtn).hide();
+ qi(OrShowBtn).show();
+ };
+ if (OrCHeight > IsHeight) {
+ if (IsShow === 0) {
+ callHide();
+ }
+ else {
+ callShow();
+ }
+ qi(OrShowBtn).on('click', function () {
+ callShow();
+ });
+ qi(OrHideBtn).on('click', function () {
+ callHide();
+ });
+ }
+ else {
+ qi(OrHideBtn).remove();
+ qi(OrShowBtn).remove();
+ }
+ }
+ // tabs-type=2 设置一个点击元素,执行toggle显示隐藏操作。
+ function toggleclick(OrThis) {
+ var OrOnck = OrThis.attr('tabs-on');
+ var OrToggle = OrThis.attr('tabs-toggle');
+ var OrIsShow = OrThis.attr('tabs-init');
+ var IsShow = OrIsShow ? OrIsShow : 0;
+ if (IsShow === 0) {
+ qi(OrToggle).hide();
+ }
+ else {
+ qi(OrToggle).show();
+ }
+ qi(OrOnck).on('click', function () {
+ qi(OrToggle).toggle();
+ });
+ }
+ // tabs-type=3 设置两个点击元素,执行显示隐藏操作。
+ function showclick(OrThis) {
+ var OrShow = OrThis.attr('tabs-show');
+ var OrHide = OrThis.attr('tabs-hide');
+ var OrToggle = OrThis.attr('tabs-toggle');
+ var OrIsShow = OrThis.attr('tabs-init');
+ var OrSclass = OrThis.attr('show-class');
+ var OrHclass = OrThis.attr('hide-class');
+ var IsShow = OrIsShow ? OrIsShow : 0;
+ var IsSclass = OrSclass ? OrSclass : '';
+ var IsHclass = OrHclass ? OrHclass : '';
+ var callShow = function () {
+ qi(OrToggle).show();
+ qi(OrShow).hide();
+ qi(OrHide).show();
+ if (IsSclass !== '') {
+ qi(OrToggle).removeClass(IsHclass);
+ qi(OrToggle).addClass(IsSclass);
+ }
+ };
+ var callHide = function () {
+ qi(OrToggle).hide();
+ qi(OrShow).show();
+ qi(OrHide).hide();
+ if (IsHclass !== '') {
+ qi(OrToggle).removeClass(IsSclass);
+ qi(OrToggle).addClass(IsHclass);
+ }
+ };
+ if (IsShow === 0) {
+ callHide();
+ }
+ else {
+ callShow();
+ }
+ qi(OrShow).on('click', function () {
+ callShow();
+ });
+ qi(OrHide).on('click', function () {
+ callHide();
+ });
+ }
+ // tabs-type=4 Tab内容切换。
+ function tabsclick(OrThis) {
+ var OrNav = OrThis.attr('tabs-nav');
+ var OrKey = OrThis.attr('tabs-key');
+ var OrNcur = OrThis.attr('nav-cur');
+ var OrKcur = OrThis.attr('key-cur');
+ var OrInit = OrThis.attr('tabs-init');
+ var IsNcur = OrNcur ? OrNcur : '';
+ var IsKcur = OrKcur ? OrKcur : '';
+ var IsInit = OrInit ? OrInit : 0;
+ IsInit = !isNaN(IsInit) ? IsInit : 0;
+ IsInit = (IsInit > qi(OrNav).length || IsInit < 1) ? 0 : IsInit - 1;
+ var callNav = function (i) {
+ qi(OrNav).removeClass(IsNcur);
+ qi(OrNav).eq(i).addClass(IsNcur);
+ };
+ var callTab = function (i) {
+ qi(OrKey).removeClass(IsKcur).hide();
+ qi(OrKey).eq(i).addClass(IsKcur).show();
+ };
+ callNav(IsInit);
+ callTab(IsInit);
+ qi(OrNav).on('click', function () {
+ var Thisnum = qi(this).index();
+ callNav(Thisnum);
+ callTab(Thisnum);
+ });
+ }
+ // tabs-type=5 点击元素滚动事件。
+ function scrollclick(OrThis) {
+ var OrKey = OrThis.attr('tabs-key');
+ var OrTop = OrThis.attr('tabs-top');
+ var OrTo = OrThis.attr('tabs-to');
+ var OrOn = OrThis.attr('tabs-on');
+ var OrEq = OrThis.attr('tabs-eq');
+ var OrOf = OrThis.attr('tabs-of');
+ var IsTo = OrTo ? OrTo : '';
+ var IsOn = OrOn ? OrOn : '';
+ var IsEq = OrEq ? OrEq : 0;
+ var IsOf = OrOf ? OrOf : 0;
+ var IsTop = OrTop ? OrTop : 0;
+ if (IsTop > 0) {
+ qi(OrKey).hide();
+ port.on('scroll', function () {
+ if (port.getScrollTop() > IsTop) {
+ qi(OrKey).show();
+ }
+ else {
+ qi(OrKey).hide();
+ }
+ });
+ }
+ qi(OrKey).on('click', function () {
+ if (IsTo !== '') {
+ var Toint = 0;
+ switch (OrTo) {
+ case 'top':
+ Toint = 0;
+ break;
+ case 'bottom':
+ Toint = port.getScrollHeight();
+ break;
+ default :
+ Toint = qi(OrTo).offset().top;
+ }
+ Toint = Toint + IsOf * 1;
+ port.setScrollTop(Toint);
+ }
+ if (IsOn !== '') {
+ qi(IsOn).eq(IsEq).click();
+ }
+ });
+ }
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var element = qi(this.element);
+ var OrType = element.attr('tabs-type');
+ sethtmltabs(element, OrType);
+ if (element.html().length === 0) {
+ element.remove();
+ }
+ };
+ return customElem;
+});
diff --git a/mip-html-tabs/package.json b/mip-html-tabs/package.json
new file mode 100644
index 00000000..42909214
--- /dev/null
+++ b/mip-html-tabs/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-html-tabs",
+ "version": "1.1.2",
+
+ "author": {
+ "name": "Qi",
+ "email": "335994677@qq.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-huajun-downtag/README.md b/mip-huajun-downtag/README.md
new file mode 100644
index 00000000..fee1682f
--- /dev/null
+++ b/mip-huajun-downtag/README.md
@@ -0,0 +1,29 @@
+# mip-huajun-downtag
+
+点击切换组件
+
+标题|内容
+----|----
+类型|事件
+支持布局| responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-huajun-downtag/mip-huajun-downtag.js
+
+## 示例
+
+### 切换
+
+```
+
+
+ 使用极速下载
+
+
+
+
+
使用普通下载xxx
+
通过极速下载,xxx!
+
+
+
+
+
diff --git a/mip-huajun-downtag/mip-huajun-downtag.js b/mip-huajun-downtag/mip-huajun-downtag.js
new file mode 100644
index 00000000..d03d8c67
--- /dev/null
+++ b/mip-huajun-downtag/mip-huajun-downtag.js
@@ -0,0 +1,35 @@
+/**
+ * @file downtag component
+ * @author 873920193@qq.com
+ * @version 1.0
+ * @copyright 2016 onlinedown.net, Inc. All Rights Reserved
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function init() {
+ $('em.icon').click(function () {
+ if ($(this).hasClass('checkhover')) {
+ // 未勾选
+ $(this).removeClass('checkhover');
+ $('.ptdownload').hide();
+ $('.spdownload').show();
+ $('.text1').hide();
+ $('.text2').show();
+ }
+ else {
+ $(this).addClass('checkhover');
+ $('.spdownload').hide();
+ $('.ptdownload').show();
+ $('.text2').hide();
+ $('.text1').show();
+ }
+ });
+ }
+ customElem.prototype.build = function () {
+ init();
+ };
+ return customElem;
+});
+
diff --git a/mip-huajun-downtag/package.json b/mip-huajun-downtag/package.json
new file mode 100644
index 00000000..fc5bf20e
--- /dev/null
+++ b/mip-huajun-downtag/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-huajun-downtag",
+ "version": "1.0.0",
+ "author": {
+ "name": "huajun",
+ "email": "87390193@qq.com",
+ "url": "http://www.onlinedown.net"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-huajun-fixdnav/README.md b/mip-huajun-fixdnav/README.md
new file mode 100644
index 00000000..de45a580
--- /dev/null
+++ b/mip-huajun-fixdnav/README.md
@@ -0,0 +1,37 @@
+# mip-huajun-fixdnav
+
+固定nav组件
+
+标题|内容
+----|----
+类型|事件
+支持布局| responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-huajun-fixdnav/mip-huajun-fixdnav.js
+
+## 示例
+
+### 切换
+
+```
+
+
+
+
+```
+
+
diff --git a/mip-huajun-fixdnav/mip-huajun-fixdnav.js b/mip-huajun-fixdnav/mip-huajun-fixdnav.js
new file mode 100644
index 00000000..2f8e6162
--- /dev/null
+++ b/mip-huajun-fixdnav/mip-huajun-fixdnav.js
@@ -0,0 +1,27 @@
+/**
+* fixed nav
+* @file fixed nav component
+* @author 873920193@qq.com
+* @version 1.0
+* @copyright 2016 onlinedown.net, Inc. All Rights Reserved
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function init() {
+ $(window).scroll(function () {
+ var scroH = $(this).scrollTop();
+ if (scroH >= 150) {
+ $('nav.side-down').addClass('menu_scroll');
+ }
+ else if (scroH < 150) {
+ $('nav.side-down').removeClass('menu_scroll');
+ }
+ });
+ }
+ customElem.prototype.build = function () {
+ init();
+ };
+ return customElem;
+});
diff --git a/mip-huajun-fixdnav/package.json b/mip-huajun-fixdnav/package.json
new file mode 100644
index 00000000..40d170a6
--- /dev/null
+++ b/mip-huajun-fixdnav/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-huajun-fixdnav",
+ "version": "1.0.5",
+ "author": {
+ "name": "huajun",
+ "email": "87390193@qq.com",
+ "url": "http://www.onlinedown.net"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-huajun-loadmore/README.md b/mip-huajun-loadmore/README.md
new file mode 100644
index 00000000..a9370cc4
--- /dev/null
+++ b/mip-huajun-loadmore/README.md
@@ -0,0 +1,86 @@
+# mip-huajun-loadmore
+
+点击加载更多组件
+
+|标题|内容|
+|----|----|
+|类型|事件|
+|支持布局|N/S|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-huajun-loadmore/mip-huajun-loadmore.js|
+
+## 示例
+
+
+```
+
+
+
+
+```
+## 属性
+
+### url
+
+说明:ajax请求地址
+必选项:是
+类型:字符串
+取值范围:请求url
+单位:无
+默认值:无
+
+### scon
+
+说明:软件内容
+必选项:否
+类型:字符串
+取值范围:无
+单位:无
+默认值:无
+
+### sid
+说明:该类id
+必选项:否
+类型:字符串
+取值范围:无
+单位:无
+默认值:无
+
+
+
+
+
diff --git a/mip-huajun-loadmore/mip-huajun-loadmore.js b/mip-huajun-loadmore/mip-huajun-loadmore.js
new file mode 100644
index 00000000..8f99d47c
--- /dev/null
+++ b/mip-huajun-loadmore/mip-huajun-loadmore.js
@@ -0,0 +1,52 @@
+/**
+ * @file loadmore component
+ * @author 873920193@qq.com
+ * @version 1.0
+ * @copyright 2016 onlinedown.net, Inc. All Rights Reserved
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var page = 2;
+ var options = {};
+ function more(element, options) {
+ var bd = $(element).find('.bd');
+ var htmlStr = '';
+ $.get(options.url, {page: page, scon: options.scon, sid: options.sid}, function (data) {
+ if (window.parseInt(data.status) === 200) {
+ $.each(data.content, function (key, val) {
+ htmlStr += '';
+ htmlStr += '';
+ htmlStr += ' ';
+ htmlStr += '- ';
+ htmlStr += '
';
+ htmlStr += '' + val.title + '
';
+ htmlStr += '- ' + val.filesize + 'M';
+ htmlStr += '/' + val.language + '';
+ htmlStr += '/' + val.lastdotime + '
';
+ htmlStr += ' ';
+ htmlStr += '
';
+ htmlStr += ' ';
+ htmlStr += '';
+ htmlStr += '';
+ htmlStr += '
';
+ });
+ bd.append(htmlStr);
+ page = page + 1;
+ }
+ else if (window.parseInt(data.status) === 201) {
+ $('.laypage_next').text('已经没有了');
+ }
+ });
+ }
+ customElem.prototype.build = function () {
+ var element = this.element;
+ options.url = element.getAttribute('url');
+ options.scon = element.getAttribute('scon');
+ options.sid = element.getAttribute('sid');
+ $('.laypage_next').click(function () {
+ more(element, options);
+ });
+ };
+ return customElem;
+});
diff --git a/mip-huajun-loadmore/package.json b/mip-huajun-loadmore/package.json
new file mode 100644
index 00000000..3ae14a36
--- /dev/null
+++ b/mip-huajun-loadmore/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-huajun-loadmore",
+ "version": "1.0.0",
+ "author": {
+ "name": "huajun",
+ "email": "87390193@qq.com",
+ "url": "http://www.onlinedown.net"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-huayi-kst/README.md b/mip-huayi-kst/README.md
index 1060493c..defc57f8 100644
--- a/mip-huayi-kst/README.md
+++ b/mip-huayi-kst/README.md
@@ -6,7 +6,7 @@
|----|----|
|类型|事件|
|支持布局|N/S|
-|所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-huayi-kst/mip-huayi-kst.js|
+|所需脚本|https://c.mipcdn.com/static/v1/mip-huayi-kst/mip-huayi-kst.js|
## 示例
diff --git a/mip-huimee/README.md b/mip-huimee/README.md
new file mode 100644
index 00000000..40591e5d
--- /dev/null
+++ b/mip-huimee/README.md
@@ -0,0 +1,26 @@
+# mip-huimee
+
+mip-huimee 汇米广告联盟广告投放插件
+
+标题|内容
+----|----
+类型|广告
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-huimee/mip-huimee.js
+
+## 示例
+
+### 基本用法
+```html
+
+
+
+```
+
+
+## 属性
+
+### mip-huimee
+
+说明:汇米广告联盟广告投放组件 , 广告类型是下浮广告 , 在页面底部以图片的形式展示
+必选项:否
\ No newline at end of file
diff --git a/mip-huimee/mip-huimee.js b/mip-huimee/mip-huimee.js
new file mode 100644
index 00000000..c2c0f15d
--- /dev/null
+++ b/mip-huimee/mip-huimee.js
@@ -0,0 +1,26 @@
+/**
+ * @file 第三方广告组件
+ *
+ * @author hoogecc
+ */
+
+define(function (require) {
+
+ var customElement = require('customElement').create();
+
+ /**
+ * 构造元素,只会运行一次
+ */
+ customElement.prototype.build = function () {
+ var node = document.createElement('script');
+ node.type = 'text/javascript';
+ node.src = 'https://tt.huimee.net/huimee_ads.js';
+ node.async = 'true';
+ var parent = document.getElementsByTagName('head')[0];
+ if (parent) {
+ parent.insertBefore(node, parent.firstChild);
+ }
+ };
+
+ return customElement;
+});
diff --git a/mip-huimee/package.json b/mip-huimee/package.json
new file mode 100644
index 00000000..b606465d
--- /dev/null
+++ b/mip-huimee/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-huimee",
+ "version": "1.0.0",
+ "description": "用于投放第三方广告",
+ "contributors": [
+ {
+ "name": "hoogecc",
+ "email": "hoogecc17@163.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-huixinkang-nav/README.md b/mip-huixinkang-nav/README.md
index ef63f242..72ebc7c5 100644
--- a/mip-huixinkang-nav/README.md
+++ b/mip-huixinkang-nav/README.md
@@ -6,7 +6,7 @@ mip-huixinkang-nav网页头部导航js效果实现
----|----
类型|通用
支持布局|fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-huixinkang-nav/mip-huixinkang-nav.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-huixinkang-nav/mip-huixinkang-nav.js
## 示例
diff --git a/mip-iask-ajax/README.md b/mip-iask-ajax/README.md
new file mode 100644
index 00000000..2b1222f1
--- /dev/null
+++ b/mip-iask-ajax/README.md
@@ -0,0 +1,51 @@
+# mip-iask-ajax
+
+mip-iask-ajax iask—ajax请求
+
+标题|内容
+----|----
+类型|业务
+支持布局|N,S|
+所需脚本|https://c.mipcdn.com/static/v1/mip-iask-ajax/mip-iask-ajax.js
+
+## 示例
+
+### 基本用法
+```html
+1
+
+
+```
+
+## 属性
+
+### url
+
+说明:ajax请求路径
+必选项:是
+类型:字符串
+## 注意事项
+
+### data
+
+说明:ajax请求所需参数
+必选项:是
+类型:json
+
+### isLogin
+
+说明:是否需要登录验证
+必选项:是
+类型:true false
+
+### type
+
+说明:根据业务设置
+必选项:否
+类型:字符串 1 :点赞
+
+### div
+
+说明:div的id或者class等
+必选项:否
+类型:字符串
diff --git a/mip-iask-ajax/mip-iask-ajax.js b/mip-iask-ajax/mip-iask-ajax.js
new file mode 100644
index 00000000..0131ceef
--- /dev/null
+++ b/mip-iask-ajax/mip-iask-ajax.js
@@ -0,0 +1,71 @@
+/**
+* @file 脚本支持
+* @author hejieye
+* @time 2016-12-13
+* @version 1.0.6
+*/
+define(function (require) {
+
+ var $ = require('zepto');
+ var viewer = require('viewer');
+ var customElem = require('customElement').create();
+ var checkLogin = function (url, params, isLogin, div, type) {
+ var json = $.parseJSON(params);
+ if (isLogin) {
+ // 验证是否登录
+ var checkLoginUrl = 'https://mipp.iask.cn/checkLogin?mip=' + Math.random();
+ $.get(checkLoginUrl,
+ function (e) {
+ if (e === null || e === 'null') {
+ // 跳转到登录页面
+ var thisHref = window.location.href;
+ if (viewer.isIframed) {
+ viewer.sendMessage('mibm-jumplink', {
+ 'url': 'https://mipp.iask.cn/login?source=' + thisHref
+ });
+ }
+ else {
+ window.location.href = 'https://mipp.iask.cn/login?source=' + thisHref;
+ }
+ }
+ else {
+ ajaxPost(url, json, div, type);
+ }
+ });
+ }
+ else {
+ ajaxPost(url, json, div, type);
+ }
+
+ };
+ var ajaxPost = function (url, params, div, type) {
+ $.post(url, params,
+ function (data) {
+ var res = $.parseJSON(data);
+ if (type === '1') {
+ if (res.succ === 'Y' && res.jsonData === '1') {
+ var txt = $(div);
+ txt.text(parseInt(txt.text(), 0) + 1);
+ }
+ }
+ });
+ };
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+
+ var elem = this.element;
+ var url = $(elem).attr('url');
+ var params = $(elem).attr('data');
+ var isLogin = $(elem).attr('isLogin');
+ var click = $(elem).attr('click');
+ var type = $(elem).attr('type');
+ var div = $(elem).attr('div');
+ $(click).on('click', function () {
+ checkLogin(url, params, isLogin, div, type);
+ });
+
+ };
+
+ return customElem;
+});
diff --git a/mip-iask-ajax/package.json b/mip-iask-ajax/package.json
new file mode 100644
index 00000000..f02be974
--- /dev/null
+++ b/mip-iask-ajax/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-iask-ajax",
+ "version": "1.0.6",
+ "description": "iask-ajax组件",
+ "contributors": [
+ {
+ "name": "hejieye",
+ "email": "jieye.he@iask.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-iask-business/README.md b/mip-iask-business/README.md
new file mode 100644
index 00000000..5d388f3d
--- /dev/null
+++ b/mip-iask-business/README.md
@@ -0,0 +1,19 @@
+# mip-iask-business
+
+mip-iask-business 爱问商业广告组件
+
+标题|内容
+----|----
+类型|业务
+支持布局|N,S|
+所需脚本|https://c.mipcdn.com/static/v1/mip-iask-business/mip-iask-business.js
+
+## 示例
+
+### 基本用法
+```html
+
+```
+## 属性
+
+## 注意事项
diff --git a/mip-iask-business/mip-iask-business.js b/mip-iask-business/mip-iask-business.js
new file mode 100644
index 00000000..b6737f5f
--- /dev/null
+++ b/mip-iask-business/mip-iask-business.js
@@ -0,0 +1,1553 @@
+/**
+* @file 脚本支持
+* @author hejieye
+* @time 2018-03-22
+* @version 2.1.4
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var busUid = '';
+ var utf8Encode = function (string) {
+ string = string.replace(/\r\n/g, '\n');
+ var utftext = '';
+ for (var n = 0; n < string.length; n++) {
+ var c = string.charCodeAt(n);
+ if (c < 128) {
+ utftext += String.fromCharCode(c);
+ }
+ else if ((c > 127) && (c < 2048)) {
+ utftext += String.fromCharCode((c >> 6) | 192);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ else {
+ utftext += String.fromCharCode((c >> 12) | 224);
+ utftext += String.fromCharCode(((c >> 6) & 63) | 128);
+ utftext += String.fromCharCode((c & 63) | 128);
+ }
+ }
+ return utftext;
+ };
+ var utf8Decode = function (utftext) {
+ var string = '';
+ var i = 0;
+ var c = 0;
+ var c1 = 0;
+ var c2 = 0;
+ var c3 = 0;
+ while (i < utftext.length) {
+ c = utftext.charCodeAt(i);
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ }
+ else if ((c > 191) && (c < 224)) {
+ c2 = utftext.charCodeAt(i + 1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ }
+ else {
+ c2 = utftext.charCodeAt(i + 1);
+ c3 = utftext.charCodeAt(i + 2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+ }
+ return string;
+ };
+ var Base64 = function () {
+ var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+ this.encode = function (input) {
+ var output = '';
+ var chr1;
+ var chr2;
+ var chr3;
+ var enc1;
+ var enc2;
+ var enc3;
+ var enc4;
+ var i = 0;
+ var input = utf8Encode(input);
+ while (i < input.length) {
+ 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 + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
+ }
+ return output;
+ };
+ this.decode = function (input) {
+ if (input.indexOf('appid') !== -1 || input.indexOf('smqid') !== -1 || input.indexOf('adList') !== -1) {
+ return input;
+ }
+ var output = '';
+ var chr1;
+ var chr2;
+ var chr3;
+ var enc1;
+ var enc2;
+ var enc3;
+ var enc4;
+ var i = 0;
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
+ while (i < input.length) {
+ enc1 = keyStr.indexOf(input.charAt(i++));
+ enc2 = keyStr.indexOf(input.charAt(i++));
+ enc3 = keyStr.indexOf(input.charAt(i++));
+ enc4 = keyStr.indexOf(input.charAt(i++));
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+ output = output + String.fromCharCode(chr1);
+ if (enc3 !== 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 !== 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+ }
+ output = utf8Decode(output);
+ return output;
+ };
+ };
+ var encodeURIStr = function (str) {
+ var result = encodeURIComponent(JSON.stringify(str));
+ return result;
+ };
+ var loadStatsToken = function() {
+ // 等广告全部加载完成,最后加载百度统计的token
+ var $tokenDiv = document.querySelector('.mip-stats-token-div');
+ var $tokenValue = document.querySelector('.mip-token-value');
+ $tokenDiv.innerHTML = '';
+ };
+ var ipLoad = function (callback) {
+// var url = 'http://ipip.iask.cn/iplookup/search?format=json&callback=?';
+ var url = 'https://mipp.iask.cn/iplookup/search?format=json&callback=?';
+ try {
+ $.getJSON(url, function (data) {
+ callback(data);
+ });
+ }
+ catch (e) {}
+ };
+
+ var hotRecommendUnLi = function (url, img, title, statsBaid, pos) {
+ var htmls = '';
+ if (pos === '') {
+ htmls += '';
+ }
+ else {
+ htmls += '
';
+ }
+ htmls += '
';
+ htmls += '' + title + '
';
+ htmls += '';
+ htmls += '
';
+ return htmls;
+ };
+ var hotRecommend = function (url, img, title, statsBaid, pos) {
+ var htmls = '';
+ htmls += '
';
+ if (pos === '') {
+ htmls += '';
+ }
+ else {
+ htmls += '
';
+ }
+ htmls += '
';
+ htmls += '' + title + '
';
+ htmls += '';
+ htmls += '
';
+ return htmls;
+ };
+ var hotSpotUnLi = function (url, title) {
+ var htmls = '';
+ htmls += '
' + title + '';
+ return htmls;
+ };
+ var getChannel = function () {
+ var list = {};
+ var $that = document.querySelectorAll('.channel_source li');
+ if($that.length > 0) {
+ for(var i=0; i< $that.length; i ++) {
+ var txt = $that[i].getAttribute('txt');
+ var val = $that[i].getAttribute('val');
+ list[txt] = val;
+ }
+ }
+ return list;
+ };
+ var getUserId = function (source, uid, adOwnerId) {
+ if (source === '202' || source === '500' || source === '501'
+ || source === '600' || source === '700' || source === '800') {
+ return uid;
+ }
+ else if (source === '1000' || source === '101') {
+ return adOwnerId;
+ }
+ return '';
+ };
+ var advLogInfo = function (sourceType, mode) {
+ var ele = this.document;
+ var $that = ele.querySelector('.paramDiv');
+ var qid = $that.getAttribute('qid') || '';
+ var materialTag = $that.getAttribute('mainTags') || '';
+ var ip = '';
+ var province = '';
+ var city = '';
+ var qcid = $that.getAttribute('qcid') || '';
+ var cid = $that.getAttribute('cid') || '';
+ var uid = $that.getAttribute('uid') || '';
+ var adOwnerId = $that.getAttribute('adOwnerId') || '';
+ var source = getChannel()[sourceType] || 999;
+ uid = getUserId(source, uid, adOwnerId) || busUid;
+ var pos = $that.getAttribute('pos') || '';
+ var pv = '';
+ if(pos === undefined || pos === 'undefined') {
+ pos = '';
+ }
+ ipLoad(function (data) {
+ ip = data.ip || '';
+ province = data.province || '';
+ city = data.city || '';
+ if (!!materialTag) {
+ materialTag = materialTag.replace('[', '').replace(']', '');
+ }
+ pv = encodeURI('pv=' + mode + '_' + qid + '_' + ip + '_' + province + '_' + city + '_' + materialTag
+ + '_' + qcid + '_' + cid + '_' + source + '_' + uid + '_' + pos);
+ var url = 'https://mipp.iask.cn/advLogInfo?' + pv;
+ $.ajax({
+ type: 'GET',
+ url: url
+ });
+ });
+ };
+
+ var advLogInfoClick = function (ele, $that) {
+ /*$('.href_log').on('click', function(){
+ var pos = $(this).attr('pos');
+ var url = $(this).attr('href');
+ openURL(pos, url);
+ });*/
+ $(ele).on('click', function(){
+ var pos = $(this).attr('pos');
+ var url = $(this).attr('href');
+ openURL(pos, url, $that);
+ });
+ };
+
+ function openURL(pos, url, $that) {
+ var sources = $that.getAttribute('sources');
+ var sysource = $that.getAttribute('sysources');
+ if (sysource === 'COMMERCIAL_ZWZD') {
+ sysource = 'COOPERATE_COMMERCIAL';
+ }
+ $that.setAttribute('pos', pos);
+ advLogInfo(sources, 1);
+ window.top.open(url);
+ }
+
+ var subStringIask = function (str, size) {
+ if (!str) {
+ return '';
+ }
+ if (str.length > size) {
+ return str.substring(0, size) + '...';
+ }
+ return str;
+ };
+ // 动态添加 mip-fixed悬浮广告
+ var putMXfAd = function (picLink, picLocal, statsBaidu, pos) {
+ var htmls = '';
+ htmls += '
';
+ htmls += '';
+ htmls += '
关闭
';
+ if (pos === '') {
+ htmls += '
';
+ }
+ else {
+ htmls += '
';
+ }
+ htmls += '';
+ htmls += '
';
+ htmls += '
';
+ htmls += '
';
+ return htmls;
+ };
+ var putMXfAdTel = function (picLink, picLocal, statsBaidu, pos) {
+ var htmls = '';
+ htmls += '
';
+ htmls += '';
+ return htmls;
+ };
+ var putQiyeInfo = function (companyName, drName, website, picLocal, statsBaidu, pos) {
+ var ele = this.document;
+ var $thatQS = ele.querySelectorAll('.qs_bar');
+ var $thatDiv = ele.querySelectorAll('.mip_as_other_qiye_div');
+ if (companyName.length > 9) {
+ companyName = companyName.substring(0, 9);
+ }
+ if(drName !== null && drName.length > 15) {
+ drName = drName.substring(0, 15);
+ }
+ var htmls = '
';
+ htmls += '
';
+ htmls += '';
+ htmls += '';
+ htmls += '
';
+ htmls += '
';
+ htmls += '
' + companyName + '';
+ htmls += ' 1小时前广告
';
+ htmls += '
' + drName + '
';
+ htmls += '
';
+ htmls += '
咨询专家';
+ htmls += '
';
+ htmls += '
';
+ try {
+ if ($thatQS.length > 0) {
+ $thatQS[0].innerHTML = htmls;
+ }
+ else if($thatDiv.length > 0){
+ $thatDiv[0].innerHTML = htmls;
+ }
+ advLogInfoClick('.mip_as_other_qiye_div .href_log', ele.querySelector('.paramDiv'));
+ } catch (e) {
+ console.log(e);
+ }
+ };
+ var putBrandQiyeInfo = function (companyName, drName, website, picLocal, statsBaidu, pos, brandLink) {
+ var ele = this.document;
+ var $thatQS = ele.querySelectorAll('.qs_bar');
+ var $thatDiv = ele.querySelectorAll('.mip_as_other_qiye_div');
+ if (companyName.length > 9) {
+ companyName = companyName.substring(0, 9);
+ }
+ var htmls = '';
+ htmls += '
';
+ htmls += '
';
+ htmls += '
' + companyName + '';
+ htmls += ' 1小时前广告
';
+ htmls += '
' + drName + '
';
+ htmls += '
';
+ htmls += '
咨询专家';
+ htmls += '
';
+ htmls += ' ';
+ if ($thatQS.length > 0) {
+ $thatQS[0].innerHTML = htmls;
+ advLogInfoClick('.qs_bar .href_log', ele.querySelector('.paramDiv'));
+ }
+ else if($thatDiv.length > 0){
+ $thatDiv[0].innerHTML = htmls;
+ advLogInfoClick('.mip_as_other_qiye_div .href_log', ele.querySelector('.paramDiv'));
+ }
+ };
+ var feedInfo = function (statsBaidu, userImg,useName, shortIntroduce, materialIntroduce, materialLink, picList, pos) {
+ var ele = this.document;
+ var $that = ele.querySelector('.youlai_feed_div');
+ var $thatLink = ele.querySelector('.youlai_feed_div a');
+ var $thatFeed = ele.querySelectorAll('.youlai_feed_div .youlai_feed');
+ var objPicUrl = '';
+ ele.querySelector('.youlai_feed_div .youlai_feed_title').innerHTML = materialIntroduce;
+ ele.querySelector('.youlai_feed_div .youlai_feed_use_img').innerHTML = objPicUrl;
+ ele.querySelector('.youlai_feed_div .youlai_feed_use_name').innerHTML = useName;
+ ele.querySelector('.youlai_feed_div .youlai_feed_txt').innerHTML = shortIntroduce;
+ $thatLink.setAttribute('pos', pos);
+ $thatLink.setAttribute('href', materialLink);
+ $thatLink.setAttribute('class', 'href_log');
+ $thatLink.setAttribute('data-stats-baidu-obj', statsBaidu);
+ if($thatFeed.length > 0) {
+ for(var i = 0; i < $thatFeed.length; i ++) {
+ $thatFeed[i].innerHTML = '';
+ }
+ }
+ addClass($that, 'show');
+ advLogInfoClick('.youlai_feed_div .href_log', ele.querySelector('.paramDiv'));
+ };
+ // 商业广告
+ var busBottomAM = function () {
+ var $thatBottomDiv = document.querySelector('.mip_as_bottm_div');
+ var $thatBottom = document.querySelectorAll('.bus_bottom_div div');
+ var $thatHot = document.querySelectorAll('.bus_hot_recommend_div div');
+ var $thatRecomd = document.querySelector('.hot_recomd_div');
+ var $thatHotSpot = document.querySelectorAll('.bus_hot_spot div');
+ if($thatBottom.length > 0) {
+ for(var i = 0; i<$thatBottom.length; i ++ ) {
+ var area = $thatBottom[i].getAttribute('area');
+ var imgurl = $thatBottom[i].getAttribute('imgurl');
+ var picurl = $thatBottom[i].getAttribute('picurl');
+ busUid = $thatBottom[i].getAttribute('uid');
+ if (area === '') { // 区域为空表示投放全国
+ $thatBottomDiv.innerHTML = putMXfAd(imgurl, picurl, '', '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ }
+ else {
+ ipLoad(function (data) {
+ if (area.indexOf(data.province) > -1) {
+ $thatBottomDiv.innerHTML = putMXfAd(imgurl, picurl, '', '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ }
+ });
+ }
+ }
+ }
+ if($thatHot.length > 0) {
+ for(var i=0; i< $thatHot.length; i ++) {
+ var area = $thatHot[i].getAttribute('area');
+ var url = $thatHot[i].getAttribute('texturl');
+ var img = $thatHot[i].getAttribute('img');
+ var title = $thatHot[i].getAttribute('imgtitle');
+ busUid = $thatHot[i].getAttribute('uid');
+ if (area === '') {
+ var str = hotRecommendUnLi(url, img, title, '', '');
+ var liDom = document.createElement("li");
+ liDom.innerHTML = str;
+ document.querySelector('.hot-tui-list').appendChild(liDom);
+ }
+ else {
+ ipLoad(function (data) {
+ var str = hotRecommendUnLi(url, img, title, '', '');
+ var liDom = document.createElement("li");
+ liDom.innerHTML = str;
+ document.querySelector('.hot-tui-list').appendChild(liDom);
+ });
+ }
+ }
+ addClass($thatRecomd, 'show');
+ advLogInfoClick('.hot_recomd_div .href_log', document.querySelector('.paramDiv'));
+ }
+
+ if($thatHotSpot.length > 0) {
+ for(var i=0; i<$thatHotSpot.length; i++) {
+ var area = $thatHotSpot[i].getAttribute('area');
+ var url = $thatHotSpot[i].getAttribute('texturl');
+ var title = $thatHotSpot[i].getAttribute('imgtitle');
+ busUid = $thatHotSpot[i].getAttribute('uid');
+ if (area === '') {
+ var str = hotSpotUnLi(url, title);
+ var liDom = document.createElement("li");
+ liDom.innerHTML = str;
+ document.querySelector('.hot-point-list').appendChild(liDom);
+ }
+ else {
+ ipLoad(function (data) {
+ var str = hotSpotUnLi(url, title);
+ var liDom = document.createElement("li");
+ liDom.innerHTML = str;
+ document.querySelector('.hot-point-list').appendChild(liDom);
+ });
+ }
+ }
+ advLogInfoClick('.hot-point-list .href_log', document.querySelector('.paramDiv'));
+ }
+ loadStatsToken();
+ };
+ var validatePut = function () {
+ var ele = this.document;
+ var $that = ele.querySelectorAll('.paramDiv')[0];
+ var mmaintags = $that.getAttribute('mainTags');
+ var qcid = $that.getAttribute('qcid') || '';
+ var sources = $that.getAttribute('sources');
+ var version = $that.getAttribute('version');
+ var iscommercial = $that.getAttribute('iscommercial');
+ if ('COOPERATE_BRAND' === sources && version === '2') {
+ return false;
+ }
+ if (iscommercial === 'true' && sources !== 'COOPERATE_COMMERCIAL') { // 过滤掉第三合作广告
+ return false;
+ }
+ if (qcid === '82' && (mmaintags.indexOf('财务税务') !== -1 || mmaintags.indexOf('商业工具') !== -1)) {
+ return true;
+ }
+ return false;
+ };
+ var putTestButHtml = function (putUrl, picUrl) {
+ var statsBaidu = 'data-stats-baidu-obj="%20%7B%22type%22:%22click%22,'
+ + '%22data%22:%22%5B\'_setCustomVar\',%20\'100m,%20\'0\',%20\'8002m\'%5D%22%7D"';
+ return putMXfAd(putUrl, picUrl, statsBaidu, '');
+ };
+ // 移除百度广告
+ var removeBaiduAd = function () {
+ var $that = document.querySelectorAll('.mip_baidu_sy');
+ if($that.length > 0) {
+ for(var i=0; i<$that.length; i++) {
+ var t = $that[i];
+ t.parentNode.removeChild(t);
+ }
+ }
+ };
+ var youLai = function (data) {
+ var ele = this.document;
+ var $thatDiv = ele.querySelector('.mip_as_bottm_div');
+ var $thatHotList = ele.querySelector('.hot-tui-list');
+ var $thatHotDiv = ele.querySelector('.hot_recomd_div');
+
+ var json = data.adList;
+ for (var key in json) {
+ if (json[key].type === '4') {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_1000", "skip", "MIP_SY_1000_top"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ $thatDiv.innerHTML = putMXfAd(json[key].picLink, json[key].picUrl, baiduObj, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', ele.querySelector('.paramDiv'));
+ }
+ else if (json[key].type === '3') { // 企业信息
+ var obj = json[key];
+ var companyName = obj.companyName || '';
+ var drName = obj.drName || '';
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_1000", "skip", "MIP_SY_1000_qy"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ putQiyeInfo(drName, companyName, data.website, obj.picUrl, baiduObj, '');
+ }
+ else if (json[key].type === '5') {
+ var obj2 = {};
+ for (var k in json) {
+ if (json[k].type === '3') {
+ obj2 = json[k];
+ }
+ }
+ var obj = json[key];
+ var picList = obj.picList;
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_1000", "skip", "MIP_SY_1000_feed"]};
+ var baiduObj = encodeURIStr(baiduStr);
+ feedInfo(baiduObj, obj2.picUrl, obj2.companyName,obj.describe, obj.title, obj.picLink, picList, '');
+ }
+ else if (json[key].type === '6') {
+ var obj = json[key];
+ var picList = obj.adDetailList;
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_1000", "skip", "MIP_SY_1000_mpic"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var str = '';
+ for (var pic in picList) {
+ var picLink = obj.picLink;
+ var picUrl = picList[pic].picUrl;
+ var describe = picList[pic].describe;
+ str += hotRecommend(picLink, picUrl, describe, baiduObj, '');
+ }
+ $thatHotList.innerHTML = str;
+ addClass($thatHotDiv, 'show');
+ advLogInfoClick('.hot-tui-list .href_log', ele.querySelector('.paramDiv'));
+ }
+ }
+ loadStatsToken();
+ };
+
+ var soulew = function (data) {
+ var ele = this.document;
+ var $thatDiv = ele.querySelector('.mip_as_bottm_div');
+ var $thatHotList = ele.querySelector('.hot-tui-list');
+ var $thatHotDiv = ele.querySelector('.hot_recomd_div');
+ var json = data.adList;
+ for (var key in json) {
+ if (json[key].type === '4') {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_101", "skip", "MIP_SY_101_SY"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ $thatDiv.innerHTML = putMXfAd(json[key].picLink, json[key].picUrl, baiduObj, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', ele.querySelector('.paramDiv'));
+ }
+ else if (json[key].type === '3') { // 企业信息
+ var obj = json[key];
+ var companyName = obj.companyName || '';
+ var drName = obj.drName || '';
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_101", "skip", "MIP_SY_101_SY"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ putQiyeInfo(drName, companyName, data.website, obj.picUrl, baiduObj, '');
+ }
+ else if (json[key].type === '5') {
+ var obj2 = {};
+ for (var k in json) {
+ if (json[k].type === '3') {
+ obj2 = json[k];
+ }
+ }
+ var obj = json[key];
+ var picList = obj.picList;
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_101", "skip", "MIP_SY_101_SY"]};
+ var baiduObj = encodeURIStr(baiduStr);
+ feedInfo(baiduObj, obj2.picUrl, obj.companyName, obj.describe,obj.title, obj.picLink, picList, '');
+ }
+ else if (json[key].type === '6') {
+ var obj = json[key];
+ var picList = obj.adDetailList;
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_101", "skip", "MIP_SY_101_SY"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var str = '';
+ for (var pic in picList) {
+ var picLink = obj.picLink;
+ var picUrl = picList[pic].picUrl;
+ var describe = picList[pic].describe;
+ str += hotRecommend(picLink, picUrl, describe, baiduObj, '');
+ }
+ $thatHotList.innerHTML = str;
+ addClass($thatHotDiv, 'show');
+ advLogInfoClick('.hot-tui-list .href_log', ele.querySelector('.paramDiv'));
+ }
+ }
+ loadStatsToken();
+ };
+
+ var removeClass = function (obj, cls) {
+ var obj_class = ' '+obj.className+' ';//获取 class 内容, 并在首尾各加一个空格. ex) 'abc bcd' -> ' abc bcd '
+ obj_class = obj_class.replace(/(\s+)/gi, ' ');//将多余的空字符替换成一个空格. ex) ' abc bcd ' -> ' abc bcd '
+ var removed = obj_class.replace(' '+cls+' ', ' ');//在原来的 class 替换掉首尾加了空格的 class. ex) ' abc bcd ' -> 'bcd '
+ removed = removed.replace(/(^\s+)|(\s+$)/g, '');//去掉首尾空格. ex) 'bcd ' -> 'bcd'
+ obj.className = removed;//替换原来的 class.
+ };
+ var addClass = function (obj, cls) {
+ var obj_class = ' '+obj.className+' ';
+ var add = obj_class +' ' + cls;
+ obj.className = add;
+ };
+ var brandMedical = function (data) { // 品牌医疗
+ var json = data.adList;
+ var qiyeData = null;
+ var ele = this.document;
+ var $thatDiv = ele.querySelector('.mip_as_bottm_div');
+ var $thatHotList = ele.querySelector('.hot-tui-list');
+ var $thatHotDiv = ele.querySelector('.hot_recomd_div');
+ for (var k in json) {
+ if (json[k].adType === 3) {
+ qiyeData = json[k];
+ }
+ }
+ for (var key in json) {
+ if (json[key].adType === 1) {
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_800", "skip", "MIP_SY_800_top"]};
+ var statsBaidu = 'data-stats-baidu-obj="' + encodeURIStr(tempStr) + '"';
+ $thatDiv.innerHTML = putMXfAd(json[key].materialLink, json[key].materialImg, statsBaidu, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', ele.querySelector('.paramDiv'));
+ }
+ else if (json[key].adType === 3) { // 企业信息
+ var obj = qiyeData;
+ var companyName = obj.shortIntroduce || '';
+ var drName = obj.brandName || '';
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_800", "skip", "MIP_SY_800_qy"]};
+ var statsBaidu = 'data-stats-baidu-obj="' + encodeURIStr(tempStr) + '"';
+ putQiyeInfo(drName, companyName, obj.materialLink, obj.materialImg, statsBaidu, '');
+ }
+ else if (json[key].adType === 5) {
+ var obj = json[key];
+ var picList = new Array(0);
+ var materImg = obj.materialImg.split(",");
+ for(var k in materImg) {
+ picList.push(materImg[k]);
+ }
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_800", "skip", "MIP_SY_800_feed"]};
+ var statsBaidu = encodeURIStr(tempStr);
+ feedInfo(statsBaidu, qiyeData.materialImg, qiyeData.brandName, obj.materialIntroduce, obj.shortIntroduce, obj.materialLink, picList, '');
+ }
+ else if (json[key].adType === 6) {
+ var obj = json[key];
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_800", "skip", "MIP_SY_800_mpic"]};
+ var statsBaidu = 'data-stats-baidu-obj="' + encodeURIStr(tempStr) + '"';
+ var arrayPic = new Array(0);
+ var arrayDesc = new Array(0);
+ var materImg = obj.materialImg.split(",");
+ var materDesc = obj.materialIntroduce.split("|");
+ for(var index in materImg) {
+ arrayPic.push(materImg[index]);
+ arrayDesc.push(materDesc[index]);
+ }
+ var str = '';
+ for (var pic in arrayPic) {
+ var picLink = obj.materialLink;
+ var picUrl = arrayPic[pic];
+ var describe = arrayDesc[pic];
+ str += hotRecommend(picLink, picUrl, describe, statsBaidu, '');
+ }
+ $thatHotList.innerHTML = str;
+ removeClass($thatHotDiv, 'mask');
+ advLogInfoClick('.hot-tui-list .href_log', ele.querySelector('.paramDiv'));
+ }
+ }
+ loadStatsToken();
+ };
+
+ var tianZhu = function (data) {
+ var ele = this.document;
+ var $thatDiv = ele.querySelector('.mip_as_bottm_div');
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_300", "skip", "MIP_SY_300_sj"]};
+ var statsBaidu = 'data-stats-baidu-obj="' + encodeURIStr(tempStr) + '"';
+ $thatDiv.innerHTML = putMXfAd(data.pics[3].picLink, data.pics[3].picLocal, statsBaidu, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', ele.querySelector('.paramDiv'));
+ loadStatsToken();
+ };
+ // 商业广告标准版企业信息
+ var commercialSqc = function (divData, commercialStandardHover) {
+ var ele = this.document;
+ var $thatDiv = ele.querySelector('.mip_as_bottm_div');
+ var tempStr = {"type":"click", "data":["_trackEvent", "MIP_SY_600", "skip", "MIP_SY_600_2_qy"]};
+ var statsBaidu = 'data-stats-baidu-obj="' + encodeURIStr(tempStr) + '"';
+ var imgsrc = divData.getAttribute('imgsrc');
+ var brandname = divData.getAttribute('brandname');
+ var link = divData.getAttribute('link');
+ var introduce = divData.getAttribute('introduce');
+ var uid = divData.getAttribute('uid');
+ var brandLink = 'http://m.iask.sina.com.cn/brand/' + uid + '.html';
+ putBrandQiyeInfo(brandname, introduce, link, imgsrc, statsBaidu, '', brandLink);
+ var tImgSrc = commercialStandardHover.getAttribute('imgsrc');
+ var tLink = commercialStandardHover.getAttribute('link');
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_600", "skip", "MIP_SY_600_2_top"]};
+ var baiduTop = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ $thatDiv.innerHTML = putMXfAd(tLink, tImgSrc, baiduTop, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', ele.querySelector('.paramDiv'));
+ loadStatsToken();
+ };
+ var loadAd = function (sources, openId, questionId, version) {
+ var type = '';
+ if (sources === 'COOPERATE_HUASHENG') {
+ type = 'HS';
+ }
+ else if (sources === 'COOPERATE_HUASHENG_QA') {
+ type = 'HSQA';
+ }
+ else if (sources === 'COOPERATE_YOULAI') {
+ type = 'YL';
+ }
+ else if (sources === 'COOPERATE_TIANZHU') {
+ type = 'TZ';
+ }
+ else if(sources === 'COOPERATE_BRAND_MARKET' && version === '4') {
+ type = 'PPYL';
+ }
+ else if(sources === 'COOPERATE_SOULE') {
+ type = 'SLW';
+ }
+ else if (type === '') {
+ return;
+ }
+// var url = 'https://mipp.iask.cn/t/wlsh?openCorporationId=' + openId + '&type=' + type;
+ var url = 'https://mipp.iask.cn/t/wlsh?openCorporationId=' + openId + '&type=' + type +"&questionId=" + questionId + "&version=" + version;
+ $.get(url,
+ function (data) {
+ var base = new Base64();
+ var res = $.parseJSON(data);
+ if (res.succ === 'Y') {
+ var json = $.parseJSON(base.decode(res.html));
+ var htmls = '';
+ if (type === 'YL') {
+ youLai(json);
+ return;
+ }
+ if (type === 'TZ') {
+ tianZhu(json);
+ return;
+ }
+ if (type === 'PPYL') {
+ brandMedical(json);
+ return;
+ }
+ if (type === 'SLW') {
+ soulew(json);
+ return;
+ };
+ if (type === 'XYH') {
+ isHuasheng = false;
+ htmls = putMXfAd(json.pics[1].picLink, json.pics[1].picLocal, '');
+ }
+ else {
+ var pic = json.pics[3] || '';
+ htmls = putMXfAd(pic.picLink, pic.picLocal, '');
+ var companyName = json.companyName || '';
+ var drName = json.drName || '';
+ var website = json.website || '';
+ var pic = json.pics[0] || '';
+ putQiyeInfo(companyName, drName, website, pic.picLocal, '', '');
+ }
+ $('.mip_as_bottm_div').empty();
+ $('.mip_as_bottm_div').append(htmls);
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ loadStatsToken();
+ }
+ });
+ };
+ // 加载url中的js
+ var loadURLJS = function (tags, params, sourceType) {
+// var url = 'https://mipp.iask.cn/mib/tag/';
+ var url = 'https://mipp.iask.cn/mib/tag';
+ var arry = tags.split(':');
+ for (var i = 0; i < arry.length; i++) {
+ url = url + arry[i].replace('[', '').replace(']', '');
+ }
+ try {
+ var province = ''; // 省份
+ var city = ''; // 城市
+ var ip = '';
+ ipLoad(function (data) {
+ province = data.province;
+ city = data.city;
+ ip = data.ip;
+ $.get(url, function (datas) {
+ var res = '';
+ try {
+ res = $.parseJSON(datas);
+ if (res.succ === 'Y') { // 不等于空
+ var paramsArry = params.split(':');
+ var cmJsonData = $.parseJSON(res.html);
+ var lenGood = parseInt(paramsArry[0], 0); // 好评回答数量
+ var lenOther = parseInt(paramsArry[1], 0); // 普通答案数量
+ var qSourceType = paramsArry[2]; // 来源
+ var commercialSource = paramsArry[3]; // 商业广告类型
+ var qTags = paramsArry[4]; // 标签
+ var mainTags = paramsArry[5]; // 病种
+ var nowTime = getSysTime(); // 时间
+ var qCid = paramsArry[6] || '79';
+ var bCid = paramsArry[7];
+ var sCid = paramsArry[8];
+ var qid = paramsArry[9];
+ if ('undefined' !== typeof cmJsonData) {
+ var param = loadInit({
+ mainTags: mainTags,
+ province: province,
+ qCid: qCid,
+ bCid: bCid,
+ city: city,
+ lenGood: lenGood,
+ lenother: lenOther,
+ commercialSource: commercialSource,
+ qSourceType: qSourceType,
+ qTags: qTags,
+ nowTime: nowTime
+ });
+ loadData(param, cmJsonData);
+ advLogInfo(sourceType, 0);
+ }
+ }
+ } catch (e) {}
+ });
+ });
+ }
+ catch (e) {
+ }
+
+ };
+
+ var loadInit = function (options) {
+ var defaults = {
+ province: '',
+ lenGood: 0,
+ lenother: 0,
+ commercialSource: '',
+ qSourceType: ''
+ };
+ var opts = $.extend(defaults, options);
+ return opts;
+ };
+
+ var loadData = function (options, cmJsonData) {
+ var iscmBy = false;
+ if (options.commercialSource === 'COMMERCIAL_ZWZD' || options.qSourceType === 'COOPERATE_SOUTHNETWORK'
+ || options.qSourceType === 'COOPERATE_HUASHENG'
+ || options.qSourceType === 'COOPERATE_HUASHENG_QA') {
+ return iscmBy;
+ }
+ // 如果ip获取不到城市信息,则根据省份进行广告主病种随机投放
+ if (!options.city) {
+ return noCityPutAd(options, cmJsonData);
+ }
+ $.each(cmJsonData,
+ function (index) {
+ try {
+ var val = cmJsonData[index];
+ var qTags = val.qTags;
+ var mainTags = val.mainTags;
+ var startTime = val.startTime;
+ var endTime = val.endTime;
+ var province = '不限';
+ var nprovince = '不限';
+ var city = '不限';
+ var ncity = '不限';
+ if (!!val.city) {
+ city = val.city;
+ }
+ if (!!val.ncity) {
+ ncity = val.ncity;
+ }
+ if (!!val.province) {
+ province = val.province;
+ }
+ if (!!val.nprovince) {
+ nprovince = val.nprovince;
+ }
+ if (options.qCid === '79' || options.bCid === '147') {
+ if (checkTime(options.nowTime, startTime, endTime)
+ && checkProvince(options.province, province, nprovince)
+ && checkCity(options.city, city, ncity)
+ && checkTag(options.qTags, qTags, options.mainTags, mainTags)) {
+ adPut(val, options);
+ iscmBy = true;
+ }
+ }
+ }
+ catch (e) {}
+ });
+ return iscmBy;
+ };
+
+ var adPut = function (val, options) {
+ if (val === undefined) {
+ return;
+ }
+ var adPosition = val.adPosition;
+ var arry = adPosition.split(',');
+ for (var i = 0; i < arry.length; i++) {
+ callMethod(arry[i], val, options);
+ }
+ };
+ var callMethod = function (tp, val, options) {
+ try {
+ if (tp === '1') {
+ put1(val, options);
+ } else if (tp === '2') {
+ put2(val, options);
+ } else if (tp === '3') {
+ put3(val, options);
+ }
+ }
+ catch (e) {}
+ };
+ var putAppend = function (pos, options, htmls) {
+ };
+ var put1 = function (val, options) {
+ putAppend(1, options, putQiyeInfo(val.hospitalName, val.contacts, val.url, val.logo, '', ''));
+ };
+ var put2 = function (val, options) {
+ putAppend(2, options, putQiyeInfo(val.hospitalName, val.contacts, val.url, val.logo, '', ''));
+ };
+ var put3 = function (val, options) {
+ var $that = document.querySelector('.mip_as_bottm_div');
+ $that.innerHTML = putMXfAd(val.url, val.mSuspensionImage, '', '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ };
+
+ function noCityPutAd(options, cmJsonData) {
+ var iscmBy = false;
+ var arr1 = new Array(0);
+ var arr2 = new Array(0);
+ var arr3 = new Array(0);
+ $.each(cmJsonData,
+ function (index) {
+ try {
+ var val = cmJsonData[index];
+ var qTags = val.qTags;
+ var mainTags = val.mainTags;
+ var startTime = val.startTime;
+ var endTime = val.endTime;
+ var province = '不限';
+ var nprovince = '不限';
+ if (!!val.province) {
+ province = val.province;
+ }
+ if (!!val.nprovince) {
+ nprovince = val.nprovince;
+ }
+ if (options.qCid === '79' || options.bCid === '147') {
+ if (checkTime(options.nowTime, startTime, endTime)
+ && checkProvince(options.province, province, nprovince)
+ && checkTag(options.qTags, qTags, options.mainTags, mainTags)) {
+ var adPosition = val.adPosition;
+ var arry = adPosition.split(',');
+ for (var i = 0; i < arry.length; i++) {
+ if (arry[i] === '1') {
+ arr1.push(val);
+ }
+ if (arry[i] === '2') {
+ arr2.push(val);
+ }
+ if (arry[i] === '3') {
+ arr3.push(val);
+ }
+ iscmBy = true;
+ }
+
+ }
+ }
+ }
+ catch (e) {}
+ });
+ if (arr1.length > 0) {
+ var index1 = fRandomBy(0, arr1.length - 1);
+ adPut(arr1[index1], options);
+ }
+ if (arr2.length > 0) {
+ var index2 = fRandomBy(0, arr2.length - 1);
+ adPut(arr2[index2], options);
+ }
+ if (arr3.length > 0) {
+ var index3 = fRandomBy(0, arr3.length - 1);
+ adPut(arr3[index3], options);
+ }
+ return iscmBy;
+ }
+ var checkTime = function (nowTime, startTime, endTime) {
+ if (startTime <= nowTime && nowTime < endTime) {
+ return true;
+ }
+ return false;
+ };
+ var checkCity = function (city, putCity, nCity) {
+ if (nCity.indexOf(city) !== -1) {
+ return false;
+ }
+ if (putCity === '不限') {
+ return true;
+ }
+ if (putCity.indexOf(city) !== -1) {
+ return true;
+ }
+ return false;
+ };
+ var checkProvince = function (province, putProvince, nprovince) {
+ if (nprovince.indexOf(province) !== -1 && nprovince !== '不限') {
+ return false;
+ }
+ if (putProvince === '不限') {
+ return true;
+ }
+ if (putProvince.indexOf(province) !== -1) {
+ return true;
+ }
+ return false;
+ };
+ var checkTag = function (tag, putTag, mainTags, putMainTags) {
+ if (!!putTag) {
+ var arr = putTag.split(',');
+ for (var i = 0; i < arr.length; i++) {
+ if (tag.indexOf(arr[i]) !== -1) {
+ return true;
+ }
+ }
+ }
+ if (!!putMainTags) {
+ var arr = putMainTags.split(',');
+ for (var i = 0; i < arr.length; i++) {
+ if (mainTags.indexOf(arr[i]) !== -1) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+ var fRandomBy = function (under, over) {
+ switch (arguments.length) {
+ case 1 :
+ return parseInt((Math.random() * under + 1), 0);
+ case 2:
+ return parseInt((Math.random() * (over - under + 1) + under), 0);
+ default:
+ return 0;
+ }
+ };
+ var getSysTime = function () {
+ var mydate = new Date();
+ var month = mydate.getMonth() + 1;
+ var day = mydate.getDate();
+ var hours = mydate.getHours();
+ var minutes = mydate.getMinutes();
+ var seconds = mydate.getSeconds();
+ return mydate.getFullYear() + '-' + (month < 10 ? '0' + month : month)
+ + '-' + (day < 10 ? '0' + day : day) + ' ' + (hours < 10 ? '0' + hours : hours)
+ + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
+ };
+ // 广告
+ var currencyAM = function (sourceType, openId, questionId, version) {
+ loadAd(sourceType, openId, questionId, version);
+ advLogInfo(sourceType, 0);
+ };
+ var southnetwork = function (openId) {
+ var ele = this.document;
+ var $thatHotList = ele.querySelector('.hot-tui-list');
+ var $thatHotDiv = ele.querySelector('.hot_recomd_div');
+ var $that = ele.querySelector('.mip_as_bottm_div');
+ var url = 'https://imgv2-ssl.g3user.com/api/iask.php?uid=' + openId + '&type=m&callback=?';
+ try {
+ $.getJSON(url, function (data) {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_100", "skip", "MIP_SY_100_sj"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var htmls = putMXfAd(data.mobile.link, data.mobile.pic, baiduObj, '');
+ $that[0].innerHTML = htmls;
+ putQiyeInfo(data.logo.brand, data.logo.intro, data.logo.link, data.logo.pic, baiduObj, '');
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ if(data.feed !== null && data.feed.length > 0) {
+ var str = '';
+ for(var index in data.feed) {
+ var picLink = data.feed[index].link;
+ var picUrl = data.feed[index].pic;
+ var picDesc = data.feed[index].title;
+ str += hotRecommend(picLink, picUrl, picDesc, baiduObj, '');
+ }
+ $thatHotList.innerHTML = str;
+ addClass($thatHotDiv, 'show');
+ advLogInfoClick('.hot-tui-list .href_log', document.querySelector('.paramDiv'));
+ }
+ advLogInfo('COOPERATE_SOUTHNETWORK', 0);
+ loadStatsToken();
+ });
+ }
+ catch (e) {}
+ };
+
+ // 商业效果广告
+ var effectAvertisement = function (questionId, sourceType) {
+ ipLoad(function (data) {
+ var provinceCode = data.provinceCode;
+ var url = 'https://mipp.iask.cn/mib/tag/test?q=' + questionId + '&c=' + provinceCode;
+ try {
+ $.getJSON(url, function (res) {
+ if (res.jsonData != null) {
+ advEffectCallBack(res.jsonData);
+ advLogInfo(sourceType, 0);
+ loadStatsToken();
+ }
+ else {
+ advLogInfo('', 0);
+ }
+ });
+ }
+ catch (e) {
+ }
+ });
+ };
+ var advEffectCallBack = function (dd) {
+ var ele = this.document;
+ var $that = ele.querySelector('.paramDiv');
+ var $thatDiv = ele.querySelector('.baidu_label_div');
+ var list = dd.materialList;
+ var ve = dd.version;
+ var channelCode = dd.channelCode;
+ $that.setAttribute('uid', dd.userId);
+ if ("2" !== ve) {
+ // 如果不是标准版,则删除百度广告
+ removeBaiduAd();
+ }
+ for (var i = 0; i < list.length; i++) {
+ var obj = list[i];
+ if (ve === '1') {
+ showEffectAdv(obj, 1, channelCode);
+ }
+ else if (ve === '2') {
+ showEffectAdv(obj, 2, channelCode);
+ }
+ else {
+ showEffectAdv(obj, 3, channelCode);
+ }
+ }
+ var baiduStr = {"type":"load", "data":["_setPageTag", "MIP_SY_700", "skip", "MIP_SY_效果广告"]};
+ var baiduObj = '';
+ $thatDiv.innerHTML = baiduObj;
+ };
+ var showEffectAdv = function (json, tp, channelCode) {
+ var ele = this.document;
+ var $that = ele.querySelector('.mip_as_bottm_div');
+ var $thatParam = ele.querySelector('.paramDiv');
+ if (json.adType === '3') {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_700", "skip", "MIP_SY_700_' + tp + '_qiye"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var brandLink = '';
+ var uid = $thatParam.getAttribute('uid');
+ if('10002' === channelCode) {
+ brandLink = json.materialLink; // 南方网通效果广告-链接跳转自己的物料链接
+ }
+ else {
+ brandLink = 'http://m.iask.sina.com.cn/brand/' + uid + '.html';
+ }
+ putBrandQiyeInfo(json.brandName, json.shortIntroduce, json.materialLink, json.materialImg, baiduObj, '', brandLink);
+ return;
+ }
+ if (json.adType === '2') {
+ return;
+ }
+ // 旗舰版feed
+ if (json.adType === '5') {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_700", "skip", "MIP_SY_700_' + tp + '_feed"]};
+ var materialImg = json.materialImg;
+ var picList = materialImg.split(',');
+ var picUrl = 'http://tp2.sinaimg.cn/1169181841/50/0/1';
+ feedInfo(encodeURIStr(baiduStr), picUrl, json.brandName, json.shortIntroduce, json.materialIntroduce, json.materialLink, picList, '');
+ return;
+ }
+ // 旗舰版-顶部悬浮
+ if (json.materialType === '5' && tp === 1) {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_700", "skip", "MIP_SY_700_' + tp + '_top"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var htmls = putMXfAd(json.materialLink, json.materialImg, baiduObj, '');
+ $that.innerHTML = htmls;
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ return;
+ }
+ // 标准版顶部悬浮
+ if (json.materialType === '5' && tp === 2) {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_700", "skip", "MIP_SY_700_' + tp + '_top"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var htmls = putMXfAd(json.materialLink, json.materialImg, baiduObj, '');
+ $that.innerHTML = htmls;
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ return;
+ }
+ // 专业版-顶部悬浮
+ if (json.materialType === '5' && tp === 3) {
+ var baiduStr = {"type":"click", "data":["_trackEvent", "MIP_SY_700", "skip", "MIP_SY_700_' + tp + '_top"]};
+ var baiduObj = 'data-stats-baidu-obj="' + encodeURIStr(baiduStr) + '"';
+ var htmls = putMXfAd(json.materialLink, json.materialImg, baiduObj, '');
+ $that.innerHTML = htmls;
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ return;
+ }
+ };
+ var brandAvertisement = function (sourceType) {
+ advLogInfo(sourceType, 0);
+ advLogInfoClick('.href_log', document.querySelector('.paramDiv'));
+ loadStatsToken();
+ };
+ // 保险广告
+ var iAskDisplayHtml = {
+ iaskCallMethod: function (opts) {
+ var type = opts.type;
+ var dsbo = this.iaskInsuranceLabel(type);
+ if (type === '1') {
+ this.topSuspension(opts, dsbo); // 顶部悬浮
+ }
+ else if (type === '2') {
+ this.answerInfo(opts, dsbo); // 企业信息
+ }
+ else if (type === '3') {
+ this.image(opts, dsbo);
+ }
+ else if (type === '4') {
+ this.hot(opts, dsbo); // 热门推荐
+ }
+ else if (type === '5') {
+ this.feed(opts, dsbo); // feed 广告
+ }
+ else if (type === '6') {
+ this.wenzilian(opts, dsbo); // 文字链接
+ }
+ else if (type === '7') {
+ this.tuijian(opts, dsbo); // 推荐
+ }
+ else if (type === '8') {
+ this.info(opts, dsbo);
+ }
+ },
+ getUUid: function () { // 随机ID
+ return 'iAsk-uuid-' + Math.random().toString(36).slice(2);
+ },
+ topSuspension: function (opts, dsbo) {
+ var $that = document.querySelector('.mip_as_bottm_div');
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ $that.innerHTML = putMXfAdTel(opts.phone, opts.mPicUrl, statsBaidu, opts.type);
+ advLogInfoClick('.mip_as_bottm_div .href_log', document.querySelector('.paramDiv'));
+ },
+ answerInfo: function (opts, dsbo) {
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ putQiyeInfo(opts.drName, opts.companyName, opts.website, opts.picUrl, statsBaidu, opts.type);
+ },
+ feed: function (opts, dsbo) {
+ var object = this.conversionsObject(opts);
+ feedInfo(dsbo, object.picUrl, object.companyName, object.title, object.describe, object.picLink, object.picList, object.type);
+ },
+ hot: function (opts, dsbo) {
+ var $that = document.querySelector('.hot-tui-list');
+ var $thatDiv = document.querySelector('.hot_recomd_div');
+ var object = this.conversionsObject(opts);
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ var str = '';
+ for (var pic in object.adDetailList) {
+ var picUrl = object.adDetailList[pic].picUrl;
+ var describe = object.adDetailList[pic].describe;
+ str += hotRecommend(object.picLink, picUrl, describe, statsBaidu, object.type);
+ }
+ $that.innerHTML = str;
+ $($thatDiv).addClass("show");
+ advLogInfoClick('.hot-tui-list .href_log', document.querySelector('.paramDiv'));
+ },
+ tuijian: function (opts, dsbo) {
+ var $that = document.querySelector('.wj_relative_kownlege');
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ var object = this.conversionsObject(opts);
+ var html = '';
+ html += '
为您推荐
';
+ html += '
';
+ html += '';
+ html += '
';
+ html += '
' + object.adDetailList[0].describe + '
';
+ html += '';
+ html += '
';
+ html += '
' + object.adDetailList[1].describe + '
';
+ html += '';
+ html += '
';
+ html += '
' + object.adDetailList[2].describe + '
';
+ html += '
';
+ $that.innerHTML = html;
+ advLogInfoClick('.wj_relative_kownlege .href_log', document.querySelector('.paramDiv'));
+ },
+ wenzilian: function (opts, dsbo) {
+ var $that = document.querySelector('.wj_wait_question_as');
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ var object = this.conversionsObject(opts);
+ var html = '';
+ $that.innerHTML = html;
+ addClass($that, 'background-color-eee');
+ advLogInfoClick('.wj_wait_question_as .href_log', document.querySelector('.paramDiv'));
+ },
+ image: function (opts, dsbo) {
+ var $that = document.querySelector('.wj_djgz_as');
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ var object = this.conversionsObject(opts);
+ var html = '';
+ html += '';
+ html += '广告
';
+ $that.innerHTML = html;
+ advLogInfoClick('.wj_djgz_as .href_log', document.querySelector('.paramDiv'));
+ },
+ info: function (opts, dsbo) {
+ var $that = document.querySelector('.wj_hot_top_as');
+ var statsBaidu = 'data-stats-baidu-obj="' + dsbo + '"';
+ var object = this.conversionsObject(opts);
+ var html = '';
+ $that.innerHTML = html;
+ advLogInfoClick('.wj_hot_top_as .href_log', document.querySelector('.paramDiv'));
+ },
+ iaskInsuranceLabel: function (type) {
+ var $that = document.querySelectorAll('.iaskInsuranceDsbo')[0];
+ var dsbo = $that.getAttribute('dsbo');
+ return dsbo.replace('number', type);
+ },
+ conversionsObject: function (opts) {
+ var nObject = opts;
+ nObject.title = opts.title || '';
+ nObject.picLink = opts.picLink || '';
+ nObject.picList = opts.picList || {};
+ nObject.describe = opts.describe || '';
+ nObject.picUrl = opts.picUrl || '';
+ nObject.czctype = 'MIP_SY_901';
+ nObject.czcintroduce = 'MIP_SY_901_' + opts.type;
+ nObject.companyName = opts.companyName || '';
+ nObject.pos = opts.pos || '';
+ return nObject;
+ }
+ };
+ var iaskInsurance = function (data) {
+ var ele = this.document;
+ var $that = ele.querySelector('.paramDiv');
+ $that.setAttribute('sources', 'COOPERATE_BAOXIAN');
+ try {
+ var base = new Base64();
+ var res = $.parseJSON(data);
+ var json = null;
+ if (res.succ === 'Y') {
+ json = $.parseJSON(base.decode(res.html));
+ }
+ else {
+ return;
+ }
+ var url = json.website;
+ // 去掉百度广告
+ removeBaiduAd();
+ for (var index in json.adList) {
+ var obj = json.adList[index];
+ obj.website = url;
+ iAskDisplayHtml.iaskCallMethod(obj);
+ }
+ if (typeof data.successCallBack === 'function') {
+ data.successCallBack();
+ }
+ // 给点击事件做来源
+ advLogInfo('COOPERATE_BAOXIAN', 0);
+ loadStatsToken();
+ }
+ catch (e) {
+ if (typeof data.failCallBack === 'function') {
+ data.failCallBack();
+ }
+ }
+ };
+ // 选择投放广告
+ var selectAS = function () {
+ var ele = this.document;
+ var $thatParam = ele.querySelector('.paramDiv');
+ var $thatHover = ele.querySelector('.commercialStandardHover');
+ var $thatQiye = ele.querySelector('.commercialStandard_qiye_cp');
+ var sourceType = $thatParam.getAttribute('sources');
+ var sources = $thatParam.getAttribute("sysources") == null ? sourceType : $thatParam.getAttribute("sysources");
+ var version = $thatParam.getAttribute('syversion') == null ? $thatParam.getAttribute('version') : $thatParam.getAttribute('syversion');
+ var openId = $thatParam.getAttribute('openId');
+ var tags = $thatParam.getAttribute('tags');
+ var params = $thatParam.getAttribute('params');
+ var mainTags = $thatParam.getAttribute('maintags');
+ var questionId = $thatParam.getAttribute('qid');
+ var qcid = $thatParam.getAttribute('qcid');
+ if (sources === 'COMMERCIAL_IAD' || sources === 'COMMERCIAL_ZWZD' || sources === 'COMMERCIAL_CAD') {
+ // 商业广告
+ removeBaiduAd();
+ busBottomAM();
+ if (sources === 'COMMERCIAL_ZWZD') {
+ sources = 'COOPERATE_COMMERCIAL';
+ }
+ advLogInfo(sources, 0);
+ }
+ else if ((sources === 'COOPERATE_BRAND' || sources === 'COOPERATE_BRAND_MARKET')
+ && (version === '1' || version === '3')) {
+ // 商业广告-旗舰版、专业版本
+ brandAvertisement(sources);
+ }
+ else if (sourceType === 'COOPERATE_BRAND' && version === '2') {
+ // 商业广告-标准版
+ $that.setAttribute('sources', 'COOPERATE_BRAND');
+ commercialSqc($thatQiye, $thatHover);
+ advLogInfo('COOPERATE_BRAND', 0);
+ }
+ else if (sourceType === 'COOPERATE_HUASHENG' || sourceType === 'COOPERATE_HUASHENG_QA'
+ || sourceType === 'COOPERATE_YOULAI' || sourceType === 'COOPERATE_TIANZHU'
+ || sourceType === 'COOPERATE_BRAND_MARKET' || sourceType === 'COOPERATE_SOULE') {
+ // 第三方合作广告
+ if (sourceType === 'COOPERATE_YOULAI' || sourceType === 'COOPERATE_TIANZHU'
+ || sourceType === 'COOPERATE_SOULE'
+ || (sourceType === 'COOPERATE_BRAND_MARKET' && version === '4')) {
+ // 需要删除百度广告
+ removeBaiduAd();
+ }
+ currencyAM(sourceType, openId, questionId, version);
+ }
+ else if (sourceType === 'COOPERATE_SOUTHNETWORK') {
+ // 南方网通广告
+ southnetwork(openId);
+ }
+ else if ('82' === qcid && (mainTags.indexOf('意外险') > -1
+ || mainTags.indexOf('品牌词') > -1 || mainTags.indexOf('少儿险') > -1
+ || mainTags.indexOf('重疾险') > -1 || mainTags.indexOf('保险') > -1)) {
+ var nowTime = getSysTime();
+ var startTime = ele.querySelectorAll('.bxStartTime')[0].innerText;
+ var endTime = ele.querySelectorAll('.bxEndTime')[0].innerText;
+ if (startTime <= nowTime && nowTime < endTime) {
+ // 商业广告-保险标签投放
+ var url = 'https://mipp.iask.cn/t/wlsh?type=BX&bxt=';
+ if (mainTags.indexOf('少儿险') > -1) {
+ url += 'jl';
+ }
+ else if (mainTags.indexOf('品牌词') > -1 || mainTags.indexOf('意外险') > -1 || mainTags.indexOf('保险') > -1) {
+ url += 'at';
+ }
+ else if (mainTags.indexOf('重疾险') > -1) {
+ url += 'il';
+ }
+ $.get(url, function (data) {
+ iaskInsurance(data);
+ });
+ }
+ }
+ else if (sourceType !== 'COOPERATE_HUASHENG' && sourceType !== 'COOPERATE_HUASHENG_QA') {
+ if (tags) {
+ loadURLJS(tags, params, sourceType);
+ }
+ var $thatLog = ele.querySelectorAll('.href_log');
+ if ($thatLog.length === 0) {
+ sourceType = 'COOPERATE_EFFECT';
+ $thatParam.setAttribute('sources', sourceType);
+ // 商业效果广告
+ effectAvertisement(questionId, sourceType);
+ }
+ }
+ };
+
+ var selectCommercail = function() {
+ var ele = this.document;
+ var $thatDiv = ele.querySelectorAll('.mip_as_bottm_div');
+ var $that = ele.querySelectorAll('.paramDiv');
+ if (validatePut()) {
+ var nowTime = getSysTime();
+ var startTime = ele.querySelector('.yongyouStartTime').innerText;
+ var endTime = ele.querySelector('.yongyouEndTime').innerText;
+ if (startTime <= nowTime && nowTime < endTime) {
+ // 删除百度广告
+ removeBaiduAd();
+ var putUrl = ele.querySelector('.yongyouPutUrl').innerText;
+ var picUrl = ele.querySelector('.yongyouPicUrl').innerText;
+ $thatDiv.append(putTestButHtml(putUrl, picUrl));
+ var urlr = 'https://mipp.iask.cn/t/mipdf?t=yongyou';
+ $.ajax({
+ type: 'GET',
+ url: urlr,
+ dataType: 'html',
+ success: function (data) {
+ if (!!data) {
+ ele.querySelector('.breadcast_middle_commercial').innerHTML = '';
+ ele.querySelectorAll('.breadcast_middle_commercial').innerHTML = data;
+ advLogInfoClick('.breadcast_middle_commercial .href_log', ele.querySelector('.paramDiv'));
+ }
+ }
+ });
+ var sources = $that.getAttribute('sources');
+ advLogInfo(sources, 0);
+ loadStatsToken();
+ }
+ }
+
+ };
+ var effects = {
+ newLoadAd: function () {
+ selectAS();
+ },
+ commercialLoad: function () {
+ selectCommercail();
+ },
+ init: function () {
+ this.newLoadAd();
+ this.commercialLoad();
+ }
+ };
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ effects.init();
+ };
+ return customElem;
+});
diff --git a/mip-iask-business/package.json b/mip-iask-business/package.json
new file mode 100644
index 00000000..91e9d062
--- /dev/null
+++ b/mip-iask-business/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-iask-business",
+ "version": "2.1.4",
+ "description": "商业广告组件",
+ "contributors": [
+ {
+ "name": "hejieye",
+ "email": "jieye.he@iask.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-iask-ext/README.md b/mip-iask-ext/README.md
new file mode 100644
index 00000000..a77502c8
--- /dev/null
+++ b/mip-iask-ext/README.md
@@ -0,0 +1,19 @@
+# mip-iask-ext
+
+mip-iask-ext 爱问详细页插件
+
+标题|内容
+----|----
+类型|业务
+支持布局|N,S|
+所需脚本|https://c.mipcdn.com/static/v1/mip-iask-ext/mip-iask-ext.js
+
+## 示例
+
+### 基本用法
+```html
+
+```
+## 属性
+
+## 注意事项
diff --git a/mip-iask-ext/mip-iask-ext.js b/mip-iask-ext/mip-iask-ext.js
new file mode 100644
index 00000000..6744e230
--- /dev/null
+++ b/mip-iask-ext/mip-iask-ext.js
@@ -0,0 +1,310 @@
+/**
+* @file 脚本支持
+* @author hejieye
+* @time 20170106
+* @version 1.3.2
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ // 页面交互效果
+ var effects = {
+ // 标签切换
+ switchBlock: function () {
+ $('.similar-nav').on('click', 'li',
+ function () {
+ event.preventDefault();
+ try {
+ $(this).siblings().removeClass('current');
+ $(this).addClass('current');
+
+ var index = $(this).index();
+ var nodes = $('.relative_kownlege').find('.tabs-con');
+ $(nodes).hide();
+ $(nodes).slice(index, index + 1).show();
+ }
+ catch (e) {}
+ });
+ },
+ // 换一换
+ changeMore: function () {
+ $('.link-change').on('click', function (event) {
+ event.preventDefault();
+ try {
+ var pagesize = 5;
+ var childNodes = $(this).parent().next().children();
+ var pagecount = $(this).attr('pagecount');
+ if (!pagecount) {
+ pagecount = pagesize;
+ }
+ if (pagecount >= childNodes.length) {
+ pagecount = 0;
+ }
+ var endcount = Number(pagecount) + pagesize;
+ $(childNodes).hide();
+ $(childNodes).slice(pagecount, endcount).show();
+ $(this).attr('pagecount', endcount);
+ }
+ catch (e) {}
+ });
+ },
+ // 相关知识换一换
+ kownlegMore: function () {
+ $('.kownleg-change').on('click', function (event) {
+ event.preventDefault();
+ $('div.similar').find('div.show').removeClass('show').addClass('hide').appendTo($('div.similar'));
+ var i = 1;
+ $('div.similar').find('div.hide').each(function () {
+ if (i === 1) {
+ $(this).removeClass('hide').addClass('show');
+ }
+ i ++;
+ });
+ });
+ },
+ // 展开 or 收起
+ openOrStop: function () {
+ $('.os-click').on('click',
+ function (event) {
+ event.preventDefault();
+ try {
+ var txt = $(this);
+ if (txt.text() === '[展开]') {
+ txt.text('[收起]');
+ txt.prev().show();
+ }
+ else {
+ txt.text('[展开]');
+ txt.prev().hide();
+ }
+ }
+ catch (e) {}
+ });
+ },
+ // 选择举报项
+ reportChange: function () {
+ $('.reportList li').on('click',
+ function () {
+ var fake = $(this).find('span').attr('class');
+ if (fake === 'fakeChecked') {
+ $(this).find('span').removeClass();
+ $(this).find('span').addClass('fakeCheck');
+ }
+ else {
+ $(this).find('span').removeClass();
+ $(this).find('span').addClass('fakeChecked');
+ }
+ });
+ },
+ clearReport: function () {
+ $('.report-body').hide();
+ $('.report_id').text('');
+ $('.report_type').text('');
+ $('.report_typeId').text('');
+ $('.reportList li').each(function () {
+ $(this).find('span').removeClass();
+ $(this).find('span').addClass('fakeCheck');
+ });
+ },
+ // 取消举报
+ cannelReport: function () {
+ $('.cannelReport').on('click',
+ function () {
+ effects.clearReport();
+ });
+ },
+ // 举报
+ okReport: function () {
+ $('.okReport').click(function () {
+ var reportList = '';
+ $('.reportList li').each(function () {
+ var fake = $(this).find('span').attr('class');
+ if (fake === 'fakeChecked') {
+ reportList += $(this).text().trim() + '-';
+ }
+ });
+ if (reportList === '') {
+ alert('请选择举报原因!');
+ }
+ else {
+ var checkLoginUrl = 'https://mipp.iask.cn/checkLogin?mip=' + Math.random();
+ $.get(checkLoginUrl,
+ function (e) {
+ if (e == null || e === 'null') {
+ // 跳转到登录页面
+ var thisHref = window.location.href;
+ window.location.href = 'https://mipp.iask.cn/login?source=' + thisHref;
+ }
+ else {
+ var questionId = $('.report_id').text();
+ var type = $('.report_type').text();
+ var typeId = $('.report_typeId').text();
+ $.post('http://m.iask.sina.com.cn/question/reportnew', {
+ 'reportList': reportList,
+ 'questionId': questionId,
+ 'type': type,
+ 'typeId': typeId
+ },
+ function (data) {
+ var res = $.parseJSON(data);
+ alert(res.desc);
+ });
+ effects.clearReport();
+ }
+ });
+ }
+ });
+ },
+ // 问题搜索
+ btnSearch: function () {
+ $('.btn-search').click(function () {
+ var content = $('.search-input').val();
+ if (content.trim().length < 2) {
+ alert('关键字必须大于等于2个字!');
+ return;
+ }
+ window.location.href = 'http://m.iask.sina.com.cn/search/1.html?content=' + content;
+ });
+ },
+ // 提问
+ btnSend: function () {
+ try {
+ $('.btn-send').click(function () {
+ event.preventDefault();
+ var content = $('.search-input').val();
+ console.log(content);
+ window.location.href = 'http://m.iask.sina.com.cn/ask?content=' + content;
+ });
+ }
+ catch (e) {}
+ },
+ // 验证登录信息
+ checkLogin: function () {
+ var btnUser = $('.btn-user');
+ var thisHref = window.location.href;
+ var checkLoginUrl = 'https://mipp.iask.cn/checkLogin?mip=' + Math.random();
+ $.get(checkLoginUrl,
+ function (e) {
+ if (e === null || e === 'null') {
+ $('.icon-ency-login').attr('href', 'https://mipp.iask.cn/login?source=' + thisHref);
+ btnUser.click(function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $('.login-out').show();
+ });
+ }
+ else {
+ btnUser.click(function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $('.login-in').show();
+ });
+ }
+ });
+ },
+ userInfoHide: function () {
+ $(document).click(function (event) {
+ $('.user-more').hide();
+ });
+ $('.user-more').click(function (event) {
+ event.stopPropagation();
+ });
+ },
+ sendPost: function (url, positionId, advertId, status, contentId, connId) {
+ url = url + '?positionId=' + positionId + '&advertId='
+ + advertId + '&contentId=' + contentId + '&connId=' + connId;
+ if (status !== null) {
+ url += '&status=1';
+ }
+ $('body').append('');
+ },
+ // 数据上报
+ checkData: function () {
+ $('mip-ad,mip-embed').each(function () {
+ effects.sendPost('https://mipu.iask.cn/ddd/adAudit', '144', '241', null, '195', 'm_q_detail_attention_1');
+ });
+ setInterval(function () {
+ // 判断是否加载出来
+ $('mip-ad,mip-embed').each(function () {
+ var loadFlag = $(this).attr('loadFlag');
+ if ($(this).html().indexOf('iframe') > 0 && loadFlag === undefined) {
+ $(this).attr('loadFlag', true);
+ effects.sendPost('https://mipu.iask.cn/ddd/adStatus', '144', '241', 1, '195', 'm_q_detail_attention_1');
+ }
+ });
+ }, 100);
+ },
+ accordion: function () {
+ $('.iask-show-more').click(function () {
+ $(this).parent().siblings('.iask-accordion').each(function () {
+ $(this).show();
+ });
+ $(this).hide();
+ $(this).siblings('.iask-show-less').show();
+ });
+ $('.iask-show-less').click(function () {
+ $(this).parent().siblings('.iask-accordion').each(function () {
+ $(this).hide();
+ });
+ $(this).hide();
+ $(this).siblings('.iask-show-more').show();
+ });
+ },
+ // 好万家导流
+ guideData: function () {
+ var urlf = 'https://mipp.iask.cn/t/mipdf?t=fous';
+ var urlr = 'https://mipp.iask.cn/t/mipdf?t=recom';
+ try {
+ $.ajax({
+ type: 'GET',
+ url: urlf,
+ dataType: 'html',
+ success: function (data) {
+ if (!!data) {
+ $('.load_today_focus').empty();
+ $('.load_today_focus').append(data);
+ }
+ }
+ });
+ $.ajax({
+ type: 'GET',
+ url: urlr,
+ dataType: 'html',
+ success: function (data) {
+ if (!!data) {
+ $('.load_recom_red').empty();
+ $('.load_recom_red').append(data);
+ }
+ }
+ });
+ }
+ catch (e) {
+ console.log(e);
+ }
+ },
+ init: function () {
+ this.switchBlock();
+ this.changeMore();
+ this.openOrStop();
+ this.reportChange();
+ this.cannelReport();
+ this.okReport();
+ this.btnSearch();
+ this.btnSend();
+ this.checkLogin();
+ this.userInfoHide();
+ // this.checkData();
+ this.accordion();
+ this.guideData();
+ this.kownlegMore();
+ }
+ };
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ effects.init();
+ };
+
+ return customElem;
+});
diff --git a/mip-iask-ext/package.json b/mip-iask-ext/package.json
new file mode 100644
index 00000000..56d042df
--- /dev/null
+++ b/mip-iask-ext/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-iask-ext",
+ "version": "1.3.2",
+ "description": "业务插件",
+ "contributors": [
+ {
+ "name": "hejieye",
+ "email": "jieye.he@iask.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-iask-report/README.md b/mip-iask-report/README.md
new file mode 100644
index 00000000..2fa189f1
--- /dev/null
+++ b/mip-iask-report/README.md
@@ -0,0 +1,37 @@
+# mip-iask-report
+
+mip-iask-report 打开举报div
+
+标题|内容
+----|----
+类型|通用
+支持布局|N,S|
+所需脚本|https://c.mipcdn.com/static/v1/mip-iask-report/mip-iask-report.js
+
+## 示例
+
+### 基本用法
+```html
+举报
+
+
+## 属性
+
+### questionId
+
+说明:问题ID
+必选项:是
+类型:字符串
+## 注意事项
+
+### type
+
+说明:举报的类型
+必选项:是
+类型:question answer
+
+### typeId
+
+说明:类型的ID 类型为question则是问题ID 类型为answer则是回答ID
+必选项:是
+类型:字符串
diff --git a/mip-iask-report/mip-iask-report.js b/mip-iask-report/mip-iask-report.js
new file mode 100644
index 00000000..b0e33477
--- /dev/null
+++ b/mip-iask-report/mip-iask-report.js
@@ -0,0 +1,56 @@
+/**
+* @file 脚本支持
+* @author hejieye
+* @time 2016-12-07
+* @version 1.1.0
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var openReportDiv = function (elem) {
+ $('.report-body').show();
+ var questionId = $(elem).attr('questionId');
+ var type = $(elem).attr('type');
+ var typeId = $(elem).attr('typeId');
+ $('.report_id').text(questionId);
+ $('.report_type').text(type);
+ $('.report_typeId').text(typeId);
+ };
+ var getRandomNum = function (min, max) {
+ var Range = max - min;
+ var Rand = Math.random();
+ return (min + Math.round(Rand * Range));
+ };
+ var abTest = function (a, b, div) {
+ var random = $('.randomNum');
+ if (!random.text()) {
+ random.text(getRandomNum(1, 100));
+ }
+ if (random.text() > 50) {
+ $(div).append(a);
+ }
+ else {
+ $(div).append(b);
+ }
+
+ };
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var elem = this.element;
+ var click = $(elem).attr('typeId');
+ var type = $(elem).attr('type');
+ $('.' + type + '_' + click).on('click',
+ function () {
+ // 打开举报div
+ openReportDiv(elem);
+ });
+ // ab 测试广告
+ var a = $(elem).attr('a');
+ var b = $(elem).attr('b');
+ var div = $(elem).attr('div');
+ if (a && b && div) {
+ abTest(a, b, div);
+ }
+ };
+ return customElem;
+});
diff --git a/mip-iask-report/package.json b/mip-iask-report/package.json
new file mode 100644
index 00000000..55f25622
--- /dev/null
+++ b/mip-iask-report/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-iask-report",
+ "version": "1.0.2",
+ "description": "问题回答举报",
+ "contributors": [
+ {
+ "name": "hejieye",
+ "email": "jieye.he@iask.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-icms-comment/README.md b/mip-icms-comment/README.md
index 45d06fb2..3fc9a4d3 100644
--- a/mip-icms-comment/README.md
+++ b/mip-icms-comment/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-icms-comment/mip-icms-comment.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-icms-comment/mip-icms-comment.js
## 示例
```html
diff --git a/mip-jia-apply/README.md b/mip-jia-apply/README.md
index 4e439d85..f9fca8bc 100644
--- a/mip-jia-apply/README.md
+++ b/mip-jia-apply/README.md
@@ -6,7 +6,7 @@ mip-jia-apply 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-apply/mip-jia-apply.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-apply/mip-jia-apply.js
## 示例
diff --git a/mip-jia-bottomshare/README.md b/mip-jia-bottomshare/README.md
index c9783506..dc6fafa2 100644
--- a/mip-jia-bottomshare/README.md
+++ b/mip-jia-bottomshare/README.md
@@ -6,7 +6,7 @@ mip-jia-bottomshare 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-bottomshare/mip-jia-bottomshare.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-bottomshare/mip-jia-bottomshare.js
## 示例
diff --git a/mip-jia-city-select/README.md b/mip-jia-city-select/README.md
index dbdddd75..8a393fa2 100644
--- a/mip-jia-city-select/README.md
+++ b/mip-jia-city-select/README.md
@@ -6,7 +6,7 @@ mip-jia-city-select 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-city-select/mip-jia-city-select.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-city-select/mip-jia-city-select.js
## 示例
diff --git a/mip-jia-coupons/README.md b/mip-jia-coupons/README.md
index 58c780b1..c9dd8899 100644
--- a/mip-jia-coupons/README.md
+++ b/mip-jia-coupons/README.md
@@ -6,7 +6,7 @@ mip-jia-coupons 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-coupons/mip-jia-coupons.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-coupons/mip-jia-coupons.js
## 示例
diff --git a/mip-jia-distance/README.md b/mip-jia-distance/README.md
index 8b967a9b..e952748f 100644
--- a/mip-jia-distance/README.md
+++ b/mip-jia-distance/README.md
@@ -6,7 +6,7 @@ mip-jia-distance 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-mip-jia-distance/mip-mip-jia-distance.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-mip-jia-distance/mip-mip-jia-distance.js
## 示例
diff --git a/mip-jia-footertl/README.md b/mip-jia-footertl/README.md
index c2c8b14a..6fb8dcd5 100644
--- a/mip-jia-footertl/README.md
+++ b/mip-jia-footertl/README.md
@@ -6,7 +6,7 @@ mip-jia-footertl 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-footertl/mip-jia-footertl.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-footertl/mip-jia-footertl.js
## 示例
diff --git a/mip-jia-footerzxbj/README.md b/mip-jia-footerzxbj/README.md
index 3c592a85..a7768853 100644
--- a/mip-jia-footerzxbj/README.md
+++ b/mip-jia-footerzxbj/README.md
@@ -6,7 +6,7 @@ mip-jia-footerzxbj 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-footerzxbj/mip-jia-footerzxbj.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-footerzxbj/mip-jia-footerzxbj.js
## 示例
diff --git a/mip-jia-headermenu/README.md b/mip-jia-headermenu/README.md
index 93064820..9cc8137c 100644
--- a/mip-jia-headermenu/README.md
+++ b/mip-jia-headermenu/README.md
@@ -6,7 +6,7 @@ mip-jia-headermenu 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-headermenu/mip-jia-headermenu.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-headermenu/mip-jia-headermenu.js
## 示例
diff --git a/mip-jia-house-style/README.md b/mip-jia-house-style/README.md
index 964263e6..a003b7cd 100644
--- a/mip-jia-house-style/README.md
+++ b/mip-jia-house-style/README.md
@@ -6,7 +6,7 @@ mip-jia-house-style 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-house-style/mip-jia-house-style.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-house-style/mip-jia-house-style.js
## 示例
diff --git a/mip-jia-log/README.md b/mip-jia-log/README.md
index 40b8edb3..3b7a7219 100644
--- a/mip-jia-log/README.md
+++ b/mip-jia-log/README.md
@@ -6,7 +6,7 @@ mip-jia-log 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-log/mip-jia-log.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-log/mip-jia-log.js
## 示例
diff --git a/mip-jia-masonrymore/README.md b/mip-jia-masonrymore/README.md
index 3a1778c3..63195c59 100644
--- a/mip-jia-masonrymore/README.md
+++ b/mip-jia-masonrymore/README.md
@@ -6,7 +6,7 @@ mip-jia-masonrymore 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-masonrymore/mip-jia-masonrymore.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-masonrymore/mip-jia-masonrymore.js
## 示例
diff --git a/mip-jia-redpacket/README.md b/mip-jia-redpacket/README.md
index aa0d0b0a..8eb5e6dc 100644
--- a/mip-jia-redpacket/README.md
+++ b/mip-jia-redpacket/README.md
@@ -6,7 +6,7 @@ mip-jia-redpacket 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-redpacket/mip-jia-redpacket.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-redpacket/mip-jia-redpacket.js
## 示例
diff --git a/mip-jia-shopcomment/README.md b/mip-jia-shopcomment/README.md
index 6d4c1cfb..37fe71d5 100644
--- a/mip-jia-shopcomment/README.md
+++ b/mip-jia-shopcomment/README.md
@@ -6,7 +6,7 @@ mip-jia-shopcomment 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-shopcomment/mip-jia-shopcomment.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-shopcomment/mip-jia-shopcomment.js
## 示例
diff --git a/mip-jia-signup/README.md b/mip-jia-signup/README.md
index c9ae846d..94b8ab34 100644
--- a/mip-jia-signup/README.md
+++ b/mip-jia-signup/README.md
@@ -6,7 +6,7 @@ mip-jia-signup 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-signup/mip-jia-signup.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-signup/mip-jia-signup.js
## 示例
diff --git a/mip-jia-style-test/README.md b/mip-jia-style-test/README.md
index a667fd73..1478275e 100644
--- a/mip-jia-style-test/README.md
+++ b/mip-jia-style-test/README.md
@@ -6,7 +6,7 @@ mip-jia-style-test 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-style-test/mip-jia-style-test.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-style-test/mip-jia-style-test.js
## 示例
diff --git a/mip-jia-stylexam/README.md b/mip-jia-stylexam/README.md
index a0da9dc7..df156e82 100644
--- a/mip-jia-stylexam/README.md
+++ b/mip-jia-stylexam/README.md
@@ -6,7 +6,7 @@ mip-jia-stylexam 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-stylexam/mip-jia-stylexam.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-stylexam/mip-jia-stylexam.js
## 示例
diff --git a/mip-jia-swiper/README.md b/mip-jia-swiper/README.md
index 32c04f1a..d4c9302c 100644
--- a/mip-jia-swiper/README.md
+++ b/mip-jia-swiper/README.md
@@ -6,7 +6,7 @@ mip-jia-swiper 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-swiper/mip-jia-swiper.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-swiper/mip-jia-swiper.js
## 示例
diff --git a/mip-jia-tbs/README.md b/mip-jia-tbs/README.md
index 8ce4dfd0..8f01b998 100644
--- a/mip-jia-tbs/README.md
+++ b/mip-jia-tbs/README.md
@@ -6,7 +6,7 @@ mip-jia-tbs 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-tbs/mip-jia-tbs.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-tbs/mip-jia-tbs.js
## 示例
diff --git a/mip-jia-wporder/README.md b/mip-jia-wporder/README.md
index abe72111..f4474659 100644
--- a/mip-jia-wporder/README.md
+++ b/mip-jia-wporder/README.md
@@ -6,7 +6,7 @@ mip-jia-wporder 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-wporder/mip-jia-wporder.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-wporder/mip-jia-wporder.js
## 示例
diff --git a/mip-jia-zxpacket/README.md b/mip-jia-zxpacket/README.md
index e4c73296..9e593499 100644
--- a/mip-jia-zxpacket/README.md
+++ b/mip-jia-zxpacket/README.md
@@ -6,7 +6,7 @@ mip-jia-zxpacket 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jia-zxpacket/mip-jia-zxpacket.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jia-zxpacket/mip-jia-zxpacket.js
## 示例
diff --git a/mip-jjpz/README.md b/mip-jjpz/README.md
new file mode 100644
index 00000000..ec16e064
--- /dev/null
+++ b/mip-jjpz/README.md
@@ -0,0 +1,300 @@
+# mip-jjpz
+
+mip-jjpz 自有业务详情页整体交互组件
+
+标题|内容
+----|----
+类型|业务,定制
+支持布局|responsive,fill,container
+所需脚本|https://c.mipcdn.com/static/v1/mip-jjpz/mip-jjpz.js
+
+## 示例
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
基金经理变动一览
+
+
现任基金经理简介
+
+
+
+
+
+
+查看与本基金有关的个讨论帖
+
+
+
+ -
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ 购买手续费
+ |
+ |
+ 购买 |
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ 购买手续费
+ |
+ |
+ 购买 |
+
+
+
+
+
+
+
+
+```
+
diff --git a/mip-jjpz/iconfont/iconfont.css b/mip-jjpz/iconfont/iconfont.css
new file mode 100644
index 00000000..88b43b84
--- /dev/null
+++ b/mip-jjpz/iconfont/iconfont.css
@@ -0,0 +1,26 @@
+
+@font-face {font-family: "iconfont";
+ src: url('iconfont.eot'); /* IE9*/
+ src: url('iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('iconfont.woff') format('woff'), /* chrome、firefox */
+ url('iconfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
+ url('iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
+}
+
+.iconfont {
+ font-family:"iconfont" !important;
+ font-size:16px;
+ font-style:normal;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-stroke-width: 0.2px;
+ -moz-osx-font-smoothing: grayscale;
+}
+.icon-right:before { content: "\e6a3"; }
+.icon-check:before { content: "\e645"; }
+.icon-unfold:before { content: "\e661"; }
+.icon-back:before { content: "\e679"; }
+.icon-search:before { content: "\e65c"; }
+.icon-roundcheckfill:before { content: "\e656"; }
+.icon-roundclosefill:before { content: "\e658"; }
+.icon-roundadd:before { content: "\e6d9"; }
+.icon-fold:before { content: "\e6de"; }
diff --git a/mip-jjpz/iconfont/iconfont.eot b/mip-jjpz/iconfont/iconfont.eot
new file mode 100644
index 00000000..db35104a
Binary files /dev/null and b/mip-jjpz/iconfont/iconfont.eot differ
diff --git a/mip-jjpz/iconfont/iconfont.svg b/mip-jjpz/iconfont/iconfont.svg
new file mode 100644
index 00000000..6a84ccdc
--- /dev/null
+++ b/mip-jjpz/iconfont/iconfont.svg
@@ -0,0 +1,61 @@
+
+
+
diff --git a/mip-jjpz/iconfont/iconfont.ttf b/mip-jjpz/iconfont/iconfont.ttf
new file mode 100644
index 00000000..d4f0683e
Binary files /dev/null and b/mip-jjpz/iconfont/iconfont.ttf differ
diff --git a/mip-jjpz/iconfont/iconfont.woff b/mip-jjpz/iconfont/iconfont.woff
new file mode 100644
index 00000000..8ab6713c
Binary files /dev/null and b/mip-jjpz/iconfont/iconfont.woff differ
diff --git a/mip-jjpz/img/20150831173017783.png b/mip-jjpz/img/20150831173017783.png
new file mode 100644
index 00000000..c44944fd
Binary files /dev/null and b/mip-jjpz/img/20150831173017783.png differ
diff --git a/mip-jjpz/img/down.png b/mip-jjpz/img/down.png
new file mode 100644
index 00000000..abd64759
Binary files /dev/null and b/mip-jjpz/img/down.png differ
diff --git a/mip-jjpz/img/loading2.gif b/mip-jjpz/img/loading2.gif
new file mode 100644
index 00000000..f864d5fd
Binary files /dev/null and b/mip-jjpz/img/loading2.gif differ
diff --git a/mip-jjpz/mip-jjpz.js b/mip-jjpz/mip-jjpz.js
new file mode 100644
index 00000000..06715d16
--- /dev/null
+++ b/mip-jjpz/mip-jjpz.js
@@ -0,0 +1,845 @@
+/**
+ * @file jjpz
+ * @author elang126(liiang2006@126.com)
+ */
+
+/**
+ * @date: 2016-12-28
+ * @time: 13:10
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var param = {
+ data: {
+ FCODE: '',
+ deviceid: 'Wap',
+ version: '4.3.0',
+ product: 'EFund',
+ plat: 'Wap'
+ },
+ dataPage: {
+ FCODE: '',
+ deviceid: 'Wap',
+ version: '4.3.0',
+ product: 'EFund',
+ plat: 'Wap',
+ pageIndex: 1,
+ pageSize: 3
+ },
+ apiurl: 'https://fundmobapi.eastmoney.com/FundMApi/',
+ funurl: 'http://m.1234567.com.cn/m/fund/funddetail/',
+ funBuyUrl: 'https://tradewap.1234567.com.cn/buyfund.html',
+ sameCompany: 'http://m.1234567.com.cn/m/fund/jjjz.shtml',
+ sameType: 'http://m.1234567.com.cn/m/fund/jjph.shtml',
+ fundtype: '',
+ fundGetType: 1,
+ isclick: {
+ gotoJBCC: true,
+ gotoFHPS: true,
+ gotoJJJL: true,
+ gotoJJGG: true
+ },
+ page: {
+ gotoJJGG: 1,
+ gotoFHPS: 1
+ }
+ };
+ var optionjjba = {
+ deviceid: 'Wap',
+ ps: 20,
+ plat: 'Wap',
+ product: 'Fund',
+ version: '201',
+ code: 'of' + param.data.FCODE,
+ p: '',
+ type: '',
+ postid: 10,
+ sorttype: 0
+ };
+ var timeoutFun = function () {};
+ var $f = {
+ init: function () {
+ var tthis = this;
+ tthis.tAjax('FundBase.ashx', param.data, tthis.baseLoad);
+ tthis.tAjax('FundSameCompanyList.ashx', param.data, tthis.sameCompany);
+ tthis.tAjax('FundSameTypeList.ashx', param.dataPage, tthis.sameType);
+ tthis.discussLink();
+ },
+ baseLoad: function (datas) {
+ var data = datas.Datas;
+ switch (data.FUNDTYPE) {
+ case '003':
+ param.fundtype = 5;
+ param.fundGetType = 1;
+ break;
+ case '004':
+ case '005':
+ param.fundtype = 10;
+ param.fundGetType = 2;
+ break;
+ case '006':
+ param.fundtype = 7;
+ param.fundGetType = 1;
+ break;
+ case '007':
+ param.fundtype = 8;
+ param.fundGetType = 1;
+ break;
+ case '201':
+ param.fundtype = 12;
+ param.fundGetType = 1;
+ break;
+ default:
+ param.fundtype = 3;
+ param.fundGetType = 1;
+ break;
+
+ }
+ var RISKLEVEL = ['--', '低风险', '中低风险', '中风险', '中高风险', '高风险'];
+ var RISKLEVELname;
+ var RISKLEVELt;
+ RISKLEVELt = Number(data.RISKLEVEL);
+ switch (RISKLEVELt) {
+ case 0:
+ RISKLEVELname = RISKLEVEL[0];
+ break;
+ case 1:
+ RISKLEVELname = RISKLEVEL[1];
+ break;
+ case 2:
+ RISKLEVELname = RISKLEVEL[2];
+ break;
+ case 3:
+ RISKLEVELname = RISKLEVEL[3];
+ break;
+ case 4:
+ RISKLEVELname = RISKLEVEL[4];
+ break;
+ case 5:
+ RISKLEVELname = RISKLEVEL[5];
+ break;
+ }
+ var date2;
+ var dellineData;
+ data.DWJZ = $f.initNumber2(data.DWJZ, 4, true);
+
+ $f.bindData(data.SHORTNAME, '.Fname');
+ $f.bindData('(' + data.FCODE + ')', '.coder', '.header1');
+ $f.bindData(data.FTYPE, '.header2', '.ui_outer');
+ $('.baseInfo_url').find('.jjxx-h').append('' + data.FTYPE + '');
+ if (RISKLEVELname !== '--') {
+ $('.ui_outer').find('.header2').append('|' + RISKLEVELname);
+ $('.baseInfo_url').find('.jjxx-h').append('|' + RISKLEVELname);
+ }
+
+ if (data.RLEVEL_SZ !== null && data.RLEVEL_SZ !== '--' && data.RLEVEL_SZ !== '') {
+ $('.ui_outer').find('.header2').append('|' + data.RLEVEL_SZ + '星评级');
+ $('.baseInfo_url').find('.jjxx-h').append('|' + data.RLEVEL_SZ + '星评级');
+ }
+ if (param.fundGetType === 1) {
+ $('.Fearnings').html('最新净值(' + data.FSRQ.slice(5, 10) + ')');
+ $('.FearningsDay').html('日涨幅');
+ var DWJZJ = $f.initNumber2(data.DWJZ, 4, true);
+ $f.bindData(DWJZJ, '.FearningsN1', 0, $f.isRed(data.RZDF));
+ $f.bindData($f.initNumber2(data.RZDF, 2), '.FearningsN2', 0, $f.isRed(data.RZDF));
+ var valuationG = data.Valuation ? (JSON.parse(data.Valuation).gsz) : '--';
+ var valuationColor = data.Valuation ? $f.isRed(JSON.parse(data.Valuation).gszzl) : '';
+ $f.bindData(valuationG, 'span:nth-child(2)', '.Fevaluation', valuationColor);
+ var ValuationGL = data.Valuation ? ($f.initNumber2(JSON.parse(data.Valuation).gszzl, 2)) : '--';
+ var valuationglColor = data.Valuation ? $f.isRed(JSON.parse(data.Valuation).gszzl) : '';
+ $f.bindData(ValuationGL, 'span:nth-child(3)', '.Fevaluation', valuationglColor);
+ $('.tab').html('净值估值
单位净值
累计收益
');
+ $('.tabContent').html('');
+ date2 = data.Valuation ? JSON.parse(data.Valuation).gztime.substring(5, 16) : '';
+ $f.bindData('(' + date2 + ')', 'span:nth-child(4)', '.Fevaluation');
+ var dataJN = $f.initNumber2(data.SYL_JN, 2);
+ var dataY = $f.initNumber2(data.SYL_Y, 2);
+ var data6Y = $f.initNumber2(data.SYL_6Y, 2);
+ var data1N = $f.initNumber2(data.SYL_1N, 2);
+ var colorJN = $f.isRed(data.SYL_JN);
+ var colorY = $f.isRed(data.SYL_Y);
+ var color6Y = $f.isRed(data.SYL_6Y);
+ var color1N = $f.isRed(data.SYL_1N);
+ $f.bindData(dataJN, 'span', '.Info_url div:nth-child(1)', colorJN);
+ $f.bindData(dataY, 'span', '.Info_url div:nth-child(2)', colorY);
+ $f.bindData(data6Y, 'span', '.Info_url div:nth-child(3)', color6Y);
+ $f.bindData(data1N, 'span', '.Info_url div:nth-child(4)', color1N);
+ }
+ else {
+ $('.Fearnings').html('万份收益(' + data.FSRQ.slice(5, 10) + ')');
+ var DWJZW = $f.initNumber2(data.DWJZ, 4, true);
+ $f.bindData(DWJZW, '.FearningsN1', 0, $f.isRed(data.SYI));
+ $f.bindData($f.initNumber2(data.SYI, 4), '.FearningsN2', 0, $f.isRed(data.SYI));
+ $('.FearningsDay').html('7日年化');
+ $('.tab').addClass('tabtwo').html('万元收益
7日年化
');
+ $('.tabContent').html('');
+ $('.Fevaluation').hide();
+ $('.Info_url div:nth-child(1) font').html('14日年化:');
+ $('.Info_url div:nth-child(2) font').html('28日年化:');
+ $('.Info_url div:nth-child(3) font').html('今年来:');
+ $('.Info_url div:nth-child(4) font').html('近1年:');
+ var dataFTYI = $f.initNumber2(data.FTYI, 2);
+ var dataTEYI = $f.initNumber2(data.TEYI, 2);
+ var dataWSYLJN = $f.initNumber2(data.SYL_JN, 2);
+ var dataWSYL1N = $f.initNumber2(data.SYL_1N, 2);
+ $f.bindData(dataFTYI, 'span', '.Info_url div:nth-child(1) ', $f.isRed(data.FTYI));
+ $f.bindData(dataTEYI, 'span', '.Info_url div:nth-child(2)', $f.isRed(data.TEYI));
+ $f.bindData(dataWSYLJN, 'span', '.Info_url div:nth-child(3) ', $f.isRed(data.SYL_JN));
+ $f.bindData(dataWSYL1N, 'span', '.Info_url div:nth-child(4) ', $f.isRed(data.SYL_1N));
+ }
+
+ if (data.SOURCERATE !== '' && data.SOURCERATE !== null) {
+ dellineData = '' + data.SOURCERATE + ' ' + data.RATE;
+ }
+ else {
+ dellineData = data.RATE;
+ }
+ $f.bindData(dellineData, '.ui_black.numberFont', '.buyInfo_url');
+ $('.Info_url').on('click', function () {
+ window.location.href = 'http://m.1234567.com.cn/m/fund/fundjdsy/' + param.data.FCODE;
+ });
+ if (data.BUY) {
+ $('.buyJJ').on('click', function () {
+ window.location.href = 'https://tradewap.1234567.com.cn/buyfund.html#code=' + param.data.FCODE;
+ });
+ }
+ else {
+ $('.buyJJ').removeClass('ui-btn-orange').addClass('ui-btn-gray');
+ }
+ $f.domclick();
+
+ $f.tAjax('FundNetDiagram.ashx', param.data, $f.diagramLoad);
+ },
+
+ diagramLoad: function (data) {
+ var gotoLSSYL = $('.gotoLSSYL');
+ var gotoLSSYLdiv = '';
+ var datas = data.Datas;
+ gotoLSSYL.find('.ui-grid-8 p').html(param.fundGetType === 1 ? '历史净值' : '历史收益率');
+ gotoLSSYLdiv += '';
+ if (data.TotalCount > 5) {
+ gotoLSSYLdiv += '
下载天天基金网APP,查看更多>
';
+ }
+
+ gotoLSSYLdiv += '
';
+
+ gotoLSSYL.after(gotoLSSYLdiv);
+ },
+ gotoJBCC: function (data) {
+ if (data.Datas.length === 0) {
+ $('.gotoJBCC').next('.fold_content').html('暂无数据');
+ return;
+ }
+
+ param.isclick.gotoJBCC = false;
+ var gotoJBCCcontent = $('.jbcc_scroll').find('tbody');
+ var tbody = '';
+ var datas = data.Datas;
+ $('.cjjzri').html('截止日期:' + datas[0].ShareDate + '').show();
+ for (var i = 0; i < datas.length; i++) {
+ tbody += ' ' + datas[i].ShareName + ' | ';
+ tbody += '' + datas[i].ShareProportion + ' | ';
+ var ShareGaincolor = $f.isRed(datas[i].ShareGain);
+ var ShareGaindata = $f.initNumber2(datas[i].ShareGain * 100, 2);
+ tbody += '' + ShareGaindata + ' |
';
+ }
+ gotoJBCCcontent.html(tbody);
+ },
+ sameCompany: function (data) {
+ var tgsqxjjurl = $('.tgsqxjj_url');
+ var pageContentTgsqxjj = $('.pageContentTgsqxjj');
+ var thisul = pageContentTgsqxjj.find('.fund-list');
+ tgsqxjjurl.attr('data-href', param.sameCompany + '?companyid=' + data.Expansion + '#1');
+ $f.bindData(data.TotalCount, '.numberFont', '.tgsqxjj_url');
+ var datas = data.Datas;
+ var thishtml = thisul.html();
+ thisul.html('');
+ for (var i = 0; i < datas.length; i++) {
+ thisul.append(thishtml);
+ var tthisli = thisul.find('li').eq(i);
+ tthisli.children('a').attr('href', param.funurl + '?fundcode=' + datas[i].FCODE);
+ var tthistbl = tthisli.find('.fund-tbl');
+ var thishref = param.funurl + '?fundcode=' + datas[i].FCODE;
+ tthistbl.find('.fund-title a').attr('href', thishref).html(datas[i].SHORTNAME);
+ tthistbl.find('.fund_minsg span').html($f.numberM(datas[i].MINSG));
+ tthistbl.find('.profit').html($f.initNumber2(datas[i].SYL, 2));
+ tthistbl.find('.profit').next('.profit-title').html(datas[i].SYLMARK);
+ tthistbl.find('.fund-fl.font15').html(datas[i].SOURCERATE);
+ tthistbl.find('.fund-fl.font15W').html(datas[i].RATE);
+ tthistbl.find('.fund-buy').attr('href', param.funBuyUrl + '#code=' + datas[i].FCODE);
+ }
+ var turl = param.sameCompany + '?companyid=' + data.Expansion + '#1';
+ var thishtmlul = '';
+ thishtmlul += '查看同公司旗下基金>';
+ thisul.append(thishtmlul);
+ tgsqxjjurl.on('click', function () {
+ window.location.href = $(this).attr('data-href');
+ });
+ },
+ sameType: function (data) {
+ var tljjzfurl = $('.tljjzf_url');
+ var pageContentTljjzf = $('.pageContentTljjzf');
+ var thisul = pageContentTljjzf.find('.fund-list');
+ tljjzfurl.attr('data-href', param.sameType + '#' + param.fundtype);
+ $f.bindData(data.TotalCount, '.numberFont', '.tljjzf_url');
+ var datas = data.Datas;
+ var thishtml = thisul.html();
+ thisul.html('');
+ for (var i = 0; i < datas.length; i++) {
+ thisul.append(thishtml);
+ var tthisli = thisul.find('li').eq(i);
+ tthisli.children('a').attr('href', param.funurl + '?fundcode=' + datas[i].FCODE);
+ var tthistbl = tthisli.find('.fund-tbl');
+ var tthishref = param.funurl + '?fundcode=' + datas[i].FCODE;
+ tthistbl.find('.fund-title a').attr('href', tthishref).html(datas[i].SHORTNAME);
+ tthistbl.find('.fund_minsg span').html($f.numberM(datas[i].MINSG));
+ tthistbl.find('.profit').html($f.initNumber2(datas[i].SYL, 2));
+ tthistbl.find('.profit').next('.profit-title').html(datas[i].SYLMARK);
+ tthistbl.find('.fund-fl.font15').html(datas[i].SOURCERATE);
+ tthistbl.find('.fund-fl.font15W').html(datas[i].RATE);
+ tthistbl.find('.fund-buy').attr('href', param.funBuyUrl + '#code=' + datas[i].FCODE);
+ }
+ var turl = param.sameType + '#' + param.fundtype;
+ var htmlli = '';
+ htmlli += '
查看全部同类基金>>';
+ thisul.append(htmlli);
+ tljjzfurl.on('click', function () {
+ window.location.href = $(this).attr('data-href');
+ });
+ },
+ gotoJJJL: function (data) {
+ var gotoJJJLscroll = $('.gotoJJJL_scroll');
+ var scrolltable = '';
+ var datas = data.Datas;
+ for (var i = 0; i < datas.length; i++) {
+ var t = datas[i].LEMPDATE === '' ? '至今' : datas[i].LEMPDATE;
+ scrolltable += '
' + datas[i].FEMPDATE + ' | ';
+ scrolltable += '' + t + ' | ';
+ scrolltable += '' + datas[i].MGRNAME + ' | ';
+ scrolltable += '' + datas[i].DAYS + '天 | ';
+ var PENAVGROWTHcolor = $f.isRed(datas[i].PENAVGROWTH.toString());
+ var PENAVGROWTHdata = $f.initNumber2(datas[i].PENAVGROWTH, 2);
+ scrolltable += '' + PENAVGROWTHdata + ' |
';
+ }
+ gotoJJJLscroll.find('tbody').html(scrolltable);
+ param.isclick.gotoJJJL = false;
+ },
+ gotoJJJLdetail: function (data) {
+ var gotoJJJLdetail = $('.gotoJJJL_detail');
+ var detail = '';
+ var datas = data.Datas;
+ for (var i = 0; i < datas.length; i++) {
+ var imgurl;
+ if (datas[i].PHOTOURL == null) {
+ imgurl = 'http://j5.dfcfw.com/avatar/nopic.gif';
+ }
+ else {
+ imgurl = 'https://fundmobapi.eastmoney.com/FundMApi/HttpToHttps.ashx?TYPE=pic&URL=' + datas[i].PHOTOURL;
+ }
+ detail += '
';
+ detail += '
';
+ detail += '
';
+ detail += '
姓名:' + datas[i].MGRNAME + '
';
+ detail += '
上任日期:' + datas[i].FEMPDATE + '
';
+ detail += '
管理年限' + $f.fomateDate(datas[i].DAYS) + '
';
+ detail += '
';
+ detail += '
' + datas[i].RESUME + '
';
+ detail += '
全部简介 ';
+
+ }
+ gotoJJJLdetail.html(detail);
+ $('.togglebtn').on('click', function () {
+ if ($(this).hasClass('down')) {
+ $(this).removeClass('down').addClass('up').prev('p').removeClass('on');
+ }
+ else {
+ $(this).removeClass('up').addClass('down').prev('p').addClass('on');
+ }
+ });
+ },
+ gotoJJGG: function (data) {
+ var gotoJJGG = $('.gotoJJGG').next('.fold_content');
+ var div = '';
+ var datas = data.Datas;
+ div += '
';
+ for (var i = 0; i < datas.length; i++) {
+ div += '
';
+ div += '
' + datas[i].PUBLISHDATE.slice(5, 10) + '
';
+ div += '
' + datas[i].TITLE + '
';
+ }
+ div += '
';
+ data.TotalCount > 5 ? div += '
查看更多
' : '';
+ gotoJJGG.html(div).show();
+
+ param.isclick.gotoJJGG = false;
+ $('.jjggMore').on('click', $f.gotoJJGGmore);
+ $('.jjggContent').find('.ui-grid-row').on('click', function () {
+ window.location.href = 'http://m.1234567.com.cn/m/fund/FundJJGSGG/' + param.data.FCODE + '_' + $(this).attr('data-id');
+ });
+ },
+ gotoJJGGmore: function () {
+ param.page.gotoJJGG++;
+ param.dataPage.pageIndex = 1;
+ param.dataPage.pageSize = 5 * param.page.gotoJJGG;
+ $f.tAjax('FundNoticeList.ashx', param.dataPage, $f.gotoJJGG);
+ },
+ gotoFHPS: function (data) {
+ var gotoFHPS = $('.gotoFHPS').next('.fold_content');
+ var div = '';
+ var datas = data.Datas;
+ var CFnumber = 0;
+ div += '
';
+ div += '';
+ for (var i = 0; i < datas.length; i++) {
+ div += '
';
+ div += '
' + datas[i].FSRQ + '
';
+ div += '
每份派现金' + datas[i].FHFCZ + '元
';
+ div += '
';
+ if (Number(datas[i].FHFCBZ) !== 0) {
+ CFnumber++;
+ }
+
+ }
+ div += '
';
+ data.TotalCount > 5 ? div += '
查看更多
' : '';
+ gotoFHPS.html(div).show();
+ if (CFnumber !== 0) {
+ $('p.ui_remark').html('成立以来,总计分红' + data.TotalCount + '次,拆分' + CFnumber + '次');
+ }
+
+ for (var j = 5; j < datas.length; j++) {
+ gotoFHPS.find('.ui-grid-row').eq(j).hide();
+ }
+
+ param.isclick.gotoFHPS = false;
+ $('.fhpsMore').on('click', function () {
+ for (var q = 5 * param.page.gotoFHPS; q < datas.length && q < 5 * param.page.gotoFHPS + 5; q++) {
+ gotoFHPS.find('.ui-grid-row').eq(q).show();
+ }
+ param.page.gotoFHPS++;
+ param.page.gotoFHPS * 5 > datas.length ? $('.fhpsMore').hide() : '';
+ });
+ },
+ gotoFHPSMore: function (data) {
+ var div = '';
+ for (var i = 5; i < data.length; i++) {
+ div += '
';
+ div += '
' + data[i].FSRQ + '
';
+ div += '
每份派现金' + data[i].FHFCZ + '元
';
+ div += '
';
+ }
+ $('.gotoFHPS').next('.fold_content').find('.fhpsContent:last-child').after(div);
+ },
+ discussLink: function (options) {
+ $.ajax({
+ type: 'GET',
+ dataType: 'json',
+ url: 'https://jijinbaapi.eastmoney.com/gubaapi/v3/Read/Article/Post/Articlelist.ashx',
+ data: optionjjba,
+ success: function (resultData) {
+ $('.discussLink').attr('href', 'http://jjbmob.eastmoney.com/fundDynamicsForFundBar.html#postid=of' + param.data.FCODE);
+ $('.discussLink').find('span').html(resultData.count);
+
+ },
+ error: function (error) {
+ $f.alertWindow('网络不给力,请稍后重试');
+ }
+ });
+ },
+
+ tAjax: function (url, data, callback) {
+ $f.hardLoad();
+ $.ajax({
+ type: 'GET',
+ dataType: 'jsonp',
+ // type: "POST",
+ url: param.apiurl + url,
+ data: data,
+ success: function (resultData) {
+ if (typeof resultData === 'string') {
+ resultData = JSON.parse(resultData);
+ }
+
+ if (resultData.ErrCode === 0) {
+ callback(resultData);
+ }
+ else {
+ if ($f.isEmpty(resultData.ErrMsg)) {
+ resultData.ErrMsg = '网络不给力,请稍后重试';
+ }
+ else if (resultData.ErrMsg === '服务异常' || resultData.ErrMsg === '系统繁忙!') {
+ resultData.ErrMsg = '网络不给力,请稍后重试';
+ }
+
+ $f.alertWindow(resultData.ErrMsg, callback);
+ }
+ $f.hideMask();
+
+ },
+ error: function (error) {
+ $f.alertWindow('网络不给力,请稍后重试', callback);
+ $f.hideMask();
+ }
+
+ });
+ },
+ domclick: function () {
+ $('.tabp').find('.tab p').on('click', function () {
+ $(this).addClass('active').siblings('p').removeClass('active');
+ var thisimg = $(this).parents('.tab').next('.tabContent').find('img');
+ thisimg.attr('src', $(this).attr('data-imgurl') + param.data.FCODE + '.png');
+ });
+ $('.gotoLSSYL').on('click', function () {
+ var tthis = this;
+ $f.toggleShow(tthis);
+ });
+ $('.buyInfo_url').on('click', function () {
+ window.location.href = 'http://m.1234567.com.cn/m/fund/fundfl/' + param.data.FCODE;
+ });
+ $('.baseInfo_url').on('click', function () {
+ window.location.href = 'http://m.1234567.com.cn/m/fund/fundjbxx/' + param.data.FCODE;
+ });
+ $('.tljjzf_url').on('click', function () {
+ window.location.href = $(this).attr('data-href');
+ });
+ $('.gotoJBCC').on('click', function () {
+ var tthis = this;
+ $f.toggleShow(tthis);
+ if (param.isclick.gotoJBCC) {
+ $f.tAjax('FundPositionList.ashx', param.data, $f.gotoJBCC);
+ }
+
+ });
+ $('.gotoFHPS').on('click', function () {
+ var tthis = this;
+ $f.toggleShow(tthis);
+ if (param.isclick.gotoFHPS) {
+ $f.tAjax('FundBonusList.ashx', param.data, $f.gotoFHPS);
+ }
+
+ });
+ $('.gotoJJJL').on('click', function () {
+ var tthis = this;
+ $f.toggleShow(tthis);
+ if (param.isclick.gotoJJJL) {
+ $f.tAjax('FundManagerList.ashx', param.data, $f.gotoJJJL);
+ $f.tAjax('FundMangerDetail.ashx', param.data, $f.gotoJJJLdetail);
+ }
+
+ });
+ $('.gotoJJGG').on('click', function () {
+ var tthis = this;
+ $f.toggleShow(tthis);
+ if (param.isclick.gotoJJGG) {
+ param.dataPage.pageIndex = 1;
+ param.dataPage.pageSize = 5;
+ $f.tAjax('FundNoticeList.ashx', param.dataPage, $f.gotoJJGG);
+ }
+
+ });
+ $('.gotopinglun').on('click', function () {
+ window.location.href = 'http://jjbmob.eastmoney.com/fundDynamicsForFundBar.html#postid=of' + param.data.FCODE;
+ });
+ $('.shareInfo').on('click', function (e) {
+ var tthis = $(this);
+ var $flexShare = tthis.siblings('.flexShare');
+ $flexShare.toggleClass('hide');
+
+ if (!this.Share) {
+ this.Share = true;
+ $flexShare.on('touchend', 'li', function () {
+ var j = $(this);
+ setTimeout(function () {
+ var type = j.data('type');
+ if (type) {
+ shareTo(type);
+ }
+ }, 1200);
+
+ });
+ }
+ e.stopPropagation();
+
+ });
+ var shareTo = function (dest) {
+ var shareTitle = '天天基金网';
+ var url = location.href;
+ var source = '基金详情';
+ var sourceUrl = 'https://m.1234567.com.cn/';
+ var sinaAppkey = '2136217547';
+ var sinaRalateUid = '2627698865';
+
+ var title = shareTitle + '-' + source + '(m.1234567.com.cn)';
+
+ if (url === null || title === null || url === '' || title === '') {
+ $f.alertWindow('错误的链接地址或标题');
+ return;
+ }
+ var shareUrl = '';
+ switch (dest.toLowerCase()) {
+ case 'sina':
+ shareUrl = 'http://service.weibo.com/share/share.php?url=' + encodeURIComponent(url) + '&appkey=' + sinaAppkey + '&title=' + encodeURIComponent(title) + '&pic=&ralateUid=' + sinaRalateUid + '&source=' + encodeURIComponent(source) + '&sourceUrl=' + encodeURIComponent(sourceUrl);
+ break;
+ case 'qq':
+ shareUrl = 'http://v.t.qq.com/share/share.php?url=' + encodeURIComponent(url) + '&appkey=801004939&site=https://wap.eastmoney.com&title=' + encodeURIComponent(title) + '&pic=';
+ break;
+ case 'qzone':
+ shareUrl = 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=' + encodeURIComponent(url) + '&appkey=801004939&site=https://wap.eastmoney.com&title=' + encodeURIComponent(title) + '&desc=&summary=&site=https://wap.eastmoney.com';
+ break;
+ }
+
+ window.parent.location.href = shareUrl;
+ };
+ $('.addFavor').on('click', function (e) {
+ window.location.href = 'http://m.1234567.com.cn/m/fund/funddetail/?fundcode=' + param.data.FCODE;
+ });
+ },
+ toggleShow: function (options) {
+ if ($(options).next('.fold_content').css('display') === 'block') {
+ $(options).next('.fold_content').hide();
+ $(options).find('.iconfont').removeClass('ui_fold').addClass('ui_unfold');
+ }
+ else {
+ $(options).next('.fold_content').show();
+ $(options).find('.iconfont').removeClass('ui_unfold').addClass('ui_fold');
+ }
+
+ },
+ initNumber2: function (n, m, bol) {
+ if (!n) {
+ return '--';
+ }
+
+ var b = !bol ? '%' : '';
+ n = parseFloat(n).toFixed(m) + b;
+
+ return n;
+ },
+ numberM: function (n) {
+ return Number(n) > 10000 ? Math.round(Number(n) / 10000) + '万' : n;
+ },
+ fomateDate: function (d) {
+ if (d < 365) {
+ return d + '天';
+ }
+ else {
+ var n = parseInt(d / 365, 10);
+ var a = parseInt(d % 365, 10);
+
+ return n + '年又' + a + '天';
+ }
+ },
+ isRed: function (str) {
+ if (!str || str === null || str === 'null') {
+ return 'ui_black';
+ }
+
+ var bol = str.indexOf('-');
+ if (bol < 0) {
+ if (parseFloat(str) === 0) {
+ return 'ui_black';
+ }
+ return 'ui_red';
+ }
+ else if (parseFloat(str) < 0) {
+ return 'ui_green';
+ }
+ else {
+ return 'ui_black';
+ }
+ },
+ isEmpty: function (value, allowEmptyString) {
+ return (value === null) || (value === undefined) || (value === '');
+ },
+ initAlertMask: function () {
+
+ var alertMaskerUI = $('._alertMaskerUI_');
+ if (alertMaskerUI != null) {
+ alertMaskerUI.remove();
+ }
+ var alertMaskhtml = '';
+ alertMaskhtml += '
';
+ alertMaskhtml += '
';
+ $('mip-jjpz').append(alertMaskhtml);
+
+ },
+ alertWindow: function (txt, callback, option) {
+ var tthis = this;
+ var target = $('._alertMaskerUI_');
+ if (target.css('display') === 'block') {
+ return false;
+ }
+
+ tthis.initAlertMask();
+
+ var tempTxt = txt ? txt : '';
+ var tempCallback = callback ? callback : function () {
+ };
+
+ var btnTxt = option ? '确定' : option;
+ target = $('._alertMaskerUI_');
+ target.find('.btn').html(btnTxt);
+
+ target.show();
+ target.find('p').html(tempTxt);
+ target.find('.alert').addClass('show');
+
+ var tempTapFun = function (e) {
+ // e.stopPropagation();
+
+ setTimeout(function () {
+ tthis.closeAlertWindow();
+ tempCallback(false);
+ }, 300);
+
+ };
+
+ var btn = target.find('.button[for=yes]');
+ btn.off('click').on('click', tempTapFun);
+ },
+ closeAlertWindow: function () {
+ var target = $('._alertMaskerUI_');
+ target.hide();
+ target.find('.alert').removeClass('show');
+ },
+ addFavor: function (option, callback) {
+ $f.hardLoad();
+ $.ajax({
+ type: 'GET',
+ dataType: 'jsonp',
+ url: 'https://fundex2.eastmoney.com/FundMobileApi/FundFavor.ashx',
+ data: option,
+ success: function (resultData) {
+ if (typeof resultData === 'string') {
+ resultData = JSON.parse(resultData);
+ }
+ if (resultData.ErrCode === 0) {
+ callback(resultData);
+ }
+ else {
+ if ($f.isEmpty(resultData.ErrMsg)) {
+ resultData.ErrMsg = '网络不给力,请稍后重试';
+ }
+ else if (resultData.ErrMsg === '服务异常' || resultData.ErrMsg === '系统繁忙!') {
+ resultData.ErrMsg = '网络不给力,请稍后重试';
+ }
+
+ $f.alertWindow(resultData.ErrMsg, callback);
+ }
+ $f.hideMask();
+
+ },
+ error: function (error) {
+ $f.alertWindow('网络不给力,请稍后重试', callback);
+ $f.hideMask();
+ }
+
+ });
+ },
+ hardLoad: function (txt, callback) {
+ $f.load(txt ? txt : '加载中');
+ var loadingMaskUI = $('._loadingMaskUI_');
+ var c = loadingMaskUI.attr('c');
+
+ if (!c) {
+ c = 0;
+ }
+
+ loadingMaskUI.attr('c', ++c);
+
+ if (loadingMaskUI.is(':visible')) {
+
+ return false;
+ }
+
+ loadingMaskUI.css({display: 'table', background: 'rgba(0,0,0,0)'});
+
+ var tempCustomFun = callback ? callback : function () {
+ $f.alertWindow('网络不给力,请稍后重试');
+ };
+
+ var tempCallback = function () {
+ $f.hideMaskForce();
+ tempCustomFun();
+ };
+
+ clearTimeout(timeoutFun);
+ timeoutFun = setTimeout(tempCallback, 30000);
+ },
+ load: function (txt) {
+ $('._loadingMaskUI_ div[ui]').hide();
+ $('._loadingMaskUI_ ._maskload_').show().find('span').html(txt);
+ },
+ hideMaskForce: function () {
+ var loadingMaskUI = $('._loadingMaskUI_');
+ loadingMaskUI.css('display', 'none');
+ clearTimeout(timeoutFun);
+ },
+ hideMask: function () {
+
+ var loadingMaskUI = $('._loadingMaskUI_');
+ var c = loadingMaskUI.attr('c');
+
+ if (c <= 1) {
+ loadingMaskUI.attr('c', 0);
+ loadingMaskUI.css('display', 'none');
+ }
+ else {
+ loadingMaskUI.attr('c', --c);
+ }
+ clearTimeout(timeoutFun);
+
+ },
+ getQueryString: function (name) {
+ var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
+ var r = window.location.search.substr(1).match(reg);
+ if (r != null) {
+ return unescape(r[2]);
+ }
+
+ return '';
+
+ },
+ bindData: function (data, Cid, Pid, color) {
+ var dom;
+ dom = Pid !== null && Pid !== undefined && Pid !== 0 ? $(Pid).find(Cid) : $(Cid);
+ if (color !== undefined) {
+ dom.addClass(color);
+ }
+
+ dom.html(data);
+ }
+ };
+
+ customElem.prototype.build = function () {
+ var element = this.element;
+ param.data.FCODE = $f.getQueryString('fundcode') || '000009';
+ param.dataPage.FCODE = $f.getQueryString('fundcode') || '000009';
+ $f.init();
+ };
+
+ return customElem;
+});
diff --git a/mip-jjpz/mip-jjpz.less b/mip-jjpz/mip-jjpz.less
new file mode 100644
index 00000000..e38099d9
--- /dev/null
+++ b/mip-jjpz/mip-jjpz.less
@@ -0,0 +1,1887 @@
+mip-jjpz {
+
+ .ui-left,
+ .ui-right {
+ display: inline;
+ }
+
+ .ui-left {
+ float: left;
+ }
+
+ .ui-right {
+ float: right;
+ }
+
+ .ui-text-overflow {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .ui-webkit-adjust {
+ -webkit-text-size-adjust: none;
+ }
+
+ a:link,
+ a:visited {
+ color: #4372ba;
+ }
+
+ a:hover {
+ color: #ff4400;
+ text-decoration: none;
+ }
+
+ .ui-verticalMiddle {
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ }
+ .ui-grid-1,
+ .ui-grid-2,
+ .ui-grid-3,
+ .ui-grid-4,
+ .ui-grid-5,
+ .ui-grid-6,
+ .ui-grid-7,
+ .ui-grid-8,
+ .ui-grid-9,
+ .ui-grid-10 {
+ float: left;
+ display: inline;
+ }
+
+ .ui-grid-1 {
+ width: 10%;
+ }
+
+ .ui-grid-2 {
+ width: 20%;
+ }
+
+ .ui-grid-3 {
+ width: 30%;
+ }
+
+ .ui-grid-4 {
+ width: 40%;
+ }
+
+ .ui-grid-5 {
+ width: 50%;
+ }
+
+ .ui-grid-6 {
+ width: 60%;
+ }
+
+ .ui-grid-7 {
+ width: 70%;
+ }
+
+ .ui-grid-8 {
+ width: 80%;
+ }
+
+ .ui-grid-9 {
+ width: 90%;
+ }
+
+ .ui-grid-10 {
+ width: 100%;
+ }
+
+ .ui-grid-row:after {
+ display: block;
+ content: " ";
+ clear: both;
+ }
+ /*ui-table*/
+ .ui-table {
+ border-collapse: collapse;
+ border-spacing: 0;
+ width: 100%;
+ }
+
+ .ui-table tr th,
+ .ui-table tr td {
+ color: #333333;
+ text-align: center;
+ line-height: 20px;
+ font-size: 12px;
+ background: #fff;
+ }
+
+ .ui-table tr th {
+ font-weight: 500;
+ line-height: 30px;
+ color: #808080;
+ border-bottom: 1px solid #bbbbbb;
+ }
+
+ .ui-table tr td {
+ background: #fff;
+ border-bottom: 1px solid #bbbbbb;
+ padding: 5px 0;
+ vertical-align: middle;
+ }
+
+ .ui-table tr td:first-child {
+ padding-left: 5px;
+ text-align: left;
+ }
+
+ .ui-table tr td:last-child {
+ padding-right: 5px;
+ }
+
+ .ui-table tr td.ui-table-up {
+ color: #cc0000;
+ }
+
+ .ui-table tr td.ui-table-down {
+ color: #008000;
+ }
+
+ .ui-table-hover tr:active td {
+ background: #eeeeee;
+ }
+ .ui-btn {
+ margin-bottom: 0;
+
+ display: inline-block;
+ text-align: center;
+ text-decoration: none;
+ vertical-align: middle;
+ cursor: pointer;
+ font-family: inherit;
+ background-image: none;
+ border: 1px solid transparent;
+ white-space: nowrap;
+ }
+
+ .ui-btn:hover {
+ text-decoration: none;
+ }
+
+ .ui-btn-orange {
+ color: #ffffff;
+ background: #ff4400;
+ border-color: #ff4400;
+ }
+
+ a.ui-btn-orange:link,
+ a.ui-btn-orange:active,
+ a.ui-btn-orange:visited {
+ color: #ffffff;
+ }
+
+ .ui-btn-gray {
+ cursor: default;
+ color: #cccccc;
+ background: #e4e4e4;
+ border-color: #e4e4e4;
+ }
+
+ a.ui-btn-gray:link,
+ a.ui-btn-gray:visited {
+ color: #cccccc;
+ }
+
+ .ui-btn-gray:active {
+ color: #cccccc;
+ background: #e4e4e4;
+ border-color: #e4e4e4;
+ }
+
+ .ui-btn-sgray {
+ color: #808080;
+ background: #e6e6e6;
+ border-color: #cccccc;
+ }
+
+ a.ui-btn-sgray:link,
+ a.ui-btn-sgray:visited {
+ color: #808080;
+ }
+
+ .ui-btn-sgray:active {
+ color: #808080;
+ background: #e6e6e6;
+ border-color: #cccccc;
+ }
+
+ .ui-btn-blue {
+ color: #c0c7cf;
+ background: #5199e8;
+ border-color: #2980d2;
+ }
+
+ a.ui-btn-blue:link,
+ a.ui-btn-blue:visited {
+ color: #c0c7cf;
+ }
+
+ .ui-btn-blue:active {
+ color: #c0c7cf;
+ background: #5199e8;
+ border-color: #2980d2;
+ }
+
+ .ui-btn-fwhite {
+ color: #ff4400;
+ background: #ffffff;
+ border-color: #ff4400;
+ }
+
+ a.ui-btn-fwhite:link,
+ a.ui-btn-fwhite:visited {
+ color: #ff4400;
+ }
+
+ .ui-btn-fwhite:active {
+ color: #ff4400;
+ background: #ffffff;
+ border-color: #ff4400;
+ }
+
+ .ui-btn-white {
+ color: #333333;
+ background: #ffffff;
+ border-color: #d9d9d9;
+ }
+
+ a.ui-btn-white:link,
+ a.ui-btn-white:visited {
+ color: #333333;
+ }
+
+ .ui-btn-white:active {
+ color: #333333;
+ background: #d9d9d9;
+ border-color: #d9d9d9;
+ }
+
+ .ui-btn-l {
+ width: 100%;
+ min-width: 58px;
+ height: 44px;
+ font-size: 16px;
+ line-height: 44px;
+ border-radius: 5px;
+ }
+
+ button.ui-btn-l,
+ input.ui-btn-l {
+ width: 100%;
+ min-width: 60px;
+ height: 46px;
+ line-height: normal;
+ border-radius: 5px;
+ }
+
+ .ui-btn-s {
+ width: 100%;
+ min-width: 48px;
+ height: 25px;
+ font-size: 12px;
+ line-height: 25px;
+ border-radius: 3px;
+ }
+
+ button.ui-btn-s,
+ input.ui-btn-s {
+ width: 100%;
+ min-width: 50px;
+ height: 27px;
+ line-height: normal;
+ border-radius: 3px;
+ }
+
+ .ui-affix {
+ position: fixed;
+ }
+
+ .lt-ie7 .ui-affix {
+ position: absolute;
+ }
+
+ .ui-affix-bottom,
+ .ui-affix-top {
+ position: absolute;
+ }
+
+ @font-face {
+ font-family: "iconfont";
+ src: url('./mip-jjpz/iconfont/iconfont.eot'); /* IE9*/
+ src: url('./mip-jjpz/iconfont/iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('./mip-jjpz/iconfont/iconfont.woff') format('woff'), /* chrome¡¢firefox */ url('./mip-jjpz/iconfont/iconfont.ttf') format('truetype'), /* chrome¡¢firefox¡¢opera¡¢Safari, Android, iOS 4.2+*/ url('./mip-jjpz/iconfont/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */
+ }
+
+ .iconfont:before {
+ font-family: "iconfont" !important;
+ font-size: 16px;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-stroke-width: 0.2px;
+ -moz-osx-font-smoothing: grayscale;
+ }
+
+ .iconfont:after {
+ font-family: "iconfont" !important;
+ font-size: 16px;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -webkit-text-stroke-width: 0.2px;
+ -moz-osx-font-smoothing: grayscale;
+ }
+
+ .loading[type] {
+ display: block;
+ overflow: hidden;
+ -webkit-animation-duration: 1.5s;
+ -ms-animation-duration: 1.5s;
+ -moz-animation-duration: 1.5s;
+ -o-animation-duration: 1.5s;
+ animation-duration: 1.5s;
+ -webkit-animation-timing-function: linear;
+ -ms-animation-timing-function: linear;
+ -moz-animation-timing-function: linear;
+ -o-animation-timing-function: linear;
+ animation-timing-function: linear;
+ -webkit-animation-iteration-count: infinite;
+ -ms-animation-iteration-count: infinite;
+ -moz-animation-iteration-count: infinite;
+ -o-animation-iteration-count: infinite;
+ animation-iteration-count: infinite;
+ -webkit-background-size: 100%;
+ -ms-background-size: 100%;
+ -moz-background-size: 100%;
+ -o-background-size: 100%;
+ background-size: 100%;
+ -webkit-transform: rotate(0);
+ -ms-transform: rotate(0);
+ -moz-transform: rotate(0);
+ -o-transform: rotate(0);
+ transform: rotate(0);
+ -webkit-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -o-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ transform: translateZ(0);
+ -webkit-perspective: 1000;
+ -ms-perspective: 1000;
+ -moz-perspective: 1000;
+ -o-perspective: 1000;
+ perspective: 1000;
+ }
+
+ .ui_outer + .ui_outer, .fold_content + .ui_outer {
+ border-top: none;
+ }
+
+ .ui_outer {
+ background: #fff;
+ border-bottom: 1px solid #e3e2e7;
+ border-top: 1px solid #e3e2e7;
+ }
+
+ .ui_transparent {
+ background: #efeff4;
+ }
+
+ .ui_outer:active {
+ background: #fff;
+ }
+
+ .ui_outer.ui_activated {
+ background: #eee;
+ }
+
+ .ui_outer.ui_scrolling {
+ background: #fff;
+ }
+
+ .ui_outer.ui_blank {
+ border-top: 1px solid #e3e2e7;
+ // margin-top: 20px;
+ }
+
+ .ui_inner {
+ padding: 6px 15px 5px 0;
+ border-top: 1px solid #e3e2e7;
+ margin-top: -1px;
+ position: relative;
+ }
+
+ .ui_inner_m15 {
+ margin-left: 15px;
+ }
+
+ .ui_inner p {
+ font-size: 16px;
+ }
+
+ .ui_inner .ui_m {
+ font-size: 14px;
+ }
+
+ .ui_inner .ui_s {
+ font-size: 12px;
+ }
+
+ .ui_inner .ui_redLabel {
+ padding: 3px 4px;
+ color: #fff;
+ background: #ff0000;
+ border-radius: 5px;
+ position: relative;
+ top: -2px;
+ }
+
+ .ui_inner input {
+ width: 100%;
+ font-size: 16px;
+ height: 16px;
+ line-height: 16px;
+ background: transparent;
+ padding: 8px 0;
+ }
+
+ .ui_h1 {
+ line-height: 2;
+ }
+
+ .ui_h2,
+ .ui_h3 {
+ line-height: 1.5;
+ }
+
+ .ui_alignRight {
+ text-align: right;
+ }
+
+ .ui_alignCenter {
+ text-align: center;
+ }
+
+ .ui_inner .ui_tips {
+ color: #808080;
+ font-size: 12px;
+ }
+
+ .ui_delLine {
+ text-decoration: line-through;
+ color: #808080;
+ }
+
+ .ui_inner .ui_black {
+ color: #000;
+ }
+
+ .ui_inner .ui_red {
+ color: #ff0000;
+ }
+
+ .ui_inner .ui_green {
+ color: #008800;
+ }
+
+ .ui_inner .ui_orange {
+ color: #ff4400;
+ }
+
+ .ui_inner .ui_gray {
+ color: #808080;
+ }
+
+ .ui_inner .ui_arr,
+ .ui_inner .ui_good,
+ .ui_inner .ui_fold,
+ .ui_inner .ui_unfold,
+ .ui_inner .ui_error {
+ position: absolute;
+ top: 50%;
+ right: 15px;
+ transform: translate(0, -50%);
+ -webkit-transform: translate(0, -50%);
+ }
+
+ .ui_inner .ui_unfold:before {
+ content: "\e661";
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #808080;
+ line-height: 1;
+ top: 50%;
+ margin-top: -8px;
+ right: 0;
+ }
+
+ .ui_inner .ui_fold:before {
+ content: "\e661";
+ -webkit-transform: rotate(180deg);
+ -webkit-transform-origin: center center;
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #808080;
+ line-height: 1;
+ top: 50%;
+ margin-top: -8px;
+ right: 0;
+ }
+
+ .ui_inner .ui_error:before {
+ content: "\e658";
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #ff0000;
+ line-height: 1;
+ top: 50%;
+ margin-top: -8px;
+ right: 0;
+ }
+
+ .ui_inner .ui_arr:before {
+ content: "\e6a3";
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #808080;
+ line-height: 1;
+ top: 50%;
+ margin-top: -8px;
+ right: 0;
+ }
+
+ .ui_inner .ui_arr p {
+ padding-right: 20px;
+ }
+
+ .ui_inner .ui_good:before {
+ content: "\e645";
+ position: absolute;
+ display: block;
+ width: 24px;
+ height: 24px;
+ font-size: 24px;
+ color: #ff4400;
+ line-height: 1;
+ top: 50%;
+ margin-top: -12px;
+ right: 0;
+ }
+
+ .pageContent>p {
+ padding: 10px 15px;
+ line-height: 1.5;
+ }
+
+ .pageContent>p + p {
+ padding-top: 0;
+ }
+
+ p.ui_remark {
+ font-size: 13px;
+ color: #808080;
+ }
+
+ p.ui_remark span.orange {
+ color: #ff4400;
+ }
+
+ p.ui_info {
+ font-size: 14px;
+ color: #808080;
+ }
+
+ p.ui_info span.black {
+ color: #000;
+ }
+
+ div.ui_btn {
+ padding: 20px 15px;
+ }
+
+ div.ui_btn + p.ui_remark {
+ margin-top: -20px;
+ }
+
+ .pageContent {
+ padding-top: 45px;
+ padding-bottom: 49px;
+ }
+
+ .ui_inner p.txtBig {
+ font-size: 26px;
+ font-family: 'arial';
+ }
+
+ .ui_inner p.djs span {
+ color: #ff0000;
+ font-size: 30px;
+ }
+
+ .paddingRight>div p>span {
+ float: right;
+ }
+
+ .paddingRight>div:nth-child(2n+1) p {
+ padding-right: 15px;
+ }
+
+ .fold_content {
+ padding: 0px 15px;
+ background: #fff;
+ }
+
+ .fold_content table th {
+ white-space: nowrap;
+ }
+
+ .fold_content table td {
+ padding: 5px 10px;
+ white-space: nowrap;
+ }
+
+ .fold_content table td span.o {
+ color: #E8670C;
+ }
+
+ .fold_content .ui-table td:first-child {
+ text-align: center;
+ }
+
+ .ui-table tr th,
+ .ui-table tr td {
+ border-color: #ddd;
+ }
+
+ .ui-table tr td.ui_red {
+ color: #ff0000;
+ }
+
+ .ui-table tr td.ui_green {
+ color: #008800;
+ }
+
+ .fold_content {
+ overflow: hidden;
+ }
+
+ .fold_content p.tip {
+ padding: 10px 15px;
+ }
+
+ .fold_content p a {
+ font-size: 14px;
+ }
+ .fhpsContent .ui-grid-row,.fhpsContent .ui_remark{
+ border-bottom: 1px solid #ddd;
+ }
+ .fhpsContent p {
+ font-size: 12px;
+ padding: 6px 0;
+ }
+
+ .fhpsContent p b {
+ color: #ff0000;
+ }
+
+ .jjjlContent {
+ padding: 0 15px;
+ }
+
+ .jjjlContent h3 {
+ font-size: 12px;
+ padding:0 0 6px;
+ margin-left: -15px;
+ margin-bottom: 10px;
+ border-bottom: 1px solid #ddd;
+ text-indent: 10px;
+ }
+
+ .jjjlContent h3:before {
+ content: " ";
+ border-left: 3px solid #ff4400;
+ position: relative;
+ left: -10px;
+ top: 0;
+ }
+
+ .jjjlInfo p {
+ font-size: 12px;
+ color: #808080;
+ line-height: 27px;
+ }
+
+ .jjjlInfo p span {
+ float: right;
+ color: #000;
+ }
+
+ .jjjltxt {
+ padding-bottom: 8px;
+ }
+
+ .jjjltxt p {
+ font-size: 12px;
+ color: #333;
+ line-height: 18px;
+ overflow: hidden;
+ }
+
+ .jjjltxt p.on {
+ height: 36px;
+ }
+
+ .jjjltxt a {
+ font-size: 12px;
+ color: #4372ba;
+ }
+
+ a:visited, a:link {
+ color: #4372ba;
+ }
+
+ .jjjltxt a:after {
+ content: ' ';
+ display: inline-block;
+ border: 6px solid transparent;
+ width: 0;
+ height: 0;
+ position: relative;
+ }
+
+ .jjjltxt a.down:hover {
+ color: #4372ba;
+ }
+
+ .jjjltxt a.down:after {
+ border-top-color: #4372ba;
+ top: 6px;
+ }
+
+ .jjjltxt a.up:after {
+ border-bottom-color: #4372ba;
+ top: -2px;
+ }
+
+ .jjggContent>div {
+ border-bottom: 1px solid #ddd;
+ }
+
+ .jjggContent>div p {
+ font-size: 12px;
+ padding: 6px 0;
+ }
+
+ .jjggContent>div p:first {
+ color: #808080;
+ }
+
+ .animfooter {
+ transition: bottom 0.5s;
+ -webkit-transition: bottom 0.5s;
+ }
+
+ .fixContent {
+ border-top: 1px solid #d4d4d4;
+ background: #fff;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+
+ .fixContent>div>div:last-child {
+ padding: 6px 0 5px;
+ line-height: 38px;
+ }
+
+ .fixContent > div > div:last-child span {
+ display: block;
+ float: left;
+ color: #333;
+ border-left: 1px solid #d4d4d4;
+ text-align: center;
+ font-size: 17px;
+ }
+
+ .fixContent > div > div:last-child > span:first-child {
+ border-left: none;
+ }
+
+ .fixContent p {
+ color: #808080;
+ font-size: 13px;
+ padding-left: 15px;
+ }
+
+ .fixContent p span {
+ color: #000;
+ }
+
+ .fixContent p span.red {
+ color: #ff0000;
+ }
+
+ .titleFooter {
+ padding: 20px 15px;
+ }
+
+ .titleFooter p {
+ padding: 0;
+ line-height: 2;
+ }
+
+ .titleFooter p #gotoSafe {
+ position: relative;
+ left: 11px;
+ }
+
+ .titleFooter p #gotoSafe:before {
+ content: "";
+ position: absolute;
+ margin-right: 4px;
+ display: inline-block;
+ top: -4px;
+ left: -22px;
+ width: 16px;
+ height: 26.5px;
+ margin: 0 auto;
+ background-position: -69px 0;
+ background-size: 105px 21px;
+ }
+
+ .btn-inline {
+ width: 100%;
+ height: 47px;
+ font-size: 18px;
+ line-height: 47px;
+ }
+
+ .anotherPadding {
+ padding: 6px 15px 3px 0;
+ }
+
+ .tabp p {
+ height: 40px;
+ line-height: 40px;
+ width: 33.33%;
+ float: left;
+ color: #666;
+ background: #eee;
+ text-align: center;
+ }
+ .tabtwo p{width: 50%;}
+ .hbtab p {
+ width: 50%;
+ }
+
+ .tabp p.active {
+ background: #fff;
+ color: #333;
+ }
+
+ .tabContent {
+ padding: 10px 0;
+ text-align: center;
+ }
+
+ .tabContent.loading {
+ background: url('./mip-jjpz/img/loading2.gif') center center no-repeat;
+ min-height: 50px;
+ }
+
+ .refresh {
+ height: 44px;
+ line-height: 44px;
+ color: #999;
+ }
+
+ .fundName {
+ font-size: 17px;
+ color: #000;
+ width: auto;
+ font-family: "???????";
+ }
+
+ .fundCode {
+ display: block;
+ float: left;
+ font-size: 13px;
+ color: #000;
+ line-height: 34px;
+ font-family: "???????";
+ }
+
+ .noborder {
+ border: none;
+ }
+
+ .ui_outer .nobodrder-top {
+ border-top: none;
+ }
+
+ .width100 {
+ width: 100%;
+ }
+
+ .split {
+ font-size: 14px;
+ padding: 0 10px;
+ color: #ccc;
+ }
+
+ .rzfLeftBorder {
+ border-left: solid 1px #e3e2e7;
+ padding-left: 25px;
+ width: auto;
+ }
+
+ .ui_inner .ui_gray {
+ color: #666;
+ }
+
+ .ui_gray {
+ color: #666;
+ }
+
+ .ui_red {
+ color: #ff0000;
+ }
+
+ .ui_green {
+ color: #008800;
+ }
+
+ .padding-right5px {
+ padding-right: 5px;
+ }
+
+ .ui_inner .ui_arr:before {
+ color: #cccccc;
+ }
+
+ .ui_arr:before {
+ content: "\e6a3";
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #cccccc;
+ line-height: 1;
+ top: 50%;
+ margin-top: -8px;
+ right: 5px;
+ margin-left: 10px;
+ }
+
+ .ui_arr1:before {
+ color: #cccccc;
+ }
+
+ .ui_arr1:before {
+ content: "\e6a3";
+ position: absolute;
+ display: block;
+ width: 16px;
+ height: 16px;
+ color: #cccccc;
+ line-height: 1;
+ margin-top: 1px;
+ right: 15px;
+ margin-left: 10px;
+ }
+
+ .ui_inner .ui_unfold:before {
+ color: #cccccc;
+ }
+
+ .ui_inner .ui_fold:before {
+ color: #cccccc;
+ }
+
+ .padding-r10 {
+ padding-right: 10px;
+ }
+
+ .paddingRight1 > div p > span {
+ float: none;
+ padding-left: 5px;
+ }
+
+ .ui_black {
+ color: #000;
+ }
+
+ .ui_outer.ui_blank {
+ margin-top: 10px;
+ }
+
+ .tab p {
+ background: #f8f8f8;
+ color: #666;
+ }
+
+ .tab p.active {
+ color: #ff4400;
+ font-size: 16px;
+ border-bottom: solid 2px #ff4400;
+ }
+
+ .ui-grid-3 {
+ width: 30%;
+ }
+
+ .ui-grid-7 {
+ width: 70%;
+ }
+
+ .ui_m12 {
+ font-size: 12px;
+ }
+
+ .user_info {
+ width: 60%;
+ float: left;
+ margin-left: 10px;
+ }
+
+ .user_info p {
+ font-size: 14px;
+ line-height: 16px;
+ }
+
+ .jjba_head {
+ width: 100%;
+ height: 32px;
+ margin-top: 6px;
+ }
+
+ .jjba_head .touxiang {
+ height: 32px;
+ width: 32px;
+ float: left;
+ }
+
+ .user_info .uName {
+ font-size: 15px;
+ color: #000;
+ }
+
+ .user_info .upTime {
+ font-size: 10px;
+ height: 10px;
+ line-height: 10px;
+ padding-top: 7px;
+ }
+
+ .jjba_head .updateTime {
+ float: right;
+ font-size: 10px;
+ height: 10px;
+ margin-top: 22px;
+ line-height: 10px;
+ }
+
+ .jjba_content {
+ text-align: justify;
+ font-size: 15px;
+ margin-top: 10px;
+ line-height: 22px;
+ }
+
+ .jjba_title {
+ text-align: justify;
+ font-weight: bold;
+ color: #000;
+ font-size: 17px;
+ line-height: 26px;
+ margin-top: 10px;
+ }
+
+ .jjba_footer {
+ float: right;
+ }
+
+ .jjba_footer div {
+ float: left;
+ margin-left: 5px;
+ font-size: 14px;
+ margin-top: 14px;
+ }
+
+ .margintop0 {
+ margin-top: 0px;
+ }
+
+ .pageContentTgsqxjj ul, li {
+ list-style: none;
+ }
+
+ .pddingRight15 {
+ padding-right: 15px;
+ }
+
+ .fund-list {
+ background-color: #fff;
+ margin-bottom: 0
+ }
+
+ .fund-item-last {
+ border-bottom: 1px solid #d4d4d4;
+ margin: 0 -15px;
+ padding: 10px 15px
+ }
+
+ .fund-item-last1 {
+ border-bottom: 1px solid #d4d4d4;
+ margin: 0 -15px;
+ padding: 4px 15px
+ }
+
+ .fund-item {
+ border-bottom: 1px solid #d4d4d4;
+ margin: 0 -15px;
+ padding: 10px 0px;
+ margin-left: 0;
+ padding-right: 15px;
+ }
+
+ .fund-tbl {
+ width: 100%;
+ line-height: 1
+ }
+
+ .fund-tbl .col_1 {
+ width: 30%;
+ padding-top: 10px
+ }
+
+ .fund-tbl .col_2 {
+ width: 40%
+ }
+
+ .fund-tbl td.left {
+ text-align: left;
+ width: 35%
+ }
+
+ .fund-tbl td.right {
+ text-align: right
+ }
+
+ .fund-tbl .fund-title {
+ float: left;
+ margin-right: 10px;
+ line-height: 1;
+ font-size: 17px;
+ width: 100%
+ }
+
+ .fund-tbl .fund-title a {
+ color: #333;
+ text-decoration: none;
+ font-size: 17px;
+ width:60%;
+ line-height: 20px;
+ display:inline-block;
+ overflow-x: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .fund-tbl .fund-fl {
+ font-size: 17px
+ }
+
+ .fund-tbl .fund-qigou {
+ float: right;
+ color: #999;
+ font-size: .8em
+ }
+
+ .fund-tbl .profit-title {
+ font-size: 13px;
+ color: #6d6d72;
+ display: block;
+ padding-top: 8px
+ }
+
+ .fund-tbl .profit {
+ display: block;
+ font-weight: 400;
+ color: #c00
+ }
+
+ .fund-tbl .align-bottom {
+ vertical-align: bottom
+ }
+
+ .fund-tbl .fund-buy {
+ float: right;
+ width: 90%;
+ height: 36px;
+ line-height: 36px;
+ font-size: 15px;
+ text-align: center;
+ color: #fff;
+ background-color: #f40;
+ text-decoration: none;
+ border-radius: 5px;
+ display: block;
+ }
+
+ .fund-tbl .profit {
+ color: #f00
+ }
+
+ .fund-list a:hover {
+ text-decoration: none;
+ }
+
+ .fund-list {
+ padding-left: 15px;
+ padding-right: 15px;
+ }
+
+ .fund_minsg {
+ display: block;
+ float: right;
+ font-size: 14px;
+ line-height: 17px;
+ }
+
+ .fixContent {
+ position: fixed;
+ bottom: 0;
+ }
+
+ .mip-layout-size-defined .mip-fill-content {
+ background: none;
+ }
+
+ .hide {
+ display: none;
+ }
+
+ .ui_inner .header1 {
+ font-size: 17px;
+ line-height: 17px;
+ color: #000;
+ padding-top: 4px;
+ }
+
+ .ui_inner .header1 .coder {
+ font-size: 13px;
+ line-height: 17px;
+ }
+
+ .ui_inner .header2 {
+ font-size: 14px;
+ color: #666;
+ line-height: 14px;
+ padding-top: 7px;
+ padding-bottom: 10px;
+ }
+
+ .ui_inner_topborder_none {
+ border-top: none;
+ }
+
+ .pageContent1 {
+ margin-top: 10px;
+ }
+
+ .jjxx-h {
+ display: block;
+ text-align: right;
+ padding-right: 15px;
+ }
+
+ .ui-table tr td {
+ border: none;
+ }
+
+ .ui-table tr th {
+ border: none;
+ }
+
+ .fund-list .more {
+ display: block;
+ text-align: center;
+ color: #008aff;
+ background-color: #fff;
+ }
+
+ .border-topnone {
+ border-top: none;
+ }
+
+ .borde-bottomrnone {
+ border: none;
+ }
+
+ .boder-bottom {
+ border-bottom: 1px solid #e3e2e7;
+ }
+
+ .lssylMore {
+ display: block;
+ text-align: center;
+ background-color: #fff;
+ }
+
+ .lssylMore a {
+ color: #008aff;
+ }
+
+ .cjjzri {
+ font-size: 14px;
+ display: inline-block;
+ padding-left: 5px;
+ color: #666;
+ }
+
+ .jjbagoto {
+ background-color: #fff;
+ }
+
+ .fundcode {
+ display: inline-block;
+ font-family: Arial;
+ }
+
+ .gotopinglun, .shareInfo, .addFavor {
+ width: 33%;
+ }
+
+ .numberFont {
+ font-family: Arial;
+ }
+
+ .jjbaNum {
+ color: #000;
+ font-size: 14px;
+ margin: 0 5px;
+ font-family: Arial;
+ }
+
+ .fund-tbl .profit {
+ font-family: Arial;
+ }
+
+ .fund-tbl .fund-fl {
+ font-family: Arial;
+ }
+
+ .gotoJJJL_scroll::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+ }
+
+ .divcenter {
+ text-align: center;
+ font-size: 12px;
+ margin: auto;
+ padding-top: 15px;
+ }
+
+ a:hover {
+ color: #4372ba;
+ }
+
+ .tab::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
+ .notips {
+ width: 100%;
+ height: 50px;
+ line-height: 50px;
+ text-align: center;
+ display: block;
+ }
+
+ section .more {
+ line-height: 31px;
+ height: 31px;
+ background-color: #dedede;
+ text-align: right;
+ padding-right: 14px
+ }
+
+ header {
+ display: block;
+ width: 100%;
+ height: 45px;
+ position: relative;
+ margin-top: 0px;
+ }
+
+ header .icon {
+ float: left;
+ width: 100%;
+ height: 45px;
+ line-height: 13px;
+ font-size: 13px;
+ overflow: hidden;
+ background: #2f5387;
+ position: relative;
+ }
+
+ .font-MS {
+ font-family: 'Microsoft Yahei', 'Simsun'
+ }
+
+ .height60 {
+ height: 60px !important;
+ }
+
+ .font17 {
+ color: #000;
+ font-size: 17px;
+ }
+
+ .font22 {
+ font-size: 22px;
+ color: #ff0000
+ }
+
+ .font15 {
+ font-size: 15px;
+ width: 22px;
+ line-height: 22px;
+ text-decoration: line-through;
+ color: #6d6d72
+ }
+
+ .font15W {
+ font-size: 15px !important;
+ color: #000;
+ width: 22px;
+ line-height: 22px;
+ }
+
+ .paddingR15 {
+ padding-right: 15px;
+ }
+
+ .margintop10 {
+ margin-top: 10px;
+ }
+
+ .overflow-x {
+ overflow-x: scroll;
+ }
+
+ .hidden {
+ display: none;
+ }
+
+ .fontBlue {
+ color: #4372ba !important;
+ }
+
+ a:visited, a:link {
+ color: #4372ba;
+ }
+
+ .gotoJJJL_detail img {
+ width: 80px;
+ height: 100px;
+ }
+
+ .fold_content .tip {
+ font-size: 14px;
+ color: #4372ba;
+ }
+
+ header .icon .title h1 {
+ text-align: center;
+ width: 100%;
+ color: #fff;
+ line-height: 45px;
+ font-size: 20px;
+ }
+
+ .discussLink {
+ width: 90%;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ border: 1px solid #008aff;
+ border-radius: 2px;
+ color: #008aff;
+ display: block;
+ margin: 10px auto 0;
+ }
+
+ .discussLink span {
+ color: #ff4400;
+ margin: 0 4px;
+ }
+
+ section {
+ margin-top: 10px;
+ }
+
+ body {
+ background-color: #f5f5f9;
+ }
+
+ .tuiguang_xz {
+ background: #fff;
+ font-size: 14px;
+ height: 30px;
+ line-height: 30px;
+ margin: 10px 0;
+ overflow: hidden;
+ padding-left: 24px;
+ position: relative;
+ text-align: left;
+ }
+
+ .tuiguang_xz::before {
+ content: '';
+ display: block;
+ left: 5px;
+ position: absolute;
+ width: 21px;
+ height: 26px;
+ background: url('./mip-jjpz/img/20150831173017783.png') no-repeat left center;
+ }
+
+ .tuiguang_xz span:first-child, .tuiguang_xz span:first-child a {
+ color: #333;
+ }
+
+ .tuiguang_xz span.red a {
+ color: #f00;
+ text-decoration: underline;
+ }
+
+ footer {
+ padding-bottom: 60px;
+ }
+
+ footer .foot_href {
+ height: 45px;
+ line-height: 45px;
+ text-align: center;
+ color: #8b98a8;
+ background: #2f5387;
+ font-size: 17px;
+ list-style-type: none;
+ margin-top: 10px;
+ }
+
+ footer .foot_href li {
+ width: 13.5%;
+ float: left;
+ text-align: center;
+ }
+
+ footer .foot_href li:last-child {
+ width: 16%;
+ }
+
+ footer .foot_href li a {
+ font-size: 17px;
+ color: white;
+ font-family: "华文细黑";
+ }
+
+ footer .foot_about {
+ height: 55px;
+ line-height: 55px;
+ text-align: center;
+ font-size: 17px;
+ list-style-type: none;
+ border-bottom: 1px solid #D7CFCF;
+ }
+
+ footer .foot_about li {
+ float: left;
+ text-align: center;
+ }
+
+ footer .foot_about .item {
+ width: 32%;
+ }
+
+ footer .foot_about .split {
+ width: 1px;
+ color: #D7CFCF;
+ padding: 0;
+ }
+
+ footer .foot_about li a {
+ font-size: 14px;
+ color: #959595;
+ font-family: "华文细黑";
+ }
+
+ footer .foot_download {
+ height: 45px;
+ line-height: 45px;
+ text-align: center;
+ width: 100%;
+ margin: 18px 0;
+ }
+
+ footer .foot_download a {
+ padding: 11px 16px;
+ border: 1px solid #4372ba;
+ color: #4372ba;
+ font-size: 16px;
+ font-family: "华文细黑";
+ }
+
+ footer .foot_desc {
+ height: 45px;
+ text-align: center;
+ width: 100%;
+ }
+
+ footer .foot_desc {
+ font-size: 14px;
+ font-family: "华文细黑";
+ }
+
+ footer .foot_desc font {
+ color: #ff4400;
+ font-size: 14px;
+ padding: 5px;
+ font-family: "Arial";
+ }
+
+ footer .foot_desc p.foot_time {
+ font-size: 12px;
+ color: #959595;
+ margin-top: 7px;
+ font-family: "华文细黑";
+ }
+
+ footer .foot_desc p.foot_time .foot_time_s {
+ font-family: "Arial";
+ }
+ footer .foot_desc .foot_title a{
+ color: #ff4400;
+ }
+ .flexShare {
+ position: absolute;
+ -webkit-transform: translate(50%, -100%);
+ margin-top: -6px;
+ background-color: rgba(53, 53, 53, 0.9);
+ border-radius: 5px;
+ color: #fff;
+ padding: 6px 15px;
+ font-size: 15px;
+ right: 33%;
+ }
+ .flexShare::after {
+ content: '';
+ width: 10px;
+ height: 9px;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 5px auto;
+ background: url('./mip-jjpz/img/down.png') no-repeat;
+ background-size: 100% 100%;
+ }
+
+
+.alertMasker {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.4);
+ z-index: 99998;
+ display: none;
+}
+.alertMasker>div .alert, {
+ display: table-row;
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+.alertMasker>div .alert .inner {
+ width:270px;
+ background: #fff;
+ border-radius:14px;
+ overflow:hidden;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform:translate(-50%, -50%);
+ -webkit-transform:translate(-50%, -50%);
+ padding-top: 10px;
+}
+.alertMasker>div .alert h2 {
+ box-sizing: content-box;
+ font-size:19px;
+ font-weight:normal;
+ text-align:center;
+ height:28px;
+ line-height:28px;
+ text-shadow:0 -1px 0 rgba(0,0,0,0.8);
+ color:#000000;
+ padding-top:5px
+}
+.PasswordMasker>div .Password p,
+.confirmMasker>div .confirm p,
+.alertMasker>div .alert p {
+ color: #000000;
+ padding:8px 15px;
+ font-size:14px;
+ text-align:center;
+ font-weight:normal;
+
+}
+.alertMasker footer {
+ display: flex;
+ display: -webkit-flex;
+ display: -webkit-box;
+
+ -webkit-box-orient: horizontal;
+ -ms-box-orient: horizontal;
+ -o-box-orient: horizontal;
+ -moz-box-orient: horizontal;
+ box-orient: horizontal;
+
+ -webkit-flex-direction: row;
+ -ms-flex-direction: row;
+ -o-flex-direction: row;
+ -moz-flex-direction: row;
+ flex-direction: row;
+
+ -webkit-justify-content: distribute;
+ -ms-justify-content: distribute;
+ -o-justify-content: distribute;
+ -moz-justify-content: distribute;
+ justify-content: space-around;
+
+ -webkit-box-align: stretch;
+ -ms-box-align: stretch;
+ -o-box-align: stretch;
+ -moz-box-align: stretch;
+ box-align: stretch;
+
+ -webkit-align-items: stretch;
+ -ms-align-items: stretch;
+ -moz-align-items: stretch;
+ -o-align-items: stretch;
+ align-items: stretch;
+
+ width: 100%;
+ box-sizing: border-box;
+ padding: 0;
+ padding-top: 10px;
+}
+
+.alertMasker footer>.button {
+ display: block;
+
+ -webkit-box-flex: 1;
+ -ms-box-flex: 1;
+ -o-box-flex: 1;
+ -moz-box-flex: 1;
+ box-flex: 1;
+
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ -o-flex: 1;
+ -moz-flex: 1;
+ flex: 1;
+
+ height: 100%;
+ line-height: 30px;
+ max-width: 320px;
+ font-weight: normal;
+ border-top: solid 1px #b5b5b5;
+ color: #2d2d2d;
+}
+
+.alertMasker footer>.button:first-child{
+ border-bottom-left-radius:14px;
+}
+.alertMasker footer>.button:last-child{
+ border-bottom-right-radius:14px;
+}
+.alertMasker a.button {
+
+ cursor: pointer;
+ text-decoration: none;
+ font-size: 13pt;
+
+/* -webkit-transition: all .35s ease-out;
+ -ms-transition: all .35s ease-out;
+ -o-transition: all .35s ease-out;
+ -moz-transition: all .35s ease-out;
+ transition: all .35s ease-out;*/
+
+ display: block;
+
+ -webkit-box-sizing: auto;
+ -ms-box-sizing: auto;
+ -o-box-sizing: auto;
+ -moz-box-sizing: auto;
+ box-sizing: auto;
+
+ padding: 8px 0;
+ position: relative;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ text-align: center;
+ color: #007aff;
+ border-color: #e1e1e6;
+ background-color:#fff;
+}
+.alertMasker a.button.BTNactive{
+ background-color:#c8c7cc;
+}
+.alertMasker footer>.button {
+ display: block;
+
+ -webkit-box-flex: 1;
+ -ms-box-flex: 1;
+ -o-box-flex: 1;
+ -moz-box-flex: 1;
+ box-flex: 1;
+
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ -o-flex: 1;
+ -moz-flex: 1;
+ flex: 1;
+
+ height: 100%;
+ line-height: 30px;
+ max-width: 320px;
+ font-weight: normal;
+ border-top: solid 1px #b5b5b5;
+ color: #2d2d2d;
+}
+}
\ No newline at end of file
diff --git a/mip-jjpz/package.json b/mip-jjpz/package.json
new file mode 100644
index 00000000..d2c822b0
--- /dev/null
+++ b/mip-jjpz/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-jjpz",
+ "version": "1.0.3",
+ "description": "tiantian fund business",
+ "author": {
+ "name": "elang126",
+ "email": "liiang2006@126.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-jt-ams/README.md b/mip-jt-ams/README.md
index b2e25ad9..fc2e1177 100644
--- a/mip-jt-ams/README.md
+++ b/mip-jt-ams/README.md
@@ -6,7 +6,7 @@ mip-jt-ams 金投网广告组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-ams/mip-jt-ams.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-ams/mip-jt-ams.js
## 示例
diff --git a/mip-jt-banner/README.md b/mip-jt-banner/README.md
index e4610a2f..750352be 100644
--- a/mip-jt-banner/README.md
+++ b/mip-jt-banner/README.md
@@ -6,7 +6,7 @@ mip-jt-banner 头部banner
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-jiepan/mip-jt-jiepan.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-jiepan/mip-jt-jiepan.js
## 示例
diff --git a/mip-jt-calc-homeyk/README.md b/mip-jt-calc-homeyk/README.md
index 597fe8ef..c8cabc05 100644
--- a/mip-jt-calc-homeyk/README.md
+++ b/mip-jt-calc-homeyk/README.md
@@ -6,7 +6,7 @@ mip-jt-calc-homeyk 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-calc-homeyk/mip-jt-calc-homeyk.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-calc-homeyk/mip-jt-calc-homeyk.js
## 示例
diff --git a/mip-jt-calc-homezh/README.md b/mip-jt-calc-homezh/README.md
index 35536491..82de9b4e 100644
--- a/mip-jt-calc-homezh/README.md
+++ b/mip-jt-calc-homezh/README.md
@@ -6,7 +6,7 @@ mip-jt-calc-homezh 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-calc-homezh/mip-jt-calc-homezh.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-calc-homezh/mip-jt-calc-homezh.js
## 示例
diff --git a/mip-jt-calendar/README.md b/mip-jt-calendar/README.md
index 0fcd86fa..e0c84730 100644
--- a/mip-jt-calendar/README.md
+++ b/mip-jt-calendar/README.md
@@ -6,7 +6,7 @@ mip-jt-calendar 财经日历组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-calendar/mip-jt-calendar.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-calendar/mip-jt-calendar.js
## 示例
diff --git a/mip-jt-comment/README.md b/mip-jt-comment/README.md
index d34caa6e..4e8e78c2 100644
--- a/mip-jt-comment/README.md
+++ b/mip-jt-comment/README.md
@@ -6,7 +6,7 @@ mip-jt-comment 评论组件
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-comment/mip-jt-comment.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-comment/mip-jt-comment.js |
## 示例a
diff --git a/mip-jt-jiepan/README.md b/mip-jt-jiepan/README.md
index 7f4c677c..518eb2fa 100644
--- a/mip-jt-jiepan/README.md
+++ b/mip-jt-jiepan/README.md
@@ -6,7 +6,7 @@ mip-jt-jiepan 解盘组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-jiepan/mip-jt-jiepan.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-jiepan/mip-jt-jiepan.js
## 示例
diff --git a/mip-jt-madapt/README.md b/mip-jt-madapt/README.md
index b3cef7c9..75ca246b 100644
--- a/mip-jt-madapt/README.md
+++ b/mip-jt-madapt/README.md
@@ -6,7 +6,7 @@ mip-jt-madapt 自动适配手机屏幕宽度
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-madapt/mip-jt-madapt.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-madapt/mip-jt-madapt.js
## 示例
diff --git a/mip-jt-map/README.md b/mip-jt-map/README.md
index 0cd8c011..1cbe154a 100644
--- a/mip-jt-map/README.md
+++ b/mip-jt-map/README.md
@@ -6,7 +6,7 @@ mip-jt-map 金投地图组件使用说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-map/mip-jt-map.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-map/mip-jt-map.js
## 示例
diff --git a/mip-jt-passport/README.md b/mip-jt-passport/README.md
index 17a66e28..8c3a891a 100644
--- a/mip-jt-passport/README.md
+++ b/mip-jt-passport/README.md
@@ -6,7 +6,7 @@ mip-jt-passport
----|----
类型|业务
支持布局|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-passport/mip-jt-passport.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-passport/mip-jt-passport.js
## 示例
diff --git a/mip-jt-quote-chart/README.md b/mip-jt-quote-chart/README.md
index 3c5d0e31..207a8c1b 100644
--- a/mip-jt-quote-chart/README.md
+++ b/mip-jt-quote-chart/README.md
@@ -6,7 +6,7 @@ mip-jt-quote-chart 图表组件
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-quote-chart/mip-jt-quote-chart.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-quote-chart/mip-jt-quote-chart.js |
## 示例
diff --git a/mip-jt-quote-content/README.md b/mip-jt-quote-content/README.md
index ee4ff0ed..fa7cf67f 100644
--- a/mip-jt-quote-content/README.md
+++ b/mip-jt-quote-content/README.md
@@ -6,7 +6,7 @@ mip-jt-quote-content 行情内容页js组件
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-quote-content/mip-jt-quote-content.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-quote-content/mip-jt-quote-content.js |
## 示例
diff --git a/mip-jt-quote-his/README.md b/mip-jt-quote-his/README.md
index 06cc84b8..6d365e45 100644
--- a/mip-jt-quote-his/README.md
+++ b/mip-jt-quote-his/README.md
@@ -6,7 +6,7 @@ mip-jt-quote-his 历史组件
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-quote-his/mip-jt-quote-his.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-quote-his/mip-jt-quote-his.js |
## 示例
diff --git a/mip-jt-quote-realtime/README.md b/mip-jt-quote-realtime/README.md
index e0bfbe92..3d327265 100644
--- a/mip-jt-quote-realtime/README.md
+++ b/mip-jt-quote-realtime/README.md
@@ -6,7 +6,7 @@ mip-jt-quote-realtime 组件说明
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-quote-realtime/mip-jt-quote-realtime.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-quote-realtime/mip-jt-quote-realtime.js |
## 示例
diff --git a/mip-jt-quote-rt/README.md b/mip-jt-quote-rt/README.md
index b2f07b29..19e68e64 100644
--- a/mip-jt-quote-rt/README.md
+++ b/mip-jt-quote-rt/README.md
@@ -6,7 +6,7 @@ mip-jt-quote-rt 获取行情排序数据组件
| ---- | ---------------------------------------- |
| 类型 | 通用 |
| 支持布局 | responsive,fixed-height,fill,container,fixed |
-| 所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-jt-quote-rt/mip-jt-quote-rt.js |
+| 所需脚本 | https://c.mipcdn.com/static/v1/mip-jt-quote-rt/mip-jt-quote-rt.js |
## 示例
diff --git a/mip-jt-resize/README.md b/mip-jt-resize/README.md
index 761f60b0..17495963 100644
--- a/mip-jt-resize/README.md
+++ b/mip-jt-resize/README.md
@@ -6,7 +6,7 @@ mip-jt-resize 金投适配
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-jt-resize/mip-jt-resize.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-jt-resize/mip-jt-resize.js
## 示例
diff --git a/mip-judge-port/README.md b/mip-judge-port/README.md
index 7f0c4388..f55304a5 100644
--- a/mip-judge-port/README.md
+++ b/mip-judge-port/README.md
@@ -6,7 +6,7 @@ mip-judge-port 判断访问是什么设备,给下载按钮,相应连接
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-judge-port/mip-judge-port.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-judge-port/mip-judge-port.js
## 示例
diff --git a/mip-jx-ad/README.md b/mip-jx-ad/README.md
new file mode 100644
index 00000000..abafbe22
--- /dev/null
+++ b/mip-jx-ad/README.md
@@ -0,0 +1,36 @@
+# mip-jx-ad
+
+mip-jx-ad 第三方聚效广告扩展组件
+
+标题|内容
+----|----
+类型|通用,定制
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-jx-ad/mip-jx-ad.js
+
+## 示例
+
+用于投放聚效第三方广告的扩展组件
+```html
+
+
+```
+
+## 属性
+
+### ayid
+
+说明:扩展组件广告id
+必填:是
+类型:字符串
+
+### adtype
+
+说明:广告类型
+必填:是
+类型:字符串
+取值:ad-dsf
+
\ No newline at end of file
diff --git a/mip-jx-ad/mip-jx-ad.js b/mip-jx-ad/mip-jx-ad.js
new file mode 100644
index 00000000..c8ba2387
--- /dev/null
+++ b/mip-jx-ad/mip-jx-ad.js
@@ -0,0 +1,20 @@
+/**
+ * @file 第三方广告
+ *
+ * @author jxl
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var appendtxt = $('
');
+ $(document.body).append(appendtxt);
+ var node = document.createElement('script');
+ node.type = 'text/javascript';
+ node.src = 'http://p.tanx.com/ex?i=mm_34618856_4222645_14288433';
+ node.async = 'true';
+ node.id = 'tanx-s-mm_34618856_4222645_14288433';
+ var tanxh = document.getElementsByTagName('head')[0];
+ if (tanxh) {
+ tanxh.insertBefore(node, tanxh.firstChild);
+ }
+ });
diff --git a/mip-jx-ad/package.json b/mip-jx-ad/package.json
new file mode 100644
index 00000000..422ff63f
--- /dev/null
+++ b/mip-jx-ad/package.json
@@ -0,0 +1,10 @@
+{
+ "name": "mip-jx-ad",
+ "version": "1.0.0",
+ "description": "article advertisement",
+ "author": "jixiangling",
+ "email": "1334935207@qq.com",
+ "engines": {
+ "mip": ">=1.0.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-kw-ajax/README.md b/mip-kw-ajax/README.md
index 30253279..c434108a 100644
--- a/mip-kw-ajax/README.md
+++ b/mip-kw-ajax/README.md
@@ -6,7 +6,7 @@ mip-kw-ajax kw—ajax请求
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-kw-ajax/mip-kw-ajax.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-kw-ajax/mip-kw-ajax.js
## 示例
diff --git a/mip-leshu-tab/README.md b/mip-leshu-tab/README.md
index c039bd73..5532c201 100644
--- a/mip-leshu-tab/README.md
+++ b/mip-leshu-tab/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-leshu-tabs/mip-leshu-tab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-leshu-tabs/mip-leshu-tab.js
## 示例
diff --git a/mip-lezun/README.md b/mip-lezun/README.md
index dbf5f255..5a6f63bb 100644
--- a/mip-lezun/README.md
+++ b/mip-lezun/README.md
@@ -6,7 +6,7 @@ mip-lezun 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-lezun/mip-lezun.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-lezun/mip-lezun.js
## 示例
diff --git a/mip-linkeddb-changeHref/README.md b/mip-linkeddb-changeHref/README.md
index e3e258c7..a6234744 100644
--- a/mip-linkeddb-changeHref/README.md
+++ b/mip-linkeddb-changeHref/README.md
@@ -6,7 +6,7 @@ mip-linkeddb-changeHref 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linkeddb-changeHref/mip-linkeddb-changeHref.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linkeddb-changeHref/mip-linkeddb-changeHref.js
## 示例
diff --git a/mip-linkeddb-mad/README.md b/mip-linkeddb-mad/README.md
index db707f4b..810cb3cc 100644
--- a/mip-linkeddb-mad/README.md
+++ b/mip-linkeddb-mad/README.md
@@ -6,7 +6,7 @@ mip-linkeddb-mad 用来添加linkeddb网站广告的组件
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linkeddb-mad/mip-linkeddb-mad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linkeddb-mad/mip-linkeddb-mad.js
## 示例
diff --git a/mip-linkeddb-relmap/README.md b/mip-linkeddb-relmap/README.md
index fb06e825..e80e5f72 100644
--- a/mip-linkeddb-relmap/README.md
+++ b/mip-linkeddb-relmap/README.md
@@ -6,7 +6,7 @@ mip-linkeddb-relmap 用来添加linkeddb网站人物关系图绘制、分页数
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linkeddb-relmap/mip-linkeddb-relmap.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linkeddb-relmap/mip-linkeddb-relmap.js
## 示例
diff --git a/mip-linktion-city/README.md b/mip-linktion-city/README.md
index 45851fd1..58bf9a50 100644
--- a/mip-linktion-city/README.md
+++ b/mip-linktion-city/README.md
@@ -6,7 +6,7 @@ mip-linktion-city 级联城市组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|htthttps://c.mipcdn.com/extensions/platform/v1/mip-linktion-city/mip-linktion-city.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
https://c.mipcdn.com/static/v1/mip-vd-tabs/mip-vd-tabs.js
+所需脚本|htthttps://c.mipcdn.com/static/v1/mip-linktion-city/mip-linktion-city.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
https://c.mipcdn.com/static/v1/mip-vd-tabs/mip-vd-tabs.js
## 示例
diff --git a/mip-linktion-fortune-video/README.md b/mip-linktion-fortune-video/README.md
index a3bb2ac8..9e09ce11 100644
--- a/mip-linktion-fortune-video/README.md
+++ b/mip-linktion-fortune-video/README.md
@@ -6,7 +6,7 @@ mip-linktion-fortune-video 控制弹框视频在第一次点击全屏播放,
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linktion-fortune-video/mip-linktion-fortune-video.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linktion-fortune-video/mip-linktion-fortune-video.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
## 示例
diff --git a/mip-linktion-fortune/README.md b/mip-linktion-fortune/README.md
index 7d3b5c58..7b297519 100644
--- a/mip-linktion-fortune/README.md
+++ b/mip-linktion-fortune/README.md
@@ -6,7 +6,7 @@ mip-linktion-fortune 通用代码使用
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linktion-fortune/mip-linktion-fortune.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linktion-fortune/mip-linktion-fortune.js
## 示例
diff --git a/mip-linktion-try/README.md b/mip-linktion-try/README.md
index 055e749f..3d766a83 100644
--- a/mip-linktion-try/README.md
+++ b/mip-linktion-try/README.md
@@ -6,7 +6,7 @@ mip-linktion-try 弹框ajax提交信息并后续弹框提示操作结果
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-linktion-try/mip-linktion-try.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-linktion-try/mip-linktion-try.js
https://c.mipcdn.com/static/v1/mip-lightbox/mip-lightbox.js
## 示例
diff --git a/mip-liuxue-main/README.md b/mip-liuxue-main/README.md
index 372d9503..8d9d419a 100644
--- a/mip-liuxue-main/README.md
+++ b/mip-liuxue-main/README.md
@@ -6,7 +6,7 @@ mip-liuxue-main 提供页面部分逻辑代码
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-liuxue-main/mip-liuxue-main.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-liuxue-main/mip-liuxue-main.js
## 示例
diff --git a/mip-lmb-script/README.md b/mip-lmb-script/README.md
index 1cc447be..def4384a 100644
--- a/mip-lmb-script/README.md
+++ b/mip-lmb-script/README.md
@@ -5,7 +5,7 @@ mip-lmb-script 辣妈帮mip:URL跳转、关闭元素显示、点击切换元素
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-lmb-script/mip-lmb-script.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-lmb-script/mip-lmb-script.js
## 示例
diff --git a/mip-lnxyw-switch/README.md b/mip-lnxyw-switch/README.md
index 23f06e15..f7e95b7a 100644
--- a/mip-lnxyw-switch/README.md
+++ b/mip-lnxyw-switch/README.md
@@ -6,7 +6,7 @@ mip-lnxyw-switch 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-lnxyw-switch/mip-lnxyw-switch.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-lnxyw-switch/mip-lnxyw-switch.js
## 示例
diff --git a/mip-ls-pagination/README.md b/mip-ls-pagination/README.md
new file mode 100644
index 00000000..fdb6d1ab
--- /dev/null
+++ b/mip-ls-pagination/README.md
@@ -0,0 +1,31 @@
+# mip-ls-pagination
+
+mip-ls-pagination 用来实现招聘信息列表的分页功能
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-ls-pagination/mip-ls-pagination.js
+
+## 示例
+```
+
+
+ - 1
+ - 2
+ - 3
+ - 4
+ - 5
+ - 6
+ - 7
+
+
+```
+
+## 属性
+
+### data-page-size
+
+说明:每页显示的条数,(Number) 。默认每页显示5条记录。
+必选项:否
\ No newline at end of file
diff --git a/mip-ls-pagination/mip-ls-pagination.js b/mip-ls-pagination/mip-ls-pagination.js
new file mode 100644
index 00000000..02a9f4ff
--- /dev/null
+++ b/mip-ls-pagination/mip-ls-pagination.js
@@ -0,0 +1,115 @@
+/**
+ * @author: Keith
+ * @date: 2016-11-24
+ * @file: mip-ls-pagination.js
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var currentPage = 0;
+ var zp = require('zepto');
+
+ function createPagination(elem) {
+ var pageSize = 5;
+ if (elem.hasAttribute('data-page-size')) {
+ pageSize = Number(elem.getAttribute('data-page-size'));
+ }
+ // 创建页码选择器
+ var items = elem.querySelectorAll('li');
+ var pages = Math.ceil(items.length / pageSize);
+ var elemBox = elem.getBoundingClientRect();
+ var parentElem = elem.parentNode;
+
+ var paginationElem = document.createElement('div');
+ paginationElem.className = 'pagination';
+ paginationElem.style.width = elemBox.width + 'px';
+ parentElem.appendChild(paginationElem);
+
+ var firstPageElem = document.createElement('div');
+ firstPageElem.className = 'first';
+ firstPageElem.innerHTML = '首页';
+ paginationElem.appendChild(firstPageElem);
+
+ var prevPageElem = document.createElement('div');
+ prevPageElem.className = 'prev';
+ prevPageElem.innerHTML = '上一页';
+ paginationElem.appendChild(prevPageElem);
+
+ var chooserElem = document.createElement('select');
+ chooserElem.className = 'choose';
+ paginationElem.appendChild(chooserElem);
+ for (var i = 0; i < pages; i++) {
+ chooserElem.innerHTML += '
';
+ }
+
+ var nextPageElem = document.createElement('div');
+ nextPageElem.className = 'next';
+ nextPageElem.innerHTML = '下一页';
+ paginationElem.appendChild(nextPageElem);
+
+ var lastPageElem = document.createElement('div');
+ lastPageElem.className = 'last';
+ lastPageElem.innerHTML = '末页';
+ paginationElem.appendChild(lastPageElem);
+
+ zp(firstPageElem).on('click', function () {
+ choosePage(0, elem);
+ });
+ zp(lastPageElem).on('click', function () {
+ choosePage(pages, elem);
+ });
+ zp(prevPageElem).on('click', function () {
+ if (currentPage > 0) {
+ choosePage(currentPage - 1, elem);
+ }
+ });
+ zp(nextPageElem).on('click', function () {
+ if (currentPage < pages - 1) {
+ choosePage(currentPage + 1, elem);
+ }
+ });
+ zp(chooserElem).on('click', function () {
+ choosePage(Number(chooserElem.value), elem);
+ });
+ }
+
+ function init(elem) {
+ choosePage(0, elem);
+
+ createPagination(elem);
+ }
+
+ function choosePage(index, elem) {
+ var pageSize = 5;
+ if (elem.hasAttribute('data-page-size')) {
+ pageSize = Number(elem.getAttribute('data-page-size'));
+ }
+ var items = elem.querySelectorAll('li');
+ var pages = Math.ceil(items.length / pageSize);
+
+ currentPage = index;
+
+ // 切换页码时,修改location.search值
+ location.search = '?p=' + index;
+
+ var prevElem = elem.querySelector('.prev');
+ var nextElem = elem.querySelector('.next');
+ if (currentPage === 0) {
+ !prevElem.classList.contains('disabled') && prevElem.classList.add('disabled');
+ nextElem.classList.contains('disabled') && nextElem.classList.remove('disabled');
+ } else if (currentPage === (pages - 1)) {
+ !nextElem.classList.contains('disabled') && nextElem.classList.add('disabled');
+ prevElem.classList.contains('disabled') && prevElem.classList.remove('disabled');
+ } else {
+ prevElem.classList.contains('disabled') && prevElem.classList.remove('disabled');
+ nextElem.classList.contains('disabled') && nextElem.classList.remove('disabled');
+ }
+ }
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var element = this.element;
+ init(element);
+ };
+ return customElem;
+});
diff --git a/mip-ls-pagination/mip-ls-pagination.less b/mip-ls-pagination/mip-ls-pagination.less
new file mode 100644
index 00000000..b3081975
--- /dev/null
+++ b/mip-ls-pagination/mip-ls-pagination.less
@@ -0,0 +1,24 @@
+mip-ls-pagination {
+ li.available {
+ display: block;
+ }
+ .pagination {
+ height: 40px;
+ line-height: 40px;
+ color: #666;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ }
+ .pagination .prev, .pagination .next {
+ color: #63ff72;
+ }
+ .pagination .prev.disabled, .pagination .next.disabled {
+ color: #666;
+ }
+ .pagination .choose {
+ border: none;
+ -webkit-tap-highlight-color: transparent;
+ outline: none;
+ }
+}
\ No newline at end of file
diff --git a/mip-ls-pagination/package.json b/mip-ls-pagination/package.json
new file mode 100644
index 00000000..d82988ba
--- /dev/null
+++ b/mip-ls-pagination/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-ls-pagination",
+ "version": "1.0.0",
+ "author": {
+ "name": "Keith",
+ "email": "61826293@qq.com",
+ "url": "http://m.zhaopin.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-med-showmore/README.md b/mip-med-showmore/README.md
index 1bc56de5..5afe9d54 100644
--- a/mip-med-showmore/README.md
+++ b/mip-med-showmore/README.md
@@ -6,7 +6,7 @@ mip-med-showmore 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-med-showmore/mip-med-showmore.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-med-showmore/mip-med-showmore.js
## 示例
diff --git a/mip-meishij/README.md b/mip-meishij/README.md
new file mode 100644
index 00000000..18de3675
--- /dev/null
+++ b/mip-meishij/README.md
@@ -0,0 +1,84 @@
+# mip-meishij
+
+mip-meishij实现了百分点统计,收藏和菜单添加,社会化分享。
+
+标题|内容
+----|----
+类型|业务
+支持布局|不使用布局
+所需脚本|https://c.mipcdn.com/static/v1/mip-meishij/mip-meishij.js
+
+## 示例
+
+### 百分点统计
+
+在`
`下添加一个``即可开启百分点统计,其参数通过`data`配置。例如:
+
+```html
+
+
+
+```
+
+### 社会化分享
+
+社会化分享包括QQ分享和微博分享,要求与原页面有一样的Class和层级。
+
+#### 微博分享
+
+通过``启用微博分享(选择符:`.sharebox .weibo`)。同样通过`data`配置参数:
+
+```html
+
+
+
+
+
+
+```
+
+#### QQ分享
+
+QQ分享和微博分享类似:
+
+```html
+
+
+
+
+
+
+```
+
+### 收藏和菜单
+
+收藏和菜单HTML和原来保持一致即可。`mip-meishij`对原JavaScript进行了封装。
+另外,ID可通过`#addfav_box`的`data-id`属性进行定义。例如:
+
+```javascript
+
+ ...
+
+ ...
+
+```
+
+因为跨域所以域名`http://m.meishij.net`下的AJAX API应全部改为JSONP。包括:
+
+* `/ajax/do_user_caidans.php?id=1685441&act=modi&rids=xxx`
+* `/ajax/do_user_caidans.php?id=1685441`
+* `/ajax/add_nfav.php?obj_id=1685441`
+* `/ajax/add_nfav.php?act=cancel&obj_id=1685441`
+* `/ajax/create_caidan.php?t=xxx`
+
diff --git a/mip-meishij/mip-meishij.js b/mip-meishij/mip-meishij.js
new file mode 100644
index 00000000..4a7b4696
--- /dev/null
+++ b/mip-meishij/mip-meishij.js
@@ -0,0 +1,233 @@
+/**
+ * @file mip-meishij
+ * @author yangjun14(yangjun14@baidu.com)
+ */
+
+define(function (require) {
+ var URL = 'http://m.meishij.net';
+ var id = $('.addfav_box').data('id');
+
+ // 工具方法
+ function jsonpPost(url, data, cb) {
+ return $.ajax({
+ type: 'POST',
+ data: data,
+ url: url,
+ dataType: 'jsonp',
+ success: cb
+ });
+ }
+
+ // 微博分享点击处理函数
+ function initWeiboShare($el) {
+ var config = {
+ appkey: $el.data('appkey'),
+ title: $el.data('title'),
+ pic: $el.data('pic'),
+ url: $el.data('url'),
+ relateUid: $el.data('relate-uid')
+ };
+ var url = 'http://service.t.sina.com.cn/share/share.php?' + 'appkey=' + config.appkey + '&title=' + encodeURIComponent(config.title) + '&pic=' + config.pic + '&url=' + config.url + '&ralateUid=' + config.ralateUid;
+ $el.click(function () {
+ window.open(url);
+ });
+ }
+
+ // 初始化QQ空间分享
+ function initQQShare($el) {
+ var config = {
+ url: $el.data('url'),
+ desc: $el.data('desc'),
+ pics: $el.data('pics'),
+ summary: $el.data('summary'),
+ title: $el.data('title'),
+ site: $el.data('site')
+ };
+ var query = 'url=' + encodeURIComponent(config.url);
+ query = query + 'desc=' + encodeURIComponent(config.desc);
+ query = query + 'pics=' + encodeURIComponent(config.pics);
+ query = query + 'summary=' + encodeURIComponent(config.summary);
+ query = query + 'title=' + encodeURIComponent(config.title);
+ query = query + 'site=' + encodeURIComponent(config.site);
+ var url = 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?' + query;
+
+ $el.click(function () {
+ var tmpStr = 'height=330,width=550,top=' + (screen.height - 280) / 2;
+ tmpStr = tmpStr + ',left=' + (screen.width - 550) / 2 + ', toolbar=no, menubar=no';
+ tmpStr = tmpStr + ' scrollbars=no,resizable=yes,location=no, status=no';
+ window.open(url, 'newQQwindow', tmpStr);
+ });
+ }
+
+ // 初始化社会化分享
+ function initShare($el) {
+ var $weibo = $el.find('.weibo');
+ var $qq = $el.find('.qq');
+
+ initWeiboShare($weibo);
+ initQQShare($qq);
+
+ $el.find('#share_box_shutbtn').click(function () {
+ $('#share_box').animate({
+ bottom: '-210px'
+ }, 300, function () {
+ $('#blackbg').remove();
+ });
+ });
+
+ $('#sharebtn_in_con').on('click', function () {
+ var tmpStr = '
';
+ $('body').append(tmpStr);
+ $('#share_box').animate({
+ bottom: '0px'
+ }, 300);
+ });
+ }
+ // 定时延迟
+ function removeConTips1() {
+ $('#con_tips1').remove();
+ }
+ // 添加操作
+ function addFavTmp1() {
+ jsonpPost(URL + '/ajax/do_user_caidans.php?id=' + id, function (data) {
+ if (data !== '' && data !== 0) {
+ jsonpPost(URL + '/ajax/add_nfav.php?obj_id=' + id, function (data) {
+ $('#addfavbtn_in_con img').attr('src", "images/rd_b_sc_h@3x.png');
+ $('#addfavbtn_in_con strong').css('color", "#ff4c35').html('已收藏');
+ });
+ $('#addfav_box #addfav_box_c2').html(data);
+ $('#addfav_box').animate({
+ bottom: '0px'
+ }, 300);
+ }
+ else if (data === 0) {
+ location.href = '/login.php?redirect=' + encodeURIComponent(location.href);
+ }
+ });
+ }
+
+ // 添加到收藏
+ function initFavorite() {
+ $(document).on('click', '.addfav_box_c2_item', function () {
+ if (!($(this).hasClass('current'))) {
+ $(this).addClass('current');
+ }
+ else {
+ $(this).removeClass('current');
+ }
+ });
+
+ $('#addfav_box_b2').on('click', function () {
+ var cdids = '';
+ $.each($('.addfav_box_c2_item.current'), function () {
+ cdids = cdids + $(this).attr('cdid') + ',';
+ });
+ if (cdids === '' && !$('#addfavbtn_in_con').hasClass('cbicon1_cur')) {
+ $('#addfav_box_shutbtn').click();
+ }
+ else {
+ jsonpPost(URL + '/ajax/do_user_caidans.php?id=' + id + '&act=modi&rids=' + cdids, function (data) {
+ if (data === 1) {
+ $('body').append('
收藏成功
');
+ setTimeout(removeConTips1, 3000);
+ $('#addfav_box_shutbtn').trigger('click');
+ }
+ else {
+ $('body').append('
取消收藏成功
');
+ setTimeout(removeConTips1, 3000);
+ $('#addfav_box_shutbtn').trigger('click');
+ $('#addfavbtn_in_con').removeClass('cbicon1_cur').addClass('cbicon1').html('收藏');
+ }
+ });
+ }
+ });
+
+ $('#addfavbtn_in_con').on('click', function () {
+ var tmpStr = '
';
+ $('body').append(tmpStr);
+ $('#addfav_box').animate({
+ bottom: '0px'
+ }, 300, function () {});
+ });
+ $('#addfavbtn_in_con').on('click', function () {
+ $('#addfav_box').show();
+ $('#addnewcd_box').show();
+ if (!$(this).hasClass('cbicon1_cur')) {
+ addFavTmp1();
+ }
+ else {
+ jsonpPost(URL + '/ajax/add_nfav.php?act=cancel&obj_id=' + id, function (data) {
+ $('body').append('
取消收藏成功
');
+ setTimeout(removeConTips1, 3000);
+ $('#addfav_box_shutbtn').click();
+
+ $('#addfavbtn_in_con img').attr('src", "http://site.meishij.net/p2/20160307/20160307180334_918.png');
+ $('#addfavbtn_in_con strong').css('color", "#a5a5a5').html('收藏');
+ });
+ }
+ });
+ $('#addfav_box_shutbtn').on('click', function () {
+ $('#addfav_box').animate({
+ bottom: '-320px'
+ }, 300, function () {
+ $('#blackbg').remove();
+ });
+ });
+ }
+
+
+ // 初始化菜单
+ function initMenu() {
+ $('#addfav_box_b1').on('click', function () {
+ $('#addnewcd_box').animate({
+ top: '74px'
+ }, 300);
+ });
+
+ $('#addnewcd_box_a1').on('click', function () {
+ $('#addnewcd_box').animate({
+ top: '-400px'
+ }, 300, function () {});
+ });
+ $('#addnewcd_box_a2').on('click', function () {
+ var t = $('#addnewcd_box_input').html();
+ jsonpPost(URL + '/ajax/create_caidan.php?t=' + encodeURIComponent(t), function (data) {
+ if (data !== '') {
+ $('#addfav_box_c2').prepend(data);
+ $('#addnewcd_box_a1').trigger('click');
+ $('#addnewcd_box_input').val('');
+ }
+ else {
+ alert('请重试~');
+ }
+ });
+ });
+ $('#addnewcd_box_input').focus(function () {
+ var ThisT = $(this);
+ var txt = ThisT.html();
+ if (txt === '请输入菜单名称') {
+ ThisT.html('');
+ ThisT.css('color', '#333');
+ $('#addnewcd_box_input').focus();
+ }
+ });
+ }
+
+ var customElem = require('customElement').create();
+
+ customElem.prototype.build = function () {
+ var $el = $(this.element);
+
+ // DOM元素列表
+ var $share = $el.find('.share_box');
+
+ // 初始化各模块
+ initShare($share);
+ initFavorite();
+ initMenu();
+ };
+
+ return customElem;
+});
diff --git a/mip-meishij/package.json b/mip-meishij/package.json
new file mode 100644
index 00000000..100a9cf8
--- /dev/null
+++ b/mip-meishij/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-meishij",
+ "version": "1.0.1",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-mipengine-preview-v2/README.md b/mip-mipengine-preview-v2/README.md
index 754821a4..637f2e60 100644
--- a/mip-mipengine-preview-v2/README.md
+++ b/mip-mipengine-preview-v2/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-mipengine-preview-v2/mip-mipengine-preview-v2.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-mipengine-preview-v2/mip-mipengine-preview-v2.js
## 示例
diff --git a/mip-mmbang-flexible/README.md b/mip-mmbang-flexible/README.md
index 987ea4a0..e82e8ed8 100644
--- a/mip-mmbang-flexible/README.md
+++ b/mip-mmbang-flexible/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|不使用布局
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-mmbang-flexible/mip-mmbang-flexible.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-mmbang-flexible/mip-mmbang-flexible.js
## 示例
diff --git a/mip-mmtpk-script/README.md b/mip-mmtpk-script/README.md
index e239eaec..79ee6144 100644
--- a/mip-mmtpk-script/README.md
+++ b/mip-mmtpk-script/README.md
@@ -6,7 +6,7 @@ mip-mmtpk-script 网站自用业务组件
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-mmtpk-script/mip-mmtpk-script.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-mmtpk-script/mip-mmtpk-script.js
## 示例
diff --git a/mip-mz-addonapp/README.md b/mip-mz-addonapp/README.md
new file mode 100644
index 00000000..3635e00c
--- /dev/null
+++ b/mip-mz-addonapp/README.md
@@ -0,0 +1,38 @@
+# mip-mz-addonapp
+
+mip-mz-addonapp 推荐app下载!
+
+标题|内容
+----|----
+类型|通用
+所需脚本|https://c.mipcdn.com/static/v1/mip-mz-addonapp/mip-mz-addonapp.js
+
+## 示例
+
+### 推荐app下载!
+```html
+
+```
+
+## 属性
+
+### ajaxurl
+
+说明:jsonp配置文件地址
+必选项:是
+类型:url字符串
+单位:无
+默认值:无
+
+### type
+
+说明:app类型
+必选项:是
+类型:字符串
+取值范围:comm,full
+单位:无
+默认值:无
+
+
+
+
diff --git a/mip-mz-addonapp/mip-mz-addonapp.js b/mip-mz-addonapp/mip-mz-addonapp.js
new file mode 100644
index 00000000..225844fa
--- /dev/null
+++ b/mip-mz-addonapp/mip-mz-addonapp.js
@@ -0,0 +1,45 @@
+/**
+ * @file mip-mz-addonapp 推荐app下载
+ * @author pifire
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var util = require('util');
+ var fetchJsonp = require('fetch-jsonp');
+ var platform = util.platform;
+ var customElement = require('customElement').create();
+ function initAD(type, obj) {
+ var sys = (platform.isIos()) ? 'ios' : 'android';
+ var apps = obj[type][sys];
+ var html = '
';
+ return html;
+ }
+
+ /**
+ * createdCallback
+ */
+ customElement.prototype.createdCallback = function () {
+ var element = this.element;
+ var $element = $(element);
+ var type = $element.attr('type');
+ var ajaxurl = $element.attr('ajaxurl');
+ function callback(json) {
+ var innerHTML = initAD(type, json);
+ var obj = ($('.vother').length > 0) ? $('.vother') : $('.info');
+ obj.after(innerHTML);
+ }
+ fetchJsonp(ajaxurl, {
+ timeout: 3000,
+ jsonpCallback: 'ck'
+ }).then(function (response) {
+ return response.json();
+ }).then(callback);
+ };
+ return customElement;
+});
diff --git a/mip-mz-addonapp/package.json b/mip-mz-addonapp/package.json
new file mode 100644
index 00000000..b494e119
--- /dev/null
+++ b/mip-mz-addonapp/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-mz-addonapp",
+ "version": "1.0.0",
+ "description": "推荐app下载",
+ "contributors": [
+ {
+ "name": "pifire",
+ "email": "104460712@qq.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-mz-appdownload/README.md b/mip-mz-appdownload/README.md
new file mode 100644
index 00000000..d9e2a491
--- /dev/null
+++ b/mip-mz-appdownload/README.md
@@ -0,0 +1,56 @@
+# mip-mz-appdownload
+
+mip-mz-appdownload app下载组件,在高速下载和直接下载之间切换!
+
+标题|内容
+----|----
+类型|通用
+所需脚本|https://c.mipcdn.com/static/v1/mip-mz-appdownload/mip-mz-appdownload.js
+
+## 示例
+
+### app下载组件,在高速下载和直接下载之间切换!
+```html
+
+```
+
+## 属性
+### ajaxurl
+
+说明:jsonp配置文件地址
+必选项:是
+类型:url字符串
+单位:无
+默认值:无
+
+### ad
+
+说明:是否启用高速下载
+必选项:是
+类型:数字
+取值范围:0,1
+单位:无
+默认值:无
+
+### aid
+
+说明:对应的应用id
+必选项:是
+类型:字符串
+取值范围:数字或者其他
+单位:无
+默认值:无
+
+### addr
+
+说明:应用的下载地址
+必选项:是
+类型:字符串
+取值范围:空或者字符串
+单位:无
+默认值:无
+
+
+
+
+
diff --git a/mip-mz-appdownload/mip-mz-appdownload.js b/mip-mz-appdownload/mip-mz-appdownload.js
new file mode 100644
index 00000000..b277a831
--- /dev/null
+++ b/mip-mz-appdownload/mip-mz-appdownload.js
@@ -0,0 +1,131 @@
+/**
+ * @file mip-mz-appdownload 木子的app下载切换效果
+ * @author pifire
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var util = require('util');
+ var fetchJsonp = require('fetch-jsonp');
+ var platform = util.platform;
+ var customElement = require('customElement').create();
+ var localhref = 'http://m.muzisoft.com/mz/';
+ function initAD(ad, aid, addr, obj) {
+ if (platform.isIos()) {
+ var softid = aid.substring(0, aid.length - 5);
+ if (obj.ios[1].url !== '' && inarray(obj.vnpids, softid)) {
+ return '
' + obj.ios[0].btnvalue + '';
+ }
+ return '
'
+ + obj.ios[0].btnvalue + '';
+ }
+ if (ad > 0) {
+ if (obj.android.length > 0) {
+ $('.down').css('height', '90px');
+ var presenti = 0;
+ for (var i = 0; i < obj.android.length; ++i) {
+ if (!localStorage.getItem(obj.android[i].id)) {
+ presenti = i;
+ break;
+ }
+ }
+ return '
'
+ + '
' + obj.android[presenti].name + ''
+ + '
' + obj.android[presenti].btnvalue + ''
+ + '
' + obj.android[presenti].info + '
';
+ }
+ }
+ return '
本地下载';
+ }
+ // 判断url下载还是id下载
+ function checkurl(aid, addr) {
+ return (addr.length === 0) ? localhref + aid : addr;
+ }
+ // 点击按钮切换下载
+ function changeDown(aid, addr, androidAD) {
+ var chk = document.getElementById('ckb');
+ var yybtext = $('.yybtext');
+ var gsdbtn = document.getElementById('gsdbtn');
+ var presenti = 0;
+ for (var i = 0; i < androidAD.length; ++i) {
+ if (!localStorage.getItem(androidAD[i].id)) {
+ presenti = i;
+ break;
+ }
+ }
+ if (chk.checked) {
+ yybtext.css('color', 'black');
+ document.querySelector('#down span').innerText = androidAD[presenti].name;
+ gsdbtn.setAttribute('href', androidAD[presenti].url);
+ gsdbtn.innerText = androidAD[presenti].btnvalue;
+ yybtext.innerText = androidAD[presenti].info;
+ }
+ else {
+ yybtext.css('color', 'red');
+ document.querySelector('#down span').innerText = androidAD[presenti].name;
+ gsdbtn.setAttribute('href', checkurl(aid, addr));
+ gsdbtn.innerText = androidAD[presenti].ubtnvalue;
+ yybtext.innerText = androidAD[presenti].uinfo;
+ }
+ }
+ // 数组中是否包含
+ function inarray(arr, obj) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === obj) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * createdCallback
+ */
+ customElement.prototype.createdCallback = function () {
+ var element = this.element;
+ var $element = $(element);
+ var ad = $element.attr('ad');
+ var aid = $element.attr('aid');
+ var addr = $element.attr('addr');
+ var ajaxurl = $element.attr('ajaxurl');
+ function callback(json) {
+ var innerHTML = initAD(ad, aid, addr, json);
+ $('.down ul li').append(innerHTML);
+ $('.ckb').click(function () {
+ changeDown(aid, addr, json.android);
+ });
+ // 安卓点击了高速按钮,写cookie
+ $('.gsdbtn').click(function () {
+ if (json.android.length > 0) {
+ for (var i = 0; i < json.android.length; ++i) {
+ var j = (i === json.android.length - 1) ? 0 : i + 1;
+ if ($('.gsdbtn').attr('presentid') === json.android[i].id) {
+ localStorage.removeItem(json.android[j].id);
+ }
+ else {
+ localStorage.setItem(json.android[j].id, 1);
+ }
+ }
+ }
+ });
+ // 苹果点击了下载
+ $('.confirmios').click(function () {
+ if (json.ios[0].url !== '') {
+ if (confirm(json.ios[0].name)) {
+ window.location.href = json.ios[0].url;
+ return false;
+ }
+ }
+ });
+ }
+ fetchJsonp(ajaxurl, {
+ timeout: 3000,
+ jsonpCallback: 'ck'
+ }).then(function (response) {
+ return response.json();
+ }).then(callback);
+ };
+ return customElement;
+});
diff --git a/mip-mz-appdownload/package.json b/mip-mz-appdownload/package.json
new file mode 100644
index 00000000..69dde8bd
--- /dev/null
+++ b/mip-mz-appdownload/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-mz-appdownload",
+ "version": "1.0.0",
+ "description": "app下载组件,在高速下载和直接下载之间切换!",
+ "contributors": [
+ {
+ "name": "pifire",
+ "email": "104460712@qq.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-nav-show-hide/README.md b/mip-nav-show-hide/README.md
index e25b6834..6329b157 100644
--- a/mip-nav-show-hide/README.md
+++ b/mip-nav-show-hide/README.md
@@ -6,7 +6,7 @@ mip-nav-show-hide 点击按钮,显示掩藏的nav
----|----
类型|通用
支持布局| responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-nav-show-hide/mip-nav-show-hide.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-nav-show-hide/mip-nav-show-hide.js
## 示例
diff --git a/mip-netease-article-recommend/README.md b/mip-netease-article-recommend/README.md
index 8b7c39b2..71d63e5b 100644
--- a/mip-netease-article-recommend/README.md
+++ b/mip-netease-article-recommend/README.md
@@ -6,7 +6,7 @@ mip-netease-article-recommend 为网易移动站文章页实现了底部相关
----|----
类型|业务
支持布局|不使用布局
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-netease-article-recommend/mip-netease-article-recommend.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-netease-article-recommend/mip-netease-article-recommend.js
## 示例
diff --git a/mip-netease-tracker/README.md b/mip-netease-tracker/README.md
index 5d2561d9..29ee4b31 100644
--- a/mip-netease-tracker/README.md
+++ b/mip-netease-tracker/README.md
@@ -6,7 +6,7 @@ mip-netease-tracker 为网易移动站文章页实现了统计功能
----|----
类型|业务
支持布局|不使用布局
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-netease-tracker/mip-netease-tracker.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-netease-tracker/mip-netease-tracker.js
## 示例
diff --git a/mip-news-iframe-interface/README.md b/mip-news-iframe-interface/README.md
new file mode 100644
index 00000000..acd23214
--- /dev/null
+++ b/mip-news-iframe-interface/README.md
@@ -0,0 +1,20 @@
+# mip-news-iframe-interface
+
+mip-news-iframe-interface 资讯下iframe双向通信接口
+
+标题|内容
+----|----
+类型|通用
+支持布局|N/A
+所需脚本|https://c.mipcdn.com/static/v1/mip-news-iframe-interface/mip-news-iframe-interface.js
+
+## 示例
+
+MIP提供支持畅言的扩展组件,代码示例:
+
+```
+
+```
+
+## 属性
+
diff --git a/mip-news-iframe-interface/mip-news-iframe-interface.js b/mip-news-iframe-interface/mip-news-iframe-interface.js
new file mode 100644
index 00000000..eea6332d
--- /dev/null
+++ b/mip-news-iframe-interface/mip-news-iframe-interface.js
@@ -0,0 +1,115 @@
+/**
+ * @file iframe双向通信接口
+ * @author chenrui09
+ * @time 2016.12.13
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var IframeInterface = require('customElement').create();
+ var messageHandler;
+
+ IframeInterface.prototype.createdCallback = initElement;
+
+ function initElement() {
+ // 绑定消息接受事件
+ window.addEventListener('message', function (event) {
+ if (messageHandler) {
+ messageHandler.handle(event.data);
+ }
+ });
+
+ }
+
+ messageHandler = {
+ handle: function (data) {
+ var evtName = data.event;
+ var events = this.events;
+ var handler = events[evtName];
+ var result;
+
+ if (handler) {
+ result = handler(data);
+ }
+
+ // 发送双向绑定消息
+ if (result && data.type === 'two-way') {
+ data.sentinel = 'PM_RESPONSE';
+ data.data = result;
+
+ window.parent.postMessage(data, '*');
+ }
+ },
+
+ events: {
+ // 获取分享信息
+ getShareInfo: function () {
+ var shareObj = {};
+ var $img = $(document.body).find('mip-img');
+
+ shareObj.title = document.title;
+ shareObj.articleUrl = location.href.replace(/\#.*$/g, '');
+
+ if ($img.length) {
+ shareObj.iconUrl = $img.eq(0).attr('src');
+ }
+
+ return shareObj;
+ },
+
+ // 滚动到指定位置
+ scrollTo: function (data) {
+ var scrollY = data.scrollY;
+
+ document.body.scrollTop = scrollY;
+
+ return true;
+ },
+
+ // 获取滚动位置
+ getScrollY: function () {
+ var result = {};
+
+ result.scrollY = document.body.scrollTop;
+
+ return result;
+ },
+
+ // 获取字号大小
+ getDetailFont: function () {
+ var result = {};
+ var key = 'min_detail_font_size';
+
+ result.font = localStorage.getItem(key);
+
+ return result;
+ },
+
+ // 设置字体大小
+ setDetailFont: function (data) {
+ var key = 'min_detail_font_size';
+
+ $('.' + key).remove();
+
+ $(document.head).append([
+ ''
+ ].join(''));
+
+ localStorage.setItem(key, data.font);
+ }
+ }
+
+ };
+
+
+ // 设置预存的字体配置
+ var fontConfig = messageHandler.events.getDetailFont();
+
+ if (fontConfig && fontConfig.font) {
+ messageHandler.events.setDetailFont(fontConfig);
+ }
+
+ return IframeInterface;
+});
diff --git a/mip-news-iframe-interface/package.json b/mip-news-iframe-interface/package.json
new file mode 100644
index 00000000..084906f0
--- /dev/null
+++ b/mip-news-iframe-interface/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-news-iframe-interface",
+ "version": "1.0.0",
+ "author": {
+ "name": "chenrui09",
+ "email": "chenrui09@baidu.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-news-recommend/README.md b/mip-news-recommend/README.md
new file mode 100644
index 00000000..495cfbea
--- /dev/null
+++ b/mip-news-recommend/README.md
@@ -0,0 +1,21 @@
+# mip-news-recommend
+
+mip-news-recommend 新闻和热点推荐组件
+
+标题|内容
+----|----
+类型|通用
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-news-recommend/mip-news-recommend.js
+
+## 示例
+
+MIP提供支持畅言的扩展组件,代码示例:
+
+```
+
+```
+
+## 属性
+
+
diff --git a/mip-news-recommend/mip-news-recommend.js b/mip-news-recommend/mip-news-recommend.js
new file mode 100644
index 00000000..1a633dfc
--- /dev/null
+++ b/mip-news-recommend/mip-news-recommend.js
@@ -0,0 +1,212 @@
+/**
+ * @file 推荐组件
+ * @author chenrui09
+ * @time 2016.11.21
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var util = require('util');
+ var viewer = require('viewer');
+ var RecommendElement = require('customElement').create();
+ var recommend;
+
+ RecommendElement.prototype.createdCallback = renderElement;
+
+ function renderElement() {
+ var $ele = $(this.element);
+ var url = $ele.attr('src') || '//headline.baidu.com/doc/recommended';
+
+ recommend.init({
+ $container: $ele,
+ url: url
+ });
+ }
+
+ function getUrlQuery(name) {
+ var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
+ var r = window.location.search.substr(1).match(reg);
+ if (r != null) {
+ return r[2];
+ }
+ return null;
+ }
+
+ function getOriginUrl() {
+ var url = location.href;
+ url = util.parseCacheUrl(url);
+ url = url.replace(/\#.*$/g, '');
+ return url;
+ }
+
+ function getCdnUrl(url) {
+ return '//mib.bdstatic.com/doc/detail/' + encodeURIComponent(url) + '/0/#from=sub';
+ }
+
+ function formatTime(time) {
+ var tempSeconds = 1000 * time;
+ if ((new Date() - tempSeconds) < 60000) {
+ return '刚刚';
+ }
+ var tempMinutes = Math.floor((new Date() - tempSeconds) / 60000);
+ if (tempMinutes < 60) {
+ return tempMinutes + '分钟前';
+ }
+ var tempHours = Math.floor(tempMinutes / 60);
+ if (tempHours < 24) {
+ return tempHours + '小时前';
+ }
+ var tempDate = new Date(tempSeconds);
+ var month = tempDate.getMonth() + 1;
+ month = month < 10 ? ('0' + month) : month;
+ var day = tempDate.getDate() < 10 ? ('0' + tempDate.getDate()) : tempDate.getDate();
+ return month + '-' + day;
+ }
+
+ recommend = {
+ url: null,
+ ajaxData: null,
+ isIframe: window.parent !== window,
+
+ init: function (props) {
+ this.$container = props.$container;
+
+ this.url = props.url;
+ this.ajaxData = {
+ 'url_key': getOriginUrl(),
+ 'from': getUrlQuery('from') || 'search',
+ 'app_from': getUrlQuery('app_from') || 'midway',
+ 'qid': window.B ? window.B.qid : 0,
+ 'is_mip': true
+ };
+
+ this.request();
+ this.delegate();
+ },
+
+ request: function () {
+ var self = this;
+
+ $.ajax({
+ url: this.url,
+ dataType: 'jsonp',
+ jsonp: 'cb',
+ data: this.ajaxData,
+ success: function (res) {
+ if (res.status !== 0) {
+ self.error(res.data);
+ } else {
+ self.display(res.data);
+ }
+ }
+ });
+ },
+
+ delegate: function () {
+ var isIframe = this.isIframe;
+
+ if (isIframe) {
+ this.$container.on('click', '.mip-news-recommend-href', function (e) {
+ e.preventDefault();
+
+ var $ele = $(this);
+ viewer.sendMessage('loadiframe', {
+ 'url': $ele.data('url'),
+ 'title': $ele.find('.mip-news-recommend-provider').text(),
+ 'click': $ele.data('click')
+ });
+ });
+
+ this.$container.on('click', '.mip-news-recommend-hot-href', function (e) {
+ e.preventDefault();
+
+ var $ele = $(this);
+ viewer.sendMessage('urljump', {
+ 'url': $ele.data('url'),
+ 'click': $ele.data('click')
+ });
+ });
+ }
+ },
+
+ handleData: function (item, i, action) {
+ var data = {
+ action: action,
+ order: i,
+ href: item.url
+ };
+
+ return JSON.stringify(data);
+ },
+
+ display: function (data) {
+ var self = this;
+ var isIframe = this.isIframe;
+ var htmlNews = '';
+ var htmlHots = '';
+
+ $.each(data.recommend, function (i, item) {
+ var dataClick = self.handleData(item, i, 'recommend');
+ var href = isIframe ? 'javascript:void(0);' : getCdnUrl(item.url);
+
+ htmlNews += [
+ '
'
+ ].join('');
+ });
+
+ $.each(data.hot_card, function (i, item) {
+ var dataClick = self.handleData(item, i, 'hotpoint');
+ var href = isIframe ? 'javascript:void(0);' : item.url;
+
+ if (i % 2 === 0) {
+ htmlHots += '
';
+ }
+
+ htmlHots += [
+ '
'
+ ].join('');
+
+ if (i % 2 === 1) {
+ htmlHots += '
';
+ }
+ });
+
+ var html = [
+ '
',
+ '
相关推荐
',
+ '
',
+ htmlNews,
+ '
',
+ '
',
+ '
',
+ '
新闻热点
',
+ '
',
+ htmlHots,
+ '
',
+ '
'
+ ].join('');
+
+ this.$container.append(html);
+ },
+
+ error: function () {
+
+ }
+ };
+
+ return RecommendElement;
+});
diff --git a/mip-news-recommend/mip-news-recommend.less b/mip-news-recommend/mip-news-recommend.less
new file mode 100644
index 00000000..2e079483
--- /dev/null
+++ b/mip-news-recommend/mip-news-recommend.less
@@ -0,0 +1,85 @@
+.mip-news-recommend-list,
+.mip-news-recommend-hotpoints {
+ padding: 12px 17px;
+ background-color: #fff;
+}
+
+.mip-news-recommend-list h5,
+.mip-news-recommend-hotpoints h5 {
+ color: #999;
+ font-size: 16px;
+ font-weight: normal;
+}
+
+.mip-news-recommend-hotpoints {
+ margin-top: 8px;
+}
+
+.mip-news-recommend-item {
+ padding: 14px 0;
+ overflow: hidden;
+ border-top: #eee 1px solid;
+}
+
+.mip-news-recommend-item:first-child {
+ border-top-width: 0;
+}
+
+.mip-news-recommend-title {
+ font-size: 16px;
+ margin-bottom: 9px;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.mip-news-recommend-info {
+ font-size: 13px;
+ color: #999;
+}
+
+.mip-news-recommend-info span {
+ margin-right: 4px;
+}
+
+.mip-news-recommend-row {
+ margin-top: 8px;
+ width: auto;
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-direction: normal;
+ -webkit-box-pack: justify;
+ -webkit-box-align: stretch;
+ -webkit-box-lines: single;
+ display: -webkit-flex;
+ -webkit-flex-direction: row;
+ -webkit-justify-content: space-between;
+ -webkit-align-items: strecth;
+ -webkit-align-content: flex-start;
+ -webkit-flex-wrap: nowrap;
+}
+
+.mip-news-recommend-row:first-child {
+ margin-top: 12px;
+}
+
+.mip-news-recommend-hot-item {
+ margin-right: 5px;
+ width: 50%;
+ -webkit-box-flex: 6;
+ -webkit-flex: 6 6 auto;
+}
+
+.mip-news-recommend-hot-item a {
+ display: block;
+ color: #333;
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 39px;
+ text-align: center;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ background-color: #f5f5f5;
+}
+
diff --git a/mip-news-recommend/package.json b/mip-news-recommend/package.json
new file mode 100644
index 00000000..7eb4def7
--- /dev/null
+++ b/mip-news-recommend/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-news-recommend",
+ "version": "1.0.4",
+ "author": {
+ "name": "chenrui09",
+ "email": "chenrui09@baidu.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-next-news/README.md b/mip-next-news/README.md
index db4169fb..b2c14378 100644
--- a/mip-next-news/README.md
+++ b/mip-next-news/README.md
@@ -6,7 +6,7 @@ mip-next-news 用来给文章内容页增加相关新闻第一篇的效果
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-next-news/mip-next-news.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-next-news/mip-next-news.js
## 示例
### 相关新闻替换下一页
diff --git a/mip-next-page/README.md b/mip-next-page/README.md
index e04517e6..39728477 100644
--- a/mip-next-page/README.md
+++ b/mip-next-page/README.md
@@ -6,7 +6,7 @@ mip-next-page 用来支持下载详情页统计功能
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-next-page/mip-next-page.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-next-page/mip-next-page.js
## 示例
### 替换下一页
diff --git a/mip-nine-business/README.md b/mip-nine-business/README.md
new file mode 100644
index 00000000..10f713cf
--- /dev/null
+++ b/mip-nine-business/README.md
@@ -0,0 +1,17 @@
+# mip-nine-business
+
+mip-nine-business用来支持页面公共业务逻辑
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-nine-business/mip-nine-business.js
+
+## 示例
+
+```
+
+
+
+```
\ No newline at end of file
diff --git a/mip-nine-business/mip-nine-business.js b/mip-nine-business/mip-nine-business.js
new file mode 100644
index 00000000..f316e0b4
--- /dev/null
+++ b/mip-nine-business/mip-nine-business.js
@@ -0,0 +1,118 @@
+/**
+ * @file 页面逻辑公共脚本
+ * @description 实时新增优化
+ * @author Zhou
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var global = {
+ hideList: function (obj, option, nub) {
+ $(obj).each(function () {
+ if ($(this).find(option).length < nub) {
+ $(this).remove();
+ }
+
+ });
+ },
+ tongJi: function (element) {
+ var webDatetime = $('.down-href').attr('dateTime');
+ var webUsername = $('.down-href').attr('username');
+ if (typeof webDatetime !== 'undefined') {
+ var hmToken = '';
+ if (webUsername !== '') {
+ switch (webUsername) {
+ case 'pw':
+ hmToken = '4eb538db483dbcb8ac7805e88c0e68d2';
+ break;
+ case 'fanyx':
+ hmToken = '7a38a3cd8273420bff96defee91ffd52';
+ break;
+ case 'wangc':
+ hmToken = '6a4fb943703f72f5a107bae74297c0d8';
+ break;
+ case 'liut':
+ hmToken = 'f8caf941f69dfc45994a289bea52c391';
+ break;
+ case 'lishan':
+ hmToken = 'e0e619af38f59771499e9af8af354abe';
+ break;
+ case 'fut':
+ hmToken = 'a1d2b8898d627d479edc7598830c43e7';
+ break;
+ case 'yangmy':
+ hmToken = 'ad6ed66a4ae1b5a773c928cf6839fdb7';
+ break;
+ case 'zl':
+ hmToken = 'bea0af369e866a005d7d8976e05428f8';
+ break;
+ case 'sqh':
+ hmToken = 'ca254020a12728b4783fa86aac377088';
+ break;
+ case 'zhuk':
+ hmToken = 'c9d39ecb6ecb1cb4ce0d4ff2afd6d269';
+ break;
+ case 'xiaolx':
+ hmToken = '146ce1d702b563cb3d31d04286fd90ec';
+ break;
+ case 'sunwj':
+ hmToken = '50640e063c2dc9b759d39586346584c9';
+ break;
+ case 'lgk':
+ hmToken = '8825275a4f99989fbb285093c23c0eca';
+ break;
+ case 'liutao':
+ hmToken = 'b1f0584d2a768bcdc085c03f144691ec';
+ break;
+ case 'liuy':
+ hmToken = '7aed2a841f972814d68a7f679deeca59';
+ break;
+ case 'wangq':
+ hmToken = '259b0294b80d0206726d4d5396d87e63';
+ break;
+ case 'xuz':
+ hmToken = 'cab296cd83c9cc2c3168d3137f190b46';
+ break;
+ case 'shenyf':
+ hmToken = '18425a2424b310dd0aa0ef2d9b5611f4';
+ break;
+ case 'jians':
+ hmToken = '6db5e268937dd16915d9c8988fe3fca0';
+ break;
+ case 'lim':
+ hmToken = '0a2270b92aa610d66c5a50a16fc9d46f';
+ break;
+ case 'yangz':
+ hmToken = 'ef4d4313433fc1da73934066af26ad73';
+ break;
+ case 'yangn':
+ hmToken = 'a8a90472fbe0c01b6f45bc2eeac1ba71';
+ break;
+ case 'lins':
+ hmToken = 'e4be2c0dcebcf381fc1cf0064f7e2ec4';
+ break;
+ case 'wangj':
+ hmToken = '552cde6fac57857ea31b57079fc2bf4a';
+ break;
+ case 'gx':
+ hmToken = '9c262ade13c6a440110121bd8aee7de7';
+ break;
+ }
+ if (hmToken !== '') {
+ $('body').append('
');
+ }
+ }
+ }
+
+ },
+ init: function (element) {
+ this.hideList('.hidelist', 'li', 1); // 优化隐藏
+ this.tongJi(element); // 编辑统计
+ }
+ };
+ customElem.prototype.createdCallback = function () {
+ var element = this.element;
+ global.init(element);
+ };
+ return customElem;
+});
diff --git a/mip-nine-business/package.json b/mip-nine-business/package.json
new file mode 100644
index 00000000..9ec08924
--- /dev/null
+++ b/mip-nine-business/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-nine-business",
+ "version": "1.0.0",
+ "author": {
+ "name": "Zhou",
+ "email": "zhouhappy01@gmail.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-nine-download/README.md b/mip-nine-download/README.md
new file mode 100644
index 00000000..84a76431
--- /dev/null
+++ b/mip-nine-download/README.md
@@ -0,0 +1,17 @@
+# mip-nine-download
+
+mip-nine-download 用来支持页面业务逻辑
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-nine-download/mip-nine-download.js
+
+## 示例
+
+```
+
+
+
+```
\ No newline at end of file
diff --git a/mip-nine-download/mip-nine-download.js b/mip-nine-download/mip-nine-download.js
new file mode 100644
index 00000000..64b217fe
--- /dev/null
+++ b/mip-nine-download/mip-nine-download.js
@@ -0,0 +1,197 @@
+/**
+ * @file 页面逻辑脚本
+ * @author Zhou
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var viewport = require('viewport');
+ var util = require('util');
+ var platform = util.platform;
+ var customElem = require('customElement').create();
+ var down = {
+ webInfoId: $('.down-href').attr('downid'),
+ webInfoCid: $('.down-href').attr('cid'),
+ webInfoRid: $('.down-href').attr('rid'),
+ platAndroidId: $('.plat_Android').attr('platid'),
+ platAndroidAddress: $('.plat_Android').attr('Address'),
+ platAndroidResSystem: $('.plat_Android').attr('ResSystem'),
+ platAndroidResName: $('.plat_Android').attr('ResName'),
+ platAndroidResVer: $('.plat_Android').attr('ResVer'),
+ platAndroidCid: $('.plat_Android').attr('cid'),
+ platAndroidRid: $('.plat_Android').attr('rid'),
+ platIPhoneId: $('.plat_iPhone').attr('platid'),
+ platIPhoneAddress: $('.plat_iPhone').attr('Address'),
+ platIPhoneResSystem: $('.plat_iPhone').attr('ResSystem'),
+ platIPhoneResName: $('.plat_iPhone').attr('ResName'),
+ platIPhoneResVer: $('.plat_iPhone').attr('ResVer'),
+ platIPhoneCid: $('.plat_iPhone').attr('cid'),
+ platIPhoneRid: $('.plat_iPhone').attr('rid'),
+ assid: parseInt($('.info .Associate').html(), 10),
+ scrollNav: function () {
+ var h = $('.tabNav').offset().top; // 浮动距顶
+ viewport.on('scroll', function () {
+ viewport.getScrollTop() >= h ? $('.tabNav').addClass('fix') : $('.tabNav').removeClass('fix');
+ });
+ },
+ downHref: function () {
+ if (platform.isAndroid() && typeof (this.platAndroidAddress) !== 'undefined') {
+ if (this.platAndroidAddress.indexOf('http:') >= 0 || this.platAndroidAddress.indexOf('ftp:') >= 0
+ || this.platAndroidAddress.indexOf('https:') >= 0) {
+ $('.game-detail-main .bxz').attr('href', this.platAndroidAddress);
+ }
+ else {
+ $('.game-detail-main .bxz').attr('href', 'http://m.9ht.com/down.asp?id=' + this.platAndroidId);
+ }
+ $('.game-detail-main h1').html(this.platAndroidResName);
+ }
+ else if (platform.isIos() && typeof (this.platIPhoneAddress) !== 'undefined') {
+ if (this.platIPhoneAddress.indexOf('http:') >= 0 || this.platIPhoneAddress.indexOf('ftp:') >= 0
+ || this.platIPhoneAddress.indexOf('https:') >= 0) {
+ $('.game-detail-main .bxz').attr('href', this.platIPhoneAddress);
+ }
+ else {
+ $('.game-detail-main .bxz').attr('href', 'http://m.9ht.com/down.asp?id=' + this.platIPhoneId);
+ }
+ $('.game-detail-main h1').html(this.platIPhoneResName);
+ }
+
+ },
+ titTab: function () {
+ $('.tabNav span').on('click', function () {
+ if ($('.fix').length > 0) {
+ $('.tabNav.fix').removeClass('fix');
+ viewport.setScrollTop($('.tabNav').offset().top);
+ }
+
+ $(this).addClass('active').siblings().removeClass('active');
+ if ($(this).text() === '详情') {
+ $('.game-focus,.tagsk,.intro-main,.tcsyy,.xgxz,.xg-news,.tags-wrap,.comment').show();
+ }
+ else if ($(this).text() === '评论') {
+ $('.comment').show();
+ $('.game-focus,.tagsk,.intro-main,.tcsyy,.xgxz,.xg-news,.tags-wrap').hide();
+ }
+ else if ($(this).text() === '相关') {
+ $('.game-focus,.tagsk,.intro-main').hide();
+ $('.tcsyy,.xgxz,.xg-news,.tags-wrap,.comment').show();
+ }
+
+ });
+ },
+ touchSlide: function () {
+ var obj = $('.guess');
+ if (obj.length === 0) {
+ return;
+ }
+
+ var oul = obj.find('.tags-main-ul');
+ var oli = oul.find('.tags-main-box');
+ var onavLi = $('.tags-tab ul li');
+ var ospan = '';
+ var windowW = parseInt($(window).width() - 16, 10);
+ var touch = {s: [], d: ''};
+ var iNow = 0;
+ oli.width(windowW);
+ $('.tags-main').width(windowW);
+ for (var i = 1; i < oli.length; i++) {
+ ospan += '';
+ }
+ $('.pagenum').html(ospan);
+ oul.width(oli.length * oli.width());
+ // 点击li
+ onavLi.eq(0).addClass('active');
+ onavLi.click(function () {
+ var i = $(this).index();
+ iNow = i;
+ onavLi.eq(i).addClass('active').siblings().removeClass('active');
+ oul.css({
+ '-webkit-transform': 'translate3d(' + -windowW * i + 'px, 0px, 0px)'
+ });
+ });
+ // 滑动事件
+ oul[0].addEventListener('touchstart', function (e) {
+ touch.s[0] = e.targetTouches[0].pageX;
+ touch.s[1] = e.targetTouches[0].pageY;
+ touch.s[2] = (new Date()).getTime();
+ }, false);
+ // 滑动过程
+ oul[0].addEventListener('touchmove', function (e) {
+ if (Math.abs(e.targetTouches[0].pageX - touch.s[0]) >= Math.abs(
+ e.targetTouches[0].pageY - touch.s[1]) && touch.d === '') {
+ touch.d = 1; // 左右
+ }
+ else if (touch.d === '') {
+ touch.d = 0; // 上下或者偏上下
+ }
+
+ if (touch.d === 1) { // 左右滚动
+ e.preventDefault();
+ oul.css({
+ '-webkit-transform': 'translate3d(' + -(windowW * iNow - e.targetTouches[0].pageX
+ + touch.s[0]) + 'px, 0px, 0px)'
+ });
+ }
+
+ }, false);
+
+ oul[0].addEventListener('touchend', function (e) {
+ if (touch.d === 1) {
+ if ((new Date()).getTime() - touch.s[2] > 700) {
+ if (e.changedTouches[0].pageX - touch.s[0] > windowW / 3) {
+ auto('right');
+ }
+ else if (touch.s[0] - e.changedTouches[0].pageX > windowW / 3) {
+ auto('left');
+ }
+ else {
+ auto('reset');
+ }
+ }
+ else {
+ if (e.changedTouches[0].pageX > touch.s[0]) {
+ auto('right');
+ }
+ else if (touch.s[0] > e.changedTouches[0].pageX) {
+ auto('left');
+ }
+ }
+ }
+
+ touch.d = '';
+ }, false);
+ // 运动函数
+ function auto(dir) {
+ if (dir === 'left') {
+ iNow >= oli.length - 1 ? iNow === oli.length - 1 : iNow++;
+ oul.animate({
+ '-webkit-transform': 'translate3d(' + -windowW * iNow + 'px, 0px, 0px)'
+ });
+ }
+ else if (dir === 'reset') {
+ oul.animate({
+ '-webkit-transform': 'translate3d(' + -windowW * iNow + 'px, 0px, 0px)'
+ });
+ }
+ else if (dir === 'right') {
+ iNow <= 0 ? iNow = 0 : iNow--;
+ oul.animate({
+ '-webkit-transform': 'translate3d(' + -windowW * iNow + 'px, 0px, 0px)'
+ });
+ }
+
+ $('.pagenum span').eq(iNow).addClass('active').siblings().removeClass('active');
+ $('.tags-tab ul li').eq(iNow).addClass('active').siblings().removeClass('active');
+ }
+ },
+ init: function () {
+ this.scrollNav();
+ this.titTab(); // 菜单切换
+ this.downHref(); // 动态下载地址
+ this.touchSlide(); // 滑动切换
+ }
+ };
+ customElem.prototype.createdCallback = function () {
+ down.init();
+ };
+ return customElem;
+});
diff --git a/mip-nine-download/package.json b/mip-nine-download/package.json
new file mode 100644
index 00000000..8c4eb2e1
--- /dev/null
+++ b/mip-nine-download/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-nine-download",
+ "version": "1.0.0",
+ "author": {
+ "name": "Zhou",
+ "email": "zhouhappy01@gmail.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-njt-ad/README.md b/mip-njt-ad/README.md
index a220de81..a9ac8576 100644
--- a/mip-njt-ad/README.md
+++ b/mip-njt-ad/README.md
@@ -5,7 +5,7 @@ mip-njt-ad 组件说明
标题|内容
----|----
类型|不通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-njt-ad/mip-njt-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-njt-ad/mip-njt-ad.js
## 示例
diff --git a/mip-nph/README.md b/mip-nph/README.md
index dd8352b8..7fc28138 100644
--- a/mip-nph/README.md
+++ b/mip-nph/README.md
@@ -5,7 +5,7 @@ mip-nph 组件说明
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-nph/mip-nph.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-nph/mip-nph.js
## 示例
diff --git a/mip-nszxyu-bookcase/README.md b/mip-nszxyu-bookcase/README.md
index 3e5f850c..eaabfed5 100644
--- a/mip-nszxyu-bookcase/README.md
+++ b/mip-nszxyu-bookcase/README.md
@@ -6,7 +6,7 @@ mip-nszxyu-bookcase 主要用于jieqi小说网书架系统,包括加入书架
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-nszxyu-bookcase/mip-nszxyu-bookcase.js
https://c.mipcdn.com/static/v1/mip-mustache/mip-mustache.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-nszxyu-bookcase/mip-nszxyu-bookcase.js
https://c.mipcdn.com/static/v1/mip-mustache/mip-mustache.js
## 示例
diff --git a/mip-nszxyu-login/README.md b/mip-nszxyu-login/README.md
index af30e94c..783513f3 100644
--- a/mip-nszxyu-login/README.md
+++ b/mip-nszxyu-login/README.md
@@ -6,7 +6,7 @@ mip-nszxyu-login 判断jieqi小说网的登录状态,根据不同的状态显
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-nszxyu-login/mip-nszxyu-login.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-nszxyu-login/mip-nszxyu-login.js
## 示例
diff --git a/mip-nszxyu-read/README.md b/mip-nszxyu-read/README.md
index c36ee904..b80068ba 100644
--- a/mip-nszxyu-read/README.md
+++ b/mip-nszxyu-read/README.md
@@ -6,7 +6,7 @@ mip-nszxyu-read 主要用于jieqi小说网阅读功能,包括调整字体大
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-nszxyu-read/mip-nszxyu-read.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-nszxyu-read/mip-nszxyu-read.js
## 示例
diff --git a/mip-objectpropertycheck-min/README.md b/mip-objectpropertycheck-min/README.md
index 056f066a..a0639c28 100644
--- a/mip-objectpropertycheck-min/README.md
+++ b/mip-objectpropertycheck-min/README.md
@@ -6,7 +6,7 @@ mip-objectpropertycheck-min 元素属性检测,可根据检测到的属性,
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-objectpropertycheck-min/mip-objectpropertycheck-min.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-objectpropertycheck-min/mip-objectpropertycheck-min.js
## 示例
diff --git a/mip-onga/README.md b/mip-onga/README.md
index 5c2e8e9b..c44995c9 100644
--- a/mip-onga/README.md
+++ b/mip-onga/README.md
@@ -6,7 +6,7 @@ mip-onga 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-onga/mip-onga.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-onga/mip-onga.js
## 示例
diff --git a/mip-openweb-search/README.md b/mip-openweb-search/README.md
index 90bdc0d7..b48fb1da 100644
--- a/mip-openweb-search/README.md
+++ b/mip-openweb-search/README.md
@@ -6,7 +6,7 @@ mip-openweb-search 配置ghost rss搜索, 事件绑定
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-openweb-search/mip-openweb-search.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-openweb-search/mip-openweb-search.js
## 示例
diff --git a/mip-pc-ad/README.md b/mip-pc-ad/README.md
index 1fb65d70..62176065 100644
--- a/mip-pc-ad/README.md
+++ b/mip-pc-ad/README.md
@@ -6,7 +6,7 @@ mip-pc-ad 广告位组件
----|----
类型|广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pc-ad/mip-pc-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pc-ad/mip-pc-ad.js
## 示例
diff --git a/mip-pc6-dif/README.md b/mip-pc6-dif/README.md
index 1db47f23..d2f744d0 100644
--- a/mip-pc6-dif/README.md
+++ b/mip-pc6-dif/README.md
@@ -5,7 +5,7 @@ mip-pc6-dif 软件、手游区分
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pc6-dif/mip-pc6-dif.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pc6-dif/mip-pc6-dif.js
## 示例
diff --git a/mip-pc6-down/README.md b/mip-pc6-down/README.md
index 083e5c4d..d6d8bab5 100644
--- a/mip-pc6-down/README.md
+++ b/mip-pc6-down/README.md
@@ -5,7 +5,7 @@ mip-pc6-down 文章页面逻辑
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pc6-down/mip-pc6-down.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pc6-down/mip-pc6-down.js
## 示例
diff --git a/mip-pc6-news/README.md b/mip-pc6-news/README.md
index 92c78074..1335b104 100644
--- a/mip-pc6-news/README.md
+++ b/mip-pc6-news/README.md
@@ -5,7 +5,7 @@ mip-pc6-news 文章页面逻辑
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pc6-news/mip-pc6-news.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pc6-news/mip-pc6-news.js
## 示例
diff --git a/mip-pc6-touchslide/README.md b/mip-pc6-touchslide/README.md
index 0e6b2faf..8e9fa900 100644
--- a/mip-pc6-touchslide/README.md
+++ b/mip-pc6-touchslide/README.md
@@ -5,7 +5,7 @@ mip-pc6-touchslide 多屏轮播
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pc6-touchslide/mip-pc6-touchslide.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pc6-touchslide/mip-pc6-touchslide.js
## 示例
diff --git a/mip-pcad/README.md b/mip-pcad/README.md
index 78481894..72cf2529 100644
--- a/mip-pcad/README.md
+++ b/mip-pcad/README.md
@@ -6,7 +6,7 @@ mip-pcad 广告位组件
----|----
类型|广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcad/mip-pcad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcad/mip-pcad.js
## 示例
diff --git a/mip-pcbabyad/README.md b/mip-pcbabyad/README.md
index 8cf7da71..2f26bef3 100644
--- a/mip-pcbabyad/README.md
+++ b/mip-pcbabyad/README.md
@@ -6,7 +6,7 @@ mip-pcbabyad 广告位组件
----|----
类型|广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcbabyad/mip-pcbabyad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcbabyad/mip-pcbabyad.js
## 示例
diff --git a/mip-pcgroup-commentshow/README.md b/mip-pcgroup-commentshow/README.md
index c6e30bc8..aa377ddf 100644
--- a/mip-pcgroup-commentshow/README.md
+++ b/mip-pcgroup-commentshow/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-commentshow 太平洋网络网站群的评论组件,有展示、
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-commentshow/mip-pcgroup-commentshow.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-commentshow/mip-pcgroup-commentshow.js
## 示例
diff --git a/mip-pcgroup-counter/README.md b/mip-pcgroup-counter/README.md
index c0f9ddde..c3584945 100644
--- a/mip-pcgroup-counter/README.md
+++ b/mip-pcgroup-counter/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-counter 太平洋网络通用计数器组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-counter/mip-pcgroup-counter.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-counter/mip-pcgroup-counter.js
## 示例
diff --git a/mip-pcgroup-pconline-artproduct/README.md b/mip-pcgroup-pconline-artproduct/README.md
index 9883e0ac..dfb11a52 100644
--- a/mip-pcgroup-pconline-artproduct/README.md
+++ b/mip-pcgroup-pconline-artproduct/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-pconline-artproduct 太平洋网络电脑网wap文章页产品库模
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-pconline-artproduct/mip-pcgroup-pconline-artproduct.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-pconline-artproduct/mip-pcgroup-pconline-artproduct.js
## 示例
diff --git a/mip-pcgroup-rem/README.md b/mip-pcgroup-rem/README.md
index 7b926d4a..8926a49b 100644
--- a/mip-pcgroup-rem/README.md
+++ b/mip-pcgroup-rem/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-rem 太平洋网络wap站rem布局组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-rem/mip-pcgroup-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-rem/mip-pcgroup-rem.js
## 示例
diff --git a/mip-pcgroup-user/README.md b/mip-pcgroup-user/README.md
index 67df8633..1274b725 100644
--- a/mip-pcgroup-user/README.md
+++ b/mip-pcgroup-user/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-user 太平洋网络判断登录组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-user/mip-pcgroup-user.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-user/mip-pcgroup-user.js
## 示例
diff --git a/mip-pcgroup-zancai/README.md b/mip-pcgroup-zancai/README.md
index 85e47fc5..866ba43e 100644
--- a/mip-pcgroup-zancai/README.md
+++ b/mip-pcgroup-zancai/README.md
@@ -6,7 +6,7 @@ mip-pcgroup-zancai 太平洋网络文章踩赞组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcgroup-zancai/mip-pcgroup-zancai.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcgroup-zancai/mip-pcgroup-zancai.js
## 示例
diff --git a/mip-pcsoft-form/README.md b/mip-pcsoft-form/README.md
index 4ca8af3c..a87106c0 100644
--- a/mip-pcsoft-form/README.md
+++ b/mip-pcsoft-form/README.md
@@ -7,7 +7,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcsoft-form/mip-pcsoft-form.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcsoft-form/mip-pcsoft-form.js
## 示例
diff --git a/mip-pcsoft-paging/README.md b/mip-pcsoft-paging/README.md
index 6e59ea7d..bf209276 100644
--- a/mip-pcsoft-paging/README.md
+++ b/mip-pcsoft-paging/README.md
@@ -7,7 +7,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pcsoft-paging/mip-pcsoft-paging.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pcsoft-paging/mip-pcsoft-paging.js
## 示例
diff --git a/mip-pcsoft-slide/README.md b/mip-pcsoft-slide/README.md
index f20f2ffa..78d0c334 100644
--- a/mip-pcsoft-slide/README.md
+++ b/mip-pcsoft-slide/README.md
@@ -5,7 +5,7 @@
标题 | 内容
----|------
支持布局 |responsive,fixed-height,fill,container,fixed
-所需脚本 | https://c.mipcdn.com/extensions/platform/v1/mip-pcsoft-slide/mip-pcsoft-slide.js
+所需脚本 | https://c.mipcdn.com/static/v1/mip-pcsoft-slide/mip-pcsoft-slide.js
## 示例
```
diff --git a/mip-pingao-icon/README.md b/mip-pingao-icon/README.md
index dcff80e4..681b65b1 100644
--- a/mip-pingao-icon/README.md
+++ b/mip-pingao-icon/README.md
@@ -6,7 +6,7 @@ mip-pingao-icon 品告推广连接,品告展示在网页的图片和连接进
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pingao-icon/mip-pingao-icon.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pingao-icon/mip-pingao-icon.js
## 示例
diff --git a/mip-pop-video/README.md b/mip-pop-video/README.md
index 3c225fea..beb44c8a 100644
--- a/mip-pop-video/README.md
+++ b/mip-pop-video/README.md
@@ -5,7 +5,7 @@ mip-pop-video 播放完成后带弹出层的视频播放器
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-pop-video/mip-pop-video.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-pop-video/mip-pop-video.js
## 示例
### 带弹层的播放
diff --git a/mip-popup-layer/README.md b/mip-popup-layer/README.md
index bca115bd..62ec922e 100644
--- a/mip-popup-layer/README.md
+++ b/mip-popup-layer/README.md
@@ -6,7 +6,7 @@ mip-popup-layer 在指定的位置弹出层(需要在css中自己定义位置)
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-close-dom/mip-popup-layer.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-close-dom/mip-popup-layer.js
## 示例
diff --git a/mip-ppkao-bottomnav/README.md b/mip-ppkao-bottomnav/README.md
index 2ae8f08b..07eed6dd 100644
--- a/mip-ppkao-bottomnav/README.md
+++ b/mip-ppkao-bottomnav/README.md
@@ -6,7 +6,7 @@ mip-ppkao-bottomnav ppkao试题bottomnav
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-bottomnav/mip-ppkao-bottomnav.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-bottomnav/mip-ppkao-bottomnav.js
## 示例
diff --git a/mip-ppkao-dropdown/README.md b/mip-ppkao-dropdown/README.md
index 01f60e7f..e83abf6d 100644
--- a/mip-ppkao-dropdown/README.md
+++ b/mip-ppkao-dropdown/README.md
@@ -6,7 +6,7 @@ mip-ppkao-dropdown PPkao试题dropdown
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-dropdown/mip-ppkao-dropdown.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-dropdown/mip-ppkao-dropdown.js
## 示例
diff --git a/mip-ppkao-goback/README.md b/mip-ppkao-goback/README.md
index b3869227..9b3bbdfc 100644
--- a/mip-ppkao-goback/README.md
+++ b/mip-ppkao-goback/README.md
@@ -6,7 +6,7 @@ mip-ppkao-goback PPkao返回
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-goback/mip-ppkao-goback.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-goback/mip-ppkao-goback.js
## 示例
diff --git a/mip-ppkao-jiyancode/README.md b/mip-ppkao-jiyancode/README.md
index 97578d21..fb080f1e 100644
--- a/mip-ppkao-jiyancode/README.md
+++ b/mip-ppkao-jiyancode/README.md
@@ -6,7 +6,7 @@ mip-ppkao-jiyancode PPkao试题jiyancode
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-jiyancode/mip-ppkao-jiyancode.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-jiyancode/mip-ppkao-jiyancode.js
## 示例
diff --git a/mip-ppkao-kaoshixinxi/README.md b/mip-ppkao-kaoshixinxi/README.md
index 5815f746..ff0864d7 100644
--- a/mip-ppkao-kaoshixinxi/README.md
+++ b/mip-ppkao-kaoshixinxi/README.md
@@ -6,7 +6,7 @@ mip-ppkao-kaoshixinxi ppkao考试信息
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-kaoshixinxi/mip-ppkao-kaoshixinxi.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-kaoshixinxi/mip-ppkao-kaoshixinxi.js
## 示例
diff --git a/mip-ppkao-login/README.md b/mip-ppkao-login/README.md
index 10a1d95f..a89a72b1 100644
--- a/mip-ppkao-login/README.md
+++ b/mip-ppkao-login/README.md
@@ -6,7 +6,7 @@ mip-ppkao-login PPkao登录
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-login/mip-ppkao-login.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-login/mip-ppkao-login.js
## 示例
diff --git a/mip-ppkao-rotate/README.md b/mip-ppkao-rotate/README.md
index bd1fd1cc..8de2a9cf 100644
--- a/mip-ppkao-rotate/README.md
+++ b/mip-ppkao-rotate/README.md
@@ -6,7 +6,7 @@ mip-ppkao-rotate 点击旋转dom,已经旋转则返回
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-rotate/mip-ppkao-rotate.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-rotate/mip-ppkao-rotate.js
## 示例
diff --git a/mip-ppkao-shitisearch/README.md b/mip-ppkao-shitisearch/README.md
index 3a20656c..b5415dea 100644
--- a/mip-ppkao-shitisearch/README.md
+++ b/mip-ppkao-shitisearch/README.md
@@ -6,7 +6,7 @@ mip-ppkao-shitisearch ppkao试题shitisearch
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-shitisearch/mip-ppkao-shitisearch.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-shitisearch/mip-ppkao-shitisearch.js
## 示例
diff --git a/mip-ppkao-showsubject/README.md b/mip-ppkao-showsubject/README.md
index 20ef6dba..a7b3821f 100644
--- a/mip-ppkao-showsubject/README.md
+++ b/mip-ppkao-showsubject/README.md
@@ -6,7 +6,7 @@ mip-ppkao-showsubject 上滑和下滑
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-showsubject/mip-ppkao-showsubject.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-showsubject/mip-ppkao-showsubject.js
## 示例
diff --git a/mip-ppkao-subjectjingyanviewmore/README.md b/mip-ppkao-subjectjingyanviewmore/README.md
index ca19c2ad..99e58a6f 100644
--- a/mip-ppkao-subjectjingyanviewmore/README.md
+++ b/mip-ppkao-subjectjingyanviewmore/README.md
@@ -6,7 +6,7 @@ mip-ppkao-subjectjingyanviewmore ppkao科目经验更多
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-subjectjingyanviewmore/mip-ppkao-subjectjingyanviewmore.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-subjectjingyanviewmore/mip-ppkao-subjectjingyanviewmore.js
## 示例
diff --git a/mip-ppkao-zhinanslide/README.md b/mip-ppkao-zhinanslide/README.md
index 92e79b64..5b9eb01b 100644
--- a/mip-ppkao-zhinanslide/README.md
+++ b/mip-ppkao-zhinanslide/README.md
@@ -6,7 +6,7 @@ mip-ppkao-zhinanslide ppkao指南科目显示
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ppkao-zhinanslide/mip-ppkao-zhinanslide.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ppkao-zhinanslide/mip-ppkao-zhinanslide.js
## 示例
diff --git a/mip-praise/README.md b/mip-praise/README.md
index 7917067b..b7b429ee 100644
--- a/mip-praise/README.md
+++ b/mip-praise/README.md
@@ -6,7 +6,7 @@ mip-praise 带有参数的点赞功能
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-praise/mip-praise.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-praise/mip-praise.js
## 示例
diff --git a/mip-push-bd-caidashi/README.md b/mip-push-bd-caidashi/README.md
index 36520549..097c30c1 100644
--- a/mip-push-bd-caidashi/README.md
+++ b/mip-push-bd-caidashi/README.md
@@ -5,7 +5,7 @@ mip-push-bd-caidashi 百度主动推送
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-push-bd-caidashi/mip-push-bd-caidashi.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-push-bd-caidashi/mip-push-bd-caidashi.js
## 示例
diff --git a/mip-push/README.md b/mip-push/README.md
index 0f6653a8..4adecce4 100644
--- a/mip-push/README.md
+++ b/mip-push/README.md
@@ -6,7 +6,7 @@ mip-push 用来支持站长添加百度自动推送
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-push/mip-push.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-push/mip-push.js
## 示例
diff --git a/mip-qbb-base64/README.md b/mip-qbb-base64/README.md
index 826c69ea..659ffa4c 100644
--- a/mip-qbb-base64/README.md
+++ b/mip-qbb-base64/README.md
@@ -6,7 +6,7 @@ mip-qbb-base64 支持base64编码,base64解码,utf16转utf8,utf8转utf16
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-base64/mip-qbb-base64.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-base64/mip-qbb-base64.js
## 示例
```html
diff --git a/mip-qbb-comment/README.md b/mip-qbb-comment/README.md
index 800e9c77..9c31f3ff 100644
--- a/mip-qbb-comment/README.md
+++ b/mip-qbb-comment/README.md
@@ -6,7 +6,7 @@ mip-qbb-comment 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-comment/mip-qbb-comment.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-comment/mip-qbb-comment.js
## 示例
```html
diff --git a/mip-qbb-detail/README.md b/mip-qbb-detail/README.md
index bb98e0e8..9e67dc3d 100644
--- a/mip-qbb-detail/README.md
+++ b/mip-qbb-detail/README.md
@@ -6,7 +6,7 @@ mip-qbb-detail 页面滚动时,实现选项卡的固定,选项卡固定的
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-detail/mip-qbb-detail.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-detail/mip-qbb-detail.js
## 示例
diff --git a/mip-qbb-isweixin/README.md b/mip-qbb-isweixin/README.md
index db513bdb..f7eb8f7e 100644
--- a/mip-qbb-isweixin/README.md
+++ b/mip-qbb-isweixin/README.md
@@ -6,7 +6,7 @@ mip-qbb-isweixin 在Android,ipad,iPhone的平台设置不同的下载地址,
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-isweixin/mip-qbb-isweixin.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-isweixin/mip-qbb-isweixin.js
## 示例
```html
diff --git a/mip-qbb-qinxin/README.md b/mip-qbb-qinxin/README.md
index 8d7dbf00..387d3a45 100644
--- a/mip-qbb-qinxin/README.md
+++ b/mip-qbb-qinxin/README.md
@@ -6,7 +6,7 @@ mip-qbb-qinxin 在亲信(公司自己的app)中打开时,隐藏header
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-qinxin/mip-qbb-qinxin.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-qinxin/mip-qbb-qinxin.js
## 示例
```html
diff --git a/mip-qbb-resetpic/README.md b/mip-qbb-resetpic/README.md
index abc8c393..5017c562 100644
--- a/mip-qbb-resetpic/README.md
+++ b/mip-qbb-resetpic/README.md
@@ -6,7 +6,7 @@ mip-qbb-resetpic 根据原始图片的宽高,计算图片是横向图片,还
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qbb-resetpic/mip-qbb-resetpic.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qbb-resetpic/mip-qbb-resetpic.js
## 示例
```html
diff --git a/mip-qcode/README.md b/mip-qcode/README.md
index fa8725c6..2cbf1be1 100644
--- a/mip-qcode/README.md
+++ b/mip-qcode/README.md
@@ -6,7 +6,7 @@ mip-qcode 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-QCcode/mip-qcode.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-QCcode/mip-qcode.js
## 示例
diff --git a/mip-qinbao-collection/README.md b/mip-qinbao-collection/README.md
index 5d089136..4e6e133e 100644
--- a/mip-qinbao-collection/README.md
+++ b/mip-qinbao-collection/README.md
@@ -6,7 +6,7 @@ mip-qinbao-collection 加入我的收藏夹
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qinbao-collection/mip-qinbao-collection.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qinbao-collection/mip-qinbao-collection.js
## 示例
diff --git a/mip-qinbao-customaudio/README.md b/mip-qinbao-customaudio/README.md
index c2e6e6cd..cb30d03d 100644
--- a/mip-qinbao-customaudio/README.md
+++ b/mip-qinbao-customaudio/README.md
@@ -6,7 +6,7 @@ mip-qinbao-customaudio 文字语音播报
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qinbao-customaudio/mip-qinbao-customaudio.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qinbao-customaudio/mip-qinbao-customaudio.js
## 示例
diff --git a/mip-qqtn-addmore/README.md b/mip-qqtn-addmore/README.md
index 4beff13a..e76cf029 100644
--- a/mip-qqtn-addmore/README.md
+++ b/mip-qqtn-addmore/README.md
@@ -6,7 +6,7 @@ mip-qqtn-addmore 点击加载更多,为单文件,没有参数
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-addmore/mip-qqtn-addmore.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-addmore/mip-qqtn-addmore.js
## 示例
diff --git a/mip-qqtn-addtab/README.md b/mip-qqtn-addtab/README.md
index a40833f4..e1f27df0 100644
--- a/mip-qqtn-addtab/README.md
+++ b/mip-qqtn-addtab/README.md
@@ -6,7 +6,7 @@ mip-qqtn-addtab 点击下载按钮弹出推荐内容,IP过滤功能,并没有引
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-addtab/mip-qqtn-addtab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-addtab/mip-qqtn-addtab.js
## 示例
diff --git a/mip-qqtn-cnzz/README.md b/mip-qqtn-cnzz/README.md
index 98472274..89f0b55c 100644
--- a/mip-qqtn-cnzz/README.md
+++ b/mip-qqtn-cnzz/README.md
@@ -6,7 +6,7 @@ mip-qqtn-cnzz CNZZ统计
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-cnzz/mip-qqtn-cnzz.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-cnzz/mip-qqtn-cnzz.js
## 示例
### CNZZ统计
diff --git a/mip-qqtn-cnzzcount/README.md b/mip-qqtn-cnzzcount/README.md
index 15241787..42e1fe53 100644
--- a/mip-qqtn-cnzzcount/README.md
+++ b/mip-qqtn-cnzzcount/README.md
@@ -6,7 +6,7 @@ mip-qqtn-cnzzcount CNZZ统计
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-cnzzcount/mip-qqtn-cnzzcount.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-cnzzcount/mip-qqtn-cnzzcount.js
## 示例
### CNZZ统计
diff --git a/mip-qqtn-commentdrop/README.md b/mip-qqtn-commentdrop/README.md
index 253d61cd..4fa76283 100644
--- a/mip-qqtn-commentdrop/README.md
+++ b/mip-qqtn-commentdrop/README.md
@@ -6,7 +6,7 @@ mip-qqtn-commentdrop 判断假如没有li则将外层隐藏
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-commentdrop/mip-qqtn-commentdrop.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-commentdrop/mip-qqtn-commentdrop.js
## 示例
diff --git a/mip-qqtn-count/README.md b/mip-qqtn-count/README.md
index 8f875d6c..709bf047 100644
--- a/mip-qqtn-count/README.md
+++ b/mip-qqtn-count/README.md
@@ -6,7 +6,7 @@ mip-qqtn-count 站内个人统计功能
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-count/mip-qqtn-count.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-count/mip-qqtn-count.js
## 示例
diff --git a/mip-qqtn-downts/README.md b/mip-qqtn-downts/README.md
index 93e29443..f26f3e9f 100644
--- a/mip-qqtn-downts/README.md
+++ b/mip-qqtn-downts/README.md
@@ -6,7 +6,7 @@ mip-qqtn-downts
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-downts/mip-qqtn-downts.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-downts/mip-qqtn-downts.js
## 示例
### 基本用法
diff --git a/mip-qqtn-imgenlarge/README.md b/mip-qqtn-imgenlarge/README.md
index 8f1f0c73..18c7fe56 100644
--- a/mip-qqtn-imgenlarge/README.md
+++ b/mip-qqtn-imgenlarge/README.md
@@ -6,7 +6,7 @@ mip-qqtn-imgenlarge 图片放大,放大后切换
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-imgenlarge/mip-qqtn-imgenlarge.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-imgenlarge/mip-qqtn-imgenlarge.js
## 示例
### 基本用法
diff --git a/mip-qqtn-num0/README.md b/mip-qqtn-num0/README.md
index 24040a93..43cef573 100644
--- a/mip-qqtn-num0/README.md
+++ b/mip-qqtn-num0/README.md
@@ -6,7 +6,7 @@ mip-qqtn-num0 判断假如没有li则将外层隐藏
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-num0/mip-qqtn-num0.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-num0/mip-qqtn-num0.js
## 示例
diff --git a/mip-qqtn-shield/README.md b/mip-qqtn-shield/README.md
index 92d6193e..2e0ac655 100644
--- a/mip-qqtn-shield/README.md
+++ b/mip-qqtn-shield/README.md
@@ -6,7 +6,7 @@ mip-qqtn-shield
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-shield/mip-qqtn-shield.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-shield/mip-qqtn-shield.js
## 示例
### 基本用法
diff --git a/mip-qqtn-txalt/README.md b/mip-qqtn-txalt/README.md
index d21a1e39..ec6ed79c 100644
--- a/mip-qqtn-txalt/README.md
+++ b/mip-qqtn-txalt/README.md
@@ -6,7 +6,7 @@ mip-qqtn-txalt 内容页图片展示放大组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-txalt/mip-qqtn-txalt.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-txalt/mip-qqtn-txalt.js
## 示例
### 基本用法
diff --git a/mip-qqtn-txtab/README.md b/mip-qqtn-txtab/README.md
index d07793b9..7b4acc59 100644
--- a/mip-qqtn-txtab/README.md
+++ b/mip-qqtn-txtab/README.md
@@ -6,7 +6,7 @@ mip-qqtn-txtab ,针对图片的多样式绑定切换,
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtn-txtab/mip-qqtn-txtab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtn-txtab/mip-qqtn-txtab.js
## 示例
diff --git a/mip-qqtngx-shrinknav/README.md b/mip-qqtngx-shrinknav/README.md
index d6cda3f9..80b414c9 100644
--- a/mip-qqtngx-shrinknav/README.md
+++ b/mip-qqtngx-shrinknav/README.md
@@ -6,7 +6,7 @@ mip-qqtngx-shrinknav 腾牛个性页面的内容植入和伸缩导航
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtngx-shrinknav/mip-qqtngx-shrinknav.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtngx-shrinknav/mip-qqtngx-shrinknav.js
## 示例
diff --git a/mip-qqtnhealth-dynamicmenu/README.md b/mip-qqtnhealth-dynamicmenu/README.md
index 60ce1594..d784f522 100644
--- a/mip-qqtnhealth-dynamicmenu/README.md
+++ b/mip-qqtnhealth-dynamicmenu/README.md
@@ -6,7 +6,7 @@ mip-qqtnhealth-dynamicmenu 页面特效
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-qqtnhealth-dynamicmenu/mip-qqtnhealth-dynamicmenu.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-qqtnhealth-dynamicmenu/mip-qqtnhealth-dynamicmenu.js
## 示例
diff --git a/mip-rich-video/README.md b/mip-rich-video/README.md
index 9360937f..77000ded 100644
--- a/mip-rich-video/README.md
+++ b/mip-rich-video/README.md
@@ -5,7 +5,7 @@ mip-rich-video 实现了一个带前贴片后贴片广告和播放完推荐的
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-rich-video/mip-rich-video.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-rich-video/mip-rich-video.js
## 示例
diff --git a/mip-row-slide/README.md b/mip-row-slide/README.md
index 38c640f4..4cc6cc34 100644
--- a/mip-row-slide/README.md
+++ b/mip-row-slide/README.md
@@ -5,7 +5,7 @@ mip-row-slide 横向滑动区块组件,区块可展示每个区块的对应内
----|----
类型|业务,定制
支持布局|responsive,flex,container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-row-slide/mip-row-slide.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-row-slide/mip-row-slide.js
## 示例
diff --git a/mip-scroll-anchor/README.md b/mip-scroll-anchor/README.md
index ec0cdb52..885c5ec9 100644
--- a/mip-scroll-anchor/README.md
+++ b/mip-scroll-anchor/README.md
@@ -6,7 +6,7 @@ MIP 滚动定位锚点组件,常用于定位导航联动滚动事件来高亮
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-scroll-anchor/mip-scroll-anchor.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-scroll-anchor/mip-scroll-anchor.js
## 示例
diff --git a/mip-search-prompt/README.md b/mip-search-prompt/README.md
index b7f3196b..3b0aca73 100644
--- a/mip-search-prompt/README.md
+++ b/mip-search-prompt/README.md
@@ -6,7 +6,7 @@ mip-search-prompt 搜索组件,有百度提示的搜索提示框组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-search-prompt/mip-search-prompt.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-search-prompt/mip-search-prompt.js
## 示例
diff --git a/mip-sg-ad/README.md b/mip-sg-ad/README.md
index 364f119d..f7ddde23 100644
--- a/mip-sg-ad/README.md
+++ b/mip-sg-ad/README.md
@@ -6,7 +6,7 @@ mip-sg-ad 星游传媒广告组件
----|----
类型|业务,广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sg-ad/mip-sg-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sg-ad/mip-sg-ad.js
## 示例
diff --git a/mip-shouji-down/README.md b/mip-shouji-down/README.md
index e8550bea..9da65c99 100644
--- a/mip-shouji-down/README.md
+++ b/mip-shouji-down/README.md
@@ -6,7 +6,7 @@ mip-shouji-down 下载站定制组件,主要用于导航切换与下载按钮
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-shouji-down/mip-shouji-down.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-shouji-down/mip-shouji-down.js
## 示例
diff --git a/mip-shouji56-code/README.md b/mip-shouji56-code/README.md
index 63ad8d19..7b5434c0 100644
--- a/mip-shouji56-code/README.md
+++ b/mip-shouji56-code/README.md
@@ -6,7 +6,7 @@ mip-shouji56-code 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-shouji56-code/mip-shouji56-code.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-shouji56-code/mip-shouji56-code.js
## 示例
diff --git a/mip-shushi100-timeout/README.md b/mip-shushi100-timeout/README.md
index 8391141f..3d0277fc 100644
--- a/mip-shushi100-timeout/README.md
+++ b/mip-shushi100-timeout/README.md
@@ -6,7 +6,7 @@ mip-shushi100-timeout 定时触发指定dom指定绑定事件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-shushi100-timeout/mip-shushi100-timeout.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-shushi100-timeout/mip-shushi100-timeout.js
## 示例
diff --git a/mip-shushi100-toggle/README.md b/mip-shushi100-toggle/README.md
index c20c001e..05e0a2f2 100644
--- a/mip-shushi100-toggle/README.md
+++ b/mip-shushi100-toggle/README.md
@@ -6,7 +6,7 @@ mip-shushi100-toggle 关闭/展开组件,关闭/展开指定dom
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-shushi100-toggle/mip-shushi100-toggle.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-shushi100-toggle/mip-shushi100-toggle.js
## 示例
diff --git a/mip-shuyang-count/README.md b/mip-shuyang-count/README.md
index 411c5aa1..c3a81733 100644
--- a/mip-shuyang-count/README.md
+++ b/mip-shuyang-count/README.md
@@ -6,7 +6,7 @@ mip-shuyang-count 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-shuyang-count/mip-shuyang-count.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-shuyang-count/mip-shuyang-count.js
## 示例
diff --git a/mip-sina-rem/README.md b/mip-sina-rem/README.md
index f1d75fb5..21352fd4 100644
--- a/mip-sina-rem/README.md
+++ b/mip-sina-rem/README.md
@@ -5,7 +5,7 @@ mip-sina-rem 组件说明
标题|内容
----|----
类型|通用,定制
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sina-rem/mip-sina-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sina-rem/mip-sina-rem.js
## 示例
diff --git a/mip-sina-sax/README.md b/mip-sina-sax/README.md
index f964323b..97217097 100644
--- a/mip-sina-sax/README.md
+++ b/mip-sina-sax/README.md
@@ -5,7 +5,7 @@ mip-sina-sax 新浪广告组件
标题|内容
----|----
类型|定制
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sina-sax/mip-sina-sax.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sina-sax/mip-sina-sax.js
## 示例
diff --git a/mip-sina-sudalog/README.md b/mip-sina-sudalog/README.md
index 1bc79635..244961fa 100644
--- a/mip-sina-sudalog/README.md
+++ b/mip-sina-sudalog/README.md
@@ -5,7 +5,7 @@ mip-sina-sudalog 新浪日志统计组件
标题|内容
----|----
类型|定制
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sina-sudalog/mip-sina-sudalog.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sina-sudalog/mip-sina-sudalog.js
## 示例
diff --git a/mip-sina-sudamap/README.md b/mip-sina-sudamap/README.md
index 2b7a45c4..ae6ba078 100644
--- a/mip-sina-sudamap/README.md
+++ b/mip-sina-sudamap/README.md
@@ -5,7 +5,7 @@ mip-sina-sudamap 新浪页面信息&点击统计组件
标题|内容
----|----
类型|定制
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sina-sudamap/mip-sina-sudamap.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sina-sudamap/mip-sina-sudamap.js
## 示例
diff --git a/mip-sj-search/README.md b/mip-sj-search/README.md
index 56830313..9be9b9d2 100644
--- a/mip-sj-search/README.md
+++ b/mip-sj-search/README.md
@@ -5,7 +5,7 @@ mip-sj-search 组件说明
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sj-search/mip-sj-search.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sj-search/mip-sj-search.js
## 示例
diff --git a/mip-smart-recommend/README.md b/mip-smart-recommend/README.md
index d8957680..2781ad67 100644
--- a/mip-smart-recommend/README.md
+++ b/mip-smart-recommend/README.md
@@ -6,7 +6,7 @@ mip-smart-recommend 用来显示百度智荐推荐内容
----|----
类型| 通用
布局|N/S
-引用脚本|https://c.mipcdn.com/extensions/platform/v1/mip-smart-recommend/mip-smart-recommend.js
+引用脚本|https://c.mipcdn.com/static/v1/mip-smart-recommend/mip-smart-recommend.js
## 示例
diff --git a/mip-sn-shop-floor/README.md b/mip-sn-shop-floor/README.md
index 9f84343b..67f4ca20 100644
--- a/mip-sn-shop-floor/README.md
+++ b/mip-sn-shop-floor/README.md
@@ -6,7 +6,7 @@ mip-sn-shop-floor 实现了自定义实现了sn获取接口数据后,渲染列
----|----
类型|定制
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sn-shop-floor/mip-sn-shop-floor.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sn-shop-floor/mip-sn-shop-floor.js
## 示例
diff --git a/mip-sn-shop-list/README.md b/mip-sn-shop-list/README.md
index 8eb4a4d2..75ba190c 100644
--- a/mip-sn-shop-list/README.md
+++ b/mip-sn-shop-list/README.md
@@ -6,7 +6,7 @@ mip-sn-shop-list 实现了自定义实现了sn获取接口数据后,渲染列
----|----
类型|定制
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-sn-shop-list/mip-sn-shop-list.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-sn-shop-list/mip-sn-shop-list.js
## 示例
diff --git a/mip-so-ssp/README.md b/mip-so-ssp/README.md
index 45ea8e79..87f62683 100644
--- a/mip-so-ssp/README.md
+++ b/mip-so-ssp/README.md
@@ -6,7 +6,7 @@ mip-so-ssp 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-so-ssp/mip-so-ssp.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-so-ssp/mip-so-ssp.js
## 示例
diff --git a/mip-stats-ajk/README.md b/mip-stats-ajk/README.md
new file mode 100644
index 00000000..3c20277d
--- /dev/null
+++ b/mip-stats-ajk/README.md
@@ -0,0 +1,45 @@
+# mip-stats-ajk
+
+安居客统计组件
+
+标题|内容
+----|----
+类型|通用
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-stats-ajk/mip-stats-ajk.js
+
+
+## 示例
+
+### 基本使用
+
+```html
+
+
+```
+
+
+## 属性
+
+### src
+
+说明:加码url
+必选项:是
+类型:字符串
+取值范围:URL
+
+### pg-name
+
+说明:当前页面的名称
+必选项:是
+类型:字符串
+
+### city-alias
+
+说明:当前城市的简称
+必选项:是
+类型:字符串
+
diff --git a/mip-stats-ajk/mip-stats-ajk.js b/mip-stats-ajk/mip-stats-ajk.js
new file mode 100644
index 00000000..243248ae
--- /dev/null
+++ b/mip-stats-ajk/mip-stats-ajk.js
@@ -0,0 +1,30 @@
+/**
+ * @file 安居客统计
+ *
+ * @author danny
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var customElement = require('customElement').create();
+
+ customElement.prototype.build = function () {
+ var elem = this.element;
+ var pageName = elem.getAttribute('pg-name');
+ var cityAlias = elem.getAttribute('city-alias');
+
+ // init the page params
+ var APF = window.APF = window.APF || {};
+ var info = APF.info = APF.info || {};
+ pageName && (info.pageName = pageName);
+ cityAlias && (info.cityAlias = cityAlias);
+
+ var url = elem.getAttribute('src');
+ var elescrit = document.createElement('script');
+ elescrit.src = url;
+ document.getElementsByTagName('body')[0].appendChild(elescrit);
+ };
+
+ return customElement;
+
+});
diff --git a/mip-stats-ajk/package.json b/mip-stats-ajk/package.json
new file mode 100644
index 00000000..1296743c
--- /dev/null
+++ b/mip-stats-ajk/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-stats-ajk",
+ "version": "1.1.2",
+ "author": {
+ "name": "danny",
+ "email": "decailiu@anjuke.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-stats-bidu/README.md b/mip-stats-bidu/README.md
new file mode 100644
index 00000000..fcc51dba
--- /dev/null
+++ b/mip-stats-bidu/README.md
@@ -0,0 +1,25 @@
+# mip-stats-bidu
+
+mip-stats-bidu 用来支持站长添加百度统计。推荐使用 mip-stats-baidu
+
+描述|百度统计组件,用于统计页面数据
+----|----
+类型| 通用
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-stats-bidu/mip-stats-bidu.js
+
+## 示例
+
+MIP提供百度统计的插件,便于分析页面数据,需要提前到百度统计这边创建站点,会自动生成js代码使用提取工具提取token,并使用MIP提供的插件,代码示例:
+
+```
+
+```
+
+## 属性
+
+### token
+
+说明:token
+必填:是
+格式:字符串
diff --git a/mip-stats-bidu/mip-stats-bidu.js b/mip-stats-bidu/mip-stats-bidu.js
new file mode 100755
index 00000000..5256d569
--- /dev/null
+++ b/mip-stats-bidu/mip-stats-bidu.js
@@ -0,0 +1,194 @@
+/**
+ * @file 百度统计插件
+ *
+ * @author menglingjun, Jenny_L
+ * From: mip-stats-baidu
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+ var viewer = require('viewer');
+
+ var customElement = require('customElement').create();
+
+ customElement.prototype.createdCallback = function () {
+ var elem = this.element;
+ var token = elem.getAttribute('token');
+ var setConfig = elem.getAttribute('setconfig');
+
+ /**
+ * 检测token是否存在
+ */
+ if (token) {
+ window._hmt = window._hmt || [];
+ _hmt.push([
+ '_setAccount',
+ token
+ ]);
+
+ // XXX: 解决来自百度搜索,内外域名不一致问题
+ if (viewer.isIframed) {
+ bdSearchCase();
+ }
+
+ /**
+ * 检测setconfig是否存在
+ */
+ if (setConfig) {
+ var setCustom = buildArry(decodeURIComponent(setConfig));
+ _hmt.push(setCustom);
+ }
+
+ var hm = document.createElement('script');
+ hm.src = '//hm.baidu.com/hm.js?' + token;
+ $(elem).append(hm);
+ hm.onload = function () {
+ bindEle();
+ };
+ }
+
+ };
+
+
+ // 绑定事件追踪
+ function bindEle() {
+
+ // 获取所有需要触发的dom
+ var tagBox = document.querySelectorAll('*[data-stats-baidu-obj]');
+
+ for (var index = 0; index < tagBox.length; index++) {
+ var statusData = tagBox[index].getAttribute('data-stats-baidu-obj');
+
+ /**
+ * 检测statusData是否存在
+ */
+ if (!statusData) {
+ return;
+ }
+
+ try {
+ statusData = JSON.parse(decodeURIComponent(statusData));
+ }
+ catch (e) {
+ console.warn('事件追踪data-stats-baidu-obj数据不正确');
+ return;
+ }
+
+ var eventtype = statusData.type;
+
+ /**
+ * 检测传递数据是否存在
+ */
+ if (!statusData.data) {
+ return;
+ }
+
+ var data = buildArry(statusData.data);
+
+ if (eventtype !== 'click' && eventtype !== 'mouseup' && eventtype !== 'load') {
+ // 事件限制到click,mouseup,load(直接触发)
+ return;
+ }
+
+ if ($(tagBox[index]).hasClass('mip-stats-eventload')) {
+ return;
+ }
+
+ $(tagBox[index]).addClass('mip-stats-eventload');
+
+ if (eventtype === 'load') {
+ _hmt.push(data);
+ }
+ else {
+ tagBox[index].addEventListener(eventtype, function(event) {
+ var tempData = this.getAttribute('data-stats-baidu-obj');
+ if (!tempData) {
+ return;
+ }
+ var statusJson;
+ try {
+ statusJson = JSON.parse(decodeURIComponent(tempData));
+ }
+ catch (e) {
+ console.warn('事件追踪data-stats-baidu-obj数据不正确');
+ return;
+ }
+ if (!statusJson.data) {
+ return;
+ }
+ var attrData = buildArry(statusJson.data);
+ _hmt.push(attrData);
+ }, false);
+ }
+ }
+ }
+
+ // 数据换转
+ function buildArry(arrayStr) {
+ if (!arrayStr) {
+ return;
+ }
+
+ var strArr = arrayStr.slice(1, arrayStr.length - 1).split(',');
+ var newArray = [];
+
+ for (var index = 0; index < strArr.length; index++) {
+ var item = strArr[index].replace(/(^\s*)|(\s*$)/g, '').replace(/\'/g, '');
+ if (item === 'false' || item === 'true') {
+ item = Boolean(item);
+ }
+
+ newArray.push(item);
+ }
+ return newArray;
+ }
+
+ /**
+ * 解决来自百度搜索,内外域名不一致问题
+ */
+ function bdSearchCase() {
+ var referrer = '';
+
+ var bdUrl = document.referrer;
+ var hashWord = MIP.hash.get('word') || '';
+ var hashEqid = MIP.hash.get('eqid') || '';
+ if ((hashWord || hashEqid) && bdUrl) {
+ var hashObj = {};
+ if (hashEqid) {
+ hashObj.url = '';
+ hashObj.eqid = hashEqid;
+ }
+ else {
+ hashObj.word = hashWord;
+ }
+ referrer = makeReferrer(bdUrl, hashObj);
+ _hmt.push('_setReferrerOverride', referrer);
+ }
+
+ }
+
+ /**
+ * 生成百度统计_setReferrerOverride对应的referrer
+ *
+ * @param {string} url 需要被添加参数的 url
+ * @param {Object} hashObj 参数对象
+ * @return {string} 拼装后的 url
+ */
+ function makeReferrer(url, hashObj) {
+ var referrer = '';
+ var conjMark = url.indexOf('?') < 0 ? '?' : '&';
+ var urlData = '';
+ for (var key in hashObj) {
+ urlData += '&' + key + '=' + hashObj[key];
+ }
+ urlData = urlData.slice(1);
+ if (url.indexOf('#') < 0) {
+ referrer = url + conjMark + urlData;
+ }
+ else {
+ referrer = url.replace('#', conjMark + urlData + '#');
+ }
+ return referrer;
+ }
+ return customElement;
+});
\ No newline at end of file
diff --git a/mip-stats-bidu/package.json b/mip-stats-bidu/package.json
new file mode 100644
index 00000000..a3f03e1f
--- /dev/null
+++ b/mip-stats-bidu/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-stats-bidu",
+ "version": "1.0.7",
+ "author": {
+ "name": "menglingjun",
+ "email": "menglingjun@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-stats-cnzz-365caidashi/README.md b/mip-stats-cnzz-365caidashi/README.md
index 4ae9a51f..05b830e4 100644
--- a/mip-stats-cnzz-365caidashi/README.md
+++ b/mip-stats-cnzz-365caidashi/README.md
@@ -5,7 +5,7 @@ mip-stats-cnzz-365caidashi 365caidashi项目专属cnzz统计,2017.11.20 xiaoji
标题|内容
----|----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-stats-cnzz-365caidashi/mip-stats-cnzz-365caidashi.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-stats-cnzz-365caidashi/mip-stats-cnzz-365caidashi.js
## 示例
diff --git a/mip-stats-general/README.md b/mip-stats-general/README.md
new file mode 100644
index 00000000..dc7aac35
--- /dev/null
+++ b/mip-stats-general/README.md
@@ -0,0 +1,25 @@
+# mip-stats-general
+
+mip-stats-general 实现了传统的统计功能,在访问页面时访问一个地址。
+
+标题|内容
+----|----
+类型|通用
+支持布局|不使用布局
+所需脚本|https://c.mipcdn.com/static/v1/mip-stats-general/mip-stats-general.js
+
+## 示例
+
+```html
+
+```
+
+## 属性
+
+### src
+
+说明:统计地址
+必选项:是
+类型:字符串
+取值范围:`http://.*`, `https://.*`
+
diff --git a/mip-stats-general/mip-stats-general.js b/mip-stats-general/mip-stats-general.js
new file mode 100755
index 00000000..57d25902
--- /dev/null
+++ b/mip-stats-general/mip-stats-general.js
@@ -0,0 +1,41 @@
+/**
+ * @file 常规统计
+ * @author junmer
+ */
+
+define(function (require) {
+
+ var customElement = require('customElement').create();
+
+ function noop () {}
+
+ customElement.prototype.build = function () {
+ var _element = this.element;
+
+ // 隐藏
+ _element.style.display = 'none';
+
+ var url = _element.getAttribute('src');
+
+ if (!url) {
+ return;
+ }
+
+ var img = new Image();
+
+ img.onload = img.onError = noop;
+
+ _element.appendChild(img);
+
+ img.src = ''
+ + url
+ + (url.indexOf('?') > -1 ? '&' : '?')
+ + '_='
+ + (+new Date());
+
+
+ }
+
+ return customElement;
+
+});
diff --git a/mip-stats-general/package.json b/mip-stats-general/package.json
new file mode 100644
index 00000000..33e6992b
--- /dev/null
+++ b/mip-stats-general/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-stats-general",
+ "version": "1.0.2",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-stats-google/README.md b/mip-stats-google/README.md
index 2924d75d..0106b197 100644
--- a/mip-stats-google/README.md
+++ b/mip-stats-google/README.md
@@ -6,7 +6,7 @@ mip-stats-google 组件说明
----|----
类型|通用
布局|N/S
-引用脚本|https://c.mipcdn.com/extensions/platform/v1/mip-stats-google/mip-stats-google.js
+引用脚本|https://c.mipcdn.com/static/v1/mip-stats-google/mip-stats-google.js
## 示例
diff --git a/mip-stats-netease/README.md b/mip-stats-netease/README.md
new file mode 100644
index 00000000..8c79c506
--- /dev/null
+++ b/mip-stats-netease/README.md
@@ -0,0 +1 @@
+# 网易统计
diff --git a/mip-stats-netease/mip-stats-netease.js b/mip-stats-netease/mip-stats-netease.js
new file mode 100755
index 00000000..b396b2bd
--- /dev/null
+++ b/mip-stats-netease/mip-stats-netease.js
@@ -0,0 +1,31 @@
+/**
+ * @file 网易统计
+ *
+ * @author menglingjun
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var customElement = require('customElement').create();
+
+ customElement.prototype.build = function () {
+ var _element = this.element;
+ var $_element = $(_element);
+ var id = _element.getAttribute('id') || '';
+
+ var elescript = document.createElement('script');
+ elescript.src = location.protocol + '//analytics.163.com/ntes_ex.js';
+ $('body').append(elescript)
+ elescript.onload = function() {
+ // 网易小伙伴 居然支持 amd
+ require(['NTES'], function (NTES) {
+ NTES(id).pageTracker();
+ });
+ }
+ }
+
+ return customElement;
+
+});
diff --git a/mip-stats-netease/package.json b/mip-stats-netease/package.json
new file mode 100644
index 00000000..372c17d2
--- /dev/null
+++ b/mip-stats-netease/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-stats-netease",
+ "version": "1.0.2",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-stats-qiyu/README.md b/mip-stats-qiyu/README.md
index c336b7ac..dce010ed 100644
--- a/mip-stats-qiyu/README.md
+++ b/mip-stats-qiyu/README.md
@@ -6,7 +6,7 @@ mip-stats-qiyu 组件说明:网易七鱼在线客服组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-stats-qiyu/mip-stats-qiyu.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-stats-qiyu/mip-stats-qiyu.js
## 示例
diff --git a/mip-stats-sina/README.md b/mip-stats-sina/README.md
new file mode 100644
index 00000000..a0a3a476
--- /dev/null
+++ b/mip-stats-sina/README.md
@@ -0,0 +1 @@
+# 新浪统计
diff --git a/mip-stats-sina/mip-stats-sina.js b/mip-stats-sina/mip-stats-sina.js
new file mode 100755
index 00000000..a6e66f7c
--- /dev/null
+++ b/mip-stats-sina/mip-stats-sina.js
@@ -0,0 +1,89 @@
+/**
+ * @file 新浪统计
+ *
+ * @author menglingjun
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var customElement = require('customElement').create();
+
+ // SUDA地图统计
+ window.sudaMapConfig = {
+ uId: '', // 用户uid,如果用户没登陆,可以为空
+ pageId: '' // 必填
+ };
+
+ /**
+ * elemKeysToObj
+ *
+ * @param {HTMLElement} elem elem
+ * @param {Array} keys keys
+ * @param {Object} obj obj
+ */
+ function elemKeysToObj(elem, keys, obj) {
+ var $elem = $(elem);
+ $.each(keys, function (i, key) {
+ obj[key] = $(elem).attr(key) || '';
+ });
+ }
+
+ /**
+ * protocol
+ *
+ * @type {string}
+ */
+ var protocol = location.protocol;
+
+ /**
+ * SINA_TONGJI_ROOT
+ *
+ * @type {string}
+ */
+ var SINA_TONGJI_ROOT = ''
+ + protocol
+ + '//mjs'
+ + (protocol === 'https:' ? 's' : '')
+ + '.sinaimg.cn/wap/public/suda/201607111020/';
+
+ /**
+ * getScript promise
+ *
+ * @param {string} src 地址
+ * @return {promise} promise
+ */
+ function getScriptPromise (src) {
+
+ var _promise = new Promise(function(resolve,reject){
+ var elescrit = document.createElement("script");
+ elescrit.src = src;
+ $('body').append(elescrit);
+ elescrit.onload = function() {
+ resolve();
+ };
+ });
+
+ return _promise;
+ }
+
+ customElement.prototype.build = function () {
+ var elem = this.element;
+
+ getScriptPromise(SINA_TONGJI_ROOT + 'suda_log.min.js').then(function(){
+ return getScriptPromise(SINA_TONGJI_ROOT + 'suda_map.min.js');
+ }).then(function(){
+ elemKeysToObj(elem, ['uId', 'pageId'], window.sudaMapConfig);
+
+ if (window.suda_init) {
+ window.suda_init(window.sudaMapConfig.pageId, 100);
+ }
+ });
+
+ }
+
+ return customElement;
+
+});
+
diff --git a/mip-stats-sina/package.json b/mip-stats-sina/package.json
new file mode 100644
index 00000000..fa00db9c
--- /dev/null
+++ b/mip-stats-sina/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-stats-sina",
+ "version": "1.0.2",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-swt/README.md b/mip-swt/README.md
index 635c7812..3774325b 100644
--- a/mip-swt/README.md
+++ b/mip-swt/README.md
@@ -6,7 +6,7 @@ mip-swt 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-swt/mip-swt.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-swt/mip-swt.js
## 示例
diff --git a/mip-taoge-form/README.md b/mip-taoge-form/README.md
index 02798701..a6d171a1 100644
--- a/mip-taoge-form/README.md
+++ b/mip-taoge-form/README.md
@@ -6,7 +6,7 @@ scaydk.com网表单提交组件。
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-form/mip-taoge-form.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-form/mip-taoge-form.js
## 示例
diff --git a/mip-taoge-scaydk-adviser/README.md b/mip-taoge-scaydk-adviser/README.md
index cda2fbb3..d2ed3530 100644
--- a/mip-taoge-scaydk-adviser/README.md
+++ b/mip-taoge-scaydk-adviser/README.md
@@ -6,7 +6,7 @@ wap.scaydk.com 贷款顾问自定义组件
----|----
类型|通用
支持布局|不支持
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk-adviser/mip-taoge-scaydk-adviser.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk-adviser/mip-taoge-scaydk-adviser.js
## 示例
diff --git a/mip-taoge-scaydk-calculator/README.md b/mip-taoge-scaydk-calculator/README.md
index 278f4cbe..1972c5aa 100644
--- a/mip-taoge-scaydk-calculator/README.md
+++ b/mip-taoge-scaydk-calculator/README.md
@@ -6,7 +6,7 @@ wap.scaydk.com 贷款计算器组件
----|----
类型|通用
支持布局| 不支持
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk-calculator/mip-taoge-scaydk-calculator.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk-calculator/mip-taoge-scaydk-calculator.js
## 示例
diff --git a/mip-taoge-scaydk-index/README.md b/mip-taoge-scaydk-index/README.md
index 5518d7fd..ef198d56 100644
--- a/mip-taoge-scaydk-index/README.md
+++ b/mip-taoge-scaydk-index/README.md
@@ -6,7 +6,7 @@ wap.scaydk.com网首页专用组件
----|----
类型|通用
支持布局|不支持
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk-index/mip-taoge-scaydk-index.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk-index/mip-taoge-scaydk-index.js
## 示例
diff --git a/mip-taoge-scaydk-loan/README.md b/mip-taoge-scaydk-loan/README.md
index f868dd63..86b45733 100644
--- a/mip-taoge-scaydk-loan/README.md
+++ b/mip-taoge-scaydk-loan/README.md
@@ -6,7 +6,7 @@ wap.scaydk.com网贷款产品筛选组件
----|----
类型|通用
支持布局|不支持
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk-loan/mip-taoge-scaydk-loan.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk-loan/mip-taoge-scaydk-loan.js
## 示例
diff --git a/mip-taoge-scaydk-qe/README.md b/mip-taoge-scaydk-qe/README.md
index 1ad4be81..3e4bceb5 100644
--- a/mip-taoge-scaydk-qe/README.md
+++ b/mip-taoge-scaydk-qe/README.md
@@ -6,7 +6,7 @@ wap.scaydk.com网贷款额度计算组件
----|----
类型|通用
支持布局|不支持
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk-qe/mip-taoge-scaydk-qe.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk-qe/mip-taoge-scaydk-qe.js
## 示例
diff --git a/mip-taoge-scaydk/README.md b/mip-taoge-scaydk/README.md
index 11029fee..28ee0118 100644
--- a/mip-taoge-scaydk/README.md
+++ b/mip-taoge-scaydk/README.md
@@ -6,7 +6,7 @@ mip-taoge-scaydk 是wap.scaydk.com网业务逻辑组件
----|----
类型|通用
支持布局|不使用布局
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-scaydk/mip-taoge-scaydk.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-scaydk/mip-taoge-scaydk.js
## 示例
diff --git a/mip-taoge-tp/README.md b/mip-taoge-tp/README.md
index a3f71dc1..e3fe2469 100644
--- a/mip-taoge-tp/README.md
+++ b/mip-taoge-tp/README.md
@@ -6,7 +6,7 @@ mip-taoge-tp ThinPHP 系统组件
----|----
类型|通用
支持布局|不使用布局
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-taoge-tp/mip-taoge-tp.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-taoge-tp/mip-taoge-tp.js
## 示例
diff --git a/mip-terms-shield/README.md b/mip-terms-shield/README.md
index 758940b2..5d947cbd 100644
--- a/mip-terms-shield/README.md
+++ b/mip-terms-shield/README.md
@@ -6,7 +6,7 @@ mip-terms-shield 屏蔽词语
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-terms-shield/mip-terms-shield.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-terms-shield/mip-terms-shield.js
## 示例
### 根据页面里的数组,对包含关键字的页面内容进行替换。
diff --git a/mip-tiantis-51la/README.md b/mip-tiantis-51la/README.md
index a83a95bf..92a04162 100644
--- a/mip-tiantis-51la/README.md
+++ b/mip-tiantis-51la/README.md
@@ -6,7 +6,7 @@ mip-tiantis-51la 封装了51la统计
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-tiantis-51la/mip-tiantis-51la.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-tiantis-51la/mip-tiantis-51la.js
## 示例
diff --git a/mip-tiantis-54kefu/README.md b/mip-tiantis-54kefu/README.md
index 43792540..0b1c5852 100644
--- a/mip-tiantis-54kefu/README.md
+++ b/mip-tiantis-54kefu/README.md
@@ -6,7 +6,7 @@ mip-tiantis-54kefu 封装了54kefu
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-tiantis-54kefu/mip-tiantis-54kefu.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-tiantis-54kefu/mip-tiantis-54kefu.js
## 示例
diff --git a/mip-tiantis-jsridge/README.md b/mip-tiantis-jsridge/README.md
index e9e4b2c1..90fa7063 100644
--- a/mip-tiantis-jsridge/README.md
+++ b/mip-tiantis-jsridge/README.md
@@ -6,7 +6,7 @@ mip-tiantis-jsridge 封装了天体系统的JS逻辑
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-tiantis-jsridge/mip-tiantis-jsridge.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-tiantis-jsridge/mip-tiantis-jsridge.js
## 示例
diff --git a/mip-ticket/README.md b/mip-ticket/README.md
index 0e39e651..9b5893d8 100644
--- a/mip-ticket/README.md
+++ b/mip-ticket/README.md
@@ -6,7 +6,7 @@ mip-ticket 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ticket/mip-ticket.js## 示例
+所需脚本|https://c.mipcdn.com/static/v1/mip-ticket/mip-ticket.js## 示例
### 基本用法
```html
diff --git a/mip-trilobite-fontsize/README.md b/mip-trilobite-fontsize/README.md
index b777ecac..aa21b23f 100644
--- a/mip-trilobite-fontsize/README.md
+++ b/mip-trilobite-fontsize/README.md
@@ -6,7 +6,7 @@ mip-trilobite-fontsize 三叶虫字体适配
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-trilobite-fontsize/mip-trilobite-fontsize.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-trilobite-fontsize/mip-trilobite-fontsize.js
## 示例
diff --git a/mip-trilobite-log/README.md b/mip-trilobite-log/README.md
index 95da1cf1..1ecfad0d 100644
--- a/mip-trilobite-log/README.md
+++ b/mip-trilobite-log/README.md
@@ -6,7 +6,7 @@ mip-trilobite-log 三叶虫统计
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-trilobite-log/mip-trilobite-log.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-trilobite-log/mip-trilobite-log.js
## 示例
diff --git a/mip-trilobite-scroll/README.md b/mip-trilobite-scroll/README.md
index 4c4e5cf3..65b7551a 100644
--- a/mip-trilobite-scroll/README.md
+++ b/mip-trilobite-scroll/README.md
@@ -6,7 +6,7 @@ mip-trilobite-scroll 三叶虫下拉滚动功能
----|----
类型|业务
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-trilobite-scroll/mip-trilobite-scroll.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-trilobite-scroll/mip-trilobite-scroll.js
## 示例
diff --git a/mip-truckcn-hash/README.md b/mip-truckcn-hash/README.md
index b9fc3515..77f91c7b 100644
--- a/mip-truckcn-hash/README.md
+++ b/mip-truckcn-hash/README.md
@@ -8,7 +8,7 @@ mip-truckcn-hash 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-truckcn-hash/mip-truckcn-hash.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-truckcn-hash/mip-truckcn-hash.js
## 示例1
diff --git a/mip-truckcn-tel/README.md b/mip-truckcn-tel/README.md
index a12b79b8..12be50e7 100644
--- a/mip-truckcn-tel/README.md
+++ b/mip-truckcn-tel/README.md
@@ -8,7 +8,7 @@ mip-truckcn-tel 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-truckcn-tel/mip-truckcn-tel.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-truckcn-tel/mip-truckcn-tel.js
## 示例1
diff --git a/mip-trueland-rem/README.md b/mip-trueland-rem/README.md
index 38d7bc64..33a9c3bd 100644
--- a/mip-trueland-rem/README.md
+++ b/mip-trueland-rem/README.md
@@ -6,7 +6,7 @@ mip-trueland-rem 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-trueland-rem/mip-trueland-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-trueland-rem/mip-trueland-rem.js
## 示例
diff --git a/mip-trueland-utils/README.md b/mip-trueland-utils/README.md
index d36ab46f..d07a6a1d 100644
--- a/mip-trueland-utils/README.md
+++ b/mip-trueland-utils/README.md
@@ -6,7 +6,7 @@ mip-trueland-utils 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-trueland-utils/mip-trueland-utils.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-trueland-utils/mip-trueland-utils.js
## 示例
diff --git a/mip-tuijian-baidu/README.md b/mip-tuijian-baidu/README.md
index 342fa3ff..24ffb306 100644
--- a/mip-tuijian-baidu/README.md
+++ b/mip-tuijian-baidu/README.md
@@ -6,7 +6,7 @@ mip-tuijian-baidu 用来支持站长添加[百度推荐](http://tuijian.baidu.co
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-tuijian-baidu/mip-tuijian-baidu.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-tuijian-baidu/mip-tuijian-baidu.js
## 示例
diff --git a/mip-ueditor-out/README.md b/mip-ueditor-out/README.md
index 2a859b8c..f2ec62d8 100644
--- a/mip-ueditor-out/README.md
+++ b/mip-ueditor-out/README.md
@@ -6,7 +6,7 @@ mip-ueditor-out 将富文本的style以mip-data-style代替,然后将该属性
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ueditor-out/mip-ueditor-out.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ueditor-out/mip-ueditor-out.js
## 示例
diff --git a/mip-unique-visitor/README.md b/mip-unique-visitor/README.md
index ea5d2050..4c48100a 100644
--- a/mip-unique-visitor/README.md
+++ b/mip-unique-visitor/README.md
@@ -5,7 +5,7 @@ mip-unique-visitor 可以统计每天的用户访问次数(UV)
----|----
类型|业务,定制
支持布局|nodiaplay
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-unique-visitor/mip-unique-visitor.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-unique-visitor/mip-unique-visitor.js
## 示例
diff --git a/mip-vd-popup/README.md b/mip-vd-popup/README.md
new file mode 100644
index 00000000..16764f57
--- /dev/null
+++ b/mip-vd-popup/README.md
@@ -0,0 +1,77 @@
+# mip-vd-popup
+
+mip-vd-popup 用来支持网页中弹出浮层的显示。
+
+标题|内容
+----|----
+类型|通用
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-vd-popup/mip-vd-popup.js
+
+## 示例
+
+一共支持2种样式
+
+### 默认样式
+
+```html
+
+ 点击触发
+ 浮层
+
+```
+
+### 剧集浮层
+
+```html
+
+ 目录
+
+```
+
+## 属性
+
+### type
+
+说明:一共有两种特型, episode(剧情选项卡), 不填则为默认特型
+必选项:否
+
+### title
+
+说明:复层标题
+必选项:否
+
+### current
+
+说明:当前剧集,从1开始计数(current="4"表示第4集),默认为1, 前置依赖于type="episode".
+必选项:否
+
+### total
+
+说明:剧集总数. 前置依赖于type="episode",并且当type="episode"为必填
+必选项:否
+
+### page-size
+
+说明:每页显示剧集数. 前置依赖于type="episode",默认为50
+必选项:否
+
+### text-tpl
+
+说明:显示在标签页上的剧集文案, "第{{x}}集"里的"{{x}}"将被替换成表示集数的数字. 前置依赖于type="episode".
+必选项:否
+
+### link-tpl
+
+说明:标签页和下拉菜单里的剧集跳转链接, 链接里的"{{x}}"将被替换成表示集数的数字. 前置依赖于type="episode",当type="episode"为必填.
+必选项:否
+
+### head-title
+
+说明:标签页和下拉菜单里的剧集跳转新页面的头部标题. 前置依赖于type="episode",当type="episode"为必填.
+必选项:否
diff --git a/mip-vd-popup/mip-vd-popup.js b/mip-vd-popup/mip-vd-popup.js
new file mode 100644
index 00000000..c0d15c8b
--- /dev/null
+++ b/mip-vd-popup/mip-vd-popup.js
@@ -0,0 +1,178 @@
+/**
+ * @file tab组件
+ *
+ * @author zhangjignfeng
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var $ = require('zepto');
+
+ var customElement = require('customElement').create();
+ var Tab = require('./tab');
+ var Popup = require('./popup');
+ var WRAPPER_CLS = 'mip-vd-tabs';
+ var CONTENT_CLS = 'mip-vd-tabs-content';
+ var SELECTED_CLS = 'mip-vd-tabs-nav-selected';
+ var ITEM_CLS = 'mip-vd-tabs-nav-li';
+ var NAV_CLS = 'mip-vd-tabs-nav';
+ var VIEW_CLS = 'mip-vd-tabs-nav-view';
+ var TOGGLE_CLS = 'mip-vd-tabs-nav-toggle';
+ var BOTTOM_CLS = 'mip-vd-tabs-nav-bottom';
+
+ var EPISODE_PAGE_SIZE = 50;
+ var TPL_REG = /\{\{\w}}/g;
+ customElement.prototype.build = function () {
+ var el = this.element;
+ var $el = $(el);
+ var type = el.getAttribute('type');
+ var linkTpl = el.getAttribute('link-tpl');
+ var $dom = null;
+ var $trigle = null;
+ switch (type) {
+ case 'episode':
+ $dom = generateEpisode.call(this, linkTpl);
+ $trigle = $el;
+ bindEvent.call(this, $dom);
+ break;
+ default:
+ $trigle = $el.children().eq(0);
+ $dom = $el.children().eq(1).addClass('mip-vd-popup-dom');
+ }
+ generatePopup.call(this, $trigle, $dom);
+ }
+
+ function generateEpisode(linkTpl) {
+ var $el = $(this.element);
+ var pageSize = parseInt($el.attr('page-size'), 10) || EPISODE_PAGE_SIZE;
+ var currentNum = parseInt($el.attr('current'), 10) || 1;
+ var totalNum = parseInt($el.attr('total'), 10) || 1;
+ var tabCount = Math.ceil(totalNum / pageSize);
+ var tabCurNum = Math.floor((currentNum - 1) / pageSize);
+ var tabList = [];
+
+
+ for (var i = 0; i < tabCount; i++) {
+ var from = pageSize * i + 1;
+ var to = Math.min(totalNum, pageSize * (i + 1));
+ tabList.push({
+ from: from,
+ to : to,
+ text: '' + from + (from < to ? ' - ' + to : '')
+ });
+ }
+
+
+ var wrapper = $('');
+ wrapper.append(tabList.map(function (v, index) {
+ var epFragment = '';
+ for (var j = v.from; j <= v.to; j++) {
+ var selectedClass = j === currentNum ? 'mip-vd-tabs-episode-item-selected' : '';
+ var link = (linkTpl ? ' href="' + linkTpl.replace(TPL_REG, j) + '"' : '' );
+ epFragment = epFragment
+ + ''
+ + j
+ + '';
+ }
+ epFragment += '
';
+ return epFragment;
+ }).join(''))
+ .append();
+
+ if (tabCount > 1) {
+ var tabFragment = '';
+ var scrollNum = 4;
+ if (tabCount > scrollNum) {
+ tabFragment = '';
+ }
+ tabFragment += '
';
+ tabFragment += tabList.map(function (v, index) {
+ var selectedClass = index === tabCurNum ? SELECTED_CLS : '';
+ return '- ' + v.text + '
';
+ }).join('');
+ tabFragment += '
';
+ if (tabCount > scrollNum) {
+ tabFragment += '
';
+ }
+ wrapper.append(tabFragment);
+ }
+ return wrapper;
+ }
+
+
+ function generatePopup($trigle, $dom) {
+ var el = this.element;
+ var $el = $(el);
+ var pageSize = parseInt($el.attr('page-size'), 10) || EPISODE_PAGE_SIZE;
+ var currentNum = parseInt($el.attr('current'), 10) || 1;
+ var totalNum = parseInt($el.attr('total'), 10) || 1;
+ var tabCount = Math.ceil(totalNum / pageSize);
+ $($trigle).on('click', function (e) {
+ new Popup({
+ title: el.getAttribute('title') || '',
+ content: $dom,
+ fullView: false,
+ customClassName: 'mip-vd-popup-custom-modal',
+ onOpen: function () {
+ new Tab($dom, {
+ allowScroll: tabCount > 4,
+ current: Math.floor((currentNum - 1) / pageSize) || 0,
+ currentClass: SELECTED_CLS,
+ navWrapperClass: NAV_CLS,
+ viewClass: VIEW_CLS,
+ contClass: CONTENT_CLS,
+ navClass: ITEM_CLS,
+ logClass: 'mip-vd-tabs-log',
+ toggleClass: TOGGLE_CLS
+ });
+ }
+ });
+ });
+ }
+
+ function bindEvent($dom) {
+ var $el = $(this.element);
+ var headTitle = this.element.getAttribute('head-title');
+ $dom.delegate('.mip-vd-tabs-episode-item', 'click' , function(ev) {
+
+ ev.preventDefault();
+
+ var href = $(this).attr("href");
+
+ if (!href) {
+ return;
+ }
+
+
+ // 顶部标题
+ var head = $(this).text() || '';
+
+
+ var message = {
+ "event": "loadiframe",
+ "data": {
+ "url": href,
+ "title": headTitle || head,
+ "click": $el.data('click')
+ }
+ };
+
+ if (window.parent !== window) {
+ window.parent.postMessage(message, '*');
+ }
+ else {
+ location.href = href;
+ }
+
+ });
+ }
+
+ return customElement;
+
+});
diff --git a/mip-vd-popup/mip-vd-popup.less b/mip-vd-popup/mip-vd-popup.less
new file mode 100644
index 00000000..f4ba39aa
--- /dev/null
+++ b/mip-vd-popup/mip-vd-popup.less
@@ -0,0 +1,296 @@
+.mip-vd-popup-wrapper {
+ z-index: 900;
+}
+
+.mip-vd-popup-mask {
+ display: none;
+ position: fixed;
+ left: 0;
+ top: 0;
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, .4);
+ z-index: 901;
+}
+
+.mip-vd-popup-modal {
+ display: none;
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ background-color: #fff;
+ z-index: 902;
+ margin: 0;
+ overflow: hidden;
+ -webkit-transform: translate3d(0, 100%, 0);
+ transform: translate3d(0, 100%, 0);
+ overflow-y: scroll;
+}
+
+.mip-vd-popup-head {
+ text-align: left;
+ padding: 0 16px;
+ height: 43px;
+ line-height: 43px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+.mip-vd-popup-title {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.mip-vd-popup-remove {
+ position: absolute;
+ padding: 15px 16px;
+ right: 0px;
+ top: 0px;
+ line-height: 14px;
+ z-index: 901;
+}
+
+.mip-vd-popup-remove img{
+ width: 14px;
+ height: 14px;
+}
+
+.mip-vd-popup-content {
+ text-align: center;
+ margin: 0;
+}
+
+.mip-vd-popup-dom {
+ display: none;
+}
+
+.mip-vd-popup-content .mip-vd-popup-dom {
+ display: block;
+}
+.mip-vd-popup-modal .mip-vd-tabs-nav-view {
+ height: 38px;
+}
+.mip-vd-tabs {
+ position: relative;
+}
+
+.mip-vd-tabs-nav {
+ display: -webkit-box;
+ display: -webkit-flex;
+ height: 44px;
+ font-size: 14px;
+ white-space: nowrap;
+ background-color: #fff;
+
+ -webkit-align-content: flex-start;
+ -webkit-align-items: stretch;
+ -webkit-box-align: stretch;
+ -webkit-box-direction: normal;
+ -webkit-box-lines: single;
+ -webkit-box-orient: horizontal;
+ -webkit-box-pack: justify;
+ -webkit-flex-direction: row;
+ -webkit-flex-wrap: nowrap;
+ -webkit-justify-content: space-between;
+ padding: 0 16px;
+}
+
+.mip-vd-tabs-nav * {
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.mip-vd-tabs-nav-li {
+ display: block;
+ overflow: hidden;
+ width: 16.66666667%;
+ height: 44px;
+ padding: 0 14px 0 15px;
+ line-height: 44px;
+ text-align: center;
+ white-space: nowrap;
+ text-decoration: none;
+ text-overflow: ellipsis;
+ list-style: none;
+ color: #333;
+
+ -webkit-box-flex: 1;
+ -webkit-flex: 1 1 auto;
+}
+
+.mip-vd-tabs-nav .mip-vd-tabs-nav-selected {
+ border-bottom: 1px solid #38f;
+ color: #38f;
+}
+
+.mip-vd-tabs-nav-bottom {
+ border-top: 1px solid #eee;
+ background: #f8f8f8;
+}
+
+.mip-vd-tabs-nav-bottom .mip-vd-tabs-nav-li {
+ color: #666;
+}
+
+.mip-vd-tabs-nav-bottom .mip-vd-tabs-nav-icon {
+ display: none;
+}
+
+.mip-vd-tabs-nav-bottom .mip-vd-tabs-nav-li.mip-vd-tabs-nav-selected {
+ position: relative;
+ top: -1px;
+ height: 38px;
+ border: 1px solid #f1f1f1;
+ border-color: #fff #f1f1f1 #38f #f1f1f1;
+ line-height: 38px;
+ color: #333;
+ background-color: #fff;
+}
+
+.mip-vd-tabs-nav-bottom .mip-vd-tabs-nav-selected:first-child {
+ margin-left: -1px;
+}
+
+.mip-vd-tabs-nav-bottom .mip-vd-tabs-nav-selected .mip-vd-tabs-nav-icon {
+ display: inline-block;
+}
+
+.mip-vd-tabs-nav-view {
+ position: relative;
+ overflow: hidden;
+ height: 44px;
+ text-align: left;
+ background-color: #fff;
+}
+
+.mip-vd-tabs-nav-view .mip-vd-tabs-nav {
+ display: inline-block;
+ position: relative;
+}
+
+.mip-vd-tabs-nav-view .mip-vd-tabs-nav .mip-vd-tabs-nav-li {
+ display: inline-block;
+ vertical-align: middle;
+ width: auto;
+ padding: 0 15px;
+}
+
+.mip-vd-tabs-nav-view .mip-vd-tabs-nav.mip-vd-tabs-nav-bottom {
+ background-color: #f8f8f8;
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.mip-vd-tabs-nav-layer {
+ position: absolute;
+ z-index: 8;
+ top: 0;
+ width: 100%;
+ border-bottom: 1px solid #e7e7e7;
+ background-color: #fff;
+}
+
+.mip-vd-tabs-nav-layer p {
+ height: 44px;
+ padding: 0 16px;
+ border-bottom: 1px solid #eee;
+ line-height: 44px;
+ text-align: left;
+ color: #333;
+}
+
+.mip-vd-tabs-nav-layer-ul .mip-vd-tabs-nav-li {
+ display: inline-block;
+ width: 16.66666667%;
+ padding: 0;
+}
+
+.mip-vd-tabs-nav-layer-ul .mip-vd-tabs-nav-selected {
+ color: #38f;
+}
+
+.mip-vd-tabs .mip-vd-tabs-nav-toggle {
+ position: absolute;
+ z-index: 9;
+ top: 0;
+ right: 0;
+ display: block;
+ width: 42px;
+ height: 26px;
+ margin: 9px 0;
+ border-left: 1px solid #ccc;
+ text-align: center;
+ background-color: #fff;
+ img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ display: inline-block;
+ width: 10px;
+ height: 6px;
+ margin: -3px 0 0 -5px;
+ }
+}
+
+.mip-vd-tabs .mip-vd-tabs-nav-toggle-holder {
+ display: inline-block;
+ width: 29px;
+ height: 1px;
+ visibility: hidden;
+}
+
+.mip-vd-tabs-scroll-touch {
+ position: relative;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ padding-bottom: 30px;
+ margin-top: -30px;
+ -webkit-transform: translateY(30px);
+ transform: translateY(30px);
+ width: 1px;
+ min-width: 100%;
+}
+
+.mip-vd-tabs-episode-content {
+ padding: 15px 16px;
+ text-align: left;
+}
+
+.mip-vd-tabs-episode-item {
+ display: inline-block;
+ width: 20%;
+ height: 40px;
+ line-height: 40px;
+ text-align: center;
+}
+
+.mip-vd-tabs-episode-item-selected {
+ color: #38f;
+}
+
+.mip-vd-tabs-episode-bottom-nav {
+ padding: 0;
+ height: 38px;
+}
+
+.mip-vd-tabs-episode-bottom-nav .mip-vd-tabs-nav-li {
+ height: 38px;
+ line-height: 38px;
+}
+
+.mip-vd-tabs-nav-view > .mip-vd-tabs-scroll-touch > .mip-vd-tabs-episode-bottom-nav.mip-vd-tabs-nav > .mip-vd-tabs-nav-li {
+ display: inline-block;
+}
+
+.mip-vd-tabs-episode-bottom-nav.mip-vd-tabs-nav .mip-vd-tabs-nav-li {
+ display: block;
+}
+
+.mip-vd-tabs-row-tile {
+ width: 100%;
+ position: relative;
+ overflow: hidden;
+}
diff --git a/mip-vd-popup/package.json b/mip-vd-popup/package.json
new file mode 100644
index 00000000..e81d470d
--- /dev/null
+++ b/mip-vd-popup/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-vd-popup",
+ "version": "1.1.2",
+ "author": {
+ "name": "MIP authors",
+ "email": "mip-support@baidu.com",
+ "url": "https://www.mipengine.org"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-vd-popup/popup.js b/mip-vd-popup/popup.js
new file mode 100644
index 00000000..6496c8f3
--- /dev/null
+++ b/mip-vd-popup/popup.js
@@ -0,0 +1,169 @@
+/**
+ * @file popup弹窗
+ * @author dongshihao
+ * @update zhangjignfeng
+ */
+define(function () {
+ var PopupFrame = function (opt) {
+ var me = this;
+ // 设置默认值
+ me.options = $.extend({
+ title: '', // 标题,支持html和Zepto对象
+ content: '', // 内容,支持html和Zepto对象
+ fullView: false, // 是否全屏
+ duration: 400, // 动画执行时间
+ customClassName: '', // 自定义样式名
+ onOpen: function () {},
+ onClose: function () {}
+ }, opt);
+ // 初始化
+ me._init();
+ };
+
+ PopupFrame.prototype = {
+
+ constructor: PopupFrame,
+
+ version: '0.0.1',
+
+ /*
+ * 初始化:渲染父层dom,阻止遮罩的滚动,弹出popup
+ */
+ _init: function () {
+ var me = this;
+ // 渲染父层dom单例
+ me._preparePopupWrapper();
+ // 阻止遮罩滚动
+ //me._stopScroll();
+ me.popup();
+ },
+ /*
+ * 创建.mip-vd-popup-wrapper父容器单例,所有pop内容都append到这个dom中
+ */
+ _preparePopupWrapper: function () {
+ var me = this;
+ var popWrapperDom = $('.mip-vd-popup-wrapper');
+ if (popWrapperDom.length) {
+ me.$popupFrame = popWrapperDom;
+ me.$popupFrame.empty();
+ } else {
+ me.$popupFrame = $('');
+ $(document.body).append(me.$popupFrame);
+ }
+ },
+ /*
+ * 阻止mask以及结果层的滚动
+ */
+ _stopScroll: function () {
+ var me = this;
+ // 阻止遮罩层滚动,不会影响内部touchmove事件
+ me.$popupFrame.on('touchmove', function (e) {
+ e.preventDefault();
+ });
+ },
+ /*
+ * 父层事件绑定
+ */
+ _bindEvent: function () {
+ var me = this;
+ // mask遮罩和绑定退场事件
+ me.$popupFrame.on('click', '.mip-vd-popup-mask,.mip-vd-popup-remove', function () {
+ me.closePopup();
+ });
+ },
+ /*
+ * 装填&&渲染
+ */
+ _randerContent: function () {
+ var me = this;
+ // 遮罩层
+ me.$popupMask = $('');
+ // modal层
+ me.$popupModal = $('');
+ // modal内content
+ me.$popupContent = $('');
+ // modal内head
+ me.$popupHead = $('');
+ // 装填head内容
+ if (me.options.title) {
+ var titleWrapper = $('');
+ titleWrapper.append(me.options.title);
+ me.$popupHead.append(titleWrapper);
+ }
+ var remove = $('');
+ me.$popupHead.append(remove);
+ // 装填content
+ me.$popupContent.append(me.options.content);
+ // 装填modal
+ me.$popupModal.append(me.$popupHead).append(me.$popupContent).addClass(me.options.customClassName);
+ // 最后装填外层wrapper
+ me.$popupFrame.append(me.$popupModal).append(me.$popupMask);
+ },
+ /*
+ * 弹出层
+ */
+ popup: function () {
+ var me = this;
+ var wHeight = $(window).height();
+ me._randerContent();
+ me._bindEvent();
+ // mask淡入
+ me.$popupMask.show().animate({
+ opacity: 1
+ }, 'fast', 'ease');
+ // 展现modal
+ me.$popupModal.show();
+ // 计算modal实际高度
+ var mHeight = me.$popupModal.height();
+ if (me.options.fullView || mHeight > wHeight) {
+ me.$popupModal.height('100%');
+ }
+ // 入场动画
+ me.$popupModal.animate({
+ '-webkit-transform': 'translate3d(0, 0, 0)',
+ 'transform': 'translate3d(0, 0, 0)'
+ }, me.options.duration, 'ease', function () {
+ $(this).css({
+ '-webkit-transform': 'none',
+ 'transform': 'none'
+ });
+ me.options.onOpen();
+ });
+ },
+ /*
+ * 关闭弹层
+ */
+ closePopup: function () {
+ var me = this;
+ // 退场动画
+ me.$popupModal.animate({
+ '-webkit-transform': 'translate3d(0, 100%, 0)',
+ 'transform': 'translate3d(0, 100%, 0)'
+ }, me.options.duration, 'ease', function () {
+ $(this).css({
+ '-webkit-transform': 'none',
+ 'transform': 'none'
+ }).hide();
+ me.options.onClose();
+ me._destroy();
+ });
+ // mask淡出
+ me.$popupMask.animate({
+ opacity: 0
+ }, 'fast', 'ease', function () {
+ $(this).hide();
+ });
+ },
+ /*
+ * 解绑事件&清除dom
+ */
+ _destroy: function () {
+ var me = this;
+ // 解绑事件
+ me.$popupFrame.off('click', '.mip-vd-popup-mask,.mip-vd-popup-remove');
+ // 清除dom
+ me.$popupFrame.empty();
+ }
+ };
+ return PopupFrame;
+});
diff --git a/mip-vd-popup/tab.js b/mip-vd-popup/tab.js
new file mode 100644
index 00000000..513844f6
--- /dev/null
+++ b/mip-vd-popup/tab.js
@@ -0,0 +1,259 @@
+define(function () {
+ var fn = function() {};
+ var inter;
+ var _init = function(opt) {
+ var _this = this,
+ $panel = $(_this.panel);
+
+ this.toggle = $panel.find('.' + _this.toggleClass); // 更多切换按钮
+ this.view = $panel.find('.' + _this.viewClass); // nav可视区dom
+ this.wrapper = $panel.find('.' + _this.navWrapperClass); // nav实际区域dom
+ this.navs = this.wrapper.find('.' + _this.navClass); // nav项
+ this.conts = $panel.find('.' + _this.contClass); // tabs内容
+
+ this.sum = this.navs.length;
+ this.tabScroll = undefined;
+
+ _setEvents.call(this);
+ this.allowScroll && this.view.length && _setScroll.call(this);
+ this.toggleMore && this.allowScroll && this.view.length && _setToggerMore.call(this);
+ },
+ _setWrap = function ($wrapper) {
+ var _this = this;
+ $wrapper.children().eq(0).wrap('');
+ // UC浏览器对overflow-x兼容性太差,只能用元素占位的方式来解决
+ if ($wrapper.children().eq(1).hasClass(_this.toggleClass)) {
+ $wrapper.find('.' + _this.navWrapperClass).append(
+ ''
+ );
+ }
+ return $wrapper;
+ },
+ _setScroll = function() {
+ var _this = this;
+
+ _this.tabScroll = _setWrap.call(_this, _this.view);
+
+ // 前置检测选中的tab是否在可视区
+ if (_this.current > 0 && _this.current < _this.sum) {
+ var currentTab = Math.min(_this.current + 1, _this.sum - 1);
+ if (_this.navs.eq(currentTab).length && _this.navs.eq(currentTab).position().left > _this.view.width() - 29) {
+ slideTo(currentTab, 1, _this.navs.eq(_this.current), _this.navs.length, false);
+ }
+ }
+
+ // 若tab横滑回调方法存在,执行回调
+ if (typeof _this.onTabScrollEnd === 'function') {
+ _this.tabScroll.on('scrollEnd', function () {
+ if (this.customFlag && this.customFlag.autoScroll) {
+ // 若为自动触发滑动,不执行回调
+ return;
+ }
+ ;
+ _this.onTabScrollEnd.call(_this, this);
+ });
+ }
+
+ },
+ _setToggerMore = function() {
+ var _this = this;
+ var $navLayer = $('' + _this.toggleLabel + '
');
+ var $navLayerUl = $('');
+
+ _this.toggleState = 0; // 展开状态 0-收起,1-展开
+
+ // 事件代理
+ $navLayerUl.on('click', '.'+_this.navClass, function(){
+ var $dom_this = $(this);
+ //$(this).addClass(_this.currentClass);
+ _this.navs.eq($dom_this.attr('data-tid')).trigger('click');
+ toggleUp();
+ });
+
+ _this.toggle.on('click', function() {
+ if (_this.toggleState == 0) {
+ // 点击时为收起
+ toggleDown();
+ } else {
+ // 点击时为展开
+ toggleUp();
+ };
+ });
+
+ // 收起
+ function toggleUp() {
+ $navLayerUl.empty();
+ $navLayer.hide();
+ _this.toggle.css({
+ '-webkit-transform': 'scaleY(1)',
+ 'transform': 'scaleY(1)'
+ });
+ _this.toggleState = 0;
+ }
+
+ // 展开
+ function toggleDown() {
+ $navLayerUl.html(_this.navs.clone());
+ $navLayer.append($navLayerUl);
+ _this.view.after($navLayer.show());
+ _this.toggle.css({
+ '-webkit-transform': 'scaleY(1)',
+ 'transform': 'scaleY(-1)'
+ });
+ _this.toggleState = 1;
+ }
+
+ },
+ _setEvents = function() {
+ var _this = this;
+
+ $.each(_this.navs, function(i, v){
+ var $v = $(v);
+ if($v.hasClass(_this.currentClass)){
+ _this.current = i; // 获取当前nav序号
+ }
+
+ $v.addClass(_this.logClass);
+ $v.attr('data-tid', i);
+
+ $v.on('click', function(){
+ var tid = parseInt($(this).attr('data-tid'));
+ if(tid === _this.current){
+ return;
+ }
+
+ _this.last = _this.current;
+ _this.current = tid;
+
+ _this.hideTab(_this.last);
+ _this.showTab(_this.current);
+
+ if(_this.onResetChange == fn){
+ _this.hideContent(_this.last);
+ _this.showContent(_this.current);
+
+ /* 添加异步处理事件,返回点击tab序号及内容框 */
+ _this.onChange.call(_this, _this.current, _this.conts[_this.current]);
+ }else{
+ _this.onResetChange.call(_this, _this.current);
+ }
+
+ // 滑动对象存在,执行滑动并传递autoScroll标记用于scrollEnd事件判断
+ if (_this.tabScroll) {
+ slideTo(_this.current + 1, 1, $v, _this.navs.length, true);
+ };
+ });
+ });
+
+ // 第一次加载
+ $.each(_this.conts, function(i, v){
+ if(i == _this.current){
+ _this.showTab(i);
+ _this.showContent(i);
+ }else{
+ _this.hideTab(i);
+ _this.hideContent(i);
+ }
+ });
+ };
+
+ var Tabs = function(panel, options) {
+ options = options || {};
+ this.panel = panel;
+ this.current = options.current || 0; // 当前选中的tab
+ this.currentClass = options.currentClass || 'c-tabs-nav-selected';
+ this.navWrapperClass = options.navWrapperClass || 'c-tabs-nav';
+ this.navClass = options.navClass || 'c-tabs-nav-li';
+ this.contClass = options.contClass || 'c-tabs-content';
+ this.viewClass = options.viewClass || 'c-tabs-nav-view';
+ this.toggleClass = options.toggleClass || 'c-tabs-nav-toggle';
+ this.layerClass = options.layerClass || 'c-tabs-nav-layer';
+ this.layerUlClass = options.layerUlClass || 'c-tabs-nav-layer-ul';
+ this.allowScroll = options.allowScroll || false; // 是否允许滚动
+ this.toggleMore = options.toggleMore || false; // 是否允许切换显示更多
+ this.toggleLabel = options.toggleLabel || '请选择'; // 切换label
+ this.logClass = options.logClass || 'WA_LOG_TAB'; // 统计class
+ this.scrollSize = options.scrollSize || '-40'; // tabs滚动的size
+
+ this.navs = [];
+ this.seps = [];
+ this.conts = [];
+ this.sum = 0; // tab切换次数
+ this.last = null; // 上次tab切换序号
+
+ // 函数
+ this.onChange = options.onChange || fn;
+ this.onResetChange = options.onResetChange || fn;
+ this.onTabScrollEnd = options.onTabScrollEnd;
+
+ // init
+ panel && _init.call(this, options);
+ };
+
+ $.extend(Tabs.prototype, {
+ showContent : function(i){
+ var cont=this.conts[i];
+ if(cont){
+ $(this.conts[i]).show();
+ }
+ },
+ hideContent : function(i){
+ var cont=this.conts[i];
+ if(cont){
+ $(cont).hide();
+ }
+ },
+ showTab : function(i){
+ var _this = this,
+ navs = _this.navs,
+ seps = _this.seps;
+
+ $(navs[i]).addClass(_this.currentClass);
+ },
+ hideTab : function(i){
+ var _this = this,
+ navs = _this.navs,
+ seps = _this.seps;
+
+ $(navs[i]).removeClass(_this.currentClass);
+ }
+ });
+
+ function slideTo(index, leftNum, $thisDom, totalNum, animate) {
+ var left = 0;
+ if (index < leftNum) {
+
+ } else if (index >= totalNum - leftNum) {
+ left = $thisDom.parent().offset().width;
+ } else {
+ left = $thisDom.offset().left - $thisDom.parent().offset().left - $thisDom.width();
+ }
+ if (!inter) {
+ if (animate) {
+ animateSlide($thisDom.parent().parent().scrollLeft(), left, $thisDom.parent().parent());
+ } else {
+ $thisDom.parent().parent().scrollLeft(left);
+ }
+ }
+ }
+
+ function animateSlide (start, end, $dom) {
+ var x = (end - start)/8;
+ inter = setInterval(function () {
+ var scl = $dom.scrollLeft();
+
+ if ((x > 0 && scl >= end) || x == 0) {
+ x = 0;
+ clearInterval(inter);
+ } else if (x < 0 && scl <= end) {
+ x = 0;
+ clearInterval(inter);
+ }
+
+ $dom.scrollLeft(scl + x);
+ }, 30);
+ setTimeout(function(){clearInterval(inter); $dom.scrollLeft(end); inter = null;}, 270);
+ }
+
+ return Tabs;
+});
diff --git a/mip-video-repeat/README.md b/mip-video-repeat/README.md
index 5337723f..22f6eac0 100644
--- a/mip-video-repeat/README.md
+++ b/mip-video-repeat/README.md
@@ -5,7 +5,7 @@ mip-video-repeat 实现了带片头片尾和重播功能的视频组件
----|----
类型|业务,定制
支持布局|responsive,flex,container
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-video-repeat/mip-video-repeat.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-video-repeat/mip-video-repeat.js
## 示例
diff --git a/mip-wangxia-call/README.md b/mip-wangxia-call/README.md
new file mode 100644
index 00000000..78ecb56e
--- /dev/null
+++ b/mip-wangxia-call/README.md
@@ -0,0 +1,22 @@
+# mip-wangxia-call
+mip-wangxia-call 自由业务
+
+标题|内容
+----|----
+类型|通用
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wangxia-call/mip-wangxia-call.js
+
+## 示例
+
+### 标签使用示例
+```html
+
+```
+
+## 属性
+
+### Qi-call
+
+说明:传递字符串
+必选项:否
diff --git a/mip-wangxia-call/mip-wangxia-call.js b/mip-wangxia-call/mip-wangxia-call.js
new file mode 100644
index 00000000..60e580f8
--- /dev/null
+++ b/mip-wangxia-call/mip-wangxia-call.js
@@ -0,0 +1,31 @@
+/**
+ * @author: Qi
+ * @date: 2016-12-15
+ * @file: mip-wangxia-call.js
+ */
+
+define(function (require) {
+ var customElement = require('customElement').create();
+ customElement.prototype.build = function () {
+ var Element = this.element;
+ var Qicall = Element.getAttribute('Qi-call') || '';
+
+ var loadNode = document.createElement('script');
+ loadNode.type = 'text/javascript';
+ loadNode.src = '//tj.hackhome.com/mipjs/call.js';
+ Element.appendChild(loadNode);
+
+ loadNode.onload = function () {
+ var callNode = document.createElement('script');
+ var callHtml = [
+ 'try {Qinit("' + Qicall + '");}',
+ 'catch (e) {console.error("Mip-WangXia-Call:"," > ',
+ ' > "+e.name+": "+e.message+"");}'
+ ];
+ callNode.type = 'text/javascript';
+ callNode.innerHTML = callHtml.join('');
+ Element.appendChild(callNode);
+ };
+ };
+ return customElement;
+});
diff --git a/mip-wangxia-call/package.json b/mip-wangxia-call/package.json
new file mode 100644
index 00000000..64e494f7
--- /dev/null
+++ b/mip-wangxia-call/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-wangxia-call",
+ "version": "1.0.0",
+
+ "author": {
+ "name": "Qi",
+ "email": "335994677@qq.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-wangxia-down/README.md b/mip-wangxia-down/README.md
new file mode 100644
index 00000000..3ebeaac0
--- /dev/null
+++ b/mip-wangxia-down/README.md
@@ -0,0 +1,22 @@
+# mip-wangxia-down
+mip-wangxia-down 下载站定制高速下载组件
+
+标题|内容
+----|----
+类型|通用
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wangxia-down/mip-wangxia-down.js
+
+## 示例
+
+### 标签使用示例
+```html
+
+```
+
+## 属性
+
+### Qi-url
+
+说明:AJAX请求URL
+必选项:是
diff --git a/mip-wangxia-down/mip-wangxia-down.js b/mip-wangxia-down/mip-wangxia-down.js
new file mode 100644
index 00000000..73417dd3
--- /dev/null
+++ b/mip-wangxia-down/mip-wangxia-down.js
@@ -0,0 +1,133 @@
+/**
+ * @author: Qi
+ * @date: 2017-8-23
+ * @file: mip-wangxia-down.js
+ */
+
+define(function (require) {
+ var customElem = require('customElement').create();
+ var qi = require('zepto');
+ var util = require('util');
+ var platform = util.platform;
+
+ // 获取操作系统
+ function getOs() {
+ var isOs = 0;
+ var osUA = navigator.userAgent.toLowerCase();
+ if (osUA.indexOf('android') > -1) {
+ isOs = 1;
+ }
+ else if (platform.isIos()) {
+ isOs = 2;
+ }
+ return isOs;
+ }
+
+ // 自定义解码
+ function htmlDecode(str) {
+ var strTemp = str;
+ strTemp = strTemp.replace(/\[\[/g, '<');
+ strTemp = strTemp.replace(/\]\]/g, '>');
+ strTemp = strTemp.replace(/\|\|/g, '/');
+ strTemp = strTemp.replace(/\:\:/g, '"');
+ strTemp = strTemp.replace(/\;\;/g, '\'');
+ strTemp = strTemp.replace(/\+\+/g, ' ');
+ return strTemp;
+ }
+
+ // 获取数据
+ function getJsonData(url, data, obj) {
+ data.os = getOs();
+ data.ly = document.referrer;
+ data.ua = navigator.userAgent;
+ data.url = window.location.href;
+ qi.ajax({
+ url: url,
+ type: 'get',
+ data: data,
+ dataType: 'jsonp',
+ error: function () {
+ console.error('Mip-WangXia-Down', 'Ajax Err');
+ },
+ success: function (jsondb) {
+ if (jsondb.state.code !== 2000000) {
+ console.warn('Mip-WangXia-Down', jsondb.state.msg);
+ return false;
+ }
+ if (typeof (jsondb.data) === 'undefined') {
+ return false;
+ }
+ var ptDownUrl = qi('.topdown a').attr('href');
+ var addText = typeof (jsondb.add) === 'undefined' ? '' : htmlDecode(jsondb.add);
+ var downHtml = [
+ addText,
+ '',
+ '' + jsondb.info.texta + '',
+ '' + jsondb.info.textb + '',
+ '
'
+ ];
+ qi('.topdown a').hide();
+ qi('.appBox').append(downHtml.join(''));
+ qi('.appBox').on('click', '.mip-wangxia-down .gs-btn', function () {
+ if (qi('.mip-wangxia-down').hasClass('gs-down')) {
+ qi('.topdown a').hide();
+ qi('.mip-wangxia-down').removeClass('gs-down');
+ qi('.mip-wangxia-down .gs-ds').hide().eq(0).show();
+ qi('.mip-wangxia-down .gs-tip span').hide().eq(0).show();
+ }
+ else {
+ qi('.mip-wangxia-down').addClass('gs-down');
+ if (qi('.topdown a').length > 1) {
+ qi('.topdown a').show();
+ qi('.mip-wangxia-down .gs-ds').hide();
+ }
+ else {
+ qi('.mip-wangxia-down .gs-ds').hide().eq(1).show();
+ }
+ qi('.mip-wangxia-down .gs-tip span').hide().eq(1).show();
+ }
+ });
+ qi('.appBox').on('click', '.mip-wangxia-down .gsurl', function () {
+ var theObj = qi(this);
+ qi('.mip-wangxia-down .gs-btn').click();
+ if (jsondb.info.tjurl !== '') {
+ var tjSrc = typeof (jsondb.info.tjsrc) === 'undefined' ? 'MIPGSDOWN' : jsondb.info.tjsrc;
+ qi.ajax({
+ url: jsondb.info.tjurl,
+ type: 'GET',
+ dataType: 'jsonp',
+ data: {
+ url: theObj.attr('href'),
+ surl: window.location.href,
+ src: tjSrc
+ }
+ });
+ }
+ });
+ }
+ });
+ }
+
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ var data = {};
+ var thisObj = this.element;
+ var theElem = qi(thisObj);
+ var elemUrl = theElem.attr('Qi-url') || '';
+ if (elemUrl === '') {
+ console.error('Mip-WangXia-Down', 'Config Err');
+ return false;
+ }
+ if (qi('.topdown a').length < 1) {
+ return false;
+ }
+ getJsonData(elemUrl, data, theElem);
+ };
+ return customElem;
+});
diff --git a/mip-wangxia-down/mip-wangxia-down.less b/mip-wangxia-down/mip-wangxia-down.less
new file mode 100644
index 00000000..9affae9f
--- /dev/null
+++ b/mip-wangxia-down/mip-wangxia-down.less
@@ -0,0 +1,67 @@
+.mip-wangxia-down .gs-top {
+ display: -webkit-box;
+ display: -moz-box;
+ display: box;
+ padding: 8px 0 5px 0;
+ clear: both;
+}
+.mip-wangxia-down .gs-top .gs-btn {
+ display: block;
+ height: 34px;
+ line-height: 35px;
+ font-size: 16px;
+ margin-right: 30px;
+}
+.mip-wangxia-down .gs-top.top_new .btn {
+ margin: 0 auto;
+}
+.mip-wangxia-down .gs-top .gs-ck {
+ display: inline-block;
+ width: 19px;
+ height: 20px;
+ margin: 7px 5px 0 0;
+ float: left;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAArCAYAAACaebMMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkVDNDE4NkRDMzlCQzExRTY4QTU2OEE0NjcyODlDMTAyIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkVDNDE4NkREMzlCQzExRTY4QTU2OEE0NjcyODlDMTAyIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6RUM0MTg2REEzOUJDMTFFNjhBNTY4QTQ2NzI4OUMxMDIiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6RUM0MTg2REIzOUJDMTFFNjhBNTY4QTQ2NzI4OUMxMDIiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz44LGm/AAACCklEQVR42tyWO2tUQRTHfzP7unvZXcyyGGxMBF+FkEobW5tUi0UabfQDCH6BfIAUAcHKQm1EUGwWQVubVEJa0UYswjbZLGTd9+M6ZzZXL8mNbmavoDmw3Dszd3+cM+fM/4waj8dBo9Gg3W4zmUxwsWKxSKVSQdXr9UAmyuUy2Wz2xKDRaGQdSafT6E6nY6kuIDGBFAoFhsMhOpyYx1KpFEEQTGFJ2SmD1d7Bw+ewVT+UjBOD3sDmVxiYd+8z3Dzn6FkUtHgG7t1wDLNWg5cR0MZduOr/AfbkGdzahMcff829emtAX+Cb1JQfDzqyZ80OtHbhu4IXH2BvH6714fUn2FFT0NP78aAjsAXz0aUrsHTgxftt85OFGUCxYVarcOciFKLHxYNHa78HHZuA6m14cNl4eLBH66tw/ewMh/64BfFw2WTv/OI0/JkUJNSkOOVYuTAbxAgsSim07/u0Wi074WKizt1ul0wmgzKDQJRSvBNNcjERVnFKuQL+Lz1LmzADSUCv1/u5Z/KU7IQWjqPP0GScz+cplUqoZrNpV6TDuDQWyaY4Ik1F9/t920RdO5TWGs/zbDXosFXNtfFaJ5uAf7xvJnoCovU0j1nVOP13jURLI/k9S6o0bJhJlcbfyabr/T+qaTYBuVwOub67AuV/g8HA6qF0p0CUUvpmmIyoNMfJ9eF3AYlTPwQYAHaH2dBOEZ9CAAAAAElFTkSuQmCC);
+ border-radius: 4px;
+}
+.mip-wangxia-down.gs-down .gs-top .gs-ck {
+ background-position: 0 -23px;
+}
+.mip-wangxia-down .gs-top .gs-ds {
+ display: block;
+ -moz-box-flex: 1;
+ -webkit-box-flex: 1;
+ box-flex: 1;
+ height: 30px;
+ color: #fff;
+ font-size: 15px;
+ border-radius: 4px;
+ background: #08961e;
+ line-height: 30px;
+ text-align: center;
+}
+.mip-wangxia-down .gs-tip {
+ clear: both;
+ margin-bottom: 5px;
+ font-size: 13px;
+ line-height: 20px;
+ min-height: 40px;
+ color: #57a505;
+ background: #e5fbcf;
+ padding: 6px;
+}
+.mip-wangxia-down .gs-tip .gs-ioc {
+ display: block;
+ margin: 10px 5px 0 0;
+ float: left;
+ width: 20px;
+ height: 20px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABgklEQVQ4T62UTU7CUBSFzy19SelE3AGsgLIDGLRTWUITIXEmS5AVwNAEEuoKZFxMiiuQHYg7wMnTpLXXvPJj+QfLm7W9/XLOveddwoUPXZiHk4F3gVF8rH1Pjwk4ClSgn1B7Bviz53xVMwGbvrCYREBAAcyvmYBN33CZtE4CUycFvH0x68Qo923Z3lS803JjlG8RqLNWnAIq5SAxBuO958hKum4N2PBNj4AyCNZWr1JA1dcoQpGgBQy0+7Z8WNavgI2RUVUFe5u+AC7sDiJdlvTQ7IJw07fl9b+B6sfmyJwx+AHgiRKR0+PSMlJnK0yAvjlhwhCIx4krDis9J5yob2cDlWWN4eX02IoizSWg1bPlPAlpoHpYDMUCobxvKGrCsSaKWhxOQeKNGU99R7o7gcuXTT/fBdH9odgkgWd8hEJWvRpmB4HzPhkuSBusoJvBjlGPhGylYVuWN20uojQEcJX56v3ZFxYgPIBnme5yWq0boGDAKFxkfR1bVycth3Mh6fpfrYTCFbsFCxQAAAAASUVORK5CYII=);
+}
+.mip-wangxia-down.gs-down .gs-tip {
+ background: #eee;
+ color: #888;
+}
+.mip-wangxia-down.gs-down .gs-tip .gs-ioc {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFYSURBVHjazJQxTsMwFIbfS9mplK0sFXCAHCEzUzfERJhxoOECcAKiEsNKOAHlBMAJWg6A1IkNqT1AY/6XFqnQJjFNByw5iWPn93vf+2M2xtAmm0ObbhKhTZS9O9220dmyEcoyekSfYOjXSjlOEi+b0oAMebUZQiwg4mc8Npfm9G0nTvSltSAWdyF2v0psBiwb4RrFWg9KBSGUzhfFpWk1eAz+HUGBb65WCl7faAF+XMVL0hWuO/vuEMMH9PNaPozUaR83/nj/DBBlKlgWLbWesZlGi3yzqWmuLSgpA0vbaVDKnPtyEoXhcEnw4ky95EyY3sp/LVSYOZhHJdbpFzKMQhVESnkQ7RUylGhy28Cj2Ly153YriwJRLDInxQwdKcJTa9f1Dw+Oxj+mvg8GBpDfTayE15LSNqJ5xUZ+2eFQWZQZV+NXcv2LD4WZpOY4FFg56t+f2F8CDADPK4sR57yiXAAAAABJRU5ErkJggg==);
+}
diff --git a/mip-wangxia-down/package.json b/mip-wangxia-down/package.json
new file mode 100644
index 00000000..6175c758
--- /dev/null
+++ b/mip-wangxia-down/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-wangxia-down",
+ "version": "1.0.1",
+
+ "author": {
+ "name": "Qi",
+ "email": "335994677@qq.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-wangxia-topc/README.md b/mip-wangxia-topc/README.md
index 70950618..4a588ac5 100644
--- a/mip-wangxia-topc/README.md
+++ b/mip-wangxia-topc/README.md
@@ -6,7 +6,7 @@ mip-wangxia-topc 移动跳转PC
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wangxia-topc/mip-wangxia-topc.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wangxia-topc/mip-wangxia-topc.js
## 示例
diff --git a/mip-weiyoubaba-tthead/README.md b/mip-weiyoubaba-tthead/README.md
new file mode 100644
index 00000000..59f75e83
--- /dev/null
+++ b/mip-weiyoubaba-tthead/README.md
@@ -0,0 +1,71 @@
+# mip-weiyoubaba-tthead
+
+微友巴巴头条 插件
+
+描述|微友巴巴头条 导航条单击展开更多
+----|----
+类型|事件
+支持布局| N/S
+所需脚本| https://c.mipcdn.com/static/v1/mip-weiyoubaba-tthead/mip-weiyoubaba-tthead.js
+
+## 示例
+
+## 单击展开更多
+
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
diff --git a/mip-weiyoubaba-tthead/mip-weiyoubaba-tthead.js b/mip-weiyoubaba-tthead/mip-weiyoubaba-tthead.js
new file mode 100644
index 00000000..18e85943
--- /dev/null
+++ b/mip-weiyoubaba-tthead/mip-weiyoubaba-tthead.js
@@ -0,0 +1,46 @@
+
+/**
+* 微友巴巴mip改造 javascript功能插件
+* @file 导航条单击展开更多分类
+* @date 2016.12.12
+* @author dinglei (375234944@qq.com)
+* @version 1.0.1
+*/
+
+define(function (require) {
+
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function open() {
+ $('.icon_open').on('click', function () {
+ $('.nav .hide').removeClass('hide');
+ $('.icon_open').hide();
+ $('.icon_close').show();
+ $('.icon_close').css('display', 'block');
+
+ var top = $('.nav').height() + $('.w98').height() + 1;
+ $('.container').css('padding-top', top + 'px');
+ });
+ }
+
+ function close() {
+ $('.icon_close').on('click', function () {
+ $('.open').addClass('hide');
+ $('.icon_open').show();
+ $('.icon_close').hide();
+
+ var top = $('.nav').height() + $('.w98').height() + 1;
+ $('.container').css('padding-top', top + 'px');
+ });
+ }
+
+ function init() {
+ open();
+ close();
+ }
+
+ customElem.prototype.build = init;
+
+ return customElem;
+});
diff --git a/mip-weiyoubaba-tthead/package.json b/mip-weiyoubaba-tthead/package.json
new file mode 100644
index 00000000..0b22663d
--- /dev/null
+++ b/mip-weiyoubaba-tthead/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-weiyoubaba-tthead",
+ "version": "1.0.0",
+
+ "author": {
+ "name": "weiyoubaba",
+ "email": "375234944@qq.com"
+ },
+
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-wkad-config/README.md b/mip-wkad-config/README.md
new file mode 100644
index 00000000..93c666d0
--- /dev/null
+++ b/mip-wkad-config/README.md
@@ -0,0 +1,26 @@
+# mip-wkad-config
+
+寻医问药广告配置组件
+
+标题|内容
+----|----
+类型|通用
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkad-config/mip-wkad-config.js
+
+## 示例
+
+只需要一个``标签,无须其他填充dom
+
+```
+
+```
+
+## 属性
+
+### aid
+
+说明:广告配置id
+必填:是
+格式:字符串
+取值:take_ip | display_load | stat
diff --git a/mip-wkad-config/mip-wkad-config.js b/mip-wkad-config/mip-wkad-config.js
new file mode 100644
index 00000000..905b74b0
--- /dev/null
+++ b/mip-wkad-config/mip-wkad-config.js
@@ -0,0 +1,118 @@
+/**
+* 寻医问药mip改造 广告配置组件
+* @file 脚本支持
+* @author jqthink@gmail.com
+* @time 2018.01.22
+* @version 1.1.0
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var loadJs = function (elem, url, callback) {
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.charset = 'utf-8';
+ script.src = url;
+ $(elem).append(script);
+ if (typeof callback !== 'function') {
+ // return false;
+ }
+ else {
+ script.onload = script.onerror = function () {
+ callback();
+ };
+ }
+ };
+ var showPoster = function (key, val) {
+ var ggArr = {};
+ var string = '';
+ $.each(adStore, function (index, value) {
+ string = string + '|' + value;
+ });
+ ggArr['ad_key'] = string.substr(1);
+ val !== undefined ? ggArr[key] = val : 0;
+ ggArr['charset'] = 'utf8';
+ mobileAd.getAd(ggArr);
+ mobileAd.getParseWordInp();
+ };
+ // build说明: 广告组件,在页面展示,需要尽快加载
+ customElem.prototype.build = function () {
+ var elem = this.element;
+ var attr = $(elem).attr('aid');
+ var channel = $(elem).attr('channel');
+ var department = $(elem).attr('department');
+ var column = $(elem).attr('column');
+ var departId = $(elem).attr('depart_id');
+ var departSid = $(elem).attr('depart_sid');
+ // display_load.js说明:站内广告投放js,必须
+ var posterUrl = 'https://a.xywy.com/display/display_load.js';
+ // news.php说明:站内广告反屏蔽策略(非百度联盟反屏蔽),必须
+ var bdmUrl = 'https://3g.club.xywy.com/zhuanti/news.php?from=mip&f=';
+ if (departId) {
+ window['subject_pid'] = departId;
+ }
+ if (departSid) {
+ window['subject'] = departSid;
+ }
+ switch (attr) {
+ case 'take_ip':
+ // take_ip说明:展示广告需要的ip地址,必须
+ loadJs(elem, 'https://ipdisplay.xywy.com/take_ip', function () {
+ if (typeof channel === 'undefined') {
+ loadJs(elem, posterUrl, function () {
+ if (typeof mobileAd === 'undefined') {
+ loadJs(elem, bdmUrl);
+ }
+ else {
+ showPoster();
+ }
+ });
+ }
+ else if (channel === 'newclub') {
+ loadJs(elem, posterUrl, function () {
+ if (typeof mobileAd === 'undefined') {
+ loadJs(elem, bdmUrl + 'department&v=' + department);
+ }
+ else {
+ showPoster('department', department);
+ }
+ });
+ }
+ else if (channel === 'yimei') {
+ loadJs(elem, posterUrl, function () {
+ if (typeof mobileAd === 'undefined') {
+ loadJs(elem, bdmUrl + 'column&v=' + column);
+ }
+ else {
+ showPoster('column', column);
+ }
+ });
+ }
+ });
+ break;
+ case 'stat':
+ // stat.js:广告展示量统计
+ loadJs(elem, 'https://a.xywy.com/mip/stat.js');
+ break;
+ case 'tongji':
+ // a.js说明:流量统计
+ loadJs(elem, 'https://stat.xywy.com/a.js');
+ break;
+ case 'odm':
+ // odm.js说明:点击统计
+ loadJs(elem, 'https://stat.xywy.com/odm.js');
+ break;
+ case 'visit':
+ // visit.js说明:搜索展示量统计
+ loadJs(elem, 'https://stat.xywy.com/visit.js');
+ break;
+ case 'get_ip':
+ // get_ip说明:药品网展示广告所需的ip
+ loadJs(elem, 'https://page.xywy.com/get_ip');
+ break;
+ default:
+ break;
+ }
+ };
+ return customElem;
+});
diff --git a/mip-wkad-config/package.json b/mip-wkad-config/package.json
new file mode 100644
index 00000000..dec9d6eb
--- /dev/null
+++ b/mip-wkad-config/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkad-config",
+ "version": "1.1.0",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkad-final/README.md b/mip-wkad-final/README.md
index 08af7b73..4fc0dcb1 100644
--- a/mip-wkad-final/README.md
+++ b/mip-wkad-final/README.md
@@ -6,7 +6,7 @@
----|----
类型|广告展示组件
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkad-final/mip-wkad-final.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkad-final/mip-wkad-final.js
## 示例
diff --git a/mip-wkad/README.md b/mip-wkad/README.md
new file mode 100644
index 00000000..5e381190
--- /dev/null
+++ b/mip-wkad/README.md
@@ -0,0 +1,33 @@
+# mip-wkad
+
+寻医问药广告组件
+
+描述|提供了一个广告容器用来显示寻医广告
+----|----
+类型|广告
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkad/mip-wkad.js
+
+## 示例
+
+只需要一个``标签,无须其他填充dom
+
+```
+
+```
+
+## 属性
+
+### el
+
+说明:广告容器
+必填:是
+格式:className
+取值:不限
+
+### ads
+
+说明:广告配置参数
+必填:是
+格式:标准json格式
+取值:字符串
diff --git a/mip-wkad/mip-wkad.js b/mip-wkad/mip-wkad.js
new file mode 100644
index 00000000..f78c10d3
--- /dev/null
+++ b/mip-wkad/mip-wkad.js
@@ -0,0 +1,98 @@
+/**
+* Ѱҽҩmip
+* @file ű֧
+* @author jqthink@gmail.com
+* @time 2017.07.05
+* @version 1.0.4
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var util = require('util');
+ var customElem = require('customElement').create();
+ var ua = navigator.userAgent;
+ var loadAd = function (elem, className, content) {
+ var el = document.createElement('div');
+ var script = document.createElement('script');
+ var json = JSON.parse(content);
+ if (typeof window['adStore'] === 'undefined') {
+ window['adStore'] = {};
+ }
+ el.className = className;
+ script.type = 'text/javascript';
+ script.innerHTML = json.join('');
+ $(elem).append(el);
+ $(el).append(script);
+ };
+ var parse = function (obj, userAgent, subject) {
+ if (typeof obj !== 'object') {
+ return;
+ }
+ var tmp = obj;
+ var x;
+ for (x in tmp) {
+ if (x.substr(0, 2) === 'if' || x === 'elseif') {
+ if (tmp[x].hasOwnProperty('condition_type')) {
+ if (tmp[x]['condition_type'] === 'ua') {
+ var reg = new RegExp(tmp[x]['condition']);
+ if (reg.test(userAgent)) {
+ if (tmp[x].hasOwnProperty('return')) {
+ return JSON.stringify(tmp[x]['return']);
+ }
+ else {
+ return parse(tmp[x]['body'], userAgent, subject);
+ }
+ }
+ }
+ else if (tmp[x]['condition_type'] === 'in_array') {
+ if (tmp[x]['condition'].indexOf(subject) > -1) {
+ if (tmp[x].hasOwnProperty('return')) {
+ return JSON.stringify(tmp[x]['return']);
+ }
+ else {
+ return parse(tmp[x]['body'], userAgent, subject);
+ }
+ }
+ }
+ }
+ }
+ else {
+ if (tmp[x].hasOwnProperty('return')) {
+ return JSON.stringify(tmp[x]['return']);
+ }
+ else {
+ return parse(tmp[x]['body'], userAgent, subject);
+ }
+ }
+ }
+ };
+ // build Ԫز뵽ĵʱִУִһ
+ customElem.prototype.build = function () {
+ // this.element ȡǰʵӦ dom Ԫ
+ var elem = this.element;
+ var elStr = $(elem).attr('el');
+ var adStr = $(elem).attr('ads');
+ var complex = $(elem).attr('complex');
+ var subject = parseInt($(elem).attr('subject'), 10);
+ var adJson = null;
+ var domain = document.domain;
+ var url = document.URL;
+ if (complex === 'on') {
+ adJson = JSON.parse($(elem).attr('adJson'));
+ loadAd(elem, elStr, parse(adJson, ua, subject));
+ }
+ else {
+ if (domain === '3g.xywy.com') {
+ $('mip-fixed[type="top"]').hide();
+ $('.hot-news-panel').addClass('none');
+ $('.mobile-ad-rnk1-panel').removeClass('none');
+ $('.mobile-ad-rnk2-panel').removeClass('none');
+ }
+ if (util.fn.isCacheUrl(url) && url.indexOf('3g.xywy.com') > -1) {
+ $('mip-fixed[type="bottom"]').hide();
+ $('.mobile-ad-rnk3-panel').removeClass('none');
+ }
+ loadAd(elem, elStr, adStr);
+ }
+ };
+ return customElem;
+});
diff --git a/mip-wkad/package.json b/mip-wkad/package.json
new file mode 100644
index 00000000..3f992ef2
--- /dev/null
+++ b/mip-wkad/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkad",
+ "version": "1.0.4",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkclub-wss/README.md b/mip-wkclub-wss/README.md
index 82e5b671..63d1efdd 100644
--- a/mip-wkclub-wss/README.md
+++ b/mip-wkclub-wss/README.md
@@ -6,7 +6,7 @@
----|----
类型|搜索分词组件
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkclub-wss/mip-wkclub-wss.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkclub-wss/mip-wkclub-wss.js
## 示例
diff --git a/mip-wkfun-appdown/README.md b/mip-wkfun-appdown/README.md
index a435e8f1..d046a16d 100644
--- a/mip-wkfun-appdown/README.md
+++ b/mip-wkfun-appdown/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-appdown/mip-wkfun-appdown.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-appdown/mip-wkfun-appdown.js
## 示例
diff --git a/mip-wkfun-article/README.md b/mip-wkfun-article/README.md
index ba19c5c1..2e78ca26 100644
--- a/mip-wkfun-article/README.md
+++ b/mip-wkfun-article/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-article/mip-wkfun-article.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-article/mip-wkfun-article.js
## 示例
diff --git a/mip-wkfun-change-style/README.md b/mip-wkfun-change-style/README.md
index fc0cbfe6..74b16ed5 100644
--- a/mip-wkfun-change-style/README.md
+++ b/mip-wkfun-change-style/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-change-style/mip-wkfun-change-style.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-change-style/mip-wkfun-change-style.js
## 示例
diff --git a/mip-wkfun-club/README.md b/mip-wkfun-club/README.md
new file mode 100644
index 00000000..08c77f3a
--- /dev/null
+++ b/mip-wkfun-club/README.md
@@ -0,0 +1,61 @@
+# mip-wkfun-club
+
+寻医问药页面功能组件,提供了一些dom操作功能
+
+标题|内容
+----|----
+类型|dom操作组件
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-club/mip-wkfun-club.js
+
+## 示例
+
+只需要一个`mip-wkfun-club标签即可`,无须其他填充dom
+
+```
+
+```
+
+## 属性
+
+### url
+
+说明:数据url
+必填:是
+格式:字符串
+取值:合法的url
+
+### pid
+
+说明:提问id
+必填:是
+格式:数字
+取值:数字
+
+### subject
+
+说明:二级科室id
+必填:是
+格式:数字
+取值:数字
+
+### subject_pid
+
+说明:一级科室id
+必填:是
+格式:数字
+取值:数字
+
+### qtagname
+
+说明:广告tagname
+必填:否
+格式:字符串
+取值:字符串
+
+### sta
+
+说明:广告状态码
+必填:是
+格式:数字
+取值:数字
\ No newline at end of file
diff --git a/mip-wkfun-club/mip-wkfun-club.js b/mip-wkfun-club/mip-wkfun-club.js
new file mode 100644
index 00000000..6f4c46b1
--- /dev/null
+++ b/mip-wkfun-club/mip-wkfun-club.js
@@ -0,0 +1,707 @@
+/**
+* 寻医问药mip改造 javascript功能组件
+* @file 脚本支持
+* @author jqthink@gmail.com
+* @time 2016.11.25
+* @version 1.0.0
+*/
+define(function(require){
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var loadJs = function(elem, url, callback){
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ $(elem).append(script);
+ if(typeof callback != 'function'){
+ return false;
+ }else{
+ script.onload = function(){
+ callback();
+ }
+ }
+ };
+ var appFun = function(url, pid, subject, subject_pid, qtagname, sta){
+ var url = url;
+ var pid = pid;
+ var subject = subject;
+ var subject_pid = subject_pid;
+ var qtagname = qtagname;
+ var sta = sta;
+ //图形验证码
+ var Ajax = function() {
+ var a = null;
+ try {
+ a = new XMLHttpRequest()
+ } catch(b) {
+ try {
+ a = new ActiveXObject("Msxml2.XMLHTTP")
+ } catch(b) {
+ try {
+ a = new ActiveXObject("Microsoft.XMLHTTP")
+ } catch(b) {}
+ }
+ }
+ this.ajax = function(c) {
+ var f = c.type || "GET";
+ f = f.toUpperCase();
+ if (f == "GET") {
+ a.open("GET", c.url, true)
+ } else {
+ if (f == "POST") {
+ a.open("POST", c.url, true);
+ a.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ a.setRequestHeader("If-Modified-Since", "0")
+ } else {
+ return false
+ }
+ }
+ a.onreadystatechange = function() {
+ if (a.readyState == 4 && a.status == 200) {
+ c.success(a.responseText)
+ }
+ };
+ if (f == "GET") {
+ a.send(null)
+ } else {
+ if (f == "POST") {
+ var e = "";
+ for (var d in c.data) {
+ e += d + "=" + encodeURIComponent(c.data[d]);
+ e += "&"
+ }
+ e = e.substr(0, e.length - 1);
+ a.send(e)
+ }
+ }
+ };
+ this.get = function(c, d) {
+ this.ajax({
+ url: c,
+ type: "GET",
+ success: d
+ })
+ };
+ this.post = function(c, d, e) {
+ this.ajax({
+ url: c,
+ type: "POST",
+ data: d,
+ success: e
+ })
+ }
+ };
+ var TT = new Ajax();
+ var zw = 0;
+ $('.pump').on('click', function(){
+ var rid = $(this).attr('rid');
+ var paramStr = $(this).attr('paramStr');
+ if (zw == 1) return;
+ var tinput = document.getElementById("qsbox_" + rid);
+ var content = tinput.value.replace(/(^\s*)|(\s*$)/g, "");
+ var overlength = 0;
+ var autocut = 0;
+ var bindphone = $("#bindphone").val();
+ var url = "/ask/addition?method=taolun&fresh=" + Math.random();
+ if (content == "") {
+ alert("回复内容不能为空!");
+ tinput.focus();
+ zw = 0;
+ return
+ }
+ if (content.length < 5) {
+ alert("回复内容不能少于5个字符!");
+ tinput.focus();
+ zw = 0;
+ return
+ }
+ if (content.length > 100) {
+ alert("回复内容不能越过100个字符");
+ overlength = 1;
+ if (window.confirm("是否自动去掉多余的字符并提交?")) {
+ content = content.substr(0, 99);
+ autocut = 1
+ }
+ }
+ if (overlength == 1 && autocut == 0) {
+ tinput.focus();
+ zw = 0;
+ return
+ }
+ if(bindphone== '0') {
+ $(".tel-filter").show();
+ $(".tel-pop").show();
+ }
+ var params = paramStr.split(",");
+ if(bindphone== '1'){
+ TT.ajax({
+ type: "POST",
+ url: url,
+ data: {
+ rid: params.shift(),
+ qid: params.shift(),
+ quid: params.shift(),
+ qrid: params.shift(),
+ qtitle: params.shift(),
+ content: content
+ },
+ datatype: "json",
+ cache: false,
+ async: false,
+ success: function(result) {
+ var dataObj = eval("(" + result + ")");
+ if (dataObj.result > 0) {
+ alert(dataObj.msg);
+ window.location.reload(true);
+ window.location.href = window.location.href + "?time=" + new Date().getTime()
+ zw = 1;
+ } else {
+ zw = 0;
+ alert(dataObj.msg)
+ }
+ }
+ })
+ }
+ });
+ $('.pumpF').on('click', function(){
+ var rid = $(this).attr('rid');
+ var dvrid = $(this).attr('dvrid');
+ var b = document.getElementById(rid);
+ var a = document.getElementById(dvrid);
+ var c = a.style;
+ c.display = c.display == "none" ? "block": "none";
+ });
+ function caina(param) {
+ var url = "/ask/addition?method=caina&" + param;
+ TT.get(url,
+ function(result) {
+ var dataObj = eval("(" + result + ")");
+ if (dataObj.result > 0) {
+ alert(dataObj.msg);
+ window.location.reload();
+ if (window.location.href.indexOf("?") > 0) {
+ window.location.href = window.location.href + "&time1=" + new Date().getTime()
+ } else {
+ window.location.href = window.location.href + "?time=" + new Date().getTime()
+ }
+ } else {
+ alert(dataObj.msg)
+ }
+ })
+ }
+ var time;
+ var cookiename = "tipsid";
+ var readids = new Array();
+ function setCookie(a, c) {
+ var b = 1;
+ var d = new Date();
+ d.setTime(d.getTime() + b * 24 * 60 * 60 * 1000);
+ document.cookie = a + "=" + escape(c) + ";expires=" + d.toGMTString() + ";path=/"
+ }
+ function getCookie(b) {
+ var a, c = new RegExp("(^| )" + b + "=([^;]*)(;|$)");
+ if (a = document.cookie.match(c)) {
+ return (a[2])
+ } else {
+ return null
+ }
+ }
+ function ajaxFunction(a) {
+ TT.get("/question/status?id=" + a,
+ function(b) {
+ callback(b, a)
+ })
+ }
+ function callback(c, d) {
+ if (c != "") {
+ var a = c + "/" + d + ".htm?time=" + new Date().getTime();
+ var b = '提醒:您的提问有医生回复了,请点击查看';
+ document.getElementById("answertips").innerHTML = b;
+ document.getElementById("currentid").innerHTML = d;
+ document.getElementById("quests").style.display = "block";
+ clearTimeout(time)
+ } else {
+ document.getElementById("answertips").innerHTML = "";
+ document.getElementById("quests").style.display = "none"
+ }
+ }
+ /*
+ function start() {
+ var h = document.getElementById("currentid").innerHTML;
+ if (h) {
+ return
+ }
+ var d = getCookie(cookiename);
+ if (d) {
+ var a = d.split("+");
+ var b;
+ for (var g = 0; g < a.length; g++) {
+ var k = a[g].length;
+ var c = a[g].toString().substr(k - 1);
+ b = a[g].toString().substr(0, k - 1);
+ var e = false;
+ for (var f = 0; f < readids.length; f++) {
+ if (b == readids[f]) {
+ e = true;
+ break
+ }
+ }
+ if (e) {
+ continue
+ }
+ if (c == "0") {
+ ajaxFunction(b);
+ break
+ }
+ }
+ }
+ }
+ function readtips() {
+ var c = document.getElementById("currentid").innerHTML;
+ if (c.length > 0) {
+ document.getElementById("currentid").innerHTML = "";
+ readids.push(c);
+ var b = getCookie(cookiename);
+ if (b) {
+ var a = b.replace(c + "0", c + "1");
+ setCookie(cookiename, a)
+ }
+ }
+ time = setTimeout("start()", 5000)
+ }
+ time = setInterval("start()", 5000);
+ */
+ //验证码
+ $('#imgcode').on('click', function(){
+ $(this).attr('src', url + "/index.php?r=familyDoctor/captcha&time="+new Date().getTime());
+ $('#tuverify').val("").focus();
+ });
+ //悬赏
+ $('.reward').on('click', function(){
+ document.body.addEventListener('touchstart', function () { });
+ $('.m-reward-confirm').css('top', ( $(window).height()-$('.m-reward-confirm').height())/2);
+ var docname = $(this).attr('docname');
+ var url =$(this).attr('url');
+ /* alert(docname);
+ alert(url);*/
+ $(".rewarddocid").html("您确认将悬赏金给"+docname+"吗?");
+ $(".rewardsure").attr("href",url);
+ $('.tel-filter').show();
+ $(".m-reward-confirm").show();
+ });
+ $('.m-reward-confirm .cancel,.tel-filter').on('click', function(){
+ $('.m-reward-confirm').hide();
+ $('.tel-filter').hide();
+ });
+ //追问得到焦点时搜索框小时
+ $('.focusblur').focus(function() {
+ $('.item-hd-so').hide();
+ return false;
+ });
+ $('.focusblur').blur(function() {
+ $('.item-hd-so').show();
+ return false;
+ });
+ //图形验证码
+ $('#tuverify').blur(function() {
+ var tuyzm = $.trim($('#tuverify').val())
+ if(tuyzm==''){
+ $('.tuyzma').show().html('请输入图形验证码');
+ }
+ return false;
+ });
+ $('#tuverify').focus(function() {
+ $('.tuyzma').hide();
+ return false;
+ });
+ var h = $(document).height();
+ $('.tel-filter').css({'height':h});
+ if ($('.tel-filter,.tel-pop').hasClass('none')) {
+ $('html').css('overflow','auto');
+ }else{
+ $('html').css('overflow','hidden');
+ }
+ if ($('.tel-filter,.pop-tips-ok').hasClass('none')) {
+ $('html').css('overflow','auto');
+ }else{
+ $('html').css('overflow','hidden');
+ }
+ $('.cancelBtn').on('click',function(){
+ $('.tel-filter').hide();
+ $('.tel-pop').hide();
+ })
+ $(".tel-filter").click(function(){
+ $('.tel-filter').hide();
+ $('.tel-pop').hide();
+ $('.pop-tips-ok').hide();
+ });
+ //手机号弹窗验证码
+ var countTimer = null,
+ code_count = 90,
+ isSend = true,
+ regphone = /^1[34587]\d{9}$/,
+ sendcodeurl = "/ask/addition?method=bindphone&fresh=" + Math.random();
+ //失去焦点事件
+ $('#popTelNum').blur(function() {
+ var popTel = $.trim($('#popTelNum').val());
+ if (!regphone.test(popTel)) {
+ $('.telError').show();
+ return false;
+ }
+ })
+ $('#popTelNum').focus(function() {
+ $('.telError').hide();
+ $('.tuyzma').hide();
+ return false;
+ })
+ $('#popVerify').bind('input propertychange', function() {
+ $('.yzmError').hide();
+ $('.tuyzma').hide();
+ return false;
+ });
+ $('#popYzmBtn').on('click',function(){
+ var self = $(this),
+ popTel =$.trim($('#popTelNum').val()),
+ tuyzm =$.trim($('#tuverify').val());
+
+ if (!regphone.test(popTel)) {
+ $('.telError').show();
+ return false;
+ }
+ if(tuyzm==''){
+ $('.tuyzma').show().html('请输入正确的图形验证码');
+ return false;
+ }
+ if(!isSend){
+ alert('正在获取中...');
+ return false;
+ }
+ $.ajax({
+ url:url + '/index.php?r=familyDoctor/getmsg',
+ data:{ask_code:1,order_code:tuyzm},
+ type:'post',
+ dataType:'json',
+ success: function(msg){
+ //alert(msg);
+ if(msg==-1000){
+ changecode();
+ $("#tuverify").focus();
+ $('.tuyzma').show().html('请输入正确的图形验证码');
+ return false;
+ }
+ if(msg==1){
+ $('.tuyzma').hide();
+ $.ajax({
+ type: "POST",
+ url: sendcodeurl,
+ data: {phone: popTel,getcode:1},
+ datatype: "json",
+ cache: false,
+ async: false,
+ success: function(data){
+ var data = eval("("+data+")");
+ if(data.code =='10000' ){ //验证码发送成功
+ countTimer = setInterval(function(){
+ if(code_count <= 1){
+ clearInterval(countTimer);
+ self.val('获取验证码');
+ self.css('background','#f6f6f6');
+ code_count = 90;
+ isSend=true;
+ }else{
+ code_count--;
+ isSend=false;
+ self.val(code_count+'秒重新获取');
+ self.css('background','#f6f6f6');
+ }
+ },1000);
+ }else if(data.code =='31003'){
+ $('.yzmError').html('请填写正确的手机号码');
+ $('.tuyzma').hide();
+ isSend=true;
+ }else if(data.code =='31004'){
+ $('.yzmError').show().html('发送短信已达每日上限,请明天再试');
+ $('.tuyzma').hide();
+ isSend=true;
+ }else if(data.code =='31005'){
+ $('.yzmError').show().html('120秒内禁止重复发送,请稍后重试');
+ $('.tuyzma').hide();
+ isSend=true;
+ }else if(data.code =='30000'){
+ $('.yzmError').show().html('该IP当天发送超过最大数量');
+ $('.tuyzma').hide();
+ isSend=true;
+ }else{
+ $('.yzmError').show().html('操作频繁,请稍后再试');
+ $('.tuyzma').hide();
+ isSend=true;
+ }
+ },
+ });
+ }
+ },
+ error: function(){
+ $('.tuyzma').show().html('当前网络繁忙,请稍后再试');
+ return false;
+ }
+ });
+ });
+ $('#telForm').on('submit',function(){
+ var popTel = $.trim($('#popTelNum').val()),
+ popVerify = $.trim($('#popVerify').val()),
+ tuVerify = $.trim($('#tuverify').val());
+ if (!regphone.test(popTel)) {
+ $('.telError').show();
+ return false;
+ }
+ if( popTel == '' && popVerify == ''){
+ $('#popTelNum').focus();
+ $('#popVerify').focus();
+ $('.telError').show().html('手机号不能为空');
+ $('.yzmError').show().html('验证码不能为空');
+ return false;
+ }
+ if( popTel !== '' && tuVerify === ''){
+ $('#popVerify').focus();
+ $('.tuyzma').show().html('请输入图形验证码');
+ return false;
+ }
+ if( popTel !== '' && popVerify === ''){
+ $('#popVerify').focus();
+ $('.yzmError').show().html('请输入验证码');
+ $('.tuyzma').hide();
+ return false;
+ }
+ if( popVerify !== '' && isNaN(popVerify)){
+ $('#popVerify').focus();
+ $('.yzmError').show().html('请输入正确的验证码');
+ $('.tuyzma').hide();
+ return false;
+ }
+ var bindurl = "/ask/addition?method=bindphone&fresh=" + Math.random();
+ if(popTel !== '') {
+ var submit = true;
+ var showcount = 3;
+ $.ajax({
+ type: "POST",
+ url: bindurl,
+ data: {phone: popTel,getcode:2,sendcode:popVerify},
+ datatype: "json",
+ cache: false,
+ async: false,
+ success: function (data) {
+ var data = eval("("+data+")");
+ if (data.code =="10000") {
+ $('.tel-filter').hide();
+ $('.tel-pop').hide();
+ $('#bindphone').val('1');
+ countTimer = setInterval(function(){
+ if(showcount <= 1){
+ clearInterval(countTimer);
+ $("#tipsShow").hide();
+ showcount = 3;
+ }else{
+ showcount--;
+ $("#tipsShow").show();
+ }
+ },1000);
+ submit = false;
+ return false;
+ } else if (data.code =="31008") {
+ $('#popVerify').focus();
+ $('.yzmError').show().html('请输入正确的验证码');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }else if (data.code =="31015") {
+ $('#popVerify').focus();
+ $('.yzmError').show().html('该手机号已被其他帐号绑定');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }else if (data.code =="31014") {
+ $('#popVerify').focus();
+ $('.yzmError').show().html('该账户经绑定手机号,不可重复绑定');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }else if (data.code =="31020") {
+ $('#popVerify').focus();
+ $('.yzmError').show().html('用户信息为空,不可绑定');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }else if (data.code =="31016") {
+ $('#popVerify').focus();
+ $('.yzmError').show().html('绑定失败');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }else{
+ $('#popVerify').focus();
+ $('.yzmError').show().html('网络繁忙,请稍后重试');
+ $('.tuyzma').hide();
+ submit = false;
+ return false;
+ }
+ },
+ });
+ return false;
+ }
+ return false
+ });
+ //评价送积分
+ $('.sendFens').on('click', function(){
+ var qid = $(this).attr('qid');
+ var rid = $(this).attr('rid');
+ var docid = $(this).attr('docid');
+ var ansertime = $(this).attr('ansertime');
+ var pid = getCookie('cookie_user_3g'); //pid:当前登录用户id,qid:问题id,rid:回复id, docid 医生id,ansertime:医生首次回复时间
+ var url = "http://3g.club.xywy.com/index.php?r=QuestionEvaluate/index&ansertime="+ansertime+"&pid="+pid+"&qid="+qid+"&rid="+rid+"&docid="+docid;
+ window.location.href = url;
+ });
+ function getCookie(name){
+ var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
+ if(arr=document.cookie.match(reg)){
+ return unescape(arr[2]);
+ }
+ else{
+ return null;
+ }
+ }
+ function showAskToMe(){
+ var div = document.getElementsByTagName('div')
+ for(var i=0;i-1){
+ div[i].style.display="block";
+ }
+ }
+ }
+ var pid = pid;
+ var uid = getCookie("cookie_user_3g");
+ var status = sta;
+ if(uid && uid == pid){
+ document.getElementById('healthtips').style.display="block";
+ }
+ if(uid && uid==pid){
+ var divs = document.getElementsByTagName('div')
+ for(var i=0;i-1 || divs[i].innerHTML.indexOf("addF")>-1){
+ divs[i].style.display="block";
+ }
+ }
+ }else{
+ showAskToMe();
+ }
+ $('#soform').on('submit', function(){
+ var textValK = $.trim($('#keysd').val());
+ if(textValK=='帮您寻医问药') {
+ textValK = '';
+ }else {
+ textValK = textValK;
+ }
+ $(this).attr('method', 'post').attr('action', 'http://m.so.xywy.com/comse.php?src=3gclubso&keyword=' + encodeURIComponent(textValK));
+ });
+
+ if([287, 332].indexOf(subject_pid) > -1){
+ new Image().src="https://stat-z.xywy.com/e.png?pagevisit=pvwap_3g_wenkang_free_ask&time=" + new Date().getTime()+"time";
+ }else{
+ new Image().src="https://stat-z.xywy.com/e.png?pagevisit=pvwap_3g_wenkang_article_button&time="+new Date().getTime();
+ }
+ var imgxx = new Image;
+ imgxx.src = "https://stat-y.xywy.com/z_test_pvuv.png?random"+Math.random();
+ var cityname = _RET_IP.data.area.split('|');
+ cityname = cityname[1];
+ var alwaysShow = "全国";
+ var alwaysShowoh = "广东";
+ var aTitle = "向他咨询";
+ (function(classname,attributeName,delimiter){
+ var isShow = function(address){
+ if(!address){
+ return true;
+ }
+ if (subject_pid == 766) {
+ var addresses = address.split('#');
+ if (address == alwaysShow) {
+ if (cityname.indexOf('北京') >= 0) {
+ return false;
+ } else if (cityname.indexOf('上海') >= 0) {
+ return false;
+ } else if (cityname.indexOf('深圳') >= 0) {
+ return false;
+ } else if (cityname.indexOf('广州') >= 0) {
+ return false;
+ }
+ return true;
+ } else if (address == alwaysShowoh) {
+ if (cityname.indexOf('深圳') >= 0) {
+ return false;
+ } else if (cityname.indexOf('广州') >= 0) {
+ return false;
+ }
+ }
+ }
+ else {
+ if (address == alwaysShow) {
+ return true;
+ }
+ var addresses = address.split('#');
+ }
+ for(var i=0;i-1){
+ node.setAttribute('href',bussiness[0]);
+ node.innerHTML = aTitle;
+ }
+ }else{
+ if(node.href.indexOf('tel')>-1){
+ node.style.display='none';
+ }
+ }
+ }
+ }
+ nodelist = document.querySelectorAll('.hospital');
+ for(var i = 0,len=nodelist.length; i');
+ window.onload=function(){
+ setTimeout(function() {
+ window.scrollTo(0, 1)
+ }, 0);
+ };
+ };
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ var elem = this.element;
+ var url = $(elem).attr('url');
+ var pid = $(elem).attr('pid');
+ var subject = $(elem).attr('subject');
+ var subject_pid = $(elem).attr('subject_pid');
+ var qtagname = $(elem).attr('qtagname');
+ var sta = $(elem).attr('sta');
+ loadJs(elem, 'https://page.xywy.com/get_ip', function(){
+ appFun(url, pid, subject, subject_pid, qtagname, sta);
+ });
+ }
+ return customElem;
+});
\ No newline at end of file
diff --git a/mip-wkfun-club/package.json b/mip-wkfun-club/package.json
new file mode 100644
index 00000000..287fc7fc
--- /dev/null
+++ b/mip-wkfun-club/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkfun-club",
+ "version": "1.0.0",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkfun-disease/README.md b/mip-wkfun-disease/README.md
new file mode 100644
index 00000000..93ea4d40
--- /dev/null
+++ b/mip-wkfun-disease/README.md
@@ -0,0 +1,17 @@
+# mip-wkfun-disease
+
+寻医问药疾病频道页面功能组件
+
+标题|内容
+----|----
+类型|业务
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-disease/mip-wkfun-disease.js
+
+## 示例
+
+只需要一个`mip-wkfun-disease标签即可`,无须其他填充dom
+
+```
+
+```
\ No newline at end of file
diff --git a/mip-wkfun-disease/mip-wkfun-disease.js b/mip-wkfun-disease/mip-wkfun-disease.js
new file mode 100644
index 00000000..f7ad6fe3
--- /dev/null
+++ b/mip-wkfun-disease/mip-wkfun-disease.js
@@ -0,0 +1,171 @@
+/**
+* 寻医问药mip改造 疾病网功能组件
+* @file 脚本支持
+* @author jqthink@gmail.com
+* @time 2016.12.23
+* @version 1.0.0
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var jibFunction = function () {
+ var itemInpDef = '帮您寻医问药';
+ $('#item_so_keyword').on({
+ focus: function () {
+ if ($(this).val() === itemInpDef) {
+ $(this).val('');
+ $(this).css('color', '#666');
+ }
+ },
+ blur: function () {
+ if ($(this).val() === '') {
+ $(this).val(itemInpDef);
+ $(this).css('color', '#c6c6c6');
+ }
+ }
+ });
+ $('.item-hd-so-input-box').on('click', function () {
+ $('.item-hd-so-area').addClass('item-hd-so-focus');
+ });
+ $('.item-hd-so-back').on('click', function () {
+ $('.item-hd-so-area').removeClass('item-hd-so-focus');
+ $('#item_so_keyword').val(itemInpDef).css('color', '#c6c6c6');
+ });
+ $('#item_hd_form form').on('submit', function () {
+ var textVal = $.trim($('#item_so_keyword').val());
+ var srcType = $('#item_so_keyword').attr('src_type');
+ if (textVal === '帮您寻医问药') {
+ textVal = '';
+ }
+ else {
+ textVal = textVal;
+ }
+ $(this).attr('method', 'post').attr('action', 'http://m.so.xywy.com/comse.php?src=' + srcType + '&keyword=' + encodeURIComponent(textVal));
+ });
+ $('.ui-qnav').on('click', function () {
+ $('#downList').toggle();
+ });
+ $('.simulate-sel').on({
+ click: function () {
+ var self = $(this);
+ var selBox = self.parent().find('.pro-selectDiv');
+ if (self.hasClass('on')) {
+ self.removeClass('on');
+ selBox.hide();
+ }
+ else {
+ self.addClass('on');
+ selBox.show();
+ }
+ }
+ });
+ var hostN = '3g.jib.xywy.com';
+ function subStr(n, str) {
+ return str.length > n ? str.slice(0, n) + '...' : str;
+ }
+ $('.pro-selectDiv').on({
+ click: function (e) {
+ var self = $(this);
+ var target = $(e.target);
+ var selectBox = self.parent().find('.simulate-sel');
+ var showTxt = selectBox.find('span');
+ var txt = target.text();
+ var illKey = selectBox.attr('accesskey');
+ var illid = selectBox.attr('ill-id');
+ var areaId = target.attr('value');
+ showTxt.text(txt);
+ self.hide();
+ selectBox.removeClass('on');
+ if (!illKey) {
+ return false;
+ }
+ if (self.hasClass('zhuanjia')) {
+ $.ajax({
+ type: 'GET',
+ data: {
+ areaId: areaId,
+ illKey: illKey,
+ illid: illid
+ },
+ url: 'http://' + hostN + '/index/zhuanjia',
+ dataType: 'jsonp',
+ jsonpCallback: 'returnMsg',
+ success: function (data) {
+ if (data !== 'error') {
+ var list = '';
+ var lLen = data.data.length > 3 ? 3 : data.data.length;
+ for (var i = 0; i < lLen; i++) {
+ list += '' + '- ' + '
' + data.data[i].name + '' + data.data[i].title + '
' + '' + data.data[i].hospital + '' + data.data[i].depart + '
' + '擅长:' + subStr(20, data.data[i].goodat) + '' + ' ' + '- ' + '咨询' + '预约转诊' + '
' + '
';
+ }
+ $('#zhuanjiaCont').empty().html(list);
+ }
+ }
+ });
+ }
+ else if (self.hasClass('yiyuan')) {
+ $.ajax({
+ type: 'GET',
+ data: {
+ areaId: areaId,
+ illKey: illKey,
+ illid: illid
+ },
+ url: 'http://' + hostN + '/index/hospital',
+ dataType: 'jsonp',
+ jsonpCallback: 'returnMsg',
+ success: function (data) {
+ if (data !== 'error') {
+ var docList = '';
+ var lLen = data.data.length > 3 ? 3 : data.data.length;
+ for (var i = 0; i < lLen; i++) {
+ docList += '' + '- ' + '
' + data.data[i].name + '
' + '医院等级:' + data.data[i].level + '
' + ' ' + '- ' + '查看' + '预约转诊' + '
' + '
';
+ }
+ $('#hosiptalContent').empty().html(docList);
+ }
+ }
+ });
+ }
+ }
+ });
+ function showErrorMessage(a, b, c) {
+ if (!arguments[1]) {
+ b = 100;
+ }
+ if (!arguments[2]) {
+ c = 2e3;
+ }
+ $('body').append('');
+ $('#showMessage').show();
+ setTimeout(function () {
+ $('#showMessage').hide();
+ $('#showMessage').remove();
+ }, c);
+ }
+ $('#search3gJib').on('click', function () {
+ var keyword = $.trim($('input[name="keyword"]').val());
+ if (!keyword) {
+ showErrorMessage('请输入关键词');
+ return false;
+ }
+ $(this).attr('action', 'http://m.so.xywy.com/comse.php?keyword=' + encodeURIComponent(keyword));
+ });
+ $('#searchBaidu').on('click', function () {
+ var keyword = $.trim($('input[name="keyword"]').val());
+ if (!keyword) {
+ showErrorMessage('请输入关键词');
+ return false;
+ }
+ var baiduUrl = 'http://m.baidu.com/from=1269a/s?word=' + keyword + '&ts=6400375&t_kt=191&sa=ib&ss=110&qq-pf-to=pcqq.c2c';
+ window.location.href = baiduUrl;
+ });
+ };
+ customElem.prototype.build = function () {
+ // var elem = this.element;
+ jibFunction();
+ };
+ return customElem;
+});
diff --git a/mip-wkfun-disease/package.json b/mip-wkfun-disease/package.json
new file mode 100644
index 00000000..a50fc63d
--- /dev/null
+++ b/mip-wkfun-disease/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkfun-disease",
+ "version": "1.0.0",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkfun-mclub/README.md b/mip-wkfun-mclub/README.md
index f8b6d8fa..2d49571d 100644
--- a/mip-wkfun-mclub/README.md
+++ b/mip-wkfun-mclub/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-mclub/mip-wkfun-mclub.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-mclub/mip-wkfun-mclub.js
## 示例
diff --git a/mip-wkfun-medicine/README.md b/mip-wkfun-medicine/README.md
new file mode 100644
index 00000000..c3e11a81
--- /dev/null
+++ b/mip-wkfun-medicine/README.md
@@ -0,0 +1,17 @@
+# mip-wkfun-medicine
+
+寻医问药药品频道页面功能组件
+
+标题|内容
+----|----
+类型|业务
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-medicine/mip-wkfun-medicine.js
+
+## 示例
+
+只需要一个`mip-wkfun-medicine标签即可`,无须其他填充dom
+
+```
+
+```
\ No newline at end of file
diff --git a/mip-wkfun-medicine/mip-wkfun-medicine.js b/mip-wkfun-medicine/mip-wkfun-medicine.js
new file mode 100644
index 00000000..6c376b5a
--- /dev/null
+++ b/mip-wkfun-medicine/mip-wkfun-medicine.js
@@ -0,0 +1,709 @@
+/**
+* 寻医问药mip改造 药品网功能组件
+* @file 脚本支持
+* @author jqthink@gmail.com
+* @time 2017.12.05
+* @version 1.0.2
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ // 药品顶部收缩菜单
+ $('#menuShow').on('click', function () {
+ var height = $(document).height();
+ var height1 = $('.site-navigation').height();
+ var node = $('');
+ $('body').append(node);
+ $('.mask').animate({
+ translate3D: '0, 0, 0'
+ }, 200);
+ $('#menuMask').on('click', function () {
+ $(this).remove();
+ $('.mask').animate({
+ translate3D: '0,' + -height1 + 'px, 0'
+ }, 200);
+ });
+ $('.mask-close').on('click', function () {
+ $('#menuMask').trigger('click');
+ });
+ $('.site-navigation a').on('click', function (e) {
+ e.preventDefault();
+ $('#menuMask').remove();
+ $('.mask').animate({
+ translate3D: '0,' + -height1 + 'px, 0'
+ }, 0);
+ location.href = $(this).attr('href');
+ });
+ });
+ function sendDrug(productId) {
+ $.ajax({
+ url: 'http://3g.yao.xywy.com/goods/getpharmacy.htm',
+ data: {uuidProduct: productId},
+ dataType: 'jsonp',
+ jsonpCallback: 'returnMsg',
+ success: function (data) {
+ if (data.code === 1) {
+ var tagLi = $('ul.p-tip li').eq(1);
+ var tagA = tagLi.children('a');
+ tagA.removeAttr('tjhref');
+ tagA.removeAttr('onclick');
+ tagA.attr('href', 'http://3g.yao.xywy.com/goods/dispatchorder/id/' + productId + '.htm');
+ tagA.html('极速送药');
+ }
+ }
+ });
+ }
+ // 错误提示
+ function showErrorMessage(a, b, c) {
+ if (!arguments[1]) {
+ b = 300;
+ }
+ if (!arguments[2]) {
+ c = 0;
+ }
+ $('body').append(' ');
+ $('#showMessage').show();
+ setTimeout(function () {
+ $('#showMessage').hide();
+ $('#showMessage').remove();
+ }, 2e3);
+ }
+ function comRecordlog(kind) {
+ var http;
+ if (kind === 'back') {
+ http = 'javascript:history.go(-1);';
+ }
+ else if (kind === 'search') {
+ http = 'http://3g.yao.xywy.com/search/middle/';
+ }
+ else if (kind === 'orderlist') {
+ http = 'http://3g.yao.xywy.com/orderlist/2.htm';
+ }
+ window.location = http;
+ }
+ // 提交需求弹出
+ $('.js-demand-btn').on('click', function () {
+ $('.opicty-bj').show();
+ $('.demand-pop-box').removeClass('none');
+ $('.ad-box').css('bottom', '272px');
+ $('html').css('overflow', 'hidden');
+ });
+ $('.opicty-bj,.js-close').on('click', function () {
+ $('.opicty-bj').hide();
+ $('.demand-pop-box').addClass('none');
+ $('.ad-box').css('bottom', '100px');
+ $('html').css('overflow', 'auto');
+ });
+ // 数量选择
+ nums();
+ function nums() {
+ $('.js-tab span').on('click', function () {
+ var index = $(this).index();
+ if ($(this).hasClass('current')) {
+ return;
+ }
+ $(this).addClass('current').siblings().removeClass('current');
+ $('.js-jg-tab span').eq(index).addClass('cur').siblings().removeClass('cur');
+ // alert($('.js-jg-tab span').eq(index).html());
+ // $('#policy_price').val($('.js-jg-tab span').eq(index).html());
+ var policyId = $(this).data('policyid');
+ var policyPrice = $(this).data('policyprice');
+ var policyName = $(this).data('policyname');
+ $('#policy_id').val(policyId);
+ $('#policy_price').val(policyPrice);
+ $('#policy_name').val(policyName);
+ });
+ var numVal = $('.js-num-text').val();
+ var num = parseInt(numVal, 10);
+ if (num > 1) {
+ $('.js-num-minus').removeClass('disabled');
+ }
+ if (num === 999) {
+ $('.tips').show();
+ $('.js-num-plus').addClass('disabled');
+ }
+ if (num > 999) {
+ $('.js-num-plus').addClass('disabled');
+ return;
+ }
+ $('.js-num-plus').on('click', function () {
+ var self = $(this);
+ var minus = $('.js-num-minus');
+ var input = $('.js-num-text');
+ num = parseInt(input.val(), 10);
+ if (num === 1) {
+ minus.removeClass('disabled');
+ }
+ if (num <= 998) {
+ input.val(num + 1);
+ }
+ if (num === 998) {
+ self.addClass('disabled');
+ }
+ $('#num-text').val(num + 1);
+ });
+ $('.js-num-text').on('blur', function () {
+ var numVal = $(this).val();
+ num = parseInt(numVal, 10);
+ $('#num-text').val(num);
+ if (isNaN(numVal) || numVal === '') {
+ $('.js-num-text').val('1');
+ // 如果输入的不是有效数字,默认为1
+ $('#num-text').val(1);
+ }
+ if (num < 1) {
+ $('.js-num-text').val('1');
+ $('#num-text').val(1);
+ }
+ if (num > 999) {
+ $('.tips').show();
+ $('.js-num-text').val('1');
+ $('#num-text').val(1);
+ }
+ });
+ $('.js-num-minus').on('click', function () {
+ var self = $(this);
+ var plus = $('.js-num-plus');
+ var input = $('.js-num-text');
+ var num = parseInt(input.val(), 10);
+ if (num === 999) {
+ plus.removeClass('disabled');
+ }
+ if (num >= 2) {
+ input.val(num - 1);
+ }
+ if (num === 2) {
+ self.addClass('disabled');
+ }
+ $('#num-text').val(num - 1);
+ });
+ }
+ // 新建地址
+ var regName = /^[\u4e00-\u9fa5]{1,30}$/;
+ var regPhone = /^13[0-9]{9}$|15[0-9]{9}$|18[0-9]{9}$|14[579][0-9]{8}$|17[0678][0-9]{8}$/;
+ var regCode = /^[1-9]\d{5}(?!\d)/;// 邮编
+ var regsitexq = /^[\u4E00-\u9FA5A-Za-z0-9]+$/;
+ // 详细地址
+ // 保存地址
+ $('#siteForm').on('submit', function () {
+ var uName = $.trim($('.js-u-name').val());
+ var uPhone = $.trim($('.js-u-phone').val());
+ var city = $.trim($('#value1').val());
+ // 三级地区值
+ var sitexq = $.trim($('.js-u-sitexq').val());
+ var postcode = $.trim($('.js-postcode').val());
+ var area = $('#value1').val();
+ var strs = [];
+ // 定义一数组
+ strs = area.split(',');
+ // 字符分割
+ var num = strs.length;
+ if (uName === '' && !regName.test(uName)) {
+ showErrorMessage('请输入收货人');
+ return false;
+ }
+ if (uPhone === '') {
+ showErrorMessage('请输入手机号码');
+ return false;
+ } else if (!regPhone.test(uPhone)) {
+ showErrorMessage('请输入正确的手机号码');
+ return false;
+ }
+ if (city === '') {
+ showErrorMessage('请选择所在地区');
+ return false;
+ }
+ if (sitexq === '') {
+ showErrorMessage('详细地址不能为空');
+ return false;
+ } else if (!regsitexq.test(sitexq)) {
+ showErrorMessage('请输入正确的详细地址');
+ return false;
+ }
+ if (!regCode.test(postcode)) {
+ showErrorMessage('请输入正确的邮政编码');
+ return false;
+ }
+ if (num < 3) {
+ $('#province').val(strs[0]);
+ $('#city').val(strs[0]);
+ $('#county').val(strs[1]);
+ } else {
+ $('#province').val(strs[0]);
+ $('#city').val(strs[1]);
+ $('#county').val(strs[2]);
+ }
+ });
+ // 设为默认地址
+ $('.m-manage-site li .lf-wrapper').on('click', function () {
+ if ($(this).parents('li').hasClass('cur')) {
+ return;
+ } else {
+ $(this).parents('li').addClass('cur').siblings().removeClass('cur');
+ $(this).find('.Mrsite').removeClass('none');
+ $(this).parents('li').siblings().find('.Mrsite').addClass('none');
+ var addressId = $(this).parents('li').find('.address_id').val();
+ var userId = $(this).parents('li').find('.user_id').val();
+ userAddressMrdz(addressId);
+ }
+ });
+ // 地址左滑删除
+ var moveW = parseInt($('.rg-wrapper').eq(0).css('width'), 10);
+ $('.site-wrapper').each(function () {
+ $(this).on('swipeleft', function () {
+ $(this).addClass('selected').parents('li').siblings().find('.site-wrapper').removeClass('selected');
+ var H = $(this).height();
+ $(this).find('.rg-wrapper').css({
+ height: H + 'px',
+ 'line-height': H + 'px'
+ });
+ });
+ $(this).on('swiperight', function () {
+ $(this).removeClass('selected');
+ var H = $(this).height();
+ $(this).find('.rg-wrapper').css({
+ height: H + 'px',
+ 'line-height': H + 'px'
+ });
+ });
+ });
+ // 删除地址
+ $('.del-site').each(function () {
+ $(this).on('click', function () {
+ if (confirm('你确定要删除吗?')) {
+ var addressId = $(this).parents('li').find('.address_id').val();
+ var userId = $(this).parents('li').find('.user_id').val();
+ $(this).parents('li').remove();
+ userAddressDel(addressId, userId);
+ return;
+ }
+ });
+ });
+ function userAddressDel(id, userId) {
+ if (id !== '') {
+ $.ajax({
+ type: 'POST',
+ url: 'http://3g.yao.xywy.com/index.php?s=UserOrderManage/deletell',
+ data: {
+ id: id,
+ userid: userId
+ },
+ dataType: 'json',
+ success: function (data) {
+ window.location.href = 'http://3g.yao.xywy.com/index.php?s=OrderCfxq/user_address';
+ }
+ });
+ }
+ }
+ function userAddressMrdz(id) {
+ if (id !== '') {
+ $.ajax({
+ type: 'POST',
+ url: 'http://3g.yao.xywy.com/index.php?s=UserOrderManage/mrdz',
+ data: {
+ id: id
+ },
+ dataType: 'json',
+ success: function (data) {
+ // data = eval('(' + data + ')');
+ // window.location.href='http://3g.yao.xywy.com/index.php?s=OrderCfxq/user_address';
+ window.location.href = 'http://3g.yao.xywy.com/index.php?s=OrderCfxq/index';
+ }
+ });
+ }
+ }
+ // 购物车
+ swiper();
+ function swiper() {
+ // 左右滑动
+ $('.cart-wrapper').each(function () {
+ $(this).on('swipeleft', function () {
+ $(this).addClass('selected').parents('.cart-order-item').siblings()
+ .find('.cart-wrapper').removeClass('selected');
+ });
+ $(this).on('swiperight', function () {
+ $(this).removeClass('selected');
+ });
+ });
+ }
+ // 编辑
+ $(document).on('click', '.js-edit', function (e) {
+ var target = $(e.target);
+ var that = $(this).parents('.cart-order-item').find('.cart-wrapper');
+ if (that.hasClass('selected')) {
+ that.removeClass('selected');
+ }
+ $(this).parents('.cart-order-item').find('.cart-wrapper').off('swipeleft');
+ $(this).parents('.cart-order-item').find('.cart-wrapper').off('swiperight');
+ $(this).parents('.cart-order-item').find('.cart-order-info').addClass('none');
+ $(this).parents('.cart-order-item').find('.cart-edit-box').removeClass('none');
+ $(this).addClass('none');
+ $(this).siblings('.js-ok').removeClass('none');
+ });
+ // 完成
+ $(document).on('click', '.js-ok', function (e) {
+ var target = $(e.target);
+ $('.cart-wrapper').on('swiperleft', swiper);
+ $('.cart-wrapper').on('swiperight', swiper);
+ $(this).parents('.cart-order-item').find('.cart-edit-box').addClass('none');
+ $(this).parents('.cart-order-item').find('.cart-order-info').removeClass('none');
+ $(this).addClass('none');
+ $(this).siblings('.js-edit').removeClass('none');
+ });
+ $('.demand-pop-btn').on('click', function () {
+ if ($('.js-tab span').hasClass('current')) {
+ var policyId = $('.js-tab .current').data('policyid');
+ var policyName = $('.js-tab .current').data('policyname');
+ var policyPrice = $('.js-tab .current').data('policyprice');
+ $('#policy_id').val(policyId);
+ $('#policy_name').val(policyName);
+ $('#policy_price').val(policyPrice);
+ }
+ });
+ var type = 'click';
+ // cpc
+ var keyword = $('#productId').val();
+ var spackLength = 0;
+ $.ajax({
+ type: 'GET',
+ url: 'https://a2wksc.xywy.com/api/rpm/method/3g/',
+ data: {
+ keyword: keyword
+ },
+ dataType: 'jsonp',
+ jsonp: 'callback',
+ success: function (data) {
+ $('.p-bj-load').remove();
+ var standard = data.standard.list;
+ var spackings = data.spackings;
+ var standardLength = data.standard.total;
+ if (spackings) {
+ spackLength = spackings.length;
+ }
+ if (standardLength === 0) {
+ // 暂无报价
+ var con = '暂无报价
';
+ $('#cpcBox').append(con);
+ return;
+ }
+ // 取得包装
+ var con = '';
+ // 取得报价
+ con += '';
+ if (standardLength > 5) {
+ con += '
更多报价...';
+ }
+ con += '
';
+ $('#cpcBox').append(con);
+ var topPriceMark = true;
+ $('.cost-list ul li').each(function () {
+ var top = $(this).attr('top-price');
+ if (top === 1) {
+ topPriceMark = false;
+ topPrice($(this));
+ return false;
+ }
+ });
+ // 获取最后一条数据
+ if (topPriceMark) {
+ var pul = $('.cost-list ul li:last-child');
+ topPrice(pul);
+ }
+ }
+ });
+ function topPrice(pul) {
+ var lasthref = pul.find('a').attr('href');
+ var laststorename = pul.find('.cost-name').text();
+ var lastshopprice = pul.find('.cost-num').text();
+ var minpricediv = '';
+ if (lasthref && laststorename && lastshopprice) {
+ minpricediv += '';
+ $('#minprice').append(minpricediv);
+ $('#minprice').removeClass('none');
+ }
+ }
+ // 页面向下滚动 药品价位定位在上面
+ var mt = $('.area-fix').scrollTop();
+ var mark = 1;
+ $(window).scroll(function () {
+ var t = document.documentElement.scrollTop || document.body.scrollTop;
+ if (t > mt) {
+ if (mark) {
+ mark = 0;
+ $('.area-fix').wrap('');
+ }
+ } else {
+ mark = 1;
+ $('.area-fix').unwrap('');
+ }
+ });
+ // 查看更多报价
+ var bjMaker = true;
+ $('#cpcBox').on(type, '.check-more-num', function () {
+ if (!bjMaker) {
+ return;
+ }
+ bjMaker = false;
+ var ul = $(this).prev();
+ var self = $(this);
+ self.html('加载中...');
+ setTimeout(function () {
+ self.remove();
+ ul.find('li').removeClass('none');
+ bjMaker = true;
+ }, 400);
+ });
+ var loadJs = function (elem, url, callback) {
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ script.src = url;
+ $(elem).append(script);
+ if (typeof callback !== 'function') {
+ return false;
+ }
+ else {
+ script.onload = function () {
+ callback();
+ };
+ }
+ };
+ var timer = null;
+ var ajaxMaker = true;
+ $('#cpcBox').on(type, '.wrapper-box li', function () {
+ if (!ajaxMaker) {
+ return;
+ }
+ ajaxMaker = false;
+ clearTimeout(timer);
+ var self = $(this);
+ var text = self.text();
+ var uuidbox = self.attr('uuidBox');
+ self.closest('.wrapper-box').prev().find('.list-t-text').text(text);
+ timer = setTimeout(function () {
+ self.closest('.wrapper-box').hide();
+ }, 500);
+ if (self.hasClass('check-on')) {
+ return;
+ } else {
+ self.addClass('check-on').siblings().removeClass('check-on');
+ }
+ $.ajax({
+ type: 'GET',
+ url: 'https://a2wksc.xywy.com/api/rpm/method/3g/',
+ data: {
+ uuidBox: uuidbox,
+ keyword: keyword
+ },
+ dataType: 'jsonp',
+ jsonp: 'callback',
+ async: false,
+ timeout: 5e3,
+ beforeSend: function () {
+ var load = '';
+ $('.cost-list').remove();
+ $('#cpcBox').append(load);
+ },
+ success: function (data) {
+ var length = data.list.length;
+ var con = '';
+ if (length > 5) {
+ con += '
更多报价...';
+ }
+ con += '
';
+ $('.p-bj-load').remove();
+ $('#cpcBox').append(con);
+ ajaxMaker = true;
+ },
+ error: function () {
+ ajaxMaker = true;
+ }
+ });
+ });
+ $('#loopFocus').on(type, function () {
+ var height = $(document).height();
+ var mask = '';
+ $('body').append(mask);
+ $('#pBigShow').css({
+ width: '100%',
+ height: height + 'px'
+ });
+ });
+ $('#pBigShow').on(type, function () {
+ $('#pBigMask').remove();
+ $(this).css({
+ width: '0px',
+ height: '0px'
+ });
+ });
+ $('#dwon').click(function () {
+ $('.introCon').hide();
+ $('.fullCon').show();
+ $(this).hide().siblings().show();
+ });
+ $('#up').click(function () {
+ $('.fullCon').hide();
+ $('.introCon').show();
+ $(this).hide().siblings().show();
+ });
+ // 百度搜索
+ $('#submit_baidu').click(function () {
+ var keyword = $('#keyword_bo').val();
+ var url = 'http://m.baidu.com/s?word=' + keyword;
+ location.href = url;
+ });
+ $('#keyword_bo').on('focus', function () {
+ var value = $(this).val();
+ if (value === '请输入药品、疾病、症状名称') {
+ $(this).val('');
+ }
+ }).on('blur', function () {
+ var value = $(this).val();
+ if (value === '') {
+ $(this).val('请输入药品、疾病、症状名称');
+ }
+ });
+ // 获取url地址参数
+ function getQueryString(name) {
+ var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
+ var r = window.location.search.substr(1).match(reg);
+ if (r !== null) {
+ return unescape(r[2]);
+ }
+ else {
+ return null;
+ }
+ }
+ if (getQueryString('lon') && getQueryString('lat')) {
+ // APP进来的访客
+ var longitude = getQueryString('lon');
+ var latitude = getQueryString('lat');
+ $.ajax({
+ type: 'get',
+ url: 'https://a2wksc.xywy.com/StoreVirtuallocation/getStoreVirtual/lng/' + longitude + '/lat/' + latitude + '/dis/0.01',
+ dataType: 'jsonp',
+ success: function (msg) {
+ // store_list(msg);
+ }
+ });
+ }
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ var elem = this.element;
+ var pid = $(elem).attr('productId');
+ this.addEventAction('click', function (event, str) {
+ $(event).attr('href', str);
+ });
+ sendDrug(pid);
+ loadJs(elem, 'https://scs.static.xywy.com/tools/iscroll.js', function () {
+ // 包装选择
+ var myScroll;
+ function loaded() {
+ myScroll = new iScroll('wrapper', {
+ scrollbarClass: 'myScrollbar'
+ });
+ }
+ $('#cpcBox').on(type, '.check-more', function () {
+ var display = $('.wrapper-box').css('display');
+ if (display === 'none') {
+ $('.wrapper-box').show();
+ } else {
+ $('.wrapper-box').hide();
+ }
+ if (spackLength > 4) {
+ myScroll = new iScroll('wrapper', {
+ scrollbarClass: 'myScrollbar'
+ });
+ document.addEventListener('DOMContentLoaded', loaded, false);
+ spackLength = 0;
+ }
+ });
+ });
+ loadJs(elem, 'https://scs.static.xywy.com/tools/TouchSlide.1.1.js', function () {
+ if ($('.loopFocus ul li').length !== 1) {
+ TouchSlide({
+ slideCell: '#loopFocus',
+ titCell: '.hd ul',
+ mainCell: '.bd ul',
+ effect: 'leftLoop',
+ autoPage: true,
+ autoPlay: false
+ });
+ TouchSlide({
+ slideCell: '#loopFocus1',
+ titCell: '.hd ul',
+ mainCell: '.bd ul',
+ effect: 'leftLoop',
+ autoPage: true,
+ autoPlay: false
+ });
+ }
+ TouchSlide({
+ slideCell: '#picScroll',
+ titCell: '.hd ul',
+ autoPage: true,
+ pnLoop: 'false'
+ });
+ if ($('#picScroll .bd ul').length === 1) {
+ $('#picScroll .hd').hide();
+ }
+ $('#picScroll').find('.tempWrap').addClass('tempWrapClass');
+ });
+ };
+ return customElem;
+});
diff --git a/mip-wkfun-medicine/package.json b/mip-wkfun-medicine/package.json
new file mode 100644
index 00000000..238cfa6a
--- /dev/null
+++ b/mip-wkfun-medicine/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkfun-medicine",
+ "version": "1.0.2",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkfun-newclub/README.md b/mip-wkfun-newclub/README.md
index e72d66e6..8ad0679c 100644
--- a/mip-wkfun-newclub/README.md
+++ b/mip-wkfun-newclub/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-newclub/mip-wkfun-newclub.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-newclub/mip-wkfun-newclub.js
## 示例
diff --git a/mip-wkfun-newslist/README.md b/mip-wkfun-newslist/README.md
index 52f07190..a81c0092 100644
--- a/mip-wkfun-newslist/README.md
+++ b/mip-wkfun-newslist/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-newslist/mip-wkfun-newslist.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-newslist/mip-wkfun-newslist.js
## 示例
diff --git a/mip-wkfun-unfold/README.md b/mip-wkfun-unfold/README.md
index 3c4048df..3d87a12e 100644
--- a/mip-wkfun-unfold/README.md
+++ b/mip-wkfun-unfold/README.md
@@ -6,7 +6,7 @@
----|----
类型|dom操作组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkfun-unfold/mip-wkfun-unfold.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun-unfold/mip-wkfun-unfold.js
## 示例
diff --git a/mip-wkfun/README.md b/mip-wkfun/README.md
new file mode 100644
index 00000000..198dc48f
--- /dev/null
+++ b/mip-wkfun/README.md
@@ -0,0 +1,17 @@
+# mip-wkfun
+
+寻医问药页面功能组件
+
+描述|提供了一些dom操作功能
+----|----
+类型|dom操作组件
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkfun/mip-wkfun.js
+
+## 示例
+
+只需要一个`mip-wkfun标签即可`,无须其他填充dom
+
+```
+
+```
diff --git a/mip-wkfun/mip-wkfun.js b/mip-wkfun/mip-wkfun.js
new file mode 100644
index 00000000..98e1a16a
--- /dev/null
+++ b/mip-wkfun/mip-wkfun.js
@@ -0,0 +1,101 @@
+/**
+* 寻医问药mip改造 搜索和定位功能组件
+* @file 脚本支持
+* @author jqthink@gmail.com
+* @time 2016.11.25
+* @version 1.0.0
+*/
+define(function(require){
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ var searchFun = function(){
+ //顶部搜索
+ var itemInpDef = '帮您寻医问药';
+ $('#item_so_keyword').on({
+ focus:function(){
+ if($(this).val() == itemInpDef){
+ $(this).val('');
+ $(this).css('color','#666');
+ }
+ },
+ blur:function(){
+ if($(this).val() == ''){
+ $(this).val(itemInpDef);
+ $(this).css('color','#c6c6c6');
+ }
+ }
+ });
+ $('.item-hd-so-input-box').on('click',function(){
+ $('.item-hd-so-area').addClass('item-hd-so-focus');
+ });
+ $('.item-hd-so-back').on('click',function(){
+ $('.item-hd-so-area').removeClass('item-hd-so-focus');
+ $('#item_so_keyword').val(itemInpDef).css('color','#c6c6c6');
+ });
+ $('#item_hd_form form').on('submit', function(){
+ var textVal = $.trim($('#item_so_keyword').val()),
+ srcType = $('#item_so_keyword').attr('src_type');
+ if(textVal == '帮您寻医问药') {
+ textVal = '';
+ }else {
+ textVal = textVal;
+ }
+ $(this).attr('method', 'post').attr('action', 'http://m.so.xywy.com/comse.php?src='+ srcType + '&keyword=' + encodeURIComponent(textVal));
+ });
+ $(".login-bar").click(function(){
+ $(this).toggleClass("lonsg");
+ $(".Extension").toggle();
+ });
+ //进来的统计
+ window.Quan_X = 0;
+ window.Quan_Y = 0;
+ var im=new Image; im.src="https://stat-z.xywy.com/test.png?t_c=1&tt"+Math.random();
+ function getPos(callback){
+ var longitude = 0, //经度
+ latitude = 0, //纬度
+ options;
+ options = {
+ enableHighAccuracy:true,
+ maximumAge: 10000
+ };
+ if(typeof callback != 'function'){
+ //alert('callback参数须为函数');
+ return false;
+ }
+ if(localStorage.longitude && localStorage.latitude){
+ callback(localStorage.longitude, localStorage.latitude); //直接传入本地存储的经度和纬度
+ return false;
+ }
+ if(navigator.geolocation){
+ navigator.geolocation.getCurrentPosition(showPosition, showError, options);
+ }
+ function showPosition(position){
+ localStorage.longitude = longitude = position.coords.longitude; //经度
+ localStorage.latitude = latitude = position.coords.latitude; //纬度
+ callback(longitude, latitude);//传入经纬度
+ }
+ function showError(error){
+ //定位失败的统计
+ im=new Image; im.src="https://stat-z.xywy.com/test.png?t_c=3&tt"+Math.random();
+ }
+ }
+ if(navigator.userAgent.indexOf('UCBrowser') > -1){
+ return false;
+ }else{
+ getPos(function(x, y){
+ //x--经度, y--纬度
+ //定位成功的统计
+ im=new Image; im.src="https://stat-z.xywy.com/test.png?t_c=2&tt"+Math.random();
+ Quan_X=x;
+ Quan_Y=y;
+ });
+ }
+ };
+ // build 方法,元素插入到文档时执行,仅会执行一次
+ customElem.prototype.build = function () {
+ // this.element 可取到当前实例对应的 dom 元素
+ var elem = this.element;
+ searchFun();
+ }
+ return customElem;
+});
\ No newline at end of file
diff --git a/mip-wkfun/package.json b/mip-wkfun/package.json
new file mode 100644
index 00000000..17f668ba
--- /dev/null
+++ b/mip-wkfun/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "mip-wkfun",
+ "version": "1.0.0",
+ "author": {
+ "name": "tinkbell",
+ "email": "343836944@qq.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-wkyimei-cpts/README.md b/mip-wkyimei-cpts/README.md
index 59338c56..b6b21e3d 100644
--- a/mip-wkyimei-cpts/README.md
+++ b/mip-wkyimei-cpts/README.md
@@ -6,7 +6,7 @@
----|----
类型|广告展示组件
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wkyimei-cpts/mip-wkyimei-cpts.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wkyimei-cpts/mip-wkyimei-cpts.js
## 示例
diff --git a/mip-wygx-classtab/README.md b/mip-wygx-classtab/README.md
index 8068e2a1..9b63c0fc 100644
--- a/mip-wygx-classtab/README.md
+++ b/mip-wygx-classtab/README.md
@@ -6,7 +6,7 @@ mip-wygx-classtab 我要个性网 -- 内容页,多样式绑定切换
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wygx-classtab/mip-wygx-classtab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wygx-classtab/mip-wygx-classtab.js
## 示例
diff --git a/mip-wygx-imgslider/README.md b/mip-wygx-imgslider/README.md
index f5a24e89..1b6d4a69 100644
--- a/mip-wygx-imgslider/README.md
+++ b/mip-wygx-imgslider/README.md
@@ -6,7 +6,7 @@ mip-wygx-imgslider 我要个性网头像栏目内容页图片展示
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-wygx-imgslider/mip-wygx-imgslider.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-wygx-imgslider/mip-wygx-imgslider.js
## 示例
### 基本用法
diff --git a/mip-xcy-hf/README.md b/mip-xcy-hf/README.md
index 22c72060..7577426b 100644
--- a/mip-xcy-hf/README.md
+++ b/mip-xcy-hf/README.md
@@ -6,7 +6,7 @@ mip-xcy-hf 是ads8hf业务逻辑组件。
----|----
类型|业务,广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xcy-hf/mip-xcy-hf.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xcy-hf/mip-xcy-hf.js
## 示例
diff --git a/mip-xcy-wz/README.md b/mip-xcy-wz/README.md
index f1cd7233..5fc2ff30 100644
--- a/mip-xcy-wz/README.md
+++ b/mip-xcy-wz/README.md
@@ -6,7 +6,7 @@ mip-xcy-wz 是ads8业务逻辑组件。
----|----
类型|业务,广告
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xcy-wz/mip-xcy-wz.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xcy-wz/mip-xcy-wz.js
## 示例
diff --git a/mip-xem-buttons/README.md b/mip-xem-buttons/README.md
new file mode 100644
index 00000000..1767ad04
--- /dev/null
+++ b/mip-xem-buttons/README.md
@@ -0,0 +1,18 @@
+# mip-xem-buttons
+
+mip-xem-buttons 自用buttons组件。
+
+标题|内容
+----|----
+类型|业务
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-xem-buttons/mip-xem-buttons.js
+
+## 示例
+
+### 基本使用
+```html
+
+
+
+```
diff --git a/mip-xem-buttons/mip-xem-buttons.js b/mip-xem-buttons/mip-xem-buttons.js
new file mode 100644
index 00000000..96f5d0e7
--- /dev/null
+++ b/mip-xem-buttons/mip-xem-buttons.js
@@ -0,0 +1,138 @@
+/**
+* @file 自用buttons组件
+* @author mip-support@hzxem.com
+* @version 1.0.0
+* @copyright 2016 hzxem.com, Inc. All Rights Reserved
+*/
+
+define(function (require) {
+ var $ = require('jquery');
+ var customElement = require('customElement').create();
+ function build() {
+ var el = this.element;
+ var Button = function (element, options) {
+ this.$element = $(element);
+ this.options = $.extend({}, Button.DEFAULTS, options);
+ this.isLoading = false;
+ };
+
+ Button.VERSION = '3.3.0';
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ };
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled';
+ var $el = this.$element;
+ var val = $el.is('input') ? 'val' : 'html';
+ var data = $el.data();
+
+ state = state + 'Text';
+
+ if (data.resetText == null) {
+ $el.data('resetText', $el[val]());
+ }
+
+ // push to event loop to allow forms to submit
+ var t = setTimeout($.proxy(function () {
+ $el[val](data[state] == null ? this.options[state] : data[state]);
+
+ if (state === 'loadingText') {
+ this.isLoading = true;
+ $el.addClass(d).attr(d, d);
+ }
+ else if (this.isLoading) {
+ this.isLoading = false;
+ $el.removeClass(d).removeAttr(d);
+ }
+ clearTimeout(t);
+ }, this), 0);
+ };
+
+ Button.prototype.toggle = function () {
+ var changed = true;
+ var $parent = this.$element.closest('[data-toggle="buttons"]');
+
+ if ($parent.length) {
+ var $input = this.$element.find('input');
+ if ($input.prop('type') === 'radio') {
+ if ($input.prop('checked') && this.$element.hasClass('active')) {
+ changed = false;
+ }
+ else {
+ $parent.find('.active').removeClass('active');
+ }
+ }
+ if (changed) {
+ $input.prop('checked', !this.$element.hasClass('active')).trigger('change');
+ }
+ }
+ else {
+ this.$element.attr('aria-pressed', !this.$element.hasClass('active'));
+ }
+
+ if (changed) {
+ this.$element.toggleClass('active');
+ }
+ };
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this);
+ var data = $this.data('bs.button');
+ var options = typeof option === 'object' && option;
+
+ if (!data) {
+ $this.data('bs.button', (data = new Button(this, options)));
+ }
+
+ if (option === 'toggle') {
+ data.toggle();
+ }
+ else if (option) {
+ data.setState(option);
+ }
+ });
+ }
+
+ var old = $.fn.button;
+
+ $.fn.button = Plugin;
+ $.fn.button.Constructor = Button;
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old;
+ return this;
+ };
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(el)
+ .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ var $btn = $(e.target);
+ if (!$btn.hasClass('btn')) {
+ $btn = $btn.closest('.btn');
+ }
+ Plugin.call($btn, 'toggle');
+ e.preventDefault();
+ })
+ .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+ $(e.target).closest('.btn').toggleClass('focus', e.type === 'focus');
+ });
+
+ }
+
+ customElement.prototype.build = build;
+ return customElement;
+});
diff --git a/mip-xem-buttons/package.json b/mip-xem-buttons/package.json
new file mode 100644
index 00000000..d76aaa55
--- /dev/null
+++ b/mip-xem-buttons/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xem-buttons",
+ "version": "1.0.0",
+ "author": {
+ "name": "xem",
+ "email": "mip@hzxem.com",
+ "url": "https://www.hzxem.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xem-comment-duoshuo/README.md b/mip-xem-comment-duoshuo/README.md
new file mode 100644
index 00000000..064d8db7
--- /dev/null
+++ b/mip-xem-comment-duoshuo/README.md
@@ -0,0 +1,45 @@
+# mip-xem-comment-duoshuo
+
+mip-xem-comment-duoshuo 多说评论框通用代码稳定版组件。
+
+标题|内容
+----|----
+类型|通用,评论框
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-xem-comment-duoshuo/mip-xem-comment-duoshuo.js
+
+## 示例
+
+### 基本使用
+```html
+
+
+
+
+```
+
+## 属性
+
+### short_name
+
+说明:多说站点域名前缀
+必选项:是
+类型:字符串
+
+### data-thread-key
+
+说明:评论框所在页面的唯一标识符
+必选项:否
+类型:字符串
+
+### data-title
+
+说明:评论框所在页面的标题
+必选项:否
+类型:字符串
+
+### data-url
+
+说明:评论框所在页面的url
+必选项:否
+类型:字符串
diff --git a/mip-xem-comment-duoshuo/mip-xem-comment-duoshuo.js b/mip-xem-comment-duoshuo/mip-xem-comment-duoshuo.js
new file mode 100644
index 00000000..41ff664a
--- /dev/null
+++ b/mip-xem-comment-duoshuo/mip-xem-comment-duoshuo.js
@@ -0,0 +1,30 @@
+/**
+* @file 多说评论框通用代码稳定版组件
+* @author mip-support@hzxem.com
+* @version 1.0
+* @copyright 2016 hzxem.com, Inc. All Rights Reserved
+*/
+
+define(function (require) {
+ var $ = require('zepto');
+ var customElement = require('customElement').create();
+ customElement.prototype.build = function () {
+ var elem = this.element;
+ var shortname = elem.getAttribute('short_name');
+ var $elem = $(elem);
+ var html = [
+ ''
+ ];
+ $elem.append(html.join(''));
+ };
+ return customElement;
+});
diff --git a/mip-xem-comment-duoshuo/package.json b/mip-xem-comment-duoshuo/package.json
new file mode 100644
index 00000000..428b141f
--- /dev/null
+++ b/mip-xem-comment-duoshuo/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xem-comment-duoshuo",
+ "version": "1.0.0",
+ "author": {
+ "name": "xem",
+ "email": "mip@hzxem.com",
+ "url": "https://www.hzxem.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xem-dropdowns/README.md b/mip-xem-dropdowns/README.md
new file mode 100644
index 00000000..4d5ca719
--- /dev/null
+++ b/mip-xem-dropdowns/README.md
@@ -0,0 +1,35 @@
+# mip-xem-dropdowns
+
+mip-xem-dropdowns 自用dropdowns组件。
+
+标题|内容
+----|----
+类型|业务
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-xem-dropdowns/mip-xem-dropdowns.js
+
+## 示例
+
+### 基本使用
+```html
+
+
+
+```
diff --git a/mip-xem-dropdowns/mip-xem-dropdowns.js b/mip-xem-dropdowns/mip-xem-dropdowns.js
new file mode 100644
index 00000000..de078cba
--- /dev/null
+++ b/mip-xem-dropdowns/mip-xem-dropdowns.js
@@ -0,0 +1,163 @@
+/**
+* @file 自用Dropdowns组件
+* @author mip-support@hzxem.com
+* @version 1.0.0
+* @copyright 2016 hzxem.com, Inc. All Rights Reserved
+*/
+
+define(function (require) {
+ var $ = require('jquery');
+ var customElement = require('customElement').create();
+ function build() {
+ var el = this.element;
+ var backdrop = '.dropdown-backdrop';
+ var toggle = '[data-toggle="dropdown"]';
+ var Dropdown = function (element) {
+ $(element).on('click.bs.dropdown', this.toggle);
+ };
+ Dropdown.VERSION = '3.3.0';
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this);
+ if ($this.is('.disabled, :disabled')) {
+ return;
+ }
+ var $parent = getParent($this);
+ var isActive = $parent.hasClass('open');
+ clearMenus();
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we use a backdrop because click events don't delegate
+ $('').insertAfter($(this)).on('click', clearMenus);
+ }
+ var relatedTarget = {relatedTarget: this};
+ $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
+ if (e.isDefaultPrevented()) {
+ return;
+ }
+ $this.trigger('focus').attr('aria-expanded', 'true');
+ $parent.toggleClass('open').trigger('shown.bs.dropdown', relatedTarget);
+ }
+ return false;
+ };
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27|32)/.test(e.which)) {
+ return;
+ }
+ var $this = $(this);
+ e.preventDefault();
+ e.stopPropagation();
+
+ if ($this.is('.disabled, :disabled')) {
+ return;
+ }
+
+ var $parent = getParent($this);
+ var isActive = $parent.hasClass('open');
+
+ if ((!isActive && e.which !== 27) || (isActive && e.which === 27)) {
+ if (e.which === 27) {
+ $parent.find(toggle).trigger('focus');
+ return $this.trigger('click');
+ }
+ }
+
+ var desc = ' li:not(.divider):visible a';
+ var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc);
+
+ if (!$items.length) {
+ return;
+ }
+
+ var index = $items.index(e.target);
+
+ if (e.which === 38 && index > 0) {
+ index--;
+ } // up
+ if (e.which === 40 && index < $items.length - 1) {
+ index++;
+ }// down
+ if (!~index) {
+ index = 0;
+ }
+
+ $items.eq(index).trigger('focus');
+ };
+
+ function clearMenus(e) {
+ if (e && e.which === 3) {
+ return;
+ }
+ $(backdrop).remove();
+ $(toggle).each(function () {
+ var $this = $(this);
+ var $parent = getParent($this);
+ var relatedTarget = {relatedTarget: this};
+
+ if (!$parent.hasClass('open')) {
+ return;
+ }
+
+ $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
+
+ if (e.isDefaultPrevented()) {
+ return;
+ }
+
+ $this.attr('aria-expanded', 'false');
+ $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget);
+ });
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target');
+ if (!selector) {
+ selector = $this.attr('href');
+ selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
+ }
+ var $parent = selector && $(selector);
+ return $parent && $parent.length ? $parent : $this.parent();
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ function Plugin(option) {
+ return this.each(function () {
+ var $this = $(this);
+ var data = $this.data('bs.dropdown');
+ if (!data) {
+ $this.data('bs.dropdown', (data = new Dropdown(this)));
+ }
+ if (typeof option === 'string') {
+ data[option].call($this);
+ }
+ });
+ }
+
+ var old = $.fn.dropdown;
+ $.fn.dropdown = Plugin;
+ $.fn.dropdown.Constructor = Dropdown;
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old;
+ return this;
+ };
+ $(el)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) {
+ e.stopPropagation();
+ })
+ .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
+ .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown);
+ }
+
+ customElement.prototype.build = build;
+ return customElement;
+});
diff --git a/mip-xem-dropdowns/package.json b/mip-xem-dropdowns/package.json
new file mode 100644
index 00000000..4b105fa3
--- /dev/null
+++ b/mip-xem-dropdowns/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xem-dropdowns",
+ "version": "1.0.0",
+ "author": {
+ "name": "xem",
+ "email": "mip@hzxem.com",
+ "url": "https://www.hzxem.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xem-review/README.md b/mip-xem-review/README.md
new file mode 100644
index 00000000..5e71df9f
--- /dev/null
+++ b/mip-xem-review/README.md
@@ -0,0 +1,42 @@
+# mip-xem-review
+
+mip-xem-review 自用获取随机评论组件。
+
+标题|内容
+----|----
+类型|业务
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-xem-review/mip-xem-review.js
+
+## 示例
+
+### 基本使用
+```html
+
+```
+
+## 属性
+
+### yid
+
+说明:当前页面id
+必选项:是
+类型:字符串
+
+### listofreadcid
+
+说明:已查看的评论id
+必选项:是
+类型:字符串
+
+### formhash
+
+说明:formhash
+必选项:是
+类型:字符串
+
+### url
+
+说明:请求点评内容的页面
+必选项:是
+类型:字符串
diff --git a/mip-xem-review/mip-xem-review.js b/mip-xem-review/mip-xem-review.js
new file mode 100644
index 00000000..0b9b4917
--- /dev/null
+++ b/mip-xem-review/mip-xem-review.js
@@ -0,0 +1,47 @@
+/**
+* @file 自用获取随机评论组件
+* @author mip-support@hzxem.com
+* @version 1.0.2
+* @copyright 2016 hzxem.com, Inc. All Rights Reserved
+*/
+
+define(function (require) {
+ var $ = require('jquery');
+ var customElement = require('customElement').create();
+ customElement.prototype.build = function () {
+ var elem = this.element;
+ var yid = elem.getAttribute('yid');
+ var formhash = elem.getAttribute('formhash');
+ var url = elem.getAttribute('url');
+ var $elem = $(elem);
+ var $btn = $('.nextreview');
+ function getreview() {
+ $btn.button('loading');
+ $.post(url, {
+ yid: yid,
+ listofreadcid: elem.getAttribute('listofreadcid'),
+ formhash: formhash,
+ reviewsubmit: true
+ },
+ function (data, status) {
+ if (data) {
+ $elem.html(function () {
+ var thiscid = ',' + data.tid;
+ var readcid = elem.getAttribute('listofreadcid');
+ $elem.data('listofreadcid', readcid + thiscid);
+ $btn.button('reset');
+ return data.message;
+ });
+ }
+ },
+ 'json');
+ }
+ $(document).ready(function () {
+ getreview();
+ });
+ $btn.on('click', function () {
+ getreview();
+ });
+ };
+ return customElement;
+});
diff --git a/mip-xem-review/package.json b/mip-xem-review/package.json
new file mode 100644
index 00000000..82423375
--- /dev/null
+++ b/mip-xem-review/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xem-review",
+ "version": "1.0.2",
+ "author": {
+ "name": "xem",
+ "email": "mip@hzxem.com",
+ "url": "https://www.hzxem.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xiaojp-display/README.md b/mip-xiaojp-display/README.md
index f26931a9..8b8e8af8 100644
--- a/mip-xiaojp-display/README.md
+++ b/mip-xiaojp-display/README.md
@@ -6,7 +6,7 @@ mip-xiaojp-display 点击组件,可根据传递的data-type类型,打开或
----|----
类型|通用
支持布局|无
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xiaojp-display/mip-xiaojp-display.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xiaojp-display/mip-xiaojp-display.js
## 示例
diff --git a/mip-xiaomi-router/README.md b/mip-xiaomi-router/README.md
index ad35bb7e..b8cb12e6 100644
--- a/mip-xiaomi-router/README.md
+++ b/mip-xiaomi-router/README.md
@@ -6,7 +6,7 @@ mip-xiaomi-router 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xiaomi-router/mip-xiaomi-router.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xiaomi-router/mip-xiaomi-router.js
## 示例
diff --git a/mip-xiaoniaole/README.md b/mip-xiaoniaole/README.md
index da43dfbc..b8c4b685 100644
--- a/mip-xiaoniaole/README.md
+++ b/mip-xiaoniaole/README.md
@@ -6,7 +6,7 @@ mip-xiaoniaole 用于xiaoniaole.cn站点投放汇米广告固定位广告的组
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xiaoniaole/mip-xiaoniaole.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xiaoniaole/mip-xiaoniaole.js
## 示例
diff --git a/mip-xiaoshuo-read/README.md b/mip-xiaoshuo-read/README.md
index 0420f09f..4316d206 100644
--- a/mip-xiaoshuo-read/README.md
+++ b/mip-xiaoshuo-read/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xiaoshuo-read/mip-xiaoshuo-read.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xiaoshuo-read/mip-xiaoshuo-read.js
## 示例
diff --git a/mip-xilu-ad/README.md b/mip-xilu-ad/README.md
index 469dcb9c..dff619d8 100644
--- a/mip-xilu-ad/README.md
+++ b/mip-xilu-ad/README.md
@@ -6,7 +6,7 @@ mip-xilu-ad 用来支持淘宝等第三方广告
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xilu-ad/mip-xilu-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xilu-ad/mip-xilu-ad.js
## 示例
diff --git a/mip-xinshijue-script/README.md b/mip-xinshijue-script/README.md
index 74d06a30..b712aa17 100644
--- a/mip-xinshijue-script/README.md
+++ b/mip-xinshijue-script/README.md
@@ -6,7 +6,7 @@ mip-xinshijue-script 组件说明
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xinshijue-script/mip-xinshijue-script.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xinshijue-script/mip-xinshijue-script.js
## 示例
diff --git a/mip-xuexb-like/README.md b/mip-xuexb-like/README.md
index 7270d233..3ea8a798 100644
--- a/mip-xuexb-like/README.md
+++ b/mip-xuexb-like/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xuexb-like/mip-xuexb-like.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xuexb-like/mip-xuexb-like.js
## 示例
diff --git a/mip-xuexb-login/README.md b/mip-xuexb-login/README.md
index fa9d6f5a..3b5aa3ef 100644
--- a/mip-xuexb-login/README.md
+++ b/mip-xuexb-login/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/static/v1/mip-form/mip-form.js,https://c.mipcdn.com/extensions/platform/v1/mip-xuexb-login/mip-xuexb-login.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-form/mip-form.js,https://c.mipcdn.com/static/v1/mip-xuexb-login/mip-xuexb-login.js
## 示例
diff --git a/mip-xueyou-ad/README.md b/mip-xueyou-ad/README.md
new file mode 100644
index 00000000..57f55670
--- /dev/null
+++ b/mip-xueyou-ad/README.md
@@ -0,0 +1,31 @@
+# mip-xueyou-ad
+
+mip-xueyou-ad 用来支持淘宝,谷歌广告联盟
+
+标题|内容
+----|----
+类型|通用
+支持布局|container
+所需脚本|https://c.mipcdn.com/static/v1/mip-xueyou-ad/mip-xueyou-ad.js
+
+## 示例
+
+```html
+
+```
+
+## 属性
+
+### type
+
+说明:广告类型
+必选项:是
+类型:字符串
+格式:必须为 google / alibaba
+
+
+### token
+
+说明:广告音元ID
+必选项:是
+类型:字符串
diff --git a/mip-xueyou-ad/mip-xueyou-ad.js b/mip-xueyou-ad/mip-xueyou-ad.js
new file mode 100644
index 00000000..192b725d
--- /dev/null
+++ b/mip-xueyou-ad/mip-xueyou-ad.js
@@ -0,0 +1,58 @@
+/**
+* 学优网mip改造 第三方联盟广告插件
+* @file 插入联盟广告
+* @author myoa@163.com
+* @version 1.0.2
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var UA = navigator.userAgent.toLowerCase();
+ var customElement = require('customElement').create();
+ customElement.prototype.createdCallback = function () {
+ var el = this.element;
+ var adtype = el.getAttribute('type');
+ var token = el.getAttribute('token');
+ var $element = $(el);
+ var html = [];
+ switch (adtype) {
+ case 'alibaba':
+ if(!/micromessenger/.test(UA)){
+ html.push('');
+ html.push('');
+ }
+ break;
+
+ case 'google' :
+ html.push('');
+ html.push('');
+ html.push('');
+ html.push('');
+ break;
+ }
+ $element.append(html.join(''));
+ };
+ return customElement;
+});
diff --git a/mip-xueyou-ad/package.json b/mip-xueyou-ad/package.json
new file mode 100644
index 00000000..c33e680a
--- /dev/null
+++ b/mip-xueyou-ad/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xueyou-ad",
+ "version": "1.0.2",
+ "author": {
+ "name": "myoa",
+ "email": "myoa@163.com",
+ "url": "http://www.gkstk.com/"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xueyou-article/README.md b/mip-xueyou-article/README.md
new file mode 100644
index 00000000..600f99ac
--- /dev/null
+++ b/mip-xueyou-article/README.md
@@ -0,0 +1,31 @@
+# mip-xueyou-article
+
+mip-xueyou-article 学优网mip改造插件
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-xueyou-article/mip-xueyou-article.js
+
+## 示例
+
+### 基本使用
+```html
+
+```
+
+## 属性
+
+### id
+
+说明:唯一编号
+必选项:是
+类型:字符串
+
+
+### token
+
+说明:加密编码
+必选项:是
+类型:字符串
diff --git a/mip-xueyou-article/mip-xueyou-article.js b/mip-xueyou-article/mip-xueyou-article.js
new file mode 100644
index 00000000..140379b8
--- /dev/null
+++ b/mip-xueyou-article/mip-xueyou-article.js
@@ -0,0 +1,166 @@
+/**
+* 学优网mip改造 javascript功能插件
+* @file 脚本支持
+* @author myoa@163.com
+* @time 2016.11.18
+* @version 1.1.3
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var dbshow = false;
+ var customElem = require('customElement').create();
+ customElem.prototype.build = function () {
+ var el = this.element;
+ var docId = el.getAttribute('id');
+ var code = el.getAttribute('token');
+ // 以self方式重置a标签
+ $('.openself').attr('target', '_self');
+ // 查看更多按钮功能
+ var btnMax = $('#btnToMax');
+ var artbox = $('#artbox');
+ var relHeight = artbox.height();
+ if (relHeight < 800) {
+ maxpage();
+ }
+ var timeOutEvent = 0;
+ btnMax.on({
+ click: function (e) {
+ maxpage();
+ },
+ touchstart: function (e) {
+ timeOutEvent = setTimeout(function () {
+ timeOutEvent = 0;
+ maxpage();
+ }, 500);
+ e.preventDefault();
+ },
+ touchmove: function () {
+ clearTimeout(timeOutEvent);
+ timeOutEvent = 0;
+ },
+ touchend: function () {
+ clearTimeout(timeOutEvent);
+ if (timeOutEvent !== 0) {
+ maxpage();
+ }
+ return false;
+ }
+ });
+ function maxpage() {
+ dbshow || opendubao();
+ artbox.removeClass('minbox');
+ btnMax.parent().remove();
+ }
+ // 广告控制
+ var admnum = 2;
+ var admtopInit = getCookie('admtop');
+ var admbottomInit = getCookie('admbottom');
+ var admtop = $('#mip-adm-top');
+ var admbottom = $('#mip-adm-bottom');
+ if (admtopInit === 'close') {
+ // 头部广告单元以嵌入方式加载
+ admnum--;
+ admtop.removeClass('hide');
+ admtop.removeClass('fix');
+ dbshow || opendubao();
+ }
+ else {
+ var body = $('body');
+ body.on('touchmove', function () {
+ var gt = getScrollTop();
+ if (gt > 100) {
+ admtop.removeClass('hide');
+ admtop.addClass('fix');
+ }
+ if (gt < -50) {
+ admtop.removeClass('fix');
+ }
+ });
+ }
+ if (admbottomInit === 'close') {
+ admnum--;
+ admbottom.removeClass('fix');
+ }
+ if (admnum > 0) {
+ var admbtn = $('.btnclose');
+ admbtn.on('click', function () {
+ if ($(this).parent().attr('id') === 'mip-adm-top') {
+ setCookie('admtop', 'close');
+ admtop.remove();
+ }
+ else {
+ setCookie('admbottom', 'close');
+ admbottom.removeClass('fix');
+ }
+ dbshow || opendubao();
+ });
+ }
+ // 统计系统
+ var readed = getCookie('readed_' + docId);
+ var dtp = 'wenku';
+ if (parseInt(docId, 10) < 356835) {
+ dtp = 'fanwen';
+ }
+ if (readed !== 'false') {
+ $.getJSON('http://www.wangshengbo.cn/api/' + dtp + '/hits/?id=' + docId + '&code=' + code + '&jsoncallback=?',
+ function (rda) {
+ setCookie('readed_' + docId, 'false');
+ }
+ );
+ }
+ function getScrollTop() {
+ var scrollTop = 0;
+ if (document.documentElement && document.documentElement.scrollTop) {
+ scrollTop = document.documentElement.scrollTop;
+ }
+ else if (document.body) {
+ scrollTop = document.body.scrollTop;
+ }
+ return scrollTop;
+ }
+ function setCookie(name, value) {
+ var Days = .5;
+ // 12个小时过期
+ var exp = new Date();
+ exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1e3);
+ document.cookie = name + '=' + escape(value) + ';expires=' + exp.toGMTString();
+ }
+ function opendubao() {
+ return true;
+ }
+ function getScript(url, callback) {
+ var script = document.createElement('script');
+ script.type = 'text/javascript';
+ if (script.readyState) {
+ script.onreadystatechange = function () {
+ if (script.readyState === 'loaded' || script.readyState === 'complete') {
+ script.onreadystatechange = null;
+ callback();
+ }
+ };
+ }
+ else {
+ script.onload = function () {
+ callback();
+ };
+ }
+ script.src = url;
+ document.getElementsByTagName('head')[0].appendChild(script);
+ }
+ function getCookie(cnm) {
+ if (document.cookie.length > 0) {
+ var cstart = document.cookie.indexOf(cnm + '=');
+ if (cstart !== -1) {
+ cstart = cstart + cnm.length + 1;
+ var cend = document.cookie.indexOf(';', cstart);
+ if (cend === -1) {
+ cend = document.cookie.length;
+ }
+ return unescape(document.cookie.substring(cstart, cend));
+ }
+ }
+ return '';
+ }
+ };
+ return customElem;
+});
diff --git a/mip-xueyou-article/package.json b/mip-xueyou-article/package.json
new file mode 100644
index 00000000..305f5501
--- /dev/null
+++ b/mip-xueyou-article/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xueyou-article",
+ "version": "1.1.3",
+ "author": {
+ "name": "myoa",
+ "email": "myoa@163.com",
+ "url": "http://www.gkstk.com/"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-xueyou-list/README.md b/mip-xueyou-list/README.md
new file mode 100644
index 00000000..4f2a8703
--- /dev/null
+++ b/mip-xueyou-list/README.md
@@ -0,0 +1,18 @@
+# mip-xueyou-list
+
+学优网列表插件
+
+描述|列表页加载更多js脚本
+----|----
+类型|广告
+支持布局| N/S
+所需脚本| https://c.mipcdn.com/static/v1/mip-xueyou-list/mip-xueyou-list.js
+
+## 示例
+
+只需要一个``标签
+
+```
+
+```
+
diff --git a/mip-xueyou-list/mip-xueyou-list.js b/mip-xueyou-list/mip-xueyou-list.js
new file mode 100644
index 00000000..5d1956d8
--- /dev/null
+++ b/mip-xueyou-list/mip-xueyou-list.js
@@ -0,0 +1,32 @@
+/**
+* 学优网mip改造 javascript功能插件
+* @file ajax异步加载更多列表内容
+* @author myoa@163.com
+* @version 1.0.1
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+ customElem.prototype.build = function () {
+ $('.openself').attr('target', '_self');
+ var more = $('#more');
+ var loading = $('.spinner');
+ var listbox = $('#listbox');
+ var cid = listbox.data('cid');
+ var url = listbox.data('url');
+ more.on('click', function () {
+ var id = $('#listbox li').last().data('id');
+ more.hide();
+ loading.show();
+ $.get(url, {
+ id: id,
+ cid: cid
+ }, function (mlst) {
+ listbox.append(mlst);
+ more.show();
+ loading.hide();
+ });
+ });
+ };
+ return customElem;
+});
diff --git a/mip-xueyou-list/package.json b/mip-xueyou-list/package.json
new file mode 100644
index 00000000..b7abbd9b
--- /dev/null
+++ b/mip-xueyou-list/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xueyou-list",
+ "version": "1.0.2",
+ "author": {
+ "name": "myoa",
+ "email": "myoa@163.com",
+ "url": "http://www.gkstk.com/"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-xxcms-fd/README.md b/mip-xxcms-fd/README.md
index cacd722f..0897350d 100644
--- a/mip-xxcms-fd/README.md
+++ b/mip-xxcms-fd/README.md
@@ -6,7 +6,7 @@ mip-xxcms-fd 汇米广告联盟广告投放插件
----|----
类型|广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xxcms-fd/mip-xxcms-fd.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xxcms-fd/mip-xxcms-fd.js
## 示例
diff --git a/mip-xzh-payment/README.md b/mip-xzh-payment/README.md
index 133ceefb..80a9471a 100644
--- a/mip-xzh-payment/README.md
+++ b/mip-xzh-payment/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xzh-payment/mip-xzh-payment.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xzh-payment/mip-xzh-payment.js
## 说明
diff --git a/mip-xzw-article/README.md b/mip-xzw-article/README.md
new file mode 100644
index 00000000..f8f09097
--- /dev/null
+++ b/mip-xzw-article/README.md
@@ -0,0 +1,15 @@
+# mip-xzw-article
+mip-xzw-article 星座屋mip改造插件
+
+标题|内容
+----|----
+类型|业务
+支持布局|N/S
+所需脚本|
+
+## 示例
+
+### 基本使用
+```html
+
+```
\ No newline at end of file
diff --git a/mip-xzw-article/mip-xzw-article.js b/mip-xzw-article/mip-xzw-article.js
new file mode 100644
index 00000000..3f1962d6
--- /dev/null
+++ b/mip-xzw-article/mip-xzw-article.js
@@ -0,0 +1,126 @@
+/**
+* 星座屋mip改造 javascript功能插件
+* @file 页面主要内容改造
+* @author mipxzw@163.com
+* @version 1.0.4
+*/
+define(function (require) {
+ var $ = require('zepto');
+ var isShowStar = false;
+ var customElem = require('customElement').create();
+ customElem.prototype.build = function () {
+ var starBox = $('.xz_select');
+ var starbg = $('.bg_black');
+ // 控制弹层开启
+ $(document).on('click', '.chos_btn', function (event) {
+ event = event || window.event;
+ event.stopPropagation();
+ if (isShowStar) {
+ starBox.hide();
+ starbg.hide();
+ isShowStar = false;
+ }
+ else {
+ starBox.show();
+ starbg.show();
+ isShowStar = true;
+ }
+ });
+ // 关闭弹框
+ $(document).on('click', '.bg_black', function (e) {
+ starBox.hide();
+ starbg.hide();
+ isShowStar = false;
+ });
+ // 根据请求更换不同星座数据
+ var aid = getUrl('aid');
+ if (!aid) {
+ aid = 1;
+ }
+ aid = aid - 1;
+ $.ajax({
+ url: 'https://cache.xzw.com/mip/data.js',
+ dataType: 'jsonp',
+ data: {'id': aid},
+ jsonp: 'callback',
+ jsonpCallback: 'call_data',
+ cache:true,
+ success: function (data) {
+ var starData = data.data;
+ var items = starData[aid];
+ $('.top_part').html(items.toppart);
+ $('.peidui').html(items.peidui);
+ $('.libox').html(items.bottompart);
+ },
+ timeout: 3000
+ });
+ // 获取今日运势内容
+ $.ajax({
+ url: 'https://cache.xzw.com/mip/fortune/1/' + myDates() + '/' + aid + '.js',
+ dataType: 'jsonp',
+ jsonp: 'callback',
+ jsonpCallback: 'call_fortune',
+ cache:true,
+ success: function (data) {
+ $('.fortune em em').width(data.data.s);
+ $('.fortune p a').html(data.data.v).attr('href', 'fortune.html?aid=' + (aid + 1) + '');
+ },
+ timeout: 3000
+ });
+ // 更换星座
+ $(document).on('click', '.xz_select li', function (e) {
+ var i = $(this).index() + 1;
+ changeStar(i);
+ });
+
+ // 改变星座
+ function changeStar(index) {
+ window.location.href = urlUpdateParams(window.location.href, 'aid', index);
+ }
+
+ // 获取url参数
+ function getUrl(name) {
+ var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
+ var r = window.location.search.substr(1).match(reg);
+ if (r != null) {
+ return unescape(r[2]);
+ }
+ return null;
+ }
+
+ // 改变url参数
+ function urlUpdateParams(url, name, value) {
+ var r = url;
+ if (r !== null && r !== 'undefined' && r !== '') {
+ value = encodeURIComponent(value);
+ var reg = new RegExp('(^|)' + name + '=([^&]*)(|$)');
+ var tmp = name + '=' + value;
+ if (url.match(reg) !== null) {
+ r = url.replace(evil(reg), tmp);
+ }
+ else {
+ if (url.match('[\?]')) {
+ r = url + '&' + tmp;
+ }
+ else {
+ r = url + '?' + tmp;
+ }
+ }
+ }
+ return r;
+ }
+
+ // 获取时间
+ function myDates() {
+ var date = new Date();
+ var s = '' + date.getFullYear() + (date.getMonth() + 1) + date.getDate();
+ return s;
+ }
+ // 转换对象
+ function evil(fn) {
+ var Fn = Function;
+ return new Fn('return ' + fn)();
+ }
+ };
+ return customElem;
+});
diff --git a/mip-xzw-article/package.json b/mip-xzw-article/package.json
new file mode 100644
index 00000000..f2278091
--- /dev/null
+++ b/mip-xzw-article/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-xzw-article",
+ "version": "1.0.4",
+ "author": {
+ "name": "Amadalijun",
+ "email": "mipxzw@163.com",
+ "url": "http://www.xingzuowu.com/"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-xzw-layer/README.md b/mip-xzw-layer/README.md
index b2f123f7..043b4282 100644
--- a/mip-xzw-layer/README.md
+++ b/mip-xzw-layer/README.md
@@ -5,7 +5,7 @@ mip-xzw-layer 星座屋mip弹层功能组件
----|----
类型|通用
支持布局|responsive,fix-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xzw-layer/mip-xzw-layer.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xzw-layer/mip-xzw-layer.js
## 示例
diff --git a/mip-xzw-picker/README.md b/mip-xzw-picker/README.md
index b2120f76..d5c26889 100644
--- a/mip-xzw-picker/README.md
+++ b/mip-xzw-picker/README.md
@@ -5,7 +5,7 @@ mip-xzw-picker 星座屋mip弹层功能组件
----|----
类型|通用
支持布局|responsive,fix-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xzw-picker/mip-xzw-picker.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xzw-picker/mip-xzw-picker.js
## 示例
diff --git a/mip-xzw-rem/README.md b/mip-xzw-rem/README.md
index 261e325b..6416fce2 100644
--- a/mip-xzw-rem/README.md
+++ b/mip-xzw-rem/README.md
@@ -5,7 +5,7 @@ mip-xzw-rem 星座屋mip自适应组件,该组件主要用来支持rem单位的
----|----
类型|通用
支持布局|responsive,fix-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-xzw-rem/mip-xzw-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-xzw-rem/mip-xzw-rem.js
## 示例
diff --git a/mip-ychlyxgs-adddata/README.md b/mip-ychlyxgs-adddata/README.md
index 24569d0a..98f91b5e 100644
--- a/mip-ychlyxgs-adddata/README.md
+++ b/mip-ychlyxgs-adddata/README.md
@@ -6,7 +6,7 @@ mip-ychlyxgs-adddata 需要进入页面就执行,只执行一次,本公司
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ychlyxgs-adddata/mip-ychlyxgs-adddata.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ychlyxgs-adddata/mip-ychlyxgs-adddata.js
## 示例
diff --git a/mip-ychlyxgs-data/README.md b/mip-ychlyxgs-data/README.md
index f3abb74f..6cf836d3 100644
--- a/mip-ychlyxgs-data/README.md
+++ b/mip-ychlyxgs-data/README.md
@@ -7,7 +7,7 @@ mip-ychlyxgs-data 一个本企业独立使用的多功能组件,通过接口
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ychlyxgs-data/mip-ychlyxgs-data.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ychlyxgs-data/mip-ychlyxgs-data.js
## 示例
diff --git a/mip-yibai-zhaosheng/README.md b/mip-yibai-zhaosheng/README.md
index 8e2fbae7..2284f5c1 100644
--- a/mip-yibai-zhaosheng/README.md
+++ b/mip-yibai-zhaosheng/README.md
@@ -6,7 +6,7 @@ mip-liuxue-main 提供页面部分逻辑代码
----|----
类型|业务
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yibai-zhaosheng/mip-yibai-zhaosheng.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yibai-zhaosheng/mip-yibai-zhaosheng.js
## 示例
diff --git a/mip-yiqibazi-tap/README.md b/mip-yiqibazi-tap/README.md
new file mode 100644
index 00000000..afe3a184
--- /dev/null
+++ b/mip-yiqibazi-tap/README.md
@@ -0,0 +1,109 @@
+# mip-yiqibazi-tap
+
+点击切换组件
+
+描述|注册点击事件
+----|----
+类型|事件
+支持布局| N/S
+所需脚本|https://c.mipcdn.com/static/v1/mip-yiqibazi-tap/mip-yiqibazi-tap.js
+
+## 示例
+
+### 切换
+
+```
+
+
+
+ 生肖星座d面相痣相解梦
+
+
+
+
+
+
+
+
+
+ 所谓的感情失败者,古代所说的无非是红颜薄命,多情女子,薄情郎。而十二生肖中,谁是
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 与其在对方跟自己分手那一刻遭受一个猛烈的打击,不如抢先一步弄清楚对方心里的变化,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 在相学里有言相由心生,讲的就是一个人的内心的好与坏都能够从面相里看出来,面相也关
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 眉心长痣——心底善良 女人眉心一点痣,活菩萨转世,长有这样一颗痣的女人,天生慈眉
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 梦见与妻子拌嘴,夫妻恩爱,生活幸福、美满。梦见自杀,表示你对目前的生活状况感到非
+
+
+
+
+
+
+
+
+```
+
+
diff --git a/mip-yiqibazi-tap/mip-yiqibazi-tap.js b/mip-yiqibazi-tap/mip-yiqibazi-tap.js
new file mode 100644
index 00000000..ba772630
--- /dev/null
+++ b/mip-yiqibazi-tap/mip-yiqibazi-tap.js
@@ -0,0 +1,47 @@
+/**
+ * click switch
+ * @file click component
+ * @author lin156635304@163.com
+ * @version 1.0
+ * @copyright 2016 yiqibazi.com, Inc. All Rights Reserved
+ */
+define(function (require) {
+ var $ = require('zepto');
+ var customElem = require('customElement').create();
+
+ function labelSwitching() {
+ var $liuniannav = $('.mip-liunian .title span');
+ var $liuniancon = $('.mip-liunian .answer-box');
+ $liuniannav.on('click', function () {
+ var index = $(this).index();
+ $liuniannav.removeClass('navs-active');
+ $(this).addClass('navs-active');
+ $liuniancon.eq(index).addClass('mip-show').siblings().removeClass('mip-show');
+ });
+ var $liunianyunnav = $('.mip-liunianyun .title span');
+ var $liunianyuncon = $('.mip-liunianyun .answer-box');
+ $liunianyunnav.on('click', function () {
+ var index = $(this).index();
+ if (index === 2) {
+ location.href = 'http://wap.yiqijixiang.com/sx2017/?referraluserid=ydyiqibazi';
+ }
+ else {
+ $liunianyunnav.removeClass('navs-active');
+ $(this).addClass('navs-active');
+ $liunianyuncon.eq(index).addClass('mip-show').siblings().removeClass('mip-show');
+ }
+ });
+ }
+ function load() {
+ $('.mip-liunian').children('.list-con').children('.answer-box').eq(0).addClass('mip-show');
+ $('.mip-liunianyun').children('.list-con').children('.answer-box').eq(0).addClass('mip-show');
+ }
+ function init() {
+ load();
+ labelSwitching();
+ }
+ customElem.prototype.build = function () {
+ init();
+ };
+ return customElem;
+});
diff --git a/mip-yiqibazi-tap/package.json b/mip-yiqibazi-tap/package.json
new file mode 100644
index 00000000..2f0a1fdd
--- /dev/null
+++ b/mip-yiqibazi-tap/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "mip-yiqibazi-tap",
+ "version": "1.0.5",
+ "author": {
+ "name": "yiqibazi",
+ "email": "lin156635304@163.com",
+ "url": "http://www.yiqibazi.com"
+ },
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-youlai-audio/README.md b/mip-youlai-audio/README.md
index 0f0f6e78..98b48dd0 100644
--- a/mip-youlai-audio/README.md
+++ b/mip-youlai-audio/README.md
@@ -6,7 +6,7 @@ mip-youlai-audio 有来音频组件
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-youlai-audio/mip-youlai-audio.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-youlai-audio/mip-youlai-audio.js
## 示例
diff --git a/mip-youth-tb360/README.md b/mip-youth-tb360/README.md
new file mode 100644
index 00000000..3393996e
--- /dev/null
+++ b/mip-youth-tb360/README.md
@@ -0,0 +1,37 @@
+# mip-youth-tb360
+
+mip-youth-tb360 青网移动适配页头部360广告
+
+标题|内容
+----|----
+类型|通用,定制
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-youth-tb360/mip-youth-tb360.js
+
+## 示例
+
+移动适配页头部360第三方广告插件
+```html
+
+
+
+```
+
+## 属性
+
+### ayid
+
+说明:扩展组件广告id
+必填:是
+类型:字符串
+
+### adtype
+
+说明:广告类型
+必填:是
+类型:字符串
+取值:ad-dsf
+
\ No newline at end of file
diff --git a/mip-youth-tb360/mip-youth-tb360.js b/mip-youth-tb360/mip-youth-tb360.js
new file mode 100644
index 00000000..61fa97cb
--- /dev/null
+++ b/mip-youth-tb360/mip-youth-tb360.js
@@ -0,0 +1,24 @@
+/**
+ * @file 移动适配页面头部360广告
+ * @date 2016.11.23
+ * @author jxl
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+ var s = '_' + Math.random().toString(36).slice(2);
+ document.write('');
+ (window.slotbydup = window.slotbydup || []).push({
+ id: '2893098',
+ container: s,
+ size: '20,6',
+ display: 'inlay-fix'
+ });
+ var node = document.createElement('script');
+ node.setAttribute('type', 'javascript');
+ node.setAttribute('src', 'http://dup.baidustatic.com/js/om.js');
+ node.setAttribute('ignoreapd', '1');
+ document.body.appendChild(node);
+});
+
+
diff --git a/mip-youth-tb360/package.json b/mip-youth-tb360/package.json
new file mode 100644
index 00000000..79540246
--- /dev/null
+++ b/mip-youth-tb360/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-youth-tb360",
+ "version": "1.0.0",
+ "description": "article tb360",
+ "author": {
+ "name": "jixiangling",
+ "email": "1334935207@qq.com",
+ "url": "http://www.youth.cn"
+ },
+ "engines": {
+ "mip": ">=1.0.0"
+ }
+}
\ No newline at end of file
diff --git a/mip-youth-ttyd/README.md b/mip-youth-ttyd/README.md
new file mode 100644
index 00000000..7661f327
--- /dev/null
+++ b/mip-youth-ttyd/README.md
@@ -0,0 +1,39 @@
+# mip-youth-ttyd
+
+mip-youth-ttyd 青网移动适配页精彩推荐广告
+
+标题|内容
+----|----
+类型|通用,定制
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-youth-ttyd/mip-youth-ttyd.js
+
+## 示例
+
+移动适配页精彩推荐第三方广告插件
+```html
+
+
+
+```
+
+## 属性
+
+### ayid
+
+说明:扩展组件广告id
+必填:是
+类型:字符串
+
+### adtype
+
+说明:广告类型
+必填:是
+类型:字符串
+取值:ad-dsf
+
\ No newline at end of file
diff --git a/mip-youth-ttyd/mip-youth-ttyd.js b/mip-youth-ttyd/mip-youth-ttyd.js
new file mode 100644
index 00000000..5fba4288
--- /dev/null
+++ b/mip-youth-ttyd/mip-youth-ttyd.js
@@ -0,0 +1,15 @@
+/**
+ * @file 移动适配页面的相关推荐
+ * @date 2016.11.23
+ * @author jxl
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+
+define(function (require) {
+ var node = document.createElement('script');
+ node.setAttribute('src', 'http://3g.youth.cn/images/youth.min.js');
+ node.setAttribute('ignoreapd', '1');
+ document.body.appendChild(node);
+ (window.readsByToutiao = window.readsByToutiao || []).push({id: 'toutiao_container', num: 6});
+});
diff --git a/mip-youth-ttyd/package.json b/mip-youth-ttyd/package.json
new file mode 100644
index 00000000..d03dc246
--- /dev/null
+++ b/mip-youth-ttyd/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "mip-youth-ttyd",
+ "version": "1.0.1",
+ "description": "article jctj",
+ "author": {
+ "name": "jixiangling",
+ "email": "1334935207@qq.com",
+ "url": "http://www.youth.cn"
+ },
+ "engines": {
+ "mip": ">=1.0.1"
+ }
+}
\ No newline at end of file
diff --git a/mip-ys137-ad/README.md b/mip-ys137-ad/README.md
index 7fe64362..65488589 100644
--- a/mip-ys137-ad/README.md
+++ b/mip-ys137-ad/README.md
@@ -6,7 +6,7 @@ mip-ys137-ad 管理页面上的所有广告位
----|----
类型|定制
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ys137-ad/mip-ys137-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ys137-ad/mip-ys137-ad.js
## 示例
diff --git a/mip-ys137-loaddata/README.md b/mip-ys137-loaddata/README.md
index 60265126..fd977025 100644
--- a/mip-ys137-loaddata/README.md
+++ b/mip-ys137-loaddata/README.md
@@ -6,7 +6,7 @@ mip-ys137-loaddata 异步加载数据
----|----
类型|定制
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-ys137-loaddata/mip-ys137-loaddata.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-ys137-loaddata/mip-ys137-loaddata.js
## 示例
diff --git a/mip-yuanxiaoku-diqufenshuxian/README.md b/mip-yuanxiaoku-diqufenshuxian/README.md
index 06a32048..cb525066 100644
--- a/mip-yuanxiaoku-diqufenshuxian/README.md
+++ b/mip-yuanxiaoku-diqufenshuxian/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-diqufenshuxian 院校库地区分数线
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-diqufenshuxian/mip-yuanxiaoku-diqufenshuxian.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-diqufenshuxian/mip-yuanxiaoku-diqufenshuxian.js
## 示例
diff --git a/mip-yuanxiaoku-fastsearch/README.md b/mip-yuanxiaoku-fastsearch/README.md
index 60f5f1f7..c3788606 100644
--- a/mip-yuanxiaoku-fastsearch/README.md
+++ b/mip-yuanxiaoku-fastsearch/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-fastsearch 院校库快速搜索
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-fastsearch/mip-yuanxiaoku-fastsearch.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-fastsearch/mip-yuanxiaoku-fastsearch.js
## 示例
diff --git a/mip-yuanxiaoku-fenshuxian/README.md b/mip-yuanxiaoku-fenshuxian/README.md
index 3e7c6e25..76053f16 100644
--- a/mip-yuanxiaoku-fenshuxian/README.md
+++ b/mip-yuanxiaoku-fenshuxian/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-fenshuxian 院校库分数线
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-fenshuxian/mip-yuanxiaoku-fenshuxian.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-fenshuxian/mip-yuanxiaoku-fenshuxian.js
## 示例
diff --git a/mip-yuanxiaoku-homevs/README.md b/mip-yuanxiaoku-homevs/README.md
index 7f40bc4c..fac18ae6 100644
--- a/mip-yuanxiaoku-homevs/README.md
+++ b/mip-yuanxiaoku-homevs/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-homevs 院校库首页对比
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-homevs/mip-yuanxiaoku-homevs.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-homevs/mip-yuanxiaoku-homevs.js
## 示例
diff --git a/mip-yuanxiaoku-schooldetails/README.md b/mip-yuanxiaoku-schooldetails/README.md
index 27726f9e..c18aa65d 100644
--- a/mip-yuanxiaoku-schooldetails/README.md
+++ b/mip-yuanxiaoku-schooldetails/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-schooldetails 院校库学校详情
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-schooldetails/mip-yuanxiaoku-schooldetails.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-schooldetails/mip-yuanxiaoku-schooldetails.js
## 示例
diff --git a/mip-yuanxiaoku-schoollist/README.md b/mip-yuanxiaoku-schoollist/README.md
index 151d1676..4aa42af8 100644
--- a/mip-yuanxiaoku-schoollist/README.md
+++ b/mip-yuanxiaoku-schoollist/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-schoollist 院校库学校列表
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-schoollist/mip-yuanxiaoku-schoollist.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-schoollist/mip-yuanxiaoku-schoollist.js
## 示例
diff --git a/mip-yuanxiaoku-schoolvs/README.md b/mip-yuanxiaoku-schoolvs/README.md
index cd9ab3f7..d298fa2d 100644
--- a/mip-yuanxiaoku-schoolvs/README.md
+++ b/mip-yuanxiaoku-schoolvs/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-schoolvs 院校库学校对比
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-schoolvs/mip-yuanxiaoku-schoolvs.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-schoolvs/mip-yuanxiaoku-schoolvs.js
## 示例
diff --git a/mip-yuanxiaoku-tiaodaxue/README.md b/mip-yuanxiaoku-tiaodaxue/README.md
index 60d687ca..2dfe4b8f 100644
--- a/mip-yuanxiaoku-tiaodaxue/README.md
+++ b/mip-yuanxiaoku-tiaodaxue/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-tiaodaxue 院校库挑大学
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-tiaodaxue/mip-yuanxiaoku-tiaodaxue.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-tiaodaxue/mip-yuanxiaoku-tiaodaxue.js
## 示例
diff --git a/mip-yuanxiaoku-xuanzhuanye/README.md b/mip-yuanxiaoku-xuanzhuanye/README.md
index 0ffb39ab..e6f6efa7 100644
--- a/mip-yuanxiaoku-xuanzhuanye/README.md
+++ b/mip-yuanxiaoku-xuanzhuanye/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-xuanzhuanye 院校库选专业
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-xuanzhuanye/mip-yuanxiaoku-xuanzhuanye.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-xuanzhuanye/mip-yuanxiaoku-xuanzhuanye.js
## 示例
diff --git a/mip-yuanxiaoku-zhuanyedetails/README.md b/mip-yuanxiaoku-zhuanyedetails/README.md
index 53598f56..935bf06d 100644
--- a/mip-yuanxiaoku-zhuanyedetails/README.md
+++ b/mip-yuanxiaoku-zhuanyedetails/README.md
@@ -6,7 +6,7 @@ mip-yuanxiaoku-zhuanyedetails 院校库专业详情
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yuanxiaoku-zhuanyedetails/mip-yuanxiaoku-zhuanyedetails.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yuanxiaoku-zhuanyedetails/mip-yuanxiaoku-zhuanyedetails.js
## 示例
diff --git a/mip-yxdown-floatad/README.md b/mip-yxdown-floatad/README.md
new file mode 100644
index 00000000..3bb27d39
--- /dev/null
+++ b/mip-yxdown-floatad/README.md
@@ -0,0 +1,26 @@
+# mip-yxdown-floatad
+
+mip-yxdown-floatad 第三方广告投放插件
+
+标题|内容
+----|----
+类型|广告
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/v1/mip-yxdown-floatad/mip-yxdown-floatad.js
+
+## 示例
+
+### 基本用法
+```html
+
+
+
+```
+
+
+## 属性
+
+### mmip-yxdown-floatad
+
+说明:第三方广告投放组件
+必选项:否
\ No newline at end of file
diff --git a/mip-yxdown-floatad/mip-yxdown-floatad.js b/mip-yxdown-floatad/mip-yxdown-floatad.js
new file mode 100644
index 00000000..e24009d9
--- /dev/null
+++ b/mip-yxdown-floatad/mip-yxdown-floatad.js
@@ -0,0 +1,27 @@
+/**
+ * @file 第三方广告组件
+ *
+ * @author lixkoo
+ * @copyright 2016 Baidu.com, Inc. All Rights Reserved
+ */
+
+define(function (require) {
+
+ var customElement = require('customElement').create();
+
+ /**
+ * 构造元素,只会运行一次
+ */
+ customElement.prototype.build = function () {
+ var node = document.createElement('script');
+ node.type = 'text/javascript';
+ node.src = 'https://t.yiwan.com/static/yxdown/m_gg_float_tl.js';
+ node.async = 'true';
+ var tanxh = document.getElementsByTagName('head')[0];
+ if (tanxh) {
+ tanxh.insertBefore(node, tanxh.firstChild);
+ }
+ };
+
+ return customElement;
+});
diff --git a/mip-yxdown-floatad/package.json b/mip-yxdown-floatad/package.json
new file mode 100644
index 00000000..1bd54883
--- /dev/null
+++ b/mip-yxdown-floatad/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-yxdown-floatad",
+ "version": "1.0.0",
+ "description": "用于投放第三方广告",
+ "contributors": [
+ {
+ "name": "lixkoo",
+ "email": "lixkoo@outlook.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-yxdown-games/README.md b/mip-yxdown-games/README.md
index 21b0585a..7469b0fd 100644
--- a/mip-yxdown-games/README.md
+++ b/mip-yxdown-games/README.md
@@ -6,7 +6,7 @@ mip-yxdown-games 顶部游戏推荐
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yxdown-games/mip-yxdown-games.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yxdown-games/mip-yxdown-games.js
## 示例
diff --git a/mip-yxdown-shielding-page/README.md b/mip-yxdown-shielding-page/README.md
new file mode 100644
index 00000000..ffe26ca3
--- /dev/null
+++ b/mip-yxdown-shielding-page/README.md
@@ -0,0 +1,24 @@
+# mip-yxdown-shielding-page
+
+mip-yxdown-shielding-page 用来屏蔽一些问题页面
+
+标题|内容
+----|----
+类型|通用
+支持布局|responsive,fixed-height,fill,container,fixed
+所需脚本|https://c.mipcdn.com/static/mip-yxdown-shielding-page/v1/mip-yxdown-shielding-page.js
+
+## 示例
+
+### 基本用法
+```html
+
+
+```
+
+## 属性
+
+### mip-yxdown-shielding-page
+
+说明:用来屏蔽一些问题页面
+必选项:否
\ No newline at end of file
diff --git a/mip-yxdown-shielding-page/mip-yxdown-shielding-page.js b/mip-yxdown-shielding-page/mip-yxdown-shielding-page.js
new file mode 100644
index 00000000..c5cc77c1
--- /dev/null
+++ b/mip-yxdown-shielding-page/mip-yxdown-shielding-page.js
@@ -0,0 +1,52 @@
+/**
+ * @file mip-yxdown-shielding-page 组件
+ * @author lixkoo
+ */
+
+define(function (require) {
+
+ var customElement = require('customElement').create();
+ customElement.prototype.build = function () {
+ window.showkey = function (alist, blist) {
+ window.skeyA = alist;
+ window.skeyB = blist;
+ };
+ var ar = document.createElement('script');
+ ar.src = 'http://static.yxdown.com/all/js/shielding-page.js';
+ ar.type = 'text/javascript';
+ document.body.appendChild(ar);
+ ar.onload = function () {
+ var host = location.host.toLowerCase();
+ var lfun = function (arr) {
+ if (/\/zhibo\/\d+\.html/.test(location.href)) {
+ return 0;
+ }
+
+ var si = false;
+ var func = function () {
+ if (document.title) {
+ var dtitle = document.title.toLowerCase();
+ for (var i = 0; i < arr.length; i++) {
+ if (dtitle.indexOf(arr[i].toLowerCase()) >= 0) {
+
+ window.location = 'http://m.yxdown.com/404';
+ break;
+ }
+ }
+ if (si) {
+ clearInterval(si);
+ }
+ }
+ };
+ func();
+ si = setInterval(func, 100);
+ };
+ lfun(window.skeyA);
+ if (host.indexOf('yxdown.com') >= 0) {
+ lfun(window.skeyB);
+ }
+ };
+ };
+
+ return customElement;
+});
diff --git a/mip-yxdown-shielding-page/package.json b/mip-yxdown-shielding-page/package.json
new file mode 100644
index 00000000..6ae52dc6
--- /dev/null
+++ b/mip-yxdown-shielding-page/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "mip-yxdown-shielding-page",
+ "version": "1.0.0",
+ "description": "用来屏蔽一些问题页面",
+ "contributors": [
+ {
+ "name": "lixkoo",
+ "email": "lixkoo@outlook.com"
+ }
+ ],
+ "engines": {
+ "mip": ">=1.1.0"
+ }
+}
diff --git a/mip-yyg-jump-url/README.md b/mip-yyg-jump-url/README.md
index 36b3dc52..38e1a668 100644
--- a/mip-yyg-jump-url/README.md
+++ b/mip-yyg-jump-url/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yyg-jump-url/mip-yyg-jump-url.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yyg-jump-url/mip-yyg-jump-url.js
## 示例
diff --git a/mip-yyg-meun/README.md b/mip-yyg-meun/README.md
index aed15045..384c53ab 100644
--- a/mip-yyg-meun/README.md
+++ b/mip-yyg-meun/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yyg-meun/mip-yyg-meun.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yyg-meun/mip-yyg-meun.js
## 示例
diff --git a/mip-yyg-photo-wall/README.md b/mip-yyg-photo-wall/README.md
index 2446f6cb..eb369218 100644
--- a/mip-yyg-photo-wall/README.md
+++ b/mip-yyg-photo-wall/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yyg-photo-wall/mip-yyg-photo-wall.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yyg-photo-wall/mip-yyg-photo-wall.js
## 示例
diff --git a/mip-yyg-rolling-wall/README.md b/mip-yyg-rolling-wall/README.md
index b1f5bfd5..5d208fbf 100644
--- a/mip-yyg-rolling-wall/README.md
+++ b/mip-yyg-rolling-wall/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yyg-rolling-wall/mip-yyg-rolling-wall.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yyg-rolling-wall/mip-yyg-rolling-wall.js
## 示例
diff --git a/mip-yys-rem/README.md b/mip-yys-rem/README.md
index 3e102afa..51f5398f 100644
--- a/mip-yys-rem/README.md
+++ b/mip-yys-rem/README.md
@@ -6,7 +6,7 @@ mip-yys-rem 用于移动端rem布局适配
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-yys-rem/mip-yys-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-yys-rem/mip-yys-rem.js
## 示例
diff --git a/mip-zhaopin-tracker/README.md b/mip-zhaopin-tracker/README.md
index b3b4cecd..86845a92 100644
--- a/mip-zhaopin-tracker/README.md
+++ b/mip-zhaopin-tracker/README.md
@@ -6,7 +6,7 @@ mip-zhaopin-tracker pv统计
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zhaopin-tracker/mip-zhaopin-tracker.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zhaopin-tracker/mip-zhaopin-tracker.js
## 示例
diff --git a/mip-zixun/README.md b/mip-zixun/README.md
index 723895e9..56c27bb2 100644
--- a/mip-zixun/README.md
+++ b/mip-zixun/README.md
@@ -6,7 +6,7 @@ mip-zixun 根据关键词加载商务通咨询邀请框 商
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zixun/mip-zixun.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zixun/mip-zixun.js
## 示例
根据关键词加载商务通咨询邀请框
diff --git a/mip-zmall-address-list/README.md b/mip-zmall-address-list/README.md
index 35e1c34c..59367b97 100644
--- a/mip-zmall-address-list/README.md
+++ b/mip-zmall-address-list/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司业务组件
-所需脚本|https://mipcache.bdstatic.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/extensions/platform/v1/mip-zmall-address-list/mip-zmall-address-list.js
+所需脚本|https://mipcache.bdstatic.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/static/v1/mip-zmall-address-list/mip-zmall-address-list.js
## 示例
diff --git a/mip-zmall-address-map/README.md b/mip-zmall-address-map/README.md
index 781b09e5..ec13f68e 100644
--- a/mip-zmall-address-map/README.md
+++ b/mip-zmall-address-map/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型| 公司业务组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-address-map/mip-zmall-address-map.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-address-map/mip-zmall-address-map.js
## 示例
diff --git a/mip-zmall-address/README.md b/mip-zmall-address/README.md
index 63f7ec20..e94b8781 100644
--- a/mip-zmall-address/README.md
+++ b/mip-zmall-address/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司业务组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-address/mip-zmall-address.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-address/mip-zmall-address.js
## 示例
diff --git a/mip-zmall-buy/README.md b/mip-zmall-buy/README.md
index 412af7ea..4a43cc9e 100644
--- a/mip-zmall-buy/README.md
+++ b/mip-zmall-buy/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-buy/mip-zmall-buy.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-buy/mip-zmall-buy.js
## 最新版本
diff --git a/mip-zmall-coupon/README.md b/mip-zmall-coupon/README.md
index 4fb78904..9744bd37 100644
--- a/mip-zmall-coupon/README.md
+++ b/mip-zmall-coupon/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-coupon/mip-zmall-coupon.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-coupon/mip-zmall-coupon.js
## 最新版本
diff --git a/mip-zmall-distance/README.md b/mip-zmall-distance/README.md
index 51ec2d2d..24a6f2d2 100644
--- a/mip-zmall-distance/README.md
+++ b/mip-zmall-distance/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司业务组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-distance/mip-zmall-distance.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-distance/mip-zmall-distance.js
## 示例
diff --git a/mip-zmall-goback/README.md b/mip-zmall-goback/README.md
index 09ebba95..662c135e 100644
--- a/mip-zmall-goback/README.md
+++ b/mip-zmall-goback/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-goback/mip-zmall-goback.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-goback/mip-zmall-goback.js
## 示例
diff --git a/mip-zmall-map/README.md b/mip-zmall-map/README.md
index c8429429..5895520f 100644
--- a/mip-zmall-map/README.md
+++ b/mip-zmall-map/README.md
@@ -6,7 +6,7 @@ Z商城map业务组件|在地图中心显示店铺位置并计算用户与店铺
----|----
类型|指定位置可用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-map/mip-zmall-map.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-map/mip-zmall-map.js
## 最新版本
diff --git a/mip-zmall-popup-window/README.md b/mip-zmall-popup-window/README.md
index 3ce25ae4..cb8cee54 100644
--- a/mip-zmall-popup-window/README.md
+++ b/mip-zmall-popup-window/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司业务组件
-所需脚本|https://mipcache.bdstatic.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/extensions/platform/v1/mip-zmall-popup-window/mip-zmall-popup-window.js
+所需脚本|https://mipcache.bdstatic.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/static/v1/mip-zmall-popup-window/mip-zmall-popup-window.js
## 示例
diff --git a/mip-zmall-reply/README.md b/mip-zmall-reply/README.md
index 9a25c40f..17ae4e7e 100644
--- a/mip-zmall-reply/README.md
+++ b/mip-zmall-reply/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-reply/mip-zmall-reply.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-reply/mip-zmall-reply.js
## 版本更新
diff --git a/mip-zmall-share/README.md b/mip-zmall-share/README.md
index 5bb1e36a..c3e57d71 100644
--- a/mip-zmall-share/README.md
+++ b/mip-zmall-share/README.md
@@ -6,7 +6,7 @@ ZOl商成商品详情分享组件
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/static/v1/mip-fixed/mip-fixed.js
https://c.mipcdn.com/static/v1/mip-share/mip-share.js
https://c.mipcdn.com/extensions/platform/v1/mip-zmall-share/mip-zmall-share.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-fixed/mip-fixed.js
https://c.mipcdn.com/static/v1/mip-share/mip-share.js
https://c.mipcdn.com/static/v1/mip-zmall-share/mip-zmall-share.js
## 版本更新
diff --git a/mip-zmall-store/README.md b/mip-zmall-store/README.md
index af1ac80d..98cc5c97 100644
--- a/mip-zmall-store/README.md
+++ b/mip-zmall-store/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型| 公司业务组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-store/mip-zmall-store.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-store/mip-zmall-store.js
## 示例
diff --git a/mip-zmall-tab/README.md b/mip-zmall-tab/README.md
index 9376d894..291ab740 100644
--- a/mip-zmall-tab/README.md
+++ b/mip-zmall-tab/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-tab/mip-zmall-tab.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-tab/mip-zmall-tab.js
## 示例
diff --git a/mip-zmall-yunfei/README.md b/mip-zmall-yunfei/README.md
index 9c131c15..986e2dd0 100644
--- a/mip-zmall-yunfei/README.md
+++ b/mip-zmall-yunfei/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司业务组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zmall-yunfei/mip-zmall-yunfei.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zmall-yunfei/mip-zmall-yunfei.js
## 示例
diff --git a/mip-zol-ad/README.md b/mip-zol-ad/README.md
index 184b59c5..c7b02c7f 100644
--- a/mip-zol-ad/README.md
+++ b/mip-zol-ad/README.md
@@ -6,7 +6,7 @@ mip-zol-ad 是zol广告组件。
----|----
类型|业务,广告
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-ad/mip-zol-ad.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-ad/mip-zol-ad.js
## 示例
diff --git a/mip-zol-article-merchant/README.md b/mip-zol-article-merchant/README.md
index 9c2cb55d..17cd63de 100644
--- a/mip-zol-article-merchant/README.md
+++ b/mip-zol-article-merchant/README.md
@@ -6,7 +6,7 @@ mip-zol-article-merchant zol业务组件:zol资讯文章页首图下方的经
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-article-merchant/mip-zol-article-merchant.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-article-merchant/mip-zol-article-merchant.js
## 示例
diff --git a/mip-zol-ask-audio/README.md b/mip-zol-ask-audio/README.md
index 087901a3..fdda7114 100644
--- a/mip-zol-ask-audio/README.md
+++ b/mip-zol-ask-audio/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-ask-audio/mip-zol-ask-audio.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-ask-audio/mip-zol-ask-audio.js
## 示例
diff --git a/mip-zol-ask-report/README.md b/mip-zol-ask-report/README.md
index dade5c5c..3f7a89aa 100644
--- a/mip-zol-ask-report/README.md
+++ b/mip-zol-ask-report/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-ask-report/mip-zol-ask-report.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-ask-report/mip-zol-ask-report.js
## 示例
diff --git a/mip-zol-blockload/README.md b/mip-zol-blockload/README.md
index 4b4524d6..833b7614 100644
--- a/mip-zol-blockload/README.md
+++ b/mip-zol-blockload/README.md
@@ -6,7 +6,7 @@
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-blockload/mip-zol-blockload.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-blockload/mip-zol-blockload.js
## 版本介绍
diff --git a/mip-zol-commenter/README.md b/mip-zol-commenter/README.md
index eb5b823e..4140df3b 100644
--- a/mip-zol-commenter/README.md
+++ b/mip-zol-commenter/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-commenter/mip-zol-commenter.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-commenter/mip-zol-commenter.js
## 示例
diff --git a/mip-zol-cr/README.md b/mip-zol-cr/README.md
index cf0e481e..6f141fc6 100644
--- a/mip-zol-cr/README.md
+++ b/mip-zol-cr/README.md
@@ -8,7 +8,7 @@ mip-zol-cr 中关村在线版权信息
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-cr/mip-zol-cr.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-cr/mip-zol-cr.js
## 示例
中关村在线版权信息
diff --git a/mip-zol-dialog/README.md b/mip-zol-dialog/README.md
index f4db4c6b..ed6ed534 100644
--- a/mip-zol-dialog/README.md
+++ b/mip-zol-dialog/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司通用组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-dialog/mip-zol-dialog.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-dialog/mip-zol-dialog.js
## 示例
diff --git a/mip-zol-input/README.md b/mip-zol-input/README.md
index e2eb5f01..a0c08273 100644
--- a/mip-zol-input/README.md
+++ b/mip-zol-input/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司通用组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-input/mip-zol-input.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-input/mip-zol-input.js
## 示例
diff --git a/mip-zol-like/README.md b/mip-zol-like/README.md
index 26bf380d..8f1035b3 100644
--- a/mip-zol-like/README.md
+++ b/mip-zol-like/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-like/mip-zol-like.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-like/mip-zol-like.js
## 最新版本
diff --git a/mip-zol-loadmore/README.md b/mip-zol-loadmore/README.md
index 4785f510..268c9783 100644
--- a/mip-zol-loadmore/README.md
+++ b/mip-zol-loadmore/README.md
@@ -6,7 +6,7 @@
----|----
类型|ZOL内部通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/extensions/platform/v1/mip-zol-loadmore/mip-zol-loadmore.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-mustache/mip-mustache.js
https://c.mipcdn.com/static/v1/mip-zol-loadmore/mip-zol-loadmore.js
## 版本介绍
diff --git a/mip-zol-picker/README.md b/mip-zol-picker/README.md
index d3895c3f..81045c7e 100644
--- a/mip-zol-picker/README.md
+++ b/mip-zol-picker/README.md
@@ -5,7 +5,7 @@
标题|内容
----|----
类型|公司通用组件
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-picker/mip-zol-picker.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-picker/mip-zol-picker.js
## 示例
diff --git a/mip-zol-praise/README.md b/mip-zol-praise/README.md
index 0cca4868..b7e26b2e 100644
--- a/mip-zol-praise/README.md
+++ b/mip-zol-praise/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-praise/mip-zol-praise.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-praise/mip-zol-praise.js
## 示例
diff --git a/mip-zol-rem/README.md b/mip-zol-rem/README.md
index 2510315e..f409b399 100644
--- a/mip-zol-rem/README.md
+++ b/mip-zol-rem/README.md
@@ -6,7 +6,7 @@ rem设置,以及百分比class名字的预设
----|----
类型|通用
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-rem/mip-zol-rem.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-rem/mip-zol-rem.js
## 示例
diff --git a/mip-zol-stats/README.md b/mip-zol-stats/README.md
index 1e19ba0e..ef0ee606 100644
--- a/mip-zol-stats/README.md
+++ b/mip-zol-stats/README.md
@@ -5,7 +5,7 @@ ZOL统计组件
标题|内容
:----|:----
类型|通用
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-stats/mip-zol-stats.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-stats/mip-zol-stats.js
## 示例
diff --git a/mip-zol-user/README.md b/mip-zol-user/README.md
index c263f5a1..d5c2f43d 100644
--- a/mip-zol-user/README.md
+++ b/mip-zol-user/README.md
@@ -6,7 +6,7 @@
----|----
类型|业务
支持布局|N,S|
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-user/mip-zol-user.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-user/mip-zol-user.js
## 示例
diff --git a/mip-zol-wapnav/README.md b/mip-zol-wapnav/README.md
index 17c54d56..c7bcca14 100644
--- a/mip-zol-wapnav/README.md
+++ b/mip-zol-wapnav/README.md
@@ -6,7 +6,7 @@ mip-zol-wapnav 组件说明
----|----
类型|业务组件
支持布局|responsive,fixed-height,fill,container,fixed
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zol-wapnav/mip-zol-wapnav.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zol-wapnav/mip-zol-wapnav.js
## 示例
diff --git a/mip-zpm-commonga/README.md b/mip-zpm-commonga/README.md
index b7f915e9..2937fcc0 100644
--- a/mip-zpm-commonga/README.md
+++ b/mip-zpm-commonga/README.md
@@ -6,7 +6,7 @@ mip-zpm-commonga 统计需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-commonga/mip-zpm-commonga.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-commonga/mip-zpm-commonga.js
## 示例
diff --git a/mip-zpm-company/README.md b/mip-zpm-company/README.md
index c7d0e78e..e9bd757e 100644
--- a/mip-zpm-company/README.md
+++ b/mip-zpm-company/README.md
@@ -6,7 +6,7 @@ mip-zpm-company 公司业务需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-company/mip-zpm-company.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-company/mip-zpm-company.js
## 示例
diff --git a/mip-zpm-globalza/README.md b/mip-zpm-globalza/README.md
index ce823a1a..5c506dc9 100644
--- a/mip-zpm-globalza/README.md
+++ b/mip-zpm-globalza/README.md
@@ -6,7 +6,7 @@ mip-zpm-globalza 统计需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-globalza/mip-zpm-globalza.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-globalza/mip-zpm-globalza.js
## 示例
diff --git a/mip-zpm-jobdetail/README.md b/mip-zpm-jobdetail/README.md
index ec3fa86e..c36c2a0f 100644
--- a/mip-zpm-jobdetail/README.md
+++ b/mip-zpm-jobdetail/README.md
@@ -6,7 +6,7 @@ mip-zpm-jobdetail 职位业务需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-jobdetail/mip-zpm-jobdetail.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-jobdetail/mip-zpm-jobdetail.js
## 示例
diff --git a/mip-zpm-searchjobs/README.md b/mip-zpm-searchjobs/README.md
index 07ab361a..9a2df8f8 100644
--- a/mip-zpm-searchjobs/README.md
+++ b/mip-zpm-searchjobs/README.md
@@ -6,7 +6,7 @@ mip-zpm-searchjobs 搜索结果需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-searchjobs/mip-zpm-searchjobs.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-searchjobs/mip-zpm-searchjobs.js
## 示例
diff --git a/mip-zpm-sindex/README.md b/mip-zpm-sindex/README.md
index 04ffbc56..02ba8ea3 100644
--- a/mip-zpm-sindex/README.md
+++ b/mip-zpm-sindex/README.md
@@ -6,7 +6,7 @@ mip-zpm-sindex 首页需求
----|----
类型|通用
支持布局|N/S
-所需脚本|https://c.mipcdn.com/extensions/platform/v1/mip-zpm-sindex/mip-zpm-sindex.js
+所需脚本|https://c.mipcdn.com/static/v1/mip-zpm-sindex/mip-zpm-sindex.js
## 示例