Comparar commits
21 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 32a1429520 | |||
| b981f254de | |||
| fa8fc80b83 | |||
| a11f66ff4f | |||
| 82d396a1fb | |||
| 971dbaecc4 | |||
| 3aff03be84 | |||
| 9a59aee304 | |||
| 4156307792 | |||
| 249532ad45 | |||
| 6296ca8538 | |||
| 2e2dbde4b4 | |||
| 18cdf08c0e | |||
| b931b6bde9 | |||
| cfb9621884 | |||
| 5cafd387ea | |||
| 55c101de0c | |||
| f4ee2767eb | |||
| a53ef7d1b8 | |||
| 834b94385c | |||
| f606f9df50 |
@@ -6,6 +6,22 @@ _(none)_
|
||||
|
||||
--------------------
|
||||
|
||||
## 5.10.3 (2016-05-27)
|
||||
* @BrandonOCasey fixed source handlers being disposed multiple times when a video is put into the video element directly ([view](https://github.com/videojs/video.js/pull/3343))
|
||||
|
||||
## 5.10.2 (2016-05-12)
|
||||
* @gkatsev nulled out currentSource_ in setSource ([view](https://github.com/videojs/video.js/pull/3314))
|
||||
|
||||
## 5.10.1 (2016-05-03)
|
||||
* @nickygerritsen Pass tech options to source handlers ([view](https://github.com/videojs/video.js/pull/3245))
|
||||
* @gkatsev Use fonts 2.0 that do not require wrapping codepoints ([view](https://github.com/videojs/video.js/pull/3252))
|
||||
* @chrisauclair Make controls visible for accessibility reasons ([view](https://github.com/videojs/video.js/pull/3237))
|
||||
* @gkatsev updated text track documentation and crossorigin warning. Fixes #1888, #1958, #2628, #3202 ([view](https://github.com/videojs/video.js/pull/3256))
|
||||
* @BrandonOCasey added audio and video track support ([view](https://github.com/videojs/video.js/pull/3173))
|
||||
* @OwenEdwards added language attribute in HTML files for accessibility ([view](https://github.com/videojs/video.js/pull/3257))
|
||||
* @incompl clear currentSource_ after subsequent loadstarts ([view](https://github.com/videojs/video.js/pull/3285))
|
||||
* @forbesjo add an audio track selector menu button ([view](https://github.com/videojs/video.js/pull/3223))
|
||||
|
||||
## 5.9.2 (2016-04-19)
|
||||
* @gkatsev grouped text track errors in the console, if we can ([view](https://github.com/videojs/video.js/pull/3259))
|
||||
|
||||
|
||||
+2
-14
@@ -159,7 +159,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
skin: {
|
||||
files: ['src/css/**/*'],
|
||||
tasks: ['sass', 'wrapcodepoints']
|
||||
tasks: ['sass']
|
||||
},
|
||||
jshint: {
|
||||
files: ['src/**/*', 'test/unit/**/*.js', 'Gruntfile.js'],
|
||||
@@ -464,7 +464,6 @@ module.exports = function(grunt) {
|
||||
'uglify',
|
||||
|
||||
'sass',
|
||||
'wrapcodepoints',
|
||||
'version:css',
|
||||
'cssmin',
|
||||
|
||||
@@ -489,18 +488,7 @@ module.exports = function(grunt) {
|
||||
'zip:dist'
|
||||
]);
|
||||
|
||||
// Sass turns unicode codepoints into utf8 characters.
|
||||
// We don't want that so we unwrapped them in the templates/scss.hbs file.
|
||||
// After sass has generated our css file, we need to wrap the codepoints
|
||||
// in quotes for it to work.
|
||||
grunt.registerTask('wrapcodepoints', function() {
|
||||
const sassConfig = grunt.config.get('sass.build.files');
|
||||
const cssPath = Object.keys(sassConfig)[0];
|
||||
const css = grunt.file.read(cssPath);
|
||||
grunt.file.write(cssPath, css.replace(/(\\f\w+);/g, "'$1';"));
|
||||
});
|
||||
|
||||
grunt.registerTask('skin', ['sass', 'wrapcodepoints']);
|
||||
grunt.registerTask('skin', ['sass']);
|
||||
|
||||
// Default task - build and test
|
||||
grunt.registerTask('default', ['test']);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.3",
|
||||
"keywords": [
|
||||
"videojs",
|
||||
"html5",
|
||||
|
||||
externo
+41
-32
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1
-1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1703
-353
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
externo
+10
-9
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1
-1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Text Descriptions, Chapters & Captions Example</title>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
|
||||
externo
BIN
Arquivo binário não exibido.
externo
+3
@@ -94,6 +94,9 @@
|
||||
<glyph glyph-name="audio-description"
|
||||
unicode=""
|
||||
horiz-adv-x="1792" d=" M795.5138904615 457.270933L795.5138904615 1221.5248286325C971.84576475 1225.085121904 1107.39330415 1232.12360523 1207.223857 1161.5835220499998C1303.033991 1093.8857027 1377.7922305 962.20560625 1364.3373135 792.9476205000001C1350.102593 613.9029365000001 1219.6655764999998 463.4600215 1050.12389545 448.2843645000001C965.8259268 440.7398275000001 798.21890505 448.2843645000001 798.21890505 448.2843645000001C798.21890505 448.2843645000001 795.2791410655 453.016494 795.5138904615 457.270933M966.1564647 649.0863960000001C1076.16084135 644.6767075 1152.385591 707.3020429999999 1163.8910079999998 807.9351875C1179.2994744999999 942.71878505 1089.73043585 1030.3691748 960.74508635 1020.7227954L960.74508635 658.08043C960.6196169500002 652.9482330000001 962.7606933 650.3134680000001 966.1564647 649.0863960000001 M1343.2299685 457.3517725000002C1389.9059734 444.3690160000001 1404.0840274999998 496.0596970000001 1424.48294065 532.2791494999999C1469.0084255 611.2788500000001 1502.5101322 712.8584189999999 1503.0416912 828.9881705C1503.8147453000001 995.5680973 1438.8404296 1117.7973688000002 1378.4383305 1200.62456881045L1348.652139905 1200.62456881045C1346.6001063899998 1187.06858424 1356.44474056 1175.024791325 1362.18395859 1164.6588891000001C1408.2649952 1081.49431985 1450.96645015 966.7230041 1451.57490975 834.9817034999999C1452.27106325 683.8655425000002 1402.00636065 557.5072264999999 1343.2299685 457.3517725000002 M1488.0379675 457.3517725000002C1534.7139723999999 444.3690160000001 1548.8825828 496.0671625 1569.29093965 532.2791494999999C1613.8164245 611.2788500000001 1647.3113856500001 712.8584189999999 1647.8496902000002 828.9881705C1648.6227442999998 995.5680973 1583.6484286 1117.7973688000002 1523.2463295 1200.62456881045L1493.460138905 1200.62456881045C1491.40810539 1187.06858424 1501.250041305 1175.021805755 1506.9919575899999 1164.6588891000001C1553.0729942 1081.49431985 1595.7757984 966.7230041 1596.3829087499998 834.9817034999999C1597.07906225 683.8655425000002 1546.8143596500001 557.5072264999999 1488.0379675 457.3517725000002 M1631.9130380000001 457.3517725000002C1678.5890429 444.3690160000001 1692.7576533 496.0671625 1713.1660101500001 532.2791494999999C1757.691495 611.2788500000001 1791.1864561500001 712.8584189999999 1791.7247607000002 828.9881705C1792.4978148 995.5680973 1727.5234991000002 1117.7973688000002 1667.1214 1200.62456881045L1637.3352094050001 1200.62456881045C1635.28317589 1187.06858424 1645.1251118050002 1175.02329854 1650.86702809 1164.6588891000001C1696.9480647 1081.49431985 1739.64951965 966.7230041 1740.25797925 834.9817034999999C1740.95413275 683.8655425000002 1690.6894301500001 557.5072264999999 1631.9130380000001 457.3517725000002 M15.66796875 451.481947L254.03034755 451.481947L319.0356932 551.1747990000001L543.6261075 551.6487970000001C543.6261075 551.6487970000001 543.8541115 483.7032095 543.8541115 451.481947L714.4993835 451.481947L714.4993835 1230.9210795L508.643051 1230.9210795C488.8579955 1197.5411595 15.66796875 451.481947 15.66796875 451.481947L15.66796875 451.481947zM550.0048155000001 959.9708615L550.0048155000001 710.916297L408.4199 711.8642895L550.0048155000001 959.9708615L550.0048155000001 959.9708615z" />
|
||||
<glyph glyph-name="audio"
|
||||
unicode=""
|
||||
horiz-adv-x="1792" d=" M896 1717.3333333333333C524.9066666666668 1717.3333333333333 224 1416.4266666666667 224 1045.3333333333333V522.6666666666665C224 399.0933333333333 324.4266666666667 298.6666666666665 448 298.6666666666665H672V896H373.3333333333334V1045.3333333333333C373.3333333333334 1333.92 607.4133333333334 1568 896 1568S1418.6666666666667 1333.92 1418.6666666666667 1045.3333333333333V896H1120V298.6666666666665H1344C1467.5733333333335 298.6666666666665 1568 399.0933333333333 1568 522.6666666666665V1045.3333333333333C1568 1416.4266666666667 1267.0933333333332 1717.3333333333333 896 1717.3333333333333z" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 25 KiB Depois Largura: | Altura: | Tamanho: 26 KiB |
externo
BIN
Arquivo binário não exibido.
externo
BIN
Arquivo binário não exibido.
Arquivo binário não exibido.
externo
+41
-31
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1
-1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1703
-353
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
externo
+32
-14
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+10
-10
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+1
-1
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Text Descriptions, Chapters & Captions Example</title>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
|
||||
+148
-170
@@ -161,175 +161,153 @@ The following is a list of official language codes.
|
||||
|
||||
<table border="0" cellspacing="5" cellpadding="5">
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
<table>
|
||||
<tr><th>ab<th><td>Abkhazian</td></tr>
|
||||
<tr><th>aa<th><td>Afar</td></tr>
|
||||
<tr><th>af<th><td>Afrikaans</td></tr>
|
||||
<tr><th>sq<th><td>Albanian</td></tr>
|
||||
<tr><th>am<th><td>Amharic</td></tr>
|
||||
<tr><th>ar<th><td>Arabic</td></tr>
|
||||
<tr><th>an<th><td>Aragonese</td></tr>
|
||||
<tr><th>hy<th><td>Armenian</td></tr>
|
||||
<tr><th>as<th><td>Assamese</td></tr>
|
||||
<tr><th>ay<th><td>Aymara</td></tr>
|
||||
<tr><th>az<th><td>Azerbaijani</td></tr>
|
||||
<tr><th>ba<th><td>Bashkir</td></tr>
|
||||
<tr><th>eu<th><td>Basque</td></tr>
|
||||
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
|
||||
<tr><th>dz<th><td>Bhutani</td></tr>
|
||||
<tr><th>bh<th><td>Bihari</td></tr>
|
||||
<tr><th>bi<th><td>Bislama</td></tr>
|
||||
<tr><th>br<th><td>Breton</td></tr>
|
||||
<tr><th>bg<th><td>Bulgarian</td></tr>
|
||||
<tr><th>my<th><td>Burmese</td></tr>
|
||||
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
|
||||
<tr><th>km<th><td>Cambodian</td></tr>
|
||||
<tr><th>ca<th><td>Catalan</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
|
||||
<tr><th>co<th><td>Corsican</td></tr>
|
||||
<tr><th>hr<th><td>Croatian</td></tr>
|
||||
<tr><th>cs<th><td>Czech</td></tr>
|
||||
<tr><th>da<th><td>Danish</td></tr>
|
||||
<tr><th>nl<th><td>Dutch</td></tr>
|
||||
<tr><th>en<th><td>English</td></tr>
|
||||
<tr><th>eo<th><td>Esperanto</td></tr>
|
||||
<tr><th>et<th><td>Estonian</td></tr>
|
||||
<tr><th>fo<th><td>Faeroese</td></tr>
|
||||
<tr><th>fa<th><td>Farsi</td></tr>
|
||||
<tr><th>fj<th><td>Fiji</td></tr>
|
||||
<tr><th>fi<th><td>Finnish</td></tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<table>
|
||||
<tr><th>fr<th><td>French</td></tr>
|
||||
<tr><th>fy<th><td>Frisian</td></tr>
|
||||
<tr><th>gl<th><td>Galician</td></tr>
|
||||
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
|
||||
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
|
||||
<tr><th>ka<th><td>Georgian</td></tr>
|
||||
<tr><th>de<th><td>German</td></tr>
|
||||
<tr><th>el<th><td>Greek</td></tr>
|
||||
<tr><th>kl<th><td>Greenlandic</td></tr>
|
||||
<tr><th>gn<th><td>Guarani</td></tr>
|
||||
<tr><th>gu<th><td>Gujarati</td></tr>
|
||||
<tr><th>ht<th><td>Haitian Creole</td></tr>
|
||||
<tr><th>ha<th><td>Hausa</td></tr>
|
||||
<tr><th>he<th><td>Hebrew</td></tr>
|
||||
<tr><th>hi<th><td>Hindi</td></tr>
|
||||
<tr><th>hu<th><td>Hungarian</td></tr>
|
||||
<tr><th>is<th><td>Icelandic</td></tr>
|
||||
<tr><th>io<th><td>Ido</td></tr>
|
||||
<tr><th>id<th><td>Indonesian</td></tr>
|
||||
<tr><th>ia<th><td>Interlingua</td></tr>
|
||||
<tr><th>ie<th><td>Interlingue</td></tr>
|
||||
<tr><th>iu<th><td>Inuktitut</td></tr>
|
||||
<tr><th>ik<th><td>Inupiak</td></tr>
|
||||
<tr><th>ga<th><td>Irish</td></tr>
|
||||
<tr><th>it<th><td>Italian</td></tr>
|
||||
<tr><th>ja<th><td>Japanese</td></tr>
|
||||
<tr><th>jv<th><td>Javanese</td></tr>
|
||||
<tr><th>kn<th><td>Kannada</td></tr>
|
||||
<tr><th>ks<th><td>Kashmiri</td></tr>
|
||||
<tr><th>kk<th><td>Kazakh</td></tr>
|
||||
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
|
||||
<tr><th>ky<th><td>Kirghiz</td></tr>
|
||||
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
|
||||
<tr><th>ko<th><td>Korean</td></tr>
|
||||
<tr><th>ku<th><td>Kurdish</td></tr>
|
||||
<tr><th>lo<th><td>Laothian</td></tr>
|
||||
<tr><th>la<th><td>Latin</td></tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<table>
|
||||
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
|
||||
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
|
||||
<tr><th>ln<th><td>Lingala</td></tr>
|
||||
<tr><th>lt<th><td>Lithuanian</td></tr>
|
||||
<tr><th>mk<th><td>Macedonian</td></tr>
|
||||
<tr><th>mg<th><td>Malagasy</td></tr>
|
||||
<tr><th>ms<th><td>Malay</td></tr>
|
||||
<tr><th>ml<th><td>Malayalam</td></tr>
|
||||
<tr><th>mt<th><td>Maltese</td></tr>
|
||||
<tr><th>mi<th><td>Maori</td></tr>
|
||||
<tr><th>mr<th><td>Marathi</td></tr>
|
||||
<tr><th>mo<th><td>Moldavian</td></tr>
|
||||
<tr><th>mn<th><td>Mongolian</td></tr>
|
||||
<tr><th>na<th><td>Nauru</td></tr>
|
||||
<tr><th>ne<th><td>Nepali</td></tr>
|
||||
<tr><th>no<th><td>Norwegian</td></tr>
|
||||
<tr><th>oc<th><td>Occitan</td></tr>
|
||||
<tr><th>or<th><td>Oriya</td></tr>
|
||||
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
|
||||
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
|
||||
<tr><th>pl<th><td>Polish</td></tr>
|
||||
<tr><th>pt<th><td>Portuguese</td></tr>
|
||||
<tr><th>pa<th><td>Punjabi</td></tr>
|
||||
<tr><th>qu<th><td>Quechua</td></tr>
|
||||
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
|
||||
<tr><th>ro<th><td>Romanian</td></tr>
|
||||
<tr><th>ru<th><td>Russian</td></tr>
|
||||
<tr><th>sm<th><td>Samoan</td></tr>
|
||||
<tr><th>sg<th><td>Sangro</td></tr>
|
||||
<tr><th>sa<th><td>Sanskrit</td></tr>
|
||||
<tr><th>sr<th><td>Serbian</td></tr>
|
||||
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
|
||||
<tr><th>st<th><td>Sesotho</td></tr>
|
||||
<tr><th>tn<th><td>Setswana</td></tr>
|
||||
<tr><th>sn<th><td>Shona</td></tr>
|
||||
<tr><th>ii<th><td>Sichuan Yi</td></tr>
|
||||
<tr><th>sd<th><td>Sindhi</td></tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<table>
|
||||
<tr><th>si<th><td>Sinhalese</td></tr>
|
||||
<tr><th>ss<th><td>Siswati</td></tr>
|
||||
<tr><th>sk<th><td>Slovak</td></tr>
|
||||
<tr><th>sl<th><td>Slovenian</td></tr>
|
||||
<tr><th>so<th><td>Somali</td></tr>
|
||||
<tr><th>es<th><td>Spanish</td></tr>
|
||||
<tr><th>su<th><td>Sundanese</td></tr>
|
||||
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
|
||||
<tr><th>sv<th><td>Swedish</td></tr>
|
||||
<tr><th>tl<th><td>Tagalog</td></tr>
|
||||
<tr><th>tg<th><td>Tajik</td></tr>
|
||||
<tr><th>ta<th><td>Tamil</td></tr>
|
||||
<tr><th>tt<th><td>Tatar</td></tr>
|
||||
<tr><th>te<th><td>Telugu</td></tr>
|
||||
<tr><th>th<th><td>Thai</td></tr>
|
||||
<tr><th>bo<th><td>Tibetan</td></tr>
|
||||
<tr><th>ti<th><td>Tigrinya</td></tr>
|
||||
<tr><th>to<th><td>Tonga</td></tr>
|
||||
<tr><th>ts<th><td>Tsonga</td></tr>
|
||||
<tr><th>tr<th><td>Turkish</td></tr>
|
||||
<tr><th>tk<th><td>Turkmen</td></tr>
|
||||
<tr><th>tw<th><td>Twi</td></tr>
|
||||
<tr><th>ug<th><td>Uighur</td></tr>
|
||||
<tr><th>uk<th><td>Ukrainian</td></tr>
|
||||
<tr><th>ur<th><td>Urdu</td></tr>
|
||||
<tr><th>uz<th><td>Uzbek</td></tr>
|
||||
<tr><th>vi<th><td>Vietnamese</td></tr>
|
||||
<tr><th>vo<th><td>Volapük</td></tr>
|
||||
<tr><th>wa<th><td>Wallon</td></tr>
|
||||
<tr><th>cy<th><td>Welsh</td></tr>
|
||||
<tr><th>wo<th><td>Wolof</td></tr>
|
||||
<tr><th>xh<th><td>Xhosa</td></tr>
|
||||
<tr><th>yi<th><td>Yiddish</td></tr>
|
||||
<tr><th>yo<th><td>Yoruba</td></tr>
|
||||
<tr><th>zu<th><td>Zulu</td></tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
<table>
|
||||
<tr><th>ab<th><td>Abkhazian</td></tr>
|
||||
<tr><th>aa<th><td>Afar</td></tr>
|
||||
<tr><th>af<th><td>Afrikaans</td></tr>
|
||||
<tr><th>sq<th><td>Albanian</td></tr>
|
||||
<tr><th>am<th><td>Amharic</td></tr>
|
||||
<tr><th>ar<th><td>Arabic</td></tr>
|
||||
<tr><th>an<th><td>Aragonese</td></tr>
|
||||
<tr><th>hy<th><td>Armenian</td></tr>
|
||||
<tr><th>as<th><td>Assamese</td></tr>
|
||||
<tr><th>ay<th><td>Aymara</td></tr>
|
||||
<tr><th>az<th><td>Azerbaijani</td></tr>
|
||||
<tr><th>ba<th><td>Bashkir</td></tr>
|
||||
<tr><th>eu<th><td>Basque</td></tr>
|
||||
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
|
||||
<tr><th>dz<th><td>Bhutani</td></tr>
|
||||
<tr><th>bh<th><td>Bihari</td></tr>
|
||||
<tr><th>bi<th><td>Bislama</td></tr>
|
||||
<tr><th>br<th><td>Breton</td></tr>
|
||||
<tr><th>bg<th><td>Bulgarian</td></tr>
|
||||
<tr><th>my<th><td>Burmese</td></tr>
|
||||
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
|
||||
<tr><th>km<th><td>Cambodian</td></tr>
|
||||
<tr><th>ca<th><td>Catalan</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
|
||||
<tr><th>co<th><td>Corsican</td></tr>
|
||||
<tr><th>hr<th><td>Croatian</td></tr>
|
||||
<tr><th>cs<th><td>Czech</td></tr>
|
||||
<tr><th>da<th><td>Danish</td></tr>
|
||||
<tr><th>nl<th><td>Dutch</td></tr>
|
||||
<tr><th>en<th><td>English</td></tr>
|
||||
<tr><th>eo<th><td>Esperanto</td></tr>
|
||||
<tr><th>et<th><td>Estonian</td></tr>
|
||||
<tr><th>fo<th><td>Faeroese</td></tr>
|
||||
<tr><th>fa<th><td>Farsi</td></tr>
|
||||
<tr><th>fj<th><td>Fiji</td></tr>
|
||||
<tr><th>fi<th><td>Finnish</td></tr>
|
||||
<tr><th>fr<th><td>French</td></tr>
|
||||
<tr><th>fy<th><td>Frisian</td></tr>
|
||||
<tr><th>gl<th><td>Galician</td></tr>
|
||||
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
|
||||
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
|
||||
<tr><th>ka<th><td>Georgian</td></tr>
|
||||
<tr><th>de<th><td>German</td></tr>
|
||||
<tr><th>el<th><td>Greek</td></tr>
|
||||
<tr><th>kl<th><td>Greenlandic</td></tr>
|
||||
<tr><th>gn<th><td>Guarani</td></tr>
|
||||
<tr><th>gu<th><td>Gujarati</td></tr>
|
||||
<tr><th>ht<th><td>Haitian Creole</td></tr>
|
||||
<tr><th>ha<th><td>Hausa</td></tr>
|
||||
<tr><th>he<th><td>Hebrew</td></tr>
|
||||
<tr><th>hi<th><td>Hindi</td></tr>
|
||||
<tr><th>hu<th><td>Hungarian</td></tr>
|
||||
<tr><th>is<th><td>Icelandic</td></tr>
|
||||
<tr><th>io<th><td>Ido</td></tr>
|
||||
<tr><th>id<th><td>Indonesian</td></tr>
|
||||
<tr><th>ia<th><td>Interlingua</td></tr>
|
||||
<tr><th>ie<th><td>Interlingue</td></tr>
|
||||
<tr><th>iu<th><td>Inuktitut</td></tr>
|
||||
<tr><th>ik<th><td>Inupiak</td></tr>
|
||||
<tr><th>ga<th><td>Irish</td></tr>
|
||||
<tr><th>it<th><td>Italian</td></tr>
|
||||
<tr><th>ja<th><td>Japanese</td></tr>
|
||||
<tr><th>jv<th><td>Javanese</td></tr>
|
||||
<tr><th>kn<th><td>Kannada</td></tr>
|
||||
<tr><th>ks<th><td>Kashmiri</td></tr>
|
||||
<tr><th>kk<th><td>Kazakh</td></tr>
|
||||
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
|
||||
<tr><th>ky<th><td>Kirghiz</td></tr>
|
||||
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
|
||||
<tr><th>ko<th><td>Korean</td></tr>
|
||||
<tr><th>ku<th><td>Kurdish</td></tr>
|
||||
<tr><th>lo<th><td>Laothian</td></tr>
|
||||
<tr><th>la<th><td>Latin</td></tr>
|
||||
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
|
||||
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
|
||||
<tr><th>ln<th><td>Lingala</td></tr>
|
||||
<tr><th>lt<th><td>Lithuanian</td></tr>
|
||||
<tr><th>mk<th><td>Macedonian</td></tr>
|
||||
<tr><th>mg<th><td>Malagasy</td></tr>
|
||||
<tr><th>ms<th><td>Malay</td></tr>
|
||||
<tr><th>ml<th><td>Malayalam</td></tr>
|
||||
<tr><th>mt<th><td>Maltese</td></tr>
|
||||
<tr><th>mi<th><td>Maori</td></tr>
|
||||
<tr><th>mr<th><td>Marathi</td></tr>
|
||||
<tr><th>mo<th><td>Moldavian</td></tr>
|
||||
<tr><th>mn<th><td>Mongolian</td></tr>
|
||||
<tr><th>na<th><td>Nauru</td></tr>
|
||||
<tr><th>ne<th><td>Nepali</td></tr>
|
||||
<tr><th>no<th><td>Norwegian</td></tr>
|
||||
<tr><th>oc<th><td>Occitan</td></tr>
|
||||
<tr><th>or<th><td>Oriya</td></tr>
|
||||
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
|
||||
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
|
||||
<tr><th>pl<th><td>Polish</td></tr>
|
||||
<tr><th>pt<th><td>Portuguese</td></tr>
|
||||
<tr><th>pa<th><td>Punjabi</td></tr>
|
||||
<tr><th>qu<th><td>Quechua</td></tr>
|
||||
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
|
||||
<tr><th>ro<th><td>Romanian</td></tr>
|
||||
<tr><th>ru<th><td>Russian</td></tr>
|
||||
<tr><th>sm<th><td>Samoan</td></tr>
|
||||
<tr><th>sg<th><td>Sangro</td></tr>
|
||||
<tr><th>sa<th><td>Sanskrit</td></tr>
|
||||
<tr><th>sr<th><td>Serbian</td></tr>
|
||||
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
|
||||
<tr><th>st<th><td>Sesotho</td></tr>
|
||||
<tr><th>tn<th><td>Setswana</td></tr>
|
||||
<tr><th>sn<th><td>Shona</td></tr>
|
||||
<tr><th>ii<th><td>Sichuan Yi</td></tr>
|
||||
<tr><th>sd<th><td>Sindhi</td></tr>
|
||||
<tr><th>si<th><td>Sinhalese</td></tr>
|
||||
<tr><th>ss<th><td>Siswati</td></tr>
|
||||
<tr><th>sk<th><td>Slovak</td></tr>
|
||||
<tr><th>sl<th><td>Slovenian</td></tr>
|
||||
<tr><th>so<th><td>Somali</td></tr>
|
||||
<tr><th>es<th><td>Spanish</td></tr>
|
||||
<tr><th>su<th><td>Sundanese</td></tr>
|
||||
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
|
||||
<tr><th>sv<th><td>Swedish</td></tr>
|
||||
<tr><th>tl<th><td>Tagalog</td></tr>
|
||||
<tr><th>tg<th><td>Tajik</td></tr>
|
||||
<tr><th>ta<th><td>Tamil</td></tr>
|
||||
<tr><th>tt<th><td>Tatar</td></tr>
|
||||
<tr><th>te<th><td>Telugu</td></tr>
|
||||
<tr><th>th<th><td>Thai</td></tr>
|
||||
<tr><th>bo<th><td>Tibetan</td></tr>
|
||||
<tr><th>ti<th><td>Tigrinya</td></tr>
|
||||
<tr><th>to<th><td>Tonga</td></tr>
|
||||
<tr><th>ts<th><td>Tsonga</td></tr>
|
||||
<tr><th>tr<th><td>Turkish</td></tr>
|
||||
<tr><th>tk<th><td>Turkmen</td></tr>
|
||||
<tr><th>tw<th><td>Twi</td></tr>
|
||||
<tr><th>ug<th><td>Uighur</td></tr>
|
||||
<tr><th>uk<th><td>Ukrainian</td></tr>
|
||||
<tr><th>ur<th><td>Urdu</td></tr>
|
||||
<tr><th>uz<th><td>Uzbek</td></tr>
|
||||
<tr><th>vi<th><td>Vietnamese</td></tr>
|
||||
<tr><th>vo<th><td>Volapük</td></tr>
|
||||
<tr><th>wa<th><td>Wallon</td></tr>
|
||||
<tr><th>cy<th><td>Welsh</td></tr>
|
||||
<tr><th>wo<th><td>Wolof</td></tr>
|
||||
<tr><th>xh<th><td>Xhosa</td></tr>
|
||||
<tr><th>yi<th><td>Yiddish</td></tr>
|
||||
<tr><th>yo<th><td>Yoruba</td></tr>
|
||||
<tr><th>zu<th><td>Zulu</td></tr>
|
||||
</table>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
+145
-169
@@ -1,44 +1,50 @@
|
||||
Tracks
|
||||
======
|
||||
# Tracks
|
||||
|
||||
Text Tracks are a function of HTML5 video for providing time triggered text to the viewer. Video.js makes tracks work across all browsers. There are currently five types of tracks:
|
||||
|
||||
- **Subtitles**: Translations of the dialogue in the video for when audio is available but not understood. Subtitles are shown over the video.
|
||||
- **Captions**: Transcription of the dialogue, sound effects, musical cues, and other audio information for when the viewer is deaf/hard of hearing, or the video is muted. Captions are also shown over the video.
|
||||
- **Chapters**: Chapter titles that are used to create navigation within the video. Typically they're in the form of a list of chapters that the viewer can click on to go to a specific chapter.
|
||||
- **Descriptions** (not supported yet): Text descriptions of what's happening in the video for when the video portion isn't available, because the viewer is blind, not using a screen, or driving and about to crash because they're trying to enjoy a video while driving. Descriptions are read by a screen reader or turned into a separate audio track.
|
||||
- **Metadata** (not supported yet): Tracks that have data meant for javascript to parse and do something with. These aren't shown to the user.
|
||||
- **Descriptions**: Text descriptions of what's happening in the video for when the video portion isn't available, because the viewer is blind, not using a screen, or driving and about to crash because they're trying to enjoy a video while driving. Descriptions are read by a screen reader or turned into a separate audio track.
|
||||
- **Metadata**: Tracks that have data meant for javascript to parse and do something with. These aren't shown to the user.
|
||||
|
||||
Creating the Text File
|
||||
----------------------
|
||||
## Creating the Text File
|
||||
Timed text requires a text file in [WebVTT](http://dev.w3.org/html5/webvtt/) format. This format defines a list of "cues" that have a start time, and end time, and text to display. [Microsoft has a builder](https://dev.modern.ie/testdrive/demos/captionmaker/) that can help you get started on the file.
|
||||
|
||||
When creating captions, there's also additional [caption formatting techniques] (http://www.theneitherworld.com/mcpoodle/SCC_TOOLS/DOCS/SCC_FORMAT.HTML#style) that would be good to use, like brackets around sound effects: [ sound effect ]. If you'd like a more in depth style guide for captioning, you can reference the [Captioning Key](http://www.dcmp.org/captioningkey/), but keep in mind not all features are supported by WebVTT or (more likely) the Video.js WebVTT implementation.
|
||||
|
||||
Adding to Video.js
|
||||
------------------
|
||||
## Adding to Video.js
|
||||
Once you have your WebVTT file created, you can add it to Video.js using the track tag. Put your track tag after all the source elements, and before any fallback content.
|
||||
|
||||
```html
|
||||
<video id="example_video_1" class="video-js vjs-default-skin"
|
||||
<video id="example_video_1" class="video-js"
|
||||
controls preload="auto" width="640" height="264"
|
||||
data-setup='{"example_option":true}'>
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type="video/mp4" />
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.webm" type="video/webm" />
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.ogv" type="video/ogg" />
|
||||
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4" />
|
||||
<source src="http://vjs.zencdn.net/v/oceans.webm" type="video/webm" />
|
||||
<source src="http://vjs.zencdn.net/v/oceans.ogv" type="video/ogg" />
|
||||
|
||||
<track kind="captions" src="http://example.com/path/to/captions.vtt" srclang="en" label="English" default>
|
||||
|
||||
</video>
|
||||
```
|
||||
|
||||
Subtitles from Another Domain
|
||||
-----------------------------
|
||||
You can also add tracks [programatically](#api).
|
||||
|
||||
## Subtitles from Another Domain
|
||||
Because we're pulling in the text track file via Javascript, the [same-origin policy](http://en.wikipedia.org/wiki/Same_origin_policy) applies. If you'd like to have a player served from one domain,
|
||||
but the text track served from another, you'll need to [enable CORS](http://enable-cors.org/) in order to do so.
|
||||
In addition to enabling CORS on the server serving the text tracks, you will need to add the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) to the video element itself. This attribute has two values `anonymous` and `use-credentials`. Most users will want to use `anonymous` with cross-origin tracks.
|
||||
It can be added to the video element like so:
|
||||
```html
|
||||
<video class="video-js" crossorigin="anonymous">
|
||||
<source src="http://vjs.zencdn.net/v/oceans.mp4" type="video/mp4">
|
||||
<track src="http://example.com/oceans.vtt" kind="captions" srclang="en" label="English">
|
||||
</video>
|
||||
```
|
||||
One thing to be aware of is that in this case the video files themselves will *also* needs CORS headers applied to it. This is because some browsers apply the crossorigin attribute to the video source itself and not just the tracks and is considered a [security concern by the spec](https://html.spec.whatwg.org/multipage/embedded-content.html#security-and-privacy-considerations).
|
||||
|
||||
Track Attributes
|
||||
----------------
|
||||
## Track Attributes
|
||||
Additional settings for track tags.
|
||||
|
||||
### kind
|
||||
@@ -52,157 +58,127 @@ The default attribute can be used to have a track default to showing. Otherwise
|
||||
NOTE: For chapters, default is required if you want the chapters menu to show.
|
||||
|
||||
### srclang
|
||||
The two-letter code (valid BCP 47 language tag) for the language of the text track, for example "en" for English. Here's a list of available language codes.
|
||||
The two-letter code (valid BCP 47 language tag) for the language of the text track, for example "en" for English. A list of language codes is [available here](languages.md#language-codes).
|
||||
|
||||
<table border="0" cellspacing="5" cellpadding="5">
|
||||
<tr>
|
||||
<table>
|
||||
<tr><th>ab<th><td>Abkhazian</td></tr>
|
||||
<tr><th>aa<th><td>Afar</td></tr>
|
||||
<tr><th>af<th><td>Afrikaans</td></tr>
|
||||
<tr><th>sq<th><td>Albanian</td></tr>
|
||||
<tr><th>am<th><td>Amharic</td></tr>
|
||||
<tr><th>ar<th><td>Arabic</td></tr>
|
||||
<tr><th>an<th><td>Aragonese</td></tr>
|
||||
<tr><th>hy<th><td>Armenian</td></tr>
|
||||
<tr><th>as<th><td>Assamese</td></tr>
|
||||
<tr><th>ay<th><td>Aymara</td></tr>
|
||||
<tr><th>az<th><td>Azerbaijani</td></tr>
|
||||
<tr><th>ba<th><td>Bashkir</td></tr>
|
||||
<tr><th>eu<th><td>Basque</td></tr>
|
||||
<tr><th>bn<th><td>Bengali (Bangla)</td></tr>
|
||||
<tr><th>dz<th><td>Bhutani</td></tr>
|
||||
<tr><th>bh<th><td>Bihari</td></tr>
|
||||
<tr><th>bi<th><td>Bislama</td></tr>
|
||||
<tr><th>br<th><td>Breton</td></tr>
|
||||
<tr><th>bg<th><td>Bulgarian</td></tr>
|
||||
<tr><th>my<th><td>Burmese</td></tr>
|
||||
<tr><th>be<th><td>Byelorussian (Belarusian)</td></tr>
|
||||
<tr><th>km<th><td>Cambodian</td></tr>
|
||||
<tr><th>ca<th><td>Catalan</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Simplified)</td></tr>
|
||||
<tr><th>zh<th><td>Chinese (Traditional)</td></tr>
|
||||
<tr><th>co<th><td>Corsican</td></tr>
|
||||
<tr><th>hr<th><td>Croatian</td></tr>
|
||||
<tr><th>cs<th><td>Czech</td></tr>
|
||||
<tr><th>da<th><td>Danish</td></tr>
|
||||
<tr><th>nl<th><td>Dutch</td></tr>
|
||||
<tr><th>en<th><td>English</td></tr>
|
||||
<tr><th>eo<th><td>Esperanto</td></tr>
|
||||
<tr><th>et<th><td>Estonian</td></tr>
|
||||
<tr><th>fo<th><td>Faeroese</td></tr>
|
||||
<tr><th>fa<th><td>Farsi</td></tr>
|
||||
<tr><th>fj<th><td>Fiji</td></tr>
|
||||
<tr><th>fi<th><td>Finnish</td></tr>
|
||||
<tr><th>fr<th><td>French</td></tr>
|
||||
<tr><th>fy<th><td>Frisian</td></tr>
|
||||
<tr><th>gl<th><td>Galician</td></tr>
|
||||
<tr><th>gd<th><td>Gaelic (Scottish)</td></tr>
|
||||
<tr><th>gv<th><td>Gaelic (Manx)</td></tr>
|
||||
<tr><th>ka<th><td>Georgian</td></tr>
|
||||
<tr><th>de<th><td>German</td></tr>
|
||||
<tr><th>el<th><td>Greek</td></tr>
|
||||
<tr><th>kl<th><td>Greenlandic</td></tr>
|
||||
<tr><th>gn<th><td>Guarani</td></tr>
|
||||
<tr><th>gu<th><td>Gujarati</td></tr>
|
||||
<tr><th>ht<th><td>Haitian Creole</td></tr>
|
||||
<tr><th>ha<th><td>Hausa</td></tr>
|
||||
<tr><th>he<th><td>Hebrew</td></tr>
|
||||
<tr><th>hi<th><td>Hindi</td></tr>
|
||||
<tr><th>hu<th><td>Hungarian</td></tr>
|
||||
<tr><th>is<th><td>Icelandic</td></tr>
|
||||
<tr><th>io<th><td>Ido</td></tr>
|
||||
<tr><th>id<th><td>Indonesian</td></tr>
|
||||
<tr><th>ia<th><td>Interlingua</td></tr>
|
||||
<tr><th>ie<th><td>Interlingue</td></tr>
|
||||
<tr><th>iu<th><td>Inuktitut</td></tr>
|
||||
<tr><th>ik<th><td>Inupiak</td></tr>
|
||||
<tr><th>ga<th><td>Irish</td></tr>
|
||||
<tr><th>it<th><td>Italian</td></tr>
|
||||
<tr><th>ja<th><td>Japanese</td></tr>
|
||||
<tr><th>jv<th><td>Javanese</td></tr>
|
||||
<tr><th>kn<th><td>Kannada</td></tr>
|
||||
<tr><th>ks<th><td>Kashmiri</td></tr>
|
||||
<tr><th>kk<th><td>Kazakh</td></tr>
|
||||
<tr><th>rw<th><td>Kinyarwanda (Ruanda)</td></tr>
|
||||
<tr><th>ky<th><td>Kirghiz</td></tr>
|
||||
<tr><th>rn<th><td>Kirundi (Rundi)</td></tr>
|
||||
<tr><th>ko<th><td>Korean</td></tr>
|
||||
<tr><th>ku<th><td>Kurdish</td></tr>
|
||||
<tr><th>lo<th><td>Laothian</td></tr>
|
||||
<tr><th>la<th><td>Latin</td></tr>
|
||||
<tr><th>lv<th><td>Latvian (Lettish)</td></tr>
|
||||
<tr><th>li<th><td>Limburgish ( Limburger)</td></tr>
|
||||
<tr><th>ln<th><td>Lingala</td></tr>
|
||||
<tr><th>lt<th><td>Lithuanian</td></tr>
|
||||
<tr><th>mk<th><td>Macedonian</td></tr>
|
||||
<tr><th>mg<th><td>Malagasy</td></tr>
|
||||
<tr><th>ms<th><td>Malay</td></tr>
|
||||
<tr><th>ml<th><td>Malayalam</td></tr>
|
||||
<tr><th>mt<th><td>Maltese</td></tr>
|
||||
<tr><th>mi<th><td>Maori</td></tr>
|
||||
<tr><th>mr<th><td>Marathi</td></tr>
|
||||
<tr><th>mo<th><td>Moldavian</td></tr>
|
||||
<tr><th>mn<th><td>Mongolian</td></tr>
|
||||
<tr><th>na<th><td>Nauru</td></tr>
|
||||
<tr><th>ne<th><td>Nepali</td></tr>
|
||||
<tr><th>no<th><td>Norwegian</td></tr>
|
||||
<tr><th>oc<th><td>Occitan</td></tr>
|
||||
<tr><th>or<th><td>Oriya</td></tr>
|
||||
<tr><th>om<th><td>Oromo (Afan, Galla)</td></tr>
|
||||
<tr><th>ps<th><td>Pashto (Pushto)</td></tr>
|
||||
<tr><th>pl<th><td>Polish</td></tr>
|
||||
<tr><th>pt<th><td>Portuguese</td></tr>
|
||||
<tr><th>pa<th><td>Punjabi</td></tr>
|
||||
<tr><th>qu<th><td>Quechua</td></tr>
|
||||
<tr><th>rm<th><td>Rhaeto-Romance</td></tr>
|
||||
<tr><th>ro<th><td>Romanian</td></tr>
|
||||
<tr><th>ru<th><td>Russian</td></tr>
|
||||
<tr><th>sm<th><td>Samoan</td></tr>
|
||||
<tr><th>sg<th><td>Sangro</td></tr>
|
||||
<tr><th>sa<th><td>Sanskrit</td></tr>
|
||||
<tr><th>sr<th><td>Serbian</td></tr>
|
||||
<tr><th>sh<th><td>Serbo-Croatian</td></tr>
|
||||
<tr><th>st<th><td>Sesotho</td></tr>
|
||||
<tr><th>tn<th><td>Setswana</td></tr>
|
||||
<tr><th>sn<th><td>Shona</td></tr>
|
||||
<tr><th>ii<th><td>Sichuan Yi</td></tr>
|
||||
<tr><th>sd<th><td>Sindhi</td></tr>
|
||||
<tr><th>si<th><td>Sinhalese</td></tr>
|
||||
<tr><th>ss<th><td>Siswati</td></tr>
|
||||
<tr><th>sk<th><td>Slovak</td></tr>
|
||||
<tr><th>sl<th><td>Slovenian</td></tr>
|
||||
<tr><th>so<th><td>Somali</td></tr>
|
||||
<tr><th>es<th><td>Spanish</td></tr>
|
||||
<tr><th>su<th><td>Sundanese</td></tr>
|
||||
<tr><th>sw<th><td>Swahili (Kiswahili)</td></tr>
|
||||
<tr><th>sv<th><td>Swedish</td></tr>
|
||||
<tr><th>tl<th><td>Tagalog</td></tr>
|
||||
<tr><th>tg<th><td>Tajik</td></tr>
|
||||
<tr><th>ta<th><td>Tamil</td></tr>
|
||||
<tr><th>tt<th><td>Tatar</td></tr>
|
||||
<tr><th>te<th><td>Telugu</td></tr>
|
||||
<tr><th>th<th><td>Thai</td></tr>
|
||||
<tr><th>bo<th><td>Tibetan</td></tr>
|
||||
<tr><th>ti<th><td>Tigrinya</td></tr>
|
||||
<tr><th>to<th><td>Tonga</td></tr>
|
||||
<tr><th>ts<th><td>Tsonga</td></tr>
|
||||
<tr><th>tr<th><td>Turkish</td></tr>
|
||||
<tr><th>tk<th><td>Turkmen</td></tr>
|
||||
<tr><th>tw<th><td>Twi</td></tr>
|
||||
<tr><th>ug<th><td>Uighur</td></tr>
|
||||
<tr><th>uk<th><td>Ukrainian</td></tr>
|
||||
<tr><th>ur<th><td>Urdu</td></tr>
|
||||
<tr><th>uz<th><td>Uzbek</td></tr>
|
||||
<tr><th>vi<th><td>Vietnamese</td></tr>
|
||||
<tr><th>vo<th><td>Volapük</td></tr>
|
||||
<tr><th>wa<th><td>Wallon</td></tr>
|
||||
<tr><th>cy<th><td>Welsh</td></tr>
|
||||
<tr><th>wo<th><td>Wolof</td></tr>
|
||||
<tr><th>xh<th><td>Xhosa</td></tr>
|
||||
<tr><th>yi<th><td>Yiddish</td></tr>
|
||||
<tr><th>yo<th><td>Yoruba</td></tr>
|
||||
<tr><th>zu<th><td>Zulu</td></tr>
|
||||
</table>
|
||||
</tr>
|
||||
</table>
|
||||
## Interacting with Text Tracks
|
||||
### Showing tracks programmatically
|
||||
Some of you would want to turn captions on and off programmatically rather than just forcing the user to do so themselves. This can be easily achieved by modifying the `mode` of the text tracks.
|
||||
The `mode` can be one of three values `disabled`, `hidden`, and `showing`.
|
||||
When a text track's `mode` is `disabled`, the track does not show on screen as the video is playing.
|
||||
When the `mode` is set to `showing`, the track is visible to the viewer and updates while the video is playing.
|
||||
You can change of a particular track like so:
|
||||
```js
|
||||
let tracks = player.textTracks();
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
|
||||
// find the captions track that's in english
|
||||
if (track.kind === 'captions' && track.language === 'en') {
|
||||
track.mode = 'showing';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Doing something when a cue becomes active
|
||||
Above, we mentioned that `mode` can also be `hidden`, what this means is that the track will update
|
||||
as the video is playing but it won't be visible to the viewer. This is most useful for `metadata` text tracks.
|
||||
One usecase for metadata text tracks is to have something happen when their cues become active, to do so, you listen to the `cuechange` event on the track. These events fire when the mode is `showing` as well.
|
||||
Here's an example:
|
||||
```js
|
||||
let tracks = player.textTracks();
|
||||
let metadataTrack;
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
|
||||
// find the metadata track that's labeled ads
|
||||
if (track.kind === 'captions' && track.label === 'ads') {
|
||||
track.mode = 'hidden';
|
||||
// store it for usage outside of the loop
|
||||
metadataTrack = track;
|
||||
}
|
||||
}
|
||||
|
||||
metadataTrack.addEventListener('cuechange', function() {
|
||||
player.ads.startLinearAdMode();
|
||||
});
|
||||
```
|
||||
|
||||
## Emulated Text Tracks
|
||||
By default, video.js will try and use native text tracks if possible and fall back to emulated text tracks if the native functionality is broken or incomplete or non-existent.
|
||||
The Flash tech will always use the emulated text track functionality.
|
||||
The video.js API and TextTrack objects were modeled after the w3c's specification.
|
||||
video.js uses [Mozilla's vtt.js](https://github.com/mozilla/vtt.js) library to parse and display its emulated text tracks.
|
||||
|
||||
If you wanted to disable native text track functionality and force video.js to use emulated text tracks always, you can supply the `nativeTextTrack` option to the tech like so:
|
||||
```js
|
||||
let player = videojs('myvideo', {
|
||||
html5: {
|
||||
nativeTextTrack: false
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Text Track Settings
|
||||
When using emulated Text Tracks, captions will have an additional item in the menu called "caption settings".
|
||||
This allows the viewer of the player to change some styles of how the captions are displayed on screen.
|
||||
|
||||
If you don't want that, you can disable it by turning off the text track settings component and hiding the menu item like so:
|
||||
```js
|
||||
let player = videojs('myvideo', {
|
||||
// make the text track settings dialog not initialize
|
||||
textTrackSettings: false
|
||||
});
|
||||
```
|
||||
```css
|
||||
/* hide the captions settings item from the captions menu */
|
||||
.vjs-texttrack-settings {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
## Text Track Precedence
|
||||
In general, the Descriptions tracks is of lower precedence than captions and subtitles.
|
||||
What this means for you?
|
||||
* If you are using the `default` attribute, videojs will choose the first track that is marked as `default` and turn it on. If There are multiple tracks marked `default`, it will try and turn on the first `captions` or `subtitles` track *before* any `descriptions` tracks.
|
||||
* This only applied to the emulated captions support, native text tracks behavior will change depending on the browser
|
||||
* If you select a given track from the menu, videojs will turn off all the other tracks of the same kind. This may seem like you can have both subtitles and captions turned on at the same time but unfortuantely, at this time we only support one track being displayed at a time.
|
||||
* This means that for emulated text tracks, we'll choose the first captions or subtitles track that is enabled to display.
|
||||
* When native text tracks are supported, we will still disable the other tracks of the same kind but it is possible that multiple text tracks are shown.
|
||||
* If a `descriptions` track is selected and subsequently a `subtitles` or `captions` track is selected, the `descriptions` track is disabled and its menu button is also disabled.
|
||||
* When enabling a track programmatically, there's not much checking that videojs does.
|
||||
* For emulated text tracks, when it's time to display the captions, video.js would choose the first track that's showing, again choosing `subtitles` or `captions` over `descriptions`, if necessary.
|
||||
* For native text tracks, this behavior depends on the browser. Some browsers will let you have multiple text tracks but others will disable all other tracks when a new one is selected.
|
||||
|
||||
## API
|
||||
|
||||
### `player.textTracks() -> TextTrackList`
|
||||
This is the main interface into the text tracks of the player.
|
||||
It return a TextTrackList which lists all the tracks on the player.
|
||||
|
||||
### `player.remoteTextTracks() -> TextTrackList`
|
||||
This is a helper method to get a list of all the tracks that were created from `track` elements or that were added to the player by the `addRemoteTextTrack` method. All these tracks are removeable from the player, where-as not all tracks from `player.textTracks()` are necessarily removeable.
|
||||
|
||||
### `player.remoteTextTrackEls() -> HTMLTrackElementList`
|
||||
Another helper method, this is a list of all the `track` elements associated with the player. Both emulated or otherwise.
|
||||
|
||||
### `player.addTextTrack(String kind, [String label [, String language]]) -> TextTrack`
|
||||
This is based on the [w3c spec API](http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack) and when given a kind and an optional label and language, will create a new text track for you to use.
|
||||
This method is intended for purely programmatic usage of tracks and has one important limitation:
|
||||
tracks created using this method *cannot* be removed. The native `addTextTrack` does not have a corresponding `removeTextTrack`, so, we actually discourage the usage of this method.
|
||||
|
||||
### `player.addRemoteTextTrack(Object options) -> HTMLTrackElement`
|
||||
This function takes an options object that looks pretty similar to the track element and returns a HTMLTrackElement.
|
||||
This object has a `track` property on it which is the actual TextTrack object.
|
||||
This `TextTrack` object is equivalent to the one that can be returned from `player.addTextTrack` with the added bonus that it can be removed from the player.
|
||||
Internally, video.js will either add a `<track>` element for you, or emulate that depending on whether native text tracks are supported or not.
|
||||
The options available are:
|
||||
* `kind`
|
||||
* `label`
|
||||
* `language` (also `srclang`)
|
||||
* `id`
|
||||
* `src`
|
||||
|
||||
### `player.removeRemoteTextTrack(HTMLTrackElement|TextTrack)`
|
||||
This function takes either an HTMLTrackElement or a TextTrack object and removes it from the player.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "video.js",
|
||||
"description": "An HTML5 and Flash video player with a common API and skin for both.",
|
||||
"version": "5.9.2",
|
||||
"version": "5.10.3",
|
||||
"copyright": "Copyright Brightcove, Inc. <https://www.brightcove.com/>",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
@@ -28,7 +28,7 @@
|
||||
"object.assign": "^4.0.1",
|
||||
"safe-json-parse": "^4.0.0",
|
||||
"tsml": "1.0.1",
|
||||
"videojs-font": "1.5.1",
|
||||
"videojs-font": "^2.0.0",
|
||||
"videojs-ie8": "1.1.2",
|
||||
"videojs-swf": "5.0.1",
|
||||
"videojs-vtt.js": "^0.12.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Text Descriptions, Chapters & Captions Example</title>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Icons Sandbox</title>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Sandbox</title>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Video.js Plugin Example</title>
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
$icon-font-path: 'font' !default;
|
||||
$icon-codepoints: false !default;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.video-js .vjs-audio-button {
|
||||
@extend .vjs-icon-audio;
|
||||
}
|
||||
@@ -20,13 +20,22 @@
|
||||
@include transition($trans);
|
||||
}
|
||||
|
||||
// IE 8 hack for media queries
|
||||
$ie8screen: "\\0screen";
|
||||
|
||||
// Video has started playing AND user is inactive
|
||||
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
visibility: hidden;
|
||||
// Remain visible for screen reader and keyboard users
|
||||
visibility: visible;
|
||||
opacity: 0;
|
||||
|
||||
$trans: visibility 1.0s, opacity 1.0s;
|
||||
@include transition($trans);
|
||||
|
||||
// Make controls hidden in IE8 for now
|
||||
@media #{$ie8screen} {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.vjs-controls-disabled .vjs-control-bar,
|
||||
@@ -42,11 +51,11 @@
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
// IE8 is flakey with fonts, and you have to change the actual content to force
|
||||
// fonts to show/hide properly.
|
||||
// - "\9" IE8 hack didn't work for this
|
||||
// Found in XP IE8 from http://modern.ie. Does not show up in "IE8 mode" in IE9
|
||||
$ie8screen: "\\0screen";
|
||||
.vjs-user-inactive.vjs-playing .vjs-control-bar :before {
|
||||
@media #{$ie8screen} { content: ""; }
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
@import "private-variables";
|
||||
@import "utilities";
|
||||
|
||||
@if $icon-codepoints {
|
||||
@import "node_modules/videojs-font/scss/icons-codepoints";
|
||||
} @else {
|
||||
@import "node_modules/videojs-font/scss/icons";
|
||||
}
|
||||
@import "node_modules/videojs-font/scss/icons";
|
||||
|
||||
@import "components/layout";
|
||||
@import "components/big-play";
|
||||
@@ -39,6 +35,7 @@
|
||||
@import "components/chapters";
|
||||
@import "components/descriptions";
|
||||
@import "components/subtitles";
|
||||
@import "components/audio";
|
||||
@import "components/adaptive";
|
||||
@import "components/captions-settings";
|
||||
@import "components/modal-dialog";
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
$icon-codepoints: true;
|
||||
|
||||
@import "video-js";
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file audio-track-button.js
|
||||
*/
|
||||
import TrackButton from '../track-button.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import AudioTrackMenuItem from './audio-track-menu-item.js';
|
||||
|
||||
/**
|
||||
* The base class for buttons that toggle specific text track types (e.g. subtitles)
|
||||
*
|
||||
* @param {Player|Object} player
|
||||
* @param {Object=} options
|
||||
* @extends TrackButton
|
||||
* @class AudioTrackButton
|
||||
*/
|
||||
class AudioTrackButton extends TrackButton {
|
||||
constructor(player, options = {}) {
|
||||
options.tracks = player.audioTracks && player.audioTracks();
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.el_.setAttribute('aria-label', 'Audio Menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow sub components to stack CSS class names
|
||||
*
|
||||
* @return {String} The constructed class name
|
||||
* @method buildCSSClass
|
||||
*/
|
||||
buildCSSClass() {
|
||||
return `vjs-audio-button ${super.buildCSSClass()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a menu item for each audio track
|
||||
*
|
||||
* @return {Array} Array of menu items
|
||||
* @method createItems
|
||||
*/
|
||||
createItems(items = []) {
|
||||
let tracks = this.player_.audioTracks && this.player_.audioTracks();
|
||||
|
||||
if (!tracks) {
|
||||
return items;
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
|
||||
items.push(new AudioTrackMenuItem(this.player_, {
|
||||
// MenuItem is selectable
|
||||
'selectable': true,
|
||||
'track': track
|
||||
}));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('AudioTrackButton', AudioTrackButton);
|
||||
export default AudioTrackButton;
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @file audio-track-menu-item.js
|
||||
*/
|
||||
import MenuItem from '../../menu/menu-item.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
|
||||
/**
|
||||
* The audio track menu item
|
||||
*
|
||||
* @param {Player|Object} player
|
||||
* @param {Object=} options
|
||||
* @extends MenuItem
|
||||
* @class AudioTrackMenuItem
|
||||
*/
|
||||
class AudioTrackMenuItem extends MenuItem {
|
||||
constructor(player, options) {
|
||||
let track = options.track;
|
||||
let tracks = player.audioTracks();
|
||||
|
||||
// Modify options for parent MenuItem class's init.
|
||||
options.label = track.label || track.language || 'Unknown';
|
||||
options.selected = track.enabled;
|
||||
|
||||
super(player, options);
|
||||
|
||||
this.track = track;
|
||||
|
||||
if (tracks) {
|
||||
let changeHandler = Fn.bind(this, this.handleTracksChange);
|
||||
|
||||
tracks.addEventListener('change', changeHandler);
|
||||
this.on('dispose', () => {
|
||||
tracks.removeEventListener('change', changeHandler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click on audio track
|
||||
*
|
||||
* @method handleClick
|
||||
*/
|
||||
handleClick(event) {
|
||||
let tracks = this.player_.audioTracks();
|
||||
|
||||
super.handleClick(event);
|
||||
|
||||
if (!tracks) return;
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
let track = tracks[i];
|
||||
|
||||
if (track === this.track) {
|
||||
track.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle audio track change
|
||||
*
|
||||
* @method handleTracksChange
|
||||
*/
|
||||
handleTracksChange(event) {
|
||||
this.selected(this.track.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
|
||||
export default AudioTrackMenuItem;
|
||||
@@ -19,6 +19,7 @@ import ChaptersButton from './text-track-controls/chapters-button.js';
|
||||
import DescriptionsButton from './text-track-controls/descriptions-button.js';
|
||||
import SubtitlesButton from './text-track-controls/subtitles-button.js';
|
||||
import CaptionsButton from './text-track-controls/captions-button.js';
|
||||
import AudioTrackButton from './audio-track-controls/audio-track-button.js';
|
||||
import PlaybackRateMenuButton from './playback-rate-menu/playback-rate-menu-button.js';
|
||||
import CustomControlSpacer from './spacer-controls/custom-control-spacer.js';
|
||||
|
||||
@@ -63,6 +64,7 @@ ControlBar.prototype.options_ = {
|
||||
'descriptionsButton',
|
||||
'subtitlesButton',
|
||||
'captionsButton',
|
||||
'audioTrackButton',
|
||||
'fullscreenToggle'
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file text-track-button.js
|
||||
*/
|
||||
import MenuButton from '../../menu/menu-button.js';
|
||||
import TrackButton from '../track-button.js';
|
||||
import Component from '../../component.js';
|
||||
import * as Fn from '../../utils/fn.js';
|
||||
import TextTrackMenuItem from './text-track-menu-item.js';
|
||||
@@ -15,32 +15,20 @@ import OffTextTrackMenuItem from './off-text-track-menu-item.js';
|
||||
* @extends MenuButton
|
||||
* @class TextTrackButton
|
||||
*/
|
||||
class TextTrackButton extends MenuButton {
|
||||
class TextTrackButton extends TrackButton {
|
||||
|
||||
constructor(player, options = {}){
|
||||
options.tracks = player.textTracks();
|
||||
|
||||
constructor(player, options){
|
||||
super(player, options);
|
||||
|
||||
let tracks = this.player_.textTracks();
|
||||
|
||||
if (this.items.length <= 1) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
let updateHandler = Fn.bind(this, this.update);
|
||||
tracks.addEventListener('removetrack', updateHandler);
|
||||
tracks.addEventListener('addtrack', updateHandler);
|
||||
|
||||
this.player_.on('dispose', function() {
|
||||
tracks.removeEventListener('removetrack', updateHandler);
|
||||
tracks.removeEventListener('addtrack', updateHandler);
|
||||
});
|
||||
}
|
||||
|
||||
// Create a menu item for each text track
|
||||
/**
|
||||
* Create a menu item for each text track
|
||||
*
|
||||
* @return {Array} Array of menu items
|
||||
* @method createItems
|
||||
*/
|
||||
createItems(items=[]) {
|
||||
// Add an OFF menu item to turn all tracks off
|
||||
items.push(new OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file track-button.js
|
||||
*/
|
||||
import MenuButton from '../menu/menu-button.js';
|
||||
import Component from '../component.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
|
||||
/**
|
||||
* The base class for buttons that toggle specific text track types (e.g. subtitles)
|
||||
*
|
||||
* @param {Player|Object} player
|
||||
* @param {Object=} options
|
||||
* @extends MenuButton
|
||||
* @class TrackButton
|
||||
*/
|
||||
class TrackButton extends MenuButton {
|
||||
|
||||
constructor(player, options){
|
||||
let tracks = options.tracks;
|
||||
|
||||
super(player, options);
|
||||
|
||||
if (this.items.length <= 1) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
if (!tracks) {
|
||||
return;
|
||||
}
|
||||
|
||||
let updateHandler = Fn.bind(this, this.update);
|
||||
tracks.addEventListener('removetrack', updateHandler);
|
||||
tracks.addEventListener('addtrack', updateHandler);
|
||||
|
||||
this.player_.on('dispose', function() {
|
||||
tracks.removeEventListener('removetrack', updateHandler);
|
||||
tracks.removeEventListener('addtrack', updateHandler);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Component.registerComponent('TrackButton', TrackButton);
|
||||
export default TrackButton;
|
||||
@@ -11,7 +11,7 @@ EventTarget.prototype.on = function(type, fn) {
|
||||
// Remove the addEventListener alias before calling Events.on
|
||||
// so we don't get into an infinite type loop
|
||||
let ael = this.addEventListener;
|
||||
this.addEventListener = Function.prototype;
|
||||
this.addEventListener = () => {};
|
||||
Events.on(this, type, fn);
|
||||
this.addEventListener = ael;
|
||||
};
|
||||
@@ -23,7 +23,12 @@ EventTarget.prototype.off = function(type, fn) {
|
||||
EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
|
||||
|
||||
EventTarget.prototype.one = function(type, fn) {
|
||||
// Remove the addEventListener alias before calling Events.on
|
||||
// so we don't get into an infinite type loop
|
||||
let ael = this.addEventListener;
|
||||
this.addEventListener = () => {};
|
||||
Events.one(this, type, fn);
|
||||
this.addEventListener = ael;
|
||||
};
|
||||
|
||||
EventTarget.prototype.trigger = function(event) {
|
||||
|
||||
+47
-7
@@ -22,6 +22,8 @@ import safeParseTuple from 'safe-json-parse/tuple';
|
||||
import assign from 'object.assign';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
import textTrackConverter from './tracks/text-track-list-converter.js';
|
||||
import AudioTrackList from './tracks/audio-track-list.js';
|
||||
import VideoTrackList from './tracks/video-track-list.js';
|
||||
|
||||
// Include required child components (importing also registers them)
|
||||
import MediaLoader from './tech/loader.js';
|
||||
@@ -555,7 +557,9 @@ class Player extends Component {
|
||||
'source': source,
|
||||
'playerId': this.id(),
|
||||
'techId': `${this.id()}_${techName}_api`,
|
||||
'videoTracks': this.videoTracks_,
|
||||
'textTracks': this.textTracks_,
|
||||
'audioTracks': this.audioTracks_,
|
||||
'autoplay': this.options_.autoplay,
|
||||
'preload': this.options_.preload,
|
||||
'loop': this.options_.loop,
|
||||
@@ -648,7 +652,9 @@ class Player extends Component {
|
||||
*/
|
||||
unloadTech_() {
|
||||
// Save the current text tracks so that we can reuse the same text tracks with the next tech
|
||||
this.videoTracks_ = this.videoTracks();
|
||||
this.textTracks_ = this.textTracks();
|
||||
this.audioTracks_ = this.audioTracks();
|
||||
this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
|
||||
|
||||
this.isReady_ = false;
|
||||
@@ -2509,12 +2515,48 @@ class Player extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Text tracks are tracks of timed text events.
|
||||
* Captions - text displayed over the video for the hearing impaired
|
||||
* Subtitles - text displayed over the video for those who don't understand language in the video
|
||||
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
|
||||
* Descriptions - audio descriptions that are read back to the user by a screen reading device
|
||||
* Get a video track list
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
|
||||
*
|
||||
* @return {VideoTrackList} thes current video track list
|
||||
* @method videoTracks
|
||||
*/
|
||||
videoTracks() {
|
||||
// if we have not yet loadTech_, we create videoTracks_
|
||||
// these will be passed to the tech during loading
|
||||
if (!this.tech_) {
|
||||
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
|
||||
return this.videoTracks_;
|
||||
}
|
||||
|
||||
return this.tech_.videoTracks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an audio track list
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
|
||||
*
|
||||
* @return {AudioTrackList} thes current audio track list
|
||||
* @method audioTracks
|
||||
*/
|
||||
audioTracks() {
|
||||
// if we have not yet loadTech_, we create videoTracks_
|
||||
// these will be passed to the tech during loading
|
||||
if (!this.tech_) {
|
||||
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
|
||||
return this.audioTracks_;
|
||||
}
|
||||
|
||||
return this.tech_.audioTracks();
|
||||
}
|
||||
|
||||
/*
|
||||
* Text tracks are tracks of timed text events.
|
||||
* Captions - text displayed over the video for the hearing impaired
|
||||
* Subtitles - text displayed over the video for those who don't understand language in the video
|
||||
* Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
|
||||
* Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get an array of associated text tracks. captions, subtitles, chapters, descriptions
|
||||
@@ -2609,8 +2651,6 @@ class Player extends Component {
|
||||
// initialTime: function(){ return this.techCall_('initialTime'); },
|
||||
// startOffsetTime: function(){ return this.techCall_('startOffsetTime'); },
|
||||
// played: function(){ return this.techCall_('played'); },
|
||||
// videoTracks: function(){ return this.techCall_('videoTracks'); },
|
||||
// audioTracks: function(){ return this.techCall_('audioTracks'); },
|
||||
// defaultPlaybackRate: function(){ return this.techCall_('defaultPlaybackRate'); },
|
||||
// defaultMuted: function(){ return this.techCall_('defaultMuted'); }
|
||||
|
||||
|
||||
@@ -95,10 +95,11 @@ function FlashRtmpDecorator(Flash) {
|
||||
* Pass the source to the flash object
|
||||
* Adaptive source handlers will have more complicated workflows before passing
|
||||
* video data to the video element
|
||||
* @param {Object} source The source object
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} source The source object
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Flash.rtmpSourceHandler.handleSource = function(source, tech){
|
||||
Flash.rtmpSourceHandler.handleSource = function(source, tech, options){
|
||||
let srcParts = Flash.streamToParts(source.src);
|
||||
|
||||
tech['setRtmpConnection'](srcParts.connection);
|
||||
|
||||
@@ -312,7 +312,7 @@ class Flash extends Tech {
|
||||
// Create setters and getters for attributes
|
||||
const _api = Flash.prototype;
|
||||
const _readWrite = 'rtmpConnection,rtmpStream,preload,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(',');
|
||||
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoTracks,audioTracks,videoWidth,videoHeight'.split(',');
|
||||
const _readOnly = 'networkState,readyState,initialTime,duration,startOffsetTime,paused,ended,videoWidth,videoHeight'.split(',');
|
||||
|
||||
function _createSetter(attr){
|
||||
var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
|
||||
@@ -397,10 +397,11 @@ Flash.nativeSourceHandler.canHandleSource = function(source){
|
||||
* Adaptive source handlers will have more complicated workflows before passing
|
||||
* video data to the video element
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} source The source object
|
||||
* @param {Flash} tech The instance of the Flash tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Flash.nativeSourceHandler.handleSource = function(source, tech){
|
||||
Flash.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
|
||||
+119
-19
@@ -9,11 +9,14 @@ import * as Dom from '../utils/dom.js';
|
||||
import * as Url from '../utils/url.js';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import tsml from 'tsml';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import assign from 'object.assign';
|
||||
import mergeOptions from '../utils/merge-options.js';
|
||||
import toTitleCase from '../utils/to-title-case.js';
|
||||
|
||||
/**
|
||||
* HTML5 Media Controller - Wrapper for HTML5 Media API
|
||||
@@ -29,6 +32,7 @@ class Html5 extends Tech {
|
||||
super(options, ready);
|
||||
|
||||
const source = options.source;
|
||||
let crossoriginTracks = false;
|
||||
|
||||
// Set the source if one is provided
|
||||
// 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
|
||||
@@ -61,6 +65,11 @@ class Html5 extends Tech {
|
||||
// store HTMLTrackElement and TextTrack to remote list
|
||||
this.remoteTextTrackEls().addTrackElement_(node);
|
||||
this.remoteTextTracks().addTrack_(node.track);
|
||||
if (!crossoriginTracks &&
|
||||
!this.el_.hasAttribute('crossorigin') &&
|
||||
Url.isCrossOrigin(node.src)) {
|
||||
crossoriginTracks = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +79,30 @@ class Html5 extends Tech {
|
||||
}
|
||||
}
|
||||
|
||||
let trackTypes = ['audio', 'video'];
|
||||
|
||||
// ProxyNativeTextTracks
|
||||
trackTypes.forEach((type) => {
|
||||
let capitalType = toTitleCase(type);
|
||||
|
||||
if (!this[`featuresNative${capitalType}Tracks`]) {
|
||||
return;
|
||||
}
|
||||
let tl = this.el()[`${type}Tracks`];
|
||||
|
||||
if (tl && tl.addEventListener) {
|
||||
tl.addEventListener('change', Fn.bind(this, this[`handle${capitalType}TrackChange_`]));
|
||||
tl.addEventListener('addtrack', Fn.bind(this, this[`handle${capitalType}TrackAdd_`]));
|
||||
tl.addEventListener('removetrack', Fn.bind(this, this[`handle${capitalType}TrackRemove_`]));
|
||||
}
|
||||
});
|
||||
|
||||
if (this.featuresNativeTextTracks) {
|
||||
if (crossoriginTracks) {
|
||||
log.warn(tsml`Text Tracks are being loaded from another origin but the crossorigin attribute isn't used.
|
||||
This may prevent text tracks from loading.`);
|
||||
}
|
||||
|
||||
this.handleTextTrackChange_ = Fn.bind(this, this.handleTextTrackChange);
|
||||
this.handleTextTrackAdd_ = Fn.bind(this, this.handleTextTrackAdd);
|
||||
this.handleTextTrackRemove_ = Fn.bind(this, this.handleTextTrackRemove);
|
||||
@@ -96,25 +128,20 @@ class Html5 extends Tech {
|
||||
* @method dispose
|
||||
*/
|
||||
dispose() {
|
||||
let tt = this.el().textTracks;
|
||||
let emulatedTt = this.textTracks();
|
||||
|
||||
// remove native event listeners
|
||||
if (tt && tt.removeEventListener) {
|
||||
tt.removeEventListener('change', this.handleTextTrackChange_);
|
||||
tt.removeEventListener('addtrack', this.handleTextTrackAdd_);
|
||||
tt.removeEventListener('removetrack', this.handleTextTrackRemove_);
|
||||
}
|
||||
|
||||
// clearout the emulated text track list.
|
||||
let i = emulatedTt.length;
|
||||
|
||||
while (i--) {
|
||||
emulatedTt.removeTrack_(emulatedTt[i]);
|
||||
}
|
||||
// Un-ProxyNativeTracks
|
||||
['audio', 'video', 'text'].forEach((type) => {
|
||||
let capitalType = toTitleCase(type);
|
||||
let tl = this.el_[`${type}Tracks`];
|
||||
|
||||
if (tl && tl.removeEventListener) {
|
||||
tl.removeEventListener('change', this[`handle${capitalType}TrackChange_`]);
|
||||
tl.removeEventListener('addtrack', this[`handle${capitalType}TrackAdd_`]);
|
||||
tl.removeEventListener('removetrack', this[`handle${capitalType}TrackRemove_`]);
|
||||
}
|
||||
});
|
||||
|
||||
Html5.disposeMediaElement(this.el_);
|
||||
// tech will handle clearing of the emulated track list
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -290,6 +317,43 @@ class Html5 extends Tech {
|
||||
this.textTracks().removeTrack_(e.track);
|
||||
}
|
||||
|
||||
handleVideoTrackChange_(e) {
|
||||
let vt = this.videoTracks();
|
||||
this.videoTracks().trigger({
|
||||
type: 'change',
|
||||
target: vt,
|
||||
currentTarget: vt,
|
||||
srcElement: vt
|
||||
});
|
||||
}
|
||||
|
||||
handleVideoTrackAdd_(e) {
|
||||
this.videoTracks().addTrack_(e.track);
|
||||
}
|
||||
|
||||
handleVideoTrackRemove_(e) {
|
||||
this.videoTracks().removeTrack_(e.track);
|
||||
}
|
||||
|
||||
handleAudioTrackChange_(e) {
|
||||
let audioTrackList = this.audioTracks();
|
||||
this.audioTracks().trigger({
|
||||
type: 'change',
|
||||
target: audioTrackList,
|
||||
currentTarget: audioTrackList,
|
||||
srcElement: audioTrackList
|
||||
});
|
||||
}
|
||||
|
||||
handleAudioTrackAdd_(e) {
|
||||
this.audioTracks().addTrack_(e.track);
|
||||
}
|
||||
|
||||
handleAudioTrackRemove_(e) {
|
||||
this.audioTracks().removeTrack_(e.track);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Play for html5 tech
|
||||
*
|
||||
@@ -903,10 +967,11 @@ Html5.nativeSourceHandler.canHandleSource = function(source){
|
||||
* Adaptive source handlers will have more complicated workflows before passing
|
||||
* video data to the video element
|
||||
*
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the Html5 tech
|
||||
* @param {Object} source The source object
|
||||
* @param {Html5} tech The instance of the Html5 tech
|
||||
* @param {Object} options The options to pass to the source
|
||||
*/
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech){
|
||||
Html5.nativeSourceHandler.handleSource = function(source, tech, options){
|
||||
tech.setSrc(source.src);
|
||||
};
|
||||
|
||||
@@ -975,6 +1040,27 @@ Html5.supportsNativeTextTracks = function() {
|
||||
return supportsTextTracks;
|
||||
};
|
||||
|
||||
/*
|
||||
* Check to see if native video tracks are supported by this browser/device
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.supportsNativeVideoTracks = function() {
|
||||
let supportsVideoTracks = !!Html5.TEST_VID.videoTracks;
|
||||
return supportsVideoTracks;
|
||||
};
|
||||
|
||||
/*
|
||||
* Check to see if native audio tracks are supported by this browser/device
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Html5.supportsNativeAudioTracks = function() {
|
||||
let supportsAudioTracks = !!Html5.TEST_VID.audioTracks;
|
||||
return supportsAudioTracks;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An array of events available on the Html5 tech.
|
||||
*
|
||||
@@ -1048,6 +1134,20 @@ Html5.prototype['featuresProgressEvents'] = true;
|
||||
*/
|
||||
Html5.prototype['featuresNativeTextTracks'] = Html5.supportsNativeTextTracks();
|
||||
|
||||
/**
|
||||
* Sets the tech's status on native text track support
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresNativeVideoTracks'] = Html5.supportsNativeVideoTracks();
|
||||
|
||||
/**
|
||||
* Sets the tech's status on native audio track support
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
Html5.prototype['featuresNativeAudioTracks'] = Html5.supportsNativeAudioTracks();
|
||||
|
||||
// HTML5 Feature detection and Device Fixes --------------------------------- //
|
||||
let canPlayType;
|
||||
const mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
|
||||
|
||||
+147
-17
@@ -10,6 +10,10 @@ import HTMLTrackElementList from '../tracks/html-track-element-list';
|
||||
import mergeOptions from '../utils/merge-options.js';
|
||||
import TextTrack from '../tracks/text-track';
|
||||
import TextTrackList from '../tracks/text-track-list';
|
||||
import VideoTrack from '../tracks/video-track';
|
||||
import VideoTrackList from '../tracks/video-track-list';
|
||||
import AudioTrackList from '../tracks/audio-track-list';
|
||||
import AudioTrack from '../tracks/audio-track';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import log from '../utils/log.js';
|
||||
import { createTimeRange } from '../utils/time-ranges.js';
|
||||
@@ -45,6 +49,8 @@ class Tech extends Component {
|
||||
});
|
||||
|
||||
this.textTracks_ = options.textTracks;
|
||||
this.videoTracks_ = options.videoTracks;
|
||||
this.audioTracks_ = options.audioTracks;
|
||||
|
||||
// Manually track progress in cases where the browser/flash player doesn't report it.
|
||||
if (!this.featuresProgressEvents) {
|
||||
@@ -65,6 +71,7 @@ class Tech extends Component {
|
||||
}
|
||||
|
||||
this.initTextTrackListeners();
|
||||
this.initTrackListeners();
|
||||
|
||||
// Turn on component tap events
|
||||
this.emitTapEvents();
|
||||
@@ -218,15 +225,9 @@ class Tech extends Component {
|
||||
* @method dispose
|
||||
*/
|
||||
dispose() {
|
||||
// clear out text tracks because we can't reuse them between techs
|
||||
let textTracks = this.textTracks();
|
||||
|
||||
if (textTracks) {
|
||||
let i = textTracks.length;
|
||||
while(i--) {
|
||||
this.removeRemoteTextTrack(textTracks[i]);
|
||||
}
|
||||
}
|
||||
// clear out all tracks because we can't reuse them between techs
|
||||
this.clearTracks(['audio', 'video', 'text']);
|
||||
|
||||
// Turn off any manual progress or timeupdate tracking
|
||||
if (this.manualProgress) { this.manualProgressOff(); }
|
||||
@@ -236,6 +237,33 @@ class Tech extends Component {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* clear out a track list, or multiple track lists
|
||||
*
|
||||
* Note: Techs without source handlers should call this between
|
||||
* sources for video & audio tracks, as usually you don't want
|
||||
* to use them between tracks and we have no automatic way to do
|
||||
* it for you
|
||||
*
|
||||
* @method clearTracks
|
||||
* @param {Array|String} types type(s) of track lists to empty
|
||||
*/
|
||||
clearTracks(types) {
|
||||
types = [].concat(types);
|
||||
// clear out all tracks because we can't reuse them between techs
|
||||
types.forEach((type) => {
|
||||
let list = this[`${type}Tracks`]() || [];
|
||||
let i = list.length;
|
||||
while (i--) {
|
||||
let track = list[i];
|
||||
if (type === 'text') {
|
||||
this.removeRemoteTextTrack(track);
|
||||
}
|
||||
list.removeTrack_(track);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the tech. Removes all sources and resets readyState.
|
||||
*
|
||||
@@ -313,6 +341,32 @@ class Tech extends Component {
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialize audio and video track listeners
|
||||
*
|
||||
* @method initTrackListeners
|
||||
*/
|
||||
initTrackListeners() {
|
||||
const trackTypes = ['video', 'audio'];
|
||||
|
||||
trackTypes.forEach((type) => {
|
||||
let trackListChanges = () => {
|
||||
this.trigger(`${type}trackchange`);
|
||||
};
|
||||
|
||||
let tracks = this[`${type}Tracks`]();
|
||||
|
||||
tracks.addEventListener('removetrack', trackListChanges);
|
||||
tracks.addEventListener('addtrack', trackListChanges);
|
||||
|
||||
this.on('dispose', () => {
|
||||
tracks.removeEventListener('removetrack', trackListChanges);
|
||||
tracks.removeEventListener('addtrack', trackListChanges);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate texttracks
|
||||
*
|
||||
@@ -337,8 +391,10 @@ class Tech extends Component {
|
||||
script.onload = null;
|
||||
script.onerror = null;
|
||||
});
|
||||
this.el().parentNode.appendChild(script);
|
||||
// but have not loaded yet and we set it to true before the inject so that
|
||||
// we don't overwrite the injected window.WebVTT if it loads right away
|
||||
window['WebVTT'] = true;
|
||||
this.el().parentNode.appendChild(script);
|
||||
}
|
||||
|
||||
let updateDisplay = () => this.trigger('texttrackchange');
|
||||
@@ -362,6 +418,28 @@ class Tech extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get videotracks
|
||||
*
|
||||
* @returns {VideoTrackList}
|
||||
* @method videoTracks
|
||||
*/
|
||||
videoTracks() {
|
||||
this.videoTracks_ = this.videoTracks_ || new VideoTrackList();
|
||||
return this.videoTracks_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get audiotracklist
|
||||
*
|
||||
* @returns {AudioTrackList}
|
||||
* @method audioTracks
|
||||
*/
|
||||
audioTracks() {
|
||||
this.audioTracks_ = this.audioTracks_ || new AudioTrackList();
|
||||
return this.audioTracks_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Provide default methods for text tracks.
|
||||
*
|
||||
@@ -536,14 +614,31 @@ class Tech extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* List of associated text tracks
|
||||
*
|
||||
* @type {Array}
|
||||
* @type {TextTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.textTracks_;
|
||||
|
||||
/**
|
||||
* List of associated audio tracks
|
||||
*
|
||||
* @type {AudioTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.audioTracks_;
|
||||
|
||||
/**
|
||||
* List of associated video tracks
|
||||
*
|
||||
* @type {VideoTrackList}
|
||||
* @private
|
||||
*/
|
||||
Tech.prototype.videoTracks_;
|
||||
|
||||
|
||||
var createTrackHelper = function(self, kind, label, language, options={}) {
|
||||
let tracks = self.textTracks();
|
||||
|
||||
@@ -713,19 +808,54 @@ Tech.withSourceHandlers = function(_Tech){
|
||||
this.disposeSourceHandler();
|
||||
this.off('dispose', this.disposeSourceHandler);
|
||||
|
||||
this.currentSource_ = source;
|
||||
this.sourceHandler_ = sh.handleSource(source, this);
|
||||
// if we have a source and get another one
|
||||
// then we are loading something new
|
||||
// than clear all of our current tracks
|
||||
if (this.currentSource_) {
|
||||
this.clearTracks(['audio', 'video']);
|
||||
|
||||
this.currentSource_ = null;
|
||||
}
|
||||
|
||||
if (sh !== _Tech.nativeSourceHandler) {
|
||||
|
||||
this.currentSource_ = source;
|
||||
|
||||
// Catch if someone replaced the src without calling setSource.
|
||||
// If they do, set currentSource_ to null and dispose our source handler.
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
|
||||
}
|
||||
|
||||
this.sourceHandler_ = sh.handleSource(source, this, this.options_);
|
||||
this.on('dispose', this.disposeSourceHandler);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up any existing source handler
|
||||
*/
|
||||
_Tech.prototype.disposeSourceHandler = function(){
|
||||
// On the first loadstart after setSource
|
||||
_Tech.prototype.firstLoadStartListener_ = function() {
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
};
|
||||
|
||||
// On successive loadstarts when setSource has not been called again
|
||||
_Tech.prototype.successiveLoadStartListener_ = function() {
|
||||
this.currentSource_ = null;
|
||||
this.disposeSourceHandler();
|
||||
this.one(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
};
|
||||
|
||||
/*
|
||||
* Clean up any existing source handler
|
||||
*/
|
||||
_Tech.prototype.disposeSourceHandler = function() {
|
||||
if (this.sourceHandler_ && this.sourceHandler_.dispose) {
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.firstLoadStartListener_);
|
||||
this.off(this.el_, 'loadstart', _Tech.prototype.successiveLoadStartListener_);
|
||||
this.sourceHandler_.dispose();
|
||||
this.sourceHandler_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @file audio-track-list.js
|
||||
*/
|
||||
import TrackList from './track-list';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* anywhere we call this function we diverge from the spec
|
||||
* as we only support one enabled audiotrack at a time
|
||||
*
|
||||
* @param {Array|AudioTrackList} list list to work on
|
||||
* @param {AudioTrack} track the track to skip
|
||||
*/
|
||||
const disableOthers = function(list, track) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (track.id === list[i].id) {
|
||||
continue;
|
||||
}
|
||||
// another audio track is enabled, disable it
|
||||
list[i].enabled = false;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* A list of possible audio tracks. All functionality is in the
|
||||
* base class Tracklist and the spec for AudioTrackList is located at:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
|
||||
*
|
||||
* interface AudioTrackList : EventTarget {
|
||||
* readonly attribute unsigned long length;
|
||||
* getter AudioTrack (unsigned long index);
|
||||
* AudioTrack? getTrackById(DOMString id);
|
||||
*
|
||||
* attribute EventHandler onchange;
|
||||
* attribute EventHandler onaddtrack;
|
||||
* attribute EventHandler onremovetrack;
|
||||
* };
|
||||
*
|
||||
* @param {AudioTrack[]} tracks a list of audio tracks to instantiate the list with
|
||||
* @extends TrackList
|
||||
* @class AudioTrackList
|
||||
*/
|
||||
class AudioTrackList extends TrackList {
|
||||
constructor(tracks = []) {
|
||||
let list;
|
||||
|
||||
// make sure only 1 track is enabled
|
||||
// sorted from last index to first index
|
||||
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||
if (tracks[i].enabled) {
|
||||
disableOthers(tracks, tracks[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// IE8 forces us to implement inheritance ourselves
|
||||
// as it does not support Object.defineProperty properly
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
for (let prop in TrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = TrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
for (let prop in AudioTrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = AudioTrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list = super(tracks, list);
|
||||
list.changing_ = false;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
addTrack_(track) {
|
||||
if (track.enabled) {
|
||||
disableOthers(this, track);
|
||||
}
|
||||
|
||||
super.addTrack_(track);
|
||||
// native tracks don't have this
|
||||
if (!track.addEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
track.addEventListener('enabledchange', () => {
|
||||
// when we are disabling other tracks (since we don't support
|
||||
// more than one track at a time) we will set changing_
|
||||
// to true so that we don't trigger additional change events
|
||||
if (this.changing_) {
|
||||
return;
|
||||
}
|
||||
this.changing_ = true;
|
||||
disableOthers(this, track);
|
||||
this.changing_ = false;
|
||||
this.trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
addTrack(track) {
|
||||
this.addTrack_(track);
|
||||
}
|
||||
|
||||
removeTrack(track) {
|
||||
super.removeTrack_(track);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AudioTrackList;
|
||||
@@ -0,0 +1,63 @@
|
||||
import {AudioTrackKind} from './track-enums';
|
||||
import Track from './track';
|
||||
import merge from '../utils/merge-options';
|
||||
import * as browser from '../utils/browser.js';
|
||||
|
||||
/**
|
||||
* A single audio text track as defined in:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack
|
||||
*
|
||||
* interface AudioTrack {
|
||||
* readonly attribute DOMString id;
|
||||
* readonly attribute DOMString kind;
|
||||
* readonly attribute DOMString label;
|
||||
* readonly attribute DOMString language;
|
||||
* attribute boolean enabled;
|
||||
* };
|
||||
*
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @class AudioTrack
|
||||
*/
|
||||
class AudioTrack extends Track {
|
||||
constructor(options = {}) {
|
||||
let settings = merge(options, {
|
||||
kind: AudioTrackKind[options.kind] || ''
|
||||
});
|
||||
// on IE8 this will be a document element
|
||||
// for every other browser this will be a normal object
|
||||
let track = super(settings);
|
||||
let enabled = false;
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
for (let prop in AudioTrack.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
track[prop] = AudioTrack.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(track, 'enabled', {
|
||||
get() { return enabled; },
|
||||
set(newEnabled) {
|
||||
// an invalid or unchanged value
|
||||
if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
|
||||
return;
|
||||
}
|
||||
enabled = newEnabled;
|
||||
this.trigger('enabledchange');
|
||||
}
|
||||
});
|
||||
|
||||
// if the user sets this track to selected then
|
||||
// set selected to that true value otherwise
|
||||
// we keep it false
|
||||
if (settings.enabled) {
|
||||
track.enabled = settings.enabled;
|
||||
}
|
||||
track.loaded_ = true;
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
export default AudioTrack;
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @file text-track-enums.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
|
||||
*
|
||||
* enum TextTrackMode { "disabled", "hidden", "showing" };
|
||||
*/
|
||||
const TextTrackMode = {
|
||||
disabled: 'disabled',
|
||||
hidden: 'hidden',
|
||||
showing: 'showing'
|
||||
};
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
|
||||
*
|
||||
* enum TextTrackKind {
|
||||
* "subtitles",
|
||||
* "captions",
|
||||
* "descriptions",
|
||||
* "chapters",
|
||||
* "metadata"
|
||||
* };
|
||||
*/
|
||||
const TextTrackKind = {
|
||||
subtitles: 'subtitles',
|
||||
captions: 'captions',
|
||||
descriptions: 'descriptions',
|
||||
chapters: 'chapters',
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
/* jshint ignore:start */
|
||||
// we ignore jshint here because it does not see
|
||||
// TextTrackMode or TextTrackKind as defined here somehow...
|
||||
export { TextTrackMode, TextTrackKind };
|
||||
/* jshint ignore:end */
|
||||
@@ -1,14 +1,15 @@
|
||||
/**
|
||||
* @file text-track-list.js
|
||||
*/
|
||||
import EventTarget from '../event-target';
|
||||
import TrackList from './track-list';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* A text track list as defined in:
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
|
||||
* A list of possible text tracks. All functionality is in the
|
||||
* base class TrackList. The spec for TextTrackList is located at:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist
|
||||
*
|
||||
* interface TextTrackList : EventTarget {
|
||||
* readonly attribute unsigned long length;
|
||||
@@ -20,19 +21,23 @@ import document from 'global/document';
|
||||
* attribute EventHandler onremovetrack;
|
||||
* };
|
||||
*
|
||||
* @param {Track[]} tracks A list of tracks to initialize the list with
|
||||
* @extends EventTarget
|
||||
* @param {TextTrack[]} tracks A list of tracks to initialize the list with
|
||||
* @extends TrackList
|
||||
* @class TextTrackList
|
||||
*/
|
||||
|
||||
class TextTrackList extends EventTarget {
|
||||
class TextTrackList extends TrackList {
|
||||
constructor(tracks = []) {
|
||||
super();
|
||||
let list = this;
|
||||
let list;
|
||||
|
||||
// IE8 forces us to implement inheritance ourselves
|
||||
// as it does not support Object.defineProperty properly
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
|
||||
for (let prop in TrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = TrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
for (let prop in TextTrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = TextTrackList.prototype[prop];
|
||||
@@ -40,54 +45,15 @@ class TextTrackList extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
list.tracks_ = [];
|
||||
|
||||
Object.defineProperty(list, 'length', {
|
||||
get() {
|
||||
return this.tracks_.length;
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
list.addTrack_(tracks[i]);
|
||||
}
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
return list;
|
||||
}
|
||||
list = super(tracks, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add TextTrack from TextTrackList
|
||||
*
|
||||
* @param {TextTrack} track
|
||||
* @method addTrack_
|
||||
* @private
|
||||
*/
|
||||
addTrack_(track) {
|
||||
let index = this.tracks_.length;
|
||||
|
||||
if (!('' + index in this)) {
|
||||
Object.defineProperty(this, index, {
|
||||
get() {
|
||||
return this.tracks_[index];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
super.addTrack_(track);
|
||||
track.addEventListener('modechange', Fn.bind(this, function() {
|
||||
this.trigger('change');
|
||||
}));
|
||||
|
||||
// Do not add duplicate tracks
|
||||
if (this.tracks_.indexOf(track) === -1) {
|
||||
this.tracks_.push(track);
|
||||
this.trigger({
|
||||
track,
|
||||
type: 'addtrack'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,21 +113,4 @@ class TextTrackList extends EventTarget {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change - One or more tracks in the track list have been enabled or disabled.
|
||||
* addtrack - A track has been added to the track list.
|
||||
* removetrack - A track has been removed from the track list.
|
||||
*/
|
||||
TextTrackList.prototype.allowedEvents_ = {
|
||||
change: 'change',
|
||||
addtrack: 'addtrack',
|
||||
removetrack: 'removetrack'
|
||||
};
|
||||
|
||||
// emulate attribute EventHandler support to allow for feature detection
|
||||
for (let event in TextTrackList.prototype.allowedEvents_) {
|
||||
TextTrackList.prototype['on' + event] = null;
|
||||
}
|
||||
|
||||
export default TextTrackList;
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
*/
|
||||
import TextTrackCueList from './text-track-cue-list';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as Guid from '../utils/guid.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import * as TextTrackEnum from './text-track-enums';
|
||||
import {TextTrackKind, TextTrackMode} from './track-enums';
|
||||
import log from '../utils/log.js';
|
||||
import EventTarget from '../event-target';
|
||||
import document from 'global/document';
|
||||
import window from 'global/window';
|
||||
import Track from './track.js';
|
||||
import { isCrossOrigin } from '../utils/url.js';
|
||||
import XHR from 'xhr';
|
||||
import merge from '../utils/merge-options';
|
||||
import * as browser from '../utils/browser.js';
|
||||
|
||||
/**
|
||||
* takes a webvtt file contents and parses it into cues
|
||||
@@ -54,7 +54,6 @@ const parseCues = function(srcContent, track) {
|
||||
parser.flush();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* load a track from a specifed url
|
||||
*
|
||||
@@ -99,7 +98,7 @@ const loadTrack = function(src, track) {
|
||||
|
||||
/**
|
||||
* A single text track as defined in:
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack
|
||||
*
|
||||
* interface TextTrack : EventTarget {
|
||||
* readonly attribute TextTrackKind kind;
|
||||
@@ -121,21 +120,31 @@ const loadTrack = function(src, track) {
|
||||
* };
|
||||
*
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @extends EventTarget
|
||||
* @extends Track
|
||||
* @class TextTrack
|
||||
*/
|
||||
class TextTrack extends EventTarget {
|
||||
class TextTrack extends Track {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
if (!options.tech) {
|
||||
throw new Error('A tech was not provided.');
|
||||
}
|
||||
|
||||
let tt = this;
|
||||
let settings = merge(options, {
|
||||
kind: TextTrackKind[options.kind] || 'subtitles',
|
||||
language: options.language || options.srclang || ''
|
||||
});
|
||||
let mode = TextTrackMode[settings.mode] || 'disabled';
|
||||
let default_ = settings.default;
|
||||
|
||||
if (settings.kind === 'metadata' || settings.kind === 'chapters') {
|
||||
mode = 'hidden';
|
||||
}
|
||||
// on IE8 this will be a document element
|
||||
// for every other browser this will be a normal object
|
||||
let tt = super(settings);
|
||||
tt.tech_ = settings.tech;
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
tt = document.createElement('custom');
|
||||
|
||||
for (let prop in TextTrack.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
tt[prop] = TextTrack.prototype[prop];
|
||||
@@ -143,19 +152,6 @@ class TextTrack extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
tt.tech_ = options.tech;
|
||||
|
||||
let mode = TextTrackEnum.TextTrackMode[options.mode] || 'disabled';
|
||||
let kind = TextTrackEnum.TextTrackKind[options.kind] || 'subtitles';
|
||||
let default_ = options.default;
|
||||
let label = options.label || '';
|
||||
let language = options.language || options.srclang || '';
|
||||
let id = options.id || 'vjs_text_track_' + Guid.newGUID();
|
||||
|
||||
if (kind === 'metadata' || kind === 'chapters') {
|
||||
mode = 'hidden';
|
||||
}
|
||||
|
||||
tt.cues_ = [];
|
||||
tt.activeCues_ = [];
|
||||
|
||||
@@ -174,34 +170,6 @@ class TextTrack extends EventTarget {
|
||||
tt.tech_.on('timeupdate', timeupdateHandler);
|
||||
}
|
||||
|
||||
Object.defineProperty(tt, 'kind', {
|
||||
get() {
|
||||
return kind;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
Object.defineProperty(tt, 'label', {
|
||||
get() {
|
||||
return label;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
Object.defineProperty(tt, 'language', {
|
||||
get() {
|
||||
return language;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
Object.defineProperty(tt, 'id', {
|
||||
get() {
|
||||
return id;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
Object.defineProperty(tt, 'default', {
|
||||
get() {
|
||||
return default_;
|
||||
@@ -214,7 +182,7 @@ class TextTrack extends EventTarget {
|
||||
return mode;
|
||||
},
|
||||
set(newMode) {
|
||||
if (!TextTrackEnum.TextTrackMode[newMode]) {
|
||||
if (!TextTrackMode[newMode]) {
|
||||
return;
|
||||
}
|
||||
mode = newMode;
|
||||
@@ -282,16 +250,14 @@ class TextTrack extends EventTarget {
|
||||
set() {}
|
||||
});
|
||||
|
||||
if (options.src) {
|
||||
tt.src = options.src;
|
||||
loadTrack(options.src, tt);
|
||||
if (settings.src) {
|
||||
tt.src = settings.src;
|
||||
loadTrack(settings.src, tt);
|
||||
} else {
|
||||
tt.loaded_ = true;
|
||||
}
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
return tt;
|
||||
}
|
||||
return tt;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @file track-kinds.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
|
||||
*
|
||||
* enum VideoTrackKind {
|
||||
* "alternative",
|
||||
* "captions",
|
||||
* "main",
|
||||
* "sign",
|
||||
* "subtitles",
|
||||
* "commentary",
|
||||
* "",
|
||||
* };
|
||||
*/
|
||||
const VideoTrackKind = {
|
||||
alternative: 'alternative',
|
||||
captions: 'captions',
|
||||
main: 'main',
|
||||
sign: 'sign',
|
||||
subtitles: 'subtitles',
|
||||
commentary: 'commentary',
|
||||
};
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
|
||||
*
|
||||
* enum AudioTrackKind {
|
||||
* "alternative",
|
||||
* "descriptions",
|
||||
* "main",
|
||||
* "main-desc",
|
||||
* "translation",
|
||||
* "commentary",
|
||||
* "",
|
||||
* };
|
||||
*/
|
||||
const AudioTrackKind = {
|
||||
alternative: 'alternative',
|
||||
descriptions: 'descriptions',
|
||||
main: 'main',
|
||||
'main-desc': 'main-desc',
|
||||
translation: 'translation',
|
||||
commentary: 'commentary',
|
||||
};
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackkind
|
||||
*
|
||||
* enum TextTrackKind {
|
||||
* "subtitles",
|
||||
* "captions",
|
||||
* "descriptions",
|
||||
* "chapters",
|
||||
* "metadata"
|
||||
* };
|
||||
*/
|
||||
const TextTrackKind = {
|
||||
subtitles: 'subtitles',
|
||||
captions: 'captions',
|
||||
descriptions: 'descriptions',
|
||||
chapters: 'chapters',
|
||||
metadata: 'metadata'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
|
||||
*
|
||||
* enum TextTrackMode { "disabled", "hidden", "showing" };
|
||||
*/
|
||||
const TextTrackMode = {
|
||||
disabled: 'disabled',
|
||||
hidden: 'hidden',
|
||||
showing: 'showing'
|
||||
};
|
||||
|
||||
/* jshint ignore:start */
|
||||
// we ignore jshint here because it does not see
|
||||
// AudioTrackKind as defined here
|
||||
export default { VideoTrackKind, AudioTrackKind, TextTrackKind, TextTrackMode };
|
||||
/* jshint ignore:end */
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @file track-list.js
|
||||
*/
|
||||
import EventTarget from '../event-target';
|
||||
import * as Fn from '../utils/fn.js';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* Common functionaliy between Text, Audio, and Video TrackLists
|
||||
* Interfaces defined in the following spec:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
|
||||
*
|
||||
* @param {Track[]} tracks A list of tracks to initialize the list with
|
||||
* @param {Object} list the child object with inheritance done manually for ie8
|
||||
* @extends EventTarget
|
||||
* @class TrackList
|
||||
*/
|
||||
class TrackList extends EventTarget {
|
||||
constructor(tracks = [], list = null) {
|
||||
super();
|
||||
if (!list) {
|
||||
list = this;
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
for (let prop in TrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = TrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list.tracks_ = [];
|
||||
Object.defineProperty(list, 'length', {
|
||||
get() {
|
||||
return this.tracks_.length;
|
||||
}
|
||||
});
|
||||
|
||||
for (let i = 0; i < tracks.length; i++) {
|
||||
list.addTrack_(tracks[i]);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Track from TrackList
|
||||
*
|
||||
* @param {Mixed} track
|
||||
* @method addTrack_
|
||||
* @private
|
||||
*/
|
||||
addTrack_(track) {
|
||||
let index = this.tracks_.length;
|
||||
|
||||
if (!('' + index in this)) {
|
||||
Object.defineProperty(this, index, {
|
||||
get() {
|
||||
return this.tracks_[index];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Do not add duplicate tracks
|
||||
if (this.tracks_.indexOf(track) === -1) {
|
||||
this.tracks_.push(track);
|
||||
this.trigger({
|
||||
track,
|
||||
type: 'addtrack'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a Track from TrackList
|
||||
*
|
||||
* @param {Track} rtrack track to be removed
|
||||
* @method removeTrack_
|
||||
* @private
|
||||
*/
|
||||
removeTrack_(rtrack) {
|
||||
let track;
|
||||
|
||||
for (let i = 0, l = this.length; i < l; i++) {
|
||||
if (this[i] === rtrack) {
|
||||
track = this[i];
|
||||
if (track.off) {
|
||||
track.off();
|
||||
}
|
||||
|
||||
this.tracks_.splice(i, 1);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger({
|
||||
track,
|
||||
type: 'removetrack'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Track from the TrackList by a tracks id
|
||||
*
|
||||
* @param {String} id - the id of the track to get
|
||||
* @method getTrackById
|
||||
* @return {Track}
|
||||
* @private
|
||||
*/
|
||||
getTrackById(id) {
|
||||
let result = null;
|
||||
|
||||
for (let i = 0, l = this.length; i < l; i++) {
|
||||
let track = this[i];
|
||||
if (track.id === id) {
|
||||
result = track;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change - One or more tracks in the track list have been enabled or disabled.
|
||||
* addtrack - A track has been added to the track list.
|
||||
* removetrack - A track has been removed from the track list.
|
||||
*/
|
||||
TrackList.prototype.allowedEvents_ = {
|
||||
change: 'change',
|
||||
addtrack: 'addtrack',
|
||||
removetrack: 'removetrack'
|
||||
};
|
||||
|
||||
// emulate attribute EventHandler support to allow for feature detection
|
||||
for (let event in TrackList.prototype.allowedEvents_) {
|
||||
TrackList.prototype['on' + event] = null;
|
||||
}
|
||||
|
||||
export default TrackList;
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file track.js
|
||||
*/
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
import * as Guid from '../utils/guid.js';
|
||||
import EventTarget from '../event-target';
|
||||
|
||||
/**
|
||||
* setup the common parts of an audio, video, or text track
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html
|
||||
*
|
||||
* @param {String} type The type of track we are dealing with audio|video|text
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @extends EventTarget
|
||||
* @class Track
|
||||
*/
|
||||
class Track extends EventTarget {
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
|
||||
let track = this;
|
||||
if (browser.IS_IE8) {
|
||||
track = document.createElement('custom');
|
||||
for (let prop in Track.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
track[prop] = Track.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let trackProps = {
|
||||
id: options.id || 'vjs_track_' + Guid.newGUID(),
|
||||
kind: options.kind || '',
|
||||
label: options.label || '',
|
||||
language: options.language || ''
|
||||
};
|
||||
|
||||
for (let key in trackProps) {
|
||||
Object.defineProperty(track, key, {
|
||||
get() { return trackProps[key]; },
|
||||
set() {}
|
||||
});
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
export default Track;
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @file video-track-list.js
|
||||
*/
|
||||
import TrackList from './track-list';
|
||||
import * as browser from '../utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* disable other video tracks before selecting the new one
|
||||
*
|
||||
* @param {Array|VideoTrackList} list list to work on
|
||||
* @param {VideoTrack} track the track to skip
|
||||
*/
|
||||
const disableOthers = function(list, track) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (track.id === list[i].id) {
|
||||
continue;
|
||||
}
|
||||
// another audio track is enabled, disable it
|
||||
list[i].selected = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of possiblee video tracks. Most functionality is in the
|
||||
* base class Tracklist and the spec for VideoTrackList is located at:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
|
||||
*
|
||||
* interface VideoTrackList : EventTarget {
|
||||
* readonly attribute unsigned long length;
|
||||
* getter VideoTrack (unsigned long index);
|
||||
* VideoTrack? getTrackById(DOMString id);
|
||||
* readonly attribute long selectedIndex;
|
||||
*
|
||||
* attribute EventHandler onchange;
|
||||
* attribute EventHandler onaddtrack;
|
||||
* attribute EventHandler onremovetrack;
|
||||
* };
|
||||
*
|
||||
* @param {VideoTrack[]} tracks a list of video tracks to instantiate the list with
|
||||
# @extends TrackList
|
||||
* @class VideoTrackList
|
||||
*/
|
||||
class VideoTrackList extends TrackList {
|
||||
|
||||
constructor(tracks = []) {
|
||||
let list;
|
||||
|
||||
// make sure only 1 track is enabled
|
||||
// sorted from last index to first index
|
||||
for (let i = tracks.length - 1; i >= 0; i--) {
|
||||
if (tracks[i].selected) {
|
||||
disableOthers(tracks, tracks[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// IE8 forces us to implement inheritance ourselves
|
||||
// as it does not support Object.defineProperty properly
|
||||
if (browser.IS_IE8) {
|
||||
list = document.createElement('custom');
|
||||
for (let prop in TrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = TrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
for (let prop in VideoTrackList.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
list[prop] = VideoTrackList.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list = super(tracks, list);
|
||||
list.changing_ = false;
|
||||
|
||||
Object.defineProperty(list, 'selectedIndex', {
|
||||
get() {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (this[i].selected) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
addTrack_(track) {
|
||||
if (track.selected) {
|
||||
disableOthers(this, track);
|
||||
}
|
||||
|
||||
super.addTrack_(track);
|
||||
// native tracks don't have this
|
||||
if (!track.addEventListener) {
|
||||
return;
|
||||
}
|
||||
track.addEventListener('selectedchange', () => {
|
||||
if (this.changing_) {
|
||||
return;
|
||||
}
|
||||
this.changing_ = true;
|
||||
disableOthers(this, track);
|
||||
this.changing_ = false;
|
||||
this.trigger('change');
|
||||
});
|
||||
}
|
||||
|
||||
addTrack(track) {
|
||||
this.addTrack_(track);
|
||||
}
|
||||
|
||||
removeTrack(track) {
|
||||
super.removeTrack_(track);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default VideoTrackList;
|
||||
@@ -0,0 +1,63 @@
|
||||
import {VideoTrackKind} from './track-enums';
|
||||
import Track from './track';
|
||||
import merge from '../utils/merge-options';
|
||||
import * as browser from '../utils/browser.js';
|
||||
|
||||
/**
|
||||
* A single video text track as defined in:
|
||||
* @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack
|
||||
*
|
||||
* interface VideoTrack {
|
||||
* readonly attribute DOMString id;
|
||||
* readonly attribute DOMString kind;
|
||||
* readonly attribute DOMString label;
|
||||
* readonly attribute DOMString language;
|
||||
* attribute boolean selected;
|
||||
* };
|
||||
*
|
||||
* @param {Object=} options Object of option names and values
|
||||
* @class VideoTrack
|
||||
*/
|
||||
class VideoTrack extends Track {
|
||||
constructor(options = {}) {
|
||||
let settings = merge(options, {
|
||||
kind: VideoTrackKind[options.kind] || ''
|
||||
});
|
||||
|
||||
// on IE8 this will be a document element
|
||||
// for every other browser this will be a normal object
|
||||
let track = super(settings);
|
||||
let selected = false;
|
||||
|
||||
if (browser.IS_IE8) {
|
||||
for (let prop in VideoTrack.prototype) {
|
||||
if (prop !== 'constructor') {
|
||||
track[prop] = VideoTrack.prototype[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(track, 'selected', {
|
||||
get() { return selected; },
|
||||
set(newSelected) {
|
||||
// an invalid or unchanged value
|
||||
if (typeof newSelected !== 'boolean' || newSelected === selected) {
|
||||
return;
|
||||
}
|
||||
selected = newSelected;
|
||||
this.trigger('selectedchange');
|
||||
}
|
||||
});
|
||||
|
||||
// if the user sets this track to selected then
|
||||
// set selected to that true value otherwise
|
||||
// we keep it false
|
||||
if (settings.selected) {
|
||||
track.selected = settings.selected;
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoTrack;
|
||||
@@ -13,6 +13,8 @@ import plugin from './plugins.js';
|
||||
import mergeOptions from '../../src/js/utils/merge-options.js';
|
||||
import * as Fn from './utils/fn.js';
|
||||
import TextTrack from './tracks/text-track.js';
|
||||
import AudioTrack from './tracks/audio-track.js';
|
||||
import VideoTrack from './tracks/video-track.js';
|
||||
|
||||
import assign from 'object.assign';
|
||||
import { createTimeRanges } from './utils/time-ranges.js';
|
||||
@@ -549,6 +551,22 @@ videojs.xhr = xhr;
|
||||
*/
|
||||
videojs.TextTrack = TextTrack;
|
||||
|
||||
/**
|
||||
* export the AudioTrack class so that source handlers can create
|
||||
* AudioTracks and then add them to the players AudioTrackList
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
videojs.AudioTrack = AudioTrack;
|
||||
|
||||
/**
|
||||
* export the VideoTrack class so that source handlers can create
|
||||
* VideoTracks and then add them to the players VideoTrackList
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
videojs.VideoTrack = VideoTrack;
|
||||
|
||||
/**
|
||||
* Determines, via duck typing, whether or not a value is a DOM element.
|
||||
*
|
||||
|
||||
+3
-1
@@ -59,7 +59,9 @@ test('should be able to access expected player API methods', function() {
|
||||
ok(player.usingNativeControls, 'usingNativeControls exists');
|
||||
ok(player.isFullscreen, 'isFullscreen exists');
|
||||
|
||||
// TextTrack methods
|
||||
// Track methods
|
||||
ok(player.audioTracks, 'audioTracks exists');
|
||||
ok(player.videoTracks, 'videoTracks exists');
|
||||
ok(player.textTracks, 'textTracks exists');
|
||||
ok(player.remoteTextTrackEls, 'remoteTextTrackEls exists');
|
||||
ok(player.remoteTextTracks, 'remoteTextTracks exists');
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VideoJS Tests</title>
|
||||
|
||||
@@ -86,14 +86,13 @@ test('dispose removes the object element even before ready fires', function() {
|
||||
// This test appears to test bad functionaly that was fixed
|
||||
// so it's debateable whether or not it's useful
|
||||
let dispose = Flash.prototype.dispose;
|
||||
let mockFlash = {};
|
||||
let mockFlash = new MockFlash();
|
||||
let noop = function(){};
|
||||
|
||||
// Mock required functions for dispose
|
||||
mockFlash.off = noop;
|
||||
mockFlash.trigger = noop;
|
||||
mockFlash.el_ = {};
|
||||
mockFlash.textTracks = () => ([]);
|
||||
|
||||
dispose.call(mockFlash);
|
||||
strictEqual(mockFlash.el_, null, 'swf el is nulled');
|
||||
|
||||
@@ -249,6 +249,97 @@ if (Html5.supportsNativeTextTracks()) {
|
||||
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
||||
});
|
||||
}
|
||||
|
||||
if (Html5.supportsNativeAudioTracks()) {
|
||||
test('add native audioTrack listeners on startup', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let at = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.audioTracks = at;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
});
|
||||
|
||||
test('remove all tracks from emulated list on dispose', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let at = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.audioTracks = at;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
htmlTech.dispose();
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
equal(rems[0][0], 'change', 'change event handler removed');
|
||||
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
|
||||
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
|
||||
equal(adds[0][0], rems[0][0], 'change event handler removed');
|
||||
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
|
||||
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
||||
});
|
||||
}
|
||||
|
||||
if (Html5.supportsNativeVideoTracks()) {
|
||||
test('add native videoTrack listeners on startup', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let vt = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.videoTracks = vt;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
});
|
||||
|
||||
test('remove all tracks from emulated list on dispose', function() {
|
||||
let adds = [];
|
||||
let rems = [];
|
||||
let vt = {
|
||||
length: 0,
|
||||
addEventListener: (type, fn) => adds.push([type, fn]),
|
||||
removeEventListener: (type, fn) => rems.push([type, fn]),
|
||||
};
|
||||
let el = document.createElement('div');
|
||||
el.videoTracks = vt;
|
||||
|
||||
let htmlTech = new Html5({el});
|
||||
htmlTech.dispose();
|
||||
|
||||
equal(adds[0][0], 'change', 'change event handler added');
|
||||
equal(adds[1][0], 'addtrack', 'addtrack event handler added');
|
||||
equal(adds[2][0], 'removetrack', 'removetrack event handler added');
|
||||
equal(rems[0][0], 'change', 'change event handler removed');
|
||||
equal(rems[1][0], 'addtrack', 'addtrack event handler removed');
|
||||
equal(rems[2][0], 'removetrack', 'removetrack event handler removed');
|
||||
equal(adds[0][0], rems[0][0], 'change event handler removed');
|
||||
equal(adds[1][0], rems[1][0], 'addtrack event handler removed');
|
||||
equal(adds[2][0], rems[2][0], 'removetrack event handler removed');
|
||||
});
|
||||
}
|
||||
|
||||
test('should always return currentSource_ if set', function(){
|
||||
let currentSrc = Html5.prototype.currentSrc;
|
||||
equal(currentSrc.call({el_: {currentSrc:'test1'}}), 'test1', 'sould return source from element if nothing else set');
|
||||
|
||||
+218
-14
@@ -7,6 +7,12 @@ import Button from '../../../src/js/button.js';
|
||||
import { createTimeRange } from '../../../src/js/utils/time-ranges.js';
|
||||
import extendFn from '../../../src/js/extend.js';
|
||||
import MediaError from '../../../src/js/media-error.js';
|
||||
import AudioTrack from '../../../src/js/tracks/audio-track';
|
||||
import VideoTrack from '../../../src/js/tracks/video-track';
|
||||
import TextTrack from '../../../src/js/tracks/text-track';
|
||||
import AudioTrackList from '../../../src/js/tracks/audio-track-list';
|
||||
import VideoTrackList from '../../../src/js/tracks/video-track-list';
|
||||
import TextTrackList from '../../../src/js/tracks/text-track-list';
|
||||
|
||||
q.module('Media Tech', {
|
||||
'setup': function() {
|
||||
@@ -14,20 +20,10 @@ q.module('Media Tech', {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.featuresProgessEvents = Tech.prototype['featuresProgessEvents'];
|
||||
Tech.prototype['featuresProgressEvents'] = false;
|
||||
Tech.prototype['featuresNativeTextTracks'] = true;
|
||||
oldTextTracks = Tech.prototype.textTracks;
|
||||
Tech.prototype.textTracks = function() {
|
||||
return {
|
||||
addEventListener: Function.prototype,
|
||||
removeEventListener: Function.prototype
|
||||
};
|
||||
};
|
||||
},
|
||||
'teardown': function() {
|
||||
this.clock.restore();
|
||||
Tech.prototype['featuresProgessEvents'] = this.featuresProgessEvents;
|
||||
Tech.prototype['featuresNativeTextTracks'] = false;
|
||||
Tech.prototype.textTracks = oldTextTracks;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -103,6 +99,54 @@ test('dispose() should stop time tracking', function() {
|
||||
ok(true, 'no exception was thrown');
|
||||
});
|
||||
|
||||
test('dispose() should clear all tracks that are passed when its created', function() {
|
||||
var audioTracks = new AudioTrackList([new AudioTrack(), new AudioTrack()]);
|
||||
var videoTracks = new VideoTrackList([new VideoTrack(), new VideoTrack()]);
|
||||
var textTracks = new TextTrackList([new TextTrack({tech: {}}), new TextTrack({tech: {}})]);
|
||||
|
||||
equal(audioTracks.length, 2, 'should have two audio tracks at the start');
|
||||
equal(videoTracks.length, 2, 'should have two video tracks at the start');
|
||||
equal(textTracks.length, 2, 'should have two text tracks at the start');
|
||||
|
||||
var tech = new Tech({audioTracks, videoTracks, textTracks});
|
||||
equal(tech.videoTracks().length, videoTracks.length, 'should hold video tracks that we passed');
|
||||
equal(tech.audioTracks().length, audioTracks.length, 'should hold audio tracks that we passed');
|
||||
equal(tech.textTracks().length, textTracks.length, 'should hold text tracks that we passed');
|
||||
|
||||
tech.dispose();
|
||||
|
||||
equal(audioTracks.length, 0, 'should have zero audio tracks after dispose');
|
||||
equal(videoTracks.length, 0, 'should have zero video tracks after dispose');
|
||||
equal(textTracks.length, 0, 'should have zero text tracks after dispose');
|
||||
});
|
||||
|
||||
test('dispose() should clear all tracks that are added after creation', function() {
|
||||
var tech = new Tech();
|
||||
|
||||
tech.addRemoteTextTrack({});
|
||||
tech.addRemoteTextTrack({});
|
||||
|
||||
tech.audioTracks().addTrack_(new AudioTrack());
|
||||
tech.audioTracks().addTrack_(new AudioTrack());
|
||||
|
||||
tech.videoTracks().addTrack_(new VideoTrack());
|
||||
tech.videoTracks().addTrack_(new VideoTrack());
|
||||
|
||||
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||
|
||||
tech.dispose();
|
||||
|
||||
equal(tech.audioTracks().length, 0, 'should have zero audio tracks after dispose');
|
||||
equal(tech.videoTracks().length, 0, 'should have zero video tracks after dispose');
|
||||
equal(tech.remoteTextTrackEls().length, 0, 'should have zero remote text tracks els');
|
||||
equal(tech.remoteTextTracks().length, 0, 'should have zero remote text tracks');
|
||||
equal(tech.textTracks().length, 0, 'should have zero video tracks after dispose');
|
||||
});
|
||||
|
||||
test('should add the source handler interface to a tech', function(){
|
||||
var sourceA = { src: 'foo.mp4', type: 'video/mp4' };
|
||||
var sourceB = { src: 'no-support', type: 'no-support' };
|
||||
@@ -147,9 +191,10 @@ test('should add the source handler interface to a tech', function(){
|
||||
}
|
||||
return '';
|
||||
},
|
||||
handleSource: function(s, t){
|
||||
handleSource: function(s, t, o){
|
||||
strictEqual(tech, t, 'the tech instance was passed to the source handler');
|
||||
strictEqual(sourceA, s, 'the tech instance was passed to the source handler');
|
||||
strictEqual(tech.options_, o, 'the tech options were passed to the source handler');
|
||||
return new handlerInternalState();
|
||||
}
|
||||
};
|
||||
@@ -161,7 +206,7 @@ test('should add the source handler interface to a tech', function(){
|
||||
canHandleSource: function(source){
|
||||
return ''; // no support
|
||||
},
|
||||
handleSource: function(source, tech){
|
||||
handleSource: function(source, tech, options){
|
||||
ok(false, 'handlerTwo supports nothing and should never be called');
|
||||
}
|
||||
};
|
||||
@@ -184,13 +229,49 @@ test('should add the source handler interface to a tech', function(){
|
||||
strictEqual(MyTech.canPlaySource(sourceA), 'probably', 'the Tech returned probably for the valid source');
|
||||
strictEqual(MyTech.canPlaySource(sourceB), '', 'the Tech returned an empty string for the invalid source');
|
||||
|
||||
tech.addRemoteTextTrack({});
|
||||
tech.addRemoteTextTrack({});
|
||||
|
||||
tech.audioTracks().addTrack_(new AudioTrack());
|
||||
tech.audioTracks().addTrack_(new AudioTrack());
|
||||
|
||||
tech.videoTracks().addTrack_(new VideoTrack());
|
||||
tech.videoTracks().addTrack_(new VideoTrack());
|
||||
|
||||
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||
|
||||
// Pass a source through the source handler process of a tech instance
|
||||
tech.setSource(sourceA);
|
||||
|
||||
// verify that the Tracks are still there
|
||||
equal(tech.audioTracks().length, 2, 'should have two audio tracks at the start');
|
||||
equal(tech.videoTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.textTracks().length, 2, 'should have two video tracks at the start');
|
||||
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||
|
||||
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
||||
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
||||
|
||||
// Pass a second source
|
||||
tech.setSource(sourceA);
|
||||
strictEqual(tech.currentSource_, sourceA, 'sourceA was handled and stored');
|
||||
ok(tech.sourceHandler_.dispose, 'the handlerOne state instance was stored');
|
||||
|
||||
// verify that all the tracks were removed as we got a new source
|
||||
equal(tech.audioTracks().length, 0, 'should have zero audio tracks');
|
||||
equal(tech.videoTracks().length, 0, 'should have zero video tracks');
|
||||
equal(tech.textTracks().length, 2, 'should have two text tracks');
|
||||
equal(tech.remoteTextTrackEls().length, 2, 'should have two remote text tracks els');
|
||||
equal(tech.remoteTextTracks().length, 2, 'should have two remote text tracks');
|
||||
|
||||
// Check that the handler dipose method works
|
||||
ok(!disposeCalled, 'dispose has not been called for the handler yet');
|
||||
ok(disposeCalled, 'dispose has been called for the handler yet');
|
||||
disposeCalled = false;
|
||||
tech.dispose();
|
||||
ok(disposeCalled, 'the handler dispose method was called when the tech was disposed');
|
||||
});
|
||||
@@ -261,7 +342,7 @@ test('delegates seekable to the source handler', function(){
|
||||
canHandleSource: function() {
|
||||
return true;
|
||||
},
|
||||
handleSource: function(source, tech) {
|
||||
handleSource: function(source, tech, options) {
|
||||
return handler;
|
||||
}
|
||||
});
|
||||
@@ -288,3 +369,126 @@ test('Tech.isTech returns correct answers for techs and components', function()
|
||||
ok(!isTech(new Button({}, {})), 'A Button instance is not a Tech');
|
||||
ok(!isTech(isTech), 'A function is not a Tech');
|
||||
});
|
||||
|
||||
test('Tech#setSource clears currentSource_ after repeated loadstart', function() {
|
||||
let disposed = false;
|
||||
let MyTech = extendFn(Tech);
|
||||
|
||||
Tech.withSourceHandlers(MyTech);
|
||||
let tech = new MyTech();
|
||||
|
||||
var sourceHandler = {
|
||||
canPlayType: function(type) {
|
||||
return true;
|
||||
},
|
||||
canHandleSource: function(source) {
|
||||
return true;
|
||||
},
|
||||
handleSource: function(source, tech, options) {
|
||||
return {
|
||||
dispose: function() {
|
||||
disposed = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Test registering source handlers
|
||||
MyTech.registerSourceHandler(sourceHandler);
|
||||
|
||||
// First loadstart
|
||||
tech.setSource('test');
|
||||
tech.currentSource_ = 'test';
|
||||
tech.trigger('loadstart');
|
||||
equal(tech.currentSource_, 'test', 'Current source is test');
|
||||
|
||||
// Second loadstart
|
||||
tech.trigger('loadstart');
|
||||
equal(tech.currentSource_, null, 'Current source is null');
|
||||
equal(disposed, true, 'disposed is true');
|
||||
|
||||
// Third loadstart
|
||||
tech.currentSource_ = 'test';
|
||||
tech.trigger('loadstart');
|
||||
equal(tech.currentSource_, null, 'Current source is still null');
|
||||
|
||||
});
|
||||
|
||||
test('setSource after tech dispose should dispose source handler once', function(){
|
||||
let MyTech = extendFn(Tech);
|
||||
Tech.withSourceHandlers(MyTech);
|
||||
|
||||
let disposeCount = 0;
|
||||
let handler = {
|
||||
dispose() {
|
||||
disposeCount++;
|
||||
}
|
||||
};
|
||||
|
||||
MyTech.registerSourceHandler({
|
||||
canPlayType: function() {
|
||||
return true;
|
||||
},
|
||||
canHandleSource: function() {
|
||||
return true;
|
||||
},
|
||||
handleSource: function(source, tech, options) {
|
||||
return handler;
|
||||
}
|
||||
});
|
||||
|
||||
let tech = new MyTech();
|
||||
tech.setSource('test');
|
||||
|
||||
equal(disposeCount, 0, 'did not call sourceHandler_ dispose for initial dispose');
|
||||
tech.dispose();
|
||||
ok(!tech.sourceHandler_, 'sourceHandler should be unset');
|
||||
equal(disposeCount, 1, 'called the source handler dispose');
|
||||
|
||||
// this would normally be done above tech on src after dispose
|
||||
tech.el_ = tech.createEl();
|
||||
|
||||
tech.setSource('test');
|
||||
equal(disposeCount, 1, 'did not dispose after initial setSource');
|
||||
|
||||
tech.setSource('test');
|
||||
equal(disposeCount, 2, 'did dispose on second setSource');
|
||||
|
||||
});
|
||||
|
||||
test('setSource after previous setSource should dispose source handler once', function(){
|
||||
let MyTech = extendFn(Tech);
|
||||
Tech.withSourceHandlers(MyTech);
|
||||
|
||||
let disposeCount = 0;
|
||||
let handler = {
|
||||
dispose() {
|
||||
disposeCount++;
|
||||
}
|
||||
};
|
||||
|
||||
MyTech.registerSourceHandler({
|
||||
canPlayType: function() {
|
||||
return true;
|
||||
},
|
||||
canHandleSource: function() {
|
||||
return true;
|
||||
},
|
||||
handleSource: function(source, tech, options) {
|
||||
return handler;
|
||||
}
|
||||
});
|
||||
|
||||
let tech = new MyTech();
|
||||
|
||||
tech.setSource('test');
|
||||
equal(disposeCount, 0, 'did not call dispose for initial setSource');
|
||||
|
||||
tech.setSource('test');
|
||||
equal(disposeCount, 1, 'did dispose for second setSource');
|
||||
|
||||
tech.setSource('test');
|
||||
equal(disposeCount, 2, 'did dispose for third setSource');
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import AudioTrackList from '../../../src/js/tracks/audio-track-list.js';
|
||||
import AudioTrack from '../../../src/js/tracks/audio-track.js';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
|
||||
q.module('Audio Track List');
|
||||
|
||||
test('trigger "change" when "enabledchange" is fired on a track', function() {
|
||||
let track = new EventTarget();
|
||||
track.loaded_ = true;
|
||||
let audioTrackList = new AudioTrackList([track]);
|
||||
let changes = 0;
|
||||
let changeHandler = function() {
|
||||
changes++;
|
||||
};
|
||||
audioTrackList.on('change', changeHandler);
|
||||
track.trigger('enabledchange');
|
||||
equal(changes, 1, 'one change events for trigger');
|
||||
|
||||
audioTrackList.off('change', changeHandler);
|
||||
audioTrackList.onchange = changeHandler;
|
||||
|
||||
track.trigger('enabledchange');
|
||||
equal(changes, 2, 'one change events for another trigger');
|
||||
});
|
||||
|
||||
test('only one track is ever enabled', function() {
|
||||
let track = new AudioTrack({enabled: true});
|
||||
let track2 = new AudioTrack({enabled: true});
|
||||
let track3 = new AudioTrack({enabled: true});
|
||||
let track4 = new AudioTrack();
|
||||
let list = new AudioTrackList([track, track2]);
|
||||
equal(track.enabled, false, 'track is disabled');
|
||||
equal(track2.enabled, true, 'track2 is enabled');
|
||||
|
||||
track.enabled = true;
|
||||
equal(track.enabled, true, 'track is enabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
|
||||
list.addTrack_(track3);
|
||||
equal(track.enabled, false, 'track is disabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
equal(track3.enabled, true, 'track3 is enabled');
|
||||
|
||||
track.enabled = true;
|
||||
equal(track.enabled, true, 'track is disabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
equal(track3.enabled, false, 'track3 is disabled');
|
||||
|
||||
list.addTrack_(track4);
|
||||
equal(track.enabled, true, 'track is enabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
equal(track3.enabled, false, 'track3 is disabled');
|
||||
equal(track4.enabled, false, 'track4 is disabled');
|
||||
|
||||
});
|
||||
|
||||
test('all tracks can be disabled', function() {
|
||||
let track = new AudioTrack();
|
||||
let track2 = new AudioTrack();
|
||||
let list = new AudioTrackList([track, track2]);
|
||||
equal(track.enabled, false, 'track is disabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
|
||||
track.enabled = true;
|
||||
equal(track.enabled, true, 'track is enabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
|
||||
track.enabled = false;
|
||||
equal(track.enabled, false, 'track is disabled');
|
||||
equal(track2.enabled, false, 'track2 is disabled');
|
||||
});
|
||||
|
||||
test('trigger a change event per enabled change', function() {
|
||||
let track = new AudioTrack({enabled: true});
|
||||
let track2 = new AudioTrack({enabled: true});
|
||||
let track3 = new AudioTrack({enabled: true});
|
||||
let track4 = new AudioTrack();
|
||||
let list = new AudioTrackList([track, track2]);
|
||||
|
||||
let change = 0;
|
||||
list.on('change', () => change++);
|
||||
track.enabled = true;
|
||||
equal(change, 1, 'one change triggered');
|
||||
|
||||
list.addTrack_(track3);
|
||||
equal(change, 2, 'another change triggered by adding an enabled track');
|
||||
|
||||
track.enabled = true;
|
||||
equal(change, 3, 'another change trigger by changing enabled');
|
||||
|
||||
track.enabled = false;
|
||||
equal(change, 4, 'another change trigger by changing enabled');
|
||||
|
||||
list.addTrack_(track4);
|
||||
equal(change, 4, 'no change triggered by adding a disabled track');
|
||||
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import AudioTrack from '../../../src/js/tracks/audio-track.js';
|
||||
import {AudioTrackKind} from '../../../src/js/tracks/track-enums.js';
|
||||
import TrackBaseline from './track-baseline';
|
||||
|
||||
q.module('Audio Track');
|
||||
|
||||
// do baseline track testing
|
||||
TrackBaseline(AudioTrack, {
|
||||
id: '1',
|
||||
language: 'en',
|
||||
label: 'English',
|
||||
kind: 'main',
|
||||
});
|
||||
|
||||
test('can create an enabled propert on an AudioTrack', function() {
|
||||
let enabled = true;
|
||||
let track = new AudioTrack({
|
||||
enabled,
|
||||
});
|
||||
equal(track.enabled, enabled, 'enabled value matches what we passed in');
|
||||
});
|
||||
|
||||
test('defaults when items not provided', function() {
|
||||
let track = new AudioTrack();
|
||||
|
||||
equal(track.kind, '', 'kind defaulted to empty string');
|
||||
equal(track.enabled, false, 'enabled defaulted to true since there is one track');
|
||||
equal(track.label, '', 'label defaults to empty string');
|
||||
equal(track.language, '', 'language defaults to empty string');
|
||||
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||
});
|
||||
|
||||
test('kind can only be one of several options, defaults to empty string', function() {
|
||||
let track = new AudioTrack({
|
||||
kind: 'foo'
|
||||
});
|
||||
|
||||
equal(track.kind, '', 'the kind is set to empty string, not foo');
|
||||
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
|
||||
|
||||
// loop through all possible kinds to verify
|
||||
for (let key in AudioTrackKind) {
|
||||
let currentKind = AudioTrackKind[key];
|
||||
let track = new AudioTrack({
|
||||
kind: currentKind,
|
||||
});
|
||||
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
|
||||
}
|
||||
});
|
||||
|
||||
test('enabled can only be instantiated to true or false, defaults to false', function() {
|
||||
let track = new AudioTrack({
|
||||
enabled: 'foo'
|
||||
});
|
||||
|
||||
equal(track.enabled, false, 'the enabled value is set to false, not foo');
|
||||
notEqual(track.enabled, 'foo', 'the enabled value is not set to foo');
|
||||
|
||||
track = new AudioTrack({
|
||||
enabled: true
|
||||
});
|
||||
|
||||
equal(track.enabled, true, 'the enabled value is set to true');
|
||||
|
||||
track = new AudioTrack({
|
||||
enabled: false
|
||||
});
|
||||
|
||||
equal(track.enabled, false, 'the enabled value is set to false');
|
||||
});
|
||||
|
||||
test('enabled can only be changed to true or false', function() {
|
||||
let track = new AudioTrack();
|
||||
|
||||
track.enabled = 'foo';
|
||||
notEqual(track.enabled, 'foo', 'enabled not set to invalid value, foo');
|
||||
equal(track.enabled, false, 'enabled remains on the old value, false');
|
||||
|
||||
track.enabled = true;
|
||||
equal(track.enabled, true, 'enabled was set to true');
|
||||
|
||||
track.enabled = 'baz';
|
||||
notEqual(track.enabled, 'baz', 'enabled not set to invalid value, baz');
|
||||
equal(track.enabled, true, 'enabled remains on the old value, true');
|
||||
|
||||
track.enabled = false;
|
||||
equal(track.enabled, false, 'enabled was set to false');
|
||||
});
|
||||
|
||||
test('when enabled is changed enabledchange event is fired', function() {
|
||||
let track = new AudioTrack({
|
||||
tech: this.tech,
|
||||
enabled: false
|
||||
});
|
||||
let eventsTriggered = 0;
|
||||
track.addEventListener('enabledchange', () => {
|
||||
eventsTriggered++;
|
||||
});
|
||||
|
||||
// two events
|
||||
track.enabled = true;
|
||||
track.enabled = false;
|
||||
equal(eventsTriggered, 2, 'two enabled changes');
|
||||
|
||||
// no event here
|
||||
track.enabled = false;
|
||||
track.enabled = false;
|
||||
equal(eventsTriggered, 2, 'still two enabled changes');
|
||||
|
||||
// one event
|
||||
track.enabled = true;
|
||||
equal(eventsTriggered, 3, 'three enabled changes');
|
||||
|
||||
// no events
|
||||
track.enabled = true;
|
||||
track.enabled = true;
|
||||
equal(eventsTriggered, 3, 'still three enabled changes');
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import AudioTrack from '../../../src/js/tracks/text-track.js';
|
||||
import Html5 from '../../../src/js/tech/html5.js';
|
||||
import Tech from '../../../src/js/tech/tech.js';
|
||||
import Component from '../../../src/js/component.js';
|
||||
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
import document from 'global/document';
|
||||
|
||||
q.module('Tracks', {
|
||||
setup() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
},
|
||||
teardown() {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('Player track methods call the tech', function() {
|
||||
let player;
|
||||
let calls = 0;
|
||||
|
||||
player = TestHelpers.makePlayer();
|
||||
|
||||
player.tech_.audioTracks = function() {
|
||||
calls++;
|
||||
};
|
||||
|
||||
player.audioTracks();
|
||||
|
||||
equal(calls, 1, 'audioTrack defers to the tech');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
test('listen to remove and add track events in native audio tracks', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
let player;
|
||||
let options;
|
||||
let oldAudioTracks = Html5.prototype.audioTracks;
|
||||
let events = {};
|
||||
let html;
|
||||
|
||||
Html5.prototype.audioTracks = function() {
|
||||
return {
|
||||
addEventListener(type, handler) {
|
||||
events[type] = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Html5.TEST_VID = {
|
||||
audioTracks: []
|
||||
};
|
||||
|
||||
player = {
|
||||
// Function.prototype is a built-in no-op function.
|
||||
controls() {},
|
||||
ready() {},
|
||||
options() {
|
||||
return {};
|
||||
},
|
||||
addChild() {},
|
||||
id() {},
|
||||
el() {
|
||||
return {
|
||||
insertBefore() {},
|
||||
appendChild() {}
|
||||
};
|
||||
}
|
||||
};
|
||||
player.player_ = player;
|
||||
player.options_ = options = {};
|
||||
|
||||
html = new Html5(options);
|
||||
|
||||
ok(events.removetrack, 'removetrack listener was added');
|
||||
ok(events.addtrack, 'addtrack listener was added');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
Html5.prototype.audioTracks = oldAudioTracks;
|
||||
});
|
||||
|
||||
test('html5 tech supports native audio tracks if the video supports it', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
|
||||
Html5.TEST_VID = {
|
||||
audioTracks: []
|
||||
};
|
||||
|
||||
ok(Html5.supportsNativeAudioTracks(), 'native audio tracks are supported');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
});
|
||||
|
||||
test('html5 tech does not support native audio tracks if the video does not supports it', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
Html5.TEST_VID = {};
|
||||
|
||||
ok(!Html5.supportsNativeAudioTracks(), 'native audio tracks are not supported');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
});
|
||||
|
||||
test('when switching techs, we should not get a new audio track', function() {
|
||||
let player = TestHelpers.makePlayer();
|
||||
|
||||
player.loadTech_('TechFaker');
|
||||
let firstTracks = player.audioTracks();
|
||||
|
||||
player.loadTech_('TechFaker');
|
||||
let secondTracks = player.audioTracks();
|
||||
|
||||
ok(firstTracks === secondTracks, 'the tracks are equal');
|
||||
});
|
||||
@@ -2,155 +2,7 @@ import TextTrackList from '../../../src/js/tracks/text-track-list.js';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
|
||||
const genericTracks = [
|
||||
{
|
||||
id: '1',
|
||||
addEventListener() {},
|
||||
off() {}
|
||||
}, {
|
||||
id: '2',
|
||||
addEventListener() {},
|
||||
off() {}
|
||||
}, {
|
||||
id: '3',
|
||||
addEventListener() {},
|
||||
off() {}
|
||||
}
|
||||
];
|
||||
|
||||
q.module('Text Track List');
|
||||
|
||||
test('TextTrackList\'s length is set correctly', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
|
||||
});
|
||||
|
||||
test('can get text tracks by id', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
equal(ttl.getTrackById('1').id, 1, 'id "1" has id of "1"');
|
||||
equal(ttl.getTrackById('2').id, 2, 'id "2" has id of "2"');
|
||||
equal(ttl.getTrackById('3').id, 3, 'id "3" has id of "3"');
|
||||
ok(!ttl.getTrackById(1), 'there isn\'t an item with "numeric" id of `1`');
|
||||
});
|
||||
|
||||
test('length is updated when new tracks are added or removed', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
||||
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
|
||||
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
|
||||
equal(ttl.length, genericTracks.length + 2, 'the length is ' + (genericTracks.length + 2));
|
||||
|
||||
ttl.removeTrack_(ttl.getTrackById('101'));
|
||||
equal(ttl.length, genericTracks.length + 1, 'the length is ' + (genericTracks.length + 1));
|
||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
||||
equal(ttl.length, genericTracks.length, 'the length is ' + genericTracks.length);
|
||||
});
|
||||
|
||||
test('can access items by index', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
let i = 0;
|
||||
let length = ttl.length;
|
||||
|
||||
expect(length);
|
||||
|
||||
for (; i < length; i++) {
|
||||
equal(ttl[i].id, String(i + 1), 'the id of a track matches the index + 1');
|
||||
}
|
||||
});
|
||||
|
||||
test('can access new items by index', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
ttl.addTrack_({id: '100', addEventListener() {}});
|
||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
||||
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
|
||||
});
|
||||
|
||||
test('cannot access removed items by index', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
||||
ttl.addTrack_({id: '101', addEventListener() {}, off() {}});
|
||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
||||
equal(ttl[4].id, '101', 'id of item at index 4 is 101');
|
||||
|
||||
ttl.removeTrack_(ttl.getTrackById('101'));
|
||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
||||
|
||||
ok(!ttl[3], 'nothing at index 3');
|
||||
ok(!ttl[4], 'nothing at index 4');
|
||||
});
|
||||
|
||||
test('new item available at old index', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
|
||||
ttl.addTrack_({id: '100', addEventListener() {}, off() {}});
|
||||
equal(ttl[3].id, '100', 'id of item at index 3 is 100');
|
||||
|
||||
ttl.removeTrack_(ttl.getTrackById('100'));
|
||||
ok(!ttl[3], 'nothing at index 3');
|
||||
|
||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
||||
equal(ttl[3].id, '101', 'id of new item at index 3 is now 101');
|
||||
});
|
||||
|
||||
test('a "addtrack" event is triggered when new tracks are added', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
let tracks = 0;
|
||||
let adds = 0;
|
||||
let addHandler = function(e) {
|
||||
if (e.track) {
|
||||
tracks++;
|
||||
}
|
||||
adds++;
|
||||
};
|
||||
|
||||
ttl.on('addtrack', addHandler);
|
||||
|
||||
ttl.addTrack_({id: '100', addEventListener() {}});
|
||||
ttl.addTrack_({id: '101', addEventListener() {}});
|
||||
|
||||
ttl.off('addtrack', addHandler);
|
||||
|
||||
ttl.onaddtrack = addHandler;
|
||||
|
||||
ttl.addTrack_({id: '102', addEventListener() {}});
|
||||
ttl.addTrack_({id: '103', addEventListener() {}});
|
||||
|
||||
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
|
||||
equal(tracks, 4, 'we got a track with every event');
|
||||
});
|
||||
|
||||
test('a "removetrack" event is triggered when tracks are removed', function() {
|
||||
let ttl = new TextTrackList(genericTracks);
|
||||
let tracks = 0;
|
||||
let rms = 0;
|
||||
let rmHandler = function(e) {
|
||||
if (e.track) {
|
||||
tracks++;
|
||||
}
|
||||
rms++;
|
||||
};
|
||||
|
||||
ttl.on('removetrack', rmHandler);
|
||||
|
||||
ttl.removeTrack_(ttl.getTrackById('1'));
|
||||
ttl.removeTrack_(ttl.getTrackById('2'));
|
||||
|
||||
ttl.off('removetrack', rmHandler);
|
||||
|
||||
ttl.onremovetrack = rmHandler;
|
||||
|
||||
ttl.removeTrack_(ttl.getTrackById('3'));
|
||||
|
||||
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
|
||||
equal(tracks, 3, 'we got a track with every event');
|
||||
});
|
||||
|
||||
test('trigger "change" event when "modechange" is fired on a track', function() {
|
||||
let tt = new EventTarget();
|
||||
let ttl = new TextTrackList([tt]);
|
||||
@@ -160,15 +12,12 @@ test('trigger "change" event when "modechange" is fired on a track', function()
|
||||
};
|
||||
|
||||
ttl.on('change', changeHandler);
|
||||
|
||||
tt.trigger('modechange');
|
||||
|
||||
ttl.off('change', changeHandler);
|
||||
|
||||
ttl.onchange = changeHandler;
|
||||
|
||||
tt.trigger('modechange');
|
||||
|
||||
equal(changes, 2, 'two change events should have fired');
|
||||
});
|
||||
|
||||
@@ -185,11 +34,9 @@ test('trigger "change" event when mode changes on a TextTrack', function() {
|
||||
};
|
||||
|
||||
ttl.on('change', changeHandler);
|
||||
|
||||
tt.mode = 'showing';
|
||||
|
||||
ttl.off('change', changeHandler);
|
||||
|
||||
ttl.onchange = changeHandler;
|
||||
|
||||
tt.mode = 'hidden';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import window from 'global/window';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
import TrackBaseline from './track-baseline';
|
||||
import TechFaker from '../tech/tech-faker';
|
||||
import TextTrack from '../../../src/js/tracks/text-track.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
import log from '../../../src/js/utils/log.js';
|
||||
@@ -13,40 +15,38 @@ const defaultTech = {
|
||||
off() {},
|
||||
currentTime() {}
|
||||
};
|
||||
|
||||
q.module('Text Track');
|
||||
|
||||
test('text-track requires a tech', function() {
|
||||
let error = new Error('A tech was not provided.');
|
||||
|
||||
q.throws(() => new TextTrack(), error, 'a tech is required for text track');
|
||||
// do baseline track testing
|
||||
TrackBaseline(TextTrack, {
|
||||
id: '1',
|
||||
kind: 'subtitles',
|
||||
mode: 'disabled',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
tech: defaultTech
|
||||
});
|
||||
|
||||
test('can create a TextTrack with various properties', function() {
|
||||
let kind = 'captions';
|
||||
let label = 'English';
|
||||
let language = 'en';
|
||||
let id = '1';
|
||||
test('requires a tech', function() {
|
||||
let error = new Error('A tech was not provided.');
|
||||
|
||||
q.throws(() => new TextTrack({}), error, 'a tech is required');
|
||||
q.throws(() => new TextTrack({tech: null}), error, 'a tech is required');
|
||||
});
|
||||
|
||||
test('can create a TextTrack with a mode property', function() {
|
||||
let mode = 'disabled';
|
||||
let tt = new TextTrack({
|
||||
kind,
|
||||
label,
|
||||
language,
|
||||
id,
|
||||
mode,
|
||||
tech: defaultTech
|
||||
});
|
||||
|
||||
equal(tt.kind, kind, 'we have a kind');
|
||||
equal(tt.label, label, 'we have a label');
|
||||
equal(tt.language, language, 'we have a language');
|
||||
equal(tt.id, id, 'we have a id');
|
||||
equal(tt.mode, mode, 'we have a mode');
|
||||
});
|
||||
|
||||
test('defaults when items not provided', function() {
|
||||
let tt = new TextTrack({
|
||||
tech: defaultTech
|
||||
tech: TechFaker
|
||||
});
|
||||
|
||||
equal(tt.kind, 'subtitles', 'kind defaulted to subtitles');
|
||||
@@ -131,32 +131,16 @@ test('mode can only be one of several options, defaults to disabled', function()
|
||||
equal(tt.mode, 'showing', 'the mode is set to showing');
|
||||
});
|
||||
|
||||
test('kind, label, language, id, cue, and activeCues are read only', function() {
|
||||
let kind = 'captions';
|
||||
let label = 'English';
|
||||
let language = 'en';
|
||||
let id = '1';
|
||||
test('cue and activeCues are read only', function() {
|
||||
let mode = 'disabled';
|
||||
let tt = new TextTrack({
|
||||
kind,
|
||||
label,
|
||||
language,
|
||||
id,
|
||||
mode,
|
||||
tech: defaultTech
|
||||
tech: defaultTech,
|
||||
});
|
||||
|
||||
tt.kind = 'subtitles';
|
||||
tt.label = 'Spanish';
|
||||
tt.language = 'es';
|
||||
tt.id = '2';
|
||||
tt.cues = 'foo';
|
||||
tt.activeCues = 'bar';
|
||||
|
||||
equal(tt.kind, kind, 'kind is still set to captions');
|
||||
equal(tt.label, label, 'label is still set to English');
|
||||
equal(tt.language, language, 'language is still set to en');
|
||||
equal(tt.id, id, 'id is still set to \'1\'');
|
||||
notEqual(tt.cues, 'foo', 'cues is still original value');
|
||||
notEqual(tt.activeCues, 'bar', 'activeCues is still original value');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
import document from 'global/document';
|
||||
|
||||
/**
|
||||
* Tests baseline functionality for all tracks
|
||||
*
|
||||
# @param {Track} TrackClass the track class object to use for testing
|
||||
# @param {Object} options the options to setup a track with
|
||||
*/
|
||||
const TrackBaseline = function(TrackClass, options) {
|
||||
|
||||
test('is setup with id, kind, label, and language', function() {
|
||||
let track = new TrackClass(options);
|
||||
equal(track.kind, options.kind, 'we have a kind');
|
||||
equal(track.label, options.label, 'we have a label');
|
||||
equal(track.language, options.language, 'we have a language');
|
||||
equal(track.id, options.id, 'we have a id');
|
||||
});
|
||||
|
||||
test('kind, label, language, id, are read only', function() {
|
||||
let track = new TrackClass(options);
|
||||
track.kind = 'subtitles';
|
||||
track.label = 'Spanish';
|
||||
track.language = 'es';
|
||||
track.id = '2';
|
||||
|
||||
equal(track.kind, options.kind, 'we have a kind');
|
||||
equal(track.label, options.label, 'we have a label');
|
||||
equal(track.language, options.language, 'we have a language');
|
||||
equal(track.id, options.id, 'we have an id');
|
||||
});
|
||||
|
||||
test('returns an instance of itself on non ie8 browsers', function() {
|
||||
let track = new TrackClass(options);
|
||||
if (browser.IS_IE8) {
|
||||
ok(track, 'returns an object on ie8');
|
||||
return;
|
||||
}
|
||||
ok(track instanceof TrackClass, 'returns an instance');
|
||||
});
|
||||
};
|
||||
|
||||
export default TrackBaseline;
|
||||
@@ -0,0 +1,143 @@
|
||||
import TrackList from '../../../src/js/tracks/track-list.js';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
|
||||
const newTrack = function(id) {
|
||||
return {
|
||||
id,
|
||||
addEventListener() {},
|
||||
off() {},
|
||||
};
|
||||
};
|
||||
|
||||
q.module('Track List', {
|
||||
beforeEach() {
|
||||
this.tracks = [newTrack('1'), newTrack('2'), newTrack('3')];
|
||||
}
|
||||
});
|
||||
|
||||
test('TrackList\'s length is set correctly', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
equal(trackList.length, this.tracks.length, 'length is ' + this.tracks.length);
|
||||
});
|
||||
|
||||
test('can get tracks by int and string id', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
equal(trackList.getTrackById('1').id, '1', 'id "1" has id of "1"');
|
||||
equal(trackList.getTrackById('2').id, '2', 'id "2" has id of "2"');
|
||||
equal(trackList.getTrackById('3').id, '3', 'id "3" has id of "3"');
|
||||
|
||||
});
|
||||
|
||||
test('length is updated when new tracks are added or removed', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
trackList.addTrack_(newTrack('100'));
|
||||
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
|
||||
trackList.addTrack_(newTrack('101'));
|
||||
equal(trackList.length, this.tracks.length + 2, 'the length is ' + (this.tracks.length + 2));
|
||||
|
||||
trackList.removeTrack_(trackList.getTrackById('101'));
|
||||
equal(trackList.length, this.tracks.length + 1, 'the length is ' + (this.tracks.length + 1));
|
||||
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||
equal(trackList.length, this.tracks.length, 'the length is ' + this.tracks.length);
|
||||
});
|
||||
|
||||
test('can access items by index', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
let length = trackList.length;
|
||||
|
||||
expect(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
equal(trackList[i].id, String(i + 1), 'the id of a track matches the index + 1');
|
||||
}
|
||||
});
|
||||
|
||||
test('can access new items by index', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
trackList.addTrack_(newTrack('100'));
|
||||
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||
|
||||
trackList.addTrack_(newTrack('101'));
|
||||
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
|
||||
});
|
||||
|
||||
test('cannot access removed items by index', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
trackList.addTrack_(newTrack('100'));
|
||||
trackList.addTrack_(newTrack('101'));
|
||||
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||
equal(trackList[4].id, '101', 'id of item at index 4 is 101');
|
||||
|
||||
trackList.removeTrack_(trackList.getTrackById('101'));
|
||||
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||
|
||||
ok(!trackList[3], 'nothing at index 3');
|
||||
ok(!trackList[4], 'nothing at index 4');
|
||||
});
|
||||
|
||||
test('new item available at old index', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
|
||||
trackList.addTrack_(newTrack('100'));
|
||||
equal(trackList[3].id, '100', 'id of item at index 3 is 100');
|
||||
|
||||
trackList.removeTrack_(trackList.getTrackById('100'));
|
||||
ok(!trackList[3], 'nothing at index 3');
|
||||
|
||||
trackList.addTrack_(newTrack('101'));
|
||||
equal(trackList[3].id, '101', 'id of new item at index 3 is now 101');
|
||||
});
|
||||
|
||||
test('a "addtrack" event is triggered when new tracks are added', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
let tracks = 0;
|
||||
let adds = 0;
|
||||
let addHandler = (e) => {
|
||||
if (e.track) {
|
||||
tracks++;
|
||||
}
|
||||
adds++;
|
||||
};
|
||||
|
||||
trackList.on('addtrack', addHandler);
|
||||
|
||||
trackList.addTrack_(newTrack('100'));
|
||||
trackList.addTrack_(newTrack('101'));
|
||||
|
||||
trackList.off('addtrack', addHandler);
|
||||
trackList.onaddtrack = addHandler;
|
||||
|
||||
trackList.addTrack_(newTrack('102'));
|
||||
trackList.addTrack_(newTrack('103'));
|
||||
|
||||
equal(adds, 4, 'we got ' + adds + ' "addtrack" events');
|
||||
equal(tracks, 4, 'we got a track with every event');
|
||||
});
|
||||
|
||||
test('a "removetrack" event is triggered when tracks are removed', function() {
|
||||
let trackList = new TrackList(this.tracks);
|
||||
let tracks = 0;
|
||||
let rms = 0;
|
||||
let rmHandler = (e) => {
|
||||
if (e.track) {
|
||||
tracks++;
|
||||
}
|
||||
rms++;
|
||||
};
|
||||
|
||||
trackList.on('removetrack', rmHandler);
|
||||
trackList.removeTrack_(trackList.getTrackById('1'));
|
||||
trackList.removeTrack_(trackList.getTrackById('2'));
|
||||
|
||||
trackList.off('removetrack', rmHandler);
|
||||
trackList.onremovetrack = rmHandler;
|
||||
trackList.removeTrack_(trackList.getTrackById('3'));
|
||||
|
||||
equal(rms, 3, 'we got ' + rms + ' "removetrack" events');
|
||||
equal(tracks, 3, 'we got a track with every event');
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import TechFaker from '../tech/tech-faker';
|
||||
import TrackBaseline from './track-baseline';
|
||||
import Track from '../../../src/js/tracks/track.js';
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
|
||||
const defaultTech = {
|
||||
textTracks() {},
|
||||
on() {},
|
||||
off() {},
|
||||
currentTime() {}
|
||||
};
|
||||
|
||||
// do baseline track testing
|
||||
q.module('Track');
|
||||
|
||||
TrackBaseline(Track, {
|
||||
id: '1',
|
||||
kind: 'subtitles',
|
||||
mode: 'disabled',
|
||||
label: 'English',
|
||||
language: 'en',
|
||||
tech: new TechFaker()
|
||||
});
|
||||
|
||||
test('defaults when items not provided', function() {
|
||||
let track = new Track({
|
||||
tech: defaultTech
|
||||
});
|
||||
|
||||
equal(track.kind, '', 'kind defaulted to empty string');
|
||||
equal(track.label, '', 'label defaults to empty string');
|
||||
equal(track.language, '', 'language defaults to empty string');
|
||||
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import VideoTrackList from '../../../src/js/tracks/video-track-list.js';
|
||||
import VideoTrack from '../../../src/js/tracks/video-track.js';
|
||||
import EventTarget from '../../../src/js/event-target.js';
|
||||
|
||||
q.module('Video Track List');
|
||||
|
||||
test('trigger "change" when "selectedchange" is fired on a track', function() {
|
||||
let track = new EventTarget();
|
||||
track.loaded_ = true;
|
||||
let audioTrackList = new VideoTrackList([track]);
|
||||
let changes = 0;
|
||||
let changeHandler = function() {
|
||||
changes++;
|
||||
};
|
||||
audioTrackList.on('change', changeHandler);
|
||||
track.trigger('selectedchange');
|
||||
equal(changes, 1, 'one change events for trigger');
|
||||
|
||||
audioTrackList.off('change', changeHandler);
|
||||
audioTrackList.onchange = changeHandler;
|
||||
|
||||
track.trigger('selectedchange');
|
||||
equal(changes, 2, 'one change events for another trigger');
|
||||
});
|
||||
|
||||
test('only one track is ever selected', function() {
|
||||
let track = new VideoTrack({selected: true});
|
||||
let track2 = new VideoTrack({selected: true});
|
||||
let track3 = new VideoTrack({selected: true});
|
||||
let track4 = new VideoTrack();
|
||||
let list = new VideoTrackList([track, track2]);
|
||||
equal(track.selected, false, 'track is unselected');
|
||||
equal(track2.selected, true, 'track2 is selected');
|
||||
|
||||
track.selected = true;
|
||||
equal(track.selected, true, 'track is selected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
|
||||
list.addTrack_(track3);
|
||||
equal(track.selected, false, 'track is unselected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
equal(track3.selected, true, 'track3 is selected');
|
||||
|
||||
track.selected = true;
|
||||
equal(track.selected, true, 'track is unselected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
equal(track3.selected, false, 'track3 is unselected');
|
||||
|
||||
list.addTrack_(track4);
|
||||
equal(track.selected, true, 'track is selected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
equal(track3.selected, false, 'track3 is unselected');
|
||||
equal(track4.selected, false, 'track4 is unselected');
|
||||
|
||||
});
|
||||
|
||||
test('all tracks can be unselected', function() {
|
||||
let track = new VideoTrack();
|
||||
let track2 = new VideoTrack();
|
||||
let list = new VideoTrackList([track, track2]);
|
||||
equal(track.selected, false, 'track is unselected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
|
||||
track.selected = true;
|
||||
equal(track.selected, true, 'track is selected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
|
||||
track.selected = false;
|
||||
equal(track.selected, false, 'track is unselected');
|
||||
equal(track2.selected, false, 'track2 is unselected');
|
||||
});
|
||||
|
||||
test('trigger a change event per selected change', function() {
|
||||
let track = new VideoTrack({selected: true});
|
||||
let track2 = new VideoTrack({selected: true});
|
||||
let track3 = new VideoTrack({selected: true});
|
||||
let track4 = new VideoTrack();
|
||||
let list = new VideoTrackList([track, track2]);
|
||||
|
||||
let change = 0;
|
||||
list.on('change', () => change++);
|
||||
track.selected = true;
|
||||
equal(change, 1, 'one change triggered');
|
||||
|
||||
list.addTrack_(track3);
|
||||
equal(change, 2, 'another change triggered by adding an selected track');
|
||||
|
||||
track.selected = true;
|
||||
equal(change, 3, 'another change trigger by changing selected');
|
||||
|
||||
track.selected = false;
|
||||
equal(change, 4, 'another change trigger by changing selected');
|
||||
|
||||
list.addTrack_(track4);
|
||||
equal(change, 4, 'no change triggered by adding a unselected track');
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
import VideoTrack from '../../../src/js/tracks/video-track';
|
||||
import VideoTrackList from '../../../src/js/tracks/video-track-list';
|
||||
import {VideoTrackKind} from '../../../src/js/tracks/track-enums';
|
||||
import TrackBaseline from './track-baseline';
|
||||
|
||||
q.module('Video Track');
|
||||
|
||||
// do baseline track testing
|
||||
TrackBaseline(VideoTrack, {
|
||||
id: '1',
|
||||
language: 'en',
|
||||
label: 'English',
|
||||
kind: 'main',
|
||||
});
|
||||
|
||||
test('can create an VideoTrack a selected property', function() {
|
||||
let selected = true;
|
||||
let track = new VideoTrack({
|
||||
selected,
|
||||
});
|
||||
equal(track.selected, selected, 'selected value matches what we passed in');
|
||||
});
|
||||
|
||||
test('defaults when items not provided', function() {
|
||||
let track = new VideoTrack();
|
||||
|
||||
equal(track.kind, '', 'kind defaulted to empty string');
|
||||
equal(track.selected, false, 'selected defaulted to true since there is one track');
|
||||
equal(track.label, '', 'label defaults to empty string');
|
||||
equal(track.language, '', 'language defaults to empty string');
|
||||
ok(track.id.match(/vjs_track_\d{5}/), 'id defaults to vjs_track_GUID');
|
||||
});
|
||||
|
||||
test('kind can only be one of several options, defaults to empty string', function() {
|
||||
let track = new VideoTrack({
|
||||
kind: 'foo'
|
||||
});
|
||||
|
||||
equal(track.kind, '', 'the kind is set to empty string, not foo');
|
||||
notEqual(track.kind, 'foo', 'the kind is set to empty string, not foo');
|
||||
|
||||
// loop through all possible kinds to verify
|
||||
for (let key in VideoTrackKind) {
|
||||
let currentKind = VideoTrackKind[key];
|
||||
let track = new VideoTrack({kind: currentKind});
|
||||
equal(track.kind, currentKind, 'the kind is set to ' + currentKind);
|
||||
}
|
||||
});
|
||||
|
||||
test('selected can only be instantiated to true or false, defaults to false', function() {
|
||||
let track = new VideoTrack({
|
||||
selected: 'foo'
|
||||
});
|
||||
|
||||
equal(track.selected, false, 'the selected value is set to false, not foo');
|
||||
notEqual(track.selected, 'foo', 'the selected value is not set to foo');
|
||||
|
||||
track = new VideoTrack({
|
||||
selected: true
|
||||
});
|
||||
|
||||
equal(track.selected, true, 'the selected value is set to true');
|
||||
|
||||
track = new VideoTrack({
|
||||
selected: false
|
||||
});
|
||||
|
||||
equal(track.selected, false, 'the selected value is set to false');
|
||||
});
|
||||
|
||||
test('selected can only be changed to true or false', function() {
|
||||
let track = new VideoTrack();
|
||||
|
||||
track.selected = 'foo';
|
||||
notEqual(track.selected, 'foo', 'selected not set to invalid value, foo');
|
||||
equal(track.selected, false, 'selected remains on the old value, false');
|
||||
|
||||
track.selected = true;
|
||||
equal(track.selected, true, 'selected was set to true');
|
||||
|
||||
track.selected = 'baz';
|
||||
notEqual(track.selected, 'baz', 'selected not set to invalid value, baz');
|
||||
equal(track.selected, true, 'selected remains on the old value, true');
|
||||
|
||||
track.selected = false;
|
||||
equal(track.selected, false, 'selected was set to false');
|
||||
});
|
||||
|
||||
test('when selected is changed selectedchange event is fired', function() {
|
||||
let track = new VideoTrack({
|
||||
selected: false
|
||||
});
|
||||
let eventsTriggered = 0;
|
||||
track.addEventListener('selectedchange', () => {
|
||||
eventsTriggered++;
|
||||
});
|
||||
|
||||
// two events
|
||||
track.selected = true;
|
||||
track.selected = false;
|
||||
equal(eventsTriggered, 2, 'two selected changes');
|
||||
|
||||
// no event here
|
||||
track.selected = false;
|
||||
track.selected = false;
|
||||
equal(eventsTriggered, 2, 'still two selected changes');
|
||||
|
||||
// one event
|
||||
track.selected = true;
|
||||
equal(eventsTriggered, 3, 'three selected changes');
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import VideoTrack from '../../../src/js/tracks/video-track.js';
|
||||
import Html5 from '../../../src/js/tech/html5.js';
|
||||
import Tech from '../../../src/js/tech/tech.js';
|
||||
import Component from '../../../src/js/component.js';
|
||||
|
||||
import * as browser from '../../../src/js/utils/browser.js';
|
||||
import TestHelpers from '../test-helpers.js';
|
||||
import document from 'global/document';
|
||||
|
||||
q.module('Video Tracks', {
|
||||
setup() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
},
|
||||
teardown() {
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('Player track methods call the tech', function() {
|
||||
let player;
|
||||
let calls = 0;
|
||||
|
||||
player = TestHelpers.makePlayer();
|
||||
|
||||
player.tech_.videoTracks = function() {
|
||||
calls++;
|
||||
};
|
||||
|
||||
player.videoTracks();
|
||||
|
||||
equal(calls, 1, 'videoTrack defers to the tech');
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
test('listen to remove and add track events in native video tracks', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
let player;
|
||||
let options;
|
||||
let oldVideoTracks = Html5.prototype.videoTracks;
|
||||
let events = {};
|
||||
let html;
|
||||
|
||||
Html5.prototype.videoTracks = function() {
|
||||
return {
|
||||
addEventListener(type, handler) {
|
||||
events[type] = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Html5.TEST_VID = {
|
||||
videoTracks: []
|
||||
};
|
||||
|
||||
player = {
|
||||
// Function.prototype is a built-in no-op function.
|
||||
controls() {},
|
||||
ready() {},
|
||||
options() {
|
||||
return {};
|
||||
},
|
||||
addChild() {},
|
||||
id() {},
|
||||
el() {
|
||||
return {
|
||||
insertBefore() {},
|
||||
appendChild() {}
|
||||
};
|
||||
}
|
||||
};
|
||||
player.player_ = player;
|
||||
player.options_ = options = {};
|
||||
|
||||
html = new Html5(options);
|
||||
|
||||
ok(events.removetrack, 'removetrack listener was added');
|
||||
ok(events.addtrack, 'addtrack listener was added');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
Html5.prototype.videoTracks = oldVideoTracks;
|
||||
});
|
||||
|
||||
test('html5 tech supports native video tracks if the video supports it', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
|
||||
Html5.TEST_VID = {
|
||||
videoTracks: []
|
||||
};
|
||||
|
||||
ok(Html5.supportsNativeVideoTracks(), 'native video tracks are supported');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
});
|
||||
|
||||
test('html5 tech does not support native video tracks if the video does not supports it', function() {
|
||||
let oldTestVid = Html5.TEST_VID;
|
||||
Html5.TEST_VID = {};
|
||||
|
||||
ok(!Html5.supportsNativeVideoTracks(), 'native video tracks are not supported');
|
||||
|
||||
Html5.TEST_VID = oldTestVid;
|
||||
});
|
||||
|
||||
test('when switching techs, we should not get a new video track', function() {
|
||||
let player = TestHelpers.makePlayer();
|
||||
|
||||
player.loadTech_('TechFaker');
|
||||
let firstTracks = player.videoTracks();
|
||||
|
||||
player.loadTech_('TechFaker');
|
||||
let secondTracks = player.videoTracks();
|
||||
|
||||
ok(firstTracks === secondTracks, 'the tracks are equal');
|
||||
});
|
||||
Referência em uma Nova Issue
Bloquear um usuário