开发

直播

1.1 协议名

BBASMLiveAdapterProtocol

1.2 功能说明

直播功能:只针对直播答题、直播服务类目开放;需要先通过类目审核,在小程序管理后台,“接口设置 -> 实时音视频流设置”开通直播权限。目前受限各方监管,使用视频组件进行替代直播组件。

1.3 接口列表

  • 直播协议
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@protocol BBASMLiveAdapterProtocol <NSObject>

@property (nonatomic, strong) NSString *playURL; // 播放url
@property (nonatomic, strong) NSString *liveId; // liveid,用于上层保存id
@property (nonatomic, assign) BOOL isMuted; // 是否静音
@property (nonatomic, assign) BOOL backgroundMute; // 是否开启后台静音
@property (nonatomic, assign) BOOL needToggleFrameChasing; // 是否开启追帧
@property (nonatomic, assign) CGRect frame; // 布局
@property (nonatomic, copy) SWANLivePlayerStatusCallback playerStatusCallback; // 播放状态回调
@property (nonatomic, copy) SWANLivePlayerStatusCallback networkStatusCallback; // 网络状态回调
@property (nonatomic, assign) SWANLivePlayerScalingMode scalingMode; // 缩放方式
@property (nonatomic, weak) id delegate; // 存放直播所在视图
@property (nonatomic, strong) UIView *playerView; // 直播视图

/**
* 单例接口
*
* @return 实例
*/
+ (instancetype)sharedInstance;

/**
* 播放接口:会全新初始化playerVC
*
* @param playURL 播放地址
* @param shouldAutoplay 设置是否自动播放(必须提前设置,播放后不能设置)
* @return 播放内核的view
*/
- (UIView *)playWithPlayURL:(NSString*)playURL shouldAutoplay:(BOOL)shouldAutoplay;

/**
* 清除占位图中的播放器以外的视图 (主要是cover view)
*/
- (void)cleanOtherSubiews;

/**
* 停止播放
*/
- (void)stop;

/**
* 播放接口:开始上次player的播放
*/
- (void)play;

/**
* 暂停
*/
- (void)pause;

/**
* 设置静音:
*
* @param isMuted 是否静音
*/
- (void)setMuted:(BOOL)isMuted;

/**
* 设置缩放模式
*
* @param scalingMode 缩放方式
*/
- (void)setScalingMode:(SWANLivePlayerScalingMode)scalingMode;

/**
* 设置最小缓存区
*
* @param minCache 最小缓存:单位s
*/
- (void)setMinCache:(NSTimeInterval)minCache;

/**
* 设置最大缓存区
*
* @param minCache 最大缓存:单位s
*/
- (void)setMaxCache:(NSTimeInterval)maxCache;

/**
* 设置frame
*
* @param frame 布局
*/
- (void)setFrame:(CGRect)frame;

/**
* 设置是否开启追帧播放
*
* @param needToggleFrameChasing 是否开启追帧
*/
- (void)setNeedToggleFrameChasing:(BOOL)needToggleFrameChasing;

/**
* 设置是否隐藏
* @param hidden 是否隐藏
*/
- (void)setPlayerViewHidden:(BOOL)hidden;

/*
* 开启全屏
*
* @param direction 有效值为 0(正常竖向), 90(屏幕逆时针90度), -90(屏幕顺时针90度)
*/
- (void)requestFullScreen:(NSNumber *)direction;

/**
* @brief 直播所在父视图size变化
*
* @param size 变化后的size
*/
- (void)viewWillTransitionToSize:(CGSize)size;

/**
* 退出全屏
*/
- (void)exitFullScreen;
  • 播放器事件回调通过 BBALivePlayerDelegate
1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief 直播代理收到全屏事件
*
* @param direction 屏幕方向 数值参见:BBALivePlayerFullScreenDirection
*/
- (void)livePlayerFullScreen:(NSNumber *)direction;

/**
* @brief 直播代理收到退出全屏事件
*
*/
- (void)livePlayerExitFullScreen;

1.4 示例

