1
0
mirror of https://github.com/videojs/video.js.git synced 2024-11-28 08:58:46 +02:00

feat: add skip forward/backward buttons (#8147)

* remove duplicate icons from icon example

* create initial forward and back button classes

* add logic for back/forward buttons on click

* change icon used based on option passed into player

* move logic from forward and back buttons into one component

* add jsdoc comments for clarity

* create initial test file

* refactor button logic into separate files

* update skip button example and add test files

* test both the forward and backward buttons

* test handleClick fns for both forward and backward btns

* update skip buttons example

* update jsdocs for skip backward and forward buttons

* make control text accessible and use seekableEnd/Start when skipping forward/back

* update font version to use updated icons

* set control text only if config is valid

* add link to sandbox page & use localization

* update translations needed
This commit is contained in:
Usman Omar 2023-03-06 09:51:59 +00:00 committed by GitHub
parent 0022867a2a
commit 8f3f32cb2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 725 additions and 93 deletions

View File

@ -16,8 +16,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| Language file | Missing translations |
| ----------------------- | ----------------------------------------------------------------------------------- |
| ar.json (Complete) | |
| ba.json (missing 68) | Audio Player |
| ar.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ba.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -85,7 +86,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| bg.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| bg.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -153,12 +156,16 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| bn.json (missing 5) | Exit Fullscreen |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| bn.json (missing 7) | Exit Fullscreen |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| ca.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ca.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -226,7 +233,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| cs.json (missing 9) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| cs.json (missing 11) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | Exit Picture-in-Picture |
| | Picture-in-Picture |
@ -235,7 +244,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| cy.json (missing 9) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| cy.json (missing 11) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | Exit Picture-in-Picture |
| | Picture-in-Picture |
@ -244,7 +255,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| da.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| da.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -312,8 +325,11 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| de.json (Complete) | |
| el.json (missing 54) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| de.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| el.json (missing 56) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -367,24 +383,33 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| en-GB.json (has 1) | Needs manual checking. Can safely use most default strings.
| es.json (Complete) | |
| et.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| en-GB.json (has 1) | Needs manual checking. Can safely use most default strings. |
| es.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| et.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| eu.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| eu.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| fa.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| fa.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| fi.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| fi.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -452,22 +477,29 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| fr.json (Complete) | |
| gd.json (missing 7) | Exit Picture-in-Picture |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| fr.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| gd.json (missing 9) | Exit Picture-in-Picture |
| | Picture-in-Picture |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| gl.json (missing 7) | Exit Picture-in-Picture |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| gl.json (missing 9) | Exit Picture-in-Picture |
| | Picture-in-Picture |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| he.json (missing 10) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| he.json (missing 12) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | {1} is loading. |
| | Exit Picture-in-Picture |
@ -477,12 +509,16 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| hi.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| hi.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| hr.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| hr.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -550,33 +586,45 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| hu.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| hu.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| it.json (missing 7) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| it.json (missing 9) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | Raised |
| | Depressed |
| | Casual |
| | Script |
| | No content |
| ja.json (Complete) | |
| ko.json (Complete) | |
| lv.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ja.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ko.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| lv.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| nb.json (missing 7) | Exit Picture-in-Picture |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| nb.json (missing 9) | Exit Picture-in-Picture |
| | Picture-in-Picture |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| nl.json (missing 10) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| nl.json (missing 12) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | {1} is loading. |
| | Exit Picture-in-Picture |
@ -586,29 +634,39 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| nn.json (missing 7) | Exit Picture-in-Picture |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| nn.json (missing 9) | Exit Picture-in-Picture |
| | Picture-in-Picture |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| oc.json (missing 4) | Color |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| oc.json (missing 6) | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| pl.json (missing 4) | Color |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| pl.json (missing 6) | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| pt-BR.json (missing 7) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| pt-BR.json (missing 9) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| pt-PT.json (missing 53) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| pt-PT.json (missing 55) | Audio Player |
| | Video Player |
| | Seek to live, currently behind live |
| | Seek to live, currently playing live |
@ -661,17 +719,23 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| ro.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ro.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| ru.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| ru.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| sk.json (missing 9) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| sk.json (missing 11) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | Exit Picture-in-Picture |
| | Picture-in-Picture |
@ -680,7 +744,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| sl.json (missing 11) | Proportional Sans-Serif |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| sl.json (missing 13) | Proportional Sans-Serif |
| | Monospace Sans-Serif |
| | Proportional Serif |
| | Monospace Serif |
@ -691,7 +757,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| sr.json (missing 68) | Audio Player |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| sr.json (missing 70) | Audio Player |
| | Video Player |
| | Replay |
| | Seek to live, currently behind live |
@ -759,28 +827,38 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| sv.json (missing 7) | Exit Picture-in-Picture |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| sv.json (missing 9) | Exit Picture-in-Picture |
| | Picture-in-Picture |
| | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| te.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| te.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| th.json (missing 5) | No content |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| th.json (missing 7) | No content |
| | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| tr.json (missing 4) | Color |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| tr.json (missing 6) | Color |
| | Opacity |
| | Text Background |
| | Caption Area Background |
| uk.json (missing 9) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| uk.json (missing 11) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | Exit Picture-in-Picture |
| | Picture-in-Picture |
@ -789,7 +867,9 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| vi.json (missing 10) | Seek to live, currently behind live |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| vi.json (missing 12) | Seek to live, currently behind live |
| | Seek to live, currently playing live |
| | {1} is loading. |
| | Exit Picture-in-Picture |
@ -799,7 +879,11 @@ This default value is hardcoded as a default to the localize method in the SeekB
| | Opacity |
| | Text Background |
| | Caption Area Background |
| zh-CN.json (Complete) | |
| zh-TW.json (Complete) | |
| | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| zh-CN.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
| zh-TW.json (missing 2) | Skip backward {1} seconds |
| | Skip forward {1} seconds |
<!-- END langtable -->

View File

@ -27,6 +27,7 @@
<li><a href="sandbox/quality-levels.html">QualityLevels Demo</a></li>
<li><a href="sandbox/autoplay-tests.html">Autoplay Tests</a></li>
<li><a href="sandbox/noUITitleAttributes.html">noUITitleAttributes Demo</a></li>
<li><a href="sandbox/skip-buttons.html">Skip Buttons demo</a></li>
<li><a href="sandbox/debug.html">Videojs debug build test page</a></li>
</ul>

View File

@ -90,5 +90,7 @@
"Color": "Color",
"Opacity": "Opacity",
"Text Background": "Text Background",
"Caption Area Background": "Caption Area Background"
"Caption Area Background": "Caption Area Background",
"Skip backward {1} seconds": "Skip backward {1} seconds",
"Skip forward {1} seconds": "Skip forward {1} seconds"
}

