提交 22901bfd authored 作者: kongdywang's avatar kongdywang

pip optimize

上级 ba8d10e6
rootProject.ext {
compileSdkVersion = 28
compileSdkVersion = 31
buildToolsVersion = "28.0.3"
supportSdkVersion = "26.0.1"
minSdkVersion = 19
......
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tencent.vod.flutterr">
</manifest>
xmlns:tools="http://schemas.android.com/tools"
package="com.tencent.vod.flutterr">
<application>
<activity
android:name="com.tencent.vod.flutter.ui.FlutterPipImplActivity"
android:theme="@style/Theme"
android:supportsPictureInPicture="true"
android:screenOrientation="portrait"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="stateHidden"
tools:ignore="UnusedAttribute" >
<intent-filter>
<action android:name="com.tencent.flutter.startPip"/>
<action android:name="com.tencent.flutter.exitPip"/>
<action android:name="com.tencent.flutter.updatePip"/>
</intent-filter>
</activity>
</application>
</manifest>
\ No newline at end of file
......@@ -35,10 +35,11 @@ public class FTXEvent {
// 视频下载错误
public static final int EVENT_DOWNLOAD_ERROR = 305;
public static final int NO_ERROR = 0;
public static final int NO_ERROR = 0;
/**
* pip 事件
*/
public static final String PIP_CHANNEL_NAME = "cloud.tencent.com/playerPlugin/componentEvent";
// pip广播action
public final static String ACTION_PIP_PLAY_CONTROL = "vodPlayControl";
// pip 操作
......@@ -57,6 +58,40 @@ public class FTXEvent {
public static final int ERROR_PIP_DENIED_PERMISSION = -102;
// pip 错误,当前界面已销毁
public static final int ERROR_PIP_ACTIVITY_DESTROYED = -103;
// 来自画中画容器的事件,广播键值
public static final String EVENT_PIP_ACTION = "com.tencent.flutter.pipevent";
// 来自画中画容器的事件,事件键值
public static final String EVENT_PIP_MODE_NAME = "pipEventName";
// 画中画当前播放时间
public static final String EVENT_PIP_PLAY_TIME = "playTime";
// 来自画中画容器的事件,已经进入画中画
public static final int EVENT_PIP_MODE_ALREADY_ENTER = 1;
// 来自画中画容器的事件,已经退出画中画
public static final int EVENT_PIP_MODE_ALREADY_EXIT = 2;
// 来自画中画容器的事件,开始进入画中画
public static final int EVENT_PIP_MODE_REQUEST_START = 3;
// 来自画中画容器的事件,画中画UI发生变动,> android 31
public static final int EVENT_PIP_MODE_UI_STATE_CHANGED = 4;
// 画中画界面恢复,即点击放大按钮
public static final int EVENT_PIP_MODE_RESTORE_UI = 5;
// 启动画中画
public static final String PIP_ACTION_START = "com.tencent.flutter.startPip";
// 退出画中画
public static final String PIP_ACTION_EXIT = "com.tencent.flutter.exitPip";
// 更新画中画
public static final String PIP_ACTION_UPDATE = "com.tencent.flutter.updatePip";
// 画中画参数
public static final String EXTRA_NAME_PARAMS = "pipParams";
// 视频源
public static final String EXTRA_NAME_VIDEO = "videoModel";
// 画中画结束参数
public static final String EXTRA_NAME_RESULT = "pipResult";
// 点播播放器
public static final int PLAYER_VOD = 1;
// 直播播放器
public static final int PLAYER_LIVE = 2;
// 屏幕旋转事件
......@@ -71,4 +106,5 @@ public class FTXEvent {
public static final int ORIENTATION_PORTRAIT_DOWN = 413;
// 横屏,底部在左
public static final int ORIENTATION_LANDSCAPE_LEFT = 414;
}
......@@ -6,22 +6,20 @@ import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import androidx.annotation.NonNull;
import com.tencent.rtmp.ITXLivePlayListener;
import com.tencent.rtmp.TXLiveBase;
import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.TXLivePlayConfig;
import com.tencent.rtmp.TXLivePlayer;
import java.util.Map;
import com.tencent.vod.flutter.model.PipResult;
import com.tencent.vod.flutter.model.VideoModel;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.TextureRegistry;
import java.util.Map;
/**
* live player processor
......@@ -50,27 +48,15 @@ public class FTXLivePlayer extends FTXBasePlayer implements MethodChannel.Method
private final FTXPIPManager mPipManager;
private FTXPIPManager.PipParams mPipParams;
private VideoModel mVideoModel;
private final FTXPIPManager.PipCallback pipCallback = new FTXPIPManager.PipCallback() {
@Override
public void onPlayBack() {
// pip not support playback
}
@Override
public void onResumeOrPlay() {
boolean isPlaying = isPlaying();
if (isPlaying) {
pause();
} else {
public void onPipResult(PipResult result) {
// 启动pip的时候,当前player已经暂停,pip退出之后,如果退出的时候pip还处于播放状态,那么当前player也置为播放状态
boolean isPipPlaying = result.isPlaying();
if(isPipPlaying) {
resume();
}
// isPlaying取反,点击暂停/播放之后,播放状态会变化
mPipManager.updatePipActions(!isPlaying, mPipParams);
}
@Override
public void onPlayForward() {
// pip not support forward
}
};
......@@ -80,6 +66,8 @@ public class FTXLivePlayer extends FTXBasePlayer implements MethodChannel.Method
mFlutterPluginBinding = flutterPluginBinding;
mActivity = activity;
mPipManager = pipManager;
mVideoModel = new VideoModel();
mVideoModel.setPlayerType(FTXEvent.PLAYER_LIVE);
mSurfaceTextureEntry = mFlutterPluginBinding.getTextureRegistry().createSurfaceTexture();
mSurfaceTexture = mSurfaceTextureEntry.surfaceTexture();
......@@ -245,11 +233,22 @@ public class FTXLivePlayer extends FTXBasePlayer implements MethodChannel.Method
String playPauseAssetPath = call.argument("pauseIcon");
String playForwardAssetPath = call.argument("forwardIcon");
mPipManager.addCallback(getPlayerId(), pipCallback);
mPipParams = new FTXPIPManager.PipParams(playBackAssetPath, playResumeAssetPath,
playPauseAssetPath,
playForwardAssetPath, getPlayerId(), false, false, true);
int pipResult = mPipManager.enterPip(isPlaying(), mPipParams);
mPipParams = new FTXPIPManager.PipParams(
mPipManager.toAndroidPath(playBackAssetPath),
mPipManager.toAndroidPath(playResumeAssetPath),
mPipManager.toAndroidPath(playPauseAssetPath),
mPipManager.toAndroidPath(playForwardAssetPath),
getPlayerId(),false, false, true);
mPipParams.setIsPlaying(isPlaying());
int pipResult = mPipManager.enterPip(mPipParams, mVideoModel);
// 启动成功之后,暂停当前界面视频
if(pipResult == FTXEvent.NO_ERROR) {
pause();
}
result.success(pipResult);
} else if(call.method.equals("exitPictureInPictureMode")) {
mPipManager.exitPip();
result.success(null);
} else if (call.method.equals("setConfig")) {
Map<Object, Object> config = call.argument("config");
setPlayConfig(config);
......@@ -272,6 +271,8 @@ public class FTXLivePlayer extends FTXBasePlayer implements MethodChannel.Method
int startLivePlay(String url, int type) {
Log.d(TAG, "startLivePlay:");
mVideoModel.setVideoUrl(url);
mVideoModel.setLiveType(type);
if (mLivePlayer != null) {
mLivePlayer.setSurface(mSurface);
mLivePlayer.setPlayListener(this);
......
......@@ -6,9 +6,7 @@ import android.graphics.SurfaceTexture;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface;
import androidx.annotation.NonNull;
import com.tencent.rtmp.ITXVodPlayListener;
import com.tencent.rtmp.TXBitrateItem;
import com.tencent.rtmp.TXImageSprite;
......@@ -17,18 +15,18 @@ import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXPlayInfoParams;
import com.tencent.rtmp.TXVodPlayConfig;
import com.tencent.rtmp.TXVodPlayer;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tencent.vod.flutter.model.PipResult;
import com.tencent.vod.flutter.model.VideoModel;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.TextureRegistry;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* vodPlayer plugin processor
......@@ -49,6 +47,7 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
private TXVodPlayer mVodPlayer;
private TXImageSprite mTxImageSprite;
private VideoModel mVideoModel;
private static final int Uninitialized = -101;
private TextureRegistry.SurfaceTextureEntry mSurfaceTextureEntry;
......@@ -59,40 +58,18 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
private FTXPIPManager.PipParams mPipParams;
private final FTXPIPManager.PipCallback pipCallback = new FTXPIPManager.PipCallback() {
@Override
public void onPlayBack() {
boolean isPlaying = isPlaying();
if (isPlaying) {
float backPlayTime = getCurrentPlaybackTime() - 10;
if (backPlayTime < 0) {
backPlayTime = 0;
}
seek(backPlayTime);
public void onPipResult(PipResult result) {
float playTime = result.getPlayTime();
float duration = mVodPlayer.getDuration();
if (playTime > duration) {
playTime = duration;
}
}
@Override
public void onResumeOrPlay() {
boolean isPlaying = isPlaying();
if (isPlaying) {
pause();
} else {
seek(playTime);
// 启动pip的时候,当前player已经暂停,pip退出之后,如果退出的时候pip还处于播放状态,那么当前player也置为播放状态
boolean isPipPlaying = result.isPlaying();
if(isPipPlaying) {
resume();
}
// isPlaying取反,点击暂停/播放之后,播放状态会变化
mPipManager.updatePipActions(!isPlaying, mPipParams);
}
@Override
public void onPlayForward() {
boolean isPlaying = isPlaying();
if (isPlaying) {
float forwardPlayTime = getCurrentPlaybackTime() + 10;
float duration = mVodPlayer.getDuration();
if (forwardPlayTime > duration) {
forwardPlayTime = duration;
}
seek(forwardPlayTime);
}
}
};
......@@ -100,6 +77,8 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
super();
mPipManager = pipManager;
mFlutterPluginBinding = flutterPluginBinding;
mVideoModel = new VideoModel();
mVideoModel.setPlayerType(FTXEvent.PLAYER_VOD);
mMethodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "cloud.tencent" +
".com/txvodplayer/" + super.getPlayerId());
......@@ -196,10 +175,6 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
}
} else if (event == TXLiveConstants.PLAY_WARNING_HW_ACCELERATION_FAIL) {
mHardwareDecodeFail = true;
} else if (event == TXLiveConstants.PLAY_EVT_PLAY_END) {
if (null != mPipManager && mPipManager.isInPipMode()) {
mPipManager.updatePipActions(false, mPipParams);
}
}
mEventSink.success(CommonUtil.getParams(event, bundle));
}
......@@ -355,11 +330,23 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
String playPauseAssetPath = call.argument("pauseIcon");
String playForwardAssetPath = call.argument("forwardIcon");
mPipManager.addCallback(getPlayerId(), pipCallback);
mPipParams = new FTXPIPManager.PipParams(playBackAssetPath, playResumeAssetPath,
playPauseAssetPath,
playForwardAssetPath, getPlayerId());
int pipResult = mPipManager.enterPip(isPlaying(), mPipParams);
mPipParams = new FTXPIPManager.PipParams(
mPipManager.toAndroidPath(playBackAssetPath),
mPipManager.toAndroidPath(playResumeAssetPath),
mPipManager.toAndroidPath(playPauseAssetPath),
mPipManager.toAndroidPath(playForwardAssetPath),
getPlayerId());
mPipParams.setIsPlaying(isPlaying());
mPipParams.setCurrentPlayTime(getCurrentPlaybackTime());
int pipResult = mPipManager.enterPip(mPipParams, mVideoModel);
// 启动成功之后,暂停当前界面视频
if(pipResult == FTXEvent.NO_ERROR) {
pause();
}
result.success(pipResult);
} else if(call.method.equals("exitPictureInPictureMode")) {
mPipManager.exitPip();
result.success(null);
} else if(call.method.equals("initImageSprite")) {
String vvtUrl = call.argument("vvtUrl");
List<String> imageUrls = call.argument("imageUrls");
......@@ -411,6 +398,10 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
}
int startVodPlay(String url) {
mVideoModel.setVideoUrl(url);
mVideoModel.setAppId(0);
mVideoModel.setFileId("");
mVideoModel.setPSign("");
if (mVodPlayer != null) {
return mVodPlayer.startVodPlay(url);
}
......@@ -422,6 +413,10 @@ public class FTXVodPlayer extends FTXBasePlayer implements MethodChannel.MethodC
int appId = call.argument("appId");
String fileId = call.argument("fileId");
String psign = call.argument("psign");
mVideoModel.setVideoUrl("");
mVideoModel.setAppId(appId);
mVideoModel.setFileId(fileId);
mVideoModel.setPSign(psign);
TXPlayInfoParams playInfoParams = new TXPlayInfoParams(appId, fileId, psign);
mVodPlayer.startVodPlay(playInfoParams);
}
......
......@@ -246,7 +246,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, MethodCallHandler, Acti
private void initPipManagerIfNeed() {
if (null == mTxPipManager) {
mTxPipManager = new FTXPIPManager(mTxAudioManager, mActivityPluginBinding.getActivity(),
mTxPipManager = new FTXPIPManager(mTxAudioManager, mFlutterPluginBinding, mActivityPluginBinding,
mFlutterPluginBinding.getFlutterAssets());
}
}
......@@ -280,7 +280,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, MethodCallHandler, Acti
@Override
public void onDetachedFromActivity() {
if (null != mTxPipManager) {
mTxPipManager.releaseReceiver();
mTxPipManager.releaseActivityListener();
}
unregisterReceiver();
}
......
package com.tencent.vod.flutter.model;
import android.os.Parcel;
import android.os.Parcelable;
public class PipResult implements Parcelable {
private Float mPlayTime;
private boolean mIsPlaying;
private int mPlayerId;
public PipResult(){}
protected PipResult(Parcel in) {
if (in.readByte() == 0) {
mPlayTime = null;
} else {
mPlayTime = in.readFloat();
}
mIsPlaying = in.readByte() != 0;
mPlayerId = in.readInt();
}
public static final Creator<PipResult> CREATOR = new Creator<PipResult>() {
@Override
public PipResult createFromParcel(Parcel in) {
return new PipResult(in);
}
@Override
public PipResult[] newArray(int size) {
return new PipResult[size];
}
};
public Float getPlayTime() {
return mPlayTime;
}
public void setPlayTime(Float mPlayTime) {
this.mPlayTime = mPlayTime;
}
public boolean isPlaying() {
return mIsPlaying;
}
public void setPlaying(boolean playing) {
mIsPlaying = playing;
}
public int getPlayerId() {
return mPlayerId;
}
public void setPlayerId(int mPlayerId) {
this.mPlayerId = mPlayerId;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mPlayTime == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
dest.writeFloat(mPlayTime);
}
dest.writeByte((byte) (mIsPlaying ? 1 : 0));
dest.writeInt(mPlayerId);
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
package com.tencent.vod.flutter.model;
import android.os.Parcel;
import android.os.Parcelable;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.vod.flutter.FTXEvent;
/**
* 视频model
*/
public class VideoModel implements Parcelable {
private String videoUrl;
private int appId;
private String fileId;
private String pSign;
private int mPlayerType = FTXEvent.PLAYER_VOD;
private int mLiveType = TXLivePlayer.PLAY_TYPE_LIVE_FLV;
public VideoModel() {}
protected VideoModel(Parcel in) {
videoUrl = in.readString();
appId = in.readInt();
fileId = in.readString();
pSign = in.readString();
mPlayerType = in.readInt();
mLiveType = in.readInt();
}
public static final Creator<VideoModel> CREATOR = new Creator<VideoModel>() {
@Override
public VideoModel createFromParcel(Parcel in) {
return new VideoModel(in);
}
@Override
public VideoModel[] newArray(int size) {
return new VideoModel[size];
}
};
public String getVideoUrl() {
return videoUrl;
}
public void setVideoUrl(String videoUrl) {
this.videoUrl = videoUrl;
}
public int getAppId() {
return appId;
}
public void setAppId(int appId) {
this.appId = appId;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getPSign() {
return pSign;
}
public void setPSign(String pSign) {
this.pSign = pSign;
}
public int getPlayerType() {
return mPlayerType;
}
public void setPlayerType(int mPlayerType) {
this.mPlayerType = mPlayerType;
}
public int getLiveType() {
return mLiveType;
}
public void setLiveType(int mLiveType) {
this.mLiveType = mLiveType;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(videoUrl);
dest.writeInt(appId);
dest.writeString(fileId);
dest.writeString(pSign);
dest.writeInt(mPlayerType);
dest.writeInt(mLiveType);
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
tools:context="com.tencent.vod.flutter.ui.FlutterPipImplActivity">
<SurfaceView
android:id="@+id/sv_video_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
<ProgressBar
android:id="@+id/pb_video_progress"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_alignParentBottom="true"
android:max="100"
android:visibility="gone"
style="@android:style/Widget.Holo.ProgressBar.Horizontal"/>
</RelativeLayout>
\ No newline at end of file
<resources></resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 取消界面转场动画使用-->
<style name="Theme" parent="android:Theme.Translucent.NoTitleBar.Fullscreen">
<item name="android:windowAnimationStyle">@style/Animation</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Animation">
<item name="android:activityOpenEnterAnimation">@null</item>
<item name="android:activityOpenExitAnimation">@null</item>
<item name="android:activityCloseEnterAnimation">@null</item>
<item name="android:activityCloseExitAnimation">@null</item>
<item name="android:taskOpenEnterAnimation">@null</item>
<item name="android:taskOpenExitAnimation">@null</item>
<item name="android:taskCloseEnterAnimation">@null</item>
<item name="android:taskCloseExitAnimation">@null</item>
<item name="android:taskToFrontEnterAnimation">@null</item>
<item name="android:taskToFrontExitAnimation">@null</item>
<item name="android:taskToBackEnterAnimation">@null</item>
<item name="android:taskToBackExitAnimation">@null</item>
</style>
</resources>
\ No newline at end of file
// Copyright (c) 2022 Tencent. All rights reserved.
package com.example.super_player_example;
public class MainActivity extends FTXFlutterPipActivity {
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}
......@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {
/* Begin PBXBuildFile section */
......@@ -393,7 +393,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteavdemo;
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteav.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Yunxiao Develop Profile";
VERSIONING_SYSTEM = "apple-generic";
......@@ -522,7 +522,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteavdemo;
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteav.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Yunxiao Develop Profile";
VERSIONING_SYSTEM = "apple-generic";
......@@ -544,7 +544,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteavdemo;
PRODUCT_BUNDLE_IDENTIFIER = com.yunxiao.dev.liteav.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "Yunxiao Develop Profile";
VERSIONING_SYSTEM = "apple-generic";
......
......@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
......@@ -52,7 +54,5 @@
<false/>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
......@@ -8,26 +8,31 @@ import 'package:super_player_example/ui/demo_inputdialog.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
/// flutter superplayer demo
class DemoSuperplayer extends StatefulWidget {
class DemoSuperPlayer extends StatefulWidget {
Map? initParams = {};
DemoSuperPlayer({this.initParams});
@override
State<StatefulWidget> createState() => _DemoSuperplayerState();
State<StatefulWidget> createState() => _DemoSuperPlayerState();
}
class _DemoSuperplayerState extends State<DemoSuperplayer> {
class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerRestorePage {
static const DEFAULT_PLACE_HOLDER = "http://xiaozhibo-10055601.file.myqcloud.com/coverImg.jpg";
static const ARGUMENT_TYPE_POS = "arg_type_pos";
static const ARGUMENT_VIDEO_DATA = "arg_video_data";
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
);
SuperPlayerModel? currentVideoModel;
SuperPlayerModel? initVideoModel;
double? initStartTime;
TextStyle _textStyleSelected = new TextStyle(fontSize: 16, color: Colors.white);
TextStyle _textStyleUnSelected = new TextStyle(fontSize: 16, color: Colors.grey);
@override
void initState() {
......@@ -35,6 +40,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
// 监听设备旋转
SuperPlayerPlugin.startVideoOrientationService();
_controller = SuperPlayerController(context);
TXPipController.instance.setPipPlayerPage(DemoSuperPlayer, this);
FTXVodPlayConfig config = FTXVodPlayConfig();
// 如果不配置preferredResolution,则在播放多码率视频的时候优先播放720 * 1280分辨率的码率
config.preferredResolution = 720 * 1280;
......@@ -49,7 +55,16 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
print(evtName);
}
});
_getLiveListData();
if (null != widget.initParams) {
tabSelectPos = widget.initParams![ARGUMENT_TYPE_POS];
initVideoModel = widget.initParams![ARGUMENT_VIDEO_DATA];
initStartTime = widget.initParams![TXPipController.ARGUMENT_PIP_START_TIME];
}
if (tabSelectPos == 0) {
_getLiveListData();
} else if (tabSelectPos == 1) {
_getVodListData();
}
}
@override
......@@ -157,7 +172,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
playModel.title,
style: TextStyle(color: Colors.white),
),
onTap: () => playCurrentModel(playModel),
onTap: () => playCurrentModel(playModel, 0),
horizontalTitleGap: 10,
),
Divider()
......@@ -177,31 +192,50 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
showDialog(
context: context,
builder: (context) {
return DemoInputDialog("", 0, "", (String url, int appId, String fileId, String pSign) {
SuperPlayerModel model = new SuperPlayerModel();
model.appId = appId;
if (url.isNotEmpty) {
model.videoURL = url;
} else if (appId != 0 && fileId.isNotEmpty) {
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = fileId;
if (pSign.isNotEmpty) {
model.videoId!.psign = pSign;
return DemoInputDialog(
"",
0,
"",
(String url, int appId, String fileId, String pSign) {
SuperPlayerModel model = new SuperPlayerModel();
model.appId = appId;
if (url.isNotEmpty) {
model.videoURL = url;
} else if (appId != 0 && fileId.isNotEmpty) {
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = fileId;
if (pSign.isNotEmpty) {
model.videoId!.psign = pSign;
}
} else {
EasyLoading.showError("请输入播放地址!");
return;
}
} else {
EasyLoading.showError("请输入播放地址!");
return;
}
playCurrentModel(model);
}, needPisgn: !isLive, showFileEdited: !isLive,);
playCurrentModel(model, 0);
},
needPisgn: !isLive,
showFileEdited: !isLive,
);
});
}
void playCurrentModel(SuperPlayerModel model) {
void playCurrentModel(SuperPlayerModel model, double startTime) {
currentVideoModel = model;
_controller.setStartTime(startTime);
_controller.playWithModelNeedLicence(model);
}
void playVideo(SuperPlayerModel model) {
if (null != initVideoModel) {
playCurrentModel(initVideoModel!, initStartTime ?? 0);
initVideoModel = null;
initStartTime = null;
} else {
playCurrentModel(model, 0);
}
}
void _getLiveListData() async {
setState(() {
tabSelectPos = 0;
......@@ -212,7 +246,8 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
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.coverUrl =
"http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/66bc542f387702300661648850/0RyP1rZfkdQA.png";
model.playAction = playAction;
models.add(model);
......@@ -220,7 +255,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
videoModels.addAll(models);
setState(() {
if (videoModels.isNotEmpty) {
playCurrentModel(videoModels[0]);
playVideo(videoModels[0]);
} else {
EasyLoading.showError("video list request error");
}
......@@ -290,7 +325,7 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
videoModels.addAll(models);
setState(() {
if (videoModels.isNotEmpty) {
playCurrentModel(videoModels[0]);
playVideo(videoModels[0]);
} else {
EasyLoading.showError("video list request error");
}
......@@ -306,4 +341,10 @@ class _DemoSuperplayerState extends State<DemoSuperplayer> {
SuperPlayerPlugin.restorePageBrightness();
super.dispose();
}
@override
void onNeedSavePipPageState(Map<String, dynamic> params) {
params[ARGUMENT_TYPE_POS] = tabSelectPos;
params[ARGUMENT_VIDEO_DATA] = currentVideoModel;
}
}
......@@ -12,10 +12,10 @@ import 'ui/demo_video_slider_view.dart';
class DemoTXVodPlayer extends StatefulWidget {
@override
_DemoTXVodlayerState createState() => _DemoTXVodlayerState();
_DemoTXVodPlayerState createState() => _DemoTXVodPlayerState();
}
class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
class _DemoTXVodPlayerState extends State<DemoTXVodPlayer>
with WidgetsBindingObserver {
late TXVodPlayerController _controller;
double _aspectRatio = 16 / 9;
......@@ -24,8 +24,7 @@ class _DemoTXVodlayerState extends State<DemoTXVodPlayer>
int _volume = 100;
List _supportedBitrates = [];
int _curBitrateIndex = 0;
String _url =
"http://1400329073.vod2.myqcloud.com/d62d88a7vodtranscq1400329073/59c68fe75285890800381567412/adp.10.m3u8";
String _url = "http://1400329073.vod2.myqcloud.com/d62d88a7vodtranscq1400329073/59c68fe75285890800381567412/adp.10.m3u8";
int _appId = 0;
String _fileId = "";
double _rate = 1.0;
......
......@@ -5,7 +5,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/demo_superplayer.dart';
import 'package:super_player_example/shortvideo/demo_short_video_lib.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
import 'ui/treePage.dart';
......@@ -22,6 +24,7 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String? _liteAVSdkVersion = 'Unknown';
GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@override
void initState() {
......@@ -58,6 +61,9 @@ class _MyAppState extends State<MyApp> {
setState(() {
_platformVersion = platformVersion!;
});
TXPipController.instance.setNavigatorHandle((params) {
navigatorKey.currentState?.push(MaterialPageRoute(builder: (_) => DemoSuperPlayer(initParams: params)));
});
}
Future<void> _getflutterSdkVersion() async {
......@@ -70,38 +76,38 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
home: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/ic_new_vod_bg.png"),
fit: BoxFit.cover,
)
),
image: AssetImage("images/ic_new_vod_bg.png"),
fit: BoxFit.cover,
)),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
title: const Text('腾讯云Flutter播放器', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),),
),
body: Builder(
builder: (context) {
return Container(
color: Colors.transparent,
child: Stack(
children: [
TreePage(),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: new EdgeInsets.all(20.0) ,
child: Text('LiteAVSDKVersion: ${_liteAVSdkVersion}'),
)
),
],
),
);
}
title: const Text(
'腾讯云Flutter播放器',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
),
),
body: Builder(builder: (context) {
return Container(
color: Colors.transparent,
child: Stack(
children: [
TreePage(),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: new EdgeInsets.all(20.0),
child: Text('LiteAVSDKVersion: ${_liteAVSdkVersion}'),
)),
],
),
);
}),
),
),
builder: EasyLoading.init(),
......
......@@ -136,7 +136,7 @@ class _TreePageState extends State<TreePage> {
}else if (i == 1) {
return DemoTXVodPlayer();
} else if (i==2){
return DemoSuperplayer();
return DemoSuperPlayer();
} else {
return DemoShortVideoPlayer();
}
......
......@@ -15,6 +15,8 @@
#define EVENT_PIP_MODE_RESTORE_UI 5
#define EVENT_PIP_MODE_WILL_EXIT 6
#define EVENT_PIP_PLAY_TIME @"playTime"
// 画中画错误事件code
#define NO_ERROR 0 ///< 无错误
#define ERROR_IOS_PIP_DEVICE_NOT_SUPPORT -104 ///< 设备或系统版本不支持(iPad iOS9+ 才支持PIP)
......
......@@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)onPlayerPipStateDidStop;
- (void)onPlayerPipStateRestoreUI;
- (void)onPlayerPipStateRestoreUI:(double)playTime;;
- (void)onPlayerPipStateError:(NSInteger)errorId;
......
......@@ -39,6 +39,8 @@ static const int CODE_ON_RECEIVE_FIRST_FRAME = 2003;
id<FlutterPluginRegistrar> _registrar;
id<FlutterTextureRegistry> _textureRegistry;
float currentPlayTime;
}
......@@ -417,7 +419,7 @@ BOOL volatile isStop = false;
result(@(index));
}
else if([@"getPlayableDuration" isEqualToString:call.method]){
float time = [self getCurrentPlaybackTime];
float time = [self getPlayableDuration];
result(@(time));
}
else if([@"getDuration" isEqualToString:call.method]){
......@@ -454,6 +456,12 @@ BOOL volatile isStop = false;
result(nil);
}
}
else if ([@"exitPictureInPictureMode" isEqualToString:call.method]) {
if(_txVodPlayer != nil) {
[_txVodPlayer exitPictureInPicture];
}
result(nil);
}
else {
result(FlutterMethodNotImplemented);
}
......@@ -522,7 +530,12 @@ BOOL volatile isStop = false;
{
// 交给flutter共享纹理处理首帧事件返回时机
if (EvtID == CODE_ON_RECEIVE_FIRST_FRAME) {
currentPlayTime = 0;
return;
} else if(EvtID == PLAY_EVT_PLAY_PROGRESS) {
currentPlayTime = [param[EVT_PLAY_PROGRESS] floatValue];
} else if(EvtID == PLAY_EVT_PLAY_BEGIN) {
currentPlayTime = 0;
}
[_eventSink success:[FTXVodPlayer getParamsWithEvent:EvtID withParams:param]];
......@@ -769,14 +782,13 @@ BOOL volatile isStop = false;
if (pipState == TX_VOD_PLAYER_PIP_STATE_RESTORE_UI) {
self.restoreUI = YES;
if (self.delegate && [self.delegate respondsToSelector:@selector(onPlayerPipStateRestoreUI)]) {
[self.delegate onPlayerPipStateRestoreUI];
}
dispatch_async(dispatch_get_main_queue(), ^{
[player exitPictureInPicture];
[self->_txVodPlayer resume];
});
if (self.delegate && [self.delegate respondsToSelector:@selector(onPlayerPipStateRestoreUI:)]) {
[self.delegate onPlayerPipStateRestoreUI:currentPlayTime];
}
}
}
......
......@@ -63,7 +63,7 @@ SuperPlayerPlugin* instance;
_eventSink = [FTXPlayerEventSinkQueue new];
_pipEventSink = [FTXPlayerEventSinkQueue new];
_eventChannel = [FlutterEventChannel eventChannelWithName:@"cloud.tencent.com/playerPlugin/event" binaryMessenger:[registrar messenger]];
_pipEventChannel = [FlutterEventChannel eventChannelWithName:@"cloud.tencent.com/playerPlugin/pipEvent" binaryMessenger:[registrar messenger]];
_pipEventChannel = [FlutterEventChannel eventChannelWithName:@"cloud.tencent.com/playerPlugin/componentEvent" binaryMessenger:[registrar messenger]];
[_eventChannel setStreamHandler:self];
[_pipEventChannel setStreamHandler:self];
......@@ -194,7 +194,7 @@ SuperPlayerPlugin* instance;
result(@(setResult));
} else if([@"startVideoOrientationService" isEqualToString:call.method]) {
// only for android
result(@(true));
result(@(YES));
} else {
result(FlutterMethodNotImplemented);
}
......@@ -263,8 +263,8 @@ SuperPlayerPlugin* instance;
[_pipEventSink success:@{@"event" : @(errorId)}];
}
- (void)onPlayerPipStateRestoreUI {
[_pipEventSink success:@{@"event" : @(EVENT_PIP_MODE_RESTORE_UI)}];
- (void)onPlayerPipStateRestoreUI:(double)playTime {
[_pipEventSink success:@{@"event" : @(EVENT_PIP_MODE_RESTORE_UI), EVENT_PIP_PLAY_TIME : @(playTime)}];
}
#pragma mark - orientation
......
......@@ -28,7 +28,7 @@ class SuperPlayerPlugin {
EventChannel eventChannel = EventChannel("cloud.tencent.com/playerPlugin/event");
eventChannel.receiveBroadcastStream("event").listen(_eventHandler, onError: _errorHandler);
EventChannel pipEventChanne = EventChannel("cloud.tencent.com/playerPlugin/pipEvent");
EventChannel pipEventChanne = EventChannel("cloud.tencent.com/playerPlugin/componentEvent");
pipEventChanne.receiveBroadcastStream("pipEvent").listen(_pipEventHandler, onError: _errorHandler);
}
......
......@@ -311,20 +311,14 @@ class TXLivePlayerController extends ChangeNotifier implements ValueListenable<T
String? playIconForAndroid,
String? pauseIconForAndroid,
String? forwardIconForAndroid}) async {
if (_isNeedDisposed) return -1;
await _initPlayer.future;
if (Platform.isAndroid) {
return await _channel.invokeMethod("enterPictureInPictureMode", {
"backIcon": backIconForAndroid,
"playIcon": playIconForAndroid,
"pauseIcon": pauseIconForAndroid,
"forwardIcon": forwardIconForAndroid
});
} else if (Platform.isIOS) {
return -1;
} else {
return -1;
}
/// live not support
return -1;
}
/// 退出画中画,如果该播放器处于画中画模式
@override
Future<void> exitPictureInPictureMode() async {
/// live not support
}
/// 释放播放器资源占用
......
......@@ -23,4 +23,6 @@ abstract class TXPlayerController {
Future<bool> enableHardwareDecode(bool enable);
Future<int> enterPictureInPictureMode(
{String? backIconForAndroid, String? playIconForAndroid, String? pauseIconForAndroid, String? forwardIconForAndroid});
Future<void> exitPictureInPictureMode();
void dispose();
}
\ No newline at end of file
......@@ -304,6 +304,14 @@ class TXVodDownloadMedialnfo {
}
}
///
/// 播放器类型
///
abstract class TXPlayerType {
static const VOD_PLAY = 0;
static const LIVE_PLAY = 1;
}
//视频预下载事件回调Listener
typedef FTXPredownlodOnCompleteListener = void Function(int taskId, String url);
typedef FTXPredownlodOnErrorListener = void Function(int taskId, String url, int code, String msg);
......
......@@ -424,6 +424,14 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
return await _channel.invokeMethod("getDuration");
}
/// 退出画中画,如果该播放器处于画中画模式
@override
Future<void> exitPictureInPictureMode() async {
if (_isNeedDisposed) return;
await _initPlayer.future;
return await _channel.invokeMethod("exitPictureInPictureMode");
}
/// 释放controller
@override
void dispose() async {
......@@ -465,4 +473,5 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
double? videoTop = 0;
double? videoRight = 0;
double? videoBottom = 0;
}
......@@ -25,8 +25,10 @@ part 'cgi/playinfo_protocol.dart';
part 'cgi/super_vod_data_loader.dart';
part 'model/superplayer_define.dart';
part 'model/superplayer_model.dart';
part 'model/txpipplayer_data.dart';
part 'tools/video_quality_utils.dart';
part 'tools/utils.dart';
part 'tools/txpip_controller.dart';
part 'ui/superplayer_bottom_view.dart';
part 'ui/superplayer_quality_view.dart';
part 'ui/superplayer_title_view.dart';
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// 画中画状态存储参数
class TXPipPlayerData {
/// 画中画播放器实例,同时只能存在一个
final TXPlayerController _playerController;
int playerMode = TXPlayerType.VOD_PLAY;
TXPipPlayerData(this._playerController) {
playerMode = _playerController is TXVodPlayerController ? TXPlayerType.VOD_PLAY : playerMode = TXPlayerType.LIVE_PLAY;
}
}
......@@ -70,7 +70,7 @@ class SuperPlayerController {
}
void _initVodPlayer() async {
_vodPlayerController = new TXVodPlayerController();
_vodPlayerController = TXVodPlayerController();
await _vodPlayerController.initialize();
_setVodListener();
}
......@@ -349,8 +349,9 @@ class SuperPlayerController {
_playVodUrl(videoUrl);
List<VideoQuality>? qualityList = protocol.getVideoQualityList();
_isMultiBitrateStream =
protocol.getResolutionNameList() != null || qualityList != null || (videoUrl != null && videoUrl.contains("m3u8"));
_isMultiBitrateStream = protocol.getResolutionNameList() != null ||
qualityList != null ||
(videoUrl != null && videoUrl.contains("m3u8"));
_updateVideoQualityList(qualityList, protocol.getDefaultVideoQuality());
}
......@@ -416,7 +417,8 @@ class SuperPlayerController {
if (_playAction == SuperPlayerModel.PLAY_ACTION_PRELOAD) {
await _vodPlayerController.setAutoPlay(isAutoPlay: false);
_playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
} else if (_playAction == SuperPlayerModel.PLAY_ACTION_AUTO_PLAY || _playAction == SuperPlayerModel.PLAY_ACTION_MANUAL_PLAY) {
} else if (_playAction == SuperPlayerModel.PLAY_ACTION_AUTO_PLAY ||
_playAction == SuperPlayerModel.PLAY_ACTION_MANUAL_PLAY) {
await _vodPlayerController.setAutoPlay(isAutoPlay: true);
}
_setVodListener();
......@@ -586,7 +588,9 @@ class SuperPlayerController {
String title = "";
if (videoModel != null && null != videoModel!.title && videoModel!.title.isNotEmpty) {
title = videoModel!.title;
} else if (_currentProtocol != null && null != _currentProtocol!.getName() && _currentProtocol!.getName()!.isNotEmpty) {
} else if (_currentProtocol != null &&
null != _currentProtocol!.getName() &&
_currentProtocol!.getName()!.isNotEmpty) {
title = _currentProtocol!.getName()!;
}
return title;
......@@ -622,7 +626,8 @@ class SuperPlayerController {
/// 是否是HTTP-FLV协议
bool _isFLVPlay(String? videoURL) {
return (null != videoURL && videoURL.startsWith("http://") || videoURL!.startsWith("https://")) && videoURL.contains(".flv");
return (null != videoURL && videoURL.startsWith("http://") || videoURL!.startsWith("https://")) &&
videoURL.contains(".flv");
}
/// 重置播放器状态
......@@ -655,10 +660,13 @@ class SuperPlayerController {
Future<void> releasePlayer() async {
// 先移除widget的事件监听
_observer?.onDispose();
resetPlayer();
playerStreamController.close();
_vodPlayerController.dispose();
_livePlayerController.dispose();
// 如果处于画中画模式,播放器暂时不释放
if(!TXPipController.instance.isPlayerInPip(getCurrentController())) {
resetPlayer();
_vodPlayerController.dispose();
_livePlayerController.dispose();
}
}
/// return true : 执行了退出全屏等操作,消耗了返回事件 false:未消耗事件
......@@ -740,14 +748,21 @@ class SuperPlayerController {
/// </h1>
/// @param backIcon playIcon pauseIcon forwardIcon 为播放后退、播放、暂停、前进的图标,如果赋值的话,将会使用传递的图标,否则
/// 使用系统默认图标,只支持flutter本地资源图片,传递的时候,与flutter使用图片资源一致,例如: images/back_icon.png
Future<int> enterPictureInPictureMode({String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
Future<int> enterPictureInPictureMode(
{String? backIcon, String? playIcon, String? pauseIcon, String? forwardIcon}) async {
if (_playerUIStatus == SuperPlayerUIStatus.WINDOW_MODE) {
if (playerType == SuperPlayerType.VOD) {
return _vodPlayerController.enterPictureInPictureMode(
backIconForAndroid: backIcon, playIconForAndroid: playIcon, pauseIconForAndroid: pauseIcon, forwardIconForAndroid: forwardIcon);
return TXPipController.instance.enterPip(_vodPlayerController, _context,
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
} else {
return _livePlayerController.enterPictureInPictureMode(
backIconForAndroid: backIcon, playIconForAndroid: playIcon, pauseIconForAndroid: pauseIcon, forwardIconForAndroid: forwardIcon);
return TXPipController.instance.enterPip(_livePlayerController, _context,
backIconForAndroid: backIcon,
playIconForAndroid: playIcon,
pauseIconForAndroid: pauseIcon,
forwardIconForAndroid: forwardIcon);
}
}
return TXVodPlayEvent.ERROR_PIP_CAN_NOT_ENTER;
......@@ -794,6 +809,14 @@ class SuperPlayerController {
}
}
/// 设置播放开始时间
Future<void> setStartTime(double startTime) async {
if (playerType == SuperPlayerType.VOD) {
startPos = startTime;
return await _vodPlayerController.setStartTime(startTime);
}
}
Future<void> setPlayRate(double rate) async {
currentPlayRate = rate;
_vodPlayerController.setRate(rate);
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
typedef OnJumpToPipPlayer = void Function(Map params);
typedef OnPopCurrent = void Function();
///
/// 画中画控制器,单例,只能存在一个画中画
///
class TXPipController {
static const ARGUMENT_PIP_START_TIME = "argumentStartTime";
static TXPipController? _instance;
static TXPipController get instance => _sharedInstance();
/// 画中画播放器实例,同时只能存在一个
TXPipPlayerData? _playerData;
final Map<String, dynamic> _extParams = {};
TXPipPlayerRestorePage? _onPipEnterListener;
OnJumpToPipPlayer? _onJumpToPipPlayer;
StreamSubscription? _pipEventSubscription;
/// TXPipController
static TXPipController _sharedInstance() {
_instance ??= TXPipController._internal();
return _instance!;
}
TXPipController._internal();
Future<int> enterPip(TXPlayerController playerController, BuildContext context,
{String? backIconForAndroid,
String? playIconForAndroid,
String? pauseIconForAndroid,
String? forwardIconForAndroid}) async {
if (_playerData != null) {
await exitAndReleaseCurrentPip();
}
_playerData = TXPipPlayerData(playerController);
_pipEventSubscription = SuperPlayerPlugin.instance.onExtraEventBroadcast.listen((event) async {
int eventCode = event["event"];
if ((Platform.isIOS && eventCode == TXVodPlayEvent.EVENT_PIP_MODE_ALREADY_ENTER) ||
(Platform.isAndroid && eventCode == TXVodPlayEvent.EVENT_PIP_MODE_REQUEST_START)) {
_onPipEnterListener?.onNeedSavePipPageState(_extParams);
Navigator.of(context).pop();
} else if ((Platform.isIOS && eventCode == TXVodPlayEvent.EVENT_IOS_PIP_MODE_WILL_EXIT)
|| (Platform.isAndroid && eventCode == TXVodPlayEvent.EVENT_PIP_MODE_ALREADY_EXIT)) {
await exitAndReleaseCurrentPip();
} else if (eventCode == TXVodPlayEvent.EVENT_IOS_PIP_MODE_RESTORE_UI) {
_extParams[ARGUMENT_PIP_START_TIME] = event["playTime"];
await exitAndReleaseCurrentPip();
_onJumpToPipPlayer?.call(_extParams);
} else if(eventCode < 0) {
// pip enter failed
_pipEventSubscription?.cancel();
_playerData = null;
}
});
int enterResult = await _playerData!._playerController.enterPictureInPictureMode(
backIconForAndroid: backIconForAndroid,
playIconForAndroid: playIconForAndroid,
pauseIconForAndroid: pauseIconForAndroid,
forwardIconForAndroid: forwardIconForAndroid);
if (enterResult != TXVodPlayEvent.NO_ERROR) {
_playerData = null;
_pipEventSubscription?.cancel();
}
return enterResult;
}
Future<void> exitAndReleaseCurrentPip() async {
if (null != _playerData && _playerData?._playerController != null) {
if(Platform.isAndroid) {
await _playerData?._playerController.exitPictureInPictureMode();
}
await _playerData?._playerController.stop();
_playerData?._playerController.dispose();
}
_pipEventSubscription?.cancel();
_playerData = null;
}
/// 传入的controller是否处于画中画模式
bool isPlayerInPip(TXPlayerController playerController) {
if (null != _playerData) {
return _playerData!._playerController == playerController;
}
return false;
}
void setPipPlayerPage(Type a, TXPipPlayerRestorePage listener) {
_onPipEnterListener = listener;
}
void setNavigatorHandle(OnJumpToPipPlayer onJumpToPipPlayer) {
_onJumpToPipPlayer = onJumpToPipPlayer;
}
}
abstract class TXPipPlayerRestorePage {
/// 当需要保存画中画界面相关元素的时候,会回调该方法
void onNeedSavePipPageState(Map<String, dynamic> params);
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论