Skip to content

Commit 830193a

Browse files
authored
fix(llc): fix camera flip (#1094)
* webrtc m137 wip * fix camera flip * tweaks * changelog
1 parent 247aec8 commit 830193a

File tree

7 files changed

+87
-20
lines changed

7 files changed

+87
-20
lines changed

packages/stream_video/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Web] Fixed changing the output audio device during the call.
66
- [Android/iOS] Fixed an issue where screen sharing was not stopped correctly when canceled via the system UI on Android or iOS.
77
- [iOS] Improved broadcast extension handling — the app now waits for the broadcast picker selection before actually starting screen sharing.
8+
- Resolved an issue where the camera wouldn’t flip correctly if the back camera was selected initially.
89
- Fixed an issue where `callMembers` collection wasn't reflecting the actual members list after starting the call session.
910

1011
✅ Added

packages/stream_video/lib/src/call/call.dart

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,10 +2791,13 @@ class Call {
27912791

27922792
if (result.isSuccess) {
27932793
_connectOptions = connectOptions.copyWith(videoInputDevice: device);
2794-
_stateManager.participantSetVideoInputDevice(device: device);
2794+
_stateManager.participantSetVideoInputDevice(
2795+
device: device,
2796+
track: result.getDataOrNull()!,
2797+
);
27952798
}
27962799

2797-
return result;
2800+
return result.map((_) => none);
27982801
}
27992802