121
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "video.js",
"version": "8.1.1",
"version": "8.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -1664,9 +1664,9 @@
}
},
"@videojs/http-streaming": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.0.2.tgz",
"integrity": "sha512-iSZkwTLGg3Rx78ypCCq/GsMME89ElNvU02xj7reCE2PlITMQjyYsER1w5AsySvT1A694u5yuSzEzLLGF1cL4pg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.0.0.tgz",
"integrity": "sha512-AdKmY/W2dyeJP0uALgMRmhLa4pbHMvE4OMlg6yQvufnqsz6jDFo1DYnZRv2ENDYrmVdnPH58Ehgu59053+OIhQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "4.0.0",
@ -1674,19 +1674,8 @@
"global": "^4.4.0",
"m3u8-parser": "^6.0.0",
"mpd-parser": "^1.0.1",
"mux.js": "6.3.0",
"mux.js": "6.2.0",
"video.js": "^7 || ^8"
},
"dependencies": {
"mux.js": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.3.0.tgz",
"integrity": "sha512-/QTkbSAP2+w1nxV+qTcumSDN5PA98P0tjrADijIzQHe85oBK3Akhy9AHlH0ne/GombLMz1rLyvVsmrgRxoPDrQ==",
"requires": {
"@babel/runtime": "^7.11.2",
"global": "^4.4.0"
}
}
}
},
"@videojs/vhs-utils": {
@ -15201,39 +15190,89 @@
"dev": true
},
"video.js": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.0.4.tgz",
"integrity": "sha512-fvvWauPanrKDps1HQGGL+9CIAK8G0YVwlNme0hvY0k3moXQryaRcJSLHIlPKV2j9ZFTHl32VbN43jL3TaUllfg==",
"version": "7.21.1",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.1.tgz",
"integrity": "sha512-AvHfr14ePDHCfW5Lx35BvXk7oIonxF6VGhSxocmTyqotkQpxwYdmt4tnQSV7MYzNrYHb0GI8tJMt20NDkCQrxg==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "3.0.0",
"@videojs/vhs-utils": "^4.0.0",
"@videojs/http-streaming": "2.15.1",
"@videojs/vhs-utils": "^3.0.4",
"@videojs/xhr": "2.6.0",
"aes-decrypter": "^4.0.1",
"global": "4.4.0",
"keycode": "2.2.0",
"m3u8-parser": "^6.0.0",
"mpd-parser": "^1.0.1",
"mux.js": "^6.2.0",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"keycode": "^2.2.0",
"m3u8-parser": "4.8.0",
"mpd-parser": "0.22.1",
"mux.js": "6.0.1",
"safe-json-parse": "4.0.0",
"videojs-contrib-quality-levels": "3.0.0",
"videojs-font": "3.2.0",
"videojs-vtt.js": "0.15.4"
"videojs-vtt.js": "^0.15.4"
},
"dependencies": {
"@videojs/http-streaming": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.0.0.tgz",
"integrity": "sha512-AdKmY/W2dyeJP0uALgMRmhLa4pbHMvE4OMlg6yQvufnqsz6jDFo1DYnZRv2ENDYrmVdnPH58Ehgu59053+OIhQ==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.15.1.tgz",
"integrity": "sha512-/uuN3bVkEeJAdrhu5Hyb19JoUo3CMys7yf2C1vUjeL1wQaZ4Oe8JrZzRrnWZ0rjvPgKfNLPXQomsRtgrMoRMJQ==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "4.0.0",
"aes-decrypter": "4.0.1",
"@videojs/vhs-utils": "3.0.5",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"m3u8-parser": "^6.0.0",
"mpd-parser": "^1.0.1",
"mux.js": "6.2.0",
"video.js": "^7 || ^8"
"m3u8-parser": "4.8.0",
"mpd-parser": "^0.22.1",
"mux.js": "6.0.1",
"video.js": "^6 || ^7"
}
},
"@videojs/vhs-utils": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
"requires": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
}
},
"aes-decrypter": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0",
"pkcs7": "^1.0.4"
}
},
"m3u8-parser": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.8.0.tgz",
"integrity": "sha512-UqA2a/Pw3liR6Df3gwxrqghCP17OpPlQj6RBPLYygf/ZSQ4MoSgvdvhvt35qV+3NaaA0FSZx93Ix+2brT1U7cA==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0"
}
},
"mpd-parser": {
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz",
"integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==",
"requires": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"@xmldom/xmldom": "^0.8.3",
"global": "^4.4.0"
}
},
"mux.js": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
"requires": {
"@babel/runtime": "^7.11.2",
"global": "^4.4.0"
}
},
"videojs-font": {
@ -15252,9 +15291,9 @@
}
},
"videojs-font": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.0.0.tgz",
"integrity": "sha512-sRXrizXF0zBMatXjg2vGpn63G26uH3XqwyZ9PjU2H9xqGm7fRSVYuxOJCUME6us/1rFl9yxkRKk31WTQ7XZkww=="
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz",
"integrity": "sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w=="
},
"videojs-generate-karma-config": {
"version": "8.0.1",

View File

@ -97,7 +97,7 @@
"mux.js": "^6.2.0",
"safe-json-parse": "4.0.0",
"videojs-contrib-quality-levels": "3.0.0",
"videojs-font": "4.0.0",
"videojs-font": "4.1.0",
"videojs-vtt.js": "0.15.4"
},
"devDependencies": {

View File

@ -48,9 +48,6 @@
<li><span class="vjs-icon-forward-5"></span> <code>.vjs-icon-forward-5</code></li>
<li><span class="vjs-icon-forward-10"></span> <code>.vjs-icon-forward-10</code></li>
<li><span class="vjs-icon-forward-30"></span> <code>.vjs-icon-forward-30</code></li>
<li><span class="vjs-icon-forward-30"></span> <code>.vjs-icon-forward-30</code></li>
<li><span class="vjs-icon-forward-30"></span> <code>.vjs-icon-forward-30</code></li>
<li><span class="vjs-icon-forward-30"></span> <code>.vjs-icon-forward-30</code></li>
<li><span class="vjs-icon-audio"></span> <code>.vjs-icon-audio</code></li>
<li><span class="vjs-icon-next-item"></span> <code>.vjs-next-item</code></li>
<li><span class="vjs-icon-previous-item"></span> <code>.vjs-icon-previous-item</code></li>

View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Video.js Sandbox</title>
<link href="../dist/video-js.css" rel="stylesheet" type="text/css" />
<script src="../dist/video.js"></script>
</head>
<body>
<div>
<h2>Forward: 5, Backward: 10</h2>
<video-js
id="vid1"
controls
preload="auto"
width="640"
height="264"
poster="https://vjs.zencdn.net/v/oceans.png"
>
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
<source src="https://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
<source src="https://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
<track
kind="captions"
src="../docs/examples/shared/example-captions.vtt"
srclang="en"
label="English"
/>
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to
a web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank"
>supports HTML5 video</a
>
</p>
</video-js>
</div>
<div>
<h2>Forward: 10, Backward: 30</h2>
<video-js
id="vid2"
controls
preload="auto"
width="640"
height="264"
poster="https://vjs.zencdn.net/v/oceans.png"
>
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
<source src="https://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
<source src="https://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
<track
kind="captions"
src="../docs/examples/shared/example-captions.vtt"
srclang="en"
label="English"
/>
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to
a web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank"
>supports HTML5 video</a
>
</p>
</video-js>
</div>
<div>
<h2>Forward: 10</h2>
<video-js
id="vid3"
controls
preload="auto"
width="640"
height="264"
poster="https://vjs.zencdn.net/v/oceans.png"
>
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
<source src="https://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
<source src="https://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
<track
kind="captions"
src="../docs/examples/shared/example-captions.vtt"
srclang="en"
label="English"
/>
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to
a web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank"
>supports HTML5 video</a
>
</p>
</video-js>
</div>
<script>
var vid1 = document.getElementById("vid1");
var options = {
muted: true,
controlBar: { skipButtons: { forward: 5, backward: 10 } },
};
videojs(vid1, options);
var vid2 = document.getElementById("vid2");
options.controlBar.skipButtons = { forward: 10, backward: 30 };
videojs(vid2, options);
var vid3 = document.getElementById("vid3");
options.controlBar.skipButtons = {forward: 10}
videojs(vid3, options);
</script>
</body>
</html>

View File

@ -0,0 +1,40 @@
.video-js .vjs-skip-forward-5 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-forward-5;
}
}
.video-js .vjs-skip-forward-10 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-forward-10;
}
}
.video-js .vjs-skip-forward-30 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-forward-30;
}
}
.video-js .vjs-skip-backward-5 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-replay-5;
}
}
.video-js .vjs-skip-backward-10 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-replay-10;
}
}
.video-js .vjs-skip-backward-30 {
cursor: pointer;
& .vjs-icon-placeholder {
@extend .vjs-icon-replay-30;
}
}

View File

@ -42,6 +42,7 @@
@import "components/adaptive";
@import "components/captions-settings";
@import "components/title-bar";
@import "components/skip-buttons";
@import "print";

View File

@ -16,6 +16,8 @@ import './progress-control/progress-control.js';
import './picture-in-picture-toggle.js';
import './fullscreen-toggle.js';
import './volume-panel.js';
import './skip-buttons/skip-forward.js';
import './skip-buttons/skip-backward.js';
import './text-track-controls/chapters-button.js';
import './text-track-controls/descriptions-button.js';
import './text-track-controls/subtitles-button.js';
@ -55,6 +57,8 @@ class ControlBar extends Component {
ControlBar.prototype.options_ = {
children: [
'playToggle',
'skipBackward',
'skipForward',
'volumePanel',
'currentTimeDisplay',
'timeDivider',

View File

@ -0,0 +1,69 @@
import Button from '../../button';
import Component from '../../component';
/**
* Button to skip backward a configurable amount of time
* through a video. Renders in the control bar.
*
* * e.g. options: {controlBar: {skipButtons: backward: 5}}
*
* @extends Button
*/
class SkipBackward extends Button {
constructor(player, options) {
super(player, options);
this.validOptions = [5, 10, 30];
this.skipTime = this.getSkipBackwardTime();
if (this.skipTime && this.validOptions.includes(this.skipTime)) {
this.controlText(this.localize('Skip backward {1} seconds', [this.skipTime]));
this.show();
} else {
this.hide();
}
}
getSkipBackwardTime() {
const playerOptions = this.options_.playerOptions;
return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.backward;
}
buildCSSClass() {
return `vjs-skip-backward-${this.getSkipBackwardTime()} ${super.buildCSSClass()}`;
}
/**
* On click, skips backward in the video by a configurable amount of seconds.
* If the current time in the video is less than the configured 'skip backward' time,
* skips to beginning of video or seekable range.
*
* Handle a click on a `SkipBackward` button
*
* @param {EventTarget~Event} event
* The `click` event that caused this function
* to be called
*/
handleClick(event) {
const currentVideoTime = this.player_.currentTime();
const liveTracker = this.player_.liveTracker;
const seekableStart = liveTracker && liveTracker.isLive() && liveTracker.seekableStart();
let newTime;
if (seekableStart && (currentVideoTime - this.skipTime <= seekableStart)) {
newTime = seekableStart;
} else if (currentVideoTime >= this.skipTime) {
newTime = currentVideoTime - this.skipTime;
} else {
newTime = 0;
}
this.player_.currentTime(newTime);
}
}
SkipBackward.prototype.controlText_ = 'Skip Backward';
Component.registerComponent('SkipBackward', SkipBackward);
export default SkipBackward;

View File

@ -0,0 +1,66 @@
import Button from '../../button';
import Component from '../../component';
/**
* Button to skip forward a configurable amount of time
* through a video. Renders in the control bar.
*
* e.g. options: {controlBar: {skipButtons: forward: 5}}
*
* @extends Button
*/
class SkipForward extends Button {
constructor(player, options) {
super(player, options);
this.validOptions = [5, 10, 30];
this.skipTime = this.getSkipForwardTime();
if (this.skipTime && this.validOptions.includes(this.skipTime)) {
this.controlText(this.localize('Skip forward {1} seconds', [this.skipTime]));
this.show();
} else {
this.hide();
}
}
getSkipForwardTime() {
const playerOptions = this.options_.playerOptions;
return playerOptions.controlBar && playerOptions.controlBar.skipButtons && playerOptions.controlBar.skipButtons.forward;
}
buildCSSClass() {
return `vjs-skip-forward-${this.getSkipForwardTime()} ${super.buildCSSClass()}`;
}
/**
* On click, skips forward in the duration/seekable range by a configurable amount of seconds.
* If the time left in the duration/seekable range is less than the configured 'skip forward' time,
* skips to end of duration/seekable range.
*
* Handle a click on a `SkipForward` button
*
* @param {EventTarget~Event} event
* The `click` event that caused this function
* to be called
*/
handleClick(event) {
const currentVideoTime = this.player_.currentTime();
const liveTracker = this.player_.liveTracker;
const duration = (liveTracker && liveTracker.isLive()) ? liveTracker.seekableEnd() : this.player_.duration();
let newTime;
if (currentVideoTime + this.skipTime <= duration) {
newTime = currentVideoTime + this.skipTime;
} else {
newTime = duration;
}
this.player_.currentTime(newTime);
}
}
Component.registerComponent('SkipForward', SkipForward);
export default SkipForward;

View File

@ -0,0 +1,98 @@
/* eslint-env qunit */
import TestHelpers from '../../test-helpers';
import sinon from 'sinon';
import { createTimeRange } from '../../../../src/js/utils/time';
QUnit.module('SkipBackwardButton');
QUnit.test('is not visible if option is not set', function(assert) {
const player = TestHelpers.makePlayer({});
const button = player.controlBar.skipBackward;
assert.expect(1);
assert.ok(button.hasClass('vjs-hidden'), 'has the vjs-hidden class');
player.dispose();
});
QUnit.test('is not visible if option is set with an invalid config', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 4}}});
const button = player.controlBar.skipBackward;
assert.expect(1);
assert.ok(button.hasClass('vjs-hidden'), 'has the vjs-hidden class');
player.dispose();
});
QUnit.test('is visible if option is set with a valid config', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 5}}});
const button = player.controlBar.skipBackward;
assert.expect(2);
assert.notOk(button.hasClass('vjs-hidden'), 'button is not hidden');
assert.ok(button.hasClass('vjs-skip-backward-5'), 'button shows correct icon');
player.dispose();
});
QUnit.test('control text should specify amount seconds that can be skipped backward', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 10}}});
const button = player.controlBar.skipBackward;
assert.expect(1);
assert.strictEqual(button.controlText_, 'Skip backward 10 seconds', 'control text specifies number of seconds backward');
player.dispose();
});
QUnit.test('skip to beginning of seekable range in live video if current time - seekableStart is less than skip bacward time', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 30}}});
const button = player.controlBar.skipBackward;
player.options_.liveui = true;
player.seekable = () => createTimeRange(20, 40);
player.duration(Infinity);
player.currentTime(22);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 20, 'player set to start of seekable range');
player.dispose();
});
QUnit.test('skips to beginning of video if current time is less than configured skip backward time', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 30}}});
const button = player.controlBar.skipBackward;
player.currentTime(25);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 0, 'player current time is set to start of video');
player.dispose();
});
QUnit.test('skip backward in video by configured skip backward time amount', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {backward: 30}}});
const button = player.controlBar.skipBackward;
player.currentTime(31);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 1, 'player current time set 30 seconds back on button click');
player.dispose();
});

