diff --git a/README.md b/README.md index 2bc4495..8fe94d9 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,9 @@ Creates interactive transcripts from text tracks. -## Warning - -:no_entry: **Warning! This code is an early work in progress and contains bugs, typos, unimplemented features, debug cruft, and other defects. Don't use it yet.** - -### TODO: -- [x] Seek video to transcript position when transcript is clicked -- [ ] Add more setup options -- [x] Nicer default CSS -- [x] More CSS examples -- [x] Handle multiple tracks -- [x] Automatically switch caption track when user selects a different track on the video. -- [x] Separate track switcher -- [x] Autoscrolling transcript -- [x] *Smooth*, animated scrolling -- [x] Clean up scrolling logic. -- [x] Prevent autoscrolling when user is interacting with transcript (nice to have) -- [x] Tests! -- [ ] More Tests! -- [ ] More... +## Alpha Release 1 + +Please report any issues or feature requests on the tracker. Thank you! ## Getting Started @@ -29,13 +13,33 @@ Once you've added the plugin script to your page, you can use it with any video: ```html + +
``` - There's also a [working example](example.html) of the plugin you can check out if you're having trouble. +You'll also want to include one of the css files. +You can style the plugin as you like but there are a few examples in the /css folder to get you started. + ## Documentation ### Plugin Options @@ -53,6 +57,35 @@ Set to false to disable autoscrolling. Set which elements in the transcript are clickable. Options are 'timestamp', 'text', the whole 'line', or 'none'. +#### showTitle +**Default:** true + +Show a title with the transcript widget. + +(Currently the title only says 'Transcript') + +#### showTrackSelector +**Default:** true + +Show a track selector with the transcript widget. + +#### followPlayerTrack +**Default:** true + +When you change the caption track on the video, the transcript changes tracks as well. + +#### stopScrollWhenInUse +**Default:** true + +Don't autoscroll the transcript when the user is trying to scroll it. + +(This probably still has a few glitches to work out on touch screens and stuff right now) + +### Plugin Methods +**el()** + +Returns the DOM element containing the html transcript widget. You'll need to append this to your page. + ## Release History - - 0.1.0: Initial release + - 0.7.1: Alpha Release 1 diff --git a/css/videojs-transcript3.css b/css/videojs-transcript3.css index a8c606b..ef56319 100644 --- a/css/videojs-transcript3.css +++ b/css/videojs-transcript3.css @@ -1,14 +1,34 @@ +.video-container { + margin: 40px auto; + position: relative; + width: 900px; +} +#video { + margin: 0; + position: absolute; +} #transcript { - width: 600px; - margin: auto; + position: absolute; + left: 600px; + width: 298px; font-family: Arial, sans-serif; + overflow-x: scroll; + height: 298px; + border: 1px solid #111; +} +.transcript-header { + height: 19px; + padding: 2px; + font-weight: bold; +} +.transcript-selector { + height: 25px; } - .transcript-body { - height: 200px; + width: 600px; overflow-y: scroll; - border: 1px solid #111; background-color: #e7e7e7; + height: 250px; } .transcript-line { @@ -49,3 +69,4 @@ color: #e7e7e7; } + diff --git a/dist/videojs-transcript.js b/dist/videojs-transcript.js index 918d7bb..f8bff51 100644 --- a/dist/videojs-transcript.js +++ b/dist/videojs-transcript.js @@ -1,4 +1,4 @@ -/*! videojs-transcript - v0.0.0 - 2014-10-08 +/*! videojs-transcript - v0.7.1 - 2014-10-10 * Copyright (c) 2014 Matthew Walsh; Licensed MIT */ (function (window, videojs) { 'use strict'; @@ -78,6 +78,12 @@ if (!Array.prototype.forEach) { }; } +// classList polyfill +/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ +;if("document" in self&&!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p (parent.scrollTop + parent.clientHeight)) { + } else if (relBottom > (parent.scrollTop + parent.clientHeight)) { newPos = elementOffsetBottom - parentOffsetBottom; } @@ -345,8 +356,8 @@ var widget = function (plugin) { 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.settings.clickArea === 'timestamp' && clickedClasses.contains(plugin.prefix + '-timestamp')) || + (plugin.settings.clickArea === 'text' && clickedClasses.contains(plugin.prefix + '-text'))) { plugin.player.currentTime(clickedTime); } } @@ -393,10 +404,14 @@ var widget = function (plugin) { 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); + if (plugin.settings.showTitle) { + var title = createTitle(); + el.appendChild(title); + } + if (plugin.settings.showTrackSelector) { + var selector = createSelector(); + el.appendChild(selector); + } my.body = utils.createEl('div', '-body'); el.appendChild(my.body); setTrack(plugin.currentTrack); @@ -421,7 +436,7 @@ var widget = function (plugin) { 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()) { + if (plugin.settings.autoscroll && !(plugin.settings.stopScrollWhenInUse && my.body.scroll.inUse())) { my.body.scroll.to(line); } } @@ -460,8 +475,10 @@ var transcript = function (options) { if (my.validTracks.length > 0) { updateTrack(); my.player.on('timeupdate', timeUpdate); - my.player.on('captionstrackchange', updateTrack); - my.player.on('subtitlestrackchange', updateTrack); + if (my.settings.followPlayerTrack) { + my.player.on('captionstrackchange', updateTrack); + my.player.on('subtitlestrackchange', updateTrack); + } } else { throw new Error('videojs-transcript: No tracks found!'); } diff --git a/dist/videojs-transcript.min.js b/dist/videojs-transcript.min.js index d5c88dc..58fb6b8 100644 --- a/dist/videojs-transcript.min.js +++ b/dist/videojs-transcript.min.js @@ -1,3 +1,3 @@ -/*! videojs-transcript - v0.0.0 - 2014-10-08 +/*! videojs-transcript - v0.7.1 - 2014-10-10 * Copyright (c) 2014 Matthew Walsh; Licensed MIT */ -!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()})}},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=!0,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-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 +!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++}}),"document"in self&&!("classList"in document.createElement("_"))&&!function(a){if("Element"in a){var b="classList",c="prototype",d=a.Element[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(""===b)throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){for(var b=f.call(a.getAttribute("class")||""),c=b?b.split(/\s+/):[],d=0,e=c.length;e>d;d++)this.push(c[d]);this._updateClassName=function(){a.setAttribute("class",this.toString())}},k=j[c]=[],l=function(){return new j(this)};if(h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",-1!==i(this,a)},k.add=function(){var a,b=arguments,c=0,d=b.length,e=!1;do a=b[c]+"",-1===i(this,a)&&(this.push(a),e=!0);while(++cd?"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()})}},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=!0,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-c.offsetTop,h=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(a.prefix+"-timestamp")||"text"===a.settings.clickArea&&c.contains(a.prefix+"-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||a.settings.stopScrollWhenInUse&&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.settings.followPlayerTrack&&(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 d66ffde..a43bb43 100644 --- a/example.html +++ b/example.html @@ -5,7 +5,9 @@ Video.js Transcript - + + +