Last active
June 9, 2024 03:25
-
-
Save MeyCry/9f32fde2cec594385805ac570136b76d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/9260501/html5-video-background-on-ipad-iphone | |
var cvpHandlers = { | |
canvasClickHandler: null, | |
videoTimeUpdateHandler: null, | |
videoCanPlayHandler: null, | |
windowResizeHandler: null | |
}; | |
var CanvasVideoPlayer = function(options) { | |
var i; | |
this.options = { | |
framesPerSecond: 25, | |
hideVideo: true, | |
autoplay: false, | |
makeLoop: false, | |
pauseOnClick: true, | |
audio: false, | |
timelineSelector: false, | |
resetOnLastFrame: true | |
}; | |
for (i in options) { | |
this.options[i] = options[i]; | |
} | |
this.video = document.querySelector(this.options.videoSelector); | |
this.canvas = document.querySelector(this.options.canvasSelector); | |
this.timeline = document.querySelector(this.options.timelineSelector); | |
this.timelinePassed = document.querySelector(this.options.timelineSelector + '> div'); | |
if (!this.options.videoSelector || !this.video) { | |
console.error('No "videoSelector" property, or the element is not found'); | |
return; | |
} | |
if (!this.options.canvasSelector || !this.canvas) { | |
console.error('No "canvasSelector" property, or the element is not found'); | |
return; | |
} | |
if (this.options.timelineSelector && !this.timeline) { | |
console.error('Element for the "timelineSelector" selector not found'); | |
return; | |
} | |
if (this.options.timelineSelector && !this.timelinePassed) { | |
console.error('Element for the "timelinePassed" not found'); | |
return; | |
} | |
if (this.options.audio) { | |
if (typeof(this.options.audio) === 'string'){ | |
// Use audio selector from options if specified | |
this.audio = document.querySelectorAll(this.options.audio)[0]; | |
if (!this.audio) { | |
console.error('Element for the "audio" not found'); | |
return; | |
} | |
} else { | |
// Creates audio element which uses same video sources | |
this.audio = document.createElement('audio'); | |
this.audio.innerHTML = this.video.innerHTML; | |
this.video.parentNode.insertBefore(this.audio, this.video); | |
this.audio.load(); | |
} | |
var iOS = /iPad|iPhone|iPod/.test(navigator.platform); | |
if (iOS) { | |
// Autoplay doesn't work with audio on iOS | |
// User have to manually start the audio | |
this.options.autoplay = false; | |
} | |
} | |
// Canvas context | |
this.ctx = this.canvas.getContext('2d'); | |
this.playing = false; | |
this.resizeTimeoutReference = false; | |
this.RESIZE_TIMEOUT = 1000; | |
this.init(); | |
this.bind(); | |
}; | |
CanvasVideoPlayer.prototype.init = function() { | |
this.video.load(); | |
this.setCanvasSize(); | |
if (this.options.hideVideo) { | |
this.video.style.display = 'none'; | |
} | |
}; | |
// Used most of the jQuery code for the .offset() method | |
CanvasVideoPlayer.prototype.getOffset = function(elem) { | |
var docElem, rect, doc; | |
if (!elem) { | |
return; | |
} | |
rect = elem.getBoundingClientRect(); | |
// Make sure element is not hidden (display: none) or disconnected | |
if (rect.width || rect.height || elem.getClientRects().length) { | |
doc = elem.ownerDocument; | |
docElem = doc.documentElement; | |
return { | |
top: rect.top + window.pageYOffset - docElem.clientTop, | |
left: rect.left + window.pageXOffset - docElem.clientLeft | |
}; | |
} | |
}; | |
CanvasVideoPlayer.prototype.jumpTo = function(percentage) { | |
this.video.currentTime = this.video.duration * percentage; | |
if (this.options.audio) { | |
this.audio.currentTime = this.audio.duration * percentage; | |
} | |
}; | |
CanvasVideoPlayer.prototype.bind = function() { | |
var self = this; | |
// Playes or pauses video on canvas click | |
if(this.options.pauseOnClick === true){ | |
this.canvas.addEventListener('click', cvpHandlers.canvasClickHandler = function() { | |
self.playPause(); | |
}); | |
} | |
// On every time update draws frame | |
this.video.addEventListener('timeupdate', cvpHandlers.videoTimeUpdateHandler = function() { | |
self.drawFrame(); | |
if (self.options.timelineSelector) { | |
self.updateTimeline(); | |
} | |
}); | |
// Draws first frame | |
this.video.addEventListener('canplay', cvpHandlers.videoCanPlayHandler = function() { | |
self.drawFrame(); | |
}); | |
// To be sure 'canplay' event that isn't already fired | |
if (this.video.readyState >= 2) { | |
self.drawFrame(); | |
} | |
if (self.options.autoplay) { | |
self.play(); | |
} | |
// Click on the video seek video | |
if (self.options.timelineSelector) { | |
this.timeline.addEventListener('click', function(e) { | |
var offset = e.clientX - self.getOffset(self.canvas).left; | |
var percentage = offset / self.timeline.offsetWidth; | |
self.jumpTo(percentage); | |
}); | |
} | |
// Cache canvas size on resize (doing it only once in a second) | |
window.addEventListener('resize', cvpHandlers.windowResizeHandler = function() { | |
clearTimeout(self.resizeTimeoutReference); | |
self.resizeTimeoutReference = setTimeout(function() { | |
self.setCanvasSize(); | |
self.drawFrame(); | |
}, self.RESIZE_TIMEOUT); | |
}); | |
this.unbind = function() { | |
this.canvas.removeEventListener('click', cvpHandlers.canvasClickHandler); | |
this.video.removeEventListener('timeupdate', cvpHandlers.videoTimeUpdateHandler); | |
this.video.removeEventListener('canplay', cvpHandlers.videoCanPlayHandler); | |
window.removeEventListener('resize', cvpHandlers.windowResizeHandler); | |
if (this.options.audio) { | |
this.audio.parentNode.removeChild(this.audio); | |
} | |
}; | |
}; | |
CanvasVideoPlayer.prototype.updateTimeline = function() { | |
var percentage = (this.video.currentTime * 100 / this.video.duration).toFixed(2); | |
this.timelinePassed.style.width = percentage + '%'; | |
}; | |
CanvasVideoPlayer.prototype.setCanvasSize = function() { | |
this.width = this.canvas.clientWidth; | |
this.height = this.canvas.clientHeight; | |
this.canvas.setAttribute('width', this.width); | |
this.canvas.setAttribute('height', this.height); | |
}; | |
CanvasVideoPlayer.prototype.play = function() { | |
this.lastTime = Date.now(); | |
this.playing = true; | |
this.loop(); | |
if (this.options.audio) { | |
// Resync audio and video | |
this.audio.currentTime = this.video.currentTime; | |
this.audio.play(); | |
} | |
}; | |
CanvasVideoPlayer.prototype.pause = function() { | |
this.playing = false; | |
if (this.options.audio) { | |
this.audio.pause(); | |
} | |
}; | |
CanvasVideoPlayer.prototype.playPause = function() { | |
if (this.playing) { | |
this.pause(); | |
} | |
else { | |
this.play(); | |
} | |
}; | |
CanvasVideoPlayer.prototype.loop = function() { | |
var self = this; | |
var time = Date.now(); | |
var elapsed = (time - this.lastTime) / 1000; | |
// Render | |
if(elapsed >= (1 / this.options.framesPerSecond)) { | |
this.video.currentTime = this.video.currentTime + elapsed; | |
this.lastTime = time; | |
// Resync audio and video if they drift more than 300ms apart | |
if(this.audio && Math.abs(this.audio.currentTime - this.video.currentTime) > .3){ | |
this.audio.currentTime = this.video.currentTime; | |
} | |
} | |
// If we are at the end of the video stop | |
if (this.video.currentTime >= this.video.duration) { | |
if(this.options.makeLoop === false){ | |
this.playing = false; | |
} | |
if (this.options.resetOnLastFrame === true) { | |
this.video.currentTime = 0; | |
} | |
} | |
if (this.playing) { | |
this.animationFrame = requestAnimationFrame(function(){ | |
self.loop(); | |
}); | |
} | |
else { | |
cancelAnimationFrame(this.animationFrame); | |
} | |
}; | |
CanvasVideoPlayer.prototype.drawFrame = function() { | |
this.ctx.clearRect(0, 0, this.width, this.height); // optimization from me )) | |
this.ctx.drawImage(this.video, 0, 0, this.width, this.height); | |
}; | |
/* | |
var isIOS = /iPad|iPhone|iPod/.test(navigator.platform); | |
if (isIOS) { | |
var canvasVideo = new CanvasVideoPlayer({ | |
videoSelector: '.video', | |
canvasSelector: '.canvas', | |
timelineSelector: false, | |
autoplay: true, | |
makeLoop: true, | |
pauseOnClick: false, | |
audio: false | |
}); | |
} else { | |
// Use HTML5 video | |
document.querySelectorAll('.canvas')[0].style.display = 'none'; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment