ChandimaPrabath
commited on
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 |
+
};
|