diff --git a/.github/workflows/deploy-react-sample-apps.yml b/.github/workflows/deploy-react-sample-apps.yml index 3d77d186c5..bd83b08348 100644 --- a/.github/workflows/deploy-react-sample-apps.yml +++ b/.github/workflows/deploy-react-sample-apps.yml @@ -48,7 +48,7 @@ jobs: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ matrix.application.project-id }} VITE_STREAM_API_KEY: ${{ vars.EGRESS_STREAM_API_KEY }} - VITE_STREAM_TOKEN: ${{ secrets.EGRESS_USER_TOKEN }} + VITE_STREAM_USER_TOKEN: ${{ secrets.EGRESS_USER_TOKEN }} VITE_STREAM_KEY: ${{ vars.STREAM_API_KEY_SAMPLE_APPS }} VITE_STREAM_SECRET: ${{ secrets.STREAM_SECRET_SAMPLE_APPS }} VITE_VIDEO_DEMO_SENTRY_DNS: ${{secrets.VIDEO_DEMO_SENTRY_DNS}} diff --git a/sample-apps/react/egress-composite/.env-example b/sample-apps/react/egress-composite/.env-example index 69eb913b7c..02ce8040e0 100644 --- a/sample-apps/react/egress-composite/.env-example +++ b/sample-apps/react/egress-composite/.env-example @@ -1,2 +1,2 @@ VITE_STREAM_API_KEY="" -VITE_STREAM_TOKEN="" +VITE_STREAM_USER_TOKEN="" diff --git a/sample-apps/react/egress-composite/package.json b/sample-apps/react/egress-composite/package.json index a3e808efa4..de86946e0a 100644 --- a/sample-apps/react/egress-composite/package.json +++ b/sample-apps/react/egress-composite/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "start": "vite", + "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, diff --git a/sample-apps/react/egress-composite/src/CompositeApp.scss b/sample-apps/react/egress-composite/src/CompositeApp.scss index b7d394107b..8922fbdd31 100644 --- a/sample-apps/react/egress-composite/src/CompositeApp.scss +++ b/sample-apps/react/egress-composite/src/CompositeApp.scss @@ -13,6 +13,7 @@ .str-video { color: var(--str-video__text-color1); + position: relative; } body { @@ -24,7 +25,6 @@ body { } #root { - max-width: 1920px; margin: 0 auto; text-align: center; } diff --git a/sample-apps/react/egress-composite/src/CompositeApp.tsx b/sample-apps/react/egress-composite/src/CompositeApp.tsx index 5448568124..62f628d8d4 100644 --- a/sample-apps/react/egress-composite/src/CompositeApp.tsx +++ b/sample-apps/react/egress-composite/src/CompositeApp.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { PropsWithChildren, useEffect, useState } from 'react'; import { Call, CallingState, @@ -7,41 +7,50 @@ import { StreamTheme, StreamVideo, StreamVideoClient, - useCallStateHooks, } from '@stream-io/video-react-sdk'; -import Layouts, { DEFAULT_LAYOUT_ID, LayoutId } from './layouts'; -import { useAppConfig } from './hooks/useAppConfig'; + +import { useConfigurationContext } from './ConfigurationContext'; import { EgressReadyNotificationProvider } from './hooks/useNotifyEgress'; + import './CompositeApp.scss'; +import { UIDispatcher, LogoAndTitleOverlay } from './components'; export const CompositeApp = () => { - const config = useAppConfig(); + const { + base_url: baseURL, + api_key: apiKey, + user_id: userId, + call_type: callType, + call_id: callId, + token, + } = useConfigurationContext(); + const options: StreamClientOptions = {}; - if (config.baseURL) { - options.baseURL = config.baseURL; + if (baseURL) { + options.baseURL = baseURL; } const [client] = useState( - () => new StreamVideoClient(config.apiKey, options), + () => new StreamVideoClient(apiKey, options), ); useEffect(() => { client.connectUser( { - id: config.userId, + id: userId, }, - config.token, + token, ); return () => { client.disconnectUser(); }; - }, [client, config.userId, config.token]); + }, [client, token, userId]); const [activeCall, setActiveCall] = useState(); useEffect(() => { if (!client) return; let joinInterrupted = false; - const call = client.call(config.callType, config.callId); + const call = client.call(callType, callId); const currentCall = call.join().then(() => { if (!joinInterrupted) { setActiveCall(call); @@ -57,7 +66,7 @@ export const CompositeApp = () => { setActiveCall(undefined); }); }; - }, [client, config.callId, config.callType]); + }, [client, callType, callId]); if (!client) { return

