Commit ·
ded211a
1
Parent(s): 25dca33
movie player 0.1
Browse files- frontend/package-lock.json +125 -2
- frontend/package.json +1 -0
- frontend/src/app/tvshows/TvShows.css +4 -0
- frontend/src/app/tvshows/page.js +5 -2
- frontend/src/components/MoviePlayer.css +36 -12
- frontend/src/components/MoviePlayer.js +73 -41
- frontend/src/components/Spinner.css +67 -0
- frontend/src/components/Spinner.js +16 -0
frontend/package-lock.json
CHANGED
|
@@ -18,6 +18,7 @@
|
|
| 18 |
"react": "^18",
|
| 19 |
"react-dom": "^18",
|
| 20 |
"sharp": "^0.33.5",
|
|
|
|
| 21 |
"video.js": "^8.17.4"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
|
@@ -59,6 +60,24 @@
|
|
| 59 |
"tslib": "^2.4.0"
|
| 60 |
}
|
| 61 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
"node_modules/@eslint-community/eslint-utils": {
|
| 63 |
"version": "4.4.0",
|
| 64 |
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
|
@@ -865,6 +884,11 @@
|
|
| 865 |
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
| 866 |
"dev": true
|
| 867 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
"node_modules/@typescript-eslint/parser": {
|
| 869 |
"version": "7.2.0",
|
| 870 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
|
@@ -1503,6 +1527,14 @@
|
|
| 1503 |
"node": ">= 6"
|
| 1504 |
}
|
| 1505 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1506 |
"node_modules/caniuse-lite": {
|
| 1507 |
"version": "1.0.30001651",
|
| 1508 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
|
@@ -1656,6 +1688,24 @@
|
|
| 1656 |
"node": ">= 8"
|
| 1657 |
}
|
| 1658 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1659 |
"node_modules/cssesc": {
|
| 1660 |
"version": "3.0.0",
|
| 1661 |
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
|
@@ -1668,6 +1718,11 @@
|
|
| 1668 |
"node": ">=4"
|
| 1669 |
}
|
| 1670 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1671 |
"node_modules/damerau-levenshtein": {
|
| 1672 |
"version": "1.0.8",
|
| 1673 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
|
@@ -4389,8 +4444,7 @@
|
|
| 4389 |
"node_modules/postcss-value-parser": {
|
| 4390 |
"version": "4.2.0",
|
| 4391 |
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 4392 |
-
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
| 4393 |
-
"dev": true
|
| 4394 |
},
|
| 4395 |
"node_modules/prelude-ls": {
|
| 4396 |
"version": "1.2.1",
|
|
@@ -4737,6 +4791,11 @@
|
|
| 4737 |
"node": ">= 0.4"
|
| 4738 |
}
|
| 4739 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4740 |
"node_modules/sharp": {
|
| 4741 |
"version": "0.33.5",
|
| 4742 |
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
|
@@ -5077,6 +5136,65 @@
|
|
| 5077 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 5078 |
}
|
| 5079 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5080 |
"node_modules/styled-jsx": {
|
| 5081 |
"version": "5.1.1",
|
| 5082 |
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
|
@@ -5099,6 +5217,11 @@
|
|
| 5099 |
}
|
| 5100 |
}
|
| 5101 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5102 |
"node_modules/sucrase": {
|
| 5103 |
"version": "3.35.0",
|
| 5104 |
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
|
|
|
| 18 |
"react": "^18",
|
| 19 |
"react-dom": "^18",
|
| 20 |
"sharp": "^0.33.5",
|
| 21 |
+
"styled-components": "^6.1.13",
|
| 22 |
"video.js": "^8.17.4"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
|
|
|
| 60 |
"tslib": "^2.4.0"
|
| 61 |
}
|
| 62 |
},
|
| 63 |
+
"node_modules/@emotion/is-prop-valid": {
|
| 64 |
+
"version": "1.2.2",
|
| 65 |
+
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
|
| 66 |
+
"integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
|
| 67 |
+
"dependencies": {
|
| 68 |
+
"@emotion/memoize": "^0.8.1"
|
| 69 |
+
}
|
| 70 |
+
},
|
| 71 |
+
"node_modules/@emotion/memoize": {
|
| 72 |
+
"version": "0.8.1",
|
| 73 |
+
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
| 74 |
+
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
|
| 75 |
+
},
|
| 76 |
+
"node_modules/@emotion/unitless": {
|
| 77 |
+
"version": "0.8.1",
|
| 78 |
+
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
|
| 79 |
+
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
|
| 80 |
+
},
|
| 81 |
"node_modules/@eslint-community/eslint-utils": {
|
| 82 |
"version": "4.4.0",
|
| 83 |
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
|
|
|
| 884 |
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
| 885 |
"dev": true
|
| 886 |
},
|
| 887 |
+
"node_modules/@types/stylis": {
|
| 888 |
+
"version": "4.2.5",
|
| 889 |
+
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
|
| 890 |
+
"integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
|
| 891 |
+
},
|
| 892 |
"node_modules/@typescript-eslint/parser": {
|
| 893 |
"version": "7.2.0",
|
| 894 |
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
|
|
|
|
| 1527 |
"node": ">= 6"
|
| 1528 |
}
|
| 1529 |
},
|
| 1530 |
+
"node_modules/camelize": {
|
| 1531 |
+
"version": "1.0.1",
|
| 1532 |
+
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
| 1533 |
+
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
| 1534 |
+
"funding": {
|
| 1535 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 1536 |
+
}
|
| 1537 |
+
},
|
| 1538 |
"node_modules/caniuse-lite": {
|
| 1539 |
"version": "1.0.30001651",
|
| 1540 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
|
|
|
|
| 1688 |
"node": ">= 8"
|
| 1689 |
}
|
| 1690 |
},
|
| 1691 |
+
"node_modules/css-color-keywords": {
|
| 1692 |
+
"version": "1.0.0",
|
| 1693 |
+
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
| 1694 |
+
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
| 1695 |
+
"engines": {
|
| 1696 |
+
"node": ">=4"
|
| 1697 |
+
}
|
| 1698 |
+
},
|
| 1699 |
+
"node_modules/css-to-react-native": {
|
| 1700 |
+
"version": "3.2.0",
|
| 1701 |
+
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
| 1702 |
+
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
| 1703 |
+
"dependencies": {
|
| 1704 |
+
"camelize": "^1.0.0",
|
| 1705 |
+
"css-color-keywords": "^1.0.0",
|
| 1706 |
+
"postcss-value-parser": "^4.0.2"
|
| 1707 |
+
}
|
| 1708 |
+
},
|
| 1709 |
"node_modules/cssesc": {
|
| 1710 |
"version": "3.0.0",
|
| 1711 |
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
|
|
|
| 1718 |
"node": ">=4"
|
| 1719 |
}
|
| 1720 |
},
|
| 1721 |
+
"node_modules/csstype": {
|
| 1722 |
+
"version": "3.1.3",
|
| 1723 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
| 1724 |
+
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
| 1725 |
+
},
|
| 1726 |
"node_modules/damerau-levenshtein": {
|
| 1727 |
"version": "1.0.8",
|
| 1728 |
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
|
|
|
| 4444 |
"node_modules/postcss-value-parser": {
|
| 4445 |
"version": "4.2.0",
|
| 4446 |
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
| 4447 |
+
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
|
|
|
| 4448 |
},
|
| 4449 |
"node_modules/prelude-ls": {
|
| 4450 |
"version": "1.2.1",
|
|
|
|
| 4791 |
"node": ">= 0.4"
|
| 4792 |
}
|
| 4793 |
},
|
| 4794 |
+
"node_modules/shallowequal": {
|
| 4795 |
+
"version": "1.1.0",
|
| 4796 |
+
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
| 4797 |
+
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
| 4798 |
+
},
|
| 4799 |
"node_modules/sharp": {
|
| 4800 |
"version": "0.33.5",
|
| 4801 |
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
|
|
|
| 5136 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 5137 |
}
|
| 5138 |
},
|
| 5139 |
+
"node_modules/styled-components": {
|
| 5140 |
+
"version": "6.1.13",
|
| 5141 |
+
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz",
|
| 5142 |
+
"integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==",
|
| 5143 |
+
"dependencies": {
|
| 5144 |
+
"@emotion/is-prop-valid": "1.2.2",
|
| 5145 |
+
"@emotion/unitless": "0.8.1",
|
| 5146 |
+
"@types/stylis": "4.2.5",
|
| 5147 |
+
"css-to-react-native": "3.2.0",
|
| 5148 |
+
"csstype": "3.1.3",
|
| 5149 |
+
"postcss": "8.4.38",
|
| 5150 |
+
"shallowequal": "1.1.0",
|
| 5151 |
+
"stylis": "4.3.2",
|
| 5152 |
+
"tslib": "2.6.2"
|
| 5153 |
+
},
|
| 5154 |
+
"engines": {
|
| 5155 |
+
"node": ">= 16"
|
| 5156 |
+
},
|
| 5157 |
+
"funding": {
|
| 5158 |
+
"type": "opencollective",
|
| 5159 |
+
"url": "https://opencollective.com/styled-components"
|
| 5160 |
+
},
|
| 5161 |
+
"peerDependencies": {
|
| 5162 |
+
"react": ">= 16.8.0",
|
| 5163 |
+
"react-dom": ">= 16.8.0"
|
| 5164 |
+
}
|
| 5165 |
+
},
|
| 5166 |
+
"node_modules/styled-components/node_modules/postcss": {
|
| 5167 |
+
"version": "8.4.38",
|
| 5168 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
| 5169 |
+
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
| 5170 |
+
"funding": [
|
| 5171 |
+
{
|
| 5172 |
+
"type": "opencollective",
|
| 5173 |
+
"url": "https://opencollective.com/postcss/"
|
| 5174 |
+
},
|
| 5175 |
+
{
|
| 5176 |
+
"type": "tidelift",
|
| 5177 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 5178 |
+
},
|
| 5179 |
+
{
|
| 5180 |
+
"type": "github",
|
| 5181 |
+
"url": "https://github.com/sponsors/ai"
|
| 5182 |
+
}
|
| 5183 |
+
],
|
| 5184 |
+
"dependencies": {
|
| 5185 |
+
"nanoid": "^3.3.7",
|
| 5186 |
+
"picocolors": "^1.0.0",
|
| 5187 |
+
"source-map-js": "^1.2.0"
|
| 5188 |
+
},
|
| 5189 |
+
"engines": {
|
| 5190 |
+
"node": "^10 || ^12 || >=14"
|
| 5191 |
+
}
|
| 5192 |
+
},
|
| 5193 |
+
"node_modules/styled-components/node_modules/tslib": {
|
| 5194 |
+
"version": "2.6.2",
|
| 5195 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
| 5196 |
+
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
| 5197 |
+
},
|
| 5198 |
"node_modules/styled-jsx": {
|
| 5199 |
"version": "5.1.1",
|
| 5200 |
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
|
|
|
|
| 5217 |
}
|
| 5218 |
}
|
| 5219 |
},
|
| 5220 |
+
"node_modules/stylis": {
|
| 5221 |
+
"version": "4.3.2",
|
| 5222 |
+
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
|
| 5223 |
+
"integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
|
| 5224 |
+
},
|
| 5225 |
"node_modules/sucrase": {
|
| 5226 |
"version": "3.35.0",
|
| 5227 |
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
frontend/package.json
CHANGED
|
@@ -19,6 +19,7 @@
|
|
| 19 |
"react": "^18",
|
| 20 |
"react-dom": "^18",
|
| 21 |
"sharp": "^0.33.5",
|
|
|
|
| 22 |
"video.js": "^8.17.4"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
|
|
|
| 19 |
"react": "^18",
|
| 20 |
"react-dom": "^18",
|
| 21 |
"sharp": "^0.33.5",
|
| 22 |
+
"styled-components": "^6.1.13",
|
| 23 |
"video.js": "^8.17.4"
|
| 24 |
},
|
| 25 |
"devDependencies": {
|
frontend/src/app/tvshows/TvShows.css
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.page-container{
|
| 2 |
+
width: 100dvw;
|
| 3 |
+
height: 100dvh;
|
| 4 |
+
}
|
frontend/src/app/tvshows/page.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
export default function TVShows() {
|
| 2 |
return (
|
| 3 |
<>
|
| 4 |
-
<div>
|
| 5 |
<h1>TV Shows</h1>
|
| 6 |
<p className='text-white'>Browse our TV show collection.</p>
|
| 7 |
-
|
| 8 |
</div>
|
| 9 |
</>
|
| 10 |
);
|
|
|
|
| 1 |
+
import { Spinner } from "@/components/Spinner";
|
| 2 |
+
import './TvShows.css';
|
| 3 |
+
|
| 4 |
export default function TVShows() {
|
| 5 |
return (
|
| 6 |
<>
|
| 7 |
+
<div className="page-container">
|
| 8 |
<h1>TV Shows</h1>
|
| 9 |
<p className='text-white'>Browse our TV show collection.</p>
|
| 10 |
+
<Spinner/>
|
| 11 |
</div>
|
| 12 |
</>
|
| 13 |
);
|
frontend/src/components/MoviePlayer.css
CHANGED
|
@@ -11,7 +11,7 @@
|
|
| 11 |
}
|
| 12 |
|
| 13 |
.video-title {
|
| 14 |
-
color: #
|
| 15 |
text-align: left;
|
| 16 |
padding: 20px;
|
| 17 |
padding-bottom: 50px;
|
|
@@ -49,22 +49,35 @@
|
|
| 49 |
opacity: 1;
|
| 50 |
}
|
| 51 |
|
| 52 |
-
.player-controls-
|
| 53 |
display: flex;
|
| 54 |
flex-direction: row;
|
| 55 |
-
width:
|
| 56 |
justify-content: center;
|
| 57 |
align-items: center;
|
| 58 |
}
|
| 59 |
|
| 60 |
-
.player-controls-
|
| 61 |
display: flex;
|
| 62 |
-
flex-direction:
|
| 63 |
-
width:
|
| 64 |
-
|
|
|
|
| 65 |
align-items: center;
|
| 66 |
}
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
.controls {
|
| 69 |
display: flex;
|
| 70 |
justify-content: center;
|
|
@@ -73,14 +86,16 @@
|
|
| 73 |
background-color: rgba(0, 0, 0, 0.7);
|
| 74 |
bottom: 0;
|
| 75 |
position: relative;
|
| 76 |
-
width: 100dvw;
|
| 77 |
flex-direction: column;
|
| 78 |
}
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
.play-pause-btn,
|
| 81 |
.control-btn {
|
| 82 |
-
color: #
|
| 83 |
-
border: 1px solid #5f59d1;
|
| 84 |
border-radius: 10px;
|
| 85 |
padding: 10px 15px;
|
| 86 |
margin: 0 5px;
|
|
@@ -91,11 +106,11 @@
|
|
| 91 |
|
| 92 |
.play-pause-btn:hover,
|
| 93 |
.control-btn:hover {
|
| 94 |
-
|
| 95 |
}
|
| 96 |
|
| 97 |
.control-btn:disabled {
|
| 98 |
-
|
| 99 |
cursor: not-allowed;
|
| 100 |
}
|
| 101 |
|
|
@@ -113,3 +128,12 @@
|
|
| 113 |
width: 100px;
|
| 114 |
cursor: pointer;
|
| 115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
|
| 13 |
.video-title {
|
| 14 |
+
color: #8e91b4;
|
| 15 |
text-align: left;
|
| 16 |
padding: 20px;
|
| 17 |
padding-bottom: 50px;
|
|
|
|
| 49 |
opacity: 1;
|
| 50 |
}
|
| 51 |
|
| 52 |
+
.player-controls-top {
|
| 53 |
display: flex;
|
| 54 |
flex-direction: row;
|
| 55 |
+
width: 95dvw;
|
| 56 |
justify-content: center;
|
| 57 |
align-items: center;
|
| 58 |
}
|
| 59 |
|
| 60 |
+
.player-controls-down {
|
| 61 |
display: flex;
|
| 62 |
+
flex-direction: row;
|
| 63 |
+
width: 90dvw;
|
| 64 |
+
|
| 65 |
+
justify-content: space-between;
|
| 66 |
align-items: center;
|
| 67 |
}
|
| 68 |
|
| 69 |
+
.player-controls-left {
|
| 70 |
+
width: 100%;
|
| 71 |
+
display: flex;
|
| 72 |
+
justify-content: left;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.player-controls-right {
|
| 76 |
+
width: 100%;
|
| 77 |
+
display: flex;
|
| 78 |
+
justify-content: right;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
.controls {
|
| 82 |
display: flex;
|
| 83 |
justify-content: center;
|
|
|
|
| 86 |
background-color: rgba(0, 0, 0, 0.7);
|
| 87 |
bottom: 0;
|
| 88 |
position: relative;
|
|
|
|
| 89 |
flex-direction: column;
|
| 90 |
}
|
| 91 |
|
| 92 |
+
.current-time, .duration{
|
| 93 |
+
color: white;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
.play-pause-btn,
|
| 97 |
.control-btn {
|
| 98 |
+
color: #6c66b0;
|
|
|
|
| 99 |
border-radius: 10px;
|
| 100 |
padding: 10px 15px;
|
| 101 |
margin: 0 5px;
|
|
|
|
| 106 |
|
| 107 |
.play-pause-btn:hover,
|
| 108 |
.control-btn:hover {
|
| 109 |
+
color: #5f6293;
|
| 110 |
}
|
| 111 |
|
| 112 |
.control-btn:disabled {
|
| 113 |
+
color: #232435;
|
| 114 |
cursor: not-allowed;
|
| 115 |
}
|
| 116 |
|
|
|
|
| 128 |
width: 100px;
|
| 129 |
cursor: pointer;
|
| 130 |
}
|
| 131 |
+
|
| 132 |
+
.buffering-indicator{
|
| 133 |
+
color: white;
|
| 134 |
+
position: fixed;
|
| 135 |
+
top: 50%;
|
| 136 |
+
left: 50%;
|
| 137 |
+
transform: translate(-50%,-50%);
|
| 138 |
+
opacity: 1;
|
| 139 |
+
}
|
frontend/src/components/MoviePlayer.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
| 11 |
faVolumeMute,
|
| 12 |
faExpand,
|
| 13 |
} from "@fortawesome/free-solid-svg-icons";
|
|
|
|
| 14 |
|
| 15 |
export default function MoviePlayer({ videoUrl, title }) {
|
| 16 |
const videoRef = useRef(null);
|
|
@@ -20,6 +21,7 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 20 |
const [progress, setProgress] = useState(0);
|
| 21 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 22 |
const [showControls, setShowControls] = useState(true);
|
|
|
|
| 23 |
const overlayTimeout = useRef(null);
|
| 24 |
|
| 25 |
useEffect(() => {
|
|
@@ -29,15 +31,21 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 29 |
const handlePause = () => setIsPlaying(false);
|
| 30 |
const handleTimeUpdate = () =>
|
| 31 |
setProgress((videoElement.currentTime / videoElement.duration) * 100);
|
|
|
|
|
|
|
| 32 |
|
| 33 |
videoElement.addEventListener("play", handlePlay);
|
| 34 |
videoElement.addEventListener("pause", handlePause);
|
| 35 |
videoElement.addEventListener("timeupdate", handleTimeUpdate);
|
|
|
|
|
|
|
| 36 |
|
| 37 |
return () => {
|
| 38 |
videoElement.removeEventListener("play", handlePlay);
|
| 39 |
videoElement.removeEventListener("pause", handlePause);
|
| 40 |
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
|
|
|
|
|
|
|
| 41 |
};
|
| 42 |
}, []);
|
| 43 |
|
|
@@ -48,6 +56,8 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 48 |
}
|
| 49 |
overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
|
| 50 |
}
|
|
|
|
|
|
|
| 51 |
}, [showControls]);
|
| 52 |
|
| 53 |
const handleFastForward = () => {
|
|
@@ -71,8 +81,10 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 71 |
const togglePlayPause = () => {
|
| 72 |
if (isPlaying) {
|
| 73 |
videoRef.current.pause();
|
|
|
|
| 74 |
} else {
|
| 75 |
videoRef.current.play();
|
|
|
|
| 76 |
}
|
| 77 |
};
|
| 78 |
|
|
@@ -105,7 +117,7 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 105 |
const exitFullscreen =
|
| 106 |
doc.exitFullscreen ||
|
| 107 |
doc.mozCancelFullScreen ||
|
| 108 |
-
|
| 109 |
doc.msExitFullscreen;
|
| 110 |
|
| 111 |
if (!isFullscreen) {
|
|
@@ -128,64 +140,84 @@ export default function MoviePlayer({ videoUrl, title }) {
|
|
| 128 |
setShowControls(true);
|
| 129 |
};
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
return (
|
| 132 |
<div className="video-player-container" onMouseMove={handleMouseMove}>
|
| 133 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 134 |
<h2 className="video-title">{title}</h2>
|
| 135 |
<div className="controls">
|
| 136 |
<div className="player-controls-top">
|
| 137 |
-
|
| 138 |
<input
|
| 139 |
type="range"
|
| 140 |
className="progress-bar"
|
| 141 |
value={progress}
|
| 142 |
onChange={handleProgressChange}
|
| 143 |
/>
|
|
|
|
| 144 |
</div>
|
| 145 |
<div className="player-controls-down">
|
| 146 |
-
<
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
onClick={
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
/>
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
min="0"
|
| 177 |
-
max="1"
|
| 178 |
-
step="0.01"
|
| 179 |
-
value={volume}
|
| 180 |
-
onChange={handleVolumeChange}
|
| 181 |
-
/>
|
| 182 |
-
<button onClick={toggleFullscreen} className="control-btn">
|
| 183 |
-
<FontAwesomeIcon icon={faExpand} size="xl" />
|
| 184 |
-
</button>
|
| 185 |
</div>
|
| 186 |
</div>
|
|
|
|
| 187 |
</div>
|
| 188 |
-
|
| 189 |
<video
|
| 190 |
ref={videoRef}
|
| 191 |
className="video-element"
|
|
|
|
| 11 |
faVolumeMute,
|
| 12 |
faExpand,
|
| 13 |
} from "@fortawesome/free-solid-svg-icons";
|
| 14 |
+
import { Spinner } from "@/components/Spinner";
|
| 15 |
|
| 16 |
export default function MoviePlayer({ videoUrl, title }) {
|
| 17 |
const videoRef = useRef(null);
|
|
|
|
| 21 |
const [progress, setProgress] = useState(0);
|
| 22 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
| 23 |
const [showControls, setShowControls] = useState(true);
|
| 24 |
+
const [isBuffering, setIsBuffering] = useState(false);
|
| 25 |
const overlayTimeout = useRef(null);
|
| 26 |
|
| 27 |
useEffect(() => {
|
|
|
|
| 31 |
const handlePause = () => setIsPlaying(false);
|
| 32 |
const handleTimeUpdate = () =>
|
| 33 |
setProgress((videoElement.currentTime / videoElement.duration) * 100);
|
| 34 |
+
const handleWaiting = () => setIsBuffering(true);
|
| 35 |
+
const handlePlaying = () => setIsBuffering(false);
|
| 36 |
|
| 37 |
videoElement.addEventListener("play", handlePlay);
|
| 38 |
videoElement.addEventListener("pause", handlePause);
|
| 39 |
videoElement.addEventListener("timeupdate", handleTimeUpdate);
|
| 40 |
+
videoElement.addEventListener("waiting", handleWaiting);
|
| 41 |
+
videoElement.addEventListener("playing", handlePlaying);
|
| 42 |
|
| 43 |
return () => {
|
| 44 |
videoElement.removeEventListener("play", handlePlay);
|
| 45 |
videoElement.removeEventListener("pause", handlePause);
|
| 46 |
videoElement.removeEventListener("timeupdate", handleTimeUpdate);
|
| 47 |
+
videoElement.removeEventListener("waiting", handleWaiting);
|
| 48 |
+
videoElement.removeEventListener("playing", handlePlaying);
|
| 49 |
};
|
| 50 |
}, []);
|
| 51 |
|
|
|
|
| 56 |
}
|
| 57 |
overlayTimeout.current = setTimeout(() => setShowControls(false), 3000);
|
| 58 |
}
|
| 59 |
+
|
| 60 |
+
return () => clearTimeout(overlayTimeout.current);
|
| 61 |
}, [showControls]);
|
| 62 |
|
| 63 |
const handleFastForward = () => {
|
|
|
|
| 81 |
const togglePlayPause = () => {
|
| 82 |
if (isPlaying) {
|
| 83 |
videoRef.current.pause();
|
| 84 |
+
setShowControls(true);
|
| 85 |
} else {
|
| 86 |
videoRef.current.play();
|
| 87 |
+
setShowControls(false);
|
| 88 |
}
|
| 89 |
};
|
| 90 |
|
|
|
|
| 117 |
const exitFullscreen =
|
| 118 |
doc.exitFullscreen ||
|
| 119 |
doc.mozCancelFullScreen ||
|
| 120 |
+
docEl.webkitExitFullscreen ||
|
| 121 |
doc.msExitFullscreen;
|
| 122 |
|
| 123 |
if (!isFullscreen) {
|
|
|
|
| 140 |
setShowControls(true);
|
| 141 |
};
|
| 142 |
|
| 143 |
+
const formatTime = (seconds) => {
|
| 144 |
+
if (isNaN(seconds)) {
|
| 145 |
+
return '00:00:00';
|
| 146 |
+
}
|
| 147 |
+
const wholeSeconds = Math.floor(seconds);
|
| 148 |
+
const hours = Math.floor(wholeSeconds / 3600);
|
| 149 |
+
const minutes = Math.floor((wholeSeconds % 3600) / 60);
|
| 150 |
+
const secs = wholeSeconds % 60;
|
| 151 |
+
|
| 152 |
+
const formattedHours = String(hours).padStart(2, '0');
|
| 153 |
+
const formattedMinutes = String(minutes).padStart(2, '0');
|
| 154 |
+
const formattedSeconds = String(secs).padStart(2, '0');
|
| 155 |
+
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
|
| 156 |
+
};
|
| 157 |
+
|
| 158 |
return (
|
| 159 |
<div className="video-player-container" onMouseMove={handleMouseMove}>
|
| 160 |
<div className={`player-overlay ${showControls ? "show" : "hide"}`}>
|
| 161 |
<h2 className="video-title">{title}</h2>
|
| 162 |
<div className="controls">
|
| 163 |
<div className="player-controls-top">
|
| 164 |
+
<label className="current-time">{formatTime(videoRef?.current?.currentTime)}</label>
|
| 165 |
<input
|
| 166 |
type="range"
|
| 167 |
className="progress-bar"
|
| 168 |
value={progress}
|
| 169 |
onChange={handleProgressChange}
|
| 170 |
/>
|
| 171 |
+
<label className="duration">{formatTime(videoRef?.current?.duration)}</label>
|
| 172 |
</div>
|
| 173 |
<div className="player-controls-down">
|
| 174 |
+
<div className="player-controls-left">
|
| 175 |
+
<button
|
| 176 |
+
onClick={handleRewind}
|
| 177 |
+
className="control-btn"
|
| 178 |
+
disabled={!isPlaying}
|
| 179 |
+
>
|
| 180 |
+
<FontAwesomeIcon icon={faFastBackward} size="xl" />
|
| 181 |
+
</button>
|
| 182 |
+
<button onClick={togglePlayPause} className="play-pause-btn">
|
| 183 |
+
{isPlaying ? (
|
| 184 |
+
<FontAwesomeIcon icon={faPause} size="xl" />
|
| 185 |
+
) : (
|
| 186 |
+
<FontAwesomeIcon icon={faPlay} size="xl" />
|
| 187 |
+
)}
|
| 188 |
+
</button>
|
| 189 |
+
<button
|
| 190 |
+
onClick={handleFastForward}
|
| 191 |
+
className="control-btn"
|
| 192 |
+
disabled={!isPlaying}
|
| 193 |
+
>
|
| 194 |
+
<FontAwesomeIcon icon={faFastForward} size="xl" />
|
| 195 |
+
</button>
|
| 196 |
+
</div>
|
| 197 |
+
<div className="player-controls-right">
|
| 198 |
+
<button onClick={toggleMute} className="control-btn">
|
| 199 |
+
<FontAwesomeIcon
|
| 200 |
+
icon={isMuted ? faVolumeMute : faVolumeUp}
|
| 201 |
+
size="xl"
|
| 202 |
+
/>
|
| 203 |
+
</button>
|
| 204 |
+
<input
|
| 205 |
+
type="range"
|
| 206 |
+
className="volume-control"
|
| 207 |
+
min="0"
|
| 208 |
+
max="1"
|
| 209 |
+
step="0.01"
|
| 210 |
+
value={volume}
|
| 211 |
+
onChange={handleVolumeChange}
|
| 212 |
/>
|
| 213 |
+
<button onClick={toggleFullscreen} className="control-btn">
|
| 214 |
+
<FontAwesomeIcon icon={faExpand} size="xl" />
|
| 215 |
+
</button>
|
| 216 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
</div>
|
| 218 |
</div>
|
| 219 |
+
{isBuffering && <div className="buffering-indicator"><Spinner/></div>}
|
| 220 |
</div>
|
|
|
|
| 221 |
<video
|
| 222 |
ref={videoRef}
|
| 223 |
className="video-element"
|
frontend/src/components/Spinner.css
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.spinner-wrapper {
|
| 2 |
+
height: 5rem;
|
| 3 |
+
width: 5rem;
|
| 4 |
+
position: relative;
|
| 5 |
+
animation: spinner 2.5s infinite linear both;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
@keyframes spinner {
|
| 9 |
+
100% {
|
| 10 |
+
transform: rotate(-360deg);
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.spinner-dot {
|
| 15 |
+
width: 100%;
|
| 16 |
+
height: 100%;
|
| 17 |
+
position: absolute;
|
| 18 |
+
left: 0;
|
| 19 |
+
top: 0;
|
| 20 |
+
animation: spinner-dot 1.5s infinite ease-in-out both;
|
| 21 |
+
border-radius: 100%;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.spinner-dot:before {
|
| 25 |
+
content: "";
|
| 26 |
+
display: block;
|
| 27 |
+
width: 25%;
|
| 28 |
+
height: 25%;
|
| 29 |
+
background-color: rgb(93, 101, 171); /* Use a CSS variable for the color */
|
| 30 |
+
border-radius: 100%;
|
| 31 |
+
animation: spinner-dot-before 1.5s infinite ease-in-out both;
|
| 32 |
+
}
|
| 33 |
+
.spinner-dot:nth-child(1) {
|
| 34 |
+
animation-delay: -1s; /* Adjusted for smooth start */
|
| 35 |
+
}
|
| 36 |
+
.spinner-dot:nth-child(2) {
|
| 37 |
+
animation-delay: -.9s; /* Slightly less delay */
|
| 38 |
+
}
|
| 39 |
+
.spinner-dot:nth-child(3) {
|
| 40 |
+
animation-delay: -.7s; /* Medium delay */
|
| 41 |
+
}
|
| 42 |
+
.spinner-dot:nth-child(4) {
|
| 43 |
+
animation-delay: -0.6s; /* Shorter delay */
|
| 44 |
+
}
|
| 45 |
+
.spinner-dot:nth-child(5) {
|
| 46 |
+
animation-delay: -0.5s; /* Even shorter delay */
|
| 47 |
+
}
|
| 48 |
+
.spinner-dot:nth-child(6) {
|
| 49 |
+
animation-delay: -0.3s; /* Quickest delay */
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
@keyframes spinner-dot {
|
| 53 |
+
80%,
|
| 54 |
+
100% {
|
| 55 |
+
transform: rotate(360deg);
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
@keyframes spinner-dot-before {
|
| 60 |
+
50% {
|
| 61 |
+
transform: scale(0.2);
|
| 62 |
+
}
|
| 63 |
+
100%,
|
| 64 |
+
0% {
|
| 65 |
+
transform: scale(.6);
|
| 66 |
+
}
|
| 67 |
+
}
|
frontend/src/components/Spinner.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Spinner.js
|
| 2 |
+
import React from "react";
|
| 3 |
+
import "./Spinner.css"; // Import the CSS file
|
| 4 |
+
|
| 5 |
+
export const Spinner = () => {
|
| 6 |
+
return (
|
| 7 |
+
<div className="spinner-wrapper">
|
| 8 |
+
<div className="spinner-dot" />
|
| 9 |
+
<div className="spinner-dot" />
|
| 10 |
+
<div className="spinner-dot" />
|
| 11 |
+
<div className="spinner-dot" />
|
| 12 |
+
<div className="spinner-dot" />
|
| 13 |
+
<div className="spinner-dot" />
|
| 14 |
+
</div>
|
| 15 |
+
);
|
| 16 |
+
};
|