about summary refs log tree commit diff
path: root/modules/expo-bluesky-gif-view/src
diff options
context:
space:
mode:
Diffstat (limited to 'modules/expo-bluesky-gif-view/src')
-rw-r--r--modules/expo-bluesky-gif-view/src/GifView.tsx39
-rw-r--r--modules/expo-bluesky-gif-view/src/GifView.types.ts15
-rw-r--r--modules/expo-bluesky-gif-view/src/GifView.web.tsx82
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}
+      />
+    )
+  }
+}