UNPKG

simple-video-controls

Version:
505 lines (421 loc) 21.9 kB
function svControls(videoElement, options = {}) { let self = this; this.options = { play: { icon: '<svg width="10" focusable="false" data-icon="play" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="white" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"></path></svg>', show: true, }, pause: { icon: '<svg width="10" focusable="false" data-icon="pause" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="white" d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"></path></svg>', show: true, }, stop: { icon: '<svg width="10" focusable="false" data-icon="stop" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="white" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"></path></svg>', show: true, }, mute: { icon: '<svg width="10" focusable="false" data-icon="volume-mute" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zM461.64 256l45.64-45.64c6.3-6.3 6.3-16.52 0-22.82l-22.82-22.82c-6.3-6.3-16.52-6.3-22.82 0L416 210.36l-45.64-45.64c-6.3-6.3-16.52-6.3-22.82 0l-22.82 22.82c-6.3 6.3-6.3 16.52 0 22.82L370.36 256l-45.63 45.63c-6.3 6.3-6.3 16.52 0 22.82l22.82 22.82c6.3 6.3 16.52 6.3 22.82 0L416 301.64l45.64 45.64c6.3 6.3 16.52 6.3 22.82 0l22.82-22.82c6.3-6.3 6.3-16.52 0-22.82L461.64 256z"></path></svg>', show: true, }, volume: { icon: '<svg width="10" focusable="false" data-icon="volume-up" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="white" d="M215.03 71.05L126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97zm233.32-51.08c-11.17-7.33-26.18-4.24-33.51 6.95-7.34 11.17-4.22 26.18 6.95 33.51 66.27 43.49 105.82 116.6 105.82 195.58 0 78.98-39.55 152.09-105.82 195.58-11.17 7.32-14.29 22.34-6.95 33.5 7.04 10.71 21.93 14.56 33.51 6.95C528.27 439.58 576 351.33 576 256S528.27 72.43 448.35 19.97zM480 256c0-63.53-32.06-121.94-85.77-156.24-11.19-7.14-26.03-3.82-33.12 7.46s-3.78 26.21 7.41 33.36C408.27 165.97 432 209.11 432 256s-23.73 90.03-63.48 115.42c-11.19 7.14-14.5 22.07-7.41 33.36 6.51 10.36 21.12 15.14 33.12 7.46C447.94 377.94 480 319.54 480 256zm-141.77-76.87c-11.58-6.33-26.19-2.16-32.61 9.45-6.39 11.61-2.16 26.2 9.45 32.61C327.98 228.28 336 241.63 336 256c0 14.38-8.02 27.72-20.92 34.81-11.61 6.41-15.84 21-9.45 32.61 6.43 11.66 21.05 15.8 32.61 9.45 28.23-15.55 45.77-45 45.77-76.88s-17.54-61.32-45.78-76.86z"></path></svg>', show: true, value: 0.5, }, progress: { show: true, color: '#4d9dff', fontFamily: 'sans-serif', fontSize: '14px', }, fullscreen: { icon: '<svg width="10" focusable="false" data-icon="expand" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="white" d="M0 180V56c0-13.3 10.7-24 24-24h124c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12H64v84c0 6.6-5.4 12-12 12H12c-6.6 0-12-5.4-12-12zM288 44v40c0 6.6 5.4 12 12 12h84v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12V56c0-13.3-10.7-24-24-24H300c-6.6 0-12 5.4-12 12zm148 276h-40c-6.6 0-12 5.4-12 12v84h-84c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h124c13.3 0 24-10.7 24-24V332c0-6.6-5.4-12-12-12zM160 468v-40c0-6.6-5.4-12-12-12H64v-84c0-6.6-5.4-12-12-12H12c-6.6 0-12 5.4-12 12v124c0 13.3 10.7 24 24 24h124c6.6 0 12-5.4 12-12z"></path></svg>', show: true, }, zoom: { icon: '<svg width="10" focusable="false" data-icon="search-plus" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M304 192v32c0 6.6-5.4 12-12 12h-56v56c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-56h-56c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h56v-56c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v56h56c6.6 0 12 5.4 12 12zm201 284.7L476.7 505c-9.4 9.4-24.6 9.4-33.9 0L343 405.3c-4.5-4.5-7-10.6-7-17V372c-35.3 27.6-79.7 44-128 44C93.1 416 0 322.9 0 208S93.1 0 208 0s208 93.1 208 208c0 48.3-16.4 92.7-44 128h16.3c6.4 0 12.5 2.5 17 7l99.7 99.7c9.3 9.4 9.3 24.6 0 34zM344 208c0-75.2-60.8-136-136-136S72 132.8 72 208s60.8 136 136 136 136-60.8 136-136z"></path></svg>', show: true, }, settings: { icon: '<svg width="10" focusable="false" data-icon="cog" class="svcIcon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"></path></svg>', show: true, }, clickPlayPause: false, spacebarPlayPause: true, backgroundColor: 'linear-gradient(0deg, #0000007a, transparent)', color: 'white', accentColor: '#4d9dff', minWidth: 500, minHeight: 30, showOnHover: true, loop: false, autoplay: false, muted: false }; Object.keys(options).forEach(function(key) { self.options[key] = options[key]; }); if (typeof videoElement === 'string') { videoElement = document.getElementById(videoElement); } videoElement.classList.add('scvVideo'); if (self.options.loop) { videoElement.loop = self.options.loop; } if (self.options.autoplay) { videoElement.autoplay = self.options.autoplay; } if (self.options.muted) { videoElement.muted = self.options.muted; } if (self.options.volume.value != null) { videoElement.volume = self.options.volume.value; } if (self.options.accentColor) { document.documentElement.style.setProperty('--svcAccentColor', self.options.accentColor); } const videoContainer = document.createElement('div'); videoContainer.classList.add('svcContainer'); const videoControls = document.createElement('div'); videoControls.classList.add('svcControls'); if (self.options.showOnHover) { videoControls.classList.add('svcControlsHidden'); } if (self.options.backgroundColor) { videoControls.style.background = self.options.backgroundColor; } if (self.options.color) { videoControls.style.color = self.options.color; } let btnClass = 'svcBtn'; const playBtn = newBtn(self.options.play.icon, btnClass); playBtn.setAttribute('data-state', 'play'); playBtn.onclick = () => handlePause(); const pauseBtn = newBtn(self.options.pause.icon, btnClass); pauseBtn.setAttribute('data-state', 'pause'); pauseBtn.onclick = () => handlePause(); videoControls.setAttribute('data-state', 'paused'); if (videoElement.autoplay) { videoControls.setAttribute('data-state', ''); } function handlePause() { let videoPaused = videoElement.paused || videoElement.ended; videoPaused ? videoElement.play() : videoElement.pause(); videoControls.setAttribute('data-state', videoPaused ? '' : 'paused'); } videoContainer.onclick = () => { progress.focus(); }; let videoClickTimer; videoElement.onclick = (e) => { if (self.options.clickPlayPause && zoom <= 1 && e.detail === 1) { videoClickTimer = setTimeout(() => { handlePause(); }, 200) } }; document.addEventListener('keypress', (e) => { if (self.options.spacebarPlayPause && document.activeElement === progress && e.code === "Space") { handlePause(); } }); videoElement.onended = () => { if (videoElement.loop) { return; } videoControls.setAttribute('data-state', ''); }; const stopBtn = newBtn(self.options.stop.icon, btnClass); stopBtn.setAttribute('data-state', 'stop'); stopBtn.onclick = function(e) { videoElement.pause(); videoElement.currentTime = 0; videoControls.setAttribute('data-state', ''); }; const volumeDiv = document.createElement('div'); const volumeIcon = document.createElement('div'); volumeIcon.innerHTML = self.options.volume.icon; volumeDiv.classList.add('verticalSlider'); volumeDiv.classList.add('svcBtn'); volumeDiv.onclick = function(e) { if (e.target === volumeSlider) { return; } volumeDiv.setAttribute('data-state', videoElement.muted ? 'unmute' : 'mute'); videoElement.muted = !videoElement.muted; if (videoElement.muted) { volumeIcon.innerHTML = self.options.mute.icon; volumeSlider.value = 0; } else { volumeIcon.innerHTML = self.options.volume.icon; volumeSlider.value = volume; } updateVolumeSliderVisual(e); }; volumeDiv.append(volumeIcon); const volumeSlider = document.createElement('input'); let volume = videoElement.volume; volumeSlider.setAttribute('type', 'range'); //volumeSlider.setAttribute('orient', 'vertical'); volumeSlider.setAttribute('min', 0); volumeSlider.setAttribute('max', 1); volumeSlider.setAttribute('step', 0.1); volumeSlider.value = videoElement.muted ? 0 : videoElement.volume; volumeSlider.onchange = function(e) { volume = e.target.value videoElement.volume = volume; volumeDiv.setAttribute('data-state', 'unmute'); videoElement.muted = false; volumeIcon.innerHTML = self.options.volume.icon; }; let volumeSliderInterval = null; volumeSlider.onmousedown = function(e) { volumeSliderInterval = setInterval(() => { updateVolumeSliderVisual(e); }, 10); }; document.addEventListener('mouseup', (e) => { videoClickActive = false; videoClickPostX = e.pageX; videoClickPostY = e.pageY try { clearInterval(volumeSliderInterval); } catch (err) { } }); let updateVolumeSliderVisual = function () { let value = (volumeSlider.value - volumeSlider.min) / (volumeSlider.max - volumeSlider.min) * 100; volumeSlider.style.background = 'linear-gradient(to right, ' + self.options.progress.color + ' 0%, ' + self.options.progress.color + ' ' + value + '%, rgba(240, 240, 240, 0.4) ' + value + '%, rgba(240, 240, 240, 0.4) 100%)'; }; volumeDiv.prepend(volumeSlider); updateVolumeSliderVisual(); let zoom = 1; const zoomDiv = document.createElement('div'); zoomDiv.innerHTML = self.options.zoom.icon; zoomDiv.classList.add('verticalSlider'); zoomDiv.classList.add('svcBtn'); zoomDiv.onclick = function (e) { if (e.target === zoomSlider) { return; } zoom = '1'; videoElement.style.transform = 'scale(' + zoom + ')'; zoomSlider.value = zoom; videoElement.style.left = '0'; videoElement.style.top = '0'; updateZoomSliderVisual(); } const zoomSlider = document.createElement('input'); zoomSlider.setAttribute('type', 'range'); //zoomSlider.setAttribute('orient', 'vertical'); zoomSlider.setAttribute('min', 1); zoomSlider.setAttribute('max', 3); zoomSlider.setAttribute('step', 0.1); zoomSlider.value = zoom; zoomSlider.onchange = function(e) { zoom = e.target.value videoElement.style.transform = 'scale(' + zoom + ')'; if (zoom < 2) { videoElement.style.left = '0'; videoElement.style.top = '0'; } }; let zoomSliderInterval = null; zoomSlider.onmousedown = function(e) { zoomSliderInterval = setInterval(() => { updateZoomSliderVisual(e); }, 10); }; document.addEventListener('mouseup', () => { clearInterval(zoomSliderInterval); }); let updateZoomSliderVisual = function () { let value = (zoomSlider.value - zoomSlider.min) / (zoomSlider.max - zoomSlider.min) * 100; zoomSlider.style.background = 'linear-gradient(to right, ' + self.options.progress.color + ' 0%, ' + self.options.progress.color + ' ' + value + '%, rgba(240, 240, 240, 0.4) ' + value + '%, rgba(240, 240, 240, 0.4) 100%)'; }; zoomDiv.prepend(zoomSlider); const fullscreenBtn = newBtn(self.options.fullscreen.icon, btnClass); fullscreenBtn.setAttribute('data-state', 'go-fullscreen'); let fullScreenToggle = () => { if (!!(document.fullscreen || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement || document.fullscreenElement)) { if (document.exitFullscreen) document.exitFullscreen(); else if (document.mozCancelFullScreen) document.mozCancelFullScreen(); else if (document.webkitCancelFullScreen) document.webkitCancelFullScreen(); else if (document.msExitFullscreen) document.msExitFullscreen(); videoContainer.setAttribute('data-fullscreen', false); } else { if (videoContainer.requestFullscreen) videoContainer.requestFullscreen(); else if (videoContainer.mozRequestFullScreen) videoContainer.mozRequestFullScreen(); else if (videoContainer.webkitRequestFullScreen) videoContainer.webkitRequestFullScreen(); else if (videoContainer.msRequestFullscreen) videoContainer.msRequestFullscreen(); videoContainer.setAttribute('data-fullscreen', true); } }; fullscreenBtn.onclick = fullScreenToggle; videoElement.addEventListener('dblclick', () => { clearTimeout(videoClickTimer); fullScreenToggle(); }); const btnArea1 = document.createElement('div'); if (self.options.play && self.options.play.show !== false) { btnArea1.append(playBtn); } if (self.options.pause && self.options.pause.show !== false) { btnArea1.append(pauseBtn); } if (self.options.stop && self.options.stop.show !== false) { btnArea1.append(stopBtn); } btnArea1.classList.add('svcBtnArea'); const btnArea2 = document.createElement('div'); if (self.options.volume && self.options.volume.show !== false) { btnArea2.append(volumeDiv); } if (self.options.zoom && self.options.zoom.show !== false) { btnArea2.append(zoomDiv); } if (self.options.fullscreen && self.options.fullscreen.show !== false) { btnArea2.append(fullscreenBtn); } btnArea2.classList.add('svcBtnArea'); const progressDiv = document.createElement('div'); progressDiv.classList.add('svcProgressDiv'); const progressTime = document.createElement('div'); progressTime.classList.add('svcProgressTime'); progressTime.style.fontFamily = self.options.progress.fontFamily; progressTime.style.fontSize = self.options.progress.fontSize; progressTime.innerText = '00:00'; const progress = document.createElement('input'); progress.setAttribute('type', 'range'); progress.setAttribute('step', 0.01); progress.classList.add('svcProgress'); progress.onchange = function(e) { videoElement.currentTime = e.target.value; }; let progressInterval = null; let wasPlaying = true; let isSeeking = false; progress.onmousedown = function(e) { isSeeking = true; if (videoElement.paused || videoElement.ended) { wasPlaying = false; } else { videoElement.pause(); handlePause(); } progressInterval = setInterval(() => { updateProgress(e); }, 10); }; document.addEventListener('mouseup', () => { if (isSeeking && wasPlaying) { videoElement.play(); handlePause(); } isSeeking = false; clearInterval(progressInterval); }); let updateProgress = function(e) { if (e) { videoElement.currentTime = e.target.value; } updateProgressVisual(); }; let updateProgressVisual = function () { let value = (progress.value - progress.min) / (progress.max - progress.min) * 100; progress.style.background = 'linear-gradient(to right, ' + self.options.progress.color + ' 0%, ' + self.options.progress.color + ' ' + value + '%, rgba(240, 240, 240, 0.4) ' + value + '%, rgba(240, 240, 240, 0.4) 100%)'; let time = videoElement.currentTime; progressTime.innerText = new Date(time * 1000).toISOString().substr(14, 5); }; videoElement.currentTime = 0; progress.value = videoElement.currentTime; videoElement.onloadedmetadata = function() { progress.setAttribute('max', videoElement.duration); }; videoElement.ontimeupdate = function() { if (!progress.getAttribute('max')) progress.setAttribute('max', videoElement.duration); progress.value = videoElement.currentTime; updateProgressVisual(); }; progressDiv.append(progress, progressTime); if (self.options.play.show !== false || self.options.pause.show !== false || self.options.stop.show !== false) { videoControls.append(btnArea1); } if (self.options.progress.show !== false) { videoControls.append(progressDiv); } videoControls.append(btnArea2); videoElement.replaceWith(videoContainer); videoContainer.append(videoElement, videoControls); videoElement.controls = false; function newBtn(html, className) { let btn = document.createElement('button'); btn.innerHTML = html; btn.classList.add(className); return btn; } let videoClickActive = false; let videoClickPostX = null; let videoClickPostY = null; let initX, initY, firstX, firstY; if (self.options.zoom.show !== false) { videoElement.addEventListener('mousedown', function(e) { if (zoom <= 1) { return; } if (self.options.clickPlayPause) { setTimeout(() => { if (videoClickActive) { return; } if (videoClickPostX && Math.abs(initX+e.pageX-firstX - initX+videoClickPostX-firstX) > 20) { return; } if (videoClickPostY && Math.abs(initY+e.pageY-firstY - initY+videoClickPostY-firstY) > 20) { return; } handlePause(); }, 200); } videoClickActive = true; e.preventDefault(); initX = this.offsetLeft; initY = this.offsetTop; firstX = e.pageX; firstY = e.pageY; this.addEventListener('mousemove', dragIt, false); window.addEventListener('mouseup', function() { videoElement.removeEventListener('mousemove', dragIt, false); }, false); }, false); videoElement.addEventListener('touchstart', function(e) { if (zoom === 1) { return; } e.preventDefault(); initX = this.offsetLeft; initY = this.offsetTop; var touch = e.touches; firstX = touch[0].pageX; firstY = touch[0].pageY; this.addEventListener('touchmove', swipeIt, false); window.addEventListener('touchend', function(e) { e.preventDefault(); videoElement.removeEventListener('touchmove', swipeIt, false); }, false); }, false); } function dragIt(e) { videoElement.style.left = initX+e.pageX-firstX + 'px'; videoElement.style.top = initY+e.pageY-firstY + 'px'; } function swipeIt(e) { let contact = e.touches; videoElement.style.left = initX+contact[0].pageX-firstX + 'px'; videoElement.style.top = initY+contact[0].pageY-firstY + 'px'; } return this; } if (typeof exports === "object") { module.exports = svControls; }