@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.
- package/LICENSE +1 -1
- package/README.md +2 -1
- package/android/src/main/AndroidManifest.xml +7 -1
- package/android/src/main/AndroidManifestNew.xml +15 -1
- package/android/src/main/java/com/volcengine/velive/rn/pull/VolcLiveModule.java +28 -20
- package/android/src/main/java/com/volcengine/velive/rn/pull/VolcView.java +7 -8
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowHelper.java +224 -0
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +171 -0
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/IFloatingWindowHelper.java +80 -0
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/PictureInPictureManager.java +280 -0
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/VeLiveRefManager.java +119 -0
- package/android/src/main/res/drawable/button_close.xml +14 -0
- package/android/src/main/res/layout/floating_window_layout.xml +19 -0
- package/ios/VeLivePlayerMultiObserver.h +54 -0
- package/ios/VeLivePlayerMultiObserver.m +324 -0
- package/ios/pictureInpicture/PictureInPictureManager.h +29 -0
- package/ios/pictureInpicture/PictureInPictureManager.m +274 -0
- package/ios/pictureInpicture/VeLivePictureInPictureController.h +207 -0
- package/ios/pictureInpicture/VeLivePictureInPictureController.m +3393 -0
- package/lib/commonjs/index.js +524 -8
- package/lib/module/index.js +524 -8
- package/lib/typescript/core/api.d.ts +88 -1
- package/lib/typescript/core/callback.d.ts +52 -0
- package/lib/typescript/platforms/android/extends.d.ts +1 -1
- package/lib/typescript/platforms/android/pictureInpicture.d.ts +26 -0
- package/lib/typescript/platforms/ios/pictureInpicture.d.ts +32 -0
- package/package.json +6 -4
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright 2024
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
92
|
-
@Nullable WritableMap params) {
|
|
99
|
+
private void sendEvent(ReactContext reactContext, String eventName,
|
|
100
|
+
@Nullable WritableMap params) {
|
|
93
101
|
reactContext
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
+
}
|