├── LICENSE ├── assets ├── Video.mp4 ├── previewImgs │ ├── preview1.jpg │ ├── preview10.jpg │ ├── preview11.jpg │ ├── preview12.jpg │ ├── preview13.jpg │ ├── preview14.jpg │ ├── preview15.jpg │ ├── preview16.jpg │ ├── preview17.jpg │ ├── preview2.jpg │ ├── preview3.jpg │ ├── preview4.jpg │ ├── preview5.jpg │ ├── preview6.jpg │ ├── preview7.jpg │ ├── preview8.jpg │ └── preview9.jpg └── subtitles.vtt ├── index.html ├── script.js └── styles.css /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 WebDevSimplified 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/Video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/Video.mp4 -------------------------------------------------------------------------------- /assets/previewImgs/preview1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview1.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview10.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview11.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview12.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview13.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview14.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview15.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview16.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview17.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview2.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview3.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview4.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview5.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview6.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview7.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview8.jpg -------------------------------------------------------------------------------- /assets/previewImgs/preview9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebDevSimplified/youtube-video-player-clone/be07cce284a9f1ad59730fe55ade558372659a61/assets/previewImgs/preview9.jpg -------------------------------------------------------------------------------- /assets/subtitles.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:00.000 --> 00:00:00.970 5 | - In the last video, 6 | 7 | 2 8 | 00:00:00.970 --> 00:00:02.660 9 | I talked all about the window object 10 | 11 | 3 12 | 00:00:02.660 --> 00:00:04.680 13 | and how you can pretty much 14 | just ignore it completely. 15 | 16 | 4 17 | 00:00:04.680 --> 00:00:05.520 18 | And in this video, 19 | 20 | 5 21 | 00:00:05.520 --> 00:00:07.220 22 | we wanna talk about the document object, 23 | 24 | 6 25 | 00:00:07.220 --> 00:00:10.060 26 | which is incredibly useful 27 | all across programming 28 | 29 | 7 30 | 00:00:10.060 --> 00:00:11.230 31 | in the browser. 32 | 33 | 8 34 | 00:00:11.230 --> 00:00:13.313 35 | So to see exactly what 36 | this document object is, 37 | 38 | 9 39 | 00:00:13.313 --> 00:00:16.017 40 | let's just console.log out document, 41 | 42 | 10 43 | 00:00:16.017 --> 00:00:17.820 44 | and we just type in document. 45 | 46 | 11 47 | 00:00:17.820 --> 00:00:20.824 48 | This is exactly the same as 49 | if we typed window.document. 50 | 51 | 12 52 | 00:00:20.824 --> 00:00:23.950 53 | They're both the same thing we 54 | can just use document though, 55 | 56 | 13 57 | 00:00:23.950 --> 00:00:26.510 58 | or window.document it's going 59 | to print the same thing. 60 | 61 | 14 62 | 00:00:26.510 --> 00:00:27.343 63 | As you can see, 64 | 65 | 15 66 | 00:00:27.343 --> 00:00:29.298 67 | it prints out this weird 68 | like hashtag document. 69 | 70 | 16 71 | 00:00:29.298 --> 00:00:30.311 72 | And when we open this up, 73 | 74 | 17 75 | 00:00:30.311 --> 00:00:31.630 76 | you're gonna see document 77 | 78 | 18 79 | 00:00:31.630 --> 00:00:34.370 80 | is essentially just all 81 | of the html for our page. 82 | 83 | 19 84 | 00:00:34.370 --> 00:00:36.800 85 | You can see this is the exact HTML 86 | 87 | 20 88 | 00:00:36.800 --> 00:00:39.860 89 | of our entire page from 90 | this index.html here. 91 | 92 | 21 93 | 00:00:39.860 --> 00:00:42.463 94 | So it's printing out the HML of our page 95 | 96 | 22 97 | 00:00:42.463 --> 00:00:43.479 98 | and that's really useful 99 | 100 | 23 101 | 00:00:43.479 --> 00:00:45.900 102 | because the document 103 | allows us to get elements 104 | 105 | 24 106 | 00:00:45.900 --> 00:00:46.733 107 | from our HTML. 108 | 109 | 25 110 | 00:00:46.733 --> 00:00:48.660 111 | It allows us to modify our HTML, 112 | 113 | 26 114 | 00:00:48.660 --> 00:00:50.658 115 | create new elements to add into our HTML. 116 | 117 | 27 118 | 00:00:50.658 --> 00:00:54.375 119 | It's really the way you interact 120 | with the HTML of your page. 121 | 122 | 28 123 | 00:00:54.375 --> 00:00:56.450 124 | So for example, if we wanna get the body, 125 | 126 | 29 127 | 00:00:56.450 --> 00:00:58.157 128 | we could say document.body, 129 | 130 | 30 131 | 00:00:58.157 --> 00:00:59.620 132 | and this is going to give us 133 | 134 | 31 135 | 00:00:59.620 --> 00:01:01.493 136 | that body element of our document. 137 | 138 | 32 139 | 00:01:01.493 --> 00:01:04.620 140 | We could also get for 141 | example, document element, 142 | 143 | 33 144 | 00:01:04.620 --> 00:01:06.925 145 | and this is going to give 146 | us just the HTML portion 147 | 148 | 34 149 | 00:01:06.925 --> 00:01:08.047 150 | of our document. 151 | 152 | 35 153 | 00:01:08.047 --> 00:01:10.990 154 | You can notice when we 155 | had just document here, 156 | 157 | 36 158 | 00:01:10.990 --> 00:01:12.586 159 | it also gave us this doc type at the top, 160 | 161 | 37 162 | 00:01:12.586 --> 00:01:14.728 163 | but if we just wanna get the HTML element 164 | 165 | 38 166 | 00:01:14.728 --> 00:01:16.530 167 | and everything inside of it, 168 | 169 | 39 170 | 00:01:16.530 --> 00:01:18.774 171 | that's what document 172 | element is used for it. 173 | 174 | 40 175 | 00:01:18.774 --> 00:01:21.350 176 | Now, I also mentioned that 177 | you can create elements 178 | 179 | 41 180 | 00:01:21.350 --> 00:01:23.730 181 | with document.create element. 182 | 183 | 42 184 | 00:01:23.730 --> 00:01:24.563 185 | So let's just come in here 186 | 187 | 43 188 | 00:01:24.563 --> 00:01:26.035 189 | and we're gonna create a new element 190 | 191 | 44 192 | 00:01:26.035 --> 00:01:29.101 193 | and we're gonna set it equal 194 | to document.create element. 195 | 196 | 45 197 | 00:01:29.101 --> 00:01:30.410 198 | And this is a function. 199 | 200 | 46 201 | 00:01:30.410 --> 00:01:31.709 202 | And all you do is pass 203 | the name of the tag. 204 | 205 | 47 206 | 00:01:31.709 --> 00:01:33.777 207 | So you can create, for example, a div 208 | 209 | 48 210 | 00:01:33.777 --> 00:01:35.738 211 | or you can create a span, 212 | whatever you want to create. 213 | 214 | 49 215 | 00:01:35.738 --> 00:01:37.883 216 | We'll just create a 217 | span here, for example. 218 | 219 | 50 220 | 00:01:37.883 --> 00:01:39.862 221 | And then we can take our element. 222 | 223 | 51 224 | 00:01:39.862 --> 00:01:42.130 225 | And if we wanna put some 226 | text inside of this, 227 | 228 | 52 229 | 00:01:42.130 --> 00:01:43.569 230 | we'll say dot innertext, 231 | 232 | 53 233 | 00:01:43.569 --> 00:01:45.568 234 | 'cause now we have access to an element 235 | 236 | 54 237 | 00:01:45.568 --> 00:01:47.363 238 | and to specify the 239 | texts we say, innertext, 240 | 241 | 55 242 | 00:01:47.363 --> 00:01:50.570 243 | and we'll just set it 244 | equal to Hello World. 245 | 246 | 56 247 | 00:01:50.570 --> 00:01:53.140 248 | And now we just wanna add 249 | that to our document body. 250 | 251 | 57 252 | 00:01:53.140 --> 00:01:55.489 253 | So we can say document.body, 254 | 255 | 58 256 | 00:01:55.489 --> 00:01:57.330 257 | 'cause this is where 258 | we're gonna add this too. 259 | 260 | 59 261 | 00:01:57.330 --> 00:01:58.880 262 | We could say dot appendchild 263 | 264 | 60 265 | 00:01:58.880 --> 00:02:01.810 266 | and we pass in the element we want to add, 267 | 268 | 61 269 | 00:02:01.810 --> 00:02:03.397 270 | which in our case is this 271 | element we just created. 272 | 273 | 62 274 | 00:02:03.397 --> 00:02:06.350 275 | We're going to append it 276 | to the end of the body 277 | 278 | 63 279 | 00:02:06.350 --> 00:02:07.480 280 | of our document. 281 | 282 | 64 283 | 00:02:07.480 --> 00:02:08.312 284 | We click save. 285 | 286 | 65 287 | 00:02:08.312 --> 00:02:09.458 288 | You'll notice nothing gets printed out, 289 | 290 | 66 291 | 00:02:09.458 --> 00:02:11.410 292 | but if I pull over the page, 293 | 294 | 67 295 | 00:02:11.410 --> 00:02:12.820 296 | you'll see that Hello 297 | World has been appended 298 | 299 | 68 300 | 00:02:12.820 --> 00:02:14.930 301 | to the end of our document. 302 | 303 | 69 304 | 00:02:14.930 --> 00:02:15.763 305 | If I just shrink this down, 306 | 307 | 70 308 | 00:02:15.763 --> 00:02:16.930 309 | so it's on the right side of our screen. 310 | 311 | 71 312 | 00:02:16.930 --> 00:02:19.216 313 | And I changed this to like, 314 | Hello World 2 and save. 315 | 316 | 72 317 | 00:02:19.216 --> 00:02:21.643 318 | You can now see it reruns 319 | and re-adds this element 320 | 321 | 73 322 | 00:02:21.643 --> 00:02:23.523 323 | with the text, Hello World 2. 324 | 325 | 74 326 | 00:02:23.523 --> 00:02:25.173 327 | So this document is super powerful 328 | 329 | 75 330 | 00:02:25.173 --> 00:02:27.730 331 | and is really the gateway into interacting 332 | 333 | 76 334 | 00:02:27.730 --> 00:02:29.660 335 | with everything inside of our browser, 336 | 337 | 77 338 | 00:02:29.660 --> 00:02:31.530 339 | creating elements, selecting elements, 340 | 341 | 78 342 | 00:02:31.530 --> 00:02:33.360 343 | adding events, modifying things, 344 | 345 | 79 346 | 00:02:33.360 --> 00:02:35.582 347 | it's all done through 348 | this document object here. 349 | 350 | 80 351 | 00:02:35.582 --> 00:02:37.010 352 | And in the next few videos, 353 | 354 | 81 355 | 00:02:37.010 --> 00:02:38.450 356 | I'm gonna explore all the different ways 357 | 358 | 82 359 | 00:02:38.450 --> 00:02:40.670 360 | you can use this document 361 | object in more depth, 362 | 363 | 83 364 | 00:02:40.670 --> 00:02:41.824 365 | but this video was really just a precursor 366 | 367 | 84 368 | 00:02:41.824 --> 00:02:43.639 369 | to what the document object is 370 | 371 | 85 372 | 00:02:43.639 --> 00:02:46.870 373 | and how you can use it to 374 | just create a simple element. 375 | 376 | 86 377 | 00:02:46.870 --> 00:02:47.703 378 | So in the next video, 379 | 380 | 87 381 | 00:02:47.703 --> 00:02:49.410 382 | I'm gonna show you how you 383 | can select elements using 384 | 385 | 88 386 | 00:02:49.410 --> 00:02:52.220 387 | their ID and their class 388 | with this document object. 389 | 390 | 89 391 | 00:02:52.220 --> 00:02:54.320 392 | And I can't wait to see you in that video. 393 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 | 30 |
31 | 42 | 43 |
44 |
45 |
0:00
46 | / 47 |
48 |
49 | 54 | 57 | 62 | 70 | 78 |
79 |
80 | 83 |
84 | 85 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const playPauseBtn = document.querySelector(".play-pause-btn") 2 | const theaterBtn = document.querySelector(".theater-btn") 3 | const fullScreenBtn = document.querySelector(".full-screen-btn") 4 | const miniPlayerBtn = document.querySelector(".mini-player-btn") 5 | const muteBtn = document.querySelector(".mute-btn") 6 | const captionsBtn = document.querySelector(".captions-btn") 7 | const speedBtn = document.querySelector(".speed-btn") 8 | const currentTimeElem = document.querySelector(".current-time") 9 | const totalTimeElem = document.querySelector(".total-time") 10 | const previewImg = document.querySelector(".preview-img") 11 | const thumbnailImg = document.querySelector(".thumbnail-img") 12 | const volumeSlider = document.querySelector(".volume-slider") 13 | const videoContainer = document.querySelector(".video-container") 14 | const timelineContainer = document.querySelector(".timeline-container") 15 | const video = document.querySelector("video") 16 | 17 | document.addEventListener("keydown", e => { 18 | const tagName = document.activeElement.tagName.toLowerCase() 19 | 20 | if (tagName === "input") return 21 | 22 | switch (e.key.toLowerCase()) { 23 | case " ": 24 | if (tagName === "button") return 25 | case "k": 26 | togglePlay() 27 | break 28 | case "f": 29 | toggleFullScreenMode() 30 | break 31 | case "t": 32 | toggleTheaterMode() 33 | break 34 | case "i": 35 | toggleMiniPlayerMode() 36 | break 37 | case "m": 38 | toggleMute() 39 | break 40 | case "arrowleft": 41 | case "j": 42 | skip(-5) 43 | break 44 | case "arrowright": 45 | case "l": 46 | skip(5) 47 | break 48 | case "c": 49 | toggleCaptions() 50 | break 51 | } 52 | }) 53 | 54 | // Timeline 55 | timelineContainer.addEventListener("mousemove", handleTimelineUpdate) 56 | timelineContainer.addEventListener("mousedown", toggleScrubbing) 57 | document.addEventListener("mouseup", e => { 58 | if (isScrubbing) toggleScrubbing(e) 59 | }) 60 | document.addEventListener("mousemove", e => { 61 | if (isScrubbing) handleTimelineUpdate(e) 62 | }) 63 | 64 | let isScrubbing = false 65 | let wasPaused 66 | function toggleScrubbing(e) { 67 | const rect = timelineContainer.getBoundingClientRect() 68 | const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width 69 | isScrubbing = (e.buttons & 1) === 1 70 | videoContainer.classList.toggle("scrubbing", isScrubbing) 71 | if (isScrubbing) { 72 | wasPaused = video.paused 73 | video.pause() 74 | } else { 75 | video.currentTime = percent * video.duration 76 | if (!wasPaused) video.play() 77 | } 78 | 79 | handleTimelineUpdate(e) 80 | } 81 | 82 | function handleTimelineUpdate(e) { 83 | const rect = timelineContainer.getBoundingClientRect() 84 | const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width 85 | const previewImgNumber = Math.max( 86 | 1, 87 | Math.floor((percent * video.duration) / 10) 88 | ) 89 | const previewImgSrc = `assets/previewImgs/preview${previewImgNumber}.jpg` 90 | previewImg.src = previewImgSrc 91 | timelineContainer.style.setProperty("--preview-position", percent) 92 | 93 | if (isScrubbing) { 94 | e.preventDefault() 95 | thumbnailImg.src = previewImgSrc 96 | timelineContainer.style.setProperty("--progress-position", percent) 97 | } 98 | } 99 | 100 | // Playback Speed 101 | speedBtn.addEventListener("click", changePlaybackSpeed) 102 | 103 | function changePlaybackSpeed() { 104 | let newPlaybackRate = video.playbackRate + 0.25 105 | if (newPlaybackRate > 2) newPlaybackRate = 0.25 106 | video.playbackRate = newPlaybackRate 107 | speedBtn.textContent = `${newPlaybackRate}x` 108 | } 109 | 110 | // Captions 111 | const captions = video.textTracks[0] 112 | captions.mode = "hidden" 113 | 114 | captionsBtn.addEventListener("click", toggleCaptions) 115 | 116 | function toggleCaptions() { 117 | const isHidden = captions.mode === "hidden" 118 | captions.mode = isHidden ? "showing" : "hidden" 119 | videoContainer.classList.toggle("captions", isHidden) 120 | } 121 | 122 | // Duration 123 | video.addEventListener("loadeddata", () => { 124 | totalTimeElem.textContent = formatDuration(video.duration) 125 | }) 126 | 127 | video.addEventListener("timeupdate", () => { 128 | currentTimeElem.textContent = formatDuration(video.currentTime) 129 | const percent = video.currentTime / video.duration 130 | timelineContainer.style.setProperty("--progress-position", percent) 131 | }) 132 | 133 | const leadingZeroFormatter = new Intl.NumberFormat(undefined, { 134 | minimumIntegerDigits: 2, 135 | }) 136 | function formatDuration(time) { 137 | const seconds = Math.floor(time % 60) 138 | const minutes = Math.floor(time / 60) % 60 139 | const hours = Math.floor(time / 3600) 140 | if (hours === 0) { 141 | return `${minutes}:${leadingZeroFormatter.format(seconds)}` 142 | } else { 143 | return `${hours}:${leadingZeroFormatter.format( 144 | minutes 145 | )}:${leadingZeroFormatter.format(seconds)}` 146 | } 147 | } 148 | 149 | function skip(duration) { 150 | video.currentTime += duration 151 | } 152 | 153 | // Volume 154 | muteBtn.addEventListener("click", toggleMute) 155 | volumeSlider.addEventListener("input", e => { 156 | video.volume = e.target.value 157 | video.muted = e.target.value === 0 158 | }) 159 | 160 | function toggleMute() { 161 | video.muted = !video.muted 162 | } 163 | 164 | video.addEventListener("volumechange", () => { 165 | volumeSlider.value = video.volume 166 | let volumeLevel 167 | if (video.muted || video.volume === 0) { 168 | volumeSlider.value = 0 169 | volumeLevel = "muted" 170 | } else if (video.volume >= 0.5) { 171 | volumeLevel = "high" 172 | } else { 173 | volumeLevel = "low" 174 | } 175 | 176 | videoContainer.dataset.volumeLevel = volumeLevel 177 | }) 178 | 179 | // View Modes 180 | theaterBtn.addEventListener("click", toggleTheaterMode) 181 | fullScreenBtn.addEventListener("click", toggleFullScreenMode) 182 | miniPlayerBtn.addEventListener("click", toggleMiniPlayerMode) 183 | 184 | function toggleTheaterMode() { 185 | videoContainer.classList.toggle("theater") 186 | } 187 | 188 | function toggleFullScreenMode() { 189 | if (document.fullscreenElement == null) { 190 | videoContainer.requestFullscreen() 191 | } else { 192 | document.exitFullscreen() 193 | } 194 | } 195 | 196 | function toggleMiniPlayerMode() { 197 | if (videoContainer.classList.contains("mini-player")) { 198 | document.exitPictureInPicture() 199 | } else { 200 | video.requestPictureInPicture() 201 | } 202 | } 203 | 204 | document.addEventListener("fullscreenchange", () => { 205 | videoContainer.classList.toggle("full-screen", document.fullscreenElement) 206 | }) 207 | 208 | video.addEventListener("enterpictureinpicture", () => { 209 | videoContainer.classList.add("mini-player") 210 | }) 211 | 212 | video.addEventListener("leavepictureinpicture", () => { 213 | videoContainer.classList.remove("mini-player") 214 | }) 215 | 216 | // Play/Pause 217 | playPauseBtn.addEventListener("click", togglePlay) 218 | video.addEventListener("click", togglePlay) 219 | 220 | function togglePlay() { 221 | video.paused ? video.play() : video.pause() 222 | } 223 | 224 | video.addEventListener("play", () => { 225 | videoContainer.classList.remove("paused") 226 | }) 227 | 228 | video.addEventListener("pause", () => { 229 | videoContainer.classList.add("paused") 230 | }) 231 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | .video-container { 10 | position: relative; 11 | width: 90%; 12 | max-width: 1000px; 13 | display: flex; 14 | justify-content: center; 15 | margin-inline: auto; 16 | background-color: black; 17 | } 18 | 19 | .video-container.theater, 20 | .video-container.full-screen { 21 | max-width: initial; 22 | width: 100%; 23 | } 24 | 25 | .video-container.theater { 26 | max-height: 90vh; 27 | } 28 | 29 | .video-container.full-screen { 30 | max-height: 100vh; 31 | } 32 | 33 | video { 34 | width: 100%; 35 | } 36 | 37 | .video-controls-container { 38 | position: absolute; 39 | bottom: 0; 40 | left: 0; 41 | right: 0; 42 | color: white; 43 | z-index: 100; 44 | opacity: 0; 45 | transition: opacity 150ms ease-in-out; 46 | } 47 | 48 | .video-controls-container::before { 49 | content: ""; 50 | position: absolute; 51 | bottom: 0; 52 | background: linear-gradient(to top, rgba(0, 0, 0, .75), transparent); 53 | width: 100%; 54 | aspect-ratio: 6 / 1; 55 | z-index: -1; 56 | pointer-events: none; 57 | } 58 | 59 | .video-container:hover .video-controls-container, 60 | .video-container:focus-within .video-controls-container, 61 | .video-container.paused .video-controls-container { 62 | opacity: 1; 63 | } 64 | 65 | .video-controls-container .controls { 66 | display: flex; 67 | gap: .5rem; 68 | padding: .25rem; 69 | align-items: center; 70 | } 71 | 72 | .video-controls-container .controls button { 73 | background: none; 74 | border: none; 75 | color: inherit; 76 | padding: 0; 77 | height: 30px; 78 | width: 30px; 79 | font-size: 1.1rem; 80 | cursor: pointer; 81 | opacity: .85; 82 | transition: opacity 150ms ease-in-out; 83 | } 84 | 85 | .video-controls-container .controls button:hover { 86 | opacity: 1; 87 | } 88 | 89 | .video-container.paused .pause-icon { 90 | display: none; 91 | } 92 | 93 | .video-container:not(.paused) .play-icon { 94 | display: none; 95 | } 96 | 97 | .video-container.theater .tall { 98 | display: none; 99 | } 100 | 101 | .video-container:not(.theater) .wide { 102 | display: none; 103 | } 104 | 105 | .video-container.full-screen .open { 106 | display: none; 107 | } 108 | 109 | .video-container:not(.full-screen) .close { 110 | display: none; 111 | } 112 | 113 | .volume-high-icon, 114 | .volume-low-icon, 115 | .volume-muted-icon { 116 | display: none; 117 | } 118 | 119 | .video-container[data-volume-level="high"] .volume-high-icon { 120 | display: block; 121 | } 122 | 123 | .video-container[data-volume-level="low"] .volume-low-icon { 124 | display: block; 125 | } 126 | 127 | .video-container[data-volume-level="muted"] .volume-muted-icon { 128 | display: block; 129 | } 130 | 131 | .volume-container { 132 | display: flex; 133 | align-items: center; 134 | } 135 | 136 | .volume-slider { 137 | width: 0; 138 | transform-origin: left; 139 | transform: scaleX(0); 140 | transition: width 150ms ease-in-out, transform 150ms ease-in-out; 141 | } 142 | 143 | .volume-container:hover .volume-slider, 144 | .volume-slider:focus-within { 145 | width: 100px; 146 | transform: scaleX(1); 147 | } 148 | 149 | .duration-container { 150 | display: flex; 151 | align-items: center; 152 | gap: .25rem; 153 | flex-grow: 1; 154 | } 155 | 156 | .video-container.captions .captions-btn { 157 | border-bottom: 3px solid red; 158 | } 159 | 160 | .video-controls-container .controls button.wide-btn { 161 | width: 50px; 162 | } 163 | 164 | .timeline-container { 165 | height: 7px; 166 | margin-inline: .5rem; 167 | cursor: pointer; 168 | display: flex; 169 | align-items: center; 170 | } 171 | 172 | .timeline { 173 | background-color: rgba(100, 100, 100, .5); 174 | height: 3px; 175 | width: 100%; 176 | position: relative 177 | } 178 | 179 | .timeline::before { 180 | content: ""; 181 | position: absolute; 182 | left: 0; 183 | top: 0; 184 | bottom: 0; 185 | right: calc(100% - var(--preview-position) * 100%); 186 | background-color: rgb(150, 150, 150); 187 | display: none; 188 | } 189 | 190 | .timeline::after { 191 | content: ""; 192 | position: absolute; 193 | left: 0; 194 | top: 0; 195 | bottom: 0; 196 | right: calc(100% - var(--progress-position) * 100%); 197 | background-color: red; 198 | } 199 | 200 | .timeline .thumb-indicator { 201 | --scale: 0; 202 | position: absolute; 203 | transform: translateX(-50%) scale(var(--scale)); 204 | height: 200%; 205 | top: -50%; 206 | left: calc(var(--progress-position) * 100%); 207 | background-color: red; 208 | border-radius: 50%; 209 | transition: transform 150ms ease-in-out; 210 | aspect-ratio: 1 / 1; 211 | } 212 | 213 | .timeline .preview-img { 214 | position: absolute; 215 | height: 80px; 216 | aspect-ratio: 16 / 9; 217 | top: -1rem; 218 | transform: translate(-50%, -100%); 219 | left: calc(var(--preview-position) * 100%); 220 | border-radius: .25rem; 221 | border: 2px solid white; 222 | display: none; 223 | } 224 | 225 | .thumbnail-img { 226 | position: absolute; 227 | top: 0; 228 | left: 0; 229 | right: 0; 230 | bottom: 0; 231 | width: 100%; 232 | height: 100%; 233 | display: none; 234 | } 235 | 236 | .video-container.scrubbing .thumbnail-img { 237 | display: block; 238 | } 239 | 240 | .video-container.scrubbing .preview-img, 241 | .timeline-container:hover .preview-img { 242 | display: block; 243 | } 244 | 245 | .video-container.scrubbing .timeline::before, 246 | .timeline-container:hover .timeline::before { 247 | display: block; 248 | } 249 | 250 | .video-container.scrubbing .thumb-indicator, 251 | .timeline-container:hover .thumb-indicator { 252 | --scale: 1; 253 | } 254 | 255 | .video-container.scrubbing .timeline, 256 | .timeline-container:hover .timeline { 257 | height: 100%; 258 | } --------------------------------------------------------------------------------