feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
/**
* Copyright 2014 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$(document).ready(function() {
if (adTagUrl == '' || ad_client != 'vast') {
return;
}
pb = "view-player-";
var player = videojs(pb+fk);
var options = {
adTagUrl: adTagUrl,
adCancelTimeout: 5000,
playAdAlways: true,
adsEnabled: true,
vpaidFlashLoaderPath: current_url + 'f_scripts/shared/videojs/VPAIDFlash.swf'
};
player.vastClient(options);
player.on('vast.adStart', function () {
count=(typeof ad_skip != "undefined" ? parseInt(ad_skip) : 0);
if (count > 0) {
counter=setInterval(timer, 1000);
html = '<div class="vast-skip-button">Skip <span id="skipspan">in <span id="skiptime">'+count+'</span>...</span></div>';
$(".video-js").append(html);
}
$('.vjs-ad-playing .vjs-resolution-button').css({'display':'none'});
});
player.on('vast.adEnd', function () {
$('.vjs-resolution-button').css({'display':'block'});
$('.vast-skip-button').detach();
});
});
function timer() { count=count-1; if (count <= 0) { clearInterval(counter); $('#skipspan').detach(); $('.vast-skip-button').addClass('enabled'); $('.vjs-ad-playing .vjs-progress-control').css({'pointer-events':'auto'}); $(".vast-skip-button.enabled").click(function() { player.trigger('vast.adEnd'); $(this).detach(); $('.vjs-resolution-button').css({'display':'block'}); }); return; } document.getElementById("skiptime").innerHTML= count; }

View File

@@ -0,0 +1 @@
$(document).ready(function(){if(adTagUrl==""||ad_client!="vast"){return}pb="view-player-";var player=videojs(pb+fk);var options={adTagUrl:adTagUrl,adCancelTimeout:5e3,playAdAlways:true,adsEnabled:true,vpaidFlashLoaderPath:current_url+"f_scripts/shared/videojs/VPAIDFlash.swf"};player.vastClient(options);player.on("vast.adStart",function(){count=typeof ad_skip!="undefined"?parseInt(ad_skip):0;if(count>0){counter=setInterval(timer,1e3);html='<div class="vast-skip-button">Skip <span id="skipspan">in <span id="skiptime">'+count+"</span>...</span></div>";$(".video-js").append(html)}$(".vjs-ad-playing .vjs-resolution-button").css({display:"none"})});player.on("vast.adEnd",function(){$(".vjs-resolution-button").css({display:"block"});$(".vast-skip-button").detach()})});function timer(){count=count-1;if(count<=0){clearInterval(counter);$("#skipspan").detach();$(".vast-skip-button").addClass("enabled");$(".vjs-ad-playing .vjs-progress-control").css({"pointer-events":"auto"});$(".vast-skip-button.enabled").click(function(){player.trigger("vast.adEnd");$(this).detach();$(".vjs-resolution-button").css({display:"block"})});return}document.getElementById("skiptime").innerHTML=count}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,482 @@
/**
* jQuery.marquee - scrolling text like old marquee element
* @author Aamir Afridi - aamirafridi(at)gmail(dot)com / http://aamirafridi.com/jquery/jquery-marquee-plugin
*/;
(function($) {
$.fn.marquee = function(options) {
return this.each(function() {
// Extend the options if any provided
var o = $.extend({}, $.fn.marquee.defaults, options),
$this = $(this),
$marqueeWrapper, containerWidth, animationCss, verticalDir, elWidth,
loopCount = 3,
playState = 'animation-play-state',
css3AnimationIsSupported = false,
// Private methods
_prefixedEvent = function(element, type, callback) {
var pfx = ["webkit", "moz", "MS", "o", ""];
for (var p = 0; p < pfx.length; p++) {
if (!pfx[p]) type = type.toLowerCase();
element.addEventListener(pfx[p] + type, callback, false);
}
},
_objToString = function(obj) {
var tabjson = [];
for (var p in obj) {
if (obj.hasOwnProperty(p)) {
tabjson.push(p + ':' + obj[p]);
}
}
tabjson.push();
return '{' + tabjson.join(',') + '}';
},
_startAnimationWithDelay = function() {
$this.timer = setTimeout(animate, o.delayBeforeStart);
},
// Public methods
methods = {
pause: function() {
if (css3AnimationIsSupported && o.allowCss3Support) {
$marqueeWrapper.css(playState, 'paused');
} else {
// pause using pause plugin
if ($.fn.pause) {
$marqueeWrapper.pause();
}
}
// save the status
$this.data('runningStatus', 'paused');
// fire event
$this.trigger('paused');
},
resume: function() {
// resume using css3
if (css3AnimationIsSupported && o.allowCss3Support) {
$marqueeWrapper.css(playState, 'running');
} else {
// resume using pause plugin
if ($.fn.resume) {
$marqueeWrapper.resume();
}
}
// save the status
$this.data('runningStatus', 'resumed');
// fire event
$this.trigger('resumed');
},
toggle: function() {
methods[$this.data('runningStatus') == 'resumed' ? 'pause' : 'resume']();
},
destroy: function() {
// Clear timer
clearTimeout($this.timer);
// Unbind all events
$this.find("*").addBack().unbind();
// Just unwrap the elements that has been added using this plugin
$this.html($this.find('.js-marquee:first').html());
}
};
// Check for methods
if (typeof options === 'string') {
if ($.isFunction(methods[options])) {
// Following two IF statements to support public methods
if (!$marqueeWrapper) {
$marqueeWrapper = $this.find('.js-marquee-wrapper');
}
if ($this.data('css3AnimationIsSupported') === true) {
css3AnimationIsSupported = true;
}
methods[options]();
}
return;
}
/* Check if element has data attributes. They have top priority
For details https://twitter.com/aamirafridi/status/403848044069679104 - Can't find a better solution :/
jQuery 1.3.2 doesn't support $.data().KEY hence writting the following */
var dataAttributes = {},
attr;
$.each(o, function(key, value) {
// Check if element has this data attribute
attr = $this.attr('data-' + key);
if (typeof attr !== 'undefined') {
// Now check if value is boolean or not
switch (attr) {
case 'true':
attr = true;
break;
case 'false':
attr = false;
break;
}
o[key] = attr;
}
});
// since speed option is changed to duration, to support speed for those who are already using it
o.duration = o.speed || o.duration;
// Shortcut to see if direction is upward or downward
verticalDir = o.direction == 'up' || o.direction == 'down';
// no gap if not duplicated
o.gap = o.duplicated ? parseInt(o.gap) : 0;
// wrap inner content into a div
$this.wrapInner('<div class="js-marquee"></div>');
// Make copy of the element
var $el = $this.find('.js-marquee').css({
'margin-right': o.gap,
'float': 'left'
});
if (o.duplicated) {
$el.clone(true).appendTo($this);
}
// wrap both inner elements into one div
$this.wrapInner('<div style="width:100000px" class="js-marquee-wrapper"></div>');
// Save the reference of the wrapper
$marqueeWrapper = $this.find('.js-marquee-wrapper');
// If direction is up or down, get the height of main element
if (verticalDir) {
var containerHeight = $this.height();
$marqueeWrapper.removeAttr('style');
$this.height(containerHeight);
// Change the CSS for js-marquee element
$this.find('.js-marquee').css({
'float': 'none',
'margin-bottom': o.gap,
'margin-right': 0
});
// Remove bottom margin from 2nd element if duplicated
if (o.duplicated) $this.find('.js-marquee:last').css({
'margin-bottom': 0
});
var elHeight = $this.find('.js-marquee:first').height() + o.gap;
// adjust the animation speed according to the text length
if (o.startVisible && !o.duplicated) {
// Compute the complete animation duration and save it for later reference
// formula is to: (Height of the text node + height of the main container / Height of the main container) * speed;
o._completeDuration = ((parseInt(elHeight, 10) + parseInt(containerHeight, 10)) / parseInt(containerHeight, 10)) * o.duration;
// formula is to: (Height of the text node / height of the main container) * speed
o.duration = (parseInt(elHeight, 10) / parseInt(containerHeight, 10)) * o.duration;
} else {
// formula is to: (Height of the text node + height of the main container / Height of the main container) * speed;
o.duration = ((parseInt(elHeight, 10) + parseInt(containerHeight, 10)) / parseInt(containerHeight, 10)) * o.duration;
}
} else {
// Save the width of the each element so we can use it in animation
elWidth = $this.find('.js-marquee:first').width() + o.gap;
// container width
containerWidth = $this.width();
// adjust the animation speed according to the text length
if (o.startVisible && !o.duplicated) {
// Compute the complete animation duration and save it for later reference
// formula is to: (Width of the text node + width of the main container / Width of the main container) * speed;
o._completeDuration = ((parseInt(elWidth, 10) + parseInt(containerWidth, 10)) / parseInt(containerWidth, 10)) * o.duration;
// (Width of the text node / width of the main container) * speed
o.duration = (parseInt(elWidth, 10) / parseInt(containerWidth, 10)) * o.duration;
} else {
// formula is to: (Width of the text node + width of the main container / Width of the main container) * speed;
o.duration = ((parseInt(elWidth, 10) + parseInt(containerWidth, 10)) / parseInt(containerWidth, 10)) * o.duration;
}
}
// if duplicated then reduce the speed
if (o.duplicated) {
o.duration = o.duration / 2;
}
if (o.allowCss3Support) {
var
elm = document.body || document.createElement('div'),
animationName = 'marqueeAnimation-' + Math.floor(Math.random() * 10000000),
domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
animationString = 'animation',
animationCss3Str = '',
keyframeString = '';
// Check css3 support
if (elm.style.animation) {
keyframeString = '@keyframes ' + animationName + ' ';
css3AnimationIsSupported = true;
}
if (css3AnimationIsSupported === false) {
for (var i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
var prefix = '-' + domPrefixes[i].toLowerCase() + '-';
animationString = prefix + animationString;
playState = prefix + playState;
keyframeString = '@' + prefix + 'keyframes ' + animationName + ' ';
css3AnimationIsSupported = true;
break;
}
}
}
if (css3AnimationIsSupported) {
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's infinite ' + o.css3easing;
$this.data('css3AnimationIsSupported', true);
}
}
var _rePositionVertically = function() {
$marqueeWrapper.css('margin-top', o.direction == 'up' ? containerHeight + 'px' : '-' + elHeight + 'px');
},
_rePositionHorizontally = function() {
$marqueeWrapper.css('margin-left', o.direction == 'left' ? containerWidth + 'px' : '-' + elWidth + 'px');
};
// if duplicated option is set to true than position the wrapper
if (o.duplicated) {
if (verticalDir) {
if (o.startVisible) {
$marqueeWrapper.css('margin-top', 0);
} else {
$marqueeWrapper.css('margin-top', o.direction == 'up' ? containerHeight + 'px' : '-' + ((elHeight * 2) - o.gap) + 'px');
}
} else {
if (o.startVisible) {
$marqueeWrapper.css('margin-left', 0);
} else {
$marqueeWrapper.css('margin-left', o.direction == 'left' ? containerWidth + 'px' : '-' + ((elWidth * 2) - o.gap) + 'px');
}
}
// If the text starts out visible we can skip the two initial loops
if (!o.startVisible) {
loopCount = 1;
}
} else if (o.startVisible) {
// We only have two different loops if marquee is duplicated and starts visible
loopCount = 2;
} else {
if (verticalDir) {
_rePositionVertically();
} else {
_rePositionHorizontally();
}
}
// Animate recursive method
var animate = function() {
if (o.duplicated) {
// When duplicated, the first loop will be scroll longer so double the duration
if (loopCount === 1) {
o._originalDuration = o.duration;
if (verticalDir) {
o.duration = o.direction == 'up' ? o.duration + (containerHeight / ((elHeight) / o.duration)) : o.duration * 2;
} else {
o.duration = o.direction == 'left' ? o.duration + (containerWidth / ((elWidth) / o.duration)) : o.duration * 2;
}
// Adjust the css3 animation as well
if (animationCss3Str) {
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing;
}
loopCount++;
}
// On 2nd loop things back to normal, normal duration for the rest of animations
else if (loopCount === 2) {
o.duration = o._originalDuration;
// Adjust the css3 animation as well
if (animationCss3Str) {
animationName = animationName + '0';
keyframeString = $.trim(keyframeString) + '0 ';
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing;
}
loopCount++;
}
}
if (verticalDir) {
if (o.duplicated) {
// Adjust the starting point of animation only when first loops finishes
if (loopCount > 2) {
$marqueeWrapper.css('margin-top', o.direction == 'up' ? 0 : '-' + elHeight + 'px');
}
animationCss = {
'margin-top': o.direction == 'up' ? '-' + elHeight + 'px' : 0
};
} else if (o.startVisible) {
// This loop moves the marquee out of the container
if (loopCount === 2) {
// Adjust the css3 animation as well
if (animationCss3Str) {
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing;
}
animationCss = {
'margin-top': o.direction == 'up' ? '-' + elHeight + 'px' : containerHeight + 'px'
};
loopCount++;
} else if (loopCount === 3) {
// Set the duration for the animation that will run forever
o.duration = o._completeDuration;
// Adjust the css3 animation as well
if (animationCss3Str) {
animationName = animationName + '0';
keyframeString = $.trim(keyframeString) + '0 ';
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing;
}
_rePositionVertically();
}
} else {
_rePositionVertically();
animationCss = {
'margin-top': o.direction == 'up' ? '-' + ($marqueeWrapper.height()) + 'px' : containerHeight + 'px'
};
}
} else {
if (o.duplicated) {
// Adjust the starting point of animation only when first loops finishes
if (loopCount > 2) {
$marqueeWrapper.css('margin-left', o.direction == 'left' ? 0 : '-' + elWidth + 'px');
}
animationCss = {
'margin-left': o.direction == 'left' ? '-' + elWidth + 'px' : 0
};
} else if (o.startVisible) {
// This loop moves the marquee out of the container
if (loopCount === 2) {
// Adjust the css3 animation as well
if (animationCss3Str) {
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's ' + o.delayBeforeStart / 1000 + 's ' + o.css3easing;
}
animationCss = {
'margin-left': o.direction == 'left' ? '-' + elWidth + 'px' : containerWidth + 'px'
};
loopCount++;
} else if (loopCount === 3) {
// Set the duration for the animation that will run forever
o.duration = o._completeDuration;
// Adjust the css3 animation as well
if (animationCss3Str) {
animationName = animationName + '0';
keyframeString = $.trim(keyframeString) + '0 ';
animationCss3Str = animationName + ' ' + o.duration / 1000 + 's 0s infinite ' + o.css3easing;
}
_rePositionHorizontally();
}
} else {
_rePositionHorizontally();
animationCss = {
'margin-left': o.direction == 'left' ? '-' + elWidth + 'px' : containerWidth + 'px'
};
}
}
// fire event
$this.trigger('beforeStarting');
// If css3 support is available than do it with css3, otherwise use jQuery as fallback
if (css3AnimationIsSupported) {
// Add css3 animation to the element
$marqueeWrapper.css(animationString, animationCss3Str);
var keyframeCss = keyframeString + ' { 100% ' + _objToString(animationCss) + '}',
$styles = $marqueeWrapper.find('style');
// Now add the keyframe animation to the marquee element
if ($styles.length !== 0) {
// Bug fixed for jQuery 1.3.x - Instead of using .last(), use following
$styles.filter(":last").html(keyframeCss);
} else {
$('head').append('<style>' + keyframeCss + '</style>');
}
// Animation iteration event
_prefixedEvent($marqueeWrapper[0], "AnimationIteration", function() {
$this.trigger('finished');
});
// Animation stopped
_prefixedEvent($marqueeWrapper[0], "AnimationEnd", function() {
animate();
$this.trigger('finished');
});
} else {
// Start animating
$marqueeWrapper.animate(animationCss, o.duration, o.easing, function() {
// fire event
$this.trigger('finished');
// animate again
if (o.pauseOnCycle) {
_startAnimationWithDelay();
} else {
animate();
}
});
}
// save the status
$this.data('runningStatus', 'resumed');
};
// bind pause and resume events
$this.bind('pause', methods.pause);
$this.bind('resume', methods.resume);
if (o.pauseOnHover) {
$this.bind('mouseenter mouseleave', methods.toggle);
}
// If css3 animation is supported than call animate method at once
if (css3AnimationIsSupported && o.allowCss3Support) {
animate();
} else {
// Starts the recursive method
_startAnimationWithDelay();
}
});
}; // End of Plugin
// Public: plugin defaults options
$.fn.marquee.defaults = {
// If you wish to always animate using jQuery
allowCss3Support: true,
// works when allowCss3Support is set to true - for full list see http://www.w3.org/TR/2013/WD-css3-transitions-20131119/#transition-timing-function
css3easing: 'linear',
// requires jQuery easing plugin. Default is 'linear'
easing: 'linear',
// pause time before the next animation turn in milliseconds
delayBeforeStart: 1000,
// 'left', 'right', 'up' or 'down'
direction: 'left',
// true or false - should the marquee be duplicated to show an effect of continues flow
duplicated: false,
// speed in milliseconds of the marquee in milliseconds
duration: 5000,
// gap in pixels between the tickers
gap: 20,
// on cycle pause the marquee
pauseOnCycle: false,
// on hover pause the marquee - using jQuery plugin https://github.com/tobia/Pause
pauseOnHover: false,
// the marquee is visible initially positioned next to the border towards it will be moving
startVisible: false
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,109 @@
/**
* @videojs-marquee-overlay/videojs-marquee-overlay
* @version 1.0.4
* @copyright 2017 Emre Karatasoglu (emre.karatasoglu@hotmail.com)
* @license Apache-2.0
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
(global.videojsMarqueeOverlay = factory(global.videojs));
}(this, (function (videojs) { 'use strict';
videojs = 'default' in videojs ? videojs['default'] : videojs;
var version = "1.0.4";
// Default options for the plugin.
var defaults = {
contentOfMarquee: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
position: "bottom",
direction: "left",
duration: 15000,
backgroundcolor: "#dedede",
color: "#fefefe"
};
// Cross-compatibility for Video.js 5 and 6.
var registerPlugin = videojs.registerPlugin || videojs.plugin;
var dom = videojs.dom || videojs;
/**
* Function to invoke when the player is ready.
*
* This is a great place for your plugin to initialize itself. When this
* function is called, the player will have its DOM and child components
* in place.
*
* @function onPlayerReady
* @param {Player} player
* A Video.js player object.
*
* @param {Object} [options={}]
* A plain object containing options for the plugin.
*/
var onPlayerReady = function onPlayerReady(player, options) {
player.addClass('vjs-marquee-overlay');
var el = dom.createEl('div', {
className: 'vjs-emre-marquee'
});
el.innerHTML = options.contentOfMarquee;
player.el().appendChild(el);
if (options.position == "bottom") {
el.style.bottom = 0;
} else {
el.style.top = 0;
}
el.style.backgroundColor = options.backgroundcolor;
el.style.color = options.color;
player.on('timeupdate', function () {
if (player.userActive()) {
if (options.position == "bottom") {
el.style.bottom = "30px";
}
} else {
if (options.position == "bottom") {
el.style.bottom = 0;
}
}
});
$(function () {
$('.vjs-emre-marquee').marquee({
duration: options.duration,
gap: 50,
delayBeforeStart: 0,
direction: options.direction,
duplicated: true
});
});
};
/**
* A video.js plugin.
*
* In the plugin function, the value of `this` is a video.js `Player`
* instance. You cannot rely on the player being in a "ready" state here,
* depending on how the plugin is invoked. This may or may not be important
* to you; if not, remove the wait for "ready"!
*
* @function marqueeOverlay
* @param {Object} [options={}]
* An object of options left to the plugin author to define.
*/
var marqueeOverlay = function marqueeOverlay(options) {
var _this = this;
this.ready(function () {
onPlayerReady(_this, videojs.mergeOptions(defaults, options));
});
};
// Register the plugin with video.js.
registerPlugin('marqueeOverlay', marqueeOverlay);
// Include the version number.
marqueeOverlay.VERSION = version;
return marqueeOverlay;
})));

