diff options
Diffstat (limited to 'modules/expo-bluesky-gif-view/src')
-rw-r--r-- | modules/expo-bluesky-gif-view/src/GifView.tsx | 39 | ||||
-rw-r--r-- | modules/expo-bluesky-gif-view/src/GifView.types.ts | 15 | ||||
-rw-r--r-- | modules/expo-bluesky-gif-view/src/GifView.web.tsx | 82 |
3 files changed, 136 insertions, 0 deletions
diff --git a/modules/expo-bluesky-gif-view/src/GifView.tsx b/modules/expo-bluesky-gif-view/src/GifView.tsx new file mode 100644 index 000000000..87258de17 --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import {requireNativeModule} from 'expo' +import {requireNativeViewManager} from 'expo-modules-core' + +import {GifViewProps} from './GifView.types' + +const NativeModule = requireNativeModule('ExpoBlueskyGifView') +const NativeView: React.ComponentType< + GifViewProps & {ref: React.RefObject<any>} +> = requireNativeViewManager('ExpoBlueskyGifView') + +export class GifView extends React.PureComponent<GifViewProps> { + // TODO native types, should all be the same as those in this class + private nativeRef: React.RefObject<any> = React.createRef() + + constructor(props: GifViewProps | Readonly<GifViewProps>) { + super(props) + } + + static async prefetchAsync(sources: string[]): Promise<void> { + return await NativeModule.prefetchAsync(sources) + } + + async playAsync(): Promise<void> { + await this.nativeRef.current.playAsync() + } + + async pauseAsync(): Promise<void> { + await this.nativeRef.current.pauseAsync() + } + + async toggleAsync(): Promise<void> { + await this.nativeRef.current.toggleAsync() + } + + render() { + return <NativeView {...this.props} ref={this.nativeRef} /> + } +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.types.ts b/modules/expo-bluesky-gif-view/src/GifView.types.ts new file mode 100644 index 000000000..29ec277f2 --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.types.ts @@ -0,0 +1,15 @@ +import {ViewProps} from 'react-native' + +export interface GifViewStateChangeEvent { + nativeEvent: { + isPlaying: boolean + isLoaded: boolean + } +} + +export interface GifViewProps extends ViewProps { + autoplay?: boolean + source?: string + placeholderSource?: string + onPlayerStateChange?: (event: GifViewStateChangeEvent) => void +} diff --git a/modules/expo-bluesky-gif-view/src/GifView.web.tsx b/modules/expo-bluesky-gif-view/src/GifView.web.tsx new file mode 100644 index 000000000..c197e01a1 --- /dev/null +++ b/modules/expo-bluesky-gif-view/src/GifView.web.tsx @@ -0,0 +1,82 @@ +import * as React from 'react' +import {StyleSheet} from 'react-native' + +import {GifViewProps} from './GifView.types' + +export class GifView extends React.PureComponent<GifViewProps> { + private readonly videoPlayerRef: React.RefObject<HTMLMediaElement> = + React.createRef() + private isLoaded = false + + constructor(props: GifViewProps | Readonly<GifViewProps>) { + super(props) + } + + componentDidUpdate(prevProps: Readonly<GifViewProps>) { + if (prevProps.autoplay !== this.props.autoplay) { + if (this.props.autoplay) { + this.playAsync() + } else { + this.pauseAsync() + } + } + } + + static async prefetchAsync(_: string[]): Promise<void> { + console.warn('prefetchAsync is not supported on web') + } + + private firePlayerStateChangeEvent = () => { + this.props.onPlayerStateChange?.({ + nativeEvent: { + isPlaying: !this.videoPlayerRef.current?.paused, + isLoaded: this.isLoaded, + }, + }) + } + + private onLoad = () => { + // Prevent multiple calls to onLoad because onCanPlay will fire after each loop + if (this.isLoaded) { + return + } + + this.isLoaded = true + this.firePlayerStateChangeEvent() + } + + async playAsync(): Promise<void> { + this.videoPlayerRef.current?.play() + } + + async pauseAsync(): Promise<void> { + this.videoPlayerRef.current?.pause() + } + + async toggleAsync(): Promise<void> { + if (this.videoPlayerRef.current?.paused) { + await this.playAsync() + } else { + await this.pauseAsync() + } + } + + render() { + return ( + <video + src={this.props.source} + autoPlay={this.props.autoplay ? 'autoplay' : undefined} + preload={this.props.autoplay ? 'auto' : undefined} + playsInline={true} + loop="loop" + muted="muted" + style={StyleSheet.flatten(this.props.style)} + onCanPlay={this.onLoad} + onPlay={this.firePlayerStateChangeEvent} + onPause={this.firePlayerStateChangeEvent} + aria-label={this.props.accessibilityLabel} + ref={this.videoPlayerRef} + /> + ) + } +} |