diff --git a/Gruntfile.js b/Gruntfile.js index ee7df8f..ab3975d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -17,9 +17,13 @@ module.exports = function(grunt) { }, build: { src: ['src/intro.js', + 'src/polyfill.js', + 'src/options.js', 'src/utils.js', - 'src/html.js', + 'src/events.js', 'src/scroller.js', + 'src/tracklist.js', + 'src/widget.js', 'src/main.js', 'src/outro.js'], dest: 'dist/<%= pkg.name %>.js' @@ -90,7 +94,7 @@ module.exports = function(grunt) { grunt.registerTask('default', ['clean', - 'jshint:src', + //'jshint:src', 'concat', 'jshint:build', 'jshint:test', diff --git a/css/videojs-transcript.css b/css/videojs-transcript.css index 539a067..f3307d1 100644 --- a/css/videojs-transcript.css +++ b/css/videojs-transcript.css @@ -4,7 +4,7 @@ font-family: Arial, sans-serif; } -.transcript { +.transcript-body { height: 200px; overflow-y: scroll; } @@ -34,4 +34,4 @@ .transcript-line.is-active { background-color: #e2fbfb; -} \ No newline at end of file +} diff --git a/css/videojs-transcript3.css b/css/videojs-transcript3.css index 0d84470..a8c606b 100644 --- a/css/videojs-transcript3.css +++ b/css/videojs-transcript3.css @@ -4,7 +4,7 @@ font-family: Arial, sans-serif; } -.transcript-lines { +.transcript-body { height: 200px; overflow-y: scroll; border: 1px solid #111; diff --git a/dist/videojs-transcript.js b/dist/videojs-transcript.js index d309bee..05128aa 100644 --- a/dist/videojs-transcript.js +++ b/dist/videojs-transcript.js @@ -1,167 +1,233 @@ -/*! videojs-transcript - v0.0.0 - 2014-09-20 +/*! videojs-transcript - v0.0.0 - 2014-10-08 * Copyright (c) 2014 Matthew Walsh; Licensed MIT */ (function (window, videojs) { 'use strict'; -var Utils = (function () { - var niceTimestamp = function (timeInSeconds) { - var hour = Math.floor(timeInSeconds / 3600); - var min = Math.floor(timeInSeconds % 3600 / 60); - var sec = Math.floor(timeInSeconds % 60); - sec = (sec < 10) ? '0' + sec : sec; - min = (hour > 0 && min < 10) ? '0' + min : min; - if (hour > 0) { - return hour + ':' + min + ':' + sec; - } - return min + ':' + sec; - }; - return { - niceTimestamp : niceTimestamp, - }; -}()); -var Html = (function () { - var myContainer, subContainer, myPlayer, myPrefix, settings; - var createSeekClickHandler = function (time) { - return function (e) { - myPlayer.currentTime(time); +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel +// MIT license +// https://gist.github.com/paulirish/1579671 +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; }; - }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); - var createLine = function (cue) { - var line = document.createElement('div'); - var timestamp = document.createElement('span'); - var text = document.createElement('span'); - line.className = myPrefix + '-line'; - line.setAttribute('data-begin', cue.startTime); - timestamp.className = myPrefix + '-timestamp'; - timestamp.textContent = Utils.niceTimestamp(cue.startTime); - text.className = myPrefix + '-text'; - text.innerHTML = cue.text; - line.appendChild(timestamp); - line.appendChild(text); - return line; - }; - var setTrack = function (track) { - if (typeof track !== 'object') { - track = myPlayer.textTracks()[track]; +// Object.create() polyfill +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill +if (typeof Object.create != 'function') { + Object.create = (function() { + var Object = function() {}; + return function (prototype) { + if (arguments.length > 1) { + throw Error('Second argument not supported'); + } + if (typeof prototype != 'object') { + throw TypeError('Argument must be an object'); + } + Object.prototype = prototype; + var result = new Object(); + Object.prototype = null; + return result; + }; + })(); +} + +// forEach polyfill +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill +if (!Array.prototype.forEach) { + Array.prototype.forEach = function(callback, thisArg) { + var T, k; + if (this == null) { + throw new TypeError(' this is null or not defined'); } - if (subContainer === undefined) { - throw new Error('videojs-transcript: Html not initialized!'); + var O = Object(this); + var len = O.length >>> 0; + if (typeof callback != "function") { + throw new TypeError(callback + ' is not a function'); } - var line, i; - var fragment = document.createDocumentFragment(); - var createTranscript = function () { - var cues = track.cues(); - for (i = 0; i < cues.length; i++) { - line = createLine(cues[i], myPrefix); - fragment.appendChild(line); + if (arguments.length > 1) { + T = thisArg; + } + k = 0; + while (k < len) { + var kValue; + if (k in O) { + kValue = O[k]; + callback.call(T, kValue, k, O); } - subContainer.innerHTML = ''; - subContainer.appendChild(fragment); - subContainer.setAttribute('lang', track.language()); - }; - subContainer.addEventListener('click', function (event) { - var clickedClasses = event.target.classList; - var clickedTime = event.target.getAttribute('data-begin') || event.target.parentElement.getAttribute('data-begin'); - if (clickedTime !== undefined && clickedTime !== null) { // can be zero - if ((settings.clickArea === 'line') || // clickArea: 'line' activates on all elements - (settings.clickArea === 'timestamp' && clickedClasses.contains(myPrefix + '-timestamp')) || - (settings.clickArea === 'text' && clickedClasses.contains(myPrefix + '-text'))) { - myPlayer.currentTime(clickedTime); + k++; + } + }; +} + +// Global settings +var my = {}; +my.settings = {}; +my.prefix = 'transcript'; +my.player = this; + +// Defaults +var defaults = { + autoscroll: true, + clickArea: 'line' +}; + +/*global my*/ +var utils = (function (plugin) { + return { + secondsToTime: function (timeInSeconds) { + var hour = Math.floor(timeInSeconds / 3600); + var min = Math.floor(timeInSeconds % 3600 / 60); + var sec = Math.floor(timeInSeconds % 60); + sec = (sec < 10) ? '0' + sec : sec; + min = (hour > 0 && min < 10) ? '0' + min : min; + if (hour > 0) { + return hour + ':' + min + ':' + sec; + } + return min + ':' + sec; + }, + localize: function (string) { + return string; // TODO: do something here; + }, + createEl: function (elementName, classSuffix) { + classSuffix = classSuffix || ''; + var el = document.createElement(elementName); + el.className = plugin.prefix + classSuffix; + return el; + }, + extend: function(obj) { + var type = typeof obj; + if (!(type === 'function' || type === 'object' && !!obj)) { + return obj; + } + var source, prop; + for (var i = 1, length = arguments.length; i < length; i++) { + source = arguments[i]; + for (prop in source) { + obj[prop] = source[prop]; } } - }); - if (track.readyState() !== 2) { - track.load(); - track.on('loaded', createTranscript); - } else { - createTranscript(); + return obj; } }; - var createSelector = function (tracks) { - var select = document.createElement('select'); - var i, track, option; - for (i = 0; i < tracks.length; i++) { - track = tracks[i]; - option = document.createElement('option'); - option.value = i; - option.textContent = track.label() + ' (' + track.language() + ')'; - select.appendChild(option); +}(my)); + +var eventEmitter = { + handlers_: [], + on: function on (object, eventtype, callback) { + if (typeof callback === 'function') { + this.handlers_.push([object, eventtype, callback]); + } else { + throw new TypeError('Callback is not a function.'); } - select.addEventListener('change', function (e) { - setTrack(document.querySelector('#' + myPrefix + '-' + myPlayer.id() + ' option:checked').value); + }, + trigger: function trigger (object, eventtype) { + this.handlers_.forEach( function(h) { + if (h[0] === object && + h[1] === eventtype) { + h[2].apply(); + } }); - return select; - }; - var init = function (container, player, prefix, plugin) { - myContainer = container; - myPlayer = player; - myPrefix = prefix; - subContainer = document.createElement('div'); - settings = plugin.options; - myContainer.className = prefix; - subContainer.className = prefix + '-lines'; - myContainer.id = myPrefix + '-' + myPlayer.id(); - myContainer.appendChild(createSelector(myPlayer.textTracks())); - myContainer.appendChild(subContainer); - }; - return { - init: init, - setTrack: setTrack, - }; -}()); -var Scroller = (function () { + }, + delegate: function (obj) { + obj.on = function (event, callback) { + eventEmitter.on(obj, event, callback); + }; + obj.trigger = function (obj) { + eventEmitter.trigget(obj, event); + }; + return obj; + } +}; -// Keep track when the user is scrolling the transcript. +var scrollerProto = function(plugin) { - var userIsScrolling = false; -// Keep track of when the mouse is hovering the transcript. + var initHandlers = function (el) { + var self = this; + // The scroll event. We want to keep track of when the user is scrolling the transcript. + el.addEventListener('scroll', function () { + if (self.isAutoScrolling) { - var mouseIsOverTranscript = false; + // If isAutoScrolling was set to true, we can set it to false and then ignore this event. + // It wasn't the user. + self.isAutoScrolling = false; // event handled + } else { -// The initial element creation triggers a scroll event. We don't want to consider -// this as the user scrolling, so we initialize the isAutoScrolling flag to true. + // We only care about when the user scrolls. Set userIsScrolling to true and add a nice class. + self.userIsScrolling = true; + el.classList.add('is-inuse'); + } + }); - var isAutoScrolling = true; + // The mouseover event. + el.addEventListener('mouseenter', function () { + self.mouseIsOverTranscript = true; + }); + el.addEventListener('mouseleave', function () { + self.mouseIsOverTranscript = false; -// requestAnimationFrame compatibility shim. + // Have a small delay before deciding user as done interacting. + setTimeout(function () { - var requestAnimationFrame = - window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.msRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - function (callback) { - window.setTimeout(callback, 1000 / 60); - }; + // Make sure the user didn't move the pointer back in. + if (!self.mouseIsOverTranscript) { + self.userIsScrolling = false; + el.classList.remove('is-inuse'); + } + }, 1000); + }); + }; -// For smooth animation. + // Init instance variables + var init = function (element, plugin) { + this.element = element; + this.userIsScrolling = false; + this.mouseIsOverTranscript = false; + this.isAutoScrolling = true; + initHandlers.call(this, this.element); + return this; + }; + // Easing function for smoothness. var easeOut = function (time, start, change, duration) { return start + change * Math.sin(Math.min(1, time / duration) * (Math.PI / 2)); }; -// Animate the scrolling. - + // Animate the scrolling. var scrollTo = function (element, newPos, duration) { var startTime = Date.now(); var startPos = element.scrollTop; + var self = this; -// Don't try to scroll beyond the limits. You won't get there and this will loop forever. - + // Don't try to scroll beyond the limits. You won't get there and this will loop forever. newPos = Math.max(0, newPos); newPos = Math.min(element.scrollHeight - element.clientHeight, newPos); var change = newPos - startPos; -// This inner function is called until the elements scrollTop reaches newPos. - + // This inner function is called until the elements scrollTop reaches newPos. var updateScroll = function () { var now = Date.now(); var time = now - startTime; - isAutoScrolling = true; + self.isAutoScrolling = true; element.scrollTop = easeOut(time, startPos, change, duration); if (element.scrollTop !== newPos) { requestAnimationFrame(updateScroll, element); @@ -170,203 +236,250 @@ var Scroller = (function () { requestAnimationFrame(updateScroll, element); }; -// Scroll an element's parent so the element is brought into view. - + // Scroll an element's parent so the element is brought into view. var scrollToElement = function (element) { - var parent = element.parentElement; - var parentOffsetBottom = parent.offsetTop + parent.clientHeight; - var elementOffsetBottom = element.offsetTop + element.clientHeight; - var relPos = (element.offsetTop + element.clientHeight) - parent.offsetTop; - var newPos; - -// If the line is above the top of the parent view, were scrolling up, -// so we want to move the top of the element downwards to match the top of the parent. - - if (relPos < parent.scrollTop) { - newPos = element.offsetTop - parent.offsetTop; - -// If the line is below the parent view, we're scrolling down, so we want the -// bottom edge of the line to move up to meet the bottom edge of the parent. - - } else if (relPos > (parent.scrollTop + parent.clientHeight)) { - newPos = elementOffsetBottom - parentOffsetBottom; - } - -// Don't try to scroll if we haven't set a new position. If we didn't -// set a new position the line is already in view (i.e. It's not above -// or below the view) -// And don't try to scroll when the element is already in position. + if (this.canScroll()) { + var parent = element.parentElement; + var parentOffsetBottom = parent.offsetTop + parent.clientHeight; + var elementOffsetBottom = element.offsetTop + element.clientHeight; + var relPos = (element.offsetTop + element.clientHeight) - parent.offsetTop; + var newPos; + + // If the line is above the top of the parent view, were scrolling up, + // so we want to move the top of the element downwards to match the top of the parent. + if (relPos < parent.scrollTop) { + newPos = element.offsetTop - parent.offsetTop; + + // If the line is below the parent view, we're scrolling down, so we want the + // bottom edge of the line to move up to meet the bottom edge of the parent. + } else if (relPos > (parent.scrollTop + parent.clientHeight)) { + newPos = elementOffsetBottom - parentOffsetBottom; + } - if (newPos !== undefined && parent.scrollTop !== newPos) { - scrollTo(parent, newPos, 400); + // Don't try to scroll if we haven't set a new position. If we didn't + // set a new position the line is already in view (i.e. It's not above + // or below the view) + // And don't try to scroll when the element is already in position. + if (newPos !== undefined && parent.scrollTop !== newPos) { + scrollTo.call(this, parent, newPos, 400); + } } }; -// Set Event Handlers to monitor user scrolling. - - var initHandlers = function (element) { - -// The scroll event. We want to keep track of when the user is scrolling the transcript. - - element.addEventListener('scroll', function () { - if (isAutoScrolling) { - -// If isAutoScrolling was set to true, we can set it to false and then ignore this event. - - isAutoScrolling = false; // event handled - } else { - -// We only care about when the user scrolls. Set userIsScrolling to true and add a nice class. - - userIsScrolling = true; - element.classList.add('is-inuse'); - } - }); - -// The mouseover event. + // Return whether the element is scrollable. + var canScroll = function () { + var el = this.element; + return el.scrollHeight > el.offsetHeight; + }; - element.addEventListener('mouseover', function () { - mouseIsOverTranscript = true; - }); - element.addEventListener('mouseout', function () { - mouseIsOverTranscript = false; + // Return whether the user is interacting with the transcript. + var inUse = function () { + return this.userIsScrolling; + }; -// Have a small delay before deciding user as done interacting. + return { + init: init, + to : scrollToElement, + canScroll : canScroll, + inUse : inUse + } +}(my); - setTimeout(function () { +var scroller = function(element) { + return Object.create(scrollerProto).init(element); +}; -// Make sure the user didn't move the pointer back in. - if (!mouseIsOverTranscript) { - userIsScrolling = false; - element.classList.remove('is-inuse'); +/*global my*/ +var trackList = function (plugin) { + var activeTrack; + return { + get: function () { + var validTracks = []; + my.tracks = my.player.textTracks(); + my.tracks.forEach(function (track) { + if (track.kind() === 'captions' || track.kind() === 'subtitles') { + validTracks.push(track); } - }, 1000); - }); + }); + return validTracks; + }, + active: function (tracks) { + tracks.forEach(function (track) { + if (track.mode() === 2) { + activeTrack = track; + return track; + } + }); + // fallback to first track + return activeTrack || tracks[0]; + }, }; +}(my); -// Return whether the element is scrollable. +/*globals utils, eventEmitter, my, scrollable*/ - var canScroll = function (container) { - return container.scrollHeight > container.offsetHeight; +var widget = function (plugin) { + var my = {}; + my.element = {}; + my.body = {}; + var on = function (event, callback) { + eventEmitter.on(this, event, callback); }; - -// Return whether the user is interacting with the transcript. - - var inUse = function () { - return userIsScrolling; + var trigger = function (event) { + eventEmitter.trigger(this, event); }; - -// Public Methods - - return { - scrollToElement : scrollToElement, - initHandlers : initHandlers, - inUse : inUse, - canScroll : canScroll, + var createTitle = function () { + var header = utils.createEl('header', '-header'); + header.textContent = utils.localize('Transcript'); + return header; }; -}()); -var Plugin = (function (window, videojs) { - var defaults = { - autoscroll: true, - clickArea: 'line' + var createSelector = function (){ + var selector = utils.createEl('select', '-selector'); + plugin.validTracks.forEach(function (track, i) { + var option = document.createElement('option'); + option.value = i; + option.textContent = track.label() + ' (' + track.language() + ')'; + selector.appendChild(option); + }); + selector.addEventListener('change', function (e) { + setTrack(document.querySelector('#' + plugin.prefix + '-' + plugin.player.id() + ' option:checked').value); + trigger('trackchanged'); + }); + return selector; }; - - var transcript = function (options) { - var settings = videojs.util.mergeOptions(defaults, options); - var player = this; - var htmlPrefix = 'transcript'; - var htmlContainer = document.createElement('div'); - var tracks; - var currentTrack; - var getAllTracks = function () { - var i, kind; - var validTracks = []; - tracks = player.textTracks(); - for (i = 0; i < tracks.length; i++) { - kind = tracks[i].kind(); - if (kind === 'captions' || kind === 'subtitles') { - validTracks.push(tracks[i]); - } + var clickToSeekHandler = function (event) { + var clickedClasses = event.target.classList; + var clickedTime = event.target.getAttribute('data-begin') || event.target.parentElement.getAttribute('data-begin'); + if (clickedTime !== undefined && clickedTime !== null) { // can be zero + if ((plugin.settings.clickArea === 'line') || // clickArea: 'line' activates on all elements + (plugin.settings.clickArea === 'timestamp' && clickedClasses.contains(myPrefix + '-timestamp')) || + (plugin.settings.clickArea === 'text' && clickedClasses.contains(myPrefix + '-text'))) { + plugin.player.currentTime(clickedTime); } - return validTracks; - }; - var getActiveTrack = function (tracks) { - var i; - for (i = 0; i < tracks.length; i++) { - if (tracks[i].mode() === 2) { - return tracks[i]; - } + } + }; + var createLine = function (cue) { + var line = utils.createEl('div', '-line'); + var timestamp = utils.createEl('span', '-timestamp'); + var text = utils.createEl('span', '-text'); + line.setAttribute('data-begin', cue.startTime); + timestamp.textContent = utils.secondsToTime(cue.startTime); + text.innerHTML = cue.text; + line.appendChild(timestamp); + line.appendChild(text); + return line; + }; + var createTranscriptBody = function (track) { + if (typeof track !== 'object') { + track = plugin.player.textTracks()[track]; + } + var body = utils.createEl('div', '-body'); + var line, i; + var fragment = document.createDocumentFragment(); + var createTranscript = function () { + var cues = track.cues(); + for (i = 0; i < cues.length; i++) { + line = createLine(cues[i]); + fragment.appendChild(line); } - return currentTrack || tracks[0]; + body.innerHTML = ''; + body.appendChild(fragment); + body.setAttribute('lang', track.language()); }; - var getCaptionNodes = function () { - var i, node, caption; - var nodes = document.querySelectorAll('#' + htmlContainer.id + ' > .' + htmlPrefix + '-line'); - var captions = []; - for (i = 0; i < nodes.length; i++) { - node = nodes[i]; - caption = { - 'element': node, - 'begin': node.getAttribute('data-begin'), - }; - captions.push(caption); + if (track.readyState() !==2) { + track.load(); + track.on('loaded', createTranscript); + } else { + createTranscript(); + } + body.scroll = scroller(body); + body.addEventListener('click', clickToSeekHandler); + return body; + }; + var create = function () { + var el = document.createElement('div'); + my.element = el; + el.setAttribute('id', plugin.prefix + '-' + plugin.player.id()); + var title = createTitle(); + el.appendChild(title); + var selector = createSelector(); + el.appendChild(selector); + my.body = utils.createEl('div', '-body'); + el.appendChild(my.body); + setTrack(plugin.currentTrack); + return this; + }; + var setTrack = function (track) { + var newBody = createTranscriptBody(track); + my.element.replaceChild(newBody, my.body); + my.body = newBody; + }; + var setCue = function (time) { + var active, i, line, begin, end; + var lines = my.body.children; + for (i = 0; i < lines.length; i++) { + line = lines[i]; + begin = line.getAttribute('data-begin'); + if (i < lines.length - 1) { + end = lines[i + 1].getAttribute('data-begin'); + } else { + end = plugin.player.duration() || Infinity; } - return captions; - }; - var timeUpdate = function () { - var caption, end, i; - var time = player.currentTime(); - var captions = getCaptionNodes(); - for (i = 0; i < captions.length; i++) { - caption = captions[i]; - // Remain active until next caption. - // On final caption, remain active until video duration if known, or forever; - if (i < captions.length - 1) { - end = captions[i + 1].begin; - } else { - end = player.duration() || Infinity; - } - if (time > caption.begin && time < end) { - if (!caption.element.classList.contains('is-active')) { // don't update if it hasn't changed - caption.element.classList.add('is-active'); - if (settings.autoscroll && - Scroller.canScroll(htmlContainer) && - !Scroller.inUse()) { - Scroller.scrollToElement(caption.element); - } + if (time > begin && time < end) { + if (!line.classList.contains('is-active')) { // don't update if it hasn't changed + line.classList.add('is-active'); + if (plugin.settings.autoscroll && !my.body.scroll.inUse()) { + my.body.scroll.to(line); } - } else { - caption.element.classList.remove('is-active'); } + } else { + line.classList.remove('is-active'); } - }; - var trackChange = function () { - currentTrack = getActiveTrack(tracks); - Html.setTrack(currentTrack); - }; - tracks = getAllTracks(); - if (tracks.length > 0) { - Html.init(htmlContainer, player, htmlPrefix, this); - Scroller.initHandlers(htmlContainer); - trackChange(); - player.on('timeupdate', timeUpdate); - player.on('captionstrackchange', trackChange); - player.on('subtitlestrackchange', trackChange); - } else { - throw new Error('videojs-transcript: No tracks found!'); } - var el = function () { - return htmlContainer; - }; - return { - el: el, - setTrack: trackChange, - options: options, - }; }; - return {transcript: transcript}; -}(window, videojs)); + var el = function () { + return my.element; + }; + return { + create: create, + setTrack: setTrack, + setCue: setCue, + el : el, + on: on, + trigger: trigger, + }; + +}(my); + +var transcript = function (options) { + my.player = this; + my.validTracks = trackList.get(); + my.currentTrack = trackList.active(my.validTracks); + my.settings = videojs.util.mergeOptions(defaults, options); + my.widget = widget.create(); + var timeUpdate = function () { + my.widget.setCue(my.player.currentTime()); + }; + var updateTrack = function () { + my.currentTrack = trackList.active(my.validTracks); + my.widget.setTrack(my.currentTrack); + }; + if (my.validTracks.length > 0) { + updateTrack(); + my.player.on('timeupdate', timeUpdate); + my.player.on('captionstrackchange', updateTrack); + my.player.on('subtitlestrackchange', updateTrack); + } else { + throw new Error('videojs-transcript: No tracks found!'); + } + return { + el: function () { + return my.widget.el(); + }, + setTrack: my.widget.setTrack + }; +}; +videojs.plugin('transcript', transcript); - videojs.plugin('transcript', Plugin.transcript); -}(window, window.videojs)); \ No newline at end of file +}(window, videojs)); diff --git a/dist/videojs-transcript.min.js b/dist/videojs-transcript.min.js index 69c26d7..ffa7af2 100644 --- a/dist/videojs-transcript.min.js +++ b/dist/videojs-transcript.min.js @@ -1,3 +1,3 @@ -/*! videojs-transcript - v0.0.0 - 2014-09-20 +/*! videojs-transcript - v0.0.0 - 2014-10-08 * Copyright (c) 2014 Matthew Walsh; Licensed MIT */ -!function(a,b){"use strict";var c=function(){var a=function(a){var b=Math.floor(a/3600),c=Math.floor(a%3600/60),d=Math.floor(a%60);return d=10>d?"0"+d:d,c=b>0&&10>c?"0"+c:c,b>0?b+":"+c+":"+d:c+":"+d};return{niceTimestamp:a}}(),d=function(){var a,b,d,e,f,g=function(a){var b=document.createElement("div"),d=document.createElement("span"),f=document.createElement("span");return b.className=e+"-line",b.setAttribute("data-begin",a.startTime),d.className=e+"-timestamp",d.textContent=c.niceTimestamp(a.startTime),f.className=e+"-text",f.innerHTML=a.text,b.appendChild(d),b.appendChild(f),b},h=function(a){if("object"!=typeof a&&(a=d.textTracks()[a]),void 0===b)throw new Error("videojs-transcript: Html not initialized!");var c,h,i=document.createDocumentFragment(),j=function(){var d=a.cues();for(h=0;hc.scrollTop+c.clientHeight&&(b=e-d),void 0!==b&&c.scrollTop!==b&&g(c,b,400)},i=function(a){a.addEventListener("scroll",function(){d?d=!1:(b=!0,a.classList.add("is-inuse"))}),a.addEventListener("mouseover",function(){c=!0}),a.addEventListener("mouseout",function(){c=!1,setTimeout(function(){c||(b=!1,a.classList.remove("is-inuse"))},1e3)})},j=function(a){return a.scrollHeight>a.offsetHeight},k=function(){return b};return{scrollToElement:h,initHandlers:i,inUse:k,canScroll:j}}(),f=function(a,b){var c={autoscroll:!0,clickArea:"line"},f=function(a){var f,g,h=b.util.mergeOptions(c,a),i=this,j="transcript",k=document.createElement("div"),l=function(){var a,b,c=[];for(f=i.textTracks(),a=0;a ."+j+"-line"),e=[];for(a=0;aa.begin&&b>d?a.element.classList.contains("is-active")||(a.element.classList.add("is-active"),h.autoscroll&&e.canScroll(k)&&!e.inUse()&&e.scrollToElement(a.element)):a.element.classList.remove("is-active")},p=function(){g=m(f),d.setTrack(g)};if(f=l(),!(f.length>0))throw new Error("videojs-transcript: No tracks found!");d.init(k,i,j,this),e.initHandlers(k),p(),i.on("timeupdate",o),i.on("captionstrackchange",p),i.on("subtitlestrackchange",p);var q=function(){return k};return{el:q,setTrack:p,options:a}};return{transcript:f}}(a,b);b.plugin("transcript",f.transcript)}(window,window.videojs); \ No newline at end of file +!function(a,b){"use strict";!function(){for(var b=0,c=["ms","moz","webkit","o"],d=0;d1)throw Error("Second argument not supported");if("object"!=typeof b)throw TypeError("Argument must be an object");a.prototype=b;var c=new a;return a.prototype=null,c}}()),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){var c,d;if(null==this)throw new TypeError(" this is null or not defined");var e=Object(this),f=e.length>>>0;if("function"!=typeof a)throw new TypeError(a+" is not a function");for(arguments.length>1&&(c=b),d=0;f>d;){var g;d in e&&(g=e[d],a.call(c,g,d,e)),d++}});var c={};c.settings={},c.prefix="transcript",c.player=this;var d={autoscroll:!0,clickArea:"line"},e=function(a){return{secondsToTime:function(a){var b=Math.floor(a/3600),c=Math.floor(a%3600/60),d=Math.floor(a%60);return d=10>d?"0"+d:d,c=b>0&&10>c?"0"+c:c,b>0?b+":"+c+":"+d:c+":"+d},localize:function(a){return a},createEl:function(b,c){c=c||"";var d=document.createElement(b);return d.className=a.prefix+c,d},extend:function(a){var b=typeof a;if(!("function"===b||"object"===b&&a))return a;for(var c,d,e=1,f=arguments.length;f>e;e++){c=arguments[e];for(d in c)a[d]=c[d]}return a}}}(c),f={handlers_:[],on:function(a,b,c){if("function"!=typeof c)throw new TypeError("Callback is not a function.");this.handlers_.push([a,b,c])},trigger:function(a,b){this.handlers_.forEach(function(c){c[0]===a&&c[1]===b&&c[2].apply()})},delegate:function(a){return a.on=function(b,c){f.on(a,b,c)},a.trigger=function(a){f.trigget(a,event)},a}},g=function(){var a=function(a){var b=this;a.addEventListener("scroll",function(){b.isAutoScrolling?b.isAutoScrolling=!1:(b.userIsScrolling=!0,a.classList.add("is-inuse"))}),a.addEventListener("mouseenter",function(){b.mouseIsOverTranscript=!0}),a.addEventListener("mouseleave",function(){b.mouseIsOverTranscript=!1,setTimeout(function(){b.mouseIsOverTranscript||(b.userIsScrolling=!1,a.classList.remove("is-inuse"))},1e3)})},b=function(b){return this.element=b,this.userIsScrolling=!1,this.mouseIsOverTranscript=!1,this.isAutoScrolling=!0,a.call(this,this.element),this},c=function(a,b,c,d){return b+c*Math.sin(Math.min(1,a/d)*(Math.PI/2))},d=function(a,b,d){var e=Date.now(),f=a.scrollTop,g=this;b=Math.max(0,b),b=Math.min(a.scrollHeight-a.clientHeight,b);var h=b-f,i=function(){var j=Date.now(),k=j-e;g.isAutoScrolling=!0,a.scrollTop=c(k,f,h,d),a.scrollTop!==b&&requestAnimationFrame(i,a)};requestAnimationFrame(i,a)},e=function(a){if(this.canScroll()){var b,c=a.parentElement,e=c.offsetTop+c.clientHeight,f=a.offsetTop+a.clientHeight,g=a.offsetTop+a.clientHeight-c.offsetTop;gc.scrollTop+c.clientHeight&&(b=f-e),void 0!==b&&c.scrollTop!==b&&d.call(this,c,b,400)}},f=function(){var a=this.element;return a.scrollHeight>a.offsetHeight},g=function(){return this.userIsScrolling};return{init:b,to:e,canScroll:f,inUse:g}}(c),h=function(a){return Object.create(g).init(a)},i=function(){var a;return{get:function(){var a=[];return c.tracks=c.player.textTracks(),c.tracks.forEach(function(b){("captions"===b.kind()||"subtitles"===b.kind())&&a.push(b)}),a},active:function(b){return b.forEach(function(b){return 2===b.mode()?(a=b,b):void 0}),a||b[0]}}}(c),j=function(a){var b={};b.element={},b.body={};var c=function(a,b){f.on(this,a,b)},d=function(a){f.trigger(this,a)},g=function(){var a=e.createEl("header","-header");return a.textContent=e.localize("Transcript"),a},i=function(){var b=e.createEl("select","-selector");return a.validTracks.forEach(function(a,c){var d=document.createElement("option");d.value=c,d.textContent=a.label()+" ("+a.language()+")",b.appendChild(d)}),b.addEventListener("change",function(){n(document.querySelector("#"+a.prefix+"-"+a.player.id()+" option:checked").value),d("trackchanged")}),b},j=function(b){var c=b.target.classList,d=b.target.getAttribute("data-begin")||b.target.parentElement.getAttribute("data-begin");void 0!==d&&null!==d&&("line"===a.settings.clickArea||"timestamp"===a.settings.clickArea&&c.contains(myPrefix+"-timestamp")||"text"===a.settings.clickArea&&c.contains(myPrefix+"-text"))&&a.player.currentTime(d)},k=function(a){var b=e.createEl("div","-line"),c=e.createEl("span","-timestamp"),d=e.createEl("span","-text");return b.setAttribute("data-begin",a.startTime),c.textContent=e.secondsToTime(a.startTime),d.innerHTML=a.text,b.appendChild(c),b.appendChild(d),b},l=function(b){"object"!=typeof b&&(b=a.player.textTracks()[b]);var c,d,f=e.createEl("div","-body"),g=document.createDocumentFragment(),i=function(){var a=b.cues();for(d=0;df&&g>c?e.classList.contains("is-active")||(e.classList.add("is-active"),a.settings.autoscroll&&!b.body.scroll.inUse()&&b.body.scroll.to(e)):e.classList.remove("is-active")},p=function(){return b.element};return{create:m,setTrack:n,setCue:o,el:p,on:c,trigger:d}}(c),k=function(a){c.player=this,c.validTracks=i.get(),c.currentTrack=i.active(c.validTracks),c.settings=b.util.mergeOptions(d,a),c.widget=j.create();var e=function(){c.widget.setCue(c.player.currentTime())},f=function(){c.currentTrack=i.active(c.validTracks),c.widget.setTrack(c.currentTrack)};if(!(c.validTracks.length>0))throw new Error("videojs-transcript: No tracks found!");return f(),c.player.on("timeupdate",e),c.player.on("captionstrackchange",f),c.player.on("subtitlestrackchange",f),{el:function(){return c.widget.el()},setTrack:c.widget.setTrack}};b.plugin("transcript",k)}(window,videojs); \ No newline at end of file diff --git a/example.html b/example.html index f02e1f6..d66ffde 100644 --- a/example.html +++ b/example.html @@ -5,7 +5,7 @@ Video.js Transcript - +