Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions shared/app/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {SafeAreaProvider, initialWindowMetrics} from 'react-native-safe-area-con
import {makeEngine} from '../engine'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {enableFreeze} from 'react-native-screens'
import {Image as ExpoImage} from 'expo-image'
import {setKeyboardUp} from '@/styles/keyboard-state'
import {setServiceDecoration} from '@/common-adapters/markdown/react'
import ServiceDecoration from '@/common-adapters/markdown/service-decoration'
Expand All @@ -27,6 +28,10 @@ logger.info('INIT App index module load')

enableFreeze(true)
setServiceDecoration(ServiceDecoration)
// SDWebImage (used by expo-image) flushes its memory cache on iOS memory warnings, but
// the simulator never sends memory warnings. Cap the cache so loading hundreds of chat
// images doesn't exhaust VM in the simulator. On a real device this is a safety net only.
ExpoImage.configureCache({maxMemoryCost: 100 * 1024 * 1024})

module.hot?.accept(() => {
console.log('accepted update in shared/index.native')
Expand Down
7 changes: 6 additions & 1 deletion shared/chat/audio/audio-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,13 @@ const AudioPlayer = (props: Props) => {
const {duration, big, maxWidth, url, visAmps} = props
const [playedRatio, setPlayedRatio] = React.useState(0)
const [paused, setPaused] = React.useState(true)
// Only mount AudioVideo after the user first taps play; calling useAudioPlayer
// unconditionally for every message in the list spawns CoreMedia threads per
// message and exhausts VM memory.
const [everPlayed, setEverPlayed] = React.useState(false)
const onClick = () => {
if (paused) {
setEverPlayed(true)
setPaused(false)
} else {
setPaused(true)
Expand Down Expand Up @@ -104,7 +109,7 @@ const AudioPlayer = (props: Props) => {
<AudioVis height={big ? 32 : 18} amps={visAmps} maxWidth={maxWidth} playedRatio={playedRatio} />
<Kb.Text type="BodyTiny">{formatAudioRecordDuration(timeLeft)}</Kb.Text>
</Kb.Box2>
{url.length > 0 && (
{url.length > 0 && everPlayed && (
<AudioVideo url={url} paused={paused} onPositionUpdated={onPositionUpdated} onEnded={onEnded} />
)}
</Kb.Box2>
Expand Down
2 changes: 2 additions & 0 deletions shared/chat/conversation/list-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ const NativeConversationList = function NativeConversationList() {

const listRef = React.useRef<RNFlatListRef | null>(null)
const {markInitiallyLoadedThreadAsRead} = Hooks.useActions()

const keyExtractor = (ordinal: ItemType) => {
return String(ordinal)
}
Expand Down Expand Up @@ -617,6 +618,7 @@ const NativeConversationList = function NativeConversationList() {
keyExtractor={keyExtractor}
ref={listRef}
renderScrollComponent={renderScrollComponent}
windowSize={3}
maintainVisibleContentPosition={
// MUST do this else if you come into a new thread it'll slowly scroll down when it loads
numOrdinals ? maintainVisibleContentPosition : undefined
Expand Down
64 changes: 43 additions & 21 deletions shared/chat/conversation/messages/attachment/video/videoimpl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,60 @@ const DesktopVideoImpl = (p: Props) => {
)
}

const NativeVideoImpl = (p: Props) => {
const {allowPlay, message, showPopup} = p
const {fileURL: url, transferState, videoDuration} = message
const {previewURL, height, width} = getAttachmentPreviewSize(message)
const {showPoster, reveal} = usePosterState(url)
const sourceUri = `${url}&contentforce=true`
// Separated into its own component so useVideoPlayer is only called when the
// user actually taps play. Calling useVideoPlayer unconditionally for every
// video message in the list caused CoreMedia to initialize a player per
// message, spawning dozens of network threads and exhausting VM memory.
type NativeActiveVideoProps = {
sourceUri: string
height: number
width: number
}

const NativeActiveVideo = (p: NativeActiveVideoProps) => {
const {sourceUri, height, width} = p
const player = useVideoPlayer(sourceUri, pl => {
pl.loop = false
pl.play()
})

const onPress = () => {
reveal()
player.play()
}

useEventListener(player, 'playToEnd', () => {
player.replay()
})
return (
<VideoView
player={player}
nativeControls={true}
contentFit="cover"
style={(Kb.Styles.collapseStyles([nativeStyles.video, {height, width}]) ?? {}) as object}
/>
)
}

const NativeVideoImpl = (p: Props) => {
const {allowPlay, message, showPopup} = p
const {fileURL: url, transferState, videoDuration} = message
const {previewURL, height, width} = getAttachmentPreviewSize(message)
const [playerActive, setPlayerActive] = React.useState(false)
const [lastUrl, setLastUrl] = React.useState(url)
if (lastUrl !== url) {
setLastUrl(url)
setPlayerActive(false)
}
const sourceUri = `${url}&contentforce=true`

return (
<>
<ShowToastAfterSaving transferState={transferState} />
{showPoster ? (
<Pressable onPress={onPress} style={nativeStyles.pressable} onLongPress={showPopup}>
{playerActive && url ? (
<NativeActiveVideo sourceUri={sourceUri} height={height} width={width} />
) : (
<Pressable
onPress={() => {
if (allowPlay) setPlayerActive(true)
}}
style={nativeStyles.pressable}
onLongPress={showPopup}
>
<Kb.Box2
direction="vertical"
style={Kb.Styles.collapseStyles([nativeStyles.posterContainer, {height, width}])}
Expand All @@ -91,13 +120,6 @@ const NativeVideoImpl = (p: Props) => {
</Kb.Box2>
</Kb.Box2>
</Pressable>
) : (
<VideoView
player={player}
nativeControls={true}
contentFit="cover"
style={(Kb.Styles.collapseStyles([nativeStyles.video, {height, width}]) ?? {}) as object}
/>
)}
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,18 @@ const DesktopVideo = (p: Props) => {
)
}

const NativeVideo = (props: Props) => {
const {autoPlay, onClick, url, style, width, height} = props
const {playing, setPlaying} = usePlayState(url, autoPlay)
// Separated into its own component so useVideoPlayer is only called when the
// player is needed. Calling it unconditionally for every unfurl in the list
// spawns CoreMedia threads per message and exhausts VM memory.
type NativeActiveVideoProps = {
sourceUri: string
autoPlay: boolean
playing: boolean
style: object
}

const uri = url.length > 0 ? url : 'https://'
const sourceUri = `${uri}&autoplay=${autoPlay ? 'true' : 'false'}&contentforce=true`
const NativeActiveVideo = (props: NativeActiveVideoProps) => {
const {sourceUri, autoPlay, playing, style} = props

const player = useVideoPlayer(sourceUri, p => {
p.loop = true
Expand All @@ -85,22 +91,50 @@ const NativeVideo = (props: Props) => {
return () => sub.remove()
}, [player])

return (
<VideoView
player={player}
nativeControls={false}
contentFit="contain"
style={(Kb.Styles.collapseStyles([nativeStyles.player, style]) ?? {}) as object}
/>
)
}

const NativeVideo = (props: Props) => {
const {autoPlay, onClick, url, style, width, height} = props
const {playing, setPlaying} = usePlayState(url, autoPlay)
// Activate the player when autoPlay is true or the user first taps play.
// Reset when URL changes so a new source gets a fresh player.
const [active, setActive] = React.useState(autoPlay)
const [lastUrl, setLastUrl] = React.useState(url)
if (lastUrl !== url) {
setLastUrl(url)
setActive(autoPlay)
}

const uri = url.length > 0 ? url : 'https://'
const sourceUri = `${uri}&autoplay=${autoPlay ? 'true' : 'false'}&contentforce=true`

const _onClick = () => {
if (onClick) {
onClick()
return
}
setActive(true)
setPlaying(p => !p)
}

return (
<Kb.ClickableBox onClick={_onClick} style={Kb.Styles.collapseStyles([style, nativeStyles.container])}>
<VideoView
player={player}
nativeControls={false}
contentFit="contain"
style={(Kb.Styles.collapseStyles([nativeStyles.player, style]) ?? {}) as object}
/>
{active && (
<NativeActiveVideo
sourceUri={sourceUri}
autoPlay={autoPlay}
playing={playing}
style={style}
/>
)}
<Kb.Box2 direction="vertical" style={Kb.Styles.collapseStyles([sharedStyles.absoluteContainer, {height, width}])}>
{!playing && <Kb.ImageIcon type="icon-play-64" style={sharedStyles.playButton} />}
</Kb.Box2>
Expand Down