Skip to content

Commit c88bfa1

Browse files
committed
fix(filmstrip) Excludes partially visible tiles for dominant speaker slot.
1 parent 6a443b0 commit c88bfa1

File tree

5 files changed

+101
-12
lines changed

5 files changed

+101
-12
lines changed

react/features/filmstrip/actions.any.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,19 @@ export function setRemoteParticipants(participants: Array<string>) {
6060
*
6161
* @param {number} startIndex - The start index from the remote participants array.
6262
* @param {number} endIndex - The end index from the remote participants array.
63+
* @param {number} fullyVisibleCount - The number of fully visible participants (excluding partially visible).
6364
* @returns {{
6465
* type: SET_VISIBLE_REMOTE_PARTICIPANTS,
6566
* startIndex: number,
66-
* endIndex: number
67+
* endIndex: number,
68+
* fullyVisibleCount: number
6769
* }}
6870
*/
69-
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number) {
71+
export function setVisibleRemoteParticipants(startIndex: number, endIndex: number, fullyVisibleCount?: number) {
7072
return {
7173
type: SET_VISIBLE_REMOTE_PARTICIPANTS,
7274
startIndex,
73-
endIndex
75+
endIndex,
76+
fullyVisibleCount
7477
};
7578
}

react/features/filmstrip/components/native/Filmstrip.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
1010
import { getHideSelfView } from '../../../base/settings/functions.any';
1111
import { isToolboxVisible } from '../../../toolbox/functions.native';
1212
import { setVisibleRemoteParticipants } from '../../actions.native';
13+
import { calculateFullyVisibleParticipantsCount } from '../../functions.any';
1314
import {
1415
getFilmstripDimensions,
1516
isFilmstripVisible,
@@ -190,7 +191,7 @@ class Filmstrip extends PureComponent<IProps> {
190191
* @returns {void}
191192
*/
192193
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
193-
const { _disableSelfView } = this.props;
194+
const { _aspectRatio, _clientWidth, _clientHeight, _disableSelfView } = this.props;
194195

195196
if (!this._separateLocalThumbnail && !_disableSelfView && viewableItems[0]?.index === 0) {
196197
// Skip the local thumbnail.
@@ -205,13 +206,31 @@ class Filmstrip extends PureComponent<IProps> {
205206
let startIndex = Number(viewableItems[0].index);
206207
let endIndex = Number(viewableItems[viewableItems.length - 1].index);
207208

209+
// Calculate fully visible count (excluding partially visible tiles)
210+
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
211+
const { height: thumbnailHeight, width: thumbnailWidth, margin } = styles.thumbnail;
212+
const { height, width } = this._getDimensions();
213+
214+
// Calculate item size and container size based on layout orientation
215+
const itemSize = isNarrowAspectRatio
216+
? thumbnailWidth + (2 * margin) // Horizontal layout
217+
: thumbnailHeight + (2 * margin); // Vertical layout
218+
const containerSize = isNarrowAspectRatio ? width : height;
219+
220+
const fullyVisibleCount = calculateFullyVisibleParticipantsCount(
221+
startIndex,
222+
endIndex,
223+
containerSize,
224+
itemSize
225+
);
226+
208227
if (!this._separateLocalThumbnail && !_disableSelfView) {
209228
// We are off by one in the remote participants array.
210229
startIndex -= 1;
211230
endIndex -= 1;
212231
}
213232

214-
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
233+
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex, fullyVisibleCount));
215234
}
216235

217236
/**

react/features/filmstrip/components/web/Filmstrip.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
TOP_FILMSTRIP_HEIGHT,
4545
TOUCH_DRAG_HANDLE_PADDING
4646
} from '../../constants';
47+
import { calculateFullyVisibleParticipantsCount } from '../../functions.any';
4748
import {
4849
getVerticalViewMaxWidth,
4950
isFilmstripDisabled,
@@ -944,10 +945,33 @@ class Filmstrip extends PureComponent <IProps, IState> {
944945
*/
945946
_onListItemsRendered({ visibleStartIndex, visibleStopIndex }: {
946947
visibleStartIndex: number; visibleStopIndex: number; }) {
947-
const { dispatch } = this.props;
948+
const {
949+
dispatch,
950+
_currentLayout,
951+
_filmstripWidth,
952+
_filmstripHeight,
953+
_thumbnailWidth,
954+
_thumbnailHeight,
955+
_isVerticalFilmstrip
956+
} = this.props;
957+
958+
// Calculate fully visible count (excluding partially visible tiles)
959+
const isHorizontal = _currentLayout === LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW;
960+
const itemSize = isHorizontal
961+
? _thumbnailWidth + TILE_HORIZONTAL_MARGIN
962+
: _thumbnailHeight + TILE_VERTICAL_MARGIN;
963+
const containerSize = isHorizontal ? _filmstripWidth : _filmstripHeight;
964+
965+
const fullyVisibleCount = calculateFullyVisibleParticipantsCount(
966+
visibleStartIndex,
967+
visibleStopIndex,
968+
containerSize,
969+
itemSize
970+
);
971+
948972
const { startIndex, stopIndex } = this._calculateIndices(visibleStartIndex, visibleStopIndex);
949973

950-
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex));
974+
dispatch(setVisibleRemoteParticipants(startIndex, stopIndex, fullyVisibleCount));
951975
}
952976

