From 4a05b3191d476188da83ea67fdf2b6f5df6223d3 Mon Sep 17 00:00:00 2001 From: eXon Date: Tue, 7 Jul 2015 07:23:35 -0400 Subject: [PATCH 01/21] Starting v2 rewrite with minimalist approach --- .editorconfig | 25 - .gitignore | 13 - .jshintignore | 2 + .jshintrc | 20 +- .npmignore | 4 - .travis.yml | 11 - Gruntfile.js | 77 -- README.md | 61 +- LICENSE => Youtube.js | 22 +- Youtube.min.js | 1 + bower.json | 30 +- examples/autoplay.html | 12 - examples/javascript.html | 23 - examples/loop.html | 12 - examples/mixTechs.html | 29 - examples/muted.html | 12 - examples/quality.html | 12 - examples/simple.html | 19 +- package.json | 43 +- sandbox/index.html | 18 - src/youtube.js | 1097 --------------------------- test/functional/api_test.js | 159 ---- test/functional/local.config.js | 20 - test/functional/saucelabs.config.js | 86 --- test/unit/quality_test.js | 19 - test/unit/runner.html | 28 - tests.js | 0 27 files changed, 99 insertions(+), 1756 deletions(-) delete mode 100644 .editorconfig create mode 100644 .jshintignore delete mode 100644 .npmignore delete mode 100644 .travis.yml delete mode 100644 Gruntfile.js rename LICENSE => Youtube.js (72%) create mode 100644 Youtube.min.js delete mode 100644 examples/autoplay.html delete mode 100644 examples/javascript.html delete mode 100644 examples/loop.html delete mode 100644 examples/mixTechs.html delete mode 100644 examples/muted.html delete mode 100644 examples/quality.html mode change 100644 => 100755 package.json delete mode 100644 sandbox/index.html delete mode 100644 src/youtube.js delete mode 100644 test/functional/api_test.js delete mode 100644 test/functional/local.config.js delete mode 100644 test/functional/saucelabs.config.js delete mode 100644 test/unit/quality_test.js delete mode 100644 test/unit/runner.html create mode 100644 tests.js diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 5ea41508..00000000 --- a/.editorconfig +++ /dev/null @@ -1,25 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] - -# Change these settings to your own preference -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false - -[*.coffee] -indent_style = space -indent_size = 2 diff --git a/.gitignore b/.gitignore index eddeaed9..93f13619 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,2 @@ -.DS_Store -.idea -*.sublime-project -*.sublime-workspace -dist - node_modules -bower_components npm-debug.log - -mongoose*.exe - -*.orig - -*.log diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 00000000..74f71c5e --- /dev/null +++ b/.jshintignore @@ -0,0 +1,2 @@ +node_modules +*.min.js diff --git a/.jshintrc b/.jshintrc index f25664fa..f81a6aaf 100644 --- a/.jshintrc +++ b/.jshintrc @@ -16,26 +16,8 @@ "node" : false, "sub" : true, "quotmark" : "single", + "browser" : true, "predef": [ - // standard - "document", - "window", - "console", - "alert", - "setTimeout", - "toString", - "isNaN", - - // mocha - "describe", - "it", - "before", - "beforeEach", - "after", - "afterEach", - - // video.js "videojs" ] } - diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 3c62b71b..00000000 --- a/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -# Exclude everything but the contents of the src and dist directory. -**/* -!dist/** -!src/** \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dacd97dd..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: node_js -node_js: -- 0.1 -before_install: -- npm install -g grunt-cli -before_script: -- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then curl https://gist.githubusercontent.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash; fi -env: - global: - - secure: dVO5M1peBYDTyq1syr9ydPCnREDMBLmav5hS9/ttkf6Ucb1rjYkmGGCU8S7mctYdT8hMKN/FtdjRfD9U/NHQHBddmbOIfb9NSns1q4moS1ythbXUyZ/C//ozjDPuZh2CeAZox0kJ8yFC6UyLaeRA3+qFal0fyMPD+pK85uNb2tM= - - secure: GUpT/MDWTz4ePjTFe/w4oE3i34wXJ6HFvIFLPe3j1hp/pl4QRElRA8lzFOPXjHtTD8EqTrLEs/RALABm4Md29MAsZUi3yqPvD87O1X94zYghacMvhOQZ3QsqC4/BNTgTWz5XgYepokkK8tNk/p/9u+gHHx74hyxxyl6pTYTk9MY= diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 42285fb6..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,77 +0,0 @@ -/* jshint node: true */ -module.exports = function(grunt) { - - grunt.initConfig({ - jshint: { - options: { - jshintrc: true - }, - all: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'] - }, - uglify: { - vjsyoutube: { - options: { - mangle: false - }, - files: { - 'dist/vjs.youtube.js': ['src/youtube.js'] - } - } - }, - /*jshint -W106 */ - mocha_phantomjs: { - /*jshint +W106 */ - all: { - options: { - urls: ['http://localhost:8080/test/unit/runner.html'] - } - } - }, - protractor: { - options: { - keepAlive: false, - noColor: false - }, - local: { - options: { - configFile: 'test/functional/local.config.js' - } - }, - saucelabs: { - options: { - configFile: 'test/functional/saucelabs.config.js', - args: { - sauceUser: process.env.SAUCE_USERNAME, - sauceKey: process.env.SAUCE_ACCESS_KEY - } - } - } - }, - connect: { - server: { - options: { - hostname: 'localhost', - port: 8080 - } - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-protractor-runner'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-mocha-phantomjs'); - - grunt.registerTask('default', ['jshint', 'uglify']); - - grunt.registerTask('test', function() { - if (process.env.TRAVIS_PULL_REQUEST === 'false') { - grunt.task.run(['jshint', 'connect:server', 'mocha_phantomjs', 'protractor:saucelabs']); - } else if (process.env.TRAVIS) { - grunt.task.run(['jshint', 'connect:server', 'mocha_phantomjs']); - } else { - grunt.task.run(['jshint', 'connect:server', 'mocha_phantomjs', 'protractor:local']); - } - }); -}; \ No newline at end of file diff --git a/README.md b/README.md index 687cbf6f..ea2b3d3b 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,28 @@ -# YouTube Playback Technology
for [Video.js](https://github.com/videojs/video.js) [![Build Status](https://travis-ci.org/eXon/videojs-youtube.svg?branch=master)](https://travis-ci.org/eXon/videojs-youtube) -[![Selenium Test Status](https://saucelabs.com/browser-matrix/videojs-youtube.svg)](https://saucelabs.com/u/videojs-youtube) -

-YouTube playback technology for the [Video.js player](https://github.com/videojs/video.js/). +# YouTube Playback Technology
for [Video.js](https://github.com/videojs/video.js) -## Install -You can use bower (`bower install videojs-youtube`), npm (`npm install videojs-youtube`) or the source and build it using `grunt`. Then, the only file you need is dist/vjs.youtube.js. +Complete rewrite for VJS 5 in progress -## Example -```html - - - - - - - - - - - -``` +## Todo coming soon +## License +The MIT License (MIT) -See the examples folder for more +Copyright (c) 2014-2015 Benoit Tremblay -## How does it work? -Including the script vjs.youtube.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. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -It supports: -- youtube.com as well as youtu.be -- Regular URLs: http://www.youtube.com/watch?v=xjS6SftYQaQ -- Embeded URLs: http://www.youtube.com/embed/xjS6SftYQaQ -- Playlist URLs: http://www.youtube.com/playlist?list=PLA60DCEB33156E51F OR http://www.youtube.com/watch?v=xjS6SftYQaQ&list=SPA60DCEB33156E51F +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -## Additional Options -This plugin exposes the following additional [player options](https://github.com/videojs/video.js/blob/master/docs/guides/options.md): - -- `ytcontrols` (Boolean): Display the YouTube player controls instead of the Video.js player controls. (default `false`) -- `ytFullScreenControls` (Boolean): Show the fullscreen controls on the default youtube player controls. Also enables double click to fullscreen. (default `true`) -- `quality` (String): Set the default video quality. Should be one of `1080p`, `720p`, `480p`, `360p`, `240p`, `144p`. -- `playsInline` (Boolean): Sets the [`playsinline`](https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#playsinline) YouTube player parameter to enable inline playback on iOS -- `forceHTML5` (Boolean): Forces loading the YouTube HTML5 player (default `true`) - -##Special Thank You -Thanks to Steve Heffernan for the amazing Video.js and to John Hurliman for the original version of the YouTube tech +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/LICENSE b/Youtube.js similarity index 72% rename from LICENSE rename to Youtube.js index fa0596cf..1182d1fb 100644 --- a/LICENSE +++ b/Youtube.js @@ -1,6 +1,6 @@ -The MIT License (MIT) +/* The MIT License (MIT) -Copyright (c) 2014 Benoit Tremblay +Copyright (c) 2014-2015 Benoit Tremblay Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,20 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. */ + +var Tech = videojs.getComponent('Tech'); + +var Youtube = videojs.extends(Tech, { + + constructor: function(options, ready) { + Tech.call(this, options, ready); + } + +}); + +Youtube.isSupported = function() { + return true; +}; + +videojs.registerComponent('Youtube', Youtube); diff --git a/Youtube.min.js b/Youtube.min.js new file mode 100644 index 00000000..4140c7b0 --- /dev/null +++ b/Youtube.min.js @@ -0,0 +1 @@ +console.log(videojs); \ No newline at end of file diff --git a/bower.json b/bower.json index 0fb43577..68649d3b 100644 --- a/bower.json +++ b/bower.json @@ -1,22 +1,22 @@ { "name": "videojs-youtube", + "version": "2.0.0-1", + "homepage": "https://github.com/eXon/videojs-youtube", + "authors": [ + "Benoit Tremblay " + ], "description": "YouTube playback technology for Video.js", - "version": "1.2.12", - "main": ["dist/vjs.youtube.js"], - "ignore": [ - "examples", - "sandbox", - "test", - ".editorconfig", - ".gitignore", - ".jshintrc", - ".travis.yml", - "bower.json", - "Gruntfile.js", - "package.json", - "README.md" + "main": "Youtube.min.js", + "license": "MIT", + "keywords": [ + "video", + "videojs", + "video.js", + "vjs", + "YouTube", + "tech" ], "dependencies": { - "video.js": "^4.11" + "video.js": "^5.0.0-rc.4" } } diff --git a/examples/autoplay.html b/examples/autoplay.html deleted file mode 100644 index 6954c2b9..00000000 --- a/examples/autoplay.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/examples/javascript.html b/examples/javascript.html deleted file mode 100644 index 948867f9..00000000 --- a/examples/javascript.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/loop.html b/examples/loop.html deleted file mode 100644 index 1b712014..00000000 --- a/examples/loop.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/examples/mixTechs.html b/examples/mixTechs.html deleted file mode 100644 index 290aaee1..00000000 --- a/examples/mixTechs.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - diff --git a/examples/muted.html b/examples/muted.html deleted file mode 100644 index d600743a..00000000 --- a/examples/muted.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/examples/quality.html b/examples/quality.html deleted file mode 100644 index 6ea72fa7..00000000 --- a/examples/quality.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/examples/simple.html b/examples/simple.html index 90cad27a..2497bcc5 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -1,12 +1,21 @@ - - - + - + + + + diff --git a/package.json b/package.json old mode 100644 new mode 100755 index 1af1aabc..1c9ede46 --- a/package.json +++ b/package.json @@ -1,32 +1,41 @@ { "name": "videojs-youtube", "description": "YouTube playback technology for Video.js", - "version": "1.2.12", + "version": "2.0.0-1", "author": "Benoit Tremblay", - "main": "dist/vjs.youtube.js", + "main": "Youtube.min.js", + "keywords": [ + "video", + "videojs", + "video.js", + "vjs", + "YouTube", + "tech" + ], "repository": { "type": "git", "url": "https://github.com/eXon/videojs-youtube.git" }, "peerDependencies": { - "video.js": "^4.12.5" + "video.js": "^5.0.0-rc.4" }, "dependencies": { - "video.js": "^4.12.5" - }, - "devDependencies": { - "grunt": "~0.4", - "grunt-cli": "~0.1.0", - "grunt-contrib-connect": "^0.7.1", - "grunt-contrib-jshint": "^0.10.0", - "grunt-contrib-uglify": "^0.4.1", - "grunt-mocha-phantomjs": "^0.5.0", - "grunt-protractor-runner": "^1.0.1", - "mocha": "^1.19.0", - "protractor": "^0.23.1", - "should": "^3.3.2" + "video.js": "^5.0.0-rc.4" }, "scripts": { - "test": "grunt test" + "minify": "uglifyjs Youtube.js -o Youtube.min.js", + "lint": "jshint .", + "test": "TODO", + "validate": "npm ls" + }, + "pre-commit": [ + "minify", + "lint" + ], + "devDependencies": { + "http-server": "^0.8.0", + "jshint": "^2.8.0", + "precommit-hook": "^3.0.0", + "uglify-js": "^2.4.23" } } diff --git a/sandbox/index.html b/sandbox/index.html deleted file mode 100644 index 7b5b9c67..00000000 --- a/sandbox/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/src/youtube.js b/src/youtube.js deleted file mode 100644 index 76fabbc1..00000000 --- a/src/youtube.js +++ /dev/null @@ -1,1097 +0,0 @@ -/* global videojs, YT */ -/* jshint browser: true */ - -(function() { - /** - * @fileoverview YouTube Media Controller - Wrapper for YouTube Media API - */ - - /** - * YouTube Media Controller - Wrapper for YouTube Media API - * @param {videojs.Player|Object} player - * @param {Object=} options - * @param {Function=} ready - * @constructor - */ - - function addEventListener(element, event, cb) { - if(!element.addEventListener) { - element.attachEvent(event, cb); - } else { - element.addEventListener(event, cb, true); - } - } - - videojs.Youtube = videojs.MediaTechController.extend({ - /** @constructor */ - init: function(player, options, ready) { - // Save this for internal usage - this.player_ = player; - - // No event is triggering this for YouTube - this['featuresProgressEvents'] = false; - this['featuresTimeupdateEvents'] = false; - // Enable rate changes - this['featuresPlaybackRate'] = true; - - this['featuresNativeTextTracks'] = true; - - videojs.MediaTechController.call(this, player, options, ready); - - this.isIos = /(iPad|iPhone|iPod)/g.test( navigator.userAgent ); - this.isAndroid = /(Android)/g.test( navigator.userAgent ); - //used to prevent play events on IOS7 and Android > 4.2 until the user has clicked the player - this.playVideoIsAllowed = !(this.isIos || this.isAndroid); - - // autoplay is disabled for mobile - if (this.isIos || this.isAndroid) { - this.player_.options()['autoplay'] = false; - } - - // Copy the JavaScript options if they exists - if(typeof options['source'] !== 'undefined') { - for(var key in options['source']) { - if(options['source'].hasOwnProperty(key)) { - player.options()[key] = options['source'][key]; - } - } - } - - this.player_.options()['playbackRates'] = []; - - this.userQuality = videojs.Youtube.convertQualityName(player.options()['quality']); - - this.playerEl_ = player.el(); - this.playerEl_.className += ' vjs-youtube'; - - // Create the Quality button - this.qualityButton = document.createElement('div'); - this.qualityButton.setAttribute('class', 'vjs-quality-button vjs-menu-button vjs-control'); - this.qualityButton.setAttribute('tabindex', 0); - - var qualityContent = document.createElement('div'); - qualityContent.setAttribute('class', 'vjs-control-content'); - this.qualityButton.appendChild(qualityContent); - - this.qualityTitle = document.createElement('span'); - this.qualityTitle.setAttribute('class', 'vjs-control-text'); - qualityContent.appendChild(this.qualityTitle); - - if(player.options()['quality'] !== 'undefined') { - setInnerText(this.qualityTitle, player.options()['quality'] || 'auto'); - } - - var qualityMenu = document.createElement('div'); - qualityMenu.setAttribute('class', 'vjs-menu'); - qualityContent.appendChild(qualityMenu); - - this.qualityMenuContent = document.createElement('ul'); - this.qualityMenuContent.setAttribute('class', 'vjs-menu-content'); - qualityMenu.appendChild(this.qualityMenuContent); - - this.id_ = this.player_.id() + '_youtube_api'; - - this.el_ = videojs.Component.prototype.createEl('iframe', { - id: this.id_, - className: 'vjs-tech', - scrolling: 'no', - marginWidth: 0, - marginHeight: 0, - frameBorder: 0 - }); - - this.el_.setAttribute('allowFullScreen', ''); - - this.playerEl_.insertBefore(this.el_, this.playerEl_.firstChild); - - if(/MSIE (\d+\.\d+);/.test(navigator.userAgent)) { - var ieVersion = Number(RegExp.$1); - this.addIframeBlocker(ieVersion); - } else if(!/(iPad|iPhone|iPod|Android)/g.test(navigator.userAgent)) { - // the pointer-events: none block the mobile player - this.el_.className += ' onDesktop'; - this.addIframeBlocker(); - } - - this.parseSrc(player.options()['src']); - - this.playOnReady = this.player_.options()['autoplay'] && this.playVideoIsAllowed; - this.forceHTML5 = !!( - typeof this.player_.options()['forceHTML5'] === 'undefined' || - this.player_.options()['forceHTML5'] === true - ); - - this.updateIframeSrc(); - - var self = this; - - player.ready(function() { - if (self.player_.options()['controls']) { - var controlBar = self.playerEl_.querySelectorAll('.vjs-control-bar')[0]; - if (controlBar) { - controlBar.appendChild(self.qualityButton); - } - } - - if(self.playOnReady && !self.player_.options()['ytcontrols']) { - if(typeof self.player_.loadingSpinner !== 'undefined') { - self.player_.loadingSpinner.show(); - } - if(typeof self.player_.bigPlayButton !== 'undefined') { - self.player_.bigPlayButton.hide(); - } - } - - player.trigger('loadstart'); - }); - - this.on('dispose', function() { - if(this.ytplayer) { - this.ytplayer.destroy(); - } - - if(!this.player_.options()['ytcontrols']) { - this.player_.off('waiting', this.bindedWaiting); - } - - // Remove the poster - this.playerEl_.querySelectorAll('.vjs-poster')[0].style.backgroundImage = 'none'; - - // If still connected to the DOM, remove it. - if(this.el_.parentNode) { - this.el_.parentNode.removeChild(this.el_); - } - - // Get rid of the created DOM elements - if (this.qualityButton.parentNode) { - this.qualityButton.parentNode.removeChild(this.qualityButton); - } - - if(typeof this.player_.loadingSpinner !== 'undefined') { - this.player_.loadingSpinner.hide(); - } - if(typeof this.player_.bigPlayButton !== 'undefined') { - this.player_.bigPlayButton.hide(); - } - - if(this.iframeblocker) { - this.playerEl_.removeChild(this.iframeblocker); - } - }); - } - }); - - // Tries to get the highest resolution thumbnail available for the video - videojs.Youtube.prototype.loadThumbnailUrl = function(id, callback){ - - var uri = 'https://img.youtube.com/vi/' + id + '/maxresdefault.jpg'; - var fallback = 'https://img.youtube.com/vi/' + id + '/0.jpg'; - - try{ - var image = new Image(); - image.onload = function(){ - // Onload may still be called if YouTube returns the 120x90 error thumbnail - if('naturalHeight' in this){ - if(this.naturalHeight <= 90 || this.naturalWidth <= 120) { - this.onerror(); - return; - } - } else if(this.height <= 90 || this.width <= 120) { - this.onerror(); - return; - } - - callback(uri); - }; - image.onerror = function(){ - callback(fallback); - }; - image.src = uri; - } - catch(e){ callback(fallback); } - }; - - videojs.Youtube.prototype.updateIframeSrc = function() { - var fullscreenControls = ( - typeof this.player_.options()['ytFullScreenControls'] !== 'undefined' && - !this.player_.options()['ytFullScreenControls'] - ) ? 0 : 1; - - var params = { - enablejsapi: 1, - /*jshint -W106 */ - iv_load_policy: 3, - /*jshint +W106 */ - playerapiid: this.id(), - disablekb: 1, - wmode: 'transparent', - controls: (this.player_.options()['ytcontrols']) ? 1 : 0, - fs: fullscreenControls, - html5: (this.player_.options()['forceHTML5']) ? 1 : null, - playsinline: (this.player_.options()['playsInline']) ? 1 : 0, - showinfo: 0, - rel: 0, - autoplay: (this.playOnReady) ? 1 : 0, - loop: (this.player_.options()['loop']) ? 1 : 0, - list: this.playlistId, - vq: this.userQuality, - origin: window.location.protocol + '//' + window.location.host - }; - - var isLocalProtocol = window.location.protocol === 'file:' || window.location.protocol === 'app:'; - - // When running with no Web server, we can't specify the origin or it will break the YouTube API messages - if(isLocalProtocol) { - delete params.origin; - } - - // Delete unset properties - for(var prop in params) { - if(params.hasOwnProperty(prop) && - ( typeof params[ prop ] === 'undefined' || params[ prop ] === null ) - ) { - delete params[ prop ]; - } - } - var self = this; - - if(!this.videoId && !this.playlistId) { - this.el_.src = 'about:blank'; - setTimeout(function() { - self.triggerReady(); - }, 500); - } else { - this.el_.src = 'https://www.youtube.com/embed/' + - (this.videoId || 'videoseries') + '?' + videojs.Youtube.makeQueryString(params); - - if(this.player_.options()['ytcontrols']) { - // Disable the video.js controls if we use the YouTube controls - this.player_.controls(false); - } else if(this.videoId && (typeof this.player_.poster() === 'undefined' || this.player_.poster().length === 0)) { - // Wait here because the tech is still null in constructor - setTimeout(function() { - self.loadThumbnailUrl(self.videoId, function(url){ - self.player_.poster(url); - }); - }, 100); - } - - this.bindedWaiting = function() { - self.onWaiting(); - }; - - this.player_.on('waiting', this.bindedWaiting); - - if(videojs.Youtube.apiReady) { - this.loadYoutube(); - } else { - // Add to the queue because the YouTube API is not ready - videojs.Youtube.loadingQueue.push(this); - - // Load the YouTube API if it is the first YouTube video - if(!videojs.Youtube.apiLoading) { - var tag = document.createElement('script'); - tag.onerror = function(e) { - self.onError(e); - }; - tag.src = 'https://www.youtube.com/iframe_api'; - var firstScriptTag = document.getElementsByTagName('script')[0]; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - videojs.Youtube.apiLoading = true; - } - } - } - }; - - videojs.Youtube.prototype.onWaiting = function(/*e*/) { - // Make sure to hide the play button while the spinner is there - if(typeof this.player_.bigPlayButton !== 'undefined') { - this.player_.bigPlayButton.hide(); - } - }; - - videojs.Youtube.prototype.addIframeBlocker = function(ieVersion) { - this.iframeblocker = videojs.Component.prototype.createEl('div'); - - this.iframeblocker.className = 'iframeblocker'; - - this.iframeblocker.style.position = 'absolute'; - this.iframeblocker.style.left = 0; - this.iframeblocker.style.right = 0; - this.iframeblocker.style.top = 0; - this.iframeblocker.style.bottom = 0; - - // Odd quirk for IE8 (doesn't support rgba) - if(ieVersion && ieVersion < 9) { - this.iframeblocker.style.opacity = 0.01; - } else { - this.iframeblocker.style.background = 'rgba(255, 255, 255, 0.01)'; - } - - var self = this; - addEventListener(this.iframeblocker, 'mousemove', function(e) { - if(!self.player_.userActive()) { - self.player_.userActive(true); - } - - e.stopPropagation(); - e.preventDefault(); - }); - - addEventListener(this.iframeblocker, 'click', function(/*e*/) { - if(self.paused()) { - self.play(); - } else { - self.pause(); - } - }); - - this.playerEl_.insertBefore(this.iframeblocker, this.el_.nextSibling); - }; - - videojs.Youtube.prototype.parseSrc = function(src) { - this.srcVal = src; - - if(src) { - // Regex to parse the video ID - var regId = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; - var match = src.match(regId); - - if(match && match[2].length === 11) { - this.videoId = match[2]; - } else { - this.videoId = null; - } - - // Regex to parse the playlist ID - var regPlaylist = /[?&]list=([^#\&\?]+)/; - match = src.match(regPlaylist); - - if(match !== null && match.length > 1) { - this.playlistId = match[1]; - } else { - // Make sure their is no playlist - if(this.playlistId) { - delete this.playlistId; - } - } - - // Parse video quality option - var regVideoQuality = /[?&]vq=([^#\&\?]+)/; - match = src.match(regVideoQuality); - - if(match !== null && match.length > 1) { - this.userQuality = match[1]; - videojs.Youtube.appendQualityLabel(this.qualityTitle, this.userQuality); - } - } - }; - - videojs.Youtube.prototype.src = function(src) { - if(typeof src !== 'undefined') { - this.parseSrc(src); - - if(this.el_.src === 'about:blank') { - this.updateIframeSrc(); - return; - } - - delete this.defaultQuality; - - if(this.videoId !== null) { - if(this.player_.options()['autoplay'] && this.playVideoIsAllowed) { - this.ytplayer.loadVideoById({ - videoId: this.videoId, - suggestedQuality: this.userQuality - }); - } else { - this.ytplayer.cueVideoById({ - videoId: this.videoId, - suggestedQuality: this.userQuality - }); - } - - var self = this; - this.loadThumbnailUrl(this.videoId, function(url){ - // Update the poster - self.playerEl_.querySelectorAll('.vjs-poster')[0].style.backgroundImage = - 'url(' + url + ')'; - self.player_.poster(url); - }); - } - /* else Invalid URL */ - } - - return this.srcVal; - }; - - videojs.Youtube.prototype.load = function() { - }; - - videojs.Youtube.prototype.play = function() { - if(this.videoId !== null) { - - // Make sure to not display the spinner for mobile - if(!this.player_.options()['ytcontrols']) { - // Display the spinner until the video is playing by YouTube - this.player_.trigger('waiting'); - } - - if(this.isReady_) { - // Sync the player volume with YouTube - this.ytplayer.setVolume(this.player_.volume() * 100); - - if(this.volumeVal > 0) { - this.ytplayer.unMute(); - } else { - this.ytplayer.mute(); - } - - if(this.playVideoIsAllowed) { - this.ytplayer.playVideo(); - } - } else { - this.playOnReady = true; - } - } - }; - - videojs.Youtube.prototype.pause = function() { - if(this.ytplayer) { - this.ytplayer.pauseVideo(); - } - }; - videojs.Youtube.prototype.paused = function() { - return (this.ytplayer) ? - (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) - : true; - }; - videojs.Youtube.prototype.currentTime = function() { - return (this.ytplayer && this.ytplayer.getCurrentTime) ? this.ytplayer.getCurrentTime() : 0; - }; - videojs.Youtube.prototype.setCurrentTime = function(seconds) { - if (this.lastState === YT.PlayerState.PAUSED) { - this.timeBeforeSeek = this.currentTime(); - } - - this.ytplayer.seekTo(seconds, true); - this.player_.trigger('timeupdate'); - this.player_.trigger('seeking'); - this.isSeeking = true; - - // A seek event during pause does not return an event to trigger a seeked event, - // so run an interval timer to look for the currentTime to change - if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { - this.checkSeekedInPauseInterval = setInterval( videojs.bind(this, function() { - if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { - // If something changed while we were waiting for the currentTime to change, - // clear the interval timer - clearInterval(this.checkSeekedInPauseInterval); - } else if (this.currentTime() !== this.timeBeforeSeek) { - this.player_.trigger('timeupdate'); - this.player_.trigger('seeked'); - this.isSeeking = false; - clearInterval(this.checkSeekedInPauseInterval); - } - }), 250); - } - }; - - videojs.Youtube.prototype.playbackRate = function() { - return (this.ytplayer && this.ytplayer.getPlaybackRate) ? this.ytplayer.getPlaybackRate() : 1.0; - }; - - videojs.Youtube.prototype.setPlaybackRate = function(suggestedRate) { - if (this.ytplayer && this.ytplayer.setPlaybackRate) { - this.ytplayer.setPlaybackRate(suggestedRate); - var self = this; - setTimeout(function () { - self.player_.trigger('ratechange'); - }, 100); - } - }; - - videojs.Youtube.prototype.duration = function() { - return (this.ytplayer && this.ytplayer.getDuration) ? this.ytplayer.getDuration() : 0; - }; - - videojs.Youtube.prototype.currentSrc = function() { - return this.srcVal; - }; - - videojs.Youtube.prototype.ended = function() { - return (this.ytplayer) ? (this.lastState === YT.PlayerState.ENDED) : false; - }; - - videojs.Youtube.prototype.volume = function() { - if(this.ytplayer && isNaN(this.volumeVal)) { - this.volumeVal = this.ytplayer.getVolume() / 100.0; - this.volumeVal = (isNaN(this.volumeVal)) ? 1 : this.volumeVal; - this.player_.volume(this.volumeVal); - } - - return this.volumeVal; - }; - - videojs.Youtube.prototype.setVolume = function(percentAsDecimal) { - if(typeof(percentAsDecimal) !== 'undefined' && percentAsDecimal !== this.volumeVal) { - this.ytplayer.setVolume(percentAsDecimal * 100.0); - this.volumeVal = percentAsDecimal; - this.player_.trigger('volumechange'); - } - }; - - videojs.Youtube.prototype.muted = function() { - return this.mutedVal; - }; - videojs.Youtube.prototype.setMuted = function(muted) { - if(muted) { - this.storedVolume = this.volumeVal; - this.ytplayer.mute(); - this.player_.volume(0); - } else { - this.ytplayer.unMute(); - this.player_.volume(this.storedVolume); - } - - this.mutedVal = muted; - - this.player_.trigger('volumechange'); - }; - - videojs.Youtube.prototype.buffered = function() { - if(this.ytplayer && this.ytplayer.getVideoBytesLoaded) { - var loadedBytes = this.ytplayer.getVideoBytesLoaded(); - var totalBytes = this.ytplayer.getVideoBytesTotal(); - if(!loadedBytes || !totalBytes) { - return 0; - } - - var duration = this.ytplayer.getDuration(); - var secondsBuffered = (loadedBytes / totalBytes) * duration; - var secondsOffset = (this.ytplayer.getVideoStartBytes() / totalBytes) * duration; - - return videojs.createTimeRange(secondsOffset, secondsOffset + secondsBuffered); - } else { - return videojs.createTimeRange(0, 0); - } - }; - - videojs.Youtube.prototype.supportsFullScreen = function() { - if (typeof this.el_.webkitEnterFullScreen === 'function') { - - // Seems to be broken in Chromium/Chrome && Safari in Leopard - if (/Android/.test(videojs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(videojs.USER_AGENT)) { - return true; - } - } - return false; - }; - - // YouTube is supported on all platforms - videojs.Youtube.isSupported = function() { - return true; - }; - - // You can use video/youtube as a media in your HTML5 video to specify the source - videojs.Youtube.canPlaySource = function(srcObj) { - return (srcObj.type === 'video/youtube'); - }; - - // Always can control the volume - videojs.Youtube.canControlVolume = function() { - return true; - }; - - ////////////////////////////// YouTube specific functions ////////////////////////////// - - // All videos created before YouTube API is loaded - videojs.Youtube.loadingQueue = []; - - // Create the YouTube player - videojs.Youtube.prototype.loadYoutube = function() { - var self = this; - this.ytplayer = new YT.Player(this.id_, { - events: { - onReady: function(e) { - e.target.vjsTech.onReady(); - self.player_.trigger('ratechange'); - }, - onStateChange: function(e) { - e.target.vjsTech.onStateChange(e.data); - }, - onPlaybackQualityChange: function(e) { - e.target.vjsTech.onPlaybackQualityChange(e.data); - }, - onError: function(e) { - e.target.vjsTech.onError(e.data); - } - } - }); - - this.ytplayer.vjsTech = this; - }; - - // Transform a JavaScript object into URL params - videojs.Youtube.makeQueryString = function(args) { - var array = ['modestbranding=1']; - for(var key in args) { - if(args.hasOwnProperty(key)) { - array.push(key + '=' + args[key]); - } - } - - return array.join('&'); - }; - - // Called when YouTube API is ready to be used - window.onYouTubeIframeAPIReady = function() { - var yt; - while((yt = videojs.Youtube.loadingQueue.shift())) { - yt.loadYoutube(); - } - videojs.Youtube.loadingQueue = []; - videojs.Youtube.apiReady = true; - }; - - videojs.Youtube.prototype.onReady = function() { - this.isReady_ = true; - this.triggerReady(); - - this.player_.options()['playbackRates'] = this.ytplayer.getAvailablePlaybackRates(); - this.player_.controlBar.playbackRateMenuButton.update(); - - this.player_.trigger('loadedmetadata'); - - // The duration is loaded so we might as well fire off the timeupdate and duration events - // this allows for the duration of the video (timeremaining) to be displayed if styled - // to show the control bar initially. This gives the user the ability to see how long the video - // is before clicking play - this.player_.trigger('durationchange'); - this.player_.trigger('timeupdate'); - - // Let the player take care of itself as soon as the YouTube is ready - // The loading spinner while waiting for the tech would be impossible otherwise - if (typeof this.player_.loadingSpinner !== 'undefined' && !this.isIos && !this.isAndroid) { - this.player_.loadingSpinner.hide(); - } - - if(this.player_.options()['muted']) { - this.setMuted(true); - } - - // Set the poster of the first video of the playlist if not specified - if (!this.videoId && this.playlistId) { - this.videoId = this.ytplayer.getPlaylist()[0]; - var self = this; - this.loadThumbnailUrl(this.videoId, function(url){ - self.player_.poster(url); - }); - } - - // Play ASAP if they clicked play before it's ready - if(this.playOnReady) { - this.playOnReady = false; - this.play(); - } - }; - - - videojs.Youtube.prototype.updateCaptions = function() { - this.ytplayer.loadModule('captions'); - this.ytplayer.loadModule('cc'); - - var options = this.ytplayer.getOptions(); - // The name of the captions module: 'captions' for html5 or 'cc' for flash - var cc = options.indexOf('captions') >= 0? 'captions' - : (options.indexOf('cc') >= 0? 'cc' : null); - - if(cc !== null && !this.tracked_){ - - var tracks = this.ytplayer.getOption(cc, 'tracklist'); - - if(tracks && tracks.length > 0){ - - var tt; - for(var i = 0; i < tracks.length; i++){ - tt = this.addTextTrack('captions', tracks[i].displayName, tracks[i].languageCode); - } - - var self = this; - this.textTracks().on('change', function(){ - var code = null; - for(var i = 0; i < this.length; i++){ - if(this[i].mode === 'showing'){ - code = this[i].language; - break; - } - } - - if(code !== null){ - self.ytplayer.setOption(cc, 'track', {'languageCode': code}); - } - else{ - self.ytplayer.setOption(cc, 'track', {}); - } - - }); - - this.tracked_ = true; - } - } - }; - - videojs.Youtube.prototype.updateQualities = function() { - - function setupEventListener(el) { - addEventListener(el, 'click', function() { - var quality = this.getAttribute('data-val'); - self.ytplayer.setPlaybackQuality(quality); - - self.userQuality = quality; - videojs.Youtube.appendQualityLabel(self.qualityTitle, quality); - - var selected = self.qualityMenuContent.querySelector('.vjs-selected'); - if(selected) { - videojs.Youtube.removeClass(selected, 'vjs-selected'); - } - - videojs.Youtube.addClass(this, 'vjs-selected'); - }); - } - - var qualities = this.ytplayer.getAvailableQualityLevels(); - var self = this; - - if(qualities.indexOf(this.userQuality) < 0) { - videojs.Youtube.appendQualityLabel(self.qualityTitle, this.defaultQuality); - } - - if(qualities.length === 0) { - this.qualityButton.style.display = 'none'; - } else { - this.qualityButton.style.display = ''; - - while(this.qualityMenuContent.hasChildNodes()) { - this.qualityMenuContent.removeChild(this.qualityMenuContent.lastChild); - } - - for(var i = 0; i < qualities.length; ++i) { - var el = document.createElement('li'); - el.setAttribute('class', 'vjs-menu-item'); - el.setAttribute('data-val', qualities[i]); - videojs.Youtube.appendQualityLabel(el, qualities[i]); - if(qualities[i] === this.quality) { - videojs.Youtube.addClass(el, 'vjs-selected'); - } - setupEventListener(el); - - this.qualityMenuContent.appendChild(el); - } - } - }; - - videojs.Youtube.prototype.onStateChange = function(state) { - if(state !== this.lastState) { - switch(state) { - case -1: - this.player_.trigger('durationchange'); - break; - - case YT.PlayerState.ENDED: - var stopPlaying = true; - - // Stop the playlist when it is starting over - if (this.playlistId && !this.player_.options()['loop']) { - stopPlaying = this.ytplayer.getPlaylistIndex() === 0; - } - - if (stopPlaying) { - // Replace YouTube play button by our own - if(!this.player_.options()['ytcontrols']) { - this.playerEl_.querySelectorAll('.vjs-poster')[0].style.display = 'block'; - if(typeof this.player_.bigPlayButton !== 'undefined') { - this.player_.bigPlayButton.show(); - } - } - - this.player_.trigger('pause'); - this.player_.trigger('ended'); - } - - break; - - case YT.PlayerState.PLAYING: - this.playerEl_.querySelectorAll('.vjs-poster')[0].style.display = 'none'; - - this.playVideoIsAllowed = true; - this.updateQualities(); - this.updateCaptions(); - this.player_.trigger('timeupdate'); - this.player_.trigger('durationchange'); - this.player_.trigger('playing'); - this.player_.trigger('play'); - - if (this.isSeeking) { - this.player_.trigger('seeked'); - this.isSeeking = false; - } - break; - - case YT.PlayerState.PAUSED: - this.player_.trigger('pause'); - break; - - case YT.PlayerState.BUFFERING: - this.player_.trigger('timeupdate'); - - // Make sure to not display the spinner for mobile - if(!this.player_.options()['ytcontrols']) { - this.player_.trigger('waiting'); - } - break; - - case YT.PlayerState.CUED: - break; - } - - this.lastState = state; - } - }; - - videojs.Youtube.convertQualityName = function(name) { - switch(name) { - case '144p': - return 'tiny'; - - case '240p': - return 'small'; - - case '360p': - return 'medium'; - - case '480p': - return 'large'; - - case '720p': - return 'hd720'; - - case '1080p': - return 'hd1080'; - - case '1440p': - return 'hd1440'; - - case '2160p': - return 'hd2160'; - } - - return 'auto'; - }; - - videojs.Youtube.parseQualityName = function(name) { - switch(name) { - case 'tiny': - return '144p'; - - case 'small': - return '240p'; - - case 'medium': - return '360p'; - - case 'large': - return '480p'; - - case 'hd720': - return '720p'; - - case 'hd1080': - return '1080p'; - - case 'hd1440': - return '1440p'; - - case 'hd2160': - return '2160p'; - } - - return 'auto'; - }; - - videojs.Youtube.appendQualityLabel = function(element, quality) { - setInnerText(element, videojs.Youtube.parseQualityName(quality)); - - var label = document.createElement('span'); - label.setAttribute('class', 'vjs-hd-label'); - - switch(quality) { - case 'hd720': - case 'hd1080': - case 'hd1440': - setInnerText(label, 'HD'); - element.appendChild(label); - break; - - case 'hd2160': - setInnerText(label, '4K'); - element.appendChild(label); - break; - } - }; - - videojs.Youtube.prototype.onPlaybackQualityChange = function(quality) { - if(typeof this.defaultQuality === 'undefined') { - this.defaultQuality = quality; - - if(typeof this.userQuality !== 'undefined') { - return; - } - } - - this.quality = quality; - videojs.Youtube.appendQualityLabel(this.qualityTitle, quality); - - switch(quality) { - case 'medium': - this.player_.videoWidth = 480; - this.player_.videoHeight = 360; - break; - - case 'large': - this.player_.videoWidth = 640; - this.player_.videoHeight = 480; - break; - - case 'hd720': - this.player_.videoWidth = 960; - this.player_.videoHeight = 720; - break; - - case 'hd1080': - this.player_.videoWidth = 1440; - this.player_.videoHeight = 1080; - break; - - case 'highres': - this.player_.videoWidth = 1920; - this.player_.videoHeight = 1080; - break; - - case 'small': - this.player_.videoWidth = 320; - this.player_.videoHeight = 240; - break; - - case 'tiny': - this.player_.videoWidth = 144; - this.player_.videoHeight = 108; - break; - - default: - this.player_.videoWidth = 0; - this.player_.videoHeight = 0; - break; - } - - this.player_.trigger('ratechange'); - }; - - videojs.Youtube.prototype.onError = function(error) { - this.player_.error(error); - }; - - /** - * Add a CSS class name to an element - * @param {Element} element Element to add class name to - * @param {String} classToAdd Classname to add - */ - videojs.Youtube.addClass = function(element, classToAdd) { - if((' ' + element.className + ' ').indexOf(' ' + classToAdd + ' ') === -1) { - element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd; - } - }; - - /** - * Remove a CSS class name from an element - * @param {Element} element Element to remove from class name - * @param {String} classToRemove Classname to remove - */ - videojs.Youtube.removeClass = function(element, classToRemove) { - var classNames, i; - - if(element.className.indexOf(classToRemove) === -1) { - return; - } - - classNames = element.className.split(' '); - - // no arr.indexOf in ie8, and we don't want to add a big shim - for(i = classNames.length - 1; i >= 0; i--) { - if(classNames[i] === classToRemove) { - classNames.splice(i, 1); - } - } - - element.className = classNames.join(' '); - }; - - // Cross-browsers support (IE8 wink wink) - function setInnerText(element, text) { - if(typeof element === 'undefined') { - return false; - } - - var textProperty = ('innerText' in element) ? 'innerText' : 'textContent'; - - try { - element[textProperty] = text; - } catch(anException) { - //IE<9 FIX - element.setAttribute('innerText', text); - } - } - - - // Stretch the YouTube poster - var style = document.createElement('style'); - var def = ' ' + - '.vjs-youtube .vjs-poster { background-size: 100%!important; }' + - '.vjs-youtube .vjs-poster, ' + - '.vjs-youtube .vjs-loading-spinner, ' + - '.vjs-youtube .vjs-big-play-button, .vjs-youtube .vjs-text-track-display{ pointer-events: none !important; }' + - '.vjs-youtube.vjs-user-active .iframeblocker { display: none; }' + - '.vjs-youtube.vjs-user-inactive .vjs-tech.onDesktop { pointer-events: none; }' + - '.vjs-quality-button > div:first-child > span:first-child { position:relative;top:7px }'; - - style.setAttribute('type', 'text/css'); - document.getElementsByTagName('head')[0].appendChild(style); - - if(style.styleSheet) { - style.styleSheet.cssText = def; - } else { - style.appendChild(document.createTextNode(def)); - } - - // IE8 fix for indexOf - if(!Array.prototype.indexOf) { - Array.prototype.indexOf = function(elt /*, from*/) { - var len = this.length >>> 0; // jshint ignore:line - - var from = Number(arguments[1]) || 0; - from = (from < 0) ? - Math.ceil(from) - : Math.floor(from); - if(from < 0) { - from += len; - } - - for(; from < len; from++) { - if(from in this && this[from] === elt) { - return from; - } - } - return -1; - }; - } -})(); diff --git a/test/functional/api_test.js b/test/functional/api_test.js deleted file mode 100644 index 0f63e6dd..00000000 --- a/test/functional/api_test.js +++ /dev/null @@ -1,159 +0,0 @@ -/* jshint node: true */ - -// TODO: Write a YouTube API mock to test without loading the real video, the result is too unpredictable -/*var should = require('should'); // jshint ignore:line -var url = require('url'); - - -describe('Test basic API commands for YouTube tech', function() { - // YouTube sometime hate us and sometime love us (pretty random) - // We can't rely on this test to know if it really crash so we ignore his feeling - it('should play and pause', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").play()'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").paused()').then(function(paused) { - paused.should.be.false; - }); - - browser.driver.executeScript('videojs("vid1").pause();'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").paused()').then(function(paused) { - paused.should.be.true; - }); - }); - - it('should change the source with regular URL', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").src("https://www.youtube.com/watch?v=y6Sxv-sUYtM");'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").src()').then(function(src) { - src.should.equal('https://www.youtube.com/watch?v=y6Sxv-sUYtM'); - }); - }); - - it('should change the source with Youtu.be URL', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").src("https://www.youtu.be/watch?v=y6Sxv-sUYtM");'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").src()').then(function(src) { - src.should.equal('https://www.youtu.be/watch?v=y6Sxv-sUYtM'); - }); - }); - - it('should change the source with Embeded URL', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").src("https://www.youtube.com/embed/y6Sxv-sUYtM");'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").src()').then(function(src) { - src.should.equal('https://www.youtube.com/embed/y6Sxv-sUYtM'); - }); - }); - - it('should change the source with playlist URL', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript( - 'videojs("vid1").src("http://www.youtube.com/watch?v=xjS6SftYQaQ&list=SPA60DCEB33156E51F");'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").src()').then(function(src) { - src.should.equal('http://www.youtube.com/watch?v=xjS6SftYQaQ&list=SPA60DCEB33156E51F'); - }); - }); - - it('should seek at a specific time', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").currentTime(10);'); - - browser.driver.executeScript('return videojs("vid1").currentTime()').then(function(currentTime) { - currentTime.should.equal(10); - }); - }); - - it('should know duration', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").play()'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").duration()').then(function(duration) { - duration.should.be.within(227, 228); - }); - }); - - it('should set the volume, mute and unmute', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").play()'); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").volume(0.5)'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").volume()').then(function(volume) { - volume.should.equal(0.5); - }); - - browser.driver.executeScript('return videojs("vid1").muted()').then(function(muted) { - muted.should.be.false; // jshint ignore:line - }); - - browser.driver.executeScript('videojs("vid1").play();videojs("vid1").muted(true);'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").muted()').then(function(muted) { - muted.should.be.true; // jshint ignore:line - }); - - browser.driver.executeScript('videojs("vid1").play();videojs("vid1").muted(false);'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").muted()').then(function(muted) { - muted.should.be.false; // jshint ignore:line - }); - }); - - it('should switch technologies', function() { - browser.driver.get(url.resolve(browser.baseUrl, '/sandbox/index.html')); - browser.driver.sleep(5000); - browser.driver.executeScript('videojs("vid1").play()'); - - browser.driver.executeScript('videojs("vid1").src({ src: "http://vjs.zencdn.net/v/oceans.mp4", ' + - 'type: "video/mp4" });'); - browser.driver.sleep(1000); - - browser.driver.executeScript('videojs("vid1").play()'); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").src({src:"https://www.youtu.be/watch?v=y6Sxv-sUYtM", ' + - 'type: "video/youtube" });'); - browser.driver.sleep(5000); - - browser.driver.executeScript('videojs("vid1").play()'); - browser.driver.sleep(5000); - - browser.driver.executeScript('return videojs("vid1").src()').then(function(src) { - src.should.equal('https://www.youtu.be/watch?v=y6Sxv-sUYtM'); - }); - }); -}); -*/ diff --git a/test/functional/local.config.js b/test/functional/local.config.js deleted file mode 100644 index 93952c59..00000000 --- a/test/functional/local.config.js +++ /dev/null @@ -1,20 +0,0 @@ -/* jshint node: true */ - -exports.config = { - seleniumAddress: 'http://localhost:4444/wd/hub', - - capabilities: { - 'browserName': 'chrome' - }, - - baseUrl: 'http://localhost:8080', - - specs: ['*_test.js'], - - framework: 'mocha', - - mochaOpts: { - reporter: 'spec', - slow: 3000 - } -}; \ No newline at end of file diff --git a/test/functional/saucelabs.config.js b/test/functional/saucelabs.config.js deleted file mode 100644 index aa771542..00000000 --- a/test/functional/saucelabs.config.js +++ /dev/null @@ -1,86 +0,0 @@ -/* jshint node: true */ - -exports.config = { - sauceUser: process.env.SAUCE_USERNAME, - sauceKey: process.env.SAUCE_ACCESS_KEY, - - multiCapabilities: [{ - name: 'FireFox on XP', - browserName: 'firefox', - version: '19', - platform: 'XP', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'FireFox on Mac', - browserName: 'firefox', - platform: 'OS X 10.6', - version: '19', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'Chrome on XP', - browserName: 'chrome', - platform: 'XP', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'Chrome on Linux', - browserName: 'chrome', - platform: 'linux', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'Chrome on Mac', - browserName: 'chrome', - platform: 'OS X 10.8', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'IE10 on Windows 8', - browserName: 'internet explorer', - platform: 'WIN8', - version: '10', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'IE9 on Visa', - browserName: 'internet explorer', - platform: 'VISTA', - version: '9', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'IE8 on XP', - browserName: 'internet explorer', - platform: 'XP', - version: '8', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'Safari on Mac', - browserName: 'safari', - platform: 'OS X 10.8', - version: '6', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }, { - name: 'Safari on Windows 7', - browserName: 'safari', - platform: 'Windows 7', - version: '5', - build: process.env.TRAVIS_BUILD_NUMBER, - 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER - }], - - baseUrl: 'http://localhost:8080', - - specs: ['*_test.js'], - - framework: 'mocha', - - mochaOpts: { - reporter: 'spec', - slow: 3000 - } -}; \ No newline at end of file diff --git a/test/unit/quality_test.js b/test/unit/quality_test.js deleted file mode 100644 index 383ab697..00000000 --- a/test/unit/quality_test.js +++ /dev/null @@ -1,19 +0,0 @@ -describe('Quality option', function() { - it('should be converted to YouTube format correctly', function() { - videojs.Youtube.convertQualityName('144p').should.equal('tiny'); - videojs.Youtube.convertQualityName('240p').should.equal('small'); - videojs.Youtube.convertQualityName('360p').should.equal('medium'); - videojs.Youtube.convertQualityName('480p').should.equal('large'); - videojs.Youtube.convertQualityName('720p').should.equal('hd720'); - videojs.Youtube.convertQualityName('1080p').should.equal('hd1080'); - }); - - it('should be converted back from YouTube format correctly', function() { - videojs.Youtube.parseQualityName('tiny').should.equal('144p'); - videojs.Youtube.parseQualityName('small').should.equal('240p'); - videojs.Youtube.parseQualityName('medium').should.equal('360p'); - videojs.Youtube.parseQualityName('large').should.equal('480p'); - videojs.Youtube.parseQualityName('hd720').should.equal('720p'); - videojs.Youtube.parseQualityName('hd1080').should.equal('1080p'); - }); -}); \ No newline at end of file diff --git a/test/unit/runner.html b/test/unit/runner.html deleted file mode 100644 index 35df75de..00000000 --- a/test/unit/runner.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - Unit Tests - - - - - - - -
- - - - - - - - - - \ No newline at end of file diff --git a/tests.js b/tests.js new file mode 100644 index 00000000..e69de29b From 1d0ba36eefdbac16ff9ef653c572292d1d9bd4c5 Mon Sep 17 00:00:00 2001 From: eXon Date: Tue, 7 Jul 2015 07:24:18 -0400 Subject: [PATCH 02/21] Adding minified version --- Youtube.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Youtube.min.js b/Youtube.min.js index 4140c7b0..32094d7e 100644 --- a/Youtube.min.js +++ b/Youtube.min.js @@ -1 +1 @@ -console.log(videojs); \ No newline at end of file +var Tech=videojs.getComponent("Tech");var Youtube=videojs.extends(Tech,{constructor:function(options,ready){Tech.call(this,options,ready)}});Youtube.isSupported=function(){return true};videojs.registerComponent("Youtube",Youtube); \ No newline at end of file From 3912718f5c88fe3017a8a1dd3d7711bf9a9663b6 Mon Sep 17 00:00:00 2001 From: eXon Date: Tue, 7 Jul 2015 08:23:03 -0400 Subject: [PATCH 03/21] Added karma unit tests + structure --- .gitignore | 2 ++ .jshintignore | 2 +- .jshintrc | 2 ++ .npmignore | 7 +++++++ Youtube.min.js | 1 - bower.json | 11 ++++++++++- examples/simple.html | 2 +- karma.conf.js | 12 ++++++++++++ package.json | 15 +++++++++------ Youtube.js => src/Youtube.js | 23 +++++++++++++---------- tests.js | 0 tests/api.specs.js | 7 +++++++ 12 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 .npmignore delete mode 100644 Youtube.min.js create mode 100644 karma.conf.js rename Youtube.js => src/Youtube.js (77%) delete mode 100644 tests.js create mode 100644 tests/api.specs.js diff --git a/.gitignore b/.gitignore index 93f13619..15f2a242 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules npm-debug.log + +dist diff --git a/.jshintignore b/.jshintignore index 74f71c5e..f06235c4 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,2 +1,2 @@ node_modules -*.min.js +dist diff --git a/.jshintrc b/.jshintrc index f81a6aaf..ff0972e7 100644 --- a/.jshintrc +++ b/.jshintrc @@ -17,6 +17,8 @@ "sub" : true, "quotmark" : "single", "browser" : true, + "jasmine" : true, + "node" : true, "predef": [ "videojs" ] diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..d45628c0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +.npmignore +.gitignore +.jshintrc +.jshintignore +src +tests +karma.conf.js diff --git a/Youtube.min.js b/Youtube.min.js deleted file mode 100644 index 32094d7e..00000000 --- a/Youtube.min.js +++ /dev/null @@ -1 +0,0 @@ -var Tech=videojs.getComponent("Tech");var Youtube=videojs.extends(Tech,{constructor:function(options,ready){Tech.call(this,options,ready)}});Youtube.isSupported=function(){return true};videojs.registerComponent("Youtube",Youtube); \ No newline at end of file diff --git a/bower.json b/bower.json index 68649d3b..f975dfe1 100644 --- a/bower.json +++ b/bower.json @@ -6,7 +6,7 @@ "Benoit Tremblay " ], "description": "YouTube playback technology for Video.js", - "main": "Youtube.min.js", + "main": "dist/Youtube.min.js", "license": "MIT", "keywords": [ "video", @@ -16,6 +16,15 @@ "YouTube", "tech" ], + "ignore": [ + ".npmignore", + ".gitignore", + ".jshintrc", + ".jshintignore", + "src", + "tests", + "karma.conf.js" + ], "dependencies": { "video.js": "^5.0.0-rc.4" } diff --git a/examples/simple.html b/examples/simple.html index 2497bcc5..2e345208 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -16,6 +16,6 @@ > - + diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000..bd2bb545 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,12 @@ +module.exports = function(config) { + config.set({ + basePath: '', + frameworks: ['jasmine'], + files: [ + 'node_modules/video.js/dist/video.js', + 'src/Youtube.js', + 'tests/**/*.specs.js' + ], + browsers: ['Chrome'] + }); +}; diff --git a/package.json b/package.json index 1c9ede46..760c01e0 100755 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "YouTube playback technology for Video.js", "version": "2.0.0-1", "author": "Benoit Tremblay", - "main": "Youtube.min.js", + "main": "src/Youtube.js", "keywords": [ "video", "videojs", @@ -23,18 +23,21 @@ "video.js": "^5.0.0-rc.4" }, "scripts": { - "minify": "uglifyjs Youtube.js -o Youtube.min.js", + "build": "mkdir -p dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js", "lint": "jshint .", - "test": "TODO", - "validate": "npm ls" + "test": "karma start --single-run" }, "pre-commit": [ - "minify", - "lint" + "lint", + "test" ], "devDependencies": { "http-server": "^0.8.0", + "jasmine-core": "^2.3.4", "jshint": "^2.8.0", + "karma": "^0.12.37", + "karma-chrome-launcher": "^0.2.0", + "karma-jasmine": "^0.3.6", "precommit-hook": "^3.0.0", "uglify-js": "^2.4.23" } diff --git a/Youtube.js b/src/Youtube.js similarity index 77% rename from Youtube.js rename to src/Youtube.js index 1182d1fb..c7abe78e 100644 --- a/Youtube.js +++ b/src/Youtube.js @@ -19,19 +19,22 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +(function() { + 'use strict'; -var Tech = videojs.getComponent('Tech'); + var Tech = videojs.getComponent('Tech'); -var Youtube = videojs.extends(Tech, { + var Youtube = videojs.extends(Tech, { - constructor: function(options, ready) { - Tech.call(this, options, ready); - } + constructor: function(options, ready) { + Tech.call(this, options, ready); + } -}); + }); -Youtube.isSupported = function() { - return true; -}; + Youtube.isSupported = function() { + return true; + }; -videojs.registerComponent('Youtube', Youtube); + videojs.registerComponent('Youtube', Youtube); +})(); diff --git a/tests.js b/tests.js deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/api.specs.js b/tests/api.specs.js new file mode 100644 index 00000000..97e1c843 --- /dev/null +++ b/tests/api.specs.js @@ -0,0 +1,7 @@ +var Youtube = videojs.getComponent('Youtube'); + +describe('YouTube static methods', function() { + it('should define isSupported', function() { + expect(typeof Youtube.isSupported).toBe('function'); + }); +}); From 92b89a412bb4ffb65b962239b8a5b531c48f308f Mon Sep 17 00:00:00 2001 From: eXon Date: Thu, 9 Jul 2015 07:21:54 -0400 Subject: [PATCH 04/21] Loading YouTube API + the video --- examples/simple.html | 7 ++--- src/Youtube.js | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/examples/simple.html b/examples/simple.html index 2e345208..ca9c6962 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -6,14 +6,15 @@ + data-setup='{ "techOrder": ["youtube"] }' + > + + diff --git a/src/Youtube.js b/src/Youtube.js index c7abe78e..68369ad3 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -28,6 +28,47 @@ THE SOFTWARE. */ constructor: function(options, ready) { Tech.call(this, options, ready); + }, + + createEl: function() { + var div = document.createElement('div'); + div.setAttribute('id', this.options_.techId); + div.setAttribute('style', 'width:100%;height:100%') + + if (Youtube.isApiReady) { + this.initYTPlayer(); + } else { + Youtube._apiReadyQueue.push(this); + } + + return div; + }, + + initYTPlayer: function() { + this.ytPlayer = new YT.Player(this.options_.techId, { + events: { + onReady: this.onPlayerReady.bind(this), + onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), + onStateChange: this.onPlayerStateChange.bind(this), + onError: this.onPlayerError.bind(this) + } + }); + }, + + onPlayerReady: function() { + this.ytPlayer.loadVideoById(this.options_.source.src); + }, + + onPlayerPlaybackQualityChange: function() { + + }, + + onPlayerStateChange: function() { + + }, + + onPlayerError: function() { + } }); @@ -36,5 +77,28 @@ THE SOFTWARE. */ return true; }; + Youtube.canPlaySource = function(e) { + return (e.type === 'video/youtube'); + }; + + function loadApi() { + var tag = document.createElement('script'); + tag.src = 'https://www.youtube.com/iframe_api'; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + }; + + Youtube._apiReadyQueue = []; + + window.onYouTubeIframeAPIReady = function() { + Youtube.isApiReady = true; + + for (var i = 0; i < Youtube._apiReadyQueue.length; ++i) { + Youtube._apiReadyQueue[i].initYTPlayer(); + } + }; + + loadApi(); + videojs.registerComponent('Youtube', Youtube); })(); From 111e8647d537516ec6cb974b5beff9bc9b29c6a6 Mon Sep 17 00:00:00 2001 From: eXon Date: Fri, 10 Jul 2015 19:09:47 -0400 Subject: [PATCH 05/21] First draft (waiting on VJS PR) --- .jshintrc | 3 +- examples/simple.html | 4 +- package.json | 4 +- src/Youtube.js | 337 ++++++++++++++++++++++++++++++++++++++-- tests/api.specs.js | 7 - tests/parseUrl.specs.js | 9 ++ 6 files changed, 342 insertions(+), 22 deletions(-) delete mode 100644 tests/api.specs.js create mode 100644 tests/parseUrl.specs.js diff --git a/.jshintrc b/.jshintrc index ff0972e7..1036ecd2 100644 --- a/.jshintrc +++ b/.jshintrc @@ -20,6 +20,7 @@ "jasmine" : true, "node" : true, "predef": [ - "videojs" + "videojs", + "YT" ] } diff --git a/examples/simple.html b/examples/simple.html index ca9c6962..a0d239ad 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -9,11 +9,11 @@ class="video-js vjs-default-skin" controls autoplay + loop preload="auto" width="640" height="264" - data-setup='{ "techOrder": ["youtube"] }' + data-setup='{ "techOrder": ["youtube"], "sources": [{ "type": "video/youtube", "src": "https://www.youtube.com/watch?v=xjS6SftYQaQ"}] }' > - diff --git a/package.json b/package.json index 760c01e0..5852739b 100755 --- a/package.json +++ b/package.json @@ -17,10 +17,10 @@ "url": "https://github.com/eXon/videojs-youtube.git" }, "peerDependencies": { - "video.js": "^5.0.0-rc.4" + "video.js": "^5.0.0-rc.12" }, "dependencies": { - "video.js": "^5.0.0-rc.4" + "video.js": "^5.0.0-rc.12" }, "scripts": { "build": "mkdir -p dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js", diff --git a/src/Youtube.js b/src/Youtube.js index 68369ad3..031eb3ec 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -28,24 +28,42 @@ THE SOFTWARE. */ constructor: function(options, ready) { Tech.call(this, options, ready); + this.setSrc(options.source); }, createEl: function() { var div = document.createElement('div'); div.setAttribute('id', this.options_.techId); - div.setAttribute('style', 'width:100%;height:100%') + div.setAttribute('style', 'width:100%;height:100%'); + + var divBlocker = document.createElement('div'); + divBlocker.setAttribute('class', 'vjs-iframe-blocker'); + divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); + + var divWrapper = document.createElement('div'); + divWrapper.setAttribute('style', 'width:100%;height:100%;position:relative'); + + divWrapper.appendChild(div); + divWrapper.appendChild(divBlocker); if (Youtube.isApiReady) { this.initYTPlayer(); } else { - Youtube._apiReadyQueue.push(this); + Youtube.apiReadyQueue.push(this); } - return div; + return divWrapper; }, initYTPlayer: function() { this.ytPlayer = new YT.Player(this.options_.techId, { + playerVars: { + controls: 0, + modestbranding: 1, + rel: 0, + showinfo: 0, + loop: this.options_.loop ? 1 : 0 + }, events: { onReady: this.onPlayerReady.bind(this), onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), @@ -56,19 +74,254 @@ THE SOFTWARE. */ }, onPlayerReady: function() { - this.ytPlayer.loadVideoById(this.options_.source.src); + this.triggerReady(); + + if (this.playOnReady) { + this.play(); + } }, onPlayerPlaybackQualityChange: function() { }, - onPlayerStateChange: function() { + onPlayerStateChange: function(e) { + var state = e.data; + + if (state === this.lastState) { + return; + } + + switch (state) { + case -1: + this.trigger('durationchange'); + 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.trigger('seeked'); + this.isSeeking = false; + } + break; + + case YT.PlayerState.PAUSED: + if (this.isSeeking) { + this.trigger('seeked'); + this.isSeeking = false; + this.ytPlayer.playVideo(); + } else { + this.trigger('pause'); + } + break; + + case YT.PlayerState.BUFFERING: + this.player_.trigger('timeupdate'); + this.player_.trigger('waiting'); + break; + } + + this.lastState = state; + }, + + onPlayerError: function(e) { + this.trigger(e); + }, + + src: function() { + return this.source; + }, + + poster: function() { + return this.poster; + }, + + setPoster: function(poster) { + this.poster = poster; + }, + + setSrc: function(source) { + if (!source || !source.src) { + return; + } + + this.source = source; + this.url = Youtube.parseUrl(source.src); + + if (!this.options_.poster) { + Youtube.loadThumbnailUrl(this.url.videoId, function(poster) { + this.setPoster(poster); + this.trigger('posterchange'); + }.bind(this)) + } + + if (this.options_.autoplay) { + if (this.isReady_) { + this.play(); + } else { + this.playOnReady = true; + } + } + }, + + play: function() { + if (!this.url || !this.url.videoId) { + return; + } + + if (this.isReady_) { + if (this.activeVideoId === this.url.videoId) { + this.ytPlayer.playVideo(); + } else { + this.ytPlayer.loadVideoById(this.url.videoId); + this.activeVideoId = this.url.videoId; + } + } else { + this.trigger('waiting'); + this.playOnReady = true; + } + }, + + pause: function() { + if (this.ytPlayer) { + this.ytPlayer.pauseVideo(); + } + }, + + paused: function() { + return (this.ytplayer) ? + (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) + : true + }, + + currentTime: function() { + return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0; + }, + + setCurrentTime: function(seconds) { + if (this.lastState === YT.PlayerState.PAUSED) { + this.timeBeforeSeek = this.currentTime(); + } + + this.timeBeforeSeek = this.currentTime(); + + this.ytPlayer.seekTo(seconds, true); + this.trigger('timeupdate'); + this.trigger('seeking'); + this.isSeeking = true; + + // A seek event during pause does not return an event to trigger a seeked event, + // so run an interval timer to look for the currentTime to change + if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { + this.checkSeekedInPauseInterval = setInterval(function() { + if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { + // If something changed while we were waiting for the currentTime to change, + // clear the interval timer + clearInterval(this.checkSeekedInPauseInterval); + } else if (this.currentTime() !== this.timeBeforeSeek) { + this.trigger('timeupdate'); + this.trigger('seeked'); + this.isSeeking = false; + clearInterval(this.checkSeekedInPauseInterval); + } + + this.play(); + }.bind(this), 250); + } + }, + + playbackRate: function() { + return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1; + }, + + setPlaybackRate: function(suggestedRate) { + if (!this.ytPlayer) { + return; + } + + this.ytPlayer.setPlaybackRate(suggestedRate); + this.trigger('ratechange'); + }, + + duration: function() { + return this.ytPlayer ? this.ytPlayer.getDuration() : 0; + }, + + currentSrc: function() { + return this.source; + }, + + ended: function() { + return this.ytPlayer ? (this.lastState === YT.PlayerState.ENDED) : false; + }, + volume: function() { + return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1; }, - onPlayerError: function() { + setVolume: function(percentAsDecimal) { + if (!this.ytPlayer) { + return; + } + + this.ytPlayer.setVolume(percentAsDecimal * 100.0); + }, + + muted: function() { + return this.ytPlayer ? this.ytPlayer.isMuted() : false; + }, + + setMuted: function(mute) { + if (!this.ytPlayer) { + return; + } + + if (mute) { + this.ytPlayer.mute(); + } else { + this.ytPlayer.unMute(); + } + }, + + buffered: function() { + if(!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) { + return { + length: 0, + start: function() { + throw new Error('This TimeRanges object is empty'); + }, + end: function() { + throw new Error('This TimeRanges object is empty'); + } + }; + } + var end = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration(); + + return { + length: 1, + start: function() { return 0; }, + end: function() { return end; } + }; + }, + + supportsFullScreen: function() { + if (typeof this.el_.webkitEnterFullScreen === 'function') { + // Seems to be broken in Chromium/Chrome && Safari in Leopard + if (/Android/.test(videojs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(videojs.USER_AGENT)) { + return true; + } + } + + return false; } }); @@ -81,24 +334,88 @@ THE SOFTWARE. */ return (e.type === '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]; + } + + return result; + }; + + // Tries to get the highest resolution thumbnail available for the video + Youtube.loadThumbnailUrl = function(id, callback){ + + var uri = 'https://img.youtube.com/vi/' + id + '/maxresdefault.jpg'; + var fallback = 'https://img.youtube.com/vi/' + id + '/0.jpg'; + + try { + var image = new Image(); + image.onload = function(){ + // Onload may still be called if YouTube returns the 120x90 error thumbnail + if('naturalHeight' in this){ + if(this.naturalHeight <= 90 || this.naturalWidth <= 120) { + this.onerror(); + return; + } + } else if(this.height <= 90 || this.width <= 120) { + this.onerror(); + return; + } + + callback(uri); + }; + image.onerror = function(){ + callback(fallback); + }; + image.src = uri; + } + catch(e){ callback(fallback); } + }; + function loadApi() { var tag = document.createElement('script'); tag.src = 'https://www.youtube.com/iframe_api'; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - }; + } + + function injectCss() { + var css = '.vjs-iframe-blocker { display: none; }' + + '.vjs-user-inactive .vjs-iframe-blocker { display: block; }'; + + var head = document.head || document.getElementsByTagName('head')[0]; + + var style = document.createElement('style'); + style.type = 'text/css'; + + if (style.styleSheet){ + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + + head.appendChild(style); + } - Youtube._apiReadyQueue = []; + Youtube.apiReadyQueue = []; window.onYouTubeIframeAPIReady = function() { Youtube.isApiReady = true; - for (var i = 0; i < Youtube._apiReadyQueue.length; ++i) { - Youtube._apiReadyQueue[i].initYTPlayer(); + for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { + Youtube.apiReadyQueue[i].initYTPlayer(); } }; loadApi(); + injectCss(); videojs.registerComponent('Youtube', Youtube); })(); diff --git a/tests/api.specs.js b/tests/api.specs.js deleted file mode 100644 index 97e1c843..00000000 --- a/tests/api.specs.js +++ /dev/null @@ -1,7 +0,0 @@ -var Youtube = videojs.getComponent('Youtube'); - -describe('YouTube static methods', function() { - it('should define isSupported', function() { - expect(typeof Youtube.isSupported).toBe('function'); - }); -}); diff --git a/tests/parseUrl.specs.js b/tests/parseUrl.specs.js new file mode 100644 index 00000000..f052eebc --- /dev/null +++ b/tests/parseUrl.specs.js @@ -0,0 +1,9 @@ +var Youtube = videojs.getComponent('Youtube'); + +describe('parseUrl', function() { + it('should read the correct video ID', function() { + expect(Youtube.parseUrl('https://www.youtube.com/watch?v=OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); + expect(Youtube.parseUrl('https://www.youtube.com/embed/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); + expect(Youtube.parseUrl('https://youtu.be/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); + }) +}); From 7d4f33952e01eeca1780f547f18d58feeecd765d Mon Sep 17 00:00:00 2001 From: eXon Date: Fri, 10 Jul 2015 20:35:17 -0400 Subject: [PATCH 06/21] Fixes for lint --- src/Youtube.js | 4 ++-- tests/parseUrl.specs.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Youtube.js b/src/Youtube.js index 031eb3ec..1f569b8a 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -160,7 +160,7 @@ THE SOFTWARE. */ Youtube.loadThumbnailUrl(this.url.videoId, function(poster) { this.setPoster(poster); this.trigger('posterchange'); - }.bind(this)) + }.bind(this)); } if (this.options_.autoplay) { @@ -199,7 +199,7 @@ THE SOFTWARE. */ paused: function() { return (this.ytplayer) ? (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) - : true + : true; }, currentTime: function() { diff --git a/tests/parseUrl.specs.js b/tests/parseUrl.specs.js index f052eebc..7f27c523 100644 --- a/tests/parseUrl.specs.js +++ b/tests/parseUrl.specs.js @@ -5,5 +5,5 @@ describe('parseUrl', function() { expect(Youtube.parseUrl('https://www.youtube.com/watch?v=OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); expect(Youtube.parseUrl('https://www.youtube.com/embed/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); expect(Youtube.parseUrl('https://youtu.be/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); - }) + }); }); From 053369e6c07fd5de3a0c16373f0bd608f1d46a35 Mon Sep 17 00:00:00 2001 From: eXon Date: Sat, 11 Jul 2015 10:28:49 -0400 Subject: [PATCH 07/21] Disable autoplay on mobile --- examples/simple.html | 6 +++--- package.json | 5 +++-- src/Youtube.js | 23 ++++++++++++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/examples/simple.html b/examples/simple.html index a0d239ad..b479388d 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -2,6 +2,9 @@ + + + - - - diff --git a/package.json b/package.json index 5852739b..53621064 100755 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "scripts": { "build": "mkdir -p dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js", "lint": "jshint .", - "test": "karma start --single-run" + "test": "karma start --single-run", + "validate": "npm ls" }, "pre-commit": [ "lint", @@ -41,4 +42,4 @@ "precommit-hook": "^3.0.0", "uglify-js": "^2.4.23" } -} +} \ No newline at end of file diff --git a/src/Youtube.js b/src/Youtube.js index 1f569b8a..98999532 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -36,15 +36,22 @@ THE SOFTWARE. */ div.setAttribute('id', this.options_.techId); div.setAttribute('style', 'width:100%;height:100%'); - var divBlocker = document.createElement('div'); - divBlocker.setAttribute('class', 'vjs-iframe-blocker'); - divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); - var divWrapper = document.createElement('div'); divWrapper.setAttribute('style', 'width:100%;height:100%;position:relative'); - divWrapper.appendChild(div); - divWrapper.appendChild(divBlocker); + + if (!_isOnMobile) { + var divBlocker = document.createElement('div'); + divBlocker.setAttribute('class', 'vjs-iframe-blocker'); + divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); + + // In case the blocker is still there and we want to pause + divBlocker.onclick = function() { + this.pause(); + }.bind(this); + + divWrapper.appendChild(divBlocker); + } if (Youtube.isApiReady) { this.initYTPlayer(); @@ -163,7 +170,7 @@ THE SOFTWARE. */ }.bind(this)); } - if (this.options_.autoplay) { + if (this.options_.autoplay && !_isOnMobile) { if (this.isReady_) { this.play(); } else { @@ -334,6 +341,8 @@ THE SOFTWARE. */ return (e.type === 'video/youtube'); }; + var _isOnMobile = /(iPad|iPhone|iPod|Android)/g.test(navigator.userAgent); + Youtube.parseUrl = function(url) { var result = { videoId: null From a446bcee0f4dc9f7230f22a0883b3d4b1945ce79 Mon Sep 17 00:00:00 2001 From: eXon Date: Sat, 11 Jul 2015 12:55:51 -0400 Subject: [PATCH 08/21] Added playlist + examples + manage errors --- examples/global-parameters.html | 32 +++++++ examples/simple.html | 10 +-- examples/youtube-controls.html | 18 ++++ examples/youtube-list.html | 19 +++++ examples/youtube-playlist.html | 19 +++++ src/Youtube.js | 147 ++++++++++++++++++++++++++++---- 6 files changed, 222 insertions(+), 23 deletions(-) create mode 100644 examples/global-parameters.html create mode 100644 examples/youtube-controls.html create mode 100644 examples/youtube-list.html create mode 100644 examples/youtube-playlist.html diff --git a/examples/global-parameters.html b/examples/global-parameters.html new file mode 100644 index 00000000..aea7b92c --- /dev/null +++ b/examples/global-parameters.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/examples/simple.html b/examples/simple.html index b479388d..1bf70afe 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -2,9 +2,6 @@ - - - + + + diff --git a/examples/youtube-controls.html b/examples/youtube-controls.html new file mode 100644 index 00000000..5d7c3c1e --- /dev/null +++ b/examples/youtube-controls.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/examples/youtube-list.html b/examples/youtube-list.html new file mode 100644 index 00000000..f67576dd --- /dev/null +++ b/examples/youtube-list.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/examples/youtube-playlist.html b/examples/youtube-playlist.html new file mode 100644 index 00000000..755330d0 --- /dev/null +++ b/examples/youtube-playlist.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/src/Youtube.js b/src/Youtube.js index 98999532..854f5691 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -28,7 +28,7 @@ THE SOFTWARE. */ constructor: function(options, ready) { Tech.call(this, options, ready); - this.setSrc(options.source); + this.setSrc(this.options_.source, true); }, createEl: function() { @@ -40,7 +40,7 @@ THE SOFTWARE. */ divWrapper.setAttribute('style', 'width:100%;height:100%;position:relative'); divWrapper.appendChild(div); - if (!_isOnMobile) { + 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%'); @@ -63,14 +63,99 @@ THE SOFTWARE. */ }, initYTPlayer: function() { + var playerVars = { + controls: 0, + modestbranding: 1, + rel: 0, + showinfo: 0, + loop: this.options_.loop ? 1 : 0 + }; + + // Set the YouTube player on the same language than video.js + if (typeof this.options_.language !== 'undefined') { + playerVars.hl = this.options_.language.substr(0, 2); + } + + // Let the user set any YouTube parameter + // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters + // To use YouTube controls, you must use ytControls instead + // To use the loop or autoplay, use the video.js settings + + 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_.end !== 'undefined') { + playerVars.end = this.options_.end; + } + + if (typeof this.options_.fs !== 'undefined') { + playerVars.fs = this.options_.fs; + } + + if (typeof this.options_.end !== 'undefined') { + playerVars.end = this.options_.end; + } + + if (typeof this.options_.hl !== 'undefined') { + playerVars.hl = this.options_.hl; + } + + 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; + } + + 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 (typeof this.options_.start !== 'undefined') { + playerVars.start = this.options_.start; + } + + if (typeof this.options_.theme !== 'undefined') { + playerVars.theme = this.options_.theme; + } + this.ytPlayer = new YT.Player(this.options_.techId, { - playerVars: { - controls: 0, - modestbranding: 1, - rel: 0, - showinfo: 0, - loop: this.options_.loop ? 1 : 0 - }, + videoId: this.url.videoId, + playerVars: playerVars, events: { onReady: this.onPlayerReady.bind(this), onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), @@ -140,7 +225,32 @@ THE SOFTWARE. */ }, onPlayerError: function(e) { - this.trigger(e); + this.errorNumber = e.data; + this.trigger('error'); + + // Get rid of the iframe, we want to display our own error message + this.el_.removeChild( + this.el_.children[0] + ); + }, + + error: function() { + switch (this.errorNumber) { + case 2: + return { code: 'Unable to find the video' }; + + case 5: + return { code: 'Error while trying to play the video' }; + + case 100: + return { code: 'Unable to find the video' }; + + case 101: + case 150: + return { code: 'Playback on other Websites has been disabled by the video owner.' }; + } + + return { code: 'YouTube unknown error (' + this.errorNumber + ')' }; }, src: function() { @@ -155,7 +265,7 @@ THE SOFTWARE. */ this.poster = poster; }, - setSrc: function(source) { + setSrc: function(source, isFirst) { if (!source || !source.src) { return; } @@ -163,6 +273,14 @@ THE SOFTWARE. */ this.source = source; this.url = Youtube.parseUrl(source.src); + if (this.activeVideoId !== this.url.videoId) { + if (!isFirst) { + this.ytPlayer.loadVideoById(this.url.videoId); + } + + this.activeVideoId = this.url.videoId; + } + if (!this.options_.poster) { Youtube.loadThumbnailUrl(this.url.videoId, function(poster) { this.setPoster(poster); @@ -185,12 +303,7 @@ THE SOFTWARE. */ } if (this.isReady_) { - if (this.activeVideoId === this.url.videoId) { - this.ytPlayer.playVideo(); - } else { - this.ytPlayer.loadVideoById(this.url.videoId); - this.activeVideoId = this.url.videoId; - } + this.ytPlayer.playVideo(); } else { this.trigger('waiting'); this.playOnReady = true; From 64a70bcf4e6fa885f36bdbec083cfabdc5f2b2ca Mon Sep 17 00:00:00 2001 From: eXon Date: Sat, 11 Jul 2015 12:57:28 -0400 Subject: [PATCH 09/21] Remove debug var --- examples/simple.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple.html b/examples/simple.html index 1bf70afe..c3b88aa2 100644 --- a/examples/simple.html +++ b/examples/simple.html @@ -10,7 +10,7 @@ controls autoplay width="640" height="264" - data-setup='{ "techOrder": ["youtube"], "sources": [{ "type": "video/youtube", "src": "https://www.youtube.com/watch?v=xjS6SftYQaQ"}], "youtube": { "test": true } }' + data-setup='{ "techOrder": ["youtube"], "sources": [{ "type": "video/youtube", "src": "https://www.youtube.com/watch?v=xjS6SftYQaQ"}] }' > From 358cd9666eb041f58f96b05bcee27c753b7acb05 Mon Sep 17 00:00:00 2001 From: eXon Date: Sat, 11 Jul 2015 15:39:06 -0400 Subject: [PATCH 10/21] Load the YouTube list using the URL --- examples/youtube-list.html | 2 +- src/Youtube.js | 51 ++++++++++++++++++++++++-------------- tests/parseUrl.specs.js | 5 ++++ 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/examples/youtube-list.html b/examples/youtube-list.html index f67576dd..45c09148 100644 --- a/examples/youtube-list.html +++ b/examples/youtube-list.html @@ -9,7 +9,7 @@ class="video-js vjs-default-skin" width="640" height="264" controls - data-setup='{ "techOrder": ["youtube"], "sources": [{ "type": "video/youtube", "src": "https://www.youtube.com/watch?v=nyMkLwSyOVQ"}], "youtube": { "list": "PL63F0C78739B09958" } }' + data-setup='{ "techOrder": ["youtube"], "sources": [{ "type": "video/youtube", "src": "https://www.youtube.com/watch?v=nyMkLwSyOVQ&list=PL63F0C78739B09958"}] }' > diff --git a/src/Youtube.js b/src/Youtube.js index 854f5691..490032ba 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -71,11 +71,6 @@ THE SOFTWARE. */ loop: this.options_.loop ? 1 : 0 }; - // Set the YouTube player on the same language than video.js - if (typeof this.options_.language !== 'undefined') { - playerVars.hl = this.options_.language.substr(0, 2); - } - // Let the user set any YouTube parameter // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters // To use YouTube controls, you must use ytControls instead @@ -111,6 +106,9 @@ THE SOFTWARE. */ if (typeof this.options_.hl !== 'undefined') { playerVars.hl = this.options_.hl; + } else if (typeof this.options_.language !== 'undefined') { + // Set the YouTube player on the same language than video.js + playerVars.hl = this.options_.language.substr(0, 2); } if (typeof this.options_['iv_load_policy'] !== 'undefined') { @@ -119,6 +117,8 @@ THE SOFTWARE. */ if (typeof this.options_.list !== 'undefined') { playerVars.list = this.options_.list; + } else if (typeof this.url.listId !== 'undefined') { + playerVars.list = this.url.listId; } if (typeof this.options_.listType !== 'undefined') { @@ -153,6 +153,9 @@ THE SOFTWARE. */ playerVars.theme = this.options_.theme; } + this.activeVideoId = this.url.videoId; + this.activeList = playerVars.list; + this.ytPlayer = new YT.Player(this.options_.techId, { videoId: this.url.videoId, playerVars: playerVars, @@ -228,10 +231,9 @@ THE SOFTWARE. */ this.errorNumber = e.data; this.trigger('error'); - // Get rid of the iframe, we want to display our own error message - this.el_.removeChild( - this.el_.children[0] - ); + this.ytPlayer.stopVideo(); + this.ytPlayer.destroy(); + this.ytPlayer = null; }, error: function() { @@ -265,7 +267,7 @@ THE SOFTWARE. */ this.poster = poster; }, - setSrc: function(source, isFirst) { + setSrc: function(source) { if (!source || !source.src) { return; } @@ -273,14 +275,6 @@ THE SOFTWARE. */ this.source = source; this.url = Youtube.parseUrl(source.src); - if (this.activeVideoId !== this.url.videoId) { - if (!isFirst) { - this.ytPlayer.loadVideoById(this.url.videoId); - } - - this.activeVideoId = this.url.videoId; - } - if (!this.options_.poster) { Youtube.loadThumbnailUrl(this.url.videoId, function(poster) { this.setPoster(poster); @@ -303,7 +297,19 @@ THE SOFTWARE. */ } if (this.isReady_) { - this.ytPlayer.playVideo(); + 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.ytPlayer.loadVideoById(this.url.videoId); + this.activeVideoId = this.url.videoId; + } } else { this.trigger('waiting'); this.playOnReady = true; @@ -468,6 +474,13 @@ THE SOFTWARE. */ result.videoId = match[2]; } + var regPlaylist = /[?&]list=([^#\&\?]+)/; + match = url.match(regPlaylist); + + if(match && match[1]) { + result.listId = match[1]; + } + return result; }; diff --git a/tests/parseUrl.specs.js b/tests/parseUrl.specs.js index 7f27c523..bc59347c 100644 --- a/tests/parseUrl.specs.js +++ b/tests/parseUrl.specs.js @@ -6,4 +6,9 @@ describe('parseUrl', function() { expect(Youtube.parseUrl('https://www.youtube.com/embed/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); expect(Youtube.parseUrl('https://youtu.be/OPf0YbXqDm0').videoId).toBe('OPf0YbXqDm0'); }); + + it('should read the list in the URL', function() { + var url = 'https://www.youtube.com/watch?v=RgKAFK5djSk&list=PL55713C70BA91BD6E'; + expect(Youtube.parseUrl(url).listId).toBe('PL55713C70BA91BD6E'); + }); }); From e04b4e4e0bdd9f439f825f51116cd9e37c6035b6 Mon Sep 17 00:00:00 2001 From: Manuel Voss Date: Fri, 21 Aug 2015 08:18:55 +0200 Subject: [PATCH 11/21] Added the color option to the player parameters. --- src/Youtube.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Youtube.js b/src/Youtube.js index 490032ba..7d4c5ba5 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -96,6 +96,10 @@ THE SOFTWARE. */ playerVars.end = this.options_.end; } + if (typeof this.options_.color !== 'undefined') { + playerVars.color = this.options_.color; + } + if (typeof this.options_.fs !== 'undefined') { playerVars.fs = this.options_.fs; } From 94f9a05443824b49b72603bfb874f96230761412 Mon Sep 17 00:00:00 2001 From: Manuel Voss Date: Fri, 21 Aug 2015 19:28:35 +0200 Subject: [PATCH 12/21] Typo fix to make the pause button work --- src/Youtube.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Youtube.js b/src/Youtube.js index 7d4c5ba5..9fdc162b 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -327,7 +327,7 @@ THE SOFTWARE. */ }, paused: function() { - return (this.ytplayer) ? + return (this.ytPlayer) ? (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) : true; }, From 89f64561ae9bf23a3aebc4159539d0684eb9cb9a Mon Sep 17 00:00:00 2001 From: Manuel Voss Date: Thu, 27 Aug 2015 09:12:31 +0200 Subject: [PATCH 13/21] fixes the volume display --- src/Youtube.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Youtube.js b/src/Youtube.js index 9fdc162b..1984f094 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -403,6 +403,10 @@ THE SOFTWARE. */ } this.ytPlayer.setVolume(percentAsDecimal * 100.0); + this.setTimeout( function(){ + this.trigger('volumechange'); + }, 50); + }, muted: function() { @@ -413,12 +417,18 @@ THE SOFTWARE. */ 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: function() { From 7dde07a4247a451a5b88a55dd2b6cfce26842af5 Mon Sep 17 00:00:00 2001 From: eXon Date: Sun, 20 Sep 2015 16:19:20 -0400 Subject: [PATCH 14/21] Fix the poster, the play button and the fullscreen --- package.json | 9 ++---- src/Youtube.js | 78 ++++++++++++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 53621064..7d9a7bbc 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "videojs-youtube", "description": "YouTube playback technology for Video.js", - "version": "2.0.0-1", + "version": "2.0.0-beta1", "author": "Benoit Tremblay", "main": "src/Youtube.js", "keywords": [ @@ -16,11 +16,8 @@ "type": "git", "url": "https://github.com/eXon/videojs-youtube.git" }, - "peerDependencies": { - "video.js": "^5.0.0-rc.12" - }, "dependencies": { - "video.js": "^5.0.0-rc.12" + "video.js": "^5.0.0-rc.91" }, "scripts": { "build": "mkdir -p dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js", @@ -42,4 +39,4 @@ "precommit-hook": "^3.0.0", "uglify-js": "^2.4.23" } -} \ No newline at end of file +} diff --git a/src/Youtube.js b/src/Youtube.js index 490032ba..5daeb6c6 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -28,7 +28,19 @@ THE SOFTWARE. */ constructor: function(options, ready) { Tech.call(this, options, ready); + + this.setPoster(options.poster); this.setSrc(this.options_.source, true); + + // Set the vjs-youtube class to the player + // Parent is not set yet so we have to wait a tick + setTimeout(function() { + this.el_.parentNode.className += ' vjs-youtube'; + }.bind(this)); + }, + + dispose: function() { + this.el_.parentNode.className = this.el_.parentNode.className.replace(' vjs-youtube', ''); }, createEl: function() { @@ -98,6 +110,9 @@ THE SOFTWARE. */ if (typeof this.options_.fs !== 'undefined') { playerVars.fs = this.options_.fs; + } else if (!playerVars.controls) { + // Let video.js handle the fullscreen unless it is the YouTube native controls + playerVars.fs = 0; } if (typeof this.options_.end !== 'undefined') { @@ -169,10 +184,14 @@ THE SOFTWARE. */ }, onPlayerReady: function() { - this.triggerReady(); + this.playerReady_ = true; + + if (this.posterReady_) { + this.triggerReady(); - if (this.playOnReady) { - this.play(); + if (this.playOnReady) { + this.play(); + } } }, @@ -260,11 +279,11 @@ THE SOFTWARE. */ }, poster: function() { - return this.poster; + return this.poster_; }, setPoster: function(poster) { - this.poster = poster; + this.poster_ = poster; }, setSrc: function(source) { @@ -276,10 +295,28 @@ THE SOFTWARE. */ this.url = Youtube.parseUrl(source.src); if (!this.options_.poster) { - Youtube.loadThumbnailUrl(this.url.videoId, function(poster) { - this.setPoster(poster); - this.trigger('posterchange'); - }.bind(this)); + if (this.url.videoId) { + // Set the low resolution first + this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; + + // Check if their is a high res + Youtube.checkHighResPoster(this.url.videoId, function(poster) { + this.posterReady_ = true; + + // Did it found a higher resolution poster? + if (poster) { + this.setPoster(poster); + } + + if (!this.isReady_ && this.playerReady_) { + this.triggerReady(); + + if (this.playOnReady) { + this.play(); + } + } + }.bind(this)); + } } if (this.options_.autoplay && !_isOnMobile) { @@ -323,7 +360,7 @@ THE SOFTWARE. */ }, paused: function() { - return (this.ytplayer) ? + return (this.ytPlayer) ? (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) : true; }, @@ -440,14 +477,7 @@ THE SOFTWARE. */ }, supportsFullScreen: function() { - if (typeof this.el_.webkitEnterFullScreen === 'function') { - // Seems to be broken in Chromium/Chrome && Safari in Leopard - if (/Android/.test(videojs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(videojs.USER_AGENT)) { - return true; - } - } - - return false; + return true; } }); @@ -485,10 +515,9 @@ THE SOFTWARE. */ }; // Tries to get the highest resolution thumbnail available for the video - Youtube.loadThumbnailUrl = function(id, callback){ + Youtube.checkHighResPoster = function(id, callback){ var uri = 'https://img.youtube.com/vi/' + id + '/maxresdefault.jpg'; - var fallback = 'https://img.youtube.com/vi/' + id + '/0.jpg'; try { var image = new Image(); @@ -507,11 +536,11 @@ THE SOFTWARE. */ callback(uri); }; image.onerror = function(){ - callback(fallback); + callback(null); }; image.src = uri; } - catch(e){ callback(fallback); } + catch(e){ callback(null); } }; function loadApi() { @@ -522,8 +551,9 @@ THE SOFTWARE. */ } function injectCss() { - var css = '.vjs-iframe-blocker { display: none; }' + - '.vjs-user-inactive .vjs-iframe-blocker { display: block; }'; + var css = '.vjs-youtube .vjs-iframe-blocker { display: none; }' + + '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + + '.vjs-youtube .vjs-poster { background-size: cover; }'; var head = document.head || document.getElementsByTagName('head')[0]; From 320faf84731929cb42b4aa8e6e84682955f579d3 Mon Sep 17 00:00:00 2001 From: eXon Date: Sun, 20 Sep 2015 16:27:41 -0400 Subject: [PATCH 15/21] Force fs to 0 when using VJS controls --- src/Youtube.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Youtube.js b/src/Youtube.js index f24edcfc..9829c7cf 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -112,11 +112,11 @@ THE SOFTWARE. */ playerVars.color = this.options_.color; } - if (typeof this.options_.fs !== 'undefined') { - playerVars.fs = this.options_.fs; - } else if (!playerVars.controls) { + if (!playerVars.controls) { // Let video.js handle the fullscreen unless it is the YouTube native controls playerVars.fs = 0; + } else if (typeof this.options_.fs !== 'undefined') { + playerVars.fs = this.options_.fs; } if (typeof this.options_.end !== 'undefined') { @@ -443,7 +443,7 @@ THE SOFTWARE. */ this.setTimeout( function(){ this.trigger('volumechange'); }, 50); - + }, muted: function() { From a7361b91fb6de8ff051ec0defd5325ba33730a8b Mon Sep 17 00:00:00 2001 From: eXon Date: Sun, 20 Sep 2015 16:42:13 -0400 Subject: [PATCH 16/21] Update readme --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea2b3d3b..0dcf230b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,75 @@ # YouTube Playback Technology
for [Video.js](https://github.com/videojs/video.js) -Complete rewrite for VJS 5 in progress +## Install +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. -## Todo coming soon +## Example +```html + + + + + + + + + + + + +``` + +See the examples folder for more + +## How does it work? +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. + +It supports: +- youtube.com as well as youtu.be +- Regular URLs: http://www.youtube.com/watch?v=xjS6SftYQaQ +- Embeded URLs: http://www.youtube.com/embed/xjS6SftYQaQ +- Playlist URLs: http://www.youtube.com/playlist?list=PLA60DCEB33156E51F OR http://www.youtube.com/watch?v=xjS6SftYQaQ&list=SPA60DCEB33156E51F + +## Options +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`. + +```html + +``` + +### YouTube controls +Because `controls` is already a Video.js option, to use the YouTube controls, you must set the `ytControls` parameter. + +```html + +``` + +##Special Thank You +Thanks to Steve Heffernan for the amazing Video.js and to John Hurliman for the original version of the YouTube tech ## License The MIT License (MIT) From 28f2c9989e6ad087025a972a7e04cc2c5c7a9cc7 Mon Sep 17 00:00:00 2001 From: Benoit Tremblay Date: Fri, 16 Oct 2015 16:55:29 -0400 Subject: [PATCH 17/21] Use peer dependency --- .DS_Store | Bin 0 -> 6148 bytes examples/.DS_Store | Bin 0 -> 6148 bytes package.json | 7 ++++--- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .DS_Store create mode 100644 examples/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4a94de68d5a899dd7c2dad99e6bd26d6d2d04d73 GIT binary patch literal 6148 zcmeHK&2G~`5S~p^a8i(r0I3oeA3>_BkbrVcLyE+qQq-VUh@xP}hPnhhiX8%qBISs9 z0?z!u2Z9q10FS~ce6zbP&JS0F(2h0xjdy2u?a#YAULq2Wp?{soCL#;USSX=bA>7Wo zBspEP4ivJF0d3JSB@|P%1o-X)(V?U^v&Dia?pEpYSy1$=ZzZWKHx%@-q@`bfCXRTssoiF=B=|TG_ z9<@_99VQKTa9`eqe&A`l<)%j`_T4z{o!YLq9|oh2ed$&lrEVAngG}h*K^Xb*s1Xk% zKRIlx{tiMvJ?hw(TZ7;kLR)SW?1xF{g}pF+VHNr6*c;<XTn!qK0LnILsd7hqt#sP?Qk#)K<=R8vRXMxi1OlfBBoVtzfDyxhVZbnuWq|hw2g+F2I8`XO4ixeT04$(d3T*ye z;21|^UE@?CS|CD&0#&HcM+~9DQSWHKy2hzO6;47QK7<}w=o5;NqoaREx|66YG^Js{ zFfh+RUftT9|9Ad;{+|yrcZLDOz<r28ZGR+$OxSt; z>s`I5(zfb^BfK(y%?veHk@FpzbNtM4MJ)RB%MHCZ^B;C16@(D$PD>1lAykbaj=e&z{NyH{#aEHXphs4YhZz$rkGrsfYkR+hB z#(*)f&A?t5PPG1C_TT@voor+b7z6)`0U6G2XH#w|*4EDEwAMQ69aTl`nt*E;c6=$O ft(M{wsu#{X?GTfQO+e02>_@=UV2v^Grwn`oCP!bw literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 7d9a7bbc..f60f9dc7 100755 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "videojs-youtube", "description": "YouTube playback technology for Video.js", - "version": "2.0.0-beta1", + "version": "2.0.0", "author": "Benoit Tremblay", "main": "src/Youtube.js", + "license": "MIT", "keywords": [ "video", "videojs", @@ -16,8 +17,8 @@ "type": "git", "url": "https://github.com/eXon/videojs-youtube.git" }, - "dependencies": { - "video.js": "^5.0.0-rc.91" + "peerDependencies": { + "video.js": "5.x" }, "scripts": { "build": "mkdir -p dist && cp src/Youtube.js dist/Youtube.js && uglifyjs src/Youtube.js -o dist/Youtube.min.js", From 5c529371fbe0c31abb4a5fa5c65a97d6763d68f8 Mon Sep 17 00:00:00 2001 From: Benoit Tremblay Date: Fri, 16 Oct 2015 16:57:00 -0400 Subject: [PATCH 18/21] Remove .DS_Store files --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 2 ++ .npmignore | 1 + 3 files changed, 3 insertions(+) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 4a94de68d5a899dd7c2dad99e6bd26d6d2d04d73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&2G~`5S~p^a8i(r0I3oeA3>_BkbrVcLyE+qQq-VUh@xP}hPnhhiX8%qBISs9 z0?z!u2Z9q10FS~ce6zbP&JS0F(2h0xjdy2u?a#YAULq2Wp?{soCL#;USSX=bA>7Wo zBspEP4ivJF0d3JSB@|P%1o-X)(V?U^v&Dia?pEpYSy1$=ZzZWKHx%@-q@`bfCXRTssoiF=B=|TG_ z9<@_99VQKTa9`eqe&A`l<)%j`_T4z{o!YLq9|oh2ed$&lrEVAngG}h*K^Xb*s1Xk% zKRIlx{tiMvJ?hw(TZ7;kLR)SW?1xF{g}pF+VHNr6*c;<XTn!qK0LnILsd7hqt#sP?Qk#)K<=R8vRXMxi1OlfBBoVtzfDyxhVZbnuWq|hw2g+F2I8`XO4ixeT04$(d3T*ye z;21|^UE@?CS|CD&0#&HcM+~9DQSWHKy2hzO6;47QK7<}w=o5;NqoaREx|66YG^Js{ zFfh+RUftT9|9Ad;{+|yrcZLDOz< Date: Fri, 16 Oct 2015 16:57:20 -0400 Subject: [PATCH 19/21] Rename extends to extend --- src/Youtube.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Youtube.js b/src/Youtube.js index 9829c7cf..9ca5f38a 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -24,7 +24,7 @@ THE SOFTWARE. */ var Tech = videojs.getComponent('Tech'); - var Youtube = videojs.extends(Tech, { + var Youtube = videojs.extend(Tech, { constructor: function(options, ready) { Tech.call(this, options, ready); From daa5e91f8f9261013caa0c43c8a79550213670cb Mon Sep 17 00:00:00 2001 From: Benoit Tremblay Date: Fri, 16 Oct 2015 17:11:48 -0400 Subject: [PATCH 20/21] Fix #319 --- src/Youtube.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Youtube.js b/src/Youtube.js index 9ca5f38a..aef30f5e 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -136,7 +136,7 @@ THE SOFTWARE. */ if (typeof this.options_.list !== 'undefined') { playerVars.list = this.options_.list; - } else if (typeof this.url.listId !== 'undefined') { + } else if (this.url && typeof this.url.listId !== 'undefined') { playerVars.list = this.url.listId; } @@ -172,11 +172,11 @@ THE SOFTWARE. */ playerVars.theme = this.options_.theme; } - this.activeVideoId = this.url.videoId; + this.activeVideoId = this.url ? this.url.videoId : null; this.activeList = playerVars.list; this.ytPlayer = new YT.Player(this.options_.techId, { - videoId: this.url.videoId, + videoId: this.activeVideoId, playerVars: playerVars, events: { onReady: this.onPlayerReady.bind(this), From 906206664e07d9c3ecdf0800bc7a2f6045b072ec Mon Sep 17 00:00:00 2001 From: Benoit Tremblay Date: Fri, 16 Oct 2015 17:40:35 -0400 Subject: [PATCH 21/21] Fix poster + CSS --- src/Youtube.js | 88 +++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/src/Youtube.js b/src/Youtube.js index aef30f5e..206bbfa2 100644 --- a/src/Youtube.js +++ b/src/Youtube.js @@ -46,7 +46,7 @@ THE SOFTWARE. */ createEl: function() { var div = document.createElement('div'); div.setAttribute('id', this.options_.techId); - div.setAttribute('style', 'width:100%;height:100%'); + div.setAttribute('style', 'width:100%;height:100%;top:0;left:0;position:absolute'); var divWrapper = document.createElement('div'); divWrapper.setAttribute('style', 'width:100%;height:100%;position:relative'); @@ -189,13 +189,10 @@ THE SOFTWARE. */ onPlayerReady: function() { this.playerReady_ = true; + this.triggerReady(); - if (this.posterReady_) { - this.triggerReady(); - - if (this.playOnReady) { - this.play(); - } + if (this.playOnReady) { + this.play(); } }, @@ -304,22 +301,7 @@ THE SOFTWARE. */ this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; // Check if their is a high res - Youtube.checkHighResPoster(this.url.videoId, function(poster) { - this.posterReady_ = true; - - // Did it found a higher resolution poster? - if (poster) { - this.setPoster(poster); - } - - if (!this.isReady_ && this.playerReady_) { - this.triggerReady(); - - if (this.playOnReady) { - this.play(); - } - } - }.bind(this)); + this.checkHighResPoster(); } } @@ -492,8 +474,34 @@ THE SOFTWARE. */ supportsFullScreen: function() { return true; - } + }, + + // Tries to get the highest resolution thumbnail available for the video + checkHighResPoster: function(){ + var uri = 'https://img.youtube.com/vi/' + this.url.videoId + '/maxresdefault.jpg'; + + try { + var image = new Image(); + image.onload = function(){ + // Onload may still be called if YouTube returns the 120x90 error thumbnail + if('naturalHeight' in this){ + if(this.naturalHeight <= 90 || this.naturalWidth <= 120) { + this.onerror(); + return; + } + } else if(this.height <= 90 || this.width <= 120) { + this.onerror(); + return; + } + this.poster_ = uri; + this.trigger('posterchange'); + }.bind(this); + image.onerror = function(){}; + image.src = uri; + } + catch(e){} + } }); Youtube.isSupported = function() { @@ -528,35 +536,6 @@ THE SOFTWARE. */ return result; }; - // Tries to get the highest resolution thumbnail available for the video - Youtube.checkHighResPoster = function(id, callback){ - - var uri = 'https://img.youtube.com/vi/' + id + '/maxresdefault.jpg'; - - try { - var image = new Image(); - image.onload = function(){ - // Onload may still be called if YouTube returns the 120x90 error thumbnail - if('naturalHeight' in this){ - if(this.naturalHeight <= 90 || this.naturalWidth <= 120) { - this.onerror(); - return; - } - } else if(this.height <= 90 || this.width <= 120) { - this.onerror(); - return; - } - - callback(uri); - }; - image.onerror = function(){ - callback(null); - }; - image.src = uri; - } - catch(e){ callback(null); } - }; - function loadApi() { var tag = document.createElement('script'); tag.src = 'https://www.youtube.com/iframe_api'; @@ -565,7 +544,8 @@ THE SOFTWARE. */ } function injectCss() { - var css = '.vjs-youtube .vjs-iframe-blocker { display: none; }' + + var css = // iframe blocker to catch mouse events + '.vjs-youtube .vjs-iframe-blocker { display: none; }' + '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + '.vjs-youtube .vjs-poster { background-size: cover; }';