View File

@@ -0,0 +1 @@
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(require("video.js")):typeof define==="function"&&define.amd?define(["video.js"],factory):global.videojsMarqueeOverlay=factory(global.videojs)})(this,function(videojs){"use strict";videojs="default"in videojs?videojs["default"]:videojs;var version="1.0.4";var defaults={contentOfMarquee:"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",position:"bottom",direction:"left",duration:15e3,backgroundcolor:"#dedede",color:"#fefefe"};var registerPlugin=videojs.registerPlugin||videojs.plugin;var dom=videojs.dom||videojs;var onPlayerReady=function onPlayerReady(player,options){player.addClass("vjs-marquee-overlay");var el=dom.createEl("div",{className:"vjs-emre-marquee"});el.innerHTML=options.contentOfMarquee;player.el().appendChild(el);if(options.position=="bottom"){el.style.bottom=0}else{el.style.top=0}el.style.backgroundColor=options.backgroundcolor;el.style.color=options.color;player.on("timeupdate",function(){if(player.userActive()){if(options.position=="bottom"){el.style.bottom="30px"}}else{if(options.position=="bottom"){el.style.bottom=0}}});$(function(){$(".vjs-emre-marquee").marquee({duration:options.duration,gap:50,delayBeforeStart:0,direction:options.direction,duplicated:true})})};var marqueeOverlay=function marqueeOverlay(options){var _this=this;this.ready(function(){onPlayerReady(_this,videojs.mergeOptions(defaults,options))})};registerPlugin("marqueeOverlay",marqueeOverlay);marqueeOverlay.VERSION=version;return marqueeOverlay});

