diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e717f5e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c09026f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +*.log +node_modules/ +dist/ +*~ +nbproject/private/ +build diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..9141dd6 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,11 @@ +{ + "browser" : true, + "curly": true, + "eqeqeq": true, + "quotmark" : "single", + "trailing" : true, + "undef" : true, + "predef" : [ + "videojs" + ] +} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..6c17f82 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +dist/ +test/ +*~ \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..89a9142 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,84 @@ +'use strict'; + +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + + ' Licensed <%= pkg.license %> */\n', + clean: { + files: ['dist'] + }, + concat: { + options: { + banner: '<%= banner %>', + stripBanners: true + }, + dist: { + src: 'lib/**/*.js', + dest: 'dist/<%= pkg.name %>.js' + } + }, + uglify: { + options: { + banner: '<%= banner %>' + }, + dist: { + src: '<%= concat.dist.dest %>', + dest: 'dist/<%= pkg.name %>.min.js' + } + }, + qunit: { + files: 'test/**/*.html' + }, + jshint: { + gruntfile: { + options: { + node: true + }, + src: 'Gruntfile.js' + }, + src: { + options: { + jshintrc: '.jshintrc' + }, + src: ['lib/**/*.js'] + }, + test: { + options: { + jshintrc: '.jshintrc' + }, + src: ['test/**/*.js'] + } + }, + watch: { + gruntfile: { + files: '<%= jshint.gruntfile.src %>', + tasks: ['jshint:gruntfile'] + }, + src: { + files: '<%= jshint.src.src %>', + tasks: ['jshint:src', 'qunit'] + }, + test: { + files: '<%= jshint.test.src %>', + tasks: ['jshint:test', 'qunit'] + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-qunit'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + + grunt.registerTask('default', + ['clean', + 'jshint', + 'qunit', + 'concat', + 'uglify']); +}; diff --git a/LICENSE-Apache-2.0 b/LICENSE-Apache-2.0 new file mode 100644 index 0000000..6d480e1 --- /dev/null +++ b/LICENSE-Apache-2.0 @@ -0,0 +1,13 @@ +Copyright 2015 Afterster + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ae75cc --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Video.js VLC + +Video.js VLC Tech plug-in + +A Video.js tech plugin that add VLC fallback. + +## Getting Started + +Once you've added the plugin script to your page, you can use it with any supported video: + * Include JavaScript files +```html + + +``` + * And add this new tech to the player: +```html +data-setup='{ "techOrder": ["vlc"] }' +``` + +There's also a [working example](example.html) of the plugin you can check out if you're having trouble. + +## Documentation +### Plugin Options + +This plugin doesn't have any option. + +### Supported content type + +Supported content type will depends on your VLC installation, but potentially it should support the following list: + * video/mp4(.mp4) + * video/mp4(.m4v) + * video/ogg(.ogv) + * video/webm(.webm) + * video/ogg(.ogg) + * video/ogg(.anx) + * audio/ogg(.ogg) + * audio/ogg(.oga) + * audio/mp3(.mp3) + * audio/mpeg(.mp3) + * application/mpegURL(.m3u8) + * application/mpegURL(.m3u) + * application/vnd.apple.mpegURL(.m3u8) + * application/vnd.apple.mpegURL(.m3u) + * application/x-mpegURL(.m3u8) + * application/x-mpegURL(.m3u) + * application/mpegURL(.m3u8) + * application/mpegURL(.m3u) + * application/vnd.apple.mpegURL(.m3u8) + * application/vnd.apple.mpegURL(.m3u) + * application/x-mpegURL(.m3u8) + * application/x-mpegURL(.m3u) + * audio/mpegURL(.m3u8) + * audio/mpegURL(.m3u) + * text/json(.json) + * text/jsonp(.jsonp) + * text/xml(.xml) + * application/json(.json) + * application/jsonp(.jsonp) + * application/xml(.xml) + * image/jpeg(.jpg) + * image/gif(.gif) + * image/png(.png) + * text/html(.html) + * video/flv(.flv) + * video/mp4(.mp4) + * video/mp4(.f4v) + * video/quicktime(.mov) + * video/mp4(.m4v) + * application/f4m+xml(.f4m) + * application/mpegURL(.m3u8) + * application/x-mpegURL(.m3u8) + * application/vnd.apple.mpegurl(.m3u8) + * application/vnd.ms-ss(.manifest) + * video/flv(.flv) + * video/mp4(.mp4) + * video/mp4(.f4v) + * video/quicktime(.mov) + * video/mp4(.m4v) + * application/f4m+xml(.f4m) + * audio/mp3(.mp3) + * audio/mp4(.m4a) + * audio/mpeg(.m4a) + + 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. + +## Release History + + - 0.1.0: Initial release diff --git a/example.html b/example.html new file mode 100644 index 0000000..d5bc4b4 --- /dev/null +++ b/example.html @@ -0,0 +1,48 @@ + + + + + Video.js VLC + + + + + + + + + +
+

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

+
+ + + diff --git a/lib/videojs-vlc.js b/lib/videojs-vlc.js new file mode 100644 index 0000000..abac4d5 --- /dev/null +++ b/lib/videojs-vlc.js @@ -0,0 +1,348 @@ +/*! videojs-vlc - v0.1.0 - 2015-1-10 + * Video.js VLC Tech plug-in. + * Licensed under the Apache-2.0 license. */ + +videojs.Vlc = videojs.MediaTechController.extend({ + init: function (player, options, ready) { + videojs.MediaTechController.call(this, player, options, ready); + + var source = options.source; + + // Generate ID for vlc object + var objId = player.id() + '_vlc_api'; + + var self = this; + this.player_ = player; + + // Merge default parames with ones passed in + var params = videojs.util.mergeOptions({ + 'animationatstart': 'true', + 'transparentatstart': 'true', + 'windowless': 'true', + 'controls': 'false', + 'bgcolor': '#000000', + 'autostart': player.options.autoplay ? 'true' : 'false', + 'allowfullscreen': 'true', + 'text': 'Video.js VLC plug-in' + }, options.params); + + if (source) { + params.source = source.src; + this.ready(function() { + this.setSrc(source.src); + }); + } + + // Merge default attributes with ones passed in + var attributes = videojs.util.mergeOptions({ + 'id': objId, + 'name': objId, // Both ID and Name needed or xap to identify itself + 'class': 'vjs-tech' + }, options.attributes); + + var parentEl = options.parentEl; + var placeHolder = this.el_ = videojs.Component.prototype.createEl('div', {id: player.id() + 'temp_vlc'}); + + // Add placeholder to player div + if (parentEl.firstChild) { + parentEl.insertBefore(placeHolder, parentEl.firstChild); + } else { + parentEl.appendChild(placeHolder); + } + + // Having issues with VLC reloading on certain page actions (hide/resize/fullscreen) in certain browsers + // This allows resetting the playhead when we catch the reload + if (options.startTime) { + this.ready(function(){ + this.load(); + this.play(); + this.currentTime(options.startTime); + }); + } + + player.ready(function() { + //player.trigger('loadstart'); + }); + + this.el_ = videojs.Vlc.embed(placeHolder, params, attributes); + this.el_.tech = this; + + // Add VLC events + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerOpening', function() { + player.trigger('loadstart'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerBuffering', function() { + player.trigger('loadeddata'); + + // Notify video.js to refresh some data from VLC + player.trigger('volumechange'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPlaying', function() { + player.trigger('play'); + player.trigger('playing'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPaused', function() { + player.trigger('pause'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerStopped', function() { + player.trigger('pause'); + player.trigger('ended'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerEndReached', function() { + player.trigger('pause'); + player.trigger('ended'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerTimeChanged', function() { + player.trigger('timeupdate'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerPositionChanged', function() { + player.trigger('progress'); + }); + videojs.Vlc.registerEvent(this.getApi(), 'MediaPlayerLengthChanged', function() { + player.trigger('durationchange'); + }); + + // VLC plug-in doesn't have 'ready' event. We assume it is ready after few milliseconds + setTimeout(function() { + self.triggerReady(); + }, 100); + } +}); + +videojs.Vlc.prototype.params = []; + +videojs.Vlc.prototype.dispose = function () { + if (this.el_) { + this.el_.parentNode.removeChild(this.el_); + } + + videojs.MediaTechController.prototype.dispose.call(this); +}; + +videojs.Vlc.prototype.src = function (src) { + if (src === undefined) { + return this.currentSrc(); + } + + // Setting src through `src` not `setSrc` will be deprecated + return this.setSrc(src); +}; + +videojs.Vlc.prototype.setSrc = function(src){ + src = videojs.Vlc.getAbsoluteURL(src); + this.getApi().playlist.items.clear(); + this.getApi().playlist.add(src); +}; + +videojs.Vlc.prototype.currentSrc = function() { + if (this.currentSource_) { + return this.currentSource_.src; + } + else { + return this.getApi().playlist.items[this.getApi().playlist.currentItem]; + } +}; + +videojs.Vlc.prototype.load = function() { + // Done automatically +}; + +videojs.Vlc.prototype.play = function() { + this.getApi().playlist.play(); +}; + +videojs.Vlc.prototype.ended = function() { + var state = this.getApi().input.state; + return (state === 6 /* ENDED */ || state === 7 /* ERROR */); +}; + +videojs.Vlc.prototype.pause = function() { + this.getApi().playlist.pause(); +}; + +videojs.Vlc.prototype.paused = function() { + var state = this.getApi().input.state; + return (state === 4 /* PAUSED */ || state === 6 /* ENDED */); +}; + +videojs.Vlc.prototype.currentTime = function() { + return (this.getApi().input.time / 1000); +}; + +videojs.Vlc.prototype.setCurrentTime = function(seconds) { + this.getApi().input.time = (seconds * 1000); +}; + +videojs.Vlc.prototype.duration = function () { + return (this.getApi().input.length / 1000); +}; + +videojs.Vlc.prototype.buffered = function () { + // Not supported + return []; +}; + +videojs.Vlc.prototype.volume = function () { + return this.getApi().audio.volume / 100; +}; + +videojs.Vlc.prototype.setVolume = function (percentAsDecimal) { + if (percentAsDecimal) { + this.getApi().audio.volume = percentAsDecimal * 100; + } +}; + +videojs.Vlc.prototype.muted = function () { + return this.getApi().audio.mute; +}; +videojs.Vlc.prototype.setMuted = function (muted) { + this.getApi().audio.mute.mute = muted; +}; + +videojs.Vlc.prototype.supportsFullScreen = function () { + return true; +}; + +videojs.Vlc.prototype.enterFullScreen = function(){ + this.getApi().video.fullscreen = true; + this.player_.trigger('fullscreenchange'); +}; + +videojs.Vlc.prototype.exitFullScreen = function(){ + this.getApi().video.fullscreen = false; + this.player_.trigger('fullscreenchange'); +}; + +videojs.Vlc.prototype.getApi = function() { + return this.el_; +}; + +videojs.Vlc.registerEvent = function(vlc, event, handler) { + if (vlc) { + if (vlc.attachEvent) { + // Microsoft + vlc.attachEvent (event, handler); + } else if (vlc.addEventListener) { + // Mozilla: DOM level 2 + vlc.addEventListener(event, handler, false); + } else { + // DOM level 0 + vlc['on' + event] = handler; + } + } +}; + +videojs.Vlc.unregisterEvent = function(vlc, event, handler) { + if (vlc) { + if (vlc.detachEvent) { + // Microsoft + vlc.detachEvent (event, handler); + } else if (vlc.removeEventListener) { + // Mozilla: DOM level 2 + vlc.removeEventListener(event, handler, false); + } else { + // DOM level 0 + vlc['on' + event] = null; + } + } +}; + +videojs.Vlc.embed = function (placeHolder, params, attributes) { + var code = videojs.Vlc.getEmbedCode(params, attributes); + // Get element by embedding code and retrieving created element + var obj = videojs.Component.prototype.createEl('div', { innerHTML: code }).childNodes[0]; + var par = placeHolder.parentNode; + + placeHolder.parentNode.replaceChild(obj, placeHolder); + + return obj; +}; + +videojs.Vlc.getEmbedCode = function(params, attributes) { + + var objTag, + key, + paramsString = '', + attrsString = ''; + + if(window.ActiveXObject) { + objTag = ''; + } + + // Create Attributes string + for (key in attributes) { + attrsString += (key + '="' + attributes[key] + '" '); + } + + return objTag + attrsString + '>' + paramsString + ''; + } else { + objTag = ''; + } +}; + +videojs.Vlc.getAbsoluteURL = function(url){ + // Check if absolute URL + if (!url.match(/^https?:\/\//)) { + // Convert to absolute URL. + url = videojs.Component.prototype.createEl('div', { + innerHTML: 'x' + }).firstChild.href; + } + return url; +}; + +/* Vlc Support Testing */ + +videojs.Vlc.isSupported = function () { + var vlc; + + if(window.ActiveXObject) { + try { + vlc = new window.ActiveXObject('VideoLAN.VLCPlugin.2'); + } catch(e) {} + } + else if(navigator.plugins && navigator.mimeTypes.length > 0) { + var name = 'VLC'; + if (navigator.plugins && (navigator.plugins.length > 0)) { + for(var i=0;i always accept to play a source + return 'maybe'; +}; \ No newline at end of file diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..db83daa --- /dev/null +++ b/nbproject/project.properties @@ -0,0 +1,3 @@ +file.reference.dev-videojs-vlc=. +files.encoding=UTF-8 +site.root.folder=${file.reference.dev-videojs-vlc} diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..c84b24c --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,9 @@ + + + org.netbeans.modules.web.clientproject + + + videojs-vlc + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..7ff1e78 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "videojs-vlc", + "version": "0.0.0", + "author": "Afterster", + "description": "Video.js VLC Tech plug-in", + "license": "Apache-2.0", + "keywords": [ + "videojs", + "vlc", + "audio", + "video", + "player" + ], + "dependencies": {}, + "devDependencies": { + "grunt-contrib-clean": "^0.4", + "grunt-contrib-concat": "^0.3", + "grunt-contrib-jshint": "^0.6", + "grunt-contrib-qunit": "^0.2", + "grunt-contrib-uglify": "^0.2", + "grunt-contrib-watch": "^0.4", + + "video.js": "^4.5", + "qunitjs": "^1.12" + }, + "peerDependencies": { + "video.js": "^4.5" + } +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..ca385de --- /dev/null +++ b/test/index.html @@ -0,0 +1,21 @@ + + + + + Video.js VLC + + + +
+
+ + + + + + + diff --git a/test/videojs-vlc.test.js b/test/videojs-vlc.test.js new file mode 100644 index 0000000..3240ed0 --- /dev/null +++ b/test/videojs-vlc.test.js @@ -0,0 +1,57 @@ +/*! videojs-vlc - v0.0.0 - 2015-1-10 + * Copyright (c) 2015 Afterster + * Licensed under the Apache-2.0 license. */ +(function(window, videojs, qunit) { + 'use strict'; + + var realIsHtmlSupported, + player, + + // local QUnit aliases + // http://api.qunitjs.com/ + + // module(name, {[setup][ ,teardown]}) + module = qunit.module, + // test(name, callback) + test = qunit.test, + // ok(value, [message]) + ok = qunit.ok, + // equal(actual, expected, [message]) + equal = qunit.equal, + // strictEqual(actual, expected, [message]) + strictEqual = qunit.strictEqual, + // deepEqual(actual, expected, [message]) + deepEqual = qunit.deepEqual, + // notEqual(actual, expected, [message]) + notEqual = qunit.notEqual, + // throws(block, [expected], [message]) + throws = qunit.throws; + + module('videojs-vlc', { + setup: function() { + // force HTML support so the tests run in a reasonable + // environment under phantomjs + realIsHtmlSupported = videojs.Html5.isSupported; + videojs.Html5.isSupported = function() { + return true; + }; + + // create a video element + var video = document.createElement('video'); + document.querySelector('#qunit-fixture').appendChild(video); + + // create a video.js player + player = videojs(video); + + // initialize the plugin with the default options + //player.vlc(); + }, + teardown: function() { + videojs.Html5.isSupported = realIsHtmlSupported; + } + }); + + test('registers itself', function() { + ok(player, 'registered the plugin'); + }); +})(window, window.videojs, window.QUnit);