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:
238
f_scripts/shared/videojs/videojs.thumbnails.js
Normal file
238
f_scripts/shared/videojs/videojs.thumbnails.js
Normal file
@@ -0,0 +1,238 @@
|
||||
(function() {
|
||||
var defaults = {
|
||||
width:0, height:0, basePath : ""
|
||||
},
|
||||
extend = function() {
|
||||
var args, target, i, object, property;
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
target = args.shift() || {};
|
||||
for (i in args) {
|
||||
object = args[i];
|
||||
for (property in object) {
|
||||
if (object.hasOwnProperty(property)) {
|
||||
if (typeof object[property] === 'object') {
|
||||
target[property] = extend(target[property], object[property]);
|
||||
} else {
|
||||
target[property] = object[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
},
|
||||
getComputedStyle = function(el, pseudo) {
|
||||
return function(prop) {
|
||||
if (window.getComputedStyle) {
|
||||
return window.getComputedStyle(el, pseudo)[prop];
|
||||
} else {
|
||||
return el.currentStyle[prop];
|
||||
}
|
||||
};
|
||||
},
|
||||
offsetParent = function(el) {
|
||||
if (el.nodeName !== 'HTML' && getComputedStyle(el)('position') === 'static') {
|
||||
return offsetParent(el.offsetParent);
|
||||
}
|
||||
return el;
|
||||
},
|
||||
getScrollOffset = function() {
|
||||
if (window.pageXOffset) {
|
||||
return {
|
||||
x: window.pageXOffset,
|
||||
y: window.pageYOffset
|
||||
};
|
||||
}
|
||||
return {
|
||||
x: document.documentElement.scrollLeft,
|
||||
y: document.documentElement.scrollTop
|
||||
};
|
||||
},
|
||||
parseImageLink = function(imglocation) {
|
||||
var lsrc, clip, hashindex, hashstring;
|
||||
hashindex = imglocation.indexOf('#');
|
||||
if (hashindex === -1) {
|
||||
return {src:imglocation,w:0,h:0,x:0,y:0};
|
||||
}
|
||||
lsrc = imglocation.substring(0,hashindex);
|
||||
hashstring = imglocation.substring(hashindex+1);
|
||||
if (hashstring.substring(0,5) !== 'xywh=') {
|
||||
return {src:defaults.basePath + lsrc,w:0,h:0,x:0,y:0};
|
||||
}
|
||||
var data = hashstring.substring(5).split(',');
|
||||
return {src:defaults.basePath + lsrc,w:parseInt(data[2]),h:parseInt(data[3]),x:parseInt(data[0]),y:parseInt(data[1])};
|
||||
};
|
||||
|
||||
/**
|
||||
* register the thubmnails plugin
|
||||
*/
|
||||
videojs.plugin('thumbnails', function(options) {
|
||||
var div, settings, img, player, progressControl, duration, moveListener, moveCancel, thumbTrack;
|
||||
defaults.basePath = options.basePath || defaults.basePath;
|
||||
settings = extend({}, defaults, options);
|
||||
player = this;
|
||||
//detect which track we use. For now we just use the first metadata track
|
||||
var numtracks = player.textTracks().length;
|
||||
if (numtracks === 0) {
|
||||
return;
|
||||
}
|
||||
i = 0;
|
||||
while (i<numtracks) {
|
||||
if (player.textTracks()[i].kind==='metadata') {
|
||||
thumbTrack = player.textTracks()[i];
|
||||
//Chrome needs this
|
||||
thumbTrack.mode = 'hidden';
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
(function() {
|
||||
var progressControl, addFakeActive, removeFakeActive;
|
||||
// Android doesn't support :active and :hover on non-anchor and non-button elements
|
||||
// so, we need to fake the :active selector for thumbnails to show up.
|
||||
if (navigator.userAgent.toLowerCase().indexOf("android") !== -1) {
|
||||
progressControl = player.controlBar.progressControl;
|
||||
|
||||
addFakeActive = function() {
|
||||
progressControl.addClass('fake-active');
|
||||
};
|
||||
removeFakeActive = function() {
|
||||
progressControl.removeClass('fake-active');
|
||||
};
|
||||
|
||||
progressControl.on('touchstart', addFakeActive);
|
||||
progressControl.on('touchend', removeFakeActive);
|
||||
progressControl.on('touchcancel', removeFakeActive);
|
||||
}
|
||||
})();
|
||||
|
||||
// create the thumbnail
|
||||
div = document.createElement('div');
|
||||
div.className = 'vjs-thumbnail-holder';
|
||||
img = document.createElement('img');
|
||||
div.appendChild(img);
|
||||
img.className = 'vjs-thumbnail';
|
||||
dur = document.createElement('div');
|
||||
dur.className = 'vjs-thumb-duration';
|
||||
div.appendChild(dur);
|
||||
|
||||
// keep track of the duration to calculate correct thumbnail to display
|
||||
duration = player.duration();
|
||||
|
||||
// when the container is MP4
|
||||
player.on('durationchange', function(event) {
|
||||
duration = player.duration();
|
||||
});
|
||||
|
||||
// when the container is HLS
|
||||
player.on('loadedmetadata', function(event) {
|
||||
duration = player.duration();
|
||||
});
|
||||
|
||||
// add the thumbnail to the player
|
||||
progressControl = player.controlBar.progressControl;
|
||||
progressControl.el().appendChild(div);
|
||||
|
||||
var eleForTime;
|
||||
for( var e in progressControl.el().childNodes ) {
|
||||
var childNode = progressControl.el().childNodes[e];
|
||||
if( childNode.className.indexOf('vjs-progress-holder') >= 0 ) {
|
||||
eleForTime = childNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
moveListener = function(event) {
|
||||
var mouseTime, time, active, left, setting, pageX, right, width, halfWidth, pageXOffset, clientRect;
|
||||
active = 0;
|
||||
pageXOffset = getScrollOffset().x;
|
||||
clientRect = offsetParent(progressControl.el()).getBoundingClientRect();
|
||||
right = (clientRect.width || clientRect.right) + pageXOffset;
|
||||
|
||||
pageX = event.pageX;
|
||||
if (event.changedTouches) {
|
||||
pageX = event.changedTouches[0].pageX;
|
||||
}
|
||||
|
||||
// find the page offset of the mouse
|
||||
left = pageX || (event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft);
|
||||
// subtract the page offset of the positioned offset parent
|
||||
left -= eleForTime.getBoundingClientRect().left + pageXOffset;
|
||||
|
||||
// apply updated styles to the thumbnail if necessary
|
||||
// mouseTime is the position of the mouse along the progress control bar
|
||||
// `left` applies to the mouse position relative to the player so we need
|
||||
// to remove the progress control's left offset to know the mouse position
|
||||
// relative to the progress control
|
||||
mouseTime = left / eleForTime.getBoundingClientRect().width * duration;
|
||||
|
||||
dur.innerHTML = $('.vjs-mouse-display').attr('data-current-time');
|
||||
|
||||
//Now check which of the cues applies
|
||||
var cnum = thumbTrack&&thumbTrack.cues.length;
|
||||
i = 0;
|
||||
while (i<cnum) {
|
||||
var ccue = thumbTrack.cues[i];
|
||||
if (ccue.startTime <= mouseTime && ccue.endTime >= mouseTime) {
|
||||
setting = parseImageLink(ccue.text);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
//None found, so show nothing
|
||||
if (typeof setting === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
//Changed image?
|
||||
if (setting.src && img.src != setting.src) {
|
||||
img.src = setting.src;
|
||||
}
|
||||
|
||||
//Fall back to plugin defaults in case no height/width is specified
|
||||
if (setting.w === 0) {
|
||||
setting.w = settings.width;
|
||||
}
|
||||
if (setting.h === 0) {
|
||||
setting.h = settings.height;
|
||||
}
|
||||
|
||||
//Set the container width/height if it changed
|
||||
if (div.style.width != setting.w || div.style.height != setting.h) {
|
||||
div.style.width = setting.w + 'px';
|
||||
div.style.height = setting.h + 'px';
|
||||
}
|
||||
//Set the image cropping
|
||||
img.style.left = -(setting.x) + 'px';
|
||||
img.style.top = -(setting.y) + 'px';
|
||||
img.style.clip = 'rect('+setting.y+'px,'+(setting.w+setting.x)+'px,'+(setting.y+setting.h)+'px,'+setting.x+'px)';
|
||||
|
||||
width = setting.w;
|
||||
halfWidth = width / 2;
|
||||
|
||||
// make sure that the thumbnail doesn't fall off the right side of the left side of the player
|
||||
if ( (left + halfWidth) > right ) {
|
||||
left = right - width;
|
||||
} else if (left < halfWidth) {
|
||||
left = 0;
|
||||
} else {
|
||||
left = left - halfWidth;
|
||||
}
|
||||
|
||||
div.style.left = left + 'px';
|
||||
};
|
||||
|
||||
// update the thumbnail while hovering
|
||||
progressControl.on('mousemove', moveListener);
|
||||
progressControl.on('touchmove', moveListener);
|
||||
|
||||
moveCancel = function(event) {
|
||||
div.style.left = '-1000px';
|
||||
};
|
||||
|
||||
// move the placeholder out of the way when not hovering
|
||||
progressControl.on('mouseout', moveCancel);
|
||||
progressControl.on('touchcancel', moveCancel);
|
||||
progressControl.on('touchend', moveCancel);
|
||||
player.on('userinactive', moveCancel);
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user