Skip to content

Embedded Clips#

Configuring a Storyteller Embedded Clips Component#

The Storyteller SDK provides a component to embed a clips experience in your layout.

Props#

interface Configuration {
  collection?: string;
  category?: string;
  clipId?: string;
  openedReason?: string;
  topInsets?: number; // Android only
}

interface TopLevelBackTappedEvent {
  canGoBack: boolean;
}

interface DataLoadCompletedEvent {
  success: boolean;
  error: string;
  dataCount: number;
}

interface CanGoBackChangedEvent {
  canGoBack: boolean;
}

// Component Props
configuration?: Configuration;
topLevelBackButtonEnabled?: boolean;
onTopLevelBackTapped?: (event: TopLevelBackTappedEvent) => void;
onDataLoadStarted?: () => void;
onDataLoadCompleted?: (event: DataLoadCompletedEvent) => void;
onCanGoBackChanged?: (event: CanGoBackChangedEvent) => void;
style?: ViewStyle;

To customize the layout of a component you can use the following props:

  • configuration.collection: the collection ID to be displayed

  • configuration.category: specify which category to show when opening the collection. If category is not specified or the wrong value is set, we default to the first clip

  • configuration.clipId: specify which clipId to show when opening the collection. If clipId is not specified or the wrong value is set, we default to the first clip

  • configuration.openedReason - specify the reason why collection was reloaded which is used for analytics. If nil, then openedReason will be handled internally.

  • configuration.topInsets - specify the top insets of the Clips view. This is useful when the Clips view is embedded in a view that has a top inset, such as a tab bar. This property is only available on Android.

  • topLevelBackButtonEnabled - whether the back button should be displayed when the player is at the top level of the collection. Defaults to true

  • onDataLoadStarted?: () => void - use it to show any loading indicators, like a spinning wheel on a tab bar item.

  • onDataLoadCompleted?: (event: DataLoadCompletedEvent) => void - this callback tells you that loading the data has finished. The event contains success (boolean), error (string), and dataCount (number) properties.

  • onTopLevelBackTapped?: (event: TopLevelBackTappedEvent) => void - in case you set topLevelBackButtonEnabled to true, this callback will be called when the user taps on the back button when at the top level of the StorytellerEmbeddedClipsView. The event.canGoBack property tells whether the back button action is managed by the SDK. When false, the integrating app can handle the back button action. When true, the SDK will manage the action by popping the top level category.

  • onCanGoBackChanged?: (event: CanGoBackChangedEvent) => void - this callback is used to handle the result from a StorytellerEmbeddedClipsView.getCanGoBack() call. The event.canGoBack property returns true if you are within the Storyteller SDK nested navigation and false if you are on the top level of the StorytellerEmbeddedClipsView. This property is only available on Android.

Adding it in the layout#

To put it in your custom view hierarchy you will need to add the following code:

import { useRef } from 'react';
import { View, StyleSheet } from 'react-native';
import {
  StorytellerEmbeddedClipsView,
  type StorytellerEmbeddedClipsViewInterface,
} from '@getstoryteller/react-native-storyteller-sdk';

