From ffebc7b077ed64f017d2313634c934cc3af233a2 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 25 Apr 2016 19:06:55 +1000 Subject: UI tweaks --- src/js/plyr.js | 1514 ++++++++++++++++++++++---------------- src/less/plyr.less | 700 +++++++++--------- src/sprite/icon-fast-forward.svg | 12 +- src/sprite/icon-pause.svg | 16 +- src/sprite/icon-play.svg | 12 +- src/sprite/icon-rewind.svg | 12 +- 6 files changed, 1295 insertions(+), 971 deletions(-) mode change 100755 => 100644 src/sprite/icon-fast-forward.svg mode change 100755 => 100644 src/sprite/icon-play.svg (limited to 'src') diff --git a/src/js/plyr.js b/src/js/plyr.js index 52f4b9d9..5c9e329e 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1,32 +1,32 @@ // ========================================================================== // Plyr -// plyr.js v1.5.0 +// plyr.js v1.5.21 // https://github.com/selz/plyr // License: The MIT License (MIT) // ========================================================================== // Credits: http://paypal.github.io/accessible-html5-video-player/ // ========================================================================== -(function(root, factory) { +;(function(root, factory) { 'use strict'; /*global define,module*/ - if (typeof define === 'function' && define.amd) { - // AMD - define(null, function() { factory(root, document) }); - } else if (typeof module === 'object') { + if (typeof module === 'object' && typeof module.exports === 'object') { // Node, CommonJS-like module.exports = factory(root, document); + } else if (typeof define === 'function' && define.amd) { + // AMD + define(null, function() { factory(root, document) }); } else { // Browser globals (root is window) root.plyr = factory(root, document); } -}(this, function(window, document) { +}(typeof window !== 'undefined' ? window : this, function(window, document) { 'use strict'; /*global YT,$f*/ // Globals - var fullscreen, config, api = {}; + var fullscreen, api = {}; // Default config var defaults = { @@ -36,17 +36,22 @@ loop: false, seekTime: 10, volume: 5, - click: true, - tooltips: false, + duration: null, displayDuration: true, iconPrefix: 'icon', + clickToPlay: true, + hideControls: true, + tooltips: { + controls: false, + seek: true + }, selectors: { container: '.plyr', controls: { container: null, wrapper: '.plyr__controls' }, - labels: '[data-plyr] .sr-only, label .sr-only', + labels: '[data-plyr]', buttons: { seek: '[data-plyr="seek"]', play: '[data-plyr="play"]', @@ -79,6 +84,7 @@ hover: 'plyr--hover', tooltip: 'plyr__tooltip', hidden: 'plyr__sr-only', + hideControls: 'plyr--hide-controls', isIos: 'plyr--is-ios', isTouch: 'plyr--is-touch', captions: { @@ -87,37 +93,23 @@ }, fullscreen: { enabled: 'plyr--fullscreen-enabled', - active: 'plyr--fullscreen-active', - hideControls: 'plyr--fullscreen--hide-controls' + active: 'plyr--fullscreen-active' }, tabFocus: 'tab-focus' }, - handlers: { - seek: null, - play: null, - pause: null, - restart: null, - rewind: null, - forward: null, - mute: null, - volume: null, - captions: null, - fullscreen: null - }, captions: { defaultActive: false }, fullscreen: { enabled: true, fallback: true, - hideControls: true, allowAudio: false }, storage: { enabled: true, - key: 'plyr_volume' + key: 'plyr' }, - controls: ['restart', 'rewind', 'play', 'fast-forward', 'current-time', 'duration', 'mute', 'volume', 'captions', 'fullscreen'], + controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'captions', 'fullscreen'], i18n: { restart: 'Restart', rewind: 'Rewind {seektime} secs', @@ -138,160 +130,32 @@ embed: ['youtube', 'vimeo'], html5: ['video', 'audio'] }, + // URLs urls: { vimeo: { - api: 'https://cdn.plyr.io/froogaloop/1.0.0/plyr.froogaloop.js', + api: 'https://cdn.plyr.io/froogaloop/1.0.1/plyr.froogaloop.js', }, youtube: { api: 'https://www.youtube.com/iframe_api' } - } + }, + // Custom control listeners + listeners: { + seek: null, + play: null, + pause: null, + restart: null, + rewind: null, + forward: null, + mute: null, + volume: null, + captions: null, + fullscreen: null + }, + // Events to watch on HTML5 media elements + events: ['ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'emptied'] }; - // Build the default HTML - function _buildControls() { - // Open and add the progress and seek elements - var html = [ - '
', - '
', - '', - '', - '', - '0% ' + config.i18n.played, - '', - '', - '0% ' + config.i18n.buffered, - '', - '
', - '']; - - // Restart button - if (_inArray(config.controls, 'restart')) { - html.push( - '' - ); - } - - // Rewind button - if (_inArray(config.controls, 'rewind')) { - html.push( - '' - ); - } - - // Play/pause button - if (_inArray(config.controls, 'play')) { - html.push( - '', - '' - ); - } - - // Fast forward button - if (_inArray(config.controls, 'fast-forward')) { - html.push( - '' - ); - } - - // Media current time display - if (_inArray(config.controls, 'current-time')) { - html.push( - '', - '' + config.i18n.currentTime + '', - '00:00', - '' - ); - } - - // Media duration display - if (_inArray(config.controls, 'duration')) { - html.push( - '', - '' + config.i18n.duration + '', - '00:00', - '' - ); - } - - // Close left controls - html.push( - '', - '' - ); - - // Toggle mute button - if (_inArray(config.controls, 'mute')) { - html.push( - '' - ); - } - - // Volume range control - if (_inArray(config.controls, 'volume')) { - html.push( - '', - '' - ); - } - - // Toggle captions button - if (_inArray(config.controls, 'captions')) { - html.push( - '' - ); - } - - // Toggle fullscreen button - if (_inArray(config.controls, 'fullscreen')) { - html.push( - '' - ); - } - - // Close everything - html.push( - '', - '
' - ); - - return html.join(''); - } - - // Debugging - function _log(text, warn) { - if (config.debug && window.console) { - console[(warn ? 'warn' : 'log')](text); - } - } - // Credits: http://paypal.github.io/accessible-html5-video-player/ // Unfortunately, due to mixed support, UA sniffing is required function _browserSniff() { @@ -526,39 +390,63 @@ return false; } + // Debounce + // deBouncer by hnldesign.nl + // based on code by Paul Irish and the original debouncing function from John Hann + // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ + function _debounce(func, threshold, execAsap) { + var timeout; + return function debounced () { + var obj = this, args = arguments; + function delayed () { + if (!execAsap) { + func.apply(obj, args); + } + timeout = null; + } + if (timeout) { + clearTimeout(timeout); + } + else if (execAsap) { + func.apply(obj, args); + } + timeout = setTimeout(delayed, threshold || 100); + }; + } + // Bind event function _on(element, events, callback) { if (element) { - _toggleHandler(element, events, callback, true); + _toggleListener(element, events, callback, true); } } // Unbind event function _off(element, events, callback) { if (element) { - _toggleHandler(element, events, callback, false); + _toggleListener(element, events, callback, false); } } // Bind along with custom handler - function _proxyHandler(element, eventName, userHandler, defaultHandler) { + function _proxyListener(element, eventName, userListener, defaultListener) { _on(element, eventName, function(event) { - if(userHandler) { - userHandler.apply(element, [event]); + if(userListener) { + userListener.apply(element, [event]); } - defaultHandler.apply(element, [event]); + defaultListener.apply(element, [event]); }); } - // Toggle event handler - function _toggleHandler(element, events, callback, toggle) { + // Toggle event listener + function _toggleListener(element, events, callback, toggle) { var eventList = events.split(' '); // If a nodelist is passed, call itself on each node if (element instanceof NodeList) { for (var x = 0; x < element.length; x++) { if (element[x] instanceof Node) { - _toggleHandler(element[x], arguments[1], arguments[2], arguments[3]); + _toggleListener(element[x], arguments[1], arguments[2], arguments[3]); } } return; @@ -571,23 +459,21 @@ } // Trigger event - function _triggerEvent(element, event) { + function _triggerEvent(element, eventName, properties) { // Bail if no element - if(!element || !event) { + if(!element || !eventName) { return; } - // Create faux event - var fauxEvent = document.createEvent('MouseEvents'); - - // Set the event type - fauxEvent.initEvent(event, true, true); + // create and dispatch the event + var event = new CustomEvent(eventName, properties); // Dispatch the event - element.dispatchEvent(fauxEvent); + element.dispatchEvent(event); } // Toggle aria-pressed state on a toggle button + // http://www.ssbbartgroup.com/blog/how-not-to-misuse-aria-states-properties-and-roles function _toggleState(target, state) { // Bail if no target if(!target) { @@ -611,19 +497,42 @@ return ((current / max) * 100).toFixed(2); } - // Deep extend/merge two Objects + // Deep extend/merge destination object with N more objects // http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/ // Removed call to arguments.callee (used explicit function name instead) - function _extend(destination, source) { - for (var property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - _extend(destination[property], source[property]); - } - else { - destination[property] = source[property]; + function _extend() { + // Get arguments + var objects = arguments; + + // Bail if nothing to merge + if(!objects.length) { + return; + } + + // Return first if specified but nothing to merge + if(objects.lenth == 1) { + return objects[0]; + } + + // First object is the destination + var destination = Array.prototype.shift.call(objects), + length = objects.length; + + // Loop through all objects to merge + for (var i = 0; i < length; i++) { + var source = objects[i]; + + for (var property in source) { + if (source[property] && source[property].constructor && source[property].constructor === Object) { + destination[property] = destination[property] || {}; + _extend(destination[property], source[property]); + } + else { + destination[property] = source[property]; + } } } + return destination; } @@ -732,13 +641,409 @@ } // Player instance - function Plyr(container) { + function Plyr(container, config) { var plyr = this; plyr.container = container; + plyr.timers = {}; + + // Log config options + _log(config); + + // Debugging + function _log(text, warn) { + if (config.debug && window.console) { + console[(warn ? 'warn' : 'log')](text); + } + } + + // Build the default HTML + function _buildControls() { + // Create html array + var html = []; + + // Larger overlaid play button + if (_inArray(config.controls, 'play-large')) { + html.push( + '' + ); + } + + html.push('
'); + + // Restart button + if (_inArray(config.controls, 'restart')) { + html.push( + '' + ); + } + + // Rewind button + if (_inArray(config.controls, 'rewind')) { + html.push( + '' + ); + } + + // Play Pause button + // TODO: This should be a toggle button really? + if (_inArray(config.controls, 'play')) { + html.push( + '', + '' + ); + } + + // Fast forward button + if (_inArray(config.controls, 'fast-forward')) { + html.push( + '' + ); + } + + // Progress + if (_inArray(config.controls, 'progress')) { + // Create progress + html.push('', + '', + '', + '', + '0% ' + config.i18n.played, + '', + '', + '0% ' + config.i18n.buffered, + ''); + + // Seek tooltip + if (config.tooltips.seek) { + html.push('00:00'); + } + + // Close + html.push(''); + } + + // Media current time display + if (_inArray(config.controls, 'current-time')) { + html.push( + '', + '' + config.i18n.currentTime + '', + '00:00', + '' + ); + } + + // Media duration display + if (_inArray(config.controls, 'duration')) { + html.push( + '', + '' + config.i18n.duration + '', + '00:00', + '' + ); + } + + // Toggle mute button + if (_inArray(config.controls, 'mute')) { + html.push( + '' + ); + } + + // Volume range control + if (_inArray(config.controls, 'volume')) { + html.push( + '', + '' + ); + } + + // Toggle captions button + if (_inArray(config.controls, 'captions')) { + html.push( + '' + ); + } + + // Toggle fullscreen button + if (_inArray(config.controls, 'fullscreen')) { + html.push( + '' + ); + } + + // Close everything + html.push('
'); + + return html.join(''); + } + + // Setup fullscreen + function _setupFullscreen() { + if (!plyr.supported.full) { + return; + } + + if ((plyr.type != 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) { + // Check for native support + var nativeSupport = fullscreen.supportsFullScreen; + + if (nativeSupport || (config.fullscreen.fallback && !_inFrame())) { + _log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled'); + + // Add styling hook + _toggleClass(plyr.container, config.classes.fullscreen.enabled, true); + } + else { + _log('Fullscreen not supported and fallback disabled'); + } + + // Toggle state + _toggleState(plyr.buttons.fullscreen, false); + + // Setup focus trap + _focusTrap(); + } + } + + // Setup captions + function _setupCaptions() { + if (plyr.type !== 'video') { + return; + } + + // Inject the container + if (!_getElement(config.selectors.captions)) { + plyr.videoContainer.insertAdjacentHTML('afterbegin', '
'); + } + + // Determine if HTML5 textTracks is supported + plyr.usingTextTracks = false; + if (plyr.media.textTracks) { + plyr.usingTextTracks = true; + } + + // Get URL of caption file if exists + var captionSrc = '', + kind, + children = plyr.media.childNodes; + + for (var i = 0; i < children.length; i++) { + if (children[i].nodeName.toLowerCase() === 'track') { + kind = children[i].kind; + if (kind === 'captions' || kind === 'subtitles') { + captionSrc = children[i].getAttribute('src'); + } + } + } + + // Record if caption file exists or not + plyr.captionExists = true; + if (captionSrc === '') { + plyr.captionExists = false; + _log('No caption track found'); + } + else { + _log('Caption track found; URI: ' + captionSrc); + } + + // If no caption file exists, hide container for caption text + if (!plyr.captionExists) { + _toggleClass(plyr.container, config.classes.captions.enabled); + } + // If caption file exists, process captions + else { + // Turn off native caption rendering to avoid double captions + // This doesn't seem to work in Safari 7+, so the elements are removed from the dom below + var tracks = plyr.media.textTracks; + for (var x = 0; x < tracks.length; x++) { + tracks[x].mode = 'hidden'; + } + + // Enable UI + _showCaptions(plyr); + + // Disable unsupported browsers than report false positive + // Firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1033144 + if ((plyr.browser.name === 'IE' && plyr.browser.version >= 10) || + (plyr.browser.name === 'Firefox' && plyr.browser.version >= 31)) { + + // Debugging + _log('Detected browser with known TextTrack issues - using manual fallback'); + + // Set to false so skips to 'manual' captioning + plyr.usingTextTracks = false; + } + + // Rendering caption tracks + // Native support required - http://caniuse.com/webvtt + if (plyr.usingTextTracks) { + _log('TextTracks supported'); + + for (var y = 0; y < tracks.length; y++) { + var track = tracks[y]; + + if (track.kind === 'captions' || track.kind === 'subtitles') { + _on(track, 'cuechange', function() { + // Display a cue, if there is one + if (this.activeCues[0] && 'text' in this.activeCues[0]) { + _setCaption(this.activeCues[0].getCueAsHTML()); + } + else { + _setCaption(); + } + }); + } + } + } + // Caption tracks not natively supported + else { + _log('TextTracks not supported so rendering captions manually'); + + // Render captions from array at appropriate time + plyr.currentCaption = ''; + plyr.captions = []; + + if (captionSrc !== '') { + // Create XMLHttpRequest Object + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + var captions = [], + caption, + req = xhr.responseText; + + captions = req.split('\n\n'); + + for (var r = 0; r < captions.length; r++) { + caption = captions[r]; + plyr.captions[r] = []; + + // Get the parts of the captions + var parts = caption.split('\n'), + index = 0; + + // Incase caption numbers are added + if(parts[index].indexOf(":") === -1) { + index = 1; + } + + plyr.captions[r] = [parts[index], parts[index + 1]]; + } + + // Remove first element ('VTT') + plyr.captions.shift(); + + _log('Successfully loaded the caption file via AJAX'); + } + else { + _log('There was a problem loading the caption file via AJAX', true); + } + } + }; + + xhr.open('get', captionSrc, true); + + xhr.send(); + } + } + } + } + + // Set the current caption + function _setCaption(caption) { + var container = _getElement(config.selectors.captions), + content = document.createElement('span'); + + // Empty the container + container.innerHTML = ''; + + // Default to empty + if(typeof caption === 'undefined') { + caption = ''; + } + + // Set the span content + if(typeof caption === 'string') { + content.innerHTML = caption.trim(); + } + else { + content.appendChild(caption); + } + + // Set new caption text + container.appendChild(content); + + // Force redraw + var redraw = container.offsetHeight; + } // Captions functions // Seek the manual caption time and update UI function _seekManualCaptions(time) { + // Utilities for caption time codes + function _timecodeCommon(tc, pos) { + var tcpair = []; + tcpair = tc.split(' --> '); + for(var i = 0; i < tcpair.length; i++) { + // WebVTT allows for extra meta data after the timestamp line + // So get rid of this if it exists + tcpair[i] = tcpair[i].replace(/(\d+:\d+:\d+\.\d+).*/, "$1"); + } + return _subTcSecs(tcpair[pos]); + } + function _timecodeMin(tc) { + return _timecodeCommon(tc, 0); + } + function _timecodeMax(tc) { + return _timecodeCommon(tc, 1); + } + function _subTcSecs(tc) { + if (tc === null || tc === undefined) { + return 0; + } + else { + var tc1 = [], + tc2 = [], + seconds; + tc1 = tc.split(','); + tc2 = tc1[0].split(':'); + seconds = Math.floor(tc2[0]*60*60) + Math.floor(tc2[1]*60) + Math.floor(tc2[2]); + return seconds; + } + } + // If it's not video, or we're using textTracks, bail. if (plyr.usingTextTracks || plyr.type !== 'video' || !plyr.supported.full) { return; @@ -770,21 +1075,11 @@ plyr.media.currentTime.toFixed(1) <= _timecodeMax(plyr.captions[plyr.subcount][0])) { plyr.currentCaption = plyr.captions[plyr.subcount][1]; - // Trim caption text - var content = plyr.currentCaption.trim(); - - // Render the caption (only if changed) - if (plyr.captionsContainer.innerHTML != content) { - // Empty caption - // Otherwise NVDA reads it twice - plyr.captionsContainer.innerHTML = ''; - - // Set new caption text - plyr.captionsContainer.innerHTML = content; - } + // Render the caption + _setCaption(plyr.currentCaption); } else { - plyr.captionsContainer.innerHTML = ''; + _setCaption(); } } @@ -803,32 +1098,6 @@ } } - // Utilities for caption time codes - function _timecodeMin(tc) { - var tcpair = []; - tcpair = tc.split(' --> '); - return _subTcSecs(tcpair[0]); - } - function _timecodeMax(tc) { - var tcpair = []; - tcpair = tc.split(' --> '); - return _subTcSecs(tcpair[1]); - } - function _subTcSecs(tc) { - if (tc === null || tc === undefined) { - return 0; - } - else { - var tc1 = [], - tc2 = [], - seconds; - tc1 = tc.split(','); - tc2 = tc1[0].split(':'); - seconds = Math.floor(tc2[0]*60*60) + Math.floor(tc2[1]*60) + Math.floor(tc2[2]); - return seconds; - } - } - // Find all elements function _getElements(selector) { return plyr.container.querySelectorAll(selector); @@ -927,8 +1196,8 @@ container.insertAdjacentHTML('beforeend', html); // Setup tooltips - if (config.tooltips) { - var labels = _getElements(config.selectors.labels); + if (config.tooltips.controls) { + var labels = _getElements([config.selectors.controls.wrapper, ' ', config.selectors.labels, ' .', config.classes.hidden].join('')); for (var i = labels.length - 1; i >= 0; i--) { var label = labels[i]; @@ -947,7 +1216,7 @@ // Buttons plyr.buttons = {}; plyr.buttons.seek = _getElement(config.selectors.buttons.seek); - plyr.buttons.play = _getElement(config.selectors.buttons.play); + plyr.buttons.play = _getElements(config.selectors.buttons.play); plyr.buttons.pause = _getElement(config.selectors.buttons.pause); plyr.buttons.restart = _getElement(config.selectors.buttons.restart); plyr.buttons.rewind = _getElement(config.selectors.buttons.rewind); @@ -974,6 +1243,9 @@ plyr.progress.played.bar = _getElement(config.selectors.progress.played); plyr.progress.played.text = plyr.progress.played.bar && plyr.progress.played.bar.getElementsByTagName('span')[0]; + // Seek tooltip + plyr.progress.tooltip = plyr.progress.container && plyr.progress.container.querySelector('.' + config.classes.tooltip); + // Volume plyr.volume = _getElement(config.selectors.buttons.volume); @@ -988,7 +1260,7 @@ _log('It looks like there is a problem with your controls html', true); // Restore native video controls - _toggleControls(true); + _toggleNativeControls(true); return false; } @@ -996,11 +1268,11 @@ // Toggle style hook function _toggleStyleHook() { - _toggleClass(plyr.container, defaults.selectors.container.replace('.', ''), plyr.supported.full); + _toggleClass(plyr.container, config.selectors.container.replace('.', ''), plyr.supported.full); } // Toggle native controls - function _toggleControls(toggle) { + function _toggleNativeControls(toggle) { if(toggle) { plyr.media.setAttribute('controls', ''); } @@ -1021,7 +1293,9 @@ // If there's a play button, set label if (plyr.supported.full && plyr.buttons.play) { - plyr.buttons.play.setAttribute('aria-label', label); + for (var i = plyr.buttons.play.length - 1; i >= 0; i--) { + plyr.buttons.play[i].setAttribute('aria-label', label); + } } // Set iframe title @@ -1073,12 +1347,6 @@ // Clean up plyr.embedId = null; } - else { - // Autoplay - if (config.autoplay) { - _play(); - } - } } // Setup YouTube/Vimeo @@ -1169,6 +1437,9 @@ // When embeds are ready function _embedReady() { + // Store reference to API + plyr.container.plyr.embed = plyr.embed; + // Setup the UI _setupInterface(); @@ -1189,7 +1460,7 @@ plyr.embed = new YT.Player(container.id, { videoId: videoId, playerVars: { - autoplay: 0, + autoplay: (config.autoplay ? 1 : 0), controls: (plyr.supported.full ? 0 : 1), rel: 0, showinfo: 0, @@ -1224,6 +1495,9 @@ plyr.media.currentTime = instance.getCurrentTime(); plyr.media.muted = instance.isMuted(); + // Set title + config.title = instance.getVideoData().title; + // Trigger timeupdate _triggerEvent(plyr.media, 'timeupdate'); @@ -1241,6 +1515,9 @@ // Bail if we're at 100% if (plyr.media.buffered === 1) { window.clearInterval(plyr.timer.buffering); + + // Trigger event + _triggerEvent(plyr.media, 'canplaythrough'); } }, 200); @@ -1274,287 +1551,110 @@ plyr.media.paused = false; plyr.media.seeking = false; _triggerEvent(plyr.media, 'play'); + _triggerEvent(plyr.media, 'playing'); // Poll to get playback progress plyr.timer.playing = window.setInterval(function() { // Set the current time plyr.media.currentTime = instance.getCurrentTime(); - // Trigger timeupdate - _triggerEvent(plyr.media, 'timeupdate'); - }, 100); - - break; - - case 2: - plyr.media.paused = true; - _triggerEvent(plyr.media, 'pause'); - } - } - } - }); - } - - // Vimeo ready - function _vimeoReady() { - /* jshint validthis: true */ - plyr.embed = $f(this); - - // Setup on ready - plyr.embed.addEvent('ready', function() { - - // Create a faux HTML5 API using the Vimeo API - plyr.media.play = function() { - plyr.embed.api('play'); - plyr.media.paused = false; - }; - plyr.media.pause = function() { - plyr.embed.api('pause'); - plyr.media.paused = true; - }; - plyr.media.stop = function() { - plyr.embed.api('stop'); - plyr.media.paused = true; - }; - plyr.media.paused = true; - plyr.media.currentTime = 0; - - // Update UI - _embedReady(); - - plyr.embed.api('getCurrentTime', function (value) { - plyr.media.currentTime = value; - - // Trigger timeupdate - _triggerEvent(plyr.media, 'timeupdate'); - }); - - plyr.embed.api('getDuration', function(value) { - plyr.media.duration = value; - - // Display duration if available - _displayDuration(); - }); - - plyr.embed.addEvent('play', function() { - plyr.media.paused = false; - _triggerEvent(plyr.media, 'play'); - }); - - plyr.embed.addEvent('pause', function() { - plyr.media.paused = true; - _triggerEvent(plyr.media, 'pause'); - }); - - plyr.embed.addEvent('playProgress', function(data) { - plyr.media.seeking = false; - plyr.media.currentTime = data.seconds; - _triggerEvent(plyr.media, 'timeupdate'); - }); - - plyr.embed.addEvent('loadProgress', function(data) { - plyr.media.buffered = data.percent; - _triggerEvent(plyr.media, 'progress'); - }); - - plyr.embed.addEvent('finish', function() { - plyr.media.paused = true; - _triggerEvent(plyr.media, 'ended'); - }); - - // Always seek to 0 - //plyr.embed.api('seekTo', 0); - - // Prevent autoplay if needed (seek will play) - //if (!config.autoplay) { - // plyr.embed.api('pause'); - //} - }); - } - - // Setup captions - function _setupCaptions() { - if (plyr.type !== 'video') { - return; - } - - // Inject the container - if (!_getElement(config.selectors.captions)) { - plyr.videoContainer.insertAdjacentHTML('afterbegin', '
'); - } - - // Cache selector - plyr.captionsContainer = _getElement(config.selectors.captions).querySelector('span'); - - // Determine if HTML5 textTracks is supported - plyr.usingTextTracks = false; - if (plyr.media.textTracks) { - plyr.usingTextTracks = true; - } - - // Get URL of caption file if exists - var captionSrc = '', - kind, - children = plyr.media.childNodes; - - for (var i = 0; i < children.length; i++) { - if (children[i].nodeName.toLowerCase() === 'track') { - kind = children[i].kind; - if (kind === 'captions' || kind === 'subtitles') { - captionSrc = children[i].getAttribute('src'); - } - } - } - - // Record if caption file exists or not - plyr.captionExists = true; - if (captionSrc === '') { - plyr.captionExists = false; - _log('No caption track found'); - } - else { - _log('Caption track found; URI: ' + captionSrc); - } - - // If no caption file exists, hide container for caption text - if (!plyr.captionExists) { - _toggleClass(plyr.container, config.classes.captions.enabled); - } - // If caption file exists, process captions - else { - // Turn off native caption rendering to avoid double captions - // This doesn't seem to work in Safari 7+, so the elements are removed from the dom below - var tracks = plyr.media.textTracks; - for (var x = 0; x < tracks.length; x++) { - tracks[x].mode = 'hidden'; - } - - // Enable UI - _showCaptions(plyr); - - // Disable unsupported browsers than report false positive - if ((plyr.browser.name === 'IE' && plyr.browser.version >= 10) || - (plyr.browser.name === 'Firefox' && plyr.browser.version >= 31) || - (plyr.browser.name === 'Chrome' && plyr.browser.version >= 43) || - (plyr.browser.name === 'Safari' && plyr.browser.version >= 7)) { - // Debugging - _log('Detected unsupported browser for HTML5 captions - using fallback'); - - // Set to false so skips to 'manual' captioning - plyr.usingTextTracks = false; - } - - // Rendering caption tracks - // Native support required - http://caniuse.com/webvtt - if (plyr.usingTextTracks) { - _log('TextTracks supported'); - - for (var y = 0; y < tracks.length; y++) { - var track = tracks[y]; - - if (track.kind === 'captions' || track.kind === 'subtitles') { - _on(track, 'cuechange', function() { - // Clear container - plyr.captionsContainer.innerHTML = ''; - - // Display a cue, if there is one - if (this.activeCues[0] && this.activeCues[0].hasOwnProperty('text')) { - plyr.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML().trim()); - } - }); - } - } - } - // Caption tracks not natively supported - else { - _log('TextTracks not supported so rendering captions manually'); - - // Render captions from array at appropriate time - plyr.currentCaption = ''; - plyr.captions = []; + // Trigger timeupdate + _triggerEvent(plyr.media, 'timeupdate'); + }, 100); - if (captionSrc !== '') { - // Create XMLHttpRequest Object - var xhr = new XMLHttpRequest(); + break; - xhr.onreadystatechange = function() { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - var records = [], - record, - req = xhr.responseText; + case 2: + plyr.media.paused = true; + _triggerEvent(plyr.media, 'pause'); + break; + } + } + } + }); + } - records = req.split('\n\n'); + // Vimeo ready + function _vimeoReady() { + /* jshint validthis: true */ + plyr.embed = $f(this); - for (var r = 0; r < records.length; r++) { - record = records[r]; - plyr.captions[r] = []; - plyr.captions[r] = record.split('\n'); - } + // Setup on ready + plyr.embed.addEvent('ready', function() { - // Remove first element ('VTT') - plyr.captions.shift(); + // Create a faux HTML5 API using the Vimeo API + plyr.media.play = function() { + plyr.embed.api('play'); + plyr.media.paused = false; + }; + plyr.media.pause = function() { + plyr.embed.api('pause'); + plyr.media.paused = true; + }; + plyr.media.stop = function() { + plyr.embed.api('stop'); + plyr.media.paused = true; + }; + plyr.media.paused = true; + plyr.media.currentTime = 0; - _log('Successfully loaded the caption file via AJAX'); - } - else { - _log('There was a problem loading the caption file via AJAX', true); - } - } - }; + // Update UI + _embedReady(); - xhr.open('get', captionSrc, true); + plyr.embed.api('getCurrentTime', function (value) { + plyr.media.currentTime = value; - xhr.send(); - } - } + // Trigger timeupdate + _triggerEvent(plyr.media, 'timeupdate'); + }); - // If Safari 7+, removing track from DOM [see 'turn off native caption rendering' above] - if (plyr.browser.name === 'Safari' && plyr.browser.version >= 7) { - _log('Safari 7+ detected; removing track from DOM'); + plyr.embed.api('getDuration', function(value) { + plyr.media.duration = value; - // Find all elements - tracks = plyr.media.getElementsByTagName('track'); + // Display duration if available + _displayDuration(); + }); - // Loop through and remove one by one - for (var t = 0; t < tracks.length; t++) { - plyr.media.removeChild(tracks[t]); - } - } - } - } + plyr.embed.addEvent('play', function() { + plyr.media.paused = false; + _triggerEvent(plyr.media, 'play'); + _triggerEvent(plyr.media, 'playing'); + }); - // Setup fullscreen - function _setupFullscreen() { - if (!plyr.supported.full) { - return; - } + plyr.embed.addEvent('pause', function() { + plyr.media.paused = true; + _triggerEvent(plyr.media, 'pause'); + }); - if ((plyr.type != 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) { - // Check for native support - var nativeSupport = fullscreen.supportsFullScreen; + plyr.embed.addEvent('playProgress', function(data) { + plyr.media.seeking = false; + plyr.media.currentTime = data.seconds; + _triggerEvent(plyr.media, 'timeupdate'); + }); - if (nativeSupport || (config.fullscreen.fallback && !_inFrame())) { - _log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled'); + plyr.embed.addEvent('loadProgress', function(data) { + plyr.media.buffered = data.percent; + _triggerEvent(plyr.media, 'progress'); - // Add styling hook - _toggleClass(plyr.container, config.classes.fullscreen.enabled, true); - } - else { - _log('Fullscreen not supported and fallback disabled'); - } + if(parseInt(data.percent) === 1) { + // Trigger event + _triggerEvent(plyr.media, 'canplaythrough'); + } + }); - // Toggle state - _toggleState(plyr.buttons.fullscreen, false); + plyr.embed.addEvent('finish', function() { + plyr.media.paused = true; + _triggerEvent(plyr.media, 'ended'); + }); - // Setup focus trap - _focusTrap(); + // Always seek to 0 + // plyr.embed.api('seekTo', 0); - // Set control hide class hook - if (config.fullscreen.hideControls) { - _toggleClass(plyr.container, config.classes.fullscreen.hideControls, true); + // Autoplay + if (config.autoplay) { + plyr.embed.api('play'); } - } + }); } // Play media @@ -1609,7 +1709,8 @@ // The input parameter can be an event or a number function _seek(input) { var targetTime = 0, - paused = plyr.media.paused; + paused = plyr.media.paused, + duration = _getDuration(); // Explicit position if (typeof input === 'number') { @@ -1619,15 +1720,15 @@ else if (typeof input === 'object' && (input.type === 'input' || input.type === 'change')) { // It's the seek slider // Seek to the selected time - targetTime = ((input.target.value / input.target.max) * plyr.media.duration); + targetTime = ((input.target.value / input.target.max) * duration); } // Normalise targetTime if (targetTime < 0) { targetTime = 0; } - else if (targetTime > plyr.media.duration) { - targetTime = plyr.media.duration; + else if (targetTime > duration) { + targetTime = duration; } // Set the current time @@ -1646,7 +1747,8 @@ break; case 'vimeo': - plyr.embed.api('seekTo', targetTime); + // Round to nearest second for vimeo + plyr.embed.api('seekTo', targetTime.toFixed(0)); break; } @@ -1668,10 +1770,21 @@ _seekManualCaptions(targetTime); } + // Get the duration (or custom if set) + function _getDuration() { + // It should be a number, but parse it just incase + var duration = parseInt(config.duration); + + // If custom duration is funky, use regular duration + return (isNaN(duration) ? plyr.media.duration : duration); + } + // Check playing state function _checkPlaying() { _toggleClass(plyr.container, config.classes.playing, !plyr.media.paused); _toggleClass(plyr.container, config.classes.stopped, plyr.media.paused); + + _toggleControls(plyr.media.paused); } // Toggle fullscreen @@ -1729,40 +1842,11 @@ // Set button state _toggleState(plyr.buttons.fullscreen, plyr.isFullscreen); - // Toggle controls visibility based on mouse movement and location - var hoverTimer, isMouseOver = false; - - // Show the player controls - function _showControls() { - // Set shown class - _toggleClass(plyr.container, config.classes.hover, true); + // Hide on entering full screen + _toggleControls(false); - // Clear timer every movement - window.clearTimeout(hoverTimer); - - // If the mouse is not over the controls, set a timeout to hide them - if (!isMouseOver) { - hoverTimer = window.setTimeout(function() { - _toggleClass(plyr.container, config.classes.hover, false); - }, 2000); - } - } - - // Check mouse is over the controls - function _setMouseOver (event) { - isMouseOver = (event.type === 'mouseenter'); - } - - if (config.fullscreen.hideControls) { - // Hide on entering full screen - _toggleClass(plyr.controls, config.classes.hover, false); - - // Keep an eye on the mouse location in relation to controls - _toggleHandler(plyr.controls, 'mouseenter mouseleave', _setMouseOver, plyr.isFullscreen); - - // Show the controls on mouse move - _toggleHandler(plyr.container, 'mousemove', _showControls, plyr.isFullscreen); - } + // Trigger an event + _triggerEvent(plyr.container, plyr.isFullscreen ? 'enterfullscreen' : 'exitfullscreen'); } // Bail from faux-fullscreen @@ -1808,14 +1892,22 @@ function _setVolume(volume) { // Use default if no value specified if (typeof volume === 'undefined') { + volume = config.volume; + if (config.storage.enabled && _storage().supported) { - volume = window.localStorage[config.storage.key] || config.volume; - } - else { - volume = config.volume; + volume = window.localStorage.getItem(config.storage.key); + + // Clean up old volume + // https://github.com/Selz/plyr/issues/171 + window.localStorage.removeItem('plyr-volume'); } } + // Use config if all else fails + if(volume === null || isNaN(volume)) { + volume = config.volume; + } + // Maximum is 10 if (volume > 10) { volume = 10; @@ -1865,7 +1957,7 @@ } // Store the volume in storage - if (config.storage.enabled && _storage().supported) { + if (config.storage.enabled && _storage().supported && !isNaN(volume)) { window.localStorage.setItem(config.storage.key, volume); } @@ -1898,6 +1990,9 @@ // Add class hook _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled); + + // Trigger an event + _triggerEvent(plyr.container, plyr.captionsEnabled ? 'captionsenabled' : 'captionsdisabled'); } // Check if media is loading @@ -1905,10 +2000,10 @@ var loading = (event.type === 'waiting'); // Clear timer - clearTimeout(plyr.loadingTimer); + clearTimeout(plyr.timers.loading); // Timer to prevent flicker when seeking - plyr.loadingTimer = setTimeout(function() { + plyr.timers.loading = setTimeout(function() { _toggleClass(plyr.container, config.classes.loading, loading); }, (loading ? 250 : 0)); } @@ -1917,14 +2012,15 @@ function _updateProgress(event) { var progress = plyr.progress.played.bar, text = plyr.progress.played.text, - value = 0; + value = 0, + duration = _getDuration(); if (event) { switch (event.type) { // Video playing case 'timeupdate': case 'seeking': - value = _getPercentage(plyr.media.currentTime, plyr.media.duration); + value = _getPercentage(plyr.media.currentTime, duration); // Set seek range value only if it's a 'natural' time event if (event.type == 'timeupdate' && plyr.buttons.seek) { @@ -1950,7 +2046,7 @@ // HTML5 if (buffered && buffered.length) { - return _getPercentage(buffered.end(0), plyr.media.duration); + return _getPercentage(buffered.end(0), duration); } // YouTube returns between 0 and 1 else if (typeof buffered === 'number') { @@ -1988,7 +2084,7 @@ plyr.hours = parseInt(((time / 60) / 60) % 60); // Do we need to display hours? - var displayHours = (parseInt(((plyr.media.duration / 60) / 60) % 60) > 0); + var displayHours = (parseInt(((_getDuration() / 60) / 60) % 60) > 0); // Ensure it's two digits. For example, 03 rather than 3. plyr.secs = ('0' + plyr.secs).slice(-2); @@ -2004,7 +2100,8 @@ return; } - var duration = plyr.media.duration || 0; + // Determine duration + var duration = _getDuration() || 0; // If there's only one time display, display duration there if (!plyr.duration && config.displayDuration && plyr.media.paused) { @@ -2015,6 +2112,9 @@ if (plyr.duration) { _updateTimeDisplay(duration, plyr.duration); } + + // Update the tooltip (if visible) + _updateSeekTooltip(); } // Handle time change event @@ -2031,6 +2131,93 @@ _updateProgress(event); } + // Update hover tooltip for seeking + function _updateSeekTooltip(event) { + // Bail if setting not true + if (!config.tooltips.seek || plyr.browser.touch) { + return; + } + + // Calculate percentage + var clientRect = plyr.progress.container.getBoundingClientRect(), + percent = 0, + visible = config.classes.tooltip + '--visible'; + + // Determine percentage, if already visible + if (!event) { + if(_hasClass(plyr.progress.tooltip, visible)) { + percent = plyr.progress.tooltip.style.left.replace('%', ''); + } + else { + return; + } + } + else { + percent = ((100 / clientRect.width) * (event.pageX - clientRect.left)); + } + + // Set bounds + if (percent < 0) { + percent = 0; + } + else if (percent > 100) { + percent = 100; + } + + // Display the time a click would seek to + _updateTimeDisplay(((_getDuration() / 100) * percent), plyr.progress.tooltip); + + // Set position + plyr.progress.tooltip.style.left = percent + "%"; + + // Show/hide the tooltip + // If the event is a moues in/out and percentage is inside bounds + if(event && _inArray(['mouseenter', 'mouseleave'], event.type)) { + _toggleClass(plyr.progress.tooltip, visible, (event.type === 'mouseenter')); + } + } + + // Show the player controls in fullscreen mode + function _toggleControls(toggle) { + if (!config.hideControls) { + return; + } + var isMouseMove = false; + + // Default to false if no boolean + if(typeof toggle !== "boolean") { + if(toggle && toggle.type) { + isMouseMove = toggle.type === 'mousemove'; + + toggle = _inArray(['mousemove','mouseenter'], toggle.type); + } + else { + toggle = false; + } + } + + // Clear timer every movement + window.clearTimeout(plyr.timers.hover); + + // If the mouse is not over the controls, set a timeout to hide them + if(toggle) { + _toggleClass(plyr.container, config.classes.hideControls, false); + } + + // If toggle is false or if we're playing (regardless of toggle), then + // set the timer to hide the controls + if(toggle === false || !plyr.media.paused) { + plyr.timers.hover = window.setTimeout(function() { + // If the mouse is over the controls, bail + if(plyr.controls.active) { + return; + } + + _toggleClass(plyr.container, config.classes.hideControls, true); + }, isMouseMove ? 2000 : 500); + } + } + // Add common function to retrieve media source function _source(source) { // If not null or undefined, parse it @@ -2085,6 +2272,12 @@ _remove(plyr.videoContainer); } + // Remove embed object + plyr.embed = null; + + // Cancel current network requests + _cancelRequests(); + // Remove the old media _remove(plyr.media); @@ -2125,6 +2318,11 @@ // Inject the new element _prependChild(plyr.container, plyr.media); + // Autoplay the new source? + if (typeof source.autoplay !== 'undefined') { + config.autoplay = source.autoplay; + } + // Set attributes for audio video if (_inArray(config.types.html5, plyr.type)) { if (config.crossorigin) { @@ -2149,9 +2347,6 @@ _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled); _toggleStyleHook(); - // Autoplay the new source? - config.autoplay = (source.autoplay || config.autoplay); - // Set new sources for html5 if (_inArray(config.types.html5, plyr.type)) { _insertChildElements('source', source.sources); @@ -2170,23 +2365,19 @@ // Load HTML5 sources plyr.media.load(); - // Display duration if available - _displayDuration(); - // Setup interface _setupInterface(); - } - // Play if autoplay attribute is present - if (config.autoplay) { - _play(); + // Display duration if available + _displayDuration(); } // Set aria title and iframe title - if ('title' in source) { - config.title = source.title; - _setTitle(); - } + config.title = source.title; + _setTitle(); + + // Reset media objects + plyr.container.plyr.media = plyr.media; } // Update poster @@ -2196,13 +2387,15 @@ } } - // Listen for events - function _listeners() { + // Listen for control events + function _controlListeners() { // IE doesn't support input event, so we fallback to change var inputEvent = (plyr.browser.name == 'IE' ? 'change' : 'input'); // Click play/pause helper - function _togglePlay(play) { + function _togglePlay() { + var play = plyr.media.paused; + // Toggle playback if (play) { _play(); @@ -2212,8 +2405,8 @@ } // Determine which buttons - var trigger = plyr.buttons[play ? "play" : "pause"], - target = plyr.buttons[play ? "pause" : "play"]; + var trigger = plyr.buttons[play ? 'play' : 'pause'], + target = plyr.buttons[play ? 'pause' : 'play']; // Setup focus and tab focus if(target) { @@ -2264,39 +2457,59 @@ } // Play - _proxyHandler(plyr.buttons.play, 'click', config.handlers.play, function() { _togglePlay(true); }); + _proxyListener(plyr.buttons.play, 'click', config.listeners.play, _togglePlay); // Pause - _proxyHandler(plyr.buttons.pause, 'click', config.handlers.pause, function() { _togglePlay(); }); + _proxyListener(plyr.buttons.pause, 'click', config.listeners.pause, _togglePlay); // Restart - _proxyHandler(plyr.buttons.restart, 'click', config.handlers.restart, _seek); + _proxyListener(plyr.buttons.restart, 'click', config.listeners.restart, _seek); // Rewind - _proxyHandler(plyr.buttons.rewind, 'click', config.handlers.rewind, _rewind); + _proxyListener(plyr.buttons.rewind, 'click', config.listeners.rewind, _rewind); // Fast forward - _proxyHandler(plyr.buttons.forward, 'click', config.handlers.forward, _forward); + _proxyListener(plyr.buttons.forward, 'click', config.listeners.forward, _forward); // Seek - _proxyHandler(plyr.buttons.seek, inputEvent, config.handlers.seek, _seek); + _proxyListener(plyr.buttons.seek, inputEvent, config.listeners.seek, _seek); // Set volume - _proxyHandler(plyr.volume, inputEvent, config.handlers.volume, function() { + _proxyListener(plyr.volume, inputEvent, config.listeners.volume, function() { _setVolume(plyr.volume.value); }); // Mute - _proxyHandler(plyr.buttons.mute, 'click', config.handlers.mute, _toggleMute); + _proxyListener(plyr.buttons.mute, 'click', config.listeners.mute, _toggleMute); // Fullscreen - _proxyHandler(plyr.buttons.fullscreen, 'click', config.handlers.fullscreen, _toggleFullscreen); + _proxyListener(plyr.buttons.fullscreen, 'click', config.listeners.fullscreen, _toggleFullscreen); // Handle user exiting fullscreen by escaping etc if (fullscreen.supportsFullScreen) { _on(document, fullscreen.fullScreenEventName, _toggleFullscreen); } + // Captions + _on(plyr.buttons.captions, 'click', _toggleCaptions); + + // Seek tooltip + _on(plyr.progress.container, 'mouseenter mouseleave mousemove', _updateSeekTooltip); + + // Toggle controls visibility based on mouse movement + if (config.hideControls) { + _on(plyr.container, 'mouseenter mouseleave', _toggleControls); + _on(plyr.container, 'mousemove', _debounce(_toggleControls, 500, true)); + + // Watch for cursor over controls so they don't hide when trying to interact + _on(plyr.controls, 'mouseenter mouseleave', function(event) { + plyr.controls.active = (event.type === 'mouseenter'); + }); + } + } + + // Listen for media events + function _mediaListeners() { // Time change on media _on(plyr.media, 'timeupdate seeking', _timeUpdate); @@ -2304,16 +2517,13 @@ _on(plyr.media, 'timeupdate', _seekManualCaptions); // Display duration - _on(plyr.media, 'loadedmetadata', _displayDuration); - - // Captions - _on(plyr.buttons.captions, 'click', _toggleCaptions); + _on(plyr.media, 'durationchange loadedmetadata', _displayDuration); // Handle the media finishing _on(plyr.media, 'ended', function() { // Clear if (plyr.type === 'video') { - plyr.captionsContainer.innerHTML = ''; + _setCaption(); } // Reset UI @@ -2333,8 +2543,11 @@ _on(plyr.media, 'waiting canplay seeked', _checkLoading); // Click video - if (plyr.type === 'video' && config.click) { - _on(plyr.videoContainer, 'click', function() { + if (config.clickToPlay) { + // Set cursor + plyr.videoContainer.style.cursor = "pointer"; + + _on(plyr.media, 'click', function() { if (plyr.media.paused) { _play(); } @@ -2347,6 +2560,36 @@ } }); } + + // Proxy events to container + _on(plyr.media, config.events.join(' '), function(event) { + _triggerEvent(plyr.container, event.type); + }); + } + + // Cancel current network requests + // See https://github.com/Selz/plyr/issues/174 + function _cancelRequests() { + if(!_inArray(config.types.html5, plyr.type)) { + return; + } + + // Set empty src attribute + plyr.media.setAttribute('src', ''); + + // Remove child sources + var sources = plyr.media.querySelectorAll('source'); + for (var i = 0; i < sources.length; i++) { + _remove(sources[i]); + } + + // Load the new empty source + // This will cancel existing requests + // See https://github.com/Selz/plyr/issues/174 + plyr.media.load(); + + // Debugging + _log("Cancelled network requests for old media"); } // Destroy an instance @@ -2375,7 +2618,7 @@ // If video, we need to remove some more if (plyr.type === 'video') { - // Remove captions + // Remove captions container _remove(_getElement(config.selectors.captions)); // Remove video wrapper @@ -2383,7 +2626,7 @@ } // Restore native video controls - _toggleControls(true); + _toggleNativeControls(true); // Clone the media element to remove listeners // http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type @@ -2461,6 +2704,11 @@ // Set title on button and frame _setTitle(); + + // Autoplay + if (config.autoplay) { + _play(); + } } // Successful setup @@ -2470,38 +2718,44 @@ function _setupInterface() { // Don't setup interface if no support if (!plyr.supported.full) { - _log("No full support for this media type (" + plyr.type + ")", true); + _log('No full support for this media type (' + plyr.type + ')', true); // Remove controls _remove(_getElement(config.selectors.controls.wrapper)); // Restore native controls - _toggleControls(true); + _toggleNativeControls(true); // Bail return; } - // Inject custom controls - if (!_getElements(config.selectors.controls.wrapper).length) { + // Inject custom controls if not present + var controlsMissing = !_getElements(config.selectors.controls.wrapper).length; + if (controlsMissing) { // Inject custom controls _injectControls(); } - // Remove native controls - _toggleControls(); - // Find the elements if (!_findElements()) { return; } + // If the controls are injected, re-bind listeners for controls + if (controlsMissing) { + _controlListeners(); + } + + // Media element listeners + _mediaListeners(); + + // Remove native controls + _toggleNativeControls(); + // Setup fullscreen _setupFullscreen(); - // Listeners - _listeners(); - // Captions _setupCaptions(); @@ -2514,6 +2768,12 @@ // Update the UI _checkPlaying(); + + // Display duration + _displayDuration(); + + // Ready event + _triggerEvent(plyr.container, 'ready'); } // Initialize instance @@ -2608,32 +2868,35 @@ elements = document.querySelectorAll(defaults.selectors.container); } - // Extend the default options with user specified - config = _extend(defaults, options); - // Bail if disabled or no basic support // You may want to disable certain UAs etc - if (!config.enabled || !api.supported().basic || !elements.length) { + if (!api.supported().basic || !elements.length) { return false; } // Create a player instance for each element - for (var i = elements.length - 1; i >= 0; i--) { + for (var i = 0; i < elements.length; i++) { // Get the current element var element = elements[i]; // Setup a player instance and add to the element if (typeof element.plyr === 'undefined') { + // Create instance-specific config + var config = _extend(defaults, options, JSON.parse(element.getAttribute("data-plyr"))); + + // Bail if not enabled + if(!config.enabled) { + return; + } + // Create new instance - var instance = new Plyr(element); + var instance = new Plyr(element, config); // Set plyr to false if setup failed element.plyr = (Object.keys(instance).length ? instance : false); // Callback - if (typeof config.onSetup === 'function') { - config.onSetup.apply(element.plyr); - } + _triggerEvent(element, 'setup', { plyr: element.plyr }); } // Add to return array even if it's already setup @@ -2645,3 +2908,22 @@ return api; })); + +// Custom event polyfill +// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent +(function () { + if (typeof window.CustomEvent === 'function') { + return false; + } + + function CustomEvent (event, params) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + + window.CustomEvent = CustomEvent; +})(); diff --git a/src/less/plyr.less b/src/less/plyr.less index fde725d7..50a530c9 100644 --- a/src/less/plyr.less +++ b/src/less/plyr.less @@ -7,129 +7,104 @@ // ------------------------------- // Colors -@blue: #3498DB; -@gray-dark: #343F4A; -@gray: #565D64; -@gray-light: #6B7D86; -@gray-lighter: #CBD0D3; -@off-white: #D6DADD; +@plyr-color-main: #3498DB; // Font sizes -@font-size-small: 14px; -@font-size-base: 16px; +@plyr-font-size-small: 14px; +@plyr-font-size-base: 16px; // Captions -@font-size-captions-base: ceil(@font-size-base * 1.25); -@font-size-captions-medium: ceil(@font-size-base * 1.5); -@font-size-captions-large: (@font-size-base * 2); +@plyr-font-size-captions-base: ceil(@plyr-font-size-base * 1.25); +@plyr-font-size-captions-medium: ceil(@plyr-font-size-base * 1.5); +@plyr-font-size-captions-large: (@plyr-font-size-base * 2); // Controls -@control-spacing: 10px; -@controls-bg: #fff; -@control-bg-hover: @blue; -.contrast-control-color(@controls-bg); -.contrast-control-color-hover(@control-bg-hover); +@plyr-controls-bg: #000; +@plyr-control-color: #fff; +@plyr-control-color-hover: #fff; +@plyr-control-spacing: 10px; +@plyr-control-bg-hover: @plyr-color-main; +//.contrast-control-color(@plyr-controls-bg); +//.contrast-control-color-hover(@plyr-control-bg-hover); // Tooltips -@tooltip-bg: @controls-bg; -@tooltip-border-color: fade(@gray-dark, 10%); -@tooltip-border-width: 1px; -@tooltip-shadow: 0 0 5px @tooltip-border-color, 0 0 0 @tooltip-border-width @tooltip-border-color; -@tooltip-color: @control-color; -@tooltip-padding: @control-spacing; -@tooltip-arrow-size: 6px; -@tooltip-radius: 3px; +@plyr-tooltip-bg: @plyr-controls-bg; +@plyr-tooltip-border-color: fade(darken(@plyr-controls-bg, 75%), 10%); +@plyr-tooltip-arrow-border-color: fade(darken(@plyr-controls-bg, 75%), 20%); +@plyr-tooltip-border-width: 1px; +@plyr-tooltip-shadow: 0 0 5px @plyr-tooltip-border-color, 0 0 0 @plyr-tooltip-border-width @plyr-tooltip-border-color; +@plyr-tooltip-color: @plyr-control-color; +@plyr-tooltip-padding: (@plyr-control-spacing / 2); +@plyr-tooltip-arrow-size: 4px; +@plyr-tooltip-radius: 3px; // Progress -@progress-bg: fade(@gray, 20%); -@progress-playing-bg: @blue; -@progress-buffered-bg: fade(@gray, 25%); -@progress-loading-size: 40px; -@progress-loading-bg: rgba(0,0,0, .15); - -// Volume -@volume-track-height: 6px; -@volume-track-bg: darken(@controls-bg, 10%); -@volume-thumb-height: (@volume-track-height * 2); -@volume-thumb-width: (@volume-track-height * 2); -@volume-thumb-bg: @control-color; -@volume-thumb-bg-focus: @control-bg-hover; +@plyr-progress-bg: fade(@plyr-control-color, 25%); +@plyr-progress-playing-bg: @plyr-color-main; +@plyr-progress-buffered-bg: fade(@plyr-control-color, 25%); +@plyr-progress-loading-size: 25px; +@plyr-progress-loading-bg: fade(#000, 15%); + +// Range sliders +@range-track-height: 8px; +@range-track-bg: fade(#fff, 25%); +@range-thumb-height: floor(@range-track-height * 2); +@range-thumb-width: floor(@range-track-height * 2); +@range-thumb-bg: #fff; +@range-thumb-border: 2px solid transparent; +@range-thumb-active-border-color: #fff; +@range-thumb-active-bg: @plyr-control-bg-hover; +@range-thumb-shadow: 0 1px 1px fade(@plyr-controls-bg, 15%); // Breakpoints -@bp-control-split: 560px; // When controls split into left/right -@bp-captions-large: 768px; // When captions jump to the larger font size +@plyr-bp-control-split: 560px; // When controls split into left/right +@plyr-bp-captions-large: 768px; // When captions jump to the larger font size // Animation // --------------------------------------- -@keyframes progress { - to { background-position: @progress-loading-size 0; } +@keyframes plyr-progress { + to { background-position: @plyr-progress-loading-size 0; } } // Mixins // ------------------------------- // Contrast -.contrast-control-color(@color: "") when (lightness(@color) >= 65%) { - @control-color: @gray-light; +/*.contrast-control-color(@plyr-color: "") when (lightness(@plyr-color) >= 65%) { + @plyr-control-color: @plyr-gray-light; } -.contrast-control-color(@color: "") when (lightness(@color) < 65%) { - @control-color: @gray-lighter; +.contrast-control-color(@plyr-color: "") when (lightness(@plyr-color) < 65%) { + @plyr-control-color: @plyr-gray-lighter; } -.contrast-control-color-hover(@color: "") when (lightness(@color) >= 65%) { - @control-color-hover: @gray; -} -.contrast-control-color-hover(@color: "") when (lightness(@color) < 65%) { - @control-color-hover: #fff; -} - -// Font smoothing -.font-smoothing(@mode: on) when (@mode = on) { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; -} -.font-smoothing(@mode: on) when (@mode = off) { - -moz-osx-font-smoothing: auto; - -webkit-font-smoothing: subpixel-antialiased; -} - -// Contain floats: nicolasgallagher.com/micro-clearfix-hack/ -.clearfix() { - zoom: 1; - &:before, - &:after { content: ""; display: table; } - &:after { clear: both; } -} -// Tab focus styles -.tab-focus() { - outline: 1px dotted fade(@gray-dark, 80%); - outline-offset: 3px; +.contrast-control-color-hover(@plyr-color: "") when (lightness(@plyr-color) >= 65%) { + @plyr-control-color-hover: @plyr-gray; } +.contrast-control-color-hover(@plyr-color: "") when (lightness(@plyr-color) < 65%) { + @plyr-control-color-hover: #fff; +}*/ // styling -.volume-thumb() { - height: @volume-thumb-height; - width: @volume-thumb-width; - background: @volume-thumb-bg; +.range-track() { + height: @range-track-height; + background: @range-track-bg; border: 0; - border-radius: 100%; - transition: background .3s ease; - cursor: ew-resize; + border-radius: (@range-track-height / 2); + user-select: none; } -.volume-track() { - height: @volume-track-height; - background: @volume-track-bg; - border: 0; - border-radius: (@volume-track-height / 2); -} -.seek-thumb() { - background: transparent; - border: 0; - width: (@control-spacing * 4); - height: @control-spacing; - transform: translateX(-50%); +.range-thumb() { + position: relative; + height: @range-thumb-height; + width: @range-thumb-width; + background: @range-thumb-bg; + border: @range-thumb-border; + border-radius: 100%; + transition: background .2s ease, border .2s ease, transform .2s ease; + box-shadow: @range-thumb-shadow; + box-sizing: border-box; } -.seek-track() { - background: none; - border: 0; +.range-thumb-active() { + background: @range-thumb-active-bg; + border-color: @range-thumb-active-border-color; + transform: scale(1.25); } // Styles @@ -139,6 +114,7 @@ position: relative; max-width: 100%; min-width: 290px; + font-family: Avenir, "Avenir Next", "Helvetica Neue", "Segoe UI", Helvetica, Arial, sans-serif; // border-box everything // http://paulirish.com/2012/box-sizing-border-box-ftw/ @@ -168,12 +144,16 @@ // For video &__video-wrapper { position: relative; + background: #000; + border-radius: inherit; + //overflow: hidden; } video, audio { width: 100%; height: auto; vertical-align: middle; + border-radius: inherit; } // Container for embeds @@ -181,7 +161,7 @@ padding-bottom: 56.25%; /* 16:9 */ height: 0; overflow: hidden; - background: #000; + border-radius: inherit; iframe { position: absolute; @@ -199,79 +179,215 @@ padding-bottom: 200%; transform: translateY(-35.95%); } + + // To allow mouse events to be captured if full support + &.plyr iframe { + pointer-events: none; + } } // Captions + + // Hide default captions + video::-webkit-media-text-track-container { + display: none; + } &__captions { display: none; position: absolute; bottom: 0; left: 0; width: 100%; - padding: (@control-spacing * 2) (@control-spacing * 2) (@control-spacing * 3); + padding: (@plyr-control-spacing * 2) (@plyr-control-spacing * 2) (@plyr-control-spacing * 8); color: #fff; - font-size: @font-size-captions-base; + font-size: @plyr-font-size-captions-base; text-align: center; - .font-smoothing(); + font-weight: 400; span { border-radius: 2px; - padding: 3px 10px; - background: rgba(0,0,0, .9); + padding: floor(@plyr-control-spacing / 3) @plyr-control-spacing; + background: fade(#000, 85%); } span:empty { display: none; } - @media (min-width: @bp-captions-large) { - font-size: @font-size-captions-medium; + @media (min-width: @plyr-bp-captions-large) { + font-size: @plyr-font-size-captions-medium; } } &--captions-active &__captions { display: block; } &--fullscreen-active &__captions { - font-size: @font-size-captions-large; + font-size: @plyr-font-size-captions-large; + } + + // Common + // Specificity is for bootstrap compatibility + input[type='range'] { + display: block; + height: @range-thumb-height; + width: 100%; + margin: 0; + padding: 0; + vertical-align: middle; + + appearance: none; + cursor: pointer; + border: none; + background: transparent; + + // Webkit + &::-webkit-slider-runnable-track { + .range-track(); + } + &::-webkit-slider-thumb { + -webkit-appearance: none; + margin-top: -((@range-thumb-height - @range-track-height) / 2); + .range-thumb(); + } + + // Mozilla + &::-moz-range-track { + .range-track(); + } + &::-moz-range-thumb { + .range-thumb(); + } + &::-moz-focus-outer { + border: 0; + } + + // Microsoft + &::-ms-track { + height: @range-track-height; + background: transparent; + border: 0; + color: transparent; + } + &::-ms-fill-lower, + &::-ms-fill-upper { + .range-track(); + } + &::-ms-thumb { + .range-thumb(); + + // For some reason, Edge uses the -webkit margin above + margin-top: 0; + } + + &::-ms-tooltip { + display: none; + } + + // Focus styles + &:focus { + outline: 0; + } + &::-moz-focus-outer { + border: 0; + } + &.tab-focus:focus { + outline: 1px dotted fade(@plyr-control-color, 50%); + outline-offset: 3px; + } + + // Pressed styles + &:active { + &::-webkit-slider-thumb { + .range-thumb-active(); + } + &::-moz-range-thumb { + .range-thumb-active(); + } + &::-ms-thumb { + .range-thumb-active(); + } + } + } + + // Large play button + &__play-large { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: @plyr-control-spacing; + background: @plyr-control-bg-hover; + border: 4px solid @plyr-control-color; + border-radius: 100%; + color: @plyr-control-color; + + svg { + position: relative; + left: 2px; + width: 20px; + height: 20px; + display: block; + fill: currentColor; + } + + &:focus { + outline: 1px dotted fade(@plyr-control-color, 50%); + } + } + + // Shared + &__controls, + &__play-large { + transition: visibility .3s ease, opacity .3s ease; + } + &--playing &__play-large { + visibility: hidden; + opacity: 0; } // Playback controls &__controls { - .clearfix(); - .font-smoothing(); - position: relative; - padding: @control-spacing; - background: @controls-bg; + position: absolute; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing; + + background: linear-gradient(fade(@plyr-controls-bg, 0%), fade(@plyr-controls-bg, 25%)); + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + line-height: 1; text-align: center; - box-shadow: 0 1px 1px fade(@gray-dark, 20%); - // Layout - &--right { - display: block; - margin: @control-spacing auto 0; - } - @media (min-width: @bp-control-split) { - &--left { - float: left; - } - &--right { - float: right; - margin-top: 0; + // Spacing + > button, + .plyr__progress, + .plyr__time, + .plyr__volume[type="range"] { + margin-left: @plyr-control-spacing; + + &::first-child { + margin-left: 0; } } + [data-plyr="mute"] { + margin-left: 0; + } // Buttons button { + position: relative; display: inline-block; + flex-shrink: 0; vertical-align: middle; - margin: 0 2px; - padding: (@control-spacing / 2) @control-spacing; - overflow: hidden; + padding: (@plyr-control-spacing / 2) @plyr-control-spacing; border: 0; background: transparent; border-radius: 3px; cursor: pointer; - color: @control-color; + color: @plyr-control-color; transition: background .3s ease, color .3s ease, opacity .3s ease; svg { @@ -279,14 +395,13 @@ height: 18px; display: block; fill: currentColor; - transition: fill .3s ease; } // Hover and tab focus &.tab-focus:focus, &:hover { - background: @control-bg-hover; - color: @control-color-hover; + background: @plyr-control-bg-hover; + color: @plyr-control-color-hover; } // Default focus &:focus { @@ -300,53 +415,30 @@ .icon--captions-on { display: none; } - - // plyr time - .plyr__time { - display: inline-block; - vertical-align: middle; - margin-left: @control-spacing; - color: @control-color; - font-weight: 600; - font-size: @font-size-small; - } - - // Media duration hidden on small screens - .plyr__time + .plyr__time { - display: none; - - @media (min-width: @bp-control-split) { - display: inline-block; - } - - // Add a slash in before - &::before { - content: '\2044'; - margin-right: @control-spacing; - } - } } // Tooltips &__tooltip { + visibility: hidden; position: absolute; z-index: 2; bottom: 100%; - margin-bottom: @tooltip-padding; - padding: @tooltip-padding (@tooltip-padding * 1.5); + margin-bottom: (@plyr-tooltip-padding * 2); + padding: @plyr-tooltip-padding (@plyr-tooltip-padding * 1.5); + pointer-events: none; opacity: 0; - background: @tooltip-bg; - box-shadow: @tooltip-shadow; - border-radius: @tooltip-radius; - color: @tooltip-color; - font-size: @font-size-small; - line-height: 1.5; - font-weight: 600; - - transform: translate(-50%, (@tooltip-padding * 3)) scale(.8); + background: @plyr-tooltip-bg; + box-shadow: @plyr-tooltip-shadow; + border-radius: @plyr-tooltip-radius; + + color: @plyr-tooltip-color; + font-size: @plyr-font-size-small; + line-height: 1.3; + + transform: translate(-50%, 10px) scale(.8); transform-origin: 50% 100%; - transition: transform .2s .1s ease, opacity .2s .1s ease; + transition: transform .2s .1s ease, opacity .2s .1s ease, visibility .3s ease; // Arrows &::after, @@ -361,24 +453,26 @@ } // The border triangle &::after { - @border-arrow-size: (@tooltip-arrow-size + (@tooltip-border-width * 1)); - bottom: -(@border-arrow-size + @tooltip-border-width); - border-right: @border-arrow-size solid transparent; - border-top: @border-arrow-size solid @tooltip-border-color; - border-left: @border-arrow-size solid transparent; + @plyr-border-arrow-size: (@plyr-tooltip-arrow-size + (@plyr-tooltip-border-width * 1)); + bottom: -(@plyr-border-arrow-size + @plyr-tooltip-border-width); + border-right: @plyr-border-arrow-size solid transparent; + border-top: @plyr-border-arrow-size solid @plyr-tooltip-arrow-border-color; + border-left: @plyr-border-arrow-size solid transparent; z-index: 1; } // The background triangle &::before { - bottom: -@tooltip-arrow-size; - border-right: @tooltip-arrow-size solid transparent; - border-top: @tooltip-arrow-size solid @tooltip-bg; - border-left: @tooltip-arrow-size solid transparent; + bottom: -@plyr-tooltip-arrow-size; + border-right: @plyr-tooltip-arrow-size solid transparent; + border-top: @plyr-tooltip-arrow-size solid @plyr-tooltip-bg; + border-left: @plyr-tooltip-arrow-size solid transparent; z-index: 2; } } button:hover .plyr__tooltip, - button.tab-focus:focus .plyr__tooltip { + button.tab-focus:focus .plyr__tooltip, + &__tooltip--visible { + visibility: visible; opacity: 1; transform: translate(-50%, 0) scale(1); } @@ -386,122 +480,116 @@ z-index: 3; } - // Common range styles - input[type='range'].tab-focus:focus { - .tab-focus(); - } - // Playback progress // element &__progress { - position: absolute; - bottom: 100%; - left: 0; - right: 0; - width: 100%; - height: @control-spacing; - background: @progress-bg; + position: relative; + flex: 1; + + input[type="range"] { + position: relative; + z-index: 2; + + &::-webkit-slider-runnable-track { + background: transparent; + } + &::-moz-range-track { + background: transparent; + } + &::-ms-fill-lower, + &::-ms-fill-upper { + background: transparent; + } + } &--buffer[value], - &--played[value], - &--seek[type='range'] { + &--played[value] { position: absolute; left: 0; - top: 0; + top: 50%; width: 100%; - height: @control-spacing; - margin: 0; + height: @range-track-height; + margin: -(@range-track-height / 2) 0 0; padding: 0; vertical-align: top; - - -webkit-appearance: none; - -moz-appearance: none; + appearance: none; border: none; - background: transparent; - } - &--buffer[value], - &--played[value] { + border-radius: 100px; + &::-webkit-progress-bar { background: transparent; } - - // Inherit from currentColor; &::-webkit-progress-value { background: currentColor; + border-radius: 100px; + min-width: @range-track-height; } &::-moz-progress-bar { background: currentColor; + border-radius: 100px; + min-width: @range-track-height; + } + &::-ms-fill { + border-radius: 100px; } } &--played[value] { - z-index: 2; - color: @progress-playing-bg; - } - &--buffer[value] { - color: @progress-buffered-bg; - } - - // Seek control - // element - // Specificity is for bootstrap compatibility - &--seek[type='range'] { - z-index: 4; - cursor: pointer; - outline: 0; + z-index: 1; + color: @plyr-progress-playing-bg; + background: transparent; - // Webkit - &::-webkit-slider-runnable-track { - .seek-track(); - } - &::-webkit-slider-thumb { - -webkit-appearance: none; - .seek-thumb(); + &::-webkit-progress-value { + background: currentColor; + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } - - // Mozilla - &::-moz-range-track { - .seek-track(); + &::-moz-progress-bar { + background: currentColor; + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } - &::-moz-range-thumb { - -moz-appearance: none; - .seek-thumb(); + &::-ms-fill { + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + } + &--buffer[value] { + color: @plyr-progress-buffered-bg; + background: @range-track-bg; - // Microsoft - &::-ms-track { - color: transparent; - .seek-track(); - } - &::-ms-fill-lower, - &::-ms-fill-upper { - .seek-track(); - } - &::-ms-thumb { - .seek-thumb(); + &::-webkit-progress-value { + transition: width .2s ease; } + &::-moz-progress-bar { + transition: width .2s ease; + } + &::-ms-fill { + transition: width .2s ease; + } + } - &:focus { - outline: 0; - } - &::-moz-focus-outer { - border: 0; - } + // Seek tooltip to show time + .plyr__tooltip { + left: 0; } } // Loading state &--loading .plyr__progress--buffer { - animation: progress 1s linear infinite; - background-size: @progress-loading-size @progress-loading-size; + animation: plyr-progress 1s linear infinite; + background-size: @plyr-progress-loading-size @plyr-progress-loading-size; background-repeat: repeat-x; - background-color: @progress-buffered-bg; + background-color: @plyr-progress-buffered-bg; background-image: linear-gradient( -45deg, - @progress-loading-bg 25%, + @plyr-progress-loading-bg 25%, transparent 25%, transparent 50%, - @progress-loading-bg 50%, - @progress-loading-bg 75%, + @plyr-progress-loading-bg 50%, + @plyr-progress-loading-bg 75%, transparent 75%, transparent); color: transparent; @@ -516,68 +604,35 @@ display: inline-block; } - // Volume control - // element - // Specificity is for bootstrap compatibility - &__volume[type='range'] { + // plyr time + &__time { display: inline-block; vertical-align: middle; - -webkit-appearance: none; - -moz-appearance: none; - width: 100px; - margin: 0 @control-spacing 0 0; - padding: 0; - cursor: pointer; - background: transparent; - border: none; + color: @plyr-control-color; + font-size: @plyr-font-size-small; + line-height: .95; + } - // Webkit - &::-webkit-slider-runnable-track { - .volume-track(); - } - &::-webkit-slider-thumb { - -webkit-appearance: none; - margin-top: -((@volume-thumb-height - @volume-track-height) / 2); - .volume-thumb(); - } + // Media duration hidden on small screens + &__time + &__time { + display: none; - // Mozilla - &::-moz-range-track { - .volume-track(); - } - &::-moz-range-thumb { - .volume-thumb(); + @media (min-width: @plyr-bp-control-split) { + display: inline-block; } - // Microsoft - &::-ms-track { - height: @volume-track-height; - background: transparent; - border-color: transparent; - border-width: ((@volume-thumb-height - @volume-track-height) / 2) 0; - color: transparent; - } - &::-ms-fill-lower, - &::-ms-fill-upper { - .volume-track(); - } - &::-ms-thumb { - .volume-thumb(); + // Add a slash in before + &::before { + content: '\2044'; + margin-right: @plyr-control-spacing; } + } - &:focus { - outline: 0; - - &::-webkit-slider-thumb { - background: @volume-thumb-bg-focus; - } - &::-moz-range-thumb { - background: @volume-thumb-bg-focus; - } - &::-ms-thumb { - background: @volume-thumb-bg-focus; - } - } + // Volume control + // element + // Specificity is for bootstrap compatibility + &__volume[type='range'] { + max-width: 100px; } // Hide sound controls on iOS @@ -596,16 +651,16 @@ // Audio specific styles // Position the progress within the container &--audio .plyr__controls { - padding-top: (@control-spacing * 2); + padding-top: (@plyr-control-spacing * 2); } &--audio .plyr__progress { bottom: auto; top: 0; - background: @off-white; + background: #fff; } // Full screen mode - &--fullscreen, + &.plyr--fullscreen, &--fullscreen-active { position: fixed; top: 0; @@ -633,29 +688,10 @@ } // Hide controls when playing in full screen - &--fullscreen--hide-controls&--fullscreen-active&--playing { + &.plyr--hide-controls { .plyr__controls { - transform: translateY(100%) translateY(@control-spacing / 2); - transition: transform .3s .2s ease; - } - &.plyr--hover .plyr__controls { - transform: translateY(0); - } - .plyr__captions { - bottom: (@control-spacing / 2); - transition: bottom .3s .2s ease; - } - } - - // Captions - &--fullscreen .plyr__captions, - &--fullscreen-active .plyr__captions, - &--fullscreen--hide-controls&--fullscreen-active&--playing&--hover &__captions { - top: auto; - bottom: 90px; - - @media (min-width: @bp-control-split) { - bottom: 60px; + opacity: 0; + visibility: hidden; } } diff --git a/src/sprite/icon-fast-forward.svg b/src/sprite/icon-fast-forward.svg old mode 100755 new mode 100644 index 71d5d138..3ae96af6 --- a/src/sprite/icon-fast-forward.svg +++ b/src/sprite/icon-fast-forward.svg @@ -1,5 +1,7 @@ - - - Fast Forward - - + + + + + + diff --git a/src/sprite/icon-pause.svg b/src/sprite/icon-pause.svg index b4ba82e2..db51a807 100644 --- a/src/sprite/icon-pause.svg +++ b/src/sprite/icon-pause.svg @@ -1,8 +1,8 @@ - - - Pause - - - - - \ No newline at end of file + + + + + + + diff --git a/src/sprite/icon-play.svg b/src/sprite/icon-play.svg old mode 100755 new mode 100644 index f564b80f..069af73c --- a/src/sprite/icon-play.svg +++ b/src/sprite/icon-play.svg @@ -1,5 +1,7 @@ - - - Play - - + + + + + + diff --git a/src/sprite/icon-rewind.svg b/src/sprite/icon-rewind.svg index b7beaa34..fbc252d2 100644 --- a/src/sprite/icon-rewind.svg +++ b/src/sprite/icon-rewind.svg @@ -1,5 +1,7 @@ - - - Rewind - - \ No newline at end of file + + + + + + -- cgit v1.2.3 From 833d3ac36f5190c88dcbf09229a9da14996f0607 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 25 Apr 2016 20:05:01 +1000 Subject: Tweaks --- src/js/plyr.js | 15 +- src/less/plyr.less | 877 ++++++++++++++++++++++++++--------------------------- 2 files changed, 447 insertions(+), 445 deletions(-) (limited to 'src') diff --git a/src/js/plyr.js b/src/js/plyr.js index 5c9e329e..57ce0147 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -2188,7 +2188,6 @@ if(typeof toggle !== "boolean") { if(toggle && toggle.type) { isMouseMove = toggle.type === 'mousemove'; - toggle = _inArray(['mousemove','mouseenter'], toggle.type); } else { @@ -2200,10 +2199,15 @@ window.clearTimeout(plyr.timers.hover); // If the mouse is not over the controls, set a timeout to hide them - if(toggle) { + if(toggle || plyr.media.paused) { _toggleClass(plyr.container, config.classes.hideControls, false); } + // Always show controls when paused + if(plyr.media.paused) { + return; + } + // If toggle is false or if we're playing (regardless of toggle), then // set the timer to hide the controls if(toggle === false || !plyr.media.paused) { @@ -2214,7 +2218,7 @@ } _toggleClass(plyr.container, config.classes.hideControls, true); - }, isMouseMove ? 2000 : 500); + }, isMouseMove ? 2000 : 0); } } @@ -2258,6 +2262,11 @@ // Pause playback _pause(); + // Set seek input to 0 + if(plyr.buttons.seek) { + plyr.buttons.seek.value = 0; + } + // Clean up YouTube stuff if (plyr.type === 'youtube') { // Destroy the embed instance diff --git a/src/less/plyr.less b/src/less/plyr.less index 50a530c9..854ec28a 100644 --- a/src/less/plyr.less +++ b/src/less/plyr.less @@ -130,24 +130,7 @@ touch-action: manipulation; } - // Screen reader only elements - &__sr-only { - position: absolute !important; - clip: rect(1px, 1px, 1px, 1px); - padding: 0 !important; - border: 0 !important; - height: 1px !important; - width: 1px !important; - overflow: hidden; - } - - // For video - &__video-wrapper { - position: relative; - background: #000; - border-radius: inherit; - //overflow: hidden; - } + // Media elements video, audio { width: 100%; @@ -156,75 +139,7 @@ border-radius: inherit; } - // Container for embeds - &__video-embed { - padding-bottom: 56.25%; /* 16:9 */ - height: 0; - overflow: hidden; - border-radius: inherit; - - iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: 0; - user-select: none; - } - - // Vimeo hack - > div { - position: relative; - padding-bottom: 200%; - transform: translateY(-35.95%); - } - - // To allow mouse events to be captured if full support - &.plyr iframe { - pointer-events: none; - } - } - - // Captions - - // Hide default captions - video::-webkit-media-text-track-container { - display: none; - } - &__captions { - display: none; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - padding: (@plyr-control-spacing * 2) (@plyr-control-spacing * 2) (@plyr-control-spacing * 8); - color: #fff; - font-size: @plyr-font-size-captions-base; - text-align: center; - font-weight: 400; - - span { - border-radius: 2px; - padding: floor(@plyr-control-spacing / 3) @plyr-control-spacing; - background: fade(#000, 85%); - } - span:empty { - display: none; - } - - @media (min-width: @plyr-bp-captions-large) { - font-size: @plyr-font-size-captions-medium; - } - } - &--captions-active &__captions { - display: block; - } - &--fullscreen-active &__captions { - font-size: @plyr-font-size-captions-large; - } - - // Common + // Range inputs // Specificity is for bootstrap compatibility input[type='range'] { display: block; @@ -307,412 +222,490 @@ } } } +} - // Large play button - &__play-large { +// Screen reader only elements +.plyr__sr-only { + position: absolute !important; + clip: rect(1px, 1px, 1px, 1px); + padding: 0 !important; + border: 0 !important; + height: 1px !important; + width: 1px !important; + overflow: hidden; +} + +// Video +.plyr__video-wrapper { + position: relative; + background: #000; + border-radius: inherit; +} + +// Container for embeds +.plyr__video-embed { + padding-bottom: 56.25%; /* 16:9 */ + height: 0; + overflow: hidden; + border-radius: inherit; + + iframe { position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: @plyr-control-spacing; - background: @plyr-control-bg-hover; - border: 4px solid @plyr-control-color; - border-radius: 100%; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + user-select: none; + } + + // Vimeo hack + > div { + position: relative; + padding-bottom: 200%; + transform: translateY(-35.95%); + } +} +// To allow mouse events to be captured if full support +.plyr.plyr__video-embed iframe { + pointer-events: none; +} + +// Captions +// -------------------------------------------------------------- +// Hide default captions +.plyr video::-webkit-media-text-track-container { + display: none; +} +.plyr__captions { + display: none; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + padding: (@plyr-control-spacing * 2) (@plyr-control-spacing * 2) (@plyr-control-spacing * 8); + color: #fff; + font-size: @plyr-font-size-captions-base; + text-align: center; + font-weight: 400; + + span { + border-radius: 2px; + padding: floor(@plyr-control-spacing / 3) @plyr-control-spacing; + background: fade(#000, 85%); + } + span:empty { + display: none; + } + + @media (min-width: @plyr-bp-captions-large) { + font-size: @plyr-font-size-captions-medium; + } +} +.plyr--captions-active &__captions { + display: block; +} +.plyr--fullscreen-active &__captions { + font-size: @plyr-font-size-captions-large; +} + +// Controls +// -------------------------------------------------------------- +// Shared +.plyr__controls, +.plyr__play-large { + transition: visibility .3s ease, opacity .3s ease; +} + +// Playback controls +.plyr__controls { + position: absolute; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing; + + background: linear-gradient(fade(@plyr-controls-bg, 0%), fade(@plyr-controls-bg, 50%)); + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + + line-height: 1; + text-align: center; + + // Spacing + > button, + .plyr__progress, + .plyr__time, + .plyr__volume[type="range"] { + margin-left: @plyr-control-spacing; + + &::first-child { + margin-left: 0; + } + } + [data-plyr="mute"] { + margin-left: 0; + } + + // Buttons + button { + position: relative; + display: inline-block; + flex-shrink: 0; + vertical-align: middle; + padding: (@plyr-control-spacing / 2) @plyr-control-spacing; + border: 0; + background: transparent; + border-radius: 3px; + cursor: pointer; color: @plyr-control-color; + transition: background .3s ease, color .3s ease, opacity .3s ease; svg { - position: relative; - left: 2px; - width: 20px; - height: 20px; + width: 18px; + height: 18px; display: block; fill: currentColor; } + // Hover and tab focus + &.tab-focus:focus, + &:hover { + background: @plyr-control-bg-hover; + color: @plyr-control-color-hover; + } + // Default focus &:focus { - outline: 1px dotted fade(@plyr-control-color, 50%); + outline: 0; } } - // Shared - &__controls, - &__play-large { - transition: visibility .3s ease, opacity .3s ease; + // Hide toggle icons by default + .icon--exit-fullscreen, + .icon--muted, + .icon--captions-on { + display: none; } - &--playing &__play-large { - visibility: hidden; - opacity: 0; +} + +// Large play button +.plyr__play-large { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + padding: @plyr-control-spacing; + background: @plyr-control-bg-hover; + border: 4px solid @plyr-control-color; + border-radius: 100%; + color: @plyr-control-color; + + svg { + position: relative; + left: 2px; + width: 20px; + height: 20px; + display: block; + fill: currentColor; } - // Playback controls - &__controls { - position: absolute; - left: 0; - right: 0; - bottom: 0; - display: flex; - align-items: center; - padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing; - - background: linear-gradient(fade(@plyr-controls-bg, 0%), fade(@plyr-controls-bg, 25%)); - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; - - line-height: 1; - text-align: center; - - // Spacing - > button, - .plyr__progress, - .plyr__time, - .plyr__volume[type="range"] { - margin-left: @plyr-control-spacing; - - &::first-child { - margin-left: 0; - } - } - [data-plyr="mute"] { - margin-left: 0; - } + &:focus { + outline: 1px dotted fade(@plyr-control-color, 50%); + } +} - // Buttons - button { - position: relative; - display: inline-block; - flex-shrink: 0; - vertical-align: middle; - padding: (@plyr-control-spacing / 2) @plyr-control-spacing; - border: 0; - background: transparent; - border-radius: 3px; - cursor: pointer; - color: @plyr-control-color; - transition: background .3s ease, color .3s ease, opacity .3s ease; - - svg { - width: 18px; - height: 18px; - display: block; - fill: currentColor; - } +// States +.plyr__controls [data-plyr='pause'], +.plyr--playing .plyr__controls [data-plyr='play'] { + display: none; +} +.plyr--playing .plyr__controls [data-plyr='pause'] { + display: inline-block; +} - // Hover and tab focus - &.tab-focus:focus, - &:hover { - background: @plyr-control-bg-hover; - color: @plyr-control-color-hover; - } - // Default focus - &:focus { - outline: 0; - } - } +// Hide controls +&.plyr--hide-controls .plyr__controls, +.plyr--playing .plyr__play-large { + visibility: hidden; + opacity: 0; +} - // Hide toggle icons by default - .icon--exit-fullscreen, - .icon--muted, - .icon--captions-on { - display: none; - } +// Change icons on state change +.plyr--fullscreen-active .icon--exit-fullscreen, +.plyr--muted .plyr__controls .icon--muted, +.plyr--captions-active .plyr__controls .icon--captions-on { + display: block; + + & + svg { + display: none; } +} - // Tooltips - &__tooltip { - visibility: hidden; +// Some options are hidden by default +[data-plyr='captions'], +[data-plyr='fullscreen'] { + display: none; +} +.plyr--captions-enabled [data-plyr='captions'], +.plyr--fullscreen-enabled [data-plyr='fullscreen'] { + display: inline-block; +} + +// Tooltips +// -------------------------------------------------------------- +.plyr__tooltip { + visibility: hidden; + position: absolute; + z-index: 2; + bottom: 100%; + margin-bottom: (@plyr-tooltip-padding * 2); + padding: @plyr-tooltip-padding (@plyr-tooltip-padding * 1.5); + pointer-events: none; + + opacity: 0; + background: @plyr-tooltip-bg; + box-shadow: @plyr-tooltip-shadow; + border-radius: @plyr-tooltip-radius; + + color: @plyr-tooltip-color; + font-size: @plyr-font-size-small; + line-height: 1.3; + + transform: translate(-50%, 10px) scale(.8); + transform-origin: 50% 100%; + transition: transform .2s .1s ease, opacity .2s .1s ease, visibility .3s ease; + + // Arrows + &::before { + content: ''; position: absolute; + width: 0; + height: 0; + left: 50%; + transform: translateX(-50%); + } + // The background triangle + &::before { + bottom: -@plyr-tooltip-arrow-size; + border-right: @plyr-tooltip-arrow-size solid transparent; + border-top: @plyr-tooltip-arrow-size solid @plyr-tooltip-bg; + border-left: @plyr-tooltip-arrow-size solid transparent; z-index: 2; - bottom: 100%; - margin-bottom: (@plyr-tooltip-padding * 2); - padding: @plyr-tooltip-padding (@plyr-tooltip-padding * 1.5); - pointer-events: none; - - opacity: 0; - background: @plyr-tooltip-bg; - box-shadow: @plyr-tooltip-shadow; - border-radius: @plyr-tooltip-radius; - - color: @plyr-tooltip-color; - font-size: @plyr-font-size-small; - line-height: 1.3; - - transform: translate(-50%, 10px) scale(.8); - transform-origin: 50% 100%; - transition: transform .2s .1s ease, opacity .2s .1s ease, visibility .3s ease; - - // Arrows - &::after, - &::before { - content: ''; - position: absolute; - width: 0; - height: 0; - top: 100%; - left: 50%; - transform: translateX(-50%); - } - // The border triangle - &::after { - @plyr-border-arrow-size: (@plyr-tooltip-arrow-size + (@plyr-tooltip-border-width * 1)); - bottom: -(@plyr-border-arrow-size + @plyr-tooltip-border-width); - border-right: @plyr-border-arrow-size solid transparent; - border-top: @plyr-border-arrow-size solid @plyr-tooltip-arrow-border-color; - border-left: @plyr-border-arrow-size solid transparent; - z-index: 1; - } - // The background triangle - &::before { - bottom: -@plyr-tooltip-arrow-size; - border-right: @plyr-tooltip-arrow-size solid transparent; - border-top: @plyr-tooltip-arrow-size solid @plyr-tooltip-bg; - border-left: @plyr-tooltip-arrow-size solid transparent; - z-index: 2; - } } - button:hover .plyr__tooltip, - button.tab-focus:focus .plyr__tooltip, - &__tooltip--visible { - visibility: visible; - opacity: 1; - transform: translate(-50%, 0) scale(1); - } - button:hover .plyr__tooltip { - z-index: 3; - } - - // Playback progress - // element - &__progress { - position: relative; - flex: 1; +} +.plyr button:hover .plyr__tooltip, +.plyr button.tab-focus:focus .plyr__tooltip, +.plyr__tooltip--visible { + visibility: visible; + opacity: 1; + transform: translate(-50%, 0) scale(1); +} +.plyr button:hover .plyr__tooltip { + z-index: 3; +} - input[type="range"] { - position: relative; - z-index: 2; +// Playback progress +// -------------------------------------------------------------- +// element +.plyr__progress { + position: relative; + flex: 1; - &::-webkit-slider-runnable-track { - background: transparent; - } - &::-moz-range-track { - background: transparent; - } - &::-ms-fill-lower, - &::-ms-fill-upper { - background: transparent; - } - } + input[type="range"] { + position: relative; + z-index: 2; - &--buffer[value], - &--played[value] { - position: absolute; - left: 0; - top: 50%; - width: 100%; - height: @range-track-height; - margin: -(@range-track-height / 2) 0 0; - padding: 0; - vertical-align: top; - appearance: none; - border: none; - border-radius: 100px; - - &::-webkit-progress-bar { - background: transparent; - } - &::-webkit-progress-value { - background: currentColor; - border-radius: 100px; - min-width: @range-track-height; - } - &::-moz-progress-bar { - background: currentColor; - border-radius: 100px; - min-width: @range-track-height; - } - &::-ms-fill { - border-radius: 100px; - } + &::-webkit-slider-runnable-track { + background: transparent; } - &--played[value] { - z-index: 1; - color: @plyr-progress-playing-bg; + &::-moz-range-track { background: transparent; - - &::-webkit-progress-value { - background: currentColor; - min-width: @range-track-height; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - &::-moz-progress-bar { - background: currentColor; - min-width: @range-track-height; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - &::-ms-fill { - min-width: @range-track-height; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } } - &--buffer[value] { - color: @plyr-progress-buffered-bg; - background: @range-track-bg; - - &::-webkit-progress-value { - transition: width .2s ease; - } - &::-moz-progress-bar { - transition: width .2s ease; - } - &::-ms-fill { - transition: width .2s ease; - } + &::-ms-fill-lower, + &::-ms-fill-upper { + background: transparent; } + } - // Seek tooltip to show time - .plyr__tooltip { - left: 0; - } + // Seek tooltip to show time + .plyr__tooltip { + left: 0; } +} - // Loading state - &--loading .plyr__progress--buffer { - animation: plyr-progress 1s linear infinite; - background-size: @plyr-progress-loading-size @plyr-progress-loading-size; - background-repeat: repeat-x; - background-color: @plyr-progress-buffered-bg; - background-image: linear-gradient( - -45deg, - @plyr-progress-loading-bg 25%, - transparent 25%, - transparent 50%, - @plyr-progress-loading-bg 50%, - @plyr-progress-loading-bg 75%, - transparent 75%, - transparent); - color: transparent; - } - - // States - &__controls [data-plyr='pause'], - &--playing .plyr__controls [data-plyr='play'] { - display: none; +.plyr__progress--buffer[value], +.plyr__progress--played[value] { + position: absolute; + left: 0; + top: 50%; + width: 100%; + height: @range-track-height; + margin: -(@range-track-height / 2) 0 0; + padding: 0; + vertical-align: top; + appearance: none; + border: none; + border-radius: 100px; + + &::-webkit-progress-bar { + background: transparent; } - &--playing .plyr__controls [data-plyr='pause'] { - display: inline-block; + &::-webkit-progress-value { + background: currentColor; + border-radius: 100px; + min-width: @range-track-height; + } + &::-moz-progress-bar { + background: currentColor; + border-radius: 100px; + min-width: @range-track-height; + } + &::-ms-fill { + border-radius: 100px; } +} +.plyr__progress--played[value] { + z-index: 1; + color: @plyr-progress-playing-bg; + background: transparent; + + &::-webkit-progress-value { + background: currentColor; + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + &::-moz-progress-bar { + background: currentColor; + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + &::-ms-fill { + min-width: @range-track-height; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +} +.plyr__progress--buffer[value] { + color: @plyr-progress-buffered-bg; + background: @range-track-bg; - // plyr time - &__time { - display: inline-block; - vertical-align: middle; - color: @plyr-control-color; - font-size: @plyr-font-size-small; - line-height: .95; + &::-webkit-progress-value { + transition: width .2s ease; } + &::-moz-progress-bar { + transition: width .2s ease; + } + &::-ms-fill { + transition: width .2s ease; + } +} - // Media duration hidden on small screens - &__time + &__time { - display: none; +// Loading state +.plyr--loading .plyr__progress--buffer { + animation: plyr-progress 1s linear infinite; + background-size: @plyr-progress-loading-size @plyr-progress-loading-size; + background-repeat: repeat-x; + background-color: @plyr-progress-buffered-bg; + background-image: linear-gradient( + -45deg, + @plyr-progress-loading-bg 25%, + transparent 25%, + transparent 50%, + @plyr-progress-loading-bg 50%, + @plyr-progress-loading-bg 75%, + transparent 75%, + transparent); + color: transparent; +} - @media (min-width: @plyr-bp-control-split) { - display: inline-block; - } - // Add a slash in before - &::before { - content: '\2044'; - margin-right: @plyr-control-spacing; - } - } +// Time +// -------------------------------------------------------------- +.plyr__time { + display: inline-block; + vertical-align: middle; + color: @plyr-control-color; + font-size: @plyr-font-size-small; + line-height: .95; +} - // Volume control - // element - // Specificity is for bootstrap compatibility - &__volume[type='range'] { - max-width: 100px; - } +// Media duration hidden on small screens +.plyr__time + .plyr__time { + display: none; - // Hide sound controls on iOS - // It's not supported to change volume using JavaScript: - // https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html - &--is-ios &__volume, - &--is-ios [data-plyr='mute'], - &--is-ios.plyr--audio &__controls--right { - display: none; - } - // Center buttons so it looks less odd - &--is-ios.plyr--audio &__controls--left { - float: none; + @media (min-width: @plyr-bp-control-split) { + display: inline-block; } - // Audio specific styles - // Position the progress within the container - &--audio .plyr__controls { - padding-top: (@plyr-control-spacing * 2); - } - &--audio .plyr__progress { - bottom: auto; - top: 0; - background: #fff; + // Add a slash in before + &::before { + content: '\2044'; + margin-right: @plyr-control-spacing; } +} - // Full screen mode - &.plyr--fullscreen, - &--fullscreen-active { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - height: 100%; - width: 100%; - z-index: 10000000; - background: #000; - - video { - height: 100%; - } - .plyr__video-wrapper { - height: 100%; - width: 100%; - } - .plyr__controls { - position: absolute; - bottom: 0; - left: 0; - right: 0; - } - } +// Volume +// -------------------------------------------------------------- +// element +// Specificity is for bootstrap compatibility +.plyr__volume[type='range'] { + max-width: 100px; +} - // Hide controls when playing in full screen - &.plyr--hide-controls { - .plyr__controls { - opacity: 0; - visibility: hidden; - } - } +// Hide sound controls on iOS +// It's not supported to change volume using JavaScript: +// https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html +.plyr--is-ios .plyr__volume, +.plyr--is-ios [data-plyr='mute'], +.plyr--is-ios.plyr--audio .plyr__controls--right { + display: none; +} +// Center buttons so it looks less odd +.plyr--is-ios.plyr--audio .plyr__controls--left { + float: none; +} - // Change icons on state change - &--fullscreen-active .icon--exit-fullscreen, - &--muted .plyr__controls .icon--muted, - &--captions-active .plyr__controls .icon--captions-on { - display: block; +// Audio +// -------------------------------------------------------------- +// Position the progress within the container +.plyr--audio .plyr__controls { + padding-top: (@plyr-control-spacing * 2); +} +.plyr--audio .plyr__progress { + bottom: auto; + top: 0; + background: #fff; +} - & + svg { - display: none; - } +// Fullscreen +// -------------------------------------------------------------- +.plyr--fullscreen, +.plyr--fullscreen-active { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100%; + width: 100%; + z-index: 10000000; + background: #000; + + video { + height: 100%; } - - // Some options are hidden by default - [data-plyr='captions'], - [data-plyr='fullscreen'] { - display: none; + .plyr__video-wrapper { + height: 100%; + width: 100%; } - &--captions-enabled [data-plyr='captions'], - &--fullscreen-enabled [data-plyr='fullscreen'] { - display: inline-block; + .plyr__controls { + position: absolute; + bottom: 0; + left: 0; + right: 0; } } -- cgit v1.2.3 From d41249bd9056d5cc91ce0de2c4cf3fef9fe6596b Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Mon, 25 Apr 2016 21:24:07 +1000 Subject: WIP --- src/js/plyr.js | 35 ++++++++++++++++++----------------- src/less/plyr.less | 11 +++++------ 2 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/js/plyr.js b/src/js/plyr.js index 57ce0147..aae85c29 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1842,9 +1842,6 @@ // Set button state _toggleState(plyr.buttons.fullscreen, plyr.isFullscreen); - // Hide on entering full screen - _toggleControls(false); - // Trigger an event _triggerEvent(plyr.container, plyr.isFullscreen ? 'enterfullscreen' : 'exitfullscreen'); } @@ -2182,16 +2179,19 @@ if (!config.hideControls) { return; } - var isMouseMove = false; + var delay = false, + isEnterFullscreen = false, + show = toggle; // Default to false if no boolean if(typeof toggle !== "boolean") { if(toggle && toggle.type) { - isMouseMove = toggle.type === 'mousemove'; - toggle = _inArray(['mousemove','mouseenter'], toggle.type); + delay = (toggle.type === 'mousemove'); + isEnterFullscreen = (toggle.type === 'enterfullscreen'); + show = _inArray(['mousemove','mouseenter'], toggle.type); } else { - toggle = false; + show = false; } } @@ -2199,26 +2199,26 @@ window.clearTimeout(plyr.timers.hover); // If the mouse is not over the controls, set a timeout to hide them - if(toggle || plyr.media.paused) { + if(show || plyr.media.paused) { _toggleClass(plyr.container, config.classes.hideControls, false); - } - // Always show controls when paused - if(plyr.media.paused) { - return; + // Always show controls when paused + if(plyr.media.paused) { + return; + } } // If toggle is false or if we're playing (regardless of toggle), then // set the timer to hide the controls - if(toggle === false || !plyr.media.paused) { + if(!show || !plyr.media.paused) { plyr.timers.hover = window.setTimeout(function() { // If the mouse is over the controls, bail - if(plyr.controls.active) { + if(plyr.controls.active && !isEnterFullscreen) { return; } _toggleClass(plyr.container, config.classes.hideControls, true); - }, isMouseMove ? 2000 : 0); + }, delay ? 2000 : 0); } } @@ -2507,8 +2507,9 @@ // Toggle controls visibility based on mouse movement if (config.hideControls) { - _on(plyr.container, 'mouseenter mouseleave', _toggleControls); - _on(plyr.container, 'mousemove', _debounce(_toggleControls, 500, true)); + _on(plyr.container, 'mouseenter mouseleave mousemove', _toggleControls); + //_on(plyr.container, 'mousemove', _debounce(_toggleControls, 200, true)); + _on(plyr.container, 'enterfullscreen', _toggleControls); // Watch for cursor over controls so they don't hide when trying to interact _on(plyr.controls, 'mouseenter mouseleave', function(event) { diff --git a/src/less/plyr.less b/src/less/plyr.less index 854ec28a..e452889f 100644 --- a/src/less/plyr.less +++ b/src/less/plyr.less @@ -302,10 +302,10 @@ font-size: @plyr-font-size-captions-medium; } } -.plyr--captions-active &__captions { +.plyr--captions-active .plyr__captions { display: block; } -.plyr--fullscreen-active &__captions { +.plyr--fullscreen-active .plyr__captions { font-size: @plyr-font-size-captions-large; } @@ -426,7 +426,7 @@ } // Hide controls -&.plyr--hide-controls .plyr__controls, +.plyr--hide-controls .plyr__controls, .plyr--playing .plyr__play-large { visibility: hidden; opacity: 0; @@ -444,8 +444,8 @@ } // Some options are hidden by default -[data-plyr='captions'], -[data-plyr='fullscreen'] { +.plyr [data-plyr='captions'], +.plyr [data-plyr='fullscreen'] { display: none; } .plyr--captions-enabled [data-plyr='captions'], @@ -622,7 +622,6 @@ color: transparent; } - // Time // -------------------------------------------------------------- .plyr__time { -- cgit v1.2.3 From e26694c32202ed5eee2ae07c3834946aae93f5bc Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 25 Apr 2016 22:48:40 +1000 Subject: Work on Audio UI --- src/js/plyr.js | 8 ++- src/less/plyr.less | 141 +++++++++++++++++++++++++++-------------------------- 2 files changed, 78 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/js/plyr.js b/src/js/plyr.js index aae85c29..7ac9d7f2 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -1317,6 +1317,12 @@ // Add type class _toggleClass(plyr.container, config.classes.type.replace('{0}', plyr.type), true); + // Add video class for embeds + // This will require changes if audio embeds are added + if (_inArray(config.types.embed, plyr.type)) { + _toggleClass(plyr.container, config.classes.type.replace('{0}', 'video'), true); + } + // If there's no autoplay attribute, assume the video is stopped and add state class _toggleClass(plyr.container, config.classes.stopped, config.autoplay); @@ -2176,7 +2182,7 @@ // Show the player controls in fullscreen mode function _toggleControls(toggle) { - if (!config.hideControls) { + if (!config.hideControls || plyr.type === 'audio') { return; } var delay = false, diff --git a/src/less/plyr.less b/src/less/plyr.less index e452889f..99df1115 100644 --- a/src/less/plyr.less +++ b/src/less/plyr.less @@ -7,7 +7,7 @@ // ------------------------------- // Colors -@plyr-color-main: #3498DB; +@plyr-color-main: #63B4E1; // Font sizes @plyr-font-size-small: 14px; @@ -19,29 +19,32 @@ @plyr-font-size-captions-large: (@plyr-font-size-base * 2); // Controls -@plyr-controls-bg: #000; -@plyr-control-color: #fff; -@plyr-control-color-hover: #fff; +// #C6D6DB @plyr-control-spacing: 10px; -@plyr-control-bg-hover: @plyr-color-main; -//.contrast-control-color(@plyr-controls-bg); -//.contrast-control-color-hover(@plyr-control-bg-hover); +@plyr-video-controls-bg: #000; +@plyr-video-control-color: #fff; +@plyr-video-control-color-hover: #fff; +@plyr-video-control-bg-hover: @plyr-color-main; +@plyr-audio-controls-bg: transparent; +@plyr-audio-control-color: #565D64; +@plyr-audio-control-color-hover: #fff; +@plyr-audio-control-bg-hover: @plyr-color-main; // Tooltips -@plyr-tooltip-bg: @plyr-controls-bg; -@plyr-tooltip-border-color: fade(darken(@plyr-controls-bg, 75%), 10%); -@plyr-tooltip-arrow-border-color: fade(darken(@plyr-controls-bg, 75%), 20%); +@plyr-tooltip-bg: @plyr-video-controls-bg; +@plyr-tooltip-border-color: fade(darken(@plyr-video-controls-bg, 75%), 10%); +@plyr-tooltip-arrow-border-color: fade(darken(@plyr-video-controls-bg, 75%), 20%); @plyr-tooltip-border-width: 1px; @plyr-tooltip-shadow: 0 0 5px @plyr-tooltip-border-color, 0 0 0 @plyr-tooltip-border-width @plyr-tooltip-border-color; -@plyr-tooltip-color: @plyr-control-color; +@plyr-tooltip-color: @plyr-video-control-color; @plyr-tooltip-padding: (@plyr-control-spacing / 2); @plyr-tooltip-arrow-size: 4px; @plyr-tooltip-radius: 3px; // Progress -@plyr-progress-bg: fade(@plyr-control-color, 25%); +@plyr-progress-bg: fade(@plyr-video-control-color, 25%); @plyr-progress-playing-bg: @plyr-color-main; -@plyr-progress-buffered-bg: fade(@plyr-control-color, 25%); +@plyr-progress-buffered-bg: fade(@plyr-video-control-color, 25%); @plyr-progress-loading-size: 25px; @plyr-progress-loading-bg: fade(#000, 15%); @@ -53,8 +56,8 @@ @range-thumb-bg: #fff; @range-thumb-border: 2px solid transparent; @range-thumb-active-border-color: #fff; -@range-thumb-active-bg: @plyr-control-bg-hover; -@range-thumb-shadow: 0 1px 1px fade(@plyr-controls-bg, 15%); +@range-thumb-active-bg: @plyr-video-control-bg-hover; +@range-thumb-shadow: 0 1px 1px fade(@plyr-video-controls-bg, 15%); // Breakpoints @plyr-bp-control-split: 560px; // When controls split into left/right @@ -68,20 +71,6 @@ // Mixins // ------------------------------- -// Contrast -/*.contrast-control-color(@plyr-color: "") when (lightness(@plyr-color) >= 65%) { - @plyr-control-color: @plyr-gray-light; -} -.contrast-control-color(@plyr-color: "") when (lightness(@plyr-color) < 65%) { - @plyr-control-color: @plyr-gray-lighter; -} -.contrast-control-color-hover(@plyr-color: "") when (lightness(@plyr-color) >= 65%) { - @plyr-control-color-hover: @plyr-gray; -} -.contrast-control-color-hover(@plyr-color: "") when (lightness(@plyr-color) < 65%) { - @plyr-control-color-hover: #fff; -}*/ - // styling .range-track() { height: @range-track-height; @@ -205,7 +194,7 @@ border: 0; } &.tab-focus:focus { - outline: 1px dotted fade(@plyr-control-color, 50%); + outline: 1px dotted fade(@plyr-video-control-color, 50%); outline-offset: 3px; } @@ -267,7 +256,7 @@ } } // To allow mouse events to be captured if full support -.plyr.plyr__video-embed iframe { +.plyr .plyr__video-embed iframe { pointer-events: none; } @@ -319,17 +308,8 @@ // Playback controls .plyr__controls { - position: absolute; - left: 0; - right: 0; - bottom: 0; display: flex; align-items: center; - padding: (@plyr-control-spacing * 5) @plyr-control-spacing @plyr-control-spacing; - - background: linear-gradient(fade(@plyr-controls-bg, 0%), fade(@plyr-controls-bg, 50%)); - border-bottom-left-radius: inherit; - border-bottom-right-radius: inherit; line-height: 1; text-align: center; @@ -337,15 +317,17 @@ // Spacing > button, .plyr__progress, - .plyr__time, - .plyr__volume[type="range"] { + .plyr__time { margin-left: @plyr-control-spacing; - &::first-child { + &:first-child { margin-left: 0; } } - [data-plyr="mute"] { + .plyr__volume[type="range"] { + margin-left: (@plyr-control-spacing / 2); + } + [data-plyr="pause"] { margin-left: 0; } @@ -360,8 +342,8 @@ background: transparent; border-radius: 3px; cursor: pointer; - color: @plyr-control-color; transition: background .3s ease, color .3s ease, opacity .3s ease; + color: inherit; svg { width: 18px; @@ -370,12 +352,6 @@ fill: currentColor; } - // Hover and tab focus - &.tab-focus:focus, - &:hover { - background: @plyr-control-bg-hover; - color: @plyr-control-color-hover; - } // Default focus &:focus { outline: 0; @@ -390,6 +366,43 @@ } } +// Video controls +.plyr--video .plyr__controls { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: (@plyr-control-spacing * 5) (@plyr-control-spacing * 1.5) @plyr-control-spacing; + background: linear-gradient(fade(@plyr-video-controls-bg, 0%), fade(@plyr-video-controls-bg, 50%)); + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + color: @plyr-video-control-color; + + button { + // Hover and tab focus + &.tab-focus:focus, + &:hover { + background: @plyr-video-control-bg-hover; + color: @plyr-video-control-color-hover; + } + } +} +.plyr--audio .plyr__controls { + padding: @plyr-control-spacing; + border-radius: inherit; + background: @plyr-audio-controls-bg; + color: @plyr-audio-control-color; + + button { + // Hover and tab focus + &.tab-focus:focus, + &:hover { + background: @plyr-audio-control-bg-hover; + color: @plyr-audio-control-color-hover; + } + } +} + // Large play button .plyr__play-large { position: absolute; @@ -397,10 +410,10 @@ left: 50%; transform: translate(-50%, -50%); padding: @plyr-control-spacing; - background: @plyr-control-bg-hover; - border: 4px solid @plyr-control-color; + background: @plyr-video-control-bg-hover; + border: 4px solid @plyr-video-control-color; border-radius: 100%; - color: @plyr-control-color; + color: @plyr-video-control-color; svg { position: relative; @@ -412,9 +425,12 @@ } &:focus { - outline: 1px dotted fade(@plyr-control-color, 50%); + outline: 1px dotted fade(@plyr-video-control-color, 50%); } } +.plyr--audio .plyr__play-large { + display: none; +} // States .plyr__controls [data-plyr='pause'], @@ -572,13 +588,11 @@ background: transparent; &::-webkit-progress-value { - background: currentColor; min-width: @range-track-height; border-top-right-radius: 0; border-bottom-right-radius: 0; } &::-moz-progress-bar { - background: currentColor; min-width: @range-track-height; border-top-right-radius: 0; border-bottom-right-radius: 0; @@ -627,7 +641,6 @@ .plyr__time { display: inline-block; vertical-align: middle; - color: @plyr-control-color; font-size: @plyr-font-size-small; line-height: .95; } @@ -668,18 +681,6 @@ float: none; } -// Audio -// -------------------------------------------------------------- -// Position the progress within the container -.plyr--audio .plyr__controls { - padding-top: (@plyr-control-spacing * 2); -} -.plyr--audio .plyr__progress { - bottom: auto; - top: 0; - background: #fff; -} - // Fullscreen // -------------------------------------------------------------- .plyr--fullscreen, -- cgit v1.2.3