@volcengine/react-native-live-pull 1.0.3-rc.0 → 1.1.1
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/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 +225 -0
- package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +253 -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 +257 -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 +9 -0
- package/android/src/main/res/drawable/new_window.xml +16 -0
- package/android/src/main/res/layout/floating_window_layout.xml +29 -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 +1981 -5044
- package/lib/module/index.js +1981 -5044
- package/lib/typescript/codegen/pack/errorcode.d.ts +27 -27
- package/lib/typescript/codegen/pack/types.d.ts +1 -1
- 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 +1 -1
|
@@ -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,225 @@
|
|
|
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
|
+
isRunning = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public static class AssistantActivity extends Activity {
|
|
181
|
+
private Listener mListener;
|
|
182
|
+
|
|
183
|
+
@Override
|
|
184
|
+
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
|
185
|
+
super.onCreate(savedInstanceState);
|
|
186
|
+
mListener = FloatingWindowHelper.getInstance().getEventListener();
|
|
187
|
+
|
|
188
|
+
int sdkInt = Build.VERSION.SDK_INT;
|
|
189
|
+
int mOverlayRequestCode = 1001;
|
|
190
|
+
if (sdkInt >= Build.VERSION_CODES.O) { // Android 8.0 and above
|
|
191
|
+
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
|
|
192
|
+
startActivityForResult(intent, mOverlayRequestCode);
|
|
193
|
+
} else if (sdkInt >= Build.VERSION_CODES.M) {
|
|
194
|
+
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
|
|
195
|
+
intent.setData(Uri.parse("package:" + getPackageName()));
|
|
196
|
+
startActivityForResult(intent, mOverlayRequestCode);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@Override
|
|
201
|
+
protected void onActivityResult(int requestCode, int resultCode,
|
|
202
|
+
@Nullable Intent data) {
|
|
203
|
+
super.onActivityResult(requestCode, resultCode, data);
|
|
204
|
+
|
|
205
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
206
|
+
if (!Settings.canDrawOverlays(this)) {
|
|
207
|
+
// User denied permission request, return permission error
|
|
208
|
+
if (mListener != null) {
|
|
209
|
+
mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, null);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
// User granted permission
|
|
213
|
+
if (mListener != null) {
|
|
214
|
+
// Return success directly, let the caller retry
|
|
215
|
+
// startPictureInPicture
|
|
216
|
+
mListener.onOpenFloatingWindowResult(ERR_RETRY, null);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Finish activity regardless of the result
|
|
222
|
+
finish();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
package com.volcengine.velive.rn.pull.pictureInpicture;
|
|
2
|
+
|
|
3
|
+
import android.app.Service;
|
|
4
|
+
import android.content.BroadcastReceiver;
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.content.Intent;
|
|
7
|
+
import android.content.IntentFilter;
|
|
8
|
+
import android.graphics.PixelFormat;
|
|
9
|
+
import android.os.Build;
|
|
10
|
+
import android.os.Handler;
|
|
11
|
+
import android.os.IBinder;
|
|
12
|
+
import android.os.Looper;
|
|
13
|
+
import android.provider.Settings;
|
|
14
|
+
import android.util.DisplayMetrics; // Import DisplayMetrics
|
|
15
|
+
import android.util.Log;
|
|
16
|
+
import android.view.Gravity;
|
|
17
|
+
import android.view.LayoutInflater;
|
|
18
|
+
import android.view.MotionEvent;
|
|
19
|
+
import android.view.SurfaceView;
|
|
20
|
+
import android.view.View;
|
|
21
|
+
import android.view.ViewConfiguration;
|
|
22
|
+
import android.view.WindowManager;
|
|
23
|
+
import androidx.annotation.Nullable;
|
|
24
|
+
import com.volcengine.velive.rn.pull.R;
|
|
25
|
+
|
|
26
|
+
public class FloatingWindowService extends Service {
|
|
27
|
+
private static final String TAG = FloatingWindowService.class.getSimpleName();
|
|
28
|
+
private static final int LONGER_SIDE_MAX_LEN = 1000;
|
|
29
|
+
|
|
30
|
+
private WindowManager mWindowManager;
|
|
31
|
+
private WindowManager.LayoutParams mLayoutParams;
|
|
32
|
+
private SurfaceView mSurfaceView;
|
|
33
|
+
private View mSmallWindowView;
|
|
34
|
+
private ActivityLaunchReceiver mActivityLaunchReceiver;
|
|
35
|
+
|
|
36
|
+
public static final String ACTION_STOP_PIP_SERVICE =
|
|
37
|
+
"com.volcengine.velive.rn.pull.STOP_PIP_SERVICE";
|
|
38
|
+
public static final String INTENT_EXTRA_KEY_ASPECT_RATIO = "aspect_ratio";
|
|
39
|
+
public static final String INTENT_EXTRA_KEY_X_POS = "x_pos";
|
|
40
|
+
public static final String INTENT_EXTRA_KEY_Y_POS = "y_pos";
|
|
41
|
+
|
|
42
|
+
@Override
|
|
43
|
+
public void onCreate() {
|
|
44
|
+
Log.d(TAG, "onCreate");
|
|
45
|
+
super.onCreate();
|
|
46
|
+
FloatingWindowHelper.getInstance().onStartService();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Override
|
|
50
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
51
|
+
Log.d(TAG, "onStartCommand");
|
|
52
|
+
initUI(intent.getFloatExtra(INTENT_EXTRA_KEY_ASPECT_RATIO, 16f / 9f),
|
|
53
|
+
intent.getIntExtra(INTENT_EXTRA_KEY_X_POS, 300),
|
|
54
|
+
intent.getIntExtra(INTENT_EXTRA_KEY_Y_POS, 300));
|
|
55
|
+
FloatingWindowHelper.getInstance().setSurfaceView(mSurfaceView);
|
|
56
|
+
return super.onStartCommand(intent, flags, startId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Override
|
|
60
|
+
public void onDestroy() {
|
|
61
|
+
Log.d(TAG, "onDestroy");
|
|
62
|
+
super.onDestroy();
|
|
63
|
+
unregisterActivityLaunchReceiver(); // Ensure receiver is unregistered on
|
|
64
|
+
// destroy
|
|
65
|
+
if (mSmallWindowView != null) {
|
|
66
|
+
try {
|
|
67
|
+
mWindowManager.removeView(mSmallWindowView);
|
|
68
|
+
} catch (Exception e) {
|
|
69
|
+
Log.e(TAG, "Error removing view: " + e.getMessage());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
FloatingWindowHelper.getInstance().onStopService();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Nullable
|
|
76
|
+
@Override
|
|
77
|
+
public IBinder onBind(Intent intent) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private void initUI(float aspectRatio, int x, int y) {
|
|
82
|
+
mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
|
|
83
|
+
mLayoutParams = new WindowManager.LayoutParams();
|
|
84
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
85
|
+
mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
|
86
|
+
} else {
|
|
87
|
+
mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
|
|
88
|
+
}
|
|
89
|
+
mLayoutParams.format = PixelFormat.RGBA_8888;
|
|
90
|
+
mLayoutParams.gravity = Gravity.START | Gravity.TOP;
|
|
91
|
+
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
|
|
92
|
+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
|
93
|
+
|
|
94
|
+
// Get screen dimensions
|
|
95
|
+
DisplayMetrics displayMetrics = new DisplayMetrics();
|
|
96
|
+
mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
|
|
97
|
+
int screenWidth = displayMetrics.widthPixels;
|
|
98
|
+
int screenHeight = displayMetrics.heightPixels;
|
|
99
|
+
|
|
100
|
+
// Calculate the minimum screen dimension
|
|
101
|
+
int minScreenDim = Math.min(screenWidth, screenHeight);
|
|
102
|
+
|
|
103
|
+
// Determine the maximum length based on the smaller of 1000 and
|
|
104
|
+
// minScreenDim
|
|
105
|
+
int maxLen = Math.min(LONGER_SIDE_MAX_LEN, minScreenDim);
|
|
106
|
+
|
|
107
|
+
// Limit the floating window size to prevent it from being too large or too
|
|
108
|
+
// small, control the longer side to maxLen, scale the shorter
|
|
109
|
+
// side proportionally
|
|
110
|
+
int width, height;
|
|
111
|
+
if (aspectRatio >= 1) {
|
|
112
|
+
height = (int)(maxLen / aspectRatio);
|
|
113
|
+
width = maxLen;
|
|
114
|
+
} else {
|
|
115
|
+
width = (int)(maxLen * aspectRatio);
|
|
116
|
+
height = maxLen;
|
|
117
|
+
}
|
|
118
|
+
mLayoutParams.width = width;
|
|
119
|
+
mLayoutParams.height = height;
|
|
120
|
+
|
|
121
|
+
// Initial position of the floating window
|
|
122
|
+
mLayoutParams.x = x;
|
|
123
|
+
mLayoutParams.y = y;
|
|
124
|
+
|
|
125
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
126
|
+
if (Settings.canDrawOverlays(this)) {
|
|
127
|
+
LayoutInflater layoutInflater = LayoutInflater.from(this);
|
|
128
|
+
mSmallWindowView =
|
|
129
|
+
layoutInflater.inflate(R.layout.floating_window_layout, null);
|
|
130
|
+
mSmallWindowView.setOnTouchListener(new FloatingOnTouchListener());
|
|
131
|
+
mWindowManager.addView(mSmallWindowView, mLayoutParams);
|
|
132
|
+
mSurfaceView = mSmallWindowView.findViewById(R.id.surface_view);
|
|
133
|
+
mSmallWindowView.findViewById(R.id.surface_close_btn)
|
|
134
|
+
.setOnClickListener(v -> {
|
|
135
|
+
unregisterActivityLaunchReceiver();
|
|
136
|
+
stopSelf();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
mSmallWindowView.findViewById(R.id.new_window_btn)
|
|
140
|
+
.setOnClickListener(v -> {
|
|
141
|
+
Log.d(TAG, "PIP window clicked");
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
// Get the pacain activity
|
|
145
|
+
String packageName = this.getPackageName();
|
|
146
|
+
Intent launchIntent =
|
|
147
|
+
this.getPackageManager().getLaunchIntentForPackage(
|
|
148
|
+
packageName);
|
|
149
|
+
|
|
150
|
+
if (launchIntent != null) {
|
|
151
|
+
Log.d(TAG, "Launching app with intent: " + launchIntent);
|
|
152
|
+
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
153
|
+
// Register receiver to stop service when activity is launched
|
|
154
|
+
registerActivityLaunchReceiver();
|
|
155
|
+
this.startActivity(launchIntent);
|
|
156
|
+
} else {
|
|
157
|
+
Log.e(TAG, "Could not create launch intent for package: " +
|
|
158
|
+
packageName);
|
|
159
|
+
}
|
|
160
|
+
} catch (Exception e) {
|
|
161
|
+
Log.e(TAG, "Error launching app: " + e.getMessage(), e);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private void registerActivityLaunchReceiver() {
|
|
169
|
+
if (mActivityLaunchReceiver == null) {
|
|
170
|
+
mActivityLaunchReceiver = new ActivityLaunchReceiver();
|
|
171
|
+
IntentFilter filter = new IntentFilter(ACTION_STOP_PIP_SERVICE);
|
|
172
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
173
|
+
registerReceiver(mActivityLaunchReceiver, filter,
|
|
174
|
+
Context.RECEIVER_EXPORTED);
|
|
175
|
+
} else {
|
|
176
|
+
registerReceiver(mActivityLaunchReceiver, filter);
|
|
177
|
+
}
|
|
178
|
+
Log.d(TAG, "ActivityLaunchReceiver registered");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private void unregisterActivityLaunchReceiver() {
|
|
183
|
+
if (mActivityLaunchReceiver != null) {
|
|
184
|
+
try {
|
|
185
|
+
unregisterReceiver(mActivityLaunchReceiver);
|
|
186
|
+
mActivityLaunchReceiver = null;
|
|
187
|
+
Log.d(TAG, "ActivityLaunchReceiver unregistered");
|
|
188
|
+
} catch (IllegalArgumentException e) {
|
|
189
|
+
Log.e(TAG, "Receiver not registered: " + e.getMessage());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// BroadcastReceiver to listen for activity launch confirmation
|
|
195
|
+
private class ActivityLaunchReceiver extends BroadcastReceiver {
|
|
196
|
+
@Override
|
|
197
|
+
public void onReceive(Context context, Intent intent) {
|
|
198
|
+
Log.d(TAG, "Received broadcast to stop PIP service");
|
|
199
|
+
if (ACTION_STOP_PIP_SERVICE.equals(intent.getAction())) {
|
|
200
|
+
stopSelf(); // Stop the service
|
|
201
|
+
unregisterActivityLaunchReceiver(); // Unregister receiver
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private class FloatingOnTouchListener implements View.OnTouchListener {
|
|
207
|
+
private final int touchSlop =
|
|
208
|
+
ViewConfiguration
|
|
209
|
+
.get(FloatingWindowService.this.getApplicationContext())
|
|
210
|
+
.getScaledTouchSlop();
|
|
211
|
+
private int downX, downY;
|
|
212
|
+
private int x, y;
|
|
213
|
+
private boolean isDragging;
|
|
214
|
+
|
|
215
|
+
@Override
|
|
216
|
+
public boolean onTouch(View view, MotionEvent event) {
|
|
217
|
+
switch (event.getAction()) {
|
|
218
|
+
case MotionEvent.ACTION_UP:
|
|
219
|
+
if (isDragging) {
|
|
220
|
+
isDragging = false;
|
|
221
|
+
} else {
|
|
222
|
+
FloatingWindowHelper.getInstance().onClickFloatingWindow(
|
|
223
|
+
FloatingWindowService.this);
|
|
224
|
+
}
|
|
225
|
+
downX = downY = 0;
|
|
226
|
+
break;
|
|
227
|
+
case MotionEvent.ACTION_DOWN:
|
|
228
|
+
downX = x = (int)event.getRawX();
|
|
229
|
+
downY = y = (int)event.getRawY();
|
|
230
|
+
break;
|
|
231
|
+
case MotionEvent.ACTION_MOVE:
|
|
232
|
+
int nowX = (int)event.getRawX();
|
|
233
|
+
int nowY = (int)event.getRawY();
|
|
234
|
+
if (Math.abs(nowX - downX) > touchSlop ||
|
|
235
|
+
Math.abs(nowY - downY) > touchSlop) {
|
|
236
|
+
isDragging = true;
|
|
237
|
+
}
|
|
238
|
+
int movedX = nowX - x;
|
|
239
|
+
int movedY = nowY - y;
|
|
240
|
+
x = nowX;
|
|
241
|
+
y = nowY;
|
|
242
|
+
mLayoutParams.x = mLayoutParams.x + movedX;
|
|
243
|
+
mLayoutParams.y = mLayoutParams.y + movedY;
|
|
244
|
+
mWindowManager.updateViewLayout(view, mLayoutParams);
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
default:
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|