参考:以百度云播放器 SDK 实现为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <BBASMLiveAdapterProtocol.h>

@interface BBASMLiveImplement : NSObject: BBASMAdapterBaseImplement <BBASMLiveAdapterProtocol>

@property (nonatomic, strong) NSString *playURL; // 播放url
@property (nonatomic, strong) NSString *liveId; // liveid,用于上层保存id
@property (nonatomic, assign) BOOL isMuted; // 是否静音
@property (nonatomic, assign) BOOL backgroundMute; // 是否开启后台静音
@property (nonatomic, assign) BOOL needToggleFrameChasing; // 是否开启追帧
@property (nonatomic, assign) CGRect frame; // 布局
@property (nonatomic, copy) BBALivePlayerStatusCallback playerStatusCallback; // 播放状态回调
@property (nonatomic, copy) BBALivePlayerStatusCallback networkStatusCallback; // 网络状态回调
@property (nonatomic, assign) BBALivePlayerScalingMode scalingMode; // 缩放方式

@property (nonatomic, weak) id delegate; // 存放直播所在视图
/// 直播视图
@property (nonatomic, strong) UIView *playerView;

  • .m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
@interface BBASMLiveImplement()

@property (nonatomic, strong) BDCloudMediaPlayerController *playerVC;
@property (nonatomic, assign) BOOL isStop;
@property (nonatomic, assign) BOOL shouldAutoplay;
@property (nonatomic, assign) BOOL isHidden;

@end

@implementation BBALivePlayerManager

@synthesize playerVC = _playerVC;
@synthesize playURL = _playURL;
@synthesize isMuted = _isMuted;
@synthesize frame = _frame;
@synthesize scalingMode = _scalingMode;
@synthesize playerStatusCallback = _playerStatusCallback;
@synthesize networkStatusCallback = _networkStatusCallback;
@synthesize isHidden = _isHidden;


static BBALivePlayerManager *sharedInstance;

+ (instancetype) sharedInstance
{
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}

- (instancetype)init {
self = [super init];
if (self) {
_playerView = [[UIView alloc]initWithFrame:CGRectZero];
_playerView.backgroundColor = [UIColor blackColor];
[self addObservers];
}
return self;
}

- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self stop];
_playerVC = nil;
[self cancelAutoTry];
}

#pragma 属性操作

- (BOOL)isPlayerMuted {
return _isMuted;
}

- (void)setFrame:(CGRect)frame
{
_frame = frame;
if (_playerVC && _playerView) {
[_playerView setFrame:frame];
[_playerVC.view setFrame:_playerView.bounds];
}
}

- (void)setNeedToggleFrameChasing:(BOOL)needToggleFrameChasing
{
_needToggleFrameChasing = needToggleFrameChasing;
[_playerVC toggleFrameChasing:needToggleFrameChasing];
}


#pragma 播放器操作

- (UIView *)playWithPlayURL:(NSString*)playURL shouldAutoplay:(BOOL)shouldAutoplay{
if (playURL.length <= 0) {
[self stop];
return nil;
}
_playURL = playURL;
_shouldAutoplay = shouldAutoplay;
[self restartPlay];
if (!_playerView) {
_playerView = [[UIView alloc]initWithFrame:CGRectZero];
_playerView.backgroundColor = [UIColor blackColor];
}
return _playerView;
}

- (void)restartPlay {
_isHidden = NO;
if (_playerVC) {
[self stop];
}
_isStop = NO;

[BDCloudMediaPlayerController setCuid:[BBACuidSDK cuid]];
_playerVC = [[BDCloudMediaPlayerController alloc] initWithContentURL:[NSURL URLWithString:_playURL]];
[_playerVC setVideoDecodeMode:BDCloudMediaPlayerVideoDecodeModeSoftware];
[_playerVC setOptionIntValue:1 forKey:@"videotoolbox-handle-resolution-change" ofCategory:BDCloudMediaPlayerOptionCategoryPlayer];
_playerVC.view.backgroundColor = [UIColor blackColor];
_playerVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_playerVC setShouldAutoplay:_shouldAutoplay];
[_playerVC prepareToPlay];
// 设置后台语音保持
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];

