1
0
Fork 0
mirror of https://github.com/DanielnetoDotCom/YouPHPTube synced 2025-10-03 09:49:28 +02:00
Daniel Neto 2023-06-30 08:55:17 -03:00
parent 746e163d01
commit 1c7ea28b46
808 changed files with 316395 additions and 381162 deletions

402
node_modules/.package-lock.json generated vendored
View file

@ -1,5 +1,5 @@
{ {
"name": "YouPHPTube", "name": "AVideo",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
@ -141,9 +141,9 @@
} }
}, },
"node_modules/@popperjs/core": { "node_modules/@popperjs/core": {
"version": "2.11.6", "version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"peer": true, "peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@ -162,25 +162,63 @@
} }
}, },
"node_modules/@videojs/http-streaming": { "node_modules/@videojs/http-streaming": {
"version": "2.16.0", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.0.tgz", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.0.2.tgz",
"integrity": "sha512-mGNTqjENzP86XGM6HSWdWVO/KAsDlf5+idW2W7dL1+NkzWpwZlSEYhrdEVVnhoOb0A6E7JW6LM611/JA7Jn/3A==", "integrity": "sha512-iSZkwTLGg3Rx78ypCCq/GsMME89ElNvU02xj7reCE2PlITMQjyYsER1w5AsySvT1A694u5yuSzEzLLGF1cL4pg==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "3.0.5", "@videojs/vhs-utils": "4.0.0",
"aes-decrypter": "3.1.3", "aes-decrypter": "4.0.1",
"global": "^4.4.0", "global": "^4.4.0",
"m3u8-parser": "4.8.0", "m3u8-parser": "^6.0.0",
"mpd-parser": "^0.22.1", "mpd-parser": "^1.0.1",
"mux.js": "6.0.1", "mux.js": "6.3.0",
"video.js": "^6 || ^7" "video.js": "^7 || ^8"
}, },
"engines": { "engines": {
"node": ">=8", "node": ">=8",
"npm": ">=5" "npm": ">=5"
}, },
"peerDependencies": { "peerDependencies": {
"video.js": "^6 || ^7" "video.js": "^7 || ^8"
}
},
"node_modules/@videojs/http-streaming/node_modules/@videojs/vhs-utils": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz",
"integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/@videojs/http-streaming/node_modules/m3u8-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-6.0.0.tgz",
"integrity": "sha512-s3JfDtqhxTilZQf+P1m9dZc4ohL4O/aylP1VV6g9lhKuQNfAcVUzq7d2wgJ9nZR4ibjuXaP87QzGCV6vB0kV6g==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0"
}
},
"node_modules/@videojs/http-streaming/node_modules/m3u8-parser/node_modules/@videojs/vhs-utils": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
} }
}, },
"node_modules/@videojs/vhs-utils": { "node_modules/@videojs/vhs-utils": {
@ -247,9 +285,9 @@
"integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==" "integrity": "sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg=="
}, },
"node_modules/aes-decrypter": { "node_modules/aes-decrypter": {
"version": "3.1.3", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz",
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", "integrity": "sha512-H1nh/P9VZXUf17AA5NQfJML88CFjVBDuGkp5zDHa7oEhYN9TTpNLJknRY1ie0iSKWlDf6JRnJKaZVDSQdPy6Cg==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5", "@videojs/vhs-utils": "^3.0.5",
@ -365,9 +403,9 @@
"integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
}, },
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "5.2.3", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.2.3.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz",
"integrity": "sha512-cEKPM+fwb3cT8NzQZYEu4HilJ3anCrWqh3CHAok1p9jXqMPsPTBhU25fBckEJHJ/p+tTxTFTsFQGM+gaHpi3QQ==", "integrity": "sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -379,7 +417,7 @@
} }
], ],
"peerDependencies": { "peerDependencies": {
"@popperjs/core": "^2.11.6" "@popperjs/core": "^2.11.7"
} }
}, },
"node_modules/buffer": { "node_modules/buffer": {
@ -440,14 +478,14 @@
"integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA==" "integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA=="
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz",
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", "integrity": "sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==",
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
}, },
"engines": { "engines": {
"pnpm": "^7.0.0" "pnpm": ">=7"
} }
}, },
"node_modules/chrome-dgram": { "node_modules/chrome-dgram": {
@ -569,9 +607,9 @@
} }
}, },
"node_modules/dexie": { "node_modules/dexie": {
"version": "3.2.3", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.3.tgz", "resolved": "https://registry.npmjs.org/dexie/-/dexie-3.2.4.tgz",
"integrity": "sha512-iHayBd4UYryDCVUNa3PMsJMEnd8yjyh5p7a+RFeC8i8n476BC9wMhVvqiImq5zJZJf5Tuer+s4SSj+AA3x+ZbQ==", "integrity": "sha512-VKoTQRSv7+RnffpOJ3Dh6ozknBqzWw/F3iqMdsZg958R0AS8AnY9x9d1lbwENr0gzeGJHXKcGhAMRaqys6SxqA==",
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
} }
@ -671,6 +709,18 @@
"resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-3.0.0.tgz", "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-3.0.0.tgz",
"integrity": "sha512-uJj38QFQiJ/KCio5tiZhwAjIbTXSIgzBCKdKVbaYfLS053F6z23Nb0o1ZoO9gnxOQWN7BCc35jsvrCtAq3gY9g==" "integrity": "sha512-uJj38QFQiJ/KCio5tiZhwAjIbTXSIgzBCKdKVbaYfLS053F6z23Nb0o1ZoO9gnxOQWN7BCc35jsvrCtAq3gY9g=="
}, },
"node_modules/flickity": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/flickity/-/flickity-3.0.0.tgz",
"integrity": "sha512-Xw4znhglA2LVSmka0yKmzb4+fLwXm13TuKmdG1HbUoZZL95J1ZMr4hAA3w69s+Z6q/c6CyBDM25JVwMqgF3rOg==",
"dependencies": {
"ev-emitter": "^2.1.2",
"fizzy-ui-utils": "^3.0.0",
"get-size": "^3.0.0",
"imagesloaded": "^5.0.0",
"unidragger": "^3.0.0"
}
},
"node_modules/fontawesome-free": { "node_modules/fontawesome-free": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/fontawesome-free/-/fontawesome-free-1.0.4.tgz", "resolved": "https://registry.npmjs.org/fontawesome-free/-/fontawesome-free-1.0.4.tgz",
@ -681,6 +731,11 @@
"resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz",
"integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ=="
}, },
"node_modules/get-size": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-size/-/get-size-3.0.0.tgz",
"integrity": "sha512-Y8aiXLq4leR7807UY0yuKEwif5s3kbVp1nTv+i4jBeoUzByTLKkLWu/HorS6/pB+7gsB0o7OTogC8AoOOeT0Hw=="
},
"node_modules/global": { "node_modules/global": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
@ -691,9 +746,9 @@
} }
}, },
"node_modules/hls.js": { "node_modules/hls.js": {
"version": "1.3.3", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.3.3.tgz", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.5.tgz",
"integrity": "sha512-4Efu6g90HfFGDuQrkquhDoRpA/4iVpSMcfK9zguJ2qXEUfo5VknZt6rziwwFPHmC2DU3crZ0NtPD0eUqOhFyqg==" "integrity": "sha512-xb7IiSM9apU3tJWb5rdSStobXPNJJykHTwSy7JnLF5y/kLJXWjoR/fEpNBlwYxkKcDiiSfO9SQI8yFravZJxIg=="
}, },
"node_modules/ieee754": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
@ -714,6 +769,14 @@
} }
] ]
}, },
"node_modules/imagesloaded": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-5.0.0.tgz",
"integrity": "sha512-/0JGSubc1MTCoDKVmonLHgbifBWHdyLkun+R/151E1c5n79hiSxcd7cB7mPXFgojYu8xnRZv7GYxzKoxW8BetQ==",
"dependencies": {
"ev-emitter": "^2.1.2"
}
},
"node_modules/immediate": { "node_modules/immediate": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
@ -739,9 +802,9 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/inputmask": { "node_modules/inputmask": {
"version": "5.0.7", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/inputmask/-/inputmask-5.0.7.tgz", "resolved": "https://registry.npmjs.org/inputmask/-/inputmask-5.0.8.tgz",
"integrity": "sha512-rUxbRDS25KEib+c/Ow+K01oprU/+EK9t9SOPC8ov94/ftULGDqj1zOgRU/Hko6uzoKRMdwCfuhAafJ/Wk2wffQ==" "integrity": "sha512-1WcbyudPTXP1B28ozWWyFa6QRIUG4KiLoyR6LFHlpT4OfTzRqFfWgHFadNvRuMN1S9XNVz9CdNvCGjJi+uAMqQ=="
}, },
"node_modules/ip": { "node_modules/ip": {
"version": "1.1.8", "version": "1.1.8",
@ -767,9 +830,9 @@
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
}, },
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.6.3", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
"integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
}, },
"node_modules/jquery-lazy": { "node_modules/jquery-lazy": {
"version": "1.7.11", "version": "1.7.11",
@ -1032,20 +1095,20 @@
} }
}, },
"node_modules/moment-timezone": { "node_modules/moment-timezone": {
"version": "0.5.40", "version": "0.5.41",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz",
"integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "integrity": "sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg==",
"dependencies": { "dependencies": {
"moment": ">= 2.9.0" "moment": "^2.29.4"
}, },
"engines": { "engines": {
"node": "*" "node": "*"
} }
}, },
"node_modules/mpd-parser": { "node_modules/mpd-parser": {
"version": "0.22.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz", "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.1.1.tgz",
"integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==", "integrity": "sha512-uZ/db5wQdlQn1L+OD49YXBhPI9UGeK1SeQE4D5EoaJIhf0WM9X3HDj8d+9PjoG06CgCvGZw3YW/wsHku+CH3yA==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5", "@videojs/vhs-utils": "^3.0.5",
@ -1062,9 +1125,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node_modules/mux.js": { "node_modules/mux.js": {
"version": "6.0.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz", "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.3.0.tgz",
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==", "integrity": "sha512-/QTkbSAP2+w1nxV+qTcumSDN5PA98P0tjrADijIzQHe85oBK3Akhy9AHlH0ne/GombLMz1rLyvVsmrgRxoPDrQ==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.2", "@babel/runtime": "^7.11.2",
"global": "^4.4.0" "global": "^4.4.0"
@ -1613,9 +1676,9 @@
} }
}, },
"node_modules/tinymce": { "node_modules/tinymce": {
"version": "6.3.1", "version": "6.4.2",
"resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.3.1.tgz", "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.4.2.tgz",
"integrity": "sha512-+oCwXuTxAdJXVJ0130OxQz0JDNsqg3deuzgeUo8X5Vb27EzCJgXwO5eWvCxvkxpQo4oiHMVlM4tUIpTUHufHGQ==" "integrity": "sha512-te+4c8PoAwTKPMBQtMQehnSZlOO9Ay5tDgaRFJKBehYb6SlX2PYZ0v3oe/cmiv5EfqdwZLkEMXk2MNOeApZZLg=="
}, },
"node_modules/tinymce-langs": { "node_modules/tinymce-langs": {
"version": "1.0.0", "version": "1.0.0",
@ -1641,6 +1704,14 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
}, },
"node_modules/unidragger": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/unidragger/-/unidragger-3.0.1.tgz",
"integrity": "sha512-RngbGSwBFmqGBWjkaH+yB677uzR95blSQyxq6hYbrQCejH3Mx1nm8DVOuh3M9k2fQyTstWUG5qlgCnNqV/9jVw==",
"dependencies": {
"ev-emitter": "^2.0.0"
}
},
"node_modules/universalify": { "node_modules/universalify": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
@ -1706,47 +1777,103 @@
} }
}, },
"node_modules/video.js": { "node_modules/video.js": {
"version": "7.21.2", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.2.tgz", "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.3.0.tgz",
"integrity": "sha512-Zbo23oT4CbtIxeAtfTvzdl7OlN/P34ir7hDzXFtLZB+BtJsaLy0Rgh/06dBMJSGEjQCDo4MUS6uPonuX0Nl3Kg==", "integrity": "sha512-Vp3mqMLSUE354t+G8CbZKwcV520VKoS5fow8zjnEEKFuqStmkmnvK7/FurP6zuP/oWGJ1rqlKxML56kmJOrwRw==",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "2.16.0", "@videojs/http-streaming": "3.0.2",
"@videojs/vhs-utils": "^3.0.4", "@videojs/vhs-utils": "^4.0.0",
"@videojs/xhr": "2.6.0", "@videojs/xhr": "2.6.0",
"aes-decrypter": "3.1.3", "aes-decrypter": "^4.0.1",
"global": "^4.4.0", "global": "4.4.0",
"keycode": "^2.2.0", "keycode": "2.2.0",
"m3u8-parser": "4.8.0", "m3u8-parser": "^6.0.0",
"mpd-parser": "0.22.1", "mpd-parser": "^1.0.1",
"mux.js": "6.0.1", "mux.js": "^6.2.0",
"safe-json-parse": "4.0.0", "safe-json-parse": "4.0.0",
"videojs-font": "3.2.0", "videojs-contrib-quality-levels": "3.0.0",
"videojs-vtt.js": "^0.15.4" "videojs-font": "4.1.0",
"videojs-vtt.js": "0.15.4"
} }
}, },
"node_modules/video.js/node_modules/videojs-font": { "node_modules/video.js/node_modules/@videojs/vhs-utils": {
"version": "3.2.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz",
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==" "integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==",
},
"node_modules/videojs-contrib-ads": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-ads/-/videojs-contrib-ads-6.9.0.tgz",
"integrity": "sha512-nzKz+jhCGMTYffSNVYrmp9p70s05v6jUMOY3Z7DpVk3iFrWK4Zi/BIkokDWrMoHpKjdmCdKzfJVBT+CrUj6Spw==",
"dependencies": { "dependencies": {
"global": "^4.3.2", "@babel/runtime": "^7.12.5",
"video.js": "^6 || ^7" "global": "^4.4.0",
"url-toolkit": "^2.2.1"
}, },
"engines": { "engines": {
"node": ">=8", "node": ">=8",
"npm": ">=5" "npm": ">=5"
} }
}, },
"node_modules/video.js/node_modules/keycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
"integrity": "sha512-ps3I9jAdNtRpJrbBvQjpzyFbss/skHqzS+eu4RxKLaEAtFqkjZaB6TZMSivPbLxf4K7VI4SjR0P5mRCX5+Q25A=="
},
"node_modules/video.js/node_modules/m3u8-parser": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-6.0.0.tgz",
"integrity": "sha512-s3JfDtqhxTilZQf+P1m9dZc4ohL4O/aylP1VV6g9lhKuQNfAcVUzq7d2wgJ9nZR4ibjuXaP87QzGCV6vB0kV6g==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0"
}
},
"node_modules/video.js/node_modules/m3u8-parser/node_modules/@videojs/vhs-utils": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
"integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"global": "^4.4.0",
"url-toolkit": "^2.2.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/videojs-contrib-ads": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-ads/-/videojs-contrib-ads-7.2.0.tgz",
"integrity": "sha512-5DAO8BpD4mm/xKCGm4JhmIkjYOC+TByiO0XXInoc981Gr71xcFpO2wxPHMFrojhKROmYGWnYKcLsKNMkvYOWHg==",
"dependencies": {
"global": "^4.3.2"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"video.js": "^8"
}
},
"node_modules/videojs-contrib-quality-levels": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-3.0.0.tgz",
"integrity": "sha512-sNx38EYUx+Q+gmup1gVTv9P9/sPs28rM7gZOx1sedaHoKxEdYB+ysOGfHj6MSELBMNGMj6ZspdrpSiWguGvGxA==",
"dependencies": {
"global": "^4.4.0"
},
"engines": {
"node": ">=14",
"npm": ">=6"
},
"peerDependencies": {
"video.js": "^6 || ^7 || ^8"
}
},
"node_modules/videojs-font": { "node_modules/videojs-font": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.0.0.tgz", "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.1.0.tgz",
"integrity": "sha512-sRXrizXF0zBMatXjg2vGpn63G26uH3XqwyZ9PjU2H9xqGm7fRSVYuxOJCUME6us/1rFl9yxkRKk31WTQ7XZkww==" "integrity": "sha512-X1LuPfLZPisPLrANIAKCknZbZu5obVM/ylfd1CN+SsCmPZQ3UMDPcvLTpPBJxcBuTpHQq2MO1QCFt7p8spnZ/w=="
}, },
"node_modules/videojs-ima": { "node_modules/videojs-ima": {
"version": "2.1.0", "version": "2.1.0",
@ -1765,6 +1892,127 @@
"video.js": "^5.19.2 || ^6 || ^7" "video.js": "^5.19.2 || ^6 || ^7"
} }
}, },
"node_modules/videojs-ima/node_modules/@videojs/http-streaming": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.2.tgz",
"integrity": "sha512-etPTUdCFu7gUWc+1XcbiPr+lrhOcBu3rV5OL1M+3PDW89zskScAkkcdqYzP4pFodBPye/ydamQoTDScOnElw5A==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "3.0.5",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"m3u8-parser": "4.8.0",
"mpd-parser": "^0.22.1",
"mux.js": "6.0.1",
"video.js": "^6 || ^7"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"video.js": "^6 || ^7"
}
},
"node_modules/videojs-ima/node_modules/@videojs/http-streaming/node_modules/video.js": {
"version": "7.21.3",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.3.tgz",
"integrity": "sha512-fIboXbSDCT3P8eVzIEC3hnLDKC/y+6QftcHdFGUVGn5a7qmH62Mh0Bt/SrBAgdmKDQM1qdZXfXAxPg5+IaiIXQ==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "2.16.2",
"@videojs/vhs-utils": "^3.0.4",
"@videojs/xhr": "2.6.0",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"keycode": "^2.2.0",
"m3u8-parser": "4.8.0",
"mpd-parser": "0.22.1",
"mux.js": "6.0.1",
"safe-json-parse": "4.0.0",
"videojs-font": "3.2.0",
"videojs-vtt.js": "^0.15.4"
}
},
"node_modules/videojs-ima/node_modules/aes-decrypter": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz",
"integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"global": "^4.4.0",
"pkcs7": "^1.0.4"
}
},
"node_modules/videojs-ima/node_modules/mpd-parser": {
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.22.1.tgz",
"integrity": "sha512-fwBebvpyPUU8bOzvhX0VQZgSohncbgYwUyJJoTSNpmy7ccD2ryiCvM7oRkn/xQH5cv73/xU7rJSNCLjdGFor0Q==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "^3.0.5",
"@xmldom/xmldom": "^0.8.3",
"global": "^4.4.0"
},
"bin": {
"mpd-to-m3u8-json": "bin/parse.js"
}
},
"node_modules/videojs-ima/node_modules/mux.js": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
"integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
"dependencies": {
"@babel/runtime": "^7.11.2",
"global": "^4.4.0"
},
"bin": {
"muxjs-transmux": "bin/transmux.js"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/videojs-ima/node_modules/videojs-contrib-ads": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-ads/-/videojs-contrib-ads-6.9.0.tgz",
"integrity": "sha512-nzKz+jhCGMTYffSNVYrmp9p70s05v6jUMOY3Z7DpVk3iFrWK4Zi/BIkokDWrMoHpKjdmCdKzfJVBT+CrUj6Spw==",
"dependencies": {
"global": "^4.3.2",
"video.js": "^6 || ^7"
},
"engines": {
"node": ">=8",
"npm": ">=5"
}
},
"node_modules/videojs-ima/node_modules/videojs-contrib-ads/node_modules/video.js": {
"version": "7.21.3",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.3.tgz",
"integrity": "sha512-fIboXbSDCT3P8eVzIEC3hnLDKC/y+6QftcHdFGUVGn5a7qmH62Mh0Bt/SrBAgdmKDQM1qdZXfXAxPg5+IaiIXQ==",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@videojs/http-streaming": "2.16.2",
"@videojs/vhs-utils": "^3.0.4",
"@videojs/xhr": "2.6.0",
"aes-decrypter": "3.1.3",
"global": "^4.4.0",
"keycode": "^2.2.0",
"m3u8-parser": "4.8.0",
"mpd-parser": "0.22.1",
"mux.js": "6.0.1",
"safe-json-parse": "4.0.0",
"videojs-font": "3.2.0",
"videojs-vtt.js": "^0.15.4"
}
},
"node_modules/videojs-ima/node_modules/videojs-font": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
"integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
},
"node_modules/videojs-landscape-fullscreen": { "node_modules/videojs-landscape-fullscreen": {
"version": "11.1111.0", "version": "11.1111.0",
"resolved": "https://registry.npmjs.org/videojs-landscape-fullscreen/-/videojs-landscape-fullscreen-11.1111.0.tgz", "resolved": "https://registry.npmjs.org/videojs-landscape-fullscreen/-/videojs-landscape-fullscreen-11.1111.0.tgz",

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
'use strict'; 'use strict';

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
'use strict'; 'use strict';
@ -46,7 +46,7 @@ var round = Math.round;
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -355,7 +355,6 @@ var top = 'top';
var bottom = 'bottom'; var bottom = 'bottom';
var right = 'right'; var right = 'right';
var left = 'left'; var left = 'left';
var auto = 'auto';
var basePlacements = [top, bottom, right, left]; var basePlacements = [top, bottom, right, left];
var start = 'start'; var start = 'start';
var end = 'end'; var end = 'end';
@ -436,112 +435,6 @@ function debounce(fn) {
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -698,6 +591,10 @@ function getClippingRect(element, boundary, rootBoundary, strategy) {
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -847,8 +744,6 @@ function detectOverflow(state, options) {
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -910,42 +805,7 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (process.env.NODE_ENV !== "production") {
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -965,10 +825,6 @@ function popperGenerator(generatorOptions) {
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -991,18 +847,8 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
if (process.env.NODE_ENV !== "production") {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1040,10 +886,6 @@ function popperGenerator(generatorOptions) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1058,11 +900,11 @@ function popperGenerator(generatorOptions) {
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
'use strict'; 'use strict';
@ -46,7 +46,7 @@ var round = Math.round;
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -355,7 +355,6 @@ var top = 'top';
var bottom = 'bottom'; var bottom = 'bottom';
var right = 'right'; var right = 'right';
var left = 'left'; var left = 'left';
var auto = 'auto';
var basePlacements = [top, bottom, right, left]; var basePlacements = [top, bottom, right, left];
var start = 'start'; var start = 'start';
var end = 'end'; var end = 'end';
@ -436,112 +435,6 @@ function debounce(fn) {
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -698,6 +591,10 @@ function getClippingRect(element, boundary, rootBoundary, strategy) {
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -847,8 +744,6 @@ function detectOverflow(state, options) {
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -910,42 +805,7 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (process.env.NODE_ENV !== "production") {
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -965,10 +825,6 @@ function popperGenerator(generatorOptions) {
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -991,18 +847,8 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
if (process.env.NODE_ENV !== "production") {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1040,10 +886,6 @@ function popperGenerator(generatorOptions) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1058,11 +900,11 @@ function popperGenerator(generatorOptions) {
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({
@ -1171,10 +1013,9 @@ var unsetSides = {
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -1257,7 +1098,7 @@ function mapToStyles(_ref2) {
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -1283,17 +1124,6 @@ function computeStyles(_ref5) {
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
if (process.env.NODE_ENV !== "production") {
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
'use strict'; 'use strict';
@ -46,7 +46,7 @@ var round = Math.round;
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -442,112 +442,6 @@ function debounce(fn) {
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -704,6 +598,10 @@ function getClippingRect(element, boundary, rootBoundary, strategy) {
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -853,8 +751,6 @@ function detectOverflow(state, options) {
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -916,42 +812,7 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (process.env.NODE_ENV !== "production") {
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -971,10 +832,6 @@ function popperGenerator(generatorOptions) {
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -997,18 +854,8 @@ function popperGenerator(generatorOptions) {
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
if (process.env.NODE_ENV !== "production") {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1046,10 +893,6 @@ function popperGenerator(generatorOptions) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1064,11 +907,11 @@ function popperGenerator(generatorOptions) {
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({
@ -1177,10 +1020,9 @@ var unsetSides = {
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -1263,7 +1105,7 @@ function mapToStyles(_ref2) {
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -1289,17 +1131,6 @@ function computeStyles(_ref5) {
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
if (process.env.NODE_ENV !== "production") {
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),
@ -1521,10 +1352,6 @@ function computeAutoPlacement(state, options) {
if (allowedPlacements.length === 0) { if (allowedPlacements.length === 0) {
allowedPlacements = placements$1; allowedPlacements = placements$1;
if (process.env.NODE_ENV !== "production") {
console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' '));
}
} // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...
@ -1888,17 +1715,7 @@ function effect(_ref2) {
} }
} }
if (process.env.NODE_ENV !== "production") {
if (!isHTMLElement(arrowElement)) {
console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));
}
}
if (!contains(state.elements.popper, arrowElement)) { if (!contains(state.elements.popper, arrowElement)) {
if (process.env.NODE_ENV !== "production") {
console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' '));
}
return; return;
} }

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,11 @@ import getCompositeRect from "./dom-utils/getCompositeRect.js";
import getLayoutRect from "./dom-utils/getLayoutRect.js"; import getLayoutRect from "./dom-utils/getLayoutRect.js";
import listScrollParents from "./dom-utils/listScrollParents.js"; import listScrollParents from "./dom-utils/listScrollParents.js";
import getOffsetParent from "./dom-utils/getOffsetParent.js"; import getOffsetParent from "./dom-utils/getOffsetParent.js";
import getComputedStyle from "./dom-utils/getComputedStyle.js";
import orderModifiers from "./utils/orderModifiers.js"; import orderModifiers from "./utils/orderModifiers.js";
import debounce from "./utils/debounce.js"; import debounce from "./utils/debounce.js";
import validateModifiers from "./utils/validateModifiers.js";
import uniqueBy from "./utils/uniqueBy.js";
import getBasePlacement from "./utils/getBasePlacement.js";
import mergeByName from "./utils/mergeByName.js"; import mergeByName from "./utils/mergeByName.js";
import detectOverflow from "./utils/detectOverflow.js"; import detectOverflow from "./utils/detectOverflow.js";
import { isElement } from "./dom-utils/instanceOf.js"; import { isElement } from "./dom-utils/instanceOf.js";
import { auto } from "./enums.js";
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -75,42 +68,7 @@ export function popperGenerator(generatorOptions) {
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (false) {
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -130,10 +88,6 @@ export function popperGenerator(generatorOptions) {
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (false) {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -156,18 +110,8 @@ export function popperGenerator(generatorOptions) {
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
if (false) {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -205,10 +149,6 @@ export function popperGenerator(generatorOptions) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (false) {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -223,11 +163,11 @@ export function popperGenerator(generatorOptions) {
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({

View file

@ -6,8 +6,7 @@ import getMainAxisFromPlacement from "../utils/getMainAxisFromPlacement.js";
import { within } from "../utils/within.js"; import { within } from "../utils/within.js";
import mergePaddingObject from "../utils/mergePaddingObject.js"; import mergePaddingObject from "../utils/mergePaddingObject.js";
import expandToHashMap from "../utils/expandToHashMap.js"; import expandToHashMap from "../utils/expandToHashMap.js";
import { left, right, basePlacements, top, bottom } from "../enums.js"; import { left, right, basePlacements, top, bottom } from "../enums.js"; // eslint-disable-next-line import/no-unused-modules
import { isHTMLElement } from "../dom-utils/instanceOf.js"; // eslint-disable-next-line import/no-unused-modules
var toPaddingObject = function toPaddingObject(padding, state) { var toPaddingObject = function toPaddingObject(padding, state) {
padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {
@ -72,17 +71,7 @@ function effect(_ref2) {
} }
} }
if (false) {
if (!isHTMLElement(arrowElement)) {
console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));
}
}
if (!contains(state.elements.popper, arrowElement)) { if (!contains(state.elements.popper, arrowElement)) {
if (false) {
console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' '));
}
return; return;
} }

View file

@ -16,10 +16,9 @@ var unsetSides = {
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -102,7 +101,7 @@ export function mapToStyles(_ref2) {
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -128,17 +127,6 @@ function computeStyles(_ref5) {
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
if (false) {
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),

View file

@ -25,10 +25,6 @@ export default function computeAutoPlacement(state, options) {
if (allowedPlacements.length === 0) { if (allowedPlacements.length === 0) {
allowedPlacements = placements; allowedPlacements = placements;
if (false) {
console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' '));
}
} // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...

View file

@ -1,9 +0,0 @@
export default function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}

View file

@ -1,7 +1,7 @@
export default function getUAString() { export default function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');

View file

@ -1,81 +0,0 @@
import format from "./format.js";
import { modifierPhases } from "../enums.js";
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
export default function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
(function (global, factory) { (function (global, factory) {

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";var t="bottom",r="right",o="left",n="auto",a=["top",t,r,o],i="start",f="end",p=a.reduce((function(e,t){return e.concat([t+"-"+i,t+"-"+f])}),[]),c=[].concat(a,[n]).reduce((function(e,t){return e.concat([t,t+"-"+i,t+"-"+f])}),[]),d="beforeRead",s="read",u="afterRead",l="beforeMain",b="main",m="afterMain",P="beforeWrite",g="write",h="afterWrite",v=[d,s,u,l,b,m,P,g,h];e.afterMain=m,e.afterRead=u,e.afterWrite=h,e.auto=n,e.basePlacements=a,e.beforeMain=l,e.beforeRead=d,e.beforeWrite=P,e.bottom=t,e.clippingParents="clippingParents",e.end=f,e.left=o,e.main=b,e.modifierPhases=v,e.placements=c,e.popper="popper",e.read=s,e.reference="reference",e.right=r,e.start=i,e.top="top",e.variationPlacements=p,e.viewport="viewport",e.write=g,Object.defineProperty(e,"__esModule",{value:!0})})); !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";var t="bottom",r="right",o="left",n="auto",a=["top",t,r,o],i="start",f="end",p=a.reduce((function(e,t){return e.concat([t+"-"+i,t+"-"+f])}),[]),c=[].concat(a,[n]).reduce((function(e,t){return e.concat([t,t+"-"+i,t+"-"+f])}),[]),d="beforeRead",s="read",u="afterRead",l="beforeMain",b="main",m="afterMain",P="beforeWrite",g="write",h="afterWrite",v=[d,s,u,l,b,m,P,g,h];e.afterMain=m,e.afterRead=u,e.afterWrite=h,e.auto=n,e.basePlacements=a,e.beforeMain=l,e.beforeRead=d,e.beforeWrite=P,e.bottom=t,e.clippingParents="clippingParents",e.end=f,e.left=o,e.main=b,e.modifierPhases=v,e.placements=c,e.popper="popper",e.read=s,e.reference="reference",e.right=r,e.start=i,e.top="top",e.variationPlacements=p,e.viewport="viewport",e.write=g,Object.defineProperty(e,"__esModule",{value:!0})}));

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
(function (global, factory) { (function (global, factory) {
@ -48,7 +48,7 @@
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -357,7 +357,6 @@
var bottom = 'bottom'; var bottom = 'bottom';
var right = 'right'; var right = 'right';
var left = 'left'; var left = 'left';
var auto = 'auto';
var basePlacements = [top, bottom, right, left]; var basePlacements = [top, bottom, right, left];
var start = 'start'; var start = 'start';
var end = 'end'; var end = 'end';
@ -438,112 +437,6 @@
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -700,6 +593,10 @@
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -849,8 +746,6 @@
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -912,42 +807,7 @@
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
{
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -967,10 +827,6 @@
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -993,18 +849,8 @@
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
{
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1042,10 +888,6 @@
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1060,11 +902,11 @@
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
(function (global, factory) { (function (global, factory) {
@ -48,7 +48,7 @@
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -357,7 +357,6 @@
var bottom = 'bottom'; var bottom = 'bottom';
var right = 'right'; var right = 'right';
var left = 'left'; var left = 'left';
var auto = 'auto';
var basePlacements = [top, bottom, right, left]; var basePlacements = [top, bottom, right, left];
var start = 'start'; var start = 'start';
var end = 'end'; var end = 'end';
@ -438,112 +437,6 @@
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -700,6 +593,10 @@
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -849,8 +746,6 @@
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -912,42 +807,7 @@
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
{
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -967,10 +827,6 @@
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -993,18 +849,8 @@
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
{
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1042,10 +888,6 @@
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1060,11 +902,11 @@
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({
@ -1173,10 +1015,9 @@
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -1259,7 +1100,7 @@
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -1285,17 +1126,6 @@
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
{
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/** /**
* @popperjs/core v2.11.6 - MIT License * @popperjs/core v2.11.8 - MIT License
*/ */
(function (global, factory) { (function (global, factory) {
@ -48,7 +48,7 @@
function getUAString() { function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');
@ -444,112 +444,6 @@
}; };
} }
function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}
function uniqueBy(arr, fn) {
var identifiers = new Set();
return arr.filter(function (item) {
var identifier = fn(item);
if (!identifiers.has(identifier)) {
identifiers.add(identifier);
return true;
}
});
}
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function mergeByName(modifiers) { function mergeByName(modifiers) {
var merged = modifiers.reduce(function (merged, current) { var merged = modifiers.reduce(function (merged, current) {
var existing = merged[current.name]; var existing = merged[current.name];
@ -706,6 +600,10 @@
return clippingRect; return clippingRect;
} }
function getBasePlacement(placement) {
return placement.split('-')[0];
}
function getVariation(placement) { function getVariation(placement) {
return placement.split('-')[1]; return placement.split('-')[1];
} }
@ -855,8 +753,6 @@
return overflowOffsets; return overflowOffsets;
} }
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -918,42 +814,7 @@
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
{
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -973,10 +834,6 @@
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -999,18 +856,8 @@
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
{
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -1048,10 +895,6 @@
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
{
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -1066,11 +909,11 @@
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({
@ -1179,10 +1022,9 @@
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -1265,7 +1107,7 @@
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -1291,17 +1133,6 @@
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
{
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),
@ -1523,10 +1354,6 @@
if (allowedPlacements.length === 0) { if (allowedPlacements.length === 0) {
allowedPlacements = placements$1; allowedPlacements = placements$1;
{
console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' '));
}
} // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...
@ -1890,17 +1717,7 @@
} }
} }
{
if (!isHTMLElement(arrowElement)) {
console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));
}
}
if (!contains(state.elements.popper, arrowElement)) { if (!contains(state.elements.popper, arrowElement)) {
{
console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' '));
}
return; return;
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,18 +2,11 @@ import getCompositeRect from "./dom-utils/getCompositeRect.js";
import getLayoutRect from "./dom-utils/getLayoutRect.js"; import getLayoutRect from "./dom-utils/getLayoutRect.js";
import listScrollParents from "./dom-utils/listScrollParents.js"; import listScrollParents from "./dom-utils/listScrollParents.js";
import getOffsetParent from "./dom-utils/getOffsetParent.js"; import getOffsetParent from "./dom-utils/getOffsetParent.js";
import getComputedStyle from "./dom-utils/getComputedStyle.js";
import orderModifiers from "./utils/orderModifiers.js"; import orderModifiers from "./utils/orderModifiers.js";
import debounce from "./utils/debounce.js"; import debounce from "./utils/debounce.js";
import validateModifiers from "./utils/validateModifiers.js";
import uniqueBy from "./utils/uniqueBy.js";
import getBasePlacement from "./utils/getBasePlacement.js";
import mergeByName from "./utils/mergeByName.js"; import mergeByName from "./utils/mergeByName.js";
import detectOverflow from "./utils/detectOverflow.js"; import detectOverflow from "./utils/detectOverflow.js";
import { isElement } from "./dom-utils/instanceOf.js"; import { isElement } from "./dom-utils/instanceOf.js";
import { auto } from "./enums.js";
var INVALID_ELEMENT_ERROR = 'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
var INFINITE_LOOP_ERROR = 'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
var DEFAULT_OPTIONS = { var DEFAULT_OPTIONS = {
placement: 'bottom', placement: 'bottom',
modifiers: [], modifiers: [],
@ -75,42 +68,7 @@ export function popperGenerator(generatorOptions) {
state.orderedModifiers = orderedModifiers.filter(function (m) { state.orderedModifiers = orderedModifiers.filter(function (m) {
return m.enabled; return m.enabled;
}); // Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (process.env.NODE_ENV !== "production") {
var modifiers = uniqueBy([].concat(orderedModifiers, state.options.modifiers), function (_ref) {
var name = _ref.name;
return name;
}); });
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
var flipModifier = state.orderedModifiers.find(function (_ref2) {
var name = _ref2.name;
return name === 'flip';
});
if (!flipModifier) {
console.error(['Popper: "auto" placements require the "flip" modifier be', 'present and enabled to work.'].join(' '));
}
}
var _getComputedStyle = getComputedStyle(popper),
marginTop = _getComputedStyle.marginTop,
marginRight = _getComputedStyle.marginRight,
marginBottom = _getComputedStyle.marginBottom,
marginLeft = _getComputedStyle.marginLeft; // We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if ([marginTop, marginRight, marginBottom, marginLeft].some(function (margin) {
return parseFloat(margin);
})) {
console.warn(['Popper: CSS "margin" styles cannot be used to apply padding', 'between the popper and its reference element or boundary.', 'To replicate margin, use the `offset` modifier, as well as', 'the `padding` option in the `preventOverflow` and `flip`', 'modifiers.'].join(' '));
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
}, },
@ -130,10 +88,6 @@ export function popperGenerator(generatorOptions) {
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} // Store the reference and popper rects to be read by modifiers } // Store the reference and popper rects to be read by modifiers
@ -156,18 +110,8 @@ export function popperGenerator(generatorOptions) {
state.orderedModifiers.forEach(function (modifier) { state.orderedModifiers.forEach(function (modifier) {
return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); return state.modifiersData[modifier.name] = Object.assign({}, modifier.data);
}); });
var __debug_loops__ = 0;
for (var index = 0; index < state.orderedModifiers.length; index++) { for (var index = 0; index < state.orderedModifiers.length; index++) {
if (process.env.NODE_ENV !== "production") {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -205,10 +149,6 @@ export function popperGenerator(generatorOptions) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (process.env.NODE_ENV !== "production") {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }
@ -223,11 +163,11 @@ export function popperGenerator(generatorOptions) {
// one. // one.
function runModifierEffects() { function runModifierEffects() {
state.orderedModifiers.forEach(function (_ref3) { state.orderedModifiers.forEach(function (_ref) {
var name = _ref3.name, var name = _ref.name,
_ref3$options = _ref3.options, _ref$options = _ref.options,
options = _ref3$options === void 0 ? {} : _ref3$options, options = _ref$options === void 0 ? {} : _ref$options,
effect = _ref3.effect; effect = _ref.effect;
if (typeof effect === 'function') { if (typeof effect === 'function') {
var cleanupFn = effect({ var cleanupFn = effect({

View file

@ -10,21 +10,11 @@ import getCompositeRect from './dom-utils/getCompositeRect';
import getLayoutRect from './dom-utils/getLayoutRect'; import getLayoutRect from './dom-utils/getLayoutRect';
import listScrollParents from './dom-utils/listScrollParents'; import listScrollParents from './dom-utils/listScrollParents';
import getOffsetParent from './dom-utils/getOffsetParent'; import getOffsetParent from './dom-utils/getOffsetParent';
import getComputedStyle from './dom-utils/getComputedStyle';
import orderModifiers from './utils/orderModifiers'; import orderModifiers from './utils/orderModifiers';
import debounce from './utils/debounce'; import debounce from './utils/debounce';
import validateModifiers from './utils/validateModifiers';
import uniqueBy from './utils/uniqueBy';
import getBasePlacement from './utils/getBasePlacement';
import mergeByName from './utils/mergeByName'; import mergeByName from './utils/mergeByName';
import detectOverflow from './utils/detectOverflow'; import detectOverflow from './utils/detectOverflow';
import { isElement } from './dom-utils/instanceOf'; import { isElement } from './dom-utils/instanceOf';
import { auto } from './enums';
const INVALID_ELEMENT_ERROR =
'Popper: Invalid reference or popper argument provided. They must be either a DOM element or virtual element.';
const INFINITE_LOOP_ERROR =
'Popper: An infinite loop in the modifiers cycle has been detected! The cycle has been interrupted to prevent a browser crash.';
const DEFAULT_OPTIONS: OptionsGeneric<any> = { const DEFAULT_OPTIONS: OptionsGeneric<any> = {
placement: 'bottom', placement: 'bottom',
@ -45,10 +35,8 @@ function areValidElements(...args: Array<any>): boolean {
} }
export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) { export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
const { const { defaultModifiers = [], defaultOptions = DEFAULT_OPTIONS } =
defaultModifiers = [], generatorOptions;
defaultOptions = DEFAULT_OPTIONS,
} = generatorOptions;
return function createPopper<TModifier: $Shape<Modifier<any, any>>>( return function createPopper<TModifier: $Shape<Modifier<any, any>>>(
reference: Element | VirtualElement, reference: Element | VirtualElement,
@ -106,57 +94,6 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
// Strip out disabled modifiers // Strip out disabled modifiers
state.orderedModifiers = orderedModifiers.filter((m) => m.enabled); state.orderedModifiers = orderedModifiers.filter((m) => m.enabled);
// Validate the provided modifiers so that the consumer will get warned
// if one of the modifiers is invalid for any reason
if (false) {
const modifiers = uniqueBy(
[...orderedModifiers, ...state.options.modifiers],
({ name }) => name
);
validateModifiers(modifiers);
if (getBasePlacement(state.options.placement) === auto) {
const flipModifier = state.orderedModifiers.find(
({ name }) => name === 'flip'
);
if (!flipModifier) {
console.error(
[
'Popper: "auto" placements require the "flip" modifier be',
'present and enabled to work.',
].join(' ')
);
}
}
const {
marginTop,
marginRight,
marginBottom,
marginLeft,
} = getComputedStyle(popper);
// We no longer take into account `margins` on the popper, and it can
// cause bugs with positioning, so we'll warn the consumer
if (
[marginTop, marginRight, marginBottom, marginLeft].some((margin) =>
parseFloat(margin)
)
) {
console.warn(
[
'Popper: CSS "margin" styles cannot be used to apply padding',
'between the popper and its reference element or boundary.',
'To replicate margin, use the `offset` modifier, as well as',
'the `padding` option in the `preventOverflow` and `flip`',
'modifiers.',
].join(' ')
);
}
}
runModifierEffects(); runModifierEffects();
return instance.update(); return instance.update();
@ -177,9 +114,6 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
// Don't proceed if `reference` or `popper` are not valid elements // Don't proceed if `reference` or `popper` are not valid elements
// anymore // anymore
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (false) {
console.error(INVALID_ELEMENT_ERROR);
}
return; return;
} }
@ -213,16 +147,7 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
}) })
); );
let __debug_loops__ = 0;
for (let index = 0; index < state.orderedModifiers.length; index++) { for (let index = 0; index < state.orderedModifiers.length; index++) {
if (false) {
__debug_loops__ += 1;
if (__debug_loops__ > 100) {
console.error(INFINITE_LOOP_ERROR);
break;
}
}
if (state.reset === true) { if (state.reset === true) {
state.reset = false; state.reset = false;
index = -1; index = -1;
@ -254,9 +179,6 @@ export function popperGenerator(generatorOptions: PopperGeneratorArgs = {}) {
}; };
if (!areValidElements(reference, popper)) { if (!areValidElements(reference, popper)) {
if (false) {
console.error(INVALID_ELEMENT_ERROR);
}
return instance; return instance;
} }

View file

@ -6,8 +6,7 @@ import getMainAxisFromPlacement from "../utils/getMainAxisFromPlacement.js";
import { within } from "../utils/within.js"; import { within } from "../utils/within.js";
import mergePaddingObject from "../utils/mergePaddingObject.js"; import mergePaddingObject from "../utils/mergePaddingObject.js";
import expandToHashMap from "../utils/expandToHashMap.js"; import expandToHashMap from "../utils/expandToHashMap.js";
import { left, right, basePlacements, top, bottom } from "../enums.js"; import { left, right, basePlacements, top, bottom } from "../enums.js"; // eslint-disable-next-line import/no-unused-modules
import { isHTMLElement } from "../dom-utils/instanceOf.js"; // eslint-disable-next-line import/no-unused-modules
var toPaddingObject = function toPaddingObject(padding, state) { var toPaddingObject = function toPaddingObject(padding, state) {
padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, {
@ -72,17 +71,7 @@ function effect(_ref2) {
} }
} }
if (process.env.NODE_ENV !== "production") {
if (!isHTMLElement(arrowElement)) {
console.error(['Popper: "arrow" element must be an HTMLElement (not an SVGElement).', 'To use an SVG arrow, wrap it in an HTMLElement that will be used as', 'the arrow.'].join(' '));
}
}
if (!contains(state.elements.popper, arrowElement)) { if (!contains(state.elements.popper, arrowElement)) {
if (process.env.NODE_ENV !== "production") {
console.error(['Popper: "arrow" modifier\'s `element` must be a child of the popper', 'element.'].join(' '));
}
return; return;
} }

View file

@ -10,7 +10,6 @@ import { within } from '../utils/within';
import mergePaddingObject from '../utils/mergePaddingObject'; import mergePaddingObject from '../utils/mergePaddingObject';
import expandToHashMap from '../utils/expandToHashMap'; import expandToHashMap from '../utils/expandToHashMap';
import { left, right, basePlacements, top, bottom } from '../enums'; import { left, right, basePlacements, top, bottom } from '../enums';
import { isHTMLElement } from '../dom-utils/instanceOf';
// eslint-disable-next-line import/no-unused-modules // eslint-disable-next-line import/no-unused-modules
export type Options = { export type Options = {
@ -101,28 +100,7 @@ function effect({ state, options }: ModifierArguments<Options>) {
} }
} }
if (false) {
if (!isHTMLElement(arrowElement)) {
console.error(
[
'Popper: "arrow" element must be an HTMLElement (not an SVGElement).',
'To use an SVG arrow, wrap it in an HTMLElement that will be used as',
'the arrow.',
].join(' ')
);
}
}
if (!contains(state.elements.popper, arrowElement)) { if (!contains(state.elements.popper, arrowElement)) {
if (false) {
console.error(
[
'Popper: "arrow" modifier\'s `element` must be a child of the popper',
'element.',
].join(' ')
);
}
return; return;
} }

View file

@ -16,10 +16,9 @@ var unsetSides = {
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR(_ref) { function roundOffsetsByDPR(_ref, win) {
var x = _ref.x, var x = _ref.x,
y = _ref.y; y = _ref.y;
var win = window;
var dpr = win.devicePixelRatio || 1; var dpr = win.devicePixelRatio || 1;
return { return {
x: round(x * dpr) / dpr || 0, x: round(x * dpr) / dpr || 0,
@ -102,7 +101,7 @@ export function mapToStyles(_ref2) {
var _ref4 = roundOffsets === true ? roundOffsetsByDPR({ var _ref4 = roundOffsets === true ? roundOffsetsByDPR({
x: x, x: x,
y: y y: y
}) : { }, getWindow(popper)) : {
x: x, x: x,
y: y y: y
}; };
@ -128,17 +127,6 @@ function computeStyles(_ref5) {
adaptive = _options$adaptive === void 0 ? true : _options$adaptive, adaptive = _options$adaptive === void 0 ? true : _options$adaptive,
_options$roundOffsets = options.roundOffsets, _options$roundOffsets = options.roundOffsets,
roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets;
if (process.env.NODE_ENV !== "production") {
var transitionProperty = getComputedStyle(state.elements.popper).transitionProperty || '';
if (adaptive && ['transform', 'top', 'right', 'bottom', 'left'].some(function (property) {
return transitionProperty.indexOf(property) >= 0;
})) {
console.warn(['Popper: Detected CSS transitions on at least one of the following', 'CSS properties: "transform", "top", "right", "bottom", "left".', '\n\n', 'Disable the "computeStyles" modifier\'s `adaptive` option to allow', 'for smooth transitions, or remove these properties from the CSS', 'transition declaration on the popper element if only transitioning', 'opacity or background-color for example.', '\n\n', 'We recommend using the popper element as a wrapper around an inner', 'element that can have any CSS property transitioned for animations.'].join(' '));
}
}
var commonStyles = { var commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),

View file

@ -46,8 +46,7 @@ const unsetSides = {
// Round the offsets to the nearest suitable subpixel based on the DPR. // Round the offsets to the nearest suitable subpixel based on the DPR.
// Zooming can change the DPR, but it seems to report a value that will // Zooming can change the DPR, but it seems to report a value that will
// cleanly divide the values into the appropriate subpixels. // cleanly divide the values into the appropriate subpixels.
function roundOffsetsByDPR({ x, y }): Offsets { function roundOffsetsByDPR({ x, y }, win: Window): Offsets {
const win: Window = window;
const dpr = win.devicePixelRatio || 1; const dpr = win.devicePixelRatio || 1;
return { return {
@ -82,9 +81,7 @@ export function mapToStyles({
let { x = 0, y = 0 } = offsets; let { x = 0, y = 0 } = offsets;
({ x, y } = ({ x, y } =
typeof roundOffsets === 'function' typeof roundOffsets === 'function' ? roundOffsets({ x, y }) : { x, y });
? roundOffsets({ x, y })
: { x, y });
const hasX = offsets.hasOwnProperty('x'); const hasX = offsets.hasOwnProperty('x');
const hasY = offsets.hasOwnProperty('y'); const hasY = offsets.hasOwnProperty('y');
@ -150,7 +147,7 @@ export function mapToStyles({
({ x, y } = ({ x, y } =
roundOffsets === true roundOffsets === true
? roundOffsetsByDPR({ x, y }) ? roundOffsetsByDPR({ x, y }, getWindow(popper))
: { x, y }); : { x, y });
if (gpuAcceleration) { if (gpuAcceleration) {
@ -184,33 +181,6 @@ function computeStyles({ state, options }: ModifierArguments<Options>) {
roundOffsets = true, roundOffsets = true,
} = options; } = options;
if (false) {
const transitionProperty =
getComputedStyle(state.elements.popper).transitionProperty || '';
if (
adaptive &&
['transform', 'top', 'right', 'bottom', 'left'].some(
(property) => transitionProperty.indexOf(property) >= 0
)
) {
console.warn(
[
'Popper: Detected CSS transitions on at least one of the following',
'CSS properties: "transform", "top", "right", "bottom", "left".',
'\n\n',
'Disable the "computeStyles" modifier\'s `adaptive` option to allow',
'for smooth transitions, or remove these properties from the CSS',
'transition declaration on the popper element if only transitioning',
'opacity or background-color for example.',
'\n\n',
'We recommend using the popper element as a wrapper around an inner',
'element that can have any CSS property transitioned for animations.',
].join(' ')
);
}
}
const commonStyles = { const commonStyles = {
placement: getBasePlacement(state.placement), placement: getBasePlacement(state.placement),
variation: getVariation(state.placement), variation: getVariation(state.placement),

View file

@ -25,10 +25,6 @@ export default function computeAutoPlacement(state, options) {
if (allowedPlacements.length === 0) { if (allowedPlacements.length === 0) {
allowedPlacements = placements; allowedPlacements = placements;
if (process.env.NODE_ENV !== "production") {
console.error(['Popper: The `allowedAutoPlacements` option did not allow any', 'placements. Ensure the `placement` option matches the variation', 'of the allowed placements.', 'For example, "auto" cannot be used to allow "bottom-start".', 'Use "auto-start" instead.'].join(' '));
}
} // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...

View file

@ -55,18 +55,6 @@ export default function computeAutoPlacement(
if (allowedPlacements.length === 0) { if (allowedPlacements.length === 0) {
allowedPlacements = placements; allowedPlacements = placements;
if (false) {
console.error(
[
'Popper: The `allowedAutoPlacements` option did not allow any',
'placements. Ensure the `placement` option matches the variation',
'of the allowed placements.',
'For example, "auto" cannot be used to allow "bottom-start".',
'Use "auto-start" instead.',
].join(' ')
);
}
} }
// $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions...

View file

@ -1 +0,0 @@
export default function format(str: string, ...args: Array<string>): string;

View file

@ -1,9 +0,0 @@
export default function format(str) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return [].concat(args).reduce(function (p, c) {
return p.replace(/%s/, c);
}, str);
}

View file

@ -1,5 +0,0 @@
// @flow
export default function format(str: string, ...args: Array<string>) {
return [...args].reduce((p, c) => p.replace(/%s/, c), str);
}

View file

@ -1,7 +1,7 @@
export default function getUAString() { export default function getUAString() {
var uaData = navigator.userAgentData; var uaData = navigator.userAgentData;
if (uaData != null && uaData.brands) { if (uaData != null && uaData.brands && Array.isArray(uaData.brands)) {
return uaData.brands.map(function (item) { return uaData.brands.map(function (item) {
return item.brand + "/" + item.version; return item.brand + "/" + item.version;
}).join(' '); }).join(' ');

View file

@ -10,7 +10,7 @@ interface NavigatorUAData {
export default function getUAString(): string { export default function getUAString(): string {
const uaData = (navigator: Navigator).userAgentData; const uaData = (navigator: Navigator).userAgentData;
if (uaData?.brands) { if (uaData?.brands && Array.isArray(uaData.brands)) {
return uaData.brands return uaData.brands
.map((item) => `${item.brand}/${item.version}`) .map((item) => `${item.brand}/${item.version}`)
.join(' '); .join(' ');

View file

@ -1 +0,0 @@
export default function validateModifiers(modifiers: Array<any>): void;

View file

@ -1,81 +0,0 @@
import format from "./format.js";
import { modifierPhases } from "../enums.js";
var INVALID_MODIFIER_ERROR = 'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
var MISSING_DEPENDENCY_ERROR = 'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
var VALID_PROPERTIES = ['name', 'enabled', 'phase', 'fn', 'effect', 'requires', 'options'];
export default function validateModifiers(modifiers) {
modifiers.forEach(function (modifier) {
[].concat(Object.keys(modifier), VALID_PROPERTIES) // IE11-compatible replacement for `new Set(iterable)`
.filter(function (value, index, self) {
return self.indexOf(value) === index;
}).forEach(function (key) {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(format(INVALID_MODIFIER_ERROR, String(modifier.name), '"name"', '"string"', "\"" + String(modifier.name) + "\""));
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"enabled"', '"boolean"', "\"" + String(modifier.enabled) + "\""));
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"phase"', "either " + modifierPhases.join(', '), "\"" + String(modifier.phase) + "\""));
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"fn"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'effect':
if (modifier.effect != null && typeof modifier.effect !== 'function') {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"effect"', '"function"', "\"" + String(modifier.fn) + "\""));
}
break;
case 'requires':
if (modifier.requires != null && !Array.isArray(modifier.requires)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requires"', '"array"', "\"" + String(modifier.requires) + "\""));
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(format(INVALID_MODIFIER_ERROR, modifier.name, '"requiresIfExists"', '"array"', "\"" + String(modifier.requiresIfExists) + "\""));
}
break;
case 'options':
case 'data':
break;
default:
console.error("PopperJS: an invalid property has been provided to the \"" + modifier.name + "\" modifier, valid properties are " + VALID_PROPERTIES.map(function (s) {
return "\"" + s + "\"";
}).join(', ') + "; but \"" + key + "\" was provided.");
}
modifier.requires && modifier.requires.forEach(function (requirement) {
if (modifiers.find(function (mod) {
return mod.name === requirement;
}) == null) {
console.error(format(MISSING_DEPENDENCY_ERROR, String(modifier.name), requirement, requirement));
}
});
});
});
}

View file

@ -1,151 +0,0 @@
// @flow
import format from './format';
import { modifierPhases } from '../enums';
const INVALID_MODIFIER_ERROR =
'Popper: modifier "%s" provided an invalid %s property, expected %s but got %s';
const MISSING_DEPENDENCY_ERROR =
'Popper: modifier "%s" requires "%s", but "%s" modifier is not available';
const VALID_PROPERTIES = [
'name',
'enabled',
'phase',
'fn',
'effect',
'requires',
'options',
];
export default function validateModifiers(modifiers: Array<any>): void {
modifiers.forEach((modifier) => {
[...Object.keys(modifier), ...VALID_PROPERTIES]
// IE11-compatible replacement for `new Set(iterable)`
.filter((value, index, self) => self.indexOf(value) === index)
.forEach((key) => {
switch (key) {
case 'name':
if (typeof modifier.name !== 'string') {
console.error(
format(
INVALID_MODIFIER_ERROR,
String(modifier.name),
'"name"',
'"string"',
`"${String(modifier.name)}"`
)
);
}
break;
case 'enabled':
if (typeof modifier.enabled !== 'boolean') {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"enabled"',
'"boolean"',
`"${String(modifier.enabled)}"`
)
);
}
break;
case 'phase':
if (modifierPhases.indexOf(modifier.phase) < 0) {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"phase"',
`either ${modifierPhases.join(', ')}`,
`"${String(modifier.phase)}"`
)
);
}
break;
case 'fn':
if (typeof modifier.fn !== 'function') {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"fn"',
'"function"',
`"${String(modifier.fn)}"`
)
);
}
break;
case 'effect':
if (
modifier.effect != null &&
typeof modifier.effect !== 'function'
) {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"effect"',
'"function"',
`"${String(modifier.fn)}"`
)
);
}
break;
case 'requires':
if (
modifier.requires != null &&
!Array.isArray(modifier.requires)
) {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"requires"',
'"array"',
`"${String(modifier.requires)}"`
)
);
}
break;
case 'requiresIfExists':
if (!Array.isArray(modifier.requiresIfExists)) {
console.error(
format(
INVALID_MODIFIER_ERROR,
modifier.name,
'"requiresIfExists"',
'"array"',
`"${String(modifier.requiresIfExists)}"`
)
);
}
break;
case 'options':
case 'data':
break;
default:
console.error(
`PopperJS: an invalid property has been provided to the "${
modifier.name
}" modifier, valid properties are ${VALID_PROPERTIES.map(
(s) => `"${s}"`
).join(', ')}; but "${key}" was provided.`
);
}
modifier.requires &&
modifier.requires.forEach((requirement) => {
if (modifiers.find((mod) => mod.name === requirement) == null) {
console.error(
format(
MISSING_DEPENDENCY_ERROR,
String(modifier.name),
requirement,
requirement
)
);
}
});
});
});
}

View file

@ -1,6 +1,6 @@
{ {
"name": "@popperjs/core", "name": "@popperjs/core",
"version": "2.11.6", "version": "2.11.8",
"description": "Tooltip and Popover Positioning Engine", "description": "Tooltip and Popover Positioning Engine",
"main": "dist/cjs/popper.js", "main": "dist/cjs/popper.js",
"main:umd": "dist/umd/popper.js", "main:umd": "dist/umd/popper.js",

View file

@ -1,46 +1,165 @@
<a name="2.16.0"></a> <a name="3.0.2"></a>
# [2.16.0](https://github.com/videojs/http-streaming/compare/v2.15.1...v2.16.0) (2023-01-30) ## [3.0.2](https://github.com/videojs/http-streaming/compare/v3.0.1...v3.0.2) (2023-02-27)
### Bug Fixes ### Bug Fixes
* in-manifest VTT iOS MSE issue ([#1364](https://github.com/videojs/http-streaming/issues/1364)) ([e735188](https://github.com/videojs/http-streaming/commit/e735188)) * CMAF HLS. Source buffer change type is called with wrong codecs sometimes when append segment without init data because of a race condition. ([#1375](https://github.com/videojs/http-streaming/issues/1375)) ([7c3e08e](https://github.com/videojs/http-streaming/commit/7c3e08e))
<a name="2.15.1"></a>
## [2.15.1](https://github.com/videojs/http-streaming/compare/v2.15.0...v2.15.1) (2022-11-21)
### Bug Fixes
* Restart masterPlaylistLoader after media change ([#1339](https://github.com/videojs/http-streaming/issues/1339)) ([66707b4](https://github.com/videojs/http-streaming/commit/66707b4))
* resume loading on segment timeout for `experimentalBufferBasedABR` ([#1333](https://github.com/videojs/http-streaming/issues/1333)) ([5666562](https://github.com/videojs/http-streaming/commit/5666562))
### Chores ### Chores
* update mpd-parser (main) ([#1336](https://github.com/videojs/http-streaming/issues/1336)) ([404ba76](https://github.com/videojs/http-streaming/commit/404ba76)) * **changelog:** add missing bug fix ([#1362](https://github.com/videojs/http-streaming/issues/1362)) ([343f682](https://github.com/videojs/http-streaming/commit/343f682))
* update video.js for the example page ([#1340](https://github.com/videojs/http-streaming/issues/1340)) ([8a8b111](https://github.com/videojs/http-streaming/commit/8a8b111)) * update mux.js ([#1372](https://github.com/videojs/http-streaming/issues/1372)) ([1bd22c9](https://github.com/videojs/http-streaming/commit/1bd22c9))
<a name="2.15.0"></a> <a name="3.0.1"></a>
# [2.15.0](https://github.com/videojs/http-streaming/compare/v2.14.3...v2.15.0) (2022-09-14) ## [3.0.1](https://github.com/videojs/http-streaming/compare/v3.0.0...v3.0.1) (2023-01-24)
### Bug Fixes
* Linear DASH multiperiod label issue ([#1352](https://github.com/videojs/http-streaming/issues/1352)) ([d7e8713](https://github.com/videojs/http-streaming/commit/d7e8713))
* In-manifest VTT iOS MSE issue ([#1360](https://github.com/videojs/http-streaming/issues/1360)) ([6ba70e0](https://github.com/videojs/http-streaming/commit/6ba70e0))
<a name="3.0.0"></a>
# [3.0.0](https://github.com/videojs/http-streaming/compare/v2.14.2...v3.0.0) (2022-11-21)
### Features ### Features
* add frameRate property to the representation class. ([#1289](https://github.com/videojs/http-streaming/issues/1289)) ([27a970c](https://github.com/videojs/http-streaming/commit/27a970c)) * add compatibility layer for video.js 7 and 8 ([#1322](https://github.com/videojs/http-streaming/issues/1322)) ([b9d26e5](https://github.com/videojs/http-streaming/commit/b9d26e5))
* add frameRate property to the representation class. ([#1289](https://github.com/videojs/http-streaming/issues/1289)) ([fd2898f](https://github.com/videojs/http-streaming/commit/fd2898f))
<a name="2.14.3"></a> * enable LLHLS support by default and remove experimental prefix on options ([#1301](https://github.com/videojs/http-streaming/issues/1301)) ([02c3c77](https://github.com/videojs/http-streaming/commit/02c3c77))
## [2.14.3](https://github.com/videojs/http-streaming/compare/v2.14.2...v2.14.3) (2022-08-31) * remove handleManifestRedirects and always use XHR.responseURL if available ([#1226](https://github.com/videojs/http-streaming/issues/1226)) ([3ad3120](https://github.com/videojs/http-streaming/commit/3ad3120))
* rename many things to `main` ([#1309](https://github.com/videojs/http-streaming/issues/1309)) ([54cbab3](https://github.com/videojs/http-streaming/commit/54cbab3))
* Skip gaps immediately ([#1267](https://github.com/videojs/http-streaming/issues/1267)) ([f85c153](https://github.com/videojs/http-streaming/commit/f85c153))
* update tooling to remove ie 11 transpiling, update tests ([#1306](https://github.com/videojs/http-streaming/issues/1306)) ([206f099](https://github.com/videojs/http-streaming/commit/206f099))
### Bug Fixes ### Bug Fixes
* cache aes keys for text tracks ([#973](https://github.com/videojs/http-streaming/issues/973)) ([#1228](https://github.com/videojs/http-streaming/issues/1228)) ([721e1bf](https://github.com/videojs/http-streaming/commit/721e1bf)) * add Video.js 8 to the dep version range ([#1307](https://github.com/videojs/http-streaming/issues/1307)) ([325a98e](https://github.com/videojs/http-streaming/commit/325a98e))
* output-restricted event handling for unplayable streams ([#1305](https://github.com/videojs/http-streaming/issues/1305)) ([23bbf84](https://github.com/videojs/http-streaming/commit/23bbf84)) * cache aes keys for text tracks ([#973](https://github.com/videojs/http-streaming/issues/973)) ([#1228](https://github.com/videojs/http-streaming/issues/1228)) ([66a5b17](https://github.com/videojs/http-streaming/commit/66a5b17))
* output-restricted event handling for unplayable streams ([#1305](https://github.com/videojs/http-streaming/issues/1305)) ([1c62a98](https://github.com/videojs/http-streaming/commit/1c62a98))
* remove deprecation hls options, properties, and events; add migration guide ([#1229](https://github.com/videojs/http-streaming/issues/1229)) ([43fce26](https://github.com/videojs/http-streaming/commit/43fce26))
* Restart mainPlaylistLoader after media change ([#1339](https://github.com/videojs/http-streaming/issues/1339)) ([cf340f2](https://github.com/videojs/http-streaming/commit/cf340f2))
* resume loading on segment timeout for `bufferBasedABR` ([#1333](https://github.com/videojs/http-streaming/issues/1333)) ([969589e](https://github.com/videojs/http-streaming/commit/969589e))
### Chores ### Chores
* **docs:** Remove outdated information in collaborators' guide ([#1271](https://github.com/videojs/http-streaming/issues/1271)) ([5223427](https://github.com/videojs/http-streaming/commit/5223427)) * **docs:** Remove outdated information in collaborators' guide ([#1271](https://github.com/videojs/http-streaming/issues/1271)) ([6100750](https://github.com/videojs/http-streaming/commit/6100750))
* **package:** update dependencies to use new ES6 builds ([#1320](https://github.com/videojs/http-streaming/issues/1320)) ([9ae6695](https://github.com/videojs/http-streaming/commit/9ae6695))
* **package:** update m3u8-parser to v6.0.0 ([#1330](https://github.com/videojs/http-streaming/issues/1330)) ([fe15751](https://github.com/videojs/http-streaming/commit/fe15751))
* remove old-index since IE is no longer supported ([#1308](https://github.com/videojs/http-streaming/issues/1308)) ([5ba3a77](https://github.com/videojs/http-streaming/commit/5ba3a77))
* update karma-config to 8 to drop ie11 and older browsers ([#1227](https://github.com/videojs/http-streaming/issues/1227)) ([44c12ea](https://github.com/videojs/http-streaming/commit/44c12ea))
* update mpd-parser ([#1337](https://github.com/videojs/http-streaming/issues/1337)) ([7ff95b9](https://github.com/videojs/http-streaming/commit/7ff95b9))
* update package-lock ([1806b46](https://github.com/videojs/http-streaming/commit/1806b46))
* update package-lock.json ([#1319](https://github.com/videojs/http-streaming/issues/1319)) ([c7aa9c1](https://github.com/videojs/http-streaming/commit/c7aa9c1))
### Code Refactoring
* clean up parameters of excludePlaylist ([#1304](https://github.com/videojs/http-streaming/issues/1304)) ([ca3162b](https://github.com/videojs/http-streaming/commit/ca3162b))
* Remove deprecated smooth quality change ([#1268](https://github.com/videojs/http-streaming/issues/1268)) ([6041014](https://github.com/videojs/http-streaming/commit/6041014))
* rename 'blacklist' to 'exclude' ([#1274](https://github.com/videojs/http-streaming/issues/1274)) ([d79d783](https://github.com/videojs/http-streaming/commit/d79d783))
### Tests ### Tests
* change source for live DASH playback test to fix test failures ([#1303](https://github.com/videojs/http-streaming/issues/1303)) ([e39e27d](https://github.com/videojs/http-streaming/commit/e39e27d)) * change source for live DASH playback test to fix test failures ([#1303](https://github.com/videojs/http-streaming/issues/1303)) ([128b3d7](https://github.com/videojs/http-streaming/commit/128b3d7))
* fix IE11 encrypted VTT tests by using an actual encrypted VTT segment ([#1291](https://github.com/videojs/http-streaming/issues/1291)) ([97e02fb](https://github.com/videojs/http-streaming/commit/97e02fb)) * fix IE11 encrypted VTT tests by using an actual encrypted VTT segment ([#1291](https://github.com/videojs/http-streaming/issues/1291)) ([57c0e72](https://github.com/videojs/http-streaming/commit/57c0e72))
### BREAKING CHANGES
* **package:** manifests with tags lacking colons (:) are no longer supported
* **package:** This updates bundled libraries to no longer be transpiled to ES5, which means IE will no longer be supported.
* This changes the arguments for the `PlaylistController#excludePlaylist` method to take a single object instead of multiple arguments.
* This renames four experimental options to no longer be experimental and enables Low Latency HLS support by default (`llhls: false` will still disable it, if desired).
* rename PlaylistController
* rename HAVE_MASTER to HAVE_MAIN_MANIFEST
* playlist loaders updateMain and .main prop rename
* manifest.js exports mainForMedia and addPropertiesToMain
* rename media groups prop to isMainPlaylist
* rename property to mainPlaylistLoader_
* rename to PlaylistController#main()
* This removes support entirely for IE11 (and older) as well as any other platforms that do not support ES6.
* remove ^6 from the dependency version ranges.
* Skips detected gaps immediately instead of waiting the duration of the gap before skipping
* Removes deprecated `smoothQualityChange` option
* remove deprecated options, properties, events.
* remove handleManifestRedirects option. Now XHR.responseURL will always be used when available.
<a name="3.0.0-2"></a>
# [3.0.0-2](https://github.com/videojs/http-streaming/compare/v3.0.0-1...v3.0.0-2) (2022-09-30)
<a name="3.0.0-1"></a>
# [3.0.0-1](https://github.com/videojs/http-streaming/compare/v3.0.0-0...v3.0.0-1) (2022-09-30)
### Features
* add compatibility layer for video.js 7 and 8 ([#1322](https://github.com/videojs/http-streaming/issues/1322)) ([b9d26e5](https://github.com/videojs/http-streaming/commit/b9d26e5))
* add frameRate property to the representation class. ([#1289](https://github.com/videojs/http-streaming/issues/1289)) ([fd2898f](https://github.com/videojs/http-streaming/commit/fd2898f))
### Chores
* **package:** update m3u8-parser to v6.0.0 ([#1330](https://github.com/videojs/http-streaming/issues/1330)) ([fe15751](https://github.com/videojs/http-streaming/commit/fe15751))
* update package-lock ([1806b46](https://github.com/videojs/http-streaming/commit/1806b46))
### BREAKING CHANGES
* **package:** manifests with tags lacking colons (:) are no longer supported
<a name="3.0.0-0"></a>
# [3.0.0-0](https://github.com/videojs/http-streaming/compare/v2.14.2...v3.0.0-0) (2022-08-19)
### Features
* enable LLHLS support by default and remove experimental prefix on options ([#1301](https://github.com/videojs/http-streaming/issues/1301)) ([02c3c77](https://github.com/videojs/http-streaming/commit/02c3c77))
* remove handleManifestRedirects and always use XHR.responseURL if available ([#1226](https://github.com/videojs/http-streaming/issues/1226)) ([3ad3120](https://github.com/videojs/http-streaming/commit/3ad3120))
* rename many things to `main` ([#1309](https://github.com/videojs/http-streaming/issues/1309)) ([54cbab3](https://github.com/videojs/http-streaming/commit/54cbab3))
* Skip gaps immediately ([#1267](https://github.com/videojs/http-streaming/issues/1267)) ([f85c153](https://github.com/videojs/http-streaming/commit/f85c153))
* update tooling to remove ie 11 transpiling, update tests ([#1306](https://github.com/videojs/http-streaming/issues/1306)) ([206f099](https://github.com/videojs/http-streaming/commit/206f099))
### Bug Fixes
* add Video.js 8 to the dep version range ([#1307](https://github.com/videojs/http-streaming/issues/1307)) ([325a98e](https://github.com/videojs/http-streaming/commit/325a98e))
* cache aes keys for text tracks ([#973](https://github.com/videojs/http-streaming/issues/973)) ([#1228](https://github.com/videojs/http-streaming/issues/1228)) ([66a5b17](https://github.com/videojs/http-streaming/commit/66a5b17))
* output-restricted event handling for unplayable streams ([#1305](https://github.com/videojs/http-streaming/issues/1305)) ([1c62a98](https://github.com/videojs/http-streaming/commit/1c62a98))
* remove deprecation hls options, properties, and events; add migration guide ([#1229](https://github.com/videojs/http-streaming/issues/1229)) ([43fce26](https://github.com/videojs/http-streaming/commit/43fce26))
### Chores
* **docs:** Remove outdated information in collaborators' guide ([#1271](https://github.com/videojs/http-streaming/issues/1271)) ([6100750](https://github.com/videojs/http-streaming/commit/6100750))
* **package:** update dependencies to use new ES6 builds ([#1320](https://github.com/videojs/http-streaming/issues/1320)) ([9ae6695](https://github.com/videojs/http-streaming/commit/9ae6695))
* remove old-index since IE is no longer supported ([#1308](https://github.com/videojs/http-streaming/issues/1308)) ([5ba3a77](https://github.com/videojs/http-streaming/commit/5ba3a77))
* update karma-config to 8 to drop ie11 and older browsers ([#1227](https://github.com/videojs/http-streaming/issues/1227)) ([44c12ea](https://github.com/videojs/http-streaming/commit/44c12ea))
* update package-lock.json ([#1319](https://github.com/videojs/http-streaming/issues/1319)) ([c7aa9c1](https://github.com/videojs/http-streaming/commit/c7aa9c1))
### Code Refactoring
* clean up parameters of excludePlaylist ([#1304](https://github.com/videojs/http-streaming/issues/1304)) ([ca3162b](https://github.com/videojs/http-streaming/commit/ca3162b))
* Remove deprecated smooth quality change ([#1268](https://github.com/videojs/http-streaming/issues/1268)) ([6041014](https://github.com/videojs/http-streaming/commit/6041014))
* rename 'blacklist' to 'exclude' ([#1274](https://github.com/videojs/http-streaming/issues/1274)) ([d79d783](https://github.com/videojs/http-streaming/commit/d79d783))
### Tests
* change source for live DASH playback test to fix test failures ([#1303](https://github.com/videojs/http-streaming/issues/1303)) ([128b3d7](https://github.com/videojs/http-streaming/commit/128b3d7))
* fix IE11 encrypted VTT tests by using an actual encrypted VTT segment ([#1291](https://github.com/videojs/http-streaming/issues/1291)) ([57c0e72](https://github.com/videojs/http-streaming/commit/57c0e72))
### BREAKING CHANGES
* **package:** This updates bundled libraries to no longer be transpiled to ES5, which means IE will no longer be supported.
* This changes the arguments for the `PlaylistController#excludePlaylist` method to take a single object instead of multiple arguments.
* This renames four experimental options to no longer be experimental and enables Low Latency HLS support by default (`llhls: false` will still disable it, if desired).
* rename PlaylistController
* rename HAVE_MASTER to HAVE_MAIN_MANIFEST
* playlist loaders updateMain and .main prop rename
* manifest.js exports mainForMedia and addPropertiesToMain
* rename media groups prop to isMainPlaylist
* rename property to mainPlaylistLoader_
* rename to PlaylistController#main()
* This removes support entirely for IE11 (and older) as well as any other platforms that do not support ES6.
* remove ^6 from the dependency version ranges.
* Skips detected gaps immediately instead of waiting the duration of the gap before skipping
* Removes deprecated `smoothQualityChange` option
* remove deprecated options, properties, events.
* remove handleManifestRedirects option. Now XHR.responseURL will always be used when available.
<a name="2.14.2"></a> <a name="2.14.2"></a>
## [2.14.2](https://github.com/videojs/http-streaming/compare/v2.14.1...v2.14.2) (2022-04-13) ## [2.14.2](https://github.com/videojs/http-streaming/compare/v2.14.1...v2.14.2) (2022-04-13)

View file

@ -27,4 +27,4 @@ Testing is a crucial part of any software project. For all but the most trivial
[karma]: http://karma-runner.github.io/ [karma]: http://karma-runner.github.io/
[local]: http://localhost:9999/test/ [local]: http://localhost:9999/test/
[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md [conventions]: https://github.com/videojs/generator-videojs-plugin/blob/main/docs/conventions.md

View file

@ -13,7 +13,7 @@ Included in video.js 7 by default! See the [video.js 7 blog post](https://blog.v
Maintenance Status: Stable Maintenance Status: Stable
Video.js Compatibility: 6.0, 7.0 Video.js Compatibility: 7.x, 8.x
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
@ -39,18 +39,16 @@ Video.js Compatibility: 6.0, 7.0
- [Source](#source) - [Source](#source)
- [List](#list) - [List](#list)
- [withCredentials](#withcredentials) - [withCredentials](#withcredentials)
- [handleManifestRedirects](#handlemanifestredirects)
- [useCueTags](#usecuetags) - [useCueTags](#usecuetags)
- [parse708captions](#parse708captions) - [parse708captions](#parse708captions)
- [overrideNative](#overridenative) - [overrideNative](#overridenative)
- [blacklistDuration](#blacklistduration) - [playlistExclusionDuration](#playlistexclusionduration)
- [maxPlaylistRetries](#maxplaylistretries) - [maxPlaylistRetries](#maxplaylistretries)
- [bandwidth](#bandwidth) - [bandwidth](#bandwidth)
- [useBandwidthFromLocalStorage](#usebandwidthfromlocalstorage) - [useBandwidthFromLocalStorage](#usebandwidthfromlocalstorage)
- [enableLowInitialPlaylist](#enablelowinitialplaylist) - [enableLowInitialPlaylist](#enablelowinitialplaylist)
- [limitRenditionByPlayerDimensions](#limitrenditionbyplayerdimensions) - [limitRenditionByPlayerDimensions](#limitrenditionbyplayerdimensions)
- [useDevicePixelRatio](#usedevicepixelratio) - [useDevicePixelRatio](#usedevicepixelratio)
- [smoothQualityChange](#smoothqualitychange)
- [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow) - [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow)
- [customTagParsers](#customtagparsers) - [customTagParsers](#customtagparsers)
- [customTagMappers](#customtagmappers) - [customTagMappers](#customtagmappers)
@ -63,7 +61,7 @@ Video.js Compatibility: 6.0, 7.0
- [Format](#format) - [Format](#format)
- [Example](#example) - [Example](#example)
- [Runtime Properties](#runtime-properties) - [Runtime Properties](#runtime-properties)
- [vhs.playlists.master](#vhsplaylistsmaster) - [vhs.playlists.main](#vhsplaylistsmain)
- [vhs.playlists.media](#vhsplaylistsmedia) - [vhs.playlists.media](#vhsplaylistsmedia)
- [vhs.systemBandwidth](#vhssystembandwidth) - [vhs.systemBandwidth](#vhssystembandwidth)
- [vhs.bandwidth](#vhsbandwidth) - [vhs.bandwidth](#vhsbandwidth)
@ -174,7 +172,7 @@ This plugin does not support Flash playback. Instead, it is recommended that use
DRM is supported through [videojs-contrib-eme](https://github.com/videojs/videojs-contrib-eme). In order to use DRM, include the videojs-contrib-eme plug, [initialize it](https://github.com/videojs/videojs-contrib-eme#initialization), and add options to either the [plugin](https://github.com/videojs/videojs-contrib-eme#plugin-options) or the [source](https://github.com/videojs/videojs-contrib-eme#source-options). DRM is supported through [videojs-contrib-eme](https://github.com/videojs/videojs-contrib-eme). In order to use DRM, include the videojs-contrib-eme plug, [initialize it](https://github.com/videojs/videojs-contrib-eme#initialization), and add options to either the [plugin](https://github.com/videojs/videojs-contrib-eme#plugin-options) or the [source](https://github.com/videojs/videojs-contrib-eme#source-options).
Detailed option information can be found in the [videojs-contrib-eme README](https://github.com/videojs/videojs-contrib-eme/blob/master/README.md). Detailed option information can be found in the [videojs-contrib-eme README](https://github.com/videojs/videojs-contrib-eme/blob/main/README.md).
## Documentation ## Documentation
[HTTP Live Streaming](https://developer.apple.com/streaming/) (HLS) has [HTTP Live Streaming](https://developer.apple.com/streaming/) (HLS) has
@ -283,16 +281,6 @@ is set to `true`.
See html5rocks's [article](http://www.html5rocks.com/en/tutorials/cors/) See html5rocks's [article](http://www.html5rocks.com/en/tutorials/cors/)
for more info. for more info.
##### handleManifestRedirects
* Type: `boolean`
* Default: `false`
* can be used as a source option
* can be used as an initialization option
When the `handleManifestRedirects` property is set to `true`, manifest requests
which are redirected will have their URL updated to the new URL for future
requests.
##### useCueTags ##### useCueTags
* Type: `boolean` * Type: `boolean`
* can be used as an initialization option * can be used as an initialization option
@ -358,13 +346,13 @@ var player = videojs('playerId', {
Since MSE playback may be desirable on all browsers with some native support other than Safari, `overrideNative: !videojs.browser.IS_SAFARI` could be used. Since MSE playback may be desirable on all browsers with some native support other than Safari, `overrideNative: !videojs.browser.IS_SAFARI` could be used.
##### blacklistDuration ##### playlistExclusionDuration
* Type: `number` * Type: `number`
* can be used as an initialization option * can be used as an initialization option
When the `blacklistDuration` property is set to a time duration in seconds, When the `playlistExclusionDuration` property is set to a time duration in seconds,
if a playlist is blacklisted, it will be blacklisted for a period of that if a playlist is excluded, it will be excluded for a period of that
customized duration. This enables the blacklist duration to be configured customized duration. This enables the exclusion duration to be configured
by the user. by the user.
##### maxPlaylistRetries ##### maxPlaylistRetries
@ -414,18 +402,6 @@ This setting is `true` by default.
If true, this will take the device pixel ratio into account when doing rendition switching. This means that if you have a player with the width of `540px` in a high density display with a device pixel ratio of 2, a rendition of `1080p` will be allowed. If true, this will take the device pixel ratio into account when doing rendition switching. This means that if you have a player with the width of `540px` in a high density display with a device pixel ratio of 2, a rendition of `1080p` will be allowed.
This setting is `false` by default. This setting is `false` by default.
##### smoothQualityChange
* NOTE: DEPRECATED
* Type: `boolean`
* can be used as a source option
* can be used as an initialization option
smoothQualityChange is deprecated and will be removed in the next major version of VHS.
Instead of its prior behavior, smoothQualityChange will now call fastQualityChange, which
clears the buffer, chooses a new rendition, and starts loading content from the current
playhead position.
##### allowSeeksWithinUnsafeLiveWindow ##### allowSeeksWithinUnsafeLiveWindow
* Type: `boolean` * Type: `boolean`
* can be used as a source option * can be used as a source option
@ -546,11 +522,11 @@ work across all the media types that video.js supports. If you're
deploying videojs-http-streaming on your own website and want to make a deploying videojs-http-streaming on your own website and want to make a
couple tweaks though, go for it! couple tweaks though, go for it!
#### vhs.playlists.master #### vhs.playlists.main
Type: `object` Type: `object`
An object representing the parsed master playlist. If a media playlist An object representing the parsed main playlist. If a media playlist
is loaded directly, a master playlist with only one entry will be is loaded directly, a main playlist with only one entry will be
created. created.
#### vhs.playlists.media #### vhs.playlists.media
@ -561,7 +537,7 @@ media playlist. The active media playlist is referred to when
additional video data needs to be downloaded. Calling this function additional video data needs to be downloaded. Calling this function
with no arguments returns the parsed playlist object for the active with no arguments returns the parsed playlist object for the active
media playlist. Calling this function with a playlist object from the media playlist. Calling this function with a playlist object from the
master playlist or a URI string as specified in the master playlist main playlist or a URI string as specified in the main playlist
will kick off an asynchronous load of the specified media will kick off an asynchronous load of the specified media
playlist. Once it has been retreived, it will become the active media playlist. Once it has been retreived, it will become the active media
playlist. playlist.
@ -601,7 +577,7 @@ A function that returns the media playlist object to use to download
the next segment. It is invoked by the tech immediately before a new the next segment. It is invoked by the tech immediately before a new
segment is downloaded. You can override this function to provide your segment is downloaded. You can override this function to provide your
adaptive streaming logic. You must, however, be sure to return a valid adaptive streaming logic. You must, however, be sure to return a valid
media playlist object that is present in `player.tech().vhs.master`. media playlist object that is present in `player.tech().vhs.main`.
Overridding this function with your own is very powerful but is overkill Overridding this function with your own is very powerful but is overkill
for many purposes. Most of the time, you should use the much simpler for many purposes. Most of the time, you should use the much simpler
@ -715,7 +691,7 @@ This object contains a summary of HLS and player related stats.
| currentSource | object | The source object. Has the structure `{src: 'url', type: 'mimetype'}` | | currentSource | object | The source object. Has the structure `{src: 'url', type: 'mimetype'}` |
| currentTech | string | The name of the tech in use | | currentTech | string | The name of the tech in use |
| duration | number | Duration of the video in seconds | | duration | number | Duration of the video in seconds |
| master | object | The [master playlist object](#vhsplaylistsmaster) | | main | object | The [main playlist object](#vhsplaylistsmain) |
| playerDimensions | object | Contains the width and height of the player | | playerDimensions | object | Contains the width and height of the player |
| seekable | array | List of time ranges that the player can seek to | | seekable | array | List of time ranges that the player can seek to |
| timestamp | number | Timestamp of when `vhs.stats` was accessed | | timestamp | number | Timestamp of when `vhs.stats` was accessed |
@ -756,7 +732,7 @@ player.on('ready', () => {
``` ```
Note that these events are triggered as soon as a case is encountered, and often only Note that these events are triggered as soon as a case is encountered, and often only
once. For example, the `vhs-demuxed` usage event will be triggered as soon as the master once. For example, the `vhs-demuxed` usage event will be triggered as soon as the main
manifest is downloaded and parsed, and will not be triggered again. manifest is downloaded and parsed, and will not be triggered again.
#### Presence Stats #### Presence Stats
@ -765,11 +741,11 @@ Each of the following usage events are fired once per source if (and when) detec
| Name | Description | | Name | Description |
| ------------- | ------------- | | ------------- | ------------- |
| vhs-webvtt | master manifest has at least one segmented WebVTT playlist | | vhs-webvtt | main manifest has at least one segmented WebVTT playlist |
| vhs-aes | a playlist is AES encrypted | | vhs-aes | a playlist is AES encrypted |
| vhs-fmp4 | a playlist used fMP4 segments | | vhs-fmp4 | a playlist used fMP4 segments |
| vhs-demuxed | audio and video are demuxed by default | | vhs-demuxed | audio and video are demuxed by default |
| vhs-alternate-audio | alternate audio available in the master manifest | | vhs-alternate-audio | alternate audio available in the main manifest |
| vhs-playlist-cue-tags | a playlist used cue tags (see useCueTags(#usecuetags) for details) | | vhs-playlist-cue-tags | a playlist used cue tags (see useCueTags(#usecuetags) for details) |
| vhs-bandwidth-from-local-storage | starting bandwidth was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) | | vhs-bandwidth-from-local-storage | starting bandwidth was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) |
| vhs-throughput-from-local-storage | starting throughput was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) | | vhs-throughput-from-local-storage | starting throughput was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) |
@ -785,7 +761,7 @@ Each of the following usage events are fired per use:
| vhs-audio-change | a user selected an alternate audio stream | | vhs-audio-change | a user selected an alternate audio stream |
| vhs-rendition-disabled | a rendition was disabled | | vhs-rendition-disabled | a rendition was disabled |
| vhs-rendition-enabled | a rendition was enabled | | vhs-rendition-enabled | a rendition was enabled |
| vhs-rendition-blacklisted | a rendition was blacklisted | | vhs-rendition-excluded| a rendition was excluded |
| vhs-timestamp-offset | a timestamp offset was set in HLS (can identify discontinuities) | | vhs-timestamp-offset | a timestamp offset was set in HLS (can identify discontinuities) |
| vhs-unknown-waiting | the player stopped for an unknown reason and we seeked to current time try to address it | | vhs-unknown-waiting | the player stopped for an unknown reason and we seeked to current time try to address it |
| vhs-live-resync | playback fell off the back of a live playlist and we resynced to the live point | | vhs-live-resync | playback fell off the back of a live playlist and we resynced to the live point |

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,3 +1,14 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Overview](#overview)
- [HTTP Live Streaming](#http-live-streaming)
- [Dynamic Adaptive Streaming over HTTP](#dynamic-adaptive-streaming-over-http)
- [Further Documentation](#further-documentation)
- [Helpful Tools](#helpful-tools)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Overview # Overview
This project supports both [HLS][hls] and [MPEG-DASH][dash] playback in the video.js player. This document is intended as a primer for anyone interested in contributing or just better understanding how bits from a server get turned into video on their display. This project supports both [HLS][hls] and [MPEG-DASH][dash] playback in the video.js player. This document is intended as a primer for anyone interested in contributing or just better understanding how bits from a server get turned into video on their display.
@ -7,11 +18,11 @@ This project supports both [HLS][hls] and [MPEG-DASH][dash] playback in the vide
- Delivered over HTTP(S): it uses the standard application protocol of the web to deliver all its data - Delivered over HTTP(S): it uses the standard application protocol of the web to deliver all its data
- Segmented: longer videos are broken up into smaller chunks which can be downloaded independently and switched between at runtime - Segmented: longer videos are broken up into smaller chunks which can be downloaded independently and switched between at runtime
A standard HLS stream consists of a *Master Playlist* which references one or more *Media Playlists*. Each Media Playlist contains one or more sequential video segments. All these components form a logical hierarchy that informs the player of the different quality levels of the video available and how to address the individual segments of video at each of those levels: A standard HLS stream consists of a *Main Playlist* which references one or more *Media Playlists*. Each Media Playlist contains one or more sequential video segments. All these components form a logical hierarchy that informs the player of the different quality levels of the video available and how to address the individual segments of video at each of those levels:
![HLS Format](images/hls-format.png) ![HLS Format](images/hls-format.png)
HLS streams can be delivered in two different modes: a "static" mode for videos that can be played back from any point, often referred to as video-on-demand (VOD); or a "live" mode where later portions of the video become available as time goes by. In the static mode, the Master and Media playlists are fixed. The player is guaranteed that the set of video segments referenced by those playlists will not change over time. HLS streams can be delivered in two different modes: a "static" mode for videos that can be played back from any point, often referred to as video-on-demand (VOD); or a "live" mode where later portions of the video become available as time goes by. In the static mode, the Main and Media playlists are fixed. The player is guaranteed that the set of video segments referenced by those playlists will not change over time.
Live mode can work in one of two ways. For truly live events, the most common configuration is for each individual Media Playlist to only include the latest video segment and a small number of consecutive previous segments. In this mode, the player may be able to seek backwards a short time in the video but probably not all the way back to the beginning. In the other live configuration, new video segments can be appended to the Media Playlists but older segments are never removed. This configuration allows the player to seek back to the beginning of the stream at any time during the broadcast and transitions seamlessly to the static stream type when the event finishes. Live mode can work in one of two ways. For truly live events, the most common configuration is for each individual Media Playlist to only include the latest video segment and a small number of consecutive previous segments. In this mode, the player may be able to seek backwards a short time in the video but probably not all the way back to the beginning. In the other live configuration, new video segments can be appended to the Media Playlists but older segments are never removed. This configuration allows the player to seek back to the beginning of the stream at any time during the broadcast and transitions seamlessly to the static stream type when the event finishes.

View file

@ -58,25 +58,23 @@ flowchart TD
> * most EME handling for DRM and setup of the [videojs-contrib-eme plugin](https://github.com/videojs/videojs-contrib-eme) > * most EME handling for DRM and setup of the [videojs-contrib-eme plugin](https://github.com/videojs/videojs-contrib-eme)
> * mapping/handling of options passed down via Video.js > * mapping/handling of options passed down via Video.js
## MasterPlaylistController ## PlaylistController
One critical object that `VhsHandler`'s constructor creates is a new `MasterPlaylistController`. One critical object that `VhsHandler`'s constructor creates is a new `PlaylistController`.
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
``` ```
`MasterPlaylistController` is not a great name, and has grown in size to be a bit unwieldy, but it's the hub of VHS. Eventually, it should be broken into smaller pieces, but for now, it handles the creation and management of most of the other VHS modules. Its code can be found in [src/master-playlist-controller.js](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js). `PlaylistController` is not a great name, and has grown in size to be a bit unwieldy, but it's the hub of VHS. Eventually, it should be broken into smaller pieces, but for now, it handles the creation and management of most of the other VHS modules. Its code can be found in [src/playlist-controller.js](/src/playlist-controller.js).
The best way to think of `MasterPlaylistController` is as Tron's Master Control Program, though hopefully it isn't as evil. `PlaylistController` is a lot to say. So we often refer to it as PC.
`MasterPlaylistController` is a lot to say. So we often refer to it as MPC. If you need to find a place where different modules communicate, you will probably end up in PC. Just about all of `VhsHandler` that doesn't interface with Video.js or other plugins, interfaces with PC.
If you need to find a place where different modules communicate, you will probably end up in MPC. Just about all of `VhsHandler` that doesn't interface with Video.js or other plugins, interfaces with MPC. PC's [constructor](/src/playlist-controller.js#L148) does a lot. Instead of listing all of the things it does, let's go step-by-step through the main ones, passing the source we had above.
MPC's [constructor](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L148) does a lot. Instead of listing all of the things it does, let's go step-by-step through the main ones, passing the source we had above.
```html ```html
<video-js id="myPlayer" class="video-js" data-setup='{}'> <video-js id="myPlayer" class="video-js" data-setup='{}'>
@ -84,18 +82,18 @@ MPC's [constructor](https://github.com/videojs/http-streaming/blob/0964cb4827d9e
</video-js> </video-js>
``` ```
Looking at the `<source>` tag, `VhsSourceHandler` already used the "type" to tell Video.js that it could handle the source. `VhsHandler` took the manifest URL, in this case "manifest.m3u8" and provided it to the constructor of MPC. Looking at the `<source>` tag, `VhsSourceHandler` already used the "type" to tell Video.js that it could handle the source. `VhsHandler` took the manifest URL, in this case "manifest.m3u8" and provided it to the constructor of PC.
The first thing that MPC must do is download that source, but it doesn't make the request itself. Instead, it creates [this.masterPlaylistLoader_](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L264-L266). The first thing that PC must do is download that source, but it doesn't make the request itself. Instead, it creates [this.mainPlaylistLoader_](/src/laylist-controller.js#L263-L265).
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
``` ```
`masterPlaylistLoader_` is an instance of either the [HLS PlaylistLoader](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/playlist-loader.js#L379) or the [DashPlaylistLoader](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/dash-playlist-loader.js#L259). `mainPlaylistLoader_` is an instance of either the [HLS PlaylistLoader](/src/playlist-loader.js#L379) or the [DashPlaylistLoader](/src/dash-playlist-loader.js#L259).
The names betray their use. They load the playlist. The URL ("manifest.m3u8" here) is given, and the manifest/playlist is downloaded and parsed. If the content is live, the playlist loader also handles refreshing the manifest. For HLS, where manifests point to other manifests, the playlist loader requests those as well. The names betray their use. They load the playlist. The URL ("manifest.m3u8" here) is given, and the manifest/playlist is downloaded and parsed. If the content is live, the playlist loader also handles refreshing the manifest. For HLS, where manifests point to other manifests, the playlist loader requests those as well.
@ -117,9 +115,9 @@ Manifest {
} }
``` ```
Many properties are removed for simplicity. This is a top level manifest (often referred to as a master or main manifest), and within it there are playlists, each playlist being a Manifest itself. Since the JSON "schema" for main and media playlists is the same, you will see irrelevant properties within any given manifest object. For instance, you might see a `targetDuration` property on the main manifest object, though a main manifest doesn't have a target duration. You can ignore irrelevant properties. Eventually they should be cleaned up, and a proper schema defined for manifest objects. Many properties are removed for simplicity. This is a top level manifest (often referred to as a main manifest or a multivariant manifest [from the HLS spec]), and within it there are playlists, each playlist being a Manifest itself. Since the JSON "schema" for main and media playlists is the same, you will see irrelevant properties within any given manifest object. For instance, you might see a `targetDuration` property on the main manifest object, though a main manifest doesn't have a target duration. You can ignore irrelevant properties. Eventually they should be cleaned up, and a proper schema defined for manifest objects.
MPC will also use `masterPlaylistLoader_` to select which media playlist is active (e.g., the 720p rendition or the 480p rendition), so that `masterPlaylistLoader_` will only need to refresh that individual playlist if the stream is live. PC will also use `mainPlaylistLoader_` to select which media playlist is active (e.g., the 720p rendition or the 480p rendition), so that `mainPlaylistLoader_` will only need to refresh that individual playlist if the stream is live.
> :information_source: **Future Work** > :information_source: **Future Work**
> >
@ -129,26 +127,26 @@ MPC will also use `masterPlaylistLoader_` to select which media playlist is acti
### Media Source Extensions ### Media Source Extensions
The next thing MPC needs to do is set up a media source for [Media Source Extensions](https://www.w3.org/TR/media-source/). Specifically, it needs to create [this.mediaSource](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L210) and its associated [source buffers](https://github.com/videojs/http-streaming/blob/main/src/master-playlist-controller.js#L1818). These are where audio and video data will be appended, so that the browser has content to play. But those aren't used directly. Because source buffers can only handle one operation at a time, [this.sourceUpdater_](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L234) is created. `sourceUpdater_` is a queue for operations performed on the source buffers. That's pretty much it. So all of the MSE pieces for appending get wrapped up in `sourceUpdater_`. The next thing PC needs to do is set up a media source for [Media Source Extensions](https://www.w3.org/TR/media-source/). Specifically, it needs to create [this.mediaSource](/src/playlist-controller.js#L208) and its associated [source buffers](/src/playlist-controller.js#L1814). These are where audio and video data will be appended, so that the browser has content to play. But those aren't used directly. Because source buffers can only handle one operation at a time, [this.sourceUpdater_](/src/playlist-controller.js#L232) is created. `sourceUpdater_` is a queue for operations performed on the source buffers. That's pretty much it. So all of the MSE pieces for appending get wrapped up in `sourceUpdater_`.
## Segment Loaders ## Segment Loaders
The SourceUpdater created for MSE above is passed to the segment loaders. The SourceUpdater created for MSE above is passed to the segment loaders.
[this.mainSegmentLoader_](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L271-L275) is used for muxed content (audio and video in one segment) and for audio or video only streams. [this.mainSegmentLoader_](/src/playlist-controller.js#L270-L274) is used for muxed content (audio and video in one segment) and for audio or video only streams.
[this.audioSegmentLoader_](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L278-L281) is used when the content is demuxed (audio and video in separate playlists). [this.audioSegmentLoader_](/src/playlist-controller.js#L277-L280) is used when the content is demuxed (audio and video in separate playlists).
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
MasterPlaylistController --> SourceUpdater PlaylistController --> SourceUpdater
MasterPlaylistController --> SegmentLoader PlaylistController --> SegmentLoader
``` ```
Besides options and the `sourceUpdater_` from MPC, the segment loaders are given a [playlist](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/segment-loader.js#L988). This playlist is a media playlist from the `masterPlaylistLoader_`. So looking back at our parsed manifest object: Besides options and the `sourceUpdater_` from PC, the segment loaders are given a [playlist](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/segment-loader.js#L988). This playlist is a media playlist from the `mainPlaylistLoader_`. So looking back at our parsed manifest object:
``` ```
Manifest { Manifest {
@ -174,19 +172,19 @@ VHS uses a strategy called `mediaIndex++` for choosing the next segment, see [he
If there are no seeks or rendition changes, `chooseNextRequest_` will rely on the `mediaIndex++` strategy. If there are no seeks or rendition changes, `chooseNextRequest_` will rely on the `mediaIndex++` strategy.
If there are seeks or rendition changes, then `chooseNextRequest_` will look at segment timing values via the `SyncController` (created previously in MPC), the current time, and the buffer, to determine what the next segment should be, and what it's start time should be (to position it on the timeline). If there are seeks or rendition changes, then `chooseNextRequest_` will look at segment timing values via the `SyncController` (created previously in PC), the current time, and the buffer, to determine what the next segment should be, and what it's start time should be (to position it on the timeline).
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
MasterPlaylistController --> SourceUpdater PlaylistController --> SourceUpdater
MasterPlaylistController --> SegmentLoader PlaylistController --> SegmentLoader
SegmentLoader --> SyncController SegmentLoader --> SyncController
``` ```
The `SyncController` has various [strategies](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/sync-controller.js#L16) for ensuring that different renditions, which can have different media sequence and segment timing values, can be positioned on the playback timeline successfully. (It is also be [used by MPC](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/master-playlist-controller.js#L1477) to establish a `seekable` range.) The `SyncController` has various [strategies](https://github.com/videojs/http-streaming/blob/0964cb4827d9e80aa36f2fa29e35dad92ca84111/src/sync-controller.js#L16) for ensuring that different renditions, which can have different media sequence and segment timing values, can be positioned on the playback timeline successfully. (It is also be [used by PC](/src/playlist-controller.js#L1472) to establish a `seekable` range.)
### Downloading and Appending Segments ### Downloading and Appending Segments
@ -196,10 +194,10 @@ If the buffer is not full, and a segment was chosen, then `SegmentLoader` will d
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
MasterPlaylistController --> SourceUpdater PlaylistController --> SourceUpdater
MasterPlaylistController --> SegmentLoader PlaylistController --> SegmentLoader
SegmentLoader --> SyncController SegmentLoader --> SyncController
SegmentLoader --> mediaSegmentRequest SegmentLoader --> mediaSegmentRequest
``` ```
@ -213,10 +211,10 @@ When the `SegmentLoader` receives segment data events, it can append the data to
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
MasterPlaylistController --> SourceUpdater PlaylistController --> SourceUpdater
MasterPlaylistController --> SegmentLoader PlaylistController --> SegmentLoader
SegmentLoader --> SyncController SegmentLoader --> SyncController
SegmentLoader --> mediaSegmentRequest SegmentLoader --> mediaSegmentRequest
SegmentLoader --> SourceUpdater SegmentLoader --> SourceUpdater
@ -231,10 +229,10 @@ Besides downloading segments, `mediaSegmentRequest` [decrypts](https://github.co
```mermaid ```mermaid
flowchart TD flowchart TD
VhsSourceHandler --> VhsHandler VhsSourceHandler --> VhsHandler
VhsHandler --> MasterPlaylistController VhsHandler --> PlaylistController
MasterPlaylistController --> PlaylistLoader PlaylistController --> PlaylistLoader
MasterPlaylistController --> SourceUpdater PlaylistController --> SourceUpdater
MasterPlaylistController --> SegmentLoader PlaylistController --> SegmentLoader
SegmentLoader --> SyncController SegmentLoader --> SyncController
SegmentLoader --> mediaSegmentRequest SegmentLoader --> mediaSegmentRequest
SegmentLoader --> SourceUpdater SegmentLoader --> SourceUpdater
@ -247,4 +245,4 @@ The video can start playing as soon as there's enough audio and video (for muxed
But once `SegmentLoader` does finish, it starts the process again, looking for new content. But once `SegmentLoader` does finish, it starts the process again, looking for new content.
There are other modules, and other functions of the code (e.g., blacklisting logic, ABR, etc.), but this is the most critical path of VHS, the one that allows video to play in the browser. There are other modules, and other functions of the code (e.g., excluding logic, ABR, etc.), but this is the most critical path of VHS, the one that allows video to play in the browser.

View file

@ -13,7 +13,7 @@ The [playlist loader](../src/playlist-loader.js) handles all of the details of r
During VOD playback, the loader will move quickly to the HAVE_METADATA state and then stay there unless a quality switch request sends it to SWITCHING_MEDIA while it fetches an alternate playlist. The loader enters the HAVE_CURRENT_METADATA when a live stream is detected and it's time to refresh the current media playlist to find out about new video segments. During VOD playback, the loader will move quickly to the HAVE_METADATA state and then stay there unless a quality switch request sends it to SWITCHING_MEDIA while it fetches an alternate playlist. The loader enters the HAVE_CURRENT_METADATA when a live stream is detected and it's time to refresh the current media playlist to find out about new video segments.
### HLS Tech ### HLS Tech
Currently, the HLS project integrates with [video.js](http://www.videojs.com/) as a [tech](https://github.com/videojs/video.js/blob/master/docs/guides/tech.md). That means it's responsible for providing an interface that closely mirrors the `<video>` element. You can see that implementation in [videojs-http-streaming.js](../src/videojs-http-streaming.js), the primary entry point of the project. Currently, the HLS project integrates with [video.js](http://www.videojs.com/) as a [tech](https://github.com/videojs/video.js/blob/main/docs/guides/tech.md). That means it's responsible for providing an interface that closely mirrors the `<video>` element. You can see that implementation in [videojs-http-streaming.js](../src/videojs-http-streaming.js), the primary entry point of the project.
### Transmuxing ### Transmuxing
Most browsers don't have support for the file type that HLS video segments are stored in. To get HLS playing back on those browsers, contrib-hls strings together a number of technologies: Most browsers don't have support for the file type that HLS video segments are stored in. To get HLS playing back on those browsers, contrib-hls strings together a number of technologies:

View file

@ -34,7 +34,7 @@ gets used:
![Final selection](images/bitrate-switching-4.png) ![Final selection](images/bitrate-switching-4.png)
If it turns out no rendition is acceptable based on the filtering If it turns out no rendition is acceptable based on the filtering
described above, the first encoding listed in the master playlist will described above, the first encoding listed in the main playlist will
be used. be used.
If you'd like your player to use a different set of priorities, it's If you'd like your player to use a different set of priorities, it's

View file

@ -22,19 +22,19 @@ The [DPL] is written to be as similar as possible to the [PlaylistLoader][pl]. T
![DashPlaylistLoader States](images/dash-playlist-loader-states.nomnoml.svg) ![DashPlaylistLoader States](images/dash-playlist-loader-states.nomnoml.svg)
- `HAVE_NOTHING` the state before the MPD is received and parsed. - `HAVE_NOTHING` the state before the MPD is received and parsed.
- `HAVE_MASTER` the state before a media stream is setup but the MPD has been parsed. - `HAVE_MAIN_MANIFEST` the state before a media stream is setup but the MPD has been parsed.
- `HAVE_METADATA` the state after a media stream is setup. - `HAVE_METADATA` the state after a media stream is setup.
### API ### API
- `load()` this will either start or kick the loader during playback. - `load()` this will either start or kick the loader during playback.
- `start()` this will start the [DPL] and request the MPD. - `start()` this will start the [DPL] and request the MPD.
- `parseMasterXml()` this will parse the MPD manifest and return the result. - `parseMainXml()` this will parse the MPD manifest and return the result.
- `media()` this will return the currently active media stream or set a new active media stream. - `media()` this will return the currently active media stream or set a new active media stream.
### Events ### Events
- `loadedplaylist` signals the setup of a master playlist, representing the DASH source as a whole, from the MPD; or a media playlist, representing a media stream. - `loadedplaylist` signals the setup of a main playlist, representing the DASH source as a whole, from the MPD; or a media playlist, representing a media stream.
- `loadedmetadata` signals initial setup of a media stream. - `loadedmetadata` signals initial setup of a media stream.
- `minimumUpdatePeriod` signals that a update period has ended and the MPD must be requested again. - `minimumUpdatePeriod` signals that a update period has ended and the MPD must be requested again.
- `playlistunchanged` signals that no changes have been made to a MPD. - `playlistunchanged` signals that no changes have been made to a MPD.
@ -44,7 +44,7 @@ The [DPL] is written to be as similar as possible to the [PlaylistLoader][pl]. T
### Interaction with Other Modules ### Interaction with Other Modules
![DPL with MPC and MG](images/dash-playlist-loader-mpc-mg-sequence.plantuml.png) ![DPL with PC and MG](images/dash-playlist-loader-pc-mg-sequence.puml.png)
### Special Features ### Special Features
@ -64,17 +64,17 @@ To be filled out.
### Previous Behavior ### Previous Behavior
Until version 1.9.0 of [VHS], we thought that [DPL] could skip the `HAVE_NOTHING` and `HAVE_MASTER` states, as no other XHR requests are needed once the MPD has been downloaded and parsed. However, this is incorrect as there are some Presentations that signal the use of a "Segment Index box" or `sidx`. This `sidx` references specific byte ranges in a file that could contain media or potentially other `sidx` boxes. Until version 1.9.0 of [VHS], we thought that [DPL] could skip the `HAVE_NOTHING` and `HAVE_MAIN_MANIFEST` states, as no other XHR requests are needed once the MPD has been downloaded and parsed. However, this is incorrect as there are some Presentations that signal the use of a "Segment Index box" or `sidx`. This `sidx` references specific byte ranges in a file that could contain media or potentially other `sidx` boxes.
A DASH MPD that describes a `sidx` is therefore similar to an HLS master manifest, in that the MPD contains references to something that must be requested and parsed first before references to media segments can be obtained. With this in mind, it was necessary to update the initialization and state transitions of [DPL] to allow further XHR requests to be made after the initial request for the MPD. A DASH MPD that describes a `sidx` is therefore similar to an HLS main manifest, in that the MPD contains references to something that must be requested and parsed first before references to media segments can be obtained. With this in mind, it was necessary to update the initialization and state transitions of [DPL] to allow further XHR requests to be made after the initial request for the MPD.
### Current Behavior ### Current Behavior
In [this PR](https://github.com/videojs/http-streaming/pull/386), the [DPL] was updated to go through the `HAVE_NOTHING` and `HAVE_MASTER` states before arriving at `HAVE_METADATA`. If the MPD does not contain `sidx` boxes, then this transition happens quickly after `load()` is called, spending little time in the `HAVE_MASTER` state. In [this PR](https://github.com/videojs/http-streaming/pull/386), the [DPL] was updated to go through the `HAVE_NOTHING` and `HAVE_MAIN_MANIFEST` states before arriving at `HAVE_METADATA`. If the MPD does not contain `sidx` boxes, then this transition happens quickly after `load()` is called, spending little time in the `HAVE_MAIN_MANIFEST` state.
The initial media selection for `masterPlaylistLoader` is made in the `loadedplaylist` handler located in [MasterPlaylistController][mpc]. We now use `hasPendingRequest` to determine whether to automatically select a media playlist for the `masterPlaylistLoader` as a fallback in case one is not selected by [MPC]. The child [DPL]s are created with a media playlist passed in as an argument, so this fallback is not necessary for them. Instead, that media playlist is saved and auto-selected once we enter the `HAVE_MASTER` state. The initial media selection for `mainPlaylistLoader` is made in the `loadedplaylist` handler located in [PlaylistController][pc]. We now use `hasPendingRequest` to determine whether to automatically select a media playlist for the `mainPlaylistLoader` as a fallback in case one is not selected by [PC]. The child [DPL]s are created with a media playlist passed in as an argument, so this fallback is not necessary for them. Instead, that media playlist is saved and auto-selected once we enter the `HAVE_MAIN_MANIFEST` state.
The `updateMaster` method will return `null` if no updates are found. The `updateMain` method will return `null` if no updates are found.
The `selectinitialmedia` event is not triggered until an audioPlaylistLoader (which for DASH is always a child [DPL]) has a media playlist. This is signaled by triggering `loadedmetadata` on the respective [DPL]. This event is used to initialize the [Representations API][representations] and setup EME (see [contrib-eme]). The `selectinitialmedia` event is not triggered until an audioPlaylistLoader (which for DASH is always a child [DPL]) has a media playlist. This is signaled by triggering `loadedmetadata` on the respective [DPL]. This event is used to initialize the [Representations API][representations] and setup EME (see [contrib-eme]).
@ -82,6 +82,6 @@ The `selectinitialmedia` event is not triggered until an audioPlaylistLoader (wh
[sl]: ../src/segment-loader.js [sl]: ../src/segment-loader.js
[vhs]: intro.md [vhs]: intro.md
[pl]: ../src/playlist-loader.js [pl]: ../src/playlist-loader.js
[mpc]: ../src/master-playlist-controller.js [pc]: ../src/playlist-controller.js
[representations]: ../README.md#hlsrepresentations [representations]: ../README.md#hlsrepresentations
[contrib-eme]: https://github.com/videojs/videojs-contrib-eme [contrib-eme]: https://github.com/videojs/videojs-contrib-eme

View file

@ -4,9 +4,9 @@
**Media Playlist**: This is a manifest that represents a single rendition or media stream of the source. **Media Playlist**: This is a manifest that represents a single rendition or media stream of the source.
**Master Playlist Controller**: This acts as the main controller for the playback engine. It interacts with the SegmentLoaders, PlaylistLoaders, PlaybackWatcher, etc. **Playlist Controller**: This acts as the main controller for the playback engine. It interacts with the SegmentLoaders, PlaylistLoaders, PlaybackWatcher, etc.
**Playlist Loader**: This will request the source and load the master manifest. It is also instructed by the ABR algorithm to load a media playlist or wraps a media playlist if it is provided as the source. There are more details about the playlist loader [here](./arch.md). **Playlist Loader**: This will request the source and load the main manifest. It is also instructed by the ABR algorithm to load a media playlist or wraps a media playlist if it is provided as the source. There are more details about the playlist loader [here](./arch.md).
**DASH Playlist Loader**: This will do as the PlaylistLoader does, but for DASH sources. It also handles DASH specific functionaltiy, such as refreshing the MPD according to the minimumRefreshPeriod and synchronizing to a server clock. **DASH Playlist Loader**: This will do as the PlaylistLoader does, but for DASH sources. It also handles DASH specific functionaltiy, such as refreshing the MPD according to the minimumRefreshPeriod and synchronizing to a server clock.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

View file

@ -1,12 +1,12 @@
<svg width="228" height="310" version="1.1" baseProfile="full" viewbox="0 0 228 310" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" style="font-weight:bold; font-size:10pt; font-family:'Arial', Helvetica, sans-serif;;stroke-width:2;stroke-linejoin:round;stroke-linecap:round"><text x="134" y="111" style="font-weight:normal;">load()</text> <svg width="280" height="310" version="1.1" baseProfile="full" viewbox="0 0 280 310" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" style="font-weight:bold; font-size:10pt; font-family:'Arial', Helvetica, sans-serif;;stroke-width:2;stroke-linejoin:round;stroke-linecap:round"><text x="160" y="111" style="font-weight:normal;">load()</text>
<path d="M114 81 L114 105 L114 129 L114 129 " style="stroke:#33322E;fill:none;stroke-dasharray:none;"></path> <path d="M140 81 L140 105 L140 129 L140 129 " style="stroke:#33322E;fill:none;stroke-dasharray:none;"></path>
<path d="M110.8 121 L114 125 L117.2 121 L114 129 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path> <path d="M136.8 121 L140 125 L143.2 121 L140 129 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path>
<text x="134" y="211" style="font-weight:normal;">media()</text> <text x="160" y="211" style="font-weight:normal;">media()</text>
<path d="M114 181 L114 205 L114 229 L114 229 " style="stroke:#33322E;fill:none;stroke-dasharray:none;"></path> <path d="M140 181 L140 205 L140 229 L140 229 " style="stroke:#33322E;fill:none;stroke-dasharray:none;"></path>
<path d="M110.8 221 L114 225 L117.2 221 L114 229 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path> <path d="M136.8 221 L140 225 L143.2 221 L140 229 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path>
<rect x="37" y="30" height="50" width="154" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="63" y="30" height="50" width="154" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="57" y="60" style="">HAVE_NOTHING</text> <text x="83" y="60" style="">HAVE_NOTHING</text>
<rect x="40" y="130" height="50" width="149" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="30" y="130" height="50" width="220" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="60" y="160" style="">HAVE_MASTER</text> <text x="50" y="160" style="">HAVE_MAIN_MANIFEST</text>
<rect x="30" y="230" height="50" width="168" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="56" y="230" height="50" width="168" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="50" y="260" style="">HAVE_METADATA</text></svg> <text x="76" y="260" style="">HAVE_METADATA</text></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

View file

@ -15,8 +15,8 @@
<path d="M155.2 489 L152 485 L148.8 489 L152 481 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path> <path d="M155.2 489 L152 485 L148.8 489 L152 481 Z" style="stroke:#33322E;fill:#33322E;stroke-dasharray:none;"></path>
<rect x="75" y="30" height="50" width="154" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="75" y="30" height="50" width="154" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="95" y="60" style="">HAVE_NOTHING</text> <text x="95" y="60" style="">HAVE_NOTHING</text>
<rect x="78" y="130" height="50" width="149" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="42" y="130" height="50" width="220" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="98" y="160" style="">HAVE_MASTER</text> <text x="62" y="160" style="">HAVE_MAIN_MANIFEST</text>
<rect x="56" y="230" height="50" width="192" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="56" y="230" height="50" width="192" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>
<text x="76.3" y="260" style="">SWITCHING_MEDIA</text> <text x="76.3" y="260" style="">SWITCHING_MEDIA</text>
<rect x="68" y="330" height="50" width="168" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect> <rect x="68" y="330" height="50" width="168" style="stroke:#33322E;fill:#eee8d5;stroke-dasharray:none;"></rect>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before After
Before After

View file

@ -1,119 +0,0 @@
@startuml
header DashPlaylistLoader sequences
title DashPlaylistLoader sequences: Master Manifest with Alternate Audio
Participant "MasterPlaylistController" as MPC #red
Participant "MasterDashPlaylistLoader" as MPL #blue
Participant "mainSegmentLoader" as SL #blue
Participant "AudioDashPlaylistLoader" as APL #green
Participant "audioSegmentLoader" as ASL #green
Participant "external server" as ext #brown
Participant "mpdParser" as parser #orange
Participant "mediaGroups" as MG #purple
Participant Tech #lightblue
== Initialization ==
MPC -> MPL : construct MasterPlaylistLoader
MPC -> MPL: load()
== Requesting Master Manifest ==
MPL -> MPL : start()
MPL -> ext: xhr request for master manifest
ext -> MPL : response with master manifest
MPL -> parser: parse manifest
parser -> MPL: object representing manifest
note over MPL #lightblue: trigger 'loadedplaylist'
== Requesting Video Manifest ==
note over MPL #lightblue: handling loadedplaylist
MPL -> MPL: media(x)
alt if no sidx
note over MPL #lightgray: zero delay to fake network request
else if sidx
break
MPL -> ext: request sidx
end
end
note over MPL #lightblue: trigger 'loadedmetadata' on master loader [T1]
note over MPL #lightblue: handling 'loadedmetadata'
opt vod and preload !== 'none'
MPL -> SL: playlist()
MPL -> SL: load()
end
== Initializing Media Groups, Choosing Active Tracks ==
MPL -> MG: setupMediaGroups()
MG -> MG: initialize()
== Initializing Alternate Audio Loader ==
MG -> APL: create child playlist loader for alt audio
MG -> MG: activeGroup and audio variant selected
MG -> MG: enable activeTrack, onTrackChanged()
MG -> ASL: reset audio segment loader
== Requesting Alternate Audio Manifest ==
MG -> MG: startLoaders()
MG -> APL: load()
APL -> APL: start()
APL -> APL: zero delay to fake network request
break finish pending tasks
MG -> Tech: add audioTrack
MPL -> MPC: setupSourceBuffers_()
MPL -> MPC: setupFirstPlay()
loop mainSegmentLoader.monitorBufferTick_()
SL -> ext: requests media segments
ext -> SL: response with media segment bytes
end
end
APL -> APL: zero delay over
APL -> APL: media(x)
alt if no sidx
note over APL #lightgray: zero delay to fake network request
else if sidx
break
MPL -> ext: request sidx
end
end
== Requesting Alternate Audio Segments ==
note over APL #lightblue: trigger 'loadedplaylist'
note over APL #lightblue: handling 'loadedplaylist'
APL -> ASL: playlist()
note over ASL #lightblue: trigger 'loadedmetadata' [T2]
note over APL #lightblue: handling 'loadedmetadata'
APL -> ASL: playlist()
APL -> ASL: load()
loop audioSegmentLoader.monitorBufferTick_()
ASL -> ext: requests media segments
ext -> ASL: response with media segment bytes
end
@enduml

View file

@ -17,5 +17,5 @@
#.label: align=center visual=none italic #.label: align=center visual=none italic
[HAVE_NOTHING] load()-> [HAVE_MASTER] [HAVE_NOTHING] load()-> [HAVE_MAIN_MANIFEST]
[HAVE_MASTER] media()-> [HAVE_METADATA] [HAVE_MAIN_MANIFEST] media()-> [HAVE_METADATA]

View file

@ -1,246 +0,0 @@
@startuml
header PlaylistLoader sequences
title PlaylistLoader sequences: Master Manifest and Alternate Audio
Participant "MasterPlaylistController" as MPC #red
Participant "MasterPlaylistLoader" as MPL #blue
Participant "mainSegmentLoader" as SL #blue
Participant "AudioPlaylistLoader" as APL #green
Participant "audioSegmentLoader" as ASL #green
Participant "external server" as ext #brown
Participant "m3u8Parser" as parser #orange
Participant "mediaGroups" as MG #purple
Participant Tech #lightblue
== Initialization ==
group MasterPlaylistController.constructor()
MPC -> MPL : setting up MasterPlaylistLoader
note left #lightyellow
sets up mediaupdatetimeout
handler for live playlist staleness
end note
note over MPL #lightgray: state = 'HAVE_NOTHING'
MPC -> MPL: load()
end
group MasterPlaylistLoader.load()
MPL -> MPL : start()
note left #lightyellow: not started yet
== Requesting Master Manifest ==
group start()
note over MPL #lightgray: started = true
MPL -> ext: xhr request for master manifest
ext -> MPL : response with master manifest
MPL -> parser: parse master manifest
parser -> MPL: object representing manifest
MPL -> MPL: set loader's master playlist
note over MPL #lightgray: state = 'HAVE_MASTER'
note over MPL #lightblue: trigger 'loadedplaylist' on master loader
== Requesting Video Manifest ==
group 'loadedplaylist' handler
note over MPL #lightblue: handling loadedplaylist
MPL -> MPL : media()
note left #lightgray: select initial (video) playlist
note over MPL #lightyellow: state = 'SWITCHING_MEDIA'
group media()
MPL -> ext : request child manifest
ext -> MPL: child manifest returned
MPL -> MPL: haveMetadata()
note over MPL #lightyellow: state = 'HAVE_METADATA'
group haveMetadata()
MPL -> parser: parse child manifest
parser -> MPL: object representing the child manifest
note over MPL #lightyellow
update master and media playlists
end note
opt live
MPL -> MPL: setup mediaupdatetimeout
end
note over MPL #lightblue
trigger 'loadedplaylist' on master loader.
This does not end up requesting segments
at this point.
end note
group MasterPlaylistLoader 'loadedplaylist' handler
MPL -> MPL : setup durationchange handler
end
end
== Requesting Video Segments ==
note over MPL #lightblue: trigger 'loadedmetadata'
group 'loadedmetadata' handler
note over MPL #lightblue: handling 'loadedmetadata'
opt vod and preload !== 'none'
MPL -> SL: playlist()
note over SL #lightyellow: updates playlist
MPL -> SL: load()
note right #lightgray
This does nothing as mimeTypes
have not been set yet.
end note
end
MPL -> MG: setupMediaGroups()
== Initializing Media Groups, Choosing Active Tracks ==
group MediaGroups.setupMediaGroups()
group initialize()
MG -> APL: create child playlist loader for alt audio
note over APL #lightyellow: state = 'HAVE_NOTHING'
note left #lightgray
setup 'loadedmetadata' and 'loadedplaylist' listeners
on child alt audio playlist loader
end note
MG -> Tech: add audioTracks
end
MG -> MG: activeGroup and audio variant selected
MG -> MG: enable activeTrack, onTrackChanged()
note left #lightgray
There is no activePlaylistLoader at this point,
but there is an audio playlistLoader
end note
group onTrackChanged()
MG -> SL: reset mainSegmentLoader
note left #lightgray: Clears buffer, aborts all inflight requests
== Requesting Alternate Audio Manifest ==
MG -> MG: startLoaders()
group startLoaders()
note over MG #lightyellow
activePlaylistLoader = AudioPlaylistLoader
end note
MG -> APL: load()
end
group AudioPlaylistLoader.load()
APL -> APL: start()
group alt start()
note over APL #lightyellow: started = true
APL -> ext: request alt audio media manifest
break MasterPlaylistLoader 'loadedmetadata' handler
MPL -> MPC: setupSourceBuffers()
note left #lightgray
This will set mimeType.
Segments can be loaded from now on.
end note
MPL -> MPC: setupFirstPlay()
note left #lightgray
Immediate exit since the player
is paused
end note
end
ext -> APL: responds with child manifest
APL -> parser: parse child manifest
parser -> APL: object representing child manifest returned
note over APL #lightyellow: state = 'HAVE_MASTER'
note left #lightgray: Infer a master playlist
APL -> APL: haveMetadata()
note over APL #lightyellow: state = 'HAVE_METADATA'
group haveMetadata()
APL -> parser: parsing the child manifest again
parser -> APL: returning object representing child manifest
note over APL #lightyellow
update master and media references
end note
== Requesting Alternate Audio Segments ==
note over APL #lightblue: trigger 'loadedplaylist'
group 'loadedplaylist' handler
note over APL #lightblue: handling 'loadedplaylist'
APL -> ASL: playlist()
note over ASL #lightyellow: set playlist
end
end
note over APL #lightblue: trigger 'loadedmetadata'
group 'loadedmetadata' handler
note over APL #lightblue: handling 'loadedmetadata'
APL -> ASL: playlist()
APL -> ASL: load()
loop audioSegmentLoader.load()
ASL -> ext: requests media segments
ext -> ASL: response with media segment bytes
end
end
end
end
end
end
end
end
end
end
@enduml

View file

@ -1,114 +0,0 @@
@startuml
header PlaylistLoader sequences
title PlaylistLoader sequences: Master Manifest and Alternate Audio
Participant "MasterPlaylistController" as MPC #red
Participant "MasterPlaylistLoader" as MPL #blue
Participant "mainSegmentLoader" as SL #blue
Participant "AudioPlaylistLoader" as APL #green
Participant "audioSegmentLoader" as ASL #green
Participant "external server" as ext #brown
Participant "m3u8Parser" as parser #orange
Participant "mediaGroups" as MG #purple
Participant Tech #lightblue
== Initialization ==
MPC -> MPL : construct MasterPlaylistLoader
MPC -> MPL: load()
MPL -> MPL : start()
== Requesting Master Manifest ==
MPL -> ext: xhr request for master manifest
ext -> MPL : response with master manifest
MPL -> parser: parse master manifest
parser -> MPL: object representing manifest
note over MPL #lightblue: trigger 'loadedplaylist'
== Requesting Video Manifest ==
note over MPL #lightblue: handling loadedplaylist
MPL -> MPL : media()
MPL -> ext : request child manifest
ext -> MPL: child manifest returned
MPL -> parser: parse child manifest
parser -> MPL: object representing the child manifest
note over MPL #lightblue: trigger 'loadedplaylist'
note over MPL #lightblue: handleing 'loadedplaylist'
MPL -> SL: playlist()
MPL -> SL: load()
== Requesting Video Segments ==
note over MPL #lightblue: trigger 'loadedmetadata'
note over MPL #lightblue: handling 'loadedmetadata'
opt vod and preload !== 'none'
MPL -> SL: playlist()
MPL -> SL: load()
end
MPL -> MG: setupMediaGroups()
== Initializing Media Groups, Choosing Active Tracks ==
MG -> APL: create child playlist loader for alt audio
MG -> MG: activeGroup and audio variant selected
MG -> MG: enable activeTrack, onTrackChanged()
MG -> SL: reset mainSegmentLoader
== Requesting Alternate Audio Manifest ==
MG -> MG: startLoaders()
MG -> APL: load()
APL -> APL: start()
APL -> ext: request alt audio media manifest
break finish pending tasks
MG -> Tech: add audioTracks
MPL -> MPC: setupSourceBuffers()
MPL -> MPC: setupFirstPlay()
loop on monitorBufferTick
SL -> ext: requests media segments
ext -> SL: response with media segment bytes
end
end
ext -> APL: responds with child manifest
APL -> parser: parse child manifest
parser -> APL: object representing child manifest returned
== Requesting Alternate Audio Segments ==
note over APL #lightblue: trigger 'loadedplaylist'
note over APL #lightblue: handling 'loadedplaylist'
APL -> ASL: playlist()
note over APL #lightblue: trigger 'loadedmetadata'
note over APL #lightblue: handling 'loadedmetadata'
APL -> ASL: playlist()
APL -> ASL: load()
loop audioSegmentLoader.load()
ASL -> ext: requests media segments
ext -> ASL: response with media segment bytes
end
@enduml

View file

@ -17,8 +17,8 @@
#.label: align=center visual=none italic #.label: align=center visual=none italic
[HAVE_NOTHING] load()-> [HAVE_MASTER] [HAVE_NOTHING] load()-> [HAVE_MAIN_MANIFEST]
[HAVE_MASTER] media()-> [SWITCHING_MEDIA] [HAVE_MAIN_MANIFEST] media()-> [SWITCHING_MEDIA]
[SWITCHING_MEDIA] media()/ start()-> [HAVE_METADATA] [SWITCHING_MEDIA] media()/ start()-> [HAVE_METADATA]
[HAVE_METADATA] <--> [<label> mediaupdatetimeout] [HAVE_METADATA] <--> [<label> mediaupdatetimeout]

View file

@ -53,7 +53,7 @@ The first change was to request pieces of a segment. There are a few approaches
*Plain text MIME type* was chosen because of its wide support. It provides a mechanism to access progressive bytes downloaded on [XMLHttpRequest progress events](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onprogress). *Plain text MIME type* was chosen because of its wide support. It provides a mechanism to access progressive bytes downloaded on [XMLHttpRequest progress events](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/onprogress).
This change was made in [media-segment-request](https://github.com/videojs/http-streaming/blob/master/src/media-segment-request.js). This change was made in [media-segment-request](/src/media-segment-request.js).
### Transmux and Append Segment Pieces ### Transmux and Append Segment Pieces
@ -93,7 +93,7 @@ The transmuxing logic moved to a new module called segment-transmuxer, which wra
##### mux.js ##### mux.js
While most of the [mux.js pipeline](https://github.com/videojs/mux.js/blob/master/docs/diagram.png) supports pushing pieces of data (and should support LHLS by default), its "flushes" to send transmuxed data back to the caller expected full segments. While most of the [mux.js pipeline](/docs/diagram.png) supports pushing pieces of data (and should support LHLS by default), its "flushes" to send transmuxed data back to the caller expected full segments.
Much of the pipeline was reused, however, the top level audio and video segment streams, as well as the entry point, were rewritten so that instead of providing a full segment on flushes, each frame of video was provided individually (audio frames still flush as a group). The new concept of partial flushes was added into the pipeline to handle this case. Much of the pipeline was reused, however, the top level audio and video segment streams, as well as the entry point, were rewritten so that instead of providing a full segment on flushes, each frame of video was provided individually (audio frames still flush as a group). The new concept of partial flushes was added into the pipeline to handle this case.

View file

@ -33,4 +33,4 @@ With the transmux before append and LHLS changes, we only append video init segm
### Test Changes ### Test Changes
Some tests were removed because they were no longer relevant after the change to creating source buffers later. For instance, `waits for both main and audio loaders to finish before calling endOfStream if main loader starting media is unknown` no longer can be tested by waiting for an audio loader response and checking for end of stream, as the test will time out since MasterPlaylistController will wait for track info from the main loader before the source buffers are created. That condition is checked elsewhere. Some tests were removed because they were no longer relevant after the change to creating source buffers later. For instance, `waits for both main and audio loaders to finish before calling endOfStream if main loader starting media is unknown` no longer can be tested by waiting for an audio loader response and checking for end of stream, as the test will time out since PlaylistController will wait for track info from the main loader before the source buffers are created. That condition is checked elsewhere.

View file

@ -65,19 +65,19 @@ Corresponding AudioTrackList when media-group-1 is used (before any tracks have
## Startup (how tracks are added and used) ## Startup (how tracks are added and used)
> AudioTrack & AudioTrackList live in video.js > AudioTrack & AudioTrackList live in video.js
1. `HLS` creates a `MasterPlaylistController` and watches for the `loadedmetadata` event 1. `HLS` creates a `PlaylistController` and watches for the `loadedmetadata` event
1. `HLS` parses the m3u8 using the `MasterPlaylistController` 1. `HLS` parses the m3u8 using the `PlaylistController`
1. `MasterPlaylistController` creates a `PlaylistLoader` for the master m3u8 1. `PlaylistController` creates a `PlaylistLoader` for the main m3u8
1. `MasterPlaylistController` creates `PlaylistLoader`s for every audio playlist 1. `PlaylistController` creates `PlaylistLoader`s for every audio playlist
1. `MasterPlaylistController` creates a `SegmentLoader` for the main m3u8 1. `PlaylistController` creates a `SegmentLoader` for the main m3u8
1. `MasterPlaylistController` creates a `SegmentLoader` for a potential audio playlist 1. `PlaylistController` creates a `SegmentLoader` for a potential audio playlist
1. `HLS` sees the `loadedmetadata` and finds the currently selected MediaGroup and all the metadata 1. `HLS` sees the `loadedmetadata` and finds the currently selected MediaGroup and all the metadata
1. `HLS` removes all `AudioTrack`s from the `AudioTrackList` 1. `HLS` removes all `AudioTrack`s from the `AudioTrackList`
1. `HLS` created `AudioTrack`s for the MediaGroup and adds them to the `AudioTrackList` 1. `HLS` created `AudioTrack`s for the MediaGroup and adds them to the `AudioTrackList`
1. `HLS` calls `MasterPlaylistController`s `useAudio` with no arguments (causes it to use the currently enabled audio) 1. `HLS` calls `PlaylistController`s `useAudio` with no arguments (causes it to use the currently enabled audio)
1. `MasterPlaylistController` turns off the current audio `PlaylistLoader` if it is on 1. `PlaylistController` turns off the current audio `PlaylistLoader` if it is on
1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio 1. `PlaylistController` maps the `label` to the `PlaylistLoader` containing the audio
1. `MasterPlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (master or audio only) 1. `PlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (main or audio only)
1. `MediaSource`/`mux.js` determine how to mux 1. `MediaSource`/`mux.js` determine how to mux
## How tracks are switched ## How tracks are switched
@ -88,9 +88,9 @@ Corresponding AudioTrackList when media-group-1 is used (before any tracks have
1. `AudioTrackList` enables the new `Audiotrack` and disables all others 1. `AudioTrackList` enables the new `Audiotrack` and disables all others
1. `AudioTrackList` triggers a `changed` event 1. `AudioTrackList` triggers a `changed` event
1. `HLS` sees the `changed` event and finds the newly enabled `AudioTrack` 1. `HLS` sees the `changed` event and finds the newly enabled `AudioTrack`
1. `HLS` sends the `label` for the new `AudioTrack` to `MasterPlaylistController`s `useAudio` function 1. `HLS` sends the `label` for the new `AudioTrack` to `PlaylistController`s `useAudio` function
1. `MasterPlaylistController` turns off the current audio `PlaylistLoader` if it is on 1. `PlaylistController` turns off the current audio `PlaylistLoader` if it is on
1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio 1. `PlaylistController` maps the `label` to the `PlaylistLoader` containing the audio
1. `MasterPlaylistController` maps the `label` to the `PlaylistLoader` containing the audio 1. `PlaylistController` maps the `label` to the `PlaylistLoader` containing the audio
1. `MasterPlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (master or audio only) 1. `PlaylistController` turns on that `PlaylistLoader` and the Corresponding `SegmentLoader` (main or audio only)
1. `MediaSource`/`mux.js` determine how to mux 1. `MediaSource`/`mux.js` determine how to mux

View file

@ -9,7 +9,7 @@ The [PlaylistLoader][pl] (PL) is responsible for requesting m3u8s, parsing them
1. To request an m3u8. 1. To request an m3u8.
2. To parse a m3u8 into a format [videojs-http-streaming][vhs] can understand. 2. To parse a m3u8 into a format [videojs-http-streaming][vhs] can understand.
3. To allow selection of a specific media stream. 3. To allow selection of a specific media stream.
4. To refresh a live master m3u8 for changes. 4. To refresh a live m3u8 for changes.
## Design ## Design
@ -18,7 +18,7 @@ The [PlaylistLoader][pl] (PL) is responsible for requesting m3u8s, parsing them
![PlaylistLoader States](images/playlist-loader-states.nomnoml.svg) ![PlaylistLoader States](images/playlist-loader-states.nomnoml.svg)
- `HAVE_NOTHING` the state before the m3u8 is received and parsed. - `HAVE_NOTHING` the state before the m3u8 is received and parsed.
- `HAVE_MASTER` the state before a media manifest is parsed and setup but after the master manifest has been parsed and setup. - `HAVE_MAIN_MANIFEST` the state before a media manifest is parsed and setup but after the main manifest has been parsed and setup.
- `HAVE_METADATA` the state after a media stream is setup. - `HAVE_METADATA` the state after a media stream is setup.
- `SWITCHING_MEDIA` the intermediary state we go though while changing to a newly selected media playlist - `SWITCHING_MEDIA` the intermediary state we go though while changing to a newly selected media playlist
- `HAVE_CURRENT_METADATA` a temporary state after requesting a refresh of the live manifest and before receiving the update - `HAVE_CURRENT_METADATA` a temporary state after requesting a refresh of the live manifest and before receiving the update
@ -31,7 +31,7 @@ The [PlaylistLoader][pl] (PL) is responsible for requesting m3u8s, parsing them
### Events ### Events
- `loadedplaylist` signals the setup of a master playlist, representing the HLS source as a whole, from the m3u8; or a media playlist, representing a media stream. - `loadedplaylist` signals the setup of a main playlist, representing the HLS source as a whole, from the m3u8; or a media playlist, representing a media stream.
- `loadedmetadata` signals initial setup of a media stream. - `loadedmetadata` signals initial setup of a media stream.
- `playlistunchanged` signals that no changes have been made to a m3u8. - `playlistunchanged` signals that no changes have been made to a m3u8.
- `mediaupdatetimeout` signals that a live m3u8 and media stream must be refreshed. - `mediaupdatetimeout` signals that a live m3u8 and media stream must be refreshed.
@ -40,7 +40,7 @@ The [PlaylistLoader][pl] (PL) is responsible for requesting m3u8s, parsing them
### Interaction with Other Modules ### Interaction with Other Modules
![PL with MPC and MG](images/playlist-loader-mpc-mg-sequence.plantuml.png) ![PL with PC and MG](images/playlist-loader-pc-mg-sequence.puml.png)
[pl]: ../src/playlist-loader.js [pl]: ../src/playlist-loader.js
[sl]: ../src/segment-loader.js [sl]: ../src/segment-loader.js

View file

@ -2,7 +2,7 @@
## Other troubleshooting guides ## Other troubleshooting guides
For issues around data embedded into media segments (e.g., 608 captions), see the [mux.js troubleshooting guide](https://github.com/videojs/mux.js/blob/master/docs/troubleshooting.md). For issues around data embedded into media segments (e.g., 608 captions), see the [mux.js troubleshooting guide](https://github.com/videojs/mux.js/blob/main/docs/troubleshooting.md).
## Tools ## Tools

View file

@ -27,21 +27,6 @@
</style> </style>
</head> </head>
<body class="m-4"> <body class="m-4">
<script>
// if we're on IE, load up the load index page
var result = (/MSIE\s(\d+)\.\d/).exec(navigator.userAgent);
var version = result && parseFloat(result[1]);
if (!version && (/Trident\/7.0/i).test(navigator.userAgent) && (/rv:11.0/).test(navigator.userAgent)) {
// IE 11 has a different user agent string than other IE versions
version = 11.0;
}
if (version) {
window.location.href = './old-index.html';
}
</script>
<header class="container-fluid"> <header class="container-fluid">
<a href="https://github.com/videojs/http-streaming" class="d-flex align-items-center pb-3 mb-5 border-bottom" style="height: 4em"> <a href="https://github.com/videojs/http-streaming" class="d-flex align-items-center pb-3 mb-5 border-bottom" style="height: 4em">
<img src="./logo.svg" alt="VHS logo showcasing a VHS tape with the Video.js logo on the label" class="rounded mh-100"> <img src="./logo.svg" alt="VHS logo showcasing a VHS tape with the Video.js logo on the label" class="rounded mh-100">

View file

@ -1,6 +1,6 @@
{ {
"name": "@videojs/http-streaming", "name": "@videojs/http-streaming",
"version": "2.16.0", "version": "3.0.2",
"description": "Play back HLS and DASH with Video.js, even where it's not natively supported", "description": "Play back HLS and DASH with Video.js, even where it's not natively supported",
"main": "dist/videojs-http-streaming.cjs.js", "main": "dist/videojs-http-streaming.cjs.js",
"module": "dist/videojs-http-streaming.es.js", "module": "dist/videojs-http-streaming.es.js",
@ -18,10 +18,12 @@
"docs": "npm-run-all docs:*", "docs": "npm-run-all docs:*",
"docs:api": "jsdoc src -g plugins/markdown -r -d docs/api", "docs:api": "jsdoc src -g plugins/markdown -r -d docs/api",
"docs:toc": "doctoc --notitle README.md", "docs:toc": "doctoc --notitle README.md",
"docs:images": "node ./scripts/create-docs-images.js", "docs:images:puml": "for i in docs/images/sources/*.puml; do npx water-uml export $i -f png -o \"docs/images/$(echo $i | cut -d '/' -f 4)\"; done",
"docs:images:nomnoml": "node ./scripts/create-docs-images.js",
"docs:images": "npm-run-all -p docs:images:puml docs:images:nomnoml",
"clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist", "clean": "shx rm -rf ./dist ./test/dist && shx mkdir -p ./dist ./test/dist",
"lint": "vjsstandard", "lint": "vjsstandard",
"prepublishOnly": "npm-run-all build-prod && vjsverify --verbose", "prepublishOnly": "npm-run-all build-prod && vjsverify --verbose --skip-es-check",
"start": "npm-run-all -p server watch", "start": "npm-run-all -p server watch",
"server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch", "server": "karma start scripts/karma.conf.js --singleRun=false --auto-watch",
"test": "npm-run-all lint build-test && karma start scripts/karma.conf.js", "test": "npm-run-all lint build-test && karma start scripts/karma.conf.js",
@ -56,16 +58,16 @@
], ],
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@videojs/vhs-utils": "3.0.5", "@videojs/vhs-utils": "4.0.0",
"aes-decrypter": "3.1.3", "aes-decrypter": "4.0.1",
"global": "^4.4.0", "global": "^4.4.0",
"m3u8-parser": "4.8.0", "m3u8-parser": "^6.0.0",
"mpd-parser": "^0.22.1", "mpd-parser": "^1.0.1",
"mux.js": "6.0.1", "mux.js": "6.3.0",
"video.js": "^6 || ^7" "video.js": "^7 || ^8"
}, },
"peerDependencies": { "peerDependencies": {
"video.js": "^6 || ^7" "video.js": "^7 || ^8"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^2.3.4", "@rollup/plugin-replace": "^2.3.4",
@ -73,33 +75,28 @@
"@videojs/generator-helpers": "~3.1.0", "@videojs/generator-helpers": "~3.1.0",
"bootstrap": "^5.1.0", "bootstrap": "^5.1.0",
"d3": "^3.4.8", "d3": "^3.4.8",
"es5-shim": "^4.5.13", "jsdoc": "^3.6.11",
"es6-shim": "^0.35.5", "karma": "^6.4.0",
"jsdoc": "~3.6.6",
"karma": "^5.2.3",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"lodash-compat": "^3.10.0", "lodash-compat": "^3.10.0",
"nomnoml": "^0.3.0", "nomnoml": "^1.5.2",
"rollup": "^2.36.1", "rollup": "^2.36.1",
"rollup-plugin-worker-factory": "0.5.7", "rollup-plugin-worker-factory": "0.5.7",
"shelljs": "^0.8.4", "shelljs": "^0.8.5",
"sinon": "^8.1.1", "sinon": "^8.1.1",
"url-toolkit": "^2.2.1", "url-toolkit": "^2.2.1",
"videojs-contrib-eme": "^3.8.1", "videojs-contrib-eme": "^5.0.1",
"videojs-contrib-quality-levels": "^2.0.4", "videojs-contrib-quality-levels": "^2.0.4",
"videojs-generate-karma-config": "^7.1.0", "videojs-generate-karma-config": "^8.0.1",
"videojs-generate-rollup-config": "^6.2.2", "videojs-generate-rollup-config": "^7.0.0",
"videojs-generator-verify": "~3.0.1", "videojs-generator-verify": "~3.0.1",
"videojs-http-source-selector": "^1.1.6", "videojs-http-source-selector": "^1.1.6",
"videojs-standard": "^9.0.0" "videojs-standard": "^9.0.0",
"water-plant-uml": "^2.0.2"
}, },
"generator-videojs-plugin": { "generator-videojs-plugin": {
"version": "7.6.3" "version": "7.6.3"
}, },
"browserslist": [
"defaults",
"ie 11"
],
"engines": { "engines": {
"node": ">=8", "node": ">=8",
"npm": ">=5" "npm": ">=5"

View file

@ -21,7 +21,7 @@
rep.playlist.disabled = rep.id !== id; rep.playlist.disabled = rep.id !== id;
}); });
window.mpc.fastQualityChange_(); window.pc.fastQualityChange_();
}); });
var isManifestObjectType = function(url) { var isManifestObjectType = function(url) {
return (/application\/vnd\.videojs\.vhs\+json/).test(url); return (/application\/vnd\.videojs\.vhs\+json/).test(url);
@ -270,7 +270,7 @@
width: rep.width width: rep.width
}); });
if (window.mpc.media().id === rep.id) { if (window.pc.media().id === rep.id) {
selectedIndex = i; selectedIndex = i;
} }
@ -390,29 +390,29 @@
return; return;
} }
videoBufferedStat.textContent = getBuffered(player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer && videoBufferedStat.textContent = getBuffered(player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer &&
player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer.buffered); player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer.buffered);
// demuxed audio // demuxed audio
var audioBuffer = getBuffered(player.tech(true).vhs.masterPlaylistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer && var audioBuffer = getBuffered(player.tech(true).vhs.playlistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer &&
player.tech(true).vhs.masterPlaylistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer.buffered); player.tech(true).vhs.playlistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer.buffered);
// muxed audio // muxed audio
if (!audioBuffer) { if (!audioBuffer) {
audioBuffer = getBuffered(player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer && audioBuffer = getBuffered(player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer &&
player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer.buffered); player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer.buffered);
} }
audioBufferedStat.textContent = audioBuffer; audioBufferedStat.textContent = audioBuffer;
if (player.tech(true).vhs.masterPlaylistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer) { if (player.tech(true).vhs.playlistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer) {
audioTimestampOffset.textContent = player.tech(true).vhs.masterPlaylistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer.timestampOffset; audioTimestampOffset.textContent = player.tech(true).vhs.playlistController_.audioSegmentLoader_.sourceUpdater_.audioBuffer.timestampOffset;
} else if (player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer) { } else if (player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer) {
audioTimestampOffset.textContent = player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer.timestampOffset; audioTimestampOffset.textContent = player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.audioBuffer.timestampOffset;
} }
if (player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer) { if (player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer) {
videoTimestampOffset.textContent = player.tech(true).vhs.masterPlaylistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer.timestampOffset; videoTimestampOffset.textContent = player.tech(true).vhs.playlistController_.mainSegmentLoader_.sourceUpdater_.videoBuffer.timestampOffset;
} }
// bitrates // bitrates
@ -512,6 +512,20 @@
}); });
}); });
[
'llhls'
].forEach(function(name) {
stateEls[name].checked = true;
});
[
'exact-manifest-timings',
'pixel-diff-selector',
'buffer-water'
].forEach(function(name) {
stateEls[name].checked = false;
});
stateEls.debug.addEventListener('change', function(event) { stateEls.debug.addEventListener('change', function(event) {
saveState(); saveState();
window.videojs.log.level(event.target.checked ? 'debug' : 'info'); window.videojs.log.level(event.target.checked ? 'debug' : 'info');
@ -566,10 +580,10 @@
html5: { html5: {
vhs: { vhs: {
overrideNative: getInputValue(stateEls['override-native']), overrideNative: getInputValue(stateEls['override-native']),
experimentalBufferBasedABR: getInputValue(stateEls['buffer-water']), bufferBasedABR: getInputValue(stateEls['buffer-water']),
experimentalLLHLS: getInputValue(stateEls.llhls), llhls: getInputValue(stateEls.llhls),
experimentalExactManifestTimings: getInputValue(stateEls['exact-manifest-timings']), exactManifestTimings: getInputValue(stateEls['exact-manifest-timings']),
experimentalLeastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']), leastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']),
useNetworkInformationApi: getInputValue(stateEls['network-info']), useNetworkInformationApi: getInputValue(stateEls['network-info']),
useDtsForTimestampOffset: getInputValue(stateEls['dts-offset']) useDtsForTimestampOffset: getInputValue(stateEls['dts-offset'])
} }
@ -635,13 +649,13 @@
player.on('loadedmetadata', function() { player.on('loadedmetadata', function() {
if (player.tech_.vhs) { if (player.tech_.vhs) {
window.vhs = player.tech_.vhs; window.vhs = player.tech_.vhs;
window.mpc = player.tech_.vhs.masterPlaylistController_; window.pc = player.tech_.vhs.playlistController_;
window.mpc.masterPlaylistLoader_.on('mediachange', regenerateRepresentations); window.pc.mainPlaylistLoader_.on('mediachange', regenerateRepresentations);
regenerateRepresentations(); regenerateRepresentations();
} else { } else {
window.vhs = null; window.vhs = null;
window.mpc = null; window.pc = null;
} }
}); });
cb(player); cb(player);

View file

@ -10,13 +10,10 @@ module.exports = function(config) {
preferHeadless: false, preferHeadless: false,
browsers(aboutToRun) { browsers(aboutToRun) {
return aboutToRun.filter(function(launcherName) { return aboutToRun.filter(function(launcherName) {
return !(/^(Safari|Chromium)/).test(launcherName); return !(/(Safari|Chromium)/).test(launcherName);
}); });
}, },
files(defaults) { files(defaults) {
defaults.unshift('node_modules/es5-shim/es5-shim.js');
defaults.unshift('node_modules/es6-shim/es6-shim.js');
defaults.splice( defaults.splice(
defaults.indexOf('node_modules/video.js/dist/video.js'), defaults.indexOf('node_modules/video.js/dist/video.js'),
1, 1,
@ -26,9 +23,6 @@ module.exports = function(config) {
return defaults; return defaults;
}, },
browserstackLaunchers(defaults) { browserstackLaunchers(defaults) {
delete defaults.bsSafariMojave;
delete defaults.bsSafariElCapitan;
// do not run on browserstack for coverage // do not run on browserstack for coverage
if (CI_TEST_TYPE === 'coverage') { if (CI_TEST_TYPE === 'coverage') {
defaults = {}; defaults = {};

View file

@ -1,550 +0,0 @@
/* global window document */
/* eslint-disable vars-on-top, no-var, object-shorthand, no-console */
(function(window) {
var representationsEl = document.getElementById('representations');
representationsEl.addEventListener('change', function() {
var selectedIndex = representationsEl.selectedIndex;
if (!selectedIndex || selectedIndex < 1 || !window.vhs) {
return;
}
var selectedOption = representationsEl.options[representationsEl.selectedIndex];
if (!selectedOption) {
return;
}
var id = selectedOption.value;
window.vhs.representations().forEach(function(rep) {
rep.playlist.disabled = rep.id !== id;
});
window.mpc.fastQualityChange_();
});
var isManifestObjectType = function(url) {
return (/application\/vnd\.videojs\.vhs\+json/).test(url);
};
var hlsOptGroup = document.querySelector('[label="hls"]');
var dashOptGroup = document.querySelector('[label="dash"]');
var drmOptGroup = document.querySelector('[label="drm"]');
var liveOptGroup = document.querySelector('[label="live"]');
var llliveOptGroup = document.querySelector('[label="low latency live"]');
var manifestOptGroup = document.querySelector('[label="json manifest object"]');
var sourceList;
var hlsDataManifest;
var dashDataManifest;
var addSourcesToDom = function() {
if (!sourceList || !hlsDataManifest || !dashDataManifest) {
return;
}
sourceList.forEach(function(source) {
var option = document.createElement('option');
option.innerText = source.name;
option.value = source.uri;
if (source.keySystems) {
option.setAttribute('data-key-systems', JSON.stringify(source.keySystems, null, 2));
}
if (source.mimetype) {
option.setAttribute('data-mimetype', source.mimetype);
}
if (source.features.indexOf('low-latency') !== -1) {
llliveOptGroup.appendChild(option);
} else if (source.features.indexOf('live') !== -1) {
liveOptGroup.appendChild(option);
} else if (source.keySystems) {
drmOptGroup.appendChild(option);
} else if (source.mimetype === 'application/x-mpegurl') {
hlsOptGroup.appendChild(option);
} else if (source.mimetype === 'application/dash+xml') {
dashOptGroup.appendChild(option);
}
});
var hlsOption = document.createElement('option');
var dashOption = document.createElement('option');
dashOption.innerText = 'Dash Manifest Object Test, does not survive page reload';
dashOption.value = 'data:application/vnd.videojs.vhs+json,' + dashDataManifest;
hlsOption.innerText = 'HLS Manifest Object Test, does not survive page reload';
hlsOption.value = 'data:application/vnd.videojs.vhs+json,' + hlsDataManifest;
manifestOptGroup.appendChild(hlsOption);
manifestOptGroup.appendChild(dashOption);
};
var sourcesXhr = new window.XMLHttpRequest();
sourcesXhr.addEventListener('load', function() {
sourceList = JSON.parse(sourcesXhr.responseText);
addSourcesToDom();
});
sourcesXhr.open('GET', './scripts/sources.json');
sourcesXhr.send();
var hlsManifestXhr = new window.XMLHttpRequest();
hlsManifestXhr.addEventListener('load', function() {
hlsDataManifest = hlsManifestXhr.responseText;
addSourcesToDom();
});
hlsManifestXhr.open('GET', './scripts/hls-manifest-object.json');
hlsManifestXhr.send();
var dashManifestXhr = new window.XMLHttpRequest();
dashManifestXhr.addEventListener('load', function() {
dashDataManifest = dashManifestXhr.responseText;
addSourcesToDom();
});
dashManifestXhr.open('GET', './scripts/dash-manifest-object.json');
dashManifestXhr.send();
// all relevant elements
var urlButton = document.getElementById('load-url');
var sources = document.getElementById('load-source');
var stateEls = {};
var getInputValue = function(el) {
if (el.type === 'url' || el.type === 'text' || el.nodeName.toLowerCase() === 'textarea') {
if (isManifestObjectType(el.value)) {
return '';
}
return encodeURIComponent(el.value);
} else if (el.type === 'select-one') {
return el.options[el.selectedIndex].value;
} else if (el.type === 'checkbox') {
return el.checked;
}
console.warn('unhandled input type ' + el.type);
return '';
};
var setInputValue = function(el, value) {
if (el.type === 'url' || el.type === 'text' || el.nodeName.toLowerCase() === 'textarea') {
el.value = decodeURIComponent(value);
} else if (el.type === 'select-one') {
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].value === value) {
el.options[i].selected = true;
}
}
} else {
// get the `value` into a Boolean.
el.checked = JSON.parse(value);
}
};
var newEvent = function(name) {
var event;
if (typeof window.Event === 'function') {
event = new window.Event(name);
} else {
event = document.createEvent('Event');
event.initEvent(name, true, true);
}
return event;
};
// taken from video.js
var getFileExtension = function(path) {
var splitPathRe;
var pathParts;
if (typeof path === 'string') {
splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]*?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
pathParts = splitPathRe.exec(path);
if (pathParts) {
return pathParts.pop().toLowerCase();
}
}
return '';
};
var saveState = function() {
var query = '';
if (!window.history.replaceState) {
return;
}
Object.keys(stateEls).forEach(function(elName) {
var symbol = query.length ? '&' : '?';
query += symbol + elName + '=' + getInputValue(stateEls[elName]);
});
window.history.replaceState({}, 'vhs demo', query);
};
window.URLSearchParams = window.URLSearchParams || function(locationSearch) {
this.get = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(locationSearch);
return results ? decodeURIComponent(results[1]) : null;
};
};
// eslint-disable-next-line
var loadState = function() {
var params = new window.URLSearchParams(window.location.search);
return Object.keys(stateEls).reduce(function(acc, elName) {
acc[elName] = typeof params.get(elName) !== 'object' ? params.get(elName) : getInputValue(stateEls[elName]);
return acc;
}, {});
};
// eslint-disable-next-line
var reloadScripts = function(urls, cb) {
var el = document.getElementById('reload-scripts');
if (!el) {
el = document.createElement('div');
el.id = 'reload-scripts';
document.body.appendChild(el);
}
while (el.firstChild) {
el.removeChild(el.firstChild);
}
var loaded = [];
var checkDone = function() {
if (loaded.length === urls.length) {
cb();
}
};
urls.forEach(function(url) {
var script = document.createElement('script');
// scripts marked as defer will be loaded asynchronously but will be executed in the order they are in the DOM
script.defer = true;
// dynamically created scripts are async by default unless otherwise specified
// async scripts are loaded asynchronously but also executed as soon as they are loaded
// we want to load them in the order they are added therefore we want to turn off async
script.async = false;
script.src = url;
script.onload = function() {
loaded.push(url);
checkDone();
};
el.appendChild(script);
});
};
var regenerateRepresentations = function() {
while (representationsEl.firstChild) {
representationsEl.removeChild(representationsEl.firstChild);
}
var selectedIndex;
window.vhs.representations().forEach(function(rep, i) {
var option = document.createElement('option');
option.value = rep.id;
option.innerText = JSON.stringify({
id: rep.id,
videoCodec: rep.codecs.video,
audioCodec: rep.codecs.audio,
bandwidth: rep.bandwidth,
heigth: rep.heigth,
width: rep.width
});
if (window.mpc.media().id === rep.id) {
selectedIndex = i;
}
representationsEl.appendChild(option);
});
representationsEl.selectedIndex = selectedIndex;
};
[
'debug',
'autoplay',
'muted',
'minified',
'sync-workers',
'liveui',
'llhls',
'url',
'type',
'keysystems',
'buffer-water',
'exact-manifest-timings',
'pixel-diff-selector',
'override-native',
'preload',
'mirror-source'
].forEach(function(name) {
stateEls[name] = document.getElementById(name);
});
window.startDemo = function(cb) {
var state = loadState();
Object.keys(state).forEach(function(elName) {
setInputValue(stateEls[elName], state[elName]);
});
Array.prototype.forEach.call(sources.options, function(s, i) {
if (s.value === state.url) {
sources.selectedIndex = i;
}
});
stateEls.muted.addEventListener('change', function(event) {
saveState();
window.player.muted(event.target.checked);
});
stateEls.autoplay.addEventListener('change', function(event) {
saveState();
window.player.autoplay(event.target.checked);
});
// stateEls that reload the player and scripts
[
'mirror-source',
'sync-workers',
'preload',
'llhls',
'buffer-water',
'override-native',
'liveui',
'pixel-diff-selector',
'exact-manifest-timings'
].forEach(function(name) {
stateEls[name].addEventListener('change', function(event) {
saveState();
stateEls.minified.dispatchEvent(newEvent('change'));
});
});
stateEls.debug.addEventListener('change', function(event) {
saveState();
window.videojs.log.level(event.target.checked ? 'debug' : 'info');
});
stateEls.minified.addEventListener('change', function(event) {
var urls = [
'node_modules/video.js/dist/alt/video.core',
'node_modules/videojs-contrib-eme/dist/videojs-contrib-eme',
'node_modules/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels',
'node_modules/videojs-http-source-selector/dist/videojs-http-source-selector'
].map(function(url) {
return url + (event.target.checked ? '.min' : '') + '.js';
});
if (stateEls['sync-workers'].checked) {
urls.push('dist/videojs-http-streaming-sync-workers.js');
} else {
urls.push('dist/videojs-http-streaming' + (event.target.checked ? '.min' : '') + '.js');
}
saveState();
if (window.player) {
window.player.dispose();
delete window.player;
}
if (window.videojs) {
delete window.videojs;
}
reloadScripts(urls, function() {
var player;
var fixture = document.getElementById('player-fixture');
var videoEl = document.createElement('video-js');
videoEl.setAttribute('controls', '');
videoEl.setAttribute('preload', stateEls.preload.options[stateEls.preload.selectedIndex].value || 'auto');
videoEl.className = 'vjs-default-skin';
fixture.appendChild(videoEl);
var mirrorSource = getInputValue(stateEls['mirror-source']);
player = window.player = window.videojs(videoEl, {
plugins: {
httpSourceSelector: {
default: 'auto'
}
},
liveui: stateEls.liveui.checked,
enableSourceset: mirrorSource,
html5: {
vhs: {
overrideNative: getInputValue(stateEls['override-native']),
experimentalBufferBasedABR: getInputValue(stateEls['buffer-water']),
experimentalLLHLS: getInputValue(stateEls.llhls),
experimentalExactManifestTimings: getInputValue(stateEls['exact-manifest-timings']),
experimentalLeastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector'])
}
}
});
player.on('sourceset', function() {
var source = player.currentSource();
if (source.keySystems) {
var copy = JSON.parse(JSON.stringify(source.keySystems));
// have to delete pssh as it will often make keySystems too big
// for a uri
Object.keys(copy).forEach(function(key) {
if (copy[key].hasOwnProperty('pssh')) {
delete copy[key].pssh;
}
});
stateEls.keysystems.value = JSON.stringify(copy, null, 2);
}
if (source.src) {
stateEls.url.value = encodeURI(source.src);
}
if (source.type) {
stateEls.type.value = source.type;
}
saveState();
});
player.width(640);
player.height(264);
// configure videojs-contrib-eme
player.eme();
stateEls.debug.dispatchEvent(newEvent('change'));
stateEls.muted.dispatchEvent(newEvent('change'));
stateEls.autoplay.dispatchEvent(newEvent('change'));
// run the load url handler for the intial source
if (stateEls.url.value) {
urlButton.dispatchEvent(newEvent('click'));
} else {
sources.dispatchEvent(newEvent('change'));
}
player.on('loadedmetadata', function() {
if (player.tech_.vhs) {
window.vhs = player.tech_.vhs;
window.mpc = player.tech_.vhs.masterPlaylistController_;
window.mpc.masterPlaylistLoader_.on('mediachange', regenerateRepresentations);
regenerateRepresentations();
} else {
window.vhs = null;
window.mpc = null;
}
});
cb(player);
});
});
var urlButtonClick = function(event) {
var ext;
var type = stateEls.type.value;
// reset type if it's a manifest object's type
if (type === 'application/vnd.videojs.vhs+json') {
type = '';
}
if (isManifestObjectType(stateEls.url.value)) {
type = 'application/vnd.videojs.vhs+json';
}
if (!type.trim()) {
ext = getFileExtension(stateEls.url.value);
if (ext === 'mpd') {
type = 'application/dash+xml';
} else if (ext === 'm3u8') {
type = 'application/x-mpegURL';
}
}
saveState();
var source = {
src: stateEls.url.value,
type: type
};
if (stateEls.keysystems.value) {
source.keySystems = JSON.parse(stateEls.keysystems.value);
}
sources.selectedIndex = -1;
Array.prototype.forEach.call(sources.options, function(s, i) {
if (s.value === stateEls.url.value) {
sources.selectedIndex = i;
}
});
window.player.src(source);
};
urlButton.addEventListener('click', urlButtonClick);
urlButton.addEventListener('tap', urlButtonClick);
sources.addEventListener('change', function(event) {
var selectedOption = sources.options[sources.selectedIndex];
if (!selectedOption) {
return;
}
var src = selectedOption.value;
stateEls.url.value = src;
stateEls.type.value = selectedOption.getAttribute('data-mimetype');
stateEls.keysystems.value = selectedOption.getAttribute('data-key-systems');
urlButton.dispatchEvent(newEvent('click'));
});
stateEls.url.addEventListener('keyup', function(event) {
if (event.key === 'Enter') {
urlButton.click();
}
});
stateEls.url.addEventListener('input', function(event) {
if (stateEls.type.value.length) {
stateEls.type.value = '';
}
});
stateEls.type.addEventListener('keyup', function(event) {
if (event.key === 'Enter') {
urlButton.click();
}
});
// run the change handler for the first time
stateEls.minified.dispatchEvent(newEvent('change'));
};
}(window));

View file

@ -7,7 +7,7 @@ import {
} from 'mpd-parser'; } from 'mpd-parser';
import { import {
refreshDelay, refreshDelay,
updateMaster as updatePlaylist, updateMain as updatePlaylist,
isPlaylistUnchanged isPlaylistUnchanged
} from './playlist-loader'; } from './playlist-loader';
import { resolveUrl, resolveManifestRedirect } from './resolve-url'; import { resolveUrl, resolveManifestRedirect } from './resolve-url';
@ -16,13 +16,14 @@ import { segmentXhrHeaders } from './xhr';
import window from 'global/window'; import window from 'global/window';
import { import {
forEachMediaGroup, forEachMediaGroup,
addPropertiesToMaster addPropertiesToMain
} from './manifest'; } from './manifest';
import containerRequest from './util/container-request.js'; import containerRequest from './util/container-request.js';
import {toUint8} from '@videojs/vhs-utils/es/byte-helpers'; import {toUint8} from '@videojs/vhs-utils/es/byte-helpers';
import logger from './util/logger'; import logger from './util/logger';
import {merge} from './util/vjs-compat';
const { EventTarget, mergeOptions } = videojs; const { EventTarget } = videojs;
const dashPlaylistUnchanged = function(a, b) { const dashPlaylistUnchanged = function(a, b) {
if (!isPlaylistUnchanged(a, b)) { if (!isPlaylistUnchanged(a, b)) {
@ -86,11 +87,24 @@ const dashPlaylistUnchanged = function(a, b) {
}; };
/** /**
* Parses the master XML string and updates playlist URI references. * Use the representation IDs from the mpd object to create groupIDs, the NAME is set to mandatory representation
* ID in the parser. This allows for continuous playout across periods with the same representation IDs
* (continuous periods as defined in DASH-IF 3.2.12). This is assumed in the mpd-parser as well. If we want to support
* periods without continuous playback this function may need modification as well as the parser.
*/
const dashGroupId = (type, group, label, playlist) => {
// If the manifest somehow does not have an ID (non-dash compliant), use the label.
const playlistId = playlist.attributes.NAME || label;
return `placeholder-uri-${type}-${group}-${playlistId}`;
};
/**
* Parses the main XML string and updates playlist URI references.
* *
* @param {Object} config * @param {Object} config
* Object of arguments * Object of arguments
* @param {string} config.masterXml * @param {string} config.mainXml
* The mpd XML * The mpd XML
* @param {string} config.srcUrl * @param {string} config.srcUrl
* The mpd URL * The mpd URL
@ -101,49 +115,65 @@ const dashPlaylistUnchanged = function(a, b) {
* @return {Object} * @return {Object}
* The parsed mpd manifest object * The parsed mpd manifest object
*/ */
export const parseMasterXml = ({ export const parseMainXml = ({
masterXml, mainXml,
srcUrl, srcUrl,
clientOffset, clientOffset,
sidxMapping, sidxMapping,
previousManifest previousManifest
}) => { }) => {
const manifest = parseMpd(masterXml, { const manifest = parseMpd(mainXml, {
manifestUri: srcUrl, manifestUri: srcUrl,
clientOffset, clientOffset,
sidxMapping, sidxMapping,
previousManifest previousManifest
}); });
addPropertiesToMaster(manifest, srcUrl); addPropertiesToMain(manifest, srcUrl, dashGroupId);
return manifest; return manifest;
}; };
/** /**
* Returns a new master manifest that is the result of merging an updated master manifest * Removes any mediaGroup labels that no longer exist in the newMain
*
* @param {Object} update
* The previous mpd object being updated
* @param {Object} newMain
* The new mpd object
*/
const removeOldMediaGroupLabels = (update, newMain) => {
forEachMediaGroup(update, (properties, type, group, label) => {
if (!(label in newMain.mediaGroups[type][group])) {
delete update.mediaGroups[type][group][label];
}
});
};
/**
* Returns a new main manifest that is the result of merging an updated main manifest
* into the original version. * into the original version.
* *
* @param {Object} oldMaster * @param {Object} oldMain
* The old parsed mpd object * The old parsed mpd object
* @param {Object} newMaster * @param {Object} newMain
* The updated parsed mpd object * The updated parsed mpd object
* @return {Object} * @return {Object}
* A new object representing the original master manifest with the updated media * A new object representing the original main manifest with the updated media
* playlists merged in * playlists merged in
*/ */
export const updateMaster = (oldMaster, newMaster, sidxMapping) => { export const updateMain = (oldMain, newMain, sidxMapping) => {
let noChanges = true; let noChanges = true;
let update = mergeOptions(oldMaster, { let update = merge(oldMain, {
// These are top level properties that can be updated // These are top level properties that can be updated
duration: newMaster.duration, duration: newMain.duration,
minimumUpdatePeriod: newMaster.minimumUpdatePeriod, minimumUpdatePeriod: newMain.minimumUpdatePeriod,
timelineStarts: newMaster.timelineStarts timelineStarts: newMain.timelineStarts
}); });
// First update the playlists in playlist list // First update the playlists in playlist list
for (let i = 0; i < newMaster.playlists.length; i++) { for (let i = 0; i < newMain.playlists.length; i++) {
const playlist = newMaster.playlists[i]; const playlist = newMain.playlists[i];
if (playlist.sidx) { if (playlist.sidx) {
const sidxKey = generateSidxKey(playlist.sidx); const sidxKey = generateSidxKey(playlist.sidx);
@ -162,21 +192,31 @@ export const updateMaster = (oldMaster, newMaster, sidxMapping) => {
} }
// Then update media group playlists // Then update media group playlists
forEachMediaGroup(newMaster, (properties, type, group, label) => { forEachMediaGroup(newMain, (properties, type, group, label) => {
if (properties.playlists && properties.playlists.length) { if (properties.playlists && properties.playlists.length) {
const id = properties.playlists[0].id; const id = properties.playlists[0].id;
const playlistUpdate = updatePlaylist(update, properties.playlists[0], dashPlaylistUnchanged); const playlistUpdate = updatePlaylist(update, properties.playlists[0], dashPlaylistUnchanged);
if (playlistUpdate) { if (playlistUpdate) {
update = playlistUpdate; update = playlistUpdate;
// add new mediaGroup label if it doesn't exist and assign the new mediaGroup.
if (!(label in update.mediaGroups[type][group])) {
update.mediaGroups[type][group][label] = properties;
}
// update the playlist reference within media groups // update the playlist reference within media groups
update.mediaGroups[type][group][label].playlists[0] = update.playlists[id]; update.mediaGroups[type][group][label].playlists[0] = update.playlists[id];
noChanges = false; noChanges = false;
} }
} }
}); });
if (newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod) { // remove mediaGroup labels and references that no longer exist in the newMain
removeOldMediaGroupLabels(update, newMain);
if (newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod) {
noChanges = false; noChanges = false;
} }
@ -235,18 +275,18 @@ export const compareSidxEntry = (playlists, oldSidxMapping) => {
* *
* The method is exported for testing * The method is exported for testing
* *
* @param {Object} master the parsed mpd XML returned via mpd-parser * @param {Object} main the parsed mpd XML returned via mpd-parser
* @param {Object} oldSidxMapping the SIDX to compare against * @param {Object} oldSidxMapping the SIDX to compare against
*/ */
export const filterChangedSidxMappings = (master, oldSidxMapping) => { export const filterChangedSidxMappings = (main, oldSidxMapping) => {
const videoSidx = compareSidxEntry(master.playlists, oldSidxMapping); const videoSidx = compareSidxEntry(main.playlists, oldSidxMapping);
let mediaGroupSidx = videoSidx; let mediaGroupSidx = videoSidx;
forEachMediaGroup(master, (properties, mediaType, groupKey, labelKey) => { forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
if (properties.playlists && properties.playlists.length) { if (properties.playlists && properties.playlists.length) {
const playlists = properties.playlists; const playlists = properties.playlists;
mediaGroupSidx = mergeOptions( mediaGroupSidx = merge(
mediaGroupSidx, mediaGroupSidx,
compareSidxEntry(playlists, oldSidxMapping) compareSidxEntry(playlists, oldSidxMapping)
); );
@ -260,19 +300,18 @@ export default class DashPlaylistLoader extends EventTarget {
// DashPlaylistLoader must accept either a src url or a playlist because subsequent // DashPlaylistLoader must accept either a src url or a playlist because subsequent
// playlist loader setups from media groups will expect to be able to pass a playlist // playlist loader setups from media groups will expect to be able to pass a playlist
// (since there aren't external URLs to media playlists with DASH) // (since there aren't external URLs to media playlists with DASH)
constructor(srcUrlOrPlaylist, vhs, options = { }, masterPlaylistLoader) { constructor(srcUrlOrPlaylist, vhs, options = { }, mainPlaylistLoader) {
super(); super();
this.masterPlaylistLoader_ = masterPlaylistLoader || this; this.mainPlaylistLoader_ = mainPlaylistLoader || this;
if (!masterPlaylistLoader) { if (!mainPlaylistLoader) {
this.isMaster_ = true; this.isMain_ = true;
} }
const { withCredentials = false, handleManifestRedirects = false } = options; const { withCredentials = false } = options;
this.vhs_ = vhs; this.vhs_ = vhs;
this.withCredentials = withCredentials; this.withCredentials = withCredentials;
this.handleManifestRedirects = handleManifestRedirects;
if (!srcUrlOrPlaylist) { if (!srcUrlOrPlaylist) {
throw new Error('A non-empty playlist URL or object is required'); throw new Error('A non-empty playlist URL or object is required');
@ -293,12 +332,12 @@ export default class DashPlaylistLoader extends EventTarget {
this.logger_ = logger('DashPlaylistLoader'); this.logger_ = logger('DashPlaylistLoader');
// initialize the loader state // initialize the loader state
// The masterPlaylistLoader will be created with a string // The mainPlaylistLoader will be created with a string
if (this.isMaster_) { if (this.isMain_) {
this.masterPlaylistLoader_.srcUrl = srcUrlOrPlaylist; this.mainPlaylistLoader_.srcUrl = srcUrlOrPlaylist;
// TODO: reset sidxMapping between period changes // TODO: reset sidxMapping between period changes
// once multi-period is refactored // once multi-period is refactored
this.masterPlaylistLoader_.sidxMapping_ = {}; this.mainPlaylistLoader_.sidxMapping_ = {};
} else { } else {
this.childPlaylist_ = srcUrlOrPlaylist; this.childPlaylist_ = srcUrlOrPlaylist;
} }
@ -340,21 +379,21 @@ export default class DashPlaylistLoader extends EventTarget {
const sidxKey = playlist.sidx && generateSidxKey(playlist.sidx); const sidxKey = playlist.sidx && generateSidxKey(playlist.sidx);
// playlist lacks sidx or sidx segments were added to this playlist already. // playlist lacks sidx or sidx segments were added to this playlist already.
if (!playlist.sidx || !sidxKey || this.masterPlaylistLoader_.sidxMapping_[sidxKey]) { if (!playlist.sidx || !sidxKey || this.mainPlaylistLoader_.sidxMapping_[sidxKey]) {
// keep this function async // keep this function async
this.mediaRequest_ = window.setTimeout(() => cb(false), 0); this.mediaRequest_ = window.setTimeout(() => cb(false), 0);
return; return;
} }
// resolve the segment URL relative to the playlist // resolve the segment URL relative to the playlist
const uri = resolveManifestRedirect(this.handleManifestRedirects, playlist.sidx.resolvedUri); const uri = resolveManifestRedirect(playlist.sidx.resolvedUri);
const fin = (err, request) => { const fin = (err, request) => {
if (this.requestErrored_(err, request, startingState)) { if (this.requestErrored_(err, request, startingState)) {
return; return;
} }
const sidxMapping = this.masterPlaylistLoader_.sidxMapping_; const sidxMapping = this.mainPlaylistLoader_.sidxMapping_;
let sidx; let sidx;
try { try {
@ -389,7 +428,7 @@ export default class DashPlaylistLoader extends EventTarget {
response: '', response: '',
playlist, playlist,
internal: true, internal: true,
blacklistDuration: Infinity, playlistExclusionDuration: Infinity,
// MEDIA_ERR_NETWORK // MEDIA_ERR_NETWORK
code: 2 code: 2
}, request); }, request);
@ -426,9 +465,9 @@ export default class DashPlaylistLoader extends EventTarget {
this.mediaRequest_ = null; this.mediaRequest_ = null;
this.minimumUpdatePeriodTimeout_ = null; this.minimumUpdatePeriodTimeout_ = null;
if (this.masterPlaylistLoader_.createMupOnMedia_) { if (this.mainPlaylistLoader_.createMupOnMedia_) {
this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_); this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
this.masterPlaylistLoader_.createMupOnMedia_ = null; this.mainPlaylistLoader_.createMupOnMedia_ = null;
} }
this.off(); this.off();
@ -463,10 +502,10 @@ export default class DashPlaylistLoader extends EventTarget {
// find the playlist object if the target playlist has been specified by URI // find the playlist object if the target playlist has been specified by URI
if (typeof playlist === 'string') { if (typeof playlist === 'string') {
if (!this.masterPlaylistLoader_.master.playlists[playlist]) { if (!this.mainPlaylistLoader_.main.playlists[playlist]) {
throw new Error('Unknown playlist URI: ' + playlist); throw new Error('Unknown playlist URI: ' + playlist);
} }
playlist = this.masterPlaylistLoader_.master.playlists[playlist]; playlist = this.mainPlaylistLoader_.main.playlists[playlist];
} }
const mediaChange = !this.media_ || playlist.id !== this.media_.id; const mediaChange = !this.media_ || playlist.id !== this.media_.id;
@ -511,7 +550,7 @@ export default class DashPlaylistLoader extends EventTarget {
// fire loadedmetadata the first time a media playlist is loaded // fire loadedmetadata the first time a media playlist is loaded
// to resolve setup of media groups // to resolve setup of media groups
if (startingState === 'HAVE_MASTER') { if (startingState === 'HAVE_MAIN_MANIFEST') {
this.trigger('loadedmetadata'); this.trigger('loadedmetadata');
} else { } else {
// trigger media change if the active media has been updated // trigger media change if the active media has been updated
@ -520,16 +559,16 @@ export default class DashPlaylistLoader extends EventTarget {
} }
pause() { pause() {
if (this.masterPlaylistLoader_.createMupOnMedia_) { if (this.mainPlaylistLoader_.createMupOnMedia_) {
this.off('loadedmetadata', this.masterPlaylistLoader_.createMupOnMedia_); this.off('loadedmetadata', this.mainPlaylistLoader_.createMupOnMedia_);
this.masterPlaylistLoader_.createMupOnMedia_ = null; this.mainPlaylistLoader_.createMupOnMedia_ = null;
} }
this.stopRequest(); this.stopRequest();
window.clearTimeout(this.mediaUpdateTimeout); window.clearTimeout(this.mediaUpdateTimeout);
this.mediaUpdateTimeout = null; this.mediaUpdateTimeout = null;
if (this.isMaster_) { if (this.isMain_) {
window.clearTimeout(this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_); window.clearTimeout(this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_);
this.masterPlaylistLoader_.minimumUpdatePeriodTimeout_ = null; this.mainPlaylistLoader_.minimumUpdatePeriodTimeout_ = null;
} }
if (this.state === 'HAVE_NOTHING') { if (this.state === 'HAVE_NOTHING') {
// If we pause the loader before any data has been retrieved, its as if we never // If we pause the loader before any data has been retrieved, its as if we never
@ -559,11 +598,11 @@ export default class DashPlaylistLoader extends EventTarget {
} }
if (media && !media.endList) { if (media && !media.endList) {
// Check to see if this is the master loader and the MUP was cleared (this happens // Check to see if this is the main loader and the MUP was cleared (this happens
// when the loader was paused). `media` should be set at this point since one is always // when the loader was paused). `media` should be set at this point since one is always
// set during `start()`. // set during `start()`.
if (this.isMaster_ && !this.minimumUpdatePeriodTimeout_) { if (this.isMain_ && !this.minimumUpdatePeriodTimeout_) {
// Trigger minimumUpdatePeriod to refresh the master manifest // Trigger minimumUpdatePeriod to refresh the main manifest
this.trigger('minimumUpdatePeriod'); this.trigger('minimumUpdatePeriod');
// Since there was no prior minimumUpdatePeriodTimeout it should be recreated // Since there was no prior minimumUpdatePeriodTimeout it should be recreated
this.updateMinimumUpdatePeriodTimeout_(); this.updateMinimumUpdatePeriodTimeout_();
@ -577,25 +616,25 @@ export default class DashPlaylistLoader extends EventTarget {
start() { start() {
this.started = true; this.started = true;
// We don't need to request the master manifest again // We don't need to request the main manifest again
// Call this asynchronously to match the xhr request behavior below // Call this asynchronously to match the xhr request behavior below
if (!this.isMaster_) { if (!this.isMain_) {
this.mediaRequest_ = window.setTimeout(() => this.haveMaster_(), 0); this.mediaRequest_ = window.setTimeout(() => this.haveMain_(), 0);
return; return;
} }
this.requestMaster_((req, masterChanged) => { this.requestMain_((req, mainChanged) => {
this.haveMaster_(); this.haveMain_();
if (!this.hasPendingRequest() && !this.media_) { if (!this.hasPendingRequest() && !this.media_) {
this.media(this.masterPlaylistLoader_.master.playlists[0]); this.media(this.mainPlaylistLoader_.main.playlists[0]);
} }
}); });
} }
requestMaster_(cb) { requestMain_(cb) {
this.request = this.vhs_.xhr({ this.request = this.vhs_.xhr({
uri: this.masterPlaylistLoader_.srcUrl, uri: this.mainPlaylistLoader_.srcUrl,
withCredentials: this.withCredentials withCredentials: this.withCredentials
}, (error, req) => { }, (error, req) => {
if (this.requestErrored_(error, req)) { if (this.requestErrored_(error, req)) {
@ -605,55 +644,55 @@ export default class DashPlaylistLoader extends EventTarget {
return; return;
} }
const masterChanged = req.responseText !== this.masterPlaylistLoader_.masterXml_; const mainChanged = req.responseText !== this.mainPlaylistLoader_.mainXml_;
this.masterPlaylistLoader_.masterXml_ = req.responseText; this.mainPlaylistLoader_.mainXml_ = req.responseText;
if (req.responseHeaders && req.responseHeaders.date) { if (req.responseHeaders && req.responseHeaders.date) {
this.masterLoaded_ = Date.parse(req.responseHeaders.date); this.mainLoaded_ = Date.parse(req.responseHeaders.date);
} else { } else {
this.masterLoaded_ = Date.now(); this.mainLoaded_ = Date.now();
} }
this.masterPlaylistLoader_.srcUrl = resolveManifestRedirect(this.handleManifestRedirects, this.masterPlaylistLoader_.srcUrl, req); this.mainPlaylistLoader_.srcUrl = resolveManifestRedirect(this.mainPlaylistLoader_.srcUrl, req);
if (masterChanged) { if (mainChanged) {
this.handleMaster_(); this.handleMain_();
this.syncClientServerClock_(() => { this.syncClientServerClock_(() => {
return cb(req, masterChanged); return cb(req, mainChanged);
}); });
return; return;
} }
return cb(req, masterChanged); return cb(req, mainChanged);
}); });
} }
/** /**
* Parses the master xml for UTCTiming node to sync the client clock to the server * Parses the main xml for UTCTiming node to sync the client clock to the server
* clock. If the UTCTiming node requires a HEAD or GET request, that request is made. * clock. If the UTCTiming node requires a HEAD or GET request, that request is made.
* *
* @param {Function} done * @param {Function} done
* Function to call when clock sync has completed * Function to call when clock sync has completed
*/ */
syncClientServerClock_(done) { syncClientServerClock_(done) {
const utcTiming = parseUTCTiming(this.masterPlaylistLoader_.masterXml_); const utcTiming = parseUTCTiming(this.mainPlaylistLoader_.mainXml_);
// No UTCTiming element found in the mpd. Use Date header from mpd request as the // No UTCTiming element found in the mpd. Use Date header from mpd request as the
// server clock // server clock
if (utcTiming === null) { if (utcTiming === null) {
this.masterPlaylistLoader_.clientOffset_ = this.masterLoaded_ - Date.now(); this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
return done(); return done();
} }
if (utcTiming.method === 'DIRECT') { if (utcTiming.method === 'DIRECT') {
this.masterPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now(); this.mainPlaylistLoader_.clientOffset_ = utcTiming.value - Date.now();
return done(); return done();
} }
this.request = this.vhs_.xhr({ this.request = this.vhs_.xhr({
uri: resolveUrl(this.masterPlaylistLoader_.srcUrl, utcTiming.value), uri: resolveUrl(this.mainPlaylistLoader_.srcUrl, utcTiming.value),
method: utcTiming.method, method: utcTiming.method,
withCredentials: this.withCredentials withCredentials: this.withCredentials
}, (error, req) => { }, (error, req) => {
@ -665,7 +704,7 @@ export default class DashPlaylistLoader extends EventTarget {
if (error) { if (error) {
// sync request failed, fall back to using date header from mpd // sync request failed, fall back to using date header from mpd
// TODO: log warning // TODO: log warning
this.masterPlaylistLoader_.clientOffset_ = this.masterLoaded_ - Date.now(); this.mainPlaylistLoader_.clientOffset_ = this.mainLoaded_ - Date.now();
return done(); return done();
} }
@ -675,7 +714,7 @@ export default class DashPlaylistLoader extends EventTarget {
if (!req.responseHeaders || !req.responseHeaders.date) { if (!req.responseHeaders || !req.responseHeaders.date) {
// expected date header not preset, fall back to using date header from mpd // expected date header not preset, fall back to using date header from mpd
// TODO: log warning // TODO: log warning
serverTime = this.masterLoaded_; serverTime = this.mainLoaded_;
} else { } else {
serverTime = Date.parse(req.responseHeaders.date); serverTime = Date.parse(req.responseHeaders.date);
} }
@ -683,17 +722,17 @@ export default class DashPlaylistLoader extends EventTarget {
serverTime = Date.parse(req.responseText); serverTime = Date.parse(req.responseText);
} }
this.masterPlaylistLoader_.clientOffset_ = serverTime - Date.now(); this.mainPlaylistLoader_.clientOffset_ = serverTime - Date.now();
done(); done();
}); });
} }
haveMaster_() { haveMain_() {
this.state = 'HAVE_MASTER'; this.state = 'HAVE_MAIN_MANIFEST';
if (this.isMaster_) { if (this.isMain_) {
// We have the master playlist at this point, so // We have the main playlist at this point, so
// trigger this to allow MasterPlaylistController // trigger this to allow PlaylistController
// to make an initial playlist selection // to make an initial playlist selection
this.trigger('loadedplaylist'); this.trigger('loadedplaylist');
} else if (!this.media_) { } else if (!this.media_) {
@ -703,42 +742,42 @@ export default class DashPlaylistLoader extends EventTarget {
} }
} }
handleMaster_() { handleMain_() {
// clear media request // clear media request
this.mediaRequest_ = null; this.mediaRequest_ = null;
const oldMaster = this.masterPlaylistLoader_.master; const oldMain = this.mainPlaylistLoader_.main;
let newMaster = parseMasterXml({ let newMain = parseMainXml({
masterXml: this.masterPlaylistLoader_.masterXml_, mainXml: this.mainPlaylistLoader_.mainXml_,
srcUrl: this.masterPlaylistLoader_.srcUrl, srcUrl: this.mainPlaylistLoader_.srcUrl,
clientOffset: this.masterPlaylistLoader_.clientOffset_, clientOffset: this.mainPlaylistLoader_.clientOffset_,
sidxMapping: this.masterPlaylistLoader_.sidxMapping_, sidxMapping: this.mainPlaylistLoader_.sidxMapping_,
previousManifest: oldMaster previousManifest: oldMain
}); });
// if we have an old master to compare the new master against // if we have an old main to compare the new main against
if (oldMaster) { if (oldMain) {
newMaster = updateMaster(oldMaster, newMaster, this.masterPlaylistLoader_.sidxMapping_); newMain = updateMain(oldMain, newMain, this.mainPlaylistLoader_.sidxMapping_);
} }
// only update master if we have a new master // only update main if we have a new main
this.masterPlaylistLoader_.master = newMaster ? newMaster : oldMaster; this.mainPlaylistLoader_.main = newMain ? newMain : oldMain;
const location = this.masterPlaylistLoader_.master.locations && this.masterPlaylistLoader_.master.locations[0]; const location = this.mainPlaylistLoader_.main.locations && this.mainPlaylistLoader_.main.locations[0];
if (location && location !== this.masterPlaylistLoader_.srcUrl) { if (location && location !== this.mainPlaylistLoader_.srcUrl) {
this.masterPlaylistLoader_.srcUrl = location; this.mainPlaylistLoader_.srcUrl = location;
} }
if (!oldMaster || (newMaster && newMaster.minimumUpdatePeriod !== oldMaster.minimumUpdatePeriod)) { if (!oldMain || (newMain && newMain.minimumUpdatePeriod !== oldMain.minimumUpdatePeriod)) {
this.updateMinimumUpdatePeriodTimeout_(); this.updateMinimumUpdatePeriodTimeout_();
} }
return Boolean(newMaster); return Boolean(newMain);
} }
updateMinimumUpdatePeriodTimeout_() { updateMinimumUpdatePeriodTimeout_() {
const mpl = this.masterPlaylistLoader_; const mpl = this.mainPlaylistLoader_;
// cancel any pending creation of mup on media // cancel any pending creation of mup on media
// a new one will be added if needed. // a new one will be added if needed.
@ -753,7 +792,7 @@ export default class DashPlaylistLoader extends EventTarget {
mpl.minimumUpdatePeriodTimeout_ = null; mpl.minimumUpdatePeriodTimeout_ = null;
} }
let mup = mpl.master && mpl.master.minimumUpdatePeriod; let mup = mpl.main && mpl.main.minimumUpdatePeriod;
// If the minimumUpdatePeriod has a value of 0, that indicates that the current // If the minimumUpdatePeriod has a value of 0, that indicates that the current
// MPD has no future validity, so a new one will need to be acquired when new // MPD has no future validity, so a new one will need to be acquired when new
@ -782,7 +821,7 @@ export default class DashPlaylistLoader extends EventTarget {
} }
createMUPTimeout_(mup) { createMUPTimeout_(mup) {
const mpl = this.masterPlaylistLoader_; const mpl = this.mainPlaylistLoader_;
mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => { mpl.minimumUpdatePeriodTimeout_ = window.setTimeout(() => {
mpl.minimumUpdatePeriodTimeout_ = null; mpl.minimumUpdatePeriodTimeout_ = null;
@ -792,22 +831,22 @@ export default class DashPlaylistLoader extends EventTarget {
} }
/** /**
* Sends request to refresh the master xml and updates the parsed master manifest * Sends request to refresh the main xml and updates the parsed main manifest
*/ */
refreshXml_() { refreshXml_() {
this.requestMaster_((req, masterChanged) => { this.requestMain_((req, mainChanged) => {
if (!masterChanged) { if (!mainChanged) {
return; return;
} }
if (this.media_) { if (this.media_) {
this.media_ = this.masterPlaylistLoader_.master.playlists[this.media_.id]; this.media_ = this.mainPlaylistLoader_.main.playlists[this.media_.id];
} }
// This will filter out updated sidx info from the mapping // This will filter out updated sidx info from the mapping
this.masterPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings( this.mainPlaylistLoader_.sidxMapping_ = filterChangedSidxMappings(
this.masterPlaylistLoader_.master, this.mainPlaylistLoader_.main,
this.masterPlaylistLoader_.sidxMapping_ this.mainPlaylistLoader_.sidxMapping_
); );
this.addSidxSegments_(this.media(), this.state, (sidxChanged) => { this.addSidxSegments_(this.media(), this.state, (sidxChanged) => {
@ -818,25 +857,25 @@ export default class DashPlaylistLoader extends EventTarget {
} }
/** /**
* Refreshes the media playlist by re-parsing the master xml and updating playlist * Refreshes the media playlist by re-parsing the main xml and updating playlist
* references. If this is an alternate loader, the updated parsed manifest is retrieved * references. If this is an alternate loader, the updated parsed manifest is retrieved
* from the master loader. * from the main loader.
*/ */
refreshMedia_(mediaID) { refreshMedia_(mediaID) {
if (!mediaID) { if (!mediaID) {
throw new Error('refreshMedia_ must take a media id'); throw new Error('refreshMedia_ must take a media id');
} }
// for master we have to reparse the master xml // for main we have to reparse the main xml
// to re-create segments based on current timing values // to re-create segments based on current timing values
// which may change media. We only skip updating master // which may change media. We only skip updating the main manifest
// if this is the first time this.media_ is being set. // if this is the first time this.media_ is being set.
// as master was just parsed in that case. // as main was just parsed in that case.
if (this.media_ && this.isMaster_) { if (this.media_ && this.isMain_) {
this.handleMaster_(); this.handleMain_();
} }
const playlists = this.masterPlaylistLoader_.master.playlists; const playlists = this.mainPlaylistLoader_.main.playlists;
const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID]; const mediaChanged = !this.media_ || this.media_ !== playlists[mediaID];
if (mediaChanged) { if (mediaChanged) {

View file

@ -10,6 +10,11 @@ export const createPlaylistID = (index, uri) => {
return `${index}-${uri}`; return `${index}-${uri}`;
}; };
// default function for creating a group id
const groupID = (type, group, label) => {
return `placeholder-uri-${type}-${group}-${label}`;
};
/** /**
* Parses a given m3u8 playlist * Parses a given m3u8 playlist
* *
@ -23,7 +28,7 @@ export const createPlaylistID = (index, uri) => {
* An array of custom tag parsers for the m3u8-parser instance * An array of custom tag parsers for the m3u8-parser instance
* @param {Object[]} [customTagMappers] * @param {Object[]} [customTagMappers]
* An array of custom tag mappers for the m3u8-parser instance * An array of custom tag mappers for the m3u8-parser instance
* @param {boolean} [experimentalLLHLS=false] * @param {boolean} [llhls]
* Whether to keep ll-hls features in the manifest after parsing. * Whether to keep ll-hls features in the manifest after parsing.
* @return {Object} * @return {Object}
* The manifest object * The manifest object
@ -34,7 +39,7 @@ export const parseManifest = ({
manifestString, manifestString,
customTagParsers = [], customTagParsers = [],
customTagMappers = [], customTagMappers = [],
experimentalLLHLS llhls
}) => { }) => {
const parser = new M3u8Parser(); const parser = new M3u8Parser();
@ -55,7 +60,7 @@ export const parseManifest = ({
// remove llhls features from the parsed manifest // remove llhls features from the parsed manifest
// if we don't want llhls support. // if we don't want llhls support.
if (!experimentalLLHLS) { if (!llhls) {
[ [
'preloadSegment', 'preloadSegment',
'skip', 'skip',
@ -109,25 +114,25 @@ export const parseManifest = ({
}; };
/** /**
* Loops through all supported media groups in master and calls the provided * Loops through all supported media groups in main and calls the provided
* callback for each group * callback for each group
* *
* @param {Object} master * @param {Object} main
* The parsed master manifest object * The parsed main manifest object
* @param {Function} callback * @param {Function} callback
* Callback to call for each media group * Callback to call for each media group
*/ */
export const forEachMediaGroup = (master, callback) => { export const forEachMediaGroup = (main, callback) => {
if (!master.mediaGroups) { if (!main.mediaGroups) {
return; return;
} }
['AUDIO', 'SUBTITLES'].forEach((mediaType) => { ['AUDIO', 'SUBTITLES'].forEach((mediaType) => {
if (!master.mediaGroups[mediaType]) { if (!main.mediaGroups[mediaType]) {
return; return;
} }
for (const groupKey in master.mediaGroups[mediaType]) { for (const groupKey in main.mediaGroups[mediaType]) {
for (const labelKey in master.mediaGroups[mediaType][groupKey]) { for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
const mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey]; const mediaProperties = main.mediaGroups[mediaType][groupKey][labelKey];
callback(mediaProperties, mediaType, groupKey, labelKey); callback(mediaProperties, mediaType, groupKey, labelKey);
} }
@ -144,7 +149,7 @@ export const forEachMediaGroup = (master, callback) => {
* @param {Object} config.playlist * @param {Object} config.playlist
* The media playlist * The media playlist
* @param {string} [config.uri] * @param {string} [config.uri]
* The uri to the media playlist (if media playlist is not from within a master * The uri to the media playlist (if media playlist is not from within a main
* playlist) * playlist)
* @param {string} id * @param {string} id
* ID to use for the playlist * ID to use for the playlist
@ -160,7 +165,7 @@ export const setupMediaPlaylist = ({ playlist, uri, id }) => {
playlist.uri = uri; playlist.uri = uri;
} }
// For HLS master playlists, even though certain attributes MUST be defined, the // For HLS main playlists, even though certain attributes MUST be defined, the
// stream may still be played without them. // stream may still be played without them.
// For HLS media playlists, m3u8-parser does not attach an attributes object to the // For HLS media playlists, m3u8-parser does not attach an attributes object to the
// manifest. // manifest.
@ -171,27 +176,27 @@ export const setupMediaPlaylist = ({ playlist, uri, id }) => {
}; };
/** /**
* Adds ID, resolvedUri, and attributes properties to each playlist of the master, where * Adds ID, resolvedUri, and attributes properties to each playlist of the main, where
* necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to * necessary. In addition, creates playlist IDs for each playlist and adds playlist ID to
* playlist references to the playlists array. * playlist references to the playlists array.
* *
* @param {Object} master * @param {Object} main
* The master playlist * The main playlist
*/ */
export const setupMediaPlaylists = (master) => { export const setupMediaPlaylists = (main) => {
let i = master.playlists.length; let i = main.playlists.length;
while (i--) { while (i--) {
const playlist = master.playlists[i]; const playlist = main.playlists[i];
setupMediaPlaylist({ setupMediaPlaylist({
playlist, playlist,
id: createPlaylistID(i, playlist.uri) id: createPlaylistID(i, playlist.uri)
}); });
playlist.resolvedUri = resolveUrl(master.uri, playlist.uri); playlist.resolvedUri = resolveUrl(main.uri, playlist.uri);
master.playlists[playlist.id] = playlist; main.playlists[playlist.id] = playlist;
// URI reference added for backwards compatibility // URI reference added for backwards compatibility
master.playlists[playlist.uri] = playlist; main.playlists[playlist.uri] = playlist;
// Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute, // Although the spec states an #EXT-X-STREAM-INF tag MUST have a BANDWIDTH attribute,
// the stream can be played without it. Although an attributes property may have been // the stream can be played without it. Although an attributes property may have been
@ -206,19 +211,19 @@ export const setupMediaPlaylists = (master) => {
/** /**
* Adds resolvedUri properties to each media group. * Adds resolvedUri properties to each media group.
* *
* @param {Object} master * @param {Object} main
* The master playlist * The main playlist
*/ */
export const resolveMediaGroupUris = (master) => { export const resolveMediaGroupUris = (main) => {
forEachMediaGroup(master, (properties) => { forEachMediaGroup(main, (properties) => {
if (properties.uri) { if (properties.uri) {
properties.resolvedUri = resolveUrl(master.uri, properties.uri); properties.resolvedUri = resolveUrl(main.uri, properties.uri);
} }
}); });
}; };
/** /**
* Creates a master playlist wrapper to insert a sole media playlist into. * Creates a main playlist wrapper to insert a sole media playlist into.
* *
* @param {Object} media * @param {Object} media
* Media playlist * Media playlist
@ -226,11 +231,11 @@ export const resolveMediaGroupUris = (master) => {
* The media URI * The media URI
* *
* @return {Object} * @return {Object}
* Master playlist * main playlist
*/ */
export const masterForMedia = (media, uri) => { export const mainForMedia = (media, uri) => {
const id = createPlaylistID(0, uri); const id = createPlaylistID(0, uri);
const master = { const main = {
mediaGroups: { mediaGroups: {
'AUDIO': {}, 'AUDIO': {},
'VIDEO': {}, 'VIDEO': {},
@ -250,48 +255,48 @@ export const masterForMedia = (media, uri) => {
}; };
// set up ID reference // set up ID reference
master.playlists[id] = master.playlists[0]; main.playlists[id] = main.playlists[0];
// URI reference added for backwards compatibility // URI reference added for backwards compatibility
master.playlists[uri] = master.playlists[0]; main.playlists[uri] = main.playlists[0];
return master; return main;
}; };
/** /**
* Does an in-place update of the master manifest to add updated playlist URI references * Does an in-place update of the main manifest to add updated playlist URI references
* as well as other properties needed by VHS that aren't included by the parser. * as well as other properties needed by VHS that aren't included by the parser.
* *
* @param {Object} master * @param {Object} main
* Master manifest object * main manifest object
* @param {string} uri * @param {string} uri
* The source URI * The source URI
* @param {function} createGroupID
* A function to determine how to create the groupID for mediaGroups
*/ */
export const addPropertiesToMaster = (master, uri) => { export const addPropertiesToMain = (main, uri, createGroupID = groupID) => {
master.uri = uri; main.uri = uri;
for (let i = 0; i < master.playlists.length; i++) { for (let i = 0; i < main.playlists.length; i++) {
if (!master.playlists[i].uri) { if (!main.playlists[i].uri) {
// Set up phony URIs for the playlists since playlists are referenced by their URIs // Set up phony URIs for the playlists since playlists are referenced by their URIs
// throughout VHS, but some formats (e.g., DASH) don't have external URIs // throughout VHS, but some formats (e.g., DASH) don't have external URIs
// TODO: consider adding dummy URIs in mpd-parser // TODO: consider adding dummy URIs in mpd-parser
const phonyUri = `placeholder-uri-${i}`; const phonyUri = `placeholder-uri-${i}`;
master.playlists[i].uri = phonyUri; main.playlists[i].uri = phonyUri;
} }
} }
const audioOnlyMaster = isAudioOnly(master); const audioOnlyMain = isAudioOnly(main);
forEachMediaGroup(master, (properties, mediaType, groupKey, labelKey) => {
const groupId = `placeholder-uri-${mediaType}-${groupKey}-${labelKey}`;
forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
// add a playlist array under properties // add a playlist array under properties
if (!properties.playlists || !properties.playlists.length) { if (!properties.playlists || !properties.playlists.length) {
// If the manifest is audio only and this media group does not have a uri, check // If the manifest is audio only and this media group does not have a uri, check
// if the media group is located in the main list of playlists. If it is, don't add // if the media group is located in the main list of playlists. If it is, don't add
// placeholder properties as it shouldn't be considered an alternate audio track. // placeholder properties as it shouldn't be considered an alternate audio track.
if (audioOnlyMaster && mediaType === 'AUDIO' && !properties.uri) { if (audioOnlyMain && mediaType === 'AUDIO' && !properties.uri) {
for (let i = 0; i < master.playlists.length; i++) { for (let i = 0; i < main.playlists.length; i++) {
const p = master.playlists[i]; const p = main.playlists[i];
if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) { if (p.attributes && p.attributes.AUDIO && p.attributes.AUDIO === groupKey) {
return; return;
@ -303,10 +308,11 @@ export const addPropertiesToMaster = (master, uri) => {
} }
properties.playlists.forEach(function(p, i) { properties.playlists.forEach(function(p, i) {
const groupId = createGroupID(mediaType, groupKey, labelKey, p);
const id = createPlaylistID(i, groupId); const id = createPlaylistID(i, groupId);
if (p.uri) { if (p.uri) {
p.resolvedUri = p.resolvedUri || resolveUrl(master.uri, p.uri); p.resolvedUri = p.resolvedUri || resolveUrl(main.uri, p.uri);
} else { } else {
// DEPRECATED, this has been added to prevent a breaking change. // DEPRECATED, this has been added to prevent a breaking change.
// previously we only ever had a single media group playlist, so // previously we only ever had a single media group playlist, so
@ -326,12 +332,12 @@ export const addPropertiesToMaster = (master, uri) => {
p.attributes = p.attributes || {}; p.attributes = p.attributes || {};
// setup ID and URI references (URI for backwards compatibility) // setup ID and URI references (URI for backwards compatibility)
master.playlists[p.id] = p; main.playlists[p.id] = p;
master.playlists[p.uri] = p; main.playlists[p.uri] = p;
}); });
}); });
setupMediaPlaylists(master); setupMediaPlaylists(main);
resolveMediaGroupUris(master); resolveMediaGroupUris(main);
}; };

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@ import DashPlaylistLoader from './dash-playlist-loader';
import noop from './util/noop'; import noop from './util/noop';
import {isAudioOnly, playlistMatch} from './playlist.js'; import {isAudioOnly, playlistMatch} from './playlist.js';
import logger from './util/logger'; import logger from './util/logger';
import {merge} from './util/vjs-compat';
/** /**
* Convert the properties of an HLS track into an audioTrackKind. * Convert the properties of an HLS track into an audioTrackKind.
@ -94,7 +95,7 @@ export const onGroupChanged = (type, settings) => () => {
stopLoaders(segmentLoader, mediaType); stopLoaders(segmentLoader, mediaType);
if (!activeGroup || activeGroup.isMasterPlaylist) { if (!activeGroup || activeGroup.isMainPlaylist) {
// there is no group active or active group is a main playlist and won't change // there is no group active or active group is a main playlist and won't change
return; return;
} }
@ -146,7 +147,7 @@ export const onGroupChanging = (type, settings) => () => {
*/ */
export const onTrackChanged = (type, settings) => () => { export const onTrackChanged = (type, settings) => () => {
const { const {
masterPlaylistLoader, mainPlaylistLoader,
segmentLoaders: { segmentLoaders: {
[type]: segmentLoader, [type]: segmentLoader,
main: mainSegmentLoader main: mainSegmentLoader
@ -173,24 +174,24 @@ export const onTrackChanged = (type, settings) => () => {
return; return;
} }
if (activeGroup.isMasterPlaylist) { if (activeGroup.isMainPlaylist) {
// track did not change, do nothing // track did not change, do nothing
if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) { if (!activeTrack || !lastTrack || activeTrack.id === lastTrack.id) {
return; return;
} }
const mpc = settings.vhs.masterPlaylistController_; const pc = settings.vhs.playlistController_;
const newPlaylist = mpc.selectPlaylist(); const newPlaylist = pc.selectPlaylist();
// media will not change do nothing // media will not change do nothing
if (mpc.media() === newPlaylist) { if (pc.media() === newPlaylist) {
return; return;
} }
mediaType.logger_(`track change. Switching master audio from ${lastTrack.id} to ${activeTrack.id}`); mediaType.logger_(`track change. Switching main audio from ${lastTrack.id} to ${activeTrack.id}`);
masterPlaylistLoader.pause(); mainPlaylistLoader.pause();
mainSegmentLoader.resetEverything(); mainSegmentLoader.resetEverything();
mpc.fastQualityChange_(newPlaylist); pc.fastQualityChange_(newPlaylist);
return; return;
} }
@ -243,7 +244,7 @@ export const onError = {
* @param {Object} settings * @param {Object} settings
* Object containing required information for media groups * Object containing required information for media groups
* @return {Function} * @return {Function}
* Error handler. Logs warning (or error if the playlist is blacklisted) to * Error handler. Logs warning (or error if the playlist is excluded) to
* console and switches back to default audio track. * console and switches back to default audio track.
* @function onError.AUDIO * @function onError.AUDIO
*/ */
@ -251,7 +252,7 @@ export const onError = {
const { const {
segmentLoaders: { [type]: segmentLoader}, segmentLoaders: { [type]: segmentLoader},
mediaTypes: { [type]: mediaType }, mediaTypes: { [type]: mediaType },
blacklistCurrentPlaylist excludePlaylist
} = settings; } = settings;
stopLoaders(segmentLoader, mediaType); stopLoaders(segmentLoader, mediaType);
@ -263,10 +264,10 @@ export const onError = {
const defaultTrack = mediaType.tracks[id]; const defaultTrack = mediaType.tracks[id];
if (activeTrack === defaultTrack) { if (activeTrack === defaultTrack) {
// Default track encountered an error. All we can do now is blacklist the current // Default track encountered an error. All we can do now is exclude the current
// rendition and hope another will switch audio groups // rendition and hope another will switch audio groups
blacklistCurrentPlaylist({ excludePlaylist({
message: 'Problem encountered loading the default audio track.' error: { message: 'Problem encountered loading the default audio track.' }
}); });
return; return;
} }
@ -421,7 +422,7 @@ export const initialize = {
sourceType, sourceType,
segmentLoaders: { [type]: segmentLoader }, segmentLoaders: { [type]: segmentLoader },
requestOptions, requestOptions,
master: {mediaGroups}, main: {mediaGroups},
mediaTypes: { mediaTypes: {
[type]: { [type]: {
groups, groups,
@ -429,17 +430,17 @@ export const initialize = {
logger_ logger_
} }
}, },
masterPlaylistLoader mainPlaylistLoader
} = settings; } = settings;
const audioOnlyMaster = isAudioOnly(masterPlaylistLoader.master); const audioOnlyMain = isAudioOnly(mainPlaylistLoader.main);
// force a default if we have none // force a default if we have none
if (!mediaGroups[type] || if (!mediaGroups[type] ||
Object.keys(mediaGroups[type]).length === 0) { Object.keys(mediaGroups[type]).length === 0) {
mediaGroups[type] = { main: { default: { default: true } } }; mediaGroups[type] = { main: { default: { default: true } } };
if (audioOnlyMaster) { if (audioOnlyMain) {
mediaGroups[type].main.default.playlists = masterPlaylistLoader.master.playlists; mediaGroups[type].main.default.playlists = mainPlaylistLoader.main.playlists;
} }
} }
@ -452,9 +453,9 @@ export const initialize = {
let playlistLoader; let playlistLoader;
if (audioOnlyMaster) { if (audioOnlyMain) {
logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a master playlist`); logger_(`AUDIO group '${groupId}' label '${variantLabel}' is a main playlist`);
properties.isMasterPlaylist = true; properties.isMainPlaylist = true;
playlistLoader = null; playlistLoader = null;
// if vhs-json was provided as the source, and the media playlist was resolved, // if vhs-json was provided as the source, and the media playlist was resolved,
@ -478,7 +479,7 @@ export const initialize = {
properties.playlists[0], properties.playlists[0],
vhs, vhs,
requestOptions, requestOptions,
masterPlaylistLoader mainPlaylistLoader
); );
} else { } else {
// no resolvedUri means the audio is muxed with the video when using this // no resolvedUri means the audio is muxed with the video when using this
@ -486,7 +487,7 @@ export const initialize = {
playlistLoader = null; playlistLoader = null;
} }
properties = videojs.mergeOptions( properties = merge(
{ id: variantLabel, playlistLoader }, { id: variantLabel, playlistLoader },
properties properties
); );
@ -529,14 +530,14 @@ export const initialize = {
sourceType, sourceType,
segmentLoaders: { [type]: segmentLoader }, segmentLoaders: { [type]: segmentLoader },
requestOptions, requestOptions,
master: { mediaGroups }, main: { mediaGroups },
mediaTypes: { mediaTypes: {
[type]: { [type]: {
groups, groups,
tracks tracks
} }
}, },
masterPlaylistLoader mainPlaylistLoader
} = settings; } = settings;
for (const groupId in mediaGroups[type]) { for (const groupId in mediaGroups[type]) {
@ -574,7 +575,7 @@ export const initialize = {
properties.playlists[0], properties.playlists[0],
vhs, vhs,
requestOptions, requestOptions,
masterPlaylistLoader mainPlaylistLoader
); );
} else if (sourceType === 'vhs-json') { } else if (sourceType === 'vhs-json') {
playlistLoader = new PlaylistLoader( playlistLoader = new PlaylistLoader(
@ -586,7 +587,7 @@ export const initialize = {
); );
} }
properties = videojs.mergeOptions({ properties = merge({
id: variantLabel, id: variantLabel,
playlistLoader playlistLoader
}, properties); }, properties);
@ -624,7 +625,7 @@ export const initialize = {
'CLOSED-CAPTIONS': (type, settings) => { 'CLOSED-CAPTIONS': (type, settings) => {
const { const {
tech, tech,
master: { mediaGroups }, main: { mediaGroups },
mediaTypes: { mediaTypes: {
[type]: { [type]: {
groups, groups,
@ -656,7 +657,7 @@ export const initialize = {
}; };
if (captionServices[newProps.instreamId]) { if (captionServices[newProps.instreamId]) {
newProps = videojs.mergeOptions(newProps, captionServices[newProps.instreamId]); newProps = merge(newProps, captionServices[newProps.instreamId]);
} }
if (newProps.default === undefined) { if (newProps.default === undefined) {
@ -665,7 +666,7 @@ export const initialize = {
// No PlaylistLoader is required for Closed-Captions because the captions are // No PlaylistLoader is required for Closed-Captions because the captions are
// embedded within the video stream // embedded within the video stream
groups[groupId].push(videojs.mergeOptions({ id: variantLabel }, properties)); groups[groupId].push(merge({ id: variantLabel }, properties));
if (typeof tracks[variantLabel] === 'undefined') { if (typeof tracks[variantLabel] === 'undefined') {
const track = tech.addRemoteTextTrack({ const track = tech.addRemoteTextTrack({
@ -713,11 +714,11 @@ const groupMatch = (list, media) => {
*/ */
export const activeGroup = (type, settings) => (track) => { export const activeGroup = (type, settings) => (track) => {
const { const {
masterPlaylistLoader, mainPlaylistLoader,
mediaTypes: { [type]: { groups } } mediaTypes: { [type]: { groups } }
} = settings; } = settings;
const media = masterPlaylistLoader.media(); const media = mainPlaylistLoader.media();
if (!media) { if (!media) {
return null; return null;
@ -733,10 +734,10 @@ export const activeGroup = (type, settings) => (track) => {
const groupKeys = Object.keys(groups); const groupKeys = Object.keys(groups);
if (!variants) { if (!variants) {
// find the masterPlaylistLoader media // find the mainPlaylistLoader media
// that is in a media group if we are dealing // that is in a media group if we are dealing
// with audio only // with audio only
if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.master)) { if (type === 'AUDIO' && groupKeys.length > 1 && isAudioOnly(settings.main)) {
for (let i = 0; i < groupKeys.length; i++) { for (let i = 0; i < groupKeys.length; i++) {
const groupPropertyList = groups[groupKeys[i]]; const groupPropertyList = groups[groupKeys[i]];
@ -828,7 +829,7 @@ export const getActiveGroup = (type, {mediaTypes}) => () => {
/** /**
* Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles, * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
* Closed-Captions) specified in the master manifest. * Closed-Captions) specified in the main manifest.
* *
* @param {Object} settings * @param {Object} settings
* Object containing required information for setting up the media groups * Object containing required information for setting up the media groups
@ -836,16 +837,16 @@ export const getActiveGroup = (type, {mediaTypes}) => () => {
* The tech of the player * The tech of the player
* @param {Object} settings.requestOptions * @param {Object} settings.requestOptions
* XHR request options used by the segment loaders * XHR request options used by the segment loaders
* @param {PlaylistLoader} settings.masterPlaylistLoader * @param {PlaylistLoader} settings.mainPlaylistLoader
* PlaylistLoader for the master source * PlaylistLoader for the main source
* @param {VhsHandler} settings.vhs * @param {VhsHandler} settings.vhs
* VHS SourceHandler * VHS SourceHandler
* @param {Object} settings.master * @param {Object} settings.main
* The parsed master manifest * The parsed main manifest
* @param {Object} settings.mediaTypes * @param {Object} settings.mediaTypes
* Object to store the loaders, tracks, and utility methods for each media type * Object to store the loaders, tracks, and utility methods for each media type
* @param {Function} settings.blacklistCurrentPlaylist * @param {Function} settings.excludePlaylist
* Blacklists the current rendition and forces a rendition switch. * Excludes the current rendition and forces a rendition switch.
* @function setupMediaGroups * @function setupMediaGroups
*/ */
export const setupMediaGroups = (settings) => { export const setupMediaGroups = (settings) => {
@ -855,7 +856,7 @@ export const setupMediaGroups = (settings) => {
const { const {
mediaTypes, mediaTypes,
masterPlaylistLoader, mainPlaylistLoader,
tech, tech,
vhs, vhs,
segmentLoaders: { segmentLoaders: {
@ -900,11 +901,11 @@ export const setupMediaGroups = (settings) => {
} }
} }
masterPlaylistLoader.on('mediachange', () => { mainPlaylistLoader.on('mediachange', () => {
['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged()); ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanged());
}); });
masterPlaylistLoader.on('mediachanging', () => { mainPlaylistLoader.on('mediachanging', () => {
['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging()); ['AUDIO', 'SUBTITLES'].forEach(type => mediaTypes[type].onGroupChanging());
}); });
@ -912,7 +913,6 @@ export const setupMediaGroups = (settings) => {
const onAudioTrackChanged = () => { const onAudioTrackChanged = () => {
mediaTypes.AUDIO.onTrackChanged(); mediaTypes.AUDIO.onTrackChanged();
tech.trigger({ type: 'usage', name: 'vhs-audio-change' }); tech.trigger({ type: 'usage', name: 'vhs-audio-change' });
tech.trigger({ type: 'usage', name: 'hls-audio-change' });
}; };
tech.audioTracks().addEventListener('change', onAudioTrackChanged); tech.audioTracks().addEventListener('change', onAudioTrackChanged);

View file

@ -1,4 +1,3 @@
import videojs from 'video.js';
import { createTransferableMessage } from './bin-utils'; import { createTransferableMessage } from './bin-utils';
import { stringToArrayBuffer } from './util/string-to-array-buffer'; import { stringToArrayBuffer } from './util/string-to-array-buffer';
import { transmux } from './segment-transmuxer'; import { transmux } from './segment-transmuxer';
@ -8,6 +7,7 @@ import {
detectContainerForBytes, detectContainerForBytes,
isLikelyFmp4MediaSegment isLikelyFmp4MediaSegment
} from '@videojs/vhs-utils/es/containers'; } from '@videojs/vhs-utils/es/containers';
import {merge} from './util/vjs-compat';
export const REQUEST_ERRORS = { export const REQUEST_ERRORS = {
FAILURE: 2, FAILURE: 2,
@ -425,7 +425,7 @@ const handleSegmentBytes = ({
// TODO: // TODO:
// We should have a handler that fetches the number of bytes required // We should have a handler that fetches the number of bytes required
// to check if something is fmp4. This will allow us to save bandwidth // to check if something is fmp4. This will allow us to save bandwidth
// because we can only blacklist a playlist and abort requests // because we can only exclude a playlist and abort requests
// by codec after trackinfo triggers. // by codec after trackinfo triggers.
if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) { if (isLikelyFmp4MediaSegment(bytesAsUint8Array)) {
segment.isFmp4 = true; segment.isFmp4 = true;
@ -514,7 +514,7 @@ const handleSegmentBytes = ({
bytes = message.data.buffer; bytes = message.data.buffer;
segment.bytes = bytesAsUint8Array = message.data; segment.bytes = bytesAsUint8Array = message.data;
message.logs.forEach(function(log) { message.logs.forEach(function(log) {
onTransmuxerLog(videojs.mergeOptions(log, {stream: 'mp4CaptionParser'})); onTransmuxerLog(merge(log, {stream: 'mp4CaptionParser'}));
}); });
finishLoading(message.captions); finishLoading(message.captions);
} }
@ -860,7 +860,7 @@ const handleProgress = ({
return; return;
} }
segment.stats = videojs.mergeOptions(segment.stats, getProgressStats(event)); segment.stats = merge(segment.stats, getProgressStats(event));
// record the time that we receive the first byte of data // record the time that we receive the first byte of data
if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) { if (!segment.stats.firstBytesReceivedAt && segment.stats.bytesReceived) {
@ -981,7 +981,7 @@ export const mediaSegmentRequest = ({
if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) { if (segment.map && !segment.map.bytes && segment.map.key && segment.map.key.resolvedUri === segment.key.resolvedUri) {
objects.push(segment.map.key); objects.push(segment.map.key);
} }
const keyRequestOptions = videojs.mergeOptions(xhrOptions, { const keyRequestOptions = merge(xhrOptions, {
uri: segment.key.resolvedUri, uri: segment.key.resolvedUri,
responseType: 'arraybuffer' responseType: 'arraybuffer'
}); });
@ -996,7 +996,7 @@ export const mediaSegmentRequest = ({
const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri); const differentMapKey = segment.map.key && (!segment.key || segment.key.resolvedUri !== segment.map.key.resolvedUri);
if (differentMapKey) { if (differentMapKey) {
const mapKeyRequestOptions = videojs.mergeOptions(xhrOptions, { const mapKeyRequestOptions = merge(xhrOptions, {
uri: segment.map.key.resolvedUri, uri: segment.map.key.resolvedUri,
responseType: 'arraybuffer' responseType: 'arraybuffer'
}); });
@ -1005,7 +1005,7 @@ export const mediaSegmentRequest = ({
activeXhrs.push(mapKeyXhr); activeXhrs.push(mapKeyXhr);
} }
const initSegmentOptions = videojs.mergeOptions(xhrOptions, { const initSegmentOptions = merge(xhrOptions, {
uri: segment.map.resolvedUri, uri: segment.map.resolvedUri,
responseType: 'arraybuffer', responseType: 'arraybuffer',
headers: segmentXhrHeaders(segment.map) headers: segmentXhrHeaders(segment.map)
@ -1016,7 +1016,7 @@ export const mediaSegmentRequest = ({
activeXhrs.push(initSegmentXhr); activeXhrs.push(initSegmentXhr);
} }
const segmentRequestOptions = videojs.mergeOptions(xhrOptions, { const segmentRequestOptions = merge(xhrOptions, {
uri: segment.part && segment.part.resolvedUri || segment.resolvedUri, uri: segment.part && segment.part.resolvedUri || segment.resolvedUri,
responseType: 'arraybuffer', responseType: 'arraybuffer',
headers: segmentXhrHeaders(segment) headers: segmentXhrHeaders(segment)

View file

@ -32,7 +32,7 @@ export default class PlaybackWatcher {
* @param {Object} options an object that includes the tech and settings * @param {Object} options an object that includes the tech and settings
*/ */
constructor(options) { constructor(options) {
this.masterPlaylistController_ = options.masterPlaylistController; this.playlistController_ = options.playlistController;
this.tech_ = options.tech; this.tech_ = options.tech;
this.seekable = options.seekable; this.seekable = options.seekable;
this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow; this.allowSeeksWithinUnsafeLiveWindow = options.allowSeeksWithinUnsafeLiveWindow;
@ -41,7 +41,6 @@ export default class PlaybackWatcher {
this.consecutiveUpdates = 0; this.consecutiveUpdates = 0;
this.lastRecordedTime = null; this.lastRecordedTime = null;
this.timer_ = null;
this.checkCurrentTimeTimeout_ = null; this.checkCurrentTimeTimeout_ = null;
this.logger_ = logger('PlaybackWatcher'); this.logger_ = logger('PlaybackWatcher');
@ -50,9 +49,9 @@ export default class PlaybackWatcher {
const playHandler = () => this.monitorCurrentTime_(); const playHandler = () => this.monitorCurrentTime_();
const canPlayHandler = () => this.monitorCurrentTime_(); const canPlayHandler = () => this.monitorCurrentTime_();
const waitingHandler = () => this.techWaiting_(); const waitingHandler = () => this.techWaiting_();
const cancelTimerHandler = () => this.cancelTimer_(); const cancelTimerHandler = () => this.resetTimeUpdate_();
const mpc = this.masterPlaylistController_; const pc = this.playlistController_;
const loaderTypes = ['main', 'subtitle', 'audio']; const loaderTypes = ['main', 'subtitle', 'audio'];
const loaderChecks = {}; const loaderChecks = {};
@ -63,11 +62,11 @@ export default class PlaybackWatcher {
updateend: () => this.checkSegmentDownloads_(type) updateend: () => this.checkSegmentDownloads_(type)
}; };
mpc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend); pc[`${type}SegmentLoader_`].on('appendsdone', loaderChecks[type].updateend);
// If a rendition switch happens during a playback stall where the buffer // If a rendition switch happens during a playback stall where the buffer
// isn't changing we want to reset. We cannot assume that the new rendition // isn't changing we want to reset. We cannot assume that the new rendition
// will also be stalled, until after new appends. // will also be stalled, until after new appends.
mpc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset); pc[`${type}SegmentLoader_`].on('playlistupdate', loaderChecks[type].reset);
// Playback stalls should not be detected right after seeking. // Playback stalls should not be detected right after seeking.
// This prevents one segment playlists (single vtt or single segment content) // This prevents one segment playlists (single vtt or single segment content)
// from being detected as stalling. As the buffer will not change in those cases, since // from being detected as stalling. As the buffer will not change in those cases, since
@ -85,7 +84,7 @@ export default class PlaybackWatcher {
*/ */
const setSeekingHandlers = (fn) => { const setSeekingHandlers = (fn) => {
['main', 'audio'].forEach((type) => { ['main', 'audio'].forEach((type) => {
mpc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_); pc[`${type}SegmentLoader_`][fn]('appended', this.seekingAppendCheck_);
}); });
}; };
@ -136,14 +135,14 @@ export default class PlaybackWatcher {
this.tech_.off('seeked', this.clearSeekingAppendCheck_); this.tech_.off('seeked', this.clearSeekingAppendCheck_);
loaderTypes.forEach((type) => { loaderTypes.forEach((type) => {
mpc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend); pc[`${type}SegmentLoader_`].off('appendsdone', loaderChecks[type].updateend);
mpc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset); pc[`${type}SegmentLoader_`].off('playlistupdate', loaderChecks[type].reset);
this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset); this.tech_.off(['seeked', 'seeking'], loaderChecks[type].reset);
}); });
if (this.checkCurrentTimeTimeout_) { if (this.checkCurrentTimeTimeout_) {
window.clearTimeout(this.checkCurrentTimeTimeout_); window.clearTimeout(this.checkCurrentTimeTimeout_);
} }
this.cancelTimer_(); this.resetTimeUpdate_();
}; };
} }
@ -175,7 +174,7 @@ export default class PlaybackWatcher {
* @listens Tech#seeked * @listens Tech#seeked
*/ */
resetSegmentDownloads_(type) { resetSegmentDownloads_(type) {
const loader = this.masterPlaylistController_[`${type}SegmentLoader_`]; const loader = this.playlistController_[`${type}SegmentLoader_`];
if (this[`${type}StalledDownloads_`] > 0) { if (this[`${type}StalledDownloads_`] > 0) {
this.logger_(`resetting possible stalled download count for ${type} loader`); this.logger_(`resetting possible stalled download count for ${type} loader`);
@ -187,7 +186,7 @@ export default class PlaybackWatcher {
/** /**
* Checks on every segment `appendsdone` to see * Checks on every segment `appendsdone` to see
* if segment appends are making progress. If they are not * if segment appends are making progress. If they are not
* and we are still downloading bytes. We blacklist the playlist. * and we are still downloading bytes. We exclude the playlist.
* *
* @param {string} type * @param {string} type
* The segment loader type to check. * The segment loader type to check.
@ -195,8 +194,8 @@ export default class PlaybackWatcher {
* @listens SegmentLoader#appendsdone * @listens SegmentLoader#appendsdone
*/ */
checkSegmentDownloads_(type) { checkSegmentDownloads_(type) {
const mpc = this.masterPlaylistController_; const pc = this.playlistController_;
const loader = mpc[`${type}SegmentLoader_`]; const loader = pc[`${type}SegmentLoader_`];
const buffered = loader.buffered_(); const buffered = loader.buffered_();
const isBufferedDifferent = Ranges.isRangeDifferent(this[`${type}Buffered_`], buffered); const isBufferedDifferent = Ranges.isRangeDifferent(this[`${type}Buffered_`], buffered);
@ -233,9 +232,10 @@ export default class PlaybackWatcher {
// TODO: should we exclude audio tracks rather than main tracks // TODO: should we exclude audio tracks rather than main tracks
// when type is audio? // when type is audio?
mpc.blacklistCurrentPlaylist({ pc.excludePlaylist({
message: `Excessive ${type} segment downloading detected.` error: { message: `Excessive ${type} segment downloading detected.` },
}, Infinity); playlistExclusionDuration: Infinity
});
} }
/** /**
@ -277,20 +277,12 @@ export default class PlaybackWatcher {
} }
/** /**
* Cancels any pending timers and resets the 'timeupdate' mechanism * Resets the 'timeupdate' mechanism designed to detect that we are stalled
* designed to detect that we are stalled
* *
* @private * @private
*/ */
cancelTimer_() { resetTimeUpdate_() {
this.consecutiveUpdates = 0; this.consecutiveUpdates = 0;
if (this.timer_) {
this.logger_('cancelTimer_');
clearTimeout(this.timer_);
}
this.timer_ = null;
} }
/** /**
@ -347,7 +339,7 @@ export default class PlaybackWatcher {
return true; return true;
} }
const sourceUpdater = this.masterPlaylistController_.sourceUpdater_; const sourceUpdater = this.playlistController_.sourceUpdater_;
const buffered = this.tech_.buffered(); const buffered = this.tech_.buffered();
const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null; const audioBuffered = sourceUpdater.audioBuffer ? sourceUpdater.audioBuffered() : null;
const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null; const videoBuffered = sourceUpdater.videoBuffer ? sourceUpdater.videoBuffered() : null;
@ -419,7 +411,7 @@ export default class PlaybackWatcher {
// make sure there is ~3 seconds of forward buffer before taking any corrective action // make sure there is ~3 seconds of forward buffer before taking any corrective action
// to avoid triggering an `unknownwaiting` event when the network is slow. // to avoid triggering an `unknownwaiting` event when the network is slow.
if (currentRange.length && currentTime + 3 <= currentRange.end(0)) { if (currentRange.length && currentTime + 3 <= currentRange.end(0)) {
this.cancelTimer_(); this.resetTimeUpdate_();
this.tech_.setCurrentTime(currentTime); this.tech_.setCurrentTime(currentTime);
this.logger_(`Stopped at ${currentTime} while inside a buffered region ` + this.logger_(`Stopped at ${currentTime} while inside a buffered region ` +
@ -428,7 +420,6 @@ export default class PlaybackWatcher {
// unknown waiting corrections may be useful for monitoring QoS // unknown waiting corrections may be useful for monitoring QoS
this.tech_.trigger({type: 'usage', name: 'vhs-unknown-waiting'}); this.tech_.trigger({type: 'usage', name: 'vhs-unknown-waiting'});
this.tech_.trigger({type: 'usage', name: 'hls-unknown-waiting'});
return; return;
} }
} }
@ -445,7 +436,7 @@ export default class PlaybackWatcher {
const seekable = this.seekable(); const seekable = this.seekable();
const currentTime = this.tech_.currentTime(); const currentTime = this.tech_.currentTime();
if (this.tech_.seeking() || this.timer_ !== null) { if (this.tech_.seeking()) {
// Tech is seeking or already waiting on another action, no action needed // Tech is seeking or already waiting on another action, no action needed
return true; return true;
} }
@ -455,16 +446,15 @@ export default class PlaybackWatcher {
this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` + this.logger_(`Fell out of live window at time ${currentTime}. Seeking to ` +
`live point (seekable end) ${livePoint}`); `live point (seekable end) ${livePoint}`);
this.cancelTimer_(); this.resetTimeUpdate_();
this.tech_.setCurrentTime(livePoint); this.tech_.setCurrentTime(livePoint);
// live window resyncs may be useful for monitoring QoS // live window resyncs may be useful for monitoring QoS
this.tech_.trigger({type: 'usage', name: 'vhs-live-resync'}); this.tech_.trigger({type: 'usage', name: 'vhs-live-resync'});
this.tech_.trigger({type: 'usage', name: 'hls-live-resync'});
return true; return true;
} }
const sourceUpdater = this.tech_.vhs.masterPlaylistController_.sourceUpdater_; const sourceUpdater = this.tech_.vhs.playlistController_.sourceUpdater_;
const buffered = this.tech_.buffered(); const buffered = this.tech_.buffered();
const videoUnderflow = this.videoUnderflow_({ const videoUnderflow = this.videoUnderflow_({
audioBuffered: sourceUpdater.audioBuffered(), audioBuffered: sourceUpdater.audioBuffered(),
@ -477,30 +467,21 @@ export default class PlaybackWatcher {
// the gap, leading currentTime into a buffered range. Seeking to currentTime // the gap, leading currentTime into a buffered range. Seeking to currentTime
// allows the video to catch up to the audio position without losing any audio // allows the video to catch up to the audio position without losing any audio
// (only suffering ~3 seconds of frozen video and a pause in audio playback). // (only suffering ~3 seconds of frozen video and a pause in audio playback).
this.cancelTimer_(); this.resetTimeUpdate_();
this.tech_.setCurrentTime(currentTime); this.tech_.setCurrentTime(currentTime);
// video underflow may be useful for monitoring QoS // video underflow may be useful for monitoring QoS
this.tech_.trigger({type: 'usage', name: 'vhs-video-underflow'}); this.tech_.trigger({type: 'usage', name: 'vhs-video-underflow'});
this.tech_.trigger({type: 'usage', name: 'hls-video-underflow'});
return true; return true;
} }
const nextRange = Ranges.findNextRange(buffered, currentTime); const nextRange = Ranges.findNextRange(buffered, currentTime);
// check for gap // check for gap
if (nextRange.length > 0) { if (nextRange.length > 0) {
const difference = nextRange.start(0) - currentTime; this.logger_(`Stopped at ${currentTime} and seeking to ${nextRange.start(0)}`);
this.logger_(`Stopped at ${currentTime}, setting timer for ${difference}, seeking ` + this.resetTimeUpdate_();
`to ${nextRange.start(0)}`); this.skipTheGap_(currentTime);
this.cancelTimer_();
this.timer_ = setTimeout(
this.skipTheGap_.bind(this),
difference * 1000,
currentTime
);
return true; return true;
} }
@ -591,7 +572,7 @@ export default class PlaybackWatcher {
const currentTime = this.tech_.currentTime(); const currentTime = this.tech_.currentTime();
const nextRange = Ranges.findNextRange(buffered, currentTime); const nextRange = Ranges.findNextRange(buffered, currentTime);
this.cancelTimer_(); this.resetTimeUpdate_();
if (nextRange.length === 0 || if (nextRange.length === 0 ||
currentTime !== scheduledCurrentTime) { currentTime !== scheduledCurrentTime) {
@ -609,7 +590,6 @@ export default class PlaybackWatcher {
this.tech_.setCurrentTime(nextRange.start(0) + Ranges.TIME_FUDGE_FACTOR); this.tech_.setCurrentTime(nextRange.start(0) + Ranges.TIME_FUDGE_FACTOR);
this.tech_.trigger({type: 'usage', name: 'vhs-gap-skip'}); this.tech_.trigger({type: 'usage', name: 'vhs-gap-skip'});
this.tech_.trigger({type: 'usage', name: 'hls-gap-skip'});
} }
gapFromVideoUnderflow_(buffered, currentTime) { gapFromVideoUnderflow_(buffered, currentTime) {

View file

@ -11,14 +11,15 @@ import window from 'global/window';
import logger from './util/logger'; import logger from './util/logger';
import { import {
parseManifest, parseManifest,
addPropertiesToMaster, addPropertiesToMain,
masterForMedia, mainForMedia,
setupMediaPlaylist, setupMediaPlaylist,
forEachMediaGroup forEachMediaGroup
} from './manifest'; } from './manifest';
import {getKnownPartCount} from './playlist.js'; import {getKnownPartCount} from './playlist.js';
import {merge} from './util/vjs-compat';
const { mergeOptions, EventTarget } = videojs; const { EventTarget } = videojs;
const addLLHLSQueryDirectives = (uri, media) => { const addLLHLSQueryDirectives = (uri, media) => {
if (media.endList || !media.serverControl) { if (media.endList || !media.serverControl) {
@ -105,7 +106,7 @@ export const updateSegment = (a, b) => {
return b; return b;
} }
const result = mergeOptions(a, b); const result = merge(a, b);
// if only the old segment has preload hints // if only the old segment has preload hints
// and the new one does not, remove preload hints. // and the new one does not, remove preload hints.
@ -123,7 +124,7 @@ export const updateSegment = (a, b) => {
} else if (a.parts && b.parts) { } else if (a.parts && b.parts) {
for (let i = 0; i < b.parts.length; i++) { for (let i = 0; i < b.parts.length; i++) {
if (a.parts && a.parts[i]) { if (a.parts && a.parts[i]) {
result.parts[i] = mergeOptions(a.parts[i], b.parts[i]); result.parts[i] = merge(a.parts[i], b.parts[i]);
} }
} }
} }
@ -261,19 +262,19 @@ export const isPlaylistUnchanged = (a, b) => a === b ||
a.preloadSegment === b.preloadSegment); a.preloadSegment === b.preloadSegment);
/** /**
* Returns a new master playlist that is the result of merging an * Returns a new main playlist that is the result of merging an
* updated media playlist into the original version. If the * updated media playlist into the original version. If the
* updated media playlist does not match any of the playlist * updated media playlist does not match any of the playlist
* entries in the original master playlist, null is returned. * entries in the original main playlist, null is returned.
* *
* @param {Object} master a parsed master M3U8 object * @param {Object} main a parsed main M3U8 object
* @param {Object} media a parsed media M3U8 object * @param {Object} media a parsed media M3U8 object
* @return {Object} a new object that represents the original * @return {Object} a new object that represents the original
* master playlist with the updated media playlist merged in, or * main playlist with the updated media playlist merged in, or
* null if the merge produced no change. * null if the merge produced no change.
*/ */
export const updateMaster = (master, newMedia, unchangedCheck = isPlaylistUnchanged) => { export const updateMain = (main, newMedia, unchangedCheck = isPlaylistUnchanged) => {
const result = mergeOptions(master, {}); const result = merge(main, {});
const oldMedia = result.playlists[newMedia.id]; const oldMedia = result.playlists[newMedia.id];
if (!oldMedia) { if (!oldMedia) {
@ -286,7 +287,7 @@ export const updateMaster = (master, newMedia, unchangedCheck = isPlaylistUnchan
newMedia.segments = getAllSegments(newMedia); newMedia.segments = getAllSegments(newMedia);
const mergedPlaylist = mergeOptions(oldMedia, newMedia); const mergedPlaylist = merge(oldMedia, newMedia);
// always use the new media's preload segment // always use the new media's preload segment
if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) { if (mergedPlaylist.preloadSegment && !newMedia.preloadSegment) {
@ -328,7 +329,7 @@ export const updateMaster = (master, newMedia, unchangedCheck = isPlaylistUnchan
result.playlists[newMedia.uri] = mergedPlaylist; result.playlists[newMedia.uri] = mergedPlaylist;
// update media group playlist references. // update media group playlist references.
forEachMediaGroup(master, (properties, mediaType, groupKey, labelKey) => { forEachMediaGroup(main, (properties, mediaType, groupKey, labelKey) => {
if (!properties.playlists) { if (!properties.playlists) {
return; return;
} }
@ -385,23 +386,17 @@ export default class PlaylistLoader extends EventTarget {
} }
this.logger_ = logger('PlaylistLoader'); this.logger_ = logger('PlaylistLoader');
const { withCredentials = false, handleManifestRedirects = false } = options; const { withCredentials = false} = options;
this.src = src; this.src = src;
this.vhs_ = vhs; this.vhs_ = vhs;
this.withCredentials = withCredentials; this.withCredentials = withCredentials;
this.handleManifestRedirects = handleManifestRedirects;
const vhsOptions = vhs.options_; const vhsOptions = vhs.options_;
this.customTagParsers = (vhsOptions && vhsOptions.customTagParsers) || []; this.customTagParsers = (vhsOptions && vhsOptions.customTagParsers) || [];
this.customTagMappers = (vhsOptions && vhsOptions.customTagMappers) || []; this.customTagMappers = (vhsOptions && vhsOptions.customTagMappers) || [];
this.experimentalLLHLS = (vhsOptions && vhsOptions.experimentalLLHLS) || false; this.llhls = vhsOptions && vhsOptions.llhls;
// force experimentalLLHLS for IE 11
if (videojs.browser.IE_VERSION) {
this.experimentalLLHLS = false;
}
// initialize the loader state // initialize the loader state
this.state = 'HAVE_NOTHING'; this.state = 'HAVE_NOTHING';
@ -418,9 +413,9 @@ export default class PlaylistLoader extends EventTarget {
} }
const media = this.media(); const media = this.media();
let uri = resolveUrl(this.master.uri, media.uri); let uri = resolveUrl(this.main.uri, media.uri);
if (this.experimentalLLHLS) { if (this.llhls) {
uri = addLLHLSQueryDirectives(uri, media); uri = addLLHLSQueryDirectives(uri, media);
} }
this.state = 'HAVE_CURRENT_METADATA'; this.state = 'HAVE_CURRENT_METADATA';
@ -461,7 +456,7 @@ export default class PlaylistLoader extends EventTarget {
} }
this.error = { this.error = {
playlist: this.master.playlists[id], playlist: this.main.playlists[id],
status: xhr.status, status: xhr.status,
message: `HLS playlist request error at URL: ${uri}.`, message: `HLS playlist request error at URL: ${uri}.`,
responseText: xhr.responseText, responseText: xhr.responseText,
@ -478,7 +473,7 @@ export default class PlaylistLoader extends EventTarget {
manifestString, manifestString,
customTagParsers: this.customTagParsers, customTagParsers: this.customTagParsers,
customTagMappers: this.customTagMappers, customTagMappers: this.customTagMappers,
experimentalLLHLS: this.experimentalLLHLS llhls: this.llhls
}); });
} }
@ -512,16 +507,16 @@ export default class PlaylistLoader extends EventTarget {
id id
}); });
// merge this playlist into the master // merge this playlist into the main manifest
const update = updateMaster(this.master, playlist); const update = updateMain(this.main, playlist);
this.targetDuration = playlist.partTargetDuration || playlist.targetDuration; this.targetDuration = playlist.partTargetDuration || playlist.targetDuration;
this.pendingMedia_ = null; this.pendingMedia_ = null;
if (update) { if (update) {
this.master = update; this.main = update;
this.media_ = this.master.playlists[id]; this.media_ = this.main.playlists[id];
} else { } else {
this.trigger('playlistunchanged'); this.trigger('playlistunchanged');
} }
@ -581,10 +576,10 @@ export default class PlaylistLoader extends EventTarget {
// find the playlist object if the target playlist has been // find the playlist object if the target playlist has been
// specified by URI // specified by URI
if (typeof playlist === 'string') { if (typeof playlist === 'string') {
if (!this.master.playlists[playlist]) { if (!this.main.playlists[playlist]) {
throw new Error('Unknown playlist URI: ' + playlist); throw new Error('Unknown playlist URI: ' + playlist);
} }
playlist = this.master.playlists[playlist]; playlist = this.main.playlists[playlist];
} }
window.clearTimeout(this.finalRenditionTimeout); window.clearTimeout(this.finalRenditionTimeout);
@ -599,10 +594,10 @@ export default class PlaylistLoader extends EventTarget {
const startingState = this.state; const startingState = this.state;
const mediaChange = !this.media_ || playlist.id !== this.media_.id; const mediaChange = !this.media_ || playlist.id !== this.media_.id;
const masterPlaylistRef = this.master.playlists[playlist.id]; const mainPlaylistRef = this.main.playlists[playlist.id];
// switch to fully loaded playlists immediately // switch to fully loaded playlists immediately
if (masterPlaylistRef && masterPlaylistRef.endList || if (mainPlaylistRef && mainPlaylistRef.endList ||
// handle the case of a playlist object (e.g., if using vhs-json with a resolved // handle the case of a playlist object (e.g., if using vhs-json with a resolved
// media playlist or, for the case of demuxed audio, a resolved audio media group) // media playlist or, for the case of demuxed audio, a resolved audio media group)
(playlist.endList && playlist.segments.length)) { (playlist.endList && playlist.segments.length)) {
@ -620,8 +615,8 @@ export default class PlaylistLoader extends EventTarget {
if (mediaChange) { if (mediaChange) {
this.trigger('mediachanging'); this.trigger('mediachanging');
if (startingState === 'HAVE_MASTER') { if (startingState === 'HAVE_MAIN_MANIFEST') {
// The initial playlist was a master manifest, and the first media selected was // The initial playlist was a main manifest, and the first media selected was
// also provided (in the form of a resolved playlist object) as part of the // also provided (in the form of a resolved playlist object) as part of the
// source object (rather than just a URL). Therefore, since the media playlist // source object (rather than just a URL). Therefore, since the media playlist
// doesn't need to be requested, loadedmetadata won't trigger as part of the // doesn't need to be requested, loadedmetadata won't trigger as part of the
@ -678,7 +673,7 @@ export default class PlaylistLoader extends EventTarget {
playlist.lastRequest = Date.now(); playlist.lastRequest = Date.now();
playlist.resolvedUri = resolveManifestRedirect(this.handleManifestRedirects, playlist.resolvedUri, req); playlist.resolvedUri = resolveManifestRedirect(playlist.resolvedUri, req);
if (error) { if (error) {
return this.playlistRequestError(this.request, playlist, startingState); return this.playlistRequestError(this.request, playlist, startingState);
@ -691,7 +686,7 @@ export default class PlaylistLoader extends EventTarget {
}); });
// fire loadedmetadata the first time a media playlist is loaded // fire loadedmetadata the first time a media playlist is loaded
if (startingState === 'HAVE_MASTER') { if (startingState === 'HAVE_MAIN_MANIFEST') {
this.trigger('loadedmetadata'); this.trigger('loadedmetadata');
} else { } else {
this.trigger('mediachange'); this.trigger('mediachange');
@ -717,12 +712,12 @@ export default class PlaylistLoader extends EventTarget {
// Need to restore state now that no activity is happening // Need to restore state now that no activity is happening
if (this.state === 'SWITCHING_MEDIA') { if (this.state === 'SWITCHING_MEDIA') {
// if the loader was in the process of switching media, it should either return to // if the loader was in the process of switching media, it should either return to
// HAVE_MASTER or HAVE_METADATA depending on if the loader has loaded a media // HAVE_MAIN_MANIFEST or HAVE_METADATA depending on if the loader has loaded a media
// playlist yet. This is determined by the existence of loader.media_ // playlist yet. This is determined by the existence of loader.media_
if (this.media_) { if (this.media_) {
this.state = 'HAVE_METADATA'; this.state = 'HAVE_METADATA';
} else { } else {
this.state = 'HAVE_MASTER'; this.state = 'HAVE_MAIN_MANIFEST';
} }
} else if (this.state === 'HAVE_CURRENT_METADATA') { } else if (this.state === 'HAVE_CURRENT_METADATA') {
this.state = 'HAVE_METADATA'; this.state = 'HAVE_METADATA';
@ -800,7 +795,7 @@ export default class PlaylistLoader extends EventTarget {
// Since a manifest object was passed in as the source (instead of a URL), the first // Since a manifest object was passed in as the source (instead of a URL), the first
// request can be skipped (since the top level of the manifest, at a minimum, is // request can be skipped (since the top level of the manifest, at a minimum, is
// already available as a parsed manifest object). However, if the manifest object // already available as a parsed manifest object). However, if the manifest object
// represents a master playlist, some media playlists may need to be resolved before // represents a main playlist, some media playlists may need to be resolved before
// the starting segment list is available. Therefore, go directly to setup of the // the starting segment list is available. Therefore, go directly to setup of the
// initial playlist, and let the normal flow continue from there. // initial playlist, and let the normal flow continue from there.
// //
@ -839,7 +834,7 @@ export default class PlaylistLoader extends EventTarget {
return this.trigger('error'); return this.trigger('error');
} }
this.src = resolveManifestRedirect(this.handleManifestRedirects, this.src, req); this.src = resolveManifestRedirect(this.src, req);
const manifest = this.parseManifest_({ const manifest = this.parseManifest_({
manifestString: req.responseText, manifestString: req.responseText,
@ -855,17 +850,17 @@ export default class PlaylistLoader extends EventTarget {
} }
/** /**
* Given a manifest object that's either a master or media playlist, trigger the proper * Given a manifest object that's either a main or media playlist, trigger the proper
* events and set the state of the playlist loader. * events and set the state of the playlist loader.
* *
* If the manifest object represents a master playlist, `loadedplaylist` will be * If the manifest object represents a main playlist, `loadedplaylist` will be
* triggered to allow listeners to select a playlist. If none is selected, the loader * triggered to allow listeners to select a playlist. If none is selected, the loader
* will default to the first one in the playlists array. * will default to the first one in the playlists array.
* *
* If the manifest object represents a media playlist, `loadedplaylist` will be * If the manifest object represents a media playlist, `loadedplaylist` will be
* triggered followed by `loadedmetadata`, as the only available playlist is loaded. * triggered followed by `loadedmetadata`, as the only available playlist is loaded.
* *
* In the case of a media playlist, a master playlist object wrapper with one playlist * In the case of a media playlist, a main playlist object wrapper with one playlist
* will be created so that all logic can handle playlists in the same fashion (as an * will be created so that all logic can handle playlists in the same fashion (as an
* assumed manifest object schema). * assumed manifest object schema).
* *
@ -873,12 +868,12 @@ export default class PlaylistLoader extends EventTarget {
* The parsed manifest object * The parsed manifest object
*/ */
setupInitialPlaylist(manifest) { setupInitialPlaylist(manifest) {
this.state = 'HAVE_MASTER'; this.state = 'HAVE_MAIN_MANIFEST';
if (manifest.playlists) { if (manifest.playlists) {
this.master = manifest; this.main = manifest;
addPropertiesToMaster(this.master, this.srcUri()); addPropertiesToMain(this.main, this.srcUri());
// If the initial master playlist has playlists wtih segments already resolved, // If the initial main playlist has playlists wtih segments already resolved,
// then resolve URIs in advance, as they are usually done after a playlist request, // then resolve URIs in advance, as they are usually done after a playlist request,
// which may not happen if the playlist is resolved. // which may not happen if the playlist is resolved.
manifest.playlists.forEach((playlist) => { manifest.playlists.forEach((playlist) => {
@ -892,7 +887,7 @@ export default class PlaylistLoader extends EventTarget {
if (!this.request) { if (!this.request) {
// no media playlist was specifically selected so start // no media playlist was specifically selected so start
// from the first listed one // from the first listed one
this.media(this.master.playlists[0]); this.media(this.main.playlists[0]);
} }
return; return;
} }
@ -902,11 +897,11 @@ export default class PlaylistLoader extends EventTarget {
// default used. // default used.
const uri = this.srcUri() || window.location.href; const uri = this.srcUri() || window.location.href;
this.master = masterForMedia(manifest, uri); this.main = mainForMedia(manifest, uri);
this.haveMetadata({ this.haveMetadata({
playlistObject: manifest, playlistObject: manifest,
url: uri, url: uri,
id: this.master.playlists[0].id id: this.main.playlists[0].id
}); });
this.trigger('loadedmetadata'); this.trigger('loadedmetadata');
} }

View file

@ -133,8 +133,8 @@ export const comparePlaylistResolution = function(left, right) {
/** /**
* Chooses the appropriate media playlist based on bandwidth and player size * Chooses the appropriate media playlist based on bandwidth and player size
* *
* @param {Object} master * @param {Object} main
* Object representation of the master manifest * Object representation of the main manifest
* @param {number} playerBandwidth * @param {number} playerBandwidth
* Current calculated bandwidth of the player * Current calculated bandwidth of the player
* @param {number} playerWidth * @param {number} playerWidth
@ -143,23 +143,23 @@ export const comparePlaylistResolution = function(left, right) {
* Current height of the player element (should account for the device pixel ratio) * Current height of the player element (should account for the device pixel ratio)
* @param {boolean} limitRenditionByPlayerDimensions * @param {boolean} limitRenditionByPlayerDimensions
* True if the player width and height should be used during the selection, false otherwise * True if the player width and height should be used during the selection, false otherwise
* @param {Object} masterPlaylistController * @param {Object} playlistController
* the current masterPlaylistController object * the current playlistController object
* @return {Playlist} the highest bitrate playlist less than the * @return {Playlist} the highest bitrate playlist less than the
* currently detected bandwidth, accounting for some amount of * currently detected bandwidth, accounting for some amount of
* bandwidth variance * bandwidth variance
*/ */
export let simpleSelector = function( export let simpleSelector = function(
master, main,
playerBandwidth, playerBandwidth,
playerWidth, playerWidth,
playerHeight, playerHeight,
limitRenditionByPlayerDimensions, limitRenditionByPlayerDimensions,
masterPlaylistController playlistController
) { ) {
// If we end up getting called before `master` is available, exit early // If we end up getting called before `main` is available, exit early
if (!master) { if (!main) {
return; return;
} }
@ -170,11 +170,11 @@ export let simpleSelector = function(
limitRenditionByPlayerDimensions limitRenditionByPlayerDimensions
}; };
let playlists = master.playlists; let playlists = main.playlists;
// if playlist is audio only, select between currently active audio group playlists. // if playlist is audio only, select between currently active audio group playlists.
if (Playlist.isAudioOnly(master)) { if (Playlist.isAudioOnly(main)) {
playlists = masterPlaylistController.getAudioTrackPlaylists_(); playlists = playlistController.getAudioTrackPlaylists_();
// add audioOnly to options so that we log audioOnly: true // add audioOnly to options so that we log audioOnly: true
// at the buttom of this function for debugging. // at the buttom of this function for debugging.
options.audioOnly = true; options.audioOnly = true;
@ -204,12 +204,12 @@ export let simpleSelector = function(
sortedPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isIncompatible(rep.playlist)); sortedPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isIncompatible(rep.playlist));
// filter out any playlists that have been disabled manually through the representations // filter out any playlists that have been disabled manually through the representations
// api or blacklisted temporarily due to playback errors. // api or excluded temporarily due to playback errors.
let enabledPlaylistReps = sortedPlaylistReps.filter((rep) => Playlist.isEnabled(rep.playlist)); let enabledPlaylistReps = sortedPlaylistReps.filter((rep) => Playlist.isEnabled(rep.playlist));
if (!enabledPlaylistReps.length) { if (!enabledPlaylistReps.length) {
// if there are no enabled playlists, then they have all been blacklisted or disabled // if there are no enabled playlists, then they have all been excluded or disabled
// by the user through the representations api. In this case, ignore blacklisting and // by the user through the representations api. In this case, ignore exclusion and
// fallback to what the user wants by using playlists the user has not disabled. // fallback to what the user wants by using playlists the user has not disabled.
enabledPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isDisabled(rep.playlist)); enabledPlaylistReps = sortedPlaylistReps.filter((rep) => !Playlist.isDisabled(rep.playlist));
} }
@ -289,7 +289,7 @@ export let simpleSelector = function(
// If this selector proves to be better than others, // If this selector proves to be better than others,
// resolutionPlusOneRep and resolutionBestRep and all // resolutionPlusOneRep and resolutionBestRep and all
// the code involving them should be removed. // the code involving them should be removed.
if (masterPlaylistController.experimentalLeastPixelDiffSelector) { if (playlistController.leastPixelDiffSelector) {
// find the variant that is closest to the player's pixel size // find the variant that is closest to the player's pixel size
const leastPixelDiffList = haveResolution.map((rep) => { const leastPixelDiffList = haveResolution.map((rep) => {
rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight); rep.pixelDiff = Math.abs(rep.width - playerWidth) + Math.abs(rep.height - playerHeight);
@ -367,12 +367,12 @@ export const lastBandwidthSelector = function() {
const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1; const pixelRatio = this.useDevicePixelRatio ? window.devicePixelRatio || 1 : 1;
return simpleSelector( return simpleSelector(
this.playlists.master, this.playlists.main,
this.systemBandwidth, this.systemBandwidth,
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
this.limitRenditionByPlayerDimensions, this.limitRenditionByPlayerDimensions,
this.masterPlaylistController_ this.playlistController_
); );
}; };
@ -418,12 +418,12 @@ export const movingAverageBandwidthSelector = function(decay) {
} }
return simpleSelector( return simpleSelector(
this.playlists.master, this.playlists.main,
average, average,
parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'width'), 10) * pixelRatio,
parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio, parseInt(safeGetComputedStyle(this.tech_.el(), 'height'), 10) * pixelRatio,
this.limitRenditionByPlayerDimensions, this.limitRenditionByPlayerDimensions,
this.masterPlaylistController_ this.playlistController_
); );
}; };
}; };
@ -433,8 +433,8 @@ export const movingAverageBandwidthSelector = function(decay) {
* *
* @param {Object} settings * @param {Object} settings
* Object of information required to use this selector * Object of information required to use this selector
* @param {Object} settings.master * @param {Object} settings.main
* Object representation of the master manifest * Object representation of the main manifest
* @param {number} settings.currentTime * @param {number} settings.currentTime
* The current time of the player * The current time of the player
* @param {number} settings.bandwidth * @param {number} settings.bandwidth
@ -458,7 +458,7 @@ export const movingAverageBandwidthSelector = function(decay) {
*/ */
export const minRebufferMaxBandwidthSelector = function(settings) { export const minRebufferMaxBandwidthSelector = function(settings) {
const { const {
master, main,
currentTime, currentTime,
bandwidth, bandwidth,
duration, duration,
@ -470,15 +470,15 @@ export const minRebufferMaxBandwidthSelector = function(settings) {
// filter out any playlists that have been excluded due to // filter out any playlists that have been excluded due to
// incompatible configurations // incompatible configurations
const compatiblePlaylists = master.playlists.filter(playlist => !Playlist.isIncompatible(playlist)); const compatiblePlaylists = main.playlists.filter(playlist => !Playlist.isIncompatible(playlist));
// filter out any playlists that have been disabled manually through the representations // filter out any playlists that have been disabled manually through the representations
// api or blacklisted temporarily due to playback errors. // api or excluded temporarily due to playback errors.
let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled); let enabledPlaylists = compatiblePlaylists.filter(Playlist.isEnabled);
if (!enabledPlaylists.length) { if (!enabledPlaylists.length) {
// if there are no enabled playlists, then they have all been blacklisted or disabled // if there are no enabled playlists, then they have all been excluded or disabled
// by the user through the representations api. In this case, ignore blacklisting and // by the user through the representations api. In this case, ignore exclusion and
// fallback to what the user wants by using playlists the user has not disabled. // fallback to what the user wants by using playlists the user has not disabled.
enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist)); enabledPlaylists = compatiblePlaylists.filter(playlist => !Playlist.isDisabled(playlist));
} }
@ -540,7 +540,7 @@ export const minRebufferMaxBandwidthSelector = function(settings) {
export const lowestBitrateCompatibleVariantSelector = function() { export const lowestBitrateCompatibleVariantSelector = function() {
// filter out any playlists that have been excluded due to // filter out any playlists that have been excluded due to
// incompatible configurations or playback errors // incompatible configurations or playback errors
const playlists = this.playlists.master.playlists.filter(Playlist.isEnabled); const playlists = this.playlists.main.playlists.filter(Playlist.isEnabled);
// Sort ascending by bitrate // Sort ascending by bitrate
stableSort( stableSort(
@ -553,7 +553,7 @@ export const lowestBitrateCompatibleVariantSelector = function() {
// //
// If an entire manifest has no valid videos everything will get filtered // If an entire manifest has no valid videos everything will get filtered
// out. // out.
const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.master, playlist).video); const playlistsWithVideo = playlists.filter(playlist => !!codecsForPlaylist(this.playlists.main, playlist).video);
return playlistsWithVideo[0] || null; return playlistsWithVideo[0] || null;
}; };

View file

@ -3,12 +3,10 @@
* *
* Playlist related utilities. * Playlist related utilities.
*/ */
import videojs from 'video.js';
import window from 'global/window'; import window from 'global/window';
import {isAudioCodec} from '@videojs/vhs-utils/es/codecs.js'; import {isAudioCodec} from '@videojs/vhs-utils/es/codecs.js';
import {TIME_FUDGE_FACTOR} from './ranges.js'; import {TIME_FUDGE_FACTOR} from './ranges.js';
import {createTimeRanges} from './util/vjs-compat';
const {createTimeRange} = videojs;
/** /**
* Get the duration of a segment, with special cases for * Get the duration of a segment, with special cases for
@ -88,18 +86,18 @@ export const getKnownPartCount = ({preloadSegment}) => {
* Get the number of seconds to delay from the end of a * Get the number of seconds to delay from the end of a
* live playlist. * live playlist.
* *
* @param {Playlist} master the master playlist * @param {Playlist} main the main playlist
* @param {Playlist} media the media playlist * @param {Playlist} media the media playlist
* @return {number} the hold back in seconds. * @return {number} the hold back in seconds.
*/ */
export const liveEdgeDelay = (master, media) => { export const liveEdgeDelay = (main, media) => {
if (media.endList) { if (media.endList) {
return 0; return 0;
} }
// dash suggestedPresentationDelay trumps everything // dash suggestedPresentationDelay trumps everything
if (master && master.suggestedPresentationDelay) { if (main && main.suggestedPresentationDelay) {
return master.suggestedPresentationDelay; return main.suggestedPresentationDelay;
} }
const hasParts = getLastParts(media).length > 0; const hasParts = getLastParts(media).length > 0;
@ -396,9 +394,9 @@ export const seekable = function(playlist, expired, liveEdgePadding) {
const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding); const seekableEnd = playlistEnd(playlist, expired, useSafeLiveEnd, liveEdgePadding);
if (seekableEnd === null) { if (seekableEnd === null) {
return createTimeRange(); return createTimeRanges();
} }
return createTimeRange(seekableStart, seekableEnd); return createTimeRanges(seekableStart, seekableEnd);
}; };
/** /**
@ -420,7 +418,7 @@ export const getMediaInfoForTime = function({
startingSegmentIndex, startingSegmentIndex,
startingPartIndex, startingPartIndex,
startTime, startTime,
experimentalExactManifestTimings exactManifestTimings
}) { }) {
let time = currentTime - startTime; let time = currentTime - startTime;
@ -453,7 +451,7 @@ export const getMediaInfoForTime = function({
time += partAndSegment.duration; time += partAndSegment.duration;
if (experimentalExactManifestTimings) { if (exactManifestTimings) {
if (time < 0) { if (time < 0) {
continue; continue;
} }
@ -507,7 +505,7 @@ export const getMediaInfoForTime = function({
time -= partAndSegment.duration; time -= partAndSegment.duration;
if (experimentalExactManifestTimings) { if (exactManifestTimings) {
if (time > 0) { if (time > 0) {
continue; continue;
} }
@ -536,19 +534,19 @@ export const getMediaInfoForTime = function({
}; };
/** /**
* Check whether the playlist is blacklisted or not. * Check whether the playlist is excluded or not.
* *
* @param {Object} playlist the media playlist object * @param {Object} playlist the media playlist object
* @return {boolean} whether the playlist is blacklisted or not * @return {boolean} whether the playlist is excluded or not
* @function isBlacklisted * @function isExcluded
*/ */
export const isBlacklisted = function(playlist) { export const isExcluded = function(playlist) {
return playlist.excludeUntil && playlist.excludeUntil > Date.now(); return playlist.excludeUntil && playlist.excludeUntil > Date.now();
}; };
/** /**
* Check whether the playlist is compatible with current playback configuration or has * Check whether the playlist is compatible with current playback configuration or has
* been blacklisted permanently for being incompatible. * been excluded permanently for being incompatible.
* *
* @param {Object} playlist the media playlist object * @param {Object} playlist the media playlist object
* @return {boolean} whether the playlist is incompatible or not * @return {boolean} whether the playlist is incompatible or not
@ -566,9 +564,9 @@ export const isIncompatible = function(playlist) {
* @function isEnabled * @function isEnabled
*/ */
export const isEnabled = function(playlist) { export const isEnabled = function(playlist) {
const blacklisted = isBlacklisted(playlist); const excluded = isExcluded(playlist);
return (!playlist.disabled && !blacklisted); return (!playlist.disabled && !excluded);
}; };
/** /**
@ -647,14 +645,14 @@ export const estimateSegmentRequestTime = function(
* *
* @return {Boolean} true if on lowest rendition * @return {Boolean} true if on lowest rendition
*/ */
export const isLowestEnabledRendition = (master, media) => { export const isLowestEnabledRendition = (main, media) => {
if (master.playlists.length === 1) { if (main.playlists.length === 1) {
return true; return true;
} }
const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE; const currentBandwidth = media.attributes.BANDWIDTH || Number.MAX_VALUE;
return (master.playlists.filter((playlist) => { return (main.playlists.filter((playlist) => {
if (!isEnabled(playlist)) { if (!isEnabled(playlist)) {
return false; return false;
} }
@ -698,8 +696,8 @@ export const playlistMatch = (a, b) => {
return false; return false;
}; };
const someAudioVariant = function(master, callback) { const someAudioVariant = function(main, callback) {
const AUDIO = master && master.mediaGroups && master.mediaGroups.AUDIO || {}; const AUDIO = main && main.mediaGroups && main.mediaGroups.AUDIO || {};
let found = false; let found = false;
for (const groupName in AUDIO) { for (const groupName in AUDIO) {
@ -719,21 +717,21 @@ const someAudioVariant = function(master, callback) {
return !!found; return !!found;
}; };
export const isAudioOnly = (master) => { export const isAudioOnly = (main) => {
// we are audio only if we have no main playlists but do // we are audio only if we have no main playlists but do
// have media group playlists. // have media group playlists.
if (!master || !master.playlists || !master.playlists.length) { if (!main || !main.playlists || !main.playlists.length) {
// without audio variants or playlists this // without audio variants or playlists this
// is not an audio only master. // is not an audio only main.
const found = someAudioVariant(master, (variant) => const found = someAudioVariant(main, (variant) =>
(variant.playlists && variant.playlists.length) || variant.uri); (variant.playlists && variant.playlists.length) || variant.uri);
return found; return found;
} }
// if every playlist has only an audio codec it is audio only // if every playlist has only an audio codec it is audio only
for (let i = 0; i < master.playlists.length; i++) { for (let i = 0; i < main.playlists.length; i++) {
const playlist = master.playlists[i]; const playlist = main.playlists[i];
const CODECS = playlist.attributes && playlist.attributes.CODECS; const CODECS = playlist.attributes && playlist.attributes.CODECS;
// all codecs are audio, this is an audio playlist. // all codecs are audio, this is an audio playlist.
@ -742,7 +740,7 @@ export const isAudioOnly = (master) => {
} }
// playlist is in an audio group it is audio only // playlist is in an audio group it is audio only
const found = someAudioVariant(master, (variant) => playlistMatch(playlist, variant)); const found = someAudioVariant(main, (variant) => playlistMatch(playlist, variant));
if (found) { if (found) {
continue; continue;
@ -766,7 +764,7 @@ export default {
getMediaInfoForTime, getMediaInfoForTime,
isEnabled, isEnabled,
isDisabled, isDisabled,
isBlacklisted, isExcluded,
isIncompatible, isIncompatible,
playlistEnd, playlistEnd,
isAes, isAes,

View file

@ -4,8 +4,7 @@
* Utilities for working with TimeRanges. * Utilities for working with TimeRanges.
* *
*/ */
import {createTimeRanges} from './util/vjs-compat';
import videojs from 'video.js';
// Fudge factor to account for TimeRanges rounding // Fudge factor to account for TimeRanges rounding
export const TIME_FUDGE_FACTOR = 1 / 30; export const TIME_FUDGE_FACTOR = 1 / 30;
@ -40,7 +39,7 @@ const filterRanges = function(timeRanges, predicate) {
} }
} }
return videojs.createTimeRanges(results); return createTimeRanges(results);
}; };
/** /**
@ -79,7 +78,7 @@ export const findNextRange = function(timeRanges, time) {
*/ */
export const findGaps = function(buffered) { export const findGaps = function(buffered) {
if (buffered.length < 2) { if (buffered.length < 2) {
return videojs.createTimeRanges(); return createTimeRanges();
} }
const ranges = []; const ranges = [];
@ -91,7 +90,7 @@ export const findGaps = function(buffered) {
ranges.push([start, end]); ranges.push([start, end]);
} }
return videojs.createTimeRanges(ranges); return createTimeRanges(ranges);
}; };
/** /**
@ -170,7 +169,7 @@ export const bufferIntersection = function(bufferA, bufferB) {
const ranges = []; const ranges = [];
if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) { if (!bufferA || !bufferA.length || !bufferB || !bufferB.length) {
return videojs.createTimeRange(); return createTimeRanges();
} }
// Handle the case where we have both buffers and create an // Handle the case where we have both buffers and create an
@ -221,7 +220,7 @@ export const bufferIntersection = function(bufferA, bufferB) {
} }
} }
return videojs.createTimeRanges(ranges); return createTimeRanges(ranges);
}; };
/** /**
@ -300,7 +299,7 @@ export const getSegmentBufferedPercent = function(
const endOfSegment = startOfSegment + segmentDuration; const endOfSegment = startOfSegment + segmentDuration;
// The entire time range of the segment // The entire time range of the segment
const originalSegmentRange = videojs.createTimeRanges([[ const originalSegmentRange = createTimeRanges([[
startOfSegment, startOfSegment,
endOfSegment endOfSegment
]]); ]]);
@ -311,7 +310,7 @@ export const getSegmentBufferedPercent = function(
// for that and the function will still return 100% if a only half of a // for that and the function will still return 100% if a only half of a
// segment is actually in the buffer as long as the currentTime is also // segment is actually in the buffer as long as the currentTime is also
// half-way through the segment // half-way through the segment
const adjustedSegmentRange = videojs.createTimeRanges([[ const adjustedSegmentRange = createTimeRanges([[
clamp(startOfSegment, [currentTime, endOfSegment]), clamp(startOfSegment, [currentTime, endOfSegment]),
endOfSegment endOfSegment
]]); ]]);

View file

@ -1,4 +1,5 @@
import videojs from 'video.js'; import videojs from 'video.js';
import {merge} from './util/vjs-compat';
const defaultOptions = { const defaultOptions = {
errorInterval: 30, errorInterval: 30,
@ -20,11 +21,10 @@ const defaultOptions = {
const initPlugin = function(player, options) { const initPlugin = function(player, options) {
let lastCalled = 0; let lastCalled = 0;
let seekTo = 0; let seekTo = 0;
const localOptions = videojs.mergeOptions(defaultOptions, options); const localOptions = merge(defaultOptions, options);
player.ready(() => { player.ready(() => {
player.trigger({type: 'usage', name: 'vhs-error-reload-initialized'}); player.trigger({type: 'usage', name: 'vhs-error-reload-initialized'});
player.trigger({type: 'usage', name: 'hls-error-reload-initialized'});
}); });
/** /**
@ -55,7 +55,6 @@ const initPlugin = function(player, options) {
player.src(sourceObj); player.src(sourceObj);
player.trigger({type: 'usage', name: 'vhs-error-reload'}); player.trigger({type: 'usage', name: 'vhs-error-reload'});
player.trigger({type: 'usage', name: 'hls-error-reload'});
player.play(); player.play();
}; };
@ -70,7 +69,6 @@ const initPlugin = function(player, options) {
// 'errorInterval' time has elapsed since the last source-reload // 'errorInterval' time has elapsed since the last source-reload
if (Date.now() - lastCalled < localOptions.errorInterval * 1000) { if (Date.now() - lastCalled < localOptions.errorInterval * 1000) {
player.trigger({type: 'usage', name: 'vhs-error-reload-canceled'}); player.trigger({type: 'usage', name: 'vhs-error-reload-canceled'});
player.trigger({type: 'usage', name: 'hls-error-reload-canceled'});
return; return;
} }

View file

@ -4,7 +4,7 @@ import { codecsForPlaylist } from './util/codecs.js';
/** /**
* Returns a function that acts as the Enable/disable playlist function. * Returns a function that acts as the Enable/disable playlist function.
* *
* @param {PlaylistLoader} loader - The master playlist loader * @param {PlaylistLoader} loader - The main playlist loader
* @param {string} playlistID - id of the playlist * @param {string} playlistID - id of the playlist
* @param {Function} changePlaylistFn - A function to be called after a * @param {Function} changePlaylistFn - A function to be called after a
* playlist's enabled-state has been changed. Will NOT be called if a * playlist's enabled-state has been changed. Will NOT be called if a
@ -14,7 +14,7 @@ import { codecsForPlaylist } from './util/codecs.js';
* @return {Function} Function for setting/getting enabled * @return {Function} Function for setting/getting enabled
*/ */
const enableFunction = (loader, playlistID, changePlaylistFn) => (enable) => { const enableFunction = (loader, playlistID, changePlaylistFn) => (enable) => {
const playlist = loader.master.playlists[playlistID]; const playlist = loader.main.playlists[playlistID];
const incompatible = isIncompatible(playlist); const incompatible = isIncompatible(playlist);
const currentlyEnabled = isEnabled(playlist); const currentlyEnabled = isEnabled(playlist);
@ -50,12 +50,9 @@ const enableFunction = (loader, playlistID, changePlaylistFn) => (enable) => {
class Representation { class Representation {
constructor(vhsHandler, playlist, id) { constructor(vhsHandler, playlist, id) {
const { const {
masterPlaylistController_: mpc, playlistController_: pc
options_: { smoothQualityChange }
} = vhsHandler; } = vhsHandler;
// Get a reference to a bound version of the quality change function const qualityChangeFunction = pc.fastQualityChange_.bind(pc);
const changeType = smoothQualityChange ? 'smooth' : 'fast';
const qualityChangeFunction = mpc[`${changeType}QualityChange_`].bind(mpc);
// some playlist attributes are optional // some playlist attributes are optional
if (playlist.attributes) { if (playlist.attributes) {
@ -68,12 +65,12 @@ class Representation {
this.frameRate = playlist.attributes['FRAME-RATE']; this.frameRate = playlist.attributes['FRAME-RATE'];
} }
this.codecs = codecsForPlaylist(mpc.master(), playlist); this.codecs = codecsForPlaylist(pc.main(), playlist);
this.playlist = playlist; this.playlist = playlist;
// The id is simply the ordinality of the media playlist // The id is simply the ordinality of the media playlist
// within the master playlist // within the main playlist
this.id = id; this.id = id;
// Partially-apply the enableFunction to create a playlist- // Partially-apply the enableFunction to create a playlist-
@ -97,10 +94,10 @@ const renditionSelectionMixin = function(vhsHandler) {
// Add a single API-specific function to the VhsHandler instance // Add a single API-specific function to the VhsHandler instance
vhsHandler.representations = () => { vhsHandler.representations = () => {
const master = vhsHandler.masterPlaylistController_.master(); const main = vhsHandler.playlistController_.main();
const playlists = isAudioOnly(master) ? const playlists = isAudioOnly(main) ?
vhsHandler.masterPlaylistController_.getAudioTrackPlaylists_() : vhsHandler.playlistController_.getAudioTrackPlaylists_() :
master.playlists; main.playlists;
if (!playlists) { if (!playlists) {
return []; return [];

View file

@ -7,8 +7,8 @@ import _resolveUrl from '@videojs/vhs-utils/es/resolve-url.js';
export const resolveUrl = _resolveUrl; export const resolveUrl = _resolveUrl;
/** /**
* Checks whether xhr request was redirected and returns correct url depending * If the xhr request was redirected, return the responseURL, otherwise,
* on `handleManifestRedirects` option * return the original url.
* *
* @api private * @api private
* *
@ -17,12 +17,11 @@ export const resolveUrl = _resolveUrl;
* *
* @return {string} * @return {string}
*/ */
export const resolveManifestRedirect = (handleManifestRedirect, url, req) => { export const resolveManifestRedirect = (url, req) => {
// To understand how the responseURL below is set and generated: // To understand how the responseURL below is set and generated:
// - https://fetch.spec.whatwg.org/#concept-response-url // - https://fetch.spec.whatwg.org/#concept-response-url
// - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling // - https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
if ( if (
handleManifestRedirect &&
req && req &&
req.responseURL && req.responseURL &&
url !== req.responseURL url !== req.responseURL

View file

@ -24,6 +24,7 @@ import shallowEqual from './util/shallow-equal.js';
import { QUOTA_EXCEEDED_ERR } from './error-codes'; import { QUOTA_EXCEEDED_ERR } from './error-codes';
import {timeRangesToArray, lastBufferedEnd, timeAheadOf} from './ranges.js'; import {timeRangesToArray, lastBufferedEnd, timeAheadOf} from './ranges.js';
import {getKnownPartCount} from './playlist.js'; import {getKnownPartCount} from './playlist.js';
import {createTimeRanges} from './util/vjs-compat';
/** /**
* The segment loader has no recourse except to fetch a segment in the * The segment loader has no recourse except to fetch a segment in the
@ -561,7 +562,7 @@ export default class SegmentLoader extends videojs.EventTarget {
this.parse708captions_ = settings.parse708captions; this.parse708captions_ = settings.parse708captions;
this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset; this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
this.captionServices_ = settings.captionServices; this.captionServices_ = settings.captionServices;
this.experimentalExactManifestTimings = settings.experimentalExactManifestTimings; this.exactManifestTimings = settings.exactManifestTimings;
// private instance variables // private instance variables
this.checkBufferTimeout_ = null; this.checkBufferTimeout_ = null;
@ -838,7 +839,7 @@ export default class SegmentLoader extends videojs.EventTarget {
const trackInfo = this.getMediaInfo_(); const trackInfo = this.getMediaInfo_();
if (!this.sourceUpdater_ || !trackInfo) { if (!this.sourceUpdater_ || !trackInfo) {
return videojs.createTimeRanges(); return createTimeRanges();
} }
if (this.loaderType_ === 'main') { if (this.loaderType_ === 'main') {
@ -1162,6 +1163,7 @@ export default class SegmentLoader extends videojs.EventTarget {
*/ */
resetEverything(done) { resetEverything(done) {
this.ended_ = false; this.ended_ = false;
this.activeInitSegmentId_ = null;
this.appendInitSegment_ = { this.appendInitSegment_ = {
audio: true, audio: true,
video: true video: true
@ -1445,7 +1447,7 @@ export default class SegmentLoader extends videojs.EventTarget {
} else { } else {
// Find the segment containing the end of the buffer or current time. // Find the segment containing the end of the buffer or current time.
const {segmentIndex, startTime, partIndex} = Playlist.getMediaInfoForTime({ const {segmentIndex, startTime, partIndex} = Playlist.getMediaInfoForTime({
experimentalExactManifestTimings: this.experimentalExactManifestTimings, exactManifestTimings: this.exactManifestTimings,
playlist: this.playlist_, playlist: this.playlist_,
currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(), currentTime: this.fetchAtBuffer_ ? bufferedEnd : this.currentTime_(),
startingPartIndex: this.syncPoint_.partIndex, startingPartIndex: this.syncPoint_.partIndex,
@ -1653,7 +1655,7 @@ export default class SegmentLoader extends videojs.EventTarget {
} }
const switchCandidate = minRebufferMaxBandwidthSelector({ const switchCandidate = minRebufferMaxBandwidthSelector({
master: this.vhs_.playlists.master, main: this.vhs_.playlists.main,
currentTime, currentTime,
bandwidth: measuredBandwidth, bandwidth: measuredBandwidth,
duration: this.duration_(), duration: this.duration_(),
@ -1982,6 +1984,10 @@ export default class SegmentLoader extends videojs.EventTarget {
return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_; return this.getCurrentMediaInfo_(segmentInfo) || this.startingMediaInfo_;
} }
getPendingSegmentPlaylist() {
return this.pendingSegment_ ? this.pendingSegment_.playlist : null;
}
hasEnoughInfoToAppend_() { hasEnoughInfoToAppend_() {
if (!this.sourceUpdater_.ready()) { if (!this.sourceUpdater_.ready()) {
return false; return false;
@ -2690,7 +2696,7 @@ export default class SegmentLoader extends videojs.EventTarget {
} }
// if control-flow has arrived here, then the error is real // if control-flow has arrived here, then the error is real
// emit an error event to blacklist the current playlist // emit an error event to exclude the current playlist
this.mediaRequestsErrored += 1; this.mediaRequestsErrored += 1;
this.error(error); this.error(error);
this.trigger('error'); this.trigger('error');
@ -2799,7 +2805,7 @@ export default class SegmentLoader extends videojs.EventTarget {
if (!trackInfo) { if (!trackInfo) {
this.error({ this.error({
message: 'No starting media returned, likely due to an unsupported media format.', message: 'No starting media returned, likely due to an unsupported media format.',
blacklistDuration: Infinity playlistExclusionDuration: Infinity
}); });
this.trigger('error'); this.trigger('error');
return; return;
@ -2880,7 +2886,7 @@ export default class SegmentLoader extends videojs.EventTarget {
if (illegalMediaSwitchError) { if (illegalMediaSwitchError) {
this.error({ this.error({
message: illegalMediaSwitchError, message: illegalMediaSwitchError,
blacklistDuration: Infinity playlistExclusionDuration: Infinity
}); });
this.trigger('error'); this.trigger('error');
return true; return true;

View file

@ -9,6 +9,7 @@ import {getMimeForCodec} from '@videojs/vhs-utils/es/codecs.js';
import window from 'global/window'; import window from 'global/window';
import toTitleCase from './util/to-title-case.js'; import toTitleCase from './util/to-title-case.js';
import { QUOTA_EXCEEDED_ERR } from './error-codes'; import { QUOTA_EXCEEDED_ERR } from './error-codes';
import {createTimeRanges} from './util/vjs-compat';
const bufferTypes = [ const bufferTypes = [
'video', 'video',
@ -605,11 +606,11 @@ export default class SourceUpdater extends videojs.EventTarget {
// no media source/source buffer or it isn't in the media sources // no media source/source buffer or it isn't in the media sources
// source buffer list // source buffer list
if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) { if (!inSourceBuffers(this.mediaSource, this.audioBuffer)) {
return videojs.createTimeRange(); return createTimeRanges();
} }
return this.audioBuffer.buffered ? this.audioBuffer.buffered : return this.audioBuffer.buffered ? this.audioBuffer.buffered :
videojs.createTimeRange(); createTimeRanges();
} }
/** /**
@ -622,10 +623,10 @@ export default class SourceUpdater extends videojs.EventTarget {
// no media source/source buffer or it isn't in the media sources // no media source/source buffer or it isn't in the media sources
// source buffer list // source buffer list
if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) { if (!inSourceBuffers(this.mediaSource, this.videoBuffer)) {
return videojs.createTimeRange(); return createTimeRanges();
} }
return this.videoBuffer.buffered ? this.videoBuffer.buffered : return this.videoBuffer.buffered ? this.videoBuffer.buffered :
videojs.createTimeRange(); createTimeRanges();
} }
/** /**

View file

@ -29,21 +29,21 @@ const getCodecs = function(media) {
} }
}; };
export const isMaat = (master, media) => { export const isMaat = (main, media) => {
const mediaAttributes = media.attributes || {}; const mediaAttributes = media.attributes || {};
return master && master.mediaGroups && master.mediaGroups.AUDIO && return main && main.mediaGroups && main.mediaGroups.AUDIO &&
mediaAttributes.AUDIO && mediaAttributes.AUDIO &&
master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
}; };
export const isMuxed = (master, media) => { export const isMuxed = (main, media) => {
if (!isMaat(master, media)) { if (!isMaat(main, media)) {
return true; return true;
} }
const mediaAttributes = media.attributes || {}; const mediaAttributes = media.attributes || {};
const audioGroup = master.mediaGroups.AUDIO[mediaAttributes.AUDIO]; const audioGroup = main.mediaGroups.AUDIO[mediaAttributes.AUDIO];
for (const groupId in audioGroup) { for (const groupId in audioGroup) {
// If an audio group has a URI (the case for HLS, as HLS will use external playlists), // If an audio group has a URI (the case for HLS, as HLS will use external playlists),
@ -95,28 +95,28 @@ export const codecCount = function(codecObj) {
/** /**
* Calculates the codec strings for a working configuration of * Calculates the codec strings for a working configuration of
* SourceBuffers to play variant streams in a master playlist. If * SourceBuffers to play variant streams in a main playlist. If
* there is no possible working configuration, an empty object will be * there is no possible working configuration, an empty object will be
* returned. * returned.
* *
* @param master {Object} the m3u8 object for the master playlist * @param main {Object} the m3u8 object for the main playlist
* @param media {Object} the m3u8 object for the variant playlist * @param media {Object} the m3u8 object for the variant playlist
* @return {Object} the codec strings. * @return {Object} the codec strings.
* *
* @private * @private
*/ */
export const codecsForPlaylist = function(master, media) { export const codecsForPlaylist = function(main, media) {
const mediaAttributes = media.attributes || {}; const mediaAttributes = media.attributes || {};
const codecInfo = unwrapCodecList(getCodecs(media) || []); const codecInfo = unwrapCodecList(getCodecs(media) || []);
// HLS with multiple-audio tracks must always get an audio codec. // HLS with multiple-audio tracks must always get an audio codec.
// Put another way, there is no way to have a video-only multiple-audio HLS! // Put another way, there is no way to have a video-only multiple-audio HLS!
if (isMaat(master, media) && !codecInfo.audio) { if (isMaat(main, media) && !codecInfo.audio) {
if (!isMuxed(master, media)) { if (!isMuxed(main, media)) {
// It is possible for codecs to be specified on the audio media group playlist but // It is possible for codecs to be specified on the audio media group playlist but
// not on the rendition playlist. This is mostly the case for DASH, where audio and // not on the rendition playlist. This is mostly the case for DASH, where audio and
// video are always separate (and separately specified). // video are always separate (and separately specified).
const defaultCodecs = unwrapCodecList(codecsFromDefault(master, mediaAttributes.AUDIO) || []); const defaultCodecs = unwrapCodecList(codecsFromDefault(main, mediaAttributes.AUDIO) || []);
if (defaultCodecs.audio) { if (defaultCodecs.audio) {
codecInfo.audio = defaultCodecs.audio; codecInfo.audio = defaultCodecs.audio;

View file

@ -15,7 +15,6 @@ import videojs from 'video.js';
export const createCaptionsTrackIfNotExists = function(inbandTextTracks, tech, captionStream) { export const createCaptionsTrackIfNotExists = function(inbandTextTracks, tech, captionStream) {
if (!inbandTextTracks[captionStream]) { if (!inbandTextTracks[captionStream]) {
tech.trigger({type: 'usage', name: 'vhs-608'}); tech.trigger({type: 'usage', name: 'vhs-608'});
tech.trigger({type: 'usage', name: 'hls-608'});
let instreamId = captionStream; let instreamId = captionStream;

View file

@ -1,8 +1,8 @@
/** /**
* @file videojs-http-streaming.js * @file videojs-http-streaming.js
* *
* The main file for the HLS project. * The main file for the VHS project.
* License: https://github.com/videojs/videojs-http-streaming/blob/master/LICENSE * License: https://github.com/videojs/videojs-http-streaming/blob/main/LICENSE
*/ */
import document from 'global/document'; import document from 'global/document';
import window from 'global/window'; import window from 'global/window';
@ -17,7 +17,7 @@ import {
} from './util/time'; } from './util/time';
import { timeRangesToArray } from './ranges'; import { timeRangesToArray } from './ranges';
import videojs from 'video.js'; import videojs from 'video.js';
import { MasterPlaylistController } from './master-playlist-controller'; import { PlaylistController } from './playlist-controller';
import Config from './config'; import Config from './config';
import renditionSelectionMixin from './rendition-mixin'; import renditionSelectionMixin from './rendition-mixin';
import PlaybackWatcher from './playback-watcher'; import PlaybackWatcher from './playback-watcher';
@ -38,6 +38,7 @@ import {
import { unwrapCodecList } from './util/codecs.js'; import { unwrapCodecList } from './util/codecs.js';
import logger from './util/logger'; import logger from './util/logger';
import {SAFE_TIME_DELTA} from './ranges'; import {SAFE_TIME_DELTA} from './ranges';
import {merge} from './util/vjs-compat';
// IMPORTANT: // IMPORTANT:
// keep these at the bottom they are replaced at build time // keep these at the bottom they are replaced at build time
@ -125,10 +126,10 @@ const handleVhsLoadedMetadata = function(qualityLevels, vhs) {
handleVhsMediaChange(qualityLevels, vhs.playlists); handleVhsMediaChange(qualityLevels, vhs.playlists);
}; };
// HLS is a source handler, not a tech. Make sure attempts to use it // VHS is a source handler, not a tech. Make sure attempts to use it
// as one do not cause exceptions. // as one do not cause exceptions.
Vhs.canPlaySource = function() { Vhs.canPlaySource = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + return videojs.log.warn('VHS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.'); 'your player\'s techOrder.');
}; };
@ -183,7 +184,7 @@ const emeKeySystems = (keySystemOptions, mainPlaylist, audioPlaylist) => {
} }
} }
return videojs.mergeOptions(keySystemOptions, keySystemContentTypes); return merge(keySystemOptions, keySystemContentTypes);
}; };
/** /**
@ -252,7 +253,7 @@ const getAllPsshKeySystemsOptions = (playlists, keySystems) => {
* @param {Object} [audioMedia] * @param {Object} [audioMedia]
* The active audio media playlist (optional) * The active audio media playlist (optional)
* @param {Object[]} mainPlaylists * @param {Object[]} mainPlaylists
* The playlists found on the master playlist object * The playlists found on the main playlist object
* *
* @return {Object} * @return {Object}
* Promise that resolves when the key session has been created * Promise that resolves when the key session has been created
@ -391,7 +392,7 @@ const updateVhsLocalStorage = (options) => {
let objectToStore = getVhsLocalStorage(); let objectToStore = getVhsLocalStorage();
objectToStore = objectToStore ? videojs.mergeOptions(objectToStore, options) : options; objectToStore = objectToStore ? merge(objectToStore, options) : options;
try { try {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore)); window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(objectToStore));
@ -483,11 +484,11 @@ Vhs.supportsTypeNatively = (type) => {
}; };
/** /**
* HLS is a source handler, not a tech. Make sure attempts to use it * VHS is a source handler, not a tech. Make sure attempts to use it
* as one do not cause exceptions. * as one do not cause exceptions.
*/ */
Vhs.isSupported = function() { Vhs.isSupported = function() {
return videojs.log.warn('HLS is no longer a tech. Please remove it from ' + return videojs.log.warn('VHS is no longer a tech. Please remove it from ' +
'your player\'s techOrder.'); 'your player\'s techOrder.');
}; };
@ -495,7 +496,7 @@ const Component = videojs.getComponent('Component');
/** /**
* The Vhs Handler object, where we orchestrate all of the parts * The Vhs Handler object, where we orchestrate all of the parts
* of HLS to interact with video.js * of VHS to interact with video.js
* *
* @class VhsHandler * @class VhsHandler
* @extends videojs.Component * @extends videojs.Component
@ -505,11 +506,7 @@ const Component = videojs.getComponent('Component');
*/ */
class VhsHandler extends Component { class VhsHandler extends Component {
constructor(source, tech, options) { constructor(source, tech, options) {
super(tech, videojs.mergeOptions(options.hls, options.vhs)); super(tech, options.vhs);
if (options.hls && Object.keys(options.hls).length) {
videojs.log.warn('Using hls options is deprecated. Please rename `hls` to `vhs` in your options object.');
}
// if a tech level `initialBandwidth` option was passed // if a tech level `initialBandwidth` option was passed
// use that over the VHS level `bandwidth` option // use that over the VHS level `bandwidth` option
@ -519,42 +516,10 @@ class VhsHandler extends Component {
this.logger_ = logger('VhsHandler'); this.logger_ = logger('VhsHandler');
// tech.player() is deprecated but setup a reference to HLS for // we need access to the player in some cases,
// backwards-compatibility // so, get it from Video.js via the `playerId`
if (tech.options_ && tech.options_.playerId) { if (tech.options_ && tech.options_.playerId) {
const _player = videojs(tech.options_.playerId); const _player = videojs.getPlayer(tech.options_.playerId);
if (!_player.hasOwnProperty('hls')) {
Object.defineProperty(_player, 'hls', {
get: () => {
videojs.log.warn('player.hls is deprecated. Use player.tech().vhs instead.');
tech.trigger({ type: 'usage', name: 'hls-player-access' });
return this;
},
configurable: true
});
}
if (!_player.hasOwnProperty('vhs')) {
Object.defineProperty(_player, 'vhs', {
get: () => {
videojs.log.warn('player.vhs is deprecated. Use player.tech().vhs instead.');
tech.trigger({ type: 'usage', name: 'vhs-player-access' });
return this;
},
configurable: true
});
}
if (!_player.hasOwnProperty('dash')) {
Object.defineProperty(_player, 'dash', {
get: () => {
videojs.log.warn('player.dash is deprecated. Use player.tech().vhs instead.');
return this;
},
configurable: true
});
}
this.player_ = _player; this.player_ = _player;
} }
@ -572,9 +537,9 @@ class VhsHandler extends Component {
tech.overrideNativeVideoTracks(true); tech.overrideNativeVideoTracks(true);
} else if (this.options_.overrideNative && } else if (this.options_.overrideNative &&
(tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) { (tech.featuresNativeVideoTracks || tech.featuresNativeAudioTracks)) {
// overriding native HLS only works if audio tracks have been emulated // overriding native VHS only works if audio tracks have been emulated
// error early if we're misconfigured // error early if we're misconfigured
throw new Error('Overriding native HLS requires emulated tracks. ' + throw new Error('Overriding native VHS requires emulated tracks. ' +
'See https://git.io/vMpjB'); 'See https://git.io/vMpjB');
} }
@ -590,12 +555,12 @@ class VhsHandler extends Component {
document.msFullscreenElement; document.msFullscreenElement;
if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) { if (fullscreenElement && fullscreenElement.contains(this.tech_.el())) {
this.masterPlaylistController_.fastQualityChange_(); this.playlistController_.fastQualityChange_();
} else { } else {
// When leaving fullscreen, since the in page pixel dimensions should be smaller // When leaving fullscreen, since the in page pixel dimensions should be smaller
// than full screen, see if there should be a rendition switch down to preserve // than full screen, see if there should be a rendition switch down to preserve
// bandwidth. // bandwidth.
this.masterPlaylistController_.checkABR_(); this.playlistController_.checkABR_();
} }
}); });
@ -610,9 +575,9 @@ class VhsHandler extends Component {
this.on(this.tech_, 'error', function() { this.on(this.tech_, 'error', function() {
// verify that the error was real and we are loaded // verify that the error was real and we are loaded
// enough to have mpc loaded. // enough to have pc loaded.
if (this.tech_.error() && this.masterPlaylistController_) { if (this.tech_.error() && this.playlistController_) {
this.masterPlaylistController_.pauseLoading(); this.playlistController_.pauseLoading();
} }
}); });
@ -622,10 +587,8 @@ class VhsHandler extends Component {
setOptions_() { setOptions_() {
// defaults // defaults
this.options_.withCredentials = this.options_.withCredentials || false; this.options_.withCredentials = this.options_.withCredentials || false;
this.options_.handleManifestRedirects = this.options_.handleManifestRedirects === false ? false : true;
this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true; this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false; this.options_.useDevicePixelRatio = this.options_.useDevicePixelRatio || false;
this.options_.smoothQualityChange = this.options_.smoothQualityChange || false;
this.options_.useBandwidthFromLocalStorage = this.options_.useBandwidthFromLocalStorage =
typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ? typeof this.source_.useBandwidthFromLocalStorage !== 'undefined' ?
this.source_.useBandwidthFromLocalStorage : this.source_.useBandwidthFromLocalStorage :
@ -635,9 +598,11 @@ class VhsHandler extends Component {
this.options_.customTagParsers = this.options_.customTagParsers || []; this.options_.customTagParsers = this.options_.customTagParsers || [];
this.options_.customTagMappers = this.options_.customTagMappers || []; this.options_.customTagMappers = this.options_.customTagMappers || [];
this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false; this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
this.options_.llhls = this.options_.llhls === false ? false : true;
this.options_.bufferBasedABR = this.options_.bufferBasedABR || false;
if (typeof this.options_.blacklistDuration !== 'number') { if (typeof this.options_.playlistExclusionDuration !== 'number') {
this.options_.blacklistDuration = 5 * 60; this.options_.playlistExclusionDuration = 5 * 60;
} }
if (typeof this.options_.bandwidth !== 'number') { if (typeof this.options_.bandwidth !== 'number') {
@ -647,12 +612,10 @@ class VhsHandler extends Component {
if (storedObject && storedObject.bandwidth) { if (storedObject && storedObject.bandwidth) {
this.options_.bandwidth = storedObject.bandwidth; this.options_.bandwidth = storedObject.bandwidth;
this.tech_.trigger({type: 'usage', name: 'vhs-bandwidth-from-local-storage'}); this.tech_.trigger({type: 'usage', name: 'vhs-bandwidth-from-local-storage'});
this.tech_.trigger({type: 'usage', name: 'hls-bandwidth-from-local-storage'});
} }
if (storedObject && storedObject.throughput) { if (storedObject && storedObject.throughput) {
this.options_.throughput = storedObject.throughput; this.options_.throughput = storedObject.throughput;
this.tech_.trigger({type: 'usage', name: 'vhs-throughput-from-local-storage'}); this.tech_.trigger({type: 'usage', name: 'vhs-throughput-from-local-storage'});
this.tech_.trigger({type: 'usage', name: 'hls-throughput-from-local-storage'});
} }
} }
} }
@ -674,20 +637,18 @@ class VhsHandler extends Component {
'useDevicePixelRatio', 'useDevicePixelRatio',
'limitRenditionByPlayerDimensions', 'limitRenditionByPlayerDimensions',
'bandwidth', 'bandwidth',
'smoothQualityChange',
'customTagParsers', 'customTagParsers',
'customTagMappers', 'customTagMappers',
'handleManifestRedirects',
'cacheEncryptionKeys', 'cacheEncryptionKeys',
'playlistSelector', 'playlistSelector',
'initialPlaylistSelector', 'initialPlaylistSelector',
'experimentalBufferBasedABR', 'bufferBasedABR',
'liveRangeSafeTimeDelta', 'liveRangeSafeTimeDelta',
'experimentalLLHLS', 'llhls',
'useNetworkInformationApi', 'useNetworkInformationApi',
'useDtsForTimestampOffset', 'useDtsForTimestampOffset',
'experimentalExactManifestTimings', 'exactManifestTimings',
'experimentalLeastPixelDiffSelector' 'leastPixelDiffSelector'
].forEach((option) => { ].forEach((option) => {
if (typeof this.source_[option] !== 'undefined') { if (typeof this.source_[option] !== 'undefined') {
this.options_[option] = this.source_[option]; this.options_[option] = this.source_[option];
@ -708,7 +669,7 @@ class VhsHandler extends Component {
return; return;
} }
this.setOptions_(); this.setOptions_();
// add master playlist controller options // add main playlist controller options
this.options_.src = expandDataUri(this.source_.src); this.options_.src = expandDataUri(this.source_.src);
this.options_.tech = this.tech_; this.options_.tech = this.tech_;
this.options_.externVhs = Vhs; this.options_.externVhs = Vhs;
@ -719,29 +680,25 @@ class VhsHandler extends Component {
this.tech_.setCurrentTime(time); this.tech_.setCurrentTime(time);
}; };
if (this.options_.smoothQualityChange) { this.playlistController_ = new PlaylistController(this.options_);
videojs.log.warn('smoothQualityChange is deprecated and will be removed in the next major version');
}
this.masterPlaylistController_ = new MasterPlaylistController(this.options_); const playbackWatcherOptions = merge(
const playbackWatcherOptions = videojs.mergeOptions(
{ {
liveRangeSafeTimeDelta: SAFE_TIME_DELTA liveRangeSafeTimeDelta: SAFE_TIME_DELTA
}, },
this.options_, this.options_,
{ {
seekable: () => this.seekable(), seekable: () => this.seekable(),
media: () => this.masterPlaylistController_.media(), media: () => this.playlistController_.media(),
masterPlaylistController: this.masterPlaylistController_ playlistController: this.playlistController_
} }
); );
this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions); this.playbackWatcher_ = new PlaybackWatcher(playbackWatcherOptions);
this.masterPlaylistController_.on('error', () => { this.playlistController_.on('error', () => {
const player = videojs.players[this.tech_.options_.playerId]; const player = videojs.players[this.tech_.options_.playerId];
let error = this.masterPlaylistController_.error; let error = this.playlistController_.error;
if (typeof error === 'object' && !error.code) { if (typeof error === 'object' && !error.code) {
error.code = 3; error.code = 3;
@ -752,48 +709,48 @@ class VhsHandler extends Component {
player.error(error); player.error(error);
}); });
const defaultSelector = this.options_.experimentalBufferBasedABR ? const defaultSelector = this.options_.bufferBasedABR ?
Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR; Vhs.movingAverageBandwidthSelector(0.55) : Vhs.STANDARD_PLAYLIST_SELECTOR;
// `this` in selectPlaylist should be the VhsHandler for backwards // `this` in selectPlaylist should be the VhsHandler for backwards
// compatibility with < v2 // compatibility with < v2
this.masterPlaylistController_.selectPlaylist = this.selectPlaylist ? this.playlistController_.selectPlaylist = this.selectPlaylist ?
this.selectPlaylist.bind(this) : this.selectPlaylist.bind(this) :
defaultSelector.bind(this); defaultSelector.bind(this);
this.masterPlaylistController_.selectInitialPlaylist = this.playlistController_.selectInitialPlaylist =
Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this); Vhs.INITIAL_PLAYLIST_SELECTOR.bind(this);
// re-expose some internal objects for backwards compatibility with < v2 // re-expose some internal objects for backwards compatibility with < v2
this.playlists = this.masterPlaylistController_.masterPlaylistLoader_; this.playlists = this.playlistController_.mainPlaylistLoader_;
this.mediaSource = this.masterPlaylistController_.mediaSource; this.mediaSource = this.playlistController_.mediaSource;
// Proxy assignment of some properties to the master playlist // Proxy assignment of some properties to the main playlist
// controller. Using a custom property for backwards compatibility // controller. Using a custom property for backwards compatibility
// with < v2 // with < v2
Object.defineProperties(this, { Object.defineProperties(this, {
selectPlaylist: { selectPlaylist: {
get() { get() {
return this.masterPlaylistController_.selectPlaylist; return this.playlistController_.selectPlaylist;
}, },
set(selectPlaylist) { set(selectPlaylist) {
this.masterPlaylistController_.selectPlaylist = selectPlaylist.bind(this); this.playlistController_.selectPlaylist = selectPlaylist.bind(this);
} }
}, },
throughput: { throughput: {
get() { get() {
return this.masterPlaylistController_.mainSegmentLoader_.throughput.rate; return this.playlistController_.mainSegmentLoader_.throughput.rate;
}, },
set(throughput) { set(throughput) {
this.masterPlaylistController_.mainSegmentLoader_.throughput.rate = throughput; this.playlistController_.mainSegmentLoader_.throughput.rate = throughput;
// By setting `count` to 1 the throughput value becomes the starting value // By setting `count` to 1 the throughput value becomes the starting value
// for the cumulative average // for the cumulative average
this.masterPlaylistController_.mainSegmentLoader_.throughput.count = 1; this.playlistController_.mainSegmentLoader_.throughput.count = 1;
} }
}, },
bandwidth: { bandwidth: {
get() { get() {
let playerBandwidthEst = this.masterPlaylistController_.mainSegmentLoader_.bandwidth; let playerBandwidthEst = this.playlistController_.mainSegmentLoader_.bandwidth;
const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection; const networkInformation = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection;
const tenMbpsAsBitsPerSecond = 10e6; const tenMbpsAsBitsPerSecond = 10e6;
@ -816,11 +773,11 @@ class VhsHandler extends Component {
return playerBandwidthEst; return playerBandwidthEst;
}, },
set(bandwidth) { set(bandwidth) {
this.masterPlaylistController_.mainSegmentLoader_.bandwidth = bandwidth; this.playlistController_.mainSegmentLoader_.bandwidth = bandwidth;
// setting the bandwidth manually resets the throughput counter // setting the bandwidth manually resets the throughput counter
// `count` is set to zero that current value of `rate` isn't included // `count` is set to zero that current value of `rate` isn't included
// in the cumulative average // in the cumulative average
this.masterPlaylistController_.mainSegmentLoader_.throughput = { this.playlistController_.mainSegmentLoader_.throughput = {
rate: 0, rate: 0,
count: 0 count: 0
}; };
@ -869,51 +826,51 @@ class VhsHandler extends Component {
enumerable: true enumerable: true
}, },
mediaRequests: { mediaRequests: {
get: () => this.masterPlaylistController_.mediaRequests_() || 0, get: () => this.playlistController_.mediaRequests_() || 0,
enumerable: true enumerable: true
}, },
mediaRequestsAborted: { mediaRequestsAborted: {
get: () => this.masterPlaylistController_.mediaRequestsAborted_() || 0, get: () => this.playlistController_.mediaRequestsAborted_() || 0,
enumerable: true enumerable: true
}, },
mediaRequestsTimedout: { mediaRequestsTimedout: {
get: () => this.masterPlaylistController_.mediaRequestsTimedout_() || 0, get: () => this.playlistController_.mediaRequestsTimedout_() || 0,
enumerable: true enumerable: true
}, },
mediaRequestsErrored: { mediaRequestsErrored: {
get: () => this.masterPlaylistController_.mediaRequestsErrored_() || 0, get: () => this.playlistController_.mediaRequestsErrored_() || 0,
enumerable: true enumerable: true
}, },
mediaTransferDuration: { mediaTransferDuration: {
get: () => this.masterPlaylistController_.mediaTransferDuration_() || 0, get: () => this.playlistController_.mediaTransferDuration_() || 0,
enumerable: true enumerable: true
}, },
mediaBytesTransferred: { mediaBytesTransferred: {
get: () => this.masterPlaylistController_.mediaBytesTransferred_() || 0, get: () => this.playlistController_.mediaBytesTransferred_() || 0,
enumerable: true enumerable: true
}, },
mediaSecondsLoaded: { mediaSecondsLoaded: {
get: () => this.masterPlaylistController_.mediaSecondsLoaded_() || 0, get: () => this.playlistController_.mediaSecondsLoaded_() || 0,
enumerable: true enumerable: true
}, },
mediaAppends: { mediaAppends: {
get: () => this.masterPlaylistController_.mediaAppends_() || 0, get: () => this.playlistController_.mediaAppends_() || 0,
enumerable: true enumerable: true
}, },
mainAppendsToLoadedData: { mainAppendsToLoadedData: {
get: () => this.masterPlaylistController_.mainAppendsToLoadedData_() || 0, get: () => this.playlistController_.mainAppendsToLoadedData_() || 0,
enumerable: true enumerable: true
}, },
audioAppendsToLoadedData: { audioAppendsToLoadedData: {
get: () => this.masterPlaylistController_.audioAppendsToLoadedData_() || 0, get: () => this.playlistController_.audioAppendsToLoadedData_() || 0,
enumerable: true enumerable: true
}, },
appendsToLoadedData: { appendsToLoadedData: {
get: () => this.masterPlaylistController_.appendsToLoadedData_() || 0, get: () => this.playlistController_.appendsToLoadedData_() || 0,
enumerable: true enumerable: true
}, },
timeToLoadedData: { timeToLoadedData: {
get: () => this.masterPlaylistController_.timeToLoadedData_() || 0, get: () => this.playlistController_.timeToLoadedData_() || 0,
enumerable: true enumerable: true
}, },
buffered: { buffered: {
@ -936,8 +893,8 @@ class VhsHandler extends Component {
get: () => this.tech_.duration(), get: () => this.tech_.duration(),
enumerable: true enumerable: true
}, },
master: { main: {
get: () => this.playlists.master, get: () => this.playlists.main,
enumerable: true enumerable: true
}, },
playerDimensions: { playerDimensions: {
@ -960,7 +917,7 @@ class VhsHandler extends Component {
this.tech_.one( this.tech_.one(
'canplay', 'canplay',
this.masterPlaylistController_.setupFirstPlay.bind(this.masterPlaylistController_) this.playlistController_.setupFirstPlay.bind(this.playlistController_)
); );
this.tech_.on('bandwidthupdate', () => { this.tech_.on('bandwidthupdate', () => {
@ -972,24 +929,24 @@ class VhsHandler extends Component {
} }
}); });
this.masterPlaylistController_.on('selectedinitialmedia', () => { this.playlistController_.on('selectedinitialmedia', () => {
// Add the manual rendition mix-in to VhsHandler // Add the manual rendition mix-in to VhsHandler
renditionSelectionMixin(this); renditionSelectionMixin(this);
}); });
this.masterPlaylistController_.sourceUpdater_.on('createdsourcebuffers', () => { this.playlistController_.sourceUpdater_.on('createdsourcebuffers', () => {
this.setupEme_(); this.setupEme_();
}); });
// the bandwidth of the primary segment loader is our best // the bandwidth of the primary segment loader is our best
// estimate of overall bandwidth // estimate of overall bandwidth
this.on(this.masterPlaylistController_, 'progress', function() { this.on(this.playlistController_, 'progress', function() {
this.tech_.trigger('progress'); this.tech_.trigger('progress');
}); });
// In the live case, we need to ignore the very first `seeking` event since // In the live case, we need to ignore the very first `seeking` event since
// that will be the result of the seek-to-live behavior // that will be the result of the seek-to-live behavior
this.on(this.masterPlaylistController_, 'firstplay', function() { this.on(this.playlistController_, 'firstplay', function() {
this.ignoreNextSeekingEvent_ = true; this.ignoreNextSeekingEvent_ = true;
}); });
@ -1001,24 +958,24 @@ class VhsHandler extends Component {
return; return;
} }
this.mediaSourceUrl_ = window.URL.createObjectURL(this.masterPlaylistController_.mediaSource); this.mediaSourceUrl_ = window.URL.createObjectURL(this.playlistController_.mediaSource);
this.tech_.src(this.mediaSourceUrl_); this.tech_.src(this.mediaSourceUrl_);
} }
createKeySessions_() { createKeySessions_() {
const audioPlaylistLoader = const audioPlaylistLoader =
this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader; this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
this.logger_('waiting for EME key session creation'); this.logger_('waiting for EME key session creation');
waitForKeySessionCreation({ waitForKeySessionCreation({
player: this.player_, player: this.player_,
sourceKeySystems: this.source_.keySystems, sourceKeySystems: this.source_.keySystems,
audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(), audioMedia: audioPlaylistLoader && audioPlaylistLoader.media(),
mainPlaylists: this.playlists.master.playlists mainPlaylists: this.playlists.main.playlists
}).then(() => { }).then(() => {
this.logger_('created EME key session'); this.logger_('created EME key session');
this.masterPlaylistController_.sourceUpdater_.initializedEme(); this.playlistController_.sourceUpdater_.initializedEme();
}).catch((err) => { }).catch((err) => {
this.logger_('error while creating EME key session', err); this.logger_('error while creating EME key session', err);
this.player_.error({ this.player_.error({
@ -1051,7 +1008,7 @@ class VhsHandler extends Component {
*/ */
setupEme_() { setupEme_() {
const audioPlaylistLoader = const audioPlaylistLoader =
this.masterPlaylistController_.mediaTypes_.AUDIO.activePlaylistLoader; this.playlistController_.mediaTypes_.AUDIO.activePlaylistLoader;
const didSetupEmeOptions = setupEmeOptions({ const didSetupEmeOptions = setupEmeOptions({
player: this.player_, player: this.player_,
@ -1065,16 +1022,16 @@ class VhsHandler extends Component {
return; return;
} }
const masterPlaylist = this.masterPlaylistController_.master(); const mainPlaylist = this.playlistController_.main();
if (!masterPlaylist || !masterPlaylist.playlists) { if (!mainPlaylist || !mainPlaylist.playlists) {
return; return;
} }
const excludedHDPlaylists = []; const excludedHDPlaylists = [];
// Assume all HD streams are unplayable and exclude them from ABR selection // Assume all HD streams are unplayable and exclude them from ABR selection
masterPlaylist.playlists.forEach(playlist => { mainPlaylist.playlists.forEach(playlist => {
if (playlist && playlist.attributes && playlist.attributes.RESOLUTION && if (playlist && playlist.attributes && playlist.attributes.RESOLUTION &&
playlist.attributes.RESOLUTION.height >= 720) { playlist.attributes.RESOLUTION.height >= 720) {
if (!playlist.excludeUntil || playlist.excludeUntil < Infinity) { if (!playlist.excludeUntil || playlist.excludeUntil < Infinity) {
@ -1094,7 +1051,7 @@ class VhsHandler extends Component {
); );
// Clear the buffer before switching playlists, since it may already contain unplayable segments // Clear the buffer before switching playlists, since it may already contain unplayable segments
this.masterPlaylistController_.fastQualityChange_(); this.playlistController_.fastQualityChange_();
} }
}); });
@ -1105,7 +1062,7 @@ class VhsHandler extends Component {
// promises. // promises.
if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) { if (videojs.browser.IE_VERSION === 11 || !didSetupEmeOptions) {
// If EME options were not set up, we've done all we could to initialize EME. // If EME options were not set up, we've done all we could to initialize EME.
this.masterPlaylistController_.sourceUpdater_.initializedEme(); this.playlistController_.sourceUpdater_.initializedEme();
return; return;
} }
@ -1129,7 +1086,7 @@ class VhsHandler extends Component {
this.qualityLevels_ = player.qualityLevels(); this.qualityLevels_ = player.qualityLevels();
this.masterPlaylistController_.on('selectedinitialmedia', () => { this.playlistController_.on('selectedinitialmedia', () => {
handleVhsLoadedMetadata(this.qualityLevels_, this); handleVhsLoadedMetadata(this.qualityLevels_, this);
}); });
@ -1166,28 +1123,28 @@ class VhsHandler extends Component {
* Begin playing the video. * Begin playing the video.
*/ */
play() { play() {
this.masterPlaylistController_.play(); this.playlistController_.play();
} }
/** /**
* a wrapper around the function in MasterPlaylistController * a wrapper around the function in PlaylistController
*/ */
setCurrentTime(currentTime) { setCurrentTime(currentTime) {
this.masterPlaylistController_.setCurrentTime(currentTime); this.playlistController_.setCurrentTime(currentTime);
} }
/** /**
* a wrapper around the function in MasterPlaylistController * a wrapper around the function in PlaylistController
*/ */
duration() { duration() {
return this.masterPlaylistController_.duration(); return this.playlistController_.duration();
} }
/** /**
* a wrapper around the function in MasterPlaylistController * a wrapper around the function in PlaylistController
*/ */
seekable() { seekable() {
return this.masterPlaylistController_.seekable(); return this.playlistController_.seekable();
} }
/** /**
@ -1197,28 +1154,17 @@ class VhsHandler extends Component {
if (this.playbackWatcher_) { if (this.playbackWatcher_) {
this.playbackWatcher_.dispose(); this.playbackWatcher_.dispose();
} }
if (this.masterPlaylistController_) { if (this.playlistController_) {
this.masterPlaylistController_.dispose(); this.playlistController_.dispose();
} }
if (this.qualityLevels_) { if (this.qualityLevels_) {
this.qualityLevels_.dispose(); this.qualityLevels_.dispose();
} }
if (this.player_) {
delete this.player_.vhs;
delete this.player_.dash;
delete this.player_.hls;
}
if (this.tech_ && this.tech_.vhs) { if (this.tech_ && this.tech_.vhs) {
delete this.tech_.vhs; delete this.tech_.vhs;
} }
// don't check this.tech_.hls as it will log a deprecated warning
if (this.tech_) {
delete this.tech_.hls;
}
if (this.mediaSourceUrl_ && window.URL.revokeObjectURL) { if (this.mediaSourceUrl_ && window.URL.revokeObjectURL) {
window.URL.revokeObjectURL(this.mediaSourceUrl_); window.URL.revokeObjectURL(this.mediaSourceUrl_);
this.mediaSourceUrl_ = null; this.mediaSourceUrl_ = null;
@ -1233,7 +1179,7 @@ class VhsHandler extends Component {
convertToProgramTime(time, callback) { convertToProgramTime(time, callback) {
return getProgramTime({ return getProgramTime({
playlist: this.masterPlaylistController_.media(), playlist: this.playlistController_.media(),
time, time,
callback callback
}); });
@ -1243,7 +1189,7 @@ class VhsHandler extends Component {
seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) { seekToProgramTime(programTime, callback, pauseAfterSeek = true, retryCount = 2) {
return seekToProgramTime({ return seekToProgramTime({
programTime, programTime,
playlist: this.masterPlaylistController_.media(), playlist: this.playlistController_.media(),
retryCount, retryCount,
pauseAfterSeek, pauseAfterSeek,
seekTo: this.options_.seekTo, seekTo: this.options_.seekTo,
@ -1264,23 +1210,14 @@ const VhsSourceHandler = {
name: 'videojs-http-streaming', name: 'videojs-http-streaming',
VERSION: vhsVersion, VERSION: vhsVersion,
canHandleSource(srcObj, options = {}) { canHandleSource(srcObj, options = {}) {
const localOptions = videojs.mergeOptions(videojs.options, options); const localOptions = merge(videojs.options, options);
return VhsSourceHandler.canPlayType(srcObj.type, localOptions); return VhsSourceHandler.canPlayType(srcObj.type, localOptions);
}, },
handleSource(source, tech, options = {}) { handleSource(source, tech, options = {}) {
const localOptions = videojs.mergeOptions(videojs.options, options); const localOptions = merge(videojs.options, options);
tech.vhs = new VhsHandler(source, tech, localOptions); tech.vhs = new VhsHandler(source, tech, localOptions);
if (!videojs.hasOwnProperty('hls')) {
Object.defineProperty(tech, 'hls', {
get: () => {
videojs.log.warn('player.tech().hls is deprecated. Use player.tech().vhs instead.');
return tech.vhs;
},
configurable: true
});
}
tech.vhs.xhr = xhrFactory(); tech.vhs.xhr = xhrFactory();
tech.vhs.src(source.src, source.type); tech.vhs.src(source.src, source.type);
@ -1300,12 +1237,11 @@ const VhsSourceHandler = {
return canUseMsePlayback ? 'maybe' : ''; return canUseMsePlayback ? 'maybe' : '';
}, },
getOverrideNative(options = {}) { getOverrideNative(options = {}) {
const { vhs = {}, hls = {} } = options; const { vhs = {} } = options;
const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS); const defaultOverrideNative = !(videojs.browser.IS_ANY_SAFARI || videojs.browser.IS_IOS);
const { overrideNative = defaultOverrideNative } = vhs; const { overrideNative = defaultOverrideNative } = vhs;
const { overrideNative: legacyOverrideNative = false } = hls;
return legacyOverrideNative || overrideNative; return overrideNative;
} }
}; };
@ -1325,41 +1261,15 @@ if (supportsNativeMediaSources()) {
} }
videojs.VhsHandler = VhsHandler; videojs.VhsHandler = VhsHandler;
Object.defineProperty(videojs, 'HlsHandler', {
get: () => {
videojs.log.warn('videojs.HlsHandler is deprecated. Use videojs.VhsHandler instead.');
return VhsHandler;
},
configurable: true
});
videojs.VhsSourceHandler = VhsSourceHandler; videojs.VhsSourceHandler = VhsSourceHandler;
Object.defineProperty(videojs, 'HlsSourceHandler', {
get: () => {
videojs.log.warn('videojs.HlsSourceHandler is deprecated. ' +
'Use videojs.VhsSourceHandler instead.');
return VhsSourceHandler;
},
configurable: true
});
videojs.Vhs = Vhs; videojs.Vhs = Vhs;
Object.defineProperty(videojs, 'Hls', {
get: () => {
videojs.log.warn('videojs.Hls is deprecated. Use videojs.Vhs instead.');
return Vhs;
},
configurable: true
});
if (!videojs.use) { if (!videojs.use) {
videojs.registerComponent('Hls', Vhs);
videojs.registerComponent('Vhs', Vhs); videojs.registerComponent('Vhs', Vhs);
} }
videojs.options.vhs = videojs.options.vhs || {}; videojs.options.vhs = videojs.options.vhs || {};
videojs.options.hls = videojs.options.hls || {};
if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) { if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
const registerPlugin = videojs.registerPlugin || videojs.plugin; videojs.registerPlugin('reloadSourceOnError', reloadSourceOnError);
registerPlugin('reloadSourceOnError', reloadSourceOnError);
} }
export { export {

View file

@ -9,6 +9,7 @@ import { initSegmentId } from './bin-utils';
import { uint8ToUtf8 } from './util/string'; import { uint8ToUtf8 } from './util/string';
import { REQUEST_ERRORS } from './media-segment-request'; import { REQUEST_ERRORS } from './media-segment-request';
import { ONE_SECOND_IN_TS } from 'mux.js/lib/utils/clock'; import { ONE_SECOND_IN_TS } from 'mux.js/lib/utils/clock';
import {createTimeRanges} from './util/vjs-compat';
const VTT_LINE_TERMINATORS = const VTT_LINE_TERMINATORS =
new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0))); new Uint8Array('\n\n'.split('').map(char => char.charCodeAt(0)));
@ -60,14 +61,14 @@ export default class VTTSegmentLoader extends SegmentLoader {
*/ */
buffered_() { buffered_() {
if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) { if (!this.subtitlesTrack_ || !this.subtitlesTrack_.cues || !this.subtitlesTrack_.cues.length) {
return videojs.createTimeRanges(); return createTimeRanges();
} }
const cues = this.subtitlesTrack_.cues; const cues = this.subtitlesTrack_.cues;
const start = cues[0].startTime; const start = cues[0].startTime;
const end = cues[cues.length - 1].startTime; const end = cues[cues.length - 1].startTime;
return videojs.createTimeRanges([[start, end]]); return createTimeRanges([[start, end]]);
} }
/** /**

View file

@ -11,10 +11,10 @@
*/ */
import videojs from 'video.js'; import videojs from 'video.js';
import window from 'global/window'; import window from 'global/window';
import {merge} from './util/vjs-compat';
const { const {
xhr: videojsXHR, xhr: videojsXHR
mergeOptions
} = videojs; } = videojs;
const callbackWrapper = function(request, error, response, callback) { const callbackWrapper = function(request, error, response, callback) {
@ -59,7 +59,7 @@ const callbackWrapper = function(request, error, response, callback) {
const xhrFactory = function() { const xhrFactory = function() {
const xhr = function XhrFunction(options, callback) { const xhr = function XhrFunction(options, callback) {
// Add a default timeout // Add a default timeout
options = mergeOptions({ options = merge({
timeout: 45e3 timeout: 45e3
}, options); }, options);

View file

@ -1,3 +1,22 @@
<a name="4.0.1"></a>
## [4.0.1](https://github.com/videojs/aes-decrypter/compare/v4.0.0...v4.0.1) (2022-08-18)
### Chores
* do not run es-check on publish ([#87](https://github.com/videojs/aes-decrypter/issues/87)) ([6f0cbd9](https://github.com/videojs/aes-decrypter/commit/6f0cbd9))
<a name="4.0.0"></a>
# [4.0.0](https://github.com/videojs/aes-decrypter/compare/v3.1.3...v4.0.0) (2022-08-18)
### Chores
* **package:** remove IE11 support ([#86](https://github.com/videojs/aes-decrypter/issues/86)) ([3338e9b](https://github.com/videojs/aes-decrypter/commit/3338e9b))
### BREAKING CHANGES
* **package:** Internet Explorer is no longer supported.
<a name="3.1.3"></a> <a name="3.1.3"></a>
## [3.1.3](https://github.com/videojs/aes-decrypter/compare/v3.1.2...v3.1.3) (2022-04-05) ## [3.1.3](https://github.com/videojs/aes-decrypter/compare/v3.1.2...v3.1.3) (2022-04-05)

Some files were not shown because too many files have changed in this diff Show more