151 feat youtube embed iframe (#176)

* youtube embed iframe temp commit

* Fixes styling and code cleanup

* lint

* Now clicking between the pause and settings button doesn't trigger the parent

* use modest branding (less yt logos)

* Stop playing the video once there's a navigation event

* Make sure the iframe is unmounted on any navigation event

* fixes tests

* lint
This commit is contained in:
Aryan Goharzad
2023-02-09 13:11:02 -05:00
committed by GitHub
parent 00d41c9168
commit b84c88cd21
15 changed files with 328 additions and 61 deletions
+7 -3
View File
@@ -22,17 +22,21 @@ export const Link = observer(function Link({
noFeedback,
}: {
style?: StyleProp<ViewStyle>
href: string
href?: string
title?: string
children?: React.ReactNode
noFeedback?: boolean
}) {
const store = useStores()
const onPress = () => {
handleLink(store, href, false)
if (href) {
handleLink(store, href, false)
}
}
const onLongPress = () => {
handleLink(store, href, true)
if (href) {
handleLink(store, href, true)
}
}
if (noFeedback) {
return (
@@ -0,0 +1,66 @@
import React from 'react'
import {Text} from '../text/Text'
import {Image} from '../images/Image'
import {StyleSheet, View} from 'react-native'
import {usePalette} from '../../../lib/hooks/usePalette'
import {PresentedExternal} from '@atproto/api/dist/client/types/app/bsky/embed/external'
const ExternalLinkEmbed = ({
link,
onImagePress,
}: {
link: PresentedExternal
onImagePress: () => void
}) => {
const pal = usePalette('default')
return (
<>
{link.thumb ? (
<Image
uri={link.thumb}
style={styles.extImage}
onPress={onImagePress}
/>
) : undefined}
<View style={styles.extInner}>
<Text type="md-bold" numberOfLines={2} style={[pal.text]}>
{link.title || link.uri}
</Text>
<Text
type="sm"
numberOfLines={1}
style={[pal.textLight, styles.extUri]}>
{link.uri}
</Text>
{link.description ? (
<Text
type="sm"
numberOfLines={2}
style={[pal.text, styles.extDescription]}>
{link.description}
</Text>
) : undefined}
</View>
</>
)
}
const styles = StyleSheet.create({
extInner: {
padding: 10,
},
extImage: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
maxHeight: 200,
},
extUri: {
marginTop: 2,
},
extDescription: {
marginTop: 4,
},
})
export default ExternalLinkEmbed
@@ -0,0 +1,112 @@
import React, {useEffect} from 'react'
import {useState} from 'react'
import {
View,
StyleSheet,
Pressable,
TouchableWithoutFeedback,
EmitterSubscription,
} from 'react-native'
import YoutubePlayer from 'react-native-youtube-iframe'
import {usePalette} from '../../../lib/hooks/usePalette'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import ExternalLinkEmbed from './ExternalLinkEmbed'
import {PresentedExternal} from '@atproto/api/dist/client/types/app/bsky/embed/external'
import {useStores} from '../../../../state'
const YoutubeEmbed = ({
link,
videoId,
}: {
videoId: string
link: PresentedExternal
}) => {
const store = useStores()
const [displayVideoPlayer, setDisplayVideoPlayer] = useState(false)
const [playerDimensions, setPlayerDimensions] = useState({
width: 0,
height: 0,
})
const pal = usePalette('default')
const handlePlayButtonPressed = () => {
setDisplayVideoPlayer(true)
}
const handleOnLayout = (event: {
nativeEvent: {layout: {width: any; height: any}}
}) => {
setPlayerDimensions({
width: event.nativeEvent.layout.width,
height: event.nativeEvent.layout.height,
})
}
useEffect(() => {
let sub: EmitterSubscription
if (displayVideoPlayer) {
sub = store.onNavigation(() => {
setDisplayVideoPlayer(false)
})
}
return () => sub && sub.remove()
}, [displayVideoPlayer, store])
if (!displayVideoPlayer) {
return (
<View
style={[styles.extOuter, pal.view, pal.border]}
onLayout={handleOnLayout}>
<ExternalLinkEmbed link={link} onImagePress={handlePlayButtonPressed} />
<Pressable onPress={handlePlayButtonPressed} style={styles.playButton}>
<FontAwesomeIcon icon="play" size={24} color="white" />
</Pressable>
</View>
)
}
const height = (playerDimensions.width / 16) * 9
const noop = () => {}
return (
<TouchableWithoutFeedback onPress={noop}>
<View>
{/* Removing the outter View will make tap events propagate to parents */}
<YoutubePlayer
initialPlayerParams={{
modestbranding: true,
}}
webViewProps={{
startInLoadingState: true,
}}
height={height}
videoId={videoId}
webViewStyle={styles.webView}
/>
</View>
</TouchableWithoutFeedback>
)
}
const styles = StyleSheet.create({
extOuter: {
borderWidth: 1,
borderRadius: 8,
marginTop: 4,
},
playButton: {
position: 'absolute',
top: '20%',
alignSelf: 'center',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'black',
padding: 10,
borderRadius: 50,
opacity: 0.8,
},
webView: {
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
},
})
export default YoutubeEmbed
@@ -7,14 +7,16 @@ import {
Image as RNImage,
} from 'react-native'
import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
import {Link} from '../util/Link'
import {Text} from './text/Text'
import {Image} from './images/Image'
import {ImageLayoutGrid} from './images/ImageLayoutGrid'
import {ImagesLightbox} from '../../../state/models/shell-ui'
import {useStores} from '../../../state'
import {usePalette} from '../../lib/hooks/usePalette'
import {saveImageModal} from '../../../lib/images'
import {Link} from '../Link'
import {Image} from '../images/Image'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ImagesLightbox} from '../../../../state/models/shell-ui'
import {useStores} from '../../../../state'
import {usePalette} from '../../../lib/hooks/usePalette'
import {saveImageModal} from '../../../../lib/images'
import YoutubeEmbed from './YoutubeEmbed'
import ExternalLinkEmbed from './ExternalLinkEmbed'
import {getYoutubeVideoId} from '../../../../lib/strings'
type Embed =
| AppBskyEmbedImages.Presented
@@ -103,33 +105,18 @@ export function PostEmbeds({
}
if (AppBskyEmbedExternal.isPresented(embed)) {
const link = embed.external
const youtubeVideoId = getYoutubeVideoId(link.uri)
if (youtubeVideoId) {
return <YoutubeEmbed videoId={youtubeVideoId} link={link} />
}
return (
<Link
style={[styles.extOuter, pal.view, pal.border, style]}
href={link.uri}
noFeedback>
{link.thumb ? (
<Image uri={link.thumb} style={styles.extImage} />
) : undefined}
<View style={styles.extInner}>
<Text type="md-bold" numberOfLines={2} style={[pal.text]}>
{link.title || link.uri}
</Text>
<Text
type="sm"
numberOfLines={1}
style={[pal.textLight, styles.extUri]}>
{link.uri}
</Text>
{link.description ? (
<Text
type="sm"
numberOfLines={2}
style={[pal.text, styles.extDescription]}>
{link.description}
</Text>
) : undefined}
</View>
<ExternalLinkEmbed link={link} />
</Link>
)
}
@@ -149,19 +136,4 @@ const styles = StyleSheet.create({
borderRadius: 8,
marginTop: 4,
},
extInner: {
padding: 10,
},
extImage: {
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
width: '100%',
maxHeight: 200,
},
extUri: {
marginTop: 2,
},
extDescription: {
marginTop: 4,
},
})
+4
View File
@@ -65,6 +65,8 @@ import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket'
import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan'
import {faX} from '@fortawesome/free-solid-svg-icons/faX'
import {faXmark} from '@fortawesome/free-solid-svg-icons/faXmark'
import {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay'
import {faPause} from '@fortawesome/free-solid-svg-icons/faPause'
export function setup() {
library.add(
@@ -133,5 +135,7 @@ export function setup() {
faTrashCan,
faX,
faXmark,
faPlay,
faPause,
)
}