Skip to content

Commit 5a213d2

Browse files
authored
feat: adaptive floating video dimensions (#1969)
### 💡 Overview Currently our floating video dimensions are hardcoded. This looks fine in a iPhone Xs phone. Anything taller or wider, it looks small. Our dogfood app had a custom implementation based on window dimensions so we didnt use it. But this is also flawed, as this didnt adapt to portrait to landscape change. ### 📝 Implementation notes I implemented the Android AOSP PiP mode algorithm. Which is based on the video dimensions. Now it works well on larger devices like iPad too. Looks similar to Facetime behaviour now. If the video changes to landscape, will adapt to that as well now. <img width="820" height="1180" alt="IMG_0016" src="https://github.com/user-attachments/assets/fa104fc6-e04c-42ef-b071-fd8ef7178818" /> <img width="1180" height="820" alt="IMG_0017" src="https://github.com/user-attachments/assets/5879e7c4-3c86-4fbd-9671-e705cc4035fa" />
1 parent 9b7ff31 commit 5a213d2

File tree

5 files changed

+58
-25
lines changed

5 files changed

+58
-25
lines changed

packages/react-native-sdk/src/components/Participant/FloatingParticipantView/index.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
View,
77
type ViewStyle,
88
} from 'react-native';
9-
import { FLOATING_VIDEO_VIEW_STYLE, Z_INDEX } from '../../../constants';
9+
import { Z_INDEX } from '../../../constants';
1010
import { ComponentTestIds } from '../../../constants/TestIds';
1111
import { VideoSlash } from '../../../icons';
1212
import FloatingView from './FloatingView';
@@ -21,6 +21,7 @@ import {
2121
type ParticipantViewProps,
2222
} from '../ParticipantView';
2323
import { useTheme } from '../../../contexts/ThemeContext';
24+
import { useFloatingVideoDimensions } from './useFloatingVideoDimensions';
2425
import { type StreamVideoParticipant } from '@stream-io/video-client';
2526

2627
export type FloatingParticipantViewAlignment =
@@ -104,7 +105,11 @@ export const FloatingParticipantView = ({
104105
objectFit,
105106
}: FloatingParticipantViewProps) => {
106107
const {
107-
theme: { colors, floatingParticipantsView },
108+
theme: {
109+
colors,
110+
floatingParticipantsView,
111+
variants: { spacingSizes },
112+
},
108113
} = useTheme();
109114

110115
const floatingAlignmentMap: Record<
@@ -122,6 +127,12 @@ export const FloatingParticipantView = ({
122127
height: number;
123128
}>();
124129

130+
const floatingVideoDimensions = useFloatingVideoDimensions(
131+
containerDimensions,
132+
participant,
133+
'videoTrack',
134+
);
135+
125136
const participantViewProps: ParticipantViewComponentProps = {
126137
ParticipantLabel: null,
127138
ParticipantNetworkQualityIndicator,
@@ -158,7 +169,7 @@ export const FloatingParticipantView = ({
158169
});
159170
}}
160171
>
161-
{containerDimensions && (
172+
{containerDimensions && floatingVideoDimensions && (
162173
<FloatingView
163174
containerHeight={containerDimensions.height}
164175
containerWidth={containerDimensions.width}
@@ -171,6 +182,12 @@ export const FloatingParticipantView = ({
171182
trackType="videoTrack"
172183
style={[
173184
styles.participantViewContainer,
185+
{
186+
width: floatingVideoDimensions.width,
187+
height: floatingVideoDimensions.height,
188+
borderRadius: floatingVideoDimensions.width * 0.1,
189+
marginHorizontal: spacingSizes.md,
190+
},
174191
participantViewStyle,
175192
{ shadowColor: colors.sheetPrimary },
176193
floatingParticipantsView.participantViewContainer,
@@ -197,9 +214,6 @@ const styles = StyleSheet.create({
197214
flex: 1,
198215
},
199216
participantViewContainer: {
200-
height: FLOATING_VIDEO_VIEW_STYLE.height,
201-
width: FLOATING_VIDEO_VIEW_STYLE.width,
202-
borderRadius: FLOATING_VIDEO_VIEW_STYLE.borderRadius,
203217
shadowOffset: {
204218
width: 0,
205219
height: 2,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {
2+
StreamVideoParticipant,
3+
VideoTrackType,
4+
} from '@stream-io/video-client';
5+
import { useTrackDimensions } from '../../../hooks/useTrackDimensions';
6+
7+
export const useFloatingVideoDimensions = (
8+
containerDimensions:
9+
| {
10+
width: number;
11+
height: number;
12+
}
13+
| undefined,
14+
participant: StreamVideoParticipant | undefined,
15+
trackType: VideoTrackType,
16+
) => {
17+
const containerWidth = containerDimensions?.width ?? 0;
18+
const { width, height } = useTrackDimensions(participant, trackType);
19+
20+
if (width === 0 || height === 0 || containerWidth === 0) {
21+
return undefined;
22+
}
23+
24+
const aspectRatio = width / height;
25+
26+
// based on Android AOSP PiP mode default dimensions algorithm
27+
// 23% of the container width
28+
const floatingVideoWidth = containerWidth * 0.23;
29+
// the height is calculated based on the aspect ratio
30+
const floatingVideoHeight = floatingVideoWidth / aspectRatio;
31+
32+
return {
33+
width: floatingVideoWidth,
34+
height: floatingVideoHeight,
35+
};
36+
};

packages/react-native-sdk/src/constants/index.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { type StreamReactionType } from '../components';
22

3-
export const FLOATING_VIDEO_VIEW_STYLE = {
4-
height: 140,
5-
width: 80,
6-
borderRadius: 10,
7-
};
8-
93
export const defaultEmojiReactions: StreamReactionType[] = [
104
{
115
type: 'reaction',

packages/react-native-sdk/src/hooks/useTrackDimensions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import { NativeEventEmitter, NativeModules } from 'react-native';
1111
* `tracktype` should be 'videoTrack' for video track and 'screenShareTrack' for screen share track.
1212
*/
1313
export function useTrackDimensions(
14-
participant: StreamVideoParticipant,
14+
participant: StreamVideoParticipant | undefined,
1515
trackType: VideoTrackType,
1616
) {
17-
const { videoStream, screenShareStream } = participant;
17+
const { videoStream, screenShareStream } = participant || {};
1818
const stream =
1919
trackType === 'screenShareTrack' ? screenShareStream : videoStream;
2020
const [track] = stream?.getVideoTracks() ?? [];

sample-apps/react-native/dogfood/src/theme.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,6 @@ export const useCustomTheme = (mode: ThemeMode): DeepPartial<Theme> => {
5252
container: { paddingTop: 0, paddingBottom: 0, flexDirection: 'column' },
5353
};
5454

55-
const { height, width } = Dimensions.get('window');
56-
const floatingParticipantsView: DeepPartial<
57-
Theme['floatingParticipantsView']
58-
> = {
59-
participantViewContainer: {
60-
height: height * 0.2,
61-
width: width * 0.25,
62-
},
63-
};
64-
6555
const lightThemeColors: DeepPartial<Theme['colors']> = {
6656
buttonPrimary: '#3399ff',
6757
buttonSecondary: '#eff0f1',
@@ -82,7 +72,6 @@ export const useCustomTheme = (mode: ThemeMode): DeepPartial<Theme> => {
8272
const baseTheme: DeepPartial<Theme> = {
8373
variants,
8474
callContent,
85-
floatingParticipantsView,
8675
};
8776

8877
if (mode === 'light') {

0 commit comments

Comments
 (0)