Connecting...

; @@ -65,29 +74,23 @@ export const CompositeApp = () => { return ( - + {activeCall && ( - + + )} - + {/* */} + ); }; -const UiDispatcher = (props: { layout: LayoutId }) => { - const { layout } = props; - const { ParticipantsView, ScreenShareView } = - Layouts[layout || DEFAULT_LAYOUT_ID]; - - const { useHasOngoingScreenShare } = useCallStateHooks(); - const hasScreenShare = useHasOngoingScreenShare(); - if (hasScreenShare) { - return ; - } +const StreamThemeWrapper = ({ children }: PropsWithChildren) => { + // TODO: background style - return ; + return {children}; }; diff --git a/sample-apps/react/egress-composite/src/ConfigurationContext.tsx b/sample-apps/react/egress-composite/src/ConfigurationContext.tsx new file mode 100644 index 0000000000..5a5c811db4 --- /dev/null +++ b/sample-apps/react/egress-composite/src/ConfigurationContext.tsx @@ -0,0 +1,74 @@ +import { createContext, useContext } from 'react'; +import { decode } from 'js-base64'; + +export type ConfigurationValue = { + base_url?: string; + + api_key: string; + call_type: string; + call_id: string; + token: string; + user_id: string; // pulled from the token payload + + ext_css?: string; + + layout?: 'grid' | 'single_participant' | 'spotlight' | 'mobile'; + screenshare_layout?: 'single_participant' | 'spotlight'; + + options: { + 'video.background_color'?: string; + 'video.scale_mode'?: 'fill' | 'fit'; + 'video.screenshare_scale_mode'?: 'fill' | 'fit'; + + 'logo.image_url'?: string; + 'logo.horizontal_position'?: 'center' | 'left' | 'right'; + 'logo.vertical_position'?: 'center' | 'left' | 'right'; + + 'participant.label_display'?: boolean; + 'participant.label_text_color'?: string; + 'participant.label_background_color'?: string; + 'participant.label_display_border'?: boolean; + 'participant.label_border_radius'?: string; + 'participant.label_border_color'?: string; + 'participant.label_horizontal_position'?: 'center' | 'left' | 'right'; + 'participant.label_vertical_position'?: 'center' | 'left' | 'right'; + + // participant_border_color: string; + // participant_border_radius: string; + // participant_border_width: string; + 'participant.participant_highlight_border_color'?: string; // talking + 'participant.placeholder_background_color'?: string; + + // used with any layout + 'layout.size_percentage'?: number; + + // grid-specific + 'layout.grid.gap'?: string; + 'layout.grid.page_size'?: number; + // dominant_speaker-specific (single-participant) + 'layout.single_participant.mode'?: 'shuffle' | 'default'; + 'layout.single_participant.shuffle_delay'?: number; + // spotlight-specific + 'layout.spotlight.bar_position'?: 'top' | 'right' | 'bottom' | 'left'; + 'layout.spotlight.bar_limit'?: number; + }; +}; + +export const ConfigurationContext = createContext( + {} as ConfigurationValue, +); + +export const extractPayloadFromToken = (token: string) => { + const [, payload] = token.split('.'); + + if (!payload) throw new Error('Malformed token, missing payload'); + + try { + return (JSON.parse(decode(payload)) ?? {}) as Record; + } catch (e) { + console.log('Error parsing token payload', e); + return {}; + } +}; + +export const useConfigurationContext = () => useContext(ConfigurationContext); diff --git a/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx b/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx new file mode 100644 index 0000000000..378763fc7f --- /dev/null +++ b/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx @@ -0,0 +1,36 @@ +import { useConfigurationContext } from '../ConfigurationContext'; + +export const LogoAndTitleOverlay = () => { + const { options } = useConfigurationContext(); + + const image_url = options['logo.image_url']; + + return ( +
+ {/* {text?.length && ( +
+ {text} +
+ )} */} + {image_url && ( + logo + )} +
+ ); +}; diff --git a/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx b/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx new file mode 100644 index 0000000000..34677500c5 --- /dev/null +++ b/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx @@ -0,0 +1,23 @@ +import { useCallStateHooks } from '@stream-io/video-react-sdk'; + +import { useConfigurationContext } from '../ConfigurationContext'; +import { LayoutType, layoutMap } from './layouts'; +import { Spotlight } from './layouts/Spotlight'; + +const DEFAULT_LAYOUT: LayoutType = 'spotlight'; +const DEFAULT_SCREENSHARE_LAYOUT: LayoutType = 'spotlight'; + +export const UIDispatcher = () => { + const { + layout = DEFAULT_LAYOUT, + screenshare_layout = DEFAULT_SCREENSHARE_LAYOUT, + } = useConfigurationContext(); + const { useHasOngoingScreenShare } = useCallStateHooks(); + const hasScreenShare = useHasOngoingScreenShare(); + + const DefaultView = layoutMap[layout]?.[0] ?? Spotlight; + + const ScreenShareView = layoutMap[screenshare_layout]?.[1] ?? Spotlight; + + return hasScreenShare ? : ; +}; diff --git a/sample-apps/react/egress-composite/src/components/index.ts b/sample-apps/react/egress-composite/src/components/index.ts new file mode 100644 index 0000000000..1b7f446e53 --- /dev/null +++ b/sample-apps/react/egress-composite/src/components/index.ts @@ -0,0 +1,2 @@ +export * from './LogoAndTitleOverlay'; +export * from './UIDispatcher'; diff --git a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/AudioTracks.tsx b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx similarity index 92% rename from sample-apps/react/egress-composite/src/layouts/dominant-speaker/AudioTracks.tsx rename to sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx index 24bb9a9bc3..cd3a671770 100644 --- a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/AudioTracks.tsx +++ b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx @@ -12,7 +12,7 @@ export const AudioTracks = (props: { key={participant.sessionId} audioStream={participant.audioStream} muted={participant.sessionId === dominantSpeaker?.sessionId} - data-userId={participant.userId} + data-user-id={participant.userId} /> ))} diff --git a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/Spotlight.scss b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.scss similarity index 85% rename from sample-apps/react/egress-composite/src/layouts/dominant-speaker/Spotlight.scss rename to sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.scss index d619ccb290..97ed594294 100644 --- a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/Spotlight.scss +++ b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.scss @@ -1,4 +1,4 @@ -.spotlight-container { +.dominant-speaker__container { height: 100vh; .str-video__participant-view { diff --git a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/DominantSpeaker.tsx b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.tsx similarity index 93% rename from sample-apps/react/egress-composite/src/layouts/dominant-speaker/DominantSpeaker.tsx rename to sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.tsx index 65d0f94835..e644087cec 100644 --- a/sample-apps/react/egress-composite/src/layouts/dominant-speaker/DominantSpeaker.tsx +++ b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.tsx @@ -7,7 +7,7 @@ import { } from '@stream-io/video-react-sdk'; import { useSpotlightParticipant } from './useSpotlightParticipant'; import { useEgressReadyWhenAnyParticipantMounts } from '../egressReady'; -import './Spotlight.scss'; +import './DominantSpeaker.scss'; import { AudioTracks } from './AudioTracks'; export const DominantSpeaker = () => { @@ -24,7 +24,7 @@ export const DominantSpeaker = () => { if (!activeCall) return

No active call

; return ( <> -
+
{speakerInSpotlight && ( { @@ -26,9 +26,9 @@ export const DominantSpeakerScreenShare = () => { return ( <> -
+