diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..8db7cec227 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +**/node_modules/ +**/dist/ +**/.env* +**/.DS_Store +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions +.github/ +.readme-assets/ +sample-apps/** +!sample-apps/react/egress-composite/ +packages/react-native-sdk/ +sample-apps/react/egress-composite/tests/ +.styles/ diff --git a/.github/workflows/deploy-react-sample-apps.yml b/.github/workflows/deploy-react-sample-apps.yml index 9c02f00e80..046200e024 100644 --- a/.github/workflows/deploy-react-sample-apps.yml +++ b/.github/workflows/deploy-react-sample-apps.yml @@ -54,21 +54,24 @@ jobs: 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_EGRESS_SENTRY_DNS: ${{ secrets.EGRESS_SENTRY_DNS }} VITE_VIDEO_DEMO_SENTRY_DNS: ${{secrets.VIDEO_DEMO_SENTRY_DNS}} VITE_TOKEN_PROVIDER_URL: ${{secrets.TOKEN_PROVIDER_URL}} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + PRONTO_SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + EGRESS_SENTRY_AUTH_TOKEN: ${{ secrets.EGRESS_SENTRY_AUTH_TOKEN }} + MODE: ${{ github.ref_name == 'main' && 'production' || 'preview' }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 -# - uses: dorny/paths-filter@v2.11.1 -# id: changes -# with: -# filters: | -# sample-apps: -# - 'sample-apps/react/${{ matrix.application.folder || matrix.application.name }}/**/*' + # - uses: dorny/paths-filter@v2.11.1 + # id: changes + # with: + # filters: | + # sample-apps: + # - 'sample-apps/react/${{ matrix.application.folder || matrix.application.name }}/**/*' - name: Setup Node uses: actions/setup-node@v3 diff --git a/.github/workflows/egress-composite-e2e.yml b/.github/workflows/egress-composite-e2e.yml index 8ad7c8b71f..96ae4bc265 100644 --- a/.github/workflows/egress-composite-e2e.yml +++ b/.github/workflows/egress-composite-e2e.yml @@ -21,7 +21,7 @@ env: jobs: test: - timeout-minutes: 15 + timeout-minutes: 20 runs-on: ubuntu-latest steps: @@ -58,6 +58,20 @@ jobs: - name: Install Playwright system dependencies (always) run: npx playwright install-deps + # - name: Authenticate stream-video-buddy + # uses: nick-fields/retry@v2.8.3 + # with: + # timeout_minutes: 5 + # max_attempts: 2 + # command: yarn workspace @stream-io/egress-composite buddy auth + - name: Run Playwright tests - working-directory: sample-apps/react/egress-composite - run: yarn buddy auth && yarn test:e2e + run: yarn workspace @stream-io/egress-composite test:e2e + + - name: Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: sample-apps/react/egress-composite/test-results/ + retention-days: 5 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..b27fc28585 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM node:20-alpine as packager +WORKDIR /e2e + +COPY sample-apps/ sample-apps/ +RUN find sample-apps \! -name "package.json" -mindepth 3 -maxdepth 3 -print | xargs rm -rf + +COPY packages/ packages/ +RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf + +FROM node:20-bullseye as runner +WORKDIR /e2e + +COPY .yarn/ ./.yarn/ +COPY yarn.lock package.json .yarnrc.yml tsconfig.json ./ +COPY --from=packager /e2e/packages ./packages +COPY --from=packager /e2e/sample-apps ./sample-apps + +RUN yarn install + +RUN npx playwright install chromium +RUN npx playwright install-deps + +# Copy all sources next +COPY ./packages ./packages +RUN yarn build:react:deps + +COPY ./sample-apps/ ./sample-apps/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..1f109e21bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +# this compose file exists to generate screenshots +# for the CI to compare against, replace key and token with your own before running +# docker compose up, each change to the sources (not including tests) requires image rebuild +# https://github.com/microsoft/playwright/issues/8161 + +services: + generate-screenshots: + build: ./ + command: > + yarn workspace @stream-io/egress-composite run test:e2e + -u + --timeout 5000 + volumes: + - ./sample-apps/react/egress-composite/tests/:/e2e/sample-apps/react/egress-composite/tests/ + environment: + VITE_STREAM_API_KEY: + VITE_STREAM_USER_TOKEN: + CI: true diff --git a/packages/react-native-sdk/CHANGELOG.md b/packages/react-native-sdk/CHANGELOG.md index d8e9831136..56dffa9baa 100644 --- a/packages/react-native-sdk/CHANGELOG.md +++ b/packages/react-native-sdk/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +### [0.0.28](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-0.0.27...@stream-io/video-react-native-sdk-0.0.28) (2023-10-02) + + +### Features + +* **react-native:** support landscape more for CallContent ([#1119](https://github.com/GetStream/stream-video-js/issues/1119)) ([2e218b4](https://github.com/GetStream/stream-video-js/commit/2e218b4ad8f00c5eb1632d64df6c5d3456b5af41)) + ### [0.0.27](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-native-sdk-0.0.26...@stream-io/video-react-native-sdk-0.0.27) (2023-09-28) ### Dependency Updates diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx index aaf22cc179..6d4a3bb864 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-content.mdx @@ -11,6 +11,7 @@ import CallContentSpotlight from '../../assets/04-ui-components/call/call-conten import CallTopView from '../../common-content/ui-components/call/call-content/call-top-view.mdx'; import CallControls from '../../common-content/ui-components/call/call-content/call-controls.mdx'; import ParticipantsInfoBadge from '../../common-content/ui-components/call/call-content/participants-info-badge.mdx'; +import Landscape from '../../common-content/ui-components/call/call-content/landscape.mdx'; import OnBackPressed from '../../common-content/ui-components/call/call-content/on-back-pressed.mdx'; import OnParticipantInfoPress from '../../common-content/ui-components/call/call-content/on-participant-info-press.mdx'; import ParticipantLabel from '../../common-content/ui-components/call/call-content/participant-label.mdx'; @@ -93,6 +94,10 @@ This switches the list between the grid and the spotlight mode. When a screen is shared, the layout automatically changes to `spotlight` mode. ::: +### `landscape` + + + ### [`onBackPressed`](../call-top-view/#onbackpressed) diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-controls.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-controls.mdx index a03c5d0a9e..51ad938170 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-controls.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-controls.mdx @@ -4,6 +4,7 @@ title: CallControls --- import OnHangupCallHandler from '../../common-content/ui-components/call/call-content/on-hangup-call-handler.mdx'; +import Landscape from '../../common-content/ui-components/call/call-content/landscape.mdx'; CallControls allows users to execute actions during the call(for example, mute/unmute audio/video, reactions, hang-up calls, etc.). We provide a built-in `CallControls` component that displays all relevant call controls during a call. @@ -39,6 +40,10 @@ const VideoCallUI = () => { +### `landscape` + + + ## Built-in call controls Each call control is available as a separate UI component. diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-participants-list.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-participants-list.mdx index 23dbc307e7..174f5b777b 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-participants-list.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/call-participants-list.mdx @@ -6,6 +6,7 @@ title: CallParticipantsList import ImageShowcase from '@site/src/components/ImageShowcase'; import CallParticipantsListVertical from '../../assets/04-ui-components/call/call-participants-list/vertical.png'; import CallParticipantsListHorizontal from '../../assets/04-ui-components/call/call-participants-list/horizontal.png'; +import Landscape from '../../common-content/ui-components/call/call-content/landscape.mdx'; import ParticipantLabel from '../../common-content/ui-components/call/call-content/participant-label.mdx'; import ParticipantReaction from '../../common-content/ui-components/call/call-content/participant-reaction.mdx'; @@ -72,7 +73,7 @@ const VideoCallUI = () => { The list of participants to list in the view. -### numberOfColumns +### `numberOfColumns` | Type | Default Value | | ----------------------- | ------------- | @@ -80,7 +81,7 @@ The list of participants to list in the view. The number of participants to be displayed in a single row. This property is only used when there are more than 2 participants. -### horizontal +### `horizontal` | Type | Default Value | | ------------------------ | ------------- | @@ -88,6 +89,10 @@ The number of participants to be displayed in a single row. This property is onl This decides whether the participants should be listed vertically or horizontally. +### `landscape` + + + ### `ParticipantLabel` diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx index a615102479..af575901f0 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx @@ -10,6 +10,7 @@ import JoiningCallIndicator from '../../assets/04-ui-components/call/ringing-cal import CallContent from '../../assets/04-ui-components/call/ringing-call-content/call-content.png'; import CallTopView from '../../common-content/ui-components/call/call-content/call-top-view.mdx'; +import Landscape from '../../common-content/ui-components/call/call-content/landscape.mdx'; The `RingingCallContent` lets you easily build UI when you're calling or ringing other people in an app. It's used to show more information about the participants you're calling, as well as give you the option to cancel the call before anyone accepts. @@ -65,6 +66,10 @@ const Call = () => { ## Props +### `landscape` + + + ### `IncomingCall` Prop to customize the `IncomingCall` component. This component is rendered when an incoming call is received. diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/landscape.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/landscape.mdx new file mode 100644 index 0000000000..25e846e4d9 --- /dev/null +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/common-content/ui-components/call/call-content/landscape.mdx @@ -0,0 +1,5 @@ +Applies the landscape mode styles to the component. + +| Type | +| ------------------------ | +| `boolean` \| `undefined` | diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 7797bb65f2..5ac0a95760 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-native-sdk", - "version": "0.0.27", + "version": "0.0.28", "packageManager": "yarn@3.2.4", "main": "dist/commonjs/index.js", "module": "dist/module/index.js", diff --git a/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx index e26b2cf040..1c611c928c 100644 --- a/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx +++ b/packages/react-native-sdk/src/components/Call/CallContent/CallContent.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; import { CallTopView as DefaultCallTopView, CallTopViewProps, @@ -60,6 +60,11 @@ export type CallContentProps = Pick & * This switches the participant's layout between the grid and the spotlight mode. */ layout?: 'grid' | 'spotlight'; + /** + * Check if device is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; export const CallContent = ({ @@ -78,6 +83,7 @@ export const CallContent = ({ ParticipantsInfoBadge, VideoRenderer, layout = 'grid', + landscape = true, }: CallContentProps) => { const [ showRemoteParticipantInFloatingView, @@ -140,6 +146,7 @@ export const CallContent = ({ const callParticipantsGridProps: CallParticipantsGridProps = { ...participantViewProps, + landscape, showLocalParticipant: isRemoteParticipantInFloatingView, ParticipantView, CallParticipantsList, @@ -147,12 +154,17 @@ export const CallContent = ({ const callParticipantsSpotlightProps: CallParticipantsSpotlightProps = { ...participantViewProps, + landscape, ParticipantView, CallParticipantsList, }; + const landScapeStyles: ViewStyle = { + flexDirection: landscape ? 'row' : 'column', + }; + return ( - + {CallControls && ( - + )} ); diff --git a/packages/react-native-sdk/src/components/Call/CallControls/CallControls.tsx b/packages/react-native-sdk/src/components/Call/CallControls/CallControls.tsx index 4154560e80..c68a25e28a 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/CallControls.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/CallControls.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, ViewProps } from 'react-native'; +import { StyleSheet, View, ViewProps, ViewStyle } from 'react-native'; import { ToggleAudioPublishingButton } from './ToggleAudioPublishingButton'; import { ToggleVideoPublishingButton } from './ToggleVideoPublishingButton'; import { ToggleCameraFaceButton } from './ToggleCameraFaceButton'; @@ -16,6 +16,11 @@ export type CallControlProps = Pick & { * @returns void */ onHangupCallHandler?: () => void; + /** + * Check if device is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; /** @@ -25,17 +30,24 @@ export type CallControlProps = Pick & { export const CallControls = ({ style, onHangupCallHandler, + landscape, }: CallControlProps) => { const { theme: { colors, callControls }, } = useTheme(); + const landScapeStyles: ViewStyle = { + flexDirection: landscape ? 'column-reverse' : 'row', + paddingHorizontal: landscape ? 12 : 0, + paddingVertical: landscape ? 0 : 12, + }; return ( @@ -48,8 +60,6 @@ export const CallControls = ({ const styles = StyleSheet.create({ container: { - paddingVertical: 12, - flexDirection: 'row', justifyContent: 'space-evenly', zIndex: Z_INDEX.IN_FRONT, }, diff --git a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx index e7f9a89ecc..e6382142bf 100644 --- a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx +++ b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsGrid.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; import { useCallStateHooks } from '@stream-io/video-react-bindings'; import { useDebouncedValue } from '../../../utils/hooks/useDebouncedValue'; import { @@ -22,6 +22,11 @@ export type CallParticipantsGridProps = CallParticipantsListComponentProps & { * Boolean to decide if local participant will be visible in the grid when there is 1:1 call. */ showLocalParticipant?: boolean; + /** + * Check if device is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; /** @@ -36,6 +41,7 @@ export const CallParticipantsGrid = ({ ParticipantView, VideoRenderer, showLocalParticipant = false, + landscape, }: CallParticipantsGridProps) => { const { theme: { colors, callParticipantsGrid }, @@ -48,6 +54,9 @@ export const CallParticipantsGrid = ({ // we debounce the participants arrays to avoid unnecessary rerenders that happen when participant tracks are all subscribed simultaneously const remoteParticipants = useDebouncedValue(_remoteParticipants, 300); const allParticipants = useDebouncedValue(_allParticipants, 300); + const landScapeStyles: ViewStyle = { + flexDirection: landscape ? 'row' : 'column', + }; const showFloatingView = remoteParticipants.length > 0 && remoteParticipants.length < 3; @@ -71,6 +80,7 @@ export const CallParticipantsGrid = ({ )} diff --git a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx index 128508612d..e932d0c9f8 100644 --- a/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +++ b/packages/react-native-sdk/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx @@ -5,7 +5,7 @@ import { StreamVideoParticipant, } from '@stream-io/video-client'; import { useCallStateHooks } from '@stream-io/video-react-bindings'; -import { StyleSheet, View } from 'react-native'; +import { StyleSheet, View, ViewStyle } from 'react-native'; import { useDebouncedValue } from '../../../utils/hooks/useDebouncedValue'; import { ComponentTestIds } from '../../../constants/TestIds'; import { @@ -28,6 +28,11 @@ export type CallParticipantsSpotlightProps = * Component to customize the CallParticipantsList. */ CallParticipantsList?: React.ComponentType | null; + /** + * Check if device is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; const hasScreenShare = (p: StreamVideoParticipant) => @@ -45,6 +50,7 @@ export const CallParticipantsSpotlight = ({ ParticipantVideoFallback, ParticipantView = DefaultParticipantView, VideoRenderer, + landscape, }: CallParticipantsSpotlightProps) => { const { theme: { colors, callParticipantsSpotlight }, @@ -71,11 +77,20 @@ export const CallParticipantsSpotlight = ({ ParticipantView, }; + const landScapeStyles: ViewStyle = { + flexDirection: landscape ? 'row' : 'column', + }; + + const spotlightContainerLandscapeStyles: ViewStyle = { + marginHorizontal: landscape ? 0 : 8, + }; + return ( )} @@ -127,7 +145,6 @@ export const CallParticipantsSpotlight = ({ const styles = StyleSheet.create({ container: { flex: 1, - paddingVertical: 8, }, fullScreenSpotlightContainer: { flex: 1, @@ -137,7 +154,6 @@ const styles = StyleSheet.create({ overflow: 'hidden', borderRadius: 10, marginHorizontal: 8, - marginBottom: 8, }, callParticipantsListContainer: { flex: 1, diff --git a/packages/react-native-sdk/src/components/Call/CallParticipantsList/CallParticipantsList.tsx b/packages/react-native-sdk/src/components/Call/CallParticipantsList/CallParticipantsList.tsx index 190cf39b02..7df8523876 100644 --- a/packages/react-native-sdk/src/components/Call/CallParticipantsList/CallParticipantsList.tsx +++ b/packages/react-native-sdk/src/components/Call/CallParticipantsList/CallParticipantsList.tsx @@ -56,6 +56,11 @@ export type CallParticipantsListProps = CallParticipantsListComponentProps & { * If true, the list will be displayed in horizontal scrolling mode */ horizontal?: boolean; + /** + * Check if phone is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; /** @@ -74,6 +79,7 @@ export const CallParticipantsList = ({ ParticipantReaction, ParticipantVideoFallback, VideoRenderer, + landscape, }: CallParticipantsListProps) => { const [containerLayout, setContainerLayout] = useState({ width: 0, @@ -162,8 +168,11 @@ export const CallParticipantsList = ({ if (horizontal) { return [styles.participantWrapperHorizontal, style]; } + if (landscape) { + return [styles.landScapeStyle, style]; + } return style; - }, [itemWidth, itemHeight, horizontal]); + }, [itemWidth, itemHeight, horizontal, landscape]); const participantProps: ParticipantViewComponentProps = { ParticipantLabel, @@ -247,6 +256,9 @@ const styles = StyleSheet.create({ marginHorizontal: 8, borderRadius: 10, }, + landScapeStyle: { + borderRadius: 10, + }, }); /** diff --git a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx index f90668b876..4460425573 100644 --- a/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx +++ b/packages/react-native-sdk/src/components/Call/CallTopView/CallTopView.tsx @@ -50,6 +50,7 @@ export const CallTopView = ({ ParticipantsInfoBadge = DefaultParticipantsInfoBadge, }: CallTopViewProps) => { const [callTopViewHeight, setCallTopViewHeight] = useState(0); + const [callTopViewWidth, setCallTopViewWidth] = useState(0); const { theme: { colors, @@ -64,16 +65,17 @@ export const CallTopView = ({ const isCallReconnecting = callingState === CallingState.RECONNECTING; const onLayout: React.ComponentProps['onLayout'] = (event) => { - const { height } = event.nativeEvent.layout; + const { height, width } = event.nativeEvent.layout; if (setCallTopViewHeight) { setCallTopViewHeight(height); + setCallTopViewWidth(width); } }; return ( {/* Component for the background of the CallTopView. Since it has a Linear Gradient, an SVG is used to render it. */} - + {onBackPressed && ( diff --git a/packages/react-native-sdk/src/components/Call/RingingCallContent/RingingCallContent.tsx b/packages/react-native-sdk/src/components/Call/RingingCallContent/RingingCallContent.tsx index 88f3677562..e005c04d7a 100644 --- a/packages/react-native-sdk/src/components/Call/RingingCallContent/RingingCallContent.tsx +++ b/packages/react-native-sdk/src/components/Call/RingingCallContent/RingingCallContent.tsx @@ -42,6 +42,11 @@ export type RingingCallContentProps = { * Prop to customize the JoiningCallIndicator component in the RingingCallContent. It is shown when the call is accepted and is waiting to be joined. */ JoiningCallIndicator?: React.ComponentType | null; + /** + * Check if device is in landscape mode. + * This will apply the landscape mode styles to the component. + */ + landscape?: boolean; }; const RingingCallPanel = ({ @@ -50,6 +55,7 @@ const RingingCallPanel = ({ CallContent = DefaultCallContent, JoiningCallIndicator = DefaultJoiningCallIndicator, CallTopView, + landscape, }: RingingCallContentProps) => { const call = useCall(); const isCallCreatedByMe = call?.isCreatedByMe; @@ -63,7 +69,11 @@ const RingingCallPanel = ({ ? OutgoingCall && : IncomingCall && ; case CallingState.JOINED: - return CallContent && ; + return ( + CallContent && ( + + ) + ); case CallingState.JOINING: return JoiningCallIndicator && ; default: diff --git a/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx b/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx index 50cc670d25..d6b99ce1ba 100644 --- a/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx +++ b/packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx @@ -188,6 +188,7 @@ const styles = StyleSheet.create({ shadowOpacity: 0.23, shadowRadius: 2.62, elevation: 4, + borderWidth: 0, }, videoFallback: { ...StyleSheet.absoluteFillObject, diff --git a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantView.tsx b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantView.tsx index f76289dd02..2f67995ab4 100644 --- a/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantView.tsx +++ b/packages/react-native-sdk/src/components/Participant/ParticipantView/ParticipantView.tsx @@ -151,7 +151,8 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', padding: 4, overflow: 'hidden', - margin: 2, + borderWidth: 2, + borderColor: 'transparent', }, footerContainer: { flexDirection: 'row', diff --git a/packages/react-sdk/CHANGELOG.md b/packages/react-sdk/CHANGELOG.md index b467b50300..a6788ab0d3 100644 --- a/packages/react-sdk/CHANGELOG.md +++ b/packages/react-sdk/CHANGELOG.md @@ -2,6 +2,30 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +### [0.3.36](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.35...@stream-io/video-react-sdk-0.3.36) (2023-10-03) + + +### Bug Fixes + +* check if `currentParticipant` is actually initialized ([#1124](https://github.com/GetStream/stream-video-js/issues/1124)) ([797b84f](https://github.com/GetStream/stream-video-js/commit/797b84f9f63ae2c98a97b28afc08858705cd6840)) + +### [0.3.35](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.34...@stream-io/video-react-sdk-0.3.35) (2023-10-02) + + +### Bug Fixes + +* requestPermission should be no-op when permission is already granted ([#1122](https://github.com/GetStream/stream-video-js/issues/1122)) ([f3d9e34](https://github.com/GetStream/stream-video-js/commit/f3d9e349825a6052850f7a78c3d6af9f517d136e)) + +### [0.3.34](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.33...@stream-io/video-react-sdk-0.3.34) (2023-10-02) + +### Dependency Updates + +* `@stream-io/video-styling` updated to version `0.1.12` + +### Features + +* **egress-composite:** add support for new options ([#1104](https://github.com/GetStream/stream-video-js/issues/1104)) ([2e039c2](https://github.com/GetStream/stream-video-js/commit/2e039c280cd808e6464ee3ab54e8c3606a0a0180)), closes [/github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx#L53-L117](https://github.com/GetStream//github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx/issues/L53-L117) + ### [0.3.33](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-react-sdk-0.3.32...@stream-io/video-react-sdk-0.3.33) (2023-09-28) ### Dependency Updates diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index a5b2b73f9c..7f8af3ed1e 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@stream-io/video-react-sdk", - "version": "0.3.33", + "version": "0.3.36", "packageManager": "yarn@3.2.4", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/react-sdk/src/core/components/CallLayout/LivestreamLayout.tsx b/packages/react-sdk/src/core/components/CallLayout/LivestreamLayout.tsx index 94834d4811..9e1dcf4b6d 100644 --- a/packages/react-sdk/src/core/components/CallLayout/LivestreamLayout.tsx +++ b/packages/react-sdk/src/core/components/CallLayout/LivestreamLayout.tsx @@ -102,19 +102,22 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => { muteAudio // audio is rendered by ParticipantsAudio /> )} - + {currentSpeaker && ( + + )} ); }; diff --git a/packages/react-sdk/src/core/components/CallLayout/SpeakerLayout.tsx b/packages/react-sdk/src/core/components/CallLayout/SpeakerLayout.tsx index a7484c7ef2..4f959d08be 100644 --- a/packages/react-sdk/src/core/components/CallLayout/SpeakerLayout.tsx +++ b/packages/react-sdk/src/core/components/CallLayout/SpeakerLayout.tsx @@ -80,11 +80,14 @@ export const SpeakerLayout = ({ let participantsWithAppliedLimit = otherParticipants; - if (typeof participantsBarLimit !== 'undefined') { - const hardLimitToApply = isVertical - ? hardLimit.vertical - : hardLimit.horizontal; - + const hardLimitToApply = isVertical + ? hardLimit.vertical + : hardLimit.horizontal; + + if ( + typeof participantsBarLimit !== 'undefined' && + hardLimitToApply !== null + ) { participantsWithAppliedLimit = otherParticipants.slice( 0, // subtract 1 if speaker is sharing screen as diff --git a/packages/react-sdk/src/core/hooks/useCalculateHardLimit.ts b/packages/react-sdk/src/core/hooks/useCalculateHardLimit.ts index 8cf6093ddd..8ed663a0ba 100644 --- a/packages/react-sdk/src/core/hooks/useCalculateHardLimit.ts +++ b/packages/react-sdk/src/core/hooks/useCalculateHardLimit.ts @@ -12,11 +12,11 @@ export const useCalculateHardLimit = ( limit?: 'dynamic' | number, ) => { const [calculatedLimit, setCalculatedLimit] = useState<{ - vertical: number; - horizontal: number; + vertical: number | null; + horizontal: number | null; }>({ - vertical: typeof limit === 'number' ? limit : 1, - horizontal: typeof limit === 'number' ? limit : 1, + vertical: typeof limit === 'number' ? limit : null, + horizontal: typeof limit === 'number' ? limit : null, }); useEffect(() => { diff --git a/packages/react-sdk/src/hooks/useRequestPermission.ts b/packages/react-sdk/src/hooks/useRequestPermission.ts index ee46fb24be..fbdcd526bd 100644 --- a/packages/react-sdk/src/hooks/useRequestPermission.ts +++ b/packages/react-sdk/src/hooks/useRequestPermission.ts @@ -5,8 +5,6 @@ import { useCall, useHasPermissions } from '@stream-io/video-react-bindings'; export const useRequestPermission = (permission: OwnCapability) => { const call = useCall(); const hasPermission = useHasPermissions(permission); - const canRequestPermission = - !!call?.permissionsContext.canRequest(permission); const [isAwaitingPermission, setIsAwaitingPermission] = useState(false); // TODO: load with possibly pending state useEffect(() => { @@ -16,9 +14,12 @@ export const useRequestPermission = (permission: OwnCapability) => { }, [hasPermission]); const requestPermission = useCallback(async () => { - if (isAwaitingPermission || !canRequestPermission) return false; if (hasPermission) return true; + const canRequestPermission = + !!call?.permissionsContext.canRequest(permission); + if (isAwaitingPermission || !canRequestPermission) return false; + setIsAwaitingPermission(true); try { @@ -31,18 +32,12 @@ export const useRequestPermission = (permission: OwnCapability) => { } return false; - }, [ - call, - canRequestPermission, - hasPermission, - isAwaitingPermission, - permission, - ]); + }, [call, hasPermission, isAwaitingPermission, permission]); return { requestPermission, hasPermission, - canRequestPermission, + canRequestPermission: !!call?.permissionsContext.canRequest(permission), isAwaitingPermission, }; }; diff --git a/packages/react-sdk/src/hooks/useToggleAudioMuteState.ts b/packages/react-sdk/src/hooks/useToggleAudioMuteState.ts index 1623d4d2cd..b094f72ef0 100644 --- a/packages/react-sdk/src/hooks/useToggleAudioMuteState.ts +++ b/packages/react-sdk/src/hooks/useToggleAudioMuteState.ts @@ -27,7 +27,7 @@ export const useToggleAudioMuteState = () => { if (canPublish) return publishAudioStream(); } - if (!isAudioMutedReference.current) stopPublishingAudio(); + if (!isAudioMutedReference.current) await stopPublishingAudio(); }, [publishAudioStream, requestPermission, stopPublishingAudio]); return { toggleAudioMuteState, isAwaitingPermission }; diff --git a/packages/react-sdk/src/hooks/useToggleScreenShare.ts b/packages/react-sdk/src/hooks/useToggleScreenShare.ts index 6dab5d05a1..92ba4638bd 100644 --- a/packages/react-sdk/src/hooks/useToggleScreenShare.ts +++ b/packages/react-sdk/src/hooks/useToggleScreenShare.ts @@ -36,7 +36,7 @@ export const useToggleScreenShare = () => { } } - call?.stopPublish(SfuModels.TrackType.SCREEN_SHARE); + await call?.stopPublish(SfuModels.TrackType.SCREEN_SHARE); }, [call, requestPermission]); return { toggleScreenShare, isAwaitingPermission, isScreenSharing }; diff --git a/packages/react-sdk/src/hooks/useToggleVideoMuteState.ts b/packages/react-sdk/src/hooks/useToggleVideoMuteState.ts index 1420ba0308..ea574fd06f 100644 --- a/packages/react-sdk/src/hooks/useToggleVideoMuteState.ts +++ b/packages/react-sdk/src/hooks/useToggleVideoMuteState.ts @@ -27,7 +27,7 @@ export const useToggleVideoMuteState = () => { if (canPublish) return publishVideoStream(); } - if (!isVideoMutedReference.current) stopPublishingVideo(); + if (!isVideoMutedReference.current) await stopPublishingVideo(); }, [publishVideoStream, requestPermission, stopPublishingVideo]); return { toggleVideoMuteState, isAwaitingPermission }; diff --git a/packages/styling/CHANGELOG.md b/packages/styling/CHANGELOG.md index e75e4f6e14..0c231a34b3 100644 --- a/packages/styling/CHANGELOG.md +++ b/packages/styling/CHANGELOG.md @@ -2,6 +2,13 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +### [0.1.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-styling-0.1.11...@stream-io/video-styling-0.1.12) (2023-10-02) + + +### Features + +* **egress-composite:** add support for new options ([#1104](https://github.com/GetStream/stream-video-js/issues/1104)) ([2e039c2](https://github.com/GetStream/stream-video-js/commit/2e039c280cd808e6464ee3ab54e8c3606a0a0180)), closes [/github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx#L53-L117](https://github.com/GetStream//github.com/GetStream/stream-video-js/blob/acc7301c069daeff68a8ad495e4f66bc2e61a137/sample-apps/react/egress-composite/src/ConfigurationContext.tsx/issues/L53-L117) + ### [0.1.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-styling-0.1.10...@stream-io/video-styling-0.1.11) (2023-09-27) diff --git a/packages/styling/package.json b/packages/styling/package.json index 3715d149f6..39c9e2a35e 100644 --- a/packages/styling/package.json +++ b/packages/styling/package.json @@ -11,5 +11,5 @@ "rimraf": "^5.0.1", "sass": "^1.62.1" }, - "version": "0.1.11" + "version": "0.1.12" } diff --git a/packages/styling/src/ParticipantView/ParticipantView-layout.scss b/packages/styling/src/ParticipantView/ParticipantView-layout.scss index 8695bf32e4..ae951978ea 100644 --- a/packages/styling/src/ParticipantView/ParticipantView-layout.scss +++ b/packages/styling/src/ParticipantView/ParticipantView-layout.scss @@ -46,10 +46,10 @@ $scope-name: 'str-video__participant-view'; display: flex; align-items: center; gap: 0.3125rem; + border-radius: var(--str-video__border-radius-xs); + background-color: var(--str-video__background-color4); .str-video__participant-details__name { - border-radius: var(--str-video__border-radius-xs); - background-color: var(--str-video__background-color4); display: flex; align-items: center; gap: 0.3125rem; diff --git a/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml b/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml index cd8a406e18..8721ddbdb9 100644 --- a/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml +++ b/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ - + @@ -32,7 +32,6 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme"> @@ -70,4 +69,4 @@ - + \ No newline at end of file diff --git a/sample-apps/react-native/dogfood/ios/StreamReactNativeVideoSDKSample/Info.plist b/sample-apps/react-native/dogfood/ios/StreamReactNativeVideoSDKSample/Info.plist index bc60e0ed40..9611adf17c 100644 --- a/sample-apps/react-native/dogfood/ios/StreamReactNativeVideoSDKSample/Info.plist +++ b/sample-apps/react-native/dogfood/ios/StreamReactNativeVideoSDKSample/Info.plist @@ -81,10 +81,6 @@ armv7 UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index 1ab81fb971..a3a7f6238d 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useState } from 'react'; -import { useCall, CallContent } from '@stream-io/video-react-native-sdk'; +import { + useCall, + CallContent, + CallControlProps, +} from '@stream-io/video-react-native-sdk'; import { ActivityIndicator, StyleSheet } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ParticipantsInfoList } from './ParticipantsInfoList'; @@ -7,6 +11,7 @@ import { CallControlsComponent, CallControlsComponentProps, } from './CallControlsComponent'; +import { useOrientation } from '../hooks/useOrientation'; type ActiveCallProps = CallControlsComponentProps & { onBackPressed?: () => void; @@ -21,20 +26,25 @@ export const ActiveCall = ({ const [isCallParticipantsVisible, setIsCallParticipantsVisible] = useState(false); const call = useCall(); + const currentOrientation = useOrientation(); const onOpenCallParticipantsInfo = () => { setIsCallParticipantsVisible(true); }; - const CustomControlsComponent = useCallback(() => { - return ( - - ); - }, [onChatOpenHandler, onHangupCallHandler, unreadCountIndicator]); + const CustomControlsComponent = useCallback( + ({ landscape }: CallControlProps) => { + return ( + + ); + }, + [onChatOpenHandler, onHangupCallHandler, unreadCountIndicator], + ); if (!call) { return ; @@ -46,6 +56,7 @@ export const ActiveCall = ({ onBackPressed={onBackPressed} onParticipantInfoPress={onOpenCallParticipantsInfo} CallControls={CustomControlsComponent} + landscape={currentOrientation === 'landscape'} /> void; onHangupCallHandler?: () => void; unreadCountIndicator?: number; + landscape?: boolean; }; export const CallControlsComponent = ({ onChatOpenHandler, onHangupCallHandler, unreadCountIndicator, + landscape, }: CallControlsComponentProps) => { const { bottom } = useSafeAreaInsets(); + const landScapeStyles: ViewStyle = { + flexDirection: landscape ? 'column-reverse' : 'row', + paddingHorizontal: landscape ? 12 : 0, + paddingVertical: landscape ? 0 : 12, + paddingBottom: landscape ? 0 : Math.max(bottom, appTheme.spacing.lg), + }; return ( - + { + const dimensions = Dimensions.get('screen'); + return dimensions.height >= dimensions.width ? 'portrait' : 'landscape'; +}; + +/** + * A hook that returns device orientation. + * @returns 'portrait' : 'landscape' + */ +export const useOrientation = () => { + const [orientation, setOrientation] = useState(isPortrait()); + + useEffect(() => { + const updateOrientation = () => { + setOrientation(isPortrait()); + }; + + Dimensions.addEventListener('change', updateOrientation); + + return () => { + // @ts-ignore + Dimensions.removeEventListener('change', updateOrientation); + }; + }, []); + + return orientation; +}; diff --git a/sample-apps/react/egress-composite/.gitignore b/sample-apps/react/egress-composite/.gitignore index 1b6016cd16..d8594614e3 100644 --- a/sample-apps/react/egress-composite/.gitignore +++ b/sample-apps/react/egress-composite/.gitignore @@ -26,3 +26,6 @@ dist-ssr /test-results/ /playwright-report/ /playwright/.cache/ + +# Sentry Auth Token +.env.sentry-build-plugin diff --git a/sample-apps/react/egress-composite/README.md b/sample-apps/react/egress-composite/README.md index 7e5645c742..ccad81cada 100644 --- a/sample-apps/react/egress-composite/README.md +++ b/sample-apps/react/egress-composite/README.md @@ -5,18 +5,21 @@ A companion app in Call Recording process. ## Config This application is meant to be used in a browser window running in a virtualized environment [xvfb](https://en.wikipedia.org/wiki/Xvfb). -As such, this application accepts certain configuration parameters to be provided via a query parameter: +As such, this application accepts certain configuration parameters to be provided via JSONP (`window.setupLayout`). List of supported options (marked with ✅) can be found in the [`ConfigurationContext.tsx`](./src/ConfigurationContext.tsx). To test your custom options see the commented-out script at the bottom of the [`main.tsx`](./src/main.tsx) file. -| **Query Parameter** | **Description** | -| ------------------------ | ------------------------------------------------------ | -| `?call_id=` | The ID of the call to join. Defaults to `egress-test`. | -| `?call_type=` | The type of the call. Defaults to `default`. | -| `?api_key=` | The Stream API key. | -| `?token=` | The access user token. | -| `?base_url=` | The base URL of the Stream Coordinator API. | -| `?layout=` | The layout to use. Defaults to `dominant-speaker`. | +### Required configuration options -## Run +| **Parameter** | **Description** | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `call_id` | The ID of the call to join. | +| `api_key` | The Stream API key (if not provided, the application will try to load it from the environment variable `VITE_STREAM_API_KEY`). | +| `token` | The access user token (if not provided, the application will try to load it from the environment variable `VITE_STREAM_USER_TOKEN`). | +| `call_type` | The type of the call. Defaults to `default`. | -- `yarn start` +## To create and test your custom configuration + +- clone repository and run `yarn install` in the root folder (`stream-video-js/`) +- navigate to `sample-apps/react/egress-composite` folder +- run `yarn start` - open browser +- uncomment and tweak the setup script at the bottom of the [`main.tsx`](./src/main.tsx) file to your likings diff --git a/sample-apps/react/egress-composite/package.json b/sample-apps/react/egress-composite/package.json index 2b352fc1b3..3246a7c3b9 100644 --- a/sample-apps/react/egress-composite/package.json +++ b/sample-apps/react/egress-composite/package.json @@ -8,10 +8,12 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "test:e2e": "playwright test", - "buddy": "stream-video-buddy" + "test:e2e": "playwright test" }, "dependencies": { + "@emotion/css": "^11.11.2", + "@sentry/react": "^7.70.0", + "@sentry/vite-plugin": "^2.7.1", "@stream-io/video-react-sdk": "workspace:^", "clsx": "^1.2.1", "js-base64": "^3.7.5", @@ -25,7 +27,6 @@ "@vitejs/plugin-react": "^4.0.0", "axios": "^1.5.1", "nanoid": "^4.0.2", - "stream-video-buddy": "https://github.com/GetStream/stream-video-buddy#1.6.10", "typescript": "^4.9.5", "vercel": "^32.1.0", "vite": "^4.4.9" diff --git a/sample-apps/react/egress-composite/playwright.config.ts b/sample-apps/react/egress-composite/playwright.config.ts index 84b3b2438f..fb2d8808a9 100644 --- a/sample-apps/react/egress-composite/playwright.config.ts +++ b/sample-apps/react/egress-composite/playwright.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from '@playwright/test'; +import { defineConfig, devices } from '@playwright/test'; /** * See https://playwright.dev/docs/test-configuration. @@ -14,24 +14,40 @@ export default defineConfig({ expect: { toHaveScreenshot: { // to account for CI headless - maxDiffPixelRatio: 0.05, + // maxDiffPixelRatio: 0.02, }, }, - use: { - headless: !!process.env.CI, - trace: 'on-first-retry', - viewport: { width: 1920, height: 1080 }, - baseURL: 'http://localhost:5173', - // TODO: find out why custom data-test-id does not work - // testIdAttribute: 'data-testid', - }, - webServer: [ + projects: [ { - timeout: 10000, - command: 'yarn buddy server --port 4567', - reuseExistingServer: false, - port: 4567, + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + // TODO: find out why custom data-test-id does not work + // testIdAttribute: 'data-testid', + headless: true, // use --headed when debugging + trace: 'on-first-retry', + viewport: { width: 1920, height: 1080 }, + baseURL: 'http://localhost:5173', + launchOptions: { + args: [ + '--font-render-hinting=none', + '--disable-skia-runtime-opts', + '--disable-system-font-check', + '--disable-font-subpixel-positioning', + '--disable-lcd-text', + '--disable-remote-fonts', + ], + }, + }, }, + ], + webServer: [ + // { + // timeout: 10000, + // command: 'yarn buddy server --port 4567', + // reuseExistingServer: false, + // port: 4567, + // }, { timeout: 10000, command: 'yarn dev', diff --git a/sample-apps/react/egress-composite/public/example/custom.css b/sample-apps/react/egress-composite/public/example/custom.css new file mode 100644 index 0000000000..7e70cebd0d --- /dev/null +++ b/sample-apps/react/egress-composite/public/example/custom.css @@ -0,0 +1,44 @@ +.str-video { + background-color: blanchedalmond; +} + +.str-video .str-video__participant-view { + border-radius: 30px; +} + +.str-video .str-video__participant-view { + border-radius: 30px; +} + +.str-video__video-placeholder { + background-color: burlywood; +} + +.str-video__video-placeholder .str-video__video-placeholder__avatar { + border-radius: 30px; +} + +.str-video__video-placeholder .str-video__video-placeholder__initials-fallback { + border-radius: 30px; + background-color: bisque; + color: dimgray; +} + +.str-video__participant-view .str-video__participant-details { + background-color: bisque; + color: dimgray; + border-radius: 9999px; + padding: 5px; +} + +.str-video__participant-view + .str-video__participant-details + .str-video__participant-details__name { + padding: 0px; + border-radius: 0px; +} + +.str-video__participant-view--dominant-speaker + .str-video__participant-details::after { + content: '⭐️'; +} diff --git a/sample-apps/react/egress-composite/public/vite.svg b/sample-apps/react/egress-composite/public/vite.svg deleted file mode 100644 index ee9fadaf9c..0000000000 --- a/sample-apps/react/egress-composite/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sample-apps/react/egress-composite/src/CompositeApp.scss b/sample-apps/react/egress-composite/src/CompositeApp.scss index 8922fbdd31..ee0e82e86e 100644 --- a/sample-apps/react/egress-composite/src/CompositeApp.scss +++ b/sample-apps/react/egress-composite/src/CompositeApp.scss @@ -14,6 +14,7 @@ .str-video { color: var(--str-video__text-color1); position: relative; + height: 100vh; } body { diff --git a/sample-apps/react/egress-composite/src/CompositeApp.tsx b/sample-apps/react/egress-composite/src/CompositeApp.tsx index fde2b7cc71..f21cbecdd1 100644 --- a/sample-apps/react/egress-composite/src/CompositeApp.tsx +++ b/sample-apps/react/egress-composite/src/CompositeApp.tsx @@ -2,39 +2,63 @@ import { PropsWithChildren } from 'react'; import { StreamCallProvider, StreamTheme, - StreamVideo, + StreamVideoProvider, } from '@stream-io/video-react-sdk'; +import clsx from 'clsx'; import { EgressReadyNotificationProvider, useExternalCSS, + useGenericLayoutStyles, useInitializeClientAndCall, + useLogoAndTitleStyles, + useParticipantLabelStyles, + useVideoStyles, } from './hooks'; import { UIDispatcher, LogoAndTitleOverlay } from './components'; import './CompositeApp.scss'; +import { useParticipantStyles } from './hooks/options/useParticipantStyles'; export const CompositeApp = () => { const { client, call } = useInitializeClientAndCall(); return ( - - - - + + + + - - - {/* */} - - + + {/* */} + + + ); }; export const StreamThemeWrapper = ({ children }: PropsWithChildren) => { - // TODO: background style useExternalCSS(); - return {children}; + const videoStyles = useVideoStyles(); + const genericLayoutStyles = useGenericLayoutStyles(); + const participantStyles = useParticipantStyles(); + const participantLabelStyles = useParticipantLabelStyles(); + const logoAndTitleStyles = useLogoAndTitleStyles(); + + return ( + + {children} + + ); }; diff --git a/sample-apps/react/egress-composite/src/ConfigurationContext.tsx b/sample-apps/react/egress-composite/src/ConfigurationContext.tsx index 6a318cca8a..4b078b06d1 100644 --- a/sample-apps/react/egress-composite/src/ConfigurationContext.tsx +++ b/sample-apps/react/egress-composite/src/ConfigurationContext.tsx @@ -1,11 +1,37 @@ import { createContext, useContext } from 'react'; import { decode } from 'js-base64'; import { StreamVideoParticipant } from '@stream-io/video-react-sdk'; + import { Layout, ScreenshareLayout } from './components/layouts'; const DEFAULT_USER_ID = 'egress'; const DEFAULT_CALL_TYPE = 'default'; +type HorizontalPosition = 'center' | 'right' | 'left'; +type VerticalPosition = 'center' | 'top' | 'bottom'; +type ObjectFit = 'fit' | 'fill'; + +export const positionMap: { + vertical: Record; + horizontal: Record; +} = { + vertical: { + center: 'center', + top: 'start', + bottom: 'end', + }, + horizontal: { + center: 'center', + right: 'end', + left: 'start', + }, +}; + +export const objectFitMap: Record = { + fit: 'contain', + fill: 'cover', +}; + export type ConfigurationValue = { base_url?: string; @@ -25,41 +51,69 @@ export type ConfigurationValue = { }; options: { + // ✅ 'video.background_color'?: string; - 'video.scale_mode'?: 'fill' | 'fit'; - 'video.screenshare_scale_mode'?: 'fill' | 'fit'; + 'video.scale_mode'?: ObjectFit; + 'video.screenshare_scale_mode'?: ObjectFit; + // ✅ '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 + 'logo.width'?: string; + 'logo.height'?: string; + 'logo.horizontal_position'?: HorizontalPosition; + 'logo.vertical_position'?: VerticalPosition; + 'logo.margin_inline'?: string; + 'logo.margin_block'?: string; + + // ✅ + 'title.text'?: string; + 'title.font_size'?: string; + 'title.color'?: string; + 'title.horizontal_position'?: HorizontalPosition; + 'title.vertical_position'?: VerticalPosition; + 'title.margin_block'?: string; + 'title.margin_inline'?: string; + + // ✅ + 'participant.outline_color'?: string; + 'participant.outline_width'?: string; + 'participant.border_radius'?: string; 'participant.placeholder_background_color'?: string; + // ✅ + 'participant_label.display'?: boolean; + 'participant_label.text_color'?: string; + 'participant_label.background_color'?: string; + 'participant_label.border_width'?: string; + 'participant_label.border_radius'?: string; + 'participant_label.border_color'?: string; + 'participant_label.horizontal_position'?: HorizontalPosition; + 'participant_label.vertical_position'?: VerticalPosition; + 'participant_label.margin_inline'?: string; + 'participant_label.margin_block'?: string; + // used with any layout - 'layout.size_percentage'?: number; + 'layout.size_percentage'?: number; // ❌ + 'layout.background_color'?: string; // ✅ + 'layout.background_image'?: string; // ✅ + 'layout.background_size'?: string; // ✅ + 'layout.background_position'?: string; // ✅ + 'layout.background_repeat'?: string; // ✅ // grid-specific - 'layout.grid.gap'?: string; - 'layout.grid.page_size'?: number; + '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; + 'layout.single-participant.mode'?: 'shuffle' | 'default'; // ✅ + 'layout.single-participant.shuffle_delay'?: number; // ✅ + 'layout.single-participant.padding_inline'?: string; // ✅ + 'layout.single-participant.padding_block'?: string; // ✅ // spotlight-specific - 'layout.spotlight.bar_position'?: 'top' | 'right' | 'bottom' | 'left'; - 'layout.spotlight.bar_limit'?: number; + 'layout.spotlight.participants_bar_position'?: Exclude< + VerticalPosition | HorizontalPosition, + 'center' + >; // ✅ + 'layout.spotlight.participants_bar_limit'?: 'dynamic' | number; // ✅ }; }; @@ -67,13 +121,15 @@ export const ConfigurationContext = createContext( {} as ConfigurationValue, ); -export const extractPayloadFromToken = (token: string) => { +export const extractPayloadFromToken = ( + token: string, +): Record => { const [, payload] = token.split('.'); if (!payload) throw new Error('Malformed token, missing payload'); try { - return (JSON.parse(decode(payload)) ?? {}) as Record; + return JSON.parse(decode(payload)) ?? {}; } catch (e) { console.log('Error parsing token payload', e); return {}; @@ -89,8 +145,7 @@ export const applyConfigurationDefaults = ( // apply defaults api_key = import.meta.env.VITE_STREAM_API_KEY as string, token = import.meta.env.VITE_STREAM_USER_TOKEN as string, - user_id = (extractPayloadFromToken(token as string)['user_id'] ?? - DEFAULT_USER_ID) as string, + user_id = extractPayloadFromToken(token)['user_id'] ?? DEFAULT_USER_ID, call_type = DEFAULT_CALL_TYPE, options = {}, ...rest diff --git a/sample-apps/react/egress-composite/src/assets/react.svg b/sample-apps/react/egress-composite/src/assets/react.svg deleted file mode 100644 index 8e0e0f15c0..0000000000 --- a/sample-apps/react/egress-composite/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/sample-apps/react/egress-composite/src/components/LogoAndTitle.scss b/sample-apps/react/egress-composite/src/components/LogoAndTitle.scss new file mode 100644 index 0000000000..5ba76e02c8 --- /dev/null +++ b/sample-apps/react/egress-composite/src/components/LogoAndTitle.scss @@ -0,0 +1,17 @@ +.eca__logo-and-title-overlay { + position: absolute; + display: grid; + top: 0; + right: 0; + + width: 100%; + height: 100%; + + &__logo { + position: absolute; + } + &__title { + position: absolute; + } + } + \ No newline at end of file diff --git a/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx b/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx index 241f875fbb..84ba53e7a5 100644 --- a/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx +++ b/sample-apps/react/egress-composite/src/components/LogoAndTitleOverlay.tsx @@ -1,33 +1,30 @@ import { useConfigurationContext } from '../ConfigurationContext'; -export const LogoAndTitleOverlay = () => { - const { options } = useConfigurationContext(); +import './LogoAndTitle.scss'; - const image_url = options['logo.image_url']; +export const LogoAndTitleOverlay = () => { + const { + options: { 'logo.image_url': imageURL, 'title.text': titleText }, + } = useConfigurationContext(); return (
- {/* {text?.length && ( -
- {text} -
- )} */} - {image_url && ( + {titleText?.length && ( + + {titleText} + + )} + {imageURL && ( logo )} diff --git a/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx b/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx index 44d6395cdb..b3143fd68f 100644 --- a/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx +++ b/sample-apps/react/egress-composite/src/components/UIDispatcher.tsx @@ -1,12 +1,13 @@ import { useCallStateHooks } from '@stream-io/video-react-sdk'; import { useConfigurationContext } from '../ConfigurationContext'; -import { Layout, ScreenshareLayout, layoutMap } from './layouts'; +import { + DEFAULT_LAYOUT, + DEFAULT_SCREENSHARE_LAYOUT, + layoutMap, +} from './layouts'; import { Spotlight } from './layouts/Spotlight'; -const DEFAULT_LAYOUT: Layout = 'spotlight'; -const DEFAULT_SCREENSHARE_LAYOUT: ScreenshareLayout = 'spotlight'; - export const UIDispatcher = () => { const { layout = DEFAULT_LAYOUT, diff --git a/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx index c8de912634..834b58512b 100644 --- a/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx +++ b/sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/AudioTracks.tsx @@ -1,19 +1,19 @@ import { Audio, StreamVideoParticipant } from '@stream-io/video-react-sdk'; -export const AudioTracks = (props: { +export const AudioTracks = ({ + participants, + dominantSpeaker, +}: { participants: StreamVideoParticipant[]; dominantSpeaker?: StreamVideoParticipant; -}) => { - const { participants, dominantSpeaker } = props; - return ( -
- {participants.map((participant) => ( -
- ); -}; +}) => ( + <> + {participants.map((participant) => ( +