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_album'; albumId: string; playerUri: string}
|
||||||
| {type: 'apple_music_song'; songId: string; playerUri: string}
|
| {type: 'apple_music_song'; songId: string; playerUri: string}
|
||||||
| {type: 'vimeo_video'; videoId: string; playerUri: string}
|
| {type: 'vimeo_video'; videoId: string; playerUri: string}
|
||||||
| {type: 'giphy_gif'; playerUri: string}
|
| {type: 'gif'; playerUri: string}
|
||||||
| {type: 'tenor_gif'; playerUri: string}
|
|
||||||
|
|
||||||
export function parseEmbedPlayerFromUrl(
|
export function parseEmbedPlayerFromUrl(
|
||||||
url: string,
|
url: string,
|
||||||
@@ -187,7 +186,7 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
return {
|
return {
|
||||||
type: 'giphy_gif',
|
type: 'gif',
|
||||||
playerUri: `https://i.giphy.com/media/${id}/giphy.gif`,
|
playerUri: `https://i.giphy.com/media/${id}/giphy.gif`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,12 +203,12 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
if (media === 'media') {
|
if (media === 'media') {
|
||||||
if (idOrFilename === 'giphy.gif' || idOrFilename === 'giphy.gif') {
|
if (idOrFilename === 'giphy.gif' || idOrFilename === 'giphy.gif') {
|
||||||
return {
|
return {
|
||||||
type: 'giphy_gif',
|
type: 'gif',
|
||||||
playerUri: `https://i.giphy.com/media/${trackingOrId}/giphy.gif`,
|
playerUri: `https://i.giphy.com/media/${trackingOrId}/giphy.gif`,
|
||||||
}
|
}
|
||||||
} else if (filename === 'giphy.gif' || filename === 'giphy.gif') {
|
} else if (filename === 'giphy.gif' || filename === 'giphy.gif') {
|
||||||
return {
|
return {
|
||||||
type: 'giphy_gif',
|
type: 'gif',
|
||||||
playerUri: `https://i.giphy.com/media/${idOrFilename}/giphy.gif`,
|
playerUri: `https://i.giphy.com/media/${idOrFilename}/giphy.gif`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,14 +222,14 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
|
|
||||||
if (mediaOrFilename === 'media' && filename) {
|
if (mediaOrFilename === 'media' && filename) {
|
||||||
return {
|
return {
|
||||||
type: 'giphy_gif',
|
type: 'gif',
|
||||||
playerUri: `https://i.giphy.com/media/${
|
playerUri: `https://i.giphy.com/media/${
|
||||||
filename.split('.')[0]
|
filename.split('.')[0]
|
||||||
}/giphy.gif`,
|
}/giphy.gif`,
|
||||||
}
|
}
|
||||||
} else if (mediaOrFilename) {
|
} else if (mediaOrFilename) {
|
||||||
return {
|
return {
|
||||||
type: 'giphy_gif',
|
type: 'gif',
|
||||||
playerUri: `https://i.giphy.com/media/${
|
playerUri: `https://i.giphy.com/media/${
|
||||||
mediaOrFilename.split('.')[0]
|
mediaOrFilename.split('.')[0]
|
||||||
}/giphy.gif`,
|
}/giphy.gif`,
|
||||||
@@ -244,7 +243,7 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
|
|
||||||
if (parts.length === 4 && filename?.split('.').pop() === 'gif') {
|
if (parts.length === 4 && filename?.split('.').pop() === 'gif') {
|
||||||
return {
|
return {
|
||||||
type: 'tenor_gif',
|
type: 'gif',
|
||||||
playerUri: url,
|
playerUri: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +256,7 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
const includesExt = filename.split('.').pop() === 'gif'
|
const includesExt = filename.split('.').pop() === 'gif'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'tenor_gif',
|
type: 'gif',
|
||||||
playerUri: `${url}${!includesExt ? '.gif' : ''}`,
|
playerUri: `${url}${!includesExt ? '.gif' : ''}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +270,7 @@ export function parseEmbedPlayerFromUrl(
|
|||||||
|
|
||||||
if (ext === 'gif' || ext === 'webp') {
|
if (ext === 'gif' || ext === 'webp') {
|
||||||
return {
|
return {
|
||||||
type: 'tenor_gif',
|
type: 'gif',
|
||||||
playerUri: url,
|
playerUri: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,3 +313,11 @@ export function getPlayerHeight({
|
|||||||
return width
|
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 {toNiceDomain} from 'lib/strings/url-helpers'
|
||||||
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
|
||||||
import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed'
|
import {ExternalPlayer} from 'view/com/util/post-embeds/ExternalPlayerEmbed'
|
||||||
|
import {ExternalGifEmbed} from 'view/com/util/post-embeds/ExternalGifEmbed.tsx'
|
||||||
|
|
||||||
export const ExternalLinkEmbed = ({
|
export const ExternalLinkEmbed = ({
|
||||||
link,
|
link,
|
||||||
@@ -53,9 +54,12 @@ export const ExternalLinkEmbed = ({
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
{embedPlayerParams && (
|
{(embedPlayerParams && embedPlayerParams.type !== 'gif' && (
|
||||||
<ExternalPlayer link={link} params={embedPlayerParams} />
|
<ExternalPlayer link={link} params={embedPlayerParams} />
|
||||||
)}
|
)) ||
|
||||||
|
(embedPlayerParams && embedPlayerParams.type === 'gif' && (
|
||||||
|
<ExternalGifEmbed params={embedPlayerParams} thumb={link.thumb} />
|
||||||
|
))}
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
paddingHorizontal: isMobile ? 10 : 14,
|
paddingHorizontal: isMobile ? 10 : 14,
|
||||||
|
|||||||
@@ -202,12 +202,11 @@ export function ExternalPlayer({
|
|||||||
styles.topRadius,
|
styles.topRadius,
|
||||||
]}
|
]}
|
||||||
source={{
|
source={{
|
||||||
uri: 'https://media1.tenor.com/m/UNTqMDwqh1gAAAAC/hello-hi.gif',
|
uri: link.thumb,
|
||||||
}}
|
}}
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<PlaceholderOverlay
|
<PlaceholderOverlay
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isPlayerActive={isPlayerActive}
|
isPlayerActive={isPlayerActive}
|
||||||
@@ -241,6 +240,10 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
},
|
},
|
||||||
|
imageContainer: {
|
||||||
|
width: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
overlayLayer: {
|
overlayLayer: {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user