├── .editorconfig
├── .github
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── example-2.png
├── example.png
├── index.html
├── lang
├── en.json
└── fr.json
├── package-lock.json
├── package.json
├── scripts
├── karma.conf.js
├── postcss.config.js
└── rollup.config.js
├── src
├── ConcreteButton.js
├── ConcreteMenuItem.js
├── plugin.css
└── plugin.js
└── test
└── plugin.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | should-skip:
7 | continue-on-error: true
8 | runs-on: ubuntu-latest
9 | # Map a step output to a job output
10 | outputs:
11 | should-skip-job: ${{steps.skip-check.outputs.should_skip}}
12 | steps:
13 | - id: skip-check
14 | uses: fkirc/skip-duplicate-actions@v5.3.0
15 | with:
16 | github_token: ${{github.token}}
17 |
18 | ci:
19 | needs: should-skip
20 | if: ${{needs.should-skip.outputs.should-skip-job != 'true' || github.ref == 'refs/heads/main'}}
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | os: [ubuntu-latest]
25 | test-type: ['unit', 'coverage']
26 | env:
27 | BROWSER_STACK_USERNAME: ${{secrets.BROWSER_STACK_USERNAME}}
28 | BROWSER_STACK_ACCESS_KEY: ${{secrets.BROWSER_STACK_ACCESS_KEY}}
29 | CI_TEST_TYPE: ${{matrix.test-type}}
30 | runs-on: ${{matrix.os}}
31 | steps:
32 | - name: checkout code
33 | uses: actions/checkout@v3
34 |
35 | - name: read node version from .nvmrc
36 | run: echo "NVMRC=$(cat .nvmrc)" >> $GITHUB_OUTPUT
37 | shell: bash
38 | id: nvm
39 |
40 | - name: update apt cache on linux w/o browserstack
41 | run: sudo apt-get update
42 |
43 | - name: install ffmpeg/pulseaudio for firefox on linux w/o browserstack
44 | run: sudo apt-get install ffmpeg pulseaudio
45 |
46 | - name: start pulseaudio for firefox on linux w/o browserstack
47 | run: pulseaudio -D
48 |
49 | - name: setup node
50 | uses: actions/setup-node@v3
51 | with:
52 | node-version: '${{steps.nvm.outputs.NVMRC}}'
53 | cache: npm
54 |
55 | # turn off the default setup-node problem watchers...
56 | - run: echo "::remove-matcher owner=eslint-compact::"
57 | - run: echo "::remove-matcher owner=eslint-stylish::"
58 | - run: echo "::remove-matcher owner=tsc::"
59 |
60 | - name: npm install
61 | run: npm i --prefer-offline --no-audit
62 |
63 | - name: run npm test
64 | uses: coactions/setup-xvfb@v1
65 | with:
66 | run: npm run test
67 |
68 | - name: coverage
69 | uses: codecov/codecov-action@v3
70 | with:
71 | token: ${{secrets.CODECOV_TOKEN}}
72 | files: './test/dist/coverage/coverage-final.json'
73 | fail_ci_if_error: true
74 | if: ${{startsWith(env.CI_TEST_TYPE, 'coverage')}}
75 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Package to npmjs
2 | on:
3 | release:
4 | types: [published]
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v4
10 | # Setup .npmrc file to publish to npm
11 | - uses: actions/setup-node@v4
12 | with:
13 | node-version: '18.x'
14 | registry-url: 'https://registry.npmjs.org'
15 | - run: npm ci
16 | - run: npm publish
17 | env:
18 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 | Thumbs.db
3 | ehthumbs.db
4 | Desktop.ini
5 | .DS_Store
6 | ._*
7 |
8 | # Editors
9 | *~
10 | *.swp
11 | *.tmproj
12 | *.tmproject
13 | *.sublime-*
14 | .idea/
15 | .project/
16 | .settings/
17 | .vscode/
18 |
19 | # Logs
20 | logs
21 | *.log
22 | npm-debug.log*
23 |
24 | # Dependency directories
25 | bower_components/
26 | node_modules/
27 |
28 | # Build-related directories
29 | dist/
30 | es/
31 | cjs/
32 | docs/api/
33 | test/dist/
34 | .eslintcache
35 | .yo-rc.json
36 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Intentionally left blank, so that npm does not ignore anything by default,
2 | # but relies on the package.json "files" array to explicitly define what ends
3 | # up in the package.
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # [2.0.0](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.3.0...v2.0.0) (2024-03-13)
3 |
4 |
5 | # [1.3.0](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.2.0...v1.3.0) (2024-03-13)
6 |
7 | ### Features
8 |
9 | * **gha:** publish action ([cd8e58a](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/cd8e58a))
10 |
11 | ### Bug Fixes
12 |
13 | * **package:** update to node 18 ([631e973](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/631e973))
14 |
15 | ### Chores
16 |
17 | * merge conflict ([0b1efc9](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/0b1efc9))
18 | * merge conflict ([b067cba](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/b067cba))
19 | * merge conflict ([20cfee8](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/20cfee8))
20 | * **package:** npm update ([ee92237](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/ee92237))
21 |
22 | ### Chores
23 |
24 |
25 | ## Upgrade template generator for 8.0 compatibility [1.2.0](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.1.4...v1.2.0) (2023-12-29)
26 |
27 |
28 |
29 | ## [1.1.4](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.1.3...v1.1.4) (2020-10-07)
30 |
31 | ### Bug Fixes
32 |
33 | * **npm:** remove force resolutions ([ab34990](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/ab34990))
34 |
35 |
36 | ## [1.1.3](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.1.2...v1.1.3) (2020-10-07)
37 |
38 | ### Bug Fixes
39 |
40 | * **docs:** documentation formatting ([80d7bb3](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/80d7bb3))
41 | * **plugin:** add a quality getter ([13b8be9](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/13b8be9))
42 |
43 |
44 | ## [1.1.2](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.1.1...v1.1.2) (2020-09-03)
45 |
46 | ### Bug Fixes
47 |
48 | * **deps:** force update dot-prop ([4663a0c](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/4663a0c))
49 | * **deps:** update deps ([c69e167](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/c69e167))
50 |
51 | ### Chores
52 |
53 | * **deps:** bump lodash from 4.17.14 to 4.17.19 ([ecbcc28](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/ecbcc28))
54 |
55 |
56 | ## [1.1.1](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.1.0...v1.1.1) (2020-01-17)
57 |
58 | ### Bug Fixes
59 |
60 | * **deps:** bump dependencies ([4980678](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/4980678))
61 | * **plugin:** fix merged code lint issues ([dba70d5](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/dba70d5))
62 |
63 | ### Chores
64 |
65 | * **deps:** bump lodash.template from 4.4.0 to 4.5.0 ([b559126](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/b559126))
66 |
67 |
68 | # [1.1.0](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v1.0.5...v1.1.0) (2019-10-11)
69 |
70 | ### Features
71 |
72 | * **config:** display resolutions in menu button ([d285eef](https://github.com/chrisboustead/videojs-hls-quality-selector/commit/d285eef))
73 |
74 |
75 | ## [0.0.8](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v0.0.7...v0.0.8) (2018-06-27)
76 |
77 |
78 | ## [0.0.7](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v0.0.6...v0.0.7) (2018-06-05)
79 |
80 |
81 | ## [0.0.6](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v0.0.5...v0.0.6) (2018-06-05)
82 |
83 |
84 | ## [0.0.5](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v0.0.4...v0.0.5) (2018-06-05)
85 |
86 |
87 | ## [0.0.4](https://github.com/chrisboustead/videojs-hls-quality-selector/compare/v0.0.3...v0.0.4) (2018-04-27)
88 |
89 |
90 | ## 0.0.3 (2018-04-27)
91 |
92 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | We welcome contributions from everyone!
4 |
5 | ## Getting Started
6 |
7 | Make sure you have Node.js 14 or higher and npm installed.
8 |
9 | 1. Fork this repository and clone your fork
10 | 1. Install dependencies: `npm install`
11 | 1. Run a development server: `npm start`
12 |
13 | ### Making Changes
14 |
15 | Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship.
16 |
17 | When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository.
18 |
19 | ### Running Tests
20 |
21 | Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma].
22 |
23 | - In all available and supported browsers: `npm test`
24 | - In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc.
25 | - While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local]
26 |
27 |
28 | [karma]: http://karma-runner.github.io/
29 | [local]: http://localhost:9999/test/
30 | [conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Chris Boustead (chris@forgemotion.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # videojs-hls-quality-selector
2 | [](https://circleci.com/gh/chrisboustead/videojs-hls-quality-selector/tree/master)
3 | [](https://badge.fury.io/js/videojs-hls-quality-selector)
4 |
5 | **Note:**
6 | - v1.2.0 is compatible with videojs 8
7 | - v1.x.x is Only compatible with VideoJS 7.x due to the move from `videojs-contrib-hls` to `videojs/http-streaming`. For VideoJS v5 or v6 support please use a `v0.x.x` tag
8 |
9 | ## Description
10 |
11 | Adds a quality selector menu for HLS sources played in videojs.
12 |
13 | Any HLS manifest with multiple playlists/renditions should be selectable from within the added control.
14 |
15 | **Native HLS**
16 |
17 | Does not yet support browsers using native HLS (Safari, Edge, etc). To enable plugin in browsers with native HLS, you must force non-native HLS playback:
18 |
19 | ## Options
20 |
21 | **displayCurrentQuality** `boolean` - _false_
22 |
23 | Set to true to display the currently selected resolution in the menu button. When not enabled, displayed an included VJS "HD" icon.
24 |
25 | **placementIndex** `integer`
26 |
27 | Set this to override the default positioning of the menu button in the control bar relative to the other components in the control bar.
28 |
29 | **vjsIconClass** `string` - _"vjs-icon-hd"_
30 |
31 | Set this to one of the custom VJS icons ([https://videojs.github.io/font/](https://videojs.github.io/font/)) to override the icon for the menu button.
32 |
33 |
34 | ## Methods
35 |
36 | **getCurrentQuality** `string` - _'auto'__
37 |
38 | Return the current set quality or 'auto'
39 |
40 |
41 | ## Screenshots
42 |
43 | Default setup - Menu selected:
44 | 
45 |
46 |
47 | Display Current Quality option enabled:
48 | 
49 |
50 | ## Table of Contents
51 |
52 |
53 |
54 | ## Installation
55 |
56 | ```sh
57 | npm install --save videojs-hls-quality-selector
58 | ```
59 |
60 | ## Usage
61 |
62 | To include videojs-hls-quality-selector on your website or web application, use any of the following methods.
63 |
64 | ### `
70 |
71 |
76 | ```
77 |
78 | ### Browserify/CommonJS
79 |
80 | When using with Browserify, install videojs-hls-quality-selector via npm and `require` the plugin as you would any other module.
81 |
82 | ```js
83 | var videojs = require('video.js');
84 |
85 | // The actual plugin function is exported by this module, but it is also
86 | // attached to the `Player.prototype`; so, there is no need to assign it
87 | // to a variable.
88 | require('videojs-hls-quality-selector');
89 |
90 | var player = videojs('my-video');
91 |
92 | player.hlsQualitySelector({
93 | displayCurrentQuality: true,
94 | });
95 | ```
96 |
97 | ### RequireJS/AMD
98 |
99 | When using with RequireJS (or another AMD library), get the script in whatever way you prefer and `require` the plugin as you normally would:
100 |
101 | ```js
102 | require(['video.js', 'videojs-hls-quality-selector'], function(videojs) {
103 | var player = videojs('my-video');
104 |
105 | player.hlsQualitySelector();
106 | });
107 | ```
108 |
109 | ## License
110 |
111 | MIT. Copyright (c) Chris Boustead (chris@forgemotion.com)
112 |
113 |
114 | [videojs]: http://videojs.com/
115 |
--------------------------------------------------------------------------------
/example-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisboustead/videojs-hls-quality-selector/672888deb0dc41be18fcbc2e1c269d6963363f9c/example-2.png
--------------------------------------------------------------------------------
/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisboustead/videojs-hls-quality-selector/672888deb0dc41be18fcbc2e1c269d6963363f9c/example.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | videojs-hls-quality-selector Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Auto": "Auto",
3 | "Quality": "Quality"
4 | }
5 |
--------------------------------------------------------------------------------
/lang/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "Auto": "Auto",
3 | "Quality": "Qualité"
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "videojs-hls-quality-selector",
3 | "version": "2.0.0",
4 | "description": "Adds a quality selector menu for HLS sources played in videojs.",
5 | "main": "cjs/plugin.js",
6 | "module": "es/plugin.js",
7 | "browser": "dist/videojs-hls-quality-selector.js",
8 | "generator-videojs-plugin": {
9 | "version": "9.0.0"
10 | },
11 | "scripts": {
12 | "build": "npm-run-all -s clean -p build:*",
13 | "build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'",
14 | "build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'",
15 | "build:cjs": "babel-config-cjs -d ./cjs ./src",
16 | "build:css": "postcss -o dist/videojs-hls-quality-selector.css --config scripts/postcss.config.js src/plugin.css",
17 | "build:es": "babel-config-es -d ./es ./src",
18 | "build:js": "rollup -c scripts/rollup.config.js",
19 | "build:lang": "vjslang --dir dist/lang",
20 | "clean": "shx rm -rf ./dist ./test/dist ./cjs ./es && shx mkdir -p ./dist ./test/dist ./cjs ./es",
21 | "docs": "npm-run-all docs:*",
22 | "docs:api": "jsdoc src -r -d docs/api",
23 | "docs:toc": "doctoc --notitle README.md",
24 | "lint": "vjsstandard",
25 | "server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
26 | "start": "npm-run-all -p server watch",
27 | "test": "npm-run-all lint build-test && karma start scripts/karma.conf.js",
28 | "posttest": "shx cat test/dist/coverage/text.txt",
29 | "update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s",
30 | "preversion": "npm test",
31 | "version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md",
32 | "watch": "npm-run-all -p watch:*",
33 | "watch:cjs": "npm run build:cjs -- -w",
34 | "watch:css": "npm run build:css -- -w",
35 | "watch:es": "npm run build:es -- -w",
36 | "watch:js": "npm run build:js -- -w",
37 | "prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check"
38 | },
39 | "engines": {
40 | "node": ">=14",
41 | "npm": ">=6"
42 | },
43 | "keywords": [
44 | "videojs",
45 | "videojs-plugin"
46 | ],
47 | "author": "Chris Boustead (chris@forgemotion.com)",
48 | "license": "MIT",
49 | "vjsstandard": {
50 | "ignore": [
51 | "es",
52 | "cjs",
53 | "dist",
54 | "docs",
55 | "test/dist"
56 | ]
57 | },
58 | "files": [
59 | "CONTRIBUTING.md",
60 | "cjs/",
61 | "dist/",
62 | "docs/",
63 | "es/",
64 | "index.html",
65 | "scripts/",
66 | "src/",
67 | "test/"
68 | ],
69 | "husky": {
70 | "hooks": {
71 | "pre-commit": "lint-staged"
72 | }
73 | },
74 | "lint-staged": {
75 | "*.js": "vjsstandard --fix",
76 | "README.md": "doctoc --notitle"
77 | },
78 | "dependencies": {
79 | "global": "^4.4.0",
80 | "video.js": "^8"
81 | },
82 | "devDependencies": {
83 | "@babel/cli": "^7.14.3",
84 | "@babel/runtime": "^7.14.0",
85 | "@videojs/babel-config": "^0.2.0",
86 | "@videojs/generator-helpers": "~3.0.0",
87 | "jsdoc": "~3.6.7",
88 | "karma": "^6.3.2",
89 | "postcss": "^8.3.0",
90 | "postcss-cli": "^8.3.1",
91 | "rollup": "^2.50.3",
92 | "sinon": "^9.1.0",
93 | "videojs-generate-karma-config": "~8.0.0",
94 | "videojs-generate-postcss-config": "~3.0.0",
95 | "videojs-generate-rollup-config": "~7.0.1",
96 | "videojs-generator-verify": "~4.0.0",
97 | "videojs-languages": "^2.0.0",
98 | "videojs-standard": "^9.0.0"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/scripts/karma.conf.js:
--------------------------------------------------------------------------------
1 | const generate = require('videojs-generate-karma-config');
2 |
3 | module.exports = function(config) {
4 |
5 | // see https://github.com/videojs/videojs-generate-karma-config
6 | // for options
7 | const options = {};
8 |
9 | config = generate(config, options);
10 |
11 | // any other custom stuff not supported by options here!
12 | };
13 |
--------------------------------------------------------------------------------
/scripts/postcss.config.js:
--------------------------------------------------------------------------------
1 | const generate = require('videojs-generate-postcss-config');
2 |
3 | module.exports = function(context) {
4 | const result = generate({}, context);
5 |
6 | // do custom stuff here
7 |
8 | return result;
9 | };
10 |
--------------------------------------------------------------------------------
/scripts/rollup.config.js:
--------------------------------------------------------------------------------
1 | const generate = require('videojs-generate-rollup-config');
2 |
3 | // see https://github.com/videojs/videojs-generate-rollup-config
4 | // for options
5 | const options = {};
6 | const config = generate(options);
7 |
8 | // Add additonal builds/customization here!
9 |
10 | // do not build module dists with rollup
11 | // this is handled by build:es and build:cjs
12 | if (config.builds.module) {
13 | delete config.builds.module;
14 | }
15 |
16 | // export the builds to rollup
17 | export default Object.values(config.builds);
18 |
--------------------------------------------------------------------------------
/src/ConcreteButton.js:
--------------------------------------------------------------------------------
1 | import videojs from 'video.js';
2 |
3 | const MenuButton = videojs.getComponent('MenuButton');
4 | const Menu = videojs.getComponent('Menu');
5 | const Component = videojs.getComponent('Component');
6 | const Dom = videojs.dom;
7 |
8 | /**
9 | * Convert string to title case.
10 | *
11 | * @param {string} string - the string to convert
12 | * @return {string} the returned titlecase string
13 | */
14 | function toTitleCase(string) {
15 | if (typeof string !== 'string') {
16 | return string;
17 | }
18 |
19 | return string.charAt(0).toUpperCase() + string.slice(1);
20 | }
21 |
22 | /**
23 | * Extend vjs button class for quality button.
24 | */
25 | export default class ConcreteButton extends MenuButton {
26 |
27 | /**
28 | * Button constructor.
29 | *
30 | * @param {Player} player - videojs player instance
31 | */
32 | constructor(player) {
33 | super(player, {
34 | title: player.localize('Quality'),
35 | name: 'QualityButton'
36 | });
37 | }
38 |
39 | /**
40 | * Creates button items.
41 | *
42 | * @return {Array} - Button items
43 | */
44 | createItems() {
45 | return [];
46 | }
47 |
48 | /**
49 | * Create the menu and add all items to it.
50 | *
51 | * @return {Menu}
52 | * The constructed menu
53 | */
54 | createMenu() {
55 | const menu = new Menu(this.player_, { menuButton: this });
56 |
57 | this.hideThreshold_ = 0;
58 |
59 | // Add a title list item to the top
60 | if (this.options_.title) {
61 | const titleEl = Dom.createEl('li', {
62 | className: 'vjs-menu-title',
63 | innerHTML: toTitleCase(this.options_.title),
64 | tabIndex: -1
65 | });
66 | const titleComponent = new Component(this.player_, { el: titleEl });
67 |
68 | this.hideThreshold_ += 1;
69 |
70 | menu.addItem(titleComponent);
71 | }
72 |
73 | this.items = this.createItems();
74 |
75 | if (this.items) {
76 | // Add menu items to the menu
77 | for (let i = 0; i < this.items.length; i++) {
78 | menu.addItem(this.items[i]);
79 | }
80 | }
81 |
82 | return menu;
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/ConcreteMenuItem.js:
--------------------------------------------------------------------------------
1 | import videojs from 'video.js';
2 |
3 | // Concrete classes
4 | const VideoJsMenuItemClass = videojs.getComponent('MenuItem');
5 |
6 | /**
7 | * Extend vjs menu item class.
8 | */
9 | export default class ConcreteMenuItem extends VideoJsMenuItemClass {
10 |
11 | /**
12 | * Menu item constructor.
13 | *
14 | * @param {Player} player - vjs player
15 | * @param {Object} item - Item object
16 | * @param {ConcreteButton} qualityButton - The containing button.
17 | * @param {HlsQualitySelector} plugin - This plugin instance.
18 | */
19 | constructor(player, item, qualityButton, plugin) {
20 | super(player, {
21 | label: item.label,
22 | selectable: true,
23 | selected: item.selected || false
24 | });
25 | this.item = item;
26 | this.qualityButton = qualityButton;
27 | this.plugin = plugin;
28 | }
29 |
30 | /**
31 | * Click event for menu item.
32 | */
33 | handleClick() {
34 |
35 | // Reset other menu items selected status.
36 | for (let i = 0; i < this.qualityButton.items.length; ++i) {
37 | this.qualityButton.items[i].selected(false);
38 | }
39 |
40 | // Set this menu item to selected, and set quality.
41 | this.plugin.setQuality(this.item.value);
42 | this.selected(true);
43 |
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/plugin.css:
--------------------------------------------------------------------------------
1 | /**
2 | * css for videojs-hls-quality-selector
3 | * With the default plugins for postcss you can
4 | * - @import files, they will be inlined during build
5 | * - not worry about browser prefixes, they will be handled
6 | * - nest selectors. This follows the css specification that is
7 | * currently out on some browsers. See https://tabatkins.github.io/specs/css-nesting/
8 | * - custom properties (aka variables) via the var(--var-name) syntax. See
9 | * https://www.w3.org/TR/css-variables-1/
10 | */
11 |
12 |
13 | /* Note: all vars must be defined here, there are no "local" vars */
14 | /*:root {
15 | --main-color: red;
16 | --base-font-size: 9;
17 | --font-size: 7;
18 | }*/
19 |
20 | .video-js {
21 |
22 | &.vjs-hls-quality-selector {
23 | /* This class is added to the video.js element by the plugin by default. */
24 | display: block;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/plugin.js:
--------------------------------------------------------------------------------
1 | import videojs from 'video.js';
2 | import { version as VERSION } from '../package.json';
3 | import ConcreteButton from './ConcreteButton';
4 | import ConcreteMenuItem from './ConcreteMenuItem';
5 |
6 | const Plugin = videojs.getPlugin('plugin');
7 |
8 | // Default options for the plugin.
9 | const defaults = {};
10 |
11 | /**
12 | * An advanced Video.js plugin. For more information on the API
13 | *
14 | * See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
15 | */
16 | class HlsQualitySelector extends Plugin {
17 |
18 | /**
19 | * Create a HlsQualitySelector plugin instance.
20 | *
21 | * @param {Player} player
22 | * A Video.js Player instance.
23 | *
24 | * @param {Object} [options]
25 | * An optional options object.
26 | *
27 | * While not a core part of the Video.js plugin architecture, a
28 | * second argument of options is a convenient way to accept inputs
29 | * from your plugin's caller.
30 | */
31 | constructor(player, options) {
32 | // the parent class will add player under this.player
33 | super(player);
34 |
35 | this.options = videojs.obj.merge(defaults, options);
36 |
37 | this.player.ready(() => {
38 | // If there is quality levels plugin and the HLS tech exists then continue.
39 | if (this.player.qualityLevels) {
40 | this.player.addClass('vjs-hls-quality-selector');
41 | // Create the quality button.
42 | this.createQualityButton();
43 | this.bindPlayerEvents();
44 | }
45 | });
46 | }
47 |
48 | /**
49 | * Binds listener for quality level changes.
50 | */
51 | bindPlayerEvents() {
52 | this.player.qualityLevels().on('addqualitylevel', this.onAddQualityLevel.bind(this));
53 | }
54 |
55 | /**
56 | * Adds the quality menu button to the player control bar.
57 | */
58 | createQualityButton() {
59 |
60 | const player = this.player;
61 |
62 | this._qualityButton = new ConcreteButton(player);
63 |
64 | const placementIndex = player.controlBar.children().length - 2;
65 | const concreteButtonInstance = player.controlBar.addChild(
66 | this._qualityButton,
67 | { componentClass: 'qualitySelector' },
68 | this.options.placementIndex || placementIndex
69 | );
70 |
71 | concreteButtonInstance.addClass('vjs-quality-selector');
72 | if (!this.options.displayCurrentQuality) {
73 | const icon = ` ${this.options.vjsIconClass || 'vjs-icon-hd'}`;
74 |
75 | concreteButtonInstance
76 | .menuButton_.$('.vjs-icon-placeholder').className += icon;
77 | } else {
78 | this.setButtonInnerText(player.localize('Auto'));
79 | }
80 | concreteButtonInstance.removeClass('vjs-hidden');
81 |
82 | }
83 |
84 | /**
85 | *Set inner button text.
86 | *
87 | * @param {string} text - the text to display in the button.
88 | */
89 | setButtonInnerText(text) {
90 | this._qualityButton
91 | .menuButton_.$('.vjs-icon-placeholder').innerHTML = text;
92 | }
93 |
94 | /**
95 | * Builds individual quality menu items.
96 | *
97 | * @param {Object} item - Individual quality menu item.
98 | * @return {ConcreteMenuItem} - Menu item
99 | */
100 | getQualityMenuItem(item) {
101 | const player = this.player;
102 |
103 | return new ConcreteMenuItem(player, item, this._qualityButton, this);
104 | }
105 |
106 | /**
107 | * Executed when a quality level is added from HLS playlist.
108 | */
109 | onAddQualityLevel() {
110 | const player = this.player;
111 | const qualityList = player.qualityLevels();
112 | const levels = qualityList.levels_ || [];
113 | const levelItems = [];
114 |
115 | for (let i = 0; i < levels.length; ++i) {
116 | const { width, height } = levels[i];
117 | const pixels = width > height ? height : width;
118 |
119 | if (!pixels) {
120 | continue;
121 | }
122 |
123 | if (!levelItems.filter(_existingItem => {
124 | return _existingItem.item && _existingItem.item.value === pixels;
125 | }).length) {
126 | const levelItem = this.getQualityMenuItem.call(this, {
127 | label: pixels + 'p',
128 | value: pixels
129 | });
130 |
131 | levelItems.push(levelItem);
132 | }
133 | }
134 |
135 | levelItems.sort((current, next) => {
136 | if ((typeof current !== 'object') || (typeof next !== 'object')) {
137 | return -1;
138 | }
139 | if (current.item.value < next.item.value) {
140 | return -1;
141 | }
142 | if (current.item.value > next.item.value) {
143 | return 1;
144 | }
145 | return 0;
146 | });
147 |
148 | levelItems.push(this.getQualityMenuItem.call(this, {
149 | label: this.player.localize('Auto'),
150 | value: 'auto',
151 | selected: true
152 | }));
153 |
154 | if (this._qualityButton) {
155 | this._qualityButton.createItems = () => {
156 | return levelItems;
157 | };
158 | this._qualityButton.update();
159 | }
160 |
161 | }
162 |
163 | /**
164 | * Sets quality (based on media short side)
165 | *
166 | * @param {number} quality - A number representing HLS playlist.
167 | */
168 | setQuality(quality) {
169 | const qualityList = this.player.qualityLevels();
170 |
171 | // Set quality on plugin
172 | this._currentQuality = quality;
173 |
174 | if (this.options.displayCurrentQuality) {
175 | this.setButtonInnerText(quality === 'auto' ? this.player.localize('Auto') : `${quality}p`);
176 | }
177 |
178 | for (let i = 0; i < qualityList.length; ++i) {
179 | const { width, height } = qualityList[i];
180 | const pixels = width > height ? height : width;
181 |
182 | qualityList[i].enabled = (pixels === quality || quality === 'auto');
183 | }
184 | this._qualityButton.unpressButton();
185 | }
186 |
187 | /**
188 | * Return the current set quality or 'auto'
189 | *
190 | * @return {string} the currently set quality
191 | */
192 | getCurrentQuality() {
193 | return this._currentQuality || 'auto';
194 | }
195 |
196 | }
197 |
198 | // Include the version number.
199 | HlsQualitySelector.VERSION = VERSION;
200 |
201 | // Register the plugin with video.js.
202 | videojs.registerPlugin('hlsQualitySelector', HlsQualitySelector);
203 |
204 | export default HlsQualitySelector;
205 |
--------------------------------------------------------------------------------
/test/plugin.test.js:
--------------------------------------------------------------------------------
1 | import document from 'global/document';
2 |
3 | import QUnit from 'qunit';
4 | import sinon from 'sinon';
5 | import videojs from 'video.js';
6 |
7 | import plugin from '../src/plugin';
8 |
9 | const Player = videojs.getComponent('Player');
10 |
11 | QUnit.test('the environment is sane', function(assert) {
12 | assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists');
13 | assert.strictEqual(typeof sinon, 'object', 'sinon exists');
14 | assert.strictEqual(typeof videojs, 'function', 'videojs exists');
15 | assert.strictEqual(typeof plugin, 'function', 'plugin is a function');
16 | });
17 |
18 | QUnit.module('videojs-hls-quality-selector', {
19 |
20 | beforeEach() {
21 |
22 | // Mock the environment's timers because certain things - particularly
23 | // player readiness - are asynchronous in video.js 5. This MUST come
24 | // before any player is created; otherwise, timers could get created
25 | // with the actual timer methods!
26 | this.clock = sinon.useFakeTimers();
27 |
28 | this.fixture = document.getElementById('qunit-fixture');
29 | this.video = document.createElement('video');
30 | this.fixture.appendChild(this.video);
31 | this.player = videojs(this.video);
32 | },
33 |
34 | afterEach() {
35 | this.player.dispose();
36 | this.clock.restore();
37 | }
38 | });
39 |
40 | QUnit.test('registers itself with video.js', function(assert) {
41 | assert.expect(2);
42 |
43 | assert.strictEqual(
44 | typeof Player.prototype.hlsQualitySelector,
45 | 'function',
46 | 'videojs-hls-quality-selector plugin was registered'
47 | );
48 |
49 | this.player.hlsQualitySelector();
50 |
51 | // Tick the clock forward enough to trigger the player to be "ready".
52 | this.clock.tick(1);
53 |
54 | assert.ok(
55 | this.player.hasClass('vjs-hls-quality-selector'),
56 | 'the plugin adds a class to the player'
57 | );
58 | });
59 |
--------------------------------------------------------------------------------