1
vendor: 22,432 bytes, lines 1-798
1/* The MIT License (MIT) 2 3Copyright (c) 2014-2015 Benoit Tremblay <[email protected]> 4 5Permission is hereby granted, free of charge, to any person obtaining a copy 6of this software and associated documentation files (the "Software"), to deal 7in the Software without restriction, including without limitation the rights 8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9copies of the Software, and to permit persons to whom the Software is 10furnished to do so, subject to the following conditions: 11 12The above copyright notice and this permission notice shall be included in 13all copies or substantial portions of the Software. 14 15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21THE SOFTWARE. */ 22/*global define, YT*/ 23(function (root, factory) { 24 if(typeof exports==='object' && typeof module!=='undefined') { 25 var videojs = require('video.js'); 26 module.exports = factory(videojs.default || videojs); 27 } else if(typeof define === 'function' && define.amd) { 28 define(['videojs'], function(videojs){ 29 return (root.Youtube = factory(videojs)); 30 }); 31 } else { 32 root.Youtube = factory(root.videojs); 33 } 34}(this, function(videojs) { 35 'use strict'; 36 37 var _isOnMobile = videojs.browser.IS_IOS || videojs.browser.IS_NATIVE_ANDROID; 38 var Tech = videojs.getTech('Tech'); 39 40 class Youtube extends Tech { 41 42 constructor(options, ready) { 43 super(options, ready); 44 45 this.setPoster(options.poster); 46 this.setSrc(this.options_.source, true); 47 48 // Set the vjs-youtube class to the player 49 // Parent is not set yet so we have to wait a tick 50 this.setTimeout(function() { 51 if (this.el_) { 52 this.el_.parentNode.className += ' vjs-youtube'; 53 54 if (_isOnMobile) { 55 this.el_.parentNode.className += ' vjs-youtube-mobile'; 56 } 57 58 if (Youtube.isApiReady) { 59 this.initYTPlayer(); 60 } else { 61 Youtube.apiReadyQueue.push(this); 62 } 63 } 64 }.bind(this)); 65 } 66 67 dispose() { 68 if (this.ytPlayer) { 69 //Dispose of the YouTube Player 70 if (this.ytPlayer.stopVideo) { 71 this.ytPlayer.stopVideo(); 72 } 73 if (this.ytPlayer.destroy) { 74 this.ytPlayer.destroy(); 75 } 76 } else { 77 //YouTube API hasn't finished loading or the player is already disposed 78 var index = Youtube.apiReadyQueue.indexOf(this); 79 if (index !== -1) { 80 Youtube.apiReadyQueue.splice(index, 1); 81 } 82 } 83 this.ytPlayer = null; 84 85 this.el_.parentNode.className = this.el_.parentNode.className 86 .replace(' vjs-youtube', '') 87 .replace(' vjs-youtube-mobile', ''); 88 this.el_.parentNode.removeChild(this.el_); 89 90 //Needs to be called after the YouTube player is destroyed, otherwise there will be a null reference exception 91 Tech.prototype.dispose.call(this); 92 } 93 94 createEl() { 95 var div = document.createElement('div'); 96 div.setAttribute('id', this.options_.techId); 97 div.setAttribute('style', 'width:100%;height:100%;top:0;left:0;position:absolute'); 98 div.setAttribute('class', 'vjs-tech'); 99 100 var divWrapper = document.createElement('div'); 101 divWrapper.appendChild(div); 102 103 if (!_isOnMobile && !this.options_.ytControls) { 104 var divBlocker = document.createElement('div'); 105 divBlocker.setAttribute('class', 'vjs-iframe-blocker'); 106 divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); 107 108 // In case the blocker is still there and we want to pause 109 divBlocker.onclick = function() { 110 this.pause(); 111 }.bind(this); 112 113 divWrapper.appendChild(divBlocker); 114 } 115 116 return divWrapper; 117 } 118 119 initYTPlayer() { 120 var playerVars = { 121 controls: 0, 122 modestbranding: 1, 123 rel: 0, 124 showinfo: 0, 125 loop: this.options_.loop ? 1 : 0 126 }; 127 128 // Let the user set any YouTube parameter 129 // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters 130 // To use YouTube controls, you must use ytControls instead 131 // To use the loop or autoplay, use the video.js settings 132 133 if (typeof this.options_.autohide !== 'undefined') { 134 playerVars.autohide = this.options_.autohide; 135 } 136 137 if (typeof this.options_['cc_load_policy'] !== 'undefined') { 138 playerVars['cc_load_policy'] = this.options_['cc_load_policy']; 139 } 140 141 if (typeof this.options_.ytControls !== 'undefined') { 142 playerVars.controls = this.options_.ytControls; 143 } 144 145 if (typeof this.options_.disablekb !== 'undefined') { 146 playerVars.disablekb = this.options_.disablekb; 147 } 148 149 if (typeof this.options_.color !== 'undefined') { 150 playerVars.color = this.options_.color; 151 } 152 153 if (!playerVars.controls) { 154 // Let video.js handle the fullscreen unless it is the YouTube native controls 155 playerVars.fs = 0; 156 } else if (typeof this.options_.fs !== 'undefined') { 157 playerVars.fs = this.options_.fs; 158 } 159 160 if (this.options_.source.src.indexOf('end=') !== -1) { 161 var srcEndTime = this.options_.source.src.match(/end=([0-9]*)/); 162 this.options_.end = parseInt(srcEndTime[1]); 163 } 164 165 if (typeof this.options_.end !== 'undefined') { 166 playerVars.end = this.options_.end; 167 } 168 169 if (typeof this.options_.hl !== 'undefined') { 170 playerVars.hl = this.options_.hl; 171 } else if (typeof this.options_.language !== 'undefined') { 172 // Set the YouTube player on the same language than video.js 173 playerVars.hl = this.options_.language.substr(0, 2); 174 } 175 176 if (typeof this.options_['iv_load_policy'] !== 'undefined') { 177 playerVars['iv_load_policy'] = this.options_['iv_load_policy']; 178 } 179 180 if (typeof this.options_.list !== 'undefined') { 181 playerVars.list = this.options_.list; 182 } else if (this.url && typeof this.url.listId !== 'undefined') { 183 playerVars.list = this.url.listId; 184 } 185 186 if (typeof this.options_.listType !== 'undefined') { 187 playerVars.listType = this.options_.listType; 188 } 189 190 if (typeof this.options_.modestbranding !== 'undefined') { 191 playerVars.modestbranding = this.options_.modestbranding; 192 } 193 194 if (typeof this.options_.playlist !== 'undefined') { 195 playerVars.playlist = this.options_.playlist; 196 } 197 198 if (typeof this.options_.playsinline !== 'undefined') { 199 playerVars.playsinline = this.options_.playsinline; 200 } 201 202 if (typeof this.options_.rel !== 'undefined') { 203 playerVars.rel = this.options_.rel; 204 } 205 206 if (typeof this.options_.showinfo !== 'undefined') { 207 playerVars.showinfo = this.options_.showinfo; 208 } 209 210 if (this.options_.source.src.indexOf('start=') !== -1) { 211 var srcStartTime = this.options_.source.src.match(/start=([0-9]*)/); 212 this.options_.start = parseInt(srcStartTime[1]); 213 } 214 215 if (typeof this.options_.start !== 'undefined') { 216 playerVars.start = this.options_.start; 217 } 218 219 if (typeof this.options_.theme !== 'undefined') { 220 playerVars.theme = this.options_.theme; 221 } 222 223 // Allow undocumented options to be passed along via customVars 224 if (typeof this.options_.customVars !== 'undefined') { 225 var customVars = this.options_.customVars; 226 Object.keys(customVars).forEach(function(key) { 227 playerVars[key] = customVars[key]; 228 }); 229 } 230 231 this.activeVideoId = this.url ? this.url.videoId : null; 232 this.activeList = playerVars.list; 233 234 var playerConfig = { 235 videoId: this.activeVideoId, 236 playerVars: playerVars, 237 events: { 238 onReady: this.onPlayerReady.bind(this), 239 onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), 240 onPlaybackRateChange: this.onPlayerPlaybackRateChange.bind(this), 241 onStateChange: this.onPlayerStateChange.bind(this), 242 onVolumeChange: this.onPlayerVolumeChange.bind(this), 243 onError: this.onPlayerError.bind(this) 244 } 245 }; 246 247 if (typeof this.options_.enablePrivacyEnhancedMode !== 'undefined' && this.options_.enablePrivacyEnhancedMode) { 248 playerConfig.host = 'https://www.youtube-nocookie.com'; 249 } 250 251 this.ytPlayer = new YT.Player(this.options_.techId, playerConfig); 252 } 253 254 onPlayerReady() { 255 if (this.options_.muted) { 256 this.ytPlayer.mute(); 257 } 258 259 var playbackRates = this.ytPlayer.getAvailablePlaybackRates(); 260 if (playbackRates.length > 1) { 261 this.featuresPlaybackRate = true; 262 } 263 264 this.playerReady_ = true; 265 this.triggerReady(); 266 267 if (this.playOnReady) { 268 this.play(); 269 } else if (this.cueOnReady) { 270 this.cueVideoById_(this.url.videoId); 271 this.activeVideoId = this.url.videoId; 272 } 273 } 274 275 onPlayerPlaybackQualityChange() { 276 277 } 278 279 onPlayerPlaybackRateChange() { 280 this.trigger('ratechange'); 281 } 282 283 onPlayerStateChange(e) { 284 var state = e.data; 285 286 if (state === this.lastState || this.errorNumber) { 287 return; 288 } 289 290 this.lastState = state; 291 292 switch (state) { 293 case -1: 294 this.trigger('loadstart'); 295 this.trigger('loadedmetadata'); 296 this.trigger('durationchange'); 297 this.trigger('ratechange'); 298 break; 299 300 case YT.PlayerState.ENDED: 301 this.trigger('ended'); 302 break; 303 304 case YT.PlayerState.PLAYING: 305 this.trigger('timeupdate'); 306 this.trigger('durationchange'); 307 this.trigger('playing'); 308 this.trigger('play'); 309 310 if (this.isSeeking) { 311 this.onSeeked(); 312 } 313 break; 314 315 case YT.PlayerState.PAUSED: 316 this.trigger('canplay'); 317 if (this.isSeeking) { 318 this.onSeeked(); 319 } else { 320 this.trigger('pause'); 321 } 322 break; 323 324 case YT.PlayerState.BUFFERING: 325 this.player_.trigger('timeupdate'); 326 this.player_.trigger('waiting'); 327 break; 328 } 329 } 330 331 onPlayerVolumeChange() { 332 this.trigger('volumechange'); 333 } 334 335 onPlayerError(e) { 336 this.errorNumber = e.data; 337 this.trigger('pause'); 338 this.trigger('error'); 339 } 340 341 error() { 342 var code = 1000 + this.errorNumber; // as smaller codes are reserved 343 switch (this.errorNumber) { 344 case 5: 345 return { code: code, message: 'Error while trying to play the video' }; 346 347 case 2: 348 case 100: 349 return { code: code, message: 'Unable to find the video' }; 350 351 case 101: 352 case 150: 353 return { 354 code: code, 355 message: 'Playback on other Websites has been disabled by the video owner.' 356 }; 357 } 358 359 return { code: code, message: 'YouTube unknown error (' + this.errorNumber + ')' }; 360 } 361 362 loadVideoById_(id) { 363 var options = { 364 videoId: id 365 }; 366 if (this.options_.start) { 367 options.startSeconds = this.options_.start; 368 } 369 if (this.options_.end) { 370 options.endSeconds = this.options_.end; 371 } 372 this.ytPlayer.loadVideoById(options); 373 } 374 375 cueVideoById_(id) { 376 var options = { 377 videoId: id 378 }; 379 if (this.options_.start) { 380 options.startSeconds = this.options_.start; 381 } 382 if (this.options_.end) { 383 options.endSeconds = this.options_.end; 384 } 385 this.ytPlayer.cueVideoById(options); 386 } 387 388 src(src) { 389 if (src) { 390 this.setSrc({ src: src }); 391 } 392 393 return this.source; 394 } 395 396 poster() { 397 // You can't start programmaticlly a video with a mobile 398 // through the iframe so we hide the poster and the play button (with CSS) 399 if (_isOnMobile) { 400 return null; 401 } 402 403 return this.poster_; 404 } 405 406 setPoster(poster) { 407 this.poster_ = poster; 408 } 409 410 setSrc(source) { 411 if (!source || !source.src) { 412 return; 413 } 414 415 delete this.errorNumber; 416 this.source = source; 417 this.url = Youtube.parseUrl(source.src); 418 419 if (!this.options_.poster) { 420 if (this.url.videoId) { 421 // Set the low resolution first 422 this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; 423 this.trigger('posterchange'); 424 425 // Check if their is a high res 426 this.checkHighResPoster(); 427 } 428 } 429 430 if (this.options_.autoplay && !_isOnMobile) { 431 if (this.isReady_) { 432 this.play(); 433 } else { 434 this.playOnReady = true; 435 } 436 } else if (this.activeVideoId !== this.url.videoId) { 437 if (this.isReady_) { 438 this.cueVideoById_(this.url.videoId); 439 this.activeVideoId = this.url.videoId; 440 } else { 441 this.cueOnReady = true; 442 } 443 } 444 } 445 446 autoplay() { 447 return this.options_.autoplay; 448 } 449 450 setAutoplay(val) { 451 this.options_.autoplay = val; 452 } 453 454 loop() { 455 return this.options_.loop; 456 } 457 458 setLoop(val) { 459 this.options_.loop = val; 460 } 461 462 play() { 463 if (!this.url || !this.url.videoId) { 464 return; 465 } 466 467 this.wasPausedBeforeSeek = false; 468 469 if (this.isReady_) { 470 if (this.url.listId) { 471 if (this.activeList === this.url.listId) { 472 this.ytPlayer.playVideo(); 473 } else { 474 this.ytPlayer.loadPlaylist(this.url.listId); 475 this.activeList = this.url.listId; 476 } 477 } 478 479 if (this.activeVideoId === this.url.videoId) { 480 this.ytPlayer.playVideo(); 481 } else { 482 this.loadVideoById_(this.url.videoId); 483 this.activeVideoId = this.url.videoId; 484 } 485 } else { 486 this.trigger('waiting'); 487 this.playOnReady = true; 488 } 489 } 490 491 pause() { 492 if (this.ytPlayer) { 493 this.ytPlayer.pauseVideo(); 494 } 495 } 496 497 paused() { 498 return (this.ytPlayer) ? 499 (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) 500 : true; 501 } 502 503 currentTime() { 504 return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0; 505 } 506 507 setCurrentTime(seconds) { 508 if (this.lastState === YT.PlayerState.PAUSED) { 509 this.timeBeforeSeek = this.currentTime(); 510 } 511 512 if (!this.isSeeking) { 513 this.wasPausedBeforeSeek = this.paused(); 514 } 515 516 this.ytPlayer.seekTo(seconds, true); 517 this.trigger('timeupdate'); 518 this.trigger('seeking'); 519 this.isSeeking = true; 520 521 // A seek event during pause does not return an event to trigger a seeked event, 522 // so run an interval timer to look for the currentTime to change 523 if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { 524 clearInterval(this.checkSeekedInPauseInterval); 525 this.checkSeekedInPauseInterval = setInterval(function() { 526 if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { 527 // If something changed while we were waiting for the currentTime to change, 528 // clear the interval timer 529 clearInterval(this.checkSeekedInPauseInterval); 530 } else if (this.currentTime() !== this.timeBeforeSeek) { 531 this.trigger('timeupdate'); 532 this.onSeeked(); 533 } 534 }.bind(this), 250); 535 } 536 } 537 538 seeking() { 539 return this.isSeeking; 540 } 541 542 seekable() { 543 if(!this.ytPlayer) { 544 return videojs.createTimeRange(); 545 } 546 547 return videojs.createTimeRange(0, this.ytPlayer.getDuration()); 548 } 549 550 onSeeked() { 551 clearInterval(this.checkSeekedInPauseInterval); 552 this.isSeeking = false; 553 554 if (this.wasPausedBeforeSeek) { 555 this.pause(); 556 } 557 558 this.trigger('seeked'); 559 } 560 561 playbackRate() { 562 return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1; 563 } 564 565 setPlaybackRate(suggestedRate) { 566 if (!this.ytPlayer) { 567 return; 568 } 569 570 this.ytPlayer.setPlaybackRate(suggestedRate); 571 } 572 573 duration() { 574 return this.ytPlayer ? this.ytPlayer.getDuration() : 0; 575 } 576 577 currentSrc() { 578 return this.source && this.source.src; 579 } 580 581 ended() { 582 return this.ytPlayer ? (this.lastState === YT.PlayerState.ENDED) : false; 583 } 584 585 volume() { 586 return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1; 587 } 588 589 setVolume(percentAsDecimal) { 590 if (!this.ytPlayer) { 591 return; 592 } 593 594 this.ytPlayer.setVolume(percentAsDecimal * 100.0); 595 } 596 597 muted() { 598 return this.ytPlayer ? this.ytPlayer.isMuted() : false; 599 } 600 601 setMuted(mute) { 602 if (!this.ytPlayer) { 603 return; 604 } 605 else{ 606 this.muted(true); 607 } 608 609 if (mute) { 610 this.ytPlayer.mute(); 611 } else { 612 this.ytPlayer.unMute(); 613 } 614 this.setTimeout( function(){ 615 this.trigger('volumechange'); 616 }, 50); 617 } 618 619 buffered() { 620 if(!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) { 621 return videojs.createTimeRange(); 622 } 623 624 var bufferedEnd = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration(); 625 626 return videojs.createTimeRange(0, bufferedEnd); 627 } 628 629 // TODO: Can we really do something with this on YouTUbe? 630 preload() {} 631 load() {} 632 reset() {} 633 networkState() { 634 if (!this.ytPlayer) { 635 return 0; //NETWORK_EMPTY 636 } 637 switch (this.ytPlayer.getPlayerState()) { 638 case -1: //unstarted 639 return 0; //NETWORK_EMPTY 640 case 3: //buffering 641 return 2; //NETWORK_LOADING 642 default: 643 return 1; //NETWORK_IDLE 644 } 645 } 646 readyState() { 647 if (!this.ytPlayer) { 648 return 0; //HAVE_NOTHING 649 } 650 switch (this.ytPlayer.getPlayerState()) { 651 case -1: //unstarted 652 return 0; //HAVE_NOTHING 653 case 5: //video cued 654 return 1; //HAVE_METADATA 655 case 3: //buffering 656 return 2; //HAVE_CURRENT_DATA 657 default: 658 return 4; //HAVE_ENOUGH_DATA 659 } 660 } 661 662 supportsFullScreen() { 663 return document.fullscreenEnabled || 664 document.webkitFullscreenEnabled || 665 document.mozFullScreenEnabled || 666 document.msFullscreenEnabled; 667 } 668 669 // Tries to get the highest resolution thumbnail available for the video 670 checkHighResPoster(){ 671 var uri = 'https://img.youtube.com/vi/' + this.url.videoId + '/maxresdefault.jpg'; 672 673 try { 674 var image = new Image(); 675 image.onload = function(){ 676 // Onload may still be called if YouTube returns the 120x90 error thumbnail 677 if('naturalHeight' in image){ 678 if (image.naturalHeight <= 90 || image.naturalWidth <= 120) { 679 return; 680 } 681 } else if(image.height <= 90 || image.width <= 120) { 682 return; 683 } 684 685 this.poster_ = uri; 686 this.trigger('posterchange'); 687 }.bind(this); 688 image.onerror = function(){}; 689 image.src = uri; 690 } 691 catch(e){} 692 } 693 } 694 695 Youtube.isSupported = function() { 696 return true; 697 }; 698 699 Youtube.canPlaySource = function(e) { 700 return Youtube.canPlayType(e.type); 701 }; 702 703 Youtube.canPlayType = function(e) { 704 return (e === 'video/youtube'); 705 }; 706 707 Youtube.parseUrl = function(url) { 708 var result = { 709 videoId: null 710 }; 711 712 var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; 713 var match = url.match(regex); 714 715 if (match && match[2].length === 11) { 716 result.videoId = match[2]; 717 } 718 719 var regPlaylist = /[?&]list=([^#\&\?]+)/; 720 match = url.match(regPlaylist); 721 722 if(match && match[1]) { 723 result.listId = match[1]; 724 } 725 726 return result; 727 }; 728 729 function apiLoaded() { 730 YT.ready(function() { 731 Youtube.isApiReady = true; 732 733 for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { 734 Youtube.apiReadyQueue[i].initYTPlayer(); 735 } 736 }); 737 } 738 739 function loadScript(src, callback) { 740 var loaded = false; 741 var tag = document.createElement('script'); 742 var firstScriptTag = document.getElementsByTagName('script')[0]; 743 if (!firstScriptTag) { 744 // when loaded in jest without jsdom setup it doesn't get any element. 745 // In jest it doesn't really make sense to do anything, because no one is watching youtube in jest 746 return; 747 } 748 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 749 tag.onload = function () { 750 if (!loaded) { 751 loaded = true; 752 callback(); 753 } 754 }; 755 tag.onreadystatechange = function () { 756 if (!loaded && (this.readyState === 'complete' || this.readyState === 'loaded')) { 757 loaded = true; 758 callback(); 759 } 760 }; 761 tag.src = src; 762 } 763 764 function injectCss() { 765 var css = // iframe blocker to catch mouse events 766 '.vjs-youtube .vjs-iframe-blocker { display: none; }' + 767 '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + 768 '.vjs-youtube .vjs-poster { background-size: cover; }' + 769 '.vjs-youtube-mobile .vjs-big-play-button { display: none; }'; 770 771 var head = document.head || document.getElementsByTagName('head')[0]; 772 773 var style = document.createElement('style'); 774 style.type = 'text/css'; 775 776 if (style.styleSheet){ 777 style.styleSheet.cssText = css; 778 } else { 779 style.appendChild(document.createTextNode(css)); 780 } 781 782 head.appendChild(style); 783 } 784 785 Youtube.apiReadyQueue = []; 786 787 if (typeof document !== 'undefined'){ 788 loadScript('https://www.youtube.com/iframe_api', apiLoaded); 789 injectCss(); 790 } 791 792 // Older versions of VJS5 doesn't have the registerTech function 793 if (typeof videojs.registerTech !== 'undefined') { 794 videojs.registerTech('Youtube', Youtube); 795 } else { 796 videojs.registerComponent('Youtube', Youtube); 797 } 798}));
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.