vendor/assets/javascripts/webshims/shims/jme/mediacontrols-lazy.js in webshims-rails-1.14.3 vs vendor/assets/javascripts/webshims/shims/jme/mediacontrols-lazy.js in webshims-rails-1.14.4

- old
+ new

@@ -1,6 +1,6 @@ -webshims.register('jme', function($, webshims, window, doc, undefined){ +webshims.register('mediacontrols-lazy', function($, webshims, window, doc, undefined){ "use strict"; var plugins = $.jme.plugins; var pseudoClasses = 'pseudoClasses'; var playStates = { play: 1, @@ -22,81 +22,92 @@ var getTrackMenu = function(tracks){ var items = $.map(tracks, function(track){ var className = (track.kind == 'caption') ? 'caption-type' : 'subtitle-type'; var lang = track.language; lang = (lang) ? ' <span class="track-lang">'+ lang +'</span>' : ''; - return '<li class="'+ className +'" role="presentation"><button role="menuitemcheckbox" type="button">'+ track.label + lang +'</button></li>'; + return '<li class="'+ className +'" role="presentation"><button role="menuitemcheckbox" type="button" tabindex="-1">'+ track.label + lang +'</button></li>'; }) ; - return '<div><ul>' + items.join('') +'</ul></div>'; + return '<div><ul role="presentation">' + items.join('') +'</ul></div>'; }; - $.fn.wsTouchClick = (function(){ - var supportsTouchaction = ('touchAction' in document.documentElement.style); - var addTouch = !supportsTouchaction && ('ontouchstart' in window) && document.addEventListener; - return function(target, handler){ - var touchData, touchEnd, touchStart; + if(!$.fn.wsTouchClick){ - if(addTouch){ - - touchEnd = function(e){ - var ret, touch; - e = e.originalEvent || {}; - $(this).off('touchend touchcancel', touchEnd); - var changedTouches = e.changedTouches || e.touches; - if(e.type == 'touchcancel' || !touchData || !changedTouches || changedTouches.length != 1){ - return; + $.fn.wsTouchClick = (function(){ + var supportsTouchaction = ('touchAction' in document.documentElement.style); + var addTouch = !supportsTouchaction && ('ontouchstart' in window) && document.addEventListener; + return function(target, handler){ + var touchData, touchEnd, touchStart, stopClick, allowClick; + var runHandler = function(){ + if(!stopClick){ + return handler.apply(this, arguments); } + }; + if(addTouch){ + allowClick = function(){ + stopClick = false; + }; + touchEnd = function(e){ + var ret, touch; + e = e.originalEvent || {}; + $(this).off('touchend touchcancel', touchEnd); + var changedTouches = e.changedTouches || e.touches; + if(e.type == 'touchcancel' || !touchData || !changedTouches || changedTouches.length != 1){ + return; + } - touch = changedTouches[0]; - if(Math.abs(touchData.x - touch.pageX) > 40 || Math.abs(touchData.y - touch.pageY) > 40 || Date.now() - touchData.now > 300){ - return; - } - e.preventDefault(); - ret = handler.apply(this, arguments); + touch = changedTouches[0]; + if(Math.abs(touchData.x - touch.pageX) > 40 || Math.abs(touchData.y - touch.pageY) > 40 || Date.now() - touchData.now > 300){ + return; + } - return ret; - }; + e.preventDefault(); + stopClick = true; + setTimeout(allowClick, 400); - touchStart = function(e){ - var touch, elemTarget; + ret = handler.apply(this, arguments); + return ret; + }; - if((!e || e.touches.length != 1)){ - return; - } - touch = e.touches[0]; - elemTarget = target ? $(touch.target).closest(target) : $(this); - if(!elemTarget.length){ - return; - } - touchData = { - x: touch.pageX, - y: touch.pageY, - now: Date.now() + touchStart = function(e){ + var touch, elemTarget; + if((!e || e.touches.length != 1)){ + return; + } + touch = e.touches[0]; + elemTarget = target ? $(touch.target).closest(target) : $(this); + if(!elemTarget.length){ + return; + } + touchData = { + x: touch.pageX, + y: touch.pageY, + now: Date.now() + }; + elemTarget.on('touchend touchcancel', touchEnd); }; - elemTarget.on('touchend touchcancel', touchEnd); - }; - this.each(function(){ - this.addEventListener('touchstart', touchStart, true); - }); - } else if(supportsTouchaction){ - this.css('touch-action', 'manipulation'); - } + this.each(function(){ + this.addEventListener('touchstart', touchStart, true); + }); + } else if(supportsTouchaction){ + this.css('touch-action', 'manipulation'); + } - if($.isFunction(target)){ - handler = target; - target = false; - this.on('click', handler); - } else { - this.on('click', target, handler); - } - return this; - }; - })(); + if($.isFunction(target)){ + handler = target; + target = false; + this.on('click', runHandler); + } else { + this.on('click', target, runHandler); + } + return this; + }; + })(); + } function createGetSetHandler(fns){ var throttleTimer; var blocked; @@ -229,18 +240,22 @@ 'touchstart touchend mousedown click mouseover': function(e){ var delay = 500; stopFocus = true; clearTimeout(focusTimer); if(markedFocus && specialUnStop[e.type] && e.target.className.indexOf('ws-a11y-focus') != -1){ - delay = 0; + delay = 1; } focusTimer = setTimeout(unStop, delay); }, focusin: function(e){ - if(!stopFocus && e.originalEvent){ - markedFocus = true; - $(e.target).addClass('ws-a11y-focus'); + if(!stopFocus && e.originalEvent && ($.prop(e.target, 'tabIndex') > -1 || $.attr(e.target, 'role'))){ + setTimeout(function(){ + if(!stopFocus){ + markedFocus = true; + $(e.target).addClass('ws-a11y-focus'); + } + }, 20); } }, focusout: function(e){ if(markedFocus){ markedFocus = false; @@ -387,21 +402,22 @@ if(hasDuration && e.type != 'touchstart' || (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length == 1)){ var widgetLeft = (control.offset() || {left: 0}).left; var widgetWidth = control.innerWidth(); var posLeft = function(x){ - var perc = (x - widgetLeft) / widgetWidth * 100; - timeShow - .html(media.jmeFn('formatTime', duration * perc / 100)) - .css({left: perc+'%'}) - ; + var perc = ((x - widgetLeft) / widgetWidth * 100); + var marginLeft = -(timeShow.outerWidth() / 2); + timeShow[0].innerHTML = media.jmeFn('formatTime', duration * perc / 100); + + timeShow[0].style.left = perc+'%'; + timeShow[0].style.marginLeft = marginLeft+'px'; }; $.fn.rangeUI.normalizeTouch(e); setTimeout(function(){ - posLeft(e.pageX); timeShow.addClass('show-time-select'); + posLeft(e.pageX); }); if(document.addEventListener){ document.addEventListener('touchend', hideTime, true); } control @@ -526,10 +542,118 @@ }); addDoubbleClick(); updateControl(); } }, + chapters: { + _create: function(control, media, base){ + var plugin = this; + webshims.ready('track', function(){ + var menuObj, wasPlayed, hasTrack, preloadTimer, $bar; + var timedPreload = function(){ + clearTimeout(preloadTimer); + preloadTimer = setTimeout(setPreload, 999); + }; + var setPreload = function(){ + var preload; + if(hasTrack && !media.prop('readyState')){ + preload = media.attr('preload'); + if(preload != 'auto'){ + preload = 'auto'; + media.prop('preload', preload); + } + } + }; + var createMenuButton = function(){ + if(menuObj){return;} + menuObj = new $.jme.ButtonMenu(control, '<div class="mediamenu chapter-menu" />', function(e, button){ + var paused = media.prop('paused'); + var readyState = media.prop('readyState'); + if(!wasPlayed || readyState < 2){ + media.play(); + if(paused){ + media.pause(); + } + } + if(readyState < 2){ + setTimeout(function(){ + media.prop('currentTime', $(button).data('starttime')); + }, 99); + } + + if(readyState){ + media.prop('currentTime', $(button).data('starttime')); + } + + }); + }; + + var buildMenu = function(currentTrack, chapterList){ + + if($bar){ + $bar.remove(); + $bar = null; + } + + if(currentTrack && chapterList.length){ + var chapters = chapterList.map(createChapterList, { + html: '<button type="button" data-starttime="{{startTime}}" data-endtime="{{endTime}}" role="menuitem" tabindex="-1">{{title}}</button>' + }); + var text = currentTrack.label || plugin.text; + + //$bar = chapterList.map(createChapterBar); + + //$('.time-slider', base).append('<ul role="presentation" class="mediachapter-bar">'+ $bar.join('\n') + '</ul>'); + + hasTrack = true; + base.addClass('has-chapter-tracks'); + createMenuButton(); + control.attr('aria-label', text); + menuObj.addMenu('<div class="mediamenu chapter-menu" aria-label="'+ text +'"><div><h5>'+ text +'</h5><ul role="presentation">'+ chapters.join('\n') +'</div></ul></div>') + } else { + hasTrack = false; + control.attr('aria-label', plugin.text); + base.removeClass('has-chapter-tracks'); + } + + }; + + media.on({ + play: function(){ + wasPlayed = true; + }, + 'emptied loadstart': function(){ + wasPlayed = false; + timedPreload(); + } + }); + webshims.ready('WINDOWLOAD', timedPreload); + base.jmeFn('getMediaChapters', buildMenu); + + }); + } + }, + mediaconfigmenu: { + _create: function(control, media, base){ + var timer; + var menu = new $.jme.ButtonMenu(control, '<div class="mediamenu" ><div /></div>'); + var innerMenu = menu.menu.find('div'); + var enableDisable = function(){ + base[innerMenu[0].getElementsByTagName('*').length ? 'addClass' : 'removeClass']('has-config-menu'); + }; + var timedEnable = function(){ + clearTimeout(timer); + timer = setTimeout(enableDisable); + }; + $.each($.jme.configmenuPlugins, function(name, create){ + create(innerMenu, media, base, menu); + }); + + enableDisable(); + media.on('loadstart emptied loadedmetadata', timedEnable); + } + }, captions: { pseudoClasses: { menu: 'subtitle-menu' }, _create: function(control, media, base){ @@ -591,11 +715,13 @@ updateMode(); } function updateMode(){ + if(!menuObj || !menuObj.menu || !menuObj.menu.length){return;} $('button', menuObj.menu).each(function(i){ + if(!tracks[i]){return false;} var checked = (tracks[i].mode == 'showing') ? 'true' : 'false'; if(!i){ checkbox.attr('aria-checked', checked); } $.attr(this, 'aria-checked', checked); @@ -611,24 +737,20 @@ }); base.attr('data-tracks', tracks.length > 1 ? 'many' : tracks.length); if(tracks.length){ - createSubtitleMenu('<div class="'+that[pseudoClasses].menu +'" >'+ (getTrackMenu(tracks)) +'</div>'); + createSubtitleMenu('<div class="mediamenu '+that[pseudoClasses].menu +'" >'+ (getTrackMenu(tracks)) +'</div>'); $('span.jme-text, label span.jme-text', checkbox).text((tracks[0].label || ' ') + (tracks[0].lang || '')); if(!base.hasClass(that[pseudoClasses].hasTrack) || base.hasClass(that[pseudoClasses].noTrack)){ control.prop('disabled', false); - base.triggerHandler('controlschanged'); } } else if(!base.hasClass(that[pseudoClasses].noTrack) || base.hasClass(that[pseudoClasses].hasTrack)){ control.prop('disabled', true); - base - .triggerHandler('controlschanged') - ; } } if(!textTracks){ textTracks = []; @@ -656,11 +778,228 @@ }); } } }); + var trackFilters = { + chapters: function(track){ + return track.kind == 'chapters'; + }, + notDisabled: function(track){ + return track.mode != 'disabled'; + }, + activeLang: function(track){ + return track.language == webshims.activeLang(); + }, + activePartialLang: function(track){ + return track.language == webshims.activeLang().split('-')[0]; + } + }; + function getBestChapterTrack(tracks){ + var ret = $.grep(tracks, trackFilters.chapters); + var last = ret; + if(ret.length > 1){ + ret = $.grep(ret, trackFilters.chapters); + } + + if(!ret.length){ + ret = last; + } else if(ret.length > 1){ + ret = $.grep(ret, trackFilters.notDisabled); + } + + if(!ret.length){ + ret = last; + } else if(ret.length > 1){ + ret = $.grep(ret, trackFilters.activeLang); + } + + if(!ret.length){ + ret = last; + } else if(ret.length > 1){ + ret = $.grep(ret, trackFilters.activePartialLang); + } + + return ret[0] || last[0] || null; + } + + var showMode = { + captions: 'showing', + subtitles: 'showing' + }; + + $.jme.defineMethod('activateTrack', function(track, success){ + var data = $.jme.data(this); + if(!data.media){return;} + var textTrack, timer; + var callIndex = 0; + var checkTrackState = function(){ + clearTimeout(timer); + if(textTrack && textTrack.cues && textTrack.cues.length){ + success(textTrack); + success = $.noop; + data.media.find('track').off('load', checkTrackState); + } else if(callIndex < 9){ + timer = setTimeout(checkTrackState, 100 * callIndex); + callIndex++; + } + }; + if(track.jquery){ + track = track[0]; + } + if(track.nodeName){ + textTrack = $.prop(track, 'track'); + } else { + textTrack = track; + } + + if($.prop(textTrack, 'mode') == 'disabled'){ + $.prop(textTrack, 'mode', showMode[$.prop(textTrack, 'mode')] || 'hidden'); + } + data.media.prop('textTracks'); + data.media.find('track').on('load', checkTrackState); + setTimeout(checkTrackState); + }); + + $.jme.defineMethod('getMediaChapters', function(success){ + var data = $.jme.data(this); + if(!data.media){return;} + var currentChapterTrack; + var textTracks = data.media.prop('textTracks'); + + var updateChapterTrack = (function(){ + var timer; + + var update = function(){ + var oldChapterTrack; + var selectedChapterTrack = getBestChapterTrack(textTracks); + if(currentChapterTrack === selectedChapterTrack){return;} + oldChapterTrack = currentChapterTrack; + currentChapterTrack = selectedChapterTrack; + if(selectedChapterTrack){ + data.media.jmeFn('activateTrack', currentChapterTrack, function(){ + var chapterTree = getChapterTree(currentChapterTrack); + success(currentChapterTrack, chapterTree, oldChapterTrack); + + }); + } else { + success(currentChapterTrack, [], oldChapterTrack); + } + + }; + return function(){ + clearTimeout(timer); + timer = setTimeout(update); + }; + })(); + + updateChapterTrack(); + $([textTracks]).on('addtrack removetrack change', updateChapterTrack); + data.player.on('updatesubtitlestate', updateChapterTrack); + data.media.on('updatetrackdisplay emptied', updateChapterTrack); + }); + + function createChapterList(chapter){ + var item = '<li role="presentation">'+ (this.html.replace('{{startTime}}', chapter.startTime).replace('{{endTime}}', chapter.endTime).replace('{{title}}', chapter.title)); + if(chapter.list && chapter.list.length){ + item += '\n<ul role="presentation">'+ chapter.list.map(createChapterList, this).join('\n\t') +'</ul>\n'; + } + item += '</li>'; + return item; + } + + function createChapterBar(chapter){ + var item = '<li role="presentation" style="'+ chapter.style +'" data-start="'+chapter.startTime+'" data-end="'+chapter.endTime+'"><span>'+chapter.title+'</span>'; + if(chapter.list && chapter.list.length){ + item += '\n<ul role="presentation">'+ chapter.list.map(createChapterBar).join('\n\t') +'</ul>\n'; + } + item += '</li>'; + return item; + } + + function addChapterRelatives(chapterList){ + var i, start, end, multi; + if(chapterList.length){ + start = chapterList[0].startTime; + end = chapterList[chapterList.length - 1].endTime; + multi = 100 / (end - start); + for(i = 0; i < chapterList.length; i++){ + chapterList[i].rel = (chapterList[i].endTime - chapterList[i].startTime) * multi; + if(i == chapterList.length - 1){ + chapterList[i].last = true; + chapterList[i].style = 'overflow: hidden;'; + } else { + chapterList[i].style = 'float: left; width: '+chapterList[i].rel+'%;'; + } + addChapterRelatives(chapterList[i].list); + } + } + } + + function getChapterTree(track){ + var name ='__chaptertree'+track.cues.length; + if(track[name]){return track[name];} + var cue, i, chapter, start, end; + var chapterList = []; + var currentChapter = null; + for(i = 0; i < track.cues.length; i++){ + cue = track.cues[i]; + if(currentChapter && currentChapter.startTime > cue.startTime){ + continue; + } + if(currentChapter && cue.startTime >= currentChapter.endTime){ + currentChapter = currentChapter.parent; + } + + if(currentChapter && cue.endTime > currentChapter.endTime){ + continue; + } + chapter = { + startTime: cue.startTime, + endTime: cue.endTime, + parent: currentChapter, + list: [], + title: cue.text, + cue: cue + }; + if(currentChapter){ + currentChapter.list.push(chapter); + } else { + currentChapter = chapter; + chapterList.push(chapter); + } + } + + addChapterRelatives(chapterList); + track[name] = chapterList; + return chapterList; + } + + var domPrefixes = ["webkit", "moz", "o", "ms"]; + + function prefixed(prop, obj){ + var i, testProp; + var ret = false; + if(obj[prop]){ + ret = prop; + } + if(!ret){ + prop = prop.charAt(0).toUpperCase() + prop.slice(1); + for(i = 0; i < domPrefixes.length; i++){ + testProp = domPrefixes[i]+prop; + if(testProp in obj){ + ret = testProp; + break; + } + } + } + return ret; + } + + $.jme.defineMethod('getChapterTree', getChapterTree); + $.jme.defineMethod('concerningRange', function(type, time){ var elem = this; var ret = {start: 0, end: 0}; if(!type){ type = 'buffered'; @@ -737,11 +1076,11 @@ var parentData; var frameData; var doc = document.documentElement; var fullScreenApi = { - supportsFullScreen: Modernizr.prefixed('fullscreenEnabled', document, false) || Modernizr.prefixed('fullScreenEnabled', document, false), + supportsFullScreen: prefixed('fullscreenEnabled', document) || prefixed('fullScreenEnabled', document), isFullScreen: function() { return false; }, requestFullScreen: function(elem){ var tmpData; parentData = []; $(elem).parentsUntil('body').each(function(){ @@ -820,15 +1159,15 @@ fullScreenApi.requestFullWindow = fullScreenApi.requestFullScreen; // update methods to do something useful if (fullScreenApi.supportsFullScreen) { fullScreenApi.enabledName = fullScreenApi.supportsFullScreen; - fullScreenApi.exitName = Modernizr.prefixed("exitFullscreen", document, false) || Modernizr.prefixed("cancelFullScreen", document, false); - fullScreenApi.elementName = Modernizr.prefixed("fullscreenElement", document, false) || Modernizr.prefixed("fullScreenElement", document, false); + fullScreenApi.exitName = prefixed("exitFullscreen", document) || prefixed("cancelFullScreen", document); + fullScreenApi.elementName = prefixed("fullscreenElement", document) || prefixed("fullScreenElement", document); fullScreenApi.supportsFullScreen = !!fullScreenApi.supportsFullScreen; if(fullScreenApi.elementName != 'fullscreenElement' || fullScreenApi.exitName != 'exitFullscreen' || fullScreenApi.enabledName != 'fullscreenEnabled'){ - $.each(Modernizr._domPrefixes, function(i, prefix){ + $.each(domPrefixes, function(i, prefix){ var requestName = prefix+'RequestFullscreen'; if((requestName in doc) || ((requestName = prefix+'RequestFullScreen') && (requestName in doc))){ fullScreenApi.eventName = prefix + 'fullscreenchange'; fullScreenApi.requestName = requestName; return false; @@ -1000,11 +1339,11 @@ }); $.jme.ButtonMenu = function(button, menu, clickHandler){ - + var that = this; this.button = $(button).attr({'aria-haspopup': 'true'}); this.clickHandler = clickHandler; this.toggle = $.proxy(this, 'toggle'); @@ -1012,25 +1351,39 @@ this._buttonClick = $.proxy(this, '_buttonClick'); this.addMenu(menu); this._closeFocusOut(); - this.button.wsTouchClick(this.toggle); + + this.button + .wsTouchClick(this.toggle) + .on('keydown', function(e){ + if(!that.isVisible && (e.keyCode == 38 || e.keyCode == 40)){ + that.show(); + return false; + } + }) + ; }; $.jme.ButtonMenu.prototype = { addMenu: function(menu){ if(this.menu){ this.menu.remove(); } this.menu = $(menu); - this.buttons = $('button', this.menu); + this.menu.insertAfter(this.button); - this.menu - .on('keydown', this.keyIndex) - .wsTouchClick('button', this._buttonClick) - ; + if(this.clickHandler){ + this.buttons = $('button', this.menu); + this.menu + .attr('role', 'menu') + .on('keydown', this.keyIndex) + .wsTouchClick('button', this._buttonClick) + ; + + } }, _closeFocusOut: function(){ var that = this; var timer; var stopFocusOut = function(){ @@ -1039,46 +1392,51 @@ clearTimeout(timer); }, 9); }; this.menu .parent() - .on('focusin', stopFocusOut) - .on('mousedown', stopFocusOut) + .on('focusin mousedown click touchend', stopFocusOut) .on('focusout', function(e){ timer = setTimeout(function(){ + that.activeElement = false; that.hide(); }, 40); }) ; }, _buttonClick: function(e){ - this.clickHandler(this.buttons.index(e.currentTarget), e.currentTarget); - this.hide(); + if(this.clickHandler){ + this.clickHandler(this.buttons.index(e.currentTarget), e.currentTarget); + this.hide(); + } }, keyIndex: function(e){ var dir = (e.keyCode == 40) ? 1 : (e.keyCode == 38) ? -1 : 0; + if(e.keyCode == 27){ + this.hide(); + } if(dir){ var buttons = this.buttons.not(':disabled'); var activeButton = buttons.filter(':focus'); activeButton = buttons[buttons.index(activeButton) + dir] || buttons.filter(dir > 0 ? ':first' : ':last'); - activeButton.focus(); + activeButton.trigger('focus'); e.preventDefault(); } }, show: function(){ if(this.isVisible){return;} - var buttons = this.buttons.not(':disabled'); + var buttons = $('button, select, input, textarea', this.menu).not(':disabled, [aria-diabled="true"]'); this.isVisible = true; this.menu.addClass('visible-menu'); try { this.activeElement = document.activeElement || this.button[0]; } catch(er){ this.activeElement = this.button[0]; } setTimeout(function(){ - $(buttons.filter('[aria-checked="true"]')[0] || buttons[0]).focus(); + $(buttons.filter('[aria-checked="true"]').last()[0] || buttons[0]).trigger('focus'); }, 60); }, toggle: function(){ this[this.isVisible ? 'hide' : 'show'](); },