aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'src/js')
-rw-r--r--src/js/plyr.js1201
1 files changed, 744 insertions, 457 deletions
diff --git a/src/js/plyr.js b/src/js/plyr.js
index ed328202..55bfd62e 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -1,6 +1,6 @@
// ==========================================================================
// Plyr
-// plyr.js v1.3.6
+// plyr.js v1.4.0
// https://github.com/selz/plyr
// License: The MIT License (MIT)
// ==========================================================================
@@ -9,16 +9,17 @@
(function (api) {
'use strict';
- /*global YT*/
+ /*global YT,$f*/
// Globals
- var fullscreen, config, callbacks = { youtube: [] };
+ var fullscreen, config;
// Default config
var defaults = {
enabled: true,
debug: false,
autoplay: false,
+ loop: false,
seekTime: 10,
volume: 5,
click: true,
@@ -26,41 +27,41 @@
displayDuration: true,
iconPrefix: 'icon',
selectors: {
- container: '.player',
- controls: '.player-controls',
- labels: '[data-player] .sr-only, label .sr-only',
+ container: '.plyr',
+ controls: '.plyr-controls',
+ labels: '[data-plyr] .sr-only, label .sr-only',
buttons: {
- seek: '[data-player="seek"]',
- play: '[data-player="play"]',
- pause: '[data-player="pause"]',
- restart: '[data-player="restart"]',
- rewind: '[data-player="rewind"]',
- forward: '[data-player="fast-forward"]',
- mute: '[data-player="mute"]',
- volume: '[data-player="volume"]',
- captions: '[data-player="captions"]',
- fullscreen: '[data-player="fullscreen"]'
+ seek: '[data-plyr="seek"]',
+ play: '[data-plyr="play"]',
+ pause: '[data-plyr="pause"]',
+ restart: '[data-plyr="restart"]',
+ rewind: '[data-plyr="rewind"]',
+ forward: '[data-plyr="fast-forward"]',
+ mute: '[data-plyr="mute"]',
+ volume: '[data-plyr="volume"]',
+ captions: '[data-plyr="captions"]',
+ fullscreen: '[data-plyr="fullscreen"]'
},
progress: {
- container: '.player-progress',
- buffer: '.player-progress-buffer',
- played: '.player-progress-played'
+ container: '.plyr-progress',
+ buffer: '.plyr-progress-buffer',
+ played: '.plyr-progress-played'
},
- captions: '.player-captions',
- currentTime: '.player-current-time',
- duration: '.player-duration'
+ captions: '.plyr-captions',
+ currentTime: '.plyr-current-time',
+ duration: '.plyr-duration'
},
classes: {
- videoWrapper: 'player-video-wrapper',
- embedWrapper: 'player-video-embed',
- type: 'player-{0}',
+ videoWrapper: 'plyr-video-wrapper',
+ embedWrapper: 'plyr-video-embed',
+ type: 'plyr-{0}',
stopped: 'stopped',
playing: 'playing',
muted: 'muted',
loading: 'loading',
- tooltip: 'player-tooltip',
+ tooltip: 'plyr-tooltip',
hidden: 'sr-only',
- hover: 'player-hover',
+ hover: 'plyr-hover',
captions: {
enabled: 'captions-enabled',
active: 'captions-active'
@@ -77,7 +78,8 @@
fullscreen: {
enabled: true,
fallback: true,
- hideControls: true
+ hideControls: true,
+ allowAudio: false
},
storage: {
enabled: true,
@@ -98,6 +100,18 @@
toggleMute: 'Toggle Mute',
toggleCaptions: 'Toggle Captions',
toggleFullscreen: 'Toggle Fullscreen'
+ },
+ types: {
+ embed: ['youtube','vimeo'],
+ html5: ['video', 'audio']
+ },
+ urls: {
+ vimeo: {
+ api: 'https://f.vimeocdn.com/js/froogaloop2.min.js'
+ },
+ youtube: {
+ api: 'https://www.youtube.com/iframe_api'
+ }
}
};
@@ -105,23 +119,23 @@
function _buildControls() {
// Open and add the progress and seek elements
var html = [
- '<div class="player-controls">',
- '<div class="player-progress">',
+ '<div class="plyr-controls">',
+ '<div class="plyr-progress">',
'<label for="seek{id}" class="sr-only">Seek</label>',
- '<input id="seek{id}" class="player-progress-seek" type="range" min="0" max="100" step="0.5" value="0" data-player="seek">',
- '<progress class="player-progress-played" max="100" value="0">',
+ '<input id="seek{id}" class="plyr-progress-seek" type="range" min="0" max="100" step="0.5" value="0" data-plyr="seek">',
+ '<progress class="plyr-progress-played" max="100" value="0">',
'<span>0</span>% ' + config.i18n.played,
'</progress>',
- '<progress class="player-progress-buffer" max="100" value="0">',
+ '<progress class="plyr-progress-buffer" max="100" value="0">',
'<span>0</span>% ' + config.i18n.buffered,
'</progress>',
'</div>',
- '<span class="player-controls-left">'];
+ '<span class="plyr-controls-left">'];
// Restart button
if (_inArray(config.controls, 'restart')) {
html.push(
- '<button type="button" data-player="restart">',
+ '<button type="button" data-plyr="restart">',
'<svg><use xlink:href="#' + config.iconPrefix + '-restart" /></svg>',
'<span class="sr-only">' + config.i18n.restart + '</span>',
'</button>'
@@ -131,7 +145,7 @@
// Rewind button
if (_inArray(config.controls, 'rewind')) {
html.push(
- '<button type="button" data-player="rewind">',
+ '<button type="button" data-plyr="rewind">',
'<svg><use xlink:href="#' + config.iconPrefix + '-rewind" /></svg>',
'<span class="sr-only">' + config.i18n.rewind + '</span>',
'</button>'
@@ -141,11 +155,11 @@
// Play/pause button
if (_inArray(config.controls, 'play')) {
html.push(
- '<button type="button" data-player="play">',
+ '<button type="button" data-plyr="play">',
'<svg><use xlink:href="#' + config.iconPrefix + '-play" /></svg>',
'<span class="sr-only">' + config.i18n.play + '</span>',
'</button>',
- '<button type="button" data-player="pause">',
+ '<button type="button" data-plyr="pause">',
'<svg><use xlink:href="#' + config.iconPrefix + '-pause" /></svg>',
'<span class="sr-only">' + config.i18n.pause + '</span>',
'</button>'
@@ -155,7 +169,7 @@
// Fast forward button
if (_inArray(config.controls, 'fast-forward')) {
html.push(
- '<button type="button" data-player="fast-forward">',
+ '<button type="button" data-plyr="fast-forward">',
'<svg><use xlink:href="#' + config.iconPrefix + '-fast-forward" /></svg>',
'<span class="sr-only">' + config.i18n.forward + '</span>',
'</button>'
@@ -165,9 +179,9 @@
// Media current time display
if (_inArray(config.controls, 'current-time')) {
html.push(
- '<span class="player-time">',
+ '<span class="plyr-time">',
'<span class="sr-only">' + config.i18n.currentTime + '</span>',
- '<span class="player-current-time">00:00</span>',
+ '<span class="plyr-current-time">00:00</span>',
'</span>'
);
}
@@ -175,9 +189,9 @@
// Media duration display
if (_inArray(config.controls, 'duration')) {
html.push(
- '<span class="player-time">',
+ '<span class="plyr-time">',
'<span class="sr-only">' + config.i18n.duration + '</span>',
- '<span class="player-duration">00:00</span>',
+ '<span class="plyr-duration">00:00</span>',
'</span>'
);
}
@@ -185,13 +199,13 @@
// Close left controls
html.push(
'</span>',
- '<span class="player-controls-right">'
+ '<span class="plyr-controls-right">'
);
// Toggle mute button
if (_inArray(config.controls, 'mute')) {
html.push(
- '<button type="button" data-player="mute">',
+ '<button type="button" data-plyr="mute">',
'<svg class="icon-muted"><use xlink:href="#' + config.iconPrefix + '-muted" /></svg>',
'<svg><use xlink:href="#' + config.iconPrefix + '-volume" /></svg>',
'<span class="sr-only">' + config.i18n.toggleMute + '</span>',
@@ -203,14 +217,14 @@
if (_inArray(config.controls, 'volume')) {
html.push(
'<label for="volume{id}" class="sr-only">' + config.i18n.volume + '</label>',
- '<input id="volume{id}" class="player-volume" type="range" min="0" max="10" value="5" data-player="volume">'
+ '<input id="volume{id}" class="plyr-volume" type="range" min="0" max="10" value="5" data-plyr="volume">'
);
}
// Toggle captions button
if (_inArray(config.controls, 'captions')) {
html.push(
- '<button type="button" data-player="captions">',
+ '<button type="button" data-plyr="captions">',
'<svg class="icon-captions-on"><use xlink:href="#' + config.iconPrefix + '-captions-on" /></svg>',
'<svg><use xlink:href="#' + config.iconPrefix + '-captions-off" /></svg>',
'<span class="sr-only">' + config.i18n.toggleCaptions + '</span>',
@@ -221,7 +235,7 @@
// Toggle fullscreen button
if (_inArray(config.controls, 'fullscreen')) {
html.push(
- '<button type="button" data-player="fullscreen">',
+ '<button type="button" data-plyr="fullscreen">',
'<svg class="icon-exit-fullscreen"><use xlink:href="#' + config.iconPrefix + '-exit-fullscreen" /></svg>',
'<svg><use xlink:href="#' + config.iconPrefix + '-enter-fullscreen" /></svg>',
'<span class="sr-only">' + config.i18n.toggleFullscreen + '</span>',
@@ -317,12 +331,12 @@
// Check for mime type support against a player instance
// Credits: http://diveintohtml5.info/everything.html
- // Related: http://www.leanbackplayer.com/test/h5mt.html
- function _supportMime(player, mimeType) {
- var media = player.media;
+ // Related: http://www.leanbackplyr.com/test/h5mt.html
+ function _supportMime(plyr, mimeType) {
+ var media = plyr.media;
// Only check video types for video players
- if (player.type == 'video') {
+ if (plyr.type == 'video') {
// Check type
switch (mimeType) {
case 'video/webm': return !!(media.canPlayType && media.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''));
@@ -332,7 +346,7 @@
}
// Only check audio types for audio players
- else if (player.type == 'audio') {
+ else if (plyr.type == 'audio') {
// Check type
switch (mimeType) {
case 'audio/mpeg': return !!(media.canPlayType && media.canPlayType('audio/mpeg;').replace(/no/, ''));
@@ -428,10 +442,27 @@
// Set attributes
function _setAttributes(element, attributes) {
for (var key in attributes) {
- element.setAttribute(key, attributes[key]);
+ element.setAttribute(key, (typeof attributes[key] === 'boolean' && attributes[key]) ? '' : attributes[key]);
}
}
+ // Insert a HTML element
+ function _insertElement(type, parent, attributes) {
+ // Create a new <element>
+ var element = document.createElement(type);
+
+ // Set all passed attributes
+ _setAttributes(element, attributes);
+
+ // Inject the new element
+ _prependChild(parent, element);
+ }
+
+ // Get a classname from selector
+ function _getClassname(selector) {
+ return selector.replace('.', '');
+ }
+
// Toggle class on an element
function _toggleClass(element, name, state) {
if (element) {
@@ -614,68 +645,73 @@
// Player instance
function Plyr(container) {
- var player = this;
- player.container = container;
+ var plyr = this;
+ plyr.container = container;
// Captions functions
// Seek the manual caption time and update UI
function _seekManualCaptions(time) {
// If it's not video, or we're using textTracks, bail.
- if (player.usingTextTracks || player.type !== 'video' || !player.supported.full) {
+ if (plyr.usingTextTracks || plyr.type !== 'video' || !plyr.supported.full) {
return;
}
// Reset subcount
- player.subcount = 0;
+ plyr.subcount = 0;
// Check time is a number, if not use currentTime
// IE has a bug where currentTime doesn't go to 0
// https://twitter.com/Sam_Potts/status/573715746506731521
- time = typeof time === 'number' ? time : player.media.currentTime;
+ time = typeof time === 'number' ? time : plyr.media.currentTime;
+
+ // If there's no subs available, bail
+ if (!plyr.captions[plyr.subcount]) {
+ return;
+ }
- while (_timecodeMax(player.captions[player.subcount][0]) < time.toFixed(1)) {
- player.subcount++;
- if (player.subcount > player.captions.length-1) {
- player.subcount = player.captions.length-1;
+ while (_timecodeMax(plyr.captions[plyr.subcount][0]) < time.toFixed(1)) {
+ plyr.subcount++;
+ if (plyr.subcount > plyr.captions.length-1) {
+ plyr.subcount = plyr.captions.length-1;
break;
}
}
// Check if the next caption is in the current time range
- if (player.media.currentTime.toFixed(1) >= _timecodeMin(player.captions[player.subcount][0]) &&
- player.media.currentTime.toFixed(1) <= _timecodeMax(player.captions[player.subcount][0])) {
- player.currentCaption = player.captions[player.subcount][1];
+ if (plyr.media.currentTime.toFixed(1) >= _timecodeMin(plyr.captions[plyr.subcount][0]) &&
+ plyr.media.currentTime.toFixed(1) <= _timecodeMax(plyr.captions[plyr.subcount][0])) {
+ plyr.currentCaption = plyr.captions[plyr.subcount][1];
// Trim caption text
- var content = player.currentCaption.trim();
+ var content = plyr.currentCaption.trim();
// Render the caption (only if changed)
- if (player.captionsContainer.innerHTML != content) {
+ if (plyr.captionsContainer.innerHTML != content) {
// Empty caption
// Otherwise NVDA reads it twice
- player.captionsContainer.innerHTML = '';
+ plyr.captionsContainer.innerHTML = '';
// Set new caption text
- player.captionsContainer.innerHTML = content;
+ plyr.captionsContainer.innerHTML = content;
}
}
else {
- player.captionsContainer.innerHTML = '';
+ plyr.captionsContainer.innerHTML = '';
}
}
// Display captions container and button (for initialization)
function _showCaptions() {
// If there's no caption toggle, bail
- if (!player.buttons.captions) {
+ if (!plyr.buttons.captions) {
return;
}
- _toggleClass(player.container, config.classes.captions.enabled, true);
+ _toggleClass(plyr.container, config.classes.captions.enabled, true);
if (config.captions.defaultActive) {
- _toggleClass(player.container, config.classes.captions.active, true);
- _toggleState(player.buttons.captions, true);
+ _toggleClass(plyr.container, config.classes.captions.active, true);
+ _toggleState(plyr.buttons.captions, true);
}
}
@@ -707,7 +743,7 @@
// Find all elements
function _getElements(selector) {
- return player.container.querySelectorAll(selector);
+ return plyr.container.querySelectorAll(selector);
}
// Find a single element
@@ -745,7 +781,7 @@
html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000)));
// Inject into the container
- player.container.insertAdjacentHTML('beforeend', html);
+ plyr.container.insertAdjacentHTML('beforeend', html);
// Setup tooltips
if (config.tooltips) {
@@ -763,44 +799,44 @@
// Find the UI controls and store references
function _findElements() {
try {
- player.controls = _getElement(config.selectors.controls);
+ plyr.controls = _getElement(config.selectors.controls);
// Buttons
- player.buttons = {};
- player.buttons.seek = _getElement(config.selectors.buttons.seek);
- player.buttons.play = _getElement(config.selectors.buttons.play);
- player.buttons.pause = _getElement(config.selectors.buttons.pause);
- player.buttons.restart = _getElement(config.selectors.buttons.restart);
- player.buttons.rewind = _getElement(config.selectors.buttons.rewind);
- player.buttons.forward = _getElement(config.selectors.buttons.forward);
- player.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
+ plyr.buttons = {};
+ plyr.buttons.seek = _getElement(config.selectors.buttons.seek);
+ plyr.buttons.play = _getElement(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);
+ plyr.buttons.forward = _getElement(config.selectors.buttons.forward);
+ plyr.buttons.fullscreen = _getElement(config.selectors.buttons.fullscreen);
// Inputs
- player.buttons.mute = _getElement(config.selectors.buttons.mute);
- player.buttons.captions = _getElement(config.selectors.buttons.captions);
- player.checkboxes = _getElements('[type="checkbox"]');
+ plyr.buttons.mute = _getElement(config.selectors.buttons.mute);
+ plyr.buttons.captions = _getElement(config.selectors.buttons.captions);
+ plyr.checkboxes = _getElements('[type="checkbox"]');
// Progress
- player.progress = {};
- player.progress.container = _getElement(config.selectors.progress.container);
+ plyr.progress = {};
+ plyr.progress.container = _getElement(config.selectors.progress.container);
// Progress - Buffering
- player.progress.buffer = {};
- player.progress.buffer.bar = _getElement(config.selectors.progress.buffer);
- player.progress.buffer.text = player.progress.buffer.bar && player.progress.buffer.bar.getElementsByTagName('span')[0];
+ plyr.progress.buffer = {};
+ plyr.progress.buffer.bar = _getElement(config.selectors.progress.buffer);
+ plyr.progress.buffer.text = plyr.progress.buffer.bar && plyr.progress.buffer.bar.getElementsByTagName('span')[0];
// Progress - Played
- player.progress.played = {};
- player.progress.played.bar = _getElement(config.selectors.progress.played);
- player.progress.played.text = player.progress.played.bar && player.progress.played.bar.getElementsByTagName('span')[0];
+ plyr.progress.played = {};
+ plyr.progress.played.bar = _getElement(config.selectors.progress.played);
+ plyr.progress.played.text = plyr.progress.played.bar && plyr.progress.played.bar.getElementsByTagName('span')[0];
// Volume
- player.volume = _getElement(config.selectors.buttons.volume);
+ plyr.volume = _getElement(config.selectors.buttons.volume);
// Timing
- player.duration = _getElement(config.selectors.duration);
- player.currentTime = _getElement(config.selectors.currentTime);
- player.seekTime = _getElements(config.selectors.seekTime);
+ plyr.duration = _getElement(config.selectors.duration);
+ plyr.currentTime = _getElement(config.selectors.currentTime);
+ plyr.seekTime = _getElements(config.selectors.seekTime);
return true;
}
@@ -808,7 +844,7 @@
_log('It looks like there\'s a problem with your controls html. Bailing.', true);
// Restore native video controls
- player.media.setAttribute('controls', '');
+ plyr.media.setAttribute('controls', '');
return false;
}
@@ -817,126 +853,178 @@
// Setup aria attribute for play
function _setupPlayAria() {
// If there's no play button, bail
- if (!player.buttons.play) {
+ if (!plyr.buttons.play) {
return;
}
// Find the current text
- var label = player.buttons.play.innerText || config.i18n.play;
+ var label = plyr.buttons.play.innerText || config.i18n.play;
// If there's a media title set, use that for the label
if (typeof(config.title) !== 'undefined' && config.title.length) {
label += ', ' + config.title;
}
- player.buttons.play.setAttribute('aria-label', label);
+ plyr.buttons.play.setAttribute('aria-label', label);
}
// Setup media
function _setupMedia() {
// If there's no media, bail
- if (!player.media) {
+ if (!plyr.media) {
_log('No audio or video element found!', true);
return false;
}
- if (player.supported.full) {
+ if (plyr.supported.full) {
// Remove native video controls
- player.media.removeAttribute('controls');
+ plyr.media.removeAttribute('controls');
// Add type class
- _toggleClass(player.container, config.classes.type.replace('{0}', player.type), true);
+ _toggleClass(plyr.container, config.classes.type.replace('{0}', plyr.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) && !config.autoplay));
+ _toggleClass(plyr.container, config.classes.stopped, config.autoplay);
// Add iOS class
- if (player.browser.ios) {
- _toggleClass(player.container, 'ios', true);
+ if (plyr.browser.ios) {
+ _toggleClass(plyr.container, 'ios', true);
}
// Inject the player wrapper
- if (player.type === 'video') {
+ if (plyr.type === 'video') {
// Create the wrapper div
var wrapper = document.createElement('div');
wrapper.setAttribute('class', config.classes.videoWrapper);
// Wrap the video in a container
- _wrap(player.media, wrapper);
+ _wrap(plyr.media, wrapper);
// Cache the container
- player.videoContainer = wrapper;
+ plyr.videoContainer = wrapper;
}
}
- // YouTube
- if (player.type == 'youtube') {
- _setupYouTube(player.media.getAttribute('data-video-id'));
- }
+ // Embeds
+ if (_inArray(config.types.embed, plyr.type)) {
+ _setupEmbed(plyr.embedId, plyr.type);
- // Autoplay
- if (player.media.getAttribute('autoplay') !== null || config.autoplay) {
- _play();
+ // Clean up
+ plyr.embedId = null;
+ }
+ else {
+ // Autoplay
+ if (config.autoplay) {
+ _play();
+ }
}
}
- // Setup YouTube
- function _setupYouTube(id) {
+ // Setup YouTube/Vimeo
+ function _setupEmbed(videoId) {
+ var container = document.createElement('div'),
+ id = plyr.type + '-' + Math.floor(Math.random() * (10000));
+
// Remove old containers
- var containers = _getElements('[id^="youtube"]');
+ var containers = _getElements('[id^="' + plyr.type + '-"]');
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);
+ _toggleClass(plyr.media, config.classes.videoWrapper, true);
+ _toggleClass(plyr.media, config.classes.embedWrapper, true);
- if (typeof YT === 'object') {
- _YTReady(id, container);
- }
- else {
- // Load the API
- _injectScript('https://www.youtube.com/iframe_api');
+ // YouTube
+ if (plyr.type === 'youtube') {
+ // Create the YouTube container
+ plyr.media.appendChild(container);
- // Add callback to queue
- callbacks.youtube.push(function() { _YTReady(id, container); });
+ // Set ID
+ container.setAttribute('id', id);
- // Setup callback for the API
- window.onYouTubeIframeAPIReady = function () {
- for (var i = callbacks.youtube.length - 1; i >= 0; i--) {
- // Fire callback
- callbacks.youtube[i]();
+ // Setup API
+ if (typeof YT === 'object') {
+ _youTubeReady(videoId, container);
+ }
+ else {
+ // Load the API
+ _injectScript(config.urls.youtube.api);
- // Remove from queue
- callbacks.youtube.splice(i, 1);
- }
- };
+ // Setup callback for the API
+ window.onYouTubeIframeAPIReady = function () { _youTubeReady(videoId, container); };
+ }
+ }
+ // Vimeo
+ else if (plyr.type === 'vimeo') {
+ // Inject the iframe
+ var iframe = document.createElement('iframe');
+
+ // Watch for iframe load
+ iframe.loaded = false;
+ _on(iframe, 'load', function() { iframe.loaded = true; });
+
+ _setAttributes(iframe, {
+ 'src': 'https://player.vimeo.com/video/' + videoId + '?player_id=' + id + '&api=1&badge=0&byline=0&portrait=0&title=0',
+ 'id': id,
+ 'webkitallowfullscreen': '',
+ 'mozallowfullscreen': '',
+ 'allowfullscreen': '',
+ 'frameborder': 0
+ });
+ container.appendChild(iframe);
+ plyr.media.appendChild(container);
+
+ // Setup API
+ if (typeof Froogaloop === 'function') {
+ _on(iframe, 'load', _vimeoReady);
+ }
+ else {
+ // Load the API
+ _injectScript(config.urls.vimeo.api);
+
+ // Wait for fragaloop load
+ var timer = window.setInterval(function() {
+ if ('$f' in window && iframe.loaded) {
+ window.clearInterval(timer);
+
+ _vimeoReady.call(iframe);
+ }
+ }, 50);
+ }
}
}
- // Handle API ready
- function _YTReady(id, container) {
- _log('YouTube API Ready');
+ // When embeds are ready
+ function _embedReady() {
+ // Inject and update UI
+ if (plyr.supported.full) {
+ // Only setup controls once
+ if (!plyr.container.querySelectorAll(config.selectors.controls).length) {
+ _setupInterface();
+ }
+ }
+
+ // Set the volume
+ _setVolume();
+ _updateVolume();
+ }
+ // Handle YouTube API ready
+ function _youTubeReady(videoId, container) {
// Setup timers object
// We have to poll YouTube for updates
- if (!('timer' in player)) {
- player.timer = {};
+ if (!('timer' in plyr)) {
+ plyr.timer = {};
}
// Setup instance
// https://developers.google.com/youtube/iframe_api_reference
- player.embed = new YT.Player(container.id, {
- videoId: id,
+ plyr.embed = new YT.Player(container.id, {
+ videoId: videoId,
playerVars: {
autoplay: (config.autoplay ? 1 : 0),
- controls: (player.supported.full ? 0 : 1),
+ controls: (plyr.supported.full ? 0 : 1),
rel: 0,
showinfo: 0,
iv_load_policy: 3,
@@ -952,44 +1040,40 @@
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 = true;
- player.media.currentTime = instance.getCurrentTime();
- player.media.muted = instance.isMuted();
+ plyr.media.play = function() { instance.playVideo(); };
+ plyr.media.pause = function() { instance.pauseVideo(); };
+ plyr.media.stop = function() { instance.stopVideo(); };
+ plyr.media.duration = instance.getDuration();
+ plyr.media.paused = !config.autoplay;
+ plyr.media.currentTime = instance.getCurrentTime();
+ plyr.media.muted = instance.isMuted();
// Trigger timeupdate
- _triggerEvent(player.media, 'timeupdate');
+ _triggerEvent(plyr.media, 'timeupdate');
// Reset timer
- window.clearInterval(player.timer.buffering);
+ window.clearInterval(plyr.timer.buffering);
// Setup buffering
- player.timer.buffering = window.setInterval(function() {
+ plyr.timer.buffering = window.setInterval(function() {
// Get loaded % from YouTube
- player.media.buffered = instance.getVideoLoadedFraction();
+ plyr.media.buffered = instance.getVideoLoadedFraction();
// Trigger progress
- _triggerEvent(player.media, 'progress');
+ _triggerEvent(plyr.media, 'progress');
// Bail if we're at 100%
- if (player.media.buffered === 1) {
- window.clearInterval(player.timer.buffering);
+ if (plyr.media.buffered === 1) {
+ window.clearInterval(plyr.timer.buffering);
}
}, 200);
- if (player.supported.full) {
- // Only setup controls once
- if (!player.container.querySelectorAll(config.selectors.controls).length) {
- _setupInterface();
- }
+ // Update UI
+ _embedReady();
- // Display duration if available
- if (config.displayDuration) {
- _displayDuration();
- }
+ // Display duration if available
+ if (config.displayDuration) {
+ _displayDuration();
}
},
'onStateChange': function(event) {
@@ -997,7 +1081,7 @@
var instance = event.target;
// Reset timer
- window.clearInterval(player.timer.playing);
+ window.clearInterval(plyr.timer.playing);
// Handle events
// -1 Unstarted
@@ -1008,53 +1092,124 @@
// 5 Video cued
switch (event.data) {
case 0:
- player.media.paused = true;
- _triggerEvent(player.media, 'ended');
+ plyr.media.paused = true;
+ _triggerEvent(plyr.media, 'ended');
break;
case 1:
- player.media.paused = false;
- _triggerEvent(player.media, 'play');
+ plyr.media.paused = false;
+ _triggerEvent(plyr.media, 'play');
// Poll to get playback progress
- player.timer.playing = window.setInterval(function() {
+ plyr.timer.playing = window.setInterval(function() {
// Set the current time
- player.media.currentTime = instance.getCurrentTime();
+ plyr.media.currentTime = instance.getCurrentTime();
// Trigger timeupdate
- _triggerEvent(player.media, 'timeupdate');
+ _triggerEvent(plyr.media, 'timeupdate');
}, 200);
break;
case 2:
- player.media.paused = true;
- _triggerEvent(player.media, 'pause');
+ 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.pause = function() { plyr.embed.api('pause'); };
+ plyr.media.stop = function() { plyr.embed.api('stop') };
+ plyr.media.paused = !config.autoplay;
+ 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
+ if (plyr.supported.full && config.displayDuration) {
+ _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.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 (player.type === 'video') {
+ if (plyr.type === 'video') {
// Inject the container
- player.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + config.selectors.captions.replace('.', '') + '"><span></span></div>');
+ if (!_getElement(config.selectors.captions)) {
+ plyr.videoContainer.insertAdjacentHTML('afterbegin', '<div class="' + _getClassname(config.selectors.captions) + '"><span></span></div>');
+ }
// Cache selector
- player.captionsContainer = _getElement(config.selectors.captions).querySelector('span');
+ plyr.captionsContainer = _getElement(config.selectors.captions).querySelector('span');
// Determine if HTML5 textTracks is supported
- player.usingTextTracks = false;
- if (player.media.textTracks) {
- player.usingTextTracks = true;
+ plyr.usingTextTracks = false;
+ if (plyr.media.textTracks) {
+ plyr.usingTextTracks = true;
}
// Get URL of caption file if exists
var captionSrc = '',
kind,
- children = player.media.childNodes;
+ children = plyr.media.childNodes;
for (var i = 0; i < children.length; i++) {
if (children[i].nodeName.toLowerCase() === 'track') {
@@ -1066,9 +1221,9 @@
}
// Record if caption file exists or not
- player.captionExists = true;
+ plyr.captionExists = true;
if (captionSrc === '') {
- player.captionExists = false;
+ plyr.captionExists = false;
_log('No caption track found.');
}
else {
@@ -1076,36 +1231,36 @@
}
// If no caption file exists, hide container for caption text
- if (!player.captionExists) {
- _toggleClass(player.container, config.classes.captions.enabled);
+ 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 <track> elements are removed from the dom below
- var tracks = player.media.textTracks;
+ var tracks = plyr.media.textTracks;
for (var x = 0; x < tracks.length; x++) {
tracks[x].mode = 'hidden';
}
// Enable UI
- _showCaptions(player);
+ _showCaptions(plyr);
// Disable unsupported browsers than report false positive
- if ((player.browser.name === 'IE' && player.browser.version >= 10) ||
- (player.browser.name === 'Firefox' && player.browser.version >= 31) ||
- (player.browser.name === 'Chrome' && player.browser.version >= 43) ||
- (player.browser.name === 'Safari' && player.browser.version >= 7)) {
+ 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
- player.usingTextTracks = false;
+ plyr.usingTextTracks = false;
}
// Rendering caption tracks
// Native support required - http://caniuse.com/webvtt
- if (player.usingTextTracks) {
+ if (plyr.usingTextTracks) {
_log('TextTracks supported.');
for (var y = 0; y < tracks.length; y++) {
@@ -1114,11 +1269,11 @@
if (track.kind === 'captions' || track.kind === 'subtitles') {
_on(track, 'cuechange', function() {
// Clear container
- player.captionsContainer.innerHTML = '';
+ plyr.captionsContainer.innerHTML = '';
// Display a cue, if there is one
if (this.activeCues[0] && this.activeCues[0].hasOwnProperty('text')) {
- player.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML().trim());
+ plyr.captionsContainer.appendChild(this.activeCues[0].getCueAsHTML().trim());
}
});
}
@@ -1129,8 +1284,8 @@
_log('TextTracks not supported so rendering captions manually.');
// Render captions from array at appropriate time
- player.currentCaption = '';
- player.captions = [];
+ plyr.currentCaption = '';
+ plyr.captions = [];
if (captionSrc !== '') {
// Create XMLHttpRequest Object
@@ -1147,12 +1302,12 @@
for (var r = 0; r < records.length; r++) {
record = records[r];
- player.captions[r] = [];
- player.captions[r] = record.split('\n');
+ plyr.captions[r] = [];
+ plyr.captions[r] = record.split('\n');
}
// Remove first element ('VTT')
- player.captions.shift();
+ plyr.captions.shift();
_log('Successfully loaded the caption file via AJAX.');
}
@@ -1169,15 +1324,15 @@
}
// If Safari 7+, removing track from DOM [see 'turn off native caption rendering' above]
- if (player.browser.name === 'Safari' && player.browser.version >= 7) {
+ if (plyr.browser.name === 'Safari' && plyr.browser.version >= 7) {
_log('Safari 7+ detected; removing track from DOM.');
// Find all <track> elements
- tracks = player.media.getElementsByTagName('track');
+ tracks = plyr.media.getElementsByTagName('track');
// Loop through and remove one by one
for (var t = 0; t < tracks.length; t++) {
- player.media.removeChild(tracks[t]);
+ plyr.media.removeChild(tracks[t]);
}
}
}
@@ -1186,7 +1341,7 @@
// Setup fullscreen
function _setupFullscreen() {
- if (player.type != 'audio' && config.fullscreen.enabled) {
+ if ((plyr.type != 'audio' || config.fullscreen.allowAudio) && config.fullscreen.enabled) {
// Check for native support
var nativeSupport = fullscreen.supportsFullScreen;
@@ -1194,30 +1349,30 @@
_log((nativeSupport ? 'Native' : 'Fallback') + ' fullscreen enabled.');
// Add styling hook
- _toggleClass(player.container, config.classes.fullscreen.enabled, true);
+ _toggleClass(plyr.container, config.classes.fullscreen.enabled, true);
}
else {
_log('Fullscreen not supported and fallback disabled.');
}
// Toggle state
- _toggleState(player.buttons.fullscreen, false);
+ _toggleState(plyr.buttons.fullscreen, false);
// Set control hide class hook
if (config.fullscreen.hideControls) {
- _toggleClass(player.container, config.classes.fullscreen.hideControls, true);
+ _toggleClass(plyr.container, config.classes.fullscreen.hideControls, true);
}
}
}
// Play media
function _play() {
- player.media.play();
+ plyr.media.play();
}
// Pause media
function _pause() {
- player.media.pause();
+ plyr.media.pause();
}
// Toggle playback
@@ -1232,7 +1387,7 @@
}
// True toggle
else {
- player.media[player.media.paused ? 'play' : 'pause']();
+ plyr.media[plyr.media.paused ? 'play' : 'pause']();
}
}
@@ -1242,7 +1397,7 @@
if (typeof seekTime !== 'number') {
seekTime = config.seekTime;
}
- _seek(player.media.currentTime - seekTime);
+ _seek(plyr.media.currentTime - seekTime);
}
// Fast forward
@@ -1251,14 +1406,14 @@
if (typeof seekTime !== 'number') {
seekTime = config.seekTime;
}
- _seek(player.media.currentTime + seekTime);
+ _seek(plyr.media.currentTime + seekTime);
}
// Seek to time
// The input parameter can be an event or a number
function _seek(input) {
var targetTime = 0,
- paused = player.media.paused;
+ paused = plyr.media.paused;
// Explicit position
if (typeof input === 'number') {
@@ -1268,38 +1423,46 @@
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) * player.media.duration);
+ targetTime = ((input.target.value / input.target.max) * plyr.media.duration);
}
// Normalise targetTime
if (targetTime < 0) {
targetTime = 0;
}
- else if (targetTime > player.media.duration) {
- targetTime = player.media.duration;
+ else if (targetTime > plyr.media.duration) {
+ targetTime = plyr.media.duration;
}
// Set the current time
// Try/catch incase the media isn't set and we're calling seek() from source() and IE moans
try {
- player.media.currentTime = targetTime.toFixed(1);
+ plyr.media.currentTime = targetTime.toFixed(1);
}
catch(e) {}
- // YouTube
- if (player.type == 'youtube') {
- player.embed.seekTo(targetTime);
+ // Trigger timeupdate for embed and restore pause state
+ if ('embed' in plyr) {
+ // YouTube
+ if (plyr.type === 'youtube') {
+ plyr.embed.seekTo(targetTime);
+ }
- if (paused) {
- _pause();
+ // Vimeo
+ if (plyr.type === 'vimeo') {
+ plyr.embed.api('seekTo', targetTime);
}
// Trigger timeupdate
- _triggerEvent(player.media, 'timeupdate');
+ _triggerEvent(plyr.media, 'timeupdate');
+
+ if (paused) {
+ _pause();
+ }
}
// Logging
- _log('Seeking to ' + player.media.currentTime + ' seconds');
+ _log('Seeking to ' + plyr.media.currentTime + ' seconds');
// Special handling for 'manual' captions
_seekManualCaptions(targetTime);
@@ -1307,8 +1470,8 @@
// Check playing state
function _checkPlaying() {
- _toggleClass(player.container, config.classes.playing, !player.media.paused);
- _toggleClass(player.container, config.classes.stopped, player.media.paused);
+ _toggleClass(plyr.container, config.classes.playing, !plyr.media.paused);
+ _toggleClass(plyr.container, config.classes.stopped, plyr.media.paused);
}
// Toggle fullscreen
@@ -1318,13 +1481,13 @@
// If it's a fullscreen change event, it's probably a native close
if (event && event.type === fullscreen.fullScreenEventName) {
- player.isFullscreen = fullscreen.isFullScreen(player.container);
+ plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
}
// If there's native support, use it
else if (nativeSupport) {
// Request fullscreen
- if (!fullscreen.isFullScreen(player.container)) {
- fullscreen.requestFullScreen(player.container);
+ if (!fullscreen.isFullScreen(plyr.container)) {
+ fullscreen.requestFullScreen(plyr.container);
}
// Bail from fullscreen
else {
@@ -1332,14 +1495,14 @@
}
// Check if we're actually full screen (it could fail)
- player.isFullscreen = fullscreen.isFullScreen(player.container);
+ plyr.isFullscreen = fullscreen.isFullScreen(plyr.container);
}
else {
// Otherwise, it's a simple toggle
- player.isFullscreen = !player.isFullscreen;
+ plyr.isFullscreen = !plyr.isFullscreen;
// Bind/unbind escape key
- if (player.isFullscreen) {
+ if (plyr.isFullscreen) {
_on(document, 'keyup', _handleEscapeFullscreen);
document.body.style.overflow = 'hidden';
}
@@ -1350,10 +1513,10 @@
}
// Set class hook
- _toggleClass(player.container, config.classes.fullscreen.active, player.isFullscreen);
+ _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
// Set button state
- _toggleState(player.buttons.fullscreen, player.isFullscreen);
+ _toggleState(plyr.buttons.fullscreen, plyr.isFullscreen);
// Toggle controls visibility based on mouse movement and location
var hoverTimer, isMouseOver = false;
@@ -1361,7 +1524,7 @@
// Show the player controls
function _showControls() {
// Set shown class
- _toggleClass(player.container, config.classes.hover, true);
+ _toggleClass(plyr.container, config.classes.hover, true);
// Clear timer every movement
window.clearTimeout(hoverTimer);
@@ -1369,7 +1532,7 @@
// If the mouse is not over the controls, set a timeout to hide them
if (!isMouseOver) {
hoverTimer = window.setTimeout(function() {
- _toggleClass(player.container, config.classes.hover, false);
+ _toggleClass(plyr.container, config.classes.hover, false);
}, 2000);
}
}
@@ -1381,24 +1544,59 @@
if (config.fullscreen.hideControls) {
// Hide on entering full screen
- _toggleClass(player.controls, config.classes.hover, false);
+ _toggleClass(plyr.controls, config.classes.hover, false);
// Keep an eye on the mouse location in relation to controls
- _toggleHandler(player.controls, 'mouseenter mouseleave', _setMouseOver, player.isFullscreen);
+ _toggleHandler(plyr.controls, 'mouseenter mouseleave', _setMouseOver, plyr.isFullscreen);
// Show the controls on mouse move
- _toggleHandler(player.container, 'mousemove', _showControls, player.isFullscreen);
+ _toggleHandler(plyr.container, 'mousemove', _showControls, plyr.isFullscreen);
}
}
// Bail from faux-fullscreen
function _handleEscapeFullscreen(event) {
// If it's a keypress and not escape, bail
- if ((event.which || event.charCode || event.keyCode) === 27 && player.isFullscreen) {
+ if ((event.which || event.charCode || event.keyCode) === 27 && plyr.isFullscreen) {
_toggleFullscreen();
}
}
+ // Mute
+ function _toggleMute(muted) {
+ // If the method is called without parameter, toggle based on current value
+ if (typeof muted !== 'boolean') {
+ muted = !plyr.media.muted;
+ }
+
+ // Set button state
+ _toggleState(plyr.buttons.mute, muted);
+
+ // Set mute on the player
+ plyr.media.muted = muted;
+
+ // YouTube
+ if (plyr.type === 'youtube') {
+ plyr.embed[plyr.media.muted ? 'mute' : 'unMute']();
+
+ // Trigger timeupdate
+ _triggerEvent(plyr.media, 'volumechange');
+ }
+
+ // Vimeo
+ if (plyr.type === 'vimeo') {
+ if (plyr.media.muted) {
+ plyr.embed.api('setVolume', 0);
+ }
+ else {
+ plyr.embed.api('setVolume', parseFloat(config.volume / 10));
+ }
+
+ // Trigger timeupdate
+ _triggerEvent(plyr.media, 'volumechange');
+ }
+ }
+
// Set volume
function _setVolume(volume) {
// Use default if no value specified
@@ -1421,52 +1619,40 @@
}
// Set the player volume
- player.media.volume = parseFloat(volume / 10);
+ plyr.media.volume = parseFloat(volume / 10);
+
+ // Store in config
+ config.volume = volume;
// YouTube
- if (player.type == 'youtube') {
- player.embed.setVolume(player.media.volume * 100);
+ if (plyr.type === 'youtube') {
+ plyr.embed.setVolume(plyr.media.volume * 100);
+ }
- // Trigger timeupdate
- _triggerEvent(player.media, 'volumechange');
+ // Vimeo
+ if (plyr.type === 'vimeo') {
+ plyr.embed.api('setVolume', plyr.media.volume);
+ }
+
+ // Trigger volumechange for embeds
+ if ('embed' in plyr) {
+ _triggerEvent(plyr.media, 'volumechange');
}
// Toggle muted state
- if (player.media.muted && volume > 0) {
+ if (plyr.media.muted && volume > 0) {
_toggleMute();
}
}
- // Mute
- function _toggleMute(muted) {
- // If the method is called without parameter, toggle based on current value
- if (typeof muted !== 'boolean') {
- muted = !player.media.muted;
- }
-
- // Set button state
- _toggleState(player.buttons.mute, muted);
-
- // 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
function _updateVolume() {
// Get the current volume
- var volume = player.media.muted ? 0 : (player.media.volume * 10);
+ var volume = plyr.media.muted ? 0 : (plyr.media.volume * 10);
// Update the <input type="range"> if present
- if (player.supported.full && player.volume) {
- player.volume.value = volume;
+ if (plyr.supported.full && plyr.volume) {
+ plyr.volume.value = volume;
}
// Store the volume in storage
@@ -1475,31 +1661,34 @@
}
// Toggle class if muted
- _toggleClass(player.container, config.classes.muted, (volume === 0));
+ _toggleClass(plyr.container, config.classes.muted, (volume === 0));
// Update checkbox for mute state
- if (player.supported.full && player.buttons.mute) {
- _toggleState(player.buttons.mute, (volume === 0));
+ if (plyr.supported.full && plyr.buttons.mute) {
+ _toggleState(plyr.buttons.mute, (volume === 0));
}
}
// Toggle captions
function _toggleCaptions(show) {
// If there's no full support, or there's no caption toggle
- if (!player.supported.full || !player.buttons.captions) {
+ if (!plyr.supported.full || !plyr.buttons.captions) {
return;
- }
+ }
// If the method is called without parameter, toggle based on current value
if (typeof show !== 'boolean') {
- show = (player.container.className.indexOf(config.classes.captions.active) === -1);
+ show = (plyr.container.className.indexOf(config.classes.captions.active) === -1);
}
+ // Set global
+ plyr.captionsEnabled = show;
+
// Toggle state
- _toggleState(player.buttons.captions, show);
+ _toggleState(plyr.buttons.captions, plyr.captionsEnabled);
// Add class hook
- _toggleClass(player.container, config.classes.captions.active, show);
+ _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
}
// Check if media is loading
@@ -1507,18 +1696,18 @@
var loading = (event.type === 'waiting');
// Clear timer
- clearTimeout(player.loadingTimer);
+ clearTimeout(plyr.loadingTimer);
// Timer to prevent flicker when seeking
- player.loadingTimer = setTimeout(function() {
- _toggleClass(player.container, config.classes.loading, loading);
+ plyr.loadingTimer = setTimeout(function() {
+ _toggleClass(plyr.container, config.classes.loading, loading);
}, (loading ? 250 : 0));
}
// Update <progress> elements
function _updateProgress(event) {
- var progress = player.progress.played.bar,
- text = player.progress.played.text,
+ var progress = plyr.progress.played.bar,
+ text = plyr.progress.played.text,
value = 0;
if (event) {
@@ -1526,11 +1715,11 @@
// Video playing
case 'timeupdate':
case 'seeking':
- value = _getPercentage(player.media.currentTime, player.media.duration);
+ value = _getPercentage(plyr.media.currentTime, plyr.media.duration);
// Set seek range value only if it's a 'natural' time event
- if (event.type == 'timeupdate' && player.buttons.seek) {
- player.buttons.seek.value = value;
+ if (event.type == 'timeupdate' && plyr.buttons.seek) {
+ plyr.buttons.seek.value = value;
}
break;
@@ -1545,14 +1734,14 @@
// Check buffer status
case 'playing':
case 'progress':
- progress = player.progress.buffer.bar;
- text = player.progress.buffer.text;
+ progress = plyr.progress.buffer.bar;
+ text = plyr.progress.buffer.text;
value = (function() {
- var buffered = player.media.buffered;
+ var buffered = plyr.media.buffered;
// HTML5
if (buffered && buffered.length) {
- return _getPercentage(buffered.end(0), player.media.duration);
+ return _getPercentage(buffered.end(0), plyr.media.duration);
}
// YouTube returns between 0 and 1
else if (typeof buffered === 'number') {
@@ -1580,144 +1769,205 @@
return;
}
- player.secs = parseInt(time % 60);
- player.mins = parseInt((time / 60) % 60);
- player.hours = parseInt(((time / 60) / 60) % 60);
+ // Fallback to 0
+ if (isNaN(time)) {
+ time = 0;
+ }
+
+ plyr.secs = parseInt(time % 60);
+ plyr.mins = parseInt((time / 60) % 60);
+ plyr.hours = parseInt(((time / 60) / 60) % 60);
// Do we need to display hours?
- var displayHours = (parseInt(((player.media.duration / 60) / 60) % 60) > 0);
+ var displayHours = (parseInt(((plyr.media.duration / 60) / 60) % 60) > 0);
// Ensure it's two digits. For example, 03 rather than 3.
- player.secs = ('0' + player.secs).slice(-2);
- player.mins = ('0' + player.mins).slice(-2);
+ plyr.secs = ('0' + plyr.secs).slice(-2);
+ plyr.mins = ('0' + plyr.mins).slice(-2);
// Render
- element.innerHTML = (displayHours ? player.hours + ':' : '') + player.mins + ':' + player.secs;
+ element.innerHTML = (displayHours ? plyr.hours + ':' : '') + plyr.mins + ':' + plyr.secs;
}
// Show the duration on metadataloaded
function _displayDuration() {
- var duration = player.media.duration || 0;
+ var duration = plyr.media.duration || 0;
// If there's only one time display, display duration there
- if (!player.duration && config.displayDuration && player.media.paused) {
- _updateTimeDisplay(duration, player.currentTime);
+ if (!plyr.duration && config.displayDuration && plyr.media.paused) {
+ _updateTimeDisplay(duration, plyr.currentTime);
}
// If there's a duration element, update content
- if (player.duration) {
- _updateTimeDisplay(duration, player.duration);
+ if (plyr.duration) {
+ _updateTimeDisplay(duration, plyr.duration);
}
}
// Handle time change event
function _timeUpdate(event) {
// Duration
- _updateTimeDisplay(player.media.currentTime, player.currentTime);
+ _updateTimeDisplay(plyr.media.currentTime, plyr.currentTime);
// Playing progress
_updateProgress(event);
}
- // Remove <source> children and src attribute
- function _removeSources() {
- // Find child <source> elements
- var sources = player.media.querySelectorAll('source');
-
- // Remove each
- for (var i = sources.length - 1; i >= 0; i--) {
- _remove(sources[i]);
+ // Add elements to HTML5 media (source, tracks, etc)
+ function _insertChildElements(type, attributes) {
+ if (typeof attributes === 'string') {
+ _insertElement(type, plyr.media, { src: attributes });
}
-
- // Remove src attribute
- player.media.removeAttribute('src');
- }
-
- // Inject a source
- function _addSource(attributes) {
- if (attributes.src) {
- // Create a new <source>
- var element = document.createElement('source');
-
- // Set all passed attributes
- _setAttributes(element, attributes);
-
- // Inject the new source
- _prependChild(player.media, element);
+ else if (attributes.constructor === Array) {
+ for (var i = attributes.length - 1; i >= 0; i--) {
+ _insertElement(type, plyr.media, attributes[i]);
+ }
}
}
// 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();
+ function _updateSource(source) {
+ if (typeof source === 'undefined') {
+ return;
+ }
- // Re-setup YouTube
- // We don't use loadVideoBy[x] here since it has issues
- _setupYouTube(sources);
+ // Pause playback
+ _pause();
- // Update times
- _timeUpdate();
+ // Clean up YouTube stuff
+ if (plyr.type === 'youtube') {
+ // Destroy the embed instance
+ plyr.embed.destroy();
- // Bail
- return;
+ // Clear timer
+ window.clearInterval(plyr.timer.buffering);
+ window.clearInterval(plyr.timer.playing);
+ }
+ else if (plyr.type === 'video') {
+ // Remove video wrapper
+ _remove(plyr.videoContainer);
}
+
+ // Remove the old media
+ _remove(plyr.media);
- // Pause playback (webkit freaks out)
- _pause();
+ // Set the new type
+ if ('type' in source && source.type !== plyr.type) {
+ plyr.type = source.type;
+ }
- // Restart
- _seek();
+ // Create new markup
+ switch(plyr.type) {
+ case 'video':
+ plyr.media = document.createElement('video');
+ break;
- // Remove current sources
- _removeSources();
+ case 'audio':
+ plyr.media = document.createElement('audio');
+ break;
- // If a single source is passed
- // .source('path/to/video.mp4')
- if (typeof sources === 'string') {
- _addSource({ src: sources });
+ case 'youtube':
+ case 'vimeo':
+ plyr.media = document.createElement('div');
+ plyr.embedId = (typeof source.sources === 'string' ? source.sources : source.sources[0].src);
+ break;
}
- // An array of source objects
- // Check if a source exists, use that or set the 'src' attribute?
- // .source([{ src: 'path/to/video.mp4', type: 'video/mp4' },{ src: 'path/to/video.webm', type: 'video/webm' }])
- else if (sources.constructor === Array) {
- for (var index in sources) {
- _addSource(sources[index]);
+ // Inject the new element
+ _prependChild(plyr.container, plyr.media);
+
+ // Set attributes for audio video
+ if (_inArray(config.types.html5, plyr.type)) {
+ if (config.crossorigin) {
+ plyr.media.setAttribute('crossorigin', '');
+ }
+ if (config.autoplay) {
+ plyr.media.setAttribute('autoplay', '');
+ }
+ if ('poster' in source) {
+ plyr.media.setAttribute('poster', source.poster);
+ }
+ if (config.loop) {
+ plyr.media.setAttribute('loop', '');
}
}
- if (player.supported.full) {
- // Reset time display
- _timeUpdate();
+ // Classname reset
+ plyr.container.className = plyr.originalClassName;
- // Update the UI
- _checkPlaying();
- }
+ // Restore class hooks
+ _toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
+ _toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
- // Re-load sources
- player.media.load();
+ // Autoplay the new source?
+ config.autoplay = (source.autoplay || config.autoplay);
+
+ // Set media id for embeds
+ if (_inArray(config.types.embed, plyr.type)) {
+ plyr.embedId = source.sources;
+ }
- // Play if autoplay attribute is present
- if (player.media.getAttribute('autoplay') !== null || config.autoplay) {
- _play();
+ // Set new sources for html5
+ if (_inArray(config.types.html5, plyr.type)) {
+ _insertChildElements('source', source.sources);
}
+
+ // Set up from scratch
+ _setupMedia();
+
+ // Trigger media updated
+ _mediaUpdated();
+
+ // HTML5 stuff
+ if (_inArray(config.types.html5, plyr.type)) {
+ // Set volume
+ _setVolume();
+ _updateVolume();
+
+ // UI updates
+ if (plyr.supported.full) {
+ // Reset time display
+ _timeUpdate();
+
+ // Update the UI
+ _checkPlaying();
+ }
+
+ // Setup captions
+ if ('tracks' in source) {
+ _insertChildElements('track', source.tracks);
+
+ // Captions
+ _setupCaptions();
+ }
+
+ // Load HTML5 sources
+ plyr.media.load();
+
+ // Play if autoplay attribute is present
+ if (config.autoplay) {
+ _play();
+ }
+ }
+
+ if ('title' in source) {
+ config.title = source.title;
+ _setupPlayAria();
+ }
}
// Update poster
function _updatePoster(source) {
- if (player.type === 'video') {
- player.media.setAttribute('poster', source);
+ if (plyr.type === 'video') {
+ plyr.media.setAttribute('poster', source);
}
}
// Listen for events
function _listeners() {
// IE doesn't support input event, so we fallback to change
- var inputEvent = (player.browser.name == 'IE' ? 'change' : 'input');
+ var inputEvent = (plyr.browser.name == 'IE' ? 'change' : 'input');
// Detect tab focus
function checkFocus() {
@@ -1725,11 +1975,11 @@
if (!focused || focused == document.body) {
focused = null;
}
- else {
+ else if (document.querySelector) {
focused = document.querySelector(':focus');
}
- for (var button in player.buttons) {
- var element = player.buttons[button];
+ for (var button in plyr.buttons) {
+ var element = plyr.buttons[button];
_toggleClass(element, 'tab-focus', (element === focused));
}
@@ -1741,8 +1991,8 @@
checkFocus();
}
});
- for (var button in player.buttons) {
- var element = player.buttons[button];
+ for (var button in plyr.buttons) {
+ var element = plyr.buttons[button];
_on(element, 'blur', function() {
_toggleClass(element, 'tab-focus', false);
@@ -1750,39 +2000,39 @@
}
// Play
- _on(player.buttons.play, 'click', function() {
+ _on(plyr.buttons.play, 'click', function() {
_play();
- setTimeout(function() { player.buttons.pause.focus(); }, 100);
+ setTimeout(function() { plyr.buttons.pause.focus(); }, 100);
});
// Pause
- _on(player.buttons.pause, 'click', function() {
+ _on(plyr.buttons.pause, 'click', function() {
_pause();
- setTimeout(function() { player.buttons.play.focus(); }, 100);
+ setTimeout(function() { plyr.buttons.play.focus(); }, 100);
});
// Restart
- _on(player.buttons.restart, 'click', _seek);
+ _on(plyr.buttons.restart, 'click', _seek);
// Rewind
- _on(player.buttons.rewind, 'click', _rewind);
+ _on(plyr.buttons.rewind, 'click', _rewind);
// Fast forward
- _on(player.buttons.forward, 'click', _forward);
+ _on(plyr.buttons.forward, 'click', _forward);
// Seek
- _on(player.buttons.seek, inputEvent, _seek);
+ _on(plyr.buttons.seek, inputEvent, _seek);
// Set volume
- _on(player.volume, inputEvent, function() {
+ _on(plyr.volume, inputEvent, function() {
_setVolume(this.value);
});
// Mute
- _on(player.buttons.mute, 'click', _toggleMute);
+ _on(plyr.buttons.mute, 'click', _toggleMute);
// Fullscreen
- _on(player.buttons.fullscreen, 'click', _toggleFullscreen);
+ _on(plyr.buttons.fullscreen, 'click', _toggleFullscreen);
// Handle user exiting fullscreen by escaping etc
if (fullscreen.supportsFullScreen) {
@@ -1790,22 +2040,22 @@
}
// Time change on media
- _on(player.media, 'timeupdate seeking', _timeUpdate);
+ _on(plyr.media, 'timeupdate seeking', _timeUpdate);
// Update manual captions
- _on(player.media, 'timeupdate', _seekManualCaptions);
+ _on(plyr.media, 'timeupdate', _seekManualCaptions);
// Display duration
- _on(player.media, 'loadedmetadata', _displayDuration);
+ _on(plyr.media, 'loadedmetadata', _displayDuration);
// Captions
- _on(player.buttons.captions, 'click', _toggleCaptions);
+ _on(plyr.buttons.captions, 'click', _toggleCaptions);
// Handle the media finishing
- _on(player.media, 'ended', function() {
+ _on(plyr.media, 'ended', function() {
// Clear
- if (player.type === 'video') {
- player.captionsContainer.innerHTML = '';
+ if (plyr.type === 'video') {
+ plyr.captionsContainer.innerHTML = '';
}
// Reset UI
@@ -1813,29 +2063,29 @@
});
// Check for buffer progress
- _on(player.media, 'progress playing', _updateProgress);
+ _on(plyr.media, 'progress playing', _updateProgress);
// Handle native mute
- _on(player.media, 'volumechange', _updateVolume);
+ _on(plyr.media, 'volumechange', _updateVolume);
// Handle native play/pause
- _on(player.media, 'play pause', _checkPlaying);
+ _on(plyr.media, 'play pause', _checkPlaying);
// Loading
- _on(player.media, 'waiting canplay seeked', _checkLoading);
+ _on(plyr.media, 'waiting canplay seeked', _checkLoading);
// Click video
- if (player.type === 'video' && config.click) {
- _on(player.videoContainer, 'click', function() {
- if (player.media.paused) {
- _triggerEvent(player.buttons.play, 'click');
+ if (plyr.type === 'video' && config.click) {
+ _on(plyr.videoContainer, 'click', function() {
+ if (plyr.media.paused) {
+ _triggerEvent(plyr.buttons.play, 'click');
}
- else if (player.media.ended) {
+ else if (plyr.media.ended) {
_seek();
- _triggerEvent(player.buttons.play, 'click');
+ _triggerEvent(plyr.buttons.play, 'click');
}
else {
- _triggerEvent(player.buttons.pause, 'click');
+ _triggerEvent(plyr.buttons.pause, 'click');
}
});
}
@@ -1846,47 +2096,47 @@
// 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) {
+ if (!plyr.init) {
return null;
}
// Reset container classname
- player.container.setAttribute('class', config.selectors.container.replace('.', ''));
+ plyr.container.setAttribute('class', _getClassname(config.selectors.container));
// Remove init flag
- player.init = false;
+ plyr.init = false;
// Remove controls
_remove(_getElement(config.selectors.controls));
// YouTube
- if (player.type === 'youtube') {
- player.embed.destroy();
+ if (plyr.type === 'youtube') {
+ plyr.embed.destroy();
return;
}
// If video, we need to remove some more
- if (player.type === 'video') {
+ if (plyr.type === 'video') {
// Remove captions
_remove(_getElement(config.selectors.captions));
// Remove video wrapper
- _unwrap(player.videoContainer);
+ _unwrap(plyr.videoContainer);
}
// Restore native video controls
- player.media.setAttribute('controls', '');
+ plyr.media.setAttribute('controls', '');
// Clone the media element to remove listeners
// 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);
+ var clone = plyr.media.cloneNode(true);
+ plyr.media.parentNode.replaceChild(clone, plyr.media);
}
// Setup a player
function _init() {
// Bail if the element is initialized
- if (player.init) {
+ if (plyr.init) {
return null;
}
@@ -1894,40 +2144,52 @@
fullscreen = _fullscreen();
// Sniff out the browser
- player.browser = _browserSniff();
+ plyr.browser = _browserSniff();
// Get the media element
- player.media = player.container.querySelectorAll('audio, video, div')[0];
+ plyr.media = plyr.container.querySelectorAll('audio, video, div')[0];
+
+ // Get original classname
+ plyr.originalClassName = plyr.container.className;
- // Set media type
- var tagName = player.media.tagName.toLowerCase();
+ // Set media type based on tag or data attribute
+ // Supported: video, audio, vimeo, youtube
+ var tagName = plyr.media.tagName.toLowerCase();
if (tagName === 'div') {
- player.type = player.media.getAttribute('data-type');
+ plyr.type = plyr.media.getAttribute('data-type');
+ plyr.embedId = plyr.media.getAttribute('data-video-id');
+
+ // Clean up
+ plyr.media.removeAttribute('data-type');
+ plyr.media.removeAttribute('data-video-id');
}
else {
- player.type = tagName;
+ plyr.type = tagName;
+ config.crossorigin = (plyr.media.getAttribute('crossorigin') !== null);
+ config.autoplay = (config.autoplay || (plyr.media.getAttribute('autoplay') !== null));
+ config.loop = (config.loop || (plyr.media.getAttribute('loop') !== null));
}
// Check for full support
- player.supported = api.supported(player.type);
+ plyr.supported = api.supported(plyr.type);
// If no native support, bail
- if (!player.supported.basic) {
+ if (!plyr.supported.basic) {
return false;
}
// Debug info
- _log(player.browser.name + ' ' + player.browser.version);
+ _log(plyr.browser.name + ' ' + plyr.browser.version);
// Setup media
_setupMedia();
// Setup interface
- if (player.type == 'video' || player.type == 'audio') {
+ if (plyr.type == 'video' || plyr.type == 'audio') {
// Bail if no support
- if (!player.supported.full) {
+ if (!plyr.supported.full) {
// Successful setup
- player.init = true;
+ plyr.init = true;
// Don't inject controls if no full support
return;
@@ -1946,7 +2208,7 @@
}
// Successful setup
- player.init = true;
+ plyr.init = true;
}
function _setupInterface() {
@@ -1961,10 +2223,15 @@
// Captions
_setupCaptions();
+ // Media updated
+ _mediaUpdated();
+
// Set volume
_setVolume();
_updateVolume();
+ }
+ function _mediaUpdated() {
// Setup fullscreen
_setupFullscreen();
@@ -1976,27 +2243,27 @@
_init();
// If init failed, return an empty object
- if (!player.init) {
+ if (!plyr.init) {
return {};
}
return {
- media: player.media,
+ media: plyr.media,
play: _play,
pause: _pause,
restart: _seek,
rewind: _rewind,
forward: _forward,
seek: _seek,
- source: _parseSource,
+ source: _updateSource,
poster: _updatePoster,
setVolume: _setVolume,
togglePlay: _togglePlay,
toggleMute: _toggleMute,
toggleCaptions: _toggleCaptions,
toggleFullscreen: _toggleFullscreen,
- isFullscreen: function() { return player.isFullscreen || false; },
- support: function(mimeType) { return _supportMime(player, mimeType); },
+ isFullscreen: function() { return plyr.isFullscreen || false; },
+ support: function(mimeType) { return _supportMime(plyr, mimeType); },
destroy: _destroy,
restore: _init
};
@@ -2022,6 +2289,7 @@
full = (basic && !oldIE);
break;
+ case 'vimeo':
case 'youtube':
basic = true;
full = (!oldIE && !iPhone);
@@ -2039,7 +2307,30 @@
};
// Expose setup function
- api.setup = function(options) {
+ api.setup = function(elements, options) {
+ // Get the players
+ var instances = [];
+
+ // Select the elements
+ // Assume elements is a NodeList by default
+ if (typeof elements === 'string') {
+ elements = document.querySelectorAll(elements);
+ }
+ // Single HTMLElement passed
+ else if (elements instanceof HTMLElement) {
+ elements = [elements];
+ }
+ // No selector passed, possibly options as first argument
+ else if (!(elements instanceof NodeList) && typeof elements !== 'string') {
+ // If options are the first argument
+ if (typeof options === 'undefined' && typeof elements === 'object') {
+ options = elements;
+ }
+
+ // Use default selector
+ elements = document.querySelectorAll(defaults.selectors.container);
+ }
+
// Extend the default options with user specified
config = _extend(defaults, options);
@@ -2049,10 +2340,6 @@
return false;
}
- // Get the players
- var elements = document.querySelectorAll(config.selectors.container),
- players = [];
-
// Create a player instance for each element
for (var i = elements.length - 1; i >= 0; i--) {
// Get the current element
@@ -2073,10 +2360,10 @@
}
// Add to return array even if it's already setup
- players.push(element.plyr);
+ instances.push(element.plyr);
}
- return players;
+ return instances;
};
-}(this.plyr = this.plyr || {})); \ No newline at end of file
+}(this.plyr = this.plyr || {}));