@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,324 @@
|
|
|
1
|
+
#import "VeLivePlayerMultiObserver.h"
|
|
2
|
+
#import "TTSDKFramework/TVLManager.h"
|
|
3
|
+
|
|
4
|
+
@interface VeLivePlayerMultiObserver ()
|
|
5
|
+
@property(nonatomic, strong) NSMutableDictionary *observerDict;
|
|
6
|
+
@property(nonatomic, strong) dispatch_queue_t observerQueue;
|
|
7
|
+
@property(nonatomic, strong) TVLManager *player;
|
|
8
|
+
@end
|
|
9
|
+
|
|
10
|
+
@implementation VeLivePlayerMultiObserver
|
|
11
|
+
|
|
12
|
+
// 单例实现
|
|
13
|
+
+ (instancetype)sharedInstance {
|
|
14
|
+
static VeLivePlayerMultiObserver *instance = nil;
|
|
15
|
+
static dispatch_once_t onceToken;
|
|
16
|
+
dispatch_once(&onceToken, ^{
|
|
17
|
+
instance = [[self alloc] init];
|
|
18
|
+
});
|
|
19
|
+
return instance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
+ (instancetype)getInstance {
|
|
23
|
+
return [self sharedInstance];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 初始化方法
|
|
27
|
+
- (instancetype)init {
|
|
28
|
+
self = [super init];
|
|
29
|
+
if (self) {
|
|
30
|
+
_observerDict = [NSMutableDictionary dictionary];
|
|
31
|
+
_observerQueue = dispatch_queue_create("com.velive.player.multiobserver",
|
|
32
|
+
DISPATCH_QUEUE_SERIAL);
|
|
33
|
+
}
|
|
34
|
+
return self;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
- (void)setupPlayer:(TVLManager *)player {
|
|
38
|
+
if(self.player) {
|
|
39
|
+
[self clearObservers];
|
|
40
|
+
}
|
|
41
|
+
self.player = player;
|
|
42
|
+
[player setObserver:self];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 添加观察者到字典中
|
|
46
|
+
- (void)addObserver:(NSString *)observerId
|
|
47
|
+
observer:(id<VeLivePlayerObserver>)observer {
|
|
48
|
+
if (!observer || !observerId) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
dispatch_sync(self.observerQueue, ^{
|
|
53
|
+
[self.observerDict setObject:observer forKey:observerId];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 从字典中移除观察者
|
|
58
|
+
- (void)removeObserver:(NSString *)observerId {
|
|
59
|
+
if (!observerId) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
dispatch_sync(self.observerQueue, ^{
|
|
64
|
+
[self.observerDict removeObjectForKey:observerId];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 获取所有观察者
|
|
69
|
+
- (NSArray *)getObservers {
|
|
70
|
+
__block NSArray *result = nil;
|
|
71
|
+
dispatch_sync(self.observerQueue, ^{
|
|
72
|
+
result = [self.observerDict allValues];
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 清空所有观察者
|
|
78
|
+
- (void)clearObservers {
|
|
79
|
+
dispatch_sync(self.observerQueue, ^{
|
|
80
|
+
[self.observerDict removeAllObjects];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#pragma mark - VeLivePlayerObserver 方法实现
|
|
85
|
+
|
|
86
|
+
// 直接实现所有 VeLivePlayerObserver 协议方法,而不是依赖消息转发
|
|
87
|
+
|
|
88
|
+
- (void)onError:(TVLManager *)player error:(VeLivePlayerError *)error {
|
|
89
|
+
NSArray *observers = [self getObservers];
|
|
90
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
91
|
+
if ([observer respondsToSelector:@selector(onError:error:)]) {
|
|
92
|
+
[observer onError:player error:error];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
- (void)onFirstVideoFrameRender:(TVLManager *)player
|
|
98
|
+
isFirstFrame:(BOOL)isFirstFrame {
|
|
99
|
+
NSArray *observers = [self getObservers];
|
|
100
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
101
|
+
if ([observer respondsToSelector:@selector(onFirstVideoFrameRender:
|
|
102
|
+
isFirstFrame:)]) {
|
|
103
|
+
[observer onFirstVideoFrameRender:player isFirstFrame:isFirstFrame];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)onFirstAudioFrameRender:(TVLManager *)player
|
|
109
|
+
isFirstFrame:(BOOL)isFirstFrame {
|
|
110
|
+
NSArray *observers = [self getObservers];
|
|
111
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
112
|
+
if ([observer respondsToSelector:@selector(onFirstAudioFrameRender:
|
|
113
|
+
isFirstFrame:)]) {
|
|
114
|
+
[observer onFirstAudioFrameRender:player isFirstFrame:isFirstFrame];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
- (void)onStallStart:(TVLManager *)player {
|
|
120
|
+
NSArray *observers = [self getObservers];
|
|
121
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
122
|
+
if ([observer respondsToSelector:@selector(onStallStart:)]) {
|
|
123
|
+
[observer onStallStart:player];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
- (void)onStallEnd:(TVLManager *)player {
|
|
129
|
+
NSArray *observers = [self getObservers];
|
|
130
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
131
|
+
if ([observer respondsToSelector:@selector(onStallEnd:)]) {
|
|
132
|
+
[observer onStallEnd:player];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
- (void)onVideoRenderStall:(TVLManager *)player stallTime:(int64_t)stallTime {
|
|
138
|
+
NSArray *observers = [self getObservers];
|
|
139
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
140
|
+
if ([observer respondsToSelector:@selector(onVideoRenderStall:
|
|
141
|
+
stallTime:)]) {
|
|
142
|
+
[observer onVideoRenderStall:player stallTime:stallTime];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
- (void)onAudioRenderStall:(TVLManager *)player stallTime:(int64_t)stallTime {
|
|
148
|
+
NSArray *observers = [self getObservers];
|
|
149
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
150
|
+
if ([observer respondsToSelector:@selector(onAudioRenderStall:
|
|
151
|
+
stallTime:)]) {
|
|
152
|
+
[observer onAudioRenderStall:player stallTime:stallTime];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)onResolutionSwitch:(TVLManager *)player
|
|
158
|
+
resolution:(VeLivePlayerResolution)resolution
|
|
159
|
+
error:(VeLivePlayerError *)error
|
|
160
|
+
reason:(VeLivePlayerResolutionSwitchReason)reason {
|
|
161
|
+
NSArray *observers = [self getObservers];
|
|
162
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
163
|
+
if ([observer respondsToSelector:@selector
|
|
164
|
+
(onResolutionSwitch:resolution:error:reason:)]) {
|
|
165
|
+
[observer onResolutionSwitch:player
|
|
166
|
+
resolution:resolution
|
|
167
|
+
error:error
|
|
168
|
+
reason:reason];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
- (void)onVideoSizeChanged:(TVLManager *)player
|
|
174
|
+
width:(int)width
|
|
175
|
+
height:(int)height {
|
|
176
|
+
NSArray *observers = [self getObservers];
|
|
177
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
178
|
+
if ([observer respondsToSelector:@selector(onVideoSizeChanged:
|
|
179
|
+
width:height:)]) {
|
|
180
|
+
[observer onVideoSizeChanged:player width:width height:height];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
- (void)onReceiveSeiMessage:(TVLManager *)player message:(NSString *)message {
|
|
186
|
+
NSArray *observers = [self getObservers];
|
|
187
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
188
|
+
if ([observer respondsToSelector:@selector(onReceiveSeiMessage:message:)]) {
|
|
189
|
+
[observer onReceiveSeiMessage:player message:message];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (void)onMainBackupSwitch:(TVLManager *)player
|
|
195
|
+
streamType:(VeLivePlayerStreamType)streamType
|
|
196
|
+
error:(VeLivePlayerError *)error {
|
|
197
|
+
NSArray *observers = [self getObservers];
|
|
198
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
199
|
+
if ([observer respondsToSelector:@selector(onMainBackupSwitch:
|
|
200
|
+
streamType:error:)]) {
|
|
201
|
+
[observer onMainBackupSwitch:player streamType:streamType error:error];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
- (void)onPlayerStatusUpdate:(TVLManager *)player
|
|
207
|
+
status:(VeLivePlayerStatus)status {
|
|
208
|
+
NSArray *observers = [self getObservers];
|
|
209
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
210
|
+
if ([observer respondsToSelector:@selector(onPlayerStatusUpdate:status:)]) {
|
|
211
|
+
[observer onPlayerStatusUpdate:player status:status];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
- (void)onStatistics:(TVLManager *)player
|
|
217
|
+
statistics:(VeLivePlayerStatistics *)statistics {
|
|
218
|
+
NSArray *observers = [self getObservers];
|
|
219
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
220
|
+
if ([observer respondsToSelector:@selector(onStatistics:statistics:)]) {
|
|
221
|
+
[observer onStatistics:player statistics:statistics];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
- (void)onSnapshotComplete:(TVLManager *)player image:(UIImage *)image {
|
|
227
|
+
NSArray *observers = [self getObservers];
|
|
228
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
229
|
+
if ([observer respondsToSelector:@selector(onSnapshotComplete:image:)]) {
|
|
230
|
+
[observer onSnapshotComplete:player image:image];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
- (void)onRenderVideoFrame:(TVLManager *)player
|
|
236
|
+
videoFrame:(VeLivePlayerVideoFrame *)videoFrame {
|
|
237
|
+
NSArray *observers = [self getObservers];
|
|
238
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
239
|
+
if ([observer respondsToSelector:@selector(onRenderVideoFrame:
|
|
240
|
+
videoFrame:)]) {
|
|
241
|
+
// 跳过 rn-observer,只转发给其他观察者(如 pip-observer)
|
|
242
|
+
NSString *observerId = nil;
|
|
243
|
+
for (NSString *key in self.observerDict) {
|
|
244
|
+
if ([self.observerDict objectForKey:key] == observer) {
|
|
245
|
+
observerId = key;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 如果观察者ID包含 "rn",则跳过帧处理
|
|
251
|
+
if (observerId && [observerId containsString:@"rn"]) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[observer onRenderVideoFrame:player videoFrame:videoFrame];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
- (void)onRenderAudioFrame:(TVLManager *)player
|
|
261
|
+
audioFrame:(VeLivePlayerAudioFrame *)audioFrame {
|
|
262
|
+
NSArray *observers = [self getObservers];
|
|
263
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
264
|
+
if ([observer respondsToSelector:@selector(onRenderAudioFrame:
|
|
265
|
+
audioFrame:)]) {
|
|
266
|
+
// 跳过 rn-observer,只转发给其他观察者
|
|
267
|
+
NSString *observerId = nil;
|
|
268
|
+
for (NSString *key in self.observerDict) {
|
|
269
|
+
if ([self.observerDict objectForKey:key] == observer) {
|
|
270
|
+
observerId = key;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 如果观察者ID包含 "rn",则跳过帧处理
|
|
276
|
+
if (observerId && [observerId containsString:@"rn"]) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
[observer onRenderAudioFrame:player audioFrame:audioFrame];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
- (void)onStreamFailedOpenSuperResolution:(TVLManager *)player
|
|
286
|
+
error:(VeLivePlayerError *)error {
|
|
287
|
+
NSArray *observers = [self getObservers];
|
|
288
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
289
|
+
if ([observer respondsToSelector:@selector
|
|
290
|
+
(onStreamFailedOpenSuperResolution:error:)]) {
|
|
291
|
+
[observer onStreamFailedOpenSuperResolution:player error:error];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
- (NSData *)getDrmResourceLoaderCertificateData:(TVLManager *)player {
|
|
297
|
+
NSArray *observers = [self getObservers];
|
|
298
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
299
|
+
if ([observer respondsToSelector:@selector
|
|
300
|
+
(getDrmResourceLoaderCertificateData:)]) {
|
|
301
|
+
NSData *data = [observer getDrmResourceLoaderCertificateData:player];
|
|
302
|
+
if (data) {
|
|
303
|
+
return data;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return nil;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
- (NSString *)getDrmResourceLoaderLicenseUrl:(TVLManager *)player {
|
|
311
|
+
NSArray *observers = [self getObservers];
|
|
312
|
+
for (id<VeLivePlayerObserver> observer in observers) {
|
|
313
|
+
if ([observer
|
|
314
|
+
respondsToSelector:@selector(getDrmResourceLoaderLicenseUrl:)]) {
|
|
315
|
+
NSString *url = [observer getDrmResourceLoaderLicenseUrl:player];
|
|
316
|
+
if (url) {
|
|
317
|
+
return url;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return nil;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
@class TVLManager;
|
|
3
|
+
@class UIView;
|
|
4
|
+
|
|
5
|
+
@protocol VeLivePictureInPictureManagerListener <NSObject>
|
|
6
|
+
- (void)onStartPictureInPicture;
|
|
7
|
+
- (void)onStopPictureInPicture;
|
|
8
|
+
- (void)onClickPictureInPicture;
|
|
9
|
+
- (void)onError:(NSInteger)code extraData:(NSDictionary *)extraData;
|
|
10
|
+
@end
|
|
11
|
+
|
|
12
|
+
@interface VeLivePictureInPictureManager : NSObject
|
|
13
|
+
|
|
14
|
+
// Expose shared instance methods to RN
|
|
15
|
+
+ (instancetype)sharedInstance;
|
|
16
|
+
+ (instancetype)getInstance;
|
|
17
|
+
|
|
18
|
+
// Expose other methods that need to be called from RN
|
|
19
|
+
- (void)setupPlayer:(TVLManager *)player;
|
|
20
|
+
- (void)enablePictureInPicture;
|
|
21
|
+
- (void)disablePictureInPicture;
|
|
22
|
+
- (void)startPictureInPicture;
|
|
23
|
+
- (void)stopPictureInPicture;
|
|
24
|
+
- (void)destroyPictureInPicture;
|
|
25
|
+
- (BOOL)isPictureInPictureSupported;
|
|
26
|
+
- (void)setListener:(id<VeLivePictureInPictureManagerListener>)listener;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#import "PictureInPictureManager.h"
|
|
2
|
+
#import "../VeLivePlayerMultiObserver.h"
|
|
3
|
+
#import "TTSDKFramework/TVLManager.h"
|
|
4
|
+
#import "VeLivePictureInPictureController.h"
|
|
5
|
+
#import <AVKit/AVKit.h>
|
|
6
|
+
#import <UIKit/UIKit.h>
|
|
7
|
+
|
|
8
|
+
@protocol VeLivePlayerObserver;
|
|
9
|
+
@protocol VeLivePictureInPictureDelegate;
|
|
10
|
+
@protocol VeLivePictureInPictureManagerListener;
|
|
11
|
+
|
|
12
|
+
// Add class extension to declare private properties instead of redefining
|
|
13
|
+
// interface
|
|
14
|
+
@interface VeLivePictureInPictureManager () <
|
|
15
|
+
VeLivePlayerObserver, VeLivePictureInPictureDelegate,
|
|
16
|
+
VeLivePictureInPictureManagerListener>
|
|
17
|
+
@property(nonatomic, strong) VeLivePictureInPictureController *pipController;
|
|
18
|
+
@property(nonatomic, assign) CGRect pipVideoFrame;
|
|
19
|
+
@property(nonatomic, assign) CGSize pipVideoSize;
|
|
20
|
+
@property(nonatomic, assign) BOOL enableVideoObserver;
|
|
21
|
+
@property(nonatomic, strong) TVLManager *player;
|
|
22
|
+
@property(nonatomic, weak) id<VeLivePictureInPictureManagerListener> listener;
|
|
23
|
+
@property(nonatomic, assign) BOOL pipAutoStart;
|
|
24
|
+
@end
|
|
25
|
+
|
|
26
|
+
NSString *const kObserverKey = @"pip-observer";
|
|
27
|
+
|
|
28
|
+
@implementation VeLivePictureInPictureManager
|
|
29
|
+
|
|
30
|
+
+ (instancetype)sharedInstance {
|
|
31
|
+
static VeLivePictureInPictureManager *instance = nil;
|
|
32
|
+
static dispatch_once_t onceToken;
|
|
33
|
+
dispatch_once(&onceToken, ^{
|
|
34
|
+
instance = [[self alloc] init];
|
|
35
|
+
});
|
|
36
|
+
return instance;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
+ (instancetype)getInstance {
|
|
40
|
+
return [self sharedInstance];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
- (void)setupPlayer:(TVLManager *)player {
|
|
44
|
+
if (self.player) {
|
|
45
|
+
[self destroyPictureInPicture];
|
|
46
|
+
}
|
|
47
|
+
self.player = player;
|
|
48
|
+
|
|
49
|
+
// Reset video frame observer status
|
|
50
|
+
self.enableVideoObserver = NO;
|
|
51
|
+
self.pipAutoStart = NO;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)setupPictureInPicturePrepareCompletion:
|
|
55
|
+
(VELPipPrepareCompletionBlock)prepareCompletion {
|
|
56
|
+
if (!self.pipController) {
|
|
57
|
+
self.pipController = [[VeLivePictureInPictureController alloc]
|
|
58
|
+
initWithType:VeLivePictureInPictureTypeAuto
|
|
59
|
+
contentView:self.player.playerView];
|
|
60
|
+
[self setupPipVideoFrameAndSize];
|
|
61
|
+
self.pipController.delegate = self;
|
|
62
|
+
self.pipController.contentController = nil;
|
|
63
|
+
self.pipController.autoHideContentController = NO;
|
|
64
|
+
if (@available(iOS 14.0, *)) {
|
|
65
|
+
self.player.supportPictureInPictureMode = YES;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
[self.pipController prepareWithCompletion:prepareCompletion];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
- (void)setupPipVideoFrameAndSize {
|
|
72
|
+
if (!CGSizeEqualToSize(self.pipVideoSize, CGSizeZero)) {
|
|
73
|
+
[self.pipController setVideoSize:self.pipVideoSize];
|
|
74
|
+
}
|
|
75
|
+
if (!CGRectEqualToRect(self.pipVideoFrame, CGRectZero)) {
|
|
76
|
+
[self.pipController setVideoFrame:self.pipVideoFrame];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
- (void)enableVideoFrameObserver {
|
|
81
|
+
NSLog(@"PIP: Enable video frame observer");
|
|
82
|
+
if (self.enableVideoObserver) {
|
|
83
|
+
// If already enabled, disable then re-enable to ensure the observer is
|
|
84
|
+
// re-registered
|
|
85
|
+
[self disableVideoFrameObserver];
|
|
86
|
+
}
|
|
87
|
+
self.enableVideoObserver = YES;
|
|
88
|
+
[self.player
|
|
89
|
+
enableVideoFrameObserver:YES
|
|
90
|
+
pixelFormat:VeLivePlayerPixelFormatBGRA32
|
|
91
|
+
bufferType:VeLivePlayerVideoBufferTypeSampleBuffer];
|
|
92
|
+
NSLog(@"PIP: Video frame observer enabled");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
- (void)disableVideoFrameObserver {
|
|
96
|
+
NSLog(@"PIP: Disable video frame observer");
|
|
97
|
+
if (!self.enableVideoObserver) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
self.enableVideoObserver = NO;
|
|
101
|
+
[self.player enableVideoFrameObserver:NO
|
|
102
|
+
pixelFormat:VeLivePlayerPixelFormatUnknown
|
|
103
|
+
bufferType:VeLivePlayerVideoBufferTypeUnknown];
|
|
104
|
+
NSLog(@"PIP: Video frame observer disabled");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
- (void)startPictureInPicture {
|
|
108
|
+
NSLog(@"PIP: Start picture-in-picture");
|
|
109
|
+
[self enableVideoFrameObserver];
|
|
110
|
+
|
|
111
|
+
// Ensure observer is properly added
|
|
112
|
+
[[VeLivePlayerMultiObserver sharedInstance] removeObserver:kObserverKey];
|
|
113
|
+
[[VeLivePlayerMultiObserver sharedInstance] addObserver:kObserverKey
|
|
114
|
+
observer:self];
|
|
115
|
+
|
|
116
|
+
// Use main thread with delay to ensure state is updated
|
|
117
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
118
|
+
[self setupPictureInPicturePrepareCompletion:^(
|
|
119
|
+
VeLivePictureInPictureController *_Nonnull pipController,
|
|
120
|
+
NSError *_Nullable error) {
|
|
121
|
+
if (error != nil) {
|
|
122
|
+
NSLog(@"PIP: Failed to start picture-in-picture: %@", error);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!self.player.isPlaying) {
|
|
126
|
+
NSLog(@"PIP: Please start playing before entering PIP mode");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!self.pipController.canStartPictureInPicture) {
|
|
130
|
+
NSLog(@"PIP: Cannot start picture-in-picture now, please try again "
|
|
131
|
+
@"later");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
NSLog(@"PIP: Ready to start picture-in-picture");
|
|
135
|
+
[self.pipController startPictureInPicture];
|
|
136
|
+
}];
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
- (void)stopPictureInPicture {
|
|
141
|
+
[self disableVideoFrameObserver];
|
|
142
|
+
[[VeLivePlayerMultiObserver sharedInstance] removeObserver:kObserverKey];
|
|
143
|
+
[self.pipController stopPictureInPicture];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
- (void)destroyPictureInPicture {
|
|
147
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
148
|
+
[self disableVideoFrameObserver];
|
|
149
|
+
[self.pipController destroyPictureInPicture];
|
|
150
|
+
[self setPipController:nil];
|
|
151
|
+
[[VeLivePlayerMultiObserver sharedInstance] removeObserver:kObserverKey];
|
|
152
|
+
self.player = nil;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- (BOOL)isPictureInPictureStarted {
|
|
157
|
+
return VeLivePictureInPictureController.isPictureInPictureStarted;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
- (BOOL)isPictureInPictureSupported {
|
|
161
|
+
return [VeLivePictureInPictureController isPictureInPictureSupported];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
- (void)enablePictureInPicture {
|
|
165
|
+
NSLog(@"PIP: Enable picture-in-picture");
|
|
166
|
+
self.pipAutoStart = YES;
|
|
167
|
+
// main thread
|
|
168
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
169
|
+
if (@available(iOS 14.2, *)) {
|
|
170
|
+
[self setupPictureInPicturePrepareCompletion:^(
|
|
171
|
+
VeLivePictureInPictureController *_Nonnull pipController,
|
|
172
|
+
NSError *_Nullable error) {
|
|
173
|
+
// Set in callback to ensure controller is ready
|
|
174
|
+
[pipController setCanStartPictureInPictureAutomaticallyFromInline:YES];
|
|
175
|
+
}];
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
- (void)disablePictureInPicture {
|
|
181
|
+
NSLog(@"PIP: Disable picture-in-picture");
|
|
182
|
+
self.pipAutoStart = NO;
|
|
183
|
+
if (!self.pipController) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
187
|
+
if (@available(iOS 14.2, *)) {
|
|
188
|
+
[self.pipController
|
|
189
|
+
setCanStartPictureInPictureAutomaticallyFromInline:NO];
|
|
190
|
+
[self.pipController destroyPictureInPicture];
|
|
191
|
+
self.pipController = nil;
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
- (void)setListener:(id<VeLivePictureInPictureManagerListener>)listener {
|
|
197
|
+
_listener = listener;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// MARK: - VeLiveVideoFrameListener
|
|
201
|
+
- (void)onRenderVideoFrame:(TVLManager *_Nonnull)player
|
|
202
|
+
videoFrame:(VeLivePlayerVideoFrame *_Nonnull)videoFrame {
|
|
203
|
+
@autoreleasepool {
|
|
204
|
+
if (@available(iOS 15.0, *)) {
|
|
205
|
+
if (videoFrame.bufferType == VeLivePlayerVideoBufferTypePixelBuffer) {
|
|
206
|
+
[self.pipController enqueuePixelBuffer:videoFrame.pixelBuffer];
|
|
207
|
+
} else if (videoFrame.bufferType ==
|
|
208
|
+
VeLivePlayerVideoBufferTypeSampleBuffer) {
|
|
209
|
+
[self.pipController enqueueSampleBuffer:videoFrame.sampleBuffer];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
- (void)onVideoSizeChanged:(TVLManager *)player
|
|
216
|
+
width:(int)width
|
|
217
|
+
height:(int)height {
|
|
218
|
+
self.pipVideoSize = CGSizeMake(width, height);
|
|
219
|
+
if (!CGSizeEqualToSize(CGSizeZero, player.videoAreaFrame.size)) {
|
|
220
|
+
self.pipVideoFrame = player.videoAreaFrame;
|
|
221
|
+
} else {
|
|
222
|
+
self.pipVideoFrame = player.playerView.frame;
|
|
223
|
+
}
|
|
224
|
+
[self setupPipVideoFrameAndSize];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
- (void)onError:(TVLManager *)player error:(VeLivePlayerError *)error {
|
|
228
|
+
NSLog(@"VeLiveQuickStartDemo: Error %ld, %@", error.code, error.errorMsg);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/// MARK: - VeLivePictureInPictureDelegate
|
|
232
|
+
- (void)pictureInPictureIsReady:
|
|
233
|
+
(VeLivePictureInPictureController *)pictureInPicture {
|
|
234
|
+
NSLog(@"PIP: Picture-in-picture is ready");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
- (void)pictureInPictureWillStart:
|
|
238
|
+
(VeLivePictureInPictureController *)pictureInPicture {
|
|
239
|
+
NSLog(@"PIP: Picture-in-picture will start...");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
- (void)pictureInPictureDidStart:
|
|
243
|
+
(VeLivePictureInPictureController *)pictureInPicture {
|
|
244
|
+
NSLog(@"PIP: Picture-in-picture did start");
|
|
245
|
+
if (self.listener) {
|
|
246
|
+
[self.listener onStartPictureInPicture];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
- (void)pictureInPicture:(VeLivePictureInPictureController *)pictureInPicture
|
|
250
|
+
failedToStartWithError:(NSError *)error {
|
|
251
|
+
NSLog(@"PIP: Failed to start picture-in-picture: %@", error);
|
|
252
|
+
if (self.listener) {
|
|
253
|
+
[self.listener onError:error.code extraData:error.userInfo];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
- (void)pictureInPictureWillStop:
|
|
257
|
+
(VeLivePictureInPictureController *)pictureInPicture
|
|
258
|
+
isUserStop:(BOOL)isUserStop {
|
|
259
|
+
NSLog(@"PIP: Picture-in-picture will stop");
|
|
260
|
+
}
|
|
261
|
+
- (void)pictureInPictureDidStop:
|
|
262
|
+
(VeLivePictureInPictureController *)pictureInPicture
|
|
263
|
+
isUserStop:(BOOL)isUserStop {
|
|
264
|
+
NSLog(@"PIP: Picture-in-picture stopped");
|
|
265
|
+
if (self.listener) {
|
|
266
|
+
[self.listener onStopPictureInPicture];
|
|
267
|
+
}
|
|
268
|
+
if (!self.pipAutoStart) {
|
|
269
|
+
[self.pipController destroyPictureInPicture];
|
|
270
|
+
self.pipController = nil;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@end
|