@byteplus/react-native-live-pull 1.0.3-rc.0 → 1.1.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -1
  3. package/android/src/main/AndroidManifest.xml +7 -1
  4. package/android/src/main/AndroidManifestNew.xml +15 -1
  5. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcLiveModule.java +28 -20
  6. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcView.java +7 -8
  7. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowHelper.java +224 -0
  8. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +171 -0
  9. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/IFloatingWindowHelper.java +80 -0
  10. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/PictureInPictureManager.java +280 -0
  11. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/VeLiveRefManager.java +119 -0
  12. package/android/src/main/res/drawable/button_close.xml +14 -0
  13. package/android/src/main/res/layout/floating_window_layout.xml +19 -0
  14. package/ios/VeLivePlayerMultiObserver.h +54 -0
  15. package/ios/VeLivePlayerMultiObserver.m +324 -0
  16. package/ios/pictureInpicture/PictureInPictureManager.h +29 -0
  17. package/ios/pictureInpicture/PictureInPictureManager.m +274 -0
  18. package/ios/pictureInpicture/VeLivePictureInPictureController.h +207 -0
  19. package/ios/pictureInpicture/VeLivePictureInPictureController.m +3393 -0
  20. package/lib/commonjs/index.js +524 -8
  21. package/lib/module/index.js +524 -8
  22. package/lib/typescript/core/api.d.ts +88 -1
  23. package/lib/typescript/core/callback.d.ts +52 -0
  24. package/lib/typescript/platforms/android/extends.d.ts +1 -1
  25. package/lib/typescript/platforms/android/pictureInpicture.d.ts +26 -0
  26. package/lib/typescript/platforms/ios/pictureInpicture.d.ts +32 -0
  27. package/package.json +6 -4
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright 2024 Beijing Volcano Engine Technology Co., Ltd.
3
+ Copyright 2024 BytePlus Pte Ltd.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,2 +1,3 @@
1
- # 火山引擎直播拉流 React Native SDK
1
+ # BytePlus MediaLive React Native SDK
2
2
 
