'use strict'; (function ($, videojs, undefined) { // default setting var defaultSetting = { markerStyle: { 'width': '8px', 'border-radius': '30%', 'background':'url(/ananas/modules/video/marker/point.png) no-repeat center center', 'background-size':'100%' }, markerStyle2: { 'width': '8px', 'border-radius': '30%', 'background':'url(/ananas/modules/video/marker/point2.png) no-repeat center center', 'background-size':'100%' }, markerTip: { display: true, text: function text(marker) { return "Break: " + marker.text; }, time: function time(marker) { return marker.time; }, timeFormat: function timeFormate(marker) { var a = marker.time; if (a < 0) { return "NaN"; } var hh = parseInt(a / 3600); if (hh == 0) { hh = ""; } else { hh = hh < 10 ? "0" + hh : hh; } var mm = parseInt((a - hh * 3600) / 60); if (mm < 10) { mm = "0" + mm; } var ss = parseInt((a - hh * 3600) % 60); if (ss < 10) { ss = "0" + ss; } return hh > 0 ? hh + ":" + mm + ":" + ss : mm + ":" + ss; }, timeFormat1: function timeFormat1(a) { if (a < 0) { return "NaN"; } var hh = parseInt(a / 3600); if (hh == 0) { hh = ""; } else { hh = hh < 10 ? "0" + hh : hh; } var mm = parseInt((a - hh * 3600) / 60); if (mm < 10) { mm = "0" + mm; } var ss = parseInt((a - hh * 3600) % 60); if (ss < 10) { ss = "0" + ss; } return hh > 0 ? hh + ":" + mm + ":" + ss : mm + ":" + ss; } }, breakOverlay: { display: false, displayTime: 3, text: function text(marker) { return "Break overlay: " + marker.overlayText; }, style: { 'width': '100%', 'height': '20%', 'background-color': 'rgba(0,0,0,0.7)', 'color': 'white', 'font-size': '17px' } }, onMarkerClick: function onMarkerClick(marker) {}, onMarkerReached: function onMarkerReached(marker, index) {}, markers: [] }; // create a non-colliding random number function generateUUID() { var d = new Date().getTime(); var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == 'x' ? r : r & 0x3 | 0x8).toString(16); }); return uuid; }; var NULL_INDEX = -1; function registerVideoJsMarkersPlugin(options) { /** * register the markers plugin (dependent on jquery) */ var setting = $.extend(true, {}, defaultSetting, options), markersMap = {}, markersList = [], // list of markers sorted by time videoWrapper = $(this.el()), currentMarkerIndex = NULL_INDEX, player = this, markerTip = null, breakOverlay = null, overlayIndex = NULL_INDEX; function sortMarkersList() { // sort the list by time in asc order markersList.sort(function (a, b) { return setting.markerTip.time(a) - setting.markerTip.time(b); }); } function addMarkers(newMarkers) { newMarkers.forEach(function (marker) { marker.key = generateUUID(); if(marker.type == "KnowledgePoint" && marker.endTime > 0) { videoWrapper.find('.vjs-progress-holder').append(createMarkerDiv(marker)).append(createPeriodDiv(marker)); }else { videoWrapper.find('.vjs-progress-holder').append(createMarkerDiv(marker)); } // store marker in an internal hash map markersMap[marker.key] = marker; markersList.push(marker); }); sortMarkersList(); } function getPosition(marker) { return setting.markerTip.time(marker) / player.duration() * 100; } function getTipPosition(marker) { var percent = setting.markerTip.time(marker) / player.duration(); if (percent * setting.vjsProgressWidth < parseFloat(markerTip.width()) / 2) { return "-20px"; } else if (percent * setting.vjsProgressWidth + parseFloat(markerTip.width()) / 2 > setting.vjsProgressWidth){ return setting.vjsProgressWidth - parseFloat(markerTip.width()) / 2 + "px"; } else { return setting.markerTip.time(marker) / player.duration() * 100 + "%"; } } function getTipMarginLeft(marker) { var percent = setting.markerTip.time(marker) / player.duration(); if (percent * setting.vjsProgressWidth < parseFloat(markerTip.width()) / 2) { return "0px"; } else { return -parseFloat(markerTip.outerWidth()) / 2 + 'px'; } } function createMarkerDiv(marker) { var markerDiv = $("
"); var styleStr = setting.markerStyle; if(marker.text == "片头" || marker.text == "片尾") { styleStr = setting.markerStyle2; } markerDiv.css(styleStr).css({ "margin-left": -parseFloat(markerDiv.css("width")) / 2 + 'px', "left": getPosition(marker) + '%' }).attr("data-marker-key", marker.key).attr("data-marker-time", setting.markerTip.time(marker)); // add user-defined class to marker if (marker.class) { markerDiv.addClass(marker.class); } // bind click event to seek to marker time markerDiv.on('click', function (e) { var preventDefault = false; if (typeof setting.onMarkerClick === "function") { // if return false, prevent default behavior preventDefault = setting.onMarkerClick(marker) === false; } if (!preventDefault) { var key = $(this).data('marker-key'); player.currentTime(setting.markerTip.time(markersMap[key])); } }); if (setting.markerTip.display) { registerMarkerTipHandler(markerDiv); } return markerDiv; } function createPeriodDiv(marker) { var periodDiv = $('
'); var left = getPosition(marker); var right = marker.endTime / player.duration() * 100; var width = right - left; periodDiv.css({ "margin-left": '-4px', "left": left + '%', "width": width + "%" }).attr("data-marker-key", marker.key); if (setting.markerTip.display) { registerPeriodTipHandler(periodDiv); } return periodDiv; } function updateMarkers() { // update UI for markers whose time changed markersList.forEach(function (marker) { var markerDiv = videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key + "']"); var markerTime = setting.markerTip.time(marker); if (markerDiv.data('marker-time') !== markerTime) { markerDiv.css({ "left": getPosition(marker) + '%' }).attr("data-marker-time", markerTime); } }); sortMarkersList(); } function removeMarkers(indexArray) { // reset overlay if (!!breakOverlay) { overlayIndex = NULL_INDEX; breakOverlay.css("visibility", "hidden"); } currentMarkerIndex = NULL_INDEX; var deleteIndexList = []; indexArray.forEach(function (index) { var marker = markersList[index]; if (marker) { // delete from memory delete markersMap[marker.key]; deleteIndexList.push(index); // delete from dom videoWrapper.find(".vjs-marker[data-marker-key='" + marker.key + "']").remove(); } }); // clean up markers array deleteIndexList.reverse(); deleteIndexList.forEach(function (deleteIndex) { markersList.splice(deleteIndex, 1); }); // sort again sortMarkersList(); } // attach hover event handler function registerMarkerTipHandler(markerDiv) { markerDiv.on('mouseover', function () { var marker = markersMap[$(markerDiv).data('marker-key')]; if (!!markerTip) { var sTime = setting.markerTip.timeFormat(marker); var left = getTipPosition(marker); var endText = ''; if (marker.type == "KnowledgePoint" && marker.endTime > 0) { endText = '-' + setting.markerTip.timeFormat1(marker.endTime); } var textDiv = '
' + setting.markerTip.text(marker) + '
' + sTime + endText + '
'; markerTip.find('.vjs-tip-inner').html(textDiv); if (marker.type == "KnowledgePoint") { markerTip.find('.vjs-tip-inner').addClass("vjs-tip-knowledge"); } // margin-left needs to minus the padding length to align correctly with the marker markerTip.css({ "left": left, "margin-left": getTipMarginLeft(marker), "visibility": "visible" }); } }); markerDiv.on('mouseout', function () { !!markerTip && markerTip.css("visibility", "hidden"); }); } function registerPeriodTipHandler(periodDiv) { periodDiv.on('mouseover', function () { if (!!markerTip) { var marker = markersMap[$(periodDiv).data('marker-key')]; var left = $('.vjs-mouse-display')[0].style.left; var sTime = $('.vjs-mouse-display .vjs-time-tooltip').text() var textDiv = '
' + setting.markerTip.text(marker) + '
' + sTime + '
'; markerTip.find('.vjs-tip-inner').html(textDiv); if (marker.type == "KnowledgePoint") { markerTip.find('.vjs-tip-inner').addClass("vjs-tip-knowledge"); } // margin-left needs to minus the padding length to align correctly with the marker markerTip.css({ "left": left, "margin-left": getTipMarginLeft(marker), "visibility": "visible" }); } }); periodDiv.on('mouseout', function () { !!markerTip && markerTip.css("visibility", "hidden"); }); } function initializeMarkerTip() { markerTip = $("
"); videoWrapper.find('.vjs-progress-holder').append(markerTip); var markerTip2 = $("
"); videoWrapper.find('.vjs-progress-holder').append(markerTip2); } // show or hide break overlays function updateBreakOverlay() { if (!setting.breakOverlay.display || currentMarkerIndex < 0) { return; } var currentTime = player.currentTime(); var marker = markersList[currentMarkerIndex]; var markerTime = setting.markerTip.time(marker); if (currentTime >= markerTime && currentTime <= markerTime + setting.breakOverlay.displayTime) { if (overlayIndex !== currentMarkerIndex) { overlayIndex = currentMarkerIndex; breakOverlay && breakOverlay.find('.vjs-break-overlay-text').html(setting.breakOverlay.text(marker)); } breakOverlay && breakOverlay.css('visibility', "visible"); } else { overlayIndex = NULL_INDEX; breakOverlay && breakOverlay.css("visibility", "hidden"); } } // problem when the next marker is within the overlay display time from the previous marker function initializeOverlay() { breakOverlay = $("
").css(setting.breakOverlay.style); videoWrapper.append(breakOverlay); overlayIndex = NULL_INDEX; } function onTimeUpdate() { onUpdateMarker(); updateBreakOverlay(); options.onTimeUpdateAfterMarkerUpdate && options.onTimeUpdateAfterMarkerUpdate(); } function onUpdateMarker() { /* check marker reached in between markers the logic here is that it triggers a new marker reached event only if the player enters a new marker range (e.g. from marker 1 to marker 2). Thus, if player is on marker 1 and user clicked on marker 1 again, no new reached event is triggered) */ if (!markersList.length) { return; } var getNextMarkerTime = function getNextMarkerTime(index) { if (index < markersList.length - 1) { return setting.markerTip.time(markersList[index + 1]); } // next marker time of last marker would be end of video time return player.duration(); }; var currentTime = player.currentTime(); var newMarkerIndex = NULL_INDEX; if (currentMarkerIndex !== NULL_INDEX) { // check if staying at same marker var nextMarkerTime = getNextMarkerTime(currentMarkerIndex); if (currentTime >= setting.markerTip.time(markersList[currentMarkerIndex]) && currentTime < nextMarkerTime) { return; } // check for ending (at the end current time equals player duration) if (currentMarkerIndex === markersList.length - 1 && currentTime === player.duration()) { return; } } // check first marker, no marker is selected if (currentTime < setting.markerTip.time(markersList[0])) { newMarkerIndex = NULL_INDEX; } else { // look for new index for (var i = 0; i < markersList.length; i++) { nextMarkerTime = getNextMarkerTime(i); if (currentTime >= setting.markerTip.time(markersList[i]) && currentTime < nextMarkerTime) { newMarkerIndex = i; break; } } } // set new marker index if (newMarkerIndex !== currentMarkerIndex) { // trigger event if index is not null if (newMarkerIndex !== NULL_INDEX && options.onMarkerReached) { options.onMarkerReached(markersList[newMarkerIndex], newMarkerIndex); } currentMarkerIndex = newMarkerIndex; } } // setup the whole thing function initialize() { if (setting.markerTip.display) { initializeMarkerTip(); } // remove existing markers if already initialized player.markers.removeAll(); addMarkers(options.markers); if (setting.breakOverlay.display) { initializeOverlay(); } onTimeUpdate(); player.on("timeupdate", onTimeUpdate); } // setup the plugin after we loaded video's meta data player.on("loadedmetadata", function () { initialize(); }); // exposed plugin API player.markers = { getMarkers: function getMarkers() { return markersList; }, next: function next() { // go to the next marker from current timestamp var currentTime = player.currentTime(); for (var i = 0; i < markersList.length; i++) { var markerTime = setting.markerTip.time(markersList[i]); if (markerTime > currentTime) { player.currentTime(markerTime); break; } } }, prev: function prev() { // go to previous marker var currentTime = player.currentTime(); for (var i = markersList.length - 1; i >= 0; i--) { var markerTime = setting.markerTip.time(markersList[i]); // add a threshold if (markerTime + 0.5 < currentTime) { player.currentTime(markerTime); return; } } }, add: function add(newMarkers) { // add new markers given an array of index addMarkers(newMarkers); }, remove: function remove(indexArray) { // remove markers given an array of index removeMarkers(indexArray); }, removeAll: function removeAll() { var indexArray = []; for (var i = 0; i < markersList.length; i++) { indexArray.push(i); } removeMarkers(indexArray); }, updateTime: function updateTime() { // notify the plugin to update the UI for changes in marker times updateMarkers(); }, reset: function reset(newMarkers) { // remove all the existing markers and add new ones player.markers.removeAll(); addMarkers(newMarkers); }, destroy: function destroy() { // unregister the plugins and clean up even handlers player.markers.removeAll(); breakOverlay && breakOverlay.remove(); markerTip && markerTip.remove(); player.off("timeupdate", updateBreakOverlay); delete player.markers; } }; } videojs.registerPlugin('markers', registerVideoJsMarkersPlugin); })(jQuery, window.videojs);