add gif embed player
This commit is contained in:
@@ -16,8 +16,7 @@ export type EmbedPlayerParams =
|
||||
| {type: 'apple_music_album'; albumId: string; playerUri: string}
|
||||
| {type: 'apple_music_song'; songId: string; playerUri: string}
|
||||
| {type: 'vimeo_video'; videoId: string; playerUri: string}
|
||||
| {type: 'giphy_gif'; playerUri: string}
|
||||
| {type: 'tenor_gif'; playerUri: string}
|
||||
| {type: 'gif'; playerUri: string}
|
||||
|
||||
export function parseEmbedPlayerFromUrl(
|
||||
url: string,
|
||||
@@ -187,7 +186,7 @@ export function parseEmbedPlayerFromUrl(
|
||||
|
||||
if (id) {
|
||||
return {
|
||||
type: 'giphy_gif',
|
||||
type: 'gif',
|
||||
playerUri: `https://i.giphy.com/media/${id}/giphy.gif`,
|
||||
}
|
||||
}
|
||||
@@ -204,12 +203,12 @@ export function parseEmbedPlayerFromUrl(
|
||||
if (media === 'media') {
|
||||
if (idOrFilename === 'giphy.gif' || idOrFilename === 'giphy.gif') {
|
||||
return {
|
||||
type: 'giphy_gif',
|
||||
type: 'gif',
|
||||
playerUri: `https://i.giphy.com/media/${trackingOrId}/giphy.gif`,
|
||||
}
|
||||
} else if (filename === 'giphy.gif' || filename === 'giphy.gif') {
|
||||
return {
|
||||
type: 'giphy_gif',
|
||||
type: 'gif',
|
||||
playerUri: `https://i.giphy.com/media/${idOrFilename}/giphy.gif`,
|
||||
}
|
||||
}
|
||||
@@ -223,14 +222,14 @@ export function parseEmbedPlayerFromUrl(
|
||||
|
||||
if (mediaOrFilename === 'media' && filename) {
|
||||
return {
|
||||
type: 'giphy_gif',
|
||||
type: 'gif',
|
||||
playerUri: `https://i.giphy.com/media/${
|
||||
filename.split('.')[0]
|
||||
}/giphy.gif`,
|
||||
}
|
||||
} else if (mediaOrFilename) {
|
||||
return {
|
||||
type: 'giphy_gif',
|
||||
type: 'gif',
|
||||
playerUri: `https://i.giphy.com/media/${
|
||||
mediaOrFilename.split('.')[0]
|
||||
}/giphy.gif`,
|
||||
@@ -244,7 +243,7 @@ export function parseEmbedPlayerFromUrl(
|
||||
|
||||
if (parts.length === 4 && filename?.split('.').pop() === 'gif') {
|
||||
return {
|
||||
type: 'tenor_gif',
|
||||
type: 'gif',
|
||||
playerUri: url,
|
||||
}
|
||||
}
|
||||
@@ -257,7 +256,7 @@ export function parseEmbedPlayerFromUrl(
|
||||
const includesExt = filename.split('.').pop() === 'gif'
|
||||
|
||||
return {
|
||||
type: 'tenor_gif',
|
||||
type: 'gif',
|
||||
playerUri: `${url}${!includesExt ? '.gif' : ''}`,
|
||||
}
|
||||
}
|
||||
@@ -271,7 +270,7 @@ export function parseEmbedPlayerFromUrl(
|
||||
|
||||
if (ext === 'gif' || ext === 'webp') {
|
||||
return {
|
||||
type: 'tenor_gif',
|
||||
type: 'gif',
|
||||
playerUri: url,
|
||||
}
|
||||
}
|
||||
@@ -314,3 +313,11 @@ export function getPlayerHeight({
|
||||
return width
|
||||
}
|
||||
}
|
||||
|
||||
export function getGifHeight(
|
||||
originalHeight: number,
|
||||
originalWidth: number,
|
||||
viewWidth: number,
|
||||
) {
|
||||
return (originalHeight / originalWidth) * viewWidth
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import {EmbedPlayerParams, getGifHeight} from 'lib/strings/embed-player.ts'
|
||||
import React from 'react'
|
||||
import {Image, ImageLoadEventData} from 'expo-image'
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
LayoutChangeEvent,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {isWeb} from 'platform/detection.ts'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
|
||||
export function ExternalGifEmbed({
|
||||
thumb,
|
||||
params,
|
||||
}: {
|
||||
thumb?: string
|
||||
params: EmbedPlayerParams
|
||||
}) {
|
||||
const isThumbLoaded = React.useRef(false)
|
||||
const viewWidth = React.useRef(0)
|
||||
|
||||
const [isLoaded, setIsLoaded] = React.useState(false)
|
||||
const [isAnimating, setIsAnimating] = React.useState(true)
|
||||
const [height, setHeight] = React.useState(100)
|
||||
|
||||
const imageRef = React.useRef<Image>(null)
|
||||
|
||||
const onPlayPress = React.useCallback(
|
||||
(event: GestureResponderEvent) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (!isLoaded) {
|
||||
setIsLoaded(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (isWeb) return
|
||||
|
||||
setIsAnimating(prev => {
|
||||
if (prev) {
|
||||
imageRef.current?.stopAnimating()
|
||||
return false
|
||||
} else {
|
||||
imageRef.current?.startAnimating()
|
||||
return true
|
||||
}
|
||||
})
|
||||
},
|
||||
[isLoaded],
|
||||
)
|
||||
|
||||
const onLoad = React.useCallback((e: ImageLoadEventData) => {
|
||||
if (isThumbLoaded.current) return
|
||||
|
||||
const scaledHeight = getGifHeight(
|
||||
e.source.height,
|
||||
e.source.width,
|
||||
viewWidth.current,
|
||||
)
|
||||
setHeight(scaledHeight > 300 ? 300 : scaledHeight)
|
||||
}, [])
|
||||
|
||||
const onLayout = React.useCallback((e: LayoutChangeEvent) => {
|
||||
viewWidth.current = e.nativeEvent.layout.width
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
style={[{height}, styles.topRadius, styles.imageContainer]}
|
||||
onPress={onPlayPress}
|
||||
onLayout={onLayout}>
|
||||
{(!isLoaded || !isAnimating) && (
|
||||
<View style={[styles.layer, styles.overlayLayer]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Play GIF"
|
||||
accessibilityHint=""
|
||||
onPress={onPlayPress}
|
||||
style={[styles.overlayContainer, styles.topRadius]}>
|
||||
<FontAwesomeIcon icon="play" size={42} color="white" />
|
||||
</Pressable>
|
||||
</View>
|
||||
)}
|
||||
<Image
|
||||
source={{uri: !isLoaded ? thumb : params.playerUri}}
|
||||
style={{flex: 1}}
|
||||
ref={imageRef}
|
||||
onLoad={onLoad}
|
||||
contentFit="contain"
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
topRadius: {
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6,
|
||||
},
|
||||
layer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
overlayContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
imageContainer: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
overlayLayer: {
|
||||
zIndex: 2,
|
||||
},
|
||||
})
|
||||
@@ -8,6 +8,7 @@ import {AppBskyEmbedExternal} from '@atproto/api'
|
||||
import {toNiceDomain} from 'lib/strings/url-helpers'
|
||||
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
||||
import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed'
|
||||
import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed.tsx'
|
||||
|
||||
export const ExternalLinkEmbed = ({
|
||||
link,
|
||||
@@ -53,9 +54,12 @@ export const ExternalLinkEmbed = ({
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
{embedPlayerParams && (
|
||||
{(embedPlayerParams && embedPlayerParams.type !== 'gif' && (
|
||||
<ExternalPlayer link={link} params={embedPlayerParams} />
|
||||
)}
|
||||
)) ||
|
||||
(embedPlayerParams && embedPlayerParams.type === 'gif' && (
|
||||
<ExternalGifEmbed params={embedPlayerParams} thumb={link.thumb} />
|
||||
))}
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: isMobile ? 10 : 14,
|
||||
|
||||
@@ -202,12 +202,11 @@ export function ExternalPlayer({
|
||||
styles.topRadius,
|
||||
]}
|
||||
source={{
|
||||
uri: 'https://media1.tenor.com/m/UNTqMDwqh1gAAAAC/hello-hi.gif',
|
||||
uri: link.thumb,
|
||||
}}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
)}
|
||||
|
||||
<PlaceholderOverlay
|
||||
isLoading={isLoading}
|
||||
isPlayerActive={isPlayerActive}
|
||||
@@ -241,6 +240,10 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
imageContainer: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
overlayLayer: {
|
||||
zIndex: 2,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user