├── poster.jpg
├── video.mp4
├── custom-video-player-ss.jpg
├── index.js
├── index.html
├── styles.css
└── video.js
/poster.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Navaneethakrishnan2004/Video-Player/HEAD/poster.jpg
--------------------------------------------------------------------------------
/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Navaneethakrishnan2004/Video-Player/HEAD/video.mp4
--------------------------------------------------------------------------------
/custom-video-player-ss.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Navaneethakrishnan2004/Video-Player/HEAD/custom-video-player-ss.jpg
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const video = new Video({
4 | wrapperID: "video-wrapper",
5 | videoSrc: "static/video.mp4",
6 | posterSrc: "static/poster.jpg",
7 | absolute: true,
8 | hideControlsOnPlay: true,
9 | progressColor: "white"
10 | });
11 |
12 | console.log(video);
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Custom Video Player
8 |
9 |
10 |
11 | Custom Video Player
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | .m-video {
6 | position: relative;
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 |
12 | --progress-color: red;
13 | }
14 |
15 | .m-video video {
16 | width: 100%;
17 | max-width: 100%;
18 | }
19 |
20 | .m-video video::-webkit-media-controls-enclosure,
21 | .m-video video::-webkit-media-controls {
22 | display: none !important;
23 | }
24 |
25 | .m-video input[type="range"] {
26 | height: 4px;
27 | -webkit-appearance: none;
28 | outline: none;
29 | background: transparent;
30 | overflow: hidden;
31 | cursor: pointer;
32 | border-radius: 4px;
33 | }
34 |
35 | .m-video .v-controls {
36 | background: rgba(0, 0, 0, .6);
37 | padding: .5rem;
38 | color: #fff;
39 | display: flex;
40 | flex-direction: column;
41 | flex-wrap: nowrap;
42 | width: 100%;
43 | }
44 |
45 | .m-video .v-controls.--fs-abs,
46 | .m-video .v-controls.--absolute {
47 | position: absolute;
48 | width: 100%;
49 | left: 0;
50 | bottom: 0;
51 | }
52 |
53 | .m-video.playing .v-controls.--absolute.--auto-hide,
54 | .m-video .v-controls:not(.--absolute).--fs-abs {
55 | opacity: 0;
56 | transition: .3s;
57 | }
58 |
59 | .m-video .v-controls:hover.--absolute.--auto-hide,
60 | .m-video .v-controls:hover.--fs-abs {
61 | opacity: 1;
62 | }
63 |
64 |
65 | .m-video .v-controls svg {
66 | width: 24px;
67 | height: 24px;
68 | fill: none;
69 | stroke: #fff;
70 | stroke-width: 2;
71 | stroke-linecap: round;
72 | stroke-linejoin: round;
73 | }
74 |
75 | .m-video button {
76 | outline: none;
77 | background: transparent;
78 | border: none;
79 | cursor: pointer;
80 | width: 30px;
81 | height: 30px;
82 | padding: 0;
83 | margin: 0;
84 | display: inline-flex;
85 | justify-content: center;
86 | align-items: center;
87 | box-shadow: none;
88 | }
89 |
90 | .m-video button:focus,
91 | .m-video button::-moz-focus-inner {
92 | border: 0;
93 | outline: none;
94 | }
95 |
96 | /* Slider in Chrome, Firefox, and Opera */
97 | .m-video input[type="range"]::-webkit-slider-runnable-track {
98 | background: rgba(255, 255, 255, 0.6);
99 | height: 4px;
100 | border-radius: 4px;
101 | border: 0;
102 | width: 100%;
103 | }
104 |
105 | .m-video input[type="range"]::-moz-range-track {
106 | background: rgba(255, 255, 255, 0.6);
107 | height: 4px;
108 | border-radius: 4px;
109 | border: 0;
110 | width: 100%;
111 | }
112 |
113 | .m-video input[type="range"]::-ms-track {
114 | background: rgba(255, 255, 255, 0.6);
115 | height: 4px;
116 | border-radius: 4px;
117 | border: 0;
118 | width: 100%;
119 | }
120 |
121 | .m-video input[type="range"]::-ms-fill-lower {
122 | background: var(--progress-color, red);
123 | }
124 |
125 | .m-video input[type="range"]::-moz-range-progress {
126 | background: var(--progress-color, red);
127 | }
128 |
129 | .m-video input[type="range"]::-webkit-slider-thumb {
130 | -webkit-appearance: none;
131 | background: var(--progress-color, red);
132 | width: 4px;
133 | height: 4px;
134 | margin-top: 0;
135 | border-radius: 50%;
136 | cursor: pointer;
137 | }
138 |
139 | .m-video input[type="range"]::-moz-range-thumb {
140 | -moz-appearance: none;
141 | background: var(--progress-color, red);
142 | width: 4px;
143 | height: 4px;
144 | margin-top: 0;
145 | border-radius: 50%;
146 | cursor: pointer;
147 | }
148 |
149 | .m-video input[type="range"]::-ms-thumb {
150 | background: var(--progress-color, red);
151 | width: 4px;
152 | height: 4px;
153 | margin-top: 0;
154 | border-radius: 50%;
155 | cursor: pointer;
156 | }
157 |
158 | .m-video input[type="range"]::-webkit-slider-thumb {
159 | box-shadow: -1200px 0 0 1200px var(--progress-color, red);
160 | outline: none;
161 | border: 0;
162 | }
163 |
164 | .m-video .v-controls__btns {
165 | display: flex;
166 | flex-wrap: nowrap;
167 | justify-content: space-between;
168 | align-items: center;
169 | }
170 |
171 | .m-video .v-controls__btns .v-controls__btns__sound {
172 | width: 30%;
173 | flex: 0 0 30%;
174 | display: flex;
175 | justify-content: flex-start;
176 | align-items: center;
177 | }
178 |
179 | .m-video .v-controls__btns .v-controls__btns__sound input[type="range"] {
180 | max-width: 90px;
181 | }
182 |
183 | .m-video .v-controls__btns .v-controls__btns_play {
184 | width: 40%;
185 | flex: 0 0 40%;
186 | display: flex;
187 | justify-content: center;
188 | align-items: center;
189 | }
190 |
191 | .m-video .v-controls__btns .v-controls__btns_fs {
192 | width: 30%;
193 | flex: 0 0 30%;
194 | display: flex;
195 | justify-content: flex-end;
196 | }
197 |
198 | .m-video .v-controls .v-controls__timing {
199 | display: flex;
200 | justify-content: center;
201 | align-items: center;
202 | margin-top: .5rem;
203 | }
204 |
205 | .m-video .v-controls .v-controls__timing input[type="range"] {
206 | width: 100%;
207 | margin: 0 1rem;
208 | }
209 |
210 | .m-video .v-controls .v-controls__timing span {
211 | text-align: center;
212 | width: 58px;
213 | display: inline-block;
214 | }
215 |
--------------------------------------------------------------------------------
/video.js:
--------------------------------------------------------------------------------
1 | const defaultOptions = {
2 | wrapperID: "video-wrapper",
3 | videoSrc: null,
4 | posterSrc: null,
5 | absolute: false,
6 | hideControlsOnPlay: true,
7 | progressColor: "red"
8 | };
9 |
10 | function Video(options = defaultOptions) {
11 |
12 | // check options and its validity
13 | if (!options || typeof options !== "object" || !options.wrapperID)
14 | throw new Error("Options not passed to the Video functions.");
15 | this.options = Object.assign(defaultOptions, options);
16 |
17 | // check the existence of video wrapper
18 | let wrapper = document.getElementById(this.options.wrapperID);
19 | if (!wrapper && (!wrapper instanceof Element || !wrapper instanceof HTMLDocument))
20 | throw new Error("Wrapper must be a valid node.");
21 | this.wrapper = wrapper;
22 |
23 | // initialize the video
24 | this.init();
25 | }
26 |
27 | Video.prototype.init = function () {
28 | if (this.video)
29 | return;
30 |
31 | // init the icons
32 | this.icons = {
33 | stop: '',
34 | play: '',
35 | pause: '',
36 | muted: '',
37 | volume_0: '',
38 | volume_1: '',
39 | volume_2: '',
40 | fullscreen: '',
41 | exitFullscreen: '',
42 | };
43 |
44 | // add special class to the wrapper to active all styles
45 | this.wrapper.classList.add("m-video");
46 |
47 | // check the existence of video tag in wrapper.
48 | this.video = this.wrapper.querySelector("video");
49 | if (!this.video) {
50 | if (!this.options.videoSrc)
51 | throw new Error("Can not find video tag in the wrapper or videoSrc in options");
52 |
53 | // create video tag with source and append it to wrapper
54 | this.generateVideoTag();
55 | }
56 |
57 | // se default controls to false;
58 | this.video.controls = false;
59 |
60 | // create custom controls element and append it to wrapper
61 | this.generateControls();
62 |
63 | // initialize the listeners.
64 | this.initListeners();
65 | };
66 |
67 | Video.prototype.generateVideoTag = function () {
68 | if (this.video)
69 | return;
70 |
71 | this.video = document.createElement("video");
72 | this.video.src = this.options.videoSrc;
73 | this.video.poster = this.options.posterSrc;
74 |
75 | this.wrapper.append(this.video);
76 | };
77 |
78 | Video.prototype.generateControls = function () {
79 | if (!this.video || this.controls)
80 | return;
81 |
82 | this.controls = document.createElement("div");
83 | this.controls.classList.add("v-controls");
84 | if (this.options.absolute)
85 | this.controls.classList.add("--absolute");
86 |
87 | if (this.options.hideControlsOnPlay)
88 | this.controls.classList.add("--auto-hide");
89 |
90 | this.controls.innerHTML = `
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | 00:00
106 |
107 | 00:00
108 |
`;
109 |
110 |
111 | this.elapsedTimeSpan = this.controls.querySelector(".v-controls__timing .elapsed-time");
112 | this.remainingTimeSpan = this.controls.querySelector(".v-controls__timing .remaining-time");
113 |
114 | this.wrapper.style.setProperty('--progress-color', this.options.progressColor);
115 | this.wrapper.append(this.controls);
116 |
117 | this.videoPlayBtn = this.wrapper.querySelector(".v-controls__btns #video-play");
118 | this.videoStopBtn = this.wrapper.querySelector(".v-controls__btns #video-stop");
119 | this.soundToggleBtn = this.wrapper.querySelector(".v-controls__btns #sound-toggle");
120 | this.videoFullscreenBtn = this.wrapper.querySelector(".v-controls__btns #video-fs");
121 | this.videoProgress = this.wrapper.querySelector(".v-controls__timing input[type=range]");
122 | this.soundRange = this.wrapper.querySelector(".v-controls__btns__sound input[type=range]");
123 |
124 | };
125 |
126 | Video.prototype.initListeners = function () {
127 | if (!this.video || !this.controls)
128 | return;
129 |
130 | this.video.addEventListener("loadeddata", this.calcProgress.bind(this));
131 | this.video.addEventListener("play", this.onPlay.bind(this));
132 | this.video.addEventListener("pause", this.onPause.bind(this));
133 | this.video.addEventListener("timeupdate", this.calcProgress.bind(this));
134 |
135 | this.videoPlayBtn.addEventListener("click", this.togglePlay.bind(this));
136 | this.videoStopBtn.addEventListener("click", this.stop.bind(this));
137 | this.soundToggleBtn.addEventListener("click", this.soundToggle.bind(this));
138 | this.videoFullscreenBtn.addEventListener("click", this.toggleFullscreen.bind(this));
139 | this.videoProgress.addEventListener("change", this.setCurrentTime.bind(this));
140 | this.soundRange.addEventListener("change", this.updateVolume.bind(this));
141 |
142 | };
143 |
144 | Video.prototype.onPlay = function () {
145 | this.wrapper.classList.add("playing");
146 | this.videoPlayBtn.innerHTML = this.icons.pause;
147 | };
148 |
149 | Video.prototype.onPause = function () {
150 | this.wrapper.classList.remove("playing");
151 | this.videoPlayBtn.innerHTML = this.icons.play;
152 | };
153 |
154 | Video.prototype.togglePlay = function () {
155 | if (!this.video)
156 | return;
157 |
158 | if (this.video.paused) {
159 | this.video.play();
160 | } else {
161 | this.video.pause();
162 | }
163 | };
164 |
165 | Video.prototype.stop = function () {
166 | if (!this.video)
167 | return;
168 |
169 | this.video.currentTime = 0;
170 | this.video.pause();
171 | };
172 |
173 | Video.prototype.soundToggle = function () {
174 | if (!this.video)
175 | return;
176 |
177 | this.video.muted = !this.video.muted;
178 |
179 | // handle sound range animation when mute clicked
180 | const animateVolumeRange = () => {
181 | if (this.video.muted) {
182 | if (+this.soundRange.value > 0) {
183 | this.soundRange.value = +this.soundRange.value - .1;
184 | requestAnimationFrame(animateVolumeRange)
185 | }
186 | } else {
187 | if (+this.soundRange.value < this.volumeMemorize) {
188 | this.soundRange.value = +this.soundRange.value + .1;
189 | requestAnimationFrame(animateVolumeRange)
190 | }
191 | }
192 | };
193 |
194 | if (this.video.muted) {
195 | this.soundToggleBtn.innerHTML = this.icons.muted;
196 | this.volumeMemorize = this.soundRange.value;
197 | } else {
198 | this.soundToggleBtn.innerHTML = this.icons.volume_2;
199 | }
200 |
201 | animateVolumeRange();
202 |
203 | };
204 |
205 | Video.prototype.updateVolume = function (e) {
206 | if (!this.video || !e)
207 | return;
208 |
209 | this.video.volume = e.target.value;
210 | if (+e.target.value === 0) {
211 | this.soundToggleBtn.innerHTML = this.icons.volume_0;
212 | } else if (+e.target.value < .5) {
213 | this.soundToggleBtn.innerHTML = this.icons.volume_1;
214 | } else {
215 | this.soundToggleBtn.innerHTML = this.icons.volume_2;
216 | }
217 | };
218 |
219 | Video.prototype.toggleFullscreen = function () {
220 | if (!this.video)
221 | return;
222 |
223 | if (!this.fullscreen) {
224 | this.fullscreen = true;
225 | this.videoFullscreenBtn.innerHTML = this.icons.exitFullscreen;
226 | this.controls.classList.add("--fs-abs");
227 |
228 | if (this.wrapper.requestFullscreen) {
229 | this.wrapper.requestFullscreen();
230 | } else if (this.wrapper.mozRequestFullScreen) { /* Firefox */
231 | this.wrapper.mozRequestFullScreen();
232 | } else if (this.wrapper.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
233 | this.wrapper.webkitRequestFullscreen();
234 | } else if (this.wrapper.msRequestFullscreen) { /* IE/Edge */
235 | this.wrapper.msRequestFullscreen();
236 | }
237 | } else {
238 | this.fullscreen = false;
239 | this.videoFullscreenBtn.innerHTML = this.icons.fullscreen;
240 | this.controls.classList.remove("--fs-abs");
241 | if (document.exitFullscreen) {
242 | document.exitFullscreen();
243 | } else if (document.mozCancelFullScreen) { /* Firefox */
244 | document.mozCancelFullScreen();
245 | } else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */
246 | document.webkitExitFullscreen();
247 | } else if (document.msExitFullscreen) { /* IE/Edge */
248 | document.msExitFullscreen();
249 | }
250 | }
251 | };
252 |
253 | Video.prototype.setCurrentTime = function (e) {
254 | if (!this.video || !e)
255 | return;
256 |
257 | const rangeValue = e.target.value || 0;
258 |
259 | this.video.currentTime = (rangeValue * this.video.duration) / 100;
260 | };
261 |
262 | // calculate the real time progress for the video and
263 | // set the value of times spans
264 | Video.prototype.calcProgress = function () {
265 | if (!this.video)
266 | return;
267 |
268 | this.videoProgress.value = (this.video.currentTime / this.video.duration) * 100;
269 |
270 | this.elapsedTimeSpan.innerHTML = secToTimeStr(this.video.currentTime);
271 | this.remainingTimeSpan.innerHTML = secToTimeStr(this.video.duration - this.video.currentTime);
272 | };
273 |
274 | /**
275 | * Convert numbers in second to time string like 00:00
276 | *
277 | * @param seconds
278 | * @returns {string}
279 | */
280 | function secToTimeStr(seconds) {
281 |
282 | let timeInHour = Math.floor(seconds / 3600);
283 | let timeInMin = Math.floor((seconds % 3600) / 60);
284 | let timeInSec = Math.floor(seconds % 60);
285 |
286 | if (timeInHour < 10)
287 | timeInHour = `0${timeInHour}`;
288 |
289 | if (timeInMin < 10)
290 | timeInMin = `0${timeInMin}`;
291 |
292 | if (timeInSec < 10)
293 | timeInSec = `0${timeInSec}`;
294 |
295 | let timeStr = `${timeInMin}:${timeInSec}`;
296 | if (parseInt(timeInHour))
297 | timeStr = `${timeInHour}:${timeStr}`;
298 |
299 | return timeStr;
300 | }
301 |
--------------------------------------------------------------------------------