Skip to content

Commit

Permalink
feat: onPlay, onPause, and onEnd side effects for the media player (#…
Browse files Browse the repository at this point in the history
…2418)

* add triggers for onPlay, onPause, and onEnd to an effect instead of trying to run as a side effect in the useCallback functions

* update the video player storybook story to use the new events

* fix linter?

* remove the onEnd logic from the effect in Container - instead pass onEnd to video components themselves to trigger it

* use analyticsMeta instead of sessionId directly

* remove console.logs

* fix typescript issues

* make onEnd required, I think. typescript is new to me, can't you tell? ;)

Co-authored-by: Vincent Wilson <vince@classyh.at>
  • Loading branch information
liamillard and vinnyjth committed Apr 8, 2022
1 parent 78241bb commit 09f9ffa
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 5 deletions.
46 changes: 46 additions & 0 deletions packages/apollos-ui-media-player/src/Container.tsx
Expand Up @@ -23,11 +23,22 @@ import useSourceComponents from './useSourceComponents';

interface ContainerProps extends IPlayerMedia, FullScreenSlidingPlayerProps {
autoplay?: Boolean;
onPlay?: Function;
onPause?: Function;
onEnd?: Function;

/** The Player Component. Defaults to FullscreenSlidingPlayer */
PlayerComponent?: React.FunctionComponent;
}

const usePrevious = (value: any) => {
const ref = React.useRef(value);
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};

