mirror of
https://github.com/iv-org/videojs-quality-selector.git
synced 2024-10-01 06:35:49 -04:00
Merge pull request #1 from silvermine/initial_implementation
Initial implementation
This commit is contained in:
commit
d722f2c1b9
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
coverage
|
||||
dist
|
||||
|
@ -2,3 +2,4 @@
|
||||
.travis.yml
|
||||
Gruntfile.js
|
||||
tests/**
|
||||
docs
|
||||
|
120
Gruntfile.js
120
Gruntfile.js
@ -5,29 +5,145 @@
|
||||
|
||||
'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('develop', [ 'build', 'watch' ]);
|
||||
grunt.registerTask('default', [ 'standards' ]);
|
||||
|
||||
};
|
||||
|
@ -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
30
docs/demo/index.html
Normal 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" selected="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>
|
12
package.json
12
package.json
@ -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"
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {};
|
46
src/js/components/QualityOption.js
Normal file
46
src/js/components/QualityOption.js
Normal 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);
|
||||
},
|
||||
|
||||
});
|
||||
};
|
79
src/js/components/QualitySelector.js
Normal file
79
src/js/components/QualitySelector.js
Normal file
@ -0,0 +1,79 @@
|
||||
'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);
|
||||
|
||||
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.selectedSrc = player.src();
|
||||
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.selectedSrc = source ? source.src : undefined;
|
||||
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: source.src === this.selectedSrc,
|
||||
});
|
||||
}.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
7
src/js/events.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
|
||||
QUALITY_SELECTED: 'qualitySelected',
|
||||
|
||||
};
|
14
src/js/index.js
Normal file
14
src/js/index.js
Normal 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;
|
73
src/js/middleware/SourceInterceptor.js
Normal file
73
src/js/middleware/SourceInterceptor.js
Normal file
@ -0,0 +1,73 @@
|
||||
'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
|
||||
sources = _.map(sources, _.partial(_.omit, _, 'selected'));
|
||||
selectedSource = _.findWhere(sources, { src: newSource.src });
|
||||
// Note: `_.findWhere` returns a reference to an object. Thus the
|
||||
// following updates the original object in `sources`.
|
||||
selectedSource.selected = true;
|
||||
|
||||
player.src(sources);
|
||||
|
||||
player.one('loadeddata', function() {
|
||||
player.removeClass(QUALITY_CHANGE_CLASS);
|
||||
player.currentTime(currentTime);
|
||||
if (!isPaused) {
|
||||
player.play();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
setSource: function(playerSelectedSource, next) {
|
||||
var sources = player.currentSources(),
|
||||
userSelectedSource, chosenSource,
|
||||
qualitySelector;
|
||||
|
||||
// There are generally two source options, the one that videojs
|
||||
// auto-selects and the one that a "user" of this plugin has
|
||||
// supplied via the `selected` property. `selected` can come from
|
||||
// either the `<source>` tag or the list of sources passed to
|
||||
// videojs using `src()`.
|
||||
|
||||
userSelectedSource = _.find(sources, function(source) {
|
||||
// Must check for both boolean and string 'true' as sources set
|
||||
// programmatically should use a boolean, but those coming from
|
||||
// a `<source>` tag will use a string.
|
||||
return source.selected === true || source.selected === 'true';
|
||||
});
|
||||
|
||||
chosenSource = userSelectedSource || playerSelectedSource;
|
||||
|
||||
// Update the quality selector with the new source
|
||||
qualitySelector = player.controlBar.getChild('qualitySelector');
|
||||
if (qualitySelector) {
|
||||
qualitySelector.setSelectedSource(chosenSource);
|
||||
}
|
||||
|
||||
// Pass along the chosen source
|
||||
next(null, chosenSource);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
};
|
3
src/js/standalone.js
Normal file
3
src/js/standalone.js
Normal file
@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
require('./index')();
|
28
src/sass/quality-selector.scss
Normal file
28
src/sass/quality-selector.scss
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user