Refactoring and bug fixing
This commit is contained in:
parent
fc45ab48c9
commit
9d109bf02d
2
demo/dist/demo.css
vendored
2
demo/dist/demo.css
vendored
File diff suppressed because one or more lines are too long
2
demo/dist/demo.js
vendored
2
demo/dist/demo.js
vendored
@ -1 +1 @@
|
||||
"document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var e=document.createElement("_");if(e.classList.add("c1","c2"),!e.classList.contains("c2")){var t=function(e){var t=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(e){var i,o=arguments.length;for(i=0;o>i;i++)e=arguments[i],t.call(this,e)}};t("add"),t("remove")}if(e.classList.toggle("c3",!1),e.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:i.call(this,e)}}e=null}():!function(e){"use strict";if("Element"in e){var t="classList",i="prototype",o=e.Element[i],n=Object,s=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[i].indexOf||function(e){for(var t=0,i=this.length;i>t;t++)if(t in this&&this[t]===e)return t;return-1},a=function(e,t){this.name=e,this.code=DOMException[e],this.message=t},c=function(e,t){if(""===t)throw new a("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(t))throw new a("INVALID_CHARACTER_ERR","String contains an invalid character");return r.call(e,t)},l=function(e){for(var t=s.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],o=0,n=i.length;n>o;o++)this.push(i[o]);this._updateClassName=function(){e.setAttribute("class",this.toString())}},u=l[i]=[],d=function(){return new l(this)};if(a[i]=Error[i],u.item=function(e){return this[e]||null},u.contains=function(e){return e+="",-1!==c(this,e)},u.add=function(){var e,t=arguments,i=0,o=t.length,n=!1;do e=t[i]+"",-1===c(this,e)&&(this.push(e),n=!0);while(++i<o);n&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,o=0,n=i.length,s=!1;do for(e=i[o]+"",t=c(this,e);-1!==t;)this.splice(t,1),s=!0,t=c(this,e);while(++o<n);s&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),o=i?t!==!0&&"remove":t!==!1&&"add";return o&&this[o](e),t===!0||t===!1?t:!i},u.toString=function(){return this.join(" ")},n.defineProperty){var p={get:d,enumerable:!0,configurable:!0};try{n.defineProperty(o,t,p)}catch(h){-2146823252===h.number&&(p.enumerable=!1,n.defineProperty(o,t,p))}}else n[i].__defineGetter__&&o.__defineGetter__(t,d)}}(self)),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var o=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=o+(i?" "+t:"")}}function t(t,i){if(t in s&&(i||t!=r)&&(r.length||t!=s.video)){switch(t){case s.video:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"},{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm",type:"video/webm"}],poster:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt","default":!0}]});break;case s.audio:o.source({type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case s.youtube:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case s.vimeo:o.source({type:"video",title:"View From A Blue Moon",sources:[{src:"143418951",type:"vimeo"}]})}r=t;for(var a=n.length-1;a>=0;a--)e(n[a].parentElement,"active",!1);e(document.querySelector('[data-source="'+t+'"]').parentElement,"active",!0)}}document.body.addEventListener("ready",function(e){console.log(e)});var i=plyr.setup({debug:!0,title:"Video demo",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0}});plyr.loadSprite("dist/demo.svg");for(var o=i[0].plyr,n=document.querySelectorAll("[data-source]"),s={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState,c=n.length-1;c>=0;c--)n[c].addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),a&&history.pushState({type:e},"","#"+e)});if(window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var l=!r.length;l&&(r=s.video),r in s&&history.replaceState({type:r},"",l?"":"#"+r),r!==s.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,o,n,s,r){e.GoogleAnalyticsObject=n,e[n]=e[n]||function(){(e[n].q=e[n].q||[]).push(arguments)},e[n].l=1*new Date,s=t.createElement(i),r=t.getElementsByTagName(i)[0],s.async=1,s.src=o,r.parentNode.insertBefore(s,r)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview"));
|
||||
"document"in self&&("classList"in document.createElement("_")?!function(){"use strict";var e=document.createElement("_");if(e.classList.add("c1","c2"),!e.classList.contains("c2")){var t=function(e){var t=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(e){var i,s=arguments.length;for(i=0;s>i;i++)e=arguments[i],t.call(this,e)}};t("add"),t("remove")}if(e.classList.toggle("c3",!1),e.classList.contains("c3")){var i=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(e,t){return 1 in arguments&&!this.contains(e)==!t?t:i.call(this,e)}}e=null}():!function(e){"use strict";if("Element"in e){var t="classList",i="prototype",s=e.Element[i],o=Object,n=String[i].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[i].indexOf||function(e){for(var t=0,i=this.length;i>t;t++)if(t in this&&this[t]===e)return t;return-1},a=function(e,t){this.name=e,this.code=DOMException[e],this.message=t},c=function(e,t){if(""===t)throw new a("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(t))throw new a("INVALID_CHARACTER_ERR","String contains an invalid character");return r.call(e,t)},l=function(e){for(var t=n.call(e.getAttribute("class")||""),i=t?t.split(/\s+/):[],s=0,o=i.length;o>s;s++)this.push(i[s]);this._updateClassName=function(){e.setAttribute("class",this.toString())}},u=l[i]=[],d=function(){return new l(this)};if(a[i]=Error[i],u.item=function(e){return this[e]||null},u.contains=function(e){return e+="",-1!==c(this,e)},u.add=function(){var e,t=arguments,i=0,s=t.length,o=!1;do e=t[i]+"",-1===c(this,e)&&(this.push(e),o=!0);while(++i<s);o&&this._updateClassName()},u.remove=function(){var e,t,i=arguments,s=0,o=i.length,n=!1;do for(e=i[s]+"",t=c(this,e);-1!==t;)this.splice(t,1),n=!0,t=c(this,e);while(++s<o);n&&this._updateClassName()},u.toggle=function(e,t){e+="";var i=this.contains(e),s=i?t!==!0&&"remove":t!==!1&&"add";return s&&this[s](e),t===!0||t===!1?t:!i},u.toString=function(){return this.join(" ")},o.defineProperty){var p={get:d,enumerable:!0,configurable:!0};try{o.defineProperty(s,t,p)}catch(h){-2146823252===h.number&&(p.enumerable=!1,o.defineProperty(s,t,p))}}else o[i].__defineGetter__&&s.__defineGetter__(t,d)}}(self)),function(){function e(e,t,i){if(e)if(e.classList)e.classList[i?"add":"remove"](t);else{var s=(" "+e.className+" ").replace(/\s+/g," ").replace(" "+t+" ","");e.className=s+(i?" "+t:"")}}function t(t,i){if(t in n&&(i||t!=r)&&(r.length||t!=n.video)){switch(t){case n.video:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.mp4",type:"video/mp4"},{src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.webm",type:"video/webm"}],poster:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.jpg",tracks:[{kind:"captions",label:"English",srclang:"en",src:"https://cdn.selz.com/plyr/1.5/View_From_A_Blue_Moon_Trailer-HD.en.vtt","default":!0}]});break;case n.audio:s.source({type:"audio",title:"Kishi Bashi – “It All Began With A Burst”",sources:[{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.mp3",type:"audio/mp3"},{src:"https://cdn.selz.com/plyr/1.5/Kishi_Bashi_-_It_All_Began_With_a_Burst.ogg",type:"audio/ogg"}]});break;case n.youtube:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"bTqVqk7FSmY",type:"youtube"}]});break;case n.vimeo:s.source({type:"video",title:"View From A Blue Moon",sources:[{src:"143418951",type:"vimeo"}]})}r=t;for(var a=o.length-1;a>=0;a--)e(o[a].parentElement,"active",!1);e(document.querySelector('[data-source="'+t+'"]').parentElement,"active",!0)}}var i=plyr.setup({debug:!0,title:"Video demo",iconUrl:"../dist/plyr.svg",tooltips:{controls:!0},captions:{defaultActive:!0}});plyr.loadSprite("dist/demo.svg");for(var s=i[0],o=document.querySelectorAll("[data-source]"),n={video:"video",audio:"audio",youtube:"youtube",vimeo:"vimeo"},r=window.location.hash.replace("#",""),a=window.history&&window.history.pushState,c=o.length-1;c>=0;c--)o[c].addEventListener("click",function(){var e=this.getAttribute("data-source");t(e),a&&history.pushState({type:e},"","#"+e)});if(window.addEventListener("popstate",function(e){e.state&&"type"in e.state&&t(e.state.type)}),a){var l=!r.length;l&&(r=n.video),r in n&&history.replaceState({type:r},"",l?"":"#"+r),r!==n.video&&t(r,!0)}}(),document.domain.indexOf("plyr.io")>-1&&(!function(e,t,i,s,o,n,r){e.GoogleAnalyticsObject=o,e[o]=e[o]||function(){(e[o].q=e[o].q||[]).push(arguments)},e[o].l=1*new Date,n=t.createElement(i),r=t.getElementsByTagName(i)[0],n.async=1,n.src=s,r.parentNode.insertBefore(n,r)}(window,document,"script","//www.google-analytics.com/analytics.js","ga"),ga("create","UA-40881672-11","auto"),ga("send","pageview"));
|
@ -73,7 +73,8 @@
|
||||
</main>
|
||||
|
||||
<!-- Plyr core script -->
|
||||
<script src="../dist/plyr.js"></script>
|
||||
<!--<script src="../dist/plyr.js"></script>-->
|
||||
<script src="../src/js/plyr.js"></script>
|
||||
|
||||
<!-- Docs script -->
|
||||
<script src="dist/demo.js"></script>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
// General functions
|
||||
;(function() {
|
||||
document.body.addEventListener('ready', function(event) { console.log(event); });
|
||||
//document.body.addEventListener('ready', function(event) { console.log(event); });
|
||||
|
||||
// Setup the player
|
||||
var instances = plyr.setup({
|
||||
@ -25,7 +25,7 @@
|
||||
plyr.loadSprite('dist/demo.svg');
|
||||
|
||||
// Plyr returns an array regardless
|
||||
var player = instances[0].plyr;
|
||||
var player = instances[0];
|
||||
|
||||
// Setup type toggle
|
||||
var buttons = document.querySelectorAll('[data-source]'),
|
||||
|
@ -39,9 +39,10 @@ header {
|
||||
|
||||
// Sections
|
||||
section {
|
||||
padding-bottom: @padding-base;
|
||||
max-width: @example-width-video;
|
||||
margin: 0 auto @padding-base;
|
||||
|
||||
@media (min-width: @screen-sm) {
|
||||
padding-bottom: (@padding-base * 2);
|
||||
margin-bottom: (@padding-base * 2);
|
||||
}
|
||||
}
|
@ -2,11 +2,6 @@
|
||||
// Examples
|
||||
// ==========================================================================
|
||||
|
||||
section {
|
||||
margin: 0 auto @padding-base;
|
||||
max-width: @example-width-video;
|
||||
}
|
||||
|
||||
// For non supported browsers
|
||||
video {
|
||||
max-width: 100%;
|
||||
|
4
dist/plyr.js
vendored
4
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plyr",
|
||||
"version": "1.8.12",
|
||||
"version": "1.9.0",
|
||||
"description": "A simple, accessible and customizable HTML5, YouTube and Vimeo media player",
|
||||
"homepage": "http://plyr.io",
|
||||
"main": "src/js/plyr.js",
|
||||
|
@ -541,11 +541,6 @@ Here's a list of the methods supported:
|
||||
<td>—</td>
|
||||
<td>Destroys the plyr UI and any media event listeners, effectively restoring to the previous state before <code>setup()</code> was called.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>restore()</code></td>
|
||||
<td>—</td>
|
||||
<td>Reverses the effects of the <code>destroy()</code> method, restoring the UI and listeners.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>getCurrentTime()</code></td>
|
||||
<td>—</td>
|
||||
|
467
src/js/plyr.js
467
src/js/plyr.js
@ -35,7 +35,7 @@
|
||||
autoplay: false,
|
||||
loop: false,
|
||||
seekTime: 10,
|
||||
volume: 5,
|
||||
volume: 10,
|
||||
volumeMin: 0,
|
||||
volumeMax: 10,
|
||||
volumeStep: 1,
|
||||
@ -86,6 +86,8 @@
|
||||
duration: '.plyr__time--duration'
|
||||
},
|
||||
classes: {
|
||||
setup: 'plyr--setup',
|
||||
ready: 'plyr--ready',
|
||||
videoWrapper: 'plyr__video-wrapper',
|
||||
embedWrapper: 'plyr__video-embed',
|
||||
type: 'plyr--{0}',
|
||||
@ -168,7 +170,9 @@
|
||||
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']
|
||||
events: ['ended', 'progress', 'stalled', 'playing', 'waiting', 'canplay', 'canplaythrough', 'loadstart', 'loadeddata', 'loadedmetadata', 'timeupdate', 'volumechange', 'play', 'pause', 'error', 'seeking', 'emptied'],
|
||||
// Logging
|
||||
logPrefix: '[Plyr]'
|
||||
};
|
||||
|
||||
// Credits: http://paypal.github.io/accessible-html5-video-player/
|
||||
@ -346,7 +350,7 @@
|
||||
|
||||
// Unwrap an element
|
||||
// http://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/
|
||||
function _unwrap(wrapper) {
|
||||
/*function _unwrap(wrapper) {
|
||||
// Get the element's parent node
|
||||
var parent = wrapper.parentNode;
|
||||
|
||||
@ -357,7 +361,7 @@
|
||||
|
||||
// Remove the empty element
|
||||
parent.removeChild(wrapper);
|
||||
}
|
||||
}*/
|
||||
|
||||
// Remove an element
|
||||
function _remove(element) {
|
||||
@ -575,25 +579,28 @@
|
||||
return input !== null && typeof(input) === 'object';
|
||||
},
|
||||
array: function(input) {
|
||||
return input !== null && typeof(input) === 'object' && input.constructor === Array;
|
||||
return input !== null && (typeof(input) === 'object' && input.constructor === Array);
|
||||
},
|
||||
number: function(input) {
|
||||
return typeof(input) === 'number' && !isNaN(input - 0) || (typeof input == 'object' && input.constructor === Number);
|
||||
return input !== null && (typeof(input) === 'number' && !isNaN(input - 0) || (typeof input == 'object' && input.constructor === Number));
|
||||
},
|
||||
string: function(input) {
|
||||
return typeof input === 'string' || (typeof input == 'object' && input.constructor === String);
|
||||
return input !== null && (typeof input === 'string' || (typeof input == 'object' && input.constructor === String));
|
||||
},
|
||||
boolean: function(input) {
|
||||
return typeof input === 'boolean';
|
||||
return input !== null && typeof input === 'boolean';
|
||||
},
|
||||
nodeList: function(input) {
|
||||
return input instanceof NodeList;
|
||||
return input !== null && input instanceof NodeList;
|
||||
},
|
||||
htmlElement: function(input) {
|
||||
return input instanceof HTMLElement;
|
||||
return input !== null && input instanceof HTMLElement;
|
||||
},
|
||||
function: function(input) {
|
||||
return input !== null && typeof input === 'function';
|
||||
},
|
||||
undefined: function(input) {
|
||||
return typeof input === 'undefined';
|
||||
return input !== null && typeof input === 'undefined';
|
||||
}
|
||||
};
|
||||
|
||||
@ -702,25 +709,31 @@
|
||||
}
|
||||
|
||||
// Player instance
|
||||
function Plyr(container, config) {
|
||||
var plyr = this;
|
||||
plyr.container = container;
|
||||
plyr.timers = {};
|
||||
function Plyr(media, config) {
|
||||
var plyr = this,
|
||||
timers = {};
|
||||
|
||||
// Log config options
|
||||
_log(config);
|
||||
// Set media
|
||||
plyr.media = media;
|
||||
var original = media.cloneNode(true);
|
||||
|
||||
// Debugging
|
||||
function _log() {
|
||||
function _console(type, args) {
|
||||
if (config.debug && window.console) {
|
||||
console.log.apply(console, arguments);
|
||||
}
|
||||
}
|
||||
function _warn() {
|
||||
if (config.debug && window.console) {
|
||||
console.warn.apply(console, arguments);
|
||||
args = Array.prototype.slice.call(args);
|
||||
|
||||
if (_is.string(config.logPrefix) && config.logPrefix.length) {
|
||||
args.unshift(config.logPrefix);
|
||||
}
|
||||
|
||||
console[type].apply(console, args);
|
||||
}
|
||||
}
|
||||
var _log = function() { _console('log', arguments) },
|
||||
_warn = function() { _console('warn', arguments) };
|
||||
|
||||
// Log config options
|
||||
_log('Config', config);
|
||||
|
||||
// Get icon URL
|
||||
function _getIconUrl() {
|
||||
@ -1048,7 +1061,7 @@
|
||||
_log('Successfully loaded the caption file via AJAX');
|
||||
}
|
||||
else {
|
||||
_warn('There was a problem loading the caption file via AJAX');
|
||||
_warn(config.logPrefix + 'There was a problem loading the caption file via AJAX');
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1076,7 +1089,7 @@
|
||||
}
|
||||
|
||||
// Set the span content
|
||||
if (_is.undefined(caption)) {
|
||||
if (_is.string(caption)) {
|
||||
content.innerHTML = caption.trim();
|
||||
}
|
||||
else {
|
||||
@ -1279,24 +1292,20 @@
|
||||
html = _replaceAll(html, '{id}', Math.floor(Math.random() * (10000)));
|
||||
|
||||
// Controls container
|
||||
var container;
|
||||
var target;
|
||||
|
||||
// Inject to custom location
|
||||
if (config.selectors.controls.container !== null) {
|
||||
container = config.selectors.controls.container;
|
||||
|
||||
if (_is.string(container)) {
|
||||
container = document.querySelector(container);
|
||||
}
|
||||
if (_is.string(config.selectors.controls.container)) {
|
||||
target = document.querySelector(config.selectors.controls.container);
|
||||
}
|
||||
|
||||
// Inject into the container by default
|
||||
if (!_is.htmlElement(container)) {
|
||||
container = plyr.container
|
||||
if (!_is.htmlElement(target)) {
|
||||
target = plyr.container
|
||||
}
|
||||
|
||||
// Inject controls HTML
|
||||
container.insertAdjacentHTML('beforeend', html);
|
||||
target.insertAdjacentHTML('beforeend', html);
|
||||
|
||||
// Setup tooltips
|
||||
if (config.tooltips.controls) {
|
||||
@ -1475,7 +1484,7 @@
|
||||
_toggleClass(plyr.container, config.classes.stopped, config.autoplay);
|
||||
|
||||
// Add iOS class
|
||||
_toggleClass(plyr.container, config.classes.isIos, plyr.browser.isIos);
|
||||
_toggleClass(plyr.ontainer, config.classes.isIos, plyr.browser.isIos);
|
||||
|
||||
// Add touch class
|
||||
_toggleClass(plyr.container, config.classes.isTouch, plyr.browser.isTouch);
|
||||
@ -1497,9 +1506,6 @@
|
||||
// Embeds
|
||||
if (_inArray(config.types.embed, plyr.type)) {
|
||||
_setupEmbed();
|
||||
|
||||
// Clean up
|
||||
plyr.embedId = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1611,9 +1617,6 @@
|
||||
|
||||
// When embeds are ready
|
||||
function _embedReady() {
|
||||
// Store reference to API
|
||||
plyr.container.plyr.embed = plyr.embed;
|
||||
|
||||
// Setup the UI if full support
|
||||
if (plyr.supported.full) {
|
||||
_setupInterface();
|
||||
@ -1625,12 +1628,6 @@
|
||||
|
||||
// Handle YouTube API ready
|
||||
function _youTubeReady(videoId, container) {
|
||||
// Setup timers object
|
||||
// We have to poll YouTube for updates
|
||||
if (!('timer' in plyr)) {
|
||||
plyr.timer = {};
|
||||
}
|
||||
|
||||
// Setup instance
|
||||
// https://developers.google.com/youtube/iframe_api_reference
|
||||
plyr.embed = new window.YT.Player(container.id, {
|
||||
@ -1650,7 +1647,7 @@
|
||||
},
|
||||
events: {
|
||||
'onError': function(event) {
|
||||
_triggerEvent(plyr.container, 'error', true, {
|
||||
_triggerEvent(container, 'error', true, {
|
||||
code: event.data,
|
||||
embed: event.target
|
||||
});
|
||||
@ -1680,32 +1677,37 @@
|
||||
// Set title
|
||||
config.title = instance.getVideoData().title;
|
||||
|
||||
// Update UI
|
||||
_embedReady();
|
||||
|
||||
// Trigger timeupdate
|
||||
_triggerEvent(plyr.media, 'timeupdate');
|
||||
|
||||
// Reset timer
|
||||
window.clearInterval(plyr.timer.buffering);
|
||||
window.clearInterval(timers.buffering);
|
||||
|
||||
// Setup buffering
|
||||
plyr.timer.buffering = window.setInterval(function() {
|
||||
timers.buffering = window.setInterval(function() {
|
||||
// Get loaded % from YouTube
|
||||
plyr.media.buffered = instance.getVideoLoadedFraction();
|
||||
|
||||
// Trigger progress
|
||||
// Trigger progress only when we actually buffer something
|
||||
if (plyr.media.lastBuffered === null || plyr.media.lastBuffered < plyr.media.buffered) {
|
||||
_triggerEvent(plyr.media, 'progress');
|
||||
}
|
||||
|
||||
// Set last buffer point
|
||||
plyr.media.lastBuffered = plyr.media.buffered;
|
||||
|
||||
// Bail if we're at 100%
|
||||
if (plyr.media.buffered === 1) {
|
||||
window.clearInterval(plyr.timer.buffering);
|
||||
window.clearInterval(timers.buffering);
|
||||
|
||||
// Trigger event
|
||||
_triggerEvent(plyr.media, 'canplaythrough');
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// Update UI
|
||||
_embedReady();
|
||||
|
||||
// Display duration if available
|
||||
_displayDuration();
|
||||
},
|
||||
@ -1714,7 +1716,7 @@
|
||||
var instance = event.target;
|
||||
|
||||
// Reset timer
|
||||
window.clearInterval(plyr.timer.playing);
|
||||
window.clearInterval(timers.playing);
|
||||
|
||||
// Handle events
|
||||
// -1 Unstarted
|
||||
@ -1736,7 +1738,7 @@
|
||||
_triggerEvent(plyr.media, 'playing');
|
||||
|
||||
// Poll to get playback progress
|
||||
plyr.timer.playing = window.setInterval(function() {
|
||||
timers.playing = window.setInterval(function() {
|
||||
// Set the current time
|
||||
plyr.media.currentTime = instance.getCurrentTime();
|
||||
|
||||
@ -1752,7 +1754,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
_triggerEvent(plyr.container, 'statechange', false, {
|
||||
_triggerEvent(container, 'statechange', false, {
|
||||
code: event.data
|
||||
});
|
||||
}
|
||||
@ -1786,13 +1788,14 @@
|
||||
plyr.embed.stop();
|
||||
plyr.media.paused = true;
|
||||
};
|
||||
|
||||
plyr.media.paused = true;
|
||||
plyr.media.currentTime = 0;
|
||||
|
||||
// Update UI
|
||||
_embedReady();
|
||||
|
||||
plyr.embed.getCurrentTime().then(function (value) {
|
||||
plyr.embed.getCurrentTime().then(function(value) {
|
||||
plyr.media.currentTime = value;
|
||||
|
||||
// Trigger timeupdate
|
||||
@ -1811,12 +1814,14 @@
|
||||
plyr.embed.enableTextTrack('en');
|
||||
}*/
|
||||
|
||||
plyr.embed.on('loaded', function() {
|
||||
// Fix keyboard focus issues
|
||||
// https://github.com/Selz/plyr/issues/317
|
||||
plyr.embed.on('loaded', function() {
|
||||
if(_is.htmlElement(plyr.embed.element) && plyr.supported.full) {
|
||||
plyr.embed.element.setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
//console.log(plyr.embed);
|
||||
});
|
||||
|
||||
plyr.embed.on('play', function() {
|
||||
@ -1873,6 +1878,7 @@
|
||||
plyr.embed.pause();
|
||||
plyr.media.paused = true;
|
||||
};
|
||||
|
||||
plyr.media.paused = true;
|
||||
plyr.media.currentTime = 0;
|
||||
|
||||
@ -2333,10 +2339,10 @@
|
||||
var loading = (event.type === 'waiting');
|
||||
|
||||
// Clear timer
|
||||
clearTimeout(plyr.timers.loading);
|
||||
clearTimeout(timers.loading);
|
||||
|
||||
// Timer to prevent flicker when seeking
|
||||
plyr.timers.loading = setTimeout(function() {
|
||||
timers.loading = setTimeout(function() {
|
||||
_toggleClass(plyr.container, config.classes.loading, loading);
|
||||
}, (loading ? 250 : 0));
|
||||
}
|
||||
@ -2566,7 +2572,8 @@
|
||||
|
||||
// Show the player controls in fullscreen mode
|
||||
function _toggleControls(toggle) {
|
||||
if (!config.hideControls || plyr.type === 'audio') {
|
||||
// Don't hide if config says not to, it's audio, or not loaded/ready
|
||||
if (!config.hideControls || plyr.type === 'audio' || !_hasClass(plyr.container, config.classes.ready)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2599,7 +2606,7 @@
|
||||
}
|
||||
|
||||
// Clear timer every movement
|
||||
window.clearTimeout(plyr.timers.hover);
|
||||
window.clearTimeout(timers.hover);
|
||||
|
||||
// If the mouse is not over the controls, set a timeout to hide them
|
||||
if (show || plyr.media.paused) {
|
||||
@ -2619,7 +2626,7 @@
|
||||
// If toggle is false or if we're playing (regardless of toggle),
|
||||
// then set the timer to hide the controls
|
||||
if (!show || !plyr.media.paused) {
|
||||
plyr.timers.hover = window.setTimeout(function() {
|
||||
timers.hover = window.setTimeout(function() {
|
||||
// If the mouse is over the controls (and not entering fullscreen), bail
|
||||
if ((plyr.controls.pressed || plyr.controls.hover) && !isEnterFullscreen) {
|
||||
return;
|
||||
@ -2685,27 +2692,28 @@
|
||||
// Cancel current network requests
|
||||
_cancelRequests();
|
||||
|
||||
// Clean up YouTube stuff
|
||||
if (plyr.type === 'youtube') {
|
||||
// Destroy the embed instance
|
||||
plyr.embed.destroy();
|
||||
|
||||
// Clear timer
|
||||
window.clearInterval(plyr.timer.buffering);
|
||||
window.clearInterval(plyr.timer.playing);
|
||||
}
|
||||
// HTML5 Video
|
||||
else if (plyr.type === 'video' && plyr.videoContainer) {
|
||||
// Remove video wrapper
|
||||
_remove(plyr.videoContainer);
|
||||
}
|
||||
// Destroy instance adn wait for callback
|
||||
// Vimeo throws a wobbly if you don't wait
|
||||
_destroy(setup, false);
|
||||
|
||||
// Setup new source
|
||||
function setup() {
|
||||
// Remove embed object
|
||||
plyr.embed = null;
|
||||
|
||||
// Remove video container
|
||||
if (plyr.type === 'video' && plyr.videoContainer) {
|
||||
_remove(plyr.videoContainer);
|
||||
}
|
||||
|
||||
// Remove the old media
|
||||
_remove(plyr.media);
|
||||
|
||||
// Reset class name
|
||||
if (plyr.container) {
|
||||
plyr.container.removeAttribute('class');
|
||||
}
|
||||
|
||||
// Set the type
|
||||
if ('type' in source) {
|
||||
plyr.type = source.type;
|
||||
@ -2749,7 +2757,7 @@
|
||||
config.autoplay = source.autoplay;
|
||||
}
|
||||
|
||||
// Set attributes for audio video
|
||||
// Set attributes for audio and video
|
||||
if (_inArray(config.types.html5, plyr.type)) {
|
||||
if (config.crossorigin) {
|
||||
plyr.media.setAttribute('crossorigin', '');
|
||||
@ -2765,9 +2773,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Classname reset
|
||||
plyr.container.className = plyr.originalClassName;
|
||||
|
||||
// Restore class hooks
|
||||
_toggleClass(plyr.container, config.classes.fullscreen.active, plyr.isFullscreen);
|
||||
_toggleClass(plyr.container, config.classes.captions.active, plyr.captionsEnabled);
|
||||
@ -2805,9 +2810,7 @@
|
||||
// Set aria title and iframe title
|
||||
config.title = source.title;
|
||||
_setTitle();
|
||||
|
||||
// Reset media objects
|
||||
plyr.container.plyr.media = plyr.media;
|
||||
}
|
||||
}
|
||||
|
||||
// Update poster
|
||||
@ -3106,49 +3109,79 @@
|
||||
plyr.media.load();
|
||||
|
||||
// Debugging
|
||||
_log("Cancelled network requests for old media");
|
||||
_log('Cancelled network requests for old media');
|
||||
}
|
||||
|
||||
// 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() {
|
||||
function _destroy(callback, restore) {
|
||||
// Bail if the element is not initialized
|
||||
if (!plyr.init) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reset container classname
|
||||
plyr.container.setAttribute('class', _getClassname(config.selectors.container));
|
||||
// Type specific stuff
|
||||
switch (plyr.type) {
|
||||
case 'youtube':
|
||||
// Clear timers
|
||||
window.clearInterval(timers.buffering);
|
||||
window.clearInterval(timers.playing);
|
||||
|
||||
// Destroy YouTube API
|
||||
plyr.embed.destroy();
|
||||
|
||||
// Clean up
|
||||
cleanUp();
|
||||
|
||||
break;
|
||||
|
||||
case 'vimeo':
|
||||
// Destroy Vimeo API
|
||||
// then clean up (wait, to prevent postmessage errors)
|
||||
plyr.embed.unload().then(cleanUp);
|
||||
|
||||
// Vimeo does not always return
|
||||
window.setTimeout(cleanUp, 200);
|
||||
|
||||
break;
|
||||
|
||||
case 'video':
|
||||
case 'audio':
|
||||
// Restore native video controls
|
||||
_toggleNativeControls(true);
|
||||
|
||||
// Clean up
|
||||
cleanUp();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
// Default to restore original element
|
||||
if (!_is.boolean(restore)) {
|
||||
restore = true;
|
||||
}
|
||||
|
||||
// Callback
|
||||
if (_is.function(callback)) {
|
||||
callback.call(original);
|
||||
}
|
||||
|
||||
// Bail if we don't need to restore the original element
|
||||
if (!restore) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove init flag
|
||||
plyr.init = false;
|
||||
|
||||
// Remove controls
|
||||
_remove(_getElement(config.selectors.controls.wrapper));
|
||||
// Replace the container with the original element provided
|
||||
plyr.container.parentNode.replaceChild(original, plyr.container);
|
||||
|
||||
// YouTube
|
||||
if (plyr.type === 'youtube') {
|
||||
plyr.embed.destroy();
|
||||
return;
|
||||
// Event
|
||||
_triggerEvent(original, 'destroyed', true);
|
||||
}
|
||||
|
||||
// If video, we need to remove some more
|
||||
if (plyr.type === 'video') {
|
||||
// Remove captions container
|
||||
_remove(_getElement(config.selectors.captions));
|
||||
|
||||
// Remove video wrapper
|
||||
_unwrap(plyr.videoContainer);
|
||||
}
|
||||
|
||||
// Restore native video controls
|
||||
_toggleNativeControls(true);
|
||||
|
||||
// Clone the media element to remove listeners
|
||||
// http://stackoverflow.com/questions/19469881/javascript-remove-all-event-listeners-of-specific-type
|
||||
var clone = plyr.media.cloneNode(true);
|
||||
plyr.media.parentNode.replaceChild(clone, plyr.media);
|
||||
}
|
||||
|
||||
// Setup a player
|
||||
@ -3164,71 +3197,54 @@
|
||||
// Sniff out the browser
|
||||
plyr.browser = _browserSniff();
|
||||
|
||||
// Get the media element
|
||||
plyr.media = plyr.container.querySelectorAll('audio, video')[0];
|
||||
|
||||
// Get the div placeholder for YouTube and Vimeo
|
||||
if (!plyr.media) {
|
||||
plyr.media = plyr.container.querySelectorAll('[data-type]')[0];
|
||||
}
|
||||
|
||||
// Bail if nothing to setup
|
||||
if (!plyr.media) {
|
||||
if (!_is.htmlElement(plyr.media)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load saved settings from localStorage
|
||||
_setupStorage();
|
||||
|
||||
// Get original classname
|
||||
plyr.originalClassName = plyr.container.className;
|
||||
|
||||
// Set media type based on tag or data attribute
|
||||
// Supported: video, audio, vimeo, youtube
|
||||
var tagName = plyr.media.tagName.toLowerCase();
|
||||
var tagName = media.tagName.toLowerCase();
|
||||
if (tagName === 'div') {
|
||||
plyr.type = plyr.media.getAttribute('data-type');
|
||||
plyr.embedId = plyr.media.getAttribute('data-video-id');
|
||||
plyr.type = media.getAttribute('data-type');
|
||||
plyr.embedId = media.getAttribute('data-video-id');
|
||||
|
||||
// Clean up
|
||||
plyr.media.removeAttribute('data-type');
|
||||
plyr.media.removeAttribute('data-video-id');
|
||||
media.removeAttribute('data-type');
|
||||
media.removeAttribute('data-video-id');
|
||||
}
|
||||
else {
|
||||
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));
|
||||
config.crossorigin = (media.getAttribute('crossorigin') !== null);
|
||||
config.autoplay = (config.autoplay || (media.getAttribute('autoplay') !== null));
|
||||
config.loop = (config.loop || (media.getAttribute('loop') !== null));
|
||||
}
|
||||
|
||||
// Check for support
|
||||
plyr.supported = supported(plyr.type);
|
||||
|
||||
// If no native support, bail
|
||||
if (!plyr.supported.basic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap media
|
||||
plyr.container = _wrap(media, document.createElement('div'));
|
||||
|
||||
// Add style hook
|
||||
_toggleStyleHook();
|
||||
|
||||
// If no native support, bail
|
||||
if (!plyr.supported.basic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Debug info
|
||||
_log(plyr.browser.name + ' ' + plyr.browser.version);
|
||||
_log('' + plyr.browser.name + ' ' + plyr.browser.version);
|
||||
|
||||
// Setup media
|
||||
_setupMedia();
|
||||
|
||||
// Setup interface
|
||||
if (_inArray(config.types.html5, plyr.type)) {
|
||||
// Bail if no support
|
||||
if (!plyr.supported.full) {
|
||||
// Successful setup
|
||||
plyr.init = true;
|
||||
|
||||
// Don't inject controls if no full support
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup UI
|
||||
_setupInterface();
|
||||
|
||||
@ -3252,7 +3268,7 @@
|
||||
function _setupInterface() {
|
||||
// Don't setup interface if no support
|
||||
if (!plyr.supported.full) {
|
||||
_warn('No full support for this media type (' + plyr.type + ')');
|
||||
_warn('Basic support only', plyr.type);
|
||||
|
||||
// Remove controls
|
||||
_remove(_getElement(config.selectors.controls.wrapper));
|
||||
@ -3311,6 +3327,9 @@
|
||||
|
||||
// Ready event
|
||||
_triggerEvent(plyr.container, 'ready', true);
|
||||
|
||||
// Class
|
||||
_toggleClass(plyr.container, config.classes.ready, true);
|
||||
}
|
||||
|
||||
// Initialize instance
|
||||
@ -3322,9 +3341,15 @@
|
||||
}
|
||||
|
||||
return {
|
||||
media: plyr.media,
|
||||
getContainer: function() { return plyr.container },
|
||||
getEmbed: function() { return plyr.embed; },
|
||||
getMedia: function() { return plyr.media; },
|
||||
getType: function() { return plyr.type; },
|
||||
isReady: function() { return _hasClass(plyr.container, config.classes.ready); },
|
||||
on: function(event, callback) { _on(plyr.container, event, callback); },
|
||||
play: _play,
|
||||
pause: _pause,
|
||||
stop: function() { _pause(); _seek(); },
|
||||
restart: _seek,
|
||||
rewind: _rewind,
|
||||
forward: _forward,
|
||||
@ -3340,8 +3365,7 @@
|
||||
isFullscreen: function() { return plyr.isFullscreen || false; },
|
||||
support: function(mimeType) { return _supportMime(plyr, mimeType); },
|
||||
destroy: _destroy,
|
||||
restore: _init,
|
||||
getCurrentTime: function() { return plyr.media.currentTime; }
|
||||
getCurrentTime: function() { return media.currentTime; }
|
||||
};
|
||||
}
|
||||
|
||||
@ -3350,10 +3374,18 @@
|
||||
var x = new XMLHttpRequest();
|
||||
|
||||
// If the id is set and sprite exists, bail
|
||||
if (_is.string(id) && document.querySelector('#' + id) !== null) {
|
||||
if (_is.string(id) && _is.htmlElement(document.querySelector('#' + id))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create placeholder (to prevent loading twice)
|
||||
var c = document.createElement('div');
|
||||
c.setAttribute('hidden', '');
|
||||
if (_is.string(id)) {
|
||||
c.setAttribute('id', id);
|
||||
}
|
||||
document.body.insertBefore(c, document.body.childNodes[0]);
|
||||
|
||||
// Check for CORS support
|
||||
if ('withCredentials' in x) {
|
||||
x.open('GET', url, true);
|
||||
@ -3364,13 +3396,7 @@
|
||||
|
||||
// Inject hidden div with sprite on load
|
||||
x.onload = function() {
|
||||
var c = document.createElement('div');
|
||||
c.setAttribute('hidden', '');
|
||||
if (_is.string(id)) {
|
||||
c.setAttribute('id', id);
|
||||
}
|
||||
c.innerHTML = x.responseText;
|
||||
document.body.insertBefore(c, document.body.childNodes[0]);
|
||||
}
|
||||
|
||||
x.send();
|
||||
@ -3418,8 +3444,8 @@
|
||||
// Setup function
|
||||
function setup(targets, options) {
|
||||
// Get the players
|
||||
var elements = [],
|
||||
containers = [],
|
||||
var players = [],
|
||||
instances = [],
|
||||
selector = [defaults.selectors.html5, defaults.selectors.embed].join(',');
|
||||
|
||||
// Select the elements
|
||||
@ -3453,6 +3479,20 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to container list
|
||||
function add(target, media) {
|
||||
if (!_hasClass(media, defaults.classes.hook)) {
|
||||
players.push({
|
||||
// Always wrap in a <div> for styling
|
||||
//container: _wrap(media, document.createElement('div')),
|
||||
// Could be a container or the media itself
|
||||
target: target,
|
||||
// This should be the <video>, <audio> or <div> (YouTube/Vimeo)
|
||||
media: media
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the targets have multiple media elements
|
||||
for (var i = 0; i < targets.length; i++) {
|
||||
var target = targets[i];
|
||||
@ -3460,38 +3500,38 @@
|
||||
// Get children
|
||||
var children = target.querySelectorAll(selector);
|
||||
|
||||
// If there's more than one media element, wrap them
|
||||
if (children.length > 1) {
|
||||
// If there's more than one media element child, wrap them
|
||||
if (children.length) {
|
||||
for (var x = 0; x < children.length; x++) {
|
||||
containers.push({
|
||||
element: _wrap(children[x], document.createElement('div')),
|
||||
original: target
|
||||
});
|
||||
add(target, children[x]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
containers.push({
|
||||
element: target
|
||||
});
|
||||
// Wrap target if it's a media element
|
||||
else if (_matches(target, selector)) {
|
||||
add(target, target);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a player instance for each element
|
||||
for (var key in containers) {
|
||||
var element = containers[key].element,
|
||||
original = containers[key].original || element;
|
||||
players.forEach(function(player) {
|
||||
var element = player.target,
|
||||
media = player.media,
|
||||
match = false;
|
||||
|
||||
// Wrap each media element if is target is media element
|
||||
// as opposed to a wrapper
|
||||
if (_matches(element, selector)) {
|
||||
// Wrap in a <div>
|
||||
element = _wrap(element, document.createElement('div'));
|
||||
// The target element can also be the media element
|
||||
if (media === element) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
// Setup a player instance and add to the element
|
||||
if (!('plyr' in element)) {
|
||||
// Create instance-specific config
|
||||
var config = _extend({}, defaults, options, JSON.parse(original.getAttribute('data-plyr')));
|
||||
var data = {};
|
||||
|
||||
// Try parsing data attribute config
|
||||
try { data = JSON.parse(element.getAttribute('data-plyr')); }
|
||||
catch(e) { }
|
||||
|
||||
var config = _extend({}, defaults, options, data);
|
||||
|
||||
// Bail if not enabled
|
||||
if (!config.enabled) {
|
||||
@ -3499,28 +3539,73 @@
|
||||
}
|
||||
|
||||
// Create new instance
|
||||
var instance = new Plyr(element, config);
|
||||
var instance = new Plyr(media, config);
|
||||
|
||||
// Go to next if setup failed
|
||||
if (!_is.object(instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set plyr to false if setup failed
|
||||
element.plyr = (Object.keys(instance).length ? instance : false);
|
||||
// Maybe we remove this and add a .get() function to get the instance
|
||||
// If passed a media element or container?
|
||||
media.plyr = instance;
|
||||
|
||||
// Callback
|
||||
_triggerEvent(original, 'setup', true, {
|
||||
plyr: element.plyr
|
||||
// Set class hook
|
||||
_toggleClass(media, defaults.classes.setup, true);
|
||||
|
||||
// Listen for events if debugging
|
||||
if (config.debug) {
|
||||
var events = config.events.concat(['setup', 'ready', 'statechange', 'enterfullscreen', 'exitfullscreen', 'captionsenabled', 'captionsdisabled']);
|
||||
|
||||
_on(instance.getContainer(), events.join(' '), function() {
|
||||
console.log([config.logPrefix, 'event:', event.type].join(' '));
|
||||
});
|
||||
}
|
||||
|
||||
// Callback
|
||||
_triggerEvent(instance.getContainer(), 'setup', true, {
|
||||
plyr: instance
|
||||
});
|
||||
|
||||
// Add to return array even if it's already setup
|
||||
elements.push(element);
|
||||
instances.push(instance);
|
||||
});
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
return elements;
|
||||
// Get all instances within a provided container
|
||||
function get(container) {
|
||||
// Get selector if string passed
|
||||
if (_is.string(container)) {
|
||||
container = document.querySelector(container);
|
||||
}
|
||||
// Use body by default to get all on page
|
||||
else if (_is.undefined(container)) {
|
||||
container = document.body;
|
||||
}
|
||||
|
||||
// If we have a HTML element
|
||||
if (_is.htmlElement(container)) {
|
||||
var elements = container.querySelectorAll('.' + defaults.classes.setup),
|
||||
instances = [];
|
||||
|
||||
Array.prototype.slice.call(elements).forEach(function(element) {
|
||||
instances.push(element.plyr);
|
||||
});
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
setup: setup,
|
||||
supported: supported,
|
||||
loadSprite: loadSprite
|
||||
loadSprite: loadSprite,
|
||||
get: get
|
||||
};
|
||||
}));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user