├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── Gruntfile.js ├── LICENSE-Apache-2.0 ├── README.md ├── example.html ├── lib └── videojs-vlc.js ├── nbproject ├── project.properties └── project.xml ├── package.json └── test ├── index.html └── videojs-vlc.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | dist/ 5 | *~ 6 | nbproject/private/ 7 | build 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser" : true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "quotmark" : "single", 6 | "trailing" : true, 7 | "undef" : true, 8 | "predef" : [ 9 | "videojs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | test/ 3 | *~ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 7 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 8 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + 9 | ' Licensed <%= pkg.license %> */\n', 10 | clean: { 11 | files: ['dist'] 12 | }, 13 | concat: { 14 | options: { 15 | banner: '<%= banner %>', 16 | stripBanners: true 17 | }, 18 | dist: { 19 | src: 'lib/**/*.js', 20 | dest: 'dist/<%= pkg.name %>.js' 21 | } 22 | }, 23 | uglify: { 24 | options: { 25 | banner: '<%= banner %>' 26 | }, 27 | dist: { 28 | src: '<%= concat.dist.dest %>', 29 | dest: 'dist/<%= pkg.name %>.min.js' 30 | } 31 | }, 32 | qunit: { 33 | files: 'test/**/*.html' 34 | }, 35 | jshint: { 36 | gruntfile: { 37 | options: { 38 | node: true 39 | }, 40 | src: 'Gruntfile.js' 41 | }, 42 | src: { 43 | options: { 44 | jshintrc: '.jshintrc' 45 | }, 46 | src: ['lib/**/*.js'] 47 | }, 48 | test: { 49 | options: { 50 | jshintrc: '.jshintrc' 51 | }, 52 | src: ['test/**/*.js'] 53 | } 54 | }, 55 | watch: { 56 | gruntfile: { 57 | files: '<%= jshint.gruntfile.src %>', 58 | tasks: ['jshint:gruntfile'] 59 | }, 60 | src: { 61 | files: '<%= jshint.src.src %>', 62 | tasks: ['jshint:src', 'qunit'] 63 | }, 64 | test: { 65 | files: '<%= jshint.test.src %>', 66 | tasks: ['jshint:test', 'qunit'] 67 | } 68 | } 69 | }); 70 | 71 | grunt.loadNpmTasks('grunt-contrib-clean'); 72 | grunt.loadNpmTasks('grunt-contrib-concat'); 73 | grunt.loadNpmTasks('grunt-contrib-uglify'); 74 | grunt.loadNpmTasks('grunt-contrib-qunit'); 75 | grunt.loadNpmTasks('grunt-contrib-jshint'); 76 | grunt.loadNpmTasks('grunt-contrib-watch'); 77 | 78 | grunt.registerTask('default', 79 | ['clean', 80 | 'jshint', 81 | 'qunit', 82 | 'concat', 83 | 'uglify']); 84 | }; 85 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2015 Afterster 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video.js VLC 2 | 3 | Video.js VLC Tech plug-in 4 | 5 | A Video.js tech plugin that add VLC Media Player fallback. 6 | 7 | ## Getting Started 8 | 9 | VLC Web Plugin must be installed on the browser to work. 10 | 11 | Once you've added the plugin script to your page, you can use it with any supported video: 12 | * Include JavaScript files 13 | ```html 14 | 15 | 16 | ``` 17 | * And add this new tech to the player: 18 | ```html 19 | data-setup='{ "techOrder": ["vlc"] }' 20 | ``` 21 | 22 | There's also a [working example](example.html) of the plugin you can check out if you're having trouble. 23 | 24 | ## Documentation 25 | ### Plugin Options 26 | 27 | This plugin doesn't have any option. 28 | 29 | ### Supported content type 30 | 31 | Supported content type will depends on your VLC installation, but basically it should support almost all [audio](https://www.videolan.org/vlc/features.php?cat=audio) and [video](https://www.videolan.org/vlc/features.php?cat=video) formats. 32 | 33 | As this cannot be determined in advance, this plug-in always return success on file type support test. It's why you should always put it at the end of techOrder property. 34 | 35 | ## Build 36 | Building the plug-in is optional, you can directly use /lib/videojs-vlc.js file but it is good practice to be sure your environment is well configured. 37 | It will also simplify your test as you will be able to use /example.html sample file directly after build. 38 | 39 | * Install npm (node.js) 40 | * Install grunt: `npm install -g grunt` 41 | * Install project dependencies: `npm install` 42 | * Run grunt : `grunt` 43 | 44 | ## Release History 45 | 46 | - 0.2.0 47 | - Fix poster image 48 | - Fix fullscreen 49 | - Fix autoplay 50 | - 0.1.0 51 | - Initial release 52 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js VLC 6 | 7 | 8 | 9 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |

