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 define === 'function' && define.amd) { 25 define(['video.js'], function(videojs){ 26 return (root.Youtube = factory(videojs)); 27 }); 28 } else if(typeof module === 'object' && module.exports) { 29 module.exports = (root.Youtube = factory(require('video.js'))); 30 } else { 31 root.Youtube = factory(root.videojs); 32 } 33}(this, function(videojs) { 34 'use strict'; 35 36 var Tech = videojs.getComponent('Tech'); 37 38 var Youtube = videojs.extend(Tech, { 39 40 constructor: function(options, ready) { 41 Tech.call(this, options, ready); 42 43 this.setPoster(options.poster); 44 this.setSrc(this.options_.source, true); 45 46 // Set the vjs-youtube class to the player 47 // Parent is not set yet so we have to wait a tick 48 setTimeout(function() { 49 this.el_.parentNode.className += ' vjs-youtube'; 50 51 if (_isOnMobile) { 52 this.el_.parentNode.className += ' vjs-youtube-mobile'; 53 } 54 55 if (Youtube.isApiReady) { 56 this.initYTPlayer(); 57 } else { 58 Youtube.apiReadyQueue.push(this); 59 } 60 }.bind(this)); 61 }, 62 63 dispose: function() { 64 this.el_.parentNode.className = this.el_.parentNode.className 65 .replace(' vjs-youtube', '') 66 .replace(' vjs-youtube-mobile', ''); 67 }, 68 69 createEl: function() { 70 var div = document.createElement('div'); 71 div.setAttribute('id', this.options_.techId); 72 div.setAttribute('style', 'width:100%;height:100%;top:0;left:0;position:absolute'); 73 74 var divWrapper = document.createElement('div'); 75 divWrapper.appendChild(div); 76 77 if (!_isOnMobile && !this.options_.ytControls) { 78 var divBlocker = document.createElement('div'); 79 divBlocker.setAttribute('class', 'vjs-iframe-blocker'); 80 divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); 81 82 // In case the blocker is still there and we want to pause 83 divBlocker.onclick = function() { 84 this.pause(); 85 }.bind(this); 86 87 divWrapper.appendChild(divBlocker); 88 } 89 90 return divWrapper; 91 }, 92 93 initYTPlayer: function() { 94 var playerVars = { 95 controls: 0, 96 modestbranding: 1, 97 rel: 0, 98 showinfo: 0, 99 loop: this.options_.loop ? 1 : 0 100 }; 101 102 // Let the user set any YouTube parameter 103 // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters 104 // To use YouTube controls, you must use ytControls instead 105 // To use the loop or autoplay, use the video.js settings 106 107 if (typeof this.options_.autohide !== 'undefined') { 108 playerVars.autohide = this.options_.autohide; 109 } 110 111 if (typeof this.options_['cc_load_policy'] !== 'undefined') { 112 playerVars['cc_load_policy'] = this.options_['cc_load_policy']; 113 } 114 115 if (typeof this.options_.ytControls !== 'undefined') { 116 playerVars.controls = this.options_.ytControls; 117 } 118 119 if (typeof this.options_.disablekb !== 'undefined') { 120 playerVars.disablekb = this.options_.disablekb; 121 } 122 123 if (typeof this.options_.end !== 'undefined') { 124 playerVars.end = this.options_.end; 125 } 126 127 if (typeof this.options_.color !== 'undefined') { 128 playerVars.color = this.options_.color; 129 } 130 131 if (!playerVars.controls) { 132 // Let video.js handle the fullscreen unless it is the YouTube native controls 133 playerVars.fs = 0; 134 } else if (typeof this.options_.fs !== 'undefined') { 135 playerVars.fs = this.options_.fs; 136 } 137 138 if (typeof this.options_.end !== 'undefined') { 139 playerVars.end = this.options_.end; 140 } 141 142 if (typeof this.options_.hl !== 'undefined') { 143 playerVars.hl = this.options_.hl; 144 } else if (typeof this.options_.language !== 'undefined') { 145 // Set the YouTube player on the same language than video.js 146 playerVars.hl = this.options_.language.substr(0, 2); 147 } 148 149 if (typeof this.options_['iv_load_policy'] !== 'undefined') { 150 playerVars['iv_load_policy'] = this.options_['iv_load_policy']; 151 } 152 153 if (typeof this.options_.list !== 'undefined') { 154 playerVars.list = this.options_.list; 155 } else if (this.url && typeof this.url.listId !== 'undefined') { 156 playerVars.list = this.url.listId; 157 } 158 159 if (typeof this.options_.listType !== 'undefined') { 160 playerVars.listType = this.options_.listType; 161 } 162 163 if (typeof this.options_.modestbranding !== 'undefined') { 164 playerVars.modestbranding = this.options_.modestbranding; 165 } 166 167 if (typeof this.options_.playlist !== 'undefined') { 168 playerVars.playlist = this.options_.playlist; 169 } 170 171 if (typeof this.options_.playsinline !== 'undefined') { 172 playerVars.playsinline = this.options_.playsinline; 173 } 174 175 if (typeof this.options_.rel !== 'undefined') { 176 playerVars.rel = this.options_.rel; 177 } 178 179 if (typeof this.options_.showinfo !== 'undefined') { 180 playerVars.showinfo = this.options_.showinfo; 181 } 182 183 if (typeof this.options_.start !== 'undefined') { 184 playerVars.start = this.options_.start; 185 } 186 187 if (typeof this.options_.theme !== 'undefined') { 188 playerVars.theme = this.options_.theme; 189 } 190 191 this.activeVideoId = this.url ? this.url.videoId : null; 192 this.activeList = playerVars.list; 193 194 this.ytPlayer = new YT.Player(this.options_.techId, { 195 videoId: this.activeVideoId, 196 playerVars: playerVars, 197 events: { 198 onReady: this.onPlayerReady.bind(this), 199 onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), 200 onStateChange: this.onPlayerStateChange.bind(this), 201 onError: this.onPlayerError.bind(this) 202 } 203 }); 204 }, 205 206 onPlayerReady: function() { 207 this.playerReady_ = true; 208 this.triggerReady(); 209 210 if (this.playOnReady) { 211 this.play(); 212 } 213 }, 214 215 onPlayerPlaybackQualityChange: function() { 216 217 }, 218 219 onPlayerStateChange: function(e) { 220 var state = e.data; 221 222 if (state === this.lastState) { 223 return; 224 } 225 226 switch (state) { 227 case -1: 228 this.trigger('loadedmetadata'); 229 this.trigger('durationchange'); 230 break; 231 232 case YT.PlayerState.ENDED: 233 this.trigger('ended'); 234 break; 235 236 case YT.PlayerState.PLAYING: 237 this.trigger('timeupdate'); 238 this.trigger('durationchange'); 239 this.trigger('playing'); 240 this.trigger('play'); 241 242 if (this.isSeeking) { 243 this.onSeeked(); 244 } 245 break; 246 247 case YT.PlayerState.PAUSED: 248 this.trigger('canplay'); 249 if (this.isSeeking) { 250 this.onSeeked(); 251 } else { 252 this.trigger('pause'); 253 } 254 break; 255 256 case YT.PlayerState.BUFFERING: 257 this.player_.trigger('timeupdate'); 258 this.player_.trigger('waiting'); 259 break; 260 } 261 262 this.lastState = state; 263 }, 264 265 onPlayerError: function(e) { 266 this.errorNumber = e.data; 267 this.trigger('error'); 268 269 this.ytPlayer.stopVideo(); 270 this.ytPlayer.destroy(); 271 this.ytPlayer = null; 272 }, 273 274 error: function() { 275 switch (this.errorNumber) { 276 case 2: 277 return { code: 'Unable to find the video' }; 278 279 case 5: 280 return { code: 'Error while trying to play the video' }; 281 282 case 100: 283 return { code: 'Unable to find the video' }; 284 285 case 101: 286 case 150: 287 return { code: 'Playback on other Websites has been disabled by the video owner.' }; 288 } 289 290 return { code: 'YouTube unknown error (' + this.errorNumber + ')' }; 291 }, 292 293 src: function(src) { 294 if (src) { 295 this.setSrc({ src: src }); 296 this.play(); 297 } 298 299 return this.source; 300 }, 301 302 poster: function() { 303 // You can't start programmaticlly a video with a mobile 304 // through the iframe so we hide the poster and the play button (with CSS) 305 if (_isOnMobile) { 306 return null; 307 } 308 309 return this.poster_; 310 }, 311 312 setPoster: function(poster) { 313 this.poster_ = poster; 314 }, 315 316 setSrc: function(source) { 317 if (!source || !source.src) { 318 return; 319 } 320 321 this.source = source; 322 this.url = Youtube.parseUrl(source.src); 323 324 if (!this.options_.poster) { 325 if (this.url.videoId) { 326 // Set the low resolution first 327 this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; 328 329 // Check if their is a high res 330 this.checkHighResPoster(); 331 } 332 } 333 334 if (this.options_.autoplay && !_isOnMobile) { 335 if (this.isReady_) { 336 this.play(); 337 } else { 338 this.playOnReady = true; 339 } 340 } 341 }, 342 343 play: function() { 344 if (!this.url || !this.url.videoId) { 345 return; 346 } 347 348 if (this.isReady_) { 349 if (this.url.listId) { 350 if (this.activeList === this.url.listId) { 351 this.ytPlayer.playVideo(); 352 } else { 353 this.ytPlayer.loadPlaylist(this.url.listId); 354 this.activeList = this.url.listId; 355 } 356 } 357 358 if (this.activeVideoId === this.url.videoId) { 359 this.ytPlayer.playVideo(); 360 } else { 361 this.ytPlayer.loadVideoById(this.url.videoId); 362 this.activeVideoId = this.url.videoId; 363 } 364 } else { 365 this.trigger('waiting'); 366 this.playOnReady = true; 367 } 368 }, 369 370 pause: function() { 371 if (this.ytPlayer) { 372 this.ytPlayer.pauseVideo(); 373 } 374 }, 375 376 paused: function() { 377 return (this.ytPlayer) ? 378 (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) 379 : true; 380 }, 381 382 currentTime: function() { 383 return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0; 384 }, 385 386 setCurrentTime: function(seconds) { 387 if (this.lastState === YT.PlayerState.PAUSED) { 388 this.timeBeforeSeek = this.currentTime(); 389 } 390 391 if (!this.isSeeking) { 392 this.wasPausedBeforeSeek = this.paused(); 393 } 394 395 this.ytPlayer.seekTo(seconds, true); 396 this.trigger('timeupdate'); 397 this.trigger('seeking'); 398 this.isSeeking = true; 399 400 // A seek event during pause does not return an event to trigger a seeked event, 401 // so run an interval timer to look for the currentTime to change 402 if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { 403 clearInterval(this.checkSeekedInPauseInterval); 404 this.checkSeekedInPauseInterval = setInterval(function() { 405 if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { 406 // If something changed while we were waiting for the currentTime to change, 407 // clear the interval timer 408 clearInterval(this.checkSeekedInPauseInterval); 409 } else if (this.currentTime() !== this.timeBeforeSeek) { 410 this.trigger('timeupdate'); 411 this.onSeeked(); 412 } 413 }.bind(this), 250); 414 } 415 }, 416 417 onSeeked: function() { 418 clearInterval(this.checkSeekedInPauseInterval); 419 this.trigger('seeked'); 420 this.isSeeking = false;
421 if (this.wasPausedBeforeSeek) { 422 this.pause(); 423 } 424 }, 425 426 playbackRate: function() { 427 return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1; 428 }, 429 430 setPlaybackRate: function(suggestedRate) { 431 if (!this.ytPlayer) { 432 return; 433 } 434 435 this.ytPlayer.setPlaybackRate(suggestedRate); 436 this.trigger('ratechange'); 437 }, 438 439 duration: function() { 440 return this.ytPlayer ? this.ytPlayer.getDuration() : 0; 441 }, 442 443 currentSrc: function() { 444 return this.source; 445 }, 446 447 ended: function() { 448 return this.ytPlayer ? (this.lastState === YT.PlayerState.ENDED) : false; 449 }, 450 451 volume: function() { 452 return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1; 453 }, 454 455 setVolume: function(percentAsDecimal) { 456 if (!this.ytPlayer) { 457 return; 458 } 459 460 this.ytPlayer.setVolume(percentAsDecimal * 100.0); 461 this.setTimeout( function(){ 462 this.trigger('volumechange'); 463 }, 50); 464 465 }, 466 467 muted: function() { 468 return this.ytPlayer ? this.ytPlayer.isMuted() : false; 469 }, 470 471 setMuted: function(mute) { 472 if (!this.ytPlayer) { 473 return; 474 } 475 else{ 476 this.muted(true); 477 } 478 479 if (mute) { 480 this.ytPlayer.mute(); 481 } else { 482 this.ytPlayer.unMute(); 483 } 484 this.setTimeout( function(){ 485 this.trigger('volumechange'); 486 }, 50); 487 }, 488 489 buffered: function() { 490 if(!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) { 491 return { 492 length: 0, 493 start: function() { 494 throw new Error('This TimeRanges object is empty'); 495 }, 496 end: function() { 497 throw new Error('This TimeRanges object is empty'); 498 } 499 }; 500 } 501 502 var end = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration(); 503 504 return { 505 length: 1, 506 start: function() { return 0; }, 507 end: function() { return end; } 508 }; 509 }, 510 511 512 513 supportsFullScreen: function() { 514 return true; 515 }, 516 517 // Tries to get the highest resolution thumbnail available for the video 518 checkHighResPoster: function(){ 519 var uri = 'https://img.youtube.com/vi/' + this.url.videoId + '/maxresdefault.jpg'; 520 521 try { 522 var image = new Image(); 523 image.onload = function(){ 524 // Onload may still be called if YouTube returns the 120x90 error thumbnail 525 if('naturalHeight' in image){ 526 if(image.naturalHeight <= 90 || image.naturalWidth <= 120) { 527 528 if(typeof this.onerror == "function"){ 529 this.onerror(); 530 } 531 532 return; 533 } 534 } else if(image.height <= 90 || image.width <= 120) { 535 this.onerror(); 536 return; 537 } 538 539 this.poster_ = uri; 540 this.trigger('posterchange'); 541 }.bind(this); 542 image.onerror = function(){}; 543 image.src = uri; 544 } 545 catch(e){} 546 } 547 }); 548 549 Youtube.isSupported = function() { 550 return true; 551 }; 552 553 Youtube.canPlaySource = function(e) { 554 return (e.type === 'video/youtube'); 555 }; 556 557 var _isOnMobile = /(iPad|iPhone|iPod|Android)/g.test(navigator.userAgent); 558 559 Youtube.parseUrl = function(url) { 560 var result = { 561 videoId: null 562 }; 563 564 var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; 565 var match = url.match(regex); 566 567 if (match && match[2].length === 11) { 568 result.videoId = match[2]; 569 } 570 571 var regPlaylist = /[?&]list=([^#\&\?]+)/; 572 match = url.match(regPlaylist); 573 574 if(match && match[1]) { 575 result.listId = match[1]; 576 } 577 578 return result; 579 }; 580 581 function loadApi() { 582 var tag = document.createElement('script'); 583 tag.src = 'https://www.youtube.com/iframe_api'; 584 var firstScriptTag = document.getElementsByTagName('script')[0]; 585 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 586 } 587 588 function injectCss() { 589 var css = // iframe blocker to catch mouse events 590 '.vjs-youtube .vjs-iframe-blocker { display: none; }' + 591 '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + 592 '.vjs-youtube .vjs-poster { background-size: cover; }' + 593 '.vjs-youtube-mobile .vjs-big-play-button { display: none; }'; 594 595 var head = document.head || document.getElementsByTagName('head')[0]; 596 597 var style = document.createElement('style'); 598 style.type = 'text/css'; 599 600 if (style.styleSheet){ 601 style.styleSheet.cssText = css; 602 } else { 603 style.appendChild(document.createTextNode(css)); 604 } 605 606 head.appendChild(style); 607 } 608 609 Youtube.apiReadyQueue = []; 610 611 window.onYouTubeIframeAPIReady = function() { 612 Youtube.isApiReady = true; 613 614 for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { 615 Youtube.apiReadyQueue[i].initYTPlayer(); 616 } 617
618 if(typeof onYouTubeIframeAPIReadyFullscreenBackground == "function"){ 619 onYouTubeIframeAPIReadyFullscreenBackground(); 620 } 621 }; 622 623 window.onYouTubeIframeAPIReadyYoutube = function() { 624 Youtube.isApiReady = true; 625 626 for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { 627 Youtube.apiReadyQueue[i].initYTPlayer(); 628 } 629 }; 630 631 loadApi(); 632 injectCss(); 633 634 videojs.registerTech('Youtube', Youtube); 635}));
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.