View File

@@ -0,0 +1,794 @@
/**
* Basic Ad support plugin for video.js.
*
* Common code to support ad integrations.
*/
(function(window, videojs, undefined) {
'use strict';
var
VIDEO_EVENTS = videojs.getComponent('Html5').Events,
/**
* If ads are not playing, pauses the player at the next available
* opportunity. Has no effect if ads have started. This function is necessary
* because pausing a video element while processing a `play` event on iOS can
* cause the video element to continuously toggle between playing and paused
* states.
*
* @param {object} player The video player
*/
cancelContentPlay = function(player) {
if (player.ads.cancelPlayTimeout) {
// another cancellation is already in flight, so do nothing
return;
}
player.ads.cancelPlayTimeout = window.setTimeout(function() {
// deregister the cancel timeout so subsequent cancels are scheduled
player.ads.cancelPlayTimeout = null;
// pause playback so ads can be handled.
if (!player.paused()) {
player.pause();
}
// add a contentplayback handler to resume playback when ads finish.
player.one('contentplayback', function() {
if (player.paused()) {
player.play();
}
});
}, 1);
},
/**
* Returns a boolean indicating if given player is in live mode.
* Can be replaced when this is fixed: https://github.com/videojs/video.js/issues/3262
*/
isLive = function(player) {
if (player.duration() === Infinity) {
return true;
} else if (videojs.browser.IOS_VERSION === "8" && player.duration() === 0) {
return true;
}
return false;
},
/**
* Returns an object that captures the portions of player state relevant to
* video playback. The result of this function can be passed to
* restorePlayerSnapshot with a player to return the player to the state it
* was in when this function was invoked.
* @param {object} player The videojs player object
*/
getPlayerSnapshot = function(player) {
var currentTime;
if (videojs.browser.IS_IOS && isLive(player)) {
// Record how far behind live we are
currentTime = player.currentTime() - player.seekable().end(0);
} else {
currentTime = player.currentTime();
}
var
tech = player.$('.vjs-tech'),
tracks = player.remoteTextTracks ? player.remoteTextTracks() : [],
track,
i,
suppressedTracks = [],
snapshot = {
ended: player.ended(),
currentSrc: player.currentSrc(),
src: player.src(),
currentTime: currentTime,
type: player.currentType()
};
if (tech) {
snapshot.nativePoster = tech.poster;
snapshot.style = tech.getAttribute('style');
}
i = tracks.length;
while (i--) {
track = tracks[i];
suppressedTracks.push({
track: track,
mode: track.mode
});
track.mode = 'disabled';
}
snapshot.suppressedTracks = suppressedTracks;
return snapshot;
},
/**
* Attempts to modify the specified player so that its state is equivalent to
* the state of the snapshot.
* @param {object} snapshot - the player state to apply
*/
restorePlayerSnapshot = function(player, snapshot) {
var
// the playback tech
tech = player.$('.vjs-tech'),
// the number of remaining attempts to restore the snapshot
attempts = 20,
suppressedTracks = snapshot.suppressedTracks,
trackSnapshot,
restoreTracks = function() {
var i = suppressedTracks.length;
while (i--) {
trackSnapshot = suppressedTracks[i];
trackSnapshot.track.mode = trackSnapshot.mode;
}
},
// finish restoring the playback state
resume = function() {
var ended = false;
var updateEnded = function() {
ended = true;
};
var currentTime;
if (videojs.browser.IS_IOS && isLive(player)) {
if (snapshot.currentTime < 0) {
// Playback was behind real time, so seek backwards to match
currentTime = player.seekable().end(0) + snapshot.currentTime;
player.currentTime(currentTime);
}
} else {
player.currentTime(snapshot.currentTime);
}
// Resume playback if this wasn't a postroll
if (!snapshot.ended) {
player.play();
} else {
// On iOS 8.1, the "ended" event will not fire if you seek
// directly to the end of a video. To make that behavior
// consistent with the standard, fire a synthetic event if
// "ended" does not fire within 250ms. Note that the ended
// event should occur whether the browser actually has data
// available for that position
// (https://html.spec.whatwg.org/multipage/embedded-content.html#seeking),
// so it should not be necessary to wait for the seek to
// indicate completion.
player.ads.resumeEndedTimeout = window.setTimeout(function() {
if (!ended) {
player.play();
}
player.off('ended', updateEnded);
player.ads.resumeEndedTimeout = null;
}, 250);
player.on('ended', updateEnded);
// Need to clear the resume/ended timeout on dispose. If it fires
// after a player is disposed, an error will be thrown!
player.on('dispose', function() {
window.clearTimeout(player.ads.resumeEndedTimeout);
});
}
},
// determine if the video element has loaded enough of the snapshot source
// to be ready to apply the rest of the state
tryToResume = function() {
// tryToResume can either have been called through the `contentcanplay`
// event or fired through setTimeout.
// When tryToResume is called, we should make sure to clear out the other
// way it could've been called by removing the listener and clearing out
// the timeout.
player.off('contentcanplay', tryToResume);
if (player.ads.tryToResumeTimeout_) {
player.clearTimeout(player.ads.tryToResumeTimeout_);
player.ads.tryToResumeTimeout_ = null;
}
// Tech may have changed depending on the differences in sources of the
// original video and that of the ad
tech = player.el().querySelector('.vjs-tech');
if (tech.readyState > 1) {
// some browsers and media aren't "seekable".
// readyState greater than 1 allows for seeking without exceptions
return resume();
}
if (tech.seekable === undefined) {
// if the tech doesn't expose the seekable time ranges, try to
// resume playback immediately
return resume();
}
if (tech.seekable.length > 0) {
// if some period of the video is seekable, resume playback
return resume();
}
// delay a bit and then check again unless we're out of attempts
if (attempts--) {
window.setTimeout(tryToResume, 50);
} else {
(function() {
try {
resume();
} catch(e) {
videojs.log.warn('Failed to resume the content after an advertisement', e);
}
})();
}
},
// whether the video element has been modified since the
// snapshot was taken
srcChanged;
if (snapshot.nativePoster) {
tech.poster = snapshot.nativePoster;
}
if ('style' in snapshot) {
// overwrite all css style properties to restore state precisely
tech.setAttribute('style', snapshot.style || '');
}
// Determine whether the player needs to be restored to its state
// before ad playback began. With a custom ad display or burned-in
// ads, the content player state hasn't been modified and so no
// restoration is required
srcChanged = player.src() !== snapshot.src || player.currentSrc() !== snapshot.currentSrc;
if (srcChanged) {
// on ios7, fiddling with textTracks too early will cause safari to crash
player.one('contentloadedmetadata', restoreTracks);
// if the src changed for ad playback, reset it
player.src({ src: snapshot.currentSrc, type: snapshot.type });
// safari requires a call to `load` to pick up a changed source
player.load();
// and then resume from the snapshots time once the original src has loaded
// in some browsers (firefox) `canplay` may not fire correctly.
// Reace the `canplay` event with a timeout.
player.one('contentcanplay', tryToResume);
player.ads.tryToResumeTimeout_ = player.setTimeout(tryToResume, 2000);
} else if (!player.ended() || !snapshot.ended) {
// if we didn't change the src, just restore the tracks
restoreTracks();
// the src didn't change and this wasn't a postroll
// just resume playback at the current time.
player.play();
}
},
/**
* Remove the poster attribute from the video element tech, if present. When
* reusing a video element for multiple videos, the poster image will briefly
* reappear while the new source loads. Removing the attribute ahead of time
* prevents the poster from showing up between videos.
* @param {object} player The videojs player object
*/
removeNativePoster = function(player) {
var tech = player.$('.vjs-tech');
if (tech) {
tech.removeAttribute('poster');
}
},
// ---------------------------------------------------------------------------
// Ad Framework
// ---------------------------------------------------------------------------
// default framework settings
defaults = {
// maximum amount of time in ms to wait to receive `adsready` from the ad
// implementation after play has been requested. Ad implementations are
// expected to load any dynamic libraries and make any requests to determine
// ad policies for a video during this time.
timeout: 5000,
// maximum amount of time in ms to wait for the ad implementation to start
// linear ad mode after `readyforpreroll` has fired. This is in addition to
// the standard timeout.
prerollTimeout: 100,
// maximum amount of time in ms to wait for the ad implementation to start
// linear ad mode after `contentended` has fired.
postrollTimeout: 100,
// when truthy, instructs the plugin to output additional information about
// plugin state to the video.js log. On most devices, the video.js log is
// the same as the developer console.
debug: false
},
adFramework = function(options) {
var player = this;
var settings = videojs.mergeOptions(defaults, options);
var fsmHandler;
// prefix all video element events during ad playback
// if the video element emits ad-related events directly,
// plugins that aren't ad-aware will break. prefixing allows
// plugins that wish to handle ad events to do so while
// avoiding the complexity for common usage
(function() {
var videoEvents = VIDEO_EVENTS.concat([
'firstplay',
'loadedalldata'
]);
var returnTrue = function() { return true; };
var triggerEvent = function(type, event) {
// pretend we called stopImmediatePropagation because we want the native
// element events to continue propagating
event.isImmediatePropagationStopped = returnTrue;
event.cancelBubble = true;
event.isPropagationStopped = returnTrue;
player.trigger({
type: type + event.type,
state: player.ads.state,
originalEvent: event
});
};
player.on(videoEvents, function redispatch(event) {
if (player.ads.state === 'ad-playback') {
triggerEvent('ad', event);
} else if (player.ads.state === 'content-playback' && event.type === 'ended') {
triggerEvent('content', event);
} else if (player.ads.state === 'content-resuming') {
if (player.ads.snapshot) {
// the video element was recycled for ad playback
if (player.currentSrc() !== player.ads.snapshot.currentSrc) {
if (event.type === 'loadstart') {
return;
}
return triggerEvent('content', event);
// we ended playing postrolls and the video itself
// the content src is back in place
} else if (player.ads.snapshot.ended) {
if ((event.type === 'pause' ||
event.type === 'ended')) {
// after loading a video, the natural state is to not be started
// in this case, it actually has, so, we do it manually
player.addClass('vjs-has-started');
// let `pause` and `ended` events through, naturally
return;
}
// prefix all other events in content-resuming with `content`
return triggerEvent('content', event);
}
}
if (event.type !== 'playing') {
triggerEvent('content', event);
}
}
});
})();
// We now auto-play when an ad gets loaded if we're playing ads in the same video element as the content.
// The problem is that in IE11, we cannot play in addurationchange but in iOS8, we cannot play from adcanplay.
// This will allow ad-integrations from needing to do this themselves.
player.on(['addurationchange', 'adcanplay'], function() {
if (typeof player.ads.snapshot !== "undefined" && player.currentSrc() === player.ads.snapshot.currentSrc) {
return;
}
player.play();
});
player.on('nopreroll', function() {
player.ads.nopreroll_ = true;
});
player.on('nopostroll', function() {
player.ads.nopostroll_ = true;
});
// replace the ad initializer with the ad namespace
player.ads = {
state: 'content-set',
// Call this when an ad response has been received and there are
// linear ads ready to be played.
startLinearAdMode: function() {
if (player.ads.state === 'preroll?' ||
player.ads.state === 'content-playback' ||
player.ads.state === 'postroll?') {
player.trigger('adstart');
}
},
// Call this when a linear ad pod has finished playing.
endLinearAdMode: function() {
if (player.ads.state === 'ad-playback') {
player.trigger('adend');
}
},
// Call this when an ad response has been received but there are no
// linear ads to be played (i.e. no ads available, or overlays).
// This has no effect if we are already in a linear ad mode. Always
// use endLinearAdMode() to exit from linear ad-playback state.
skipLinearAdMode: function() {
if (player.ads.state !== 'ad-playback') {
player.trigger('adskip');
}
}
};
fsmHandler = function(event) {
// Ad Playback State Machine
var fsm = {
'content-set': {
events: {
'adscanceled': function() {
this.state = 'content-playback';
},
'adsready': function() {
this.state = 'ads-ready';
},
'play': function() {
this.state = 'ads-ready?';
cancelContentPlay(player);
// remove the poster so it doesn't flash between videos
removeNativePoster(player);
},
'adserror': function() {
this.state = 'content-playback';
},
'adskip': function() {
this.state = 'content-playback';
}
}
},
'ads-ready': {
events: {
'play': function() {
this.state = 'preroll?';
cancelContentPlay(player);
},
'adskip': function() {
this.state = 'content-playback';
},
'adserror': function() {
this.state = 'content-playback';
}
}
},
'preroll?': {
enter: function() {
if (player.ads.nopreroll_) {
// This will start the ads manager in case there are later ads
player.trigger('readyforpreroll');
// Don't wait for a preroll
player.trigger('nopreroll');
} else {
// change class to show that we're waiting on ads
player.addClass('vjs-ad-loading');
// schedule an adtimeout event to fire if we waited too long
player.ads.adTimeoutTimeout = window.setTimeout(function() {
player.trigger('adtimeout');
}, settings.prerollTimeout);
// signal to ad plugin that it's their opportunity to play a preroll
player.trigger('readyforpreroll');
}
},
leave: function() {
window.clearTimeout(player.ads.adTimeoutTimeout);
player.removeClass('vjs-ad-loading');
},
events: {
'play': function() {
cancelContentPlay(player);
},
'adstart': function() {
this.state = 'ad-playback';
},
'adskip': function() {
this.state = 'content-playback';
},
'adtimeout': function() {
this.state = 'content-playback';
},
'adserror': function() {
this.state = 'content-playback';
},
'nopreroll': function() {
this.state = 'content-playback';
}
}
},
'ads-ready?': {
enter: function() {
player.addClass('vjs-ad-loading');
player.ads.adTimeoutTimeout = window.setTimeout(function() {
player.trigger('adtimeout');
}, settings.timeout);
},
leave: function() {
window.clearTimeout(player.ads.adTimeoutTimeout);
player.removeClass('vjs-ad-loading');
},
events: {
'play': function() {
cancelContentPlay(player);
},
'adscanceled': function() {
this.state = 'content-playback';
},
'adsready': function() {
this.state = 'preroll?';
},
'adskip': function() {
this.state = 'content-playback';
},
'adtimeout': function() {
this.state = 'content-playback';
},
'adserror': function() {
this.state = 'content-playback';
}
}
},
'ad-playback': {
enter: function() {
// capture current player state snapshot (playing, currentTime, src)
if (videojs.browser.IS_IOS || player.duration() !== Infinity) {
this.snapshot = getPlayerSnapshot(player);
}
// Mute the player behind the ad
if (!videojs.browser.IS_IOS && player.duration() === Infinity) {
this.preAdVolume_ = player.volume();
player.volume(0);
}
// add css to the element to indicate and ad is playing.
player.addClass('vjs-ad-playing');
// remove the poster so it doesn't flash between ads
removeNativePoster(player);
// We no longer need to supress play events once an ad is playing.
// Clear it if we were.
if (player.ads.cancelPlayTimeout) {
window.clearTimeout(player.ads.cancelPlayTimeout);
player.ads.cancelPlayTimeout = null;
}
},
leave: function() {
player.removeClass('vjs-ad-playing');
if (videojs.browser.IS_IOS || player.duration() !== Infinity) {
restorePlayerSnapshot(player, this.snapshot);
}
// Reset the volume to pre-ad levels
if (!videojs.browser.IS_IOS && player.duration() === Infinity) {
player.volume(this.preAdVolume_);
}
// trigger 'adend' as a consistent notification
// event that we're exiting ad-playback.
if (player.ads.triggerevent !== 'adend') {
player.trigger('adend');
}
},
events: {
'adend': function() {
this.state = 'content-resuming';
},
'adserror': function() {
this.state = 'content-resuming';
}
}
},
'content-resuming': {
enter: function() {
if (this.snapshot && this.snapshot.ended) {
window.clearTimeout(player.ads._fireEndedTimeout);
// in some cases, ads are played in a swf or another video element
// so we do not get an ended event in this state automatically.
// If we don't get an ended event we can use, we need to trigger
// one ourselves or else we won't actually ever end the current video.
player.ads._fireEndedTimeout = window.setTimeout(function() {
player.trigger('ended');
}, 1000);
}
},
leave: function() {
window.clearTimeout(player.ads._fireEndedTimeout);
},
events: {
'contentupdate': function() {
this.state = 'content-set';
},
contentresumed: function() {
this.state = 'content-playback';
},
'playing': function() {
this.state = 'content-playback';
},
'ended': function() {
this.state = 'content-playback';
}
}
},
'postroll?': {
enter: function() {
this.snapshot = getPlayerSnapshot(player);
if (player.ads.nopostroll_) {
player.ads.state = 'content-resuming';
window.setTimeout(function() {
player.trigger('ended');
}, 1);
} else {
player.addClass('vjs-ad-loading');
player.ads.adTimeoutTimeout = window.setTimeout(function() {
player.trigger('adtimeout');
}, settings.postrollTimeout);
}
},
leave: function() {
window.clearTimeout(player.ads.adTimeoutTimeout);
player.removeClass('vjs-ad-loading');
},
events: {
'adstart': function() {
this.state = 'ad-playback';
},
'adskip': function() {
this.state = 'content-resuming';
window.setTimeout(function() {
player.trigger('ended');
}, 1);
},
'adtimeout': function() {
this.state = 'content-resuming';
window.setTimeout(function() {
player.trigger('ended');
}, 1);
},
'adserror': function() {
this.state = 'content-resuming';
window.setTimeout(function() {
player.trigger('ended');
}, 1);
}
}
},
'content-playback': {
enter: function() {
// make sure that any cancelPlayTimeout is cleared
if (player.ads.cancelPlayTimeout) {
window.clearTimeout(player.ads.cancelPlayTimeout);
player.ads.cancelPlayTimeout = null;
}
// this will cause content to start if a user initiated
// 'play' event was canceled earlier.
player.trigger({
type: 'contentplayback',
triggerevent: player.ads.triggerevent
});
},
events: {
// in the case of a timeout, adsready might come in late.
'adsready': function() {
player.trigger('readyforpreroll');
},
'adstart': function() {
this.state = 'ad-playback';
},
'contentupdate': function() {
if (player.paused()) {
this.state = 'content-set';
} else {
this.state = 'ads-ready?';
}
},
'contentended': function() {
this.state = 'postroll?';
}
}
}
};
(function(state) {
var noop = function() {};
// process the current event with a noop default handler
((fsm[state].events || {})[event.type] || noop).apply(player.ads);
// check whether the state has changed
if (state !== player.ads.state) {
// record the event that caused the state transition
player.ads.triggerevent = event.type;
// execute leave/enter callbacks if present
(fsm[state].leave || noop).apply(player.ads);
(fsm[player.ads.state].enter || noop).apply(player.ads);
// output debug logging
if (settings.debug) {
videojs.log('ads', player.ads.triggerevent + ' triggered: ' + state + ' -> ' + player.ads.state);
}
}
})(player.ads.state);
};
// register for the events we're interested in
player.on(VIDEO_EVENTS.concat([
// events emitted by ad plugin
'adtimeout',
'contentupdate',
'contentplaying',
'contentended',
'contentresumed',
// events emitted by third party ad implementors
'adsready',
'adserror',
'adscanceled',
'adstart', // startLinearAdMode()
'adend', // endLinearAdMode()
'adskip', // skipLinearAdMode()
'nopreroll'
]), fsmHandler);
// keep track of the current content source
// if you want to change the src of the video without triggering
// the ad workflow to restart, you can update this variable before
// modifying the player's source
player.ads.contentSrc = player.currentSrc();
// implement 'contentupdate' event.
(function(){
var
// check if a new src has been set, if so, trigger contentupdate
checkSrc = function() {
var src;
if (player.ads.state !== 'ad-playback') {
src = player.currentSrc();
if (src !== player.ads.contentSrc) {
player.trigger({
type: 'contentupdate',
oldValue: player.ads.contentSrc,
newValue: src
});
player.ads.contentSrc = src;
}
}
};
// loadstart reliably indicates a new src has been set
player.on('loadstart', checkSrc);
// check immediately in case we missed the loadstart
window.setTimeout(checkSrc, 1);
})();
// kick off the fsm
if (!player.paused()) {
// simulate a play event if we're autoplaying
fsmHandler({type:'play'});
}
};
// register the ad plugin framework
videojs.plugin('ads', adFramework);
})(window, videojs);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long