function MyComponent() {
  const embeddedClipsRef = useRef<StorytellerEmbeddedClipsViewInterface>(null);

  return (
    <View style={styles.container}>
      <StorytellerEmbeddedClipsView
        ref={embeddedClipsRef}
        style={styles.embeddedComponent}
        configuration={{
          collection: 'collection-id',
          category: 'category-id',
          clipId: 'clip-id',
          openedReason: 'opened-reason',
        }}
        topLevelBackButtonEnabled={false}
        onDataLoadStarted={() => {
          console.log('Data load started');
        }}
        onDataLoadCompleted={(event) => {
          console.log('Data load completed:', event.success, event.error, event.dataCount);
        }}
        onTopLevelBackTapped={(event) => {
          console.log('Top level back tapped, canGoBack:', event.canGoBack);
        }}
        onCanGoBackChanged={(event) => {
          console.log('Can go back changed:', event.canGoBack);
        }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  embeddedComponent: {
    flex: 1,
  },
});
// somewhere inside a react component
const ref = useRef<StorytellerEmbeddedClipsView | null>(null);

useEffect(() => {
  if (ref.current) {
    ref.current.create();
  }
}, []);

return (
  <SafeAreaView style={styles.container}>
    <StorytellerEmbeddedClipsView
      configuration= {{
        collection: 'collection-id',
        category: 'category-id',
        clipId: 'clip-id',
        openedReason: 'opened-reason',
      }}
      topLevelBackButtonEnabled={true}
      onTopLevelBackTapped={(canGoBack) => {
        console.log(`topLevelBackTapped canGoBack: ${canGoBack}`);
      }}
      onDataLoadStarted={() => {
        console.log('onDataLoadStarted');
      }}
      onDataLoadCompleted={(success, error) => {
        console.log('onDataLoadCompleted', success, error);
      }}
      onCanGoBackChanged={(canGoBack) => {
        console.log('onCanGoBackChanged', canGoBack);
      }}
      style={styles.clips}
      ref={ref}
    />
  </SafeAreaView>
);

Auto-pause#

If you want to pause the clips when navigating between tabs or when the app goes to background, you can use the willShow() and willHide() methods:

import { useRef, useCallback } from 'react';
import { View, StyleSheet } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';
import {
  StorytellerEmbeddedClipsView,
  type StorytellerEmbeddedClipsViewInterface,
} from '@getstoryteller/react-native-storyteller-sdk';

export default function EmbeddedClipsTab() {
  const embeddedClipsRef = useRef<StorytellerEmbeddedClipsViewInterface>(null);

  // Handle tab focus/blur to control video playback
  useFocusEffect(
    useCallback(() => {
      // Tab is focused - show the embedded clips
      embeddedClipsRef.current?.willShow();

      return () => {
        // Tab is blurred - hide the embedded clips
        embeddedClipsRef.current?.willHide();
      };
    }, [])
  );

  return (
    <View style={styles.container}>
      <StorytellerEmbeddedClipsView
        ref={embeddedClipsRef}
        style={styles.embeddedComponent}
        configuration={{
          collection: 'your-collection-id',
        }}
        topLevelBackButtonEnabled={false}
        onDataLoadCompleted={(event) =>
          console.log('Embedded clips data load completed:', event)
        }
        onTopLevelBackTapped={(event) =>
          console.log('Embedded clips top level back tapped:', event)
        }
        onCanGoBackChanged={(event) =>
          console.log('Embedded clips can go back changed:', event)
        }
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  embeddedComponent: {
    flex: 1,
  },
});
reference: StorytellerEmbeddedClipsView | null = null;

constructor(props: EmbeddedClipsScreenProps) {
  super(props);
  this.state = {
    appState: AppState.currentState as AppStateStatus,
    collectionId: 'example',
  };
}

componentDidMount(): void {
  this.props.navigation.addListener('focus', this._onFocus);
  this.props.navigation.addListener('blur', this._onBlur);
  AppState.addEventListener('change', this._handleAppStateChange);
}

componentWillUnmount(): void {
  this.props.navigation.removeListener('blur', this._onBlur);
  this.props.navigation.removeListener('focus', this._onFocus);
}

_handleAppStateChange = (nextAppState: AppStateStatus) => {
  if (
    this.state.appState.match(/inactive|background/) &&
    nextAppState === 'active'
  ) {
    this.reference?.willShow();
  }
  this.setState({ appState: nextAppState });
};

_onFocus = () => {
  this.reference?.willShow();
};

_onBlur = () => {
  this.reference?.willHide();
};

render() {
  return (
    <SafeAreaView style={styles.container}>
      <StorytellerEmbeddedClipsView
        collection={this.state.collectionId}
        style={styles.clips}
        ref={(ref) => {
          if (ref) {
            this.reference = ref;
          }
        }}
      />
    </SafeAreaView>
  );
}

Reload Data#

If you want to reload the data programmatically, you can use the reloadData() method:

import { useRef } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import {
  StorytellerEmbeddedClipsView,
  type StorytellerEmbeddedClipsViewInterface,
} from '@getstoryteller/react-native-storyteller-sdk';

function MyComponent() {
  const embeddedClipsRef = useRef<StorytellerEmbeddedClipsViewInterface>(null);

  const handleReload = () => {
    embeddedClipsRef.current?.reloadData();
  };

  return (
    <View style={styles.container}>
      <Button title="Reload Data" onPress={handleReload} />
      <StorytellerEmbeddedClipsView
        ref={embeddedClipsRef}
        style={styles.embeddedComponent}
        configuration={{
          collection: 'collection-id',
        }}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  embeddedComponent: {
    flex: 1,
  },
});
const ref = useRef<StorytellerEmbeddedClipsView | null>(null);

const reloadData = () => {
  if (ref.current) {
    ref.current.reloadData();
  }
}

return (
  <SafeAreaView style={styles.container}>
    <StorytellerEmbeddedClipsView
      collection={'collection-id'}
      style={styles.clips}
      ref={ref}
    />
  </SafeAreaView>
);

Available Methods#

The StorytellerEmbeddedClipsViewInterface provides the following methods:

  • getCanGoBack() - Triggers the onCanGoBackChanged callback with the current navigation state (Android only)
  • goBack() - Navigate back in the clips navigation stack
  • willShow() - Resume video playback (call when view becomes visible)
  • willHide() - Pause video playback (call when view becomes hidden)
  • reloadData() - Reload the clips collection data