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': false, 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: '<i class="fa"></i>', 185 initialState: true, 186 stateClassName: 'muted', 187 condition_parameter: 'muted', 188 stateChildClassNames: ['fa-volume-up', 'fa-volume-mute'], 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 = 'translateX(-50%) translateY(-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.right = '10px'; 373 controls.style['z-index'] = 2; 374 375 this.controls_element = controls; 376 parent.appendChild(controls); 377 } 378 379 return this.element; 380 }; 381 382 YoutubeBackground.prototype.play = function () { 383 //TODO: solve this with ARIA toggle states. P.S. warning repetitive code!!! 384 if (this.buttons.hasOwnProperty('play')) { 385 const btn_obj = this.buttons.play; 386 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 387 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 388 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 389 } 390 391 if (this.player) { 392 if (this.params['start-at'] && this.player.getCurrentTime() < this.params['start-at'] ) { 393 this.seekTo(this.params['start-at']); 394 } 395 this.player.playVideo(); 396 this.element.dispatchEvent(new CustomEvent('video-background-play', { bubbles: true, detail: this })); 397 } 398 }; 399 400 YoutubeBackground.prototype.pause = function () { 401 //TODO: solve this with ARIA toggle states 402 if (this.buttons.hasOwnProperty('play')) { 403 const btn_obj = this.buttons.play; 404 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 405 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 406 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 407 } 408 409 if (this.player) { 410 this.player.pauseVideo(); 411 this.element.dispatchEvent(new CustomEvent('video-background-pause', { bubbles: true, detail: this })); 412 } 413 }; 414 415 YoutubeBackground.prototype.unmute = function () { 416 //TODO: solve this with ARIA toggle states 417 if (this.buttons.hasOwnProperty('mute')) { 418 const btn_obj = this.buttons.mute; 419 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 420 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 421 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 422 } 423 424 if (this.player) { 425 this.player.unMute(); 426 this.element.dispatchEvent(new CustomEvent('video-background-unmute', { bubbles: true, detail: this })); 427 } 428 }; 429
430 YoutubeBackground.prototype.mute = function () { 431 //TODO: solve this with ARIA toggle states 432 if (this.buttons.hasOwnProperty('mute')) { 433 const btn_obj = this.buttons.mute; 434 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 435 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 436 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 437 } 438 439 if (this.player) { 440 this.player.mute(); 441 this.element.dispatchEvent(new CustomEvent('video-background-mute', { bubbles: true, detail: this })); 442 } 443 }; 444 445 //TODO: refactor states to be equal for all buttons 446 YoutubeBackground.prototype.generateActionButton = function (obj) { 447 const btn = document.createElement('button'); 448 btn.className = obj.className; 449 btn.innerHTML = obj.innerHtml; 450 addClass(btn.firstChild, obj.stateChildClassNames[0]); 451 452 //TODO: solve this with ARIA toggle states 453 if (this.params[obj.condition_parameter] === obj.initialState) { 454 addClass(btn, obj.stateClassName); 455 removeClass(btn.firstChild, obj.stateChildClassNames[0]); 456 addClass(btn.firstChild, obj.stateChildClassNames[1]); 457 } 458 459 const self = this; 460 btn.addEventListener('click', function(e) { 461 if (hasClass(this, obj.stateClassName)) { 462 self.state[obj.name] = false; 463 self[obj.actions[0]](); 464 } else { 465 self.state[obj.name] = true; 466 self[obj.actions[1]](); 467 } 468 }); 469 470 this.buttons[obj.name] = { 471 element: btn, 472 button_properties: obj 473 }; 474 475 this.controls_element.appendChild(btn); 476 }; 477 478 function VimeoBackground(elem, params, id, uid) { 479 this.is_mobile = isMobile(); 480 481 this.element = elem; 482 this.vid = id; 483 this.uid = uid; 484 this.player = null; 485 this.buttons = {}; 486 487 this.state = {}; 488 this.state.play = false; 489 this.state.mute = false; 490 491 this.params = {}; 492 493 const DEFAULTS = { 494 // 'pause': false, //deprecated 495 // 'play-button': false, 496 // 'mute-button': false, 497 'autoplay': true, 498 'muted': true, 499 'loop': true, 500 'mobile': false, 501 // 'load-background': true, 502 'resolution': '16:9', 503 'inline-styles': true, 504 'fit-box': false, 505 'offset': 200, 506 'start-at': 0, 507 'poster': null 508 }; 509 510 this.__init__ = function () { 511 if (!this.vid) { 512 return; 513 } 514 515 this.params = parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']); 516 //pause deprecated 517 if (this.params.pause) { 518 this.params['play-button'] = this.params.pause; 519 } 520 this.params.resolution_mod = parseResolutionString(this.params.resolution); 521 this.state.playing = this.params.autoplay; 522 this.state.muted = this.params.muted; 523 524 this.buildHTML(); 525 this.injectPlayer(); 526 }; 527 528 this.__init__(); 529 } 530 531 VimeoBackground.prototype.injectPlayer = function () { 532 this.iframe = document.createElement('iframe'); 533 this.iframe.setAttribute('frameborder', 0); 534 this.iframe.setAttribute('allow', ['autoplay; mute']); 535 let src = 'https://player.vimeo.com/video/'+this.vid+'?background=1&controls=0'; 536 537 if (this.params.muted) { 538 src += '&muted=1'; 539 } 540 541 if (this.params.autoplay) { 542 src += '&autoplay=1'; 543 } 544 545 if (this.params.loop) {
546 src += '&loop=1&autopause=0'; 547 } 548 549 //WARNâï¸ this is a hash not a query param 550 if (this.params['start-at']) { 551 src += '#t=' + this.params['start-at'] + 's'; 552 } 553 554 this.iframe.src = src; 555 556 if (this.uid) { 557 this.iframe.id = this.uid; 558 } 559 560 if (this.params['load-background'] || this.params['poster']) { 561 //if (this.params['load-background']) wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.ytid+'/maxresdefault.jpg)'; 562 if (this.params['poster']) wrapper_styles['background-image'] = this.params['poster']; 563 wrapper_styles['background-size'] = 'cover'; 564 wrapper_styles['background-repeat'] = 'no-repeat'; 565 wrapper_styles['background-position'] = 'center'; 566 } 567 568 if (this.params['inline-styles']) { 569 this.iframe.style.top = '50%'; 570 this.iframe.style.left = '50%'; 571 this.iframe.style.transform = 'translateX(-50%) translateY(-50%)'; 572 this.iframe.style.position = 'absolute'; 573 this.iframe.style.opacity = 1; 574 } 575 576 this.element.appendChild(this.iframe); 577 578 if (this.params['fit-box']) { 579 this.iframe.style.width = '100%'; 580 this.iframe.style.height = '100%'; 581 } else { 582 const self = this; 583 584 const onResize = function() { 585 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 586 const w = self.iframe.parentNode.offsetWidth + self.params.offset; 587 const res = self.params.resolution_mod; 588 589 if (res > w/h) { 590 self.iframe.style.width = h*res + 'px'; 591 self.iframe.style.height = h + 'px'; 592 } else { 593 self.iframe.style.width = w + 'px'; 594 self.iframe.style.height = w/res + 'px'; 595 } 596 }; 597 598 if (window.hasOwnProperty('ResizeObserver')) { 599 const resize_observer = new ResizeObserver(() => { 600 window.requestAnimationFrame(onResize); 601 }); 602 resize_observer.observe(this.element); 603 } else { 604 window.addEventListener('resize', () => { 605 window.requestAnimationFrame(onResize); 606 }); 607 } 608 onResize(); 609 } 610 }; 611 612 VimeoBackground.prototype.buildHTML = function () { 613 const parent = this.element.parentNode; 614 // wrap 615 addClass(this.element, 'youtube-background'); 616 617 //set css rules 618 const wrapper_styles = { 619 "height" : "100%", 620 "width" : "100%", 621 "z-index": "0", 622 "position": "absolute", 623 "overflow": "hidden", 624 "top": 0, // added by @insad 625 "left": 0, 626 "bottom": 0, 627 "right": 0 628 }; 629 630 if (this.params['load-background'] || this.params['poster']) { 631 //if (this.params['load-background']) wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.ytid+'/maxresdefault.jpg)'; 632 if (this.params['poster']) wrapper_styles['background-image'] = this.params['poster']; 633 wrapper_styles['background-size'] = 'cover'; 634 wrapper_styles['background-repeat'] = 'no-repeat'; 635 wrapper_styles['background-position'] = 'center'; 636 } 637 638 if (!this.params['mute-button']) { 639 wrapper_styles["pointer-events"] = "none"; // avoid right mouse click popup menu 640 } 641 642 if (this.params['load-background']) { 643 //TODO: wrapper_styles['background-image'] = 'url(https://img.youtube.com/vi/'+this.vid+'/maxresdefault.jpg)'; 644 wrapper_styles['background-size'] = 'cover'; 645 wrapper_styles['background-repeat'] = 'no-repeat'; 646 wrapper_styles['background-position'] = 'center'; 647 } 648 649 if (this.params['inline-styles']) { 650 for (let property in wrapper_styles) { 651 this.element.style[property] = wrapper_styles[property]; 652 } 653 654 if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) { 655 parent.style.position = 'relative'; 656 } 657 } 658 659 return this.element; 660 }; 661 662 function VideoBackground(elem, params, vid_data, uid) { 663 this.is_mobile = isMobile(); 664 665 this.element = elem; 666 this.link = vid_data.link; 667 this.ext = vid_data.id; 668 this.uid = uid; 669 this.player = null; 670 this.buttons = {}; 671 672 this.state = {}; 673 this.state.play = false;
674 this.state.mute = false; 675 676 this.params = {}; 677 678 const MIME_MAP = { 679 'ogv' : 'video/ogg', 680 'ogm' : 'video/ogg', 681 'ogg' : 'video/ogg', 682 'avi' : 'video/avi', 683 'mp4' : 'video/mp4', 684 'webm' : 'video/webm' 685 }; 686 687 const DEFAULTS = { 688 'pause': false, //deprecated 689 'play-button': false, 690 'mute-button': false, 691 'autoplay': true, 692 'muted': true, 693 'loop': true, 694 'mobile': false, 695 'resolution': '16:9', 696 'inline-styles': true, 697 'fit-box': false, 698 'offset': 200, 699 // 'start-at': 0, 700 // 'end-at': 0, 701 'poster': null 702 }; 703 704 this.__init__ = function () { 705 if (!this.link || !this.ext) { 706 return; 707 } 708 709 this.mime = MIME_MAP[this.ext.toLowerCase()]; 710 this.params = parseProperties(params, DEFAULTS, this.element, ['data-ytbg-', 'data-vbg-']); 711 //pause deprecated 712 if (this.params.pause) { 713 this.params['play-button'] = this.params.pause; 714 } 715 this.params.resolution_mod = parseResolutionString(this.params.resolution); 716 this.state.playing = this.params.autoplay; 717 this.state.muted = this.params.muted; 718 719 this.buildHTML(); 720 this.injectPlayer(); 721 722 723 if (this.params['play-button']) { 724 this.generateActionButton({ 725 name: 'play', 726 className: 'play-toggle', 727 innerHtml: '<i class="fa"></i>', 728 initialState: false, 729 stateClassName: 'paused', 730 condition_parameter: 'autoplay', 731 stateChildClassNames: ['fa-pause-circle', 'fa-play-circle'], 732 actions: ['play', 'pause'] 733 }); 734 } 735 736 if (this.params['mute-button']) { 737 this.generateActionButton({ 738 name: 'mute', 739 className: 'mute-toggle', 740 innerHtml: '<i class="fa"></i>', 741 initialState: true, 742 stateClassName: 'muted', 743 condition_parameter: 'muted', 744 stateChildClassNames: ['fa-volume-up', 'fa-volume-mute'], 745 actions: ['unmute', 'mute'] 746 }); 747 } 748 }; 749 750 this.__init__(); 751 } 752 753 VideoBackground.prototype.seekTo = function (seconds) { 754 if (this.player.hasOwnProperty('fastSeek')) { 755 this.player.fastSeek(seconds); 756 return; 757 } 758 this.player.currentTime = seconds; 759 }; 760 761 VideoBackground.prototype.injectPlayer = function () { 762 this.player = document.createElement('video'); 763 this.player.muted = this.params.muted; 764 this.player.autoplay = this.params.autoplay; 765 this.player.loop = this.params.loop; 766 this.player.playsinline = true; 767 768 this.player.setAttribute('id', this.uid); 769 770 if (this.params['inline-styles']) { 771 this.player.style.top = '50%'; 772 this.player.style.left = '50%'; 773 this.player.style.transform = 'translateX(-50%) translateY(-50%)'; 774 this.player.style.position = 'absolute'; 775 this.player.style.opacity = 0; 776 777 this.player.addEventListener('canplay', (e) => { 778 e.target.style.opacity = 1; 779 }); 780 } 781 782 const self = this; 783 /* 784 this.player.addEventListener('canplay', (e) => { 785 if (self.params['start-at'] && self.params.autoplay) { 786 self.seekTo(self.params['start-at']); 787 } 788 }); 789 790 this.player.addEventListener('canplaythrough', (e) => { 791 if (self.params['end-at'] > 0) { 792 self.player.addEventListener('timeupdate', (e) => { 793 if (self.params['end-at'] >= self.player.currentTime) { 794 self.seekTo(self.params['start-at']); 795 } 796 }); 797 } 798 }); 799 */ 800 801 const source = document.createElement('source'); 802 source.setAttribute('src', this.link); 803 source.setAttribute('type', this.mime); 804 this.player.appendChild(source); 805 this.element.appendChild(this.player); 806 807 if (this.params['fit-box']) { 808 this.player.style.width = '100%'; 809 this.player.style.height = '100%'; 810 } else { 811 //TODOâï¸: maybe a spacer or at least add requestAnimationFrame 812 function onResize() { 813 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 814 const w = self.player.parentNode.offsetWidth + self.params.offset; 815 const res = self.params.resolution_mod; 816 817 if (res > w/h) { 818 self.player.style.width = h*res + 'px'; 819 self.player.style.height = h + 'px'; 820 } else { 821 self.player.style.width = w + 'px'; 822 self.player.style.height = w/res + 'px'; 823 } 824 } 825 826 if (window.hasOwnProperty('ResizeObserver')) { 827 const resize_observer = new ResizeObserver(() => { 828 window.requestAnimationFrame(onResize); 829 }); 830 resize_observer.observe(this.element); 831 } else { 832 window.addEventListener('resize', () => { 833 window.requestAnimationFrame(onResize); 834 }); 835 } 836 onResize(); 837 } 838 }; 839
840 VideoBackground.prototype.buildHTML = function () { 841 const parent = this.element.parentNode; 842 // wrap 843 addClass(this.element, 'video-background'); 844 845 //set css rules 846 const wrapper_styles = { 847 "height" : "100%", 848 "width" : "100%", 849 "z-index": "0", 850 "position": "absolute", 851 "overflow": "hidden", 852 "top": 0, // added by @insad 853 "left": 0, 854 "bottom": 0, 855 "right": 0 856 }; 857 858 if (!this.params['mute-button']) { 859 wrapper_styles["pointer-events"] = "none"; // avoid right mouse click popup menu 860 } 861 862 if (this.params['load-background'] || this.params['poster']) { 863 if (this.params['poster']) wrapper_styles['background-image'] = `url('${this.params['poster']}')`; 864 wrapper_styles['background-size'] = 'cover'; 865 wrapper_styles['background-repeat'] = 'no-repeat'; 866 wrapper_styles['background-position'] = 'center'; 867 } 868 869 if (this.params['inline-styles']) { 870 for (let property in wrapper_styles) { 871 this.element.style[property] = wrapper_styles[property]; 872 } 873 874 if (!['absolute', 'fixed', 'relative', 'sticky'].indexOf(parent.style.position)) { 875 parent.style.position = 'relative'; 876 } 877 } 878 879 if (this.is_mobile && !this.params.mobile) { 880 return this.element; 881 } 882 883 // set play/mute controls wrap 884 if (this.params['play-button'] || this.params['mute-button']) { 885 const controls = document.createElement('div'); 886 controls.className = 'video-background-controls'; 887 888 controls.style.position = 'absolute'; 889 controls.style.top = '10px'; 890 controls.style.right = '10px'; 891 controls.style['z-index'] = 2; 892 893 this.controls_element = controls; 894 parent.appendChild(controls); 895 } 896 897 return this.element; 898 }; 899 900 VideoBackground.prototype.play = function () { 901 //TODO: solve this with ARIA toggle states. P.S. warning repetitive code!!! 902 if (this.buttons.hasOwnProperty('play')) { 903 const btn_obj = this.buttons.play; 904 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 905 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 906 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 907 } 908 909 if (this.player) { 910 /* if (this.params['start-at'] && this.player.currentTime < this.params['start-at'] ) { 911 this.seekTo(this.params['start-at']); 912 } */ 913 this.player.play(); 914 this.element.dispatchEvent(new CustomEvent('video-background-play', { bubbles: true, detail: this })); 915 } 916 }; 917 918 VideoBackground.prototype.pause = function () { 919 //TODO: solve this with ARIA toggle states 920 if (this.buttons.hasOwnProperty('play')) { 921 const btn_obj = this.buttons.play; 922 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 923 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 924 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 925 } 926 927 if (this.player) { 928 this.player.pause(); 929 this.element.dispatchEvent(new CustomEvent('video-background-pause', { bubbles: true, detail: this })); 930 } 931 }; 932 933 VideoBackground.prototype.unmute = function () { 934 //TODO: solve this with ARIA toggle states 935 if (this.buttons.hasOwnProperty('mute')) { 936 const btn_obj = this.buttons.mute; 937 removeClass(btn_obj.element, btn_obj.button_properties.stateClassName); 938 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 939 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 940 } 941 942 if (this.player) { 943 this.player.muted = false; 944 this.element.dispatchEvent(new CustomEvent('video-background-unmute', { bubbles: true, detail: this })); 945 } 946 }; 947 948 VideoBackground.prototype.mute = function () { 949 //TODO: solve this with ARIA toggle states 950 if (this.buttons.hasOwnProperty('mute')) { 951 const btn_obj = this.buttons.mute; 952 addClass(btn_obj.element, btn_obj.button_properties.stateClassName); 953 removeClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[0]); 954 addClass(btn_obj.element.firstChild, btn_obj.button_properties.stateChildClassNames[1]); 955 } 956 957 if (this.player) { 958 this.player.muted = true; 959 this.element.dispatchEvent(new CustomEvent('video-background-mute', { bubbles: true, detail: this })); 960 } 961 }; 962 963 //TODO: refactor states to be equal for all buttons
964 VideoBackground.prototype.generateActionButton = function (obj) { 965 const btn = document.createElement('button'); 966 btn.className = obj.className; 967 btn.innerHTML = obj.innerHtml; 968 addClass(btn.firstChild, obj.stateChildClassNames[0]); 969 970 //TODO: solve this with ARIA toggle states 971 if (this.params[obj.condition_parameter] === obj.initialState) { 972 addClass(btn, obj.stateClassName); 973 removeClass(btn.firstChild, obj.stateChildClassNames[0]); 974 addClass(btn.firstChild, obj.stateChildClassNames[1]); 975 } 976 977 const self = this; 978 btn.addEventListener('click', function(e) { 979 if (hasClass(this, obj.stateClassName)) { 980 self.state[obj.name] = false; 981 self[obj.actions[0]](); 982 } else { 983 self.state[obj.name] = true; 984 self[obj.actions[1]](); 985 } 986 }); 987 988 this.buttons[obj.name] = { 989 element: btn, 990 button_properties: obj 991 }; 992 993 this.controls_element.appendChild(btn); 994 }; 995 996 function VideoBackgrounds(selector, params) { 997 this.elements = selector; 998 999 if (typeof selector === 'string') { 1000 this.elements = document.querySelectorAll(selector); 1001 } 1002 1003 this.index = {}; 1004 this.re = {}; 1005 this.re.YOUTUBE = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i; 1006 this.re.VIMEO = /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i; 1007 this.re.VIDEO = /\/[^\/]+\.(mp4|ogg|ogv|ogm|webm|avi)\s?$/i; 1008 1009 this.__init__ = function () { 1010 for (let i = 0; i < this.elements.length; i++) { 1011 const element = this.elements[i]; 1012 1013 const link = element.getAttribute('data-youtube') || element.getAttribute('data-vbg'); 1014 const vid_data = this.getVidID(link); 1015 1016 if (!vid_data) { 1017 continue; 1018 } 1019 1020 const uid = this.generateUID(vid_data.id); 1021 1022 if (!uid) { 1023 continue; 1024 } 1025 1026 switch (vid_data.type) { 1027 case 'YOUTUBE': 1028 const yb = new YoutubeBackground(element, params, vid_data.id, uid); 1029 this.index[uid] = yb; 1030 break; 1031 case 'VIMEO': 1032 const vm = new VimeoBackground(element, params, vid_data.id, uid); 1033 this.index[uid] = vm; 1034 break; 1035 case 'VIDEO': 1036 const vid = new VideoBackground(element, params, vid_data, uid); 1037 this.index[uid] = vid; 1038 break; 1039 } 1040 } 1041 1042 this.initYTPlayers(/*function() { 1043 //TODO: FIX! 1044 if (params && 1045 (params.hasOwnProperty('activity_timeout') 1046 || params.hasOwnProperty('inactivity_timeout'))) { 1047 this.activity_monitor = new ActivityMonitor(function () { 1048 self.playVideos(); 1049 }, function() { 1050 self.pauseVideos(); 1051 }, 1052 params ? params.activity_timeout : null, 1053 params ? params.inactivity_timeout : null, 1054 ['mousemove', 'scroll'] 1055 ); 1056 } 1057 }*/); 1058 }; 1059 1060 this.__init__(); 1061 } 1062 1063 VideoBackgrounds.prototype.getVidID = function (link) { 1064 if (link !== undefined && link !== null) { 1065 for (let k in this.re) { 1066 const pts = link.match(this.re[k]); 1067 1068 if (pts && pts.length) { 1069 this.re[k].lastIndex = 0; 1070 return { 1071 id: pts[1], 1072 type: k, 1073 regex_pts: pts, 1074 link: link 1075 }; 1076 } 1077 } 1078 } 1079 1080 return null; 1081 }; 1082 1083 1084 VideoBackgrounds.prototype.generateUID = function (pref) { 1085 //index the instance 1086 let uid = pref +'-'+ getRandomIntInclusive(0, 9999); 1087 while (this.index.hasOwnProperty(uid)) { 1088 uid = pref +'-'+ getRandomIntInclusive(0, 9999); 1089 } 1090 1091 return uid; 1092 }; 1093 1094 VideoBackgrounds.prototype.pauseVideos = function () { 1095 for (let k in this.index) { 1096 this.index[k].pause(); 1097 } 1098 }; 1099 1100 VideoBackgrounds.prototype.playVideos = function () { 1101 for (let k in this.index) { 1102 this.index[k].play(); 1103 } 1104 }; 1105 1106 VideoBackgrounds.prototype.initYTPlayers = function (callback) { 1107 const self = this; 1108 1109 window.onYouTubeIframeAPIReady = function () { 1110 for (let k in self.index) {
1111 if (self.index[k] instanceof YoutubeBackground) { 1112 self.index[k].initYTPlayer(); 1113 } 1114 } 1115 1116 if (callback) { 1117 setTimeout(callback, 100); 1118 } 1119 }; 1120 1121 if (window.hasOwnProperty('YT') && window.YT.loaded) { 1122 window.onYouTubeIframeAPIReady(); 1123 } 1124 }; 1125 1126 if (typeof jQuery == 'function') { 1127 (function ($) { 1128 $.fn.youtube_background = function (params) { 1129 const $this = $(this); 1130 new VideoBackgrounds(this, params); 1131 return $this; 1132 }; 1133 })(jQuery); 1134 } 1135 1136 window.VideoBackgrounds = VideoBackgrounds; 1137 1138})();
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.