31 | You can see the Video.js VLC plugin in action below. 32 | Look at the source of this page to see how to use it with your videos. 33 |

34 |
35 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/videojs-vlc.js: -------------------------------------------------------------------------------- 1 | videojs.Vlc = videojs.MediaTechController.extend({ 2 | init: function (player, options, ready) { 3 | videojs.MediaTechController.call(this, player, options, ready); 4 | 5 | var source = options.source; 6 | 7 | // Generate ID for vlc object 8 | var objId = player.id() + '_vlc_api'; 9 | 10 | var self = this; 11 | this.player_ = player; 12 | // Merge default parames with ones passed in 13 | var params = videojs.util.mergeOptions({ 14 | 'animationatstart': 'true', 15 | 'transparentatstart': 'true', 16 | 'windowless': 'true', 17 | 'controls': 'false', 18 | 'bgcolor': '#000000', 19 | 'autostart': player.options().autoplay ? 'true' : 'false', 20 | 'allowfullscreen': 'true', 21 | 'text': 'Video.js VLC plug-in' 22 | }, options.params); 23 | 24 | if (source) { 25 | params.source = source.src; 26 | this.ready(function() { 27 | this.setSrc(source.src); 28 | if (player.options().autoplay) { 29 | this.play(); 30 | } 31 | }); 32 | } 33 | 34 | // Merge default attributes with ones passed in 35 | var attributes = videojs.util.mergeOptions({ 36 | 'id': objId, 37 | 'name': objId, // Both ID and Name needed or xap to identify itself 38 | 'class': 'vjs-tech' 39 | }, options.attributes); 40 | 41 | var parentEl = options.parentEl; 42 | var placeHolder = this.el_ = videojs.Component.prototype.createEl('div', {id: player.id() + 'temp_vlc'}); 43 | 44 | // Add placeholder to player div 45 | if (parentEl.firstChild) { 46 | parentEl.insertBefore(placeHolder, parentEl.firstChild); 47 | } else { 48 | parentEl.appendChild(placeHolder); 49 | } 50 | 51 | player.on('fullscreenchange', function() { 52 | // Workaround to force vmem to resize the video 53 | var pos = self.getApi().input.position; 54 | self.getApi().playlist.stop(); 55 | self.getApi().playlist.play(); 56 | self.getApi().input.position = pos; 57 | }); 58 | 59 | // Having issues with VLC reloading on certain page actions (hide/resize/fullscreen) in certain browsers 60 | // This allows resetting the playhead when we catch the reload 61 | if (options.startTime) { 62 | this.ready(function(){ 63 | this.load(); 64 | this.play(); 65 | this.currentTime(options.startTime); 66 | }); 67 | } 68 | 69 | player.ready(function() { 70 | //player.trigger('loadstart'); 71 | }); 72 | 73 | this.el_ = videojs.Vlc.embed(placeHolder, params, attributes); 74 | this.el_.tech = this; 75 | 76 | // Add VLC events 77 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerOpening', function() { 78 | player.trigger('loadstart'); 79 | }); 80 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerBuffering', function() { 81 | player.trigger('loadeddata'); 82 | 83 | // Notify video.js to refresh some data from VLC 84 | player.trigger('volumechange'); 85 | }); 86 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPlaying', function() { 87 | player.trigger('play'); 88 | player.trigger('playing'); 89 | }); 90 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPaused', function() { 91 | player.trigger('pause'); 92 | }); 93 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerStopped', function() { 94 | player.trigger('pause'); 95 | player.trigger('ended'); 96 | }); 97 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerEndReached', function() { 98 | player.trigger('pause'); 99 | player.trigger('ended'); 100 | }); 101 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerTimeChanged', function() { 102 | player.trigger('timeupdate'); 103 | }); 104 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPositionChanged', function() { 105 | player.trigger('progress'); 106 | }); 107 | videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerLengthChanged', function() { 108 | player.trigger('durationchange'); 109 | }); 110 | 111 | // VLC plug-in doesn't have 'ready' event. We assume it is ready after few milliseconds 112 | setTimeout(function() { 113 | self.triggerReady(); 114 | }, 100); 115 | } 116 | }); 117 | 118 | videojs.Vlc.prototype.params = []; 119 | 120 | videojs.Vlc.prototype.dispose = function () { 121 | if (this.el_) { 122 | this.el_.parentNode.removeChild(this.el_); 123 | } 124 | 125 | videojs.MediaTechController.prototype.dispose.call(this); 126 | }; 127 | 128 | videojs.Vlc.prototype.src = function (src) { 129 | if (src === undefined) { 130 | return this.currentSrc(); 131 | } 132 | 133 | // Setting src through `src` not `setSrc` will be deprecated 134 | return this.setSrc(src); 135 | }; 136 | 137 | videojs.Vlc.prototype.setSrc = function(src){ 138 | src = videojs.Vlc.getAbsoluteURL(src); 139 | this.getApi().playlist.items.clear(); 140 | this.getApi().playlist.add(src); 141 | }; 142 | 143 | videojs.Vlc.prototype.currentSrc = function() { 144 | if (this.currentSource_) { 145 | return this.currentSource_.src; 146 | } 147 | else { 148 | return this.getApi().playlist.items[this.getApi().playlist.currentItem]; 149 | } 150 | }; 151 | 152 | videojs.Vlc.prototype.load = function() { 153 | // Done automatically 154 | }; 155 | 156 | videojs.Vlc.prototype.poster = function(){ 157 | this.el_.vjs_getProperty('poster'); 158 | }; 159 | 160 | videojs.Vlc.prototype.setPoster = function(){ 161 | // poster images are not handled by the VLC tech so make this a no-op 162 | }; 163 | 164 | videojs.Vlc.prototype.play = function() { 165 | this.getApi().playlist.play(); 166 | }; 167 | 168 | videojs.Vlc.prototype.ended = function() { 169 | var state = this.getApi().input.state; 170 | return (state === 6 /* ENDED */ || state === 7 /* ERROR */); 171 | }; 172 | 173 | videojs.Vlc.prototype.pause = function() { 174 | this.getApi().playlist.pause(); 175 | }; 176 | 177 | videojs.Vlc.prototype.paused = function() { 178 | var state = this.getApi().input.state; 179 | return (state === 4 /* PAUSED */ || state === 6 /* ENDED */); 180 | }; 181 | 182 | videojs.Vlc.prototype.currentTime = function() { 183 | return (this.getApi().input.time / 1000); 184 | }; 185 | 186 | videojs.Vlc.prototype.setCurrentTime = function(seconds) { 187 | this.getApi().input.time = (seconds * 1000); 188 | }; 189 | 190 | videojs.Vlc.prototype.duration = function () { 191 | return (this.getApi().input.length / 1000); 192 | }; 193 | 194 | videojs.Vlc.prototype.buffered = function () { 195 | // Not supported 196 | return []; 197 | }; 198 | 199 | videojs.Vlc.prototype.volume = function () { 200 | return this.getApi().audio.volume / 100; 201 | }; 202 | 203 | videojs.Vlc.prototype.setVolume = function (percentAsDecimal) { 204 | if (percentAsDecimal) { 205 | this.getApi().audio.volume = percentAsDecimal * 100; 206 | } 207 | }; 208 | 209 | videojs.Vlc.prototype.muted = function () { 210 | return this.getApi().audio.mute; 211 | }; 212 | videojs.Vlc.prototype.setMuted = function (muted) { 213 | this.getApi().audio.mute.mute = muted; 214 | }; 215 | 216 | videojs.Vlc.prototype.supportsFullScreen = function () { 217 | return true; 218 | }; 219 | 220 | videojs.Vlc.prototype.enterFullScreen = function(){ 221 | this.getApi().video.fullscreen = true; 222 | this.player_.trigger('fullscreenchange'); 223 | }; 224 | 225 | videojs.Vlc.prototype.exitFullScreen = function(){ 226 | this.getApi().video.fullscreen = false; 227 | this.player_.trigger('fullscreenchange'); 228 | }; 229 | 230 | videojs.Vlc.prototype.getApi = function() { 231 | return this.el_; 232 | }; 233 | 234 | videojs.Vlc.registerEvent = function(vlc, event, handler) { 235 | if (vlc) { 236 | if (vlc.attachEvent) { 237 | // Microsoft 238 | vlc.attachEvent (event, handler); 239 | } else if (vlc.addEventListener) { 240 | // Mozilla: DOM level 2 241 | vlc.addEventListener(event, handler, false); 242 | } else { 243 | // DOM level 0 244 | vlc['on' + event] = handler; 245 | } 246 | } 247 | }; 248 | 249 | videojs.Vlc.unregisterEvent = function(vlc, event, handler) { 250 | if (vlc) { 251 | if (vlc.detachEvent) { 252 | // Microsoft 253 | vlc.detachEvent (event, handler); 254 | } else if (vlc.removeEventListener) { 255 | // Mozilla: DOM level 2 256 | vlc.removeEventListener(event, handler, false); 257 | } else { 258 | // DOM level 0 259 | vlc['on' + event] = null; 260 | } 261 | } 262 | }; 263 | 264 | videojs.Vlc.embed = function (placeHolder, params, attributes) { 265 | var code = videojs.Vlc.getEmbedCode(params, attributes); 266 | // Get element by embedding code and retrieving created element 267 | var obj = videojs.Component.prototype.createEl('div', { innerHTML: code }).childNodes[0]; 268 | var par = placeHolder.parentNode; 269 | 270 | placeHolder.parentNode.replaceChild(obj, placeHolder); 271 | 272 | return obj; 273 | }; 274 | 275 | videojs.Vlc.getEmbedCode = function(params, attributes) { 276 | 277 | var objTag, 278 | key, 279 | paramsString = '', 280 | attrsString = ''; 281 | 282 | if(window.ActiveXObject) { 283 | objTag = ''; 296 | } 297 | 298 | // Create Attributes string 299 | for (key in attributes) { 300 | attrsString += (key + '="' + attributes[key] + '" '); 301 | } 302 | 303 | return objTag + attrsString + '>' + paramsString + ''; 304 | } else { 305 | objTag = ''; 314 | } 315 | }; 316 | 317 | videojs.Vlc.getAbsoluteURL = function(url){ 318 | // Check if absolute URL 319 | if (!url.match(/^https?:\/\//)) { 320 | // Convert to absolute URL. 321 | url = videojs.Component.prototype.createEl('div', { 322 | innerHTML: 'x' 323 | }).firstChild.href; 324 | } 325 | return url; 326 | }; 327 | 328 | /* Vlc Support Testing */ 329 | 330 | videojs.Vlc.isSupported = function () { 331 | var vlc; 332 | 333 | if(window.ActiveXObject) { 334 | try { 335 | vlc = new window.ActiveXObject('VideoLAN.VLCPlugin.2'); 336 | } catch(e) {} 337 | } 338 | else if(navigator.plugins && navigator.mimeTypes.length > 0) { 339 | var name = 'VLC'; 340 | if (navigator.plugins && (navigator.plugins.length > 0)) { 341 | for(var i=0;i always accept to play a source 374 | return 'maybe'; 375 | }; -------------------------------------------------------------------------------- /nbproject/project.properties: -------------------------------------------------------------------------------- 1 | file.reference.dev-videojs-vlc=. 2 | files.encoding=UTF-8 3 | site.root.folder=${file.reference.dev-videojs-vlc} 4 | -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.web.clientproject 4 | 5 | 6 | videojs-vlc 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-vlc", 3 | "version": "0.2.0", 4 | "author": "Afterster", 5 | "description": "Video.js VLC Tech plug-in", 6 | "license": "Apache-2.0", 7 | "readmeFilename": "README.md", 8 | "bugs": { 9 | "url": "https://github.com/Afterster/videojs-vlc/issues" 10 | }, 11 | "homepage": "https://github.com/Afterster/videojs-vlc", 12 | "keywords": [ 13 | "videojs", 14 | "vlc", 15 | "audio", 16 | "video", 17 | "player" 18 | ], 19 | "repository" : { 20 | "type": "git", 21 | "url": "https://github.com/Afterster/videojs-vlc.git" 22 | }, 23 | "dependencies": {}, 24 | "devDependencies": { 25 | "grunt": "^0.4", 26 | "grunt-contrib-clean": "^0.6", 27 | "grunt-contrib-concat": "^0.5", 28 | "grunt-contrib-jshint": "^0.11", 29 | "grunt-contrib-qunit": "^0.7", 30 | "grunt-contrib-uglify": "^0.9", 31 | "grunt-contrib-watch": "^0.6", 32 | "video.js": "^4.12", 33 | "qunitjs": "^1.18" 34 | }, 35 | "peerDependencies": { 36 | "video.js": "^4.12" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js VLC 6 | 7 | 8 | 9 |
10 |
11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/videojs-vlc.test.js: -------------------------------------------------------------------------------- 1 | /*! videojs-vlc - v0.0.0 - 2015-1-10 2 | * Copyright (c) 2015 Afterster 3 | * Licensed under the Apache-2.0 license. */ 4 | (function(window, videojs, qunit) { 5 | 'use strict'; 6 | 7 | var realIsHtmlSupported, 8 | player, 9 | 10 | // local QUnit aliases 11 | // http://api.qunitjs.com/ 12 | 13 | // module(name, {[setup][ ,teardown]}) 14 | module = qunit.module, 15 | // test(name, callback) 16 | test = qunit.test, 17 | // ok(value, [message]) 18 | ok = qunit.ok, 19 | // equal(actual, expected, [message]) 20 | equal = qunit.equal, 21 | // strictEqual(actual, expected, [message]) 22 | strictEqual = qunit.strictEqual, 23 | // deepEqual(actual, expected, [message]) 24 | deepEqual = qunit.deepEqual, 25 | // notEqual(actual, expected, [message]) 26 | notEqual = qunit.notEqual, 27 | // throws(block, [expected], [message]) 28 | throws = qunit.throws; 29 | 30 | module('videojs-vlc', { 31 | setup: function() { 32 | // force HTML support so the tests run in a reasonable 33 | // environment under phantomjs 34 | realIsHtmlSupported = videojs.Html5.isSupported; 35 | videojs.Html5.isSupported = function() { 36 | return true; 37 | }; 38 | 39 | // create a video element 40 | var video = document.createElement('video'); 41 | document.querySelector('#qunit-fixture').appendChild(video); 42 | 43 | // create a video.js player 44 | player = videojs(video); 45 | 46 | // initialize the plugin with the default options 47 | //player.vlc(); 48 | }, 49 | teardown: function() { 50 | videojs.Html5.isSupported = realIsHtmlSupported; 51 | } 52 | }); 53 | 54 | test('registers itself', function() { 55 | ok(player, 'registered the plugin'); 56 | }); 57 | })(window, window.videojs, window.QUnit); 58 | --------------------------------------------------------------------------------