Last active
January 14, 2025 06:15
-
-
Save mlshv/a3d922cc42509d3670f0f3a27f2b1afe to your computer and use it in GitHub Desktop.
Framer Video Component. Fixes iOS issues
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
<script> | |
function playVideos() { | |
var videoElements = document.getElementsByTagName("video"); | |
for (const video of videoElements) { | |
const isVideoPlaying = (video) => | |
!!( | |
video.currentTime > 0 && | |
!video.paused && | |
!video.ended && | |
video.readyState > 2 | |
); | |
if (!isVideoPlaying(video)) { | |
video.play(); | |
} | |
} | |
} | |
// bypass ios safari not playing autoplay videos in power saving mode | |
document.body.addEventListener("touchstart", playVideos); | |
document.body.addEventListener("click", playVideos); | |
</script> |
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
// Welcome to Code in Framer | |
// Get Started: https://www.framer.com/developers | |
import { useEffect, useState } from "react" | |
import { addPropertyControls, ControlType } from "framer" | |
import { motion } from "framer-motion" | |
const isSafari = () => { | |
const ua = navigator.userAgent.toLowerCase() | |
return ua.includes("safari") && !ua.includes("chrome") | |
} | |
/** | |
* These annotations control how your component sizes | |
* Learn more: https://www.framer.com/developers/#code-components-auto-sizing | |
* | |
* @framerSupportedLayoutWidth fixed | |
* @framerSupportedLayoutHeight fixed | |
*/ | |
export default function Video({ | |
source, | |
url, | |
upload, | |
posterShouldUseUrl, | |
posterUrl, | |
posterUpload, | |
width, | |
height, | |
minHeight, | |
minWidth, | |
controls, | |
background, | |
value, | |
...props | |
}) { | |
const poster = posterShouldUseUrl ? posterUrl : posterUpload | |
const src = source ? url : upload | |
const [isLoaded, setIsLoaded] = useState(false) | |
const [loadedVideoSrc, setLoadedVideoSrc] = useState(null) | |
useEffect(() => { | |
const preloadVideo = async (url) => { | |
try { | |
const response = await fetch(url, { priority: "high" }) | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`) | |
} | |
const videoBlob = await response.blob() | |
const videoObjectURL = URL.createObjectURL(videoBlob) | |
setIsLoaded(true) | |
setLoadedVideoSrc(videoObjectURL) | |
} catch (error) { | |
console.error("Error preloading video:", error) | |
} | |
} | |
preloadVideo(src) | |
}, [src]) | |
return ( | |
<> | |
{!isLoaded && ( | |
<img | |
src={poster} | |
alt="" | |
style={{ | |
width, | |
height, | |
background, | |
maxHeight: "100%", | |
maxWidth: "100%", | |
minHeight, | |
minWidth, | |
}} | |
fetchpriority="high" | |
/> | |
)} | |
{isLoaded && ( | |
<video | |
{...props} | |
autoPlay | |
playsInline | |
src={loadedVideoSrc} | |
style={{ | |
...props.style, | |
position: "absolute", | |
top: 0, | |
left: 0, | |
maxWidth: "100%", | |
maxHeight: "100%", | |
background, | |
minHeight, | |
minWidth, | |
}} | |
poster={poster} | |
controls={controls} | |
/> | |
)} | |
</> | |
) | |
} | |
Video.defaultProps = { | |
url: "https://joinhuman-com-statics.s3.amazonaws.com/joinhuman-com-videos/optimized/id-white.mp4", | |
upload: "https://joinhuman-com-statics.s3.amazonaws.com/joinhuman-com-videos/optimized/network.mp4", | |
posterUrl: | |
"https://joinhuman-com-statics.s3.amazonaws.com/joinhuman-com-videos/posters/human-network.jpg", | |
minHeight: 100, | |
minWidth: 100, | |
} | |
addPropertyControls(Video, { | |
source: { | |
type: ControlType.Boolean, | |
title: "Source", | |
enabledTitle: "URL", | |
disabledTitle: "Upload", | |
}, | |
url: { | |
type: ControlType.String, | |
title: "URL", | |
hidden(props) { | |
return props.source === false | |
}, | |
description: | |
"Hosted video file URL. For Youtube, use the Youtube component.", | |
}, | |
upload: { | |
type: ControlType.File, | |
title: "Upload", | |
hidden(props) { | |
return props.source === true | |
}, | |
allowedFileTypes: ["mp4", "mov", "webm"], | |
}, | |
posterShouldUseUrl: { | |
type: ControlType.Boolean, | |
title: "Poster", | |
enabledTitle: "URL", | |
disabledTitle: "Upload", | |
}, | |
posterUrl: { | |
type: ControlType.String, | |
title: "URL", | |
hidden(props) { | |
return props.posterShouldUseUrl === false | |
}, | |
}, | |
posterUpload: { | |
type: ControlType.File, | |
title: "Upload", | |
hidden(props) { | |
return props.posterShouldUseUrl === true | |
}, | |
allowedFileTypes: ["jpg", "jpeg", "png"], | |
}, | |
background: { | |
title: "Background", | |
type: ControlType.Color, | |
defaultValue: "#00000000", | |
}, | |
loop: { | |
title: "Loop", | |
type: ControlType.Boolean, | |
enabledTitle: "Yes", | |
disabledTitle: "No", | |
}, | |
controls: { | |
title: "Controls", | |
type: ControlType.Boolean, | |
enabledTitle: "Show", | |
disabledTitle: "Hide", | |
defaultValue: false, | |
}, | |
muted: { | |
title: "Muted", | |
type: ControlType.Boolean, | |
enabledTitle: "Yes", | |
disabledTitle: "No", | |
defaultValue: true, | |
}, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment