├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Watson Design Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | youtube-player 2 | === 3 | 4 | A bare bones wrapper around the Youtube Iframe API. 5 | 6 | ## Install 7 | 8 | ``` 9 | npm install watsondg/youtube-player -S 10 | ``` 11 | 12 | ## Usage 13 | 14 | ``` 15 | var Youtube = require('youtube-player'); 16 | 17 | var player = new Youtube(); 18 | player.cue('dQw4w9WgXcQ', 6); // Start playback at 6 seconds 19 | ``` 20 | 21 | ## Instance Methods 22 | 23 | ### new Youtube(el[, options]) 24 | 25 | Create a new player instance. 26 | * `el` - an element that will be REPLACED by the player iframe. 27 | * `options` - (OPTIONAL) - configuration parameters: 28 | - controls: show or hide the default player controls 29 | - allowFullscreen: enable/disable fullscreen 30 | - hasAutoplay: automatically plays video - only works on desktop. Defaults to false 31 | - hasCueAutoplay: control if `player.cue` automatically plays the video - autoplay only works on dekstop. Defaults to the `hasAutoplay` value. 32 | 33 | The player size defaults to 100% so you should use a container for any positioning/resizing. 34 | 35 | ### cue(videoId[, startTime]) 36 | 37 | Change the displayed video. 38 | * `videoId` - the youtube video hash (in the URL after `watch?v=`). 39 | * `startTime` - (OPTIONAL) - time to start the video playback, in seconds. 40 | 41 | ### play() 42 | 43 | Resume playback. 44 | 45 | ### pause() 46 | 47 | Pause playback. 48 | 49 | ### seek(time) 50 | 51 | Jump to a specific time. 52 | * `time` - the time to jump to, in seconds. 53 | 54 | ### mute() 55 | 56 | Mute the sound. 57 | 58 | ### unmute() 59 | 60 | Unmutes the sound. 61 | 62 | ### volume(value) 63 | 64 | Sets the sound volume. 65 | * `value` - the volume, in 0-1 range. 66 | 67 | ### fullscreen() 68 | 69 | Toggle the player to fullscreen, if supported. Note that this is using the javascript requestFullScreen API since Youtube doesn't support this, unless you use built-in controls. 70 | 71 | ### getDuration() 72 | 73 | Return the current video duration. 74 | 75 | ### getVolume() 76 | 77 | Return the current sound volume. 78 | 79 | ### getCurrentTime() 80 | 81 | Return the current video time. 82 | 83 | ### isPopulated() 84 | 85 | Return whether or not the player is ready (creating the player using YT API is asynchronous). 86 | 87 | ### isPlaying() 88 | 89 | Return whether or not a video is currently playing. 90 | 91 | ### isMuted() 92 | 93 | Return whether or not the sound is muted. 94 | 95 | ### hasPlayed() 96 | 97 | Return whether or not a video has already started since the player was created. 98 | 99 | ### destroy() 100 | 101 | Stop and destroy the player. 102 | 103 | ## Instance Events 104 | 105 | ### populated 106 | ### playing 107 | ### paused 108 | ### ended 109 | ### buffering 110 | ### unstarted 111 | ### cued 112 | ### timeupdate 113 | 114 | ## License 115 | MIT. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Emitter = require('tiny-emitter'); 4 | 5 | module.exports = Youtube; 6 | 7 | function Youtube(el, opts) { 8 | opts = opts || {}; 9 | this.options = { 10 | controls: opts.controls === true ? true : false, 11 | allowFullscreen: opts.allowFullscreen === true ? true : false, 12 | hasAutoplay: opts.hasAutoplay === true ? true : false, 13 | hasCueAutoplay: (opts.hasCueAutoplay || opts.hasAutoplay) === true ? true : false 14 | }; 15 | this.el = el; 16 | 17 | this._isPopulated = false; 18 | this._hasPlayed = false; 19 | this._isPlaying = false; 20 | this.playbackInterval = -1; 21 | 22 | this.onPlayerPopulated = this.onPlayerPopulated.bind(this); 23 | this.onPlayerStateChange = this.onPlayerStateChange.bind(this); 24 | this.onPlaybackUpdate = this.onPlaybackUpdate.bind(this); 25 | } 26 | 27 | Youtube.prototype = Object.create(Emitter.prototype); 28 | Youtube.prototype.constructor = Youtube; 29 | 30 | Youtube.prototype.populatePlayer = function(videoId, startTime) { 31 | this.player = new YT.Player(this.el, { 32 | width: '100%', 33 | height: '100%', 34 | videoId: videoId, 35 | playerVars: { 36 | modestbranding: 1, 37 | rel: 0, 38 | controls: this.options.controls === true ? 1 : 0, 39 | showinfo: 0, 40 | allowfullscreen: this.options.allowFullscreen, 41 | autoplay: this.options.hasAutoplay === true ? 1 : 0, 42 | wmode: 'transparent', 43 | startSeconds: startTime, 44 | start: startTime 45 | }, 46 | events :{ 47 | onStateChange: this.onPlayerStateChange, 48 | onReady: this.onPlayerPopulated, 49 | } 50 | }); 51 | }; 52 | 53 | Youtube.prototype.cue = function(videoId, startTime) { 54 | startTime = startTime || 0; 55 | 56 | if (this._isPopulated && this.player) { 57 | if (this.options.hasAutoplay && !this._hasPlayed || this.options.hasCueAutoplay) { 58 | this.player.loadVideoById({ 59 | videoId: videoId, 60 | startSeconds: startTime 61 | }); 62 | } else { 63 | this.player.cueVideoById(videoId, startTime); 64 | } 65 | } else { 66 | this.populatePlayer(videoId, startTime); 67 | } 68 | }; 69 | 70 | Youtube.prototype.onPlayerPopulated = function(event) { 71 | this.el = this.player.getIframe(); 72 | this._isPopulated = true; 73 | this.emit('populated'); 74 | }; 75 | 76 | Youtube.prototype.onPlayerStateChange = function(state) { 77 | clearInterval(this.playbackInterval); 78 | var playerState = ''; 79 | 80 | switch(state.data) { 81 | case YT.PlayerState.UNSTARTED: 82 | playerState = 'unstarted'; 83 | break; 84 | 85 | case YT.PlayerState.ENDED: 86 | this.pause(); 87 | this.player.seekTo(this.player.getDuration() - 0.1, false); 88 | setTimeout(this.pause.bind(this), 200); 89 | playerState = 'ended'; 90 | break; 91 | 92 | case YT.PlayerState.PLAYING: 93 | this.playbackInterval = setInterval(this.onPlaybackUpdate, 100); 94 | this._isPlaying = true; 95 | this._hasPlayed = true; 96 | playerState = 'playing'; 97 | break; 98 | 99 | case YT.PlayerState.ENDED: 100 | case YT.PlayerState.PAUSED: 101 | playerState = 'paused'; 102 | this._isPlaying = false; 103 | break; 104 | 105 | case YT.PlayerState.BUFFERING: 106 | playerState = 'buffering'; 107 | break; 108 | 109 | case YT.PlayerState.CUED: 110 | playerState = 'cued'; 111 | break; 112 | } 113 | 114 | this.emit('statechange', playerState); 115 | this.emit(playerState); 116 | }; 117 | 118 | Youtube.prototype.onPlaybackUpdate = function() { 119 | this.emit('timeupdate'); 120 | }; 121 | 122 | Youtube.prototype.fullscreen = function() { 123 | var el = this.el.parentNode || this.el; 124 | var requestFullScreen = el.requestFullScreen 125 | || el.mozRequestFullScreen 126 | || el.webkitRequestFullScreen 127 | || el.msRequestFullScreen; 128 | if (requestFullScreen) requestFullScreen.call(el); 129 | }; 130 | 131 | Youtube.prototype.play = function() { 132 | if (!this._isPopulated) return; 133 | 134 | this.player.playVideo(); 135 | }; 136 | 137 | Youtube.prototype.pause = function() { 138 | if (!this._isPopulated) return; 139 | 140 | try { 141 | this.player.pauseVideo(); 142 | } catch(e) {} 143 | }; 144 | 145 | Youtube.prototype.seek = function(time) { 146 | if (!this._isPopulated) return; 147 | 148 | this.player.seekTo(time); 149 | this.player.playVideo(); 150 | }; 151 | 152 | Youtube.prototype.volume = function(value) { 153 | if (!this._isPopulated) return; 154 | 155 | this.player.setVolume(value * 100); 156 | }; 157 | 158 | Youtube.prototype.mute = function() { 159 | if (!this._isPopulated) return; 160 | 161 | this.player.mute(); 162 | }; 163 | 164 | Youtube.prototype.unmute = function() { 165 | if (!this._isPopulated) return; 166 | 167 | this.player.unMute(); 168 | }; 169 | 170 | Youtube.prototype.isMuted = function() { 171 | if (!this._isPopulated) return false; 172 | 173 | return this.player.isMuted(); 174 | }; 175 | 176 | Youtube.prototype.getVolume = function() { 177 | if (!this._isPopulated) return 0; 178 | 179 | return this.player.getVolume(); 180 | }; 181 | 182 | Youtube.prototype.getDuration = function() { 183 | if (!this._isPopulated) return 0; 184 | 185 | return this.player.getDuration(); 186 | }; 187 | 188 | Youtube.prototype.isPopulated = function() { 189 | return this._isPopulated; 190 | }; 191 | 192 | Youtube.prototype.isPlaying = function() { 193 | return this._isPlaying; 194 | }; 195 | 196 | Youtube.prototype.hasPlayed = function() { 197 | return this._hasPlayed; 198 | }; 199 | 200 | Youtube.prototype.getCurrentTime = function() { 201 | if (!this._isPopulated) return 0; 202 | 203 | return this.player.getCurrentTime(); 204 | }; 205 | 206 | Youtube.prototype.destroy = function() { 207 | this.pause(); 208 | this.off(); 209 | clearInterval(this.playbackInterval); 210 | this.el = null; 211 | if (this.player) this.player.destroy(); 212 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtube-player", 3 | "version": "2.0.2", 4 | "description": "A wrapper around the Youtube Iframe API.", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "browserify test/index.js | tap-closer | smokestack | faucet", 11 | "test-debug": "budo test/index.js --live" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+ssh://git@github.com/watsondg/youtube-player.git" 16 | }, 17 | "keywords": [ 18 | "youtube", 19 | "player", 20 | "video" 21 | ], 22 | "author": "Florian Morel", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/watsondg/youtube-player/issues" 26 | }, 27 | "homepage": "https://github.com/watsondg/youtube-player#readme", 28 | "dependencies": { 29 | "tiny-emitter": "^1.0.2" 30 | }, 31 | "devDependencies": { 32 | "faucet": "0.0.1", 33 | "smokestack": "^3.4.1", 34 | "tap-closer": "^1.0.0", 35 | "tape": "^4.5.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | var test = require('tape'); 5 | var Youtube = require('../index.js'); 6 | 7 | var videoId = 'dQw4w9WgXcQ'; 8 | var videoId2 = 'PJ_GIRTTRaA'; 9 | 10 | var script = document.createElement('script'); 11 | script.src = 'https://www.youtube.com/player_api'; 12 | document.body.appendChild(script); 13 | 14 | if (window.YT) { 15 | runTests(); 16 | } else { 17 | window.onYouTubeIframeAPIReady = function() { 18 | runTests(); 19 | }; 20 | } 21 | 22 | function runTests() { 23 | test('Youtube instance not ready test', function(assert) { 24 | var wrapper = document.createElement('div'); 25 | document.body.appendChild(wrapper); 26 | var player = new Youtube(wrapper, { 27 | hasAutoplay: false, 28 | hasCueAutoplay: true 29 | }); 30 | player.cue(videoId); 31 | player.play(); 32 | var currentTime = player.getCurrentTime(); 33 | setTimeout(function() { 34 | assert.pass('Player not ready shouldn\'t break'); 35 | assert.deepEqual(currentTime, 0, 'Player time should be 0'); 36 | player.destroy(); 37 | wrapper.remove(); 38 | assert.end(); 39 | }); 40 | }); 41 | 42 | test('Youtube autoplay test', function(assert) { 43 | var wrapper = document.createElement('div'); 44 | document.body.appendChild(wrapper); 45 | var player = new Youtube(wrapper, { 46 | hasAutoplay: false, 47 | hasCueAutoplay: true 48 | }); 49 | player.cue(videoId); 50 | setTimeout(function() { 51 | player.once('playing', function() { 52 | assert.pass('cue autoplayed'); 53 | player.destroy(); 54 | wrapper.remove(); 55 | assert.end(); 56 | }); 57 | player.cue(videoId2); 58 | }, 2500); 59 | }); 60 | 61 | test('Youtube URL test', function(assert) { 62 | var wrapper = document.createElement('div'); 63 | document.body.appendChild(wrapper); 64 | var player = new Youtube(wrapper, { 65 | hasAutoplay: true 66 | }); 67 | player.once('playing', function() { 68 | assert.ok(player.player.getVideoUrl().indexOf('dQw4w9WgXcQ') > -1, 'URL should be loaded'); 69 | player.destroy(); 70 | wrapper.remove(); 71 | assert.end(); 72 | }); 73 | player.cue(videoId); 74 | }); 75 | 76 | test('currentTime should change during playback', function(assert) { 77 | var wrapper = document.createElement('div'); 78 | document.body.appendChild(wrapper); 79 | var player = new Youtube(wrapper, { 80 | hasAutoplay: true 81 | }); 82 | 83 | setTimeout(function() { 84 | player.once('timeupdate', function() { 85 | assert.ok(player.getCurrentTime() > 0, 'currentTime should have changed.'); 86 | player.destroy(); 87 | wrapper.remove(); 88 | assert.end(); 89 | }); 90 | }, 1500); 91 | player.cue(videoId); 92 | }); 93 | 94 | test('currentTime shouldn\'t change when playback is paused', function(assert) { 95 | var wrapper = document.createElement('div'); 96 | document.body.appendChild(wrapper); 97 | var player = new Youtube(wrapper, { 98 | hasAutoplay: true 99 | }); 100 | var time = 0; 101 | 102 | setTimeout(function() { 103 | player.once('timeupdate', function() { 104 | player.pause(); 105 | setTimeout(function() { 106 | time = player.getCurrentTime(); 107 | 108 | setTimeout(function() { 109 | assert.ok(player.getCurrentTime() == time, 'currentTime should\'t have changed'); 110 | player.destroy(); 111 | wrapper.remove(); 112 | assert.end(); 113 | }, 500); 114 | }, 500); 115 | }); 116 | }, 1500); 117 | player.cue(videoId); 118 | }); 119 | }; --------------------------------------------------------------------------------