├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── .travis.yml ├── .yo-rc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── img └── oceans-thumbs.jpg ├── index.html ├── package-lock.json ├── package.json ├── scripts ├── karma.conf.js └── rollup.config.js ├── src ├── plugin.js └── sprite-thumbnails.js └── test ├── plugin.test.js └── sprite-thumbnails.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 | -------------------------------------------------------------------------------- /.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 | 14 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: node_js 3 | # node version is specified using the .nvmrc file 4 | cache: npm 5 | before_install: 6 | - npm install -g greenkeeper-lockfile@1 7 | before_script: 8 | - greenkeeper-lockfile-update 9 | after_script: 10 | - greenkeeper-lockfile-upload 11 | addons: 12 | firefox: latest 13 | chrome: stable 14 | services: 15 | - xvfb 16 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-videojs-plugin": { 3 | "scope": "", 4 | "name": "sprite-thumbnails", 5 | "description": "Plugin to display thumbnails when hovering over the progress bar.", 6 | "author": "Christian Ebert (https://phloxic.productions)", 7 | "license": "mit", 8 | "sass": false, 9 | "ie8": true, 10 | "docs": false, 11 | "lang": false, 12 | "husky": "lint", 13 | "pluginType": "advanced", 14 | "css": false, 15 | "precommit": true, 16 | "prepush": false, 17 | "library": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [2.2.3](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.2.2...v2.2.3) (2024-10-27) 3 | 4 | ### Bug Fixes 5 | 6 | * assign component variables in a sparse array ([fd445a5](https://github.com/phloxic/videojs-sprite-thumbnails/commit/fd445a5)), closes [#68](https://github.com/phloxic/videojs-sprite-thumbnails/issues/68) 7 | 8 | ### Chores 9 | 10 | * **deps-dev:** require cookie v0.7.0 ([be00e49](https://github.com/phloxic/videojs-sprite-thumbnails/commit/be00e49)) 11 | * update version in package-lock.json ([dab760b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/dab760b)) 12 | 13 | 14 | ## [2.2.2](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.2.1...v2.2.2) (2024-10-09) 15 | 16 | ### Features 17 | 18 | * **debug:** check for presence of default controls component tree ([206da48](https://github.com/phloxic/videojs-sprite-thumbnails/commit/206da48)), closes [#58](https://github.com/phloxic/videojs-sprite-thumbnails/issues/58) 19 | 20 | ### Bug Fixes 21 | 22 | * do not try to show thumbnails before video metadata is loaded ([078dc5b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/078dc5b)), closes [#67](https://github.com/phloxic/videojs-sprite-thumbnails/issues/67) 23 | * **example:** add `columns` option, mandatory since v2.2.0 ([1995d18](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1995d18)) 24 | 25 | ### Chores 26 | 27 | * bump version from 2.2.1 to 2.3.2-dev ([ce385cc](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ce385cc)) 28 | * **deps-dev:** alternatively allow rollup 2.50.3 ([efb35d8](https://github.com/phloxic/videojs-sprite-thumbnails/commit/efb35d8)) 29 | * **deps-dev:** bump braces from 3.0.2 to 3.0.3 ([bcca124](https://github.com/phloxic/videojs-sprite-thumbnails/commit/bcca124)) 30 | * **deps-dev:** bump follow-redirects from 1.15.4 to 1.15.6 ([ad9735d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ad9735d)) 31 | * **deps-dev:** bump rollup from 2.79.1 to 3.29.5 ([73b6c2e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/73b6c2e)) 32 | * **deps-dev:** bump serve-static from 1.15.0 to 1.16.2 ([32f65d8](https://github.com/phloxic/videojs-sprite-thumbnails/commit/32f65d8)) 33 | * **deps:** bump body-parser from 1.20.2 to 1.20.3 ([c4c6c08](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c4c6c08)) 34 | * update karma dependencies ([adef294](https://github.com/phloxic/videojs-sprite-thumbnails/commit/adef294)) 35 | * **yo:** update plugin description ([9d8bd0e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9d8bd0e)) 36 | 37 | ### Code Refactoring 38 | 39 | * assign component name vars by splicing descendants array ([c6b3e70](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c6b3e70)) 40 | * improve connection speed check ([9a758c3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9a758c3)) 41 | * improve integer check ([4e65019](https://github.com/phloxic/videojs-sprite-thumbnails/commit/4e65019)) 42 | * no need to store obj.merge in a top level variable ([8979d15](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8979d15)) 43 | 44 | ### Styles 45 | 46 | * always call event handlers by name ([234c4e3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/234c4e3)) 47 | 48 | 49 | ## [2.2.2](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.2.1...v2.2.2) (2024-10-08) 50 | 51 | ### Features 52 | 53 | * **debug:** check for presence of default controls component tree ([206da48](https://github.com/phloxic/videojs-sprite-thumbnails/commit/206da48)), closes [#58](https://github.com/phloxic/videojs-sprite-thumbnails/issues/58) 54 | 55 | ### Bug Fixes 56 | 57 | * do not try to show thumbnails before video metadata is loaded ([078dc5b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/078dc5b)), closes [#67](https://github.com/phloxic/videojs-sprite-thumbnails/issues/67) 58 | * **example:** add `columns` option, mandatory since v2.2.0 ([1995d18](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1995d18)) 59 | 60 | ### Chores 61 | 62 | * bump version from 2.2.1 to 2.3.2-dev ([ce385cc](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ce385cc)) 63 | * **deps-dev:** alternatively allow rollup 2.50.3 ([efb35d8](https://github.com/phloxic/videojs-sprite-thumbnails/commit/efb35d8)) 64 | * **deps-dev:** bump braces from 3.0.2 to 3.0.3 ([bcca124](https://github.com/phloxic/videojs-sprite-thumbnails/commit/bcca124)) 65 | * **deps-dev:** bump follow-redirects from 1.15.4 to 1.15.6 ([ad9735d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ad9735d)) 66 | * **deps-dev:** bump rollup from 2.79.1 to 3.29.5 ([73b6c2e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/73b6c2e)) 67 | * **deps-dev:** bump serve-static from 1.15.0 to 1.16.2 ([32f65d8](https://github.com/phloxic/videojs-sprite-thumbnails/commit/32f65d8)) 68 | * **deps:** bump body-parser from 1.20.2 to 1.20.3 ([c4c6c08](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c4c6c08)) 69 | * update karma dependencies ([adef294](https://github.com/phloxic/videojs-sprite-thumbnails/commit/adef294)) 70 | * **yo:** update plugin description ([9d8bd0e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9d8bd0e)) 71 | 72 | ### Code Refactoring 73 | 74 | * assign component name vars by splicing descendants array ([c6b3e70](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c6b3e70)) 75 | * improve connection speed check ([9a758c3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9a758c3)) 76 | * improve integer check ([4e65019](https://github.com/phloxic/videojs-sprite-thumbnails/commit/4e65019)) 77 | * no need to store obj.merge in a top level variable ([8979d15](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8979d15)) 78 | 79 | ### Styles 80 | 81 | * always call event handlers by name ([234c4e3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/234c4e3)) 82 | 83 | 84 | ## [2.2.1](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.2.0...v2.2.1) (2024-01-22) 85 | 86 | ### Bug Fixes 87 | 88 | * reinstate initialization on plugin setup ([1d0144f](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1d0144f)), closes [#61](https://github.com/phloxic/videojs-sprite-thumbnails/issues/61) 89 | 90 | ### Chores 91 | 92 | * clean up spurious log.debug alternative ([dddae85](https://github.com/phloxic/videojs-sprite-thumbnails/commit/dddae85)) 93 | * **deps-dev:** bump follow-redirects from 1.15.2 to 1.15.4 ([04f4bb5](https://github.com/phloxic/videojs-sprite-thumbnails/commit/04f4bb5)) 94 | 95 | ### Documentation 96 | 97 | * **css:** explain state classes ([6b1ac70](https://github.com/phloxic/videojs-sprite-thumbnails/commit/6b1ac70)) 98 | * github markdown flavour needs fragment identifiers in HTML ([2270748](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2270748)) 99 | 100 | 101 | # [2.2.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.1.1...v2.2.0) (2023-12-29) 102 | 103 | ### Features 104 | 105 | * add function to customize the replacement of the {index} template ([1f451c9](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1f451c9)) 106 | * avoid inheritance of urlArray and url options ([f4a43ea](https://github.com/phloxic/videojs-sprite-thumbnails/commit/f4a43ea)) 107 | * **downlink:** check on statechanged, report w/o duplicating code ([bdfb4af](https://github.com/phloxic/videojs-sprite-thumbnails/commit/bdfb4af)) 108 | * load sprite images on demand ([e22b512](https://github.com/phloxic/videojs-sprite-thumbnails/commit/e22b512)), closes [#56](https://github.com/phloxic/videojs-sprite-thumbnails/issues/56) 109 | * optionally configure image sequence with new option urlArray ([89ec8d5](https://github.com/phloxic/videojs-sprite-thumbnails/commit/89ec8d5)) 110 | * support multiple sprites and individual thumbnails ([20770f3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/20770f3)), closes [#57](https://github.com/phloxic/videojs-sprite-thumbnails/issues/57) 111 | * toggle player class vjs-thumbnails-ready according to plugin state ([2f183d6](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2f183d6)) 112 | * upgrade plugin options on loadstart, ensure that url is a string ([1444bb1](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1444bb1)) 113 | 114 | ### Chores 115 | 116 | * **deps-dev:** bump [@babel](https://github.com/babel)/traverse from 7.21.5 to 7.23.2 ([38cda59](https://github.com/phloxic/videojs-sprite-thumbnails/commit/38cda59)) 117 | * **deps-dev:** bump word-wrap from 1.2.3 to 1.2.4 ([54521ea](https://github.com/phloxic/videojs-sprite-thumbnails/commit/54521ea)) 118 | * **package:** appease dependabot complaining about underscore ([2fb1739](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2fb1739)) 119 | 120 | ### Code Refactoring 121 | 122 | * cycle through tooltipStyle with videojs.obj.each ([0c6e453](https://github.com/phloxic/videojs-sprite-thumbnails/commit/0c6e453)) 123 | * evaluate config only on loadstart, 1 special case for url ([328d82b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/328d82b)) 124 | * remove 'diagnostic' property of plugin state ([1988633](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1988633)) 125 | * scale height of background image with CSS auto value ([62ef0e4](https://github.com/phloxic/videojs-sprite-thumbnails/commit/62ef0e4)) 126 | 127 | ### Documentation 128 | 129 | * add forgotten comma ([b501f8b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/b501f8b)) 130 | * mention option merge on loadstart in Initialization section ([8e9b0f0](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8e9b0f0)) 131 | * section on how to disable the plugin ([c20ec56](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c20ec56)) 132 | 133 | ### Styles 134 | 135 | * remove spurious parentheses around single argument ([7a8b188](https://github.com/phloxic/videojs-sprite-thumbnails/commit/7a8b188)) 136 | * tweak variable names in loadstart callback ([a2c2907](https://github.com/phloxic/videojs-sprite-thumbnails/commit/a2c2907)) 137 | 138 | 139 | ### BREAKING CHANGES 140 | 141 | * Setting the new `columns` option is mandatory. 142 | 143 | 144 | ## [2.1.1](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.1.0...v2.1.1) (2023-06-10) 145 | 146 | ### Bug Fixes 147 | 148 | * do not reset mouse time tooltip css when sprite cannot be loaded ([49b079d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/49b079d)) 149 | 150 | ### Documentation 151 | 152 | * CDN link to exact latest 2.x.x release w/ higher maxage ([6bf2b73](https://github.com/phloxic/videojs-sprite-thumbnails/commit/6bf2b73)) 153 | 154 | 155 | # [2.1.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v2.0.0...v2.1.0) (2023-06-06) 156 | 157 | ### Features 158 | 159 | * **config:** lower connection downlink default to 1.5 ([875f742](https://github.com/phloxic/videojs-sprite-thumbnails/commit/875f742)) 160 | * make ready state proper boolean ([020ca39](https://github.com/phloxic/videojs-sprite-thumbnails/commit/020ca39)) 161 | 162 | ### Chores 163 | 164 | * **deps:** bump socket.io-parser from 4.2.2 to 4.2.3 ([570fd61](https://github.com/phloxic/videojs-sprite-thumbnails/commit/570fd61)) 165 | 166 | ### Code Refactoring 167 | 168 | * improve plugin defaultState handling ([239f04f](https://github.com/phloxic/videojs-sprite-thumbnails/commit/239f04f)) 169 | * slim down handling of configuration updates ([0915d10](https://github.com/phloxic/videojs-sprite-thumbnails/commit/0915d10)) 170 | 171 | 172 | ### BREAKING CHANGES 173 | 174 | * **config:** There is a subjective factor to the downlink option. As its default has been changed to 1.5 it may be worth testing and/or setting it explicitly before updating the plugin in production. 175 | 176 | 177 | # [2.0.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v1.0.0...v2.0.0) (2023-05-22) 178 | 179 | ### Features 180 | 181 | * **loadstart:** pick options from one source only ([f01b382](https://github.com/phloxic/videojs-sprite-thumbnails/commit/f01b382)) 182 | * make plugin compatible with Video.js 8.x ([75a4e5c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/75a4e5c)) 183 | 184 | ### Bug Fixes 185 | 186 | * unconditionally try loading sprite on loadstart ([ddc250e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ddc250e)) 187 | 188 | ### Code Refactoring 189 | 190 | * no parentheses needed around single argument to arrow ([2e5cd7d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2e5cd7d)) 191 | 192 | ### Documentation 193 | 194 | * **README:** explain Video.js 8.x compatible versioning ([dba689a](https://github.com/phloxic/videojs-sprite-thumbnails/commit/dba689a)) 195 | * **README:** use ES6 syntax in code examples ([e4a2073](https://github.com/phloxic/videojs-sprite-thumbnails/commit/e4a2073)) 196 | 197 | ### Tests 198 | 199 | * correct typo in sprite filename ([00a1a27](https://github.com/phloxic/videojs-sprite-thumbnails/commit/00a1a27)) 200 | 201 | 202 | ### BREAKING CHANGES 203 | 204 | - Only Video.js v8.x.x is supported 205 | - Only browsers supported by Video.js v8.x.x are supported 206 | 207 | 208 | # [1.0.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.6.0...v1.0.0) (2023-05-17) 209 | 210 | ### Features 211 | 212 | * set up in loadstart only, focus on plugin states ([4c7a616](https://github.com/phloxic/videojs-sprite-thumbnails/commit/4c7a616)) 213 | * **tooltip:** draw border around thumbnail, not inset ([6fb8b07](https://github.com/phloxic/videojs-sprite-thumbnails/commit/6fb8b07)) 214 | * truly reset tooltip style to its original state ([bbf7c05](https://github.com/phloxic/videojs-sprite-thumbnails/commit/bbf7c05)) 215 | 216 | ### Bug Fixes 217 | 218 | * double check plugin options on loadstart if state not ready ([2eb7b76](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2eb7b76)) 219 | * load sprite on player ready if configured at player level ([1c3e22c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1c3e22c)) 220 | * make logging compatible with Video.js v6 and early v7 ([87e41c1](https://github.com/phloxic/videojs-sprite-thumbnails/commit/87e41c1)) 221 | * make tests work on loadstart ([11599c2](https://github.com/phloxic/videojs-sprite-thumbnails/commit/11599c2)) 222 | * quote url parameter when used with background-image css style ([976c751](https://github.com/phloxic/videojs-sprite-thumbnails/commit/976c751)) 223 | 224 | ### Chores 225 | 226 | * **deps-dev:** bump karma from 6.3.14 to 6.3.16 ([286834a](https://github.com/phloxic/videojs-sprite-thumbnails/commit/286834a)) 227 | * **deps-dev:** bump karma from 6.3.4 to 6.3.14 ([eda20a3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/eda20a3)) 228 | * **deps:** bump [@xmldom](https://github.com/xmldom)/xmldom from 0.7.5 to 0.7.8 ([17dda7a](https://github.com/phloxic/videojs-sprite-thumbnails/commit/17dda7a)) 229 | * **deps:** bump engine.io and socket.io ([4ad8038](https://github.com/phloxic/videojs-sprite-thumbnails/commit/4ad8038)) 230 | * **deps:** bump engine.io and socket.io ([291c568](https://github.com/phloxic/videojs-sprite-thumbnails/commit/291c568)) 231 | * **deps:** bump follow-redirects from 1.14.4 to 1.14.8 ([b5a7988](https://github.com/phloxic/videojs-sprite-thumbnails/commit/b5a7988)) 232 | * **deps:** bump json5 from 2.2.0 to 2.2.3 ([2eecbd4](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2eecbd4)) 233 | * **deps:** bump minimatch from 3.0.4 to 3.1.2 ([7650aa5](https://github.com/phloxic/videojs-sprite-thumbnails/commit/7650aa5)) 234 | * **deps:** bump minimist from 1.2.5 to 1.2.6 ([1e7c274](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1e7c274)) 235 | * **deps:** bump qs and body-parser ([29e4c2d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/29e4c2d)) 236 | * **deps:** bump shelljs from 0.8.4 to 0.8.5 ([6852333](https://github.com/phloxic/videojs-sprite-thumbnails/commit/6852333)) 237 | * **deps:** bump socket.io-parser from 4.0.4 to 4.0.5 ([355686b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/355686b)) 238 | * **deps:** bump ua-parser-js from 0.7.31 to 0.7.33 ([f7a90dc](https://github.com/phloxic/videojs-sprite-thumbnails/commit/f7a90dc)) 239 | * **docs:** use https URLs in example ([e835ec6](https://github.com/phloxic/videojs-sprite-thumbnails/commit/e835ec6)) 240 | * **package:** bump generator-videojs-plugin to v9.0.0 ([1242dd9](https://github.com/phloxic/videojs-sprite-thumbnails/commit/1242dd9)) 241 | 242 | ### Code Refactoring 243 | 244 | * leave turning `this' into var to postprocessing ([055da3f](https://github.com/phloxic/videojs-sprite-thumbnails/commit/055da3f)) 245 | * make spriteEvents and log vars local ([544ebe3](https://github.com/phloxic/videojs-sprite-thumbnails/commit/544ebe3)) 246 | * use CSS style property names, not strings ([11f3be1](https://github.com/phloxic/videojs-sprite-thumbnails/commit/11f3be1)) 247 | * use template literals instead of string concatenation ([fa4be5c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/fa4be5c)) 248 | 249 | ### Tests 250 | 251 | * state change check in own file ([4483bac](https://github.com/phloxic/videojs-sprite-thumbnails/commit/4483bac)) 252 | 253 | 254 | # [0.6.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.5.3...v0.6.0) (2021-10-03) 255 | 256 | ### Features 257 | 258 | * ability to load thumbnails specific to video sources ([#14](https://github.com/phloxic/videojs-sprite-thumbnails/issues/14)) ([9a5a1a6](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9a5a1a6)) 259 | * introduce plugin ready state ([7b151b4](https://github.com/phloxic/videojs-sprite-thumbnails/commit/7b151b4)) 260 | * start loading sprites before playback commences ([#17](https://github.com/phloxic/videojs-sprite-thumbnails/issues/17)) ([f8f462e](https://github.com/phloxic/videojs-sprite-thumbnails/commit/f8f462e)) 261 | 262 | ### Bug Fixes 263 | 264 | * actually enable plugin when preloading ([ae40e45](https://github.com/phloxic/videojs-sprite-thumbnails/commit/ae40e45)) 265 | * clean up logging ([0d39e4b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/0d39e4b)) 266 | * do not try to load image while it is preloading ([356e325](https://github.com/phloxic/videojs-sprite-thumbnails/commit/356e325)) 267 | * ensure default control bar component tree is present ([8e6e2ac](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8e6e2ac)) 268 | * load cached image even when slow connection detected ([2332aaa](https://github.com/phloxic/videojs-sprite-thumbnails/commit/2332aaa)) 269 | * typo in log message ([67d4f37](https://github.com/phloxic/videojs-sprite-thumbnails/commit/67d4f37)) 270 | 271 | ### Chores 272 | 273 | * **deps:** bump http-proxy from 1.18.0 to 1.18.1 ([39ded31](https://github.com/phloxic/videojs-sprite-thumbnails/commit/39ded31)) 274 | * **deps:** bump ini from 1.3.5 to 1.3.7 ([8ba3b38](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8ba3b38)) 275 | * **deps:** bump lodash from 4.17.15 to 4.17.19 ([cf3ec7f](https://github.com/phloxic/videojs-sprite-thumbnails/commit/cf3ec7f)) 276 | * **package:** generator-videojs-plugin 8.0.0 ([7a39775](https://github.com/phloxic/videojs-sprite-thumbnails/commit/7a39775)) 277 | * **package:** update author details ([009c31f](https://github.com/phloxic/videojs-sprite-thumbnails/commit/009c31f)) 278 | 279 | ### Code Refactoring 280 | 281 | * determine mouse position based on getPointerPosition ([bcdb8fc](https://github.com/phloxic/videojs-sprite-thumbnails/commit/bcdb8fc)) 282 | * list sprite event types in array ([64d43b6](https://github.com/phloxic/videojs-sprite-thumbnails/commit/64d43b6)) 283 | * make player level (only) configuration obvious ([b85f57d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/b85f57d)) 284 | * relocate 2 variable declarations ([0b6b683](https://github.com/phloxic/videojs-sprite-thumbnails/commit/0b6b683)) 285 | * use findPosition to obtain seek and control bar top offsets ([d663eba](https://github.com/phloxic/videojs-sprite-thumbnails/commit/d663eba)) 286 | 287 | ### Tests 288 | 289 | * omit check absence of vjs-sprite-thumbnails class conditional ([44e6db4](https://github.com/phloxic/videojs-sprite-thumbnails/commit/44e6db4)) 290 | 291 | 292 | ## [0.5.3](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.5.2...v0.5.3) (2020-04-17) 293 | 294 | ### Features 295 | 296 | * do nothing while controls are not enabled ([8630f8d](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8630f8d)) 297 | 298 | ### Chores 299 | 300 | * **package:** add repo field ([e259c58](https://github.com/phloxic/videojs-sprite-thumbnails/commit/e259c58)) 301 | * **package:** generator-videojs-plugin 7.6.3 ([a33bc00](https://github.com/phloxic/videojs-sprite-thumbnails/commit/a33bc00)) 302 | 303 | 304 | ## [0.5.2](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.5.1...v0.5.2) (2019-01-20) 305 | 306 | ### Chores 307 | 308 | * **package:** generator-videojs-plugin 7.6.1 ([9e798f5](https://github.com/phloxic/videojs-sprite-thumbnails/commit/9e798f5)) 309 | 310 | ### Code Refactoring 311 | 312 | * simplify image dimensions availability check ([67e31ec](https://github.com/phloxic/videojs-sprite-thumbnails/commit/67e31ec)) 313 | * store seekBar element in var ([a4e149c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/a4e149c)) 314 | 315 | ### Tests 316 | 317 | * remove spurious assert ([d4e697c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/d4e697c)) 318 | 319 | 320 | # [0.5.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.4.0...v0.5.0) (2018-12-14) 321 | 322 | ### Chores 323 | 324 | * generator-videojs-plugin v7.4.0 ([d97b737](https://github.com/phloxic/videojs-sprite-thumbnails/commit/d97b737)) 325 | * **package:** generator-videojs-plugin v7.5.0 ([80ebd7b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/80ebd7b)) 326 | 327 | ### Code Refactoring 328 | 329 | * compact sprite top offset calculation ([7625c85](https://github.com/phloxic/videojs-sprite-thumbnails/commit/7625c85)) 330 | * omit return statements, do all declarations first ([c8a54c4](https://github.com/phloxic/videojs-sprite-thumbnails/commit/c8a54c4)) 331 | 332 | ### Documentation 333 | 334 | * provide unpkg.com CDN link ([468ea9c](https://github.com/phloxic/videojs-sprite-thumbnails/commit/468ea9c)) 335 | 336 | ### Tests 337 | 338 | * plugin only initalizes if all required params are given ([04ed1a0](https://github.com/phloxic/videojs-sprite-thumbnails/commit/04ed1a0)) 339 | 340 | 341 | # [0.4.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.3.0...v0.4.0) (2018-05-01) 342 | 343 | ### Features 344 | 345 | * **compat:** do not support videojs 5.x, but 6.0.0 upwards ([8a005fc](https://github.com/phloxic/videojs-sprite-thumbnails/commit/8a005fc)) 346 | * **package.json:** add repository field ([36e6b0b](https://github.com/phloxic/videojs-sprite-thumbnails/commit/36e6b0b)) 347 | 348 | ### Bug Fixes 349 | 350 | * do not init plugin if mouseTimeDisplay is not present ([d3c5955](https://github.com/phloxic/videojs-sprite-thumbnails/commit/d3c5955)) 351 | 352 | 353 | # [0.3.0](https://github.com/phloxic/videojs-sprite-thumbnails/compare/v0.2.0...v0.3.0) (2018-04-30) 354 | 355 | ### Features 356 | 357 | * ensure that thumb sits directly on top of control bar ([3111311](https://github.com/phloxic/videojs-sprite-thumbnails/commit/3111311)) 358 | 359 | 360 | # 0.2.0 (2018-04-27) 361 | 362 | * responsive thumbnail size 363 | 364 | 365 | # 0.1.0 (2018-04-25) 366 | 367 | * first beta 368 | -------------------------------------------------------------------------------- /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) Christian Ebert (https://phloxic.productions) 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 | 2 | 3 | 4 | - [videojs-sprite-thumbnails](#videojs-sprite-thumbnails) 5 | - [Compatibility](#compatibility) 6 | - [Features](#features) 7 | - [Installation](#installation) 8 | - [Usage](#usage) 9 | - [` 63 | 64 | 75 | ``` 76 | 77 | ### Browserify/CommonJS 78 | 79 | When using with Browserify, install videojs-sprite-thumbnails via npm and `require` the plugin as you would any other module. 80 | 81 | ```js 82 | const videojs = require('video.js'); 83 | 84 | // The actual plugin function is exported by this module, but it is also 85 | // attached to the `Player.prototype`; so, there is no need to assign it 86 | // to a variable. 87 | require('videojs-sprite-thumbnails'); 88 | 89 | const player = videojs('my-other-video'); 90 | 91 | // More than 0 rows in combination with inserting {index) in the url 92 | // signals a sprite sequence. 93 | player.spriteThumbnails({ 94 | interval: 3, 95 | url: 'https://example.com/sprite_sequence-{index}.jpg', 96 | columns: 5, 97 | rows: 5, 98 | width: 120, 99 | height: 90 100 | }); 101 | ``` 102 | 103 | ### RequireJS/AMD 104 | 105 | When using with RequireJS (or another AMD library), get the script in whatever way you prefer and `require` the plugin as you normally would: 106 | 107 | ```js 108 | require(['video.js', 'videojs-sprite-thumbnails'], function(videojs) { 109 | const player = videojs('my-video'); 110 | 111 | player.spriteThumbnails({ 112 | url: 'https://example.com/sprite.jpg', 113 | width: 160, 114 | height: 90, 115 | columns: 10 116 | }); 117 | }); 118 | ``` 119 | 120 | ### CDN 121 | 122 | Select a 2.x version of videojs-sprite-thumbnails from the [CDN](https://unpkg.com/videojs-sprite-thumbnails@2/dist/). 123 | 124 | Or load the latest Video.js v8.x compatible release of the plugin via [script tag](#script-tag): 125 | 126 | ```html 127 | 128 | ``` 129 | 130 | ### Configuration 131 | 132 | option | type | mandatory | default | description 133 | ------ | ---- | --------- | ------- | ----------- 134 | `url` | String | single sprite | `""` | Location of sprite image. Must be set by user. 135 | `urlArray` | Array | multiple images | `[]` | Locations of images. Must be set by user. 136 | `url` | String | multiple images | `""` | Locations of multiple images via template expansion. Must be set by user. 137 | `width` | Integer | ✔ | `0` | Width of a thumbnail in pixels. 138 | `height` | Integer | ✔ | `0` | Height of a thumbnail in pixels. 139 | `columns` | Integer | | `0` | Number of thumbnail columns per image. Set both `columns` and `rows` to `1` for [individual thumbnails](#individual-thumbnails). 140 | `rows` | Integer | multiple images | `0` | Number of thumbnail rows per image. If set, the plugin will expect a sequence of images. The last image may have fewer rows. 141 | `interval` | Number | | `1` | Interval between thumbnails in seconds. 142 | `idxTag` | Function | | | Function determining how the `{index}` [template](#url-by-string-expansion) in the [`url`](#taggedurl) is expanded. Returns index as is by default. 143 | `responsive` | Integer | | `600` | Width of player in pixels below which thumbnails are responsive. Set to `0` to disable. 144 | `downlink` | Number | | `1.5` | Minimum of required [NetworkInformation downlink][downlink] where supported. Set to `0` to disable. 145 | 146 | #### Three ways to configure the location of image assets 147 | 148 | 1. url as String pointing to a single sprite image: 149 | ```js 150 | { 151 | url: 'https://example.com/single-sprite.jpg', 152 | // [... more options] 153 | } 154 | ``` 155 | 156 | 2. urlArray containing multiple (sprite) images: 157 | ```js 158 | { 159 | urlArray: [ 160 | 'https://example.com/first.jpg', 161 | 'https://example.com/second.jpg', 162 | 'https://example.com/third.jpg' 163 | ], 164 | rows: 7, // must be greater than 0 165 | // [... more options] 166 | } 167 | ``` 168 | 3. url as String expanded by the `idxTag` [function](#the-idxtag-function): 169 | ```js 170 | { 171 | url: 'https://example.com/thumbs-{index}.jpg, 172 | rows: 7, // must be greater than 0 173 | idxTag(index) { // optional 174 | return index; // this is the default 175 | }, 176 | // [... more options] 177 | } 178 | ``` 179 | 180 | #### The idxTag function 181 | 182 | The function provided by this option can be used to generate various file naming schemes of sequential sprite images. 183 | 184 | Example for thumbnail images are numbered starting from 1, 4 digits long, and padded with leading zeroes: 185 | 186 | ```js 187 | myplayer.spriteThumbnails({ 188 | // [ more options ... ] 189 | url: 'https://example.com/{index}.jpg', 190 | idxTag(index) { 191 | return `000${index + 1}`.slice(-4); 192 | }, 193 | colums: 5, 194 | rows: 5 195 | }); 196 | ``` 197 | 198 | Of course the same can be achieved by setting [`urlArray`](#urlarray) to the full list of images: 199 | 200 | ```js 201 | myplayer.spriteThumbnails({ 202 | // [ more options ... ] 203 | urlArray: [ 204 | 'https://example.com/0001.jpg', 205 | 'https://example.com/0002.jpg', 206 | 'https://example.com/0003.jpg', 207 | 'https://example.com/0004.jpg' 208 | ], 209 | colums: 5, 210 | rows: 5 211 | }); 212 | ``` 213 | 214 | #### Individual thumbnails 215 | 216 | Set both `rows` and `columns` to `1`: 217 | 218 | ```js 219 | myplayer.spriteThumbnails({ 220 | // [ other options ] 221 | url: 'https://example.com/individual-thumb-{index}.avif', 222 | columns: 1, 223 | rows: 1 224 | }); 225 | ``` 226 | 227 | ### Initialization 228 | 229 | The plugin is initialized [every time video metadata is loaded](https://docs.videojs.com/player#event:loadedmetadata). It monitors all video sources for an optional `spriteThumbnails` property. Any existing plugin configuration is updated by merging this `spriteThumbnails` object into the current configuration. Typical use cases are [playlists](#playlist-example). 230 | 231 | The image(s) are then loaded on demand, when the cursor hovers or moves over the progress bar. 232 | 233 | ### Disabling and enabling the plugin 234 | 235 | The plugin can temporarily be disabled or enabled by toggling its boolean `ready` state: 236 | 237 | ```js 238 | videojs.getPlayer('myplayer').spriteThumbnails().setState({ready: false}); 239 | ``` 240 | 241 | Disable the plugin for a specific video about to be loaded: 242 | 243 | ```js 244 | videojs.getPlayer('myplayer').src([{ 245 | type: 'video/mp4', 246 | src: 'https://example.com/nothumbs.mp4', 247 | // disable plugin with empty options object 248 | spriteThumbnails: {} 249 | }]); 250 | ``` 251 | 252 | Note that the empty `spriteThumbnails: {}` configuration in this context internally uses `spriteThumbnails: {url: '', urlArray: []}` to preserve inheritance of all other options. 253 | 254 | ### Playlist example 255 | 256 | ```js 257 | const playlist = [ 258 | [{ 259 | type: 'video/webm', 260 | src: 'https://example.com/video1.webm', 261 | 262 | // only needed once, even if alternaive source is picked 263 | spriteThumbnails: { 264 | url: 'https://example.com/thumbnails1-{index}.jpg' 265 | } 266 | }, { 267 | type: 'video/mp4', 268 | src: 'https://example.com/video1.mp4' 269 | }], [{ 270 | type: 'application/x-mpegurl', 271 | src: 'https://example.com/video2.m3u8', 272 | spriteThumbnails: { 273 | url: 'https://example.com/thumbnails2-{index}.jpg' 274 | } 275 | }] 276 | ]; 277 | 278 | const player = videojs('myplayer', { 279 | // player configuration 280 | // [...] 281 | // load first video in playlist 282 | sources: playlist[0], 283 | 284 | plugins: { 285 | // default thumbnail settings for this player 286 | spriteThumbnails: { 287 | width: 160, 288 | height: 90, 289 | columns: 5, 290 | rows: 5 291 | } 292 | } 293 | }); 294 | 295 | // play 2nd video by clicking on button with id="secondvideo" 296 | videojs.on(videojs.dom.$('button#secondvideo'), 'click', () => { 297 | player.src(playlist[1]); 298 | player.play(); 299 | }); 300 | ``` 301 | 302 | ### CSS state classes 303 | 304 | The plugin uses two CSS classes on the player element to signal the current state of plugin: 305 | 306 | class name | plugin state 307 | ---------- | ------------ 308 | `vjs-sprite-thumbnails` | plugin is/not loaded 309 | `vjs-thumbnails-ready` | plugin is/not ready to show thumbnails 310 | 311 | This allows for CSS directives which apply to player elements depending on plugin state: 312 | 313 | ```css 314 | .video-js.vjs-thumbnails-ready .vjs-progress-holder { 315 | background-color: green; 316 | } 317 | ``` 318 | 319 | ### Debugging 320 | 321 | Each plugin instance has its own [log](https://docs.videojs.com/tutorial-plugins.html#logging) which can be used for targeted debugging. Its verbosity can be set by calling the player's [plugin name property](https://docs.videojs.com/tutorial-plugins.html#the-player-plugin-name-property): 322 | 323 | ```js 324 | player.spriteThumbnails().log.level('debug'); 325 | ``` 326 | 327 | The call can also be chained directly to the [manual plugin setup](https://docs.videojs.com/tutorial-plugins.html#setting-up-a-plugin): 328 | 329 | ```js 330 | const player = videojs('example-player'); 331 | player.spriteThumbnails({ 332 | url: 'https://example.com/thumbnails-{index}.jpg', 333 | width: 240, 334 | height: 100, 335 | columns: 7, 336 | rows: 6 337 | }).log.level('debug'); 338 | ``` 339 | 340 | ## Mobile devices 341 | 342 | Since version [8.22.0](https://github.com/videojs/video.js/releases/tag/v8.22.0)[^1] Video.js supports display of [time tooltips on mobile devices](https://github.com/videojs/video.js/commit/57d6ab65ea8dbbb7718c90754f4421918c3b2c28) by setting the player option `disableSeekWhileScrubbingOnMobile: true`. By consequence this configuration allows the plugin to show thumbnails on mobile devices. 343 | 344 | [^1]: `disableSeekWhileScrubbingOnMobile` was actually introduced in Video.js v8.21.0. However, there was a minor bug with hiding the tooltips. This was [solved](https://github.com/videojs/video.js/pull/8945/commits/79a0fc9379eef8c8ee36ff68ba73d9bae4bd01e2) in v8.22.0. 345 | 346 |