953977
/**

react/features/filmstrip/functions.any.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
4545
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
4646
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
4747
const speakers = new Array<string>();
48-
const { visibleRemoteParticipants } = state['features/filmstrip'];
48+
const { fullyVisibleRemoteParticipantsCount } = state['features/filmstrip'];
4949

5050
const participantsWithScreenShare = screenShareParticipants.reduce<string[]>((acc, screenshare) => {
5151
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
@@ -69,9 +69,13 @@ export function updateRemoteParticipants(store: IStore, force?: boolean, partici
6969
speakers.push(dominant.id);
7070
}
7171

72-
// Find the number of slots available for speakers.
72+
// Find the number of slots available for speakers. Use fullyVisibleRemoteParticipantsCount to exclude partially
73+
// visible tiles, ensuring dominant speaker is placed on a fully visible tile.
7374
const slotsForSpeakers
74-
= visibleRemoteParticipants.size - (screenShareParticipants.length * 2) - sharedVideos.length - dominantSpeakerSlot;
75+
= fullyVisibleRemoteParticipantsCount
76+
- (screenShareParticipants.length * 2)
77+
- sharedVideos.length
78+
- dominantSpeakerSlot;
7579

7680
// Construct the list of speakers to be shown.
7781
if (slotsForSpeakers > 0) {
@@ -127,3 +131,31 @@ export function isTileViewModeDisabled(state: IReduxState) {
127131

128132
return tileView.disabled;
129133
}
134+
135+
/**
136+
* Calculates the count of fully visible participants, excluding any partially visible tiles.
137+
* This respects the actual rendered items from the list component while accounting for
138+
* container padding/gaps.
139+
*
140+
* @param {number} visibleStartIndex - The start index of visible items.
141+
* @param {number} visibleEndIndex - The end index of visible items.
142+
* @param {number} containerSize - The width or height of the filmstrip container.
143+
* @param {number} itemSize - The width or height of each item including margin.
144+
* @returns {number} - The count of fully visible participants (at least 1).
145+
*/
146+
export function calculateFullyVisibleParticipantsCount(
147+
visibleStartIndex: number,
148+
visibleEndIndex: number,
149+
containerSize: number,
150+
itemSize: number
151+
): number {
152+
// Current visible count from the list component (includes any partially visible tile)
153+
const currentVisibleCount = visibleEndIndex - visibleStartIndex + 1;
154+
155+
// Theoretical max that can fit fully in the container
156+
const maxFullyVisible = Math.floor(containerSize / itemSize);
157+
158+
// Fully visible count is the minimum of actual visible and max that can fit fully
159+
// Ensure at least 1 if there are any visible items
160+
return Math.max(1, Math.min(currentVisibleCount, maxFullyVisible));
161+
}

react/features/filmstrip/reducer.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ const DEFAULT_STATE = {
158158
*/
159159
visibleRemoteParticipants: new Set<string>(),
160160

161+
/**
162+
* The number of fully visible remote participants (excluding partially visible ones).
163+
* Used for calculating speaker slots to avoid placing dominant speaker on partially visible tiles.
164+
*
165+
* @public
166+
* @type {number}
167+
*/
168+
fullyVisibleRemoteParticipantsCount: 0,
169+
161170
/**
162171
* The width of the resizable filmstrip.
163172
*
@@ -201,6 +210,7 @@ export interface IFilmstripState {
201210
pinned?: boolean;
202211
}>;
203212
enabled: boolean;
213+
fullyVisibleRemoteParticipantsCount: number;
204214
horizontalViewDimensions: {
205215
hasScroll?: boolean;
206216
local?: IDimensions;
@@ -301,15 +311,16 @@ ReducerRegistry.register<IFilmstripState>(
301311
}
302312
};
303313
case SET_VISIBLE_REMOTE_PARTICIPANTS: {
304-
const { endIndex, startIndex } = action;
314+
const { endIndex, startIndex, fullyVisibleCount } = action;
305315
const { remoteParticipants } = state;
306316
const visibleRemoteParticipants = new Set(remoteParticipants.slice(startIndex, endIndex + 1));
307317

308318
return {
309319
...state,
310320
visibleParticipantsStartIndex: startIndex,
311321
visibleParticipantsEndIndex: endIndex,
312-
visibleRemoteParticipants
322+
visibleRemoteParticipants,
323+
fullyVisibleRemoteParticipantsCount: fullyVisibleCount ?? visibleRemoteParticipants.size
313324
};
314325
}
315326
case PARTICIPANT_LEFT: {

0 commit comments

Comments
 (0)