1/** 2 * jquery.youtube-background v1.0.14 | Nikola Stamatovic <@stamat> | MIT 3 */ 4 5(function () { 6 'use strict'; 7 8 function hasClass(element, className) { 9 if (element.classList) { 10 return element.classList.contains(className); 11 } 12 return new RegExp('\\b'+ className+'\\b').test(element.className); 13 } 14 15 function addClass(element, classNames) { 16 if (element.classList) { 17 const classes = classNames.split(' '); 18 for (var i = 0; i < classes.length; i++) { 19 const el_class = classes[i]; 20 element.classList.add(el_class); 21 } 22 return; 23 } 24 25 if (!hasClass(element, className)) { 26 element.className += ' ' + className; 27 } 28 } 29 30 function removeClass(element, className) { 31 if (element.classList) { 32 element.classList.remove(className); 33 return; 34 } 35 36 element.className = element.className.replace(new RegExp('\\b'+ className+'\\b', 'g'), ''); 37 } 38 39 function isMobile() { 40 let is_mobile = false; 41 (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) is_mobile = true;})(navigator.userAgent||navigator.vendor||window.opera); 42 43 return is_mobile; 44 } 45 46 function getRandomIntInclusive(min, max) { 47 min = Math.ceil(min); 48 max = Math.floor(max); 49 return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive 50 } 51 52 function parseResolutionString(res) { 53 const pts = res.split(/\s?:\s?/i); 54 const DEFAULT_RESOLUTION = 16/9; 55 if (pts.length < 2) { 56 return DEFAULT_RESOLUTION; 57 } 58 59 const w = parseInt(pts[0], 10); 60 const h = parseInt(pts[1], 10); 61 62 if (isNaN(w) || isNaN(h)) { 63 return DEFAULT_RESOLUTION; 64 } 65 66 return w/h; 67 } 68 69 function parseProperties(params, defaults, element, attr_prefix) { 70 let res_params = {}; 71 72 if (!params) { 73 res_params = defaults; 74 } else { 75 for (let k in defaults) { 76 if (!params.hasOwnProperty(k)) { 77 //load in defaults if the param hasn't been set 78 res_params[k] = defaults[k]; 79 } 80 } 81 } 82 83 if (!element) return res_params; 84 // load params from data attributes 85 for (let k in res_params) { 86 let data; 87 88 if (attr_prefix instanceof Array) { 89 for (var i = 0; i < attr_prefix.length; i++) { 90 const temp_data = element.getAttribute(attr_prefix[i]+k); 91 if (temp_data) { 92 data = temp_data; 93 break; 94 } 95 } 96 } else { 97 data = element.getAttribute(attr_prefix+k); 98 } 99 100 if (data !== undefined && data !== null) { 101 data = data === 'false' ? false : data; 102 data = /^\d+$/.test(data) ? parseInt(data, 10) : data; 103 data = /^\d+\.\d+$/.test(data) ? parseFloat(data) : data; 104 res_params[k] = data; 105 } 106 } 107 108 return res_params; 109 } 110 111 const tag = document.createElement('script'); 112 tag.src = "https://www.youtube.com/player_api"; 113 const firstScriptTag = document.getElementsByTagName('script')[0]; 114 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 115
116 function YoutubeBackground(elem, params, id, uid) { 117 this.is_mobile = isMobile(); 118 119 this.element = elem; 120 this.ytid = id; 121 this.uid = uid; 122 this.player = null; 123 this.buttons = {}; 124 125 this.state = {}; 126 this.state.play = false; 127 this.state.mute = false; 128 129 this.params = {}; 130 131 const DEFAULTS = { 132 'pause': false, //deprecated 133 'play-button': false, 134 'mute-button': false, 135 'autoplay': true, 136 'muted': true, 137 'loop': true, 138 'mobile': true, 139 'load-background': true, 140 'resolution': '16:9', 141 'onStatusChange': function() {}, 142 'inline-styles': true, 143 'fit-box': false, 144 'offset': 200, 145 'start-at': 0, 146 'end-at': 0, 147 'poster': null 148 }; 149 150 this.__init__ = function () { 151 if (!this.ytid) { 152 return; 153 } 154 155 this.params = parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']); 156 //pause deprecated 157 if (this.params.pause) { 158 this.params['play-button'] = this.params.pause; 159 } 160 this.params.resolution_mod = parseResolutionString(this.params.resolution); 161 this.state.playing = this.params.autoplay; 162 this.state.muted = this.params.muted; 163 this.buildHTML(); 164 this.injectPlayer(); 165 166 167 if (this.params['play-button']) { 168 this.generateActionButton({ 169 name: 'play', 170 className: 'play-toggle', 171 innerHtml: '<i class="fa"></i>', 172 initialState: false, 173 stateClassName: 'paused', 174 condition_parameter: 'autoplay', 175 stateChildClassNames: ['fa-pause-circle', 'fa-play-circle'], 176 actions: ['play', 'pause'] 177 }); 178 } 179 180 if (this.params['mute-button']) { 181 this.generateActionButton({ 182 name: 'mute', 183 className: 'mute-toggle', 184 innerHtml: '<span class="icon icon-notext"></span>', 185 initialState: true, 186 stateClassName: 'muted', 187 condition_parameter: 'muted', 188 stateChildClassNames: ['icon-volume-high', 'icon-volume-mute2'], 189 actions: ['unmute', 'mute'] 190 }); 191 } 192 }; 193 194 this.__init__(); 195 } 196 197 YoutubeBackground.prototype.initYTPlayer = function () { 198 const self = this; 199 if (window.hasOwnProperty('YT')) { 200 this.player = new YT.Player(this.uid, { 201 events: { 202 'onReady': function(event) { 203 self.onVideoPlayerReady(event); 204 }, 205 'onStateChange': function(event) { 206 self.onVideoStateChange(event); 207 }, 208 'onError' : function(event) { 209 //console.error('player_api', event); 210 } 211 } 212 }); 213 } 214 }; 215 216 YoutubeBackground.prototype.seekTo = function (seconds) { 217 if (seconds > 0) { 218 this.player.seekTo(seconds, true); 219 } 220 }; 221 222 YoutubeBackground.prototype.onVideoPlayerReady = function (event) { 223 if (this.params.autoplay) { 224 this.seekTo(this.params['start-at']); 225 this.player.playVideo(); 226 } 227 }; 228 229 YoutubeBackground.prototype.onVideoStateChange = function (event) { 230 if (event.data === 0 && this.params.loop) { 231 this.seekTo(this.params['start-at']); 232 this.player.playVideo(); 233 } 234 235 if (event.data === -1 && this.params.autoplay) { 236 this.seekTo(this.params['start-at']); 237 this.player.playVideo(); 238 this.element.dispatchEvent(new CustomEvent('video-background-play', { bubbles: true, detail: this })); 239 } 240 241 if (event.data === 1) { 242 this.iframe.style.opacity = 1; 243 } 244 245 this.params["onStatusChange"](event); 246 }; 247 248 YoutubeBackground.prototype.injectPlayer = function () { 249 this.iframe = document.createElement('iframe'); 250 this.iframe.setAttribute('frameborder', 0); 251 this.iframe.setAttribute('allow', 'autoplay; mute'); 252 let src = `https://www.youtube.com/embed/${this.ytid}?&enablejsapi=1&disablekb=1&controls=0&rel=0&iv_load_policy=3&cc_load_policy=0&playsinline=1&showinfo=0&modestbranding=1&fs=0`; 253 254 if (this.params.muted) { 255 src += '&mute=1'; 256 } 257 258 if (this.params.autoplay) { 259 src += '&autoplay=1'; 260 } 261 262 if (this.params.loop) { 263 src += '&loop=1'; 264 } 265 266 if (this.params['end-at'] > 0) { 267 src += `&end=${this.params['end-at']}`; 268 } 269 270 this.iframe.src = src; 271 272 if (this.uid) { 273 this.iframe.id = this.uid; 274 } 275 276 if (this.params['inline-styles']) { 277 this.iframe.style.top = '50%'; 278 this.iframe.style.left = '50%'; 279 this.iframe.style.transform = 'translate(-50%, -50%)'; 280 this.iframe.style.position = 'absolute'; 281 this.iframe.style.opacity = 0; 282 } 283 284 this.element.appendChild(this.iframe); 285 286 if (this.params['fit-box']) { 287 this.iframe.style.width = '100%'; 288 this.iframe.style.height = '100%'; 289 } else { 290 const self = this; 291 292 //TODOâï¸: maybe a spacer or at least add requestAnimationFrame 293 function onResize() { 294 const h = self.iframe.parentNode.offsetHeight + self.params.offset; // since showinfo is deprecated and ignored after September 25, 2018. we add +200 to hide it in the overflow 295 const w = self.iframe.parentNode.offsetWidth + self.params.offset; 296 const res = self.params.resolution_mod; 297 298 if (res > w/h) {
299 self.iframe.style.width = h*res + 'px'; 300 self.iframe.style.height = h + 'px'; 301 } else { 302 self.iframe.style.width = w + 'px'; 303 self.iframe.style.height = w/res + 'px'; 304 } 305 } 306 307 if (window.hasOwnProperty('ResizeObserver')) { 308 const resize_observer = new ResizeObserver(() => { 309 window.requestAnimationFrame(onResize); 310 }); 311 resize_observer.observe(this.element); 312 } else { 313 window.addEventListener('resize', () => { 314 window.requestAnimationFrame(onResize); 315 }); 316 } 317 onResize(); 318 } 319 }; 320 321 YoutubeBackground.prototype.buildHTML = function () { 322 const parent = this.element.parentNode; 323 // wrap 324 addClass(this.element, 'youtube-background video-background'); 325 326 //set css rules 327 const wrapper_styles = { 328 "height" : "100%", 329 "width" : "100%", 330 "z-index": "0", 331 "position": "absolute", 332 "overflow": "hidden", 333 "top": 0, // added by @insad 334 "left": 0, 335 "bottom": 0, 336 "right": 0 337 }; 338 339 if (!this.params['mute-button']) { 340 wrapper_styles["pointer-events"] = "none"; // avoid right mouse click popup menu 341 } 342 343 if (this.params['load-background'] || this.params['poster']) { 344 if (this.params['load-background']) wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.ytid+'/maxresdefault.jpg)'; 345 if (this.params['poster']) wrapper_styles['background-image'] = this.params['poster']; 346 wrapper_styles['background-size'] = 'cover'; 347 wrapper_styles['background-repeat'] = 'no-repeat'; 348 wrapper_styles['background-position'] = 'center'; 349 } 350 351 if (this.params['inline-styles']) { 352 for (let property in wrapper_styles) { 353 this.element.style[property] = wrapper_styles[property]; 354 } 355 356 if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) { 357 parent.style.position = 'relative'; 358 } 359 } 360 361 if (this.is_mobile && !this.params.mobile) { 362 return this.element; 363 } 364 365 // set play/mute controls wrap 366 if (this.params['play-button'] || this.params['mute-button']) { 367 const controls = document.createElement('div'); 368 controls.className = 'video-background-controls'; 369 370 controls.style.position = 'absolute'; 371 // controls.style.top = '-10px'; 372 controls.style.top = '10px'; 373 controls.style.right = '10px'; 374 controls.style['z-index'] = 2; 375 376 this.controls_element = controls; 377 parent.appendChild(controls); 378 } 379 380 return this.element; 381 }; 382 383 YoutubeBackground.prototype.play = function () { 384 //TODO: solve this with ARIA toggle states. P.S. warning repetitive code!!! 385 if (this.buttons.hasOwnProperty('play')) { 386 const btn_obj = this.buttons.play; 387 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 388 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 389 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 390 } 391 392 if (this.player) { 393 if (this.params['start-at'] && this.player.getCurrentTime() < this.params['start-at'] ) { 394 this.seekTo(this.params['start-at']); 395 } 396 this.player.playVideo(); 397 this.element.dispatchEvent(new CustomEvent('video-background-play', { bubbles: true, detail: this })); 398 } 399 }; 400 401 YoutubeBackground.prototype.pause = function () { 402 //TODO: solve this with ARIA toggle states 403 if (this.buttons.hasOwnProperty('play')) { 404 const btn_obj = this.buttons.play; 405 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 406 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 407 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 408 } 409 410 if (this.player) { 411 this.player.pauseVideo(); 412 this.element.dispatchEvent(new CustomEvent('video-background-pause', { bubbles: true, detail: this })); 413 } 414 }; 415 416 YoutubeBackground.prototype.unmute = function () { 417 //TODO: solve this with ARIA toggle states 418 if (this.buttons.hasOwnProperty('mute')) { 419 const btn_obj = this.buttons.mute; 420 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 421 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 422 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 423 } 424 425 if (this.player) { 426 this.player.unMute(); 427 this.element.dispatchEvent(new CustomEvent('video-background-unmute', { bubbles: true, detail: this })); 428 } 429 }; 430
431 YoutubeBackground.prototype.mute = function () { 432 //TODO: solve this with ARIA toggle states 433 if (this.buttons.hasOwnProperty('mute')) { 434 const btn_obj = this.buttons.mute; 435 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 436 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 437 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 438 } 439 440 if (this.player) { 441 this.player.mute(); 442 this.element.dispatchEvent(new CustomEvent('video-background-mute', { bubbles: true, detail: this })); 443 } 444 }; 445 446 //TODO: refactor states to be equal for all buttons 447 YoutubeBackground.prototype.generateActionButton = function (obj) { 448 const btn = document.createElement('button'); 449 btn.className = obj.className; 450 btn.innerHTML = obj.innerHtml; 451 addClass(btn.firstChild, obj.stateChildClassNames[0]); 452 453 //TODO: solve this with ARIA toggle states 454 if (this.params[obj.condition_parameter] === obj.initialState) { 455 addClass(btn, obj.stateClassName); 456 removeClass(btn.firstChild, obj.stateChildClassNames[0]); 457 addClass(btn.firstChild, obj.stateChildClassNames[1]); 458 } 459 460 const self = this; 461 btn.addEventListener('click', function(e) { 462 if (hasClass(this, obj.stateClassName)) { 463 self.state[obj.name] = false; 464 self[obj.actions[0]](); 465 } else { 466 self.state[obj.name] = true; 467 self[obj.actions[1]](); 468 } 469 }); 470 471 this.buttons[obj.name] = { 472 element: btn, 473 button_properties: obj 474 }; 475 476 this.controls_element.appendChild(btn); 477 }; 478 479 function VimeoBackground(elem, params, id, uid) { 480 this.is_mobile = isMobile(); 481 482 this.element = elem; 483 this.vid = id; 484 this.uid = uid; 485 this.player = null; 486 this.buttons = {}; 487 488 this.state = {}; 489 this.state.play = false; 490 this.state.mute = false; 491 492 this.params = {}; 493 494 const DEFAULTS = { 495 'pause': false, //deprecated 496 'play-button': false, 497 'mute-button': false, 498 'autoplay': true, 499 'muted': true, 500 'loop': true, 501 'mobile': false, 502 'load-background': true, 503 'resolution': '16:9', 504 'inline-styles': true, 505 'fit-box': false, 506 'offset': 200, 507 'start-at': 0, 508 'poster': null 509 }; 510 511 this.__init__ = function () { 512 if (!this.vid) { 513 return; 514 } 515 516 this.params = parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']); 517 //pause deprecated 518 if (this.params.pause) { 519 this.params['play-button'] = this.params.pause; 520 } 521 this.params.resolution_mod = parseResolutionString(this.params.resolution); 522 this.state.playing = this.params.autoplay; 523 this.state.muted = this.params.muted; 524 525 this.buildHTML(); 526 this.injectPlayer(); 527 }; 528 529 this.__init__(); 530 } 531 532 VimeoBackground.prototype.injectPlayer = function () { 533 this.iframe = document.createElement('iframe'); 534 this.iframe.setAttribute('frameborder', 0); 535 this.iframe.setAttribute('allow', ['autoplay; mute']); 536 let src = 'https://player.vimeo.com/video/'+this.vid+'?background=1&controls=0'; 537 538 if (this.params.muted) { 539 src += '&muted=1'; 540 } 541 542 if (this.params.autoplay) { 543 src += '&autoplay=1'; 544 } 545 546 if (this.params.loop) {
547 src += '&loop=1&autopause=0'; 548 } 549 550 //WARNâï¸ this is a hash not a query param 551 if (this.params['start-at']) { 552 src += '#t=' + this.params['start-at'] + 's'; 553 } 554 555 this.iframe.src = src; 556 557 if (this.uid) { 558 this.iframe.id = this.uid; 559 } 560 561 if (this.params['load-background'] || this.params['poster']) { 562 //if (this.params['load-background']) wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.ytid+'/maxresdefault.jpg)'; 563 if (this.params['poster']) wrapper_styles['background-image'] = this.params['poster']; 564 wrapper_styles['background-size'] = 'cover'; 565 wrapper_styles['background-repeat'] = 'no-repeat'; 566 wrapper_styles['background-position'] = 'center'; 567 } 568 569 if (this.params['inline-styles']) { 570 this.iframe.style.top = '50%'; 571 this.iframe.style.left = '50%'; 572 this.iframe.style.transform = 'translate(-50%, -50%)'; 573 this.iframe.style.position = 'absolute'; 574 this.iframe.style.opacity = 1; 575 } 576 577 this.element.appendChild(this.iframe); 578 579 if (this.params['fit-box']) { 580 this.iframe.style.width = '100%'; 581 this.iframe.style.height = '100%'; 582 } else { 583 const self = this; 584 585 const onResize = function() { 586 const h = self.iframe.parentNode.offsetHeight + self.params.offset; // since showinfo is deprecated and ignored after September 25, 2018. we add +200 to hide it in the overflow 587 const w = self.iframe.parentNode.offsetWidth + self.params.offset; 588 const res = self.params.resolution_mod; 589 590 if (res > w/h) { 591 self.iframe.style.width = h*res + 'px'; 592 self.iframe.style.height = h + 'px'; 593 } else { 594 self.iframe.style.width = w + 'px'; 595 self.iframe.style.height = w/res + 'px'; 596 } 597 }; 598 599 if (window.hasOwnProperty('ResizeObserver')) { 600 const resize_observer = new ResizeObserver(() => { 601 window.requestAnimationFrame(onResize); 602 }); 603 resize_observer.observe(this.element); 604 } else { 605 window.addEventListener('resize', () => { 606 window.requestAnimationFrame(onResize); 607 }); 608 } 609 onResize(); 610 } 611 }; 612 613 VimeoBackground.prototype.buildHTML = function () { 614 const parent = this.element.parentNode; 615 // wrap 616 addClass(this.element, 'youtube-background'); 617 618 //set css rules 619 const wrapper_styles = { 620 "height" : "100%", 621 "width" : "100%", 622 "z-index": "0", 623 "position": "absolute", 624 "overflow": "hidden", 625 "top": 0, // added by @insad 626 "left": 0, 627 "bottom": 0, 628 "right": 0 629 }; 630 631 if (this.params['load-background'] || this.params['poster']) { 632 //if (this.params['load-background']) wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.ytid+'/maxresdefault.jpg)'; 633 if (this.params['poster']) wrapper_styles['background-image'] = this.params['poster']; 634 wrapper_styles['background-size'] = 'cover'; 635 wrapper_styles['background-repeat'] = 'no-repeat'; 636 wrapper_styles['background-position'] = 'center'; 637 } 638 639 if (!this.params['mute-button']) { 640 wrapper_styles["pointer-events"] = "none"; // avoid right mouse click popup menu 641 } 642 643 if (this.params['load-background']) { 644 //TODO: wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.vid+'/maxresdefault.jpg)'; 645 wrapper_styles['background-size'] = 'cover'; 646 wrapper_styles['background-repeat'] = 'no-repeat'; 647 wrapper_styles['background-position'] = 'center'; 648 } 649 650 if (this.params['inline-styles']) { 651 for (let property in wrapper_styles) { 652 this.element.style[property] = wrapper_styles[property]; 653 } 654 655 if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) { 656 parent.style.position = 'relative'; 657 } 658 } 659 660 return this.element; 661 }; 662 663 function VideoBackground(elem, params, vid_data, uid) { 664 this.is_mobile = isMobile(); 665 666 this.element = elem; 667 this.link = vid_data.link; 668 this.ext = vid_data.id; 669 this.uid = uid; 670 this.player = null; 671 this.buttons = {}; 672 673 this.state = {}; 674 this.state.play = false;
675 this.state.mute = false; 676 677 this.params = {}; 678 679 const MIME_MAP = { 680 'ogv' : 'video/ogg', 681 'ogm' : 'video/ogg', 682 'ogg' : 'video/ogg', 683 'avi' : 'video/avi', 684 'mp4' : 'video/mp4', 685 'webm' : 'video/webm' 686 }; 687 688 const DEFAULTS = { 689 'pause': false, //deprecated 690 'play-button': false, 691 'mute-button': false, 692 'autoplay': true, 693 'muted': true, 694 'loop': true, 695 'mobile': false, 696 'resolution': '16:9', 697 'inline-styles': true, 698 'fit-box': false, 699 'offset': 200, 700 'start-at': 0, 701 'end-at': 0, 702 'poster': null 703 }; 704 705 this.__init__ = function () { 706 if (!this.link || !this.ext) { 707 return; 708 } 709 710 this.mime = MIME_MAP[this.ext.toLowerCase()]; 711 this.params = parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']); 712 //pause deprecated 713 if (this.params.pause) { 714 this.params['play-button'] = this.params.pause; 715 } 716 this.params.resolution_mod = parseResolutionString(this.params.resolution); 717 this.state.playing = this.params.autoplay; 718 this.state.muted = this.params.muted; 719 720 this.buildHTML(); 721 this.injectPlayer(); 722 723 724 if (this.params['play-button']) { 725 this.generateActionButton({ 726 name: 'play', 727 className: 'play-toggle', 728 innerHtml: '<i class="fa"></i>', 729 initialState: false, 730 stateClassName: 'paused', 731 condition_parameter: 'autoplay', 732 stateChildClassNames: ['fa-pause-circle', 'fa-play-circle'], 733 actions: ['play', 'pause'] 734 }); 735 } 736 737 if (this.params['mute-button']) { 738 this.generateActionButton({ 739 name: 'mute', 740 className: 'mute-toggle', 741 innerHtml: '<span class="icon icon-notext"></span>', 742 initialState: true, 743 stateClassName: 'muted', 744 condition_parameter: 'muted', 745 stateChildClassNames: ['icon-volume-high', 'icon-volume-mute2'], 746 actions: ['unmute', 'mute'] 747 }); 748 } 749 }; 750 751 this.__init__(); 752 } 753 754 VideoBackground.prototype.seekTo = function (seconds) { 755 if (this.player.hasOwnProperty('fastSeek')) { 756 this.player.fastSeek(seconds); 757 return; 758 } 759 this.player.currentTime = seconds; 760 }; 761 762 VideoBackground.prototype.injectPlayer = function () { 763 this.player = document.createElement('video'); 764 this.player.muted = this.params.muted; 765 this.player.autoplay = this.params.autoplay; 766 this.player.loop = this.params.loop; 767 this.player.playsinline = true; 768 769 this.player.setAttribute('id', this.uid); 770 771 if (this.params['inline-styles']) { 772 this.player.style.top = '50%'; 773 this.player.style.left = '50%'; 774 this.player.style.transform = 'translate(-50%, -50%)'; 775 this.player.style.position = 'absolute'; 776 this.player.style.opacity = 0; 777 778 this.player.addEventListener('canplay', (e) => { 779 e.target.style.opacity = 1; 780 }); 781 } 782 783 const self = this; 784 /* 785 this.player.addEventListener('canplay', (e) => { 786 if (self.params['start-at'] && self.params.autoplay) { 787 self.seekTo(self.params['start-at']); 788 } 789 }); 790 791 this.player.addEventListener('canplaythrough', (e) => { 792 if (self.params['end-at'] > 0) { 793 self.player.addEventListener('timeupdate', (e) => { 794 if (self.params['end-at'] >= self.player.currentTime) { 795 self.seekTo(self.params['start-at']); 796 } 797 }); 798 } 799 }); 800 */ 801 802 const source = document.createElement('source'); 803 source.setAttribute('src', this.link); 804 source.setAttribute('type', this.mime); 805 this.player.appendChild(source); 806 this.element.appendChild(this.player); 807 808 if (this.params['fit-box']) { 809 this.player.style.width = '100%'; 810 this.player.style.height = '100%'; 811 } else { 812 //TODOâï¸: maybe a spacer or at least add requestAnimationFrame 813 function onResize() { 814 const h = self.player.parentNode.offsetHeight + self.params.offset; // since showinfo is deprecated and ignored after September 25, 2018. we add +200 to hide it in the overflow 815 const w = self.player.parentNode.offsetWidth + self.params.offset; 816 const res = self.params.resolution_mod; 817 818 if (res > w/h) { 819 self.player.style.width = h*res + 'px'; 820 self.player.style.height = h + 'px'; 821 } else { 822 self.player.style.width = w + 'px'; 823 self.player.style.height = w/res + 'px'; 824 } 825 } 826 827 if (window.hasOwnProperty('ResizeObserver')) { 828 const resize_observer = new ResizeObserver(() => { 829 window.requestAnimationFrame(onResize); 830 }); 831 resize_observer.observe(this.element); 832 } else { 833 window.addEventListener('resize', () => { 834 window.requestAnimationFrame(onResize); 835 }); 836 } 837 onResize(); 838 } 839 }; 840
841 VideoBackground.prototype.buildHTML = function () { 842 const parent = this.element.parentNode; 843 // wrap 844 addClass(this.element, 'video-background'); 845 846 //set css rules 847 const wrapper_styles = { 848 "height" : "100%", 849 "width" : "100%", 850 "z-index": "0", 851 "position": "absolute", 852 "overflow": "hidden", 853 "top": 0, // added by @insad 854 "left": 0, 855 "bottom": 0, 856 "right": 0 857 }; 858 859 if (!this.params['mute-button']) { 860 wrapper_styles["pointer-events"] = "none"; // avoid right mouse click popup menu 861 } 862 863 if (this.params['load-background'] || this.params['poster']) { 864 if (this.params['poster']) wrapper_styles['background-image'] = `url('${this.params['poster']}')`; 865 wrapper_styles['background-size'] = 'cover'; 866 wrapper_styles['background-repeat'] = 'no-repeat'; 867 wrapper_styles['background-position'] = 'center'; 868 } 869 870 if (this.params['inline-styles']) { 871 for (let property in wrapper_styles) { 872 this.element.style[property] = wrapper_styles[property]; 873 } 874 875 if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) { 876 parent.style.position = 'relative'; 877 } 878 } 879 880 if (this.is_mobile && !this.params.mobile) { 881 return this.element; 882 } 883 884 // set play/mute controls wrap 885 if (this.params['play-button'] || this.params['mute-button']) { 886 const controls = document.createElement('div'); 887 controls.className = 'video-background-controls'; 888 889 controls.style.position = 'absolute'; 890 controls.style.top = '-10px'; 891 controls.style.right = '10px'; 892 controls.style['z-index'] = 2; 893 894 this.controls_element = controls; 895 parent.appendChild(controls); 896 } 897 898 return this.element; 899 }; 900 901 VideoBackground.prototype.play = function () { 902 //TODO: solve this with ARIA toggle states. P.S. warning repetitive code!!! 903 if (this.buttons.hasOwnProperty('play')) { 904 const btn_obj = this.buttons.play; 905 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 906 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 907 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 908 } 909 910 if (this.player) { 911 /* if (this.params['start-at'] && this.player.currentTime < this.params['start-at'] ) { 912 this.seekTo(this.params['start-at']); 913 } */ 914 this.player.play(); 915 this.element.dispatchEvent(new CustomEvent('video-background-play', { bubbles: true, detail: this })); 916 } 917 }; 918 919 VideoBackground.prototype.pause = function () { 920 //TODO: solve this with ARIA toggle states 921 if (this.buttons.hasOwnProperty('play')) { 922 const btn_obj = this.buttons.play; 923 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 924 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 925 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 926 } 927 928 if (this.player) { 929 this.player.pause(); 930 this.element.dispatchEvent(new CustomEvent('video-background-pause', { bubbles: true, detail: this })); 931 } 932 }; 933 934 VideoBackground.prototype.unmute = function () { 935 //TODO: solve this with ARIA toggle states 936 if (this.buttons.hasOwnProperty('mute')) { 937 const btn_obj = this.buttons.mute; 938 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 939 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 940 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 941 } 942 943 if (this.player) { 944 this.player.muted = false; 945 this.element.dispatchEvent(new CustomEvent('video-background-unmute', { bubbles: true, detail: this })); 946 } 947 }; 948 949 VideoBackground.prototype.mute = function () { 950 //TODO: solve this with ARIA toggle states 951 if (this.buttons.hasOwnProperty('mute')) { 952 const btn_obj = this.buttons.mute; 953 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 954 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 955 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 956 } 957 958 if (this.player) { 959 this.player.muted = true; 960 this.element.dispatchEvent(new CustomEvent('video-background-mute', { bubbles: true, detail: this })); 961 } 962 }; 963 964 //TODO: refactor states to be equal for all buttons
965 VideoBackground.prototype.generateActionButton = function (obj) { 966 const btn = document.createElement('button'); 967 btn.className = obj.className; 968 btn.innerHTML = obj.innerHtml; 969 addClass(btn.firstChild, obj.stateChildClassNames[0]); 970 971 //TODO: solve this with ARIA toggle states 972 if (this.params[obj.condition_parameter] === obj.initialState) { 973 addClass(btn, obj.stateClassName); 974 removeClass(btn.firstChild, obj.stateChildClassNames[0]); 975 addClass(btn.firstChild, obj.stateChildClassNames[1]); 976 } 977 978 const self = this; 979 btn.addEventListener('click', function(e) { 980 if (hasClass(this, obj.stateClassName)) { 981 self.state[obj.name] = false; 982 self[obj.actions[0]](); 983 } else { 984 self.state[obj.name] = true; 985 self[obj.actions[1]](); 986 } 987 }); 988 989 this.buttons[obj.name] = { 990 element: btn, 991 button_properties: obj 992 }; 993 994 this.controls_element.appendChild(btn); 995 }; 996 997 function VideoBackgrounds(selector, params) { 998 this.elements = selector; 999 1000 if (typeof selector === 'string') { 1001 this.elements = document.querySelectorAll(selector); 1002 } 1003 1004 this.index = {}; 1005 this.re = {}; 1006 this.re.YOUTUBE = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i; 1007 this.re.VIMEO = /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i; 1008 this.re.VIDEO = /\/[^\/]+\.(mp4|ogg|ogv|ogm|webm|avi)\s?$/i; 1009 1010 this.__init__ = function () { 1011 for (let i = 0; i < this.elements.length; i++) { 1012 const element = this.elements[i]; 1013 1014 const link = element.getAttribute('data-youtube') || element.getAttribute('data-vbg'); 1015 const vid_data = this.getVidID(link); 1016 1017 if (!vid_data) { 1018 continue; 1019 } 1020 1021 const uid = this.generateUID(vid_data.id); 1022 1023 if (!uid) { 1024 continue; 1025 } 1026 1027 switch (vid_data.type) { 1028 case 'YOUTUBE': 1029 const yb = new YoutubeBackground(element, params, vid_data.id, uid); 1030 this.index[uid] = yb; 1031 break; 1032 case 'VIMEO': 1033 const vm = new VimeoBackground(element, params, vid_data.id, uid); 1034 this.index[uid] = vm; 1035 break; 1036 case 'VIDEO': 1037 const vid = new VideoBackground(element, params, vid_data, uid); 1038 this.index[uid] = vid; 1039 break; 1040 } 1041 } 1042 1043 this.initYTPlayers(/*function() { 1044 //TODO: FIX! 1045 if (params && 1046 (params.hasOwnProperty('activity_timeout') 1047 || params.hasOwnProperty('inactivity_timeout'))) { 1048 this.activity_monitor = new ActivityMonitor(function () { 1049 self.playVideos(); 1050 }, function() { 1051 self.pauseVideos(); 1052 }, 1053 params ? params.activity_timeout : null, 1054 params ? params.inactivity_timeout : null, 1055 ['mousemove', 'scroll'] 1056 ); 1057 } 1058 }*/); 1059 }; 1060 1061 this.__init__(); 1062 } 1063 1064 VideoBackgrounds.prototype.getVidID = function (link) { 1065 if (link !== undefined && link !== null) { 1066 for (let k in this.re) { 1067 const pts = link.match(this.re[k]); 1068 1069 if (pts && pts.length) { 1070 this.re[k].lastIndex = 0; 1071 return { 1072 id: pts[1], 1073 type: k, 1074 regex_pts: pts, 1075 link: link 1076 }; 1077 } 1078 } 1079 } 1080 1081 return null; 1082 }; 1083 1084 1085 VideoBackgrounds.prototype.generateUID = function (pref) { 1086 //index the instance 1087 let uid = pref +'-'+ getRandomIntInclusive(0, 9999); 1088 while (this.index.hasOwnProperty(uid)) { 1089 uid = pref +'-'+ getRandomIntInclusive(0, 9999); 1090 } 1091 1092 return uid; 1093 }; 1094 1095 VideoBackgrounds.prototype.pauseVideos = function () { 1096 for (let k in this.index) { 1097 this.index[k].pause(); 1098 } 1099 }; 1100 1101 VideoBackgrounds.prototype.playVideos = function () { 1102 for (let k in this.index) { 1103 this.index[k].play(); 1104 } 1105 }; 1106 1107 VideoBackgrounds.prototype.initYTPlayers = function (callback) { 1108 const self = this; 1109 1110 window.onYouTubeIframeAPIReady = function () { 1111 for (let k in self.index) {
1112 if (self.index[k] instanceof YoutubeBackground) { 1113 self.index[k].initYTPlayer(); 1114 } 1115 } 1116 1117 if (callback) { 1118 setTimeout(callback, 100); 1119 } 1120 }; 1121 1122 if (window.hasOwnProperty('YT') && window.YT.loaded) { 1123 window.onYouTubeIframeAPIReady(); 1124 } 1125 }; 1126 1127 if (typeof jQuery == 'function') { 1128 (function ($) { 1129 $.fn.youtube_background = function (params) { 1130 const $this = $(this); 1131 new VideoBackgrounds(this, params); 1132 return $this; 1133 }; 1134 })(jQuery); 1135 } 1136 1137 window.VideoBackgrounds = VideoBackgrounds; 1138 1139})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.