// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { DefaultModality, VideoTileState } from 'amazon-chime-sdk-js'; import React, { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef, } from 'react'; import { ContentShareControlContextType } from '../../types'; import { useAudioVideo } from '../AudioVideoProvider'; import { ContentActionType, ContentShareState, initialState, reducer, } from './state'; const ContentShareContext = createContext(null); const ContentShareControlContext = createContext(null); export const ContentShareProvider: React.FC< React.PropsWithChildren > = ({ children }) => { const audioVideo = useAudioVideo(); const [state, dispatch] = useReducer(reducer, initialState); const { paused, isLocalUserSharing, isLocalShareLoading } = state; const localUserTileIdRef = useRef(null); useEffect(() => { if (!audioVideo) { return; } const videoObserver = { videoTileDidUpdate: (tileState: VideoTileState) => { if ( !tileState.boundAttendeeId || !tileState.isContent || !tileState.tileId ) { return; } const { boundAttendeeId } = tileState; const baseAttendeeId = new DefaultModality(boundAttendeeId).base(); const localAttendeeId = (audioVideo as any).audioVideoController .realtimeController.state.localAttendeeId; const isLocalUser = baseAttendeeId === localAttendeeId; if ( !isLocalUser && localUserTileIdRef.current && localUserTileIdRef.current < tileState.tileId ) { audioVideo.stopContentShare(); localUserTileIdRef.current = null; } if (isLocalUser) { localUserTileIdRef.current = tileState.tileId; } dispatch({ type: ContentActionType.UPDATE, payload: { tileState, isLocalUser, }, }); }, videoTileWasRemoved: (tileId: number) => { if (tileId === localUserTileIdRef.current) { localUserTileIdRef.current = null; } dispatch({ type: ContentActionType.REMOVE, payload: tileId, }); }, }; const contentShareObserver = { contentShareDidStop: () => { dispatch({ type: ContentActionType.DID_STOP }); }, }; audioVideo.addObserver(videoObserver); audioVideo.addContentShareObserver(contentShareObserver); return () => { audioVideo.removeObserver(videoObserver); audioVideo.removeContentShareObserver(contentShareObserver); dispatch({ type: ContentActionType.RESET }); }; }, [audioVideo]); useEffect(() => { if (!audioVideo) { return; } const cb = (event: PromiseRejectionEvent) => { if (event.reason.name === 'NotAllowedError') { dispatch({ type: ContentActionType.DENIED }); } }; window.addEventListener('unhandledrejection', cb); return () => window.removeEventListener('unhandledrejection', cb); }, [isLocalShareLoading]); const toggleContentShare = useCallback( async (source?: string | MediaStream): Promise => { if (!audioVideo) { return; } if (isLocalUserSharing || isLocalShareLoading) { audioVideo.stopContentShare(); } else { if (source && typeof source === 'string') { audioVideo.startContentShareFromScreenCapture(source); } else if (source instanceof MediaStream) { audioVideo.startContentShare(source); } else { audioVideo.startContentShareFromScreenCapture(); } dispatch({ type: ContentActionType.STARTING }); } }, [audioVideo, isLocalUserSharing, isLocalShareLoading] ); const togglePauseContentShare = useCallback((): void => { if (!audioVideo || !isLocalUserSharing) { return; } if (paused) { audioVideo.unpauseContentShare(); } else { audioVideo.pauseContentShare(); } dispatch({ type: ContentActionType.TOGGLE_PAUSE }); }, [audioVideo, paused, isLocalUserSharing]); const controlsValue: ContentShareControlContextType = useMemo( () => ({ paused, isLocalUserSharing, isLocalShareLoading, toggleContentShare, togglePauseContentShare, }), [ paused, toggleContentShare, togglePauseContentShare, isLocalUserSharing, isLocalShareLoading, ] ); return ( {children} ); }; export const useContentShareState = (): ContentShareState => { const contentShareState = useContext(ContentShareContext); if (!contentShareState) { throw new Error( 'useContentShareState must be used within a ContentShareProvider' ); } return contentShareState; }; export const useContentShareControls = (): ContentShareControlContextType => { const context = useContext(ContentShareControlContext); if (!context) { throw new Error( 'useContentShareControlContext must be used within ContentShareControlProvider' ); } return context; };