@volcengine/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 (25) hide show
  1. package/android/src/main/AndroidManifest.xml +7 -1
  2. package/android/src/main/AndroidManifestNew.xml +15 -1
  3. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcLiveModule.java +28 -20
  4. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcView.java +7 -8
  5. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowHelper.java +224 -0
  6. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +171 -0
  7. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/IFloatingWindowHelper.java +80 -0
  8. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/PictureInPictureManager.java +280 -0
  9. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/VeLiveRefManager.java +119 -0
  10. package/android/src/main/res/drawable/button_close.xml +14 -0
  11. package/android/src/main/res/layout/floating_window_layout.xml +19 -0
  12. package/ios/VeLivePlayerMultiObserver.h +54 -0
  13. package/ios/VeLivePlayerMultiObserver.m +324 -0
  14. package/ios/pictureInpicture/PictureInPictureManager.h +29 -0
  15. package/ios/pictureInpicture/PictureInPictureManager.m +274 -0
  16. package/ios/pictureInpicture/VeLivePictureInPictureController.h +207 -0
  17. package/ios/pictureInpicture/VeLivePictureInPictureController.m +3393 -0
  18. package/lib/commonjs/index.js +524 -8
  19. package/lib/module/index.js +524 -8
  20. package/lib/typescript/core/api.d.ts +88 -1
  21. package/lib/typescript/core/callback.d.ts +52 -0
  22. package/lib/typescript/platforms/android/extends.d.ts +1 -1
  23. package/lib/typescript/platforms/android/pictureInpicture.d.ts +26 -0
  24. package/lib/typescript/platforms/ios/pictureInpicture.d.ts +32 -0
  25. package/package.json +1 -1
