Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
T
tx_player_fork
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蒋俊
tx_player_fork
Commits
8ea0a67a
提交
8ea0a67a
authored
10月 28, 2024
作者:
kongdywang
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
1. Added Picture-in-Picture capability for iOS live streaming.
2. Fixed the Picture-in-Picture service leak issue on Android.
上级
b4fe752b
隐藏空白字符变更
内嵌
并排
正在显示
31 个修改的文件
包含
1513 行增加
和
145 行删除
+1513
-145
FTXLivePlayer.java
.../src/main/java/com/tencent/vod/flutter/FTXLivePlayer.java
+2
-1
SuperPlayerPlugin.java
.../main/java/com/tencent/vod/flutter/SuperPlayerPlugin.java
+4
-0
FlutterPipImplActivity.java
...va/com/tencent/vod/flutter/ui/FlutterPipImplActivity.java
+1
-1
FTXEvent.h
Flutter/ios/Classes/FTXEvent.h
+6
-0
FTXLivePlayer.h
Flutter/ios/Classes/FTXLivePlayer.h
+3
-0
FTXLivePlayer.m
Flutter/ios/Classes/FTXLivePlayer.m
+205
-27
FTXVodPlayer.h
Flutter/ios/Classes/FTXVodPlayer.h
+1
-18
FTXVodPlayer.m
Flutter/ios/Classes/FTXVodPlayer.m
+9
-66
SuperPlayerPlugin.m
Flutter/ios/Classes/SuperPlayerPlugin.m
+4
-0
FTXPlayerConstants.h
Flutter/ios/Classes/common/FTXPlayerConstants.h
+64
-0
FTXLivePipDelegate.h
Flutter/ios/Classes/helper/FTXLivePipDelegate.h
+16
-0
FTXVodPlayerDelegate.h
Flutter/ios/Classes/helper/FTXVodPlayerDelegate.h
+23
-0
TXPipAuth.h
Flutter/ios/Classes/helper/TXPipAuth.h
+19
-0
TXPipAuth.m
Flutter/ios/Classes/helper/TXPipAuth.m
+54
-0
FTXBackPlayer.h
Flutter/ios/Classes/live/pip/FTXBackPlayer.h
+37
-0
FTXBackPlayer.m
Flutter/ios/Classes/live/pip/FTXBackPlayer.m
+246
-0
FTXPipCaller.h
Flutter/ios/Classes/live/pip/FTXPipCaller.h
+31
-0
FTXPipController.h
Flutter/ios/Classes/live/pip/FTXPipController.h
+30
-0
FTXPipController.m
Flutter/ios/Classes/live/pip/FTXPipController.m
+130
-0
FTXPipFactory.h
Flutter/ios/Classes/live/pip/FTXPipFactory.h
+19
-0
FTXPipFactory.m
Flutter/ios/Classes/live/pip/FTXPipFactory.m
+22
-0
FTXPipGlobalImpl.h
Flutter/ios/Classes/live/pip/FTXPipGlobalImpl.h
+16
-0
FTXPipGlobalImpl.m
Flutter/ios/Classes/live/pip/FTXPipGlobalImpl.m
+377
-0
FTXPipPlayerDelegate.h
Flutter/ios/Classes/live/pip/FTXPipPlayerDelegate.h
+14
-0
FTXPipRenderView.h
Flutter/ios/Classes/live/pip/FTXPipRenderView.h
+18
-0
FTXPipRenderView.m
Flutter/ios/Classes/live/pip/FTXPipRenderView.m
+51
-0
FTXImgTools.h
Flutter/ios/Classes/tools/FTXImgTools.h
+19
-0
FTXImgTools.m
Flutter/ios/Classes/tools/FTXImgTools.m
+74
-0
txliveplayer_controller.dart
Flutter/lib/Core/txliveplayer_controller.dart
+7
-14
txplayer_define.dart
Flutter/lib/Core/txplayer_define.dart
+6
-0
superplayer_controller.dart
...Widget/superplayer_widget/lib/superplayer_controller.dart
+5
-18
没有找到文件。
Flutter/android/src/main/java/com/tencent/vod/flutter/FTXLivePlayer.java
浏览文件 @
8ea0a67a
...
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
...
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
import
android.graphics.SurfaceTexture
;
import
android.graphics.SurfaceTexture
;
import
android.os.Bundle
;
import
android.os.Bundle
;
import
android.view.Surface
;
import
android.view.Surface
;
import
android.view.TextureView
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.NonNull
;
...
@@ -65,7 +66,7 @@ public class FTXLivePlayer extends FTXBasePlayer implements TXFlutterLivePlayerA
...
@@ -65,7 +66,7 @@ public class FTXLivePlayer extends FTXBasePlayer implements TXFlutterLivePlayerA
private
final
FTXV2LiveObserver
mObserver
;
private
final
FTXV2LiveObserver
mObserver
;
private
int
mLastPlayEvent
=
-
1
;
private
int
mLastPlayEvent
=
-
1
;
private
boolean
mIsPaused
=
false
;
private
boolean
mIsPaused
=
false
;
private
FtxMessages
.
TXLivePlayerFlutterAPI
mLiveFlutterApi
;
private
final
FtxMessages
.
TXLivePlayerFlutterAPI
mLiveFlutterApi
;
private
final
FTXPIPManager
.
PipCallback
pipCallback
=
new
FTXPIPManager
.
PipCallback
()
{
private
final
FTXPIPManager
.
PipCallback
pipCallback
=
new
FTXPIPManager
.
PipCallback
()
{
@Override
@Override
...
...
Flutter/android/src/main/java/com/tencent/vod/flutter/SuperPlayerPlugin.java
浏览文件 @
8ea0a67a
...
@@ -204,6 +204,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
...
@@ -204,6 +204,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
mPlayers
.
append
(
playerId
,
player
);
mPlayers
.
append
(
playerId
,
player
);
PlayerMsg
playerMsg
=
new
PlayerMsg
();
PlayerMsg
playerMsg
=
new
PlayerMsg
();
playerMsg
.
setPlayerId
((
long
)
playerId
);
playerMsg
.
setPlayerId
((
long
)
playerId
);
LiteavLog
.
i
(
TAG
,
"createVodPlayer :"
+
playerId
);
return
playerMsg
;
return
playerMsg
;
}
}
...
@@ -215,6 +216,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
...
@@ -215,6 +216,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
mPlayers
.
append
(
playerId
,
player
);
mPlayers
.
append
(
playerId
,
player
);
PlayerMsg
playerMsg
=
new
PlayerMsg
();
PlayerMsg
playerMsg
=
new
PlayerMsg
();
playerMsg
.
setPlayerId
((
long
)
playerId
);
playerMsg
.
setPlayerId
((
long
)
playerId
);
LiteavLog
.
i
(
TAG
,
"createLivePlayer :"
+
playerId
);
return
playerMsg
;
return
playerMsg
;
}
}
...
@@ -229,8 +231,10 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
...
@@ -229,8 +231,10 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
public
void
releasePlayer
(
@NonNull
PlayerMsg
playerId
)
{
public
void
releasePlayer
(
@NonNull
PlayerMsg
playerId
)
{
if
(
null
!=
playerId
.
getPlayerId
())
{
if
(
null
!=
playerId
.
getPlayerId
())
{
int
intPlayerId
=
playerId
.
getPlayerId
().
intValue
();
int
intPlayerId
=
playerId
.
getPlayerId
().
intValue
();
LiteavLog
.
i
(
TAG
,
"releasePlayer :"
+
intPlayerId
);
FTXBasePlayer
player
=
mPlayers
.
get
(
intPlayerId
);
FTXBasePlayer
player
=
mPlayers
.
get
(
intPlayerId
);
if
(
player
!=
null
)
{
if
(
player
!=
null
)
{
LiteavLog
.
i
(
TAG
,
"releasePlayer start destroy player :"
+
intPlayerId
);
player
.
destroy
();
player
.
destroy
();
mPlayers
.
remove
(
intPlayerId
);
mPlayers
.
remove
(
intPlayerId
);
}
}
...
...
Flutter/android/src/main/java/com/tencent/vod/flutter/ui/FlutterPipImplActivity.java
浏览文件 @
8ea0a67a
...
@@ -498,7 +498,7 @@ public class FlutterPipImplActivity extends Activity implements TextureView.Surf
...
@@ -498,7 +498,7 @@ public class FlutterPipImplActivity extends Activity implements TextureView.Surf
private
void
bindAndroid12BugServiceIfNeed
()
{
private
void
bindAndroid12BugServiceIfNeed
()
{
if
(
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
Q
)
{
if
(
Build
.
VERSION
.
SDK_INT
>=
VERSION_CODES
.
Q
)
{
Intent
serviceIntent
=
new
Intent
(
this
,
TXAndroid12BridgeService
.
class
);
Intent
serviceIntent
=
new
Intent
(
getApplicationContext
()
,
TXAndroid12BridgeService
.
class
);
startService
(
serviceIntent
);
startService
(
serviceIntent
);
bindService
(
serviceIntent
,
this
,
Context
.
BIND_AUTO_CREATE
);
bindService
(
serviceIntent
,
this
,
Context
.
BIND_AUTO_CREATE
);
}
}
...
...
Flutter/ios/Classes/FTXEvent.h
浏览文件 @
8ea0a67a
...
@@ -45,6 +45,12 @@
...
@@ -45,6 +45,12 @@
/// PIP function is not started.
/// PIP function is not started.
/// PIP功能没有启动
/// PIP功能没有启动
#define ERROR_IOS_PIP_NOT_RUNNING -111
#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
...
...
Flutter/ios/Classes/FTXLivePlayer.h
浏览文件 @
8ea0a67a
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import "FTXBasePlayer.h"
#import "FTXBasePlayer.h"
#import "FTXVodPlayerDelegate.h"
@protocol
FlutterPluginRegistrar
;
@protocol
FlutterPluginRegistrar
;
...
@@ -11,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
...
@@ -11,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface
FTXLivePlayer
:
FTXBasePlayer
@interface
FTXLivePlayer
:
FTXBasePlayer
@property
(
nonatomic
,
weak
)
id
<
FTXVodPlayerDelegate
>
delegate
;
-
(
instancetype
)
initWithRegistrar
:(
id
<
FlutterPluginRegistrar
>
)
registrar
;
-
(
instancetype
)
initWithRegistrar
:(
id
<
FlutterPluginRegistrar
>
)
registrar
;
-
(
void
)
notifyAppTerminate
:(
UIApplication
*
)
application
;
-
(
void
)
notifyAppTerminate
:(
UIApplication
*
)
application
;
...
...
Flutter/ios/Classes/FTXLivePlayer.m
浏览文件 @
8ea0a67a
...
@@ -11,10 +11,14 @@
...
@@ -11,10 +11,14 @@
#import "FTXLog.h"
#import "FTXLog.h"
#import <stdatomic.h>
#import <stdatomic.h>
#import "FTXV2LiveTools.h"
#import "FTXV2LiveTools.h"
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "FTXPipController.h"
#import "FTXImgTools.h"
static
const
int
uninitialized
=
-
1
;
static
const
int
uninitialized
=
-
1
;
@interface
FTXLivePlayer
()
<
FlutterTexture
,
V2TXLivePlayerObserver
,
TXFlutterLivePlayerApi
>
@interface
FTXLivePlayer
()
<
FlutterTexture
,
V2TXLivePlayerObserver
,
TXFlutterLivePlayerApi
,
FTXLivePipDelegate
,
FTXPipPlayerDelegate
>
@property
(
nonatomic
,
strong
)
V2TXLivePlayer
*
livePlayer
;
@property
(
nonatomic
,
strong
)
V2TXLivePlayer
*
livePlayer
;
@property
(
nonatomic
,
assign
)
int
lastPlayEvent
;
@property
(
nonatomic
,
assign
)
int
lastPlayEvent
;
...
@@ -22,6 +26,10 @@ static const int uninitialized = -1;
...
@@ -22,6 +26,10 @@ static const int uninitialized = -1;
@property
(
nonatomic
,
assign
)
BOOL
isOpenedPip
;
@property
(
nonatomic
,
assign
)
BOOL
isOpenedPip
;
@property
(
nonatomic
,
assign
)
BOOL
isPaused
;
@property
(
nonatomic
,
assign
)
BOOL
isPaused
;
@property
(
nonatomic
,
strong
)
TXLivePlayerFlutterAPI
*
liveFlutterApi
;
@property
(
nonatomic
,
strong
)
TXLivePlayerFlutterAPI
*
liveFlutterApi
;
@property
(
nonatomic
,
assign
)
BOOL
hasEnteredPipMode
;
@property
(
nonatomic
,
assign
)
BOOL
isStartEnterPipMode
;
@property
(
nonatomic
,
assign
)
BOOL
restoreUI
;
@property
(
nonatomic
,
assign
)
CGSize
liveSize
;
@end
@end
...
@@ -49,6 +57,10 @@ static const int uninitialized = -1;
...
@@ -49,6 +57,10 @@ static const int uninitialized = -1;
_textureId
=
-
1
;
_textureId
=
-
1
;
self
.
isOpenedPip
=
NO
;
self
.
isOpenedPip
=
NO
;
self
.
lastPlayEvent
=
-
1
;
self
.
lastPlayEvent
=
-
1
;
self
.
hasEnteredPipMode
=
NO
;
self
.
isStartEnterPipMode
=
NO
;
self
.
restoreUI
=
NO
;
self
.
liveSize
=
CGSizeZero
;
SetUpTXFlutterLivePlayerApiWithSuffix
([
registrar
messenger
],
self
,
[
self
.
playerId
stringValue
]);
SetUpTXFlutterLivePlayerApiWithSuffix
([
registrar
messenger
],
self
,
[
self
.
playerId
stringValue
]);
self
.
liveFlutterApi
=
[[
TXLivePlayerFlutterAPI
alloc
]
initWithBinaryMessenger
:[
registrar
messenger
]
messageChannelSuffix
:[
self
.
playerId
stringValue
]];
self
.
liveFlutterApi
=
[[
TXLivePlayerFlutterAPI
alloc
]
initWithBinaryMessenger
:[
registrar
messenger
]
messageChannelSuffix
:[
self
.
playerId
stringValue
]];
}
}
...
@@ -122,11 +134,41 @@ static const int uninitialized = -1;
...
@@ -122,11 +134,41 @@ static const int uninitialized = -1;
}
}
if
(
nil
!=
self
.
livePlayer
)
{
if
(
nil
!=
self
.
livePlayer
)
{
[
self
.
livePlayer
enableObserveVideoFrame
:
YES
pixelFormat
:
V2TXLivePixelFormatBGRA32
bufferType
:
V2TXLiveBufferTypePixelBuffer
];
[
self
.
livePlayer
enableObserveVideoFrame
:
YES
pixelFormat
:
V2TXLivePixelFormatBGRA32
bufferType
:
V2TXLiveBufferTypePixelBuffer
];
[
self
.
livePlayer
setProperty
:
@"enableBackgroundDecoding"
value
:
@
(
YES
)];
}
}
}
}
}
}
#pragma mark -
#pragma mark - private method
/**
Check if the current language is Simplified Chinese.
*/
-
(
BOOL
)
isCurrentLanguageHans
{
NSArray
*
languages
=
[
NSLocale
preferredLanguages
];
NSString
*
currentLanguage
=
[
languages
objectAtIndex
:
0
];
if
([
currentLanguage
isEqualToString
:
@"zh-Hans-CN"
])
{
return
YES
;
}
return
NO
;
}
-
(
CVPixelBufferRef
)
getPipImagePixelBuffer
{
NSString
*
imagePath
;
if
([
self
isCurrentLanguageHans
])
{
imagePath
=
[[
NSBundle
mainBundle
]
pathForResource
:
@"pictureInpicture_zh"
ofType
:
@"jpg"
];
}
else
{
imagePath
=
[[
NSBundle
mainBundle
]
pathForResource
:
@"pictureInpicture_en"
ofType
:
@"jpg"
];
}
UIImage
*
image
=
[
UIImage
imageWithContentsOfFile
:
imagePath
];
// must create new obj when evey called
return
[
FTXImgTools
CVPixelBufferRefFromUiImage
:
image
];
}
-
(
NSNumber
*
)
createPlayer
:
(
BOOL
)
onlyAudio
-
(
NSNumber
*
)
createPlayer
:
(
BOOL
)
onlyAudio
{
{
...
@@ -141,7 +183,7 @@ static const int uninitialized = -1;
...
@@ -141,7 +183,7 @@ static const int uninitialized = -1;
-
(
UIView
*
)
txPipView
{
-
(
UIView
*
)
txPipView
{
if
(
!
_txPipView
)
{
if
(
!
_txPipView
)
{
// Set the size to 1 pixel to ensure proper display in PIP.
// Set the size to 1 pixel to ensure proper display in PIP.
_txPipView
=
[[
UIView
alloc
]
initWithFrame
:
CGRectMake
(
0
,
0
,
1
,
1
)];
_txPipView
=
[[
UIView
alloc
]
initWithFrame
:
CGRectMake
(
0
,
0
,
1
00
,
100
)];
}
}
return
_txPipView
;
return
_txPipView
;
}
}
...
@@ -178,7 +220,6 @@ static const int uninitialized = -1;
...
@@ -178,7 +220,6 @@ static const int uninitialized = -1;
-
(
void
)
setRenderRotation
:
(
int
)
rotation
-
(
void
)
setRenderRotation
:
(
int
)
rotation
{
{
if
(
self
.
livePlayer
!=
nil
)
{
if
(
self
.
livePlayer
!=
nil
)
{
[
self
.
livePlayer
setRenderRotation
:[
FTXV2LiveTools
transRotationFromDegree
:
rotation
]];
[
self
.
livePlayer
setRenderRotation
:[
FTXV2LiveTools
transRotationFromDegree
:
rotation
]];
}
}
...
@@ -200,6 +241,7 @@ static const int uninitialized = -1;
...
@@ -200,6 +241,7 @@ static const int uninitialized = -1;
[
self
.
livePlayer
resumeVideo
];
[
self
.
livePlayer
resumeVideo
];
self
.
lastPlayEvent
=
-
1
;
self
.
lastPlayEvent
=
-
1
;
self
.
isPaused
=
NO
;
self
.
isPaused
=
NO
;
self
.
liveSize
=
CGSizeZero
;
return
(
int
)[
self
.
livePlayer
startLivePlay
:
url
];
return
(
int
)[
self
.
livePlayer
startLivePlay
:
url
];
}
}
return
uninitialized
;
return
uninitialized
;
...
@@ -211,6 +253,10 @@ static const int uninitialized = -1;
...
@@ -211,6 +253,10 @@ static const int uninitialized = -1;
_isStoped
=
YES
;
_isStoped
=
YES
;
self
.
lastPlayEvent
=
-
1
;
self
.
lastPlayEvent
=
-
1
;
self
.
isPaused
=
NO
;
self
.
isPaused
=
NO
;
self
.
liveSize
=
CGSizeZero
;
if
(
self
.
hasEnteredPipMode
)
{
[[
FTXPipController
shareInstance
]
exitPip
];
}
return
[
self
.
livePlayer
stopPlay
];
return
[
self
.
livePlayer
stopPlay
];
}
}
return
NO
;
return
NO
;
...
@@ -226,6 +272,14 @@ static const int uninitialized = -1;
...
@@ -226,6 +272,14 @@ static const int uninitialized = -1;
-
(
void
)
pause
-
(
void
)
pause
{
{
if
(
self
.
hasEnteredPipMode
)
{
[[
FTXPipController
shareInstance
]
pausePipVideo
];
}
else
{
[
self
pauseImpl
];
}
}
-
(
void
)
pauseImpl
{
if
(
self
.
livePlayer
!=
nil
)
{
if
(
self
.
livePlayer
!=
nil
)
{
[
self
.
livePlayer
pauseVideo
];
[
self
.
livePlayer
pauseVideo
];
[
self
.
livePlayer
pauseAudio
];
[
self
.
livePlayer
pauseAudio
];
...
@@ -235,6 +289,14 @@ static const int uninitialized = -1;
...
@@ -235,6 +289,14 @@ static const int uninitialized = -1;
-
(
void
)
resume
-
(
void
)
resume
{
{
if
(
self
.
hasEnteredPipMode
)
{
[[
FTXPipController
shareInstance
]
resumePipVideo
];
}
else
{
[
self
resumeImpl
];
}
}
-
(
void
)
resumeImpl
{
if
(
self
.
livePlayer
!=
nil
)
{
if
(
self
.
livePlayer
!=
nil
)
{
[
self
.
livePlayer
resumeVideo
];
[
self
.
livePlayer
resumeVideo
];
[
self
.
livePlayer
resumeAudio
];
[
self
.
livePlayer
resumeAudio
];
...
@@ -338,6 +400,25 @@ static const int uninitialized = -1;
...
@@ -338,6 +400,25 @@ static const int uninitialized = -1;
FTXLOGI
(
@"onLivePlayEvent:%i,%@"
,
evtID
,
params
[
EVT_MSG
])
FTXLOGI
(
@"onLivePlayEvent:%i,%@"
,
evtID
,
params
[
EVT_MSG
])
}
}
-
(
NSNumber
*
)
enablePictureInPicture
:
(
BOOL
)
isEnabled
{
if
(
self
.
livePlayer
)
{
if
(
isEnabled
!=
self
.
isOpenedPip
)
{
if
(
isEnabled
)
{
UIViewController
*
flutterVC
=
[
self
getFlutterViewController
];
[
flutterVC
.
view
addSubview
:
self
.
txPipView
];
[
self
.
livePlayer
setRenderView
:
self
.
txPipView
];
}
else
if
(
nil
!=
self
->
_txPipView
)
{
[
self
->
_txPipView
removeFromSuperview
];
self
->
_txPipView
=
nil
;
}
self
.
isOpenedPip
=
isEnabled
;
int
result
=
(
int
)[
self
.
livePlayer
enablePictureInPicture
:
isEnabled
];
return
@
(
result
);
}
}
return
@
(
uninitialized
);
}
#pragma mark - FlutterTexture
#pragma mark - FlutterTexture
-
(
CVPixelBufferRef
_Nullable
)
copyPixelBuffer
-
(
CVPixelBufferRef
_Nullable
)
copyPixelBuffer
...
@@ -345,6 +426,9 @@ static const int uninitialized = -1;
...
@@ -345,6 +426,9 @@ static const int uninitialized = -1;
if
(
_isTerminate
||
_isStoped
){
if
(
_isTerminate
||
_isStoped
){
return
nil
;
return
nil
;
}
}
if
(
self
.
hasEnteredPipMode
)
{
return
[
self
getPipImagePixelBuffer
];
}
CVPixelBufferRef
pixelBuffer
=
_latestPixelBuffer
;
CVPixelBufferRef
pixelBuffer
=
_latestPixelBuffer
;
while
(
!
atomic_compare_exchange_strong_explicit
(
&
_latestPixelBuffer
,
&
pixelBuffer
,
NULL
,
memory_order_release
,
memory_order_relaxed
))
{
while
(
!
atomic_compare_exchange_strong_explicit
(
&
_latestPixelBuffer
,
&
pixelBuffer
,
NULL
,
memory_order_release
,
memory_order_relaxed
))
{
pixelBuffer
=
_latestPixelBuffer
;
pixelBuffer
=
_latestPixelBuffer
;
...
@@ -360,12 +444,23 @@ static const int uninitialized = -1;
...
@@ -360,12 +444,23 @@ static const int uninitialized = -1;
}
}
-
(
nullable
IntMsg
*
)
enterPictureInPictureModePipParamsMsg
:
(
nonnull
PipParamsPlayerMsg
*
)
pipParamsMsg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
IntMsg
*
)
enterPictureInPictureModePipParamsMsg
:
(
nonnull
PipParamsPlayerMsg
*
)
pipParamsMsg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
// [self.livePlayer enablePictureInPicture:YES];
if
(
self
.
liveSize
.
width
<=
0
||
self
.
liveSize
.
height
<=
0
)
{
return
[
TXCommonUtil
intMsgWith
:
@
(
NO_ERROR
)];;
FTXLOGE
(
@"live pip opened failed, size is invalid"
);
return
[
TXCommonUtil
intMsgWith
:
@
(
ERROR_IOS_PIP_PLAYER_NOT_EXIST
)];
}
int
retCode
=
[[
FTXPipController
shareInstance
]
startOpenPip
:
self
.
livePlayer
withSize
:
self
.
liveSize
];
if
(
retCode
==
NO_ERROR
)
{
[
FTXPipController
shareInstance
].
pipDelegate
=
self
;
[
FTXPipController
shareInstance
].
playerDelegate
=
self
;
self
.
isStartEnterPipMode
=
YES
;
}
return
[
TXCommonUtil
intMsgWith
:
@
(
retCode
)];
}
}
-
(
void
)
exitPictureInPictureModePlayerMsg
:
(
nonnull
PlayerMsg
*
)
playerMsg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
void
)
exitPictureInPictureModePlayerMsg
:
(
nonnull
PlayerMsg
*
)
playerMsg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
//FlutterMethodNotImplemented
if
(
self
.
hasEnteredPipMode
)
{
[[
FTXPipController
shareInstance
]
exitPip
];
}
}
}
-
(
nullable
IntMsg
*
)
initializeOnlyAudio
:
(
nonnull
BoolPlayerMsg
*
)
onlyAudio
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
IntMsg
*
)
initializeOnlyAudio
:
(
nonnull
BoolPlayerMsg
*
)
onlyAudio
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
...
@@ -418,27 +513,10 @@ static const int uninitialized = -1;
...
@@ -418,27 +513,10 @@ static const int uninitialized = -1;
return
[
TXCommonUtil
intMsgWith
:
@
(
r
)];
return
[
TXCommonUtil
intMsgWith
:
@
(
r
)];
}
}
-
(
nullable
NSNumber
*
)
enablePictureInPictureMsg
:
(
nonnull
BoolPlayerMsg
*
)
msg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
NSNumber
*
)
enablePictureInPictureMsg
:
(
nonnull
BoolPlayerMsg
*
)
msg
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
if
(
self
.
livePlayer
)
{
return
[
self
enablePictureInPicture
:[
msg
.
value
boolValue
]];
BOOL
dstFlag
=
[
msg
.
value
boolValue
];
if
(
dstFlag
!=
self
.
isOpenedPip
)
{
if
([
msg
.
value
boolValue
])
{
UIViewController
*
flutterVC
=
[
self
getFlutterViewController
];
[
flutterVC
.
view
addSubview
:
self
.
txPipView
];
[
self
.
livePlayer
setRenderView
:
self
.
txPipView
];
}
else
if
(
nil
!=
self
->
_txPipView
)
{
[
self
->
_txPipView
removeFromSuperview
];
self
->
_txPipView
=
nil
;
}
self
.
isOpenedPip
=
dstFlag
;
int
result
=
(
int
)[
self
.
livePlayer
enablePictureInPicture
:[
msg
.
value
boolValue
]];
return
@
(
result
);
}
}
return
@
(
uninitialized
);
}
}
-
(
nullable
NSNumber
*
)
enableReceiveSeiMessagePlayerMsg
:
(
nonnull
PlayerMsg
*
)
playerMsg
isEnabled
:
(
nonnull
NSNumber
*
)
isEnabled
payloadType
:
(
nonnull
NSNumber
*
)
payloadType
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
NSNumber
*
)
enableReceiveSeiMessagePlayerMsg
:
(
nonnull
PlayerMsg
*
)
playerMsg
isEnabled
:
(
nonnull
NSNumber
*
)
isEnabled
payloadType
:
(
nonnull
NSNumber
*
)
payloadType
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
if
(
self
.
livePlayer
)
{
if
(
self
.
livePlayer
)
{
int
result
=
(
int
)[
self
.
livePlayer
enableReceiveSeiMessage
:[
isEnabled
boolValue
]
payloadType
:[
payloadType
intValue
]];
int
result
=
(
int
)[
self
.
livePlayer
enableReceiveSeiMessage
:[
isEnabled
boolValue
]
payloadType
:[
payloadType
intValue
]];
...
@@ -532,6 +610,7 @@ static const int uninitialized = -1;
...
@@ -532,6 +610,7 @@ static const int uninitialized = -1;
EVT_PARAM2
:
@
(
height
),
EVT_PARAM2
:
@
(
height
),
EVT_MSG
:
[
NSString
stringWithFormat
:
@"Resolution changed. resolution:%ldx%ld"
,
(
long
)
width
,
(
long
)
height
]
EVT_MSG
:
[
NSString
stringWithFormat
:
@"Resolution changed. resolution:%ldx%ld"
,
(
long
)
width
,
(
long
)
height
]
};
};
self
.
liveSize
=
CGSizeMake
(
width
,
height
);
[
self
notifyPlayerEvent
:
evtID
withParams
:
param
];
[
self
notifyPlayerEvent
:
evtID
withParams
:
param
];
}
}
...
@@ -684,13 +763,16 @@ static const int uninitialized = -1;
...
@@ -684,13 +763,16 @@ static const int uninitialized = -1;
}
}
old
=
_latestPixelBuffer
;
old
=
_latestPixelBuffer
;
}
}
if
(
old
&&
old
!=
pixelBuffer
)
{
if
(
old
&&
old
!=
pixelBuffer
)
{
CFRelease
(
old
);
CFRelease
(
old
);
}
}
if
(
_textureId
>=
0
&&
_textureRegistry
)
{
if
(
_textureId
>=
0
&&
_textureRegistry
)
{
[
_textureRegistry
textureFrameAvailable
:
_textureId
];
[
_textureRegistry
textureFrameAvailable
:
_textureId
];
}
}
if
(
self
.
isStartEnterPipMode
)
{
CVPixelBufferRef
pipPixelBuffer
=
_latestPixelBuffer
;
[[
FTXPipController
shareInstance
]
displayPixelBuffer
:
pipPixelBuffer
];
}
}
}
}
}
...
@@ -800,7 +882,103 @@ static const int uninitialized = -1;
...
@@ -800,7 +882,103 @@ static const int uninitialized = -1;
* @param storagePath 录制的文件地址。
* @param storagePath 录制的文件地址。
*/
*/
-
(
void
)
onLocalRecordComplete
:
(
id
<
V2TXLivePlayer
>
)
player
errCode
:
(
NSInteger
)
errCode
storagePath
:
(
NSString
*
)
storagePath
{
-
(
void
)
onLocalRecordComplete
:
(
id
<
V2TXLivePlayer
>
)
player
errCode
:
(
NSInteger
)
errCode
storagePath
:
(
NSString
*
)
storagePath
{
}
#pragma mark - FTXLivePipDelegate
-
(
void
)
pictureInPictureErrorDidOccur
:
(
FTX_LIVE_PIP_ERROR
)
errorStatus
{
NSInteger
type
=
errorStatus
;
switch
(
errorStatus
)
{
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_NONE
:
type
=
NO_ERROR
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_DEVICE_NOT_SUPPORT
:
type
=
ERROR_IOS_PIP_DEVICE_NOT_SUPPORT
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_SUPPORT
:
type
=
ERROR_IOS_PIP_PLAYER_NOT_SUPPORT
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_VIDEO_NOT_SUPPORT
:
type
=
ERROR_IOS_PIP_VIDEO_NOT_SUPPORT
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_NOT_POSSIBLE
:
type
=
ERROR_IOS_PIP_IS_NOT_POSSIBLE
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_ERROR_FROM_SYSTEM
:
type
=
ERROR_IOS_PIP_FROM_SYSTEM
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PLAYER_NOT_EXIST
:
type
=
ERROR_IOS_PIP_PLAYER_NOT_EXIST
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_IS_RUNNING
:
type
=
ERROR_IOS_PIP_IS_RUNNING
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING
:
type
=
ERROR_IOS_PIP_NOT_RUNNING
;
break
;
case
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_START_TIMEOUT
:
type
=
ERROR_IOS_PIP_START_TIME_OUT
;
break
;
default
:
type
=
errorStatus
;
break
;
}
self
.
hasEnteredPipMode
=
NO
;
self
.
isStartEnterPipMode
=
NO
;
FTXLOGE
(
@"[onPlayer], pictureInPictureErrorDidOccur errorType= %ld"
,
type
);
if
(
self
.
delegate
&&
[
self
.
delegate
respondsToSelector
:
@selector
(
onPlayerPipStateError
:)])
{
[
self
.
delegate
onPlayerPipStateError
:
type
];
}
}
-
(
void
)
pictureInPictureStateDidChange
:
(
TX_VOD_PLAYER_PIP_STATE
)
pipState
{
if
(
pipState
==
TX_VOD_PLAYER_PIP_STATE_DID_START
)
{
self
.
hasEnteredPipMode
=
YES
;
if
(
self
.
delegate
&&
[
self
.
delegate
respondsToSelector
:
@selector
(
onPlayerPipStateDidStart
)])
{
[
self
.
delegate
onPlayerPipStateDidStart
];
}
}
if
(
pipState
==
TX_VOD_PLAYER_PIP_STATE_WILL_STOP
)
{
self
.
isStartEnterPipMode
=
NO
;
if
(
self
.
delegate
&&
[
self
.
delegate
respondsToSelector
:
@selector
(
onPlayerPipStateWillStop
)])
{
[
self
.
delegate
onPlayerPipStateWillStop
];
}
}
if
(
pipState
==
TX_VOD_PLAYER_PIP_STATE_DID_STOP
)
{
self
.
hasEnteredPipMode
=
NO
;
[[
FTXPipController
shareInstance
]
exitPip
];
if
(
self
.
restoreUI
)
{
self
.
restoreUI
=
NO
;
}
else
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
if
(
self
.
delegate
&&
[
self
.
delegate
respondsToSelector
:
@selector
(
onPlayerPipStateDidStop
)])
{
[
self
.
delegate
onPlayerPipStateDidStop
];
}
});
}
}
if
(
pipState
==
TX_VOD_PLAYER_PIP_STATE_RESTORE_UI
)
{
self
.
restoreUI
=
YES
;
dispatch_async
(
dispatch_get_main_queue
(),
^
{
[
self
resume
];
});
if
(
self
.
delegate
&&
[
self
.
delegate
respondsToSelector
:
@selector
(
onPlayerPipStateRestoreUI
:)])
{
[
self
.
delegate
onPlayerPipStateRestoreUI
:
0
];
}
}
self
.
isStartEnterPipMode
=
self
.
hasEnteredPipMode
;
}
-
(
void
)
playerStateDidChange
:
(
FTXAVPlayerState
)
playerState
{
if
(
playerState
==
FTXAVPlayerStatePlaying
)
{
[
self
resumeImpl
];
}
// else if (playerState == FTXAVPlayerStatePaused) {
// [self pauseImpl];
// }
}
}
@end
@end
Flutter/ios/Classes/FTXVodPlayer.h
浏览文件 @
8ea0a67a
...
@@ -4,29 +4,12 @@
...
@@ -4,29 +4,12 @@
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import "FTXBasePlayer.h"
#import "FTXBasePlayer.h"
#import "FTXVodPlayerDelegate.h"
@protocol
FlutterPluginRegistrar
;
@protocol
FlutterPluginRegistrar
;
NS_ASSUME_NONNULL_BEGIN
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
@interface
FTXVodPlayer
:
FTXBasePlayer
@property
(
nonatomic
,
weak
)
id
<
FTXVodPlayerDelegate
>
delegate
;
@property
(
nonatomic
,
weak
)
id
<
FTXVodPlayerDelegate
>
delegate
;
...
...
Flutter/ios/Classes/FTXVodPlayer.m
浏览文件 @
8ea0a67a
...
@@ -12,6 +12,7 @@
...
@@ -12,6 +12,7 @@
#import "TXCommonUtil.h"
#import "TXCommonUtil.h"
#import "FTXLog.h"
#import "FTXLog.h"
#import <stdatomic.h>
#import <stdatomic.h>
#import "FTXImgTools.h"
static
const
int
uninitialized
=
-
1
;
static
const
int
uninitialized
=
-
1
;
static
const
int
CODE_ON_RECEIVE_FIRST_FRAME
=
2003
;
static
const
int
CODE_ON_RECEIVE_FIRST_FRAME
=
2003
;
...
@@ -547,7 +548,8 @@ 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
];
UIImage
*
image
=
[
UIImage
imageWithContentsOfFile
:
imagePath
];
return
[
self
CVPixelBufferRefFromUiImage
:
image
];
// must create new obj when evey called
return
[
FTXImgTools
CVPixelBufferRefFromUiImage
:
image
];
}
}
-
(
void
)
setPlayerConfig
:
(
FTXVodPlayConfigPlayerMsg
*
)
args
-
(
void
)
setPlayerConfig
:
(
FTXVodPlayConfigPlayerMsg
*
)
args
...
@@ -792,6 +794,12 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
...
@@ -792,6 +794,12 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
case
TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING
:
case
TX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_NOT_RUNNING
:
type
=
ERROR_IOS_PIP_NOT_RUNNING
;
type
=
ERROR_IOS_PIP_NOT_RUNNING
;
break
;
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
;
self
.
hasEnteredPipMode
=
NO
;
FTXLOGE
(
@"[onPlayer], pictureInPictureErrorDidOccur errorType= %ld"
,
type
);
FTXLOGE
(
@"[onPlayer], pictureInPictureErrorDidOccur errorType= %ld"
,
type
);
...
@@ -807,71 +815,6 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
...
@@ -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
{
-
(
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
#pragma mark - TXFlutterVodPlayerApi
-
(
nullable
BoolMsg
*
)
enableHardwareDecodeEnable
:
(
nonnull
BoolPlayerMsg
*
)
enable
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
BoolMsg
*
)
enableHardwareDecodeEnable
:
(
nonnull
BoolPlayerMsg
*
)
enable
error
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
...
...
Flutter/ios/Classes/SuperPlayerPlugin.m
浏览文件 @
8ea0a67a
...
@@ -116,6 +116,7 @@ SuperPlayerPlugin* instance;
...
@@ -116,6 +116,7 @@ SuperPlayerPlugin* instance;
FTXLOGV
(
@"called releasePlayerInner,%@ is start release"
,
playerId
);
FTXLOGV
(
@"called releasePlayerInner,%@ is start release"
,
playerId
);
FTXBasePlayer
*
player
=
[
_players
objectForKey
:
playerId
];
FTXBasePlayer
*
player
=
[
_players
objectForKey
:
playerId
];
if
(
player
!=
nil
)
{
if
(
player
!=
nil
)
{
FTXLOGI
(
@"releasePlayer start destroy player :%@"
,
playerId
);
[
player
destory
];
[
player
destory
];
[
_players
removeObjectForKey
:
playerId
];
[
_players
removeObjectForKey
:
playerId
];
}
}
...
@@ -234,8 +235,10 @@ SuperPlayerPlugin* instance;
...
@@ -234,8 +235,10 @@ SuperPlayerPlugin* instance;
-
(
nullable
PlayerMsg
*
)
createLivePlayerWithError
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
-
(
nullable
PlayerMsg
*
)
createLivePlayerWithError
:
(
FlutterError
*
_Nullable
__autoreleasing
*
_Nonnull
)
error
{
FTXLivePlayer
*
player
=
[[
FTXLivePlayer
alloc
]
initWithRegistrar
:
self
.
registrar
];
FTXLivePlayer
*
player
=
[[
FTXLivePlayer
alloc
]
initWithRegistrar
:
self
.
registrar
];
player
.
delegate
=
self
;
NSNumber
*
playerId
=
player
.
playerId
;
NSNumber
*
playerId
=
player
.
playerId
;
_players
[
playerId
]
=
player
;
_players
[
playerId
]
=
player
;
FTXLOGI
(
@"createLivePlayer :%@"
,
playerId
);
return
[
TXCommonUtil
playerMsgWith
:
playerId
];
return
[
TXCommonUtil
playerMsgWith
:
playerId
];
}
}
...
@@ -244,6 +247,7 @@ SuperPlayerPlugin* instance;
...
@@ -244,6 +247,7 @@ SuperPlayerPlugin* instance;
player
.
delegate
=
self
;
player
.
delegate
=
self
;
NSNumber
*
playerId
=
player
.
playerId
;
NSNumber
*
playerId
=
player
.
playerId
;
_players
[
playerId
]
=
player
;
_players
[
playerId
]
=
player
;
FTXLOGI
(
@"createVodPlayer :%@"
,
playerId
);
return
[
TXCommonUtil
playerMsgWith
:
playerId
];
return
[
TXCommonUtil
playerMsgWith
:
playerId
];
}
}
...
...
Flutter/ios/Classes/common/FTXPlayerConstants.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/helper/FTXLivePipDelegate.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/helper/FTXVodPlayerDelegate.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/helper/TXPipAuth.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/helper/TXPipAuth.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/ios/Classes/live/pip/FTXBackPlayer.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXBackPlayer.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/ios/Classes/live/pip/FTXPipCaller.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipController.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipController.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/ios/Classes/live/pip/FTXPipFactory.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipFactory.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/ios/Classes/live/pip/FTXPipGlobalImpl.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipGlobalImpl.m
0 → 100644
浏览文件 @
8ea0a67a
// Copyright (c) 2024 Tencent. All rights reserved.
#import "FTXPipGlobalImpl.h"
#import "TXPipAuth.h"
#import <AVKit/AVKit.h>
#import "FTXEvent.h"
#import "FTXLog.h"
#import "FTXPlayerConstants.h"
#import "FTXPipRenderView.h"
#import "FTXBackPlayer.h"
@interface
FTXPipGlobalImpl
()
<
AVPictureInPictureControllerDelegate
>
@property
(
nonatomic
,
strong
)
AVPictureInPictureController
*
pipController
;
@property
(
nonatomic
,
assign
)
TX_VOD_PLAYER_PIP_STATE
pipStatus
;
@property
(
nonatomic
,
assign
)
FTX_LIVE_PIP_ERROR
pipError
;
/// app传入的videoView在父view上的约束,恢复view的时候要重新加上之前的约束
@property
(
nonatomic
,
strong
)
NSMutableArray
*
constraintArray
;
@property
(
nonatomic
,
strong
)
UIView
*
backgroundPlayerView
;
/// 传入的videoView在移动view(coverVideoViewToPIPView)之前的父view
@property
(
nonatomic
,
weak
)
UIView
*
superViewOfVideoView
;
@property
(
nonatomic
,
strong
)
FTXPipRenderView
*
videoView
;
@property
(
nonatomic
,
strong
)
NSArray
*
tempConstraintArray
;
@property
(
nonatomic
,
strong
)
FTXBackPlayer
*
backPlayer
;
@end
static
NSString
*
kPipTag
=
@"FTXPipCaller"
;
@implementation
FTXPipGlobalImpl
@synthesize
pipDelegate
;
@synthesize
playerDelegate
;
-
(
instancetype
)
init
{
self
=
[
super
init
];
if
(
self
)
{
self
.
pipStatus
=
TX_VOD_PLAYER_PIP_STATE_UNDEFINED
;
self
.
pipError
=
FTX_VOD_PLAYER_PIP_ERROR_TYPE_NONE
;
self
.
constraintArray
=
[[
NSMutableArray
alloc
]
init
];
self
.
videoView
=
[
self
createVideoView
];
}
return
self
;
}
-
(
int
)
handleStartPip
:
(
CGSize
)
size
{
if
(
!
[
TXPipAuth
cpa
])
{
FTXLOGE
(
@"%@ pip auth is deined when handle"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_UNDEFINED
];
[
self
onPipError
:
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_AUTH_DENIED
];
return
ERROR_PIP_AUTH_DENIED
;
}
NSBundle
*
mainBundle
=
[
NSBundle
mainBundle
];
NSString
*
resourcePath
=
[
mainBundle
pathForResource
:
@"tx_vod_seamless_pip_backgroud_video"
ofType
:
@"mp4"
inDirectory
:
@"TXVodPlayer.bundle"
];
// 获取文件管理器
NSFileManager
*
fileManager
=
[
NSFileManager
defaultManager
];
// 判断资源是否存在
BOOL
resourceExists
=
[
fileManager
fileExistsAtPath
:
resourcePath
];
if
(
resourceExists
)
{
FTXLOGI
(
@"%@ Resource exists at path: %@"
,
kPipTag
,
resourcePath
);
[
self
prepareWithURL
:[
NSURL
fileURLWithPath
:
resourcePath
]
withSize
:
size
withDurationSec
:
100
isReplace
:
NO
];
}
else
{
FTXLOGE
(
@"%@ Resource does not exist at path: %@"
,
kPipTag
,
resourcePath
);
[
self
onPipError
:
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_MISS_RESOURCE
];
}
return
NO_ERROR
;
}
-
(
void
)
exitPip
{
if
(
!
[
TXPipAuth
cpa
])
{
FTXLOGE
(
@"%@ pip auth is deined when closed"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_UNDEFINED
];
[
self
onPipError
:
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_AUTH_DENIED
];
return
;
}
if
(
nil
!=
self
.
pipController
)
{
[
self
.
pipController
stopPictureInPicture
];
}
if
(
self
.
backgroundPlayerView
&&
self
.
backgroundPlayerView
.
superview
)
{
[
self
.
backgroundPlayerView
removeFromSuperview
];
self
.
backgroundPlayerView
=
nil
;
}
if
(
self
.
backPlayer
)
{
// 停止播放并释放资源
[
self
.
backPlayer
stop
];
}
}
-
(
FTXPipRenderView
*
)
getVideoView
{
return
self
.
videoView
;
}
-
(
TX_VOD_PLAYER_PIP_STATE
)
getStatus
{
return
self
.
pipStatus
;
}
-
(
void
)
pausePipVideo
{
if
(
nil
!=
self
.
backPlayer
)
{
[
self
.
backPlayer
pause
];
}
}
-
(
void
)
resumePipVideo
{
if
(
nil
!=
self
.
backPlayer
)
{
[
self
.
backPlayer
play
];
}
}
-
(
void
)
displayPixelBuffer
:
(
CVPixelBufferRef
)
pixelBuffer
{
[
self
.
videoView
displayPixelBuffer
:
pixelBuffer
];
}
#pragma mark - AVPictureInPictureControllerDelegate
-
(
void
)
pictureInPictureControllerWillStartPictureInPicture
:
(
AVPictureInPictureController
*
)
pictureInPictureController
{
if
([
TXPipAuth
cpa
])
{
FTXLOGI
(
@"%@ pictureInPictureControllerWillStartPictureInPicture"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_WILL_START
];
UIView
*
pipView
=
self
.
pipView
;
FTXPipRenderView
*
videoView
=
self
.
videoView
;
if
(
!
pipView
)
{
FTXLOGE
(
@"[%@] coverVideoViewToPIPView, pipView is nil, videoView is: %p"
,
kPipTag
,
videoView
);
return
;
}
if
(
!
videoView
)
{
FTXLOGE
(
@"[%@] coverVideoViewToPIPView, videoView is nil"
,
kPipTag
);
return
;
}
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// 把当前videoView和父view记下来
self
.
superViewOfVideoView
=
videoView
.
superview
;
// 把当前videoView在其父view上的约束记下来,等恢复的时候重新加上这些约束
[
self
.
constraintArray
removeAllObjects
];
// 移除旧的约束
for
(
NSLayoutConstraint
*
constraint
in
self
.
superViewOfVideoView
.
constraints
)
{
if
(
constraint
.
firstItem
==
self
.
videoView
)
{
[
self
.
constraintArray
addObject
:
constraint
];
}
}
videoView
.
translatesAutoresizingMaskIntoConstraints
=
NO
;
[
videoView
removeFromSuperview
];
[
pipView
addSubview
:
self
.
videoView
];
// 添加约束 使videoview撑满pipview
NSLayoutConstraint
*
contraintTop
=
[
NSLayoutConstraint
constraintWithItem
:
videoView
attribute
:
NSLayoutAttributeTop
relatedBy
:
NSLayoutRelationEqual
toItem
:
pipView
attribute
:
NSLayoutAttributeTop
multiplier
:
1
.
0
constant
:
0
];
NSLayoutConstraint
*
contraintLeft
=
[
NSLayoutConstraint
constraintWithItem
:
videoView
attribute
:
NSLayoutAttributeLeft
relatedBy
:
NSLayoutRelationEqual
toItem
:
pipView
attribute
:
NSLayoutAttributeLeft
multiplier
:
1
.
0
constant
:
0
];
NSLayoutConstraint
*
contraintBottom
=
[
NSLayoutConstraint
constraintWithItem
:
videoView
attribute
:
NSLayoutAttributeBottom
relatedBy
:
NSLayoutRelationEqual
toItem
:
pipView
attribute
:
NSLayoutAttributeBottom
multiplier
:
1
.
0
constant
:
0
];
NSLayoutConstraint
*
contraintRight
=
[
NSLayoutConstraint
constraintWithItem
:
videoView
attribute
:
NSLayoutAttributeRight
relatedBy
:
NSLayoutRelationEqual
toItem
:
pipView
attribute
:
NSLayoutAttributeRight
multiplier
:
1
.
0
constant
:
0
];
// 把约束添加到父视图pipview上
self
.
tempConstraintArray
=
[
NSArray
arrayWithObjects
:
contraintTop
,
contraintLeft
,
contraintBottom
,
contraintRight
,
nil
];
[
NSLayoutConstraint
activateConstraints
:
self
.
tempConstraintArray
];
// activateConstraints 效率更高
[
videoView
setNeedsLayout
];
[
videoView
layoutIfNeeded
];
FTXLOGI
(
@"[%@] coverVideoViewToPIPView finished, videoView's superview is: %p"
,
kPipTag
,
videoView
.
superview
);
});
}
else
{
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_UNDEFINED
];
[
self
onPipError
:
FTX_VOD_PLAYER_PIP_ERROR_TYPE_PIP_AUTH_DENIED
];
FTXLOGE
(
@"%@ pip auth is deined when opened"
,
kPipTag
);
}
}
-
(
void
)
pictureInPictureControllerDidStartPictureInPicture
:
(
AVPictureInPictureController
*
)
pictureInPictureController
{
FTXLOGI
(
@"%@ pictureInPictureControllerDidStartPictureInPicture"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_DID_START
];
}
-
(
void
)
pictureInPictureController
:
(
AVPictureInPictureController
*
)
pictureInPictureController
failedToStartPictureInPictureWithError
:
(
NSError
*
)
error
{
FTXLOGI
(
@"%@ failedToStartPictureInPictureWithError:%@"
,
kPipTag
,
error
);
}
-
(
void
)
pictureInPictureControllerWillStopPictureInPicture
:
(
AVPictureInPictureController
*
)
pictureInPictureController
{
FTXLOGI
(
@"%@ pictureInPictureControllerWillStopPictureInPicture"
,
kPipTag
);
[[
NSNotificationCenter
defaultCenter
]
removeObserver
:
self
];
[
self
.
pipController
removeObserver
:
self
forKeyPath
:
@"pictureInPicturePossible"
];
[
self
.
backPlayer
stop
];
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_WILL_STOP
];
[
self
exitPip
];
}
-
(
void
)
pictureInPictureControllerDidStopPictureInPicture
:
(
AVPictureInPictureController
*
)
pictureInPictureController
{
FTXLOGI
(
@"%@ pictureInPictureControllerDidStopPictureInPicture"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_DID_STOP
];
}
-
(
void
)
pictureInPictureController
:
(
AVPictureInPictureController
*
)
pictureInPictureController
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler
:
(
void
(
^
)(
BOOL
))
completionHandler
{
FTXLOGI
(
@"%@ restoreUserInterfaceForPictureInPictureStopWithCompletionHandler"
,
kPipTag
);
[
self
changeStatus
:
TX_VOD_PLAYER_PIP_STATE_RESTORE_UI
];
}
#pragma mark - private method
-
(
FTXPipRenderView
*
)
createVideoView
{
if
(
!
_videoView
)
{
// Set the size to 1 pixel to ensure proper display in PIP.
_videoView
=
[[
FTXPipRenderView
alloc
]
initWithFrame
:
CGRectMake
(
0
,
0
,
300
,
300
)];
}
return
_videoView
;
}
-
(
void
)
changeStatus
:
(
TX_VOD_PLAYER_PIP_STATE
)
status
{
self
.
pipStatus
=
status
;
FTXLOGE
(
@"%@ pip met status changed %ld"
,
kPipTag
,
status
);
if
(
self
.
pipDelegate
)
{
[
self
.
pipDelegate
pictureInPictureStateDidChange
:
status
];
}
}
-
(
void
)
onPipError
:
(
FTX_LIVE_PIP_ERROR
)
error
{
self
.
pipError
=
error
;
FTXLOGE
(
@"%@ pip met error %ld"
,
kPipTag
,
error
);
if
(
self
.
pipDelegate
)
{
[
self
.
pipDelegate
pictureInPictureErrorDidOccur
:
error
];
}
}
/// 获取系统画中画view
-
(
UIView
*
)
pipView
{
///画中画view是windos列表里最后一个PGHostWindow类型的view。单实例的情况下,就是列表里第一个view,多实例的情况下,要取最后一个PGHostWindow类型的view
UIView
*
pipView
=
[
UIApplication
sharedApplication
].
windows
.
firstObject
;
Class
pgHostWindowClass
=
NSClassFromString
(
@"PGHostedWindow"
);
if
(
!
pgHostWindowClass
)
{
return
pipView
;
}
// 取最后一个PGHostWindow类型的view
for
(
UIView
*
view
in
[
UIApplication
sharedApplication
].
windows
)
{
if
([
view
isKindOfClass
:
pgHostWindowClass
])
{
pipView
=
view
;
}
}
return
pipView
;
}
-
(
void
)
observeValueForKeyPath
:
(
NSString
*
)
keyPath
ofObject
:
(
id
)
object
change
:
(
NSDictionary
*
)
change
context
:
(
void
*
)
context
{
if
(
object
==
self
.
pipController
)
{
if
([
keyPath
isEqualToString
:
@"pictureInPicturePossible"
])
{
if
(
self
.
pipController
.
pictureInPicturePossible
&&
!
self
.
pipController
.
pictureInPictureActive
)
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
if
(
self
.
pipController
.
isPictureInPictureActive
)
{
[
self
.
pipController
stopPictureInPicture
];
}
else
{
[
self
.
pipController
startPictureInPicture
];
}
});
}
}
}
}
/// 1 将输入视频url, resize到指定大小,2 拼接到指定长度,3 用新的视频资源初始化avplayer(3需要在主线程中做,但耗时非常少可以忽略)
/// @param inputURL 输入视频url
/// @param size 指定视频尺寸
/// @param durationSec 指定视频时长
/// @param isReplace 是否是换源 换源的话只换播放资源 不重新创建播放器
-
(
void
)
prepareWithURL
:
(
NSURL
*
)
inputURL
withSize
:
(
CGSize
)
size
withDurationSec
:
(
NSTimeInterval
)
durationSec
isReplace
:
(
BOOL
)
isReplace
{
if
(
!
inputURL
)
{
return
;
}
AVAsset
*
videoAsset
=
[[
AVURLAsset
alloc
]
initWithURL
:
inputURL
options
:
nil
];
AVMutableComposition
*
composition
=
[
AVMutableComposition
composition
];
// 视频类型的的Track,这个方法里只添加视频track,不需要音频
AVMutableCompositionTrack
*
compositionTrack
=
[
composition
addMutableTrackWithMediaType
:
AVMediaTypeVideo
preferredTrackID
:
kCMPersistentTrackID_Invalid
];
// 拼接视频
int
localVideoDurationSec
=
CMTimeGetSeconds
(
videoAsset
.
duration
);
// 本地视频时长
if
(
localVideoDurationSec
==
0
)
{
FTXLOGW
(
@"%@ prepareWithURL failed, local video duration is 0, return"
,
kPipTag
);
return
;
}
CMTimeRange
timeRange
=
CMTimeRangeMake
(
kCMTimeZero
,
videoAsset
.
duration
);
int
counts
=
(
durationSec
/
localVideoDurationSec
);
for
(
int
i
=
0
;
i
<
counts
;
i
++
)
{
[
compositionTrack
insertTimeRange
:
timeRange
ofTrack
:[
videoAsset
tracksWithMediaType
:
AVMediaTypeVideo
][
0
]
atTime
:
kCMTimeZero
error
:
nil
];
}
// 拼最后一段视频
int
lastVideoDurationSec
=
(
int
)
durationSec
%
localVideoDurationSec
;
if
(
lastVideoDurationSec
!=
0
)
{
CMTime
lastVideoTime
=
CMTimeMake
(
lastVideoDurationSec
,
1
);
CMTimeRange
lastVideoTimeRange
=
CMTimeRangeMake
(
kCMTimeZero
,
lastVideoTime
);
[
compositionTrack
insertTimeRange
:
lastVideoTimeRange
ofTrack
:[
videoAsset
tracksWithMediaType
:
AVMediaTypeVideo
][
0
]
atTime
:
kCMTimeZero
error
:
nil
];
}
// resize
AVMutableVideoCompositionLayerInstruction
*
videoCompositionLayerIns
=
[
AVMutableVideoCompositionLayerInstruction
videoCompositionLayerInstructionWithAssetTrack
:
compositionTrack
];
[
videoCompositionLayerIns
setTransform
:
compositionTrack
.
preferredTransform
atTime
:
kCMTimeZero
];
//得到视频素材
AVMutableVideoCompositionInstruction
*
videoCompositionIns
=
[
AVMutableVideoCompositionInstruction
videoCompositionInstruction
];
[
videoCompositionIns
setTimeRange
:
CMTimeRangeMake
(
kCMTimeZero
,
compositionTrack
.
timeRange
.
duration
)];
//得到视频轨道
AVMutableVideoComposition
*
videoComposition
=
[
AVMutableVideoComposition
videoComposition
];
videoComposition
.
instructions
=
@[
videoCompositionIns
];
if
(
size
.
width
<=
0
||
size
.
height
<=
0
)
{
FTXLOGE
(
@"%@ prepareWithURL failed, wrong video size, bgPlayer: %p, return "
,
kPipTag
,
self
.
backPlayer
);
return
;
}
videoComposition
.
renderSize
=
size
;
//指定尺寸
videoComposition
.
frameDuration
=
CMTimeMake
(
2
,
2
);
// 视频裁剪拼接成功后开始prepare
NSArray
*
requestedKeys
=
@[
@"playable"
];
[
composition
loadValuesAsynchronouslyForKeys
:
requestedKeys
completionHandler
:
^
{
dispatch_async
(
dispatch_get_main_queue
(),
^
{
// NOTE:移除旧的PlayerView,防止旧BG页面残留
if
(
self
.
backgroundPlayerView
&&
self
.
backgroundPlayerView
.
superview
)
{
[
self
.
backgroundPlayerView
removeFromSuperview
];
self
.
backgroundPlayerView
=
nil
;
}
self
.
backgroundPlayerView
=
[[
UIView
alloc
]
initWithFrame
:
CGRectZero
];
self
.
backgroundPlayerView
.
backgroundColor
=
[
UIColor
clearColor
];
self
.
backgroundPlayerView
.
autoresizingMask
=
UIViewAutoresizingFlexibleWidth
|
UIViewAutoresizingFlexibleHeight
;
self
.
backgroundPlayerView
.
frame
=
self
.
pipView
.
bounds
;
AVPlayerItem
*
playerItem
=
[[
AVPlayerItem
alloc
]
initWithAsset
:
composition
];
playerItem
.
videoComposition
=
videoComposition
;
if
(
isReplace
&&
self
.
backPlayer
!=
nil
)
{
[
self
.
backPlayer
replaceCurrentItemWithPlayerItem
:
playerItem
];
[
self
.
backPlayer
play
];
}
else
{
self
.
backPlayer
=
[[
FTXBackPlayer
alloc
]
init
];
[
self
.
backPlayer
prepareVideo
:
playerItem
];
[
self
.
backPlayer
setContainerView
:
self
.
backgroundPlayerView
];
[
self
.
backPlayer
getPlayerLayer
].
frame
=
self
.
videoView
.
bounds
;
[
self
.
pipView
addSubview
:
self
.
backgroundPlayerView
];
self
.
backPlayer
.
playerDelegate
=
self
.
playerDelegate
;
[
self
.
backPlayer
setLoopback
:
YES
];
[
self
.
backPlayer
play
];
self
.
pipController
=
[[
AVPictureInPictureController
alloc
]
initWithPlayerLayer
:[
self
.
backPlayer
getPlayerLayer
]];
self
.
pipController
.
delegate
=
self
;
// 使用 KVC,隐藏播放按钮、快进快退按钮
[
self
.
pipController
setValue
:[
NSNumber
numberWithInt
:
1
]
forKey
:
@"controlsStyle"
];
[
self
setRequiresLinearPlayback
:
YES
];
[
self
.
pipController
addObserver
:
self
forKeyPath
:
@"pictureInPicturePossible"
options
:
NSKeyValueObservingOptionNew
context
:
nil
];
}
});
}];
}
-
(
void
)
setRequiresLinearPlayback
:
(
BOOL
)
requiresLinearPlayback
{
if
(
@available
(
iOS
14
.
0
,
macOS
11
.
0
,
*
))
{
//requiresLinearPlayback: NO:画中画小窗会显示快进快退按钮 YES:不会显示快进快退按钮
self
.
pipController
.
requiresLinearPlayback
=
requiresLinearPlayback
;
}
}
-
(
void
)
setPlayerDelegate
:
(
id
<
FTXPipPlayerDelegate
>
)
playerDelegate
{
if
(
self
.
backPlayer
!=
nil
)
{
self
.
backPlayer
.
playerDelegate
=
playerDelegate
;
}
self
->
playerDelegate
=
playerDelegate
;
}
@end
Flutter/ios/Classes/live/pip/FTXPipPlayerDelegate.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipRenderView.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/live/pip/FTXPipRenderView.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/ios/Classes/tools/FTXImgTools.h
0 → 100644
浏览文件 @
8ea0a67a
// 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_
Flutter/ios/Classes/tools/FTXImgTools.m
0 → 100644
浏览文件 @
8ea0a67a
// 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
Flutter/lib/Core/txliveplayer_controller.dart
浏览文件 @
8ea0a67a
...
@@ -281,20 +281,13 @@ class TXLivePlayerController extends ChangeNotifier implements ValueListenable<T
...
@@ -281,20 +281,13 @@ class TXLivePlayerController extends ChangeNotifier implements ValueListenable<T
{
String
?
backIconForAndroid
,
String
?
playIconForAndroid
,
String
?
pauseIconForAndroid
,
String
?
forwardIconForAndroid
})
async
{
{
String
?
backIconForAndroid
,
String
?
playIconForAndroid
,
String
?
pauseIconForAndroid
,
String
?
forwardIconForAndroid
})
async
{
if
(
_isNeedDisposed
)
return
-
1
;
if
(
_isNeedDisposed
)
return
-
1
;
await
_initPlayer
.
future
;
await
_initPlayer
.
future
;
if
(
defaultTargetPlatform
==
TargetPlatform
.
android
)
{
IntMsg
intMsg
=
await
_livePlayerApi
.
enterPictureInPictureMode
(
PipParamsPlayerMsg
()
IntMsg
intMsg
=
await
_livePlayerApi
.
enterPictureInPictureMode
(
PipParamsPlayerMsg
()
..
backIconForAndroid
=
backIconForAndroid
..
backIconForAndroid
=
backIconForAndroid
..
playIconForAndroid
=
playIconForAndroid
..
playIconForAndroid
=
playIconForAndroid
..
pauseIconForAndroid
=
pauseIconForAndroid
..
pauseIconForAndroid
=
pauseIconForAndroid
..
forwardIconForAndroid
=
forwardIconForAndroid
..
forwardIconForAndroid
=
forwardIconForAndroid
..
playerId
=
_playerId
);
..
playerId
=
_playerId
);
return
intMsg
.
value
??
-
1
;
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
;
}
}
}
/// Exit picture-in-picture mode if the player is in picture-in-picture mode.
/// Exit picture-in-picture mode if the player is in picture-in-picture mode.
...
...
Flutter/lib/Core/txplayer_define.dart
浏览文件 @
8ea0a67a
...
@@ -374,6 +374,12 @@ abstract class TXVodPlayEvent {
...
@@ -374,6 +374,12 @@ abstract class TXVodPlayEvent {
// PIP error, PIP function is not started (only support iOS).
// PIP error, PIP function is not started (only support iOS).
// pip 错误,PIP功能没有启动 only support iOS
// pip 错误,PIP功能没有启动 only support iOS
static
const
ERROR_IOS_PIP_NOT_RUNNING
=
-
111
;
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 error, currently unable to enter PIP mode, such as being in full screen mode.
// pip 错误,当前不能进入pip模式,例如正处于全屏模式下
// pip 错误,当前不能进入pip模式,例如正处于全屏模式下
static
const
ERROR_PIP_CAN_NOT_ENTER
=
-
120
;
static
const
ERROR_PIP_CAN_NOT_ENTER
=
-
120
;
...
...
FlutterWidget/superplayer_widget/lib/superplayer_controller.dart
浏览文件 @
8ea0a67a
...
@@ -822,24 +822,11 @@ class SuperPlayerController {
...
@@ -822,24 +822,11 @@ class SuperPlayerController {
Future
<
int
>
enterPictureInPictureMode
(
Future
<
int
>
enterPictureInPictureMode
(
{
String
?
backIcon
,
String
?
playIcon
,
String
?
pauseIcon
,
String
?
forwardIcon
})
async
{
{
String
?
backIcon
,
String
?
playIcon
,
String
?
pauseIcon
,
String
?
forwardIcon
})
async
{
if
(
_playerUIStatus
==
SuperPlayerUIStatus
.
WINDOW_MODE
)
{
if
(
_playerUIStatus
==
SuperPlayerUIStatus
.
WINDOW_MODE
)
{
if
(
playerType
==
SuperPlayerType
.
VOD
)
{
return
TXPipController
.
instance
.
enterPip
(
getCurrentController
(),
_context
,
return
TXPipController
.
instance
.
enterPip
(
_vodPlayerController
,
_context
,
backIconForAndroid:
backIcon
,
backIconForAndroid:
backIcon
,
playIconForAndroid:
playIcon
,
playIconForAndroid:
playIcon
,
pauseIconForAndroid:
pauseIcon
,
pauseIconForAndroid:
pauseIcon
,
forwardIconForAndroid:
forwardIcon
);
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
TXVodPlayEvent
.
ERROR_PIP_CAN_NOT_ENTER
;
return
TXVodPlayEvent
.
ERROR_PIP_CAN_NOT_ENTER
;
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论