@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.
- 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 +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
|
+
}
|
package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/VeLiveRefManager.java
ADDED
|
@@ -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
|