vendor/assets/javascripts/webshims/shims/track.js in webshims-rails-0.4.2 vs vendor/assets/javascripts/webshims/shims/track.js in webshims-rails-0.4.3

- old
+ new

@@ -1,763 +1,811 @@ -jQuery.webshims.register('track', function($, webshims, window, document, undefined){ - var mediaelement = webshims.mediaelement; - var id = new Date().getTime(); - var showTracks = {subtitles: 1, captions: 1}; - var notImplemented = function(){ - webshims.error('not implemented yet'); - }; - - var createEventTarget = function(obj){ - var eventList = {}; - obj.addEventListener = function(name, fn){ - if(eventList[name]){ - webshims.error('always use $.bind to the shimed event: '+ name +' already bound fn was: '+ eventList[name] +' your fn was: '+ fn); - } - eventList[name] = fn; - - }; - obj.removeEventListener = function(name, fn){ - if(eventList[name] && eventList[name] != fn){ - webshims.error('always use $.bind/$.unbind to the shimed event: '+ name +' already bound fn was: '+ eventList[name] +' your fn was: '+ fn); - } - if(eventList[name]){ - delete eventList[name]; - } - }; - return obj; - }; - - - var cueListProto = { - getCueById: function(id){ - var cue = null; - for(var i = 0, len = this.length; i < len; i++){ - if(this[i].id === id){ - cue = this[i]; - break; - } - } - return cue; - } - }; - var textTrackProto = { - shimActiveCues: null, - _shimActiveCues: null, - activeCues: null, - cues: null, - kind: 'subtitles', - label: '', - language: '', - mode: 'disabled', - readyState: 0, - oncuechange: null, - toString: function() { - return "[object TextTrack]"; - }, - addCue: function(cue){ - if(!this.cues){ - this.cues = mediaelement.createCueList(); - } else { - var lastCue = this.cues[this.cues.length-1]; - if(lastCue && lastCue.startTime > cue.startTime){ - webshims.error("cue startTime higher than previous cue's startTime"); - } - } - if(cue.track){ - webshims.error("cue already part of a track element"); - } - cue.track = this; - this.cues.push(cue); - }, - removeCue: notImplemented, - DISABLED: 'disabled', - OFF: 'disabled', - HIDDEN: 'hidden', - SHOWING: 'showing', - ERROR: 3, - LOADED: 2, - LOADING: 1, - NONE: 0 - }; - var copyProps = ['kind', 'label', 'srclang']; - - var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty); - - //ToDo: add/remove event - var updateMediaTrackList = function(baseData, trackList){ - var removed = []; - var added = []; - var newTracks = []; - var i, len; - if(!baseData){ - baseData = webshims.data(this, 'mediaelementBase') || webshims.data(this, 'mediaelementBase', {}); - } - - if(!trackList){ - baseData.blockTrackListUpdate = true; - trackList = $.prop(this, 'textTracks'); - baseData.blockTrackListUpdate = false; - } - - clearTimeout(baseData.updateTrackListTimer); - - $('track', this).each(function(){ - var track = $.prop(this, 'track'); - newTracks.push(track); - if(trackList.indexOf(track) == -1){ - added.push(track); - } - }); - - if(baseData.scriptedTextTracks){ - for(i = 0, len = baseData.scriptedTextTracks.length; i < len; i++){ - newTracks.push(baseData.scriptedTextTracks[i]); - if(trackList.indexOf(baseData.scriptedTextTracks[i]) == -1){ - added.push(baseData.scriptedTextTracks[i]); - } - } - } - - for(i = 0, len = trackList.length; i < len; i++){ - if(newTracks.indexOf(trackList[i]) == -1){ - removed.push(trackList[i]); - } - } - - if(removed.length || added.length){ - trackList.splice(0); - - for(i = 0, len = newTracks.length; i < len; i++){ - trackList.push(newTracks[i]); - } - for(i = 0, len = removed.length; i < len; i++){ - $([trackList]).triggerHandler($.Event({type: 'removetrack', track: trackList, track: removed[i]})); - } - for(i = 0, len = added.length; i < len; i++){ - $([trackList]).triggerHandler($.Event({type: 'addtrack', track: trackList, track: added[i]})); - } - if(baseData.scriptedTextTracks || removed.length){ - $(this).triggerHandler('updatetrackdisplay'); - } - } - }; - - var refreshTrack = function(track, trackData){ - var mode, kind; - if(!trackData){ - trackData = webshims.data(track, 'trackData'); - } - if(trackData && !trackData.isTriggering){ - trackData.isTriggering = true; - mode = (trackData.track || {}).mode; - kind = (trackData.track || {}).kind; - setTimeout(function(){ - if(mode !== (trackData.track || {}).mode || kind != (trackData.track || {}).kind){ - if(!(trackData.track || {}).readyState){ - $(track).triggerHandler('checktrackmode'); - } else { - $(track).parent().triggerHandler('updatetrackdisplay'); - } - } - trackData.isTriggering = false; - - }, 9); - } - }; - - var emptyDiv = $('<div />')[0]; - window.TextTrackCue = function(startTime, endTime, text){ - if(arguments.length != 3){ - webshims.error("wrong arguments.length for TextTrackCue.constructor"); - } - - this.startTime = startTime; - this.endTime = endTime; - this.text = text; - - this.id = ""; - this.pauseOnExit = false; - - createEventTarget(this); - }; - - window.TextTrackCue.prototype = { - - onenter: null, - onexit: null, - pauseOnExit: false, - getCueAsHTML: function(){ - var lastText = ""; - var parsedText = ""; - var fragment = document.createDocumentFragment(); - var fn; - if(!owns(this, 'getCueAsHTML')){ - fn = this.getCueAsHTML = function(){ - var i, len; - if(lastText != this.text){ - lastText = this.text; - parsedText = mediaelement.parseCueTextToHTML(lastText); - emptyDiv.innerHTML = parsedText; - - for(i = 0, len = emptyDiv.childNodes.length; i < len; i++){ - fragment.appendChild(emptyDiv.childNodes[i].cloneNode(true)); - } - } - return fragment.cloneNode(true); - }; - - } - return fn ? fn.apply(this, arguments) : fragment.cloneNode(true); - }, - track: null, - - - id: '' - //todo--> -// , -// snapToLines: true, -// line: 'auto', -// size: 100, -// position: 50, -// vertical: '', -// align: 'middle' - }; - - - - - - mediaelement.createCueList = function(){ - return $.extend([], cueListProto); - }; - - mediaelement.parseCueTextToHTML = (function(){ - var tagSplits = /(<\/?[^>]+>)/ig; - var allowedTags = /^(?:c|v|ruby|rt|b|i|u)/; - var regEnd = /\<\s*\//; - var addToTemplate = function(localName, attribute, tag, html){ - var ret; - if(regEnd.test(html)){ - ret = '</'+ localName +'>'; - } else { - tag.splice(0, 1); - ret = '<'+ localName +' '+ attribute +'="'+ (tag.join(' ').replace(/\"/g, '&#34;')) +'">'; - } - return ret; - }; - var replacer = function(html){ - var tag = html.replace(/[<\/>]+/ig,"").split(/[\s\.]+/); - if(tag[0]){ - tag[0] = tag[0].toLowerCase(); - if(allowedTags.test(tag[0])){ - if(tag[0] == 'c'){ - html = addToTemplate('span', 'class', tag, html); - } else if(tag[0] == 'v'){ - html = addToTemplate('q', 'title', tag, html); - } - } else { - html = ""; - } - } - return html; - }; - - return function(cueText){ - return cueText.replace(tagSplits, replacer); - }; - })(); - - mediaelement.loadTextTrack = function(mediaelem, track, trackData, _default){ - var loadEvents = 'play playing timeupdate updatetrackdisplay'; - var obj = trackData.track; - var load = function(){ - var src = $.prop(track, 'src'); - var error; - var ajax; - if(obj.mode != 'disabled' && src && $.attr(track, 'src')){ - $(mediaelem).unbind(loadEvents, load); - $(track).unbind('checktrackmode', load); - if(!obj.readyState){ - error = function(){ - obj.readyState = 3; - obj.cues = null; - obj.activeCues = obj.shimActiveCues = obj._shimActiveCues = null; - $(track).triggerHandler('error'); - }; - obj.readyState = 1; - try { - obj.cues = mediaelement.createCueList(); - obj.activeCues = obj.shimActiveCues = obj._shimActiveCues = mediaelement.createCueList(); - ajax = $.ajax({ - dataType: 'text', - url: src, - success: function(text){ - if(ajax.getResponseHeader('content-type') != 'text/vtt'){ - webshims.error('set the mime-type of your WebVTT files to text/vtt. see: http://dev.w3.org/html5/webvtt/#text/vtt'); - } - mediaelement.parseCaptions(text, obj, function(cues){ - if(cues && 'length' in cues){ - obj.readyState = 2; - $(track).triggerHandler('load'); - $(mediaelem).triggerHandler('updatetrackdisplay'); - } else { - error(); - } - }); - - }, - error: error - }); - } catch(er){ - error(); - webshims.warn(er); - } - } - } - }; - obj.readyState = 0; - obj.shimActiveCues = null; - obj._shimActiveCues = null; - obj.activeCues = null; - obj.cues = null; - $(mediaelem).unbind(loadEvents, load); - $(track).unbind('checktrackmode', load); - $(mediaelem).bind(loadEvents, load); - $(track).bind('checktrackmode', load); - if(_default){ - obj.mode = showTracks[obj.kind] ? 'showing' : 'hidden'; - load(); - } - }; - - mediaelement.createTextTrack = function(mediaelem, track){ - var obj, trackData; - if(track.nodeName){ - trackData = webshims.data(track, 'trackData'); - - if(trackData){ - refreshTrack(track, trackData); - obj = trackData.track; - } - } - - if(!obj){ - obj = createEventTarget(webshims.objectCreate(textTrackProto)); - copyProps.forEach(function(copyProp){ - var prop = $.prop(track, copyProp); - if(prop){ - if(copyProp == 'srclang'){ - copyProp = 'language'; - } - obj[copyProp] = prop; - } - }); - - - if(track.nodeName){ - trackData = webshims.data(track, 'trackData', {track: obj}); - mediaelement.loadTextTrack(mediaelem, track, trackData, $.prop(track, 'default')); - } else { - obj.cues = mediaelement.createCueList(); - obj.activeCues = obj._shimActiveCues = obj.shimActiveCues = mediaelement.createCueList(); - obj.mode = 'hidden'; - obj.readyState = 2; - } - } - return obj; - }; - - -/* -taken from: -Captionator 0.5.1 [CaptionCrunch] -Christopher Giffard, 2011 -Share and enjoy - -https://github.com/cgiffard/Captionator - -modified for webshims -*/ - mediaelement.parseCaptionChunk = (function(){ - // Set up timestamp parsers - var WebVTTTimestampParser = /^(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s+\-\-\>\s+(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/; - var GoogleTimestampParser = /^([\d\.]+)\s+\+([\d\.]+)\s*(.*)/; - var WebVTTDEFAULTSCueParser = /^(DEFAULTS|DEFAULT)\s+\-\-\>\s+(.*)/g; - var WebVTTSTYLECueParser = /^(STYLE|STYLES)\s+\-\-\>\s*\n([\s\S]*)/g; - var WebVTTCOMMENTCueParser = /^(COMMENT|COMMENTS)\s+\-\-\>\s+(.*)/g; - - return function(subtitleElement,objectCount){ - var cueDefaults = []; - - var subtitleParts, timeIn, timeOut, html, timeData, subtitlePartIndex, cueSettings = "", id, specialCueData; - var timestampMatch, tmpCue; - - // WebVTT Special Cue Logic - if ((specialCueData = WebVTTDEFAULTSCueParser.exec(subtitleElement))) { -// cueDefaults = specialCueData.slice(2).join(""); -// cueDefaults = cueDefaults.split(/\s+/g).filter(function(def) { return def && !!def.length; }); - return null; - } else if ((specialCueData = WebVTTSTYLECueParser.exec(subtitleElement))) { - return null; - } else if ((specialCueData = WebVTTCOMMENTCueParser.exec(subtitleElement))) { - return null; // At this stage, we don't want to do anything with these. - } - - subtitleParts = subtitleElement.split(/\n/g); - - // Trim off any blank lines (logically, should only be max. one, but loop to be sure) - while (!subtitleParts[0].replace(/\s+/ig,"").length && subtitleParts.length > 0) { - subtitleParts.shift(); - } - - if (subtitleParts[0].match(/^\s*[a-z0-9]+\s*$/ig)) { - // The identifier becomes the cue ID (when *we* load the cues from file. Programatically created cues can have an ID of whatever.) - id = String(subtitleParts.shift().replace(/\s*/ig,"")); - } - - for (subtitlePartIndex = 0; subtitlePartIndex < subtitleParts.length; subtitlePartIndex ++) { - var timestamp = subtitleParts[subtitlePartIndex]; - - if ((timestampMatch = WebVTTTimestampParser.exec(timestamp))) { - - // WebVTT - - timeData = timestampMatch.slice(1); - - timeIn = parseInt((timeData[0]||0) * 60 * 60,10) + // Hours - parseInt((timeData[1]||0) * 60,10) + // Minutes - parseInt((timeData[2]||0),10) + // Seconds - parseFloat("0." + (timeData[3]||0)); // MS - - timeOut = parseInt((timeData[4]||0) * 60 * 60,10) + // Hours - parseInt((timeData[5]||0) * 60,10) + // Minutes - parseInt((timeData[6]||0),10) + // Seconds - parseFloat("0." + (timeData[7]||0)); // MS -/* - if (timeData[8]) { - cueSettings = timeData[8]; - } -*/ - } - - // We've got the timestamp - return all the other unmatched lines as the raw subtitle data - subtitleParts = subtitleParts.slice(0,subtitlePartIndex).concat(subtitleParts.slice(subtitlePartIndex+1)); - break; - } - - if (!timeIn && !timeOut) { - // We didn't extract any time information. Assume the cue is invalid! - return null; - } -/* - // Consolidate cue settings, convert defaults to object - var compositeCueSettings = - cueDefaults - .reduce(function(previous,current,index,array){ - previous[current.split(":")[0]] = current.split(":")[1]; - return previous; - },{}); - - // Loop through cue settings, replace defaults with cue specific settings if they exist - compositeCueSettings = - cueSettings - .split(/\s+/g) - .filter(function(set) { return set && !!set.length; }) - // Convert array to a key/val object - .reduce(function(previous,current,index,array){ - previous[current.split(":")[0]] = current.split(":")[1]; - return previous; - },compositeCueSettings); - - // Turn back into string like the TextTrackCue constructor expects - cueSettings = ""; - for (var key in compositeCueSettings) { - if (compositeCueSettings.hasOwnProperty(key)) { - cueSettings += !!cueSettings.length ? " " : ""; - cueSettings += key + ":" + compositeCueSettings[key]; - } - } -*/ - // The remaining lines are the subtitle payload itself (after removing an ID if present, and the time); - html = subtitleParts.join("\n"); - tmpCue = new TextTrackCue(timeIn, timeOut, html); - if(id){ - tmpCue.id = id; - } - return tmpCue; - }; - })(); - - mediaelement.parseCaptions = function(captionData, track, complete) { - var subtitles = mediaelement.createCueList(); - var cue, lazyProcess, regWevVTT; - var startDate; - var isWEBVTT; - if (captionData) { - - regWevVTT = /^WEBVTT(\s*FILE)?/ig; - - lazyProcess = function(i, len){ - - for(; i < len; i++){ - cue = captionData[i]; - if(regWevVTT.test(cue)){ - isWEBVTT = true; - } else if(cue.replace(/\s*/ig,"").length){ - if(!isWEBVTT){ - webshims.error('please use WebVTT format. This is the standard'); - complete(null); - break; - } - cue = mediaelement.parseCaptionChunk(cue, i); - if(cue){ - track.addCue(cue); - } - } - if(startDate < (new Date().getTime()) - 9){ - i++; - setTimeout(function(){ - startDate = new Date().getTime(); - lazyProcess(i, len); - }, 90); - - break; - } - } - if(i >= len){ - if(!isWEBVTT){ - webshims.error('please use WebVTT format. This is the standard'); - } - complete(track.cues); - } - }; - - captionData = captionData.replace(/\r\n/g,"\n"); - - setTimeout(function(){ - captionData = captionData.replace(/\r/g,"\n"); - setTimeout(function(){ - startDate = new Date().getTime(); - captionData = captionData.split(/\n\n+/g); - lazyProcess(0, captionData.length); - }, 9); - }, 9); - - } else { - webshims.error("Required parameter captionData not supplied."); - } - }; - - - mediaelement.createTrackList = function(mediaelem, baseData){ - baseData = baseData || webshims.data(mediaelem, 'mediaelementBase') || webshims.data(mediaelem, 'mediaelementBase', {}); - if(!baseData.textTracks){ - baseData.textTracks = []; - webshims.defineProperties(baseData.textTracks, { - onaddtrack: {value: null}, - onremovetrack: {value: null} - }); - createEventTarget(baseData.textTracks); - } - return baseData.textTracks; - }; - - if(!Modernizr.track){ - webshims.defineNodeNamesBooleanProperty(['track'], 'default'); - webshims.reflectProperties(['track'], ['srclang', 'label']); - - webshims.defineNodeNameProperties('track', { - src: { - //attr: {}, - reflect: true, - propType: 'src' - } - }); - } - - webshims.defineNodeNameProperties('track', { - kind: { - attr: Modernizr.track ? { - set: function(value){ - var trackData = webshims.data(this, 'trackData'); - this.setAttribute('data-kind', value); - if(trackData){ - trackData.attrKind = value; - } - }, - get: function(){ - var trackData = webshims.data(this, 'trackData'); - if(trackData && ('attrKind' in trackData)){ - return trackData.attrKind; - } - return this.getAttribute('kind'); - } - } : {}, - reflect: true, - propType: 'enumarated', - defaultValue: 'subtitles', - limitedTo: ['subtitles', 'captions', 'descriptions', 'chapters', 'metadata'] - } - }); - - webshims.onNodeNamesPropertyModify('track', 'kind', function(){ - var trackData = webshims.data(this, 'trackData'); - if(trackData){ - trackData.track.kind = $.prop(this, 'kind'); - refreshTrack(this, trackData); - } - }); - - webshims.onNodeNamesPropertyModify('track', 'src', function(val){ - if(val){ - var data = webshims.data(this, 'trackData'); - var media; - if(data){ - media = $(this).closest('video, audio'); - if(media[0]){ - mediaelement.loadTextTrack(media, this, data); - } - } - } - - }); - - // - - webshims.defineNodeNamesProperties(['track'], { - ERROR: { - value: 3 - }, - LOADED: { - value: 2 - }, - LOADING: { - value: 1 - }, - NONE: { - value: 0 - }, - readyState: { - get: function(){ - return ($.prop(this, 'track') || {readyState: 0}).readyState; - }, - writeable: false - }, - track: { - get: function(){ - return mediaelement.createTextTrack($(this).closest('audio, video')[0], this); - }, - writeable: false - } - }, 'prop'); - - webshims.defineNodeNamesProperties(['audio', 'video'], { - textTracks: { - get: function(){ - - var media = this; - var baseData = webshims.data(media, 'mediaelementBase') || webshims.data(media, 'mediaelementBase', {}); - var tracks = mediaelement.createTrackList(media, baseData); - if(!baseData.blockTrackListUpdate){ - updateMediaTrackList.call(media, baseData, tracks); - } - return tracks; - }, - writeable: false - }, - addTextTrack: { - value: function(kind, label, lang){ - var textTrack = mediaelement.createTextTrack(this, { - kind: kind || '', - label: label || '', - srclang: lang || '' - }); - var baseData = webshims.data(this, 'mediaelementBase') || webshims.data(this, 'mediaelementBase', {}); - if (!baseData.scriptedTextTracks) { - baseData.scriptedTextTracks = []; - } - baseData.scriptedTextTracks.push(textTrack); - updateMediaTrackList.call(this); - return textTrack; - } - } - }, 'prop'); - - - $(document).bind('emptied ended updatetracklist', function(e){ - if($(e.target).is('audio, video')){ - var baseData = webshims.data(e.target, 'mediaelementBase'); - if(baseData){ - clearTimeout(baseData.updateTrackListTimer); - baseData.updateTrackListTimer = setTimeout(function(){ - updateMediaTrackList.call(e.target, baseData); - }, 0); - } - } - }); - - var getNativeReadyState = function(trackElem, textTrack){ - return textTrack.readyState || trackElem.readyState; - }; - - webshims.addReady(function(context, insertedElement){ - var insertedMedia = insertedElement.filter('video, audio, track').closest('audio, video'); - $('video, audio', context) - .add(insertedMedia) - .each(function(){ - updateMediaTrackList.call(this); - }) - .each(function(){ - if(Modernizr.track){ - var shimedTextTracks = $.prop(this, 'textTracks'); - var origTextTracks = this.textTracks; - if(shimedTextTracks.length != origTextTracks.length){ - webshims.error("textTracks couldn't be copied"); - } - - $('track', this) - .each(function(){ - var shimedTrack = $.prop(this, 'track'); - var origTrack = this.track; - var kind; - var readyState; - if(origTrack){ - kind = $.prop(this, 'kind'); - readyState = getNativeReadyState(this, origTrack); - if (origTrack.mode || readyState) { - shimedTrack.mode = origTrack.mode; - } - //disable track from showing + remove UI - if(kind != 'descriptions'){ - origTrack.mode = (typeof origTrack.mode == 'string') ? 'disabled' : 0; - this.kind = 'metadata'; - $(this).attr({kind: kind}); - } - - } - }) - .bind('load error', function(e){ - if(e.originalEvent){ - e.stopImmediatePropagation(); - } - }) - ; - } - }) - ; - insertedMedia.each(function(){ - var media = this; - var baseData = webshims.data(media, 'mediaelementBase'); - if(baseData){ - clearTimeout(baseData.updateTrackListTimer); - baseData.updateTrackListTimer = setTimeout(function(){ - updateMediaTrackList.call(media, baseData); - }, 9); - } - }); - }); - - if(Modernizr.track){ - $('video, audio').trigger('trackapichange'); - } - +jQuery.webshims.register('track', function($, webshims, window, document, undefined){ + var mediaelement = webshims.mediaelement; + var id = new Date().getTime(); + //descriptions are not really shown, but they are inserted into the dom + var showTracks = {subtitles: 1, captions: 1, descriptions: 1}; + var notImplemented = function(){ + webshims.error('not implemented yet'); + }; + var supportTrackMod = Modernizr.ES5 && Modernizr.objectAccessor; + var createEventTarget = function(obj){ + var eventList = {}; + obj.addEventListener = function(name, fn){ + if(eventList[name]){ + webshims.error('always use $.bind to the shimed event: '+ name +' already bound fn was: '+ eventList[name] +' your fn was: '+ fn); + } + eventList[name] = fn; + + }; + obj.removeEventListener = function(name, fn){ + if(eventList[name] && eventList[name] != fn){ + webshims.error('always use $.bind/$.unbind to the shimed event: '+ name +' already bound fn was: '+ eventList[name] +' your fn was: '+ fn); + } + if(eventList[name]){ + delete eventList[name]; + } + }; + return obj; + }; + + + var cueListProto = { + getCueById: function(id){ + var cue = null; + for(var i = 0, len = this.length; i < len; i++){ + if(this[i].id === id){ + cue = this[i]; + break; + } + } + return cue; + } + }; + + var textTrackProto = { + shimActiveCues: null, + _shimActiveCues: null, + activeCues: null, + cues: null, + kind: 'subtitles', + label: '', + language: '', + mode: 'disabled', + readyState: 0, + oncuechange: null, + toString: function() { + return "[object TextTrack]"; + }, + addCue: function(cue){ + if(!this.cues){ + this.cues = mediaelement.createCueList(); + } else { + var lastCue = this.cues[this.cues.length-1]; + if(lastCue && lastCue.startTime > cue.startTime){ + webshims.error("cue startTime higher than previous cue's startTime"); + } + } + if(cue.track && cue.track.removeCue){ + cue.track.removeCue(cue); + } + cue.track = this; + this.cues.push(cue); + }, + //ToDo: make it more dynamic + removeCue: function(cue){ + var cues = this.cues || []; + var i = 0; + var len = cues.length; + if(cue.track != this){ + webshims.error("cue not part of track"); + return; + } + for(; i < len; i++){ + if(cues[i] === cue){ + cues.splice(i, 1); + cue.track = null; + break; + } + } + if(cue.track){ + webshims.error("cue not part of track"); + return; + } + }, + DISABLED: 'disabled', + OFF: 'disabled', + HIDDEN: 'hidden', + SHOWING: 'showing', + ERROR: 3, + LOADED: 2, + LOADING: 1, + NONE: 0 + }; + var copyProps = ['kind', 'label', 'srclang']; + var copyName = {srclang: 'language'}; + + var owns = Function.prototype.call.bind(Object.prototype.hasOwnProperty); + + var updateMediaTrackList = function(baseData, trackList){ + var removed = []; + var added = []; + var newTracks = []; + var i, len; + if(!baseData){ + baseData = webshims.data(this, 'mediaelementBase') || webshims.data(this, 'mediaelementBase', {}); + } + + if(!trackList){ + baseData.blockTrackListUpdate = true; + trackList = $.prop(this, 'textTracks'); + baseData.blockTrackListUpdate = false; + } + + clearTimeout(baseData.updateTrackListTimer); + + $('track', this).each(function(){ + var track = $.prop(this, 'track'); + newTracks.push(track); + if(trackList.indexOf(track) == -1){ + added.push(track); + } + }); + + if(baseData.scriptedTextTracks){ + for(i = 0, len = baseData.scriptedTextTracks.length; i < len; i++){ + newTracks.push(baseData.scriptedTextTracks[i]); + if(trackList.indexOf(baseData.scriptedTextTracks[i]) == -1){ + added.push(baseData.scriptedTextTracks[i]); + } + } + } + + for(i = 0, len = trackList.length; i < len; i++){ + if(newTracks.indexOf(trackList[i]) == -1){ + removed.push(trackList[i]); + } + } + + if(removed.length || added.length){ + trackList.splice(0); + + for(i = 0, len = newTracks.length; i < len; i++){ + trackList.push(newTracks[i]); + } + for(i = 0, len = removed.length; i < len; i++){ + $([trackList]).triggerHandler($.Event({type: 'removetrack', track: trackList, track: removed[i]})); + } + for(i = 0, len = added.length; i < len; i++){ + $([trackList]).triggerHandler($.Event({type: 'addtrack', track: trackList, track: added[i]})); + } + if(baseData.scriptedTextTracks || removed.length){ + $(this).triggerHandler('updatetrackdisplay'); + } + } + }; + + var refreshTrack = function(track, trackData){ + if(!trackData){ + trackData = webshims.data(track, 'trackData'); + } + if(trackData && !trackData.isTriggering){ + trackData.isTriggering = true; + setTimeout(function(){ + if(!(trackData.track || {}).readyState){ + $(track).triggerHandler('checktrackmode'); + } else { + $(track).closest('audio, video').triggerHandler('updatetrackdisplay'); + } + trackData.isTriggering = false; + }, 1); + } + }; + + var emptyDiv = $('<div />')[0]; + window.TextTrackCue = function(startTime, endTime, text){ + if(arguments.length != 3){ + webshims.error("wrong arguments.length for TextTrackCue.constructor"); + } + + this.startTime = startTime; + this.endTime = endTime; + this.text = text; + + this.id = ""; + this.pauseOnExit = false; + + createEventTarget(this); + }; + + window.TextTrackCue.prototype = { + + onenter: null, + onexit: null, + pauseOnExit: false, + getCueAsHTML: function(){ + var lastText = ""; + var parsedText = ""; + var fragment = document.createDocumentFragment(); + var fn; + if(!owns(this, 'getCueAsHTML')){ + fn = this.getCueAsHTML = function(){ + var i, len; + if(lastText != this.text){ + lastText = this.text; + parsedText = mediaelement.parseCueTextToHTML(lastText); + emptyDiv.innerHTML = parsedText; + + for(i = 0, len = emptyDiv.childNodes.length; i < len; i++){ + fragment.appendChild(emptyDiv.childNodes[i].cloneNode(true)); + } + } + return fragment.cloneNode(true); + }; + + } + return fn ? fn.apply(this, arguments) : fragment.cloneNode(true); + }, + track: null, + + + id: '' + //todo--> +// , +// snapToLines: true, +// line: 'auto', +// size: 100, +// position: 50, +// vertical: '', +// align: 'middle' + }; + + + + + + mediaelement.createCueList = function(){ + return $.extend([], cueListProto); + }; + + mediaelement.parseCueTextToHTML = (function(){ + var tagSplits = /(<\/?[^>]+>)/ig; + var allowedTags = /^(?:c|v|ruby|rt|b|i|u)/; + var regEnd = /\<\s*\//; + var addToTemplate = function(localName, attribute, tag, html){ + var ret; + if(regEnd.test(html)){ + ret = '</'+ localName +'>'; + } else { + tag.splice(0, 1); + ret = '<'+ localName +' '+ attribute +'="'+ (tag.join(' ').replace(/\"/g, '&#34;')) +'">'; + } + return ret; + }; + var replacer = function(html){ + var tag = html.replace(/[<\/>]+/ig,"").split(/[\s\.]+/); + if(tag[0]){ + tag[0] = tag[0].toLowerCase(); + if(allowedTags.test(tag[0])){ + if(tag[0] == 'c'){ + html = addToTemplate('span', 'class', tag, html); + } else if(tag[0] == 'v'){ + html = addToTemplate('q', 'title', tag, html); + } + } else { + html = ""; + } + } + return html; + }; + + return function(cueText){ + return cueText.replace(tagSplits, replacer); + }; + })(); + + mediaelement.loadTextTrack = function(mediaelem, track, trackData, _default){ + var loadEvents = 'play playing timeupdate updatetrackdisplay'; + var obj = trackData.track; + var load = function(){ + var src = $.prop(track, 'src'); + var error; + var ajax; + if(obj.mode != 'disabled' && src && $.attr(track, 'src')){ + $(mediaelem).unbind(loadEvents, load); + $(track).unbind('checktrackmode', load); + if(!obj.readyState){ + error = function(){ + obj.readyState = 3; + obj.cues = null; + obj.activeCues = obj.shimActiveCues = obj._shimActiveCues = null; + $(track).triggerHandler('error'); + }; + obj.readyState = 1; + try { + obj.cues = mediaelement.createCueList(); + obj.activeCues = obj.shimActiveCues = obj._shimActiveCues = mediaelement.createCueList(); + ajax = $.ajax({ + dataType: 'text', + url: src, + success: function(text){ + if(ajax.getResponseHeader('content-type') != 'text/vtt'){ + webshims.error('set the mime-type of your WebVTT files to text/vtt. see: http://dev.w3.org/html5/webvtt/#text/vtt'); + } + mediaelement.parseCaptions(text, obj, function(cues){ + if(cues && 'length' in cues){ + obj.readyState = 2; + $(track).triggerHandler('load'); + $(mediaelem).triggerHandler('updatetrackdisplay'); + } else { + error(); + } + }); + + }, + error: error + }); + } catch(er){ + error(); + webshims.warn(er); + } + } + } + }; + obj.readyState = 0; + obj.shimActiveCues = null; + obj._shimActiveCues = null; + obj.activeCues = null; + obj.cues = null; + $(mediaelem).unbind(loadEvents, load); + $(track).unbind('checktrackmode', load); + $(mediaelem).bind(loadEvents, load); + $(track).bind('checktrackmode', load); + if(_default){ + obj.mode = showTracks[obj.kind] ? 'showing' : 'hidden'; + load(); + } + }; + + mediaelement.createTextTrack = function(mediaelem, track){ + var obj, trackData; + if(track.nodeName){ + trackData = webshims.data(track, 'trackData'); + + if(trackData){ + refreshTrack(track, trackData); + obj = trackData.track; + } + } + + if(!obj){ + obj = createEventTarget(webshims.objectCreate(textTrackProto)); + + if(!supportTrackMod){ + copyProps.forEach(function(copyProp){ + var prop = $.prop(track, copyProp); + if(prop){ + obj[copyName[copyProp] || copyProp] = prop; + } + }); + } + + + if(track.nodeName){ + + if(supportTrackMod){ + copyProps.forEach(function(copyProp){ + webshims.defineProperty(obj, copyName[copyProp] || copyProp, { + get: function(){ + return $.prop(track, copyProp); + } + }); + }); + } + + trackData = webshims.data(track, 'trackData', {track: obj}); + mediaelement.loadTextTrack(mediaelem, track, trackData, ($.prop(track, 'default') && $(track).siblings('track[default]').andSelf()[0] == track)); + } else { + if(supportTrackMod){ + copyProps.forEach(function(copyProp){ + webshims.defineProperty(obj, copyName[copyProp] || copyProp, { + value: track[copyProp], + writeable: false + }); + }); + } + obj.cues = mediaelement.createCueList(); + obj.activeCues = obj._shimActiveCues = obj.shimActiveCues = mediaelement.createCueList(); + obj.mode = 'hidden'; + obj.readyState = 2; + } + } + return obj; + }; + + +/* +taken from: +Captionator 0.5.1 [CaptionCrunch] +Christopher Giffard, 2011 +Share and enjoy + +https://github.com/cgiffard/Captionator + +modified for webshims +*/ + mediaelement.parseCaptionChunk = (function(){ + // Set up timestamp parsers + var WebVTTTimestampParser = /^(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s+\-\-\>\s+(\d{2})?:?(\d{2}):(\d{2})\.(\d+)\s*(.*)/; + var GoogleTimestampParser = /^([\d\.]+)\s+\+([\d\.]+)\s*(.*)/; + var WebVTTDEFAULTSCueParser = /^(DEFAULTS|DEFAULT)\s+\-\-\>\s+(.*)/g; + var WebVTTSTYLECueParser = /^(STYLE|STYLES)\s+\-\-\>\s*\n([\s\S]*)/g; + var WebVTTCOMMENTCueParser = /^(COMMENT|COMMENTS)\s+\-\-\>\s+(.*)/g; + + return function(subtitleElement,objectCount){ + var cueDefaults = []; + + var subtitleParts, timeIn, timeOut, html, timeData, subtitlePartIndex, cueSettings = "", id, specialCueData; + var timestampMatch, tmpCue; + + // WebVTT Special Cue Logic + if ((specialCueData = WebVTTDEFAULTSCueParser.exec(subtitleElement))) { +// cueDefaults = specialCueData.slice(2).join(""); +// cueDefaults = cueDefaults.split(/\s+/g).filter(function(def) { return def && !!def.length; }); + return null; + } else if ((specialCueData = WebVTTSTYLECueParser.exec(subtitleElement))) { + return null; + } else if ((specialCueData = WebVTTCOMMENTCueParser.exec(subtitleElement))) { + return null; // At this stage, we don't want to do anything with these. + } + + subtitleParts = subtitleElement.split(/\n/g); + + // Trim off any blank lines (logically, should only be max. one, but loop to be sure) + while (!subtitleParts[0].replace(/\s+/ig,"").length && subtitleParts.length > 0) { + subtitleParts.shift(); + } + + if (subtitleParts[0].match(/^\s*[a-z0-9-\_]+\s*$/ig)) { + // The identifier becomes the cue ID (when *we* load the cues from file. Programatically created cues can have an ID of whatever.) + id = String(subtitleParts.shift().replace(/\s*/ig,"")); + } + + for (subtitlePartIndex = 0; subtitlePartIndex < subtitleParts.length; subtitlePartIndex ++) { + var timestamp = subtitleParts[subtitlePartIndex]; + + if ((timestampMatch = WebVTTTimestampParser.exec(timestamp))) { + + // WebVTT + + timeData = timestampMatch.slice(1); + + timeIn = parseInt((timeData[0]||0) * 60 * 60,10) + // Hours + parseInt((timeData[1]||0) * 60,10) + // Minutes + parseInt((timeData[2]||0),10) + // Seconds + parseFloat("0." + (timeData[3]||0)); // MS + + timeOut = parseInt((timeData[4]||0) * 60 * 60,10) + // Hours + parseInt((timeData[5]||0) * 60,10) + // Minutes + parseInt((timeData[6]||0),10) + // Seconds + parseFloat("0." + (timeData[7]||0)); // MS +/* + if (timeData[8]) { + cueSettings = timeData[8]; + } +*/ + } + + // We've got the timestamp - return all the other unmatched lines as the raw subtitle data + subtitleParts = subtitleParts.slice(0,subtitlePartIndex).concat(subtitleParts.slice(subtitlePartIndex+1)); + break; + } + + if (!timeIn && !timeOut) { + // We didn't extract any time information. Assume the cue is invalid! + webshims.warn("couldn't extract time information: "+[timeIn, timeOut, subtitleParts.join("\n"), id].join(' ; ')); + return null; + } +/* + // Consolidate cue settings, convert defaults to object + var compositeCueSettings = + cueDefaults + .reduce(function(previous,current,index,array){ + previous[current.split(":")[0]] = current.split(":")[1]; + return previous; + },{}); + + // Loop through cue settings, replace defaults with cue specific settings if they exist + compositeCueSettings = + cueSettings + .split(/\s+/g) + .filter(function(set) { return set && !!set.length; }) + // Convert array to a key/val object + .reduce(function(previous,current,index,array){ + previous[current.split(":")[0]] = current.split(":")[1]; + return previous; + },compositeCueSettings); + + // Turn back into string like the TextTrackCue constructor expects + cueSettings = ""; + for (var key in compositeCueSettings) { + if (compositeCueSettings.hasOwnProperty(key)) { + cueSettings += !!cueSettings.length ? " " : ""; + cueSettings += key + ":" + compositeCueSettings[key]; + } + } +*/ + // The remaining lines are the subtitle payload itself (after removing an ID if present, and the time); + html = subtitleParts.join("\n"); + tmpCue = new TextTrackCue(timeIn, timeOut, html); + if(id){ + tmpCue.id = id; + } + return tmpCue; + }; + })(); + + mediaelement.parseCaptions = function(captionData, track, complete) { + var subtitles = mediaelement.createCueList(); + var cue, lazyProcess, regWevVTT; + var startDate; + var isWEBVTT; + if (captionData) { + + regWevVTT = /^WEBVTT(\s*FILE)?/ig; + + lazyProcess = function(i, len){ + + for(; i < len; i++){ + cue = captionData[i]; + if(regWevVTT.test(cue)){ + isWEBVTT = true; + } else if(cue.replace(/\s*/ig,"").length){ + if(!isWEBVTT){ + webshims.error('please use WebVTT format. This is the standard'); + complete(null); + break; + } + cue = mediaelement.parseCaptionChunk(cue, i); + if(cue){ + track.addCue(cue); + } + } + if(startDate < (new Date().getTime()) - 30){ + i++; + setTimeout(function(){ + startDate = new Date().getTime(); + lazyProcess(i, len); + }, 90); + + break; + } + } + if(i >= len){ + if(!isWEBVTT){ + webshims.error('please use WebVTT format. This is the standard'); + } + complete(track.cues); + } + }; + + captionData = captionData.replace(/\r\n/g,"\n"); + + setTimeout(function(){ + captionData = captionData.replace(/\r/g,"\n"); + setTimeout(function(){ + startDate = new Date().getTime(); + captionData = captionData.split(/\n\n+/g); + lazyProcess(0, captionData.length); + }, 9); + }, 9); + + } else { + webshims.error("Required parameter captionData not supplied."); + } + }; + + + mediaelement.createTrackList = function(mediaelem, baseData){ + baseData = baseData || webshims.data(mediaelem, 'mediaelementBase') || webshims.data(mediaelem, 'mediaelementBase', {}); + if(!baseData.textTracks){ + baseData.textTracks = []; + webshims.defineProperties(baseData.textTracks, { + onaddtrack: {value: null}, + onremovetrack: {value: null} + }); + createEventTarget(baseData.textTracks); + } + return baseData.textTracks; + }; + + if(!Modernizr.track){ + webshims.defineNodeNamesBooleanProperty(['track'], 'default'); + webshims.reflectProperties(['track'], ['srclang', 'label']); + + webshims.defineNodeNameProperties('track', { + src: { + //attr: {}, + reflect: true, + propType: 'src' + } + }); + } + + webshims.defineNodeNameProperties('track', { + kind: { + attr: Modernizr.track ? { + set: function(value){ + var trackData = webshims.data(this, 'trackData'); + this.setAttribute('data-kind', value); + if(trackData){ + trackData.attrKind = value; + } + }, + get: function(){ + var trackData = webshims.data(this, 'trackData'); + if(trackData && ('attrKind' in trackData)){ + return trackData.attrKind; + } + return this.getAttribute('kind'); + } + } : {}, + reflect: true, + propType: 'enumarated', + defaultValue: 'subtitles', + limitedTo: ['subtitles', 'captions', 'descriptions', 'chapters', 'metadata'] + } + }); + + $.each(copyProps, function(i, copyProp){ + var name = copyName[copyProp] || copyProp; + webshims.onNodeNamesPropertyModify('track', copyProp, function(){ + var trackData = webshims.data(this, 'trackData'); + var track = this; + if(trackData){ + if(copyProp == 'kind'){ + refreshTrack(this, trackData); + } + if(!supportTrackMod){ + trackData.track[name] = $.prop(this, copyProp); + } + clearTimeout(trackData.changedTrackPropTimer); + trackData.changedTrackPropTimer = setTimeout(function(){ + $(track).trigger('updatesubtitlestate'); + }, 1); + } + }); + }); + + + webshims.onNodeNamesPropertyModify('track', 'src', function(val){ + if(val){ + var data = webshims.data(this, 'trackData'); + var media; + if(data){ + media = $(this).closest('video, audio'); + if(media[0]){ + mediaelement.loadTextTrack(media, this, data); + } + } + } + + }); + + // + + webshims.defineNodeNamesProperties(['track'], { + ERROR: { + value: 3 + }, + LOADED: { + value: 2 + }, + LOADING: { + value: 1 + }, + NONE: { + value: 0 + }, + readyState: { + get: function(){ + return ($.prop(this, 'track') || {readyState: 0}).readyState; + }, + writeable: false + }, + track: { + get: function(){ + return mediaelement.createTextTrack($(this).closest('audio, video')[0], this); + }, + writeable: false + } + }, 'prop'); + + webshims.defineNodeNamesProperties(['audio', 'video'], { + textTracks: { + get: function(){ + var media = this; + var baseData = webshims.data(media, 'mediaelementBase') || webshims.data(media, 'mediaelementBase', {}); + var tracks = mediaelement.createTrackList(media, baseData); + if(!baseData.blockTrackListUpdate){ + updateMediaTrackList.call(media, baseData, tracks); + } + return tracks; + }, + writeable: false + }, + addTextTrack: { + value: function(kind, label, lang){ + var textTrack = mediaelement.createTextTrack(this, { + kind: kind || '', + label: label || '', + srclang: lang || '' + }); + var baseData = webshims.data(this, 'mediaelementBase') || webshims.data(this, 'mediaelementBase', {}); + if (!baseData.scriptedTextTracks) { + baseData.scriptedTextTracks = []; + } + baseData.scriptedTextTracks.push(textTrack); + updateMediaTrackList.call(this); + return textTrack; + } + } + }, 'prop'); + + + $(document).bind('emptied ended updatetracklist', function(e){ + if($(e.target).is('audio, video')){ + var baseData = webshims.data(e.target, 'mediaelementBase'); + if(baseData){ + clearTimeout(baseData.updateTrackListTimer); + baseData.updateTrackListTimer = setTimeout(function(){ + updateMediaTrackList.call(e.target, baseData); + }, 0); + } + } + }); + + var getNativeReadyState = function(trackElem, textTrack){ + return textTrack.readyState || trackElem.readyState; + }; + + webshims.addReady(function(context, insertedElement){ + var insertedMedia = insertedElement.filter('video, audio, track').closest('audio, video'); + $('video, audio', context) + .add(insertedMedia) + .each(function(){ + updateMediaTrackList.call(this); + }) + .each(function(){ + if(Modernizr.track){ + var shimedTextTracks = $.prop(this, 'textTracks'); + var origTextTracks = this.textTracks; + if(shimedTextTracks.length != origTextTracks.length){ + webshims.error("textTracks couldn't be copied"); + } + + $('track', this) + .each(function(){ + var shimedTrack = $.prop(this, 'track'); + var origTrack = this.track; + var kind; + var readyState; + if(origTrack){ + kind = $.prop(this, 'kind'); + readyState = getNativeReadyState(this, origTrack); + if (origTrack.mode || readyState) { + shimedTrack.mode = origTrack.mode; + } + //disable track from showing + remove UI + if(kind != 'descriptions'){ + origTrack.mode = (typeof origTrack.mode == 'string') ? 'disabled' : 0; + this.kind = 'metadata'; + $(this).attr({kind: kind}); + } + + } + }) + .bind('load error', function(e){ + if(e.originalEvent){ + e.stopImmediatePropagation(); + } + }) + ; + } + }) + ; + insertedMedia.each(function(){ + var media = this; + var baseData = webshims.data(media, 'mediaelementBase'); + if(baseData){ + clearTimeout(baseData.updateTrackListTimer); + baseData.updateTrackListTimer = setTimeout(function(){ + updateMediaTrackList.call(media, baseData); + }, 9); + } + }); + }); + + if(Modernizr.track){ + $('video, audio').trigger('trackapichange'); + } + }); \ No newline at end of file