if (!_playerView) {
_playerView = [[UIView alloc]initWithFrame:CGRectZero];
_playerView.backgroundColor = [UIColor blackColor];
}
[_playerVC.view setFrame: _playerView.bounds];
if (_playerVC.view.superview != _playerView) {
[_playerView insertSubview:_playerVC.view atIndex:0];
}
}

- (void)cleanOtherSubiews {
for (UIView *subview in _playerView.subviews) {
if (subview != self.playerVC.view) {
[subview removeFromSuperview];
}
}
}

- (void)play {
if (_isHidden) {
return;
}
if (!_playerVC) {
return;
}
if (_playerVC.isPreparedToPlay) {
[_playerVC play];
}
}

- (void)pause {
_isStop = YES;
if (_playerVC) {
[_playerVC pause];
}
}

- (void)stop {
_isStop = YES;
[_playerVC reset];
[_playerVC.view removeFromSuperview];
_playerVC = nil;
}

- (void)setMuted:(BOOL)isMuted
{
_isMuted = isMuted;
[_playerVC setMuted:isMuted];
}

- (void)setScalingMode:(BBALivePlayerScalingMode)scalingMode
{
_scalingMode = scalingMode;
[_playerVC setScalingMode:(BDCloudMediaPlayerScalingMode)_scalingMode];
}

- (void)setMinCache:(NSTimeInterval)minCache
{
[_playerVC setMinBufferingTime:minCache];
}

- (void)setMaxCache:(NSTimeInterval)maxCache
{
[_playerVC setMaxBufferingTime:maxCache];
}

- (void)setPlayerViewHidden:(BOOL)hidden
{
_isHidden = hidden;
if (_playerView) {
_playerView.hidden = hidden;
}
if (hidden) {
[self pause];
}
}

- (void)requestFullScreen:(NSNumber *)direction
{
if (!self.playerView.superview) {
return;
}

switch ([direction intValue]) {
case BBALivePlayerFullScreenDirectionPortrait:
{
if (_delegate && [_delegate respondsToSelector:@selector(livePlayerFullScreen:)]) {
[_delegate livePlayerFullScreen:direction];
}

[UIView animateWithDuration:BBALIVE_FULL_SCREEN_ANIMTION_INTERVAL animations:^{
self.playerView.frame = CGRectMake(0, 0, self.playerView.superview.frame.size.width, self.playerView.superview.size.height);
}];
}
break;
case BBALivePlayerFullScreenDirectionLeftLandscape:
{
[[BBAOrientationManager shareInstance] forceRotateToOrientation:UIInterfaceOrientationLandscapeLeft animated:YES completion:nil];
}
break;
case BBALivePlayerFullScreenDirectionRightLandscape:
{
[[BBAOrientationManager shareInstance] forceRotateToOrientation:UIInterfaceOrientationLandscapeRight animated:YES completion:nil];
}

default:
break;
}
}

- (void)exitFullScreen {
if (UIInterfaceOrientationPortrait == [UIApplication sharedApplication].statusBarOrientation) {
if (_delegate && [_delegate respondsToSelector:@selector(livePlayerFullScreen:)]) {
[_delegate livePlayerExitFullScreen];
}

[UIView animateWithDuration:BBALIVE_FULL_SCREEN_ANIMTION_INTERVAL animations:^{
_playerView.frame = self.frame;
}];
} else {
[[BBAOrientationManager shareInstance] forceRotateToOrientation:UIInterfaceOrientationPortrait animated:YES completion:nil];
}
}

