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

1. Remove the code logic related to the Flutter player's playCgi, and update the…

1. Remove the code logic related to the Flutter player's playCgi, and update the cover of short videos through the player callback. 2. Fix the issue of Picture-in-Picture (PiP) restoration failure on the home screen of Xiaomi devices running Android 12. 3. Fix the issue where the PiP window UI could not be updated on some devices. 4. Fix the issue of continuous callbacks for PiP configuration parameters. 5. After enabling PiP on Android, the Flutter side can now also receive player events. 6. Add video information onSuperPlayerGetInfo event and onSuperPlayerProgress progress event to the player component. 7. Fix the issue of incorrect display ratio after the player component's video goes full-screen.
上级 77ae2140
......@@ -70,6 +70,12 @@ public class FTXEvent {
// Player to be operated on PIP
// pip需要操作的播放器
public static final String EXTRA_NAME_PLAYER_ID = "vodPlayerId";
// pip player event id
// 画中画播放器事件的事件id
public static final String EXTRA_NAME_PIP_PLAYER_EVENT_ID = "pipPlayerEventId";
// pip player event params
// 画中画播放器事件的事件参数
public static final String EXTRA_NAME_PIP_PLAYER_EVENT_PARAMS = "pipPlayerEventParams";
// Progress rewind.
// 进度回退
public static final int EXTRA_PIP_PLAY_BACK = 101;
......@@ -88,10 +94,13 @@ public class FTXEvent {
// PIP error, current interface has been destroyed.
// pip 错误,当前界面已销毁
public static final int ERROR_PIP_ACTIVITY_DESTROYED = -103;
// Event from PIP container, broadcast key value.
// 来自画中画容器的事件,广播键值
// Event from PIP container,eventBus key value
// 来自画中画容器的事件,eventBus键值
public static final String EVENT_PIP_ACTION = "com.tencent.flutter.pipevent";
// Event from PIP container, event key value.
// Player event from PIP players,eventBus key value
// 来自画中画容器的事件,eventBus键值
public static final String EVENT_PIP_PLAYER_EVENT_ACTION = "com.tencent.flutter.pipplayerevent";
// Event from PIP container,eventBus key value
// 来自画中画容器的事件,事件键值
public static final String EVENT_PIP_MODE_NAME = "pipEventName";
// Current PIP playback time.
......
......@@ -13,6 +13,7 @@ import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.TXLivePlayConfig;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXVodConstants;
import com.tencent.rtmp.TXVodPlayer;
import com.tencent.vod.flutter.messages.FtxMessages.BoolMsg;
import com.tencent.vod.flutter.messages.FtxMessages.BoolPlayerMsg;
import com.tencent.vod.flutter.messages.FtxMessages.FTXLivePlayConfigPlayerMsg;
......@@ -70,6 +71,11 @@ public class FTXLivePlayer extends FTXBasePlayer implements ITXLivePlayListener,
resumePlayer();
}
}
@Override
public void onPipPlayerEvent(int event, Bundle bundle) {
onPlayEvent(event, bundle);
}
};
/**
......
......@@ -8,10 +8,8 @@ import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.PictureInPictureParams.Builder;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
......@@ -31,6 +29,7 @@ import androidx.annotation.RequiresApi;
import com.tencent.vod.flutter.model.TXPipResult;
import com.tencent.vod.flutter.model.TXVideoModel;
import com.tencent.vod.flutter.tools.TXCommonUtil;
import com.tencent.vod.flutter.tools.TXSimpleEventBus;
import com.tencent.vod.flutter.ui.FlutterPipImplActivity;
import java.io.IOException;
......@@ -38,6 +37,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
......@@ -48,53 +48,28 @@ import io.flutter.plugin.common.EventChannel;
*
* 画中画管理
*/
public class FTXPIPManager {
public class FTXPIPManager implements TXSimpleEventBus.EventSubscriber {
private static final String TAG = "FTXPIPManager";
private boolean misInit = false;
private final Map<Integer, PipCallback> pipCallbacks = new HashMap<>();
FTXAudioManager mTxAudioManager;
private ActivityPluginBinding mActivityBinding;
private final ActivityPluginBinding mActivityBinding;
private final FlutterPlugin.FlutterAssets mFlutterAssets;
private final EventChannel mPipEventChannel;
private final FTXPlayerEventSink mPipEventSink = new FTXPlayerEventSink();
private boolean mIsInPipMode = false;
private final BroadcastReceiver mPipBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (null != intent) {
int playerId = intent.getIntExtra(FTXEvent.EXTRA_NAME_PLAYER_ID, -1);
int pipEventId = intent.getIntExtra(FTXEvent.EVENT_PIP_MODE_NAME, -1);
Bundle callbackData = new Bundle();
Bundle data = intent.getExtras();
if ((pipEventId == FTXEvent.EVENT_PIP_MODE_ALREADY_EXIT
|| pipEventId == FTXEvent.EVENT_PIP_MODE_RESTORE_UI) && null != data) {
TXPipResult pipResult = data.getParcelable(FTXEvent.EXTRA_NAME_RESULT);
if (null != pipResult) {
callbackData.putDouble(FTXEvent.EVENT_PIP_PLAY_TIME, pipResult.getPlayTime());
handlePipResult(pipResult);
}
}
mPipEventSink.success(TXCommonUtil.getParams(pipEventId, callbackData));
}
}
};
/**
* Picture-in-picture management.
*
* 画中画管理
* @param mTxAudioManager Audio management, used to request audio focus in picture-in-picture mode.
* 音频管理,用于画中画模式下请求音频焦点
* @param activityBinding activityBinding
* @param flutterAssets Flutter resource management.
* flutter资源管理
*/
public FTXPIPManager(FTXAudioManager mTxAudioManager, @NonNull EventChannel pipEventChannel,
ActivityPluginBinding activityBinding, FlutterPlugin.FlutterAssets flutterAssets) {
this.mTxAudioManager = mTxAudioManager;
public FTXPIPManager(@NonNull EventChannel pipEventChannel, ActivityPluginBinding activityBinding,
FlutterPlugin.FlutterAssets flutterAssets) {
this.mPipEventChannel = pipEventChannel;
this.mActivityBinding = activityBinding;
this.mFlutterAssets = flutterAssets;
......@@ -125,13 +100,19 @@ public class FTXPIPManager {
*/
public void registerActivityListener() {
if (!mActivityBinding.getActivity().isDestroyed() && !misInit) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(FTXEvent.EVENT_PIP_ACTION);
mActivityBinding.getActivity().registerReceiver(mPipBroadcastReceiver, intentFilter);
TXSimpleEventBus.getInstance().register(FTXEvent.EVENT_PIP_ACTION, this);
TXSimpleEventBus.getInstance().register(FTXEvent.EVENT_PIP_PLAYER_EVENT_ACTION, this);
misInit = true;
}
}
private void handlePlayerEvent(int playerId, int eventId, Bundle params) {
PipCallback pipCallback = pipCallbacks.get(playerId);
if (null != pipCallback) {
pipCallback.onPipPlayerEvent(eventId, params);
}
}
private void handlePipResult(TXPipResult result) {
PipCallback pipCallback = pipCallbacks.get(result.getPlayerId());
if (null != pipCallback) {
......@@ -244,7 +225,8 @@ public class FTXPIPManager {
public void releaseActivityListener() {
try {
if (misInit) {
mActivityBinding.getActivity().unregisterReceiver(mPipBroadcastReceiver);
TXSimpleEventBus.getInstance().unregister(FTXEvent.EVENT_PIP_ACTION, this);
TXSimpleEventBus.getInstance().unregister(FTXEvent.EVENT_PIP_PLAYER_EVENT_ACTION, this);
}
} catch (Exception e) {
Log.getStackTraceString(e);
......@@ -270,6 +252,30 @@ public class FTXPIPManager {
return mFlutterAssets.getAssetFilePathByName(path);
}
@Override
public void onEvent(String eventType, Object data) {
if (TextUtils.equals(eventType, FTXEvent.EVENT_PIP_ACTION)) {
Bundle params = (Bundle) data;
int pipEventId = params.getInt(FTXEvent.EVENT_PIP_MODE_NAME, -1);
Bundle callbackData = new Bundle();
if ((pipEventId == FTXEvent.EVENT_PIP_MODE_ALREADY_EXIT
|| pipEventId == FTXEvent.EVENT_PIP_MODE_RESTORE_UI)) {
TXPipResult pipResult = params.getParcelable(FTXEvent.EXTRA_NAME_RESULT);
if (null != pipResult) {
callbackData.putDouble(FTXEvent.EVENT_PIP_PLAY_TIME, pipResult.getPlayTime());
handlePipResult(pipResult);
}
}
mPipEventSink.success(TXCommonUtil.getParams(pipEventId, callbackData));
} else if (TextUtils.equals(eventType, FTXEvent.EVENT_PIP_PLAYER_EVENT_ACTION)) {
Bundle params = (Bundle) data;
int playerId = params.getInt(FTXEvent.EXTRA_NAME_PLAYER_ID, -1);
int eventId = params.getInt(FTXEvent.EXTRA_NAME_PIP_PLAYER_EVENT_ID, -1);
Bundle playerEventParams = params.getBundle(FTXEvent.EXTRA_NAME_PIP_PLAYER_EVENT_PARAMS);
handlePlayerEvent(playerId, eventId, playerEventParams);
}
}
public static class PipParams implements Parcelable {
......@@ -381,6 +387,8 @@ public class FTXPIPManager {
return mViewHeight;
}
private AtomicInteger a = new AtomicInteger();
/**
* Construct PIP parameters.
* 构造画中画参数
......@@ -395,7 +403,7 @@ public class FTXPIPManager {
backData.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, mCurrentPlayerId);
Intent backIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(backData);
PendingIntent preIntent = PendingIntent.getBroadcast(activity, FTXEvent.EXTRA_PIP_PLAY_BACK, backIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
RemoteAction preAction = new RemoteAction(getBackIcon(activity), "skipPre", "skip pre", preIntent);
actions.add(preAction);
}
......@@ -408,8 +416,8 @@ public class FTXPIPManager {
Intent playOrPauseIntent =
new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(playOrPauseData);
Icon playIcon = mIsPlaying ? getPauseIcon(activity) : getPlayIcon(activity);
PendingIntent playIntent = PendingIntent.getBroadcast(activity, FTXEvent.EXTRA_PIP_PLAY_RESUME_OR_PAUSE,
playOrPauseIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent playIntent = PendingIntent.getBroadcast(activity, a.incrementAndGet(),
playOrPauseIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
RemoteAction playOrPauseAction = new RemoteAction(playIcon, "playOrPause", "play Or Pause", playIntent);
actions.add(playOrPauseAction);
}
......@@ -422,7 +430,7 @@ public class FTXPIPManager {
Intent forwardIntent = new Intent(FTXEvent.ACTION_PIP_PLAY_CONTROL).putExtras(forwardData);
PendingIntent nextIntent = PendingIntent.getBroadcast(activity, FTXEvent.EXTRA_PIP_PLAY_FORWARD,
forwardIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
RemoteAction nextAction = new RemoteAction(getForwardIcon(activity), "skipNext", "skip next",
nextIntent);
actions.add(nextAction);
......@@ -502,5 +510,7 @@ public class FTXPIPManager {
* pip关闭
*/
void onPipResult(TXPipResult result);
void onPipPlayerEvent(int event, Bundle bundle);
}
}
......@@ -61,7 +61,6 @@ public class FTXVodPlayer extends FTXBasePlayer implements ITXVodPlayListener, F
private static final String TAG = "FTXVodPlayer";
private FlutterPlugin.FlutterPluginBinding mFlutterPluginBinding;
private ActivityPluginBinding mActivityPluginBinding;
private final EventChannel mEventChannel;
private final EventChannel mNetChannel;
......@@ -99,6 +98,11 @@ public class FTXVodPlayer extends FTXBasePlayer implements ITXVodPlayListener, F
playerResume();
}
}
@Override
public void onPipPlayerEvent(int event, Bundle bundle) {
onPlayEvent(mVodPlayer, event, bundle);
}
};
/**
......@@ -106,8 +110,7 @@ public class FTXVodPlayer extends FTXBasePlayer implements ITXVodPlayListener, F
*
* 点播播放器
*/
public FTXVodPlayer(FlutterPlugin.FlutterPluginBinding flutterPluginBinding,
ActivityPluginBinding activityPluginBinding, FTXPIPManager pipManager) {
public FTXVodPlayer(FlutterPlugin.FlutterPluginBinding flutterPluginBinding, FTXPIPManager pipManager) {
super();
mPipManager = pipManager;
mFlutterPluginBinding = flutterPluginBinding;
......
......@@ -219,7 +219,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
@NonNull
@Override
public PlayerMsg createVodPlayer() {
FTXVodPlayer player = new FTXVodPlayer(mFlutterPluginBinding, mActivityPluginBinding, mTxPipManager);
FTXVodPlayer player = new FTXVodPlayer(mFlutterPluginBinding, mTxPipManager);
int playerId = player.getPlayerId();
mPlayers.append(playerId, player);
PlayerMsg playerMsg = new PlayerMsg();
......@@ -435,7 +435,7 @@ public class SuperPlayerPlugin implements FlutterPlugin, ActivityAware,
private void initPipManagerIfNeed() {
if (null == mTxPipManager) {
mTxPipManager = new FTXPIPManager(mTxAudioManager, mPipEventChannel, mActivityPluginBinding,
mTxPipManager = new FTXPIPManager(mPipEventChannel, mActivityPluginBinding,
mFlutterPluginBinding.getFlutterAssets());
}
}
......
package com.tencent.vod.flutter.tools;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* use to communicate with Activities frequently
*/
public class TXSimpleEventBus {
private static TXSimpleEventBus instance;
private final Map<String, List<EventSubscriber>> subscribers = new HashMap<>();
private TXSimpleEventBus() {
}
public static TXSimpleEventBus getInstance() {
if (instance == null) {
instance = new TXSimpleEventBus();
}
return instance;
}
public void register(String eventType, EventSubscriber subscriber) {
List<EventSubscriber> subscriberList = subscribers.get(eventType);
if (subscriberList == null) {
subscriberList = new ArrayList<>();
subscribers.put(eventType, subscriberList);
}
subscriberList.add(subscriber);
}
public void unregister(String eventType, EventSubscriber subscriber) {
List<EventSubscriber> subscriberList = subscribers.get(eventType);
if (subscriberList != null) {
subscriberList.remove(subscriber);
}
}
public void post(String eventType, Object data) {
List<EventSubscriber> subscriberList = subscribers.get(eventType);
if (subscriberList != null) {
for (EventSubscriber subscriber : subscriberList) {
subscriber.onEvent(eventType, data);
}
}
}
public interface EventSubscriber {
void onEvent(String eventType, Object data);
}
}
......@@ -43,6 +43,7 @@ import com.tencent.vod.flutter.FTXPIPManager.PipParams;
import com.tencent.vod.flutter.R;
import com.tencent.vod.flutter.model.TXPipResult;
import com.tencent.vod.flutter.model.TXVideoModel;
import com.tencent.vod.flutter.tools.TXSimpleEventBus;
import java.util.List;
import java.util.Set;
......@@ -80,6 +81,7 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
private boolean mIsRegisterReceiver = false;
private PipParams mCurrentParams;
private Handler mMainHandler;
private boolean mIsPipFinishing = false;
private final BroadcastReceiver pipActionReceiver = new BroadcastReceiver() {
@Override
......@@ -182,7 +184,7 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
needToExitPip = true;
} else {
if (isInPictureInPictureMode) {
sendPipBroadCast(FTXEvent.EVENT_PIP_MODE_ALREADY_ENTER, null);
sendPipEvent(FTXEvent.EVENT_PIP_MODE_ALREADY_ENTER, null);
showComponent();
} else {
handlePipExitEvent();
......@@ -199,7 +201,7 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
@Override
public void onPictureInPictureUiStateChanged(@NonNull PictureInPictureUiState pipState) {
super.onPictureInPictureUiStateChanged(pipState);
sendPipBroadCast(FTXEvent.EVENT_PIP_MODE_UI_STATE_CHANGED, null);
sendPipEvent(FTXEvent.EVENT_PIP_MODE_UI_STATE_CHANGED, null);
}
/**
......@@ -261,7 +263,7 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
data.putParcelable(FTXEvent.EXTRA_NAME_RESULT, pipResult);
}
int codeEvent = mIsNeedToStop ? FTXEvent.EVENT_PIP_MODE_ALREADY_EXIT : FTXEvent.EVENT_PIP_MODE_RESTORE_UI;
sendPipBroadCast(codeEvent, data);
sendPipEvent(codeEvent, data);
exitPip(codeEvent == FTXEvent.EVENT_PIP_MODE_ALREADY_EXIT);
}
......@@ -277,7 +279,7 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
if (TextUtils.equals(action, FTXEvent.PIP_ACTION_START)) {
startPipVideoFromIntent(intent);
} else if (TextUtils.equals(action, FTXEvent.PIP_ACTION_EXIT)) {
exitPip(false);
exitPip(true);
} else if (TextUtils.equals(action, FTXEvent.PIP_ACTION_UPDATE)) {
PipParams pipParams = intent.getParcelableExtra(FTXEvent.EXTRA_NAME_PARAMS);
updatePip(pipParams);
......@@ -326,7 +328,11 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
* mode and `false` to restore picture-in-picture mode.
*/
private void exitPip(boolean closeImmediately) {
if (!isDestroyed()) {
if (mIsPipFinishing) {
return;
}
mIsPipFinishing = true;
if (!isDestroyed() || !isFinishing()) {
// Due to the foreground service startup restriction in Android 12, if the activity interface is closed
// too early after returning from picture-in-picture mode, the app cannot be launched normally.
// Therefore, a delay processing is added here.
......@@ -340,8 +346,9 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
public void run() {
overridePendingTransition(0, 0);
finishAndRemoveTask();
mIsPipFinishing = false;
}
}, 400);
}, 500);
} else {
overridePendingTransition(0, 0);
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
......@@ -349,9 +356,10 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
} else {
finish();
}
mIsPipFinishing = false;
}
}
if (closeImmediately) {
if (!closeImmediately) {
moveAppToFront();
}
}
......@@ -497,15 +505,13 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
}
}
private void sendPipBroadCast(int eventCode, Bundle data) {
Intent intent = new Intent();
intent.setAction(FTXEvent.EVENT_PIP_ACTION);
intent.putExtra(FTXEvent.EVENT_PIP_MODE_NAME, eventCode);
intent.putExtra(FTXEvent.EXTRA_NAME_PLAYER_ID, mCurrentParams.getCurrentPlayerId());
if (null != data) {
intent.putExtras(data);
private void sendPipEvent(int eventCode, Bundle data) {
if (null == data) {
data = new Bundle();
}
sendBroadcast(intent);
data.putInt(FTXEvent.EVENT_PIP_MODE_NAME, eventCode);
data.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, mCurrentParams.getCurrentPlayerId());
TXSimpleEventBus.getInstance().post(FTXEvent.EVENT_PIP_ACTION, data);
}
/**
......@@ -536,11 +542,12 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
if (event == TXLiveConstants.PLAY_EVT_PLAY_END) {
// When playback is complete, automatically set the playback button to play.
mCurrentParams.setIsPlaying(false);
updatePip(mCurrentParams);
} else if (event == TXLiveConstants.PLAY_EVT_PLAY_BEGIN) {
// When playback starts, automatically set the playback button to pause.
mCurrentParams.setIsPlaying(true);
updatePip(mCurrentParams);
}
updatePip(mCurrentParams);
}
if (event == TXLiveConstants.PLAY_EVT_PLAY_END) {
// When playback is complete, automatically set the playback button to play.
......@@ -563,12 +570,21 @@ public class FlutterPipImplActivity extends Activity implements Callback, ITXVod
}
}
}
sendPlayerEvent(event, bundle);
}
@Override
public void onPlayEvent(int event, Bundle bundle) {
}
private void sendPlayerEvent(int eventCode, Bundle data) {
Bundle params = new Bundle();
params.putInt(FTXEvent.EXTRA_NAME_PLAYER_ID, mCurrentParams.getCurrentPlayerId());
params.putInt(FTXEvent.EXTRA_NAME_PIP_PLAYER_EVENT_ID, eventCode);
params.putBundle(FTXEvent.EXTRA_NAME_PIP_PLAYER_EVENT_PARAMS, data);
TXSimpleEventBus.getInstance().post(FTXEvent.EVENT_PIP_PLAYER_EVENT_ACTION, params);
}
@Override
public void onNetStatus(TXVodPlayer txVodPlayer, Bundle bundle) {
}
......
......@@ -16,7 +16,6 @@ class _DemoDownloadListState extends State<StatefulWidget> {
static const DEFAULT_PLACE_HOLDER = "http://xiaozhibo-10055601.file.myqcloud.com/coverImg.jpg";
List<DownloadModel> models = [];
SuperVodDataLoader loader = SuperVodDataLoader();
late FTXDownloadListener listener;
_DemoDownloadListState() {
......@@ -82,9 +81,7 @@ class _DemoDownloadListState extends State<StatefulWidget> {
model.videoId!.psign = mediaInfo.dataSource!.pSign ?? "";
model.playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
model.videoURL = mediaInfo.playPath!;
requestList.add(loader.getVideoData(model, (resultModel) {
tempModels.add(DownloadModel(resultModel, mediaInfo));
}));
tempModels.add(DownloadModel(model, mediaInfo));
} else {
SuperPlayerModel model = SuperPlayerModel();
model.isEnableDownload = false;
......
......@@ -35,8 +35,7 @@ class _DemoShortVideoPlayerState extends State<DemoShortVideoPlayer> with Widget
for (int i = 0; i < superPlayerModelList.length; i++) {
widgetList.add(ShortVideoPageWidget(
position: i,
videoUrl: superPlayerModelList[i].videoURL,
coverUrl: superPlayerModelList[i].coverUrl,
model: superPlayerModelList[i],
eventDispatcher: eventDispatcher,
));
}
......
......@@ -31,7 +31,6 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
List<SuperPlayerModel> videoModels = [];
bool _isFullScreen = false;
late SuperPlayerController _controller;
SuperVodDataLoader loader = SuperVodDataLoader();
StreamSubscription? simpleEventSubscription;
int tabSelectPos = 0;
SuperPlayerModel? currentVideoModel;
......@@ -254,10 +253,8 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
if (pSign.isNotEmpty) {
model.videoId!.psign = pSign;
}
loader.getVideoData(model, (resultModel) {
_addVideoToCurrentList(resultModel);
playCurrentModel(resultModel, 0);
});
_addVideoToCurrentList(model);
playCurrentModel(model, 0);
} else {
EasyLoading.showError(AppLocals.current.playerInputAddTip);
}
......@@ -330,6 +327,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "8602268011437356984";
model.title = AppLocals.current.playerVodVideo;
model.coverUrl = "http://1500005830.vod2.myqcloud.com/43843ec0vodtranscq1500005830/00eb06a88602268011437356984/coverBySnapshot/coverBySnapshot_10_0.jpg";
model.playAction = playAction;
model.isEnableDownload = true;
models.add(model);
......@@ -338,6 +336,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.appId = 1252463788;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "5285890781763144364";
model.coverUrl = "http://1252463788.vod2.myqcloud.com/95576ef5vodtransgzp1252463788/e1ab85305285890781763144364/1536584350_1812858038.100_0.jpg";
model.title = AppLocals.current.playerTencentCloud;
model.playAction = playAction;
model.isEnableDownload = false;
......@@ -347,6 +346,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.title = AppLocals.current.playerEncryptVideo;
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.coverUrl = "https://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/35ab25fb243791578431393746/onEqUp.png";
model.videoId!.fileId = "243791578431393746";
model.videoId!.psign =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTUwMDAwNTgzMCwiZmlsZUlkIjoiMjQzNzkxNTc4NDMxMzkzNzQ2IiwiY"
......@@ -362,6 +362,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "387702299774545556";
model.coverUrl = "http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/4fc091e4387702299774545556/387702299947278317.png";
model.playAction = playAction;
model.isEnableDownload = false;
model.title = AppLocals.current.playerVideoTitleIntroduction;
......@@ -371,6 +372,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "387702299774253670";
model.coverUrl = "http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/48d21c3d387702299774253670/387702299947604155.png";
model.playAction = playAction;
model.isEnableDownload = false;
model.title = AppLocals.current.playerVideoTitleEasy;
......@@ -380,6 +382,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "387702299774574470";
model.coverUrl = "http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/4ff64b01387702299774574470/387702304138941858.png";
model.playAction = playAction;
model.title = AppLocals.current.playerVideoTitleNumber;
model.isEnableDownload = false;
......@@ -389,6 +392,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "387702299774251236";
model.coverUrl = "http://1500005830.vod2.myqcloud.com/43843ec0vodtranscq1500005830/48d0f1f9387702299774251236/coverBySnapshot/coverBySnapshot_10_0.jpg";
model.playAction = playAction;
model.isEnableDownload = false;
model.title = AppLocals.current.playerVideoTitleAchievement;
......@@ -424,12 +428,6 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.title = "Multi-audio track video";
models.add(model);
List<Future<void>> requestList = [];
for (SuperPlayerModel tempModel in models) {
requestList.add(loader.getVideoData(tempModel, (_) {}));
}
await Future.wait(requestList);
videoModels.clear();
videoModels.addAll(models);
......
......@@ -16,8 +16,6 @@ class ShortVideoDataLoader {
];
List<SuperPlayerModel> _defaultData = [];
SuperVodDataLoader _loader = SuperVodDataLoader();
getPageListDataOneByOneFunction(Function(List<SuperPlayerModel> model) callback) {
_currentModels.clear();
for (int i = 0; i < _fileIdArray.length; i++) {
......@@ -36,9 +34,8 @@ class ShortVideoDataLoader {
}
Future<void> _getVodListData(SuperPlayerModel model, Function(List<SuperPlayerModel> models) callback) async {
await _loader.getVideoData(model, (resultModel) {
_currentModels.add(resultModel);
});
// mock net wait
_currentModels.add(model);
}
}
......@@ -2,12 +2,11 @@
part of demo_short_video_player_lib;
class ShortVideoPageWidget extends StatefulWidget {
String videoUrl;
String coverUrl;
SuperPlayerModel model;
int position;
VideoEventDispatcher eventDispatcher;
ShortVideoPageWidget({required this.position, required this.videoUrl, required this.coverUrl, required this.eventDispatcher});
ShortVideoPageWidget({required this.position, required this.model, required this.eventDispatcher});
@override
State<StatefulWidget> createState() {
......@@ -125,7 +124,7 @@ class _TXVodPlayerPageState extends State<ShortVideoPageWidget> {
decoration: BoxDecoration(
color: Colors.black,
image: DecorationImage(
image: NetworkImage(widget.coverUrl),
image: NetworkImage(widget.model.coverUrl),
fit: BoxFit.fill,
)),
child: Scaffold(
......@@ -169,7 +168,14 @@ class _TXVodPlayerPageState extends State<ShortVideoPageWidget> {
_isVideoPlaying = true;
});
await _controller.setLoop(true);
_controller.startVodPlay(widget.videoUrl);
final SuperPlayerModel model = widget.model;
if (model.videoURL.isNotEmpty) {
_controller.startVodPlay(model.videoURL);
} else if(model.videoId != null) {
_controller.startVodPlayWithParams(TXPlayInfoParams(appId: model.appId, fileId: model.videoId!.fileId, psign: model.videoId!.psign));
} else {
LogUtils.e(TAG, "shortVideo model source is empty$model");
}
}
_hideCover() {
......@@ -196,14 +202,22 @@ class _TXVodPlayerPageState extends State<ShortVideoPageWidget> {
_setPlayerListener() {
_playEventSubscription = _controller.onPlayerEventBroadcast.listen((event) async {
if (event["event"] == TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
final int eventCode = event["event"];
if (eventCode == TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
if (!mounted) return;
double currentProgress = event["EVT_PLAY_PROGRESS"].toDouble();
double videoDuration = event["EVT_PLAY_DURATION"].toDouble();
_progressSliderKey.currentState?.updateProgress(currentProgress / videoDuration, videoDuration);
} else if (event["event"] == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
} else if (eventCode == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
LogUtils.i(TAG, " [received] TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME");
_hideCover();
} else if (eventCode == TXVodPlayEvent.PLAY_EVT_GET_PLAYINFO_SUCC) {
String coverUrl = event[TXVodPlayEvent.EVT_PLAY_COVER_URL];
if (coverUrl.isNotEmpty) {
setState(() {
widget.model.coverUrl = coverUrl;
});
}
}
});
}
......
......@@ -1022,7 +1022,7 @@ BOOL CGImageRefContainsAlpha(CGImageRef imageRef) {
builder.playUrl = params.playUrl;
builder.keyLicenseUrl = params.licenseUrl;
if(params.deviceCertificateUrl) {
builder.deviceCertificateUrl = builder.deviceCertificateUrl;
builder.deviceCertificateUrl = params.deviceCertificateUrl;
}
int result = [_txVodPlayer startPlayDrm:builder];
return [TXCommonUtil intMsgWith:@(result)];
......
......@@ -228,6 +228,9 @@ abstract class TXVodPlayEvent {
// Playable duration of VOD (in milliseconds).
// 点播可播放时长(毫秒)
static const EVT_PLAYABLE_DURATION_MS = "EVT_PLAYABLE_DURATION_MS";
// Playable duration of VOD (in milliseconds).
// 点播可播放时长
static const EVT_PLAYABLE_DURATION = "EVT_PLAYABLE_DURATION";
// Playback rate.
// 播放速率
static const EVT_PLAYABLE_RATE = "EVT_PLAYABLE_RATE";
......
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// v2 request data parser
class PlayInfoParserV2 implements PlayInfoParser {
static const TAG = "PlayInfoParserV2";
@override
String name = "";
@override
String coverUrl = "";
@override
int duration = 0;
@override
String _url = "";
@override
List<ResolutionName> resolutionNameList = [];
@override
List<VideoQuality> videoQualityList = [];
@override
PlayImageSpriteInfo? imageSpriteInfo;
@override
List<PlayKeyFrameDescInfo>? keyFrameDescInfo;
@override
VideoQuality? defaultVideoQuality;
@override
String drmType = "";
@override
String token = "";
@override
List<EncryptedStreamingInfo> encryptedStreamingInfoList = [];
String defaultVideoClassification = "";
Map<String, PlayInfoStream> _transcodePlayList = new Map();
PlayInfoStream? sourceStream;
PlayInfoStream? masterPlayList; // Main video stream information.
List<VideoClassification> videoClassificationList = []; // List of video quality information.
PlayInfoParserV2(String json) {
_parseData(json);
}
void _parseData(String data) {
Map<String, dynamic> root = jsonDecode(data);
int code = root['code'];
String message = root['message'];
String warning = root['warning'];
if (code != 0) {
return;
}
int version = root['version'];
if (version == 2) {
coverUrl = root['coverInfo']['coverUrl'] ?? "";
Map<String, dynamic> videInfoRoot = root['videoInfo'];
defaultVideoClassification = root['playerInfo']['defaultVideoClassification'] ?? "";
_parseVideoClassificationList(root['playerInfo']);
imageSpriteInfo = _parseImageSpriteInfo(root['imageSpriteInfo']);
keyFrameDescInfo = _parseKeyFrameDescInfo(root['keyFrameDescInfo']);
duration = videInfoRoot['sourceVideo']['duration'] ?? 0;
name = videInfoRoot['basicInfo']['name'] ?? "";
_parseMastPlayInfo(videInfoRoot);
_parseTranscodeList(videInfoRoot);
_parseSourceVideo(videInfoRoot);
_parseVideoInfo();
}
}
List<PlayKeyFrameDescInfo>? _parseKeyFrameDescInfo(dynamic keyFrameDescInfo) {
if(null != keyFrameDescInfo && keyFrameDescInfo.isNotEmpty) {
List<dynamic> keyFrameDescList = keyFrameDescInfo['keyFrameDescList'];
if(keyFrameDescList.isNotEmpty) {
List<PlayKeyFrameDescInfo> infoList = [];
for(Map<String,dynamic> keyFrameInfo in keyFrameDescList) {
PlayKeyFrameDescInfo info = PlayKeyFrameDescInfo();
info.content = Uri.decodeFull(keyFrameInfo['content']);
int timeOffset = keyFrameInfo['timeOffset'];
info.time = (timeOffset / 1000.0); // Convert to seconds.
infoList.add(info);
}
return infoList;
}
}
return null;
}
PlayImageSpriteInfo? _parseImageSpriteInfo(dynamic imageSpriteInfo) {
if(null != imageSpriteInfo && imageSpriteInfo.isNotEmpty) {
List<dynamic> imageSpriteList = imageSpriteInfo['imageSpriteList'];
if(imageSpriteList.isNotEmpty) {
Map<String,dynamic> imageSpriteTemp = imageSpriteList[imageSpriteList.length - 1]; // Get the last one to parse.
PlayImageSpriteInfo info = PlayImageSpriteInfo();
info.webVttUrl = imageSpriteTemp['webVttUrl'];
// List<dynamic> can not transTo List<String>,so use loop assignment
List<dynamic> imageUrlsTemp = imageSpriteTemp['imageUrls'];
info.imageUrls = [];
for(String imageUrl in imageUrlsTemp) {
info.imageUrls.add(imageUrl);
}
return info;
}
}
return null;
}
void _parseVideoClassificationList(Map<String, dynamic> playerInfoRoot) {
List<VideoClassification> classList = [];
if(null != playerInfoRoot['videoClassification']) {
List<dynamic> videoClassificationRoot = playerInfoRoot['videoClassification'];
for(Map<String, dynamic> object in videoClassificationRoot) {
VideoClassification classification = new VideoClassification();
classification.id =object["id"];
classification.name = object["name"];
List<int> definitionList = [];
if(null != object['definitionList']) {
List<dynamic> array = object['definitionList'];
for(int definition in array) {
definitionList.add(definition);
}
}
classification.definitionList = definitionList;
classList.add(classification);
}
}
videoClassificationList = classList;
}
void _parseMastPlayInfo(Map<String, dynamic> videInfoRoot) {
if (null != videInfoRoot['masterPlayList']) {
Map<String, dynamic> masterPlayRoot = videInfoRoot['masterPlayList'];
masterPlayList = PlayInfoStream();
masterPlayList?.url = masterPlayRoot['url'];
}
}
void _parseTranscodeList(Map<String, dynamic> videInfoRoot) {
if (null != videInfoRoot['transcodeList']) {
List<dynamic> transcodeListRoot = videInfoRoot['transcodeList'];
List<PlayInfoStream> streamList = _parseStreamList(transcodeListRoot);
for(PlayInfoStream stream in streamList) {
if(videoClassificationList.isNotEmpty) {
for(VideoClassification classification in videoClassificationList) {
List<int> definitionList = classification.definitionList;
if(definitionList.contains(stream.definition)) {
stream.id = classification.id;
stream.name = classification.name;
}
}
}
}
// 清晰度去重
for (PlayInfoStream stream in streamList) {
if (!_transcodePlayList.containsKey(stream.id)) {
_transcodePlayList[stream.id] = stream;
} else {
PlayInfoStream copy = _transcodePlayList[stream.id] as PlayInfoStream;
if (copy.url.endsWith("mp4")) {
continue;
}
if (stream.url.endsWith("mp4")) {
_transcodePlayList[stream.id] = stream;
}
}
}
}
}
List<PlayInfoStream> _parseStreamList(List<dynamic> transcodeListRoot) {
List<PlayInfoStream> streamList = [];
for (Map<String, dynamic> videoStreamRoot in transcodeListRoot) {
PlayInfoStream playInfoStream = new PlayInfoStream();
playInfoStream.url = videoStreamRoot['url'];
playInfoStream.duration = videoStreamRoot['duration'];
playInfoStream.width = videoStreamRoot['width'];
playInfoStream.height = videoStreamRoot['height'];
playInfoStream.size = videoStreamRoot['size'];
playInfoStream.bitrate = videoStreamRoot['bitrate'];
playInfoStream.definition = videoStreamRoot['definition'];
streamList.add(playInfoStream);
}
return streamList;
}
void _parseSourceVideo(Map<String, dynamic> videInfoRoot) {
Map<String, dynamic> sourceVideoRoot = videInfoRoot['sourceVideo'];
sourceStream = PlayInfoStream();
sourceStream?.url = sourceVideoRoot['url'];
sourceStream?.duration = sourceVideoRoot['duration'];
sourceStream?.width = sourceVideoRoot['width'];
sourceStream?.height = sourceVideoRoot['height'];
sourceStream?.size = sourceVideoRoot['size'];
sourceStream?.bitrate = sourceVideoRoot['bitrate'];
}
void _parseVideoInfo() {
// If there is main playback video information, parse the URLs that support multi-bitrate playback from it.
if (null != masterPlayList) {
_url = masterPlayList!.url;
if (_transcodePlayList.isNotEmpty) {
PlayInfoStream? stream = _transcodePlayList[defaultVideoClassification];
videoQualityList =
VideoQualityUtils.convertToVideoQualityList(_transcodePlayList);
if (null != stream) {
defaultVideoQuality = VideoQualityUtils.convertToVideoQuality(stream);
}
}
return;
}
// If there is no main playback information, parse the bitstream information from the transcode video information.
if (_transcodePlayList.isNotEmpty) {
PlayInfoStream? stream = _transcodePlayList[defaultVideoClassification];
String? tempUrl;
if (stream != null) {
tempUrl = stream.url;
} else {
for (PlayInfoStream stream1 in _transcodePlayList.values) {
if (stream1 != null && stream1.url.isNotEmpty) {
stream = stream1;
tempUrl = stream1.url;
break;
}
}
}
if (tempUrl != null) {
videoQualityList =
VideoQualityUtils.convertToVideoQualityList(_transcodePlayList);
if (null != stream) {
defaultVideoQuality = VideoQualityUtils.convertToVideoQuality(stream);
}
_url = tempUrl;
return;
}
}
// If there is no main playback information or transcoding information,
// parse the playback information from the source video information
if (sourceStream != null) {
if (defaultVideoClassification != null) {
defaultVideoQuality = VideoQualityUtils.convertToVideoQuality(sourceStream!);
videoQualityList = [];
videoQualityList.add(defaultVideoQuality!);
}
_url = sourceStream!.url;
}
}
@override
String? getEncryptedURL(EncryptedURLType type) {
return null;
}
@override
String getUrl() {
return _url;
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// v4 request data parser
class PlayInfoParserV4 implements PlayInfoParser {
@override
String name = "";
@override
String coverUrl = "";
@override
String _url = "";
@override
int duration = 0;
@override
List<ResolutionName> resolutionNameList = [];
@override
List<VideoQuality> videoQualityList = [];
@override
PlayImageSpriteInfo? imageSpriteInfo;
@override
List<PlayKeyFrameDescInfo>? keyFrameDescInfo;
@override
VideoQuality? defaultVideoQuality;
@override
String drmType = "";
@override
String token = "";
@override
List<EncryptedStreamingInfo> encryptedStreamingInfoList = [];
String description = "";
String? mRequestContext; //透传字段
PlayInfoParserV4(String json) {
_parseData(json);
}
_parseData(String data) {
Map<String, dynamic> root = jsonDecode(data);
Map<String, dynamic> media = root['media'];
mRequestContext = root['context'];
if (media.isNotEmpty) {
if (null != media['basicInfo']) {
Map<String, dynamic> basicInfo = media['basicInfo'];
name = basicInfo['name'] ?? "";
description = basicInfo['description'] ?? "";
coverUrl = basicInfo['coverUrl'] ?? "";
duration = basicInfo['duration'] ?? "";
}
String audioVideoType = media['audioVideoType'];
if (audioVideoType == 'AdaptiveDynamicStream') {
if (null != media['streamingInfo']) {
Map<String, dynamic> streamingInfo = media['streamingInfo'];
if (null != streamingInfo['plainOutput']) {
Map<String, dynamic> plainOutputRoot = streamingInfo['plainOutput'];
_url = plainOutputRoot['url'] ?? "";
_parseSubStreams(plainOutputRoot['subStreams']);
}
if (null != streamingInfo['drmOutput']) {
List<dynamic> drmoutInfo = streamingInfo['drmOutput'];
encryptedStreamingInfoList = [];
for (Map<String, dynamic> drmout in drmoutInfo) {
EncryptedStreamingInfo info = new EncryptedStreamingInfo();
drmType = drmout['type'] ?? "";
info.drmType = drmType;
info.url = drmout['url'];
encryptedStreamingInfoList.add(info);
_parseSubStreams(drmout['subStreams']);
}
}
token = streamingInfo["drmToken"] ?? "";
}
} else if (audioVideoType == 'Transcode') {
Map<String, dynamic> transcodeInfo = media['transcodeInfo'];
if (transcodeInfo.isNotEmpty) {
_url = transcodeInfo['url'] ?? "";
}
} else if (audioVideoType == 'Original') {
Map<String, dynamic> originalInfo = media['originalInfo'];
if (originalInfo.isNotEmpty) {
_url = originalInfo['url'] ?? "";
}
}
if (null != media['imageSpriteInfo']) {
Map<String, dynamic> imageSpriteInfoJson = media['imageSpriteInfo'];
imageSpriteInfo = PlayImageSpriteInfo();
if (imageSpriteInfoJson != null) {
imageSpriteInfo?.webVttUrl = imageSpriteInfoJson['webVttUrl'] ?? "";
List<String> imageUrls = imageSpriteInfoJson['imageUrls'] == null
? List.empty()
: imageSpriteInfoJson['imageUrls'].cast<String>();
imageSpriteInfo?.imageUrls = imageUrls;
}
}
_parseKeyFrameDescList(media);
}
}
_parseSubStreams(dynamic substreams) {
if (null != substreams) {
List<dynamic> substreamList = substreams;
resolutionNameList = [];
for (Map<String, dynamic> substream in substreamList) {
ResolutionName resolutionName = ResolutionName();
resolutionName.width = substream['width'];
resolutionName.height = substream['height'];
resolutionName.name = substream['resolutionName'];
resolutionName.type = substream['type'];
resolutionNameList.add(resolutionName);
}
}
}
_parseKeyFrameDescList(Map<String, dynamic> media) {
if (null != media['keyFrameDescInfo']) {
Map<String, dynamic> keyFrameDescInfoJson = media['keyFrameDescInfo'];
keyFrameDescInfo = [];
List<Map<String, dynamic>> keyFrameDescList = keyFrameDescInfoJson['keyFrameDescList'];
for (Map<String, dynamic> keyFrameDesc in keyFrameDescList) {
PlayKeyFrameDescInfo info = PlayKeyFrameDescInfo();
info.time = keyFrameDesc['timeOffset'];
info.content = keyFrameDesc['content'];
keyFrameDescInfo?.add(info);
}
}
}
@override
String? getEncryptedURL(EncryptedURLType type) {
for (EncryptedStreamingInfo info in encryptedStreamingInfoList) {
if (info.drmType.toLowerCase() == type.value.toLowerCase()) {
return info.url;
}
}
return null;
}
@override
String getUrl() {
if (null != token && token.isNotEmpty) {
return getEncryptedURL(EncryptedURLType.SIMPLEAES)!;
}
return _url;
}
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// play info parser interface
abstract class PlayInfoParser {
// video duration
int duration = 0;
// cover url
String coverUrl = "";
// video url
String _url = "";
// video name
String name = "";
// v2 quality list
List<VideoQuality> videoQualityList = [];
// v4 resolutionName list
List<ResolutionName> resolutionNameList = [];
// video image sprite info
PlayImageSpriteInfo? imageSpriteInfo;
// key frame desc
List<PlayKeyFrameDescInfo>? keyFrameDescInfo;
// default quality,only support v2
VideoQuality? defaultVideoQuality;
// v4 drmType
String drmType = "";
// v4 token
String token = "";
// v4 encrypted url info
List<EncryptedStreamingInfo> encryptedStreamingInfoList = [];
// v4 encrypted url
String? getEncryptedURL(EncryptedURLType type);
// getUrl
String getUrl();
}
\ No newline at end of file
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// request handler with tencent fileId
class PlayInfoProtocol {
static const TAG = "PlayInfoProtocol";
static const _BASE_URLS_V4 = "https://playvideo.qcloud.com/getplayinfo/v4";
SuperPlayerModel _videoModel;
PlayInfoParser? _playInfoParser;
PlayInfoProtocol(this._videoModel);
/// send fileId reqeust
Future<void> sendRequest(Function(PlayInfoProtocol,SuperPlayerModel) onSuccess, Function(int errCode, String errorMsg) onError) async {
if (_videoModel.videoId == null || _videoModel.videoId!.fileId.isEmpty) {
return;
}
String url = _makeUrlString();
var httpClient = new HttpClient();
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode != HttpStatus.ok) {
onError(-1, "http request error.");
return;
}
var json = await response.transform(utf8.decoder).join();
if(json == null || json.isEmpty) {
onError(-1, "request return error!");
return;
}
Map<String, dynamic> root = jsonDecode(json);
int code = root['code'];
String message = root['message'];
String warning = root['warning'];
LogUtils.d(TAG, "_getVodListData,code=($code, ${PlayInfoProtocol.GETPLAYINFOV4_ERROR_CODE_MAP[code]}),message=$message,warning=$warning");
if (code != 0) {
onError(code, message);
return;
}
int version = root['version'];
if(version == 2) {
_playInfoParser = PlayInfoParserV2(json);
} else if(version == 4) {
_playInfoParser = PlayInfoParserV4(json);
}
onSuccess(this,_videoModel);
}
String _makeUrlString() {
String urlStr = "$_BASE_URLS_V4/${_videoModel.appId}/${_videoModel.videoId!.fileId}";
String? psign = makeJWTSignature();
String query = _videoModel.videoId != null ? makeQueryString(null, psign, null) : "";
if (query.isNotEmpty) {
urlStr = "$urlStr?$query";
}
LogUtils.d(TAG, "request url: $urlStr");
return urlStr;
}
String? makeJWTSignature() {
if (_videoModel.videoId != null && _videoModel.videoId!.psign.isNotEmpty) {
return _videoModel.videoId!.psign;
}
return null;
}
static String makeQueryString(String? pcfg, String? psign, String? content) {
String result = "";
if (null != pcfg) {
result += "pcfg=$pcfg&";
}
if(null != psign && psign.isNotEmpty) {
result += "psign=$psign&";
}
if (null != content && content.isNotEmpty) {
result += "context=$content&";
}
if (result.length > 1) {
result = result.substring(0, result.length - 1);
}
return result;
}
/// Get a 32-bit random string
/// 获取32位随机字符串
String genRandomHexString() {
List<String> hexArray = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
int keyLen = 32;
String result = "";
for (int i = 0; i < keyLen; i++) {
String randomChar = hexArray[Random().nextInt(hexArray.length)];
result += randomChar;
}
return result;
}
String? getUrl() {
return null == _playInfoParser ? null : _playInfoParser?.getUrl();
}
String? getName() {
return null == _playInfoParser ? null : _playInfoParser?.name;
}
PlayImageSpriteInfo? getImageSpriteInfo() {
return null == _playInfoParser ? null : _playInfoParser?.imageSpriteInfo;
}
String? getToken() {
return null == _playInfoParser ? null : _playInfoParser?.token;
}
String? getEncyptedUrl(EncryptedURLType type) {
return null == _playInfoParser ? null : _playInfoParser?.getEncryptedURL(type);
}
List<PlayKeyFrameDescInfo>? getKeyFrameDescInfo() {
return null == _playInfoParser ? null : _playInfoParser?.keyFrameDescInfo;
}
List<VideoQuality>? getVideoQualityList() {
return null == _playInfoParser ? null : _playInfoParser?.videoQualityList;
}
VideoQuality? getDefaultVideoQuality() {
return null == _playInfoParser ? null : _playInfoParser?.defaultVideoQuality;
}
List<ResolutionName>? getResolutionNameList() {
return null == _playInfoParser ? null : _playInfoParser?.resolutionNameList;
}
String? getDRMType() {
return null == _playInfoParser ? null : _playInfoParser?.drmType;
}
static String? getV4ErrorCodeDescription(int errorCode) {
return GETPLAYINFOV4_ERROR_CODE_MAP[errorCode];
}
///getplayinfo/v4 error codes
/// HTTP status codes: 200, 403
/// 403: Generally authentication information does not pass or request is invalid.
/// HTTP body is only available when the status code is 200.
/// Code error [1000-2000): Request issue.
/// Code error [2000-3000): Server error, retry can be initiated.
///
/// getplayinfo/v4错误码
/// http状态码 200 403
/// 403一般鉴权信息不通过或者请求不合法
/// 状态码为200的时候才会有http body
/// code错误码[1000-2000)请求有问题,
/// code错误码[2000-3000)服务端错误,可发起重试
static Map<int, String> GETPLAYINFOV4_ERROR_CODE_MAP = {
0 : 'success',
1001 : FSPLocal.current.txSpwErrFileNotExist,
1002 : FSPLocal.current.txSpwErrInvalidTrialDuration,
1003 : FSPLocal.current.txSpwErrPcfgNotUnique,
1004 : FSPLocal.current.txSpwErrLicenseExpired,
1005 : FSPLocal.current.txSpwErrNoAdaptiveStream,
1006 : FSPLocal.current.txSpwErrInvalidRequestFormat,
1007 : FSPLocal.current.txSpwErrNoUser,
1008 : FSPLocal.current.txSpwErrNoAntiLeechInfo,
1009 : FSPLocal.current.txSpwErrCheckSignFailed,
1010 : FSPLocal.current.txSpwErrOther,
2001 : FSPLocal.current.txSpwErrInternal,
};
}
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// request data handler
class SuperVodDataLoader {
static const TAG = "SuperVodDataLoader";
static const M3U8_SUFFIX = ".m3u8";
static const _BASE_URL = "https://playvideo.qcloud.com/getplayinfo/v4";
/// request data by fileId.this method will callback model that with http result
Future<void> getVideoData(SuperPlayerModel model,
Function(SuperPlayerModel resultModel) callback) async {
int appId = model.appId;
String field = model.videoId != null
? (model.videoId as SuperPlayerVideoId).fileId
: "";
String psign = model.videoId != null
? (model.videoId as SuperPlayerVideoId).psign
: "";
var url = _BASE_URL + "/$appId/$field";
var query = PlayInfoProtocol.makeQueryString(null, psign, null);
if (query.isNotEmpty) {
url = url + "?" + query;
}
var httpClient = HttpClient();
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode != HttpStatus.ok) {
callback(model);
return;
}
var json = await response.transform(utf8.decoder).join();
Map<String, dynamic> root = jsonDecode(json);
int code = root['code'];
String message = root['message'];
String warning = root['warning'];
LogUtils.d(TAG, "_getVodListData,code=($code, ${PlayInfoProtocol.getV4ErrorCodeDescription(code)}),message=$message,warning=$warning");
if (code != 0) {
callback(model);
return;
}
int version = root['version'];
if (version == 2) {
PlayInfoParserV2 parserV2 = PlayInfoParserV2(json);
model.coverUrl = parserV2.coverUrl;
model.duration = parserV2.duration;
_updateTitle(model, parserV2.name);
String url = parserV2.getUrl();
if(null != url && (url.contains(M3U8_SUFFIX) || parserV2.videoQualityList.isEmpty)) {
model.videoURL = url;
if(model.multiVideoURLs.isNotEmpty) {
model.multiVideoURLs.clear();
}
} else {
model.multiVideoURLs.clear();
List<VideoQuality> tempList = parserV2.videoQualityList;
tempList.sort((a,b) => b.bitrate.compareTo(a.bitrate)); // Sort the bitrates from high to low.
for(VideoQuality videoQuality in tempList) {
SuperPlayerUrl superPlayerUrl = SuperPlayerUrl();
superPlayerUrl.qualityName = videoQuality.title;
superPlayerUrl.url = videoQuality.url;
model.multiVideoURLs.add(superPlayerUrl);
}
}
} else if (version == 4) {
PlayInfoParserV4 parserV4 = PlayInfoParserV4(json);
String title = parserV4.description;
if(title == null || title.length == 0) {
title = parserV4.name;
}
_updateTitle(model, title);
model.coverUrl = parserV4.coverUrl;
model.duration = parserV4.duration;
if(null == parserV4.drmType || parserV4.drmType.isEmpty) {
model.videoURL = parserV4.getUrl();
}
}
callback(model);
}
/// remain user custom's title
void _updateTitle(SuperPlayerModel model, String newTitle) {
if(model.title == null || model.title.isEmpty) {
model.title = newTitle;
}
}
}
......@@ -17,11 +17,6 @@ import 'package:super_player/super_player.dart';
part 'superplayer_observer.dart';
part 'superplayer_controller.dart';
part 'cgi/play_info_parser_v2.dart';
part 'cgi/play_info_parser_v4.dart';
part 'cgi/playinfo_parser.dart';
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';
......
......@@ -38,6 +38,8 @@ class SuperPlayerViewEvent {
static const onStartFullScreenPlay = "onStartFullScreenPlay"; // Enter full screen playback
static const onStopFullScreenPlay = "onStopFullScreenPlay"; // Exit full screen playback
static const onSuperPlayerDidStart = "onSuperPlayerDidStart"; // Playback start notification
static const onSuperPlayerProgress = "onSuperPlayerProgress"; // Playback start notification
static const onSuperPlayerGetInfo = "onSuperPlayerGetInfo"; // Playback start notification
static const onSuperPlayerDidEnd = "onSuperPlayerDidEnd"; // Playback end notification
static const onSuperPlayerError = "onSuperPlayerError"; // Playback error notification
static const onSuperPlayerBackAction = "onSuperPlayerBackAction"; // Back event
......
......@@ -19,7 +19,6 @@ class SuperPlayerController {
SuperPlayerModel? videoModel;
int _playAction = SuperPlayerModel.PLAY_ACTION_AUTO_PLAY;
PlayInfoProtocol? _currentProtocol;
_SuperPlayerObserver? _observer;
VideoQuality? currentQuality;
List<VideoQuality>? currentQualityList;
......@@ -108,6 +107,7 @@ class SuperPlayerController {
});
_vodPlayerController.initImageSprite(playImageSpriteInfo.webVttUrl, playImageSpriteInfo.imageUrls);
spriteInfo = playImageSpriteInfo;
_addSimpleEvent(SuperPlayerViewEvent.onSuperPlayerGetInfo, params: event);
break;
case TXVodPlayEvent.PLAY_EVT_VOD_PLAY_PREPARED: // vodPrepared
isPrepared = true;
......@@ -167,7 +167,6 @@ class SuperPlayerController {
_updatePlayerState(SuperPlayerState.PLAYING);
break;
case TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME: // PLAY_EVT_RCV_FIRST_I_FRAME
_configVideoSize(event);
if (_needToPause) {
return;
}
......@@ -193,6 +192,7 @@ class SuperPlayerController {
}
if (videoDuration != 0) {
_observer?.onPlayProgress(currentDuration, videoDuration, await getPlayableDuration());
_addSimpleEvent(SuperPlayerViewEvent.onSuperPlayerProgress, params: event);
}
break;
case TXVodPlayEvent.PLAY_EVT_CHANGE_RESOLUTION:
......@@ -242,7 +242,6 @@ class SuperPlayerController {
_updatePlayerState(SuperPlayerState.LOADING);
break;
case TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME:
_configVideoSize(event);
_updatePlayerState(SuperPlayerState.PLAYING);
_observer?.onRcvFirstIframe();
break;
......@@ -347,20 +346,25 @@ class SuperPlayerController {
this.videoModel = videoModel;
_playAction = videoModel.playAction;
_updateImageSpriteAndKeyFrame(null, null);
_currentProtocol = null;
callResume = false;
// Priority use URL to play
if (videoModel.videoURL.isNotEmpty) {
_playWithUrl(videoModel);
getInfo(videoModel);
} else if (videoModel.videoId != null && (videoModel.videoId!.fileId.isNotEmpty)) {
_currentProtocol = PlayInfoProtocol(videoModel);
// When there is no URL, make a request based on the field
await _sendRequest();
_playWithField(videoModel);
}
}
Future<void> _playWithField(SuperPlayerModel model) async {
_setVodListener();
await _vodPlayerController.setToken(null);
_vodPlayerController.startVodPlayWithParams(TXPlayInfoParams(appId: model.appId,
fileId: model.videoId!.fileId, psign: model.videoId!.psign));
_updatePlayerType(SuperPlayerType.VOD);
_observer?.onPlayProgress(0, model.duration.toDouble(), await getPlayableDuration());
_updateImageSpriteAndKeyFrame(null, null);
}
void addSubTitle() {
if (videoModel != null && videoModel!.subtitleSources.isNotEmpty) {
for (FSubtitleSourceModel sourceModel in videoModel!.subtitleSources) {
......@@ -369,30 +373,6 @@ class SuperPlayerController {
}
}
void getInfo(SuperPlayerModel videoModel) {
PlayInfoProtocol temp = PlayInfoProtocol(videoModel);
temp.sendRequest((protocol, resultModel) async {
_updateImageSpriteAndKeyFrame(protocol.getImageSpriteInfo(), protocol.getKeyFrameDescInfo());
}, (errCode, errorMsg) {});
}
Future<void> _sendRequest() async {
_currentProtocol?.sendRequest((protocol, resultModel) async {
// onSuccess
if (videoModel != resultModel) {
return;
}
_playModeVideo(protocol);
_updatePlayerType(SuperPlayerType.VOD);
_observer?.onPlayProgress(0, resultModel.duration.toDouble(), await getPlayableDuration());
_updateImageSpriteAndKeyFrame(protocol.getImageSpriteInfo(), protocol.getKeyFrameDescInfo());
}, (errCode, message) {
// onError
_observer?.onError(SuperPlayerCode.VOD_REQUEST_FILE_ID_FAIL, FSPLocal.current.txSpwErrPlayTo.txFormat(["$errCode", message]));
_addSimpleEvent(SuperPlayerViewEvent.onSuperPlayerError);
});
}
void _updateImageSpriteAndKeyFrame(PlayImageSpriteInfo? spriteInfo, List<PlayKeyFrameDescInfo>? keyFrameInfo) {
_observer?.onVideoImageSpriteAndKeyFrameChanged(spriteInfo, keyFrameInfo);
if (null != spriteInfo) {
......@@ -436,18 +416,6 @@ class SuperPlayerController {
return await _vodPlayerController.getPlayableDuration();
}
void _playModeVideo(PlayInfoProtocol protocol) {
String? videoUrl = protocol.getUrl();
_playVodUrl(videoUrl);
List<VideoQuality>? qualityList = protocol.getVideoQualityList();
_isMultiBitrateStream = protocol.getResolutionNameList() != null ||
qualityList != null ||
(videoUrl != null && videoUrl.contains("m3u8"));
_updateVideoQualityList(qualityList, protocol.getDefaultVideoQuality());
}
void _playUrlVideo(SuperPlayerModel? model) {
if (model != null) {
if (model.multiVideoURLs != null && model.multiVideoURLs.isNotEmpty) {
......@@ -497,6 +465,7 @@ class SuperPlayerController {
_observer?.onPlayProgress(0, model.duration.toDouble(), 0);
_updatePlayerType(isLivePlay ? SuperPlayerType.LIVE : SuperPlayerType.VOD);
_updateVideoQualityList(videoQualities, defaultVideoQuality);
_updateImageSpriteAndKeyFrame(null, null);
}
Future<void> _playVodUrl(String? url) async {
......@@ -514,35 +483,9 @@ class SuperPlayerController {
await _vodPlayerController.setAutoPlay(isAutoPlay: true);
}
_setVodListener();
String drmType = "plain";
if (_currentProtocol != null) {
LogUtils.d(TAG, "TOKEN: ${_currentProtocol!.getToken()}");
await _vodPlayerController.setToken(_currentProtocol!.getToken());
if (_currentProtocol!.getDRMType() != null && _currentProtocol!.getDRMType()!.isNotEmpty) {
drmType = _currentProtocol!.getDRMType()!;
}
} else {
await _vodPlayerController.setToken(null);
}
if (videoModel!.videoId != null && videoModel!.appId != 0) {
Uri uri = Uri.parse(url);
String query = uri.query;
if (query == null || query.isEmpty) {
query = "";
} else {
query = "$query&";
if (query.contains("spfileid") || query.contains("spdrmtype") || query.contains("spappid")) {
LogUtils.d(TAG, "url contains superplay key. $query");
}
}
query += "spfileid=${videoModel!.videoId!.fileId}" "&spdrmtype=$drmType&spappid=${videoModel!.appId}";
Uri newUri = uri.replace(query: query);
LogUtils.d(TAG, 'playVodURL: newurl = ${Uri.decodeFull(newUri.toString())} ;url= $url');
await _vodPlayerController.startVodPlay(Uri.decodeFull(newUri.toString()));
} else {
LogUtils.d(TAG, "playVodURL url:$url");
await _vodPlayerController.startVodPlay(url);
}
await _vodPlayerController.setToken(null);
LogUtils.d(TAG, "playVodURL url:$url");
await _vodPlayerController.startVodPlay(url);
}
/// pause video
......@@ -681,10 +624,6 @@ 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) {
title = _currentProtocol!.getName()!;
}
return title;
}
......@@ -726,8 +665,9 @@ class SuperPlayerController {
_updateAudioTrackList(audioTrackInfoList, selectedTrack);
}
void _addSimpleEvent(String event) {
Map<String, String> eventMap = {};
void _addSimpleEvent(String event, {Map<dynamic, dynamic> params = const{}}) {
Map<dynamic, dynamic> eventMap = {};
eventMap.addAll(params);
eventMap['event'] = event;
_simpleEventStreamController.add(eventMap);
}
......@@ -774,7 +714,6 @@ class SuperPlayerController {
videoDuration = 0;
currentQuality = null;
currentQualityList?.clear();
_currentProtocol = null;
currentAudioTrackInfo = null;
audioTrackInfoList = null;
currentSubtitleTrackInfo = null;
......@@ -930,15 +869,15 @@ class SuperPlayerController {
LogUtils.d(TAG, "seek pos $_seekPos");
resetPlayer();
// When the `protocol` is empty, it means that the current video playback is a URL
if (_currentProtocol == null) {
_playUrlVideo(videoModel);
} else {
_playModeVideo(_currentProtocol!);
if (null != videoModel) {
_playWithModelInner(videoModel!);
}
}
} else {
await _vodPlayerController.enableHardwareDecode(enable);
await playWithModelNeedLicence(videoModel!);
if (null != videoModel) {
_playWithModelInner(videoModel!);
}
}
}
......
......@@ -164,8 +164,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_fullScreenController.switchToOrientation(orientation);
}
});
_registerObserver();
_initPlayerState();
_updateState();
}
void _registerObserver() {
......@@ -344,7 +343,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
// ensure that the playback status is correct.
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => setState(() {
_initPlayerState();
_resizeVideo();
_calculateSize(_playController.videoWidth, _playController.videoHeight);
}));
}
......@@ -411,23 +410,26 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_aspectRatio = 16.0 / 9.0;
}
} else {
double videoRadio = _videoWidth / _videoHeight;
double viewRadio = size.width / size.height;
if (_playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE) {
double playerHeight = isLandscape ? size.width : size.height;
// remain height
double videoRadio = _videoWidth / _videoHeight;
_radioHeight = playerHeight;
_radioWidth = playerHeight * videoRadio;
_aspectRatio = _radioWidth / _radioHeight;
if (videoRadio > viewRadio) {
_radioHeight = size.height;
_radioWidth = _radioHeight * videoRadio;
} else {
_radioWidth = size.width;
_radioHeight = _radioWidth / videoRadio;
}
} else {
double playerWidth = isLandscape ? size.height : size.width;
// remain width
double videoRadio = _videoWidth / _videoHeight;
_radioWidth = playerWidth;
_radioHeight = playerWidth / videoRadio;
_aspectRatio = _radioWidth / _radioHeight;
if (videoRadio > viewRadio) {
_radioWidth = size.width;
_radioHeight = _radioWidth / videoRadio;
} else {
_radioHeight = size.height;
_radioWidth = _radioHeight * videoRadio;
}
}
_aspectRatio = _radioWidth / _radioHeight;
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论