├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── index.js ├── lib ├── html5.js └── youtube.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | .npmignore 10 | LICENSE.md -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2015 Nick Poisson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # video-element 2 | 3 | [![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) 4 | 5 | A simple HTML5/YouTube Video Element with a unified interface 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i video-element 11 | ``` 12 | 13 | ## Usage 14 | 15 | [![NPM](https://nodei.co/npm/video-element.png)](https://www.npmjs.com/package/video-element) 16 | 17 | The constructor can be used with or without the "new" keyword 18 | 19 | ```bash 20 | var Video = require('video-element'); 21 | var myVid = new Video(options,callback); 22 | ``` 23 | 24 | OR 25 | 26 | ```bash 27 | var Video = require('video-element'); 28 | Video(options,function(error,player) { 29 | if (!error) { 30 | // use player 31 | } 32 | }); 33 | ``` 34 | 35 | The options parameter accepts these properties: 36 | 37 | ```bash 38 | type: 'youtube', // defaults to html5 39 | url: 'videos/myVideo.mp4', // or just the youtube ID if using youtube 40 | formats: ['mp4','webm','ogg'] // The list of available video file format 41 | // it will pick the best one for the browser 42 | el: 'vid2', // The dom element to add the player to, 43 | // this can be left blank and you can use appendTo later 44 | width: '100%', 45 | height: '100%', 46 | parameters: { // Any youtube supported parameters can be used here 47 | controls: 1, 48 | autoplay: 0, 49 | loop: 0, 50 | autohide: 2, 51 | showinfo: 0 52 | }, 53 | preload: true, 54 | poster: '', 55 | muted: false 56 | ``` 57 | 58 | You can find more information about the supported params in their values in [https://developers.google.com/youtube/player_parameters](https://developers.google.com/youtube/player_parameters). 59 | 60 | The callback parameter returns an error as the first paramenter, and the player object as the second. If there is an error, the player will be undefined, if not, the error will be undefined. 61 | 62 | #### Signals 63 | 64 | The player implements signals for its event interface, these are the available signals: 65 | 66 | ```bash 67 | onInit: player has been created 68 | onReady: player is ready to play 69 | onPlay: player has started playing 70 | onPause: player is paused 71 | onEnd: player has reached the end of the video 72 | onProgress: dispatched on a timer while the video is playing, useful for tracking time/duration/load 73 | onBuffering: player is buffering more video 74 | onError: player has encountered an error 75 | ``` 76 | 77 | #### `player.play()` 78 | 79 | Plays the video 80 | 81 | #### `player.pause()` 82 | 83 | Pauses the video 84 | 85 | #### `player.appendTo(dom)` 86 | 87 | Adds the player to a dom element, if a string, it will assume its an ID and use document.getElementByID. 88 | 89 | #### `player.destroy()` 90 | 91 | Destroys the video and removes it from the dom 92 | 93 | ## License 94 | 95 | MIT, see [LICENSE.md](http://github.com/jam3/video-element/blob/master/LICENSE.md) for details. 96 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Class = require('js-oop'); 2 | var Signal = require('signals').Signal; 3 | var on = require('dom-event'); 4 | 5 | var Video = new Class({ 6 | currentTime: { 7 | get: function() { 8 | return (this.isYoutube) ? this.player.getCurrentTime() : this.player.currentTime; 9 | }, 10 | set: function(value) { 11 | (this.isYoutube) ? this.player.seekTo(value) : this.player.currentTime = value; 12 | } 13 | }, 14 | volume: { 15 | get: function() { 16 | return (this.isYoutube) ? this.player.getVolume() : this.player.volume; 17 | }, 18 | set: function(value) { 19 | (this.isYoutube) ? this.player.setVolume(value) : this.player.volume = value; 20 | } 21 | }, 22 | muted: { 23 | get: function() { 24 | return (this.isYoutube) ? this.player.isMuted() : this.player.muted; 25 | }, 26 | set: function(value) { 27 | if (this.isYoutube) { 28 | (value) ? this.player.mute() : this.player.unMute(); 29 | } else { 30 | this.player.muted = value; 31 | } 32 | } 33 | }, 34 | playbackRate: { 35 | get: function() { 36 | return (this.isYoutube) ? this.player.getPlaybackRate() : this.player.playbackRate; 37 | }, 38 | set: function(value) { 39 | (this.isYoutube) ? this.player.setPlaybackRate(value) : this.player.playbackRate = value; 40 | } 41 | }, 42 | currentSrc: { 43 | get: function() { 44 | return (this.isYoutube) ? this.player.getVideoURL() : this.player.currentSrc; 45 | } 46 | }, 47 | width: { 48 | get: function() { 49 | return this.options.width; 50 | }, 51 | set: function(value) { 52 | this.options.width = value; 53 | ((this.isYoutube) ? this.player.getIframe() : this.player).setAttribute('width',this.options.width); 54 | } 55 | }, 56 | height: { 57 | get: function() { 58 | return this.options.height; 59 | }, 60 | set: function(value) { 61 | this.options.height = value; 62 | ((this.isYoutube) ? this.player.getIframe() : this.player).setAttribute('height',this.options.height); 63 | } 64 | }, 65 | videoWidth: { 66 | get: function() { 67 | return (this.isYoutube) ? this.options.width : this.player.videoWidth; 68 | } 69 | }, 70 | videoHeight: { 71 | get: function() { 72 | return (this.isYoutube) ? this.options.height : this.player.videoHeight; 73 | } 74 | }, 75 | duration: { 76 | get: function() { 77 | return this._duration; 78 | } 79 | }, 80 | loaded: { 81 | get: function() { 82 | return this._loaded; 83 | } 84 | }, 85 | _duration: 0, 86 | _loaded: 0, 87 | initialize: function(options,callback) { 88 | if (!(this instanceof Video)) return new Video(options,callback); 89 | options.width = options.width || 'auto'; 90 | options.height = options.height || 'auto'; 91 | 92 | // Signals 93 | this.onInit = new Signal(); 94 | this.onReady = new Signal(); 95 | this.onPlay = new Signal(); 96 | this.onPause = new Signal(); 97 | this.onEnd = new Signal(); 98 | this.onProgress = new Signal(); 99 | this.onBuffering = new Signal(); 100 | this.onError = new Signal(); 101 | 102 | this.options = options; 103 | this.callback = callback; 104 | this.isYoutube = (options.type=='youtube'); 105 | options.el = (typeof(options.el)=='string') ? document.getElementById(options.el) : options.el; 106 | if (this.isYoutube) { 107 | require('./lib/youtube')(options.url,options,function(error,player) { 108 | if (!error) { 109 | this.player = player; 110 | this._ready = this._ready.bind(this); 111 | this._checkYoutubeState = this._checkYoutubeState.bind(this); 112 | this._checkYoutubeError = this._checkYoutubeError.bind(this); 113 | this.player.addEventListener('onReady',this._ready); 114 | this.player.addEventListener('onStateChange',this._checkYoutubeState); 115 | this.player.addEventListener('onError',this._checkYoutubeError); 116 | this.onInit.dispatch(); 117 | if (this.callback) this.callback(undefined,this); 118 | } else { 119 | this.onError.dispatch(error.message); 120 | if (this.callback) this.callback(error); 121 | } 122 | }.bind(this)); 123 | } else { 124 | require('./lib/html5.js')(options.url,options,function(error,player) { 125 | if (!error) { 126 | this.player = player; 127 | this._checkHTML5State = this._checkHTML5State.bind(this); 128 | this._checkHTML5Error = this._checkHTML5Error.bind(this); 129 | on(this.player,'error',this._checkHTML5Error); 130 | on(this.player,'canplay',this._checkHTML5State); 131 | on(this.player,'ended',this._checkHTML5State); 132 | on(this.player,'play',this._checkHTML5State); 133 | on(this.player,'pause',this._checkHTML5State); 134 | on(this.player,'waiting',this._checkHTML5State); 135 | if (options.el) options.el.appendChild(this.player); 136 | this.onInit.dispatch(); 137 | if (this.callback) this.callback(undefined,this); 138 | } else { 139 | this.onError.dispatch(error.message); 140 | if (this.callback) this.callback(error); 141 | } 142 | }.bind(this)); 143 | } 144 | }, 145 | _checkYoutubeState: function(e) { 146 | this.playing = false; 147 | switch (e.data) { 148 | case this.player.api.PlayerState.CUED: 149 | // this._ready(); 150 | break; 151 | case this.player.api.PlayerState.ENDED: 152 | this.onEnd.dispatch(); 153 | break; 154 | case this.player.api.PlayerState.PLAYING: 155 | this.playing = true; 156 | this.onPlay.dispatch(); 157 | break; 158 | case this.player.api.PlayerState.PAUSED: 159 | this.onPause.dispatch(); 160 | break; 161 | case this.player.api.PlayerState.BUFFERING: 162 | this.onBuffering.dispatch(); 163 | break; 164 | } 165 | }, 166 | _checkYoutubeError: function(e) { 167 | switch (e.data) { 168 | case 2: 169 | this.onError.dispatch('Invalid YouTube Parameter'); 170 | break; 171 | case 100: 172 | this.onError.dispatch('YouTube Video Not Found'); 173 | break; 174 | case 101: 175 | case 150: 176 | this.onError.dispatch('Cannot Embed YouTube Player'); 177 | break; 178 | } 179 | }, 180 | _checkHTML5Error: function(e) { 181 | switch (e.target.error.code) { 182 | case e.target.error.MEDIA_ERR_ABORTED: 183 | this.onError.dispatch('You aborted the video playback.'); 184 | break; 185 | case e.target.error.MEDIA_ERR_NETWORK: 186 | this.onError.dispatch('A network error caused the video download to fail part-way.'); 187 | break; 188 | case e.target.error.MEDIA_ERR_DECODE: 189 | this.onError.dispatch('The video playback was aborted due to a corruption problem or because the video used features your browser did not support.'); 190 | break; 191 | case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED: 192 | this.onError.dispatch('The video could not be loaded, either because the server or network failed or because the format is not supported.'); 193 | break; 194 | default: 195 | this.onError.dispatch('An unknown error occurred.'); 196 | break; 197 | } 198 | }, 199 | _checkHTML5State: function(e) { 200 | this.playing = false; 201 | switch (e.type) { 202 | case "canplay": 203 | this._ready(); 204 | break; 205 | case "ended": 206 | this.onEnd.dispatch(); 207 | break; 208 | case "play": 209 | this.playing = true; 210 | this.onPlay.dispatch(); 211 | break; 212 | case "pause": 213 | this.onPause.dispatch(); 214 | break; 215 | case "waiting": 216 | this.onBuffering.dispatch(); 217 | break; 218 | } 219 | }, 220 | _ready: function() { 221 | if (!this._readySent) { 222 | if (this.options.type=='youtube' && this.options.muted) this.player.mute(); 223 | this._readySent = true; 224 | this.onReady.dispatch(); 225 | this.tick = setInterval(this._check.bind(this),50); 226 | } 227 | }, 228 | _check: function() { 229 | if (this._duration<=0) this._duration = this.player.getDuration(); 230 | this._loaded = this.player.getVideoLoadedFraction(); 231 | if (this.playing) this.onProgress.dispatch(); 232 | }, 233 | play: function() { 234 | if (this._readySent && !this.playing) { 235 | (this.isYoutube) ? this.player.playVideo() : this.player.play(); 236 | } 237 | }, 238 | pause: function() { 239 | if (this._readySent && this.playing) { 240 | (this.isYoutube) ? this.player.pauseVideo() : this.player.pause(); 241 | } 242 | }, 243 | appendTo: function(dom) { 244 | this.options.el = (typeof(dom)=='string') ? document.getElementById(dom) : dom; 245 | this.options.el.appendChild(this.player); 246 | }, 247 | destroy: function() { 248 | if (this.isYoutube) { 249 | if (this.player.removeEventListener) { 250 | this.player.removeEventListener('onStateChange',this._checkYoutubeState); 251 | this.player.removeEventListener('onError',this._checkYoutubeError); 252 | } else { 253 | this._checkYoutubeState = function() {}; 254 | this._checkYoutubeError = function() {}; 255 | } 256 | this.player.destroy(); 257 | } else { 258 | on.off(this.player,'statechange',this.checkHTML5State); 259 | on.off(this.player,'error',this._checkHTML5Error); 260 | this.player.source.setAttribute('src',''); 261 | this.player.removeChild(this.player.source); 262 | this.player.source = null; 263 | this.player.load(); 264 | this.options.el.removeChild(this.player); 265 | } 266 | } 267 | }); 268 | 269 | module.exports = Video; -------------------------------------------------------------------------------- /lib/html5.js: -------------------------------------------------------------------------------- 1 | module.exports = function(url, options, callback) { 2 | var player = document.createElement('video'); 3 | options.formats = options.formats || ['mp4','webm','ogg']; 4 | var check,format; 5 | for (var i=0, len=options.formats.length; i 0 && this.buffered.end && this.duration) { 24 | return (this.buffered.end(0)/this.duration); 25 | } else if (this.bytesTotal !== undefined && this.bytesTotal > 0 && this.bufferedBytes !== undefined) { 26 | return this.bufferedBytes / this.bytesTotal; 27 | } else { 28 | return 0; 29 | } 30 | }; 31 | player.getDuration = function() { 32 | return this.duration; 33 | }; 34 | player.source = document.createElement('source'); 35 | player.source.setAttribute('type','video/'+format); 36 | player.source.setAttribute('src',url.substr(0,url.lastIndexOf('.')+1)+format); 37 | player.appendChild(player.source); 38 | callback(undefined,player); 39 | } else { 40 | callback(new Error('HTML5 Video not supported')); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /lib/youtube.js: -------------------------------------------------------------------------------- 1 | var sdk = require('require-sdk')('https://www.youtube.com/iframe_api', 'YT'); 2 | var loaded = sdk.trigger(); 3 | 4 | window.onYouTubeIframeAPIReady = function () { 5 | loaded(); 6 | delete window.onYouTubeIframeAPIReady; 7 | }; 8 | 9 | module.exports = function(url, options, callback) { 10 | sdk(function (error, youtube) { 11 | var id = options.id || 'yt-player'; 12 | options.el.innerHTML = '
'; 13 | 14 | var paramsVars = options.parameters; 15 | 16 | var player = new youtube.Player(id, { 17 | videoId: url, 18 | height: options.height, 19 | width: options.width, 20 | playerVars: paramsVars 21 | }); 22 | player.api = youtube; 23 | callback(undefined,player); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "video-element", 3 | "version": "1.0.3", 4 | "description": "A simple HTML5/YouTube Video Element with a unified interface", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "scripts": { 10 | "test": "budo test/index.js --open" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/Jam3/video-element.git" 15 | }, 16 | "keywords": [ 17 | "video", 18 | "html5", 19 | "youtube", 20 | "dom" 21 | ], 22 | "author": { 23 | "name": "Nick Poisson", 24 | "email": "nick@jam3.com", 25 | "url": "https://github.com/njam3" 26 | }, 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/Jam3/video-element/issues" 30 | }, 31 | "homepage": "https://github.com/Jam3/video-element", 32 | "dependencies": { 33 | "dom-event": "0.0.5", 34 | "js-oop": "^1.0.0", 35 | "require-sdk": "github:Jam3/require-sdk", 36 | "signals": "^1.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var Video = require('../index'); 2 | 3 | var div = document.createElement('div'); 4 | div.id = 'youtube-video'; 5 | document.body.appendChild(div); 6 | 7 | var myVid = new Video({ 8 | type: 'youtube', 9 | url:'XZmGGAbHqa0', 10 | el:'youtube-video', 11 | width: 1000, 12 | height: 600, 13 | parameters: { 14 | controls: 0, 15 | autoplay: 0, 16 | loop: 0, 17 | showinfo: 0 18 | }, 19 | muted: false 20 | },function(error, player){ 21 | if(!error){ 22 | this.youtubePlayer = player; 23 | } 24 | }.bind(this)); --------------------------------------------------------------------------------