Skip to content

Commit 6a2be5d

Browse files
authored
support Picture in Picture (#880)
1 parent a54a62b commit 6a2be5d

35 files changed

Lines changed: 2332 additions & 396 deletions

android/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ dependencies {
165165
implementation "com.facebook.react:react-native:+"
166166
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
167167
/// dependencies start
168-
api 'io.agora.rtc:full-sdk:4.5.2'
169-
implementation 'io.agora.rtc:full-screen-sharing:4.5.2'
170-
implementation 'io.agora.rtc:iris-rtc:4.5.2-build.1'
168+
api 'io.agora.rtc:full-screen-sharing:4.5.2.140'
169+
api 'io.agora.rtc:agora-special-full:4.5.2.140'
170+
api 'io.agora.rtc:iris-rtc:4.5.2.140-build.4'
171171
/// dependencies end
172172
}
173173

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.agora.rtc.ng.react;
2+
3+
import android.app.PictureInPictureParams;
4+
import android.app.PictureInPictureUiState;
5+
import android.content.Context;
6+
import android.content.res.Configuration;
7+
import android.os.Build;
8+
9+
import com.facebook.react.ReactActivity;
10+
11+
import java.lang.ref.WeakReference;
12+
13+
import io.agora.iris.pip.AgoraPIPActivityProxy;
14+
import io.agora.iris.pip.AgoraPIPActivityListener;
15+
16+
public class AgoraPIPActivity extends ReactActivity implements AgoraPIPActivityProxy {
17+
private WeakReference<AgoraPIPActivityListener> mListener;
18+
19+
@Override
20+
public Context getApplicationContext() {
21+
return super.getApplicationContext();
22+
}
23+
24+
@Override
25+
public void setAgoraPIPActivityListener(AgoraPIPActivityListener listener) {
26+
mListener = new WeakReference<>(listener);
27+
}
28+
29+
@Override
30+
public boolean isInPictureInPictureMode() {
31+
return super.isInPictureInPictureMode();
32+
}
33+
34+
@Override
35+
public void setPictureInPictureParams(PictureInPictureParams params) {
36+
super.setPictureInPictureParams(params);
37+
}
38+
39+
@Override
40+
public boolean enterPictureInPictureMode(PictureInPictureParams params) {
41+
return super.enterPictureInPictureMode(params);
42+
}
43+
44+
@Override
45+
public void enterPictureInPictureMode() {
46+
super.enterPictureInPictureMode();
47+
}
48+
49+
@Override
50+
public boolean moveTaskToBack(boolean nonRoot) {
51+
return super.moveTaskToBack(nonRoot);
52+
}
53+
54+
@Override
55+
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
56+
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
57+
if (mListener == null) {
58+
return;
59+
}
60+
61+
AgoraPIPActivityListener listener = mListener.get();
62+
if (listener != null) {
63+
listener.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
64+
}
65+
}
66+
67+
@Override
68+
public boolean onPictureInPictureRequested() {
69+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
70+
if (mListener != null) {
71+
AgoraPIPActivityListener listener = mListener.get();
72+
if (listener != null) {
73+
return listener.onPictureInPictureRequested();
74+
}
75+
}
76+
return super.onPictureInPictureRequested();
77+
}
78+
return false;
79+
}
80+
81+
@Override
82+
public void onPictureInPictureUiStateChanged(PictureInPictureUiState state) {
83+
super.onPictureInPictureUiStateChanged(state);
84+
if (mListener == null) {
85+
return;
86+
}
87+
88+
AgoraPIPActivityListener listener = mListener.get();
89+
if (listener != null) {
90+
listener.onPictureInPictureUiStateChanged(state);
91+
}
92+
}
93+
94+
@Override
95+
public void onUserLeaveHint() {
96+
super.onUserLeaveHint();
97+
if (mListener == null) {
98+
return;
99+
}
100+
101+
AgoraPIPActivityListener listener = mListener.get();
102+
if (listener != null) {
103+
listener.onUserLeaveHint();
104+
}
105+
}
106+
107+
}

android/src/main/java/io/agora/rtc/ng/react/AgoraRtcNgModule.java

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package io.agora.rtc.ng.react;
22

3+
import android.app.Activity;
34
import android.util.Base64;
5+
import android.graphics.Rect;
6+
import android.os.Build;
7+
import android.util.Rational;
48

59
import androidx.annotation.NonNull;
610

@@ -20,15 +24,20 @@
2024