3
+ DOC see [instructions for use](https://docs.byteplus.com/en/docs/byteplus-media-live/docs-integrating-the-broadcast-and-player-sdks-for-rn)
@@ -6,4 +6,10 @@
6
6
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
7
7
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8
8
 
9
- </manifest>
9
+ <application>
10
+ <activity
11
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.AssistantActivity"
12
+ android:exported="true"
13
+ android:theme="@style/AppTheme.NoActionBar" />
14
+ </application>
15
+ </manifest>
@@ -4,5 +4,19 @@
4
4
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
5
5
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
6
6
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
7
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
7
8
 
8
- </manifest>
9
+ <application>
10
+ <activity
11
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowHelper$AssistantActivity"
12
+ android:exported="true"
13
+ />
14
+
15
+ <service
16
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService"
17
+ android:enabled="true"
18
+ android:exported="false"
19
+ android:foregroundServiceType="mediaProjection" />
20
+ </application>
21
+
22
+ </manifest>
@@ -1,7 +1,6 @@
1
1
  package com.volcengine.velive.rn.pull;
2
2
 
3
3
  import androidx.annotation.Nullable;
4
-
5
4
  import com.facebook.react.bridge.Arguments;
6
5
  import com.facebook.react.bridge.Callback;
7
6
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -11,21 +10,21 @@ import com.facebook.react.bridge.ReadableMap;
11
10
  import com.facebook.react.bridge.WritableMap;
12
11
  import com.facebook.react.module.annotations.ReactModule;
13
12
  import com.facebook.react.modules.core.DeviceEventManagerModule;
14
-
15
13
  import com.volcengine.VolcApiEngine.*;
16
14
  import com.volcengine.velive.rn.pull.autogen.MethodSignature;
17
15
 
18
16
  @ReactModule(name = VolcLiveModule.NAME)
19
- public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver {
17
+ public class VolcLiveModule
18
+ extends VolcLiveModuleSpec implements IEventReceiver {
20
19
  public static final String NAME = "VolcLiveModule";
21
-
20
+
22
21
  VolcApiEngine apiEngine = null;
23
-
22
+
24
23
  VolcLiveModule(ReactApplicationContext context) {
25
24
  super(context);
26
25
  MethodSignature.init();
27
26
  }
28
-
27
+
29
28
  @Override
30
29
  public String getName() {
31
30
  return NAME;
@@ -38,12 +37,12 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
38
37
  NativeVariableManager.init(apiEngine.msgClient, super.context);
39
38
  return true;
40
39
  }
41
-
40
+
42
41
  return false;
43
42
  }
44
43
 
45
44
  @ReactMethod(isBlockingSynchronousMethod = true)
46
- public String callApiSync(ReadableMap arg) {
45
+ public String callApiSync(ReadableMap arg) {
47
46
  try {
48
47
  newApiEngine();
49
48
  String params = arg.getString("params");
@@ -55,16 +54,26 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
55
54
  }
56
55
 
57
56
  @ReactMethod(isBlockingSynchronousMethod = false)
58
- public void callApi(ReadableMap arg, Callback callback) {
57
+ public void callApi(ReadableMap arg, Callback callback) {
59
58
  try {
60
59
  newApiEngine();
61
60
  String params = arg.getString("params");
62
- this.apiEngine.callApi(params, (res) -> {
63
- callback.invoke(res.toJsonString());
64
- });
61
+
62
+ // 获取主线程处理器
63
+ android.os.Handler mainHandler =
64
+ new android.os.Handler(android.os.Looper.getMainLooper());
65
+
66
+ // 在主线程上执行 API 调用
67
+ mainHandler.post(() -> {
68
+ try {
69
+ this.apiEngine.callApi(
70
+ params, (res) -> { callback.invoke(res.toJsonString()); });
71
+ } catch (Exception e) {
72
+ e.printStackTrace();
73
+ }
74
+ });
65
75
  } catch (Exception e) {
66
76
  e.printStackTrace();
67
- throw new RuntimeException(e);
68
77
  }
69
78
  }
70
79
 
@@ -87,12 +96,11 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
87
96
  }
88
97
  }
89
98
 
90
- private void sendEvent(ReactContext reactContext,
91
- String eventName,
92
- @Nullable WritableMap params) {
99
+ private void sendEvent(ReactContext reactContext, String eventName,
100
+ @Nullable WritableMap params) {
93
101
  reactContext
94
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
95
- .emit(eventName, params);
102
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
103
+ .emit(eventName, params);
96
104
  }
97
105
 
98
106
  @Override
@@ -102,7 +110,7 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
102
110
  map.putString("data", data);
103
111
 
104
112
  super.context
105
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
106
- .emit(VolcLiveModuleSpec.EVENT, map);
113
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
114
+ .emit(VolcLiveModuleSpec.EVENT, map);
107
115
  }
108
116
  }
@@ -2,7 +2,6 @@ package com.volcengine.velive.rn.pull;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.widget.FrameLayout;
5
-
6
5
  import com.facebook.react.bridge.Arguments;
7
6
  import com.facebook.react.bridge.ReactContext;
8
7
  import com.facebook.react.bridge.WritableMap;
@@ -11,13 +10,14 @@ import com.facebook.react.uimanager.events.RCTEventEmitter;
11
10
  public class VolcView extends FrameLayout {
12
11
  public String viewId;
13
12
  public boolean hasRegister = false;
14
-
15
- public VolcView(Context context) {
13
+
14
+ public VolcView(Context context) {
16
15
  super(context);
16
+ this.setKeepScreenOn(true);
17
17
  }
18
-
18
+
19
19
  public void setViewId(String viewId) {
20
- this.viewId = viewId;
20
+ this.viewId = viewId;
21
21
  this.hasRegister = true;
22
22
  this.emitOnLoad();
23
23
  }
@@ -25,8 +25,7 @@ public class VolcView extends FrameLayout {
25
25
  public void emitOnLoad() {
26
26
  WritableMap event = Arguments.createMap();
27
27
  ReactContext reactContext = (ReactContext)getContext();
28
- reactContext
29
- .getJSModule(RCTEventEmitter.class)
30
- .receiveEvent(getId(), "load", event);
28
+ reactContext.getJSModule(RCTEventEmitter.class)
29
+ .receiveEvent(getId(), "load", event);
31
30
  }
32
31
  }
@@ -0,0 +1,224 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_ASPECT_RATIO;
4
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_X_POS;
5
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_Y_POS;
6
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_INVALID_PARAMS;
7
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_IS_ALREADY_OPEN;
8
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_NO;
9
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_NOT_SUPPORT;
10
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_RETRY;
11
+
12
+ import android.app.Activity;
13
+ import android.content.Context;
14
+ import android.content.Intent;
15
+ import android.net.Uri;
16
+ import android.os.Build;
17
+ import android.os.Bundle;
18
+ import android.provider.Settings;
19
+ import android.view.SurfaceView;
20
+ import androidx.annotation.Nullable;
21
+ import java.util.HashMap;
22
+ import java.util.Map;
23
+
24
+ public class FloatingWindowHelper implements IFloatingWindowHelper {
25
+ private static FloatingWindowHelper mInstance = null;
26
+ private Listener mListener;
27
+ private Config mConfig;
28
+ private Map<String, Object> mExtraDataMap;
29
+ private boolean isRunning;
30
+
31
+ public synchronized static FloatingWindowHelper getInstance() {
32
+ if (mInstance == null) {
33
+ mInstance = new FloatingWindowHelper();
34
+ }
35
+ return mInstance;
36
+ }
37
+
38
+ public static boolean isPictureInPictureSupported() {
39
+ // Check if the system version supports floating windows
40
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
41
+ // Android versions below 6.0 don't support SYSTEM_ALERT_WINDOW permission
42
+ // management
43
+ return false;
44
+ }
45
+ // The system supports PIP mode
46
+ return true;
47
+ }
48
+
49
+ @Override
50
+ public void setEventListener(Listener listener) {
51
+ mListener = listener;
52
+ }
53
+
54
+ public Listener getEventListener() { return mListener; }
55
+
56
+ public void openFloatingWindow(Context context, Config config,
57
+ Map<String, Object> extraData) {
58
+ if (isRunning) {
59
+ if (mListener != null) {
60
+ mListener.onOpenFloatingWindowResult(ERR_IS_ALREADY_OPEN, extraData);
61
+ }
62
+ return;
63
+ }
64
+ // Invalid parameters
65
+ if (context == null || config.aspectRatio <= 0) {
66
+ if (mListener != null) {
67
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, extraData);
68
+ }
69
+ return;
70
+ }
71
+
72
+ // Android versions below 6.0 don't support Overlay functionality
73
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
74
+ if (mListener != null) {
75
+ mListener.onOpenFloatingWindowResult(ERR_NOT_SUPPORT, extraData);
76
+ }
77
+ return;
78
+ }
79
+
80
+ // Save configuration first, so it can be used in permission request
81
+ // callback
82
+ mConfig = config;
83
+ mExtraDataMap = extraData;
84
+
85
+ // If Overlay permission is not enabled, request it first
86
+ if (!Settings.canDrawOverlays(context)) {
87
+ if (mListener != null && mListener.onRequestOverlayPermission()) {
88
+ requestOverlayPermission(context);
89
+ } else {
90
+ // User doesn't allow permission request, trigger error callback
91
+ if (mListener != null) {
92
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, extraData);
93
+ }
94
+ }
95
+ return;
96
+ }
97
+
98
+ Intent intent = new Intent(context, FloatingWindowService.class);
99
+ intent.putExtra(INTENT_EXTRA_KEY_ASPECT_RATIO, mConfig.aspectRatio);
100
+ intent.putExtra(INTENT_EXTRA_KEY_X_POS, mConfig.x);
101
+ intent.putExtra(INTENT_EXTRA_KEY_Y_POS, mConfig.y);
102
+ context.startService(intent);
103
+ isRunning = true;
104
+ }
105
+
106
+ @Override
107
+ public void closeFloatingWindow(Context context) {
108
+ if (!isRunning) {
109
+ return;
110
+ }
111
+ context.stopService(new Intent(context, FloatingWindowService.class));
112
+ isRunning = false;
113
+ }
114
+
115
+ @Override
116
+ public void requestOverlayPermission(Context context) {
117
+ try {
118
+ Intent intent = new Intent(context, AssistantActivity.class);
119
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
120
+ context.startActivity(intent);
121
+ } catch (Exception e) {
122
+ // If starting AssistantActivity fails, trigger error callback
123
+ if (mListener != null) {
124
+ Map<String, Object> errorData = new HashMap<>();
125
+ errorData.put("error", e.getMessage());
126
+ mListener.onOpenFloatingWindowResult(ERR_NOT_SUPPORT, errorData);
127
+ }
128
+ }
129
+ }
130
+
131
+ @Override
132
+ public Config getConfig() {
133
+ return mConfig;
134
+ }
135
+
136
+ @Override
137
+ public Map<String, Object> getExtraData() {
138
+ return mExtraDataMap;
139
+ }
140
+
141
+ @Override
142
+ public boolean isOpen() {
143
+ return isRunning;
144
+ }
145
+
146
+ void setSurfaceView(SurfaceView surfaceView) {
147
+ if (mListener != null) {
148
+ mListener.onUpdateSurfaceView(surfaceView);
149
+ }
150
+ }
151
+
152
+ void performClose(Context context) {
153
+ if (mListener != null) {
154
+ mListener.onClickFloatingWindowCloseBtn(context);
155
+ }
156
+ }
157
+
158
+ void onClickFloatingWindow(Context context) {
159
+ if (mListener != null) {
160
+ mListener.onClickFloatingWindow(context);
161
+ }
162
+ }
163
+
164
+ void onStartService() {
165
+ if (mListener != null) {
166
+ mListener.onOpenFloatingWindowResult(ERR_NO, mExtraDataMap);
167
+ }
168
+ }
169
+
170
+ void onStopService() {
171
+ if (mListener != null) {
172
+ mListener.onCloseFloatingWindow(mExtraDataMap);
173
+ }
174
+
175
+ mConfig = null;
176
+ mExtraDataMap = null;
177
+ }
178
+
179
+ public static class AssistantActivity extends Activity {
180
+ private Listener mListener;
181
+
182
+ @Override
183
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
184
+ super.onCreate(savedInstanceState);
185
+ mListener = FloatingWindowHelper.getInstance().getEventListener();
186
+
187
+ int sdkInt = Build.VERSION.SDK_INT;
188
+ int mOverlayRequestCode = 1001;
189
+ if (sdkInt >= Build.VERSION_CODES.O) { // Android 8.0 and above
190
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
191
+ startActivityForResult(intent, mOverlayRequestCode);
192
+ } else if (sdkInt >= Build.VERSION_CODES.M) {
193
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
194
+ intent.setData(Uri.parse("package:" + getPackageName()));
195
+ startActivityForResult(intent, mOverlayRequestCode);
196
+ }
197
+ }
198
+
199
+ @Override
200
+ protected void onActivityResult(int requestCode, int resultCode,
201
+ @Nullable Intent data) {
202
+ super.onActivityResult(requestCode, resultCode, data);
203
+
204
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
205
+ if (!Settings.canDrawOverlays(this)) {
206
+ // User denied permission request, return permission error
207
+ if (mListener != null) {
208
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, null);
209
+ }
210
+ } else {
211
+ // User granted permission
212
+ if (mListener != null) {
213
+ // Return success directly, let the caller retry
214
+ // startPictureInPicture
215
+ mListener.onOpenFloatingWindowResult(ERR_RETRY, null);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Finish activity regardless of the result
221
+ finish();
222
+ }
223
+ }
224
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Copyright (c) 2023 Beijing Volcano Engine Technology Ltd. All rights
3
+ * reserved. Licensed under the MIT License (the "License"); you may not use
4
+ * this file except in compliance with the License. You may obtain a copy of the
5
+ * License at http://opensource.org/licenses/MIT Unless required by applicable
6
+ * law or agreed to in writing, software distributed under the License is
7
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8
+ * KIND, either express or implied. See the License for the specific language
9
+ * governing permissions and limitations under the License.
10
+ */
11
+ package com.volcengine.velive.rn.pull.pictureInpicture;
12
+
13
+ import android.app.Service;
14
+ import android.content.Intent;
15
+ import android.graphics.PixelFormat;
16
+ import android.os.Build;
17
+ import android.os.IBinder;
18
+ import android.provider.Settings;
19
+ import android.util.Log;
20
+ import android.view.Gravity;
21
+ import android.view.LayoutInflater;
22
+ import android.view.MotionEvent;
23
+ import android.view.SurfaceView;
24
+ import android.view.View;
25
+ import android.view.ViewConfiguration;
26
+ import android.view.WindowManager;
27
+ import androidx.annotation.Nullable;
28
+ import com.volcengine.velive.rn.pull.R;
29
+
30
+ public class FloatingWindowService extends Service {
31
+ private static final String TAG = FloatingWindowService.class.getSimpleName();
32
+ private static final int LONGER_SIDE_MAX_LEN = 800;
33
+
34
+ private WindowManager mWindowManager;
35
+ private WindowManager.LayoutParams mLayoutParams;
36
+ private SurfaceView mSurfaceView;
37
+ private View mSmallWindowView;
38
+
39
+ public static final String INTENT_EXTRA_KEY_ASPECT_RATIO = "aspect_ratio";
40
+ public static final String INTENT_EXTRA_KEY_X_POS = "x_pos";
41
+ public static final String INTENT_EXTRA_KEY_Y_POS = "y_pos";
42
+
43
+ @Override
44
+ public void onCreate() {
45
+ Log.d(TAG, "onCreate");
46
+ super.onCreate();
47
+ FloatingWindowHelper.getInstance().onStartService();
48
+ }
49
+
50
+ @Override
51
+ public int onStartCommand(Intent intent, int flags, int startId) {
52
+ Log.d(TAG, "onStartCommand");
53
+ initUI(intent.getFloatExtra(INTENT_EXTRA_KEY_ASPECT_RATIO, 16f / 9f),
54
+ intent.getIntExtra(INTENT_EXTRA_KEY_X_POS, 300),
55
+ intent.getIntExtra(INTENT_EXTRA_KEY_Y_POS, 300));
56
+ FloatingWindowHelper.getInstance().setSurfaceView(mSurfaceView);
57
+ return super.onStartCommand(intent, flags, startId);
58
+ }
59
+
60
+ @Override
61
+ public void onDestroy() {
62
+ Log.d(TAG, "onDestroy");
63
+ super.onDestroy();
64
+ if (mSmallWindowView != null) {
65
+ mWindowManager.removeView(mSmallWindowView);
66
+ }
67
+ FloatingWindowHelper.getInstance().onStopService();
68
+ }
69
+
70
+ @Nullable
71
+ @Override
72
+ public IBinder onBind(Intent intent) {
73
+ return null;
74
+ }
75
+
76
+ private void initUI(float aspectRatio, int x, int y) {
77
+ mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
78
+ mLayoutParams = new WindowManager.LayoutParams();
79
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
80
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
81
+ } else {
82
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
83
+ }
84
+ mLayoutParams.format = PixelFormat.RGBA_8888;
85
+ mLayoutParams.gravity = Gravity.START | Gravity.TOP;
86
+ mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
87
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
88
+
89
+ // Limit the floating window size to prevent it from being too large or too
90
+ // small, control the longer side to LONGER_SIDE_MAX_LEN, scale the shorter
91
+ // side proportionally
92
+ int width, height;
93
+ if (aspectRatio >= 1) {
94
+ height = (int)(LONGER_SIDE_MAX_LEN / aspectRatio);
95
+ width = LONGER_SIDE_MAX_LEN;
96
+ } else {
97
+ width = (int)(LONGER_SIDE_MAX_LEN * aspectRatio);
98
+ height = LONGER_SIDE_MAX_LEN;
99
+ }
100
+ mLayoutParams.width = width;
101
+ mLayoutParams.height = height;
102
+
103
+ // Initial position of the floating window
104
+ mLayoutParams.x = x;
105
+ mLayoutParams.y = y;
106
+
107
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
108
+ if (Settings.canDrawOverlays(this)) {
109
+ LayoutInflater layoutInflater = LayoutInflater.from(this);
110
+ mSmallWindowView =
111
+ layoutInflater.inflate(R.layout.floating_window_layout, null);
112
+ mSmallWindowView.setOnTouchListener(new FloatingOnTouchListener());
113
+ mWindowManager.addView(mSmallWindowView, mLayoutParams);
114
+ mSurfaceView = mSmallWindowView.findViewById(R.id.surface_view);
115
+ mSmallWindowView.findViewById(R.id.surface_close_btn)
116
+ .setOnClickListener(v -> {
117
+ FloatingWindowHelper.getInstance().performClose(
118
+ FloatingWindowService.this);
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ private class FloatingOnTouchListener implements View.OnTouchListener {
125
+ private final int touchSlop =
126
+ ViewConfiguration
127
+ .get(FloatingWindowService.this.getApplicationContext())
128
+ .getScaledTouchSlop();
129
+ private int downX, downY;
130
+ private int x, y;
131
+ private boolean isDragging;
132
+
133
+ @Override
134
+ public boolean onTouch(View view, MotionEvent event) {
135
+ switch (event.getAction()) {
136
+ case MotionEvent.ACTION_UP:
137
+ if (isDragging) {
138
+ isDragging = false;
139
+ } else {
140
+ FloatingWindowHelper.getInstance().onClickFloatingWindow(
141
+ FloatingWindowService.this);
142
+ }
143
+ downX = downY = 0;
144
+ break;
145
+ case MotionEvent.ACTION_DOWN:
146
+ downX = x = (int)event.getRawX();
147
+ downY = y = (int)event.getRawY();
148
+ break;
149
+ case MotionEvent.ACTION_MOVE:
150
+ int nowX = (int)event.getRawX();
151
+ int nowY = (int)event.getRawY();
152
+ if (Math.abs(nowX - downX) > touchSlop ||
153
+ Math.abs(nowY - downY) > touchSlop) {
154
+ isDragging = true;
155
+ }
156
+ int movedX = nowX - x;
157
+ int movedY = nowY - y;
158
+ x = nowX;
159
+ y = nowY;
160
+ mLayoutParams.x = mLayoutParams.x + movedX;
161
+ mLayoutParams.y = mLayoutParams.y + movedY;
162
+ mWindowManager.updateViewLayout(view, mLayoutParams);
163
+ break;
164
+
165
+ default:
166
+ break;
167
+ }
168
+ return true;
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,80 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import android.content.Context;
4
+ import android.view.SurfaceView;
5
+ import java.util.Map;
6
+
7
+ public interface IFloatingWindowHelper {
8
+ // Set event listener callback
9
+ void setEventListener(Listener listener);
10
+
11
+ // Open floating window (will request permission if not granted),
12
+ // also supports caching business data for restoring the live page.
13
+ void openFloatingWindow(Context context, Config config,
14
+ Map<String, Object> extraData);
15
+
16
+ // Close floating window
17
+ void closeFloatingWindow(Context context);
18
+
19
+ // Request overlay permission
20
+ void requestOverlayPermission(Context context);
21
+
22
+ // Get floating window configuration
23
+ Config getConfig();
24
+
25
+ // Get cached business data
26
+ Map<String, Object> getExtraData();
27
+
28
+ // Check if floating window is open
29
+ boolean isOpen();
30
+
31
+ interface Listener {
32
+ // Error codes for opening floating window callback
33
+ int ERR_NO = 0;
34
+ int ERR_NOT_SUPPORT = 1;
35
+ int ERR_INVALID_PARAMS = 2;
36
+ int ERR_IS_ALREADY_OPEN = 3;
37
+ int ERR_RETRY = 4;
38
+
39
+ // Triggered when requesting overlay permission
40
+ // return: boolean, whether to allow requesting overlay permission
41
+ // - true: allow
42
+ // - false: disallow
43
+ default boolean onRequestOverlayPermission() { return true; }
44
+
45
+ // Callback for the result of opening floating window
46
+ // param: errCode, ERR_NO means success, other values indicate errors
47
+ default void onOpenFloatingWindowResult(int errCode,
48
+ Map<String, Object> extraData) {}
49
+
50
+ // Callback when closing floating window
51
+ default void onCloseFloatingWindow(Map<String, Object> extraData) {}
52
+
53
+ // Callback when clicking the floating window, needs to be handled by
54
+ // business logic
55
+ default void onClickFloatingWindow(Context context) {}
56
+
57
+ // Callback when clicking the close button of floating window, needs to be
58
+ // handled by business logic
59
+ default void onClickFloatingWindowCloseBtn(Context context) {
60
+ FloatingWindowHelper.getInstance().closeFloatingWindow(context);
61
+ }
62
+
63
+ // Callback for the SurfaceView of floating window, needs to be set to SDK
64
+ // to render video
65
+ void onUpdateSurfaceView(SurfaceView surfaceView);
66
+ }
67
+
68
+ class Config {
69
+ public float aspectRatio;
70
+ int x, y; // Position for creating the floating window
71
+
72
+ public Config(float aspectRatio) { this(aspectRatio, 0, 0); }
73
+
74
+ public Config(float aspectRatio, int x, int y) {
75
+ this.aspectRatio = aspectRatio;
76
+ this.x = x;
77
+ this.y = y;
78
+ }
79
+ }
80
+ }