View File

@ -0,0 +1,117 @@
/* eslint-env qunit */
import TestHelpers from '../../test-helpers';
import sinon from 'sinon';
import { createTimeRange } from '../../../../src/js/utils/time';
QUnit.module('SkipForwardButton');
QUnit.test('is not visible if option is not set', function(assert) {
const player = TestHelpers.makePlayer({});
const button = player.controlBar.skipForward;
assert.expect(1);
assert.ok(button.hasClass('vjs-hidden'), 'has the vjs-hidden class');
player.dispose();
});
QUnit.test('is not visible if option is set with an invalid config', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 4}}});
const button = player.controlBar.skipForward;
assert.expect(1);
assert.ok(button.hasClass('vjs-hidden'), 'has the vjs-hidden class');
player.dispose();
});
QUnit.test('is visible if option is set with a valid config', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 5}}});
const button = player.controlBar.skipForward;
assert.expect(2);
assert.notOk(button.hasClass('vjs-hidden'), 'button is not hidden');
assert.ok(button.hasClass('vjs-skip-forward-5'), 'button shows correct icon');
player.dispose();
});
QUnit.test('control text should specify how many seconds forward can be skipped', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 5}}});
const button = player.controlBar.skipForward;
assert.expect(1);
assert.strictEqual(button.controlText_, 'Skip forward 5 seconds', 'control text specifies seconds forward');
player.dispose();
});
QUnit.test('skips to end of video if time remaining is less than configured skip forward time', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 30}}});
const button = player.controlBar.skipForward;
player.currentTime(25);
player.duration(30);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 30, 'player current time is set to end of video');
player.dispose();
});
QUnit.test('skips to end of seekable range in live video if time remaining is less than configured skip forward time', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 30}}});
const button = player.controlBar.skipForward;
player.options_.liveui = true;
player.seekable = () => createTimeRange(0, 20);
player.duration(Infinity);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 20, 'player current time is set to end of seekable range in live video');
player.dispose();
});
QUnit.test('skips forward in live video by configured skip forward time amount', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 10}}});
const button = player.controlBar.skipForward;
player.options_.liveui = true;
player.seekable = () => createTimeRange(0, 45);
player.duration(Infinity);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 10, 'player current time is set 10 seconds forward in live video');
player.dispose();
});
QUnit.test('skips forward in video by configured skip forward time amount', function(assert) {
const player = TestHelpers.makePlayer({controlBar: {skipButtons: {forward: 30}}});
const button = player.controlBar.skipForward;
player.currentTime(0);
player.duration(50);
const curTimeSpy = sinon.spy(player, 'currentTime');
button.trigger('click');
assert.expect(1);
assert.equal(curTimeSpy.getCall(1).args[0], 30, 'player current time set 30 seconds forward after button click');
player.dispose();
});