Initial implementation

This commit is contained in:
Ethan Smith 2017-06-22 09:34:19 -04:00
parent f261cc04a2
commit 156e5923c7
14 changed files with 413 additions and 9 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.DS_Store
node_modules
coverage
dist

View File

@ -2,3 +2,4 @@
.travis.yml
Gruntfile.js
tests/**
docs

View File

@ -5,29 +5,144 @@
'use strict';
var path = require('path'),
getCodeVersion = require('silvermine-serverless-utils/src/get-code-version');
module.exports = function(grunt) {
var config;
var DEBUG = !!grunt.option('debug'),
config;
config = {
js: {
all: [ 'Gruntfile.js', 'src/**/*.js', 'tests/**/*.js' ],
standalone: path.join(__dirname, 'src', 'js', 'standalone.js'),
},
sass: {
base: path.join(__dirname, 'src', 'sass'),
all: [ 'src/**/*.scss' ],
},
dist: {
base: path.join(__dirname, 'dist'),
},
};
config.dist.js = {
bundle: path.join(config.dist.base, 'js', '<%= pkg.name %>.js'),
minified: path.join(config.dist.base, 'js', '<%= pkg.name %>.min.js'),
};
config.dist.css = {
base: path.join(config.dist.base, 'css'),
all: path.join(config.dist.base, '**', '*.css'),
};
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
versionInfo: getCodeVersion.both(),
config: config,
browserify: {
main: {
src: config.js.standalone,
dest: config.dist.js.bundle,
},
},
uglify: {
main: {
files: {
'<%= config.dist.js.minified %>': config.dist.js.bundle,
},
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> <%= versionInfo %> */\n',
sourceMap: true,
sourceMapIncludeSources: true,
mangle: true,
compress: true,
beautify: false,
},
},
},
sass: {
options: {
sourceMap: DEBUG,
indentWidth: 3,
outputStyle: DEBUG ? 'expanded' : 'compressed',
sourceComments: DEBUG,
},
main: {
files: [
{
expand: true,
cwd: config.sass.base,
src: [ '**/*.scss' ],
dest: config.dist.css.base,
ext: '.css',
extDot: 'first',
},
],
},
},
postcss: {
options: {
map: DEBUG,
processors: [
require('autoprefixer')({ browsers: '> .05%' }), // eslint-disable-line global-require
],
},
main: {
src: config.dist.css.all,
},
},
eslint: {
target: config.js.all,
},
sasslint: {
options: {
configFile: path.join(__dirname, 'node_modules', 'sass-lint-config-silvermine', 'sass-lint.yml'),
},
target: config.sass.all,
},
watch: {
grunt: {
files: [ 'Gruntfile.js' ],
tasks: [ 'build' ],
},
js: {
files: [ 'src/**/*.js' ],
tasks: [ 'build-js' ],
},
css: {
files: [ 'src/**/*.scss' ],
tasks: [ 'build-css' ],
},
},
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-sass-lint');
grunt.registerTask('standards', [ 'eslint' ]);
grunt.registerTask('standards', [ 'eslint', 'sasslint' ]);
grunt.registerTask('build-js', [ 'browserify', 'uglify' ]);
grunt.registerTask('build-css', [ 'sass', 'postcss' ]);
grunt.registerTask('build', [ 'build-js', 'build-css' ]);
grunt.registerTask('default', [ 'standards' ]);
};

View File