2125
import java.util.ArrayList;
2226
import java.util.List;
27+
import java.util.Map;
2328

2429
import io.agora.iris.IrisApiEngine;
2530
import io.agora.iris.IrisEventHandler;
2631

32+
import io.agora.iris.pip.AgoraPIPActivityProxy;
33+
import io.agora.iris.pip.AgoraPIPController;
34+
2735
@ReactModule(name = AgoraRtcNgModule.NAME)
2836
public class AgoraRtcNgModule extends AgoraRtcNgSpec implements IrisEventHandler {
2937
public static final String NAME = "AgoraRtcNg";
3038
public final Object irisApiLock = new Object();
3139
public IrisApiEngine irisApiEngine;
40+
private AgoraPIPController pipController;
3241

3342
AgoraRtcNgModule(ReactApplicationContext context) {
3443
super(context);
@@ -47,6 +56,10 @@ public boolean newIrisApiEngine() {
4756
IrisApiEngine.enableUseJsonArray(true);
4857
irisApiEngine = new IrisApiEngine(getReactApplicationContext());
4958
irisApiEngine.setEventHandler(this);
59+
Activity currentActivity = getReactApplicationContext().getCurrentActivity();
60+
if (currentActivity != null) {
61+
initPipController(currentActivity);
62+
}
5063
return true;
5164
}
5265
}
@@ -60,6 +73,7 @@ public boolean destroyIrisApiEngine() {
6073
irisApiEngine.setEventHandler(null);
6174
irisApiEngine.destroy();
6275
irisApiEngine = null;
76+
pipController = null;
6377
return true;
6478
}
6579
}
@@ -95,6 +109,34 @@ public String callApi(ReadableMap args) {
95109
}
96110
}
97111