Migrating from v2.1.x

347 | 348 | Plugin version 2.2.0 introduced the *mandatory* option [`columns`](#columns). Thumbnail images are now [loaded on demand](https://github.com/phloxic/videojs-sprite-thumbnails/issues/56) which interferes less with video playback. Please apply the option to your existing setups. 349 | 350 | ## Constraints 351 | 352 | - To display thumbnails the plugin expects the control bar in its [default tree structure](https://docs.videojs.com/tutorial-components.html#default-component-tree) to be present. 353 | - Live streams are not supported. 354 | 355 | ## License 356 | 357 | MIT. Copyright (c) Christian Ebert <bcc@phloxic.productions> 358 | 359 | 360 | [videojs]: http://videojs.com/ 361 | [downlink]: https://developer.mozilla.org/docs/Web/API/NetworkInformation/downlink 362 | -------------------------------------------------------------------------------- /img/oceans-thumbs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phloxic/videojs-sprite-thumbnails/d961f25ace73d9d918d1397b0ceb7f0a6d4a3137/img/oceans-thumbs.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | videojs-sprite-thumbnails Demo 6 | 7 | 8 | 9 | 10 | 15 | 19 | 20 | 21 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-sprite-thumbnails", 3 | "version": "2.2.3", 4 | "description": "Plugin to display thumbnails when hovering over the progress bar.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:phloxic/videojs-sprite-thumbnails.git" 8 | }, 9 | "main": "dist/videojs-sprite-thumbnails.cjs.js", 10 | "module": "dist/videojs-sprite-thumbnails.es.js", 11 | "generator-videojs-plugin": { 12 | "version": "9.0.0" 13 | }, 14 | "browserslist": [ 15 | ">0.2%", 16 | "not dead", 17 | "not op_mini all", 18 | "not safari < 10", 19 | "not chrome < 51", 20 | "not android < 5", 21 | "not ie < 12" 22 | ], 23 | "engines": { 24 | "node": ">=14", 25 | "npm": ">=6" 26 | }, 27 | "keywords": [ 28 | "videojs", 29 | "videojs-plugin" 30 | ], 31 | "author": "Christian Ebert (https://phloxic.productions)", 32 | "license": "MIT", 33 | "vjsstandard": { 34 | "ignore": [ 35 | "es", 36 | "cjs", 37 | "dist", 38 | "docs", 39 | "test/dist" 40 | ] 41 | }, 42 | "files": [ 43 | "CONTRIBUTING.md", 44 | "cjs/", 45 | "dist/", 46 | "docs/", 47 | "es/", 48 | "index.html", 49 | "scripts/", 50 | "src/", 51 | "test/" 52 | ], 53 | "husky": { 54 | "hooks": { 55 | "pre-commit": "lint-staged" 56 | } 57 | }, 58 | "lint-staged": { 59 | "*.js": "vjsstandard --fix", 60 | "README.md": "doctoc --notitle" 61 | }, 62 | "browser": "dist/videojs-sprite-thumbnails.js", 63 | "scripts": { 64 | "build": "npm-run-all -s clean -p build:*", 65 | "build-prod": "cross-env-shell NO_TEST_BUNDLE=1 'npm run build'", 66 | "build-test": "cross-env-shell TEST_BUNDLE_ONLY=1 'npm run build'", 67 | "build:js": "rollup -c scripts/rollup.config.js", 68 | "clean": "shx rm -rf ./dist ./test/dist ./cjs ./es && shx mkdir -p ./dist ./test/dist ./cjs ./es", 69 | "lint": "vjsstandard", 70 | "server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch", 71 | "start": "npm-run-all -p server watch", 72 | "test": "npm-run-all lint build-test && karma start scripts/karma.conf.js", 73 | "posttest": "shx cat test/dist/coverage/text.txt", 74 | "update-changelog": "conventional-changelog -p videojs -i CHANGELOG.md -s", 75 | "preversion": "npm test", 76 | "version": "is-prerelease || npm run update-changelog && git add CHANGELOG.md", 77 | "watch": "npm-run-all -p watch:*", 78 | "watch:js": "npm run build:js -- -w", 79 | "prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check" 80 | }, 81 | "dependencies": { 82 | "global": "^4.4.0", 83 | "video.js": "^8" 84 | }, 85 | "devDependencies": { 86 | "@babel/runtime": "^7.14.0", 87 | "@videojs/generator-helpers": "~3.0.0", 88 | "karma": "^6.4.3", 89 | "rollup": "^3.29.5 || ^2.50.3", 90 | "sinon": "^9.1.0", 91 | "videojs-generate-karma-config": "^8.1.0", 92 | "videojs-generate-rollup-config": "^7.0.1", 93 | "videojs-generator-verify": "^4.0.0", 94 | "videojs-standard": "^9.0.1" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /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/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 | // export the builds to rollup 11 | export default Object.values(config.builds); 12 | -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import spriteThumbs from './sprite-thumbnails.js'; 3 | import {version as VERSION} from '../package.json'; 4 | 5 | const Plugin = videojs.getPlugin('plugin'); 6 | 7 | /** 8 | * Default plugin options 9 | * 10 | * @param {String} url 11 | * Location of image(s). Must be set by user. For multiple images the 12 | * filename must contain the template {index} which is replaced by the 13 | * zero based index number of the image in the sequence. Default: ''. 14 | * @param {Array} urlArray 15 | * Array of image locations. Default: []. 16 | * @param {Integer} width 17 | * Width of a thumbnail in pixels. Must be set by user. Default: 0. 18 | * @param {Integer} height 19 | * Height of a thumbnail in pixels. Must be set by user. Default: 0. 20 | * @param {Integer} columns 21 | * Number of thumbnail columns per image. Must be set by user. 22 | * @param {Integer} rows 23 | * Number of thumbnail rows per image. If set to greater than 0, the 24 | * plugin will expect a sequence of images. Default: 0. 25 | * @param {Number} interval 26 | * Interval between thumbnail frames in seconds. Default: 1. 27 | * @param {Function} idxTag 28 | * Function determining the substitiuton of the {index} template in the 29 | * current url. Default: returns index as is. 30 | * @param {Integer} responsive 31 | * Width of player below which thumbnails are reponsive. Default: 600. 32 | * @param {Number} downlink 33 | * Minimum of NetworkInformation downlink where supported. Default: 1.5. 34 | * https://developer.mozilla.org/docs/Web/API/NetworkInformation/downlink 35 | */ 36 | const defaults = { 37 | url: '', 38 | idxTag(i) { 39 | return i; 40 | }, 41 | urlArray: [], 42 | width: 0, 43 | height: 0, 44 | columns: 0, 45 | rows: 0, 46 | interval: 1, 47 | responsive: 600, 48 | downlink: 1.5 49 | }; 50 | 51 | /** 52 | * An advanced Video.js plugin. For more information on the API 53 | * 54 | * See: https://blog.videojs.com/feature-spotlight-advanced-plugins/ 55 | */ 56 | class SpriteThumbnails extends Plugin { 57 | 58 | /** 59 | * Create a SpriteThumbnails plugin instance. 60 | * 61 | * @param {Player} player 62 | * A Video.js Player instance. 63 | * 64 | * @param {Object} [options] 65 | * An optional options object. 66 | */ 67 | constructor(player, options) { 68 | // the parent class will add player under this.player 69 | super(player, options); 70 | 71 | this.options = videojs.obj.merge(defaults, options); 72 | 73 | this.player.ready(() => { 74 | spriteThumbs(this.player, this, this.options); 75 | }); 76 | } 77 | } 78 | 79 | // Define default values for the plugin's `state` object here. 80 | SpriteThumbnails.defaultState = {ready: false}; 81 | 82 | // Include the version number. 83 | SpriteThumbnails.VERSION = VERSION; 84 | 85 | // Register the plugin with video.js. 86 | videojs.registerPlugin('spriteThumbnails', SpriteThumbnails); 87 | 88 | export default SpriteThumbnails; 89 | -------------------------------------------------------------------------------- /src/sprite-thumbnails.js: -------------------------------------------------------------------------------- 1 | import videojs from 'video.js'; 2 | import window from 'global/window'; 3 | 4 | /** 5 | * Set up sprite thumbnails for a player. 6 | * 7 | * @function spriteThumbs 8 | * @param {Player} player 9 | * The current player instance. 10 | * @param {Plugin} plugin 11 | * The current spriteThumbnails plugin instance. 12 | * @param {Object} options 13 | * Plugin configuration options. 14 | */ 15 | const spriteThumbs = (player, plugin, options) => { 16 | const navigator = window.navigator; 17 | 18 | const dom = videojs.dom; 19 | const obj = videojs.obj; 20 | const log = plugin.log; 21 | const debug = log.debug; 22 | 23 | const defaultState = { ...plugin.state }; 24 | const setDefaultState = () => { 25 | plugin.setState(defaultState); 26 | }; 27 | 28 | // default control bar component tree is expected 29 | // https://docs.videojs.com/tutorial-components.html#default-component-tree 30 | const descendants = [ 31 | 'ControlBar', 32 | 'ProgressControl', 33 | 'SeekBar', 34 | 'MouseTimeDisplay', 35 | 'TimeTooltip' 36 | ]; 37 | const [ 38 | _controlBar, 39 | _progressControl, 40 | _seekBar, 41 | // no need to assign MouseTimeDisplay 42 | , 43 | _timeTooltip 44 | ] = descendants; 45 | 46 | const playerDescendant = componentName => { 47 | const idx = descendants.indexOf(componentName); 48 | const component = player.getDescendant(descendants.slice(0, idx + 1)); 49 | 50 | if (!component) { 51 | setDefaultState(); 52 | debug(`component tree ${descendants.join(' > ')} required`); 53 | } 54 | return component; 55 | }; 56 | 57 | let tooltipEl; 58 | let tooltipStyleOrig; 59 | 60 | const getUrl = idx => { 61 | const urlArray = options.urlArray; 62 | 63 | return urlArray.length ? 64 | urlArray[idx] : options.url.replace('{index}', options.idxTag(idx)); 65 | }; 66 | 67 | const hijackMouseTooltip = evt => { 68 | if (!playerDescendant(_timeTooltip)) { 69 | return; 70 | } 71 | const seekBarEl = playerDescendant(_seekBar).el(); 72 | const controlsTop = dom 73 | .findPosition(playerDescendant(_controlBar).el()).top; 74 | const playerWidth = player.currentWidth(); 75 | const duration = player.duration(); 76 | const interval = options.interval; 77 | const columns = options.columns; 78 | const responsive = options.responsive; 79 | 80 | const rowDuration = interval * columns; 81 | // spriteDuration is needed to calculate idx 82 | const spriteDuration = rowDuration * 83 | (options.rows || Math.ceil(duration / rowDuration)); 84 | 85 | let position = dom.getPointerPosition(seekBarEl, evt).x * duration; 86 | // for single sprites idx is always 0 87 | const idx = Math.floor(position / spriteDuration); 88 | 89 | // if (idx == 0) position /= interval 90 | position = (position - spriteDuration * idx) / interval; 91 | 92 | const scaleFactor = responsive && playerWidth < responsive ? 93 | playerWidth / responsive : 1; 94 | const scaledWidth = options.width * scaleFactor; 95 | const scaledHeight = options.height * scaleFactor; 96 | const cleft = Math.floor(position % columns) * -scaledWidth; 97 | const ctop = Math.floor(position / columns) * -scaledHeight; 98 | const seekBarTop = dom.findPosition(seekBarEl).top; 99 | // top of seekBar is 0 position 100 | const topOffset = -scaledHeight - Math.max(0, seekBarTop - controlsTop); 101 | 102 | const tooltipStyle = { 103 | backgroundImage: `url("${getUrl(idx)}")`, 104 | backgroundRepeat: 'no-repeat', 105 | backgroundPosition: `${cleft}px ${ctop}px`, 106 | backgroundSize: `${scaledWidth * columns}px auto`, 107 | top: `${topOffset}px`, 108 | color: '#fff', 109 | textShadow: '1px 1px #000', 110 | // box-sizing: border-box inherited from .video-js 111 | border: '1px solid #000', 112 | // border should not overlay thumbnail area 113 | width: `${scaledWidth + 2}px`, 114 | height: `${scaledHeight + 2}px` 115 | }; 116 | 117 | obj.each(tooltipStyle, (value, key) => { 118 | tooltipEl.style[key] = value; 119 | }); 120 | }; 121 | 122 | const intCheck = opt => { 123 | const val = options[opt]; 124 | const min = opt !== 'rows' ? 1 : 0; 125 | const check = parseInt(val, 10) === val && val >= min; 126 | 127 | if (!check) { 128 | log.warn(`${opt} must be an integer greater than ${min - 1}`); 129 | } 130 | return check; 131 | }; 132 | 133 | const downlinkCheck = () => { 134 | const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; 135 | const dl = 'downlink'; 136 | const check = !connection || connection[dl] >= options[dl]; 137 | 138 | if (!check) { 139 | log(`connection.${dl} < ${options[dl]}`); 140 | } 141 | return check; 142 | }; 143 | 144 | const handleStateChanged = evt => { 145 | const pstate = plugin.state; 146 | const spriteEvents = ['mousemove', 'touchmove']; 147 | const progress = playerDescendant(_progressControl); 148 | 149 | if (pstate.ready) { 150 | debug('ready to show thumbnails'); 151 | progress.on(spriteEvents, hijackMouseTooltip); 152 | } else { 153 | if (!options.url && !options.urlArray.length) { 154 | debug('no urls given, resetting'); 155 | } 156 | if (progress) { 157 | progress.off(spriteEvents, hijackMouseTooltip); 158 | tooltipEl.style = tooltipStyleOrig; 159 | } 160 | } 161 | player.toggleClass('vjs-thumbnails-ready', pstate.ready); 162 | }; 163 | 164 | const init = evt => { 165 | // `loadstart` callback is only needed when all of the following apply: 166 | // - player is set up to load an initial video via `src` or `loadMedia` 167 | // specifying a `spriteThumbnails` config object 168 | // - the player is told e.g. by user action to load a different video `src` 169 | // or `loadMedia` before metadata of the initial video is loaded and its 170 | // `spriteThumbnails` options cannot be merged 171 | // Thereafter the `loadstart` callback is redundant. 172 | player.off('loadstart', init); 173 | 174 | // clean slate 175 | setDefaultState(); 176 | 177 | // if present, merge source config with current config 178 | const plugName = plugin.name; 179 | const thumbSource = player.currentSources().find(source => { 180 | return source.hasOwnProperty(plugName); 181 | }); 182 | let srcOpts = thumbSource && thumbSource[plugName]; 183 | 184 | if (srcOpts) { 185 | // empty config unsets url and urlArray 186 | // force urlArray or url according to precedence 187 | const urlArray = srcOpts.urlArray; 188 | 189 | if (!Object.keys(srcOpts).length) { 190 | srcOpts = {url: '', urlArray: []}; 191 | } else if (urlArray && urlArray.length) { 192 | srcOpts.url = ''; 193 | } else if (srcOpts.url) { 194 | srcOpts.urlArray = []; 195 | } 196 | plugin.options = options = obj.merge(options, srcOpts); 197 | } 198 | 199 | const mouseTimeTooltip = playerDescendant(_timeTooltip); 200 | 201 | if (!mouseTimeTooltip || evt.type === 'loadstart') { 202 | return; 203 | } 204 | tooltipEl = mouseTimeTooltip.el(); 205 | tooltipStyleOrig = tooltipEl.style; 206 | 207 | plugin.setState({ 208 | ready: !!((options.urlArray.length || options.url) && 209 | intCheck('width') && intCheck('height') && intCheck('columns') && 210 | intCheck('rows') && downlinkCheck()) 211 | }); 212 | }; 213 | 214 | plugin.on('statechanged', handleStateChanged); 215 | player.on(['loadstart', 'loadedmetadata'], init); 216 | player.addClass('vjs-sprite-thumbnails'); 217 | }; 218 | 219 | export default spriteThumbs; 220 | -------------------------------------------------------------------------------- /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-sprite-thumbnails', { 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.spriteThumbnails, 45 | 'function', 46 | 'videojs-sprite-thumbnails plugin was registered' 47 | ); 48 | 49 | this.player.spriteThumbnails({ 50 | url: '../img/oceans-thumbs.jpg', 51 | width: 240, 52 | height: 100, 53 | columns: 10 54 | }).log.level('debug'); 55 | 56 | // Tick the clock forward enough to trigger the player to be "ready". 57 | this.clock.tick(1); 58 | 59 | assert.ok( 60 | this.player.hasClass('vjs-sprite-thumbnails'), 61 | 'the plugin adds a class to the player' 62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /test/sprite-thumbnails.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-sprite-thumbnails', { 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('checking controls default tree', function(assert) { 41 | assert.expect(2); 42 | 43 | const mouseTimeDisplay = this.player.controlBar.progressControl.seekBar.mouseTimeDisplay; 44 | 45 | mouseTimeDisplay.removeChild('TimeTooltip'); 46 | assert.strictEqual( 47 | mouseTimeDisplay.getChild('TimeTooltip'), 48 | null, 49 | 'removed mouse display time tooltip: no default controls tree' 50 | ); 51 | 52 | this.player.spriteThumbnails({ 53 | url: '../img/oceans-thumbs.jpg', 54 | width: 240, 55 | height: 100, 56 | columns: 10 57 | }).log.level('all'); 58 | 59 | this.clock.tick(1); 60 | this.player.trigger('loadedmetadata'); 61 | 62 | assert.strictEqual( 63 | this.player.spriteThumbnails().state.ready, 64 | false, 65 | 'no default controls tree: plugin not ready' 66 | ); 67 | }); 68 | 69 | QUnit.test('changes ready state', function(assert) { 70 | assert.expect(7); 71 | 72 | this.player.spriteThumbnails({ 73 | url: '../img/oceans-thumbs.jpg', 74 | width: 240, 75 | height: 100 76 | }).log.level('all'); 77 | 78 | this.player.trigger('loadedmetadata'); 79 | 80 | assert.strictEqual( 81 | this.player.spriteThumbnails().state.ready, 82 | false, 83 | 'no columns given, plugin not ready to show thumbnails' 84 | ); 85 | 86 | this.player.src({src: 'dummy.mp4', spriteThumbnails: { 87 | url: '../img/oceans-thumbs.jpg', 88 | columns: 10 89 | }}); 90 | 91 | this.clock.tick(1); 92 | this.player.trigger('loadedmetadata'); 93 | 94 | assert.strictEqual( 95 | this.player.spriteThumbnails().state.ready, 96 | true, 97 | 'the plugin is now able to show thumbnails' 98 | ); 99 | assert.strictEqual( 100 | this.player.hasClass('vjs-thumbnails-ready'), 101 | true, 102 | 'player has class vjs-thumbnails-ready' 103 | ); 104 | 105 | const currentConfig = this.player.spriteThumbnails().options; 106 | 107 | this.player.src({src: 'dummy.mp4', spriteThumbnails: {}}); 108 | this.player.trigger('loadedmetadata'); 109 | 110 | assert.strictEqual( 111 | this.player.spriteThumbnails().state.ready, 112 | false, 113 | 'options empty: plugin disabled and not ready' 114 | ); 115 | assert.strictEqual( 116 | this.player.hasClass('vjs-thumbnails-ready'), 117 | false, 118 | 'player does not have class vjs-thumbnails-ready' 119 | ); 120 | 121 | const overridingConfig = this.player.spriteThumbnails().options; 122 | 123 | assert.strictEqual( 124 | overridingConfig.url === '' && 125 | overridingConfig.url !== currentConfig.url && 126 | overridingConfig.width === currentConfig.width, 127 | true, 128 | 'options empty: disabled plugin inherits options, except for url' 129 | ); 130 | 131 | this.player.src({src: 'dummy.mp4', spriteThumbnails: {url: '../img/oceans-thumbs.jpg'}}); 132 | this.player.trigger('loadedmetadata'); 133 | 134 | assert.strictEqual( 135 | this.player.spriteThumbnails().state.ready, 136 | true, 137 | 'url given on loadedmetadata, width and height inherited: the plugin will show thumbnails' 138 | ); 139 | }); 140 | --------------------------------------------------------------------------------