Skip to content

Commit 583b3dc

Browse files
committed
fix(android): match reuse check by camera facing direction instead of first-found
1 parent f76e413 commit 583b3dc

File tree

1 file changed

+25
-26
lines changed

1 file changed

+25
-26
lines changed

android/src/main/java/com/cloudwebrtc/webrtc/GetUserMediaImpl.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -703,32 +703,6 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi
703703

704704
Log.i(TAG, "getUserMedia(video): " + videoConstraintsMap);
705705

706-
// If a camera is already active, reuse its VideoSource instead of opening a second
707-
// Camera2 session for the same camera device.
708-
//
709-
// When the same camera is opened twice from one process, cameraserver "steals" it from
710-
// the first client: the first CaptureSession receives onDisconnected(), which internally
711-
// calls close() -> stopRepeating() -> cancelRequest(). The camera HAL returns ENOSYS
712-
// (-38 / "Function not implemented") for cancelRequest when the pipeline is already being
713-
// torn down, and Android's CameraCaptureSessionImpl does not handle that error - it
714-
// propagates as CameraAccessException(CAMERA_ERROR=3).
715-
//
716-
// This is reproducible on stock AOSP (Pixel) and any Android device: it is a bug in the
717-
// Android Camera2 framework (CameraCaptureSessionImpl.onDisconnected does not guard
718-
// against ENOSYS from cancelRequest), not an OEM-specific issue.
719-
// NOTE: reuses the first active primary camera entry regardless of camera ID (front/back).
720-
// This is intentional for the current use case where all calls share the same camera.
721-
// If a future use case requires different calls to use different cameras simultaneously,
722-
// add a cameraName check here before reusing.
723-
for (Map.Entry<String, VideoCapturerInfoEx> entry : mVideoCapturers.entrySet()) {
724-
VideoCapturerInfoEx existing = entry.getValue();
725-
if (!existing.isScreenCapture && existing.primaryTrackId == null && existing.videoSource != null) {
726-
Log.w(TAG, "getUserMedia(video): camera already active (track=" + entry.getKey()
727-
+ "), reusing VideoSource to prevent concurrent camera access");
728-
return buildSharedVideoTrack(existing, entry.getKey(), mediaStream);
729-
}
730-
}
731-
732706
// NOTE: to support Camera2, the device should:
733707
// 1. Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
734708
// 2. all camera support level should greater than LEGACY
@@ -748,6 +722,31 @@ private ConstraintsMap getUserVideo(ConstraintsMap constraints, MediaStream medi
748722
String facingMode = getFacingMode(videoConstraintsMap);
749723
isFacing = facingMode == null || !facingMode.equals("environment");
750724
String deviceId = getSourceIdConstraint(videoConstraintsMap);
725+
726+
// If a camera is already active for the same facing direction, reuse its VideoSource
727+
// instead of opening a second Camera2 session for the same camera device.
728+
//
729+
// When the same camera is opened twice from one process, cameraserver "steals" it from
730+
// the first client: the first CaptureSession receives onDisconnected(), which internally
731+
// calls close() -> stopRepeating() -> cancelRequest(). The camera HAL returns ENOSYS
732+
// (-38 / "Function not implemented") for cancelRequest when the pipeline is already being
733+
// torn down, and Android's CameraCaptureSessionImpl does not handle that error - it
734+
// propagates as CameraAccessException(CAMERA_ERROR=3).
735+
//
736+
// This is reproducible on stock AOSP (Pixel) and any Android device: it is a bug in the
737+
// Android Camera2 framework (CameraCaptureSessionImpl.onDisconnected does not guard
738+
// against ENOSYS from cancelRequest), not an OEM-specific issue.
739+
final boolean requestedFacingFront = isFacing;
740+
for (Map.Entry<String, VideoCapturerInfoEx> entry : mVideoCapturers.entrySet()) {
741+
VideoCapturerInfoEx existing = entry.getValue();
742+
if (!existing.isScreenCapture && existing.primaryTrackId == null
743+
&& existing.videoSource != null && existing.cameraName != null
744+
&& cameraEnumerator.isFrontFacing(existing.cameraName) == requestedFacingFront) {
745+
Log.w(TAG, "getUserMedia(video): camera already active (track=" + entry.getKey()
746+
+ "), reusing VideoSource to prevent concurrent camera access");
747+
return buildSharedVideoTrack(existing, entry.getKey(), mediaStream);
748+
}
749+
}
751750
CameraEventsHandler cameraEventsHandler = new CameraEventsHandler();
752751
Pair<String, VideoCapturer> result = createVideoCapturer(cameraEnumerator, isFacing, deviceId, cameraEventsHandler);
753752

0 commit comments

Comments
 (0)