const Container: React.FunctionComponent<ContainerProps> = ({
VideoComponent,
ControlsComponent,
Expand All @@ -43,6 +54,9 @@ const Container: React.FunctionComponent<ContainerProps> = ({
useNativeFullscreeniOS,
scrollViewRef,
videos,
onPlay = () => {},
onPause = () => {},
onEnd = () => {},
}) => {
/*
We're going to set up 4 context state objects in this component:
Expand Down Expand Up @@ -75,6 +89,7 @@ const Container: React.FunctionComponent<ContainerProps> = ({
// ---------
// setup PlayerControls Context
const [isPlaying, setIsPlaying] = React.useState<boolean>(!!autoplay);
const prevIsPlaying = usePrevious(isPlaying);
const [seek, setSeekHandler] = React.useState<IPlayerControls['seek']>(
() => {}
);
Expand Down Expand Up @@ -151,6 +166,35 @@ const Container: React.FunctionComponent<ContainerProps> = ({
ControlsComponent,
});

// we need a session id for some analytics events. The session ID should be
// created on mount, and different for each video player
const { current: sessionId } = React.useRef<string>(Date.now().toString());
const analyticsMeta = { sessionId };

// ---------
// setup onEnd effect on unmount
React.useEffect(
() => {
const { elapsedTime, totalDuration } = playhead;
if (autoplay) {
onPlay({ elapsedTime, totalDuration, ...analyticsMeta });
}
return () => {
return onEnd({ elapsedTime, totalDuration, ...analyticsMeta });
};
},
[] // eslint-disable-line react-hooks/exhaustive-deps
);

React.useEffect(() => {
const { elapsedTime, totalDuration } = playhead;
if (prevIsPlaying && !isPlaying) {
onPause({ elapsedTime, totalDuration, ...analyticsMeta });
} else if (!prevIsPlaying && isPlaying) {
onPlay({ elapsedTime, totalDuration, ...analyticsMeta });
}
}, [isPlaying, prevIsPlaying]); // eslint-disable-line react-hooks/exhaustive-deps

// ---------
// 🚀 Go Time
return (
Expand All @@ -168,6 +212,7 @@ const Container: React.FunctionComponent<ContainerProps> = ({
useNativeFullscreeniOS={useNativeFullscreeniOS}
scrollViewRef={scrollViewRef}
videos={videos}
onEnd={onEnd}
>
{children}
</PlayerComponent>
Expand All @@ -180,6 +225,7 @@ const Container: React.FunctionComponent<ContainerProps> = ({
useNativeFullscreeniOS,
scrollViewRef,
videos,
onEnd,
]
)}
</PlayheadContext.Provider>
Expand Down
Expand Up @@ -50,6 +50,7 @@ const FullscreenSlidingPlayer: React.FunctionComponent<FullScreenSlidingPlayerPr
allowFullscreenControl = true,
scrollViewRef: scrollViewRefProp,
videos,
onEnd,
}) => {
// Setup layout and window objects for size references inside of computed
// styles that are below.
Expand Down Expand Up @@ -254,6 +255,7 @@ const FullscreenSlidingPlayer: React.FunctionComponent<FullScreenSlidingPlayerPr
<VideoPresentationContainer
VideoComponent={VideoComponent}
useNativeFullscreeniOS={useNativeFullscreeniOS}
onEnd={onEnd}
/>
{(!isFullscreen || Platform.OS === 'android') && ControlsComponent ? (
<ControlsComponent
Expand Down
9 changes: 8 additions & 1 deletion packages/apollos-ui-media-player/src/RNVideo.tsx
Expand Up @@ -29,8 +29,10 @@ interface VideoExpanded extends Video {

const RNVideoPresentation = ({
useNativeFullscreeniOS,
onEnd,
}: {
useNativeFullscreeniOS?: boolean;
onEnd: Function;
}) => {
const nowPlaying = useNowPlaying();
const { isPlaying, pictureMode, setPictureMode, pause } = usePlayerControls();
Expand Down Expand Up @@ -155,6 +157,11 @@ const RNVideoPresentation = ({
};
}, []);

const handleVideoEnded = () => {
onEnd();
pause();
};

return (
<Container>
{nowPlaying?.source ? (
Expand All @@ -177,7 +184,7 @@ const RNVideoPresentation = ({
setShowLoading(true);
setShowCoverImage(true);
}}
onEnd={pause}
onEnd={handleVideoEnded}
onFullscreenPlayerWillDismiss={() => {
setPictureMode(PictureMode.Normal);
if (Platform.OS === 'ios') pause();
Expand Down
Expand Up @@ -7,19 +7,25 @@ import { PortalOrigin } from './portals';
const VideoPresentationContainer = ({
VideoComponent,
useNativeFullscreeniOS,
onEnd,
}: {
VideoComponent?: React.FunctionComponent<{
useNativeFullscreeniOS?: boolean;
onEnd?: Function;
}>;
useNativeFullscreeniOS?: boolean;
onEnd?: Function;
}) => {
const { playerId } = React.useContext(InternalPlayerContext);

if (!VideoComponent) return null;

return (
<PortalOrigin destination={playerId} style={StyleSheet.absoluteFill}>
<VideoComponent useNativeFullscreeniOS={useNativeFullscreeniOS} />
<VideoComponent
useNativeFullscreeniOS={useNativeFullscreeniOS}
onEnd={onEnd}
/>
</PortalOrigin>
);
};
Expand Down
7 changes: 5 additions & 2 deletions packages/apollos-ui-media-player/src/YoutubeVideo.tsx
Expand Up @@ -17,7 +17,7 @@ export const youtubeRegEx = new RegExp(
/(?:youtube(?:-nocookie)?\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
);

const YoutubeVideoPresentation = () => {
const YoutubeVideoPresentation = ({ onEnd }: { onEnd: Function }) => {
const nowPlaying = useNowPlaying();
const { isPlaying, play, pause } = usePlayerControls();
const {
Expand Down Expand Up @@ -66,13 +66,16 @@ const YoutubeVideoPresentation = () => {

const handleChangeState = React.useCallback(
(state) => {
if (state === 'ended') {
onEnd();
}
if (state === 'playing') {
play();
} else if (state !== 'buffering') {
pause();
}
},
[pause, play]
[pause, play, onEnd]
);

const skip = React.useCallback(
Expand Down
5 changes: 4 additions & 1 deletion packages/apollos-ui-media-player/storybook/stories/index.js
Expand Up @@ -161,7 +161,7 @@ const BasicPlayer = (props) => (
<ApollosPlayerContainer
source={{
uri:
'http://embed.wistia.com/deliveries/0e364f7e6f6604384ece8a35905a53a864386e9f.bin',
'https://player.vimeo.com/external/571891787.m3u8?s=dd9980634828664e654f99068ffb1ae765b8d0b1',
}}
presentationProps={{
title: 'Video Title',
Expand All @@ -170,6 +170,9 @@ const BasicPlayer = (props) => (
coverImage={{
uri: `https://picsum.photos/seed/${Math.random()}/100/100`,
}}
onPlay={(e) => console.log('playing!', e)}
onPause={(e) => console.log('pausing!', e)}
onEnd={(e) => console.log('ending!', e)}
{...props}
>
<PlayerExamples />
Expand Down

0 comments on commit 09f9ffa

Please sign in to comment.