New WebRTC and Video Player
This commit is contained in:
Generated
+387
-54
@@ -10,6 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@eyevinn/webrtc-player": "^0.13.0",
|
||||
"@floating-ui/core": "^1.7.3",
|
||||
"@floating-ui/dom": "1.7.4",
|
||||
"@tailwindcss/cli": "4.1.13",
|
||||
@@ -271,6 +272,27 @@
|
||||
"fontkit": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@dailymotion/vast-client": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@dailymotion/vast-client/-/vast-client-4.0.1.tgz",
|
||||
"integrity": "sha512-oRUCc06bWdX/1f2Eha/od5WCj+MLHiD6rgyAudgjOUyWXhCcbkUTZeejBSQsLft0nFnd+5nrDqGkBD8mrEVd/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@dailymotion/vmap": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@dailymotion/vmap/-/vmap-3.3.3.tgz",
|
||||
"integrity": "sha512-M/z1U6Pm19HKasHQA6n/IxqpbukcW37iCRQ8Zr567suw+nNDqJowKFWQcW8dW7SX8QZWItf7XSOawaQrzgQj+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.22.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@emmetio/abbreviation": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
|
||||
@@ -752,6 +774,54 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@eyevinn/csai-manager": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eyevinn/csai-manager/-/csai-manager-0.1.2.tgz",
|
||||
"integrity": "sha512-ZehCxXlSIHF5Ej1n/WZEaLa6sLIH5zrE1ZB71yyT/KbFoeDsDDoo+jw61xzzT+pKBb4vNBtKQRgaVDGZdT2L/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dailymotion/vast-client": "^4.0.0",
|
||||
"@dailymotion/vmap": "^3.3.0",
|
||||
"@eyevinn/video-event-filter": "^2.0.0",
|
||||
"mitt": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eyevinn/video-event-filter": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@eyevinn/video-event-filter/-/video-event-filter-2.0.0.tgz",
|
||||
"integrity": "sha512-S5pECmymqg7WqlAeerhtPVEFxWhLY3cz3DTuhuljZreVDLnEBV6/DlWI6SWDnKdpDJMgD6VjPfaRJssmGatNBg==",
|
||||
"deprecated": "Package no longer maintained, we recommend migrating to @eyevinn/media-event-filter",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mitt": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eyevinn/video-event-filter/node_modules/mitt": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-2.1.0.tgz",
|
||||
"integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eyevinn/webrtc-player": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@eyevinn/webrtc-player/-/webrtc-player-0.13.0.tgz",
|
||||
"integrity": "sha512-X5Nzznbyw9PXj85HYV7nyHSbeC0tWpTTAFEYjilnlLLWfo9EARx1tCQk5tdhmImnDST8VN50SYt6uMDudoVnWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eyevinn/csai-manager": "^0.1.2",
|
||||
"@eyevinn/whpp-client": "^0.1.2",
|
||||
"events": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eyevinn/whpp-client": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eyevinn/whpp-client/-/whpp-client-0.1.2.tgz",
|
||||
"integrity": "sha512-Mau11ykVIZudy+7Qz2tJZbV2cuj8Un7eT2QRBGFA42RPL8djfEi/RIZ+6b9C1Bc1EAppl26zoh28D8/KstFioQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
|
||||
@@ -2301,6 +2371,12 @@
|
||||
"tailwindcss": "dist/index.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/cli/node_modules/tailwindcss": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
|
||||
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||
@@ -2329,6 +2405,12 @@
|
||||
"tailwindcss": "4.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node/node_modules/tailwindcss": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
|
||||
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz",
|
||||
@@ -2572,19 +2654,276 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.13.tgz",
|
||||
"integrity": "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==",
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.14.tgz",
|
||||
"integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.1.13",
|
||||
"@tailwindcss/oxide": "4.1.13",
|
||||
"tailwindcss": "4.1.13"
|
||||
"@tailwindcss/node": "4.1.14",
|
||||
"@tailwindcss/oxide": "4.1.14",
|
||||
"tailwindcss": "4.1.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.14.tgz",
|
||||
"integrity": "sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"enhanced-resolve": "^5.18.3",
|
||||
"jiti": "^2.6.0",
|
||||
"lightningcss": "1.30.1",
|
||||
"magic-string": "^0.30.19",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.14.tgz",
|
||||
"integrity": "sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.4",
|
||||
"tar": "^7.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.1.14",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.1.14",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.1.14",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.14",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.14",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.14",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.14",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.14.tgz",
|
||||
"integrity": "sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.14.tgz",
|
||||
"integrity": "sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.14.tgz",
|
||||
"integrity": "sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.14.tgz",
|
||||
"integrity": "sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.14.tgz",
|
||||
"integrity": "sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.14.tgz",
|
||||
"integrity": "sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.14.tgz",
|
||||
"integrity": "sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.14.tgz",
|
||||
"integrity": "sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.14.tgz",
|
||||
"integrity": "sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.14.tgz",
|
||||
"integrity": "sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
"@emnapi/runtime",
|
||||
"@tybys/wasm-util",
|
||||
"@emnapi/wasi-threads",
|
||||
"tslib"
|
||||
],
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@emnapi/wasi-threads": "^1.1.0",
|
||||
"@napi-rs/wasm-runtime": "^1.0.5",
|
||||
"@tybys/wasm-util": "^0.10.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.14.tgz",
|
||||
"integrity": "sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.14.tgz",
|
||||
"integrity": "sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite/node_modules/detect-libc": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz",
|
||||
"integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
@@ -2659,9 +2998,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.0.tgz",
|
||||
"integrity": "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA==",
|
||||
"version": "24.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz",
|
||||
"integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.13.0"
|
||||
@@ -2791,6 +3130,15 @@
|
||||
"integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
@@ -3159,24 +3507,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/async-function": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
|
||||
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/async-generator-function": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/async-generator-function/-/async-generator-function-1.0.0.tgz",
|
||||
"integrity": "sha512-+NAXNqgCrB95ya4Sr66i1CL2hqLVckAk7xwRYWdcm39/ELQ6YNn1aw5r0bdQtqNZgQpEWzc5yc/igXc7aL5SLA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -4223,6 +4553,15 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz",
|
||||
@@ -4471,15 +4810,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/generator-function": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.0.tgz",
|
||||
"integrity": "sha512-xPypGGincdfyl/AiSGa7GjXLkvld9V7GjZlowup9SHIJnQnHLFiLODCd/DqKOp0PBagbHJ68r1KJI9Mut7m4sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -4503,19 +4833,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.1.tgz",
|
||||
"integrity": "sha512-fk1ZVEeOX9hVZ6QzoBNEC55+Ucqg4sTVwrVuigZhuRPESVFpMyXnd3sbXvPOwp7Y9riVyANiqhEuRF0G1aVSeQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async-function": "^1.0.0",
|
||||
"async-generator-function": "^1.0.0",
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"generator-function": "^2.0.0",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
@@ -5042,9 +5369,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz",
|
||||
"integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==",
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
@@ -6339,6 +6666,12 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
@@ -7478,9 +7811,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.13",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
|
||||
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
@@ -7639,9 +7972,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -8045,9 +8378,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
|
||||
"integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
|
||||
"version": "7.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.8.tgz",
|
||||
"integrity": "sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@eyevinn/webrtc-player": "^0.13.0",
|
||||
"@floating-ui/core": "^1.7.3",
|
||||
"@floating-ui/dom": "1.7.4",
|
||||
"@tailwindcss/cli": "4.1.13",
|
||||
|
||||
@@ -26,6 +26,8 @@ export { default as PasswordField } from "../src/components/PasswordField.astro"
|
||||
export { default as ThemeSwitcher } from "../src/components/ThemeSwitcher.astro";
|
||||
export { default as TimePicker } from "../src/components/TimePicker.astro";
|
||||
export { default as Tooltip } from "../src/components/Tooltip.astro";
|
||||
export { default as VideoPlayer } from "../src/components/VideoPlayer.astro";
|
||||
export { default as WebRtcPlayer } from "../src/components/WebRtcPlayer.astro";
|
||||
export { cn } from "../src/utils/cn.js";
|
||||
`;
|
||||
|
||||
@@ -51,6 +53,8 @@ export const PasswordField: any;
|
||||
export const ThemeSwitcher: any;
|
||||
export const TimePicker: any;
|
||||
export const Tooltip: any;
|
||||
export const VideoPlayer: any;
|
||||
export const WebRtcPlayer: any;
|
||||
export function cn(...classes: Array<string | number | false | null | undefined>): string;
|
||||
`;
|
||||
|
||||
|
||||
@@ -0,0 +1,815 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { cn } from "../utils/cn";
|
||||
|
||||
interface Props extends HTMLAttributes<"div"> {
|
||||
videoUrl: string;
|
||||
videoType: string;
|
||||
autoPlay?: boolean;
|
||||
muted?: boolean;
|
||||
volume?: number;
|
||||
overlayInfo?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
videoUrl,
|
||||
videoType,
|
||||
class: className = "",
|
||||
autoPlay = false,
|
||||
muted = false,
|
||||
volume = 0.75,
|
||||
overlayInfo,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class={cn("video-card", className)}>
|
||||
<div class="video-wrap">
|
||||
<video
|
||||
id="video-player"
|
||||
preload="metadata"
|
||||
playsinline
|
||||
autoplay={autoPlay}
|
||||
muted
|
||||
>
|
||||
<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">
|
||||
<button 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>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
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;"
|
||||
>
|
||||
1080p • 30 FPS
|
||||
</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>
|
||||
<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);
|
||||
}
|
||||
|
||||
.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 type="module" is:inline define:vars={{ volume, muted }}>
|
||||
// ---------- Fixed & improved JS ----------
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
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 fullscreenBtn = document.getElementById("fullscreen-btn");
|
||||
|
||||
let prevVolume = volume;
|
||||
let draggingProgress = false;
|
||||
let draggingVolume = false;
|
||||
let controlsTimeout;
|
||||
let lastMouseX = 0,
|
||||
lastMouseY = 0;
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,247 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { cn } from "../utils/cn";
|
||||
|
||||
interface Props extends HTMLAttributes<"div"> {
|
||||
streamUrl: string;
|
||||
poster?: string;
|
||||
checkStreamFn?: string;
|
||||
retryInterval?: number;
|
||||
}
|
||||
|
||||
const {
|
||||
streamUrl,
|
||||
poster,
|
||||
class: className,
|
||||
checkStreamFn,
|
||||
retryInterval = 5000,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<style>
|
||||
.video-card {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
|
||||
}
|
||||
|
||||
.video-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
background: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.live-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
background: red;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 20;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.overlay.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Controls bar */
|
||||
.controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 6px;
|
||||
}
|
||||
.btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div
|
||||
class={cn("video-card", className)}
|
||||
data-checkStreamFn={checkStreamFn ?? ""}
|
||||
data-retryInterval={retryInterval}
|
||||
>
|
||||
<div class="video-wrap">
|
||||
<video
|
||||
id="live-video"
|
||||
autoplay
|
||||
playsinline
|
||||
muted
|
||||
poster={poster}
|
||||
data-stream={streamUrl}></video>
|
||||
<div class="live-badge">LIVE</div>
|
||||
<div id="overlay" class="overlay"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button id="volume-btn" class="btn" aria-label="Mute/Unmute"
|
||||
>🔇</button
|
||||
>
|
||||
<button id="fullscreen-btn" class="btn" aria-label="Fullscreen"
|
||||
>⤢</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { WebRTCPlayer } from "@eyevinn/webrtc-player";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const videoCardEl = document.getElementById("video-card");
|
||||
const videoEl = document.getElementById("live-video");
|
||||
const overlayEl = document.getElementById("overlay");
|
||||
const volumeBtn = document.getElementById("volume-btn");
|
||||
const fullscreenBtn = document.getElementById("fullscreen-btn");
|
||||
|
||||
let player: any;
|
||||
let retryTimer: any;
|
||||
let streamUrl = videoEl?.dataset.stream;
|
||||
|
||||
const showOverlay = (msg: string) => {
|
||||
overlayEl!.textContent = msg;
|
||||
overlayEl!.classList.add("show");
|
||||
};
|
||||
const hideOverlay = () => overlayEl!.classList.remove("show");
|
||||
|
||||
showOverlay("Connecting to live stream…");
|
||||
|
||||
async function startPlayer() {
|
||||
try {
|
||||
if (videoEl)
|
||||
player = new WebRTCPlayer({
|
||||
video: videoEl as HTMLVideoElement,
|
||||
type: "whep",
|
||||
statsTypeFilter: "^candidate-*|^inbound-rtp",
|
||||
});
|
||||
|
||||
player.on("media-recovered", () => {
|
||||
hideOverlay();
|
||||
});
|
||||
|
||||
player.on("peer-connection-connected", () => {
|
||||
hideOverlay();
|
||||
});
|
||||
|
||||
player.on("no-media", () => {
|
||||
showOverlay(
|
||||
"Stream not started yet or Stream already ended",
|
||||
);
|
||||
});
|
||||
|
||||
player.on("error", (err: any) => {
|
||||
showOverlay("Error: " + err.message);
|
||||
});
|
||||
|
||||
await player.load(new URL(streamUrl || ""));
|
||||
player.unmute();
|
||||
} catch (err: any) {
|
||||
showOverlay("Failed to initialize: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAndStart() {
|
||||
try {
|
||||
const checkStreamFn: any =
|
||||
videoCardEl?.dataset.checkStreamFn || "";
|
||||
|
||||
if (checkStreamFn) {
|
||||
const fn: any = window[checkStreamFn];
|
||||
const status = await fn();
|
||||
if (status.ok) {
|
||||
clearTimeout(retryTimer);
|
||||
startPlayer();
|
||||
hideOverlay();
|
||||
} else {
|
||||
showOverlay(status.message);
|
||||
scheduleRetry();
|
||||
}
|
||||
} else {
|
||||
clearTimeout(retryTimer);
|
||||
startPlayer();
|
||||
hideOverlay();
|
||||
}
|
||||
} catch (err: any) {
|
||||
showOverlay("Check failed: " + err.message);
|
||||
scheduleRetry();
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
clearTimeout(retryTimer);
|
||||
const retryInterval: number = parseInt(
|
||||
videoCardEl?.dataset.retryInterval || "5000",
|
||||
);
|
||||
retryTimer = setTimeout(checkAndStart, retryInterval);
|
||||
}
|
||||
|
||||
checkAndStart();
|
||||
|
||||
(videoEl as HTMLVideoElement).volume = 0;
|
||||
(videoEl as HTMLVideoElement).muted = true;
|
||||
volumeBtn!.textContent = "🔇";
|
||||
|
||||
volumeBtn!.addEventListener("click", () => {
|
||||
if (
|
||||
(videoEl as HTMLVideoElement).muted ||
|
||||
(videoEl as HTMLVideoElement).volume === 0
|
||||
) {
|
||||
(videoEl as HTMLVideoElement).muted = false;
|
||||
(videoEl as HTMLVideoElement).volume = 1.0;
|
||||
volumeBtn!.textContent = "🔊";
|
||||
} else {
|
||||
(videoEl as HTMLVideoElement).muted = true;
|
||||
volumeBtn!.textContent = "🔇";
|
||||
}
|
||||
});
|
||||
|
||||
fullscreenBtn!.addEventListener("click", async () => {
|
||||
const videoCard = document.querySelector(".video-card");
|
||||
if (!document.fullscreenElement) {
|
||||
await videoCard!.requestFullscreen();
|
||||
} else {
|
||||
await document.exitFullscreen();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -74,6 +74,12 @@ import Base from "./Base.astro";
|
||||
<li class="list-item">
|
||||
<a href="/panel">Panel</a>
|
||||
</li>
|
||||
<li class="list-item">
|
||||
<a href="/video-player">Video Player</a>
|
||||
</li>
|
||||
<li class="list-item">
|
||||
<a href="/webrtc-player">WebRTC Player</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
+10
-6
@@ -4,7 +4,7 @@ import Panel from "../components/Panel.astro";
|
||||
---
|
||||
|
||||
<ComponentLayout class="wr:p-4">
|
||||
<div class="wr:h-full wr:w-full wr:relative wr:bg-yellow-200">
|
||||
<div class="wr:h-full wr:w-full wr:relative wr:bg-orange-800 wr:p-4">
|
||||
<div id="panel-wrapper">
|
||||
<button id="openPanelBtn"> Open Panel </button>
|
||||
|
||||
@@ -20,12 +20,16 @@ import Panel from "../components/Panel.astro";
|
||||
id="settings"
|
||||
isOpen={true}
|
||||
width={400}
|
||||
position="left"
|
||||
class="wr:bg-neutral-700 wr:p-4"
|
||||
position="right"
|
||||
class="wr:bg-transparent wr:p-4"
|
||||
>
|
||||
<button id=`close-panel-settings`> Close </button>
|
||||
<h2>Settings Panel</h2>
|
||||
<p>This is a settings panel with a width of 400px.</p>
|
||||
<div
|
||||
class="wr:bg-neutral-700 wr:w-full wr:h-full wr:rounded-2xl wr:p-4"
|
||||
>
|
||||
<button id=`close-panel-settings`> Close </button>
|
||||
<h2>Settings Panel</h2>
|
||||
<p>This is a settings panel with a width of 400px.</p>
|
||||
</div>
|
||||
</Panel>
|
||||
</div>
|
||||
</ComponentLayout>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import VideoPlayer from "../components/VideoPlayer.astro";
|
||||
import ComponentLayout from "../layouts/ComponentLayout.astro";
|
||||
---
|
||||
|
||||
<ComponentLayout>
|
||||
<VideoPlayer
|
||||
videoUrl="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4"
|
||||
videoType="video/mp4"
|
||||
class="wr:w-[500px]"
|
||||
muted={false}
|
||||
volume={0.7}
|
||||
/>
|
||||
</ComponentLayout>
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
import WebRtcPlayer from "../components/WebRtcPlayer.astro";
|
||||
import ComponentLayout from "../layouts/ComponentLayout.astro";
|
||||
---
|
||||
|
||||
<ComponentLayout>
|
||||
<WebRtcPlayer
|
||||
streamUrl="https://stream-webrtc.workroot.in/live/t05Pbqv01N/whep?token=1OZMx5f8WL"
|
||||
class="wr:w-[500px]"
|
||||
/>
|
||||
</ComponentLayout>
|
||||
Reference in New Issue
Block a user