Added New HslPlayer
This commit is contained in:
Generated
+107
-100
@@ -17,6 +17,7 @@
|
|||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"esbuild": "^0.25.10",
|
"esbuild": "^0.25.10",
|
||||||
|
"hls.js": "^1.6.13",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.13",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
@@ -1962,9 +1963,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
|
||||||
"integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==",
|
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1976,9 +1977,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
|
||||||
"integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==",
|
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1990,9 +1991,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
|
||||||
"integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==",
|
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2004,9 +2005,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
|
||||||
"integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==",
|
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2018,9 +2019,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
|
||||||
"integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==",
|
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2032,9 +2033,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
|
||||||
"integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==",
|
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2046,9 +2047,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
|
||||||
"integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==",
|
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2060,9 +2061,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
|
||||||
"integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==",
|
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -2074,9 +2075,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==",
|
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2088,9 +2089,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
|
||||||
"integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==",
|
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2102,9 +2103,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==",
|
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -2116,9 +2117,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==",
|
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -2130,9 +2131,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==",
|
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2144,9 +2145,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
|
||||||
"integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==",
|
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -2158,9 +2159,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==",
|
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -2172,9 +2173,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==",
|
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2186,9 +2187,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
|
||||||
"integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==",
|
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2200,9 +2201,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
|
||||||
"integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==",
|
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2214,9 +2215,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
|
||||||
"integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==",
|
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -2228,9 +2229,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
|
||||||
"integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==",
|
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -2242,9 +2243,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
|
||||||
"integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==",
|
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -2256,9 +2257,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
|
||||||
"integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==",
|
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5188,6 +5189,12 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hls.js": {
|
||||||
|
"version": "1.6.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.13.tgz",
|
||||||
|
"integrity": "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||||
@@ -7531,9 +7538,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.52.3",
|
"version": "4.52.4",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
|
||||||
"integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==",
|
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -7547,28 +7554,28 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.52.3",
|
"@rollup/rollup-android-arm-eabi": "4.52.4",
|
||||||
"@rollup/rollup-android-arm64": "4.52.3",
|
"@rollup/rollup-android-arm64": "4.52.4",
|
||||||
"@rollup/rollup-darwin-arm64": "4.52.3",
|
"@rollup/rollup-darwin-arm64": "4.52.4",
|
||||||
"@rollup/rollup-darwin-x64": "4.52.3",
|
"@rollup/rollup-darwin-x64": "4.52.4",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.52.3",
|
"@rollup/rollup-freebsd-arm64": "4.52.4",
|
||||||
"@rollup/rollup-freebsd-x64": "4.52.3",
|
"@rollup/rollup-freebsd-x64": "4.52.4",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.3",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.3",
|
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.52.3",
|
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.52.3",
|
"@rollup/rollup-linux-arm64-musl": "4.52.4",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.52.3",
|
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.3",
|
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.3",
|
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.52.3",
|
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.52.3",
|
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.52.3",
|
"@rollup/rollup-linux-x64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.52.3",
|
"@rollup/rollup-linux-x64-musl": "4.52.4",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.52.3",
|
"@rollup/rollup-openharmony-arm64": "4.52.4",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.52.3",
|
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.52.3",
|
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.52.3",
|
"@rollup/rollup-win32-x64-gnu": "4.52.4",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.52.3",
|
"@rollup/rollup-win32-x64-msvc": "4.52.4",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -7817,9 +7824,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.3",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||||
"integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==",
|
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -8014,9 +8021,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.21.3",
|
"version": "6.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
|
||||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17"
|
"node": ">=18.17"
|
||||||
@@ -8378,9 +8385,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.8",
|
"version": "7.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz",
|
||||||
"integrity": "sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==",
|
"integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"esbuild": "^0.25.10",
|
"esbuild": "^0.25.10",
|
||||||
|
"hls.js": "^1.6.13",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.13",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"zod": "^3.25.76"
|
"zod": "^3.25.76"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export { default as Field } from "../src/components/Field.astro";
|
|||||||
export { default as FieldRow } from "../src/components/FieldRow.astro";
|
export { default as FieldRow } from "../src/components/FieldRow.astro";
|
||||||
export { default as FileUploader } from "../src/components/FileUploader.astro";
|
export { default as FileUploader } from "../src/components/FileUploader.astro";
|
||||||
export { default as Form } from "../src/components/Form.astro";
|
export { default as Form } from "../src/components/Form.astro";
|
||||||
|
export { default as HSLPLayer } from "../src/components/HSLPLayer.astro";
|
||||||
export { default as Link } from "../src/components/Link.astro";
|
export { default as Link } from "../src/components/Link.astro";
|
||||||
export { default as Modal } from "../src/components/Modal.astro";
|
export { default as Modal } from "../src/components/Modal.astro";
|
||||||
export { default as MultiSelect } from "../src/components/MultiSelect.astro";
|
export { default as MultiSelect } from "../src/components/MultiSelect.astro";
|
||||||
@@ -44,6 +45,7 @@ export const Field: any;
|
|||||||
export const FieldRow: any;
|
export const FieldRow: any;
|
||||||
export const FileUploader: any;
|
export const FileUploader: any;
|
||||||
export const Form: any;
|
export const Form: any;
|
||||||
|
export const HSLPLayer: any;
|
||||||
export const Link: any;
|
export const Link: any;
|
||||||
export const Modal: any;
|
export const Modal: any;
|
||||||
export const MultiSelect: any;
|
export const MultiSelect: any;
|
||||||
|
|||||||
@@ -0,0 +1,941 @@
|
|||||||
|
---
|
||||||
|
import type { HTMLAttributes } from "astro/types";
|
||||||
|
import { cn } from "../utils/cn";
|
||||||
|
|
||||||
|
interface Props extends HTMLAttributes<"div"> {
|
||||||
|
videoUrl: string;
|
||||||
|
videoType: string;
|
||||||
|
isLive?: boolean;
|
||||||
|
autoPlay?: boolean;
|
||||||
|
muted?: boolean;
|
||||||
|
volume?: number;
|
||||||
|
overlayInfo?: string;
|
||||||
|
poster?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
videoUrl,
|
||||||
|
videoType,
|
||||||
|
isLive = false,
|
||||||
|
class: className = "",
|
||||||
|
autoPlay = false,
|
||||||
|
muted = false,
|
||||||
|
volume = 0.75,
|
||||||
|
overlayInfo,
|
||||||
|
poster = "",
|
||||||
|
} = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class={cn("video-card", className)}>
|
||||||
|
<div class="video-wrap">
|
||||||
|
<video
|
||||||
|
id="video-player"
|
||||||
|
preload="metadata"
|
||||||
|
playsinline
|
||||||
|
autoplay={autoPlay}
|
||||||
|
muted
|
||||||
|
poster={poster}
|
||||||
|
>
|
||||||
|
<source src={videoUrl} type={videoType} />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<div id="loading-overlay">
|
||||||
|
<div class="spinner" aria-hidden="true"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="placeholder-overlay" aria-hidden="false">
|
||||||
|
<svg
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
style="opacity:.7"
|
||||||
|
>
|
||||||
|
<rect x="2" y="6" width="14" height="12" rx="2"></rect>
|
||||||
|
<path
|
||||||
|
d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<div class="small">{overlayInfo}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="overlay">
|
||||||
|
<div id="overlay-play" class="btn" aria-label="Play/Pause">
|
||||||
|
<svg
|
||||||
|
width="36"
|
||||||
|
height="36"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.8"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"
|
||||||
|
>
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="quality-info"
|
||||||
|
style="position:absolute; top:12px; right:12px; z-index:8; background:rgba(0,0,0,0.6); padding:6px 8px; border-radius:8px; font-size:12px;"
|
||||||
|
>
|
||||||
|
Auto • --
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls-area">
|
||||||
|
<div class="progress-block">
|
||||||
|
<!-- progress slider -->
|
||||||
|
<div
|
||||||
|
id="progress-slider"
|
||||||
|
class="slider"
|
||||||
|
role="slider"
|
||||||
|
aria-label="Seek"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
tabindex="0"
|
||||||
|
aria-valuenow="0"
|
||||||
|
>
|
||||||
|
<div class="track"></div>
|
||||||
|
<div class="buffered" id="buffered-range" style="width:0%">
|
||||||
|
</div>
|
||||||
|
<div class="fill" id="progress-range" style="width:0%"></div>
|
||||||
|
<div
|
||||||
|
class="thumb"
|
||||||
|
id="progress-thumb"
|
||||||
|
style="left:0%"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display:flex; justify-content:space-between; margin-top:6px; font-size:13px; color:rgba(255,255,255,0.75)"
|
||||||
|
>
|
||||||
|
<div id="current-time">0:00</div>
|
||||||
|
<div id="max-duration">00:00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls-row">
|
||||||
|
<div class="controls-left">
|
||||||
|
<button id="play-pause" class="btn" aria-label="Play/Pause"
|
||||||
|
>▶</button
|
||||||
|
>
|
||||||
|
<button id="skip-back" class="btn" aria-label="Back 10s"
|
||||||
|
>⟲10s</button
|
||||||
|
>
|
||||||
|
<button id="skip-forward" class="btn" aria-label="Forward 10s"
|
||||||
|
>10s⟳</button
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="volume-wrap">
|
||||||
|
<button id="volume-btn" class="btn" aria-label="Toggle mute"
|
||||||
|
>🔊</button
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="volume-slider"
|
||||||
|
class="slider volume-slider"
|
||||||
|
role="slider"
|
||||||
|
aria-label="Volume"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
tabindex="0"
|
||||||
|
aria-valuenow={volume}
|
||||||
|
>
|
||||||
|
<div class="track"></div>
|
||||||
|
<div class="fill" id="volume-range" style="width:75%">
|
||||||
|
</div>
|
||||||
|
<div class="thumb" id="volume-thumb" style="left:75%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; align-items:center; gap:8px;">
|
||||||
|
<select
|
||||||
|
id="speed-select"
|
||||||
|
aria-label="Playback speed"
|
||||||
|
style="background:#071028; color:#fff; border-radius:6px; padding:6px; border:0"
|
||||||
|
>
|
||||||
|
<option value="0.5">0.5x</option>
|
||||||
|
<option value="1" selected>1x</option>
|
||||||
|
<option value="1.25">1.25x</option>
|
||||||
|
<option value="1.5">1.5x</option>
|
||||||
|
<option value="2">2x</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
id="quality-select"
|
||||||
|
aria-label="Quality"
|
||||||
|
style="background:#071028; color:#fff; border-radius:6px; padding:6px; border:0"
|
||||||
|
>
|
||||||
|
<option value="auto" selected>Auto</option>
|
||||||
|
</select>
|
||||||
|
<button id="fullscreen-btn" class="btn" aria-label="Fullscreen"
|
||||||
|
><svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M8 3H5a2 2 0 0 0-2 2v3"></path>
|
||||||
|
<path d="M21 8V5a2 2 0 0 0-2-2h-3"></path>
|
||||||
|
<path d="M3 16v3a2 2 0 0 0 2 2h3"></path>
|
||||||
|
<path d="M16 21h3a2 2 0 0 0 2-2v-3"></path>
|
||||||
|
</svg></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--thumb-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-area {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgba(0, 0, 0, 0.7),
|
||||||
|
rgba(0, 0, 0, 0)
|
||||||
|
);
|
||||||
|
background: rgba(0 0, 0, 0.5);
|
||||||
|
padding: 14px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card .controls-area {
|
||||||
|
opacity: 0 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card.show-controls .controls-area {
|
||||||
|
opacity: 1 !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-card.show-controls #overlay {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* overlays */
|
||||||
|
#loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-top: 4px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#placeholder-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 6;
|
||||||
|
background: rgba(0, 0, 0, 0.15);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video.paused + #overlay {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-block {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic slider styles (track + fill + thumb) */
|
||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
height: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .track {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 6px;
|
||||||
|
background: #374151;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .buffered {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(99, 102, 241, 0.35);
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .fill {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: linear-gradient(90deg, #1e40af, #3b82f6);
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider .thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: var(--thumb-size);
|
||||||
|
height: var(--thumb-size);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #000;
|
||||||
|
box-shadow: 0 2px 6px rgba(2, 6, 23, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
color: #fff;
|
||||||
|
padding: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Volume small slider */
|
||||||
|
.volume-wrap {
|
||||||
|
width: 140px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-slider {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* simple responsive */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.volume-wrap {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script is:inline define:vars={{ videoUrl, volume, muted, autoPlay, isLive }}>
|
||||||
|
// ---------- Fixed & improved JS ----------
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const Hls = window.hls;
|
||||||
|
const hls = new Hls({
|
||||||
|
startPosition: 0,
|
||||||
|
autoStartLoad: true,
|
||||||
|
capLevelToPlayerSize: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoCard = document.querySelector(".video-card");
|
||||||
|
const video = document.getElementById("video-player");
|
||||||
|
const loadingOverlay = document.getElementById("loading-overlay");
|
||||||
|
const placeholder = document.getElementById("placeholder-overlay");
|
||||||
|
const overlay = document.getElementById("overlay");
|
||||||
|
const overlayPlay = document.getElementById("overlay-play");
|
||||||
|
const playPauseBtn = document.getElementById("play-pause");
|
||||||
|
const skipBack = document.getElementById("skip-back");
|
||||||
|
const skipForward = document.getElementById("skip-forward");
|
||||||
|
const volumeBtn = document.getElementById("volume-btn");
|
||||||
|
const volumeThumb = document.getElementById("volume-thumb");
|
||||||
|
const volumeRange = document.getElementById("volume-range");
|
||||||
|
const volumeSlider = document.getElementById("volume-slider");
|
||||||
|
const progressThumb = document.getElementById("progress-thumb");
|
||||||
|
const progressRange = document.getElementById("progress-range");
|
||||||
|
const bufferedRange = document.getElementById("buffered-range");
|
||||||
|
const progressSlider = document.getElementById("progress-slider");
|
||||||
|
const currentTimeEl = document.getElementById("current-time");
|
||||||
|
const maxDurationEl = document.getElementById("max-duration");
|
||||||
|
const speedSelect = document.getElementById("speed-select");
|
||||||
|
const qualitySelect = document.getElementById("quality-select");
|
||||||
|
const fullscreenBtn = document.getElementById("fullscreen-btn");
|
||||||
|
const qualityInfoEl = document.getElementById("quality-info");
|
||||||
|
|
||||||
|
let prevVolume = volume;
|
||||||
|
let draggingProgress = false;
|
||||||
|
let draggingVolume = false;
|
||||||
|
let controlsTimeout;
|
||||||
|
let lastMouseX = 0,
|
||||||
|
lastMouseY = 0;
|
||||||
|
|
||||||
|
if (video && Hls.isSupported()) {
|
||||||
|
hls.loadSource(videoUrl);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
const levels = hls.levels
|
||||||
|
.map((level, i) => ({
|
||||||
|
index: i,
|
||||||
|
height: level.height,
|
||||||
|
name: level.height + "p",
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.height - b.height);
|
||||||
|
|
||||||
|
// Populate dropdown
|
||||||
|
qualitySelect.innerHTML = `<option value="auto" selected>Auto</option>`;
|
||||||
|
levels.forEach((level) => {
|
||||||
|
const opt = document.createElement("option");
|
||||||
|
opt.value = level.index;
|
||||||
|
opt.textContent = level.name;
|
||||||
|
qualitySelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Change quality dynamically
|
||||||
|
qualitySelect.addEventListener("change", (e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
if (val === "auto") {
|
||||||
|
hls.currentLevel = -1; // auto
|
||||||
|
qualityInfoEl.textContent = `Auto • ${levels[levels.length - 1].fps || 0} FPS`;
|
||||||
|
console.log("🔁 Switched to Auto quality");
|
||||||
|
} else {
|
||||||
|
const levelIndex = parseInt(val, 10);
|
||||||
|
const level = hls.levels[levelIndex]; // ✅ define it properly
|
||||||
|
if (level) {
|
||||||
|
hls.currentLevel = levelIndex;
|
||||||
|
const fps = level.frameRate
|
||||||
|
? Math.round(eval(level.frameRate))
|
||||||
|
: 30;
|
||||||
|
const bitrate = Math.round(
|
||||||
|
(level.bitrate || 0) / 1000,
|
||||||
|
);
|
||||||
|
qualityInfoEl.textContent = `${level.height}p • ${fps} FPS • ${bitrate} kbps`;
|
||||||
|
console.log(
|
||||||
|
`🎚 Switched to ${level.height}p (${fps} FPS, ${bitrate} kbps)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (autoPlay) video?.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.error("HLS network error, retrying...");
|
||||||
|
hls.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.error("HLS media error, recovering...");
|
||||||
|
hls.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error("Unrecoverable HLS error:", data);
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
|
||||||
|
const currentLevel = hls.levels[data.level];
|
||||||
|
if (!currentLevel) return;
|
||||||
|
|
||||||
|
let fps = 0;
|
||||||
|
try {
|
||||||
|
const track = video.getVideoPlaybackQuality?.();
|
||||||
|
if (
|
||||||
|
track &&
|
||||||
|
track.totalVideoFrames > 0 &&
|
||||||
|
track.totalFrameDelay > 0
|
||||||
|
) {
|
||||||
|
fps = Math.round(
|
||||||
|
track.totalVideoFrames / video.currentTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (!fps)
|
||||||
|
fps = Math.round(
|
||||||
|
eval(currentLevel.frameRate || "30") || 30,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fpsText = `${fps} FPS`;
|
||||||
|
const height = currentLevel.height || "Auto";
|
||||||
|
|
||||||
|
qualityInfoEl.textContent = `${height}p • ${fpsText}`;
|
||||||
|
});
|
||||||
|
} else if (video?.canPlayType("application/vnd.apple.mpegurl")) {
|
||||||
|
video.src = videoUrl;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
let vol = Number(`${volume}`);
|
||||||
|
if (isFinite(vol) && vol >= 0 && vol <= 1) {
|
||||||
|
video.volume = vol;
|
||||||
|
} else {
|
||||||
|
video.volume = 0.75; // fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
video.muted = muted;
|
||||||
|
|
||||||
|
// clamp helper
|
||||||
|
const clamp = (v, a, b) => Math.min(Math.max(v, a), b);
|
||||||
|
|
||||||
|
function formatTime(sec) {
|
||||||
|
if (!isFinite(sec) || isNaN(sec)) return "00:00";
|
||||||
|
sec = Math.floor(sec);
|
||||||
|
const h = Math.floor(sec / 3600);
|
||||||
|
const m = Math.floor((sec % 3600) / 60);
|
||||||
|
const s = sec % 60;
|
||||||
|
if (h > 0)
|
||||||
|
return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
||||||
|
return `${m}:${String(s).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update UI helpers
|
||||||
|
function updateProgressUI() {
|
||||||
|
if (!isFinite(video.duration) || video.duration === 0) return;
|
||||||
|
const percent = clamp(
|
||||||
|
(video.currentTime / video.duration) * 100,
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
progressRange.style.width = percent + "%";
|
||||||
|
progressThumb.style.left = percent + "%";
|
||||||
|
progressSlider.setAttribute(
|
||||||
|
"aria-valuenow",
|
||||||
|
String(Math.floor(video.currentTime)),
|
||||||
|
);
|
||||||
|
currentTimeEl.textContent = formatTime(video.currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBuffered() {
|
||||||
|
if (!isFinite(video.duration) || video.duration === 0) return;
|
||||||
|
if (video.buffered && video.buffered.length) {
|
||||||
|
const end = video.buffered.end(video.buffered.length - 1);
|
||||||
|
const percent = clamp((end / video.duration) * 100, 0, 100);
|
||||||
|
bufferedRange.style.width = percent + "%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateVolumeUI() {
|
||||||
|
const volPercent = clamp(
|
||||||
|
video.muted ? 0 : Math.round((video.volume || 0) * 100),
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
volumeRange.style.width = volPercent + "%";
|
||||||
|
volumeThumb.style.left = volPercent + "%";
|
||||||
|
volumeSlider.setAttribute("aria-valuenow", String(volPercent));
|
||||||
|
volumeSlider.setAttribute("aria-valuetext", volPercent + "%");
|
||||||
|
// update icon
|
||||||
|
if (volPercent === 0) volumeBtn.textContent = "🔈";
|
||||||
|
else if (volPercent < 50) volumeBtn.textContent = "🔉";
|
||||||
|
else volumeBtn.textContent = "🔊";
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Initial setup ---
|
||||||
|
video.volume = prevVolume;
|
||||||
|
updateVolumeUI();
|
||||||
|
|
||||||
|
video.addEventListener("loadedmetadata", () => {
|
||||||
|
maxDurationEl.textContent = formatTime(video.duration);
|
||||||
|
progressSlider.setAttribute(
|
||||||
|
"aria-valuemax",
|
||||||
|
String(Math.floor(video.duration)),
|
||||||
|
);
|
||||||
|
updateProgressUI();
|
||||||
|
updateBuffered();
|
||||||
|
video.playbackRate = parseFloat(speedSelect.value) || 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
// play/pause logic
|
||||||
|
function setPlayIcon(isPlaying) {
|
||||||
|
playPauseBtn.textContent = isPlaying ? "⏸" : "▶";
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePlayPause() {
|
||||||
|
if (video.paused) {
|
||||||
|
video.play().catch(() => {});
|
||||||
|
setOverlayIcon(true);
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
setOverlayIcon(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener("click", togglePlayPause);
|
||||||
|
overlay.addEventListener("click", togglePlayPause);
|
||||||
|
//overlayPlay.addEventListener("click", togglePlayPause);
|
||||||
|
playPauseBtn.addEventListener("click", togglePlayPause);
|
||||||
|
|
||||||
|
video.addEventListener("play", () => {
|
||||||
|
setPlayIcon(true);
|
||||||
|
setOverlayIcon(true);
|
||||||
|
placeholder.style.display = "none";
|
||||||
|
loadingOverlay.style.display = "none";
|
||||||
|
showControls();
|
||||||
|
});
|
||||||
|
video.addEventListener("pause", () => {
|
||||||
|
setPlayIcon(false);
|
||||||
|
setOverlayIcon(false);
|
||||||
|
videoCard.classList.add("show-controls");
|
||||||
|
videoCard.style.cursor = "default";
|
||||||
|
clearTimeout(controlsTimeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
function setOverlayIcon(isPlaying) {
|
||||||
|
if (isPlaying) {
|
||||||
|
overlayPlay.innerHTML = `
|
||||||
|
<svg width="36" height="36" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="6" y="4" width="4" height="16"></rect>
|
||||||
|
<rect x="14" y="4" width="4" height="16"></rect>
|
||||||
|
</svg>`; // pause icon
|
||||||
|
} else {
|
||||||
|
overlayPlay.innerHTML = `
|
||||||
|
<svg width="36" height="36" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M5 3l14 9-14 9V3z"></path>
|
||||||
|
</svg>`; // play icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video.addEventListener("timeupdate", updateProgressUI);
|
||||||
|
video.addEventListener("progress", updateBuffered);
|
||||||
|
video.addEventListener("canplay", () => {
|
||||||
|
loadingOverlay.style.display = "none";
|
||||||
|
updateBuffered();
|
||||||
|
});
|
||||||
|
video.addEventListener("waiting", () => {
|
||||||
|
loadingOverlay.style.display = "flex";
|
||||||
|
});
|
||||||
|
video.addEventListener("loadstart", () => {
|
||||||
|
loadingOverlay.style.display = "flex";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seeking (supports pointer and keyboard)
|
||||||
|
function handlePointerPositionOnSlider(evt, sliderEl) {
|
||||||
|
const rect = sliderEl.getBoundingClientRect();
|
||||||
|
const clientX =
|
||||||
|
evt.touches && evt.touches[0]
|
||||||
|
? evt.touches[0].clientX
|
||||||
|
: evt.clientX;
|
||||||
|
const percent = clamp(
|
||||||
|
((clientX - rect.left) / rect.width) * 100,
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
return percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// progress pointer handling
|
||||||
|
progressSlider.addEventListener("pointerdown", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
draggingProgress = true;
|
||||||
|
progressSlider.setPointerCapture(e.pointerId);
|
||||||
|
const p = handlePointerPositionOnSlider(e, progressSlider);
|
||||||
|
const t = (p / 100) * video.duration || 0;
|
||||||
|
video.currentTime = t;
|
||||||
|
updateProgressUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
progressSlider.addEventListener("pointermove", (e) => {
|
||||||
|
if (!draggingProgress) return;
|
||||||
|
const p = handlePointerPositionOnSlider(e, progressSlider);
|
||||||
|
const t = (p / 100) * video.duration || 0;
|
||||||
|
video.currentTime = t;
|
||||||
|
updateProgressUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
progressSlider.addEventListener("pointerup", (e) => {
|
||||||
|
draggingProgress = false;
|
||||||
|
try {
|
||||||
|
progressSlider.releasePointerCapture(e.pointerId);
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// keyboard support for seek
|
||||||
|
progressSlider.addEventListener("keydown", (e) => {
|
||||||
|
if (!isFinite(video.duration)) return;
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime - 5,
|
||||||
|
0,
|
||||||
|
video.duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowRight") {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime + 5,
|
||||||
|
0,
|
||||||
|
video.duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// skip buttons
|
||||||
|
skipBack.addEventListener("click", () => {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime - 10,
|
||||||
|
0,
|
||||||
|
video.duration || Infinity,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
skipForward.addEventListener("click", () => {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime + 10,
|
||||||
|
0,
|
||||||
|
video.duration || Infinity,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Volume interactions
|
||||||
|
volumeSlider.addEventListener("pointerdown", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
draggingVolume = true;
|
||||||
|
volumeSlider.setPointerCapture(e.pointerId);
|
||||||
|
const p = handlePointerPositionOnSlider(e, volumeSlider);
|
||||||
|
video.volume = p / 100;
|
||||||
|
video.muted = p === 0;
|
||||||
|
prevVolume = video.volume || prevVolume;
|
||||||
|
updateVolumeUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
volumeSlider.addEventListener("pointermove", (e) => {
|
||||||
|
if (!draggingVolume) return;
|
||||||
|
const p = handlePointerPositionOnSlider(e, volumeSlider);
|
||||||
|
video.volume = p / 100;
|
||||||
|
video.muted = p === 0;
|
||||||
|
prevVolume = video.volume || prevVolume;
|
||||||
|
updateVolumeUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
volumeSlider.addEventListener("pointerup", (e) => {
|
||||||
|
draggingVolume = false;
|
||||||
|
try {
|
||||||
|
volumeSlider.releasePointerCapture(e.pointerId);
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// click on volume track
|
||||||
|
volumeSlider.addEventListener("click", (e) => {
|
||||||
|
const p = handlePointerPositionOnSlider(e, volumeSlider);
|
||||||
|
video.volume = p / 100;
|
||||||
|
video.muted = p === 0;
|
||||||
|
prevVolume = video.volume || prevVolume;
|
||||||
|
updateVolumeUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
// toggle mute
|
||||||
|
volumeBtn.addEventListener("click", () => {
|
||||||
|
if (video.muted || video.volume === 0) {
|
||||||
|
video.muted = false;
|
||||||
|
video.volume = prevVolume || 0.75;
|
||||||
|
} else {
|
||||||
|
prevVolume = video.volume || prevVolume;
|
||||||
|
video.muted = true;
|
||||||
|
}
|
||||||
|
updateVolumeUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
// sync on change
|
||||||
|
video.addEventListener("volumechange", updateVolumeUI);
|
||||||
|
|
||||||
|
// playback speed
|
||||||
|
speedSelect.addEventListener("change", () => {
|
||||||
|
video.playbackRate = parseFloat(speedSelect.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// fullscreen
|
||||||
|
fullscreenBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
await videoCard.requestFullscreen();
|
||||||
|
} else {
|
||||||
|
await document.exitFullscreen();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Fullscreen error:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showControls() {
|
||||||
|
videoCard.classList.add("show-controls");
|
||||||
|
videoCard.style.cursor = "default";
|
||||||
|
clearTimeout(controlsTimeout);
|
||||||
|
|
||||||
|
if (!video.paused) {
|
||||||
|
controlsTimeout = setTimeout(() => {
|
||||||
|
videoCard.classList.remove("show-controls");
|
||||||
|
// Hide cursor too in fullscreen
|
||||||
|
if (document.fullscreenElement === videoCard) {
|
||||||
|
videoCard.style.cursor = "none";
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoCard.addEventListener("mousemove", (e) => {
|
||||||
|
if (e.clientX !== lastMouseX || e.clientY !== lastMouseY) {
|
||||||
|
showControls();
|
||||||
|
lastMouseX = e.clientX;
|
||||||
|
lastMouseY = e.clientY;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
videoCard.addEventListener("click", showControls);
|
||||||
|
|
||||||
|
document.addEventListener("fullscreenchange", () => {
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
// videoCard.classList.add('show-controls');
|
||||||
|
// videoCard.style.cursor = 'default';
|
||||||
|
// clearTimeout(controlsTimeout);
|
||||||
|
|
||||||
|
showControls();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// keyboard shortcuts
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
// prevent interfering when typing in inputs/selects
|
||||||
|
const tag =
|
||||||
|
document.activeElement &&
|
||||||
|
document.activeElement.tagName.toLowerCase();
|
||||||
|
if (tag === "input" || tag === "textarea" || tag === "select")
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (e.code === "Space") {
|
||||||
|
e.preventDefault();
|
||||||
|
togglePlayPause();
|
||||||
|
}
|
||||||
|
if (e.code === "ArrowLeft") {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime - 10,
|
||||||
|
0,
|
||||||
|
video.duration || Infinity,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (e.code === "ArrowRight") {
|
||||||
|
video.currentTime = clamp(
|
||||||
|
video.currentTime + 10,
|
||||||
|
0,
|
||||||
|
video.duration || Infinity,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (e.code === "KeyM") {
|
||||||
|
if (video.muted) {
|
||||||
|
video.muted = false;
|
||||||
|
video.volume = prevVolume || 0.75;
|
||||||
|
} else {
|
||||||
|
prevVolume = video.volume || prevVolume;
|
||||||
|
video.muted = true;
|
||||||
|
}
|
||||||
|
updateVolumeUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// pointerup anywhere to cancel drags (safety)
|
||||||
|
document.addEventListener("pointerup", () => {
|
||||||
|
draggingProgress = false;
|
||||||
|
draggingVolume = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Vendored
+3
@@ -1,3 +1,5 @@
|
|||||||
|
import Hls from 'hls.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
schema: any;
|
schema: any;
|
||||||
@@ -5,6 +7,7 @@ declare global {
|
|||||||
upload: any;
|
upload: any;
|
||||||
myModal_open: any;
|
myModal_open: any;
|
||||||
myModal_close: any;
|
myModal_close: any;
|
||||||
|
hls: typeof Hls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ import Base from "./Base.astro";
|
|||||||
<li class="list-item">
|
<li class="list-item">
|
||||||
<a href="/video-player">Video Player</a>
|
<a href="/video-player">Video Player</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="hls-item">
|
||||||
|
<a href="/hls">hls Player</a>
|
||||||
|
</li>
|
||||||
<li class="list-item">
|
<li class="list-item">
|
||||||
<a href="/webrtc-player">WebRTC Player</a>
|
<a href="/webrtc-player">WebRTC Player</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
import HSLPLayer from "../components/HSLPLayer.astro";
|
||||||
|
import ComponentLayout from "../layouts/ComponentLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<ComponentLayout>
|
||||||
|
<HSLPLayer
|
||||||
|
videoUrl="https://stream-middleware.workroot.in/hls/live/t05Pbqv01N/2025-10-04_15-26-30/master.m3u8"
|
||||||
|
poster="https://stream-middleware.workroot.in/thumbnail/live/t05Pbqv01N/2025-10-04_15-26-30.jpg"
|
||||||
|
videoType="application/x-mpegURL"
|
||||||
|
class="wr:w-[500px]"
|
||||||
|
muted={false}
|
||||||
|
volume={0.7}
|
||||||
|
/>
|
||||||
|
</ComponentLayout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Hls from "hls.js";
|
||||||
|
|
||||||
|
window.hls = Hls;
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user