提交 efb9316d authored 作者: kongdywang's avatar kongdywang

SuperPlayer support live for Android&IOS platform

上级 2a99865e
......@@ -47,6 +47,8 @@
### `pubspec.yaml`配置
**推荐flutter sdk 版本 3.0.0 及以上**
集成LiteAVSDK_Player版本,默认情况下也是集成此版本。在`pubspec.yaml`中增加配置
```yaml
......
......@@ -205,40 +205,45 @@ public class FTXPIPManager {
return;
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
List<RemoteAction> actions = new ArrayList<>();
// play back
Bundle backData = new Bundle();
backData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_BACK);
backData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
Intent backIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(backData);
PendingIntent preIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_BACK, backIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction preAction = new RemoteAction(getBackIcon(params), "skipPre", "skip pre", preIntent);
if(params.mIsNeedPlayBack) {
Bundle backData = new Bundle();
backData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_BACK);
backData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
Intent backIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(backData);
PendingIntent preIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_BACK, backIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction preAction = new RemoteAction(getBackIcon(params), "skipPre", "skip pre", preIntent);
actions.add(preAction);
}
// resume or pause
Bundle playOrPauseData = new Bundle();
playOrPauseData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
playOrPauseData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_RESUME_OR_PAUSE);
Intent playOrPauseIntent =
new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(playOrPauseData);
Icon playIcon = isPlaying ? getPauseIcon(params) : getPlayIcon(params);
PendingIntent playIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_RESUME_OR_PAUSE,
playOrPauseIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction playOrPauseAction = new RemoteAction(playIcon, "playOrPause", "play Or Pause", playIntent);
if(params.mIsNeedPlayControl) {
Bundle playOrPauseData = new Bundle();
playOrPauseData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
playOrPauseData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_RESUME_OR_PAUSE);
Intent playOrPauseIntent =
new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(playOrPauseData);
Icon playIcon = isPlaying ? getPauseIcon(params) : getPlayIcon(params);
PendingIntent playIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_RESUME_OR_PAUSE,
playOrPauseIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction playOrPauseAction = new RemoteAction(playIcon, "playOrPause", "play Or Pause", playIntent);
actions.add(playOrPauseAction);
}
// forward
Bundle forwardData = new Bundle();
forwardData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_FORWARD);
forwardData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
Intent forwardIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(forwardData);
PendingIntent nextIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_FORWARD,
forwardIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction nextAction = new RemoteAction(getForwardIcon(params), "skipNext", "skip next", nextIntent);
List<RemoteAction> actions = new ArrayList<>();
actions.add(preAction);
actions.add(playOrPauseAction);
actions.add(nextAction);
if(params.mIsNeedPlayForward) {
Bundle forwardData = new Bundle();
forwardData.putInt(FTXEvent.EXTRA_NAME_PLAY_OP, FTXEvent.EXTRA_PIP_PLAY_FORWARD);
forwardData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, params.mCurrentPlayerId);
Intent forwardIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(forwardData);
PendingIntent nextIntent = PendingIntent.getBroadcast(mActivity, FTXEvent.EXTRA_PIP_PLAY_FORWARD,
forwardIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
RemoteAction nextAction = new RemoteAction(getForwardIcon(params), "skipNext", "skip next", nextIntent);
actions.add(nextAction);
}
params.mPipParams.setActions(actions);
mActivity.setPictureInPictureParams(params.mPipParams.build());
......@@ -285,6 +290,9 @@ public class FTXPIPManager {
String mPlayForwardAssetPath;
int mCurrentPlayerId;
protected PictureInPictureParams.Builder mPipParams;
private boolean mIsNeedPlayBack = true;
private boolean mIsNeedPlayForward = true;
private boolean mIsNeedPlayControl = true;
/**
* @param mPlayBackAssetPath 回退按钮图片资源路径,传空则使用系统默认图标
......@@ -295,11 +303,21 @@ public class FTXPIPManager {
*/
public PipParams(String mPlayBackAssetPath, String mPlayResumeAssetPath, String mPlayPauseAssetPath,
String mPlayForwardAssetPath, int mCurrentPlayerId) {
this(mPlayBackAssetPath, mPlayResumeAssetPath, mPlayPauseAssetPath, mPlayForwardAssetPath,
mCurrentPlayerId, true, true, true);
}
public PipParams(String mPlayBackAssetPath, String mPlayResumeAssetPath, String mPlayPauseAssetPath,
String mPlayForwardAssetPath, int mCurrentPlayerId, boolean isNeedPlayBack,
boolean isNeedPlayForward, boolean isNeedPlayControl) {
this.mPlayBackAssetPath = mPlayBackAssetPath;
this.mPlayResumeAssetPath = mPlayResumeAssetPath;
this.mPlayPauseAssetPath = mPlayPauseAssetPath;
this.mPlayForwardAssetPath = mPlayForwardAssetPath;
this.mCurrentPlayerId = mCurrentPlayerId;
this.mIsNeedPlayBack = isNeedPlayBack;
this.mIsNeedPlayForward = isNeedPlayForward;
this.mIsNeedPlayControl = isNeedPlayControl;
}
}
......
......@@ -3,7 +3,7 @@ package com.tencent.vod.flutter;
import android.text.TextUtils;
import com.tencent.rtmp.TXPlayerGlobalSetting;
import com.tencent.rtmp.TXLivePlayConfig;
import com.tencent.rtmp.TXVodPlayConfig;
import java.util.HashMap;
......@@ -15,75 +15,61 @@ import java.util.Map;
public class FTXTransformation {
@SuppressWarnings("unchecked")
public static TXVodPlayConfig transformToConfig(Map<Object, Object> config) {
public static TXVodPlayConfig transformToVodConfig(Map<Object, Object> config) {
TXVodPlayConfig playConfig = new TXVodPlayConfig();
Integer connectRetryCount = (Integer) config.get("connectRetryCount");
if (intIsNotEmpty(connectRetryCount)) {
playConfig.setConnectRetryCount(connectRetryCount);
}
Integer connectRetryInterval = (Integer) config.get("connectRetryInterval");
if (intIsNotEmpty(connectRetryInterval)) {
playConfig.setConnectRetryInterval(connectRetryInterval);
}
Integer timeout = (Integer) config.get("timeout");
if (intIsNotEmpty(timeout)) {
playConfig.setTimeout(timeout);
}
Integer playerType = (Integer) config.get("playerType");
if(null != playerType) {
playConfig.setPlayerType(playerType);
}
Map<String, String> headers = (Map<String, String>) config.get("headers");
if (null == headers) {
headers = new HashMap<>();
}
playConfig.setHeaders(headers);
Boolean enableAccurateSeek = (Boolean) config.get("enableAccurateSeek");
if(null != enableAccurateSeek) {
playConfig.setEnableAccurateSeek(enableAccurateSeek);
}
Boolean autoRotate = (Boolean) config.get("autoRotate");
if(null != autoRotate) {
playConfig.setAutoRotate(autoRotate);
}
Boolean smoothSwitchBitrate = (Boolean) config.get("smoothSwitchBitrate");
if(null != smoothSwitchBitrate) {
playConfig.setSmoothSwitchBitrate(smoothSwitchBitrate);
}
String cacheMp4ExtName = (String) config.get("cacheMp4ExtName");
if (!TextUtils.isEmpty(cacheMp4ExtName)) {
playConfig.setCacheMp4ExtName(cacheMp4ExtName);
}
Integer progressInterval = (Integer) config.get("progressInterval");
if (intIsNotEmpty(progressInterval)) {
playConfig.setProgressInterval(progressInterval);
}
Integer maxBufferSize = (Integer) config.get("maxBufferSize");
if (intIsNotEmpty(maxBufferSize)) {
playConfig.setMaxBufferSize(maxBufferSize);
}
Integer maxPreloadSize = (Integer) config.get("maxPreloadSize");
if (intIsNotEmpty(maxPreloadSize)) {
playConfig.setMaxPreloadSize(maxPreloadSize);
}
Integer firstStartPlayBufferTime = (Integer) config.get("firstStartPlayBufferTime");
if(null != firstStartPlayBufferTime) {
playConfig.setFirstStartPlayBufferTime(firstStartPlayBufferTime);
}
Integer nextStartPlayBufferTime = (Integer) config.get("nextStartPlayBufferTime");
if(null != nextStartPlayBufferTime) {
playConfig.setNextStartPlayBufferTime(nextStartPlayBufferTime);
......@@ -93,23 +79,19 @@ public class FTXTransformation {
if (!TextUtils.isEmpty(overlayKey)) {
playConfig.setOverlayKey(overlayKey);
}
String overlayIv = (String) config.get("overlayIv");
if (!TextUtils.isEmpty(overlayIv)) {
playConfig.setOverlayIv(overlayIv);
}
Map<String, Object> extInfoMap = (Map<String, Object>) config.get("extInfoMap");
if (null == extInfoMap) {
extInfoMap = new HashMap<>();
}
playConfig.setExtInfo(extInfoMap);
Boolean enableRenderProcess = (Boolean) config.get("enableRenderProcess");
if(null != enableRenderProcess) {
playConfig.setEnableRenderProcess(enableRenderProcess);
}
String preferredResolutionStr = (String) config.get("preferredResolution");
if (null != preferredResolutionStr) {
long preferredResolution = Long.parseLong(preferredResolutionStr);
......@@ -119,8 +101,60 @@ public class FTXTransformation {
return playConfig;
}
public static TXLivePlayConfig transformToLiveConfig(Map<Object, Object> config) {
TXLivePlayConfig livePlayConfig = new TXLivePlayConfig();
Double cacheTime = (Double) config.get("cacheTime");
if(doubleIsNotEmpty(cacheTime)) {
livePlayConfig.setCacheTime(cacheTime.floatValue());
}
Double maxAutoAdjustCacheTime = (Double) config.get("maxAutoAdjustCacheTime");
if(doubleIsNotEmpty(maxAutoAdjustCacheTime)) {
livePlayConfig.setMaxAutoAdjustCacheTime(maxAutoAdjustCacheTime.floatValue());
}
Double minAutoAdjustCacheTime = (Double) config.get("minAutoAdjustCacheTime");
if(doubleIsNotEmpty(minAutoAdjustCacheTime)) {
livePlayConfig.setMinAutoAdjustCacheTime(minAutoAdjustCacheTime.floatValue());
}
Integer videoBlockThreshold = (Integer) config.get("videoBlockThreshold");
if(intIsNotEmpty(videoBlockThreshold)) {
livePlayConfig.setVideoBlockThreshold(videoBlockThreshold);
}
Integer connectRetryCount = (Integer) config.get("connectRetryCount");
if(intIsNotEmpty(connectRetryCount)) {
livePlayConfig.setConnectRetryCount(connectRetryCount);
}
Integer connectRetryInterval = (Integer) config.get("connectRetryInterval");
if(intIsNotEmpty(connectRetryInterval)) {
livePlayConfig.setConnectRetryInterval(connectRetryInterval);
}
Boolean autoAdjustCacheTime = (Boolean) config.get("autoAdjustCacheTime");
if(null != autoAdjustCacheTime) {
livePlayConfig.setAutoAdjustCacheTime(autoAdjustCacheTime);
}
Boolean enableAec= (Boolean) config.get("enableAec");
if(null != enableAec) {
livePlayConfig.setEnableAEC(enableAec);
}
Boolean enableMessage = (Boolean) config.get("enableMessage");
if(null != enableMessage) {
livePlayConfig.setEnableMessage(enableMessage);
}
Boolean enableMetaData = (Boolean) config.get("enableMetaData");
if(null != enableMetaData) {
livePlayConfig.setEnableMetaData(enableMetaData);
}
String flvSessionKey = (String) config.get("flvSessionKey");
if(!TextUtils.isEmpty(flvSessionKey)) {
livePlayConfig.setFlvSessionKey(flvSessionKey);
}
return livePlayConfig;
}
private static boolean intIsNotEmpty(Integer value) {
return null != value && value > 0;
}
private static boolean doubleIsNotEmpty(Double value) {
return null != value && value > 0;
}
}
......@@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
......@@ -510,7 +509,7 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
void setPlayConfig(Map<Object, Object> config) {
if (mVodPlayer != null) {
TXVodPlayConfig playConfig = FTXTransformation.transformToConfig(config);
TXVodPlayConfig playConfig = FTXTransformation.transformToVodConfig(config);
mVodPlayer.setConfig(playConfig);
}
}
......
......@@ -133,7 +133,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, MethodCallHandler, Acti
mPlayers.append(playerId, player);
result.success(playerId);
} else if (call.method.equals("createLivePlayer")) {
FTXLivePlayer player = new FTXLivePlayer(mFlutterPluginBinding, mActivityPluginBinding.getActivity());
FTXLivePlayer player = new FTXLivePlayer(mFlutterPluginBinding, mActivityPluginBinding.getActivity(), mTxPipManager);
int playerId = player.getPlayerId();
mPlayers.append(playerId, player);
result.success(playerId);
......@@ -210,6 +210,10 @@ public class SuperPlayerPlugin implements FlutterPlugin, MethodCallHandler, Acti
result.success(mTxPipManager.isSupportDevice());
} else if (call.method.equals("getLiteAVSDKVersion")) {
result.success(TXLiveBase.getSDKVersionStr());
} else if(call.method.equals("setGlobalEnv")) {
String envConfig = call.argument("envConfig");
int setResult = TXLiveBase.setGlobalEnv(envConfig);
result.success(setResult);
} else {
result.notImplemented();
}
......
......@@ -13,26 +13,12 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<!-- <android:uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
</manifest>
......@@ -14,10 +14,20 @@ class DemoSuperplayer extends StatefulWidget {
}
class _DemoSuperplayerState extends State<DemoSuperplayer> {
static const DEFAULT_PLACE_HOLDER = "http://xiaozhibo-10055601.file.myqcloud.com/coverImg.jpg";
List<SuperPlayerModel> videoModels = [];
bool _isFullScreen = false;
late SuperPlayerController _controller;
StreamSubscription? simpleEventSubscription;
int tabSelectPos = 0;
TextStyle _textStyleSelected = new TextStyle(
fontSize: 16, color: Colors.white
);
TextStyle _textStyleUnSelected = new TextStyle(
fontSize: 16, color: Colors.grey
);
@override
void initState() {
......@@ -30,12 +40,14 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
simpleEventSubscription = _controller.onSimplePlayerEventBroadcast.listen((event) {
String evtName = event["event"];
if (evtName == SuperPlayerViewEvent.onStartFullScreenPlay) {
// enter fullscreen
} else if (evtName == SuperPlayerViewEvent.onStopFullScreenPlay) {
// exit fullscreen
} else {
print(evtName);
}
});
initData();
_getLiveListData();
}
@override
......@@ -69,6 +81,34 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
return !_controller.onBackPress();
}
Widget getTabRow() {
return new Container(
height: 40,
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new GestureDetector(
child: new Container(
child: Text(
"直播",
style: tabSelectPos == 0 ? _textStyleSelected : _textStyleUnSelected,
),
),
onTap: _getLiveListData,
),
new GestureDetector(
onTap: _getVodListData,
child: new Container(
child: Text(
"点播",
style: tabSelectPos == 1 ? _textStyleSelected : _textStyleUnSelected,
),
)),
],
),
);
}
Widget getBody() {
return Column(
children: [_getPlayArea(), Expanded(flex: 1, child: _getListArea()), _getAddArea()],
......@@ -85,32 +125,39 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
Widget _getListArea() {
return Container(
margin: EdgeInsets.only(top: 10),
child: ListView.builder(
shrinkWrap: true,
itemCount: videoModels.length,
itemBuilder: (context, i) => _buildVideoItem(videoModels[i]),
),
);
margin: EdgeInsets.only(top: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
getTabRow(),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: videoModels.length,
itemBuilder: (context, i) => _buildVideoItem(videoModels[i]),
))
],
));
}
Widget _buildVideoItem(SuperPlayerModel playModel) {
return Column(
children: [
ListTile(
leading: Image.network(
playModel.coverUrl,
width: 100,
height: 60,
fit: BoxFit.cover,
alignment: Alignment.center,
),
title: new Text(
playModel.title,
style: TextStyle(color: Colors.white),
),
onTap: () => playCurrentModel(playModel),
horizontalTitleGap: 10,),
leading: Image.network(
playModel.coverUrl.isEmpty ? DEFAULT_PLACE_HOLDER : playModel.coverUrl,
width: 100,
height: 60,
fit: BoxFit.cover,
alignment: Alignment.center,
),
title: new Text(
playModel.title,
style: TextStyle(color: Colors.white),
),
onTap: () => playCurrentModel(playModel),
horizontalTitleGap: 10,
),
Divider()
],
);
......@@ -124,6 +171,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
}
void onAddVideoTap(BuildContext context) {
bool isLive = tabSelectPos == 0;
showDialog(
context: context,
builder: (context) {
......@@ -144,7 +192,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
}
playCurrentModel(model);
}, needPisgn: true);
}, needPisgn: !isLive, showFileEdited: !isLive,);
});
}
......@@ -152,7 +200,35 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
_controller.playWithModel(model);
}
void initData() async {
void _getLiveListData() async {
setState(() {
tabSelectPos = 0;
});
List<SuperPlayerModel> models = [];
int playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
SuperPlayerModel model = SuperPlayerModel();
model.title = "测试视频";
model.videoURL = "http://liteavapp.qcloud.com/live/liteavdemoplayerstreamid_demo1080p.flv";
model.coverUrl = "http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/66bc542f387702300661648850/0RyP1rZfkdQA.png";
model.playAction = playAction;
models.add(model);
videoModels.clear();
videoModels.addAll(models);
setState(() {
if (videoModels.isNotEmpty) {
playCurrentModel(videoModels[0]);
} else {
EasyLoading.showError("video list request error");
}
});
}
void _getVodListData() async {
setState(() {
tabSelectPos = 1;
});
List<SuperPlayerModel> models = [];
int playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
......@@ -196,13 +272,12 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
List<Future<void>> requestList = [];
SuperVodDataLoader loader = SuperVodDataLoader();
for (SuperPlayerModel tempModel in models) {
requestList.add(loader.getVideoData(tempModel, (resultModel) {
videoModels.add(resultModel);
}));
requestList.add(loader.getVideoData(tempModel, (_) {}));
}
await Future.wait(requestList);
videoModels.clear();
videoModels.addAll(models);
setState(() {
if (videoModels.isNotEmpty) {
playCurrentModel(videoModels[0]);
......
......@@ -81,15 +81,16 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
await SuperPlayerPlugin.setConsoleEnabled(true);
await _controller.initialize();
await _controller.setConfig(FTXLivePlayConfig());
// 安卓需要设置hls格式才可正常播放
await _controller.play(_url, playType: TXPlayType.LIVE_FLV);
await _controller.startPlay(_url, playType: TXPlayType.LIVE_FLV);
}
@override
void initState() {
super.initState();
init();
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance.addObserver(this);
EasyLoading.show(status: 'loading...');
}
......@@ -207,7 +208,7 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
),
new GestureDetector(
onTap: () async {
_controller.play(_url, playType: TXPlayType.LIVE_FLV);
_controller.startPlay(_url, playType: TXPlayType.LIVE_FLV);
},
child: Container(
color: Colors.transparent,
......@@ -290,7 +291,7 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
playNetEventSubscription?.cancel();
_controller.dispose();
super.dispose();
WidgetsBinding.instance?.removeObserver(this);
WidgetsBinding.instance.removeObserver(this);
EasyLoading.dismiss();
}
......@@ -302,7 +303,7 @@ class _DemoTXLivelayerState extends State<DemoTXLivePlayer> with WidgetsBindingO
_url = url;
_controller.stop();
if (url.isNotEmpty) {
_controller.play(url);
_controller.startPlay(url);
}
}, showFileEdited: false);
});
......
......@@ -18,7 +18,7 @@ class DemoTXVodPlayer extends StatefulWidget {
class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
with WidgetsBindingObserver {
late TXVodPlayerController _controller;
double _aspectRatio = 0;
double _aspectRatio = 16 / 9;
double _currentProgress = 0.0;
bool _isMute = false;
int _volume = 100;
......@@ -92,7 +92,7 @@ class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
void initState() {
super.initState();
init();
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance.addObserver(this);
EasyLoading.show(status: 'loading...');
}
......@@ -386,7 +386,7 @@ class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
playEventSubscription?.cancel();
_controller.dispose();
super.dispose();
WidgetsBinding.instance?.removeObserver(this);
WidgetsBinding.instance.removeObserver(this);
EasyLoading.dismiss();
}
......
......@@ -6,4 +6,5 @@ class ColorResource {
static const COLOR_GRAY = 0xFFBBBBBB;
static const COLOR_TRANS_BLACK = 0xBB000000;
static const COLOR_WHITE = 0xFFFFFFFF;
static const COLOR_TRANS_GRAY = 0x11BBBBBB;
}
\ No newline at end of file
......@@ -34,7 +34,7 @@ class SuperPlayerCode {
static const VOD_REQUEST_FILE_ID_FAIL = 40002;
}
abstract class SuperPlayerViewEvent {
class SuperPlayerViewEvent {
static const onStartFullScreenPlay = "onStartFullScreenPlay"; //进入全屏播放
static const onStopFullScreenPlay = "onStopFullScreenPlay"; //退出全屏播放
static const onSuperPlayerDidStart = "onSuperPlayerDidStart"; //播放开始通知
......@@ -43,4 +43,11 @@ abstract class SuperPlayerViewEvent {
static const onSuperPlayerBackAction = "onSuperPlayerBackAction"; //返回事件
}
/// 播放器插件当前所处的布局状态
class SuperPlayerUIStatus {
static const WINDOW_MODE = 0;
static const FULLSCREEN_MODE = 1;
static const PIP_MODE = 2;
}
......@@ -8,10 +8,10 @@ class _SuperPlayerObserver {
Function onPlayPause;
Function onPlayStop;
Function onPlayLoading;
Function(int current, int duration,double playableDuration) onPlayProgress;
Function(double current, double duration,double playableDuration) onPlayProgress;
Function(double position) onSeek;
Function(bool success, SuperPlayerType playerType, VideoQuality quality) onSwitchStreamStart;
Function(bool success, SuperPlayerType playerType, VideoQuality quality) onSwitchStreamEnd;
Function(bool success, SuperPlayerType playerType, VideoQuality? quality) onSwitchStreamStart;
Function(bool success, SuperPlayerType playerType, VideoQuality? quality) onSwitchStreamEnd;
Function(int code, String msg) onError;
Function(SuperPlayerType playerType) onPlayerTypeChange;
Function(TXLivePlayerController controller, String url) onPlayTimeShiftLive;
......
......@@ -17,15 +17,15 @@ class VideoBottomView extends StatefulWidget {
class _VideoBottomViewState extends State<VideoBottomView> {
static const TAG = "VideoBottomView";
double _currentProgress = 0;
double _playableProgress = 0;
int _currentDuration = 0;
int _videoDuration = 0;
double _currentDuration = 0;
double _videoDuration = 0;
double _bufferedDuration = 0;
bool _showFullScreenBtn = true;
bool _isPlayMode = false;
bool _isShowQuality = false; // only showed on fullscreen mode
bool _isOnDraging = false;
SuperPlayerType _playerType = SuperPlayerType.VOD;
VideoQuality? _currentQuality;
@override
......@@ -34,13 +34,15 @@ class _VideoBottomViewState extends State<VideoBottomView> {
_videoDuration = widget._playerController.videoDuration;
_currentDuration = widget._playerController.currentDuration;
} else if (null != widget._playerController.videoModel) {
_videoDuration = widget._playerController.videoModel!.duration;
_videoDuration = widget._playerController.videoModel!.duration.toDouble();
}
_isPlayMode = (widget._playerController.playerState == SuperPlayerState.PLAYING);
_showFullScreenBtn = !widget._playerController._isFullScreen;
_isShowQuality = widget._playerController._isFullScreen;
bool isFullScreen = widget._playerController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE;
_showFullScreenBtn = !isFullScreen;
_isShowQuality = isFullScreen;
_currentQuality = widget._playerController.currentQuality;
_playerType = widget._playerController.playerType;
_fixProgress();
super.initState();
......@@ -128,8 +130,8 @@ class _VideoBottomViewState extends State<VideoBottomView> {
return Expanded(
child: VideoSlider(
min: 0,
max: 1,
value: _currentProgress,
max: _videoDuration,
value: _currentDuration,
bufferedValue: _playableProgress,
activeColor: Color(ColorResource.COLOR_MAIN_THEME),
inactiveColor: Color(ColorResource.COLOR_GRAY),
......@@ -138,16 +140,18 @@ class _VideoBottomViewState extends State<VideoBottomView> {
progressHeight: 2,
sliderRadius: 4,
sliderOutterRadius: 10,
// 直播禁止时移
canDrag: _playerType == SuperPlayerType.VOD,
onDragUpdate: (value) {
_isOnDraging = true;
},
onDragEnd: (value) {
setState(() {
_isOnDraging = false;
_currentProgress = value;
widget._playerController.seek(_currentProgress * _videoDuration);
_currentDuration = value * _videoDuration;
widget._playerController.seek(_currentDuration);
LogUtils.d(TAG,
"_currentProgress:$_currentProgress,_videoDuration:$_videoDuration,currentDuration:${_currentProgress * _videoDuration}");
"_currentDuration:$_currentDuration,_videoDuration:$_videoDuration");
});
},
),
......@@ -166,7 +170,7 @@ class _VideoBottomViewState extends State<VideoBottomView> {
widget._controller.onTapQuality();
}
void updateDuration(int duration, int videoDuration, double bufferedDration) {
void updateDuration(double duration, double videoDuration, double bufferedDration) {
if (_isOnDraging) {
return;
}
......@@ -183,19 +187,6 @@ class _VideoBottomViewState extends State<VideoBottomView> {
}
void _fixProgress() {
// provent division zero problem
if (_videoDuration == 0) {
_currentProgress = 0;
} else {
_currentProgress = _currentDuration / _videoDuration;
}
if (_currentProgress < 0) {
_currentProgress = 0;
}
if (_currentProgress > 1) {
_currentProgress = 1;
}
if (_bufferedDuration == 0) {
_playableProgress = 0;
} else {
......@@ -218,15 +209,24 @@ class _VideoBottomViewState extends State<VideoBottomView> {
}
}
void updateFullScreen(bool showFullScreen) {
void updatePlayerType(SuperPlayerType type) {
if(_playerType != type) {
setState(() {
_playerType = type;
});
}
}
void updateUIStatus(int status) {
setState(() {
_showFullScreenBtn = !showFullScreen;
_isShowQuality = showFullScreen;
bool isFullScreen = widget._playerController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE;
_showFullScreenBtn = !isFullScreen;
_isShowQuality = isFullScreen;
});
}
String _buildTextString(int time) {
Duration duration = Duration(seconds: time);
String _buildTextString(double time) {
Duration duration = Duration(seconds: time.toInt());
// 返回此持续时间跨越的整秒数。
String inSeconds = (duration.inSeconds % 60).toString().padLeft(2, "0");
// 返回此持续时间跨越的整分钟数。
......
......@@ -43,7 +43,7 @@ class _SuperPlayerCoverViewState extends State<SuperPlayerCoverView> {
}
return Visibility(
visible: _isShowCover,
visible: _isShowCover && hasCover,
child: Positioned.fill(
top: topBottomOffset,
bottom: topBottomOffset,
......@@ -53,7 +53,11 @@ class _SuperPlayerCoverViewState extends State<SuperPlayerCoverView> {
onDoubleTap: _onDoubleTapVideo,
onTap: _onSingleTapVideo,
child: Container(
child: hasCover ? Image.network(coverUrl,fit: BoxFit.cover,) : Container(),
decoration: BoxDecoration(
// 增加一个半透明背景,防止透明封面图的出现
color:Color(ColorResource.COLOR_TRANS_GRAY)
),
child: Image.network(coverUrl,fit: BoxFit.cover),
)
)),
);
......
......@@ -18,6 +18,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
double _currentVolumn = 0;
bool _isShowMoreView = false;
bool _isOpenAccelerate = true;
bool _isVodPlay = false;
String _currentRate = "";
Map<String, double> playRateStr = {"1.0x": 1.0, "1.25x": 1.25, "1.5x": 1.5, "2.0x": 2.0};
StreamSubscription? volumeSubscription;
......@@ -25,6 +26,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
@override
void initState() {
super.initState();
_isVodPlay = widget.controller.getIsVodPlay();
double playerPlayRate = widget.controller.getPlayRate();
for(String rateStr in playRateStr.keys) {
if(playerPlayRate == playRateStr[rateStr]) {
......@@ -132,10 +134,13 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
),
));
}
return Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: playRateChild,
return Visibility(
visible: _isVodPlay,
child: Container(
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: playRateChild,
),
),
);
}
......@@ -243,6 +248,15 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
}
}
void updatePlayerType(SuperPlayerType playerType) {
bool isVodPlay = playerType == SuperPlayerType.VOD;
if(isVodPlay != _isVodPlay) {
setState(() {
_isVodPlay = isVodPlay;
});
}
}
@override
void dispose() {
super.dispose();
......@@ -255,6 +269,7 @@ class _MoreViewController {
DoubleFunction getPlayRate;
Function(bool value) siwtchAccelerate;
Function(double playRate) onChangedPlayRate;
BoolFunction getIsVodPlay;
_MoreViewController(this.getAccelerateIsOpen, this.getPlayRate, this.siwtchAccelerate, this.onChangedPlayRate);
_MoreViewController(this.getAccelerateIsOpen, this.getPlayRate, this.siwtchAccelerate, this.onChangedPlayRate, this.getIsVodPlay);
}
......@@ -76,9 +76,9 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
}
}
void updateFullScreen(bool showFullScreen) {
void updateUIStatus(int status) {
setState(() {
_isFullScreen = showFullScreen;
_isFullScreen = status == SuperPlayerUIStatus.FULLSCREEN_MODE;
});
}
}
......
......@@ -17,6 +17,7 @@ class VideoSlider extends StatefulWidget {
final Color? bufferedColor;
final Color? sliderColor;
final Color? sliderOutterColor;
bool? canDrag = true;
// calback
final Function? onDragStart;
......@@ -38,12 +39,16 @@ class VideoSlider extends StatefulWidget {
this.sliderOutterColor,
this.onDragStart,
this.onDragUpdate,
this.onDragEnd}) {
this.onDragEnd,
this.canDrag}) {
double range = (max - min);
_checkRange(range, valueName: "max - min");
double currentProgress = value / range;
double? bufferedProgress = bufferedValue != null ? bufferedValue! / range : null;
controller = _VideoSliderController(currentProgress, bufferedProgress: bufferedProgress);
if(range <= 0) {
controller = _VideoSliderController(1, bufferedProgress: 1);
} else {
double currentProgress = value / range;
double? bufferedProgress = bufferedValue != null ? bufferedValue! / range : null;
controller = _VideoSliderController(currentProgress, bufferedProgress: bufferedProgress);
}
}
@override
......@@ -77,24 +82,31 @@ class VideoSliderState extends State<VideoSlider> {
double rightPadding = overlayRadius;
return GestureDetector(
onHorizontalDragStart: (DragStartDetails details) {
isDraging = true;
widget.onDragStart?.call();
if(widget.canDrag!) {
isDraging = true;
widget.onDragStart?.call();
}
},
onHorizontalDragUpdate: (DragUpdateDetails details) {
isDraging = true;
_seekToPosition(details.globalPosition);
widget.onDragUpdate?.call(widget.controller.progress);
if(widget.canDrag!) {
isDraging = true;
_seekToPosition(details.globalPosition);
widget.onDragUpdate?.call(widget.controller.progress);}
},
onHorizontalDragEnd: (DragEndDetails details) {
isDraging = false;
widget.onDragEnd?.call(widget.controller.progress);
if(widget.canDrag!) {
isDraging = false;
widget.onDragEnd?.call(widget.controller.progress);}
},
onHorizontalDragCancel: () {
isDraging = false;
widget.onDragEnd?.call(widget.controller.progress);
if(widget.canDrag!) {
isDraging = false;
widget.onDragEnd?.call(widget.controller.progress);}
},
onTapDown: (TapDownDetails details) {
_seekToPosition(details.globalPosition);
if(widget.canDrag!) {
_seekToPosition(details.globalPosition);
}
},
child: Center(
child: Container(
......
......@@ -7,7 +7,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=1.20.0"
flutter: ">=2.0.0"
dependencies:
flutter:
......
......@@ -2,6 +2,7 @@
#import "FTXLivePlayer.h"
#import "FTXPlayerEventSinkQueue.h"
#import "FTXTransformation.h"
#import <TXLiteAVSDK_Professional/TXLiteAVSDK.h>
#import <Flutter/Flutter.h>
#import <stdatomic.h>
......@@ -25,7 +26,7 @@ static const int uninitialized = -1;
// 旧的一帧
CVPixelBufferRef _lastBuffer;
int64_t _textureId;
id<FlutterPluginRegistrar> _registrar;
id<FlutterTextureRegistry> _textureRegistry;
}
......@@ -39,20 +40,20 @@ static const int uninitialized = -1;
_textureId = -1;
_eventSink = [FTXPlayerEventSinkQueue new];
_netStatusSink = [FTXPlayerEventSinkQueue new];
__weak typeof(self) weakSelf = self;
_methodChannel = [FlutterMethodChannel methodChannelWithName:[@"cloud.tencent.com/txliveplayer/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]];
[_methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
[weakSelf handleMethodCall:call result:result];
}];
_eventChannel = [FlutterEventChannel eventChannelWithName:[@"cloud.tencent.com/txliveplayer/event/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]];
[_eventChannel setStreamHandler:self];
_netStatusChannel = [FlutterEventChannel eventChannelWithName:[@"cloud.tencent.com/txliveplayer/net/" stringByAppendingString:[self.playerId stringValue]] binaryMessenger:[registrar messenger]];
[_netStatusChannel setStreamHandler:self];
}
return self;
}
......@@ -61,7 +62,7 @@ static const int uninitialized = -1;
[self stopPlay];
[_txLivePlayer removeVideoWidget];
_txLivePlayer = nil;
if (_textureId >= 0) {
[_textureRegistry unregisterTexture:_textureId];
_textureId = -1;
......@@ -81,7 +82,7 @@ static const int uninitialized = -1;
CVPixelBufferRelease(_lastBuffer);
_lastBuffer = nil;
}
[_methodChannel setMethodCallHandler:nil];
_methodChannel = nil;
......@@ -101,7 +102,7 @@ static const int uninitialized = -1;
int64_t tId = [_textureRegistry registerTexture:self];
_textureId = tId;
}
if (_txLivePlayer != nil) {
TXLivePlayConfig *config = [TXLivePlayConfig new];
[config setPlayerPixelFormatType:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange];
......@@ -137,11 +138,12 @@ static const int uninitialized = -1;
}
}
- (void)switchStream:(NSString *)url
- (int)switchStream:(NSString *)url
{
if (_txLivePlayer != nil) {
[_txLivePlayer switchStream:url];
return [_txLivePlayer switchStream:url];
}
return -1;
}
- (int)seek:(float)progress
......@@ -207,7 +209,7 @@ static const int uninitialized = -1;
- (void)setLiveMode:(int)type
{
TXLivePlayConfig *config = _txLivePlayer.config;
if (type == 0) {
//自动模式
config.bAutoAdjustCacheTime = YES;
......@@ -238,7 +240,7 @@ static const int uninitialized = -1;
if (_txLivePlayer != nil) {
return [_txLivePlayer prepareLiveSeek:domain bizId:bizId];
}
return uninitialized;
}
......@@ -246,7 +248,7 @@ static const int uninitialized = -1;
if (_txLivePlayer != nil) {
return [_txLivePlayer resumeLive];
}
return uninitialized;
}
......@@ -262,12 +264,26 @@ static const int uninitialized = -1;
}
}
- (BOOL)enableHardwareDecode:(BOOL)enable {
if (_txLivePlayer != nil) {
_txLivePlayer.enableHWAcceleration = enable;
}
return false;
}
- (void)setPlayConfig:(NSDictionary *)args
{
if (_txLivePlayer != nil && [args[@"config"] isKindOfClass:[NSDictionary class]]) {
_txLivePlayer.config = [FTXTransformation transformToLiveConfig:args];
}
}
#pragma mark -
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result
{
NSDictionary *args = call.arguments;
if([@"init" isEqualToString:call.method]){
BOOL onlyAudio = [args[@"onlyAudio"] boolValue];
NSNumber* textureId = [self createPlayer:onlyAudio];
......@@ -309,12 +325,15 @@ static const int uninitialized = -1;
result(nil);
}else if([@"destory" isEqualToString:call.method]) {
[self destory];
result(nil);
}else if([@"setRenderRotation" isEqualToString:call.method]) {
int rotation = [args[@"rotation"] intValue];
[self setRenderRotation:rotation];
result(nil);
}else if([@"switchStream" isEqualToString:call.method]) {
NSString *url = args[@"url"];
[self switchStream:url];
int switchResult = [self switchStream:url];
result(@(switchResult));
}else if ([@"seek" isEqualToString:call.method]) {
result(FlutterMethodNotImplemented);
}else if ([@"setAppID" isEqualToString:call.method]) {
......@@ -327,6 +346,13 @@ static const int uninitialized = -1;
}else if([@"resumeLive" isEqualToString:call.method]) {
int r = [self resumeLive];
result(@(r));
}else if([@"enableHardwareDecode" isEqualToString:call.method]) {
BOOL enable = [args[@"enable"] boolValue];
int r = [self enableHardwareDecode:enable];
result(@(r));
}else if([@"setConfig" isEqualToString:call.method]){
[self setPlayConfig:args];
result(nil);
}else {
result(FlutterMethodNotImplemented);
}
......
......@@ -6,6 +6,8 @@ static NSString* cacheFolder = nil;
static int maxCacheItems = -1;
@interface FTXTransformation : NSObject
+ (TXVodPlayConfig *)transformToConfig:(NSDictionary*)map;
+ (TXVodPlayConfig *)transformToVodConfig:(NSDictionary*)map;
+ (TXLivePlayConfig *)transformToLiveConfig:(NSDictionary*)map;
@end
......@@ -4,7 +4,7 @@
@implementation FTXTransformation
+ (TXVodPlayConfig *)transformToConfig:(NSDictionary *)args
+ (TXVodPlayConfig *)transformToVodConfig:(NSDictionary *)args
{
TXVodPlayConfig *playConfig = [[TXVodPlayConfig alloc] init];
playConfig.connectRetryCount = [args[@"config"][@"connectRetryCount"] intValue];
......@@ -48,6 +48,25 @@
return playConfig;
}
+ (TXLivePlayConfig *)transformToLiveConfig:(NSDictionary *)args
{
TXLivePlayConfig *playConfig = [[TXLivePlayConfig alloc] init];
playConfig.cacheTime = [args[@"config"][@"cacheTime"] floatValue];
playConfig.maxAutoAdjustCacheTime = [args[@"config"][@"maxAutoAdjustCacheTime"] floatValue];
playConfig.minAutoAdjustCacheTime = [args[@"config"][@"minAutoAdjustCacheTime"] floatValue];
playConfig.videoBlockThreshold = [args[@"config"][@"videoBlockThreshold"] intValue];
playConfig.connectRetryCount = [args[@"config"][@"connectRetryCount"] intValue];
playConfig.connectRetryInterval = [args[@"config"][@"connectRetryInterval"] intValue];
playConfig.bAutoAdjustCacheTime = [args[@"config"][@"autoAdjustCacheTime"] boolValue];
playConfig.enableAEC = [args[@"config"][@"enableAec"] boolValue];
playConfig.enableMessage = [args[@"config"][@"enableMessage"] intValue];
playConfig.enableMetaData = [args[@"config"][@"enableMetaData"] intValue];
playConfig.flvSessionKey = [args[@"config"][@"flvSessionKey"] stringValue];
return playConfig;
}
@end
......@@ -583,7 +583,7 @@ BOOL volatile isStop = false;
- (void)setPlayConfig:(NSDictionary *)args
{
if (_txVodPlayer != nil && [args[@"config"] isKindOfClass:[NSDictionary class]]) {
_txVodPlayer.config = [FTXTransformation transformToConfig:args];
_txVodPlayer.config = [FTXTransformation transformToVodConfig:args];
}
}
......
......@@ -187,6 +187,10 @@ SuperPlayerPlugin* instance;
} else if ([@"isDeviceSupportPip" isEqualToString:call.method]) {
BOOL isSupport = [TXVodPlayer isSupportPictureInPicture];
result([NSNumber numberWithBool:isSupport]);
} else if([@"setGlobalEnv" isEqualToString:call.method]) {
NSString *envConfig = call.arguments[@"envConfig"];
int setResult = [TXLiveBase setGlobalEnv:[envConfig UTF8String]];
result(@(setResult));
} else {
result(FlutterMethodNotImplemented);
}
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of SuperPlayer;
abstract class TXModel {
}
class TXPlayerHolder extends TXModel {
TXPlayerController controller;
TXPlayerHolder(this.controller);
void updateController(TXPlayerController playerController) {
controller = playerController;
}
}
......@@ -19,6 +19,7 @@ class SuperPlayerPlugin {
/// 原生交互,通用事件监听,来自插件的事件,例如 声音变化等事件
Stream<Map<dynamic, dynamic>> get onEventBroadcast => _eventStreamController.stream;
/// 原生交互,通用事件监听,来自原生容器的事件,例如 PIP事件、activity/controller 生命周期变化
Stream<Map<dynamic, dynamic>> get onExtraEventBroadcast => _eventPipStreamController.stream;
......@@ -153,4 +154,16 @@ class SuperPlayerPlugin {
static Future<String?> getLiteAVSDKVersion() async {
return await _channel.invokeMethod('getLiteAVSDKVersion');
}
///
/// 设置 liteav SDK 接入的环境。
/// 腾讯云在全球各地区部署的环境,按照各地区政策法规要求,需要接入不同地区接入点。
///
/// @param envConfig 需要接入的环境,SDK 默认接入的环境是:默认正式环境。
/// @return 0:成功;其他:错误
/// @note 目标市场为中国大陆的客户请不要调用此接口,如果目标市场为海外用户,请通过技术支持联系我们,了解 env_config 的配置方法,以确保 App 遵守 GDPR 标准。
///
static Future<int> setGlobalEnv(String envConfig) async {
return await _channel.invokeMethod("setGlobalEnv", {"envConfig": envConfig});
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of SuperPlayer;
/// TXLivePlayer config
class FTXLivePlayConfig {
// 播放器缓存时间,单位秒,取值需要大于0,默认值:5
double cacheTime = 5.0;
// 播放器缓存自动调整的最大时间,单位秒,取值需要大于0,默认值:5
double maxAutoAdjustCacheTime = 5.0;
// 播放器缓存自动调整的最小时间,单位秒,取值需要大于0,默认值为1
double minAutoAdjustCacheTime = 1.0;
// 播放器视频卡顿报警阈值,单位毫秒,只有渲染间隔超过这个阈值的卡顿才会有 PLAY_WARNING_VIDEO_PLAY_LAG 通知
int videoBlockThreshold = 800;
// 播放器遭遇网络连接断开时 SDK 默认重试的次数,取值范围1 - 10,默认值:3。
int connectRetryCount = 3;
// 网络重连的时间间隔,单位秒,取值范围3 - 30,默认值:3。
int connectRetryInterval = 3;
// 是否自动调整播放器缓存时间,默认值:true
// true:启用自动调整,自动调整的最大值和最小值可以分别通过修改 maxCacheTime 和 minCacheTime 来设置
// false:关闭自动调整,采用默认的指定缓存时间(1s),可以通过修改 cacheTime 来调整缓存时间
bool autoAdjustCacheTime = true;
// 是否开启回声消除, 默认值为 false
bool enableAec = false;
// 是否开启消息通道, 默认值为 true
bool enableMessage = true;
// 是否开启 MetaData 数据回调,默认值为 NO。
// true:SDK 通过 EVT_PLAY_GET_METADATA 消息抛出视频流的 MetaData 数据;
// false:SDK 不抛出视频流的 MetaData 数据。
// 标准直播流都会在最开始的阶段有一个 MetaData 数据头,该数据头支持定制。
// 您可以通过 TXLivePushConfig 中的 metaData 属性设置一些自定义数据,再通过 TXLivePlayListener 中的
// onPlayEvent(EVT_PLAY_GET_METADATA) 消息接收到这些数据。
//【特别说明】每条音视频流中只能设置一个 MetaData 数据头,除非断网重连,否则 TXLivePlayer 的
// EVT_PLAY_GET_METADATA 消息也只会收到一次。
bool enableMetaData = false;
// 是否开启 HTTP 头信息回调,默认值为 “”
// HTTP
// 响应头中除了“content-length”、“content-type”等标准字段,不同云服务商还可能会添加一些非标准字段。
// 比如腾讯云会在直播 CDN 的 HTTP-FLV 格式的直播流中增加 “X-Tlive-SpanId”
// 响应头,并在其中设置一个随机字符串,用来唯一标识一次直播。
//
// 如果您在使用腾讯云的直播 CDN,可以设置 flvSessionKey 为 “X-Tlive-SpanId”,SDK 会在 HTTP
// 响应头里解析这个字段, 并通过 TXLivePlayListener 中的 onPlayEvent(EVT_PLAY_GET_FLVSESSIONKEY)
// 事件通知给您的 App。
//
//【特别说明】每条音视频流中只能解析一个 flvSessionKey,除非断网重连,否则
// EVT_PLAY_GET_FLVSESSIONKEY 只会抛送一次。
String flvSessionKey = "";
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
json["cacheTime"] = cacheTime;
json["maxAutoAdjustCacheTime"] = maxAutoAdjustCacheTime;
json["minAutoAdjustCacheTime"] = minAutoAdjustCacheTime;
json["videoBlockThreshold"] = videoBlockThreshold;
json["connectRetryCount"] = connectRetryCount;
json["connectRetryInterval"] = connectRetryInterval;
json["autoAdjustCacheTime"] = autoAdjustCacheTime;
json["enableAec"] = enableAec;
json["enableMessage"] = enableMessage;
json["enableMetaData"] = enableMetaData;
json["flvSessionKey"] = flvSessionKey;
return json;
}
}
\ No newline at end of file
......@@ -9,4 +9,18 @@ abstract class TXPlayerController {
double? get videoTop;
double? get videoRight;
double? get videoBottom;
Future<bool> startPlay(String url);
Future<void> initialize({bool? onlyAudio});
Future<void> setAutoPlay({bool? isAutoPlay});
Future<bool> stop({bool isNeedClear = false});
Future<bool> isPlaying();
Future<void> pause();
Future<void> resume();
Future<void> setMute(bool mute);
Future<void> seek(double progress);
Future<void> setRate(double rate);
Future<bool> enableHardwareDecode(bool enable);
Future<int> enterPictureInPictureMode(
{String? backIconForAndroid, String? playIconForAndroid, String? pauseIconForAndroid, String? forwardIconForAndroid});
}
\ No newline at end of file
......@@ -116,7 +116,7 @@ abstract class TXVodPlayEvent {
static const ERROR_IOS_PIP_PLAYER_NOT_EXIST = -109; // pip 错误,播放器对象不存在 only support iOS
static const ERROR_IOS_PIP_IS_RUNNING = -110; // pip 错误,PIP功能已经运行 only support iOS
static const ERROR_IOS_PIP_NOT_RUNNING = -111; // pip 错误,PIP功能没有启动 only support iOS
static const ERROR_PIP_CAN_NOT_ENTER = -120; // pip 错误,当前不能进入pip模式,例如正处于全屏模式下
/// 视频下载相关事件
static const EVENT_PREDOWNLOAD_ON_COMPLETE = 200; // 视频预下载完成
......
......@@ -3,10 +3,9 @@ part of SuperPlayer;
class TXPlayerVideo extends StatefulWidget {
final TXPlayerController controller;
final Stream<TXPlayerHolder>? playerStream;
TXPlayerVideo({required this.controller, Key? key}) : super(key: key) {
assert(controller != null);
}
TXPlayerVideo({required this.controller, this.playerStream});
@override
TXPlayerVideoState createState() => TXPlayerVideoState();
......@@ -16,52 +15,82 @@ class TXPlayerVideoState extends State<TXPlayerVideo> {
static const TAG = "TXPlayerVideo";
int _textureId = -1;
StreamSubscription? streamSubscription;
late TXPlayerController controller;
@override
void initState() {
super.initState();
controller = widget.controller;
_checkStreamListen();
_resetControllerLink();
}
void _checkStreamListen() {
if(null != streamSubscription) {
streamSubscription!.cancel();
}
streamSubscription = widget.playerStream?.listen((event) {
controller = event.controller;
_resetControllerLink();
});
}
widget.controller.textureId.then((newTextureId) {
if (_textureId != newTextureId) {
void _resetControllerLink() async {
int remainTextureId = await controller.textureId;
if (remainTextureId >= 0) {
if (remainTextureId != _textureId) {
setState(() {
LogUtils.d(TAG, "_textureId = $newTextureId");
_textureId = newTextureId;
LogUtils.d(TAG, "_textureId = $remainTextureId");
_textureId = remainTextureId;
});
}
});
} else {
setState(() {
_textureId = -1;
});
controller.textureId.then((newTextureId) {
if (_textureId != newTextureId) {
setState(() {
LogUtils.d(TAG, "_textureId = $newTextureId");
_textureId = newTextureId;
});
}
});
}
}
@override
Widget build(BuildContext context) {
if ((defaultTargetPlatform == TargetPlatform.android) &&
(widget.controller.resizeVideoHeight! > 0 && widget.controller.resizeVideoWidth! > 0)) {
(controller.resizeVideoHeight! > 0 && controller.resizeVideoWidth! > 0)) {
return _textureId == -1
? Container()
: LayoutBuilder(builder: (context, constrains) {
var viewWidth = constrains.maxWidth;
var viewHeight = constrains.maxHeight;
var videoWidth = widget.controller.resizeVideoWidth!;
var videoHeight = widget.controller.resizeVideoHeight!;
var viewWidth = constrains.maxWidth;
var viewHeight = constrains.maxHeight;
var videoWidth = controller.resizeVideoWidth!;
var videoHeight = widget.controller.resizeVideoHeight!;
double left = widget.controller.videoLeft! * viewWidth / videoWidth;
double top = widget.controller.videoTop! * viewHeight / videoHeight;
double right = widget.controller.videoRight! * viewWidth / videoWidth;
double bottom = widget.controller.videoBottom! * viewHeight / videoHeight;
return Stack(
children: [
Positioned(
top: top,
left: left,
right: right,
bottom: bottom,
child: Texture(textureId: _textureId)
)
],
);
});
double left = controller.videoLeft! * viewWidth / videoWidth;
double top = controller.videoTop! * viewHeight / videoHeight;
double right = controller.videoRight! * viewWidth / videoWidth;
double bottom = controller.videoBottom! * viewHeight / videoHeight;
return Stack(
children: [
Positioned(
top: top, left: left, right: right, bottom: bottom, child: Texture(textureId: _textureId))
],
);
});
} else {
return _textureId == -1
? Container()
: Texture(textureId: _textureId);
return _textureId == -1 ? Container() : Texture(textureId: _textureId);
}
}
@override
void dispose() {
streamSubscription?.cancel();
super.dispose();
}
}
......@@ -17,9 +17,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
StreamSubscription? _netSubscription;
final StreamController<TXPlayerState?> _stateStreamController = StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _eventStreamController = StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _netStatusStreamController = StreamController.broadcast();
/// 播放状态监听,@see TXPlayerState
......@@ -137,6 +135,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// 通过url开始播放视频
/// @param url : 视频播放地址
/// return 是否播放成功
@override
Future<bool> startPlay(String url) async {
await _initPlayer.future;
await _createTexture.future;
......@@ -165,6 +164,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// 播放器初始化,创建共享纹理、初始化播放器
/// @param onlyAudio 是否是纯音频模式
@override
Future<void> initialize({bool? onlyAudio}) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -176,6 +176,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 设置是否自动播放
@override
Future<void> setAutoPlay({bool? isAutoPlay}) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -184,7 +185,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// 停止播放
/// return 是否停止成功
Future<bool> stop({bool isNeedClear = true}) async {
@override
Future<bool> stop({bool isNeedClear = false}) async {
if (_isNeedDisposed) return false;
await _initPlayer.future;
final result = await _channel.invokeMethod("stop", {"isNeedClear": isNeedClear});
......@@ -193,12 +195,14 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 视频是否处于正在播放中
Future<bool?> isPlaying() async {
@override
Future<bool> isPlaying() async {
await _initPlayer.future;
return await _channel.invokeMethod("isPlaying");
}
/// 视频暂停,必须在播放器开始播放的时候调用
@override
Future<void> pause() async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -207,6 +211,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 继续播放,在暂停的时候调用
@override
Future<void> resume() async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -214,6 +219,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 设置是否静音
@override
Future<void> setMute(bool mute) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -229,6 +235,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// 将视频播放进度定位到指定的进度进行播放
/// progress 要定位的视频时间,单位 秒
@override
Future<void> seek(double progress) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -236,6 +243,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 设置播放速率,默认速率 1
@override
Future<void> setRate(double rate) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -294,7 +302,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 设置播放器配置
/// config @see FTXVodPlayConfig
/// config @see [FTXVodPlayConfig]
Future<void> setConfig(FTXVodPlayConfig config) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
......@@ -351,6 +359,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
/// 开启/关闭硬件编码
@override
Future<bool> enableHardwareDecode(bool enable) async {
if (_isNeedDisposed) return false;
await _initPlayer.future;
......@@ -363,12 +372,26 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// </h1>
/// @param backIcon playIcon pauseIcon forwardIcon 为播放后退、播放、暂停、前进的图标,如果赋值的话,将会使用传递的图标,否则
/// 使用系统默认图标,只支持flutter本地资源图片,传递的时候,与flutter使用图片资源一致,例如: images/back_icon.png
@override
Future<int> enterPictureInPictureMode(
{String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
{String? backIconForAndroid,
String? playIconForAndroid,
String? pauseIconForAndroid,
String? forwardIconForAndroid}) async {
if (_isNeedDisposed) return -1;
await _initPlayer.future;
return await _channel.invokeMethod("enterPictureInPictureMode",
{"backIcon": backIcon, "playIcon": playIcon, "pauseIcon": pauseIcon, "forwardIcon": forwardIcon});
if (Platform.isAndroid) {
return await _channel.invokeMethod("enterPictureInPictureMode", {
"backIcon": backIconForAndroid,
"playIcon": playIconForAndroid,
"pauseIcon": pauseIconForAndroid,
"forwardIcon": forwardIconForAndroid
});
} else if (Platform.isIOS) {
return await _channel.invokeMethod("enterPictureInPictureMode");
} else {
return -1;
}
}
/// 获取总时长
......@@ -378,13 +401,15 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
return await _channel.invokeMethod("getDuration");
}
/// 废弃controller
/// 释放controller
@override
void dispose() async {
_isNeedDisposed = true;
if (!_isDisposed) {
await _eventSubscription!.cancel();
_eventSubscription = null;
await _netSubscription!.cancel();
_netSubscription = null;
await _release();
_changeState(TXPlayerState.disposed);
......@@ -398,8 +423,6 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
}
@override
// TODO: implement value
get value => _value;
set value(TXPlayerValue? val) {
......
......@@ -3,6 +3,7 @@ library SuperPlayer;
import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'dart:math';
import 'package:flutter/cupertino.dart';
......@@ -19,4 +20,6 @@ part 'Core/txplayer_widget.dart';
part 'Core/txvodplayer_config.dart';
part 'Core/txvodplayer_controller.dart';
part 'Core/txvoddownload_controller.dart';
part 'Core/tools/common_utils.dart';
\ No newline at end of file
part 'Core/txliveplayer_config.dart';
part 'Core/tools/common_utils.dart';
part 'Core/provider/txplayer_holder.dart';
\ No newline at end of file
name: super_player
description: player plugin.
version: 1.0.3
version: 1.0.5
author:
homepage:
environment:
sdk: '>=2.12.0 <3.0.0'
flutter: ">=1.20.0"
flutter: ">=2.0.0"
dependencies:
flutter:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论