112+
private void initPipController(@NonNull Activity activity) {
113+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
114+
115+
if (!(activity instanceof AgoraPIPActivityProxy)) {
116+
return;
117+
}
118+
119+
if (pipController != null) {
120+
pipController.dispose();
121+
}
122+
123+
pipController = new AgoraPIPController(
124+
(AgoraPIPActivityProxy) activity,
125+
new AgoraPIPController.PIPStateChangedListener() {
126+
@Override
127+
public void onPIPStateChangedListener(
128+
AgoraPIPController.PIPState state, String error) {
129+
try {
130+
OnEvent("AgoraPip_onPipStateChanged",
131+
new JSONObject().put("state", state.getValue()).put("error", error).toString(), null);
132+
} catch (JSONException e) {
133+
throw new RuntimeException(e);
134+
}
135+
}
136+
});
137+
}
138+
}
139+
98140
@ReactMethod
99141
public void showRPSystemBroadcastPickerView(boolean showsMicrophoneButton, Promise promise) {
100142
promise.reject("", "not support");
@@ -110,6 +152,112 @@ public void removeListeners(double count) {
110152

111153
}
112154

155+
private boolean checkPipIsReady() {
156+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
157+
return false;
158+
}
159+
if (pipController == null) {
160+
return false;
161+
}
162+
return true;
163+
}
164+
165+
@ReactMethod(isBlockingSynchronousMethod = true)
166+
public boolean pipIsSupported() {
167+
synchronized (irisApiLock) {
168+
return checkPipIsReady() && pipController.isSupported();
169+
}
170+
}
171+
172+
@ReactMethod(isBlockingSynchronousMethod = true)
173+
public boolean pipIsAutoEnterSupported() {
174+
synchronized (irisApiLock) {
175+
return checkPipIsReady() && pipController.isAutoEnterSupported();
176+
}
177+
}
178+
179+
@ReactMethod(isBlockingSynchronousMethod = true)
180+
public boolean isPipActivated() {
181+
synchronized (irisApiLock) {
182+
return checkPipIsReady() && pipController.isActivated();
183+
}
184+
}
185+
186+
@ReactMethod(isBlockingSynchronousMethod = true)
187+
public boolean pipSetup(ReadableMap options) {
188+
synchronized (irisApiLock) {
189+
if (!checkPipIsReady()) {
190+
return false;
191+
}
192+
Rational aspectRatio = null;
193+
if (options.hasKey("aspectRatioX") && options.hasKey("aspectRatioY")) {
194+
aspectRatio = new Rational(options.getInt("aspectRatioX"),
195+
options.getInt("aspectRatioY"));
196+
}
197+
Boolean autoEnterEnabled = null;
198+
if (options.hasKey("autoEnterEnabled")) {
199+
autoEnterEnabled = options.getBoolean("autoEnterEnabled");
200+
}
201+
Rect sourceRectHint = null;
202+
if (options.hasKey("sourceRectHintLeft") &&
203+
options.hasKey("sourceRectHintTop") &&
204+
options.hasKey("sourceRectHintRight") &&
205+
options.hasKey("sourceRectHintBottom")) {
206+
sourceRectHint = new Rect(
207+
options.getInt("sourceRectHintLeft"),
208+
options.getInt("sourceRectHintTop"),
209+
options.getInt("sourceRectHintRight"),
210+
options.getInt("sourceRectHintBottom"));
211+
}
212+
Boolean seamlessResizeEnabled = null;
213+
if (options.hasKey("seamlessResizeEnabled")) {
214+
seamlessResizeEnabled = options.getBoolean("seamlessResizeEnabled");
215+
}
216+
Boolean useExternalStateMonitor = null;
217+
if (options.hasKey("useExternalStateMonitor")) {
218+
useExternalStateMonitor = options.getBoolean("useExternalStateMonitor");
219+
} else {
220+
useExternalStateMonitor = true;
221+
}
222+
Integer externalStateMonitorInterval = null;
223+
if (options.hasKey("externalStateMonitorInterval")) {
224+
externalStateMonitorInterval = options.getInt("externalStateMonitorInterval");
225+
} else {
226+
externalStateMonitorInterval = 100;
227+
}
228+
boolean result = pipController.setup(
229+
aspectRatio, autoEnterEnabled, sourceRectHint,
230+
seamlessResizeEnabled, useExternalStateMonitor,
231+
externalStateMonitorInterval);
232+
return result;
233+
}
234+
}
235+
236+
@ReactMethod(isBlockingSynchronousMethod = true)
237+
public boolean pipStart() {
238+
synchronized (irisApiLock) {
239+
return checkPipIsReady() && pipController.start();
240+
}
241+
}
242+
243+
@ReactMethod(isBlockingSynchronousMethod = true)
244+
public void pipStop() {
245+
synchronized (irisApiLock) {
246+
if (checkPipIsReady()) {
247+
pipController.stop();
248+
}
249+
}
250+
}
251+
252+
@ReactMethod(isBlockingSynchronousMethod = true)
253+
public void pipDispose() {
254+
synchronized (irisApiLock) {
255+
if (checkPipIsReady()) {
256+
pipController.dispose();
257+
}
258+
}
259+
}
260+
113261
@Override
114262
public void OnEvent(String event, String data, List<byte[]> buffers) {
115263
final WritableMap map = Arguments.createMap();

android/src/oldarch/io/agora/rtc/ng/react/AgoraRtcNgSpec.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,19 @@ public abstract class AgoraRtcNgSpec extends ReactContextBaseJavaModule {
2222
public abstract void addListener(String eventName);
2323

2424
public abstract void removeListeners(double count);
25+
26+
public abstract boolean pipIsSupported();
27+
28+
public abstract boolean pipIsAutoEnterSupported();
29+
30+
public abstract boolean isPipActivated();
31+
32+
public abstract boolean pipSetup(ReadableMap options);
33+
34+
public abstract boolean pipStart();
35+
36+
public abstract void pipStop();
37+
38+
public abstract void pipDispose();
39+
2540
}

example/android/app/src/main/AndroidManifest.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
12
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
23

34
<uses-permission android:name="android.permission.INTERNET" />
5+
<uses-permission android:name="android.permission.CAMERA" />
6+
<uses-permission android:name="android.permission.RECORD_AUDIO" />
7+
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
8+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
9+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
10+
<uses-permission android:name="android.permission.BLUETOOTH" />
11+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
412

513
<application
614
android:name=".MainApplication"
@@ -14,6 +22,7 @@
1422
<activity
1523
android:name=".MainActivity"
1624
android:label="@string/app_name"
25+
android:supportsPictureInPicture="true"
1726
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
1827
android:launchMode="singleTask"
1928
android:windowSoftInputMode="adjustResize"
@@ -23,5 +32,11 @@
2332
<category android:name="android.intent.category.LAUNCHER" />
2433
</intent-filter>
2534
</activity>
35+
<service
36+
android:name=".AgoraForegroundService"
37+
android:enabled="true"
38+
android:exported="false"
39+
android:stopWithTask="false"
40+
android:foregroundServiceType="mediaPlayback|microphone" />
2641
</application>
2742
</manifest>

0 commit comments

Comments
 (0)