Skip to content

Commit 3871348

Browse files
After AOS 14, changeCaptureFormat() should resize the VirtualDisplay instead of recreating it due to the limitation on reusing MediaProjectionPermissionResultData. And it is necessary to obtain capture permission first and request foreground service; otherwise, the app crashes due to the media projection permission policy. (flutter-webrtc#1552)
Use Helper.requestCapturePermission() to obtain capture permission on Android 14 specially targetSdk 34 or above.
1 parent 33c9a3e commit 3871348

File tree

4 files changed

+271
-125
lines changed

4 files changed

+271
-125
lines changed

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

+115-92
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ class GetUserMediaImpl {
115115
private final SparseArray<MediaRecorderImpl> mediaRecorders = new SparseArray<>();
116116
private AudioDeviceInfo preferredInput = null;
117117
private boolean isTorchOn;
118+
private Intent mediaProjectionData = null;
118119

119120
public void screenRequestPermissions(ResultReceiver resultReceiver) {
121+
mediaProjectionData = null;
120122
final Activity activity = stateProvider.getActivity();
121123
if (activity == null) {
122124
// Activity went away, nothing we can do.
@@ -143,6 +145,22 @@ public void screenRequestPermissions(ResultReceiver resultReceiver) {
143145
}
144146
}
145147

148+
public void requestCapturePermission(final Result result) {
149+
screenRequestPermissions(
150+
new ResultReceiver(new Handler(Looper.getMainLooper())) {
151+
@Override
152+
protected void onReceiveResult(int requestCode, Bundle resultData) {
153+
int resultCode = resultData.getInt(GRANT_RESULTS);
154+
if (resultCode == Activity.RESULT_OK) {
155+
mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
156+
result.success(true);
157+
} else {
158+
result.success(false);
159+
}
160+
}
161+
});
162+
}
163+
146164
public static class ScreenRequestPermissionsFragment extends Fragment {
147165

148166
private ResultReceiver resultReceiver = null;
@@ -474,116 +492,121 @@ public void invoke(Object... args) {
474492

475493
void getDisplayMedia(
476494
final ConstraintsMap constraints, final Result result, final MediaStream mediaStream) {
495+
if (mediaProjectionData == null) {
496+
screenRequestPermissions(
497+
new ResultReceiver(new Handler(Looper.getMainLooper())) {
498+
@Override
499+
protected void onReceiveResult(int requestCode, Bundle resultData) {
500+
Intent mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
501+
int resultCode = resultData.getInt(GRANT_RESULTS);
502+
503+
if (resultCode != Activity.RESULT_OK) {
504+
resultError("screenRequestPermissions", "User didn't give permission to capture the screen.", result);
505+
return;
506+
}
507+
getDisplayMedia(result, mediaStream, mediaProjectionData);
508+
}
509+
});
510+
} else {
511+
getDisplayMedia(result, mediaStream, mediaProjectionData);
512+
}
513+
}
477514

478-
screenRequestPermissions(
479-
new ResultReceiver(new Handler(Looper.getMainLooper())) {
480-
@Override
481-
protected void onReceiveResult(int requestCode, Bundle resultData) {
515+
private void getDisplayMedia(final Result result, final MediaStream mediaStream, final Intent mediaProjectionData) {
516+
/* Create ScreenCapture */
517+
MediaStreamTrack[] tracks = new MediaStreamTrack[1];
518+
VideoCapturer videoCapturer = null;
519+
videoCapturer =
520+
new OrientationAwareScreenCapturer(
521+
mediaProjectionData,
522+
new MediaProjection.Callback() {
523+
@Override
524+
public void onStop() {
525+
super.onStop();
526+
// After Huawei P30 and Android 10 version test, the onstop method is called, which will not affect the next process,
527+
// and there is no need to call the resulterror method
528+
//resultError("MediaProjection.Callback()", "User revoked permission to capture the screen.", result);
529+
}
530+
});
531+
if (videoCapturer == null) {
532+
resultError("screenRequestPermissions", "GetDisplayMediaFailed, User revoked permission to capture the screen.", result);
533+
return;
534+
}
482535

483-
/* Create ScreenCapture */
484-
int resultCode = resultData.getInt(GRANT_RESULTS);
485-
Intent mediaProjectionData = resultData.getParcelable(PROJECTION_DATA);
536+
PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
537+
VideoSource videoSource = pcFactory.createVideoSource(true);
486538

487-
if (resultCode != Activity.RESULT_OK) {
488-
resultError("screenRequestPermissions", "User didn't give permission to capture the screen.", result);
489-
return;
490-
}
539+
String threadName = Thread.currentThread().getName() + "_texture_screen_thread";
540+
SurfaceTextureHelper surfaceTextureHelper =
541+
SurfaceTextureHelper.create(threadName, EglUtils.getRootEglBaseContext());
542+
videoCapturer.initialize(
543+
surfaceTextureHelper, applicationContext, videoSource.getCapturerObserver());
491544

492-
MediaStreamTrack[] tracks = new MediaStreamTrack[1];
493-
VideoCapturer videoCapturer = null;
494-
videoCapturer =
495-
new OrientationAwareScreenCapturer(
496-
mediaProjectionData,
497-
new MediaProjection.Callback() {
498-
@Override
499-
public void onStop() {
500-
super.onStop();
501-
// After Huawei P30 and Android 10 version test, the onstop method is called, which will not affect the next process,
502-
// and there is no need to call the resulterror method
503-
//resultError("MediaProjection.Callback()", "User revoked permission to capture the screen.", result);
504-
}
505-
});
506-
if (videoCapturer == null) {
507-
resultError("screenRequestPermissions", "GetDisplayMediaFailed, User revoked permission to capture the screen.", result);
508-
return;
509-
}
545+
WindowManager wm =
546+
(WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
510547

511-
PeerConnectionFactory pcFactory = stateProvider.getPeerConnectionFactory();
512-
VideoSource videoSource = pcFactory.createVideoSource(true);
548+
Display display = wm.getDefaultDisplay();
549+
Point size = new Point();
550+
display.getRealSize(size);
513551

514-
String threadName = Thread.currentThread().getName() + "_texture_screen_thread";
515-
SurfaceTextureHelper surfaceTextureHelper =
516-
SurfaceTextureHelper.create(threadName, EglUtils.getRootEglBaseContext());
517-
videoCapturer.initialize(
518-
surfaceTextureHelper, applicationContext, videoSource.getCapturerObserver());
552+
VideoCapturerInfo info = new VideoCapturerInfo();
553+
info.width = size.x;
554+
info.height = size.y;
555+
info.fps = DEFAULT_FPS;
556+
info.isScreenCapture = true;
557+
info.capturer = videoCapturer;
519558

520-
WindowManager wm =
521-
(WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
559+
videoCapturer.startCapture(info.width, info.height, info.fps);
560+
Log.d(TAG, "OrientationAwareScreenCapturer.startCapture: " + info.width + "x" + info.height + "@" + info.fps);
522561

523-
Display display = wm.getDefaultDisplay();
524-
Point size = new Point();
525-
display.getRealSize(size);
562+
String trackId = stateProvider.getNextTrackUUID();
563+
mVideoCapturers.put(trackId, info);
526564

527-
VideoCapturerInfo info = new VideoCapturerInfo();
528-
info.width= size.x;
529-
info.height = size.y;
530-
info.fps = DEFAULT_FPS;
531-
info.isScreenCapture = true;
532-
info.capturer = videoCapturer;
565+
tracks[0] = pcFactory.createVideoTrack(trackId, videoSource);
533566

534-
videoCapturer.startCapture(info.width, info.height, info.fps);
535-
Log.d(TAG, "OrientationAwareScreenCapturer.startCapture: " + info.width + "x" + info.height + "@" + info.fps);
567+
ConstraintsArray audioTracks = new ConstraintsArray();
568+
ConstraintsArray videoTracks = new ConstraintsArray();
569+
ConstraintsMap successResult = new ConstraintsMap();
536570

537-
String trackId = stateProvider.getNextTrackUUID();
538-
mVideoCapturers.put(trackId, info);
571+
for (MediaStreamTrack track : tracks) {
572+
if (track == null) {
573+
continue;
574+
}
539575

540-
tracks[0] = pcFactory.createVideoTrack(trackId, videoSource);
576+
String id = track.id();
541577

542-
ConstraintsArray audioTracks = new ConstraintsArray();
543-
ConstraintsArray videoTracks = new ConstraintsArray();
544-
ConstraintsMap successResult = new ConstraintsMap();
578+
if (track instanceof AudioTrack) {
579+
mediaStream.addTrack((AudioTrack) track);
580+
} else {
581+
mediaStream.addTrack((VideoTrack) track);
582+
}
583+
stateProvider.putLocalTrack(id, track);
545584

546-
for (MediaStreamTrack track : tracks) {
547-
if (track == null) {
548-
continue;
549-
}
585+
ConstraintsMap track_ = new ConstraintsMap();
586+
String kind = track.kind();
550587

551-
String id = track.id();
588+
track_.putBoolean("enabled", track.enabled());
589+
track_.putString("id", id);
590+
track_.putString("kind", kind);
591+
track_.putString("label", kind);
592+
track_.putString("readyState", track.state().toString());
593+
track_.putBoolean("remote", false);
552594

553-
if (track instanceof AudioTrack) {
554-
mediaStream.addTrack((AudioTrack) track);
555-
} else {
556-
mediaStream.addTrack((VideoTrack) track);
557-
}
558-
stateProvider.putLocalTrack(id, track);
559-
560-
ConstraintsMap track_ = new ConstraintsMap();
561-
String kind = track.kind();
562-
563-
track_.putBoolean("enabled", track.enabled());
564-
track_.putString("id", id);
565-
track_.putString("kind", kind);
566-
track_.putString("label", kind);
567-
track_.putString("readyState", track.state().toString());
568-
track_.putBoolean("remote", false);
569-
570-
if (track instanceof AudioTrack) {
571-
audioTracks.pushMap(track_);
572-
} else {
573-
videoTracks.pushMap(track_);
574-
}
575-
}
595+
if (track instanceof AudioTrack) {
596+
audioTracks.pushMap(track_);
597+
} else {
598+
videoTracks.pushMap(track_);
599+
}
600+
}
576601

577-
String streamId = mediaStream.getId();
602+
String streamId = mediaStream.getId();
578603

579-
Log.d(TAG, "MediaStream id: " + streamId);
580-
stateProvider.putLocalStream(streamId, mediaStream);
581-
successResult.putString("streamId", streamId);
582-
successResult.putArray("audioTracks", audioTracks.toArrayList());
583-
successResult.putArray("videoTracks", videoTracks.toArrayList());
584-
result.success(successResult.toMap());
585-
}
586-
});
604+
Log.d(TAG, "MediaStream id: " + streamId);
605+
stateProvider.putLocalStream(streamId, mediaStream);
606+
successResult.putString("streamId", streamId);
607+
successResult.putArray("audioTracks", audioTracks.toArrayList());
608+
successResult.putArray("videoTracks", videoTracks.toArrayList());
609+
result.success(successResult.toMap());
587610
}
588611

589612
/**

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

+4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ public void onMethodCall(MethodCall call, @NonNull Result notSafeResult) {
624624
AudioSwitchManager.instance.enableSpeakerButPreferBluetooth();
625625
result.success(null);
626626
break;
627+
case "requestCapturePermission": {
628+
getUserMediaImpl.requestCapturePermission(result);
629+
break;
630+
}
627631
case "getDisplayMedia": {
628632
Map<String, Object> constraints = call.argument("constraints");
629633
ConstraintsMap constraintsMap = new ConstraintsMap(constraints);

0 commit comments

Comments
 (0)