28002803
Future<Result<None>> setCameraEnabled({
@@ -2832,11 +2835,17 @@ class Call {
28322835
iOSMultitaskingCameraAccessEnabled: multitaskingEnabled,
28332836
);
28342837

2838+
var facingMode = constraints?.facingMode;
2839+
if (facingMode == null && result.getDataOrNull() is RtcLocalCameraTrack) {
2840+
final track = result.getDataOrNull()! as RtcLocalCameraTrack;
2841+
facingMode = track.mediaConstraints.facingMode;
2842+
}
2843+
28352844
_connectOptions = _connectOptions.copyWith(
28362845
camera: enabled
28372846
? TrackOption.enabled(constraints: constraints)
28382847
: TrackOption.disabled(),
2839-
cameraFacingMode: constraints?.facingMode ?? FacingMode.user,
2848+
cameraFacingMode: facingMode ?? _connectOptions.cameraFacingMode,
28402849
);
28412850
}
28422851

packages/stream_video/lib/src/call/session/call_session.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,14 +1110,15 @@ class CallSession extends Disposable {
11101110
return result;
11111111
}
11121112

1113-
Future<Result<None>> setVideoInputDevice(RtcMediaDevice device) async {
1113+
Future<Result<RtcLocalTrack<CameraConstraints>>> setVideoInputDevice(
1114+
RtcMediaDevice device,
1115+
) async {
11141116
final rtcManager = this.rtcManager;
11151117
if (rtcManager == null) {
11161118
return Result.error('Unable to set video input, Call not connected');
11171119
}
11181120

1119-
final result = await rtcManager.setVideoInputDevice(device: device);
1120-
return result.map((_) => none);
1121+
return rtcManager.setVideoInputDevice(device: device);
11211122
}
11221123

11231124
Future<Result<None>> setCameraPosition(CameraPosition position) async {

packages/stream_video/lib/src/call/state/mixins/state_participant_mixin.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import '../../../models/call_track_state.dart';
77
import '../../../models/viewport_visibility.dart';
88
import '../../../sfu/data/models/sfu_track_type.dart';
99
import '../../../webrtc/media/constraints/camera_position.dart';
10+
import '../../../webrtc/media/media_constraints.dart';
1011
import '../../../webrtc/model/rtc_video_dimension.dart';
1112
import '../../../webrtc/rtc_media_device/rtc_media_device.dart';
13+
import '../../../webrtc/rtc_track/rtc_local_track.dart';
1214

1315
final _logger = taggedLogger(tag: 'SV:CoordNotifier');
1416

@@ -246,7 +248,13 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
246248

247249
void participantSetVideoInputDevice({
248250
required RtcMediaDevice device,
251+
required RtcLocalTrack<CameraConstraints> track,
249252
}) {
253+
final facingMode = track.mediaConstraints.facingMode;
254+
final cameraPosition = facingMode == FacingMode.user
255+
? CameraPosition.front
256+
: CameraPosition.back;
257+
250258
state = state.copyWith(
251259
videoInputDevice: device,
252260
callParticipants: state.callParticipants.map((participant) {
@@ -259,7 +267,7 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
259267
SfuTrackType.video: trackState.copyWith(
260268
sourceDevice: device,
261269
// reset camera position to default
262-
cameraPosition: CameraPosition.front,
270+
cameraPosition: cameraPosition,
263271
),
264272
},
265273
);

packages/stream_video/lib/src/webrtc/rtc_track/rtc_local_track.dart

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,21 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
7171
throw VideoException('No camera track found');
7272
}
7373

74+
final detectedFacingMode = _detectFacingModeFromTrack(
75+
videoTrack,
76+
fallbackFacingMode: constraints.facingMode,
77+
);
78+
79+
final updatedConstraints = constraints.copyWith(
80+
facingMode: detectedFacingMode,
81+
);
82+
7483
final track = RtcLocalTrack(
7584
trackIdPrefix: trackIdPrefix,
7685
trackType: SfuTrackType.video,
7786
mediaStream: stream,
7887
mediaTrack: videoTrack,
79-
mediaConstraints: constraints,
88+
mediaConstraints: updatedConstraints,
8089
);
8190

8291
return track;
@@ -254,6 +263,46 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
254263

255264
const _cameraTag = 'SV:RtcLocalCameraTrack';
256265

266+
/// Detects the facing mode from a video track.
267+
FacingMode _detectFacingModeFromTrack(
268+
rtc.MediaStreamTrack videoTrack, {
269+
required FacingMode fallbackFacingMode,
270+
String? deviceLabel,
271+
}) {
272+
try {
273+
final settings = videoTrack.getSettings();
274+
final settingsFacingMode = settings['facingMode'];
275+
276+
if (settingsFacingMode != null) {
277+
return FacingMode.fromAlias(settingsFacingMode);
278+
} else {
279+
// Try to infer from device label if available
280+
if (deviceLabel != null) {
281+
return _inferFacingModeFromLabel(deviceLabel, fallbackFacingMode);
282+
}
283+
}
284+
} catch (e) {
285+
if (deviceLabel != null) {
286+
return _inferFacingModeFromLabel(deviceLabel, fallbackFacingMode);
287+
}
288+
}
289+
290+
return fallbackFacingMode;
291+
}
292+
293+
FacingMode _inferFacingModeFromLabel(String label, FacingMode fallback) {
294+
final lowerLabel = label.toLowerCase();
295+
if (lowerLabel.contains('back') ||
296+
lowerLabel.contains('rear') ||
297+
lowerLabel.contains('environment')) {
298+
return FacingMode.environment;
299+
} else if (lowerLabel.contains('front')) {
300+
return FacingMode.user;
301+
}
302+
303+
return fallback;
304+
}
305+
257306
extension RtcLocalCameraTrackHardwareExt on RtcLocalCameraTrack {
258307
Future<RtcLocalCameraTrack> flipCamera() async {
259308
streamLog.i(_cameraTag, () => 'Flipping camera');
@@ -312,16 +361,12 @@ extension RtcLocalCameraTrackHardwareExt on RtcLocalCameraTrack {
312361
),
313362
);
314363

315-
// Default to user facing mode.
316-
var facingMode = FacingMode.user;
317-
318-
// Use the facingMode from the track settings if available.
319-
try {
320-
final settings = updatedTrack.mediaTrack.getSettings();
321-
facingMode = FacingMode.fromAlias(settings['facingMode']);
322-
} catch (e) {
323-
// ignore
324-
}
364+
// Detect the actual facing mode from the track.
365+
final facingMode = _detectFacingModeFromTrack(
366+
updatedTrack.mediaTrack,
367+
fallbackFacingMode: mediaConstraints.facingMode,
368+
deviceLabel: device.label,
369+
);
325370

326371
return updatedTrack.copyWith(
327372
mediaConstraints: updatedTrack.mediaConstraints.copyWith(

packages/stream_video_flutter/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
- [Web] Fixed changing the output audio device during the call.
66
- [Android/iOS] Fixed an issue where screen sharing was not stopped correctly when canceled via the system UI on Android or iOS.
77
- [iOS] Improved broadcast extension handling — the app now waits for the broadcast picker selection before actually starting screen sharing.
8+
- Resolved an issue where the camera wouldn’t flip correctly if the back camera was selected initially.
9+
- Fixed an issue where `callMembers` collection wasn't reflecting the actual members list after starting the call session.
810

911
✅ Added
1012
- [Web] Added `checkIfAudioOutputChangeSupported()` to the `Call` class to check whether the browser supports changing the audio output device.
11-
=======
1213

1314
## 0.11.1
1415

packages/stream_video_flutter/lib/src/call_screen/lobby_video.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ class _StreamLobbyVideoState extends State<StreamLobbyVideo> {
149149
children: [
150150
if (cameraEnabled)
151151
VideoTrackRenderer(
152-
mirror: true,
152+
mirror:
153+
_cameraTrack!.mediaConstraints.facingMode ==
154+
FacingMode.user,
153155
videoTrack: _cameraTrack!,
154156
placeholderBuilder: placeHolderBuilder,
155157
)

0 commit comments

Comments
 (0)