├── .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 |
--------------------------------------------------------------------------------