提交 8ea0a67a authored 作者: kongdywang's avatar kongdywang

1. Added Picture-in-Picture capability for iOS live streaming.

2. Fixed the Picture-in-Picture service leak issue on Android.
上级 b4fe752b
......@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.NonNull;
......@@ -65,7 +66,7 @@ public class FTXLivePlayer extends FTXBasePlayer implements TXFlutterLivePlayerA
private final FTXV2LiveObserver mObserver;
private int mLastPlayEvent = -1;
private boolean mIsPaused = false;
private FtxMessages.TXLivePlayerFlutterAPI mLiveFlutterApi;
private final FtxMessages.TXLivePlayerFlutterAPI mLiveFlutterApi;
private final FTXPIPManager.PipCallback pipCallback = new FTXPIPManager.PipCallback() {
@Override
......
......@@ -204,6 +204,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
mPlayers.append(playerId, player);
PlayerMsg playerMsg = new PlayerMsg();
playerMsg.setPlayerId((long) playerId);
LiteavLog.i(TAG, "createVodPlayer :" + playerId);
return playerMsg;
}
......@@ -215,6 +216,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
mPlayers.append(playerId, player);
PlayerMsg playerMsg = new PlayerMsg();
playerMsg.setPlayerId((long) playerId);
LiteavLog.i(TAG, "createLivePlayer :" + playerId);
return playerMsg;
}
......@@ -229,8 +231,10 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
public void releasePlayer(@NonNull PlayerMsg playerId) {
if (null != playerId.getPlayerId()) {
int intPlayerId = playerId.getPlayerId().intValue();
LiteavLog.i(TAG, "releasePlayer :" + intPlayerId);
FTXBasePlayer player = mPlayers.get(intPlayerId);
if (player != null) {
LiteavLog.i(TAG, "releasePlayer start destroy player :" + intPlayerId);
player.destroy();
mPlayers.remove(intPlayerId);
}
......
......@@ -498,7 +498,7 @@ public class FlutterPipImplActivity extends Activity implements TextureView.Surf
private void bindAndroid12BugServiceIfNeed() {
if (Build.VERSION.SDK_INT >= VERSION_CODES.Q) {
Intent serviceIntent = new Intent(this, TXAndroid12BridgeService.class);
Intent serviceIntent = new Intent(getApplicationContext(), TXAndroid12BridgeService.class);
startService(serviceIntent);
bindService(serviceIntent, this, Context.BIND_AUTO_CREATE);
}
......
......@@ -45,6 +45,12 @@
/// PIP function is not started.
/// PIP功能没有启动
#define ERROR_IOS_PIP_NOT_RUNNING -111
/// PIP start time out
/// PIP 启动超时
#define ERROR_IOS_PIP_START_TIME_OUT -112
/// Insufficient permissions, currently only appears in Picture-in-Picture live streaming
/// 权限不足,目前只出现在直播画中画
#define ERROR_PIP_AUTH_DENIED -201
......
......@@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#import "FTXBasePlayer.h"
#import "FTXVodPlayerDelegate.h"
@protocol FlutterPluginRegistrar;
......@@ -11,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface FTXLivePlayer : FTXBasePlayer
@property(nonatomic, weak) id<FTXVodPlayerDelegate> delegate;
- (instancetype)initWithRegistrar:(id<FlutterPluginRegistrar>)registrar;
- (void)notifyAppTerminate:(UIApplication *)application;
......
......@@ -4,29 +4,12 @@
#import <Foundation/Foundation.h>
#import "FTXBasePlayer.h"
#import "FTXVodPlayerDelegate.h"
@protocol FlutterPluginRegistrar;
NS_ASSUME_NONNULL_BEGIN
@protocol FTXVodPlayerDelegate <NSObject>
- (void)onPlayerPipRequestStart;
- (void)onPlayerPipStateDidStart;
- (void)onPlayerPipStateWillStop;
- (void)onPlayerPipStateDidStop;
- (void)onPlayerPipStateRestoreUI:(double)playTime;;
- (void)onPlayerPipStateError:(NSInteger)errorId;
- (void) releasePlayerInner:(NSNumber*)playerId;
@end
@interface FTXVodPlayer : FTXBasePlayer
@property(nonatomic, weak) id<FTXVodPlayerDelegate> delegate;
......
......@@ -12,6 +12,7 @@
#import "TXCommonUtil.h"
#import "FTXLog.h"
#import <stdatomic.h>
#import "FTXImgTools.h"
static const int uninitialized = -1;
static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
......@@ -547,7 +548,8 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
}
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
return [self CVPixelBufferRefFromUiImage:image];
// must create new obj when evey called
return [FTXImgTools CVPixelBufferRefFromUiImage:image];
}
- (void)setPlayerConfig:(FTXVodPlayConfigPlayerMsg *)args
......@@ -792,6 +794,12 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
case TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING:
type = ERROR_IOS_PIP_NOT_RUNNING;
break;
case TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_START_TIMEOUT:
type = ERROR_IOS_PIP_START_TIME_OUT;
break;
default:
type = errorType;
break;
}
self.hasEnteredPipMode = NO;
FTXLOGE(@"[onPlayer], pictureInPictureErrorDidOccur errorType= %ld", type);
......@@ -807,71 +815,6 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
- (void)onPlayer:(TXVodPlayer *)player airPlayStateDidChange:(TX_VOD_PLAYER_AIRPLAY_STATE)airPlayState withParam:(NSDictionary *)param {
}
#pragma mark - Convert UIImage to CVPixelBufferRef
- (CVPixelBufferRef)CVPixelBufferRefFromUiImage:(UIImage *)img {
CGSize size = img.size;
CGImageRef image = [img CGImage];
BOOL hasAlpha = CGImageRefContainsAlpha(image);
CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
empty, kCVPixelBufferIOSurfacePropertiesKey,
nil];
CVPixelBufferRef pxbuffer = NULL;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, inputPixelFormat(), (__bridge CFDictionaryRef) options, &pxbuffer);
NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
NSParameterAssert(pxdata != NULL);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
uint32_t bitmapInfo = bitmapInfoWithPixelFormatType(inputPixelFormat(), (bool)hasAlpha);
CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, CVPixelBufferGetBytesPerRow(pxbuffer), rgbColorSpace, bitmapInfo);
NSParameterAssert(context);
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
return pxbuffer;
}
static OSType inputPixelFormat(){
return kCVPixelFormatType_32BGRA;
}
static uint32_t bitmapInfoWithPixelFormatType(OSType inputPixelFormat, bool hasAlpha){
if (inputPixelFormat == kCVPixelFormatType_32BGRA) {
uint32_t bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
if (!hasAlpha) {
bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
}
return bitmapInfo;
}else if (inputPixelFormat == kCVPixelFormatType_32ARGB) {
uint32_t bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
return bitmapInfo;
}else{
return 0;
}
}
// Check alpha value
BOOL CGImageRefContainsAlpha(CGImageRef imageRef) {
if (!imageRef) {
return NO;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
return hasAlpha;
}
#pragma mark - TXFlutterVodPlayerApi
- (nullable BoolMsg *)enableHardwareDecodeEnable:(nonnull BoolPlayerMsg *)enable error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
......
......@@ -116,6 +116,7 @@ SuperPlayerPlugin* instance;
FTXLOGV(@"called releasePlayerInner,%@ is start release", playerId);
FTXBasePlayer *player = [_players objectForKey:playerId];
if (player != nil) {
FTXLOGI(@"releasePlayer start destroy player :%@", playerId);
[player destory];
[_players removeObjectForKey:playerId];
}
......@@ -234,8 +235,10 @@ SuperPlayerPlugin* instance;
- (nullable PlayerMsg *)createLivePlayerWithError:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
FTXLivePlayer* player = [[FTXLivePlayer alloc] initWithRegistrar:self.registrar];
player.delegate = self;
NSNumber *playerId = player.playerId;
_players[playerId] = player;
FTXLOGI(@"createLivePlayer :%@", playerId);
return [TXCommonUtil playerMsgWith:playerId];
}
......@@ -244,6 +247,7 @@ SuperPlayerPlugin* instance;
player.delegate = self;
NSNumber *playerId = player.playerId;
_players[playerId] = player;
FTXLOGI(@"createVodPlayer :%@", playerId);
return [TXCommonUtil playerMsgWith:playerId];
}
......
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_COMMON_FTXPLAYERCONSTANTS_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_COMMON_FTXPLAYERCONSTANTS_H_
#import "FTXLiteAVSDKHeader.h"
// 点播高级套餐 Feature
#define TUI_FEATURE_PLAYER_PREMIUM (0b10000000)
// 点播企业套餐 Feature
#define TUI_FEATURE_PLAYER_ENTERPRISE (0b100000000)
// pip status
typedef NS_ENUM(NSInteger, FTX_LIVE_PIP_ERROR) {
/// 无错误
FTX_VOD_PLAYER_PIP_ERROR_TYPE_NONE = TX_VOD_PLAYER_PIP_ERROR_TYPE_NONE,
/// 设备或系统版本不支持(iPad iOS9+ 才支持PIP)
FTX_VOD_PLAYER_PIP_ERROR_TYPE_DEVICE_NOT_SUPPORT = TX_VOD_PLAYER_PIP_ERROR_TYPE_DEVICE_NOT_SUPPORT,
/// 播放器不支持
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_SUPPORT = TX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_SUPPORT,
/// 视频不支持
FTX_VOD_PLAYER_PIP_ERROR_TYPE_VIDEO_NOT_SUPPORT = TX_VOD_PLAYER_PIP_ERROR_TYPE_VIDEO_NOT_SUPPORT,
/// PIP控制器不可用
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_NOT_POSSIBLE = TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_NOT_POSSIBLE,
/// PIP控制器报错
FTX_VOD_PLAYER_PIP_ERROR_TYPE_ERROR_FROM_SYSTEM = TX_VOD_PLAYER_PIP_ERROR_TYPE_ERROR_FROM_SYSTEM,
/// 播放器对象不存在
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_EXIST = TX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_EXIST,
/// PIP功能已经运行
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_RUNNING = TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_RUNNING,
/// PIP功能没有启动
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING = TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING,
/// PIP启动超时
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_START_TIMEOUT = TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_START_TIMEOUT,
/// pip 没有 sdk 权限
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_AUTH_DENIED = 101,
// 缺乏画中画 bundle 资源
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_MISS_RESOURCE = 102,
};
/**
后台画中画播放器的播放状态
*/
typedef NS_ENUM(NSInteger, FTXAVPlayerState) {
FTXAVPlayerStateIdle = 0, // 初始状态
FTXAVPlayerStatePrepared, // 播放准备完毕
FTXAVPlayerStatePlaying, // 播放中
FTXAVPlayerStatePaused, // 播放暂停
FTXAVPlayerStateStopped, // 播放停止
FTXAVPlayerStateComplete, // 播放完毕
FTXAVPlayerStateError, // 播放失败
};
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_COMMON_FTXPLAYERCONSTANTS_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXLIVEPIPDELEGATE_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXLIVEPIPDELEGATE_H_
#import "FTXLiteAVSDKHeader.h"
#import "FTXPlayerConstants.h"
@protocol FTXLivePipDelegate <NSObject>
- (void)pictureInPictureStateDidChange:(TX_VOD_PLAYER_PIP_STATE)status;
- (void)pictureInPictureErrorDidOccur:(FTX_LIVE_PIP_ERROR)errorStatus;
@end
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXLIVEPIPDELEGATE_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXVODPLAYERDELEGATE_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXVODPLAYERDELEGATE_H_
@protocol FTXVodPlayerDelegate <NSObject>
- (void)onPlayerPipRequestStart;
- (void)onPlayerPipStateDidStart;
- (void)onPlayerPipStateWillStop;
- (void)onPlayerPipStateDidStop;
- (void)onPlayerPipStateRestoreUI:(double)playTime;;
- (void)onPlayerPipStateError:(NSInteger)errorId;
- (void) releasePlayerInner:(NSNumber*)playerId;
@end
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_FTXVODPLAYERDELEGATE_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_TXPIPAUTH_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_TXPIPAUTH_H_
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TXPipAuth : NSObject
+ (instancetype)shareInstance;
+ (BOOL)cpa;
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_HELPER_TXPIPAUTH_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import "TXPipAuth.h"
#import "FTXPlayerConstants.h"
#define TUI_RGSKEY_PARAM1 @"KEY_PARAM1"
#define TUI_RETKEY_PARAM1 @"KEY_PARAM1"
#define TUI_ID_CHECK_FEATURE_AUTH (2) ///< 校验某个 feature 是否授权
@implementation TXPipAuth
+ (instancetype)shareInstance {
static TXPipAuth *g_playerAuth = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
g_playerAuth = [[self alloc] init];
});
return g_playerAuth;
}
+ (BOOL)cpa{
return [TXPipAuth cfa:TUI_FEATURE_PLAYER_PREMIUM];
}
+ (BOOL)cfa:(int)featureId {
// 输入参数
NSMutableDictionary *inputParams = [NSMutableDictionary dictionary];
NSString *featureIdStr = [NSString stringWithFormat:@"%@", @(featureId)];
[inputParams setObject:featureIdStr forKey:TUI_RGSKEY_PARAM1];
// 函数调用
__block BOOL result = NO;
Class HostEngineManagerClass = NSClassFromString(@"TXCHostEngineManager");
SEL sharedManagerSEL = NSSelectorFromString(@"sharedManager");
IMP sharedManagerIMP = [HostEngineManagerClass methodForSelector:sharedManagerSEL];
NSObject *(*sharedManagerFunc)(id, SEL) = (void *)sharedManagerIMP;
NSObject *sharedManagerObj = sharedManagerFunc(HostEngineManagerClass, sharedManagerSEL);
void (^SyncRequestToHostFuncBlock)(NSDictionary *) = ^(NSDictionary *outParams) {
NSObject *featureAuthObj = [outParams objectForKey:TUI_RETKEY_PARAM1];
if ([featureAuthObj isKindOfClass:[NSNumber class]]) {
result = [(NSNumber *)featureAuthObj boolValue];
}
};
SEL sendSyncRequestToHostSEL = NSSelectorFromString(@"sendSyncRequestToHostWithFunctionId:inputParams:completionHandler:");
IMP sendSyncRequestToHostIMP = [sharedManagerObj methodForSelector:sendSyncRequestToHostSEL];
void (*sendSyncRequestToHostFunc)(id, SEL, NSInteger, NSDictionary *, void (^)(NSDictionary<NSString *, NSObject *> *)) = (void *)sendSyncRequestToHostIMP;
sendSyncRequestToHostFunc(sharedManagerObj, sendSyncRequestToHostSEL, TUI_ID_CHECK_FEATURE_AUTH, inputParams, SyncRequestToHostFuncBlock);
return result;
}
@end
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXBACKPLAYER_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXBACKPLAYER_H_
#import <Foundation/Foundation.h>
#import <AVKit/AVKit.h>
#import "FTXPipPlayerDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface FTXBackPlayer : NSObject
@property(nonatomic, strong)id<FTXPipPlayerDelegate> playerDelegate;
- (void)prepareVideo:(AVPlayerItem *)item;
- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item;
- (void)play;
- (void)setContainerView:(UIView*)container;
- (void)setLoopback:(BOOL)isLoop;
- (void)seekTo:(int64_t)positionMs;
- (void)stop;
- (void)pause;
- (AVPlayerLayer*)getPlayerLayer;
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXBACKPLAYER_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import "FTXBackPlayer.h"
#import "FTXPlayerConstants.h"
#import "FTXLog.h"
/// 微妙转毫秒
#define USEC_TO_MSEC 1000ull
static NSString* kPipTag = @"FTXBackPlayer";
static void *gTVKAVPlayerKVOContextTimeControlStatus = &gTVKAVPlayerKVOContextTimeControlStatus;
static void *gTVKAVPlayerKVOContextAirplay = &gTVKAVPlayerKVOContextAirplay;
static void *gTVKAVPlayerKVOContextError = &gTVKAVPlayerKVOContextError;
static void *gTVKAVPlayerItemKVOContextState = &gTVKAVPlayerItemKVOContextState;
@interface FTXBackPlayer()
@property (nonatomic, strong) AVPlayer* avPlayer;
@property (nonatomic, strong) AVPlayerLayer* playerLayer;
@property (nonatomic, strong) AVPlayerItem* playerItem;
@property (nonatomic, assign) BOOL isLoopBack;
@property (nonatomic, strong) AVAsset* asset;
@property (nonatomic, assign) FTXAVPlayerState playerState;/// 系统播放资源
@end
@implementation FTXBackPlayer
- (instancetype)init
{
self = [super init];
if (self) {
self.playerState = FTXAVPlayerStateIdle;
}
return self;
}
- (void)prepareVideo:(AVPlayerItem *)item {
self.asset = item.asset;
self.playerItem = item;
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
self.avPlayer.volume = 0;
self.avPlayer.rate = 1;
self.avPlayer.muted = YES;
if (self.isLoopBack) {
[self.avPlayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
}
// 不允许airplay
self.avPlayer.allowsExternalPlayback = NO;
self.playerLayer = [[AVPlayerLayer alloc] init];
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.playerLayer.hidden = YES;
[self.playerLayer setPlayer:self.avPlayer];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playbackFinished:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
[self addKVOWithPlayer:self.avPlayer];
self.playerState = FTXAVPlayerStatePrepared;
}
- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)playerItem {
FTXLOGI(@"[%@]replaceCurrentItemWithPlayerItem", kPipTag);
if (nil == self.avPlayer) {
FTXLOGW(@"[%@]replaceCurrentItemWithPlayerItem met null player", kPipTag);
return;
}
self.asset = playerItem.asset;
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
self.playerItem = playerItem;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playbackFinished:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:self.playerItem];
}
- (void)seekTo:(int64_t)positionMs {
FTXLOGI(@"[%@]seekto %lld ms", kPipTag, positionMs);
if (positionMs < 0) {
return;
}
if (nil == self.avPlayer) {
FTXLOGW(@"[%@]seekto met null player", kPipTag);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.avPlayer seekToTime:CMTimeMake(positionMs * 1000, USEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
});
}
- (void)play {
FTXLOGI(@"[%@]play", kPipTag);
if (nil == self.avPlayer) {
FTXLOGW(@"[%@]play met null player", kPipTag);
return;
}
[self.avPlayer play];
self.playerState = FTXAVPlayerStatePlaying;
}
- (void)pause {
FTXLOGI(@"[%@]pause", kPipTag);
if (nil == self.avPlayer) {
FTXLOGW(@"[%@]pause met null player", kPipTag);
return;
}
dispatch_async(dispatch_get_main_queue(),^{
[self.avPlayer pause];
self.playerState = FTXAVPlayerStatePaused;
});
}
- (void)stop {
FTXLOGI(@"[%@]stop", kPipTag);
if (nil == self.avPlayer) {
FTXLOGW(@"[%@]stop met null player", kPipTag);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.avPlayer pause];
self.playerState = FTXAVPlayerStateStopped;
});
[self reset];
}
- (void)setContainerView:(UIView*)container {
FTXLOGI(@"[%@]setContainerView", kPipTag);
if (nil == self.playerLayer) {
FTXLOGW(@"[%@]setContainerView met null playerLayer", kPipTag);
return;
}
[container.layer addSublayer:self.playerLayer];
self.playerLayer.frame = container.bounds;
}
- (void)setPlayerState:(FTXAVPlayerState)playerState {
FTXLOGI(@"[%@]setPlayerState,playerState:%ld", kPipTag, (long)playerState);
if (self.playerDelegate != nil) {
[self.playerDelegate playerStateDidChange:playerState];
}
self->_playerState = playerState;
}
- (void)setLoopback:(BOOL)isLoop {
FTXLOGI(@"[%@]setLoopback,isLoop:%i", kPipTag, isLoop);
self.isLoopBack = isLoop;
}
- (AVPlayerLayer *)getPlayerLayer {
return self.playerLayer;
}
- (void)reset {
[self.playerLayer removeFromSuperlayer];
[self.playerLayer setPlayer:nil];
[self removeKVOWithPlayer:self.avPlayer];
self.isLoopBack = NO;
self.playerState = FTXAVPlayerStateIdle;
self.avPlayer = nil;
self.playerLayer = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.avPlayer) {
if (context == gTVKAVPlayerKVOContextTimeControlStatus) {
// 画中画播放时,才管系统播放器的播放暂停状态。 非画中画情况下,都是主播放器影响系统播放器,不需要系统播放器反过来影响主播放器
[self handleAVPlayerTimeControlStatusChanged];
}
}
}
- (int64_t)currentPositionMs {
return CMTimeGetSeconds(self.avPlayer.currentTime) * USEC_TO_MSEC;
}
- (int64_t)durationMs {
return (int64_t)(CMTimeGetSeconds(self.asset.duration) * 1000);
}
- (void)playbackFinished:(NSNotification *)notification {
if (self.isLoopBack) {
[self.avPlayer seekToTime:CMTimeMake(0, USEC_PER_SEC) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[self.avPlayer play];
}
}
- (void)addKVOWithPlayer:(AVPlayer *)player {
if (player != nil) {
[player addObserver:self forKeyPath:@"error" options:NSKeyValueObservingOptionNew context:gTVKAVPlayerKVOContextError];
// kvo监听系统播放器的暂停播放状态(之前监听播放速度rate来判断状态,可能不准,监听timeControlStatus是最准的)
if (@available(iOS 10.0, macOS 10.12, *)) {
[player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:gTVKAVPlayerKVOContextTimeControlStatus];
} else {
[player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:gTVKAVPlayerKVOContextTimeControlStatus];
}
[player addObserver:self forKeyPath:@"airPlayVideoActive" options:NSKeyValueObservingOptionNew context:gTVKAVPlayerKVOContextAirplay];
[player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:gTVKAVPlayerItemKVOContextState];
}
}
- (void)removeKVOWithPlayer:(AVPlayer *)player {
if (player != nil) {
[player removeObserver:self forKeyPath:@"error"];
// kvo监听系统播放器的暂停播放状态(之前监听播放速度rate来判断状态,可能不准,监听timeControlStatus是最准的)
if (@available(iOS 10.0, macOS 10.12, *)) {
[player removeObserver:self forKeyPath:@"timeControlStatus"];
} else {
[player removeObserver:self forKeyPath:@"rate"];
}
[player removeObserver:self forKeyPath:@"airPlayVideoActive"];
[player removeObserver:self forKeyPath:@"status"];
}
}
- (void)handleAVPlayerTimeControlStatusChanged {
BOOL isPlayingState = NO;
if (@available(iOS 10.0, macOS 10.12, *)) {
isPlayingState = self.avPlayer.timeControlStatus == AVPlayerTimeControlStatusPlaying
|| self.avPlayer.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate;
} else {
isPlayingState = (self.avPlayer.rate != 0);
}
if (isPlayingState && self.playerState != FTXAVPlayerStatePlaying) {
FTXLOGI(@"[%@]playerStateDidChange:playing", kPipTag);
self.playerState = FTXAVPlayerStatePlaying;
} else if (!isPlayingState && self.playerState == FTXAVPlayerStatePlaying) {
FTXLOGI(@"[%@]playerStateDidChange:paused", kPipTag);
// 换源之后,播放完成前一刻,监听系统播放器TimeControlStatus属性变为暂停状态,这不是用户调用的暂停,不需要回抛暂停
if (self.currentPositionMs == self.durationMs) {
FTXLOGI(@"[%@]playerStateDidChange:complete, duration:%lld == currentPosition:%lld",
kPipTag, self.durationMs, self.currentPositionMs);
return;
}
self.playerState = FTXAVPlayerStatePaused;
}
}
@end
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCALLER_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCALLER_H_
#import "FTXLiteAVSDKHeader.h"
#import "FTXLivePipDelegate.h"
#import "FTXPipPlayerDelegate.h"
#import "FTXPipRenderView.h"
@protocol FTXPipCaller <NSObject>
@property(nonatomic, strong)id<FTXLivePipDelegate> pipDelegate;
@property(nonatomic, strong)id<FTXPipPlayerDelegate> playerDelegate;
- (int)handleStartPip:(CGSize)size;
- (void)exitPip;
- (FTXPipRenderView*)getVideoView;
- (TX_VOD_PLAYER_PIP_STATE)getStatus;
- (void)pausePipVideo;
- (void)resumePipVideo;
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer;
@end
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCALLER_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCONTROLLER_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCONTROLLER_H_
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "FTXLiteAVSDKHeader.h"
#import "FTXLivePipDelegate.h"
#import "FTXPipPlayerDelegate.h"
@interface FTXPipController : NSObject
@property(nonatomic, strong)id<FTXLivePipDelegate> pipDelegate;
@property(nonatomic, strong)id<FTXPipPlayerDelegate> playerDelegate;
+ (instancetype)shareInstance;
- (int)startOpenPip:(V2TXLivePlayer*)livePlayer withSize:(CGSize)size;
- (void)pausePipVideo;
- (void)resumePipVideo;
- (void)exitPip;
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer;
@end
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPCONTROLLER_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import <Foundation/Foundation.h>
#import "FTXPipController.h"
#import "FTXEvent.h"
#import "FTXPipCaller.h"
#import "FTXPipFactory.h"
#import "TXPipAuth.h"
#import "FTXLog.h"
#import <CoreVideo/CoreVideo.h>
@interface FTXPipController()
@property (nonatomic, strong) id<FTXPipCaller> pipImpl;
@property (nonatomic, strong) FTXPipFactory* pipFactory;
@property (atomic, strong) NSObject* controlLock;
@end
static NSString* kPipTag = @"FTXPipController";
static FTXPipController *_shareInstance = nil;
@implementation FTXPipController
+ (instancetype)shareInstance {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
_shareInstance = [[FTXPipController alloc] init];
});
return _shareInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.pipFactory = [[FTXPipFactory alloc] init];
self.controlLock = [[NSObject alloc] init];
}
return self;
}
- (int)startOpenPip:(V2TXLivePlayer *)livePlayer withSize:(CGSize)size{
if (![TXPipAuth cpa]) {
FTXLOGE(@"%@ pip auth is deined when enter", kPipTag);
if (nil != self.pipDelegate) {
[self.pipDelegate pictureInPictureErrorDidOccur:FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_AUTH_DENIED];
}
return ERROR_PIP_AUTH_DENIED;
}
if (![TXVodPlayer isSupportPictureInPicture]) {
FTXLOGE(@"%@ pip is not support", kPipTag);
if (nil != self.pipDelegate) {
[self.pipDelegate pictureInPictureErrorDidOccur:FTX_VOD_PLAYER_PIP_ERROR_TYPE_DEVICE_NOT_SUPPORT];
}
return ERROR_IOS_PIP_DEVICE_NOT_SUPPORT;
}
if (nil != self.pipImpl) {
TX_VOD_PLAYER_PIP_STATE status = [self.pipImpl getStatus];
if (status == TX_VOD_PLAYER_PIP_STATE_WILL_START
|| status == TX_VOD_PLAYER_PIP_STATE_DID_START) {
FTXLOGE(@"%@ pip is running when enter, status %ld", kPipTag, (long)status);
if (nil != self.pipDelegate) {
[self.pipDelegate pictureInPictureErrorDidOccur:FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_RUNNING];
}
return ERROR_IOS_PIP_IS_RUNNING;
}
}
self.pipImpl = [self.pipFactory createPipCaller];
if (self.pipDelegate != nil) {
self.pipImpl.pipDelegate = self.pipDelegate;
}
if (self.playerDelegate != nil) {
self.pipImpl.playerDelegate = self.playerDelegate;
}
int retCode = [self.pipImpl handleStartPip:size];
return retCode;
}
- (void)setPipDelegate:(id<FTXLivePipDelegate>)pipDelegate {
if (nil != self.pipImpl) {
self.pipImpl.pipDelegate = pipDelegate;
}
self->_pipDelegate = pipDelegate;
}
- (void)setPlayerDelegate:(id<FTXPipPlayerDelegate>)playerDelegate {
if (nil != self.pipImpl) {
self.pipImpl.playerDelegate = playerDelegate;
}
self->_playerDelegate = playerDelegate;
}
- (void)exitPip {
@synchronized (self.controlLock) {
if (nil != self.pipImpl) {
[self.pipImpl exitPip];
self.pipImpl = nil;
self.pipDelegate = nil;
self.playerDelegate = nil;
}
}
}
- (void)pausePipVideo {
if (nil != self.pipImpl) {
[self.pipImpl pausePipVideo];
}
}
- (void)resumePipVideo {
if (nil != self.pipImpl) {
[self.pipImpl resumePipVideo];
}
}
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer {
@synchronized (self.controlLock) {
if (!pixelBuffer || pixelBuffer == NULL) {
NSLog(@"Invalid CVPixelBufferRef");
return;
}
if (nil != self.pipImpl) {
[self.pipImpl displayPixelBuffer:pixelBuffer];
}
}
}
@end
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPFACTORY_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPFACTORY_H_
#import <Foundation/Foundation.h>
#import "FTXPipCaller.h"
NS_ASSUME_NONNULL_BEGIN
@interface FTXPipFactory : NSObject
- (id<FTXPipCaller>)createPipCaller;
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPFACTORY_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import "FTXPipFactory.h"
#import "FTXPipGlobalImpl.h"
@implementation FTXPipFactory
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (nonnull id<FTXPipCaller>)createPipCaller {
id<FTXPipCaller> pipCalled = [[FTXPipGlobalImpl alloc] init];
return pipCalled;
}
@end
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPGLOBALIMPL_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPGLOBALIMPL_H_
#import <Foundation/Foundation.h>
#import "FTXPipCaller.h"
NS_ASSUME_NONNULL_BEGIN
@interface FTXPipGlobalImpl : NSObject<FTXPipCaller>
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPGLOBALIMPL_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPPLAYERDELEGATE_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPPLAYERDELEGATE_H_
#import "FTXPlayerConstants.h"
@protocol FTXPipPlayerDelegate <NSObject>
-(void)playerStateDidChange:(FTXAVPlayerState)playerState;
@end
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPPLAYERDELEGATE_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPRENDERVIEW_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPRENDERVIEW_H_
#import <Foundation/Foundation.h>
#import "FTXBackPlayer.h"
NS_ASSUME_NONNULL_BEGIN
@interface FTXPipRenderView : UIView
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer;
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_LIVE_PIP_FTXPIPRENDERVIEW_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import "FTXPipRenderView.h"
#import "FTXImgTools.h"
@interface FTXPipRenderView()
@property (nonatomic, strong) CALayer *videoLayer;
@end
@implementation FTXPipRenderView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.videoLayer = [CALayer layer];
self.videoLayer.frame = self.bounds;
[self.layer addSublayer:self.videoLayer];
}
return self;
}
- (void)displayPixelBuffer:(CVPixelBufferRef)pixelBuffer {
CIImage *ciImage = [FTXImgTools ciImageFromPixelBuffer:pixelBuffer];
// 创建一个CIContext
CIContext *context = [CIContext contextWithOptions:nil];
// 将CIImage渲染到CGImage
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
// 更新CALayer的内容
self.videoLayer.contents = (__bridge id _Nullable)(cgImage);
// 释放CGImage
CGImageRelease(cgImage);
}
- (void)layoutSubviews {
[super layoutSubviews];
CGSize oldSize = self.videoLayer.frame.size;
CGRect newRect = self.frame;
CGSize newSize = newRect.size;
if (oldSize.width != newSize.width || oldSize.height != newSize.height) {
self.videoLayer.frame = newRect;
}
}
@end
// Copyright (c) 2024 Tencent. All rights reserved.
#ifndef SUPERPLAYER_FLUTTER_IOS_CLASSES_TOOLS_FTXIMGTOOLS_H_
#define SUPERPLAYER_FLUTTER_IOS_CLASSES_TOOLS_FTXIMGTOOLS_H_
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FTXImgTools : NSObject
+ (CVPixelBufferRef)CVPixelBufferRefFromUiImage:(UIImage *)img;
+ (CIImage *)ciImageFromPixelBuffer:(CVPixelBufferRef)pixelBuffer;
@end
NS_ASSUME_NONNULL_END
#endif // SUPERPLAYER_FLUTTER_IOS_CLASSES_TOOLS_FTXIMGTOOLS_H_
// Copyright (c) 2024 Tencent. All rights reserved.
#import "FTXImgTools.h"
@implementation FTXImgTools
+ (CVPixelBufferRef)CVPixelBufferRefFromUiImage:(UIImage *)img {
CGSize size = img.size;
CGImageRef image = [img CGImage];
BOOL hasAlpha = CGImageRefContainsAlpha(image);
CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
empty, kCVPixelBufferIOSurfacePropertiesKey,
nil];
CVPixelBufferRef pxbuffer = NULL;
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, inputPixelFormat(), (__bridge CFDictionaryRef) options, &pxbuffer);
NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);
CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
NSParameterAssert(pxdata != NULL);
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
uint32_t bitmapInfo = bitmapInfoWithPixelFormatType(inputPixelFormat(), (bool)hasAlpha);
CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, CVPixelBufferGetBytesPerRow(pxbuffer), rgbColorSpace, bitmapInfo);
NSParameterAssert(context);
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);
return pxbuffer;
}
+ (CIImage *)ciImageFromPixelBuffer:(CVPixelBufferRef)pixelBuffer {
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
return ciImage;
}
static OSType inputPixelFormat(void){
return kCVPixelFormatType_32BGRA;
}
static uint32_t bitmapInfoWithPixelFormatType(OSType inputPixelFormat, bool hasAlpha){
if (inputPixelFormat == kCVPixelFormatType_32BGRA) {
uint32_t bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
if (!hasAlpha) {
bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
}
return bitmapInfo;
}else if (inputPixelFormat == kCVPixelFormatType_32ARGB) {
uint32_t bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big;
return bitmapInfo;
}else{
return 0;
}
}
// Check alpha value
BOOL CGImageRefContainsAlpha(CGImageRef imageRef) {
if (!imageRef) {
return NO;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
return hasAlpha;
}
@end
......@@ -281,20 +281,13 @@ class TXLivePlayerController extends ChangeNotifier implements ValueListenable<T
{String? backIconForAndroid, String? playIconForAndroid, String? pauseIconForAndroid, String? forwardIconForAndroid}) async {
if (_isNeedDisposed) return -1;
await _initPlayer.future;
if (defaultTargetPlatform == TargetPlatform.android) {
IntMsg intMsg = await _livePlayerApi.enterPictureInPictureMode(PipParamsPlayerMsg()
..backIconForAndroid = backIconForAndroid
..playIconForAndroid = playIconForAndroid
..pauseIconForAndroid = pauseIconForAndroid
..forwardIconForAndroid = forwardIconForAndroid
..playerId = _playerId);
return intMsg.value ?? -1;
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
// The background picture-in-picture feature for ios steaming live is temporarily disabled.
return -1;
} else {
return -1;
}
IntMsg intMsg = await _livePlayerApi.enterPictureInPictureMode(PipParamsPlayerMsg()
..backIconForAndroid = backIconForAndroid
..playIconForAndroid = playIconForAndroid
..pauseIconForAndroid = pauseIconForAndroid
..forwardIconForAndroid = forwardIconForAndroid
..playerId = _playerId);
return intMsg.value ?? -1;
}
/// Exit picture-in-picture mode if the player is in picture-in-picture mode.
......
......@@ -374,6 +374,12 @@ abstract class TXVodPlayEvent {
// PIP error, PIP function is not started (only support iOS).
// pip 错误,PIP功能没有启动 only support iOS
static const ERROR_IOS_PIP_NOT_RUNNING = -111;
// PIP start time out
// PIP 启动超时
static const ERROR_IOS_PIP_START_TIME_OUT = -112;
// Insufficient permissions, currently only appears in Picture-in-Picture live streaming
// 权限不足,目前只出现在直播画中画
static const ERROR_PIP_AUTH_DENIED = -201;
// PIP error, currently unable to enter PIP mode, such as being in full screen mode.
// pip 错误,当前不能进入pip模式,例如正处于全屏模式下
static const ERROR_PIP_CAN_NOT_ENTER = -120;
......
......@@ -822,24 +822,11 @@ class SuperPlayerController {
Future<int> enterPictureInPictureMode(
{String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
if (_playerUIStatus == SuperPlayerUIStatus.WINDOW_MODE) {
if (playerType == SuperPlayerType.VOD) {
return TXPipController.instance.enterPip(_vodPlayerController, _context,
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
} else {
if (defaultTargetPlatform == TargetPlatform.android) {
return TXPipController.instance.enterPip(_livePlayerController, _context,
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
TXPipController.instance.exitAndReleaseCurrentPip();
return _livePlayerController.enterPictureInPictureMode();
}
}
return TXPipController.instance.enterPip(getCurrentController(), _context,
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
}
return TXVodPlayEvent.ERROR_PIP_CAN_NOT_ENTER;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论