- (void)viewWillTransitionToSize:(CGSize)size {
if (size.width > size.height) {
if (_delegate && [_delegate respondsToSelector:@selector(livePlayerFullScreen:)]) {
// 包含手动强制转屏:(UIInterfaceOrientationLandscapeLeft == [[BBAOrientationManager shareInstance] adaptingOrientation])+重力感应转屏(UIDeviceOrientationLandscapeRight == [UIDevice currentDevice].orientation )
if (UIDeviceOrientationLandscapeRight == [UIDevice currentDevice].orientation || UIInterfaceOrientationLandscapeLeft == [[BBAOrientationManager shareInstance] adaptingOrientation]) {
[_delegate livePlayerFullScreen:[NSNumber numberWithInt:BBALivePlayerFullScreenDirectionLeftLandscape]];
} else {
[_delegate livePlayerFullScreen:@(BBALivePlayerFullScreenDirectionRightLandscape)];
}
}
} else {
if (_delegate && [_delegate respondsToSelector:@selector(livePlayerFullScreen:)]) {
[_delegate livePlayerExitFullScreen];
}
}

[UIView animateWithDuration:BBALIVE_FULL_SCREEN_ANIMTION_INTERVAL animations:^{
if (size.width > size.height) {
_playerView.frame = CGRectMake(0, 0, APPLICATION_FRAME_HEIGHT, APPLICATION_FRAME_WIDTH);
} else {
_playerView.frame = self.frame;
}
}];
}

#pragma mark - 回调方法
// 播放状态变化
- (void)cloudMediaPlayerStatusChange:(NSNotification *)notify
{
if (_playerStatusCallback) {
_playerStatusCallback(notify.userInfo);
}
}

// 网络状态变化
- (void)handleNetworkStatusChange:(AFNetworkReachabilityStatus)status{
if (_networkStatusCallback) {
NSMutableDictionary *infoDic = [[NSMutableDictionary alloc]init];
[infoDic setObjectSafely:[NSNumber numberWithInteger:status] forKey:@"netStatus"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC videoBitrate]] forKey:@"videoBitrate"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC audioBitrate]] forKey:@"audioBitrate"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC videoFPS]] forKey:@"videoFPS"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC videoGopLength]] forKey:@"videoGopLength"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC netSpeed]] forKey:@"netSpeed"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC videoWidth]] forKey:@"videoWidth"];
[infoDic setObjectSafely:[NSNumber numberWithInteger:[_playerVC videoHeight]] forKey:@"videoHeight"];
_networkStatusCallback(infoDic);
}
}

// 监听通知
- (void)addObservers {
// 播放状态更新 小程序专属通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cloudMediaPlayerStatusChange:)
name:BDCloudMediaPlayerStatusNotification
object:nil];

// 播放状态更新 public
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cloudMediaPlayerPlaybackStateDidChange:)
name:BDCloudMediaPlayerPlaybackStateDidChangeNotification
object:nil];
//播放完成
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cloudMediaPlayerPlaybackDidFinish:)
name:BDCloudMediaPlayerPlaybackDidFinishNotification
object:nil];


@weakify(self);
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
@strongify(self);
[self handleNetworkStatusChange:status];
}];
}

- (void)cloudMediaPlayerPlaybackStateDidChange:(NSNotification *)notify {
if (_isStop) {
return;
}
if(BDCloudMediaPlayerPlaybackStatePaused == _playerVC.playbackState) {
[self play];
}
else if(BDCloudMediaPlayerPlaybackStateStopped == _playerVC.playbackState) {
[self autoTry];
}
}

- (void)cloudMediaPlayerPlaybackDidFinish:(NSNotification *)notify {
if (_isStop) {
return;
}

NSDictionary *userInfo = notify.userInfo;
if([[userInfo objectForKeySafely:BDCloudMediaPlayerPlaybackDidFinishReasonUserInfoKey] intValue] == BDCloudMediaPlayerFinishReasonError) {
[self autoTry];
}
else if ([[userInfo objectForKeySafely:BDCloudMediaPlayerPlaybackDidFinishReasonUserInfoKey] intValue] == BDCloudMediaPlayerFinishReasonEnd) {
[self autoTry];
}
}

- (void)cancelAutoTry {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}

- (void)autoTry {
[self cancelAutoTry];
[self performSelector:@selector(delayTry) withObject:nil afterDelay:4];
}

- (void)delayTry {
if (!_isStop) {
[self restartPlay];
}
}

@end