diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/js/plyr.js | 306 | ||||
-rw-r--r-- | src/less/plyr.less | 113 | ||||
-rw-r--r-- | src/sass/plyr.scss | 96 |
3 files changed, 403 insertions, 112 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js index debbecfc..232c49a8 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -9,6 +9,7 @@ (function (api) { "use strict"; + /*global YT*/ // Globals var fullscreen, config; @@ -49,9 +50,9 @@ duration: ".player-duration" }, classes: { - video: "player-video", videoWrapper: "player-video-wrapper", - audio: "player-audio", + embedWrapper: "player-video-embed", + type: "player-{0}", stopped: "stopped", playing: "playing", muted: "muted", @@ -82,7 +83,7 @@ key: "plyr_volume" }, controls: ["restart", "rewind", "play", "fast-forward", "current-time", "duration", "mute", "volume", "captions", "fullscreen"], - onSetup: function() {}, + onSetup: function() {} }; // Build the default HTML @@ -331,6 +332,18 @@ return false; } + // Inject a script + function _injectScript(source) { + if(document.querySelectorAll("script[src='" + source + "']").length) { + return; + } + + var tag = document.createElement("script"); + tag.src = source; + var firstScriptTag = document.getElementsByTagName("script")[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + } + // Element exists in an array function _inArray(haystack, needle) { return Array.prototype.indexOf && (haystack.indexOf(needle) != -1); @@ -817,7 +830,7 @@ player.media.removeAttribute("controls"); // Add type class - _toggleClass(player.container, config.classes[player.type], true); + _toggleClass(player.container, config.classes.type.replace("{0}", player.type), true); // If there's no autoplay attribute, assume the video is stopped and add state class _toggleClass(player.container, config.classes.stopped, (player.media.getAttribute("autoplay") === null)); @@ -839,6 +852,11 @@ // Cache the container player.videoContainer = wrapper; } + + // YouTube + if(player.type == "youtube") { + _setupYouTube(player.media.getAttribute("data-video-id")); + } } // Autoplay @@ -847,6 +865,149 @@ } } + // Setup YouTube + function _setupYouTube(id) { + // Remove old containers + var containers = _getElements("[id^='youtube']"); + for (var i = containers.length - 1; i >= 0; i--) { + _remove(containers[i]); + } + + // Create the YouTube container + var container = document.createElement("div"); + container.setAttribute("id", "youtube-" + Math.floor(Math.random() * (10000))); + player.media.appendChild(container); + + // Add embed class for responsive + _toggleClass(player.media, config.classes.videoWrapper, true); + _toggleClass(player.media, config.classes.embedWrapper, true); + + if(typeof YT === "object") { + _YTReady(id, container); + } + else { + // Load the API + _injectScript("https://www.youtube.com/iframe_api"); + + // Setup callback for the API + window.onYouTubeIframeAPIReady = function () { _YTReady(id, container); } + } + } + + // Handle API ready + function _YTReady(id, container) { + _log("YouTube API Ready"); + + // Setup timers object + // We have to poll YouTube for updates + if(!("timer" in player)) { + player.timer = {}; + } + + // Setup instance + // https://developers.google.com/youtube/iframe_api_reference + player.embed = new YT.Player(container.id, { + videoId: id, + playerVars: { + autoplay: 0, + controls: 0, + vq: "hd720", + rel: 0, + showinfo: 0, + iv_load_policy: 3, + cc_lang_pref: "en", + wmode: "transparent", + modestbranding: 1 + }, + events: { + onReady: function(event) { + // Get the instance + var instance = event.target; + + // Create a faux HTML5 API using the YouTube API + player.media.play = function() { instance.playVideo(); }; + player.media.pause = function() { instance.pauseVideo(); }; + player.media.stop = function() { instance.stopVideo(); }; + player.media.duration = instance.getDuration(); + player.media.paused = (instance.getPlayerState() == 2); + player.media.currentTime = instance.getCurrentTime(); + player.media.muted = instance.isMuted(); + + // Trigger timeupdate + _triggerEvent(player.media, "timeupdate"); + + // Reset timer + window.clearInterval(player.timer.buffering); + + // Setup buffering + player.timer.buffering = window.setInterval(function() { + // Get loaded % from YouTube + player.media.buffered = instance.getVideoLoadedFraction(); + + // Trigger progress + _triggerEvent(player.media, "progress"); + + // Bail if we're at 100% + if(player.media.buffered === 1) { + window.clearInterval(player.timer.buffering); + } + }, 200); + + // Only setup controls once + if(!player.container.querySelectorAll(config.selectors.controls).length) { + _setupInterface(); + } + + // Display duration if available + if(config.displayDuration) { + _displayDuration(); + } + }, + onStateChange: function(event) { + // Get the instance + var instance = event.target; + + // Reset timer + window.clearInterval(player.timer.playing); + + // Handle events + // -1 Unstarted + // 0 Ended + // 1 Playing + // 2 Paused + // 3 Buffering + // 5 Video cued + switch(event.data) { + case 0: + player.media.paused = true; + _triggerEvent(player.media, "ended"); + break; + + case 1: + player.media.paused = false; + _triggerEvent(player.media, "play"); + + // Poll to get playback progress + player.timer.playing = window.setInterval(function() { + // Set the current time + player.media.currentTime = instance.getCurrentTime(); + + // Trigger timeupdate + _triggerEvent(player.media, "timeupdate"); + }, 200); + + break; + + case 2: + player.media.paused = true; + _triggerEvent(player.media, "pause"); + break; + } + } + } + }); + } + // Setup captions function _setupCaptions() { if(player.type === "video") { @@ -997,7 +1158,7 @@ // Setup fullscreen function _setupFullscreen() { - if(player.type === "video" && config.fullscreen.enabled) { + if(player.type != "audio" && config.fullscreen.enabled) { // Check for native support var nativeSupport = fullscreen.supportsFullScreen; @@ -1093,6 +1254,14 @@ } catch(e) {} + // YouTube + if(player.type == "youtube") { + player.embed.seekTo(player.media.currentTime); + + // Trigger timeupdate + _triggerEvent(player.media, "timeupdate"); + } + // Logging _log("Seeking to " + player.media.currentTime + " seconds"); @@ -1215,6 +1384,14 @@ // Set the player volume player.media.volume = parseFloat(volume / 10); + // YouTube + if(player.type == "youtube") { + player.embed.setVolume(player.media.volume * 100); + + // Trigger timeupdate + _triggerEvent(player.media, "volumechange"); + } + // Toggle muted state if(player.media.muted && volume > 0) { _toggleMute(); @@ -1230,6 +1407,14 @@ // Set mute on the player player.media.muted = muted; + + // YouTube + if(player.type === "youtube") { + player.embed[player.media.muted ? "mute" : "unMute"](); + + // Trigger timeupdate + _triggerEvent(player.media, "volumechange"); + } } // Update volume UI and storage @@ -1320,9 +1505,14 @@ value = (function() { var buffered = player.media.buffered; - if(buffered.length) { + // HTML5 + if(buffered && buffered.length) { return _getPercentage(buffered.end(0), player.media.duration); } + // YouTube returns between 0 and 1 + else if(typeof buffered == "number") { + return (buffered * 100); + } return 0; })(); @@ -1416,6 +1606,22 @@ // Update source // Sources are not checked for support so be careful function _parseSource(sources) { + // YouTube + if(player.type === "youtube" && typeof sources === "string") { + // Destroy YouTube instance + player.embed.destroy(); + + // Re-setup YouTube + // We don't use loadVideoBy[x] here since it has issues + _setupYouTube(sources); + + // Update times + _timeUpdate(); + + // Bail + return; + } + // Pause playback (webkit freaks out) _pause(); @@ -1537,10 +1743,7 @@ }); // Check for buffer progress - _on(player.media, "progress", _updateProgress); - - // Also check on start of playing - _on(player.media, "playing", _updateProgress); + _on(player.media, "progress playing", _updateProgress); // Handle native mute _on(player.media, "volumechange", _updateVolume); @@ -1572,6 +1775,8 @@ } // Destroy an instance + // Event listeners are removed when elements are removed + // http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory function _destroy() { // Bail if the element is not initialized if(!player.init) { @@ -1581,12 +1786,18 @@ // Reset container classname player.container.setAttribute("class", config.selectors.container.replace(".", "")); - // Event listeners are removed when elements are removed - // http://stackoverflow.com/questions/12528049/if-a-dom-element-is-removed-are-its-listeners-also-removed-from-memory + // Remove init flag + player.init = false; // Remove controls _remove(_getElement(config.selectors.controls)); + // YouTube + if(player.type === "youtube") { + player.embed.destroy(); + return; + } + // If video, we need to remove some more if(player.type === "video") { // Remove captions @@ -1603,9 +1814,6 @@ // http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type var clone = player.media.cloneNode(true); player.media.parentNode.replaceChild(clone, player.media); - - // Remove init flag - player.init = false; } // Setup a player @@ -1622,11 +1830,20 @@ player.browser = _browserSniff(); // Get the media element - player.media = player.container.querySelectorAll("audio, video")[0]; + player.media = player.container.querySelectorAll("audio, video, div")[0]; // Set media type - player.type = player.media.tagName.toLowerCase(); + var tagName = player.media.tagName.toLowerCase(); + switch(tagName) { + case "div": + player.type = player.media.getAttribute("data-type"); + break; + default: + player.type = tagName; + break; + } + // Check for full support player.supported = api.supported(player.type); @@ -1641,16 +1858,16 @@ // Setup media _setupMedia(); - // If there's full support - if(player.supported.full) { - // Inject custom controls - _injectControls(); - - // Find the elements - if(!_findElements()) { - return false; + // Setup interface + if(player.type == "video" || player.type == "audio") { + // Bail if no support + if(!player.supported.full) { + return; } + // Setup UI + _setupInterface(); + // Display duration if available if(config.displayDuration) { _displayDuration(); @@ -1658,23 +1875,33 @@ // Set up aria-label for Play button with the title option _setupAria(); + } - // Captions - _setupCaptions(); - - // Set volume - _setVolume(); - _updateVolume(); + // Successful setup + player.init = true; + } - // Setup fullscreen - _setupFullscreen(); + function _setupInterface() { + // Inject custom controls + _injectControls(); - // Listeners - _listeners(); + // Find the elements + if(!_findElements()) { + return false; } - // Successful setup - player.init = true; + // Captions + _setupCaptions(); + + // Set volume + _setVolume(); + _updateVolume(); + + // Setup fullscreen + _setupFullscreen(); + + // Listeners + _listeners(); } // Initialize instance @@ -1727,6 +1954,11 @@ full = (basic && !oldIE); break; + case "youtube": + basic = true; + full = !oldIE; + break; + default: basic = (audio && video); full = (basic && !oldIE); diff --git a/src/less/plyr.less b/src/less/plyr.less index 3f572c3e..75d94b0f 100644 --- a/src/less/plyr.less +++ b/src/less/plyr.less @@ -4,12 +4,14 @@ // Variables // ------------------------------- + // Colors @blue: #3498DB; -@gray-dark: #343f4a; -@gray: #565d64; -@gray-light: #cbd0d3; -@off-white: #d6dadd; +@gray-dark: #343F4A; +@gray: #565D64; +@gray-light: #6B7D86; +@gray-lighter: #CBD0D3; +@off-white: #D6DADD; // Font sizes @font-size-small: 14px; @@ -18,11 +20,10 @@ // Controls @control-spacing: 10px; -@controls-bg: @gray-dark; +@controls-bg: #fff; @control-bg-hover: @blue; -@control-color: @gray-light; -@control-color-inactive: @gray; -@control-color-hover: #fff; +.contrast-control-color(@controls-bg); +.contrast-control-color-hover(@control-bg-hover); // Tooltips @tooltip-bg: @controls-bg; @@ -40,7 +41,7 @@ // Volume @volume-track-height: 6px; -@volume-track-bg: @gray; +@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; @@ -50,18 +51,40 @@ @bp-control-split: 560px; // When controls split into left/right @bp-captions-large: 768px; // When captions jump to the larger font size -// Utility classes & mixins +// Animation +// --------------------------------------- + +@keyframes progress { + to { background-position: @progress-loading-size 0; } +} + +// Mixins // ------------------------------- -// Screen reader only -.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; + +// Contrast +.contrast-control-color(@color: "") when (lightness(@color) >= 65%) { + @control-color: @gray-light; +} +.contrast-control-color(@color: "") when (lightness(@color) < 65%) { + @control-color: @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; @@ -75,14 +98,7 @@ outline-offset: 0; } -// Animation -// --------------------------------------- -@keyframes progress { - to { background-position: @progress-loading-size 0; } -} - // <input type="range"> styling -// --------------------------------------- .volume-thumb() { height: @volume-thumb-height; width: @volume-thumb-width; @@ -109,15 +125,16 @@ border: 0; } -// 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; +// Screen reader only +// ------------------------------- +.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; } // Styles @@ -141,12 +158,28 @@ &-video-wrapper { position: relative; } - video { + video, + audio { width: 100%; height: auto; vertical-align: middle; } + // For embeds + &-video-embed { + padding-bottom: 56.25%; /* 16:9 */ + height: 0; + + iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + } + } + // Captions &-captions { display: none; @@ -184,6 +217,7 @@ background: @controls-bg; line-height: 1; text-align: center; + box-shadow: 0 1px 1px rgba(red(@gray-dark), green(@gray-dark), blue(@gray-dark), .2); // Layout &-right { @@ -207,7 +241,7 @@ margin: 0 2px; padding: (@control-spacing / 2) @control-spacing; - transition: background .3s ease; + transition: background .3s ease, color .3s ease, opacity .3s ease; border-radius: 3px; cursor: pointer; @@ -221,12 +255,13 @@ } input + label, .inverted:checked + label { - color: @control-color-inactive; + opacity: .5; } button, .inverted + label, input:checked + label { color: @control-color; + opacity: 1; } button { border: 0; @@ -241,6 +276,7 @@ [type="checkbox"] + label:hover { background: @control-bg-hover; color: @control-color-hover; + opacity: 1; } button:focus, input:focus + label { @@ -273,7 +309,6 @@ &::before { content: "\2044"; margin-right: @control-spacing; - color: darken(@control-color, 30%); } } } diff --git a/src/sass/plyr.scss b/src/sass/plyr.scss index bf027db0..011a5368 100644 --- a/src/sass/plyr.scss +++ b/src/sass/plyr.scss @@ -4,12 +4,14 @@ // Variables // ------------------------------- + // Colors $blue: #3498DB !default; -$gray-dark: #343f4a !default; -$gray: #565d64 !default; -$gray-light: #cbd0d3 !default; -$off-white: #d6dadd !default; +$gray-dark: #343F4A !default; +$gray: #565D64 !default; +$gray-light: #6B7D86 !default; +$gray-lighter: #CBD0D3 !default; +$off-white: #D6DADD !default; // Font sizes $font-size-small: 14px !default; @@ -18,11 +20,10 @@ $font-size-large: ceil(($font-size-base * 1.5)) !default; // Controls $control-spacing: 10px !default; -$controls-bg: $gray-dark !default; -$control-bg-hover: $blue !default; -$control-color: $gray-light !default; -$control-color-inactive: $gray !default; -$control-color-hover: #fff !default; +$controls-bg: #fff !default; +$control-bg-hover: @blue !default; +.contrast-control-color($controls-bg); +.contrast-control-color-hover($control-bg-hover); // Tooltips $tooltip-bg: $controls-bg !default; @@ -40,7 +41,7 @@ $progress-loading-bg: rgba(0,0,0, .15) !default; // Volume $volume-track-height: 6px !default; -$volume-track-bg: $gray !default; +$volume-track-bg: darken($controls-bg, 10%) !default; $volume-thumb-height: ($volume-track-height * 2) !default; $volume-thumb-width: ($volume-track-height * 2) !default; $volume-thumb-bg: $control-color !default; @@ -50,18 +51,40 @@ $volume-thumb-bg-focus: $control-bg-hover !default; $bp-control-split: 560px !default; // When controls split into left/right $bp-captions-large: 768px !default; // When captions jump to the larger font size -// Utility classes & mixins +// Mixins // ------------------------------- -// Screen reader only -.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; + +// Contrast +@mixin contrast-control-color($color: "") { + @if (lightness($color) >= 65%) { + $control-color: $gray-light; + } + @else if(lightness(@color) < 65%) { + $control-color: $gray-lighter; + } +} +@mixin contrast-control-color-hover($color: "") { + @if (lightness($color) >= 65%) { + $control-color-hover: $gray; + } + @else if (lightness($color) < 65%) { + $control-color-hover: #fff; + } } + +// Font smoothing +@mixin font-smoothing($mode: on) +{ + @if ($mode == 'on') { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + } + @else if ($mode == 'off') { + -moz-osx-font-smoothing: auto; + -webkit-font-smoothing: subpixel-antialiased; + } +} + // Contain floats: nicolasgallagher.com/micro-clearfix-hack/ @mixin clearfix() { @@ -84,7 +107,6 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo } // <input type="range"> styling -// --------------------------------------- @mixin volume-thumb() { height: $volume-thumb-height; @@ -115,17 +137,16 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo border: 0; } -// Font smoothing -// --------------------------------------- -@mixin font-smoothing($mode: on) -{ - @if $mode == 'on' { - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - } @else if $mode == 'off' { - -moz-osx-font-smoothing: auto; - -webkit-font-smoothing: subpixel-antialiased; - } +// Screen reader only +// ------------------------------- +.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; } // Styles @@ -149,7 +170,8 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo &-video-wrapper { position: relative; } - video { + video, + audio { width: 100%; height: auto; vertical-align: middle; @@ -192,6 +214,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo background: $controls-bg; line-height: 1; text-align: center; + box-shadow: 0 1px 1px rgba(red($gray-dark), green($gray-dark), blue($gray-dark), .2); // Layout &-right { @@ -215,7 +238,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo margin: 0 2px; padding: ($control-spacing / 2) $control-spacing; - transition: background .3s ease; + background .3s ease, color .3s ease, opacity .3s ease; border-radius: 3px; cursor: pointer; @@ -229,12 +252,13 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo } input + label, .inverted:checked + label { - color: $control-color-inactive; + opacity: .5; } button, .inverted + label, input:checked + label { color: $control-color; + opacity: 1; } button { border: 0; @@ -249,6 +273,7 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo [type="checkbox"] + label:hover { background: $control-bg-hover; color: $control-color-hover; + opacity: 1; } button:focus, input:focus + label { @@ -281,7 +306,6 @@ $bp-captions-large: 768px !default; // When captions jump to the larger fo &::before { content: "\2044"; margin-right: $control-spacing; - color: darken($control-color, 30%); } } } |