├── .npmrc
├── .jshintignore
├── .gitignore
├── .travis.yml
├── .npmignore
├── karma.conf.js
├── .jshintrc
├── examples
├── simple.html
├── youtube-controls.html
├── youtube-list.html
├── youtube-playlist.html
├── youtube-javascript.html
├── global-parameters.html
└── switch.html
├── bower.json
├── tests
└── parseUrl.specs.js
├── package.json
├── README.md
├── dist
├── Youtube.min.js
└── Youtube.js
└── src
└── Youtube.js
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | node_modules
4 | npm-debug.log
5 |
6 | dist
7 | .idea
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - lts/*
4 | addons:
5 | chrome: stable
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .npmignore
3 | .gitignore
4 | .jshintrc
5 | .jshintignore
6 | src
7 | tests
8 | karma.conf.js
9 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function(config) {
2 | var configuration = {
3 | basePath: '',
4 | frameworks: ['jasmine'],
5 | files: [
6 | 'node_modules/video.js/dist/video.js',
7 | 'src/Youtube.js',
8 | 'tests/**/*.specs.js'
9 | ],
10 | browsers: ['ChromeHeadless'],
11 | customLaunchers: {
12 | 'ChromeTravisCI': {
13 | base: 'Chrome',
14 | flags: ['--no-sandbox']
15 | }
16 | }
17 | };
18 |
19 | config.set(configuration);
20 | };
21 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "camelcase" : true,
3 | "indent" : 2,
4 | "maxlen" : 120,
5 | "trailing" : true,
6 | "bitwise" : true,
7 | "curly" : true,
8 | "eqeqeq" : true,
9 | "forin" : true,
10 | "noarg" : true,
11 | "noempty" : true,
12 | "nonew" : true,
13 | "undef" : true,
14 | "unused" : true,
15 | "devel" : true,
16 | "node" : false,
17 | "sub" : true,
18 | "quotmark" : "single",
19 | "browser" : true,
20 | "jasmine" : true,
21 | "node" : true,
22 | "esversion" : 6,
23 | "predef": [
24 | "videojs",
25 | "YT"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/youtube-controls.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/youtube-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/youtube-playlist.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "videojs-youtube",
3 | "version": "2.0.0",
4 | "homepage": "https://github.com/eXon/videojs-youtube",
5 | "authors": [
6 | "Benoit Tremblay "
7 | ],
8 | "description": "YouTube playback technology for Video.js",
9 | "main": "dist/Youtube.min.js",
10 | "license": "MIT",
11 | "keywords": [
12 | "video",
13 | "videojs",
14 | "video.js",
15 | "vjs",
16 | "YouTube",
17 | "tech"
18 | ],
19 | "ignore": [
20 | ".npmignore",
21 | ".gitignore",
22 | ".jshintrc",
23 | ".jshintignore",
24 | "src",
25 | "tests",
26 | "karma.conf.js"
27 | ],
28 | "dependencies": {
29 | "video.js": "^5.0.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/parseUrl.specs.js:
--------------------------------------------------------------------------------
1 | var Youtube = videojs.getTech('Youtube');
2 |
3 | describe('parseUrl', function() {
4 | it('should read the correct video ID', function() {
5 | expect(Youtube.parseUrl('https://www.youtube.com/watch?v=OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0');
6 | expect(Youtube.parseUrl('https://www.youtube.com/embed/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0');
7 | expect(Youtube.parseUrl('https://youtu.be/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0');
8 | });
9 |
10 | it('should read the list in the URL', function() {
11 | var url = 'https://www.youtube.com/watch?v=RgKAFK5djSk&list=PL55713C70BA91BD6E';
12 | expect(Youtube.parseUrl(url).listId).toBe('PL55713C70BA91BD6E');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/examples/youtube-javascript.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
17 |
18 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/examples/global-parameters.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/switch.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
19 |
20 |
21 |
22 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "videojs-youtube",
3 | "description": "YouTube playback technology for Video.js",
4 | "version": "3.0.1",
5 | "author": "Benoit Tremblay",
6 | "main": "dist/Youtube.js",
7 | "license": "MIT",
8 | "keywords": [
9 | "video",
10 | "videojs",
11 | "video.js",
12 | "vjs",
13 | "YouTube",
14 | "tech"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/videojs/videojs-youtube.git"
19 | },
20 | "peerDependencies": {
21 | "video.js": "5.x || 6.x || 7.x || 8.x"
22 | },
23 | "scripts": {
24 | "build": "mkdirp dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js",
25 | "lint": "jshint .",
26 | "test": "karma start --single-run",
27 | "validate": "npm ls",
28 | "version": "npm run build && git add -f dist"
29 | },
30 | "pre-commit": [
31 | "lint"
32 | ],
33 | "devDependencies": {
34 | "http-server": "^0.10.0",
35 | "jasmine-core": "^2.3.4",
36 | "jshint": "^2.8.0",
37 | "karma": "^1.7.1",
38 | "karma-chrome-launcher": "^2.0.0",
39 | "karma-jasmine": "^1.0.2",
40 | "mkdirp": "^0.5.1",
41 | "precommit-hook": "^3.0.0",
42 | "uglify-js": "^3.4.6"
43 | },
44 | "dependencies": {
45 | "video.js": "5.x || 6.x || 7.x || 8.x"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YouTube Playback Technology
for [Video.js](https://github.com/videojs/video.js)
2 |
3 | [](https://greenkeeper.io/)
4 |
5 | ## Install
6 | You can use bower (`bower install videojs-youtube`), npm (`npm install videojs-youtube`) or the source and build it using `npm run build`. Then, the only file you need is dist/Youtube.min.js.
7 |
8 | ## Version Note
9 | Use branch `vjs4` if you still using old VideoJS `v4.x`.
10 |
11 | ## Example
12 | ```html
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | See the examples folder for more
36 |
37 | ## How does it work?
38 | Including the script Youtube.min.js will add the YouTube as a tech. You just have to add it to your techOrder option. Then, you add the option src with your YouTube URL.
39 |
40 | It supports:
41 | - youtube.com as well as youtu.be
42 | - Regular URLs: http://www.youtube.com/watch?v=xjS6SftYQaQ
43 | - Embeded URLs: http://www.youtube.com/embed/xjS6SftYQaQ
44 | - Playlist URLs: http://www.youtube.com/playlist?list=PLA60DCEB33156E51F OR http://www.youtube.com/watch?v=xjS6SftYQaQ&list=SPA60DCEB33156E51F
45 |
46 | ## Options
47 | It supports every regular Video.js options. Additionally, you can change any [YouTube parameter](https://developers.google.com/youtube/player_parameters?hl=en#Parameters). Here is an example of setting the `iv_load_policy` parameter to `1`.
48 |
49 | ```html
50 |
59 | ```
60 |
61 | ### YouTube controls
62 | Because `controls` is already a Video.js option, to use the YouTube controls, you must set the `ytControls` parameter.
63 |
64 | ```html
65 |
74 | ```
75 |
76 | ### Custom Player Options
77 | If you need to set any additional options on the YouTube player, you may do so with the `customVars` parameter:
78 |
79 | ```html
80 |
89 | ```
90 |
91 | ## Special Thank You
92 | Thanks to Steve Heffernan for the amazing Video.js and to John Hurliman for the original version of the YouTube tech
93 |
94 | ## License
95 | The MIT License (MIT)
96 |
97 | Copyright (c) Benoit Tremblay and videojs-youtube contributors
98 |
99 | Permission is hereby granted, free of charge, to any person obtaining a copy
100 | of this software and associated documentation files (the "Software"), to deal
101 | in the Software without restriction, including without limitation the rights
102 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
103 | copies of the Software, and to permit persons to whom the Software is
104 | furnished to do so, subject to the following conditions:
105 |
106 | The above copyright notice and this permission notice shall be included in
107 | all copies or substantial portions of the Software.
108 |
109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
110 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
111 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
112 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
113 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
114 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
115 | THE SOFTWARE.
116 |
--------------------------------------------------------------------------------
/dist/Youtube.min.js:
--------------------------------------------------------------------------------
1 | (function(root,factory){if(typeof exports==="object"&&typeof module!=="undefined"){var videojs=require("video.js");module.exports=factory(videojs.default||videojs)}else if(typeof define==="function"&&define.amd){define(["videojs"],function(videojs){return root.Youtube=factory(videojs)})}else{root.Youtube=factory(root.videojs)}})(this,function(videojs){"use strict";var _isOnMobile=videojs.browser.IS_IOS||videojs.browser.IS_NATIVE_ANDROID;var Tech=videojs.getTech("Tech");class Youtube extends Tech{constructor(options,ready){super(options,ready);this.setPoster(options.poster);this.setSrc(this.options_.source,true);this.setTimeout(function(){if(this.el_){this.el_.parentNode.className+=" vjs-youtube";if(_isOnMobile){this.el_.parentNode.className+=" vjs-youtube-mobile"}if(Youtube.isApiReady){this.initYTPlayer()}else{Youtube.apiReadyQueue.push(this)}}}.bind(this))}dispose(){if(this.ytPlayer){if(this.ytPlayer.stopVideo){this.ytPlayer.stopVideo()}if(this.ytPlayer.destroy){this.ytPlayer.destroy()}}else{var index=Youtube.apiReadyQueue.indexOf(this);if(index!==-1){Youtube.apiReadyQueue.splice(index,1)}}this.ytPlayer=null;this.el_.parentNode.className=this.el_.parentNode.className.replace(" vjs-youtube","").replace(" vjs-youtube-mobile","");this.el_.parentNode.removeChild(this.el_);Tech.prototype.dispose.call(this)}createEl(){var div=document.createElement("div");div.setAttribute("id",this.options_.techId);div.setAttribute("style","width:100%;height:100%;top:0;left:0;position:absolute");div.setAttribute("class","vjs-tech");var divWrapper=document.createElement("div");divWrapper.appendChild(div);if(!_isOnMobile&&!this.options_.ytControls){var divBlocker=document.createElement("div");divBlocker.setAttribute("class","vjs-iframe-blocker");divBlocker.setAttribute("style","position:absolute;top:0;left:0;width:100%;height:100%");divBlocker.onclick=function(){this.pause()}.bind(this);divWrapper.appendChild(divBlocker)}return divWrapper}initYTPlayer(){var playerVars={controls:0,modestbranding:1,rel:0,showinfo:0,loop:this.options_.loop?1:0};if(typeof this.options_.autohide!=="undefined"){playerVars.autohide=this.options_.autohide}if(typeof this.options_["cc_load_policy"]!=="undefined"){playerVars["cc_load_policy"]=this.options_["cc_load_policy"]}if(typeof this.options_.ytControls!=="undefined"){playerVars.controls=this.options_.ytControls}if(typeof this.options_.disablekb!=="undefined"){playerVars.disablekb=this.options_.disablekb}if(typeof this.options_.color!=="undefined"){playerVars.color=this.options_.color}if(!playerVars.controls){playerVars.fs=0}else if(typeof this.options_.fs!=="undefined"){playerVars.fs=this.options_.fs}if(this.options_.source.src.indexOf("end=")!==-1){var srcEndTime=this.options_.source.src.match(/end=([0-9]*)/);this.options_.end=parseInt(srcEndTime[1])}if(typeof this.options_.end!=="undefined"){playerVars.end=this.options_.end}if(typeof this.options_.hl!=="undefined"){playerVars.hl=this.options_.hl}else if(typeof this.options_.language!=="undefined"){playerVars.hl=this.options_.language.substr(0,2)}if(typeof this.options_["iv_load_policy"]!=="undefined"){playerVars["iv_load_policy"]=this.options_["iv_load_policy"]}if(typeof this.options_.list!=="undefined"){playerVars.list=this.options_.list}else if(this.url&&typeof this.url.listId!=="undefined"){playerVars.list=this.url.listId}if(typeof this.options_.listType!=="undefined"){playerVars.listType=this.options_.listType}if(typeof this.options_.modestbranding!=="undefined"){playerVars.modestbranding=this.options_.modestbranding}if(typeof this.options_.playlist!=="undefined"){playerVars.playlist=this.options_.playlist}if(typeof this.options_.playsinline!=="undefined"){playerVars.playsinline=this.options_.playsinline}if(typeof this.options_.rel!=="undefined"){playerVars.rel=this.options_.rel}if(typeof this.options_.showinfo!=="undefined"){playerVars.showinfo=this.options_.showinfo}if(this.options_.source.src.indexOf("start=")!==-1){var srcStartTime=this.options_.source.src.match(/start=([0-9]*)/);this.options_.start=parseInt(srcStartTime[1])}if(typeof this.options_.start!=="undefined"){playerVars.start=this.options_.start}if(typeof this.options_.theme!=="undefined"){playerVars.theme=this.options_.theme}if(typeof this.options_.customVars!=="undefined"){var customVars=this.options_.customVars;Object.keys(customVars).forEach(function(key){playerVars[key]=customVars[key]})}this.activeVideoId=this.url?this.url.videoId:null;this.activeList=playerVars.list;var playerConfig={videoId:this.activeVideoId,playerVars:playerVars,events:{onReady:this.onPlayerReady.bind(this),onPlaybackQualityChange:this.onPlayerPlaybackQualityChange.bind(this),onPlaybackRateChange:this.onPlayerPlaybackRateChange.bind(this),onStateChange:this.onPlayerStateChange.bind(this),onVolumeChange:this.onPlayerVolumeChange.bind(this),onError:this.onPlayerError.bind(this)}};if(typeof this.options_.enablePrivacyEnhancedMode!=="undefined"&&this.options_.enablePrivacyEnhancedMode){playerConfig.host="https://www.youtube-nocookie.com"}this.ytPlayer=new YT.Player(this.options_.techId,playerConfig)}onPlayerReady(){if(this.options_.muted){this.ytPlayer.mute()}var playbackRates=this.ytPlayer.getAvailablePlaybackRates();if(playbackRates.length>1){this.featuresPlaybackRate=true}this.playerReady_=true;this.triggerReady();if(this.playOnReady){this.play()}else if(this.cueOnReady){this.cueVideoById_(this.url.videoId);this.activeVideoId=this.url.videoId}}onPlayerPlaybackQualityChange(){}onPlayerPlaybackRateChange(){this.trigger("ratechange")}onPlayerStateChange(e){var state=e.data;if(state===this.lastState||this.errorNumber){return}this.lastState=state;switch(state){case-1:this.trigger("loadstart");this.trigger("loadedmetadata");this.trigger("durationchange");this.trigger("ratechange");break;case YT.PlayerState.ENDED:this.trigger("ended");break;case YT.PlayerState.PLAYING:this.trigger("timeupdate");this.trigger("durationchange");this.trigger("playing");this.trigger("play");if(this.isSeeking){this.onSeeked()}break;case YT.PlayerState.PAUSED:this.trigger("canplay");if(this.isSeeking){this.onSeeked()}else{this.trigger("pause")}break;case YT.PlayerState.BUFFERING:this.player_.trigger("timeupdate");this.player_.trigger("waiting");break}}onPlayerVolumeChange(){this.trigger("volumechange")}onPlayerError(e){this.errorNumber=e.data;this.trigger("pause");this.trigger("error")}error(){var code=1e3+this.errorNumber;switch(this.errorNumber){case 5:return{code:code,message:"Error while trying to play the video"};case 2:case 100:return{code:code,message:"Unable to find the video"};case 101:case 150:return{code:code,message:"Playback on other Websites has been disabled by the video owner."}}return{code:code,message:"YouTube unknown error ("+this.errorNumber+")"}}loadVideoById_(id){var options={videoId:id};if(this.options_.start){options.startSeconds=this.options_.start}if(this.options_.end){options.endSeconds=this.options_.end}this.ytPlayer.loadVideoById(options)}cueVideoById_(id){var options={videoId:id};if(this.options_.start){options.startSeconds=this.options_.start}if(this.options_.end){options.endSeconds=this.options_.end}this.ytPlayer.cueVideoById(options)}src(src){if(src){this.setSrc({src:src})}return this.source}poster(){if(_isOnMobile){return null}return this.poster_}setPoster(poster){this.poster_=poster}setSrc(source){if(!source||!source.src){return}delete this.errorNumber;this.source=source;this.url=Youtube.parseUrl(source.src);if(!this.options_.poster){if(this.url.videoId){this.poster_="https://img.youtube.com/vi/"+this.url.videoId+"/0.jpg";this.trigger("posterchange");this.checkHighResPoster()}}if(this.options_.autoplay&&!_isOnMobile){if(this.isReady_){this.play()}else{this.playOnReady=true}}else if(this.activeVideoId!==this.url.videoId){if(this.isReady_){this.cueVideoById_(this.url.videoId);this.activeVideoId=this.url.videoId}else{this.cueOnReady=true}}}autoplay(){return this.options_.autoplay}setAutoplay(val){this.options_.autoplay=val}loop(){return this.options_.loop}setLoop(val){this.options_.loop=val}play(){if(!this.url||!this.url.videoId){return}this.wasPausedBeforeSeek=false;if(this.isReady_){if(this.url.listId){if(this.activeList===this.url.listId){this.ytPlayer.playVideo()}else{this.ytPlayer.loadPlaylist(this.url.listId);this.activeList=this.url.listId}}if(this.activeVideoId===this.url.videoId){this.ytPlayer.playVideo()}else{this.loadVideoById_(this.url.videoId);this.activeVideoId=this.url.videoId}}else{this.trigger("waiting");this.playOnReady=true}}pause(){if(this.ytPlayer){this.ytPlayer.pauseVideo()}}paused(){return this.ytPlayer?this.lastState!==YT.PlayerState.PLAYING&&this.lastState!==YT.PlayerState.BUFFERING:true}currentTime(){return this.ytPlayer?this.ytPlayer.getCurrentTime():0}setCurrentTime(seconds){if(this.lastState===YT.PlayerState.PAUSED){this.timeBeforeSeek=this.currentTime()}if(!this.isSeeking){this.wasPausedBeforeSeek=this.paused()}this.ytPlayer.seekTo(seconds,true);this.trigger("timeupdate");this.trigger("seeking");this.isSeeking=true;if(this.lastState===YT.PlayerState.PAUSED&&this.timeBeforeSeek!==seconds){clearInterval(this.checkSeekedInPauseInterval);this.checkSeekedInPauseInterval=setInterval(function(){if(this.lastState!==YT.PlayerState.PAUSED||!this.isSeeking){clearInterval(this.checkSeekedInPauseInterval)}else if(this.currentTime()!==this.timeBeforeSeek){this.trigger("timeupdate");this.onSeeked()}}.bind(this),250)}}seeking(){return this.isSeeking}seekable(){if(!this.ytPlayer){return videojs.createTimeRange()}return videojs.createTimeRange(0,this.ytPlayer.getDuration())}onSeeked(){clearInterval(this.checkSeekedInPauseInterval);this.isSeeking=false;if(this.wasPausedBeforeSeek){this.pause()}this.trigger("seeked")}playbackRate(){return this.ytPlayer?this.ytPlayer.getPlaybackRate():1}setPlaybackRate(suggestedRate){if(!this.ytPlayer){return}this.ytPlayer.setPlaybackRate(suggestedRate)}duration(){return this.ytPlayer?this.ytPlayer.getDuration():0}currentSrc(){return this.source&&this.source.src}ended(){return this.ytPlayer?this.lastState===YT.PlayerState.ENDED:false}volume(){return this.ytPlayer?this.ytPlayer.getVolume()/100:1}setVolume(percentAsDecimal){if(!this.ytPlayer){return}this.ytPlayer.setVolume(percentAsDecimal*100)}muted(){return this.ytPlayer?this.ytPlayer.isMuted():false}setMuted(mute){if(!this.ytPlayer){return}else{this.muted(true)}if(mute){this.ytPlayer.mute()}else{this.ytPlayer.unMute()}this.setTimeout(function(){this.trigger("volumechange")},50)}buffered(){if(!this.ytPlayer||!this.ytPlayer.getVideoLoadedFraction){return videojs.createTimeRange()}var bufferedEnd=this.ytPlayer.getVideoLoadedFraction()*this.ytPlayer.getDuration();return videojs.createTimeRange(0,bufferedEnd)}preload(){}load(){}reset(){}networkState(){if(!this.ytPlayer){return 0}switch(this.ytPlayer.getPlayerState()){case-1:return 0;case 3:return 2;default:return 1}}readyState(){if(!this.ytPlayer){return 0}switch(this.ytPlayer.getPlayerState()){case-1:return 0;case 5:return 1;case 3:return 2;default:return 4}}supportsFullScreen(){return document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled}checkHighResPoster(){var uri="https://img.youtube.com/vi/"+this.url.videoId+"/maxresdefault.jpg";try{var image=new Image;image.onload=function(){if("naturalHeight"in image){if(image.naturalHeight<=90||image.naturalWidth<=120){return}}else if(image.height<=90||image.width<=120){return}this.poster_=uri;this.trigger("posterchange")}.bind(this);image.onerror=function(){};image.src=uri}catch(e){}}}Youtube.isSupported=function(){return true};Youtube.canPlaySource=function(e){return Youtube.canPlayType(e.type)};Youtube.canPlayType=function(e){return e==="video/youtube"};Youtube.parseUrl=function(url){var result={videoId:null};var regex=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;var match=url.match(regex);if(match&&match[2].length===11){result.videoId=match[2]}var regPlaylist=/[?&]list=([^#\&\?]+)/;match=url.match(regPlaylist);if(match&&match[1]){result.listId=match[1]}return result};function apiLoaded(){YT.ready(function(){Youtube.isApiReady=true;for(var i=0;i
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE 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 | }));
799 |
--------------------------------------------------------------------------------
/src/Youtube.js:
--------------------------------------------------------------------------------
1 | /* The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Benoit Tremblay
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE 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 | }));
799 |
--------------------------------------------------------------------------------