@ -1,9 +1,9 @@
# Silvermine VideoJS Quality/Resolution Selector
[![Build Status](https://travis-ci.org/silvermine/videojs-quality-selector.png?branch=master)](https://travis-ci.org/silvermine/videojs-quality-selector)
[![Build Status](https://travis-ci.org/silvermine/videojs-quality-selector.svg?branch=master)](https://travis-ci.org/silvermine/videojs-quality-selector)
[![Coverage Status](https://coveralls.io/repos/github/silvermine/videojs-quality-selector/badge.svg?branch=master)](https://coveralls.io/github/silvermine/videojs-quality-selector?branch=master)
[![Dependency Status](https://david-dm.org/silvermine/videojs-quality-selector.png)](https://david-dm.org/silvermine/videojs-quality-selector)
[![Dev Dependency Status](https://david-dm.org/silvermine/videojs-quality-selector/dev-status.png)](https://david-dm.org/silvermine/videojs-quality-selector#info=devDependencies&view=table)
[![Dependency Status](https://david-dm.org/silvermine/videojs-quality-selector.svg)](https://david-dm.org/silvermine/videojs-quality-selector)
[![Dev Dependency Status](https://david-dm.org/silvermine/videojs-quality-selector/dev-status.svg)](https://david-dm.org/silvermine/videojs-quality-selector?type=dev)
## What is it?

30
docs/demo/index.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>videojs-quality-selector Demo</title>
<link href="https://unpkg.com/video.js@6.1.0/dist/video-js.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@6.1.0/dist/video.js"></script>
<script src="../../dist/js/silvermine-videojs-quality-selector.min.js"></script>
<link href="../../dist/css/quality-selector.css" rel="stylesheet">
</head>
<body>
<h1>Demo of <code>videojs-quality-selector</code></h1>
<video id="video_1" class="video-js vjs-default-skin" controls preload="auto" data-setup='{}'>
<source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/a/ab/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm.720p.webm" type="video/webm" label="720P">
<source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/a/ab/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm.480p.webm" type="video/webm" label="480P" isdefault="true">
<source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/a/ab/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm.360p.webm" type="video/webm" label="360P">
<source src="https://upload.wikimedia.org/wikipedia/commons/transcoded/a/ab/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm/Caminandes_3_-_Llamigos_-_Blender_Animated_Short.webm.240p.webm" type="video/webm" label="240P">
</video>
<script>
videojs("video_1", {}, function() {
var player = this;
player.controlBar.addChild('QualitySelector');
});
</script>
</body>
</html>

View File

@ -2,8 +2,9 @@
"name": "silvermine-videojs-quality-selector",
"version": "0.9.0",
"description": "video.js plugin for selecting a video quality or resolution",
"main": "src/index.js",
"main": "src/js/index.js",
"scripts": {
"prepublish": "grunt build",
"test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- -R spec 'tests/**/*.test.js'"
},
"author": "Jeremy Thomerson",
@ -24,17 +25,26 @@
},
"homepage": "https://github.com/silvermine/videojs-quality-selector#readme",
"devDependencies": {
"autoprefixer": "7.1.1",
"class.extend": "0.9.2",
"coveralls": "2.13.1",
"eslint": "4.0.0",
"eslint-config-silvermine": "1.3.0",
"expect.js": "0.3.1",
"grunt": "1.0.1",
"grunt-browserify": "5.0.0",
"grunt-contrib-uglify": "3.0.1",
"grunt-contrib-watch": "1.0.0",
"grunt-eslint": "20.0.0",
"grunt-postcss": "0.8.0",
"grunt-sass": "2.0.0",
"grunt-sass-lint": "0.2.2",
"istanbul": "0.4.5",
"mocha": "3.4.2",
"mocha-lcov-reporter": "1.3.0",
"rewire": "2.5.2",
"sass-lint-config-silvermine": "1.0.1",
"silvermine-serverless-utils": "git+https://github.com/silvermine/serverless-utils.git#910f1149af824fc8d0fa840878079c7d3df0f414",
"sinon": "2.3.5",
"underscore": "1.8.3"
}

View File

@ -1,3 +0,0 @@
'use strict';
module.exports = {};

View File

@ -0,0 +1,46 @@
'use strict';
var _ = require('underscore'),
events = require('../events');
module.exports = function(videojs) {
var MenuItem = videojs.getComponent('MenuItem');
/**
* A MenuItem to represent a video resolution
*
* @class QualityOption
* @extends videojs.MenuItem
*/
return videojs.extend(MenuItem, {
/**
* @inheritdoc
*/
constructor: function(player, options) {
var source = options.source;
if (!_.isObject(source)) {
throw new Error('was not provided a "source" object, but rather: ' + (typeof source));
}
options = _.extend({
selectable: true,
label: source.label,
}, options);
MenuItem.call(this, player, options);
this.source = source;
},
/**
* @inheritdoc
*/
handleClick: function(event) {
MenuItem.prototype.handleClick.call(this, event);
this.player().trigger(events.QUALITY_SELECTED, this.source);
},
});
};

View File

@ -0,0 +1,80 @@
'use strict';
var _ = require('underscore'),
events = require('../events'),
qualityOptionFactory = require('./QualityOption');
module.exports = function(videojs) {
var MenuButton = videojs.getComponent('MenuButton'),
QualityOption = qualityOptionFactory(videojs),
QualitySelector;
/**
* A component for changing video resolutions
*
* @class QualitySelector
* @extends videojs.Button
*/
QualitySelector = videojs.extend(MenuButton, {
/**
* @inheritdoc
*/
constructor: function(player, options) {
MenuButton.call(this, player, options);
this.selectedSource = options.selectedSource || player.currentSource();
player.on(events.QUALITY_SELECTED, function(event, source) {
this.setSelectedSource(source);
}.bind(this));
// Since it's possible for the player to get a source before the selector is
// created, make sure to update once we get a "ready" signal.
player.one('ready', function() {
this.update();
}.bind(this));
},
/**
* Updates the source that is selected in the menu
*
* @param source {object} player source to display as selected
*/
setSelectedSource: function(source) {
this.selectedSource = source;
this.update();
},
/**
* @inheritdoc
*/
createItems: function() {
var player = this.player(),
sources = player.currentSources();
if (!sources || sources.length < 2) {
return [];
}
return _.map(sources, function(source) {
return new QualityOption(player, {
source: source,
selected: this.selectedSource ? source.src === this.selectedSource.src : false,
});
}.bind(this));
},
/**
* @inheritdoc
*/
buildWrapperCSSClass: function() {
return 'vjs-quality-selector ' + MenuButton.prototype.buildWrapperCSSClass.call(this);
},
});
videojs.registerComponent('QualitySelector', QualitySelector);
return QualitySelector;
};

7
src/js/events.js Normal file
View File

@ -0,0 +1,7 @@
'use strict';
module.exports = {
QUALITY_SELECTED: 'qualitySelected',
};

14
src/js/index.js Normal file
View File

@ -0,0 +1,14 @@
'use strict';
var events = require('./events'),
qualitySelectorFactory = require('./components/QualitySelector'),
sourceInterceptorFactory = require('./middleware/SourceInterceptor');
module.exports = function(videojs) {
videojs = videojs || window.videojs;
qualitySelectorFactory(videojs);
sourceInterceptorFactory(videojs);
};
module.exports.EVENTS = events;

View File

@ -0,0 +1,72 @@
'use strict';
var _ = require('underscore'),
events = require('../events'),
QUALITY_CHANGE_CLASS = 'vjs-quality-changing';
module.exports = function(videojs) {
videojs.use('*', function(player) {
player.on(events.QUALITY_SELECTED, function(event, newSource) {
var sources = player.currentSources(),
currentTime = player.currentTime(),
isPaused = player.paused(),
selectedSource;
player.addClass(QUALITY_CHANGE_CLASS);
// Find and set the new selected source
// Note: See `setSource` for the reason behind using both 'isDefault'
// and 'isdefault'
sources = _.map(sources, _.partial(_.omit, _, [ 'isDefault', 'isdefault' ]));
selectedSource = _.findWhere(sources, { src: newSource.src });
// Note: `_.findWhere` returns a reference to an object. Thus the
// following updates the original object in `sources`.
selectedSource.isDefault = true;
player.src(sources);
player.one('loadeddata', function() {
player.removeClass(QUALITY_CHANGE_CLASS);
player.currentTime(currentTime);
if (!isPaused) {
player.play();
}
});
});
return {
setSource: function(autoSelectedSource, next) {
var sources = player.currentSources(),
defaultSource, selectedSource,
qualitySelector;
defaultSource = _.find(sources, function(source) {
// While the simplest check would be `!!source.isDefault`, remember that
// the sources can come from a `<source>` tag. Therefore, the lowercase
// form, `isdefault`, needs to be checked.
return source.isDefault === true
|| source.isDefault === 'true'
|| source.isdefault === true
|| source.isdefault === 'true';
});
selectedSource = defaultSource || autoSelectedSource;
// Update the quality selector with the new source
qualitySelector = player.controlBar.getChild('qualitySelector');
if (qualitySelector) {
qualitySelector.update();
}
// Pass along selected source
next(null, selectedSource);
},
};
});
};

3
src/js/standalone.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
require('./index')();

View File

@ -0,0 +1,28 @@
.vjs-quality-selector {
.vjs-menu-button {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
.vjs-icon-placeholder {
// From video.js font: https://github.com/videojs/font
font-family: 'VideoJS';
font-weight: normal;
font-style: normal;
&:before {
content: '\f110';
}
}
}
.vjs-quality-changing {
.vjs-big-play-button {
display: none;
}
.vjs-control-bar {
display: flex;
visibility: visible;
opacity: 1;
}
}