@@ -0,0 +1,280 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import android.content.Context;
4
+ import android.content.Intent;
5
+ import android.util.Log;
6
+ import android.view.SurfaceView;
7
+ import com.ss.videoarch.liveplayer.VeLivePlayer;
8
+ import java.util.HashMap;
9
+ import java.util.Map;
10
+
11
+ /**
12
+ * Manager class that provides easy access to Picture-in-Picture functionality
13
+ * for React Native code.
14
+ */
15
+ public class PictureInPictureManager {
16
+ private static final String TAG = "PipManager";
17
+ private static PictureInPictureManager sInstance;
18
+ private final FloatingWindowHelper mFloatingWindowHelper;
19
+
20
+ private Context mContext;
21
+ private SurfaceView mSurfaceView;
22
+ private VeLivePlayer mPlayer;
23
+ // Flag indicating whether we're in the process of switching to PIP mode
24
+ // Used to control SurfaceView switching state
25
+ private boolean mSwitchingToPip = false;
26
+ private IFloatingWindowHelper.Config mConfig =
27
+ new IFloatingWindowHelper.Config(16f / 9f, 0, 0);
28
+ private Listener mListener;
29
+
30
+ private PictureInPictureManager() {
31
+ mFloatingWindowHelper = FloatingWindowHelper.getInstance();
32
+ setupFloatingWindowListener();
33
+ }
34
+
35
+ public static synchronized PictureInPictureManager getInstance() {
36
+ if (sInstance == null) {
37
+ sInstance = new PictureInPictureManager();
38
+ }
39
+ return sInstance;
40
+ }
41
+
42
+ /**
43
+ * Set the player instance to be managed
44
+ */
45
+ public void setupPlayer(VeLivePlayer player, Context context,
46
+ SurfaceView surfaceView) {
47
+ mPlayer = player;
48
+ mContext = context;
49
+ mSurfaceView = surfaceView;
50
+
51
+ // Register player with reference manager
52
+ if (player instanceof VeLiveRefManager.IObject) {
53
+ VeLiveRefManager.addRef((VeLiveRefManager.IObject)player);
54
+ }
55
+ }
56
+
57
+ public void setupConfig(float aspectRatio, int x, int y) {
58
+ mConfig = new IFloatingWindowHelper.Config(aspectRatio, x, y);
59
+ }
60
+
61
+ /**
62
+ * Check if the device supports picture-in-picture
63
+ * @return true if supported and has permission, false otherwise
64
+ */
65
+ public boolean isPictureInPictureSupported() {
66
+ return FloatingWindowHelper.isPictureInPictureSupported();
67
+ }
68
+
69
+ public boolean startPictureInPicture() {
70
+ return startPictureInPicture(mConfig);
71
+ }
72
+
73
+ public boolean startPictureInPicture(float aspectRatio, int x, int y) {
74
+ mConfig = new IFloatingWindowHelper.Config(aspectRatio, x, y);
75
+ return startPictureInPicture(mConfig);
76
+ }
77
+
78
+ /**
79
+ * Start picture-in-picture mode
80
+ * @return true if PIP was started, false otherwise
81
+ */
82
+ public boolean startPictureInPicture(IFloatingWindowHelper.Config config) {
83
+ Log.d(TAG, "Starting picture-in-picture mode");
84
+ if (mPlayer == null) {
85
+ Log.e(TAG, "Cannot start PIP: player is null");
86
+ return false;
87
+ }
88
+
89
+ if (mFloatingWindowHelper.isOpen()) {
90
+ Log.d(TAG, "PIP already active");
91
+ return true;
92
+ }
93
+
94
+ // Check overlay permissions
95
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M &&
96
+ !android.provider.Settings.canDrawOverlays(mContext)) {
97
+ Log.d(TAG, "Requesting overlay permission");
98
+ mFloatingWindowHelper.requestOverlayPermission(mContext);
99
+ return false;
100
+ }
101
+
102
+ // Mark that we're switching to PIP mode
103
+ // This flag will be used in onUpdateSurfaceView callback
104
+ mSwitchingToPip = true;
105
+
106
+ // Default empty data
107
+ Map<String, Object> extraData = new HashMap<>();
108
+
109
+ // Call FloatingWindowHelper to open the floating window
110
+ mFloatingWindowHelper.openFloatingWindow(mContext, config, extraData);
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Stop picture-in-picture mode
116
+ */
117
+ public void stopPictureInPicture() {
118
+ Log.d(TAG, "Stopping picture-in-picture mode");
119
+
120
+ // Ensure the floating window is closed
121
+ if (mFloatingWindowHelper.isOpen() && mContext != null) {
122
+ mFloatingWindowHelper.closeFloatingWindow(mContext);
123
+ }
124
+
125
+ // Reset the state flag regardless of whether the window was closed
126
+ // successfully
127
+ mSwitchingToPip = false;
128
+ }
129
+
130
+ /**
131
+ * Check if picture-in-picture is active
132
+ *
133
+ * @return true if PIP is active, false otherwise
134
+ */
135
+ public boolean isPictureInPictureActive() {
136
+ return mFloatingWindowHelper.isOpen();
137
+ }
138
+
139
+ /**
140
+ * Request overlay permission if needed
141
+ *
142
+ * @param context Android context
143
+ */
144
+ public void requestOverlayPermission(Context context) {
145
+ mFloatingWindowHelper.requestOverlayPermission(context);
146
+ }
147
+
148
+ /**
149
+ * Set up floating window listener
150
+ */
151
+ private void setupFloatingWindowListener() {
152
+ mFloatingWindowHelper.setEventListener(
153
+ new IFloatingWindowHelper.Listener() {
154
+ @Override
155
+ public void onOpenFloatingWindowResult(
156
+ int errCode, Map<String, Object> extraData) {
157
+ if (errCode == ERR_RETRY) {
158
+ // Retry
159
+ startPictureInPicture(mConfig);
160
+ return;
161
+ }
162
+ if (mListener != null) {
163
+ if (errCode == ERR_NO) {
164
+ mListener.onStartPictureInPicture();
165
+ } else {
166
+ mListener.onError(errCode, extraData);
167
+ }
168
+ }
169
+ Log.d(TAG, "PIP window opened");
170
+ }
171
+
172
+ @Override
173
+ public void onUpdateSurfaceView(SurfaceView surfaceView) {
174
+ Log.d(TAG, "onUpdateSurfaceView called with new SurfaceView");
175
+ if (mSwitchingToPip && mPlayer != null && surfaceView != null) {
176
+ Log.d(TAG, "Switching player to PIP surface");
177
+ // Switch player to the SurfaceView provided by
178
+ // FloatingWindowService
179
+ mPlayer.setSurfaceHolder(surfaceView.getHolder());
180
+ // Reset the flag after switching is complete
181
+ mSwitchingToPip = false;
182
+ }
183
+ }
184
+
185
+ @Override
186
+ public void onClickFloatingWindow(Context context) {
187
+ if (mListener != null) {
188
+ mListener.onClickPictureInPicture();
189
+ }
190
+ Log.d(TAG, "PIP window clicked");
191
+
192
+ try {
193
+ // Get the package manager and launch the app's main activity
194
+ String packageName = context.getPackageName();
195
+ Intent launchIntent =
196
+ context.getPackageManager().getLaunchIntentForPackage(
197
+ packageName);
198
+
199
+ if (launchIntent != null) {
200
+ Log.d(TAG, "Launching app with intent: " + launchIntent);
201
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
202
+ context.startActivity(launchIntent);
203
+ } else {
204
+ Log.e(TAG, "Could not create launch intent for package: " +
205
+ packageName);
206
+ }
207
+ } catch (Exception e) {
208
+ Log.e(TAG, "Error launching app: " + e.getMessage(), e);
209
+ }
210
+
211
+ // Close the floating window
212
+ mFloatingWindowHelper.closeFloatingWindow(context);
213
+ }
214
+
215
+ @Override
216
+ public void onClickFloatingWindowCloseBtn(Context context) {
217
+ Log.d(TAG, "PIP close button clicked");
218
+ mFloatingWindowHelper.closeFloatingWindow(context);
219
+ }
220
+
221
+ @Override
222
+ public void onCloseFloatingWindow(Map<String, Object> extraData) {
223
+ if (mListener != null) {
224
+ mListener.onStopPictureInPicture();
225
+ }
226
+ Log.d(TAG, "PIP window closed");
227
+ // Ensure we're completely out of PIP mode
228
+ mSwitchingToPip = false;
229
+ if (mPlayer != null && mSurfaceView != null) {
230
+ Log.d(TAG, "Switching player back to original surface");
231
+ mPlayer.setSurfaceHolder(mSurfaceView.getHolder());
232
+ }
233
+
234
+ // Release player reference when closing
235
+ if (mPlayer instanceof VeLiveRefManager.IObject) {
236
+ VeLiveRefManager.decRef((VeLiveRefManager.IObject)mPlayer);
237
+ }
238
+ }
239
+ });
240
+ }
241
+
242
+ public void setListener(Listener listener) { mListener = listener; }
243
+
244
+ public interface Listener {
245
+ /**
246
+ * Callback when picture-in-picture mode starts
247
+ * Default empty implementation, subclasses can override as needed
248
+ */
249
+ public default void onStartPictureInPicture() {
250
+ // Empty implementation, subclasses can override as needed
251
+ }
252
+
253
+ /**
254
+ * Callback when picture-in-picture mode stops
255
+ * Default empty implementation, subclasses can override as needed
256
+ */
257
+ public default void onStopPictureInPicture() {
258
+ // Empty implementation, subclasses can override as needed
259
+ }
260
+
261
+ /**
262
+ * Callback when the picture-in-picture window is clicked
263
+ * Default empty implementation, subclasses can override as needed
264
+ */
265
+ public default void onClickPictureInPicture() {
266
+ // Empty implementation, subclasses can override as needed
267
+ }
268
+
269
+ /**
270
+ * Error callback
271
+ * @param errCode Error code, 0 means success, other values indicate errors
272
+ * @param extraData Additional data, can be used to pass error information
273
+ * Default empty implementation, subclasses can override as needed
274
+ */
275
+ public default void onError(int errCode, Map<String, Object> extraData) {
276
+ // Empty implementation, subclasses can override as needed
277
+ }
278
+ }
279
+ ;
280
+ }
@@ -0,0 +1,119 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import android.util.Log;
4
+ import java.util.HashMap;
5
+ import java.util.Map;
6
+
7
+ /**
8
+ * Player reference count manager
9
+ * Used to share player instances between Activity and floating window
10
+ */
11
+ public class VeLiveRefManager {
12
+ private static final String TAG = "VeLiveRefManager";
13
+ private static final VeLiveRefManager INSTANCE = new VeLiveRefManager();
14
+
15
+ /**
16
+ * Interface for objects that can be reference-counted
17
+ */
18
+ public interface IObject {
19
+ /**
20
+ * Get the unique identifier for the object, defaults to the object itself
21
+ */
22
+ default Object getId() { return this; }
23
+
24
+ /**
25
+ * Callback triggered when reference count drops to zero
26
+ */
27
+ void onDecRef();
28
+ }
29
+
30
+ private static final Map<Object, Integer> mRefMap = new HashMap<>();
31
+
32
+ private VeLiveRefManager() {
33
+ // Private constructor to ensure singleton pattern
34
+ }
35
+
36
+ public static VeLiveRefManager getInstance() { return INSTANCE; }
37
+
38
+ /**
39
+ * Increase the reference count for an object
40
+ * @param object The object to increase reference count for
41
+ */
42
+ public synchronized static void addRef(IObject object) {
43
+ if (object == null) {
44
+ Log.d(TAG, "addRef: object is null");
45
+ return;
46
+ }
47
+
48
+ Object id = object.getId();
49
+ int refCnt = 1;
50
+ if (mRefMap.containsKey(id)) {
51
+ refCnt += mRefMap.get(id);
52
+ }
53
+ mRefMap.put(id, refCnt);
54
+ Log.d(TAG, "addRef: object=" + id + ", new refCnt=" + refCnt);
55
+ }
56
+
57
+ /**
58
+ * Decrease the reference count for an object
59
+ * When the count reaches zero, the object's onDecRef method will be called
60
+ * @param object The object to decrease reference count for
61
+ */
62
+ public synchronized static void decRef(IObject object) {
63
+ if (object == null) {
64
+ Log.d(TAG, "decRef: object is null");
65
+ return;
66
+ }
67
+
68
+ Object id = object.getId();
69
+ if (!mRefMap.containsKey(id)) {
70
+ Log.d(TAG, "decRef: object=" + id + " not found in refMap");
71
+ return;
72
+ }
73
+
74
+ int refCnt = mRefMap.get(id) - 1;
75
+ Log.d(TAG, "decRef: object=" + id + ", new refCnt=" + refCnt);
76
+
77
+ if (refCnt <= 0) {
78
+ mRefMap.remove(id);
79
+ Log.d(TAG,
80
+ "decRef: object=" + id + " removed from refMap, calling onDecRef");
81
+ object.onDecRef();
82
+ } else {
83
+ mRefMap.put(id, refCnt);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get the current reference count for an object
89
+ * @param object The object to query
90
+ * @return The reference count, or 0 if the object doesn't exist
91
+ */
92
+ public synchronized int getRefCount(IObject object) {
93
+ if (object == null) {
94
+ return 0;
95
+ }
96
+
97
+ Object id = object.getId();
98
+ if (!mRefMap.containsKey(id)) {
99
+ return 0;
100
+ }
101
+
102
+ return mRefMap.get(id);
103
+ }
104
+
105
+ /**
106
+ * Clear all reference count records
107
+ * Note: This will call onDecRef for all objects
108
+ */
109
+ public synchronized void clearAll() {
110
+ Log.d(TAG, "clearAll: clearing all references");
111
+ for (Map.Entry<Object, Integer> entry : mRefMap.entrySet()) {
112
+ if (entry.getKey() instanceof IObject) {
113
+ Log.d(TAG, "clearAll: calling onDecRef for object=" + entry.getKey());
114
+ ((IObject)entry.getKey()).onDecRef();
115
+ }
116
+ }
117
+ mRefMap.clear();
118
+ }
119
+ }
@@ -0,0 +1,14 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="24dp"
3
+ android:height="24dp"
4
+ android:autoMirrored="true"
5
+ android:viewportWidth="612"
6
+ android:viewportHeight="612">
7
+ <path
8
+ android:fillColor="#FF000000"
9
+ android:pathData="M612,306C612,137.004 474.995,0 306,0C137.004,0 0,137.004 0,
10
+ 306c0,168.995 137.004,306 306,306C474.995,612 612,474.995 612,
11
+ 306zM168.3,424.032L286.333,306L168.3,187.967l19.667,-19.667L306,
12
+ 286.333L424.032,168.3l19.668,19.667L325.667,306L443.7,424.032L424.032,
13
+ 443.7L306,325.667L187.967,443.7L168.3,424.032z" />
14
+ </vector>
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <RelativeLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ android:layout_width="match_parent"
5
+ android:layout_height="match_parent">
6
+
7
+ <SurfaceView
8
+ android:id="@+id/surface_view"
9
+ android:layout_width="match_parent"
10
+ android:layout_height="match_parent">
11
+ </SurfaceView>
12
+ <Button
13
+ android:id="@+id/surface_close_btn"
14
+ android:background="@drawable/button_close"
15
+ android:layout_width="20dp"
16
+ android:layout_height="20dp"
17
+ android:layout_marginTop="4dp"
18
+ android:layout_marginStart="4dp" />
19
+ </RelativeLayout>
@@ -0,0 +1,54 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import "TTSDKFramework/TVLManager.h"
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ /**
7
+ * 多观察者管理器 - 用于同时支持多个 VeLivePlayerObserver
8
+ * 解决不同组件同时需要接收回调的问题
9
+ */
10
+ @interface VeLivePlayerMultiObserver : NSObject <VeLivePlayerObserver>
11
+
12
+ /**
13
+ * 获取单例实例
14
+ */
15
+ + (instancetype)sharedInstance;
16
+
17
+ /**
18
+ * 获取单例实例,等同于 sharedInstance
19
+ */
20
+ + (instancetype)getInstance;
21
+
22
+ /**
23
+ * 设置播放器的观察者为此 MultiObserver
24
+ * @param player 播放器实例
25
+ */
26
+ - (void)setupPlayer:(TVLManager *)player;
27
+
28
+ /**
29
+ * 添加一个观察者到代理列表
30
+ * @param observerId 观察者ID,相同ID会覆盖之前的观察者
31
+ * @param observer 观察者对象
32
+ */
33
+ - (void)addObserver:(NSString *)observerId observer:(id<VeLivePlayerObserver>)observer;
34
+
35
+ /**
36
+ * 从代理列表中移除观察者
37
+ * @param observerId 观察者ID
38
+ */
39
+ - (void)removeObserver:(NSString *)observerId;
40
+
41
+ /**
42
+ * 获取当前所有注册的观察者
43
+ * @return 观察者数组
44
+ */
45
+ - (NSArray *)getObservers;
46
+
47
+ /**
48
+ * 清除所有观察者
49
+ */
50
+ - (void)clearObservers;
51
+
52
+ @end
53
+
54
+ NS_ASSUME_NONNULL_END