提交 cacc3d68 authored 作者: jiangjun's avatar jiangjun

实现android点播播放器截图

上级 6c5fa77a
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -7,6 +7,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
......@@ -15,6 +16,7 @@ import com.tencent.rtmp.ITXVodPlayListener;
import com.tencent.rtmp.TXBitrateItem;
import com.tencent.rtmp.TXImageSprite;
import com.tencent.rtmp.TXLiveConstants;
import com.tencent.rtmp.TXLivePlayer;
import com.tencent.rtmp.TXPlayInfoParams;
import com.tencent.rtmp.TXPlayerDrmBuilder;
import com.tencent.rtmp.TXTrackInfo;
......@@ -23,6 +25,7 @@ import com.tencent.rtmp.TXVodDef;
import com.tencent.rtmp.TXVodPlayConfig;
import com.tencent.rtmp.TXVodPlayer;
import com.tencent.rtmp.ui.TXCloudVideoView;
import com.tencent.ugc.TXRecordCommon;
import com.tencent.vod.flutter.FTXEvent;
import com.tencent.vod.flutter.FTXPIPManager;
import com.tencent.vod.flutter.FTXTransformation;
......@@ -52,6 +55,9 @@ import com.tencent.vod.flutter.ui.render.FTXRenderView;
import com.tencent.vod.flutter.ui.render.FTXRenderViewFactory;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
......@@ -74,6 +80,7 @@ public class FTXVodPlayer extends FTXVodPlayerRenderHost implements ITXVodPlayLi
private TXVodPlayer mVodPlayer;
private TXImageSprite mTxImageSprite;
private FTXRenderView mRenderView;
private static final int Uninitialized = -101;
private boolean mEnableHardwareDecode = true;
......@@ -272,6 +279,56 @@ public class FTXVodPlayer extends FTXVodPlayerRenderHost implements ITXVodPlayLi
}
}
@NonNull
@Override
public FtxMessages.SnapshotRespMsg snapshot(@NonNull FtxMessages.SnapshotReqMsg playerMsg) {
// mVodPlayer.snapshot(bitmap -> {
//
// });
if (mVodPlayer != null && mRenderView != null) {
final FtxMessages.SnapshotRespMsg.Builder respBuilder = new FtxMessages.SnapshotRespMsg.Builder();
final Bitmap bitmap = mRenderView.captureBitmap();
if (bitmap != null && playerMsg.getFilePath() != null) {
// 确定图片格式
Bitmap.CompressFormat format = (playerMsg.getFormat() != null && playerMsg.getFormat() == 1)
? Bitmap.CompressFormat.JPEG
: Bitmap.CompressFormat.PNG;
// 确定图片质量
int quality = 100;
if (playerMsg.getQuality() != null) {
quality = Math.max(0, Math.min(100, (int) (playerMsg.getQuality() * 100)));
}
// 保存图片
try {
File file = new File(playerMsg.getFilePath());
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(format, quality, out);
out.flush();
out.close();
respBuilder.setSuccess(true);
respBuilder.setReason("");
} catch (IOException e) {
Log.e(TAG, "save bitmap failed", e);
respBuilder.setSuccess(false);
respBuilder.setReason(e.getMessage());
}
} else {
respBuilder.setSuccess(false);
respBuilder.setReason("bitmap is null or file path is null");
}
return respBuilder.build();
}
// 如果播放器为空,返回失败结果
return new FtxMessages.SnapshotRespMsg.Builder()
.setSuccess(false)
.setReason("player is null or renderView is null")
.build();
}
protected long init(boolean onlyAudio) {
if (mVodPlayer == null) {
mVodPlayer = new TXVodPlayer(mFlutterPluginBinding.getApplicationContext());
......@@ -887,6 +944,7 @@ public class FTXVodPlayer extends FTXVodPlayerRenderHost implements ITXVodPlayLi
public void setPlayerView(@NonNull Long renderViewId) {
int viewId = renderViewId.intValue();
FTXRenderView renderView = mRenderViewFactory.findViewById(viewId);
mRenderView = renderView;
if (null == renderView) {
LiteavLog.e(TAG, "setPlayerView can not find renderView by id:"
+ viewId + ", release player's renderView");
......
package com.tencent.vod.flutter.player.render.gl;
import android.graphics.Bitmap;
import android.opengl.GLES20;
import java.nio.IntBuffer;
import android.graphics.SurfaceTexture;
import android.opengl.EGL14;
......@@ -498,4 +501,76 @@ public class FTXEGLRender implements SurfaceTexture.OnFrameAvailableListener {
}
}
/**
* 捕获当前渲染帧的图像
* @return Bitmap 截图结果,如果失败则返回null
*/
public Bitmap captureFrame() {
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY || mEGLSurfaceEncoder == EGL14.EGL_NO_SURFACE) {
LiteavLog.e(TAG, "Cannot capture frame, EGL not initialized");
return null;
}
try {
// 保存当前EGL环境
saveCurrentEglEnvironment();
// 设置当前EGL上下文
if (!makeCurrent(1)) {
LiteavLog.e(TAG, "Failed to make current context");
restoreEglEnvironment();
return null;
}
// 读取像素数据
int width = mViewWidth > 0 ? mViewWidth : mWidth;
int height = mViewHeight > 0 ? mViewHeight : mHeight;
if (width <= 0 || height <= 0) {
LiteavLog.e(TAG, "Invalid dimensions for capture: " + width + "x" + height);
restoreEglEnvironment();
return null;
}
// 创建缓冲区存储像素数据
int[] pixels = new int[width * height];
IntBuffer pixelBuffer = IntBuffer.wrap(pixels);
// 从OpenGL ES读取像素
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
// 检查是否有OpenGL错误
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
LiteavLog.e(TAG, "OpenGL error during glReadPixels: " + error);
restoreEglEnvironment();
return null;
}
// 恢复之前的EGL环境
restoreEglEnvironment();
// 创建Bitmap并转换像素格式(OpenGL坐标系与Bitmap坐标系不同,需要垂直翻转)
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
IntBuffer flippedBuffer = IntBuffer.allocate(width * height);
// 垂直翻转图像数据
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int srcIndex = (height - 1 - y) * width + x;
flippedBuffer.put(pixels[srcIndex]);
}
}
flippedBuffer.rewind();
bitmap.copyPixelsFromBuffer(flippedBuffer);
return bitmap;
} catch (Exception e) {
LiteavLog.e(TAG, "Error capturing frame", e);
restoreEglEnvironment();
return null;
}
}
}
package com.tencent.vod.flutter.ui.render;
import android.graphics.Bitmap;
import com.tencent.vod.flutter.player.render.FTXPlayerRenderSurfaceHost;
public interface FTXRenderCarrier {
......@@ -27,4 +29,6 @@ public interface FTXRenderCarrier {
void removeSurfaceTextureListener(FTXCarrierSurfaceListener listener);
void removeAllSurfaceListener();
Bitmap captureBitmap();
}
package com.tencent.vod.flutter.ui.render;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import android.view.ViewGroup;
......@@ -103,4 +104,16 @@ public class FTXRenderView implements PlatformView {
mContainer.setCarrier(null);
LiteavLog.i(TAG, "render view is dispose, id:" + mViewId + ", view:" + hashCode());
}
/**
* 截取当前纹理视图的图像
* @return Bitmap 截图结果,如果失败则返回null
*/
public Bitmap captureBitmap() {
if (mTextureView != null) {
return mTextureView.captureBitmap();
}
return null;
}
}
package com.tencent.vod.flutter.ui.render;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
......@@ -227,6 +228,14 @@ public class FTXSurfaceView extends SurfaceView implements FTXRenderCarrier {
mSurfaceListenerDelegate.mExternalSurfaceListeners.clear();
}
@Override
public Bitmap captureBitmap() {
if (mRender != null) {
return mRender.captureFrame();
}
return null;
}
private static class SurfaceViewInnerListener implements SurfaceHolder.Callback {
private final List<FTXCarrierSurfaceListener> mExternalSurfaceListeners = new CopyOnWriteArrayList<>();
......
package com.tencent.vod.flutter.ui.render;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import android.view.TextureView;
......@@ -24,7 +25,7 @@ public class FTXTextureView extends TextureView implements FTXRenderCarrier {
private FTXPlayerRenderSurfaceHost mPlayer;
private Surface mSurface;
private SurfaceTexture mSurfaceTexture;
private final GLSurfaceTools mGlSurfaceTools = new GLSurfaceTools();
private final GLSurfaceTools mGlSurfaceTools = new GLSurfaceTools();
private long mRenderMode = FTXPlayerConstants.FTXRenderMode.FULL_FILL_CONTAINER;
private int mVideoWidth = 0;
......@@ -284,4 +285,13 @@ public class FTXTextureView extends TextureView implements FTXRenderCarrier {
}
}
@Override
public Bitmap captureBitmap() {
if (mRender != null) {
return mRender.captureFrame();
}
return null;
}
}
......@@ -27,7 +27,7 @@ android {
namespace="com.example.flutter.player"
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.tencent.liteav.demo"
applicationId "com.hiteacher.industrylearner2"
minSdkVersion flutter.minSdkVersion
targetSdk 34
versionCode flutterVersionCode.toInteger()
......
......@@ -4,8 +4,8 @@
import 'dart:async';
// The obtained license URL
const LICENSE_URL = "";
const LICENSE_URL = "https://1390221090.trtcube-license.cn/license/v2/1390221090_1/v_cube.license";
// The obtained license key
const LICENSE_KEY = "";
const LICENSE_KEY = "ac0e94a28975a987b86c29429275ecf5";
Completer<bool> isLicenseSuc = Completer();
\ No newline at end of file
......@@ -320,6 +320,8 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
SuperPlayerModel model = SuperPlayerModel();
model.title = AppLocals.current.playerTestVideo;
model.videoURL = "http://liteavapp.qcloud.com/live/liteavdemoplayerstreamid_demo1080p.flv";
// model.videoURL = 'https://cdn.pixabay.com/video/2025/10/22/311442_large.mp4';
// model.videoURL = 'https://1257307760.vod2.myqcloud.com/ce465e88vodcq1257307760/df8e0dfa1253642701442822691/Km2GPDojwSEA.mp4';
model.coverUrl =
"http://1500005830.vod2.myqcloud.com/6c9a5118vodcq1500005830/66bc542f387702300661648850/0RyP1rZfkdQA.png";
model.playAction = playAction;
......
// Copyright (c) 2022 Tencent. All rights reserved.
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:path_provider/path_provider.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/res/app_localizations.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
......@@ -19,14 +21,19 @@ class DemoTXVodPlayer extends StatefulWidget {
_DemoTXVodPlayerState createState() => _DemoTXVodPlayerState();
}
class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingObserver {
class _DemoTXVodPlayerState extends State<DemoTXVodPlayer>
with WidgetsBindingObserver {
late TXVodPlayerController _controller;
double _currentProgress = 0.0;
bool _isMute = false;
int _volume = 100;
List _supportedBitrates = [];
int _curBitrateIndex = 0;
String _url = "https://1500005830.vod2.myqcloud.com/43843ec0vodtranscq1500005830/48d0f1f9387702299774251236/adp.10.m3u8";
// String _url = "http://1500005830.vod2.myqcloud.com/43843ec0vodtranscq1500005830/48d0f1f9387702299774251236/adp.10.m3u8";
String _url = 'https://cdn.pixabay.com/video/2025/10/22/311442_large.mp4';
// String _url = 'https://1257307760.vod2.myqcloud.com/ce465e88vodcq1257307760/df8e0dfa1253642701442822691/Km2GPDojwSEA.mp4';
TXPlayInfoParams? _videoParams;
int _appId = 0;
String _fileId = "";
......@@ -37,10 +44,12 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
StreamSubscription? playEventSubscription;
StreamSubscription? playNetEventSubscription;
FTXAndroidRenderViewType _renderType = FTXAndroidRenderViewType.SURFACE_VIEW;
FTXPlayerRenderMode _renderMode = FTXPlayerRenderMode.ADJUST_RESOLUTION;
FTXPlayerRenderMode _renderMode = FTXPlayerRenderMode.FULL_FILL_CONTAINER;
GlobalKey<VideoSliderViewState> progressSliderKey = GlobalKey();
String? _snapshotPath;
Future<void> init() async {
if (!mounted) return;
await SuperPlayerPlugin.setConsoleEnabled(true);
......@@ -48,25 +57,28 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
debugPrint("Playback status ${val?.name}");
});
LogUtils.logOpen = true;
playEventSubscription = _controller.onPlayerEventBroadcast.listen((event) async {
playEventSubscription =
_controller.onPlayerEventBroadcast.listen((event) async {
// Subscribe to event distribution
final int code = event["event"];
if (code == TXVodPlayEvent.PLAY_EVT_RCV_FIRST_I_FRAME) {
EasyLoading.dismiss();
_supportedBitrates = (await _controller.getSupportedBitrates())!;
} else if (code== TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
} else if (code == TXVodPlayEvent.PLAY_EVT_PLAY_PROGRESS) {
_isPlaying = true;
_currentProgress = event[TXVodPlayEvent.EVT_PLAY_PROGRESS].toDouble();
double videoDuration = event[TXVodPlayEvent.EVT_PLAY_DURATION].toDouble(); // Total playback time, converted unit in seconds
double videoDuration = event[TXVodPlayEvent.EVT_PLAY_DURATION]
.toDouble(); // Total playback time, converted unit in seconds
if (videoDuration == 0.0) {
progressSliderKey.currentState?.updateProgress(0.0, 0.0);
} else {
progressSliderKey.currentState?.updateProgress(_currentProgress / videoDuration, videoDuration);
progressSliderKey.currentState
?.updateProgress(_currentProgress / videoDuration, videoDuration);
}
} else if (code == TXVodPlayEvent.PLAY_EVT_PLAY_LOADING) {
EasyLoading.show(status: "loading");
} else if (code == TXVodPlayEvent.PLAY_EVT_VOD_LOADING_END || code == TXVodPlayEvent.PLAY_EVT_PLAY_BEGIN) {
} else if (code == TXVodPlayEvent.PLAY_EVT_VOD_LOADING_END ||
code == TXVodPlayEvent.PLAY_EVT_PLAY_BEGIN) {
EasyLoading.dismiss();
} else if (code != -100 && code < 0) {
EasyLoading.showToast("playError:$event");
......@@ -130,25 +142,24 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
height: 230,
color: Colors.black,
child: Center(
child:Container(
width: double.infinity,
height: double.infinity,
child: TXPlayerVideo(
androidRenderType: _renderType,
onRenderViewCreatedListener: (viewId) {
/// 此处只展示了最基础的纹理和播放器的配置方式。 这里可记录下来 viewId,在多纹理之间进行切换,比如横竖屏切换场景,竖屏的画面,
/// 要切换到横屏的画面,可以在切换到横屏之后,拿到横屏的viewId 设置上去。回到竖屏的时候,再通过 viewId 切换回来。
/// Only the most basic configuration methods for textures and the player are shown here.
/// The `viewId` can be recorded here to switch between multiple textures. For example, in the scenario
/// of switching between portrait and landscape orientations:
/// To switch from the portrait view to the landscape view, obtain the `viewId` of the landscape view
/// after switching to landscape orientation and set it. When switching back to portrait orientation,
/// switch back using the recorded `viewId`.
_controller.setPlayerView(viewId);
},
),
)
),
child: Container(
width: double.infinity,
height: double.infinity,
child: TXPlayerVideo(
androidRenderType: _renderType,
onRenderViewCreatedListener: (viewId) {
/// 此处只展示了最基础的纹理和播放器的配置方式。 这里可记录下来 viewId,在多纹理之间进行切换,比如横竖屏切换场景,竖屏的画面,
/// 要切换到横屏的画面,可以在切换到横屏之后,拿到横屏的viewId 设置上去。回到竖屏的时候,再通过 viewId 切换回来。
/// Only the most basic configuration methods for textures and the player are shown here.
/// The `viewId` can be recorded here to switch between multiple textures. For example, in the scenario
/// of switching between portrait and landscape orientations:
/// To switch from the portrait view to the landscape view, obtain the `viewId` of the landscape view
/// after switching to landscape orientation and set it. When switching back to portrait orientation,
/// switch back using the recorded `viewId`.
_controller.setPlayerView(viewId);
},
),
)),
),
VideoSliderView(_controller, progressSliderKey),
Expanded(
......@@ -167,10 +178,18 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
children: [
Container(
height: 100,
child: IconButton(icon: Image.asset('images/addp.png'), onPressed: () => {onPressed()}),
child: IconButton(
icon: Image.asset('images/addp.png'),
onPressed: () => {onPressed()}),
)
],
)),
Builder(builder: (context) {
if (_snapshotPath != null) {
return Image.file(File(_snapshotPath!));
}
return const SizedBox();
})
],
),
)),
......@@ -185,14 +204,21 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
_isPlaying = false;
_controller.pause();
}),
_createItem(AppLocals.current.playerVariableSpeedPlay, () {onClickSetRate();}),
_createItem(_isMute ? AppLocals.current.playerCancelMute : AppLocals.current.playerSetMute, () {
_createItem(AppLocals.current.playerVariableSpeedPlay, () {
onClickSetRate();
}),
_createItem(
_isMute
? AppLocals.current.playerCancelMute
: AppLocals.current.playerSetMute, () {
setState(() {
_isMute = !_isMute;
_controller.setMute(_isMute);
});
}),
_createItem(AppLocals.current.playerAdjustVolume, () {onClickVolume();}),
_createItem(AppLocals.current.playerAdjustVolume, () {
onClickVolume();
}),
_createItem(AppLocals.current.playerSwitchBitrate, () {
if (_supportedBitrates.length > 1) {
onClickBitrate();
......@@ -202,7 +228,8 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
}),
_createItem(AppLocals.current.playerPlaybackDuration, () async {
double time = await _controller.getCurrentPlaybackTime();
EasyLoading.showToast('${time.toStringAsFixed(2)}${AppLocals.current.playerSecond}');
EasyLoading.showToast(
'${time.toStringAsFixed(2)}${AppLocals.current.playerSecond}');
}),
_createItem(AppLocals.current.playerVideoSize, () async {
int width = await _controller.getWidth();
......@@ -213,11 +240,15 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
bool isLoop = await _controller.isLoop();
EasyLoading.showToast('isLoop:$isLoop');
}),
_createItem(enableHardware ? AppLocals.current.playerSwitchSoft : AppLocals.current.playerSwitchHard, () async {
_createItem(
enableHardware
? AppLocals.current.playerSwitchSoft
: AppLocals.current.playerSwitchHard, () async {
TXPlayerState? state = _controller.playState;
if (state != TXPlayerState.disposed && state != TXPlayerState.stopped) {
enableHardware = !enableHardware;
bool enableSuccess = await _controller.enableHardwareDecode(enableHardware);
bool enableSuccess =
await _controller.enableHardwareDecode(enableHardware);
double startTime = await _controller.getCurrentPlaybackTime();
await _controller.setStartTime(startTime);
if (_url.isNotEmpty) {
......@@ -228,11 +259,15 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
EasyLoading.showError("video source is not exists");
}
setState(() {});
String wareMode = enableHardware ? AppLocals.current.playerHardEncode : AppLocals.current.playerSoftEncode;
String wareMode = enableHardware
? AppLocals.current.playerHardEncode
: AppLocals.current.playerSoftEncode;
if (enableSuccess) {
EasyLoading.showToast(AppLocals.current.playerSwitchSucTo.txFormat([wareMode]));
EasyLoading.showToast(
AppLocals.current.playerSwitchSucTo.txFormat([wareMode]));
} else {
EasyLoading.showToast(AppLocals.current.playerSwitchFailedTo.txFormat([wareMode]));
EasyLoading.showToast(
AppLocals.current.playerSwitchFailedTo.txFormat([wareMode]));
}
} else {
EasyLoading.showToast(AppLocals.current.playerPlayEnd);
......@@ -240,11 +275,13 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
}),
_createItem(AppLocals.current.playerPlayableTime, () async {
double time = await _controller.getPlayableDuration();
EasyLoading.showToast(AppLocals.current.playerPlayableDurationTo.txFormat([time.toString()]));
EasyLoading.showToast(AppLocals.current.playerPlayableDurationTo
.txFormat([time.toString()]));
}),
_createItem(_renderMode == FTXPlayerRenderMode.ADJUST_RESOLUTION
? AppLocals.current.playerRenderModeAdjust
: AppLocals.current.playerRenderModeFill, () async {
_createItem(
_renderMode == FTXPlayerRenderMode.ADJUST_RESOLUTION
? AppLocals.current.playerRenderModeAdjust
: AppLocals.current.playerRenderModeFill, () async {
if (_renderMode == FTXPlayerRenderMode.ADJUST_RESOLUTION) {
_renderMode = FTXPlayerRenderMode.FULL_FILL_CONTAINER;
} else {
......@@ -253,13 +290,22 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
_controller.setRenderMode(_renderMode);
setState(() {});
}),
_createItem('截图', () async {
String savePath =
await getTemporaryDirectory().then((value) => value.path);
_snapshotPath = await _controller.snapshot(saveDirPath: savePath);
if (mounted) {
setState(() {});
}
}),
];
/// iOS does not have this capability.
if (defaultTargetPlatform != TargetPlatform.iOS) {
children.add(_createItem(AppLocals.current.playerCacheTime, () async {
double time = await _controller.getBufferDuration();
EasyLoading.showToast('${time.toStringAsFixed(2)}${AppLocals.current.playerSecond}');
EasyLoading.showToast(
'${time.toStringAsFixed(2)}${AppLocals.current.playerSecond}');
}));
}
return children;
......@@ -292,32 +338,43 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
showDialog(
context: context,
builder: (context) {
return DemoInputDialog("", 0, "", (String url, int appId, String fileId, String pSign, bool enableDownload, bool isDrm) {
_url = url;
_appId = appId;
_fileId = fileId;
_controller.setStartTime(0);
FTXAndroidRenderViewType dstRenderType;
if (isDrm) {
dstRenderType = FTXAndroidRenderViewType.DRM_SURFACE_VIEW;
} else {
dstRenderType = _renderType;
}
if (dstRenderType != _renderType) {
setState(() {
_renderType = dstRenderType;
});
}
if (url.isNotEmpty) {
_videoParams = null;
_controller.startVodPlay(url);
} else if (appId != 0 && fileId.isNotEmpty) {
_controller.stop(isNeedClear: true);
TXPlayInfoParams params = TXPlayInfoParams(appId: _appId, fileId: _fileId, psign: pSign != null ? pSign : "");
_videoParams = params;
_controller.startVodPlayWithParams(params);
}
}, needPisgn: true, needDrm: true,);
return DemoInputDialog(
"",
0,
"",
(String url, int appId, String fileId, String pSign,
bool enableDownload, bool isDrm) {
_url = url;
_appId = appId;
_fileId = fileId;
_controller.setStartTime(0);
FTXAndroidRenderViewType dstRenderType;
if (isDrm) {
dstRenderType = FTXAndroidRenderViewType.DRM_SURFACE_VIEW;
} else {
dstRenderType = _renderType;
}
if (dstRenderType != _renderType) {
setState(() {
_renderType = dstRenderType;
});
}
if (url.isNotEmpty) {
_videoParams = null;
_controller.startVodPlay(url);
} else if (appId != 0 && fileId.isNotEmpty) {
_controller.stop(isNeedClear: true);
TXPlayInfoParams params = TXPlayInfoParams(
appId: _appId,
fileId: _fileId,
psign: pSign != null ? pSign : "");
_videoParams = params;
_controller.startVodPlayWithParams(params);
}
},
needPisgn: true,
needDrm: true,
);
});
}
......@@ -347,7 +404,8 @@ class _DemoTXVodPlayerState extends State<DemoTXVodPlayer> with WidgetsBindingOb
showDialog(
context: context,
builder: (context) {
return DemoBitrateCheckbox(_supportedBitrates, _curBitrateIndex, (int result) {
return DemoBitrateCheckbox(_supportedBitrates, _curBitrateIndex,
(int result) {
_curBitrateIndex = result;
_controller.setBitrateIndex(_curBitrateIndex);
EasyLoading.showSuccess(AppLocals.current.playerSwitchSuc);
......
......@@ -37,7 +37,8 @@ class _MyAppState extends State<MyApp> {
initPlayerLicense();
initPlatformState();
_getFlutterSdkVersion();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
// SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setPreferredOrientations([]);
LogUtils.logOpen = true;
}
......
......@@ -33,6 +33,8 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
path_provider: 2.1.5
dependency_overrides:
# force override flutter_easyloading dep
flutter_spinkit: 5.1.0
......
This source diff could not be displayed because it is too large. You can view the blob instead.
// Copyright (c) 2022 Tencent. All rights reserved.
part of SuperPlayer;
class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TXPlayerValue?>,
TXPlayerController, TXVodPlayerFlutterAPI {
class TXVodPlayerController extends ChangeNotifier
implements
ValueListenable<TXPlayerValue?>,
TXPlayerController,
TXVodPlayerFlutterAPI {
int? _playerId = -1;
static String kTag = "TXVodPlayerController";
......@@ -12,6 +15,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
bool _isNeedDisposed = false;
TXPlayerValue? _value;
TXPlayerState? _state;
TXPlayerState? get playState => _state;
@override
......@@ -30,9 +34,12 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
double? videoRight = 0;
double? videoBottom = 0;
final StreamController<TXPlayerState?> _stateStreamController = StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _eventStreamController = StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _netStatusStreamController = StreamController.broadcast();
final StreamController<TXPlayerState?> _stateStreamController =
StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _eventStreamController =
StreamController.broadcast();
final StreamController<Map<dynamic, dynamic>> _netStatusStreamController =
StreamController.broadcast();
/// Playback State Listener
///
......@@ -44,16 +51,17 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
///
/// 播放事件监听
/// @see:https://cloud.tencent.com/document/product/454/7886#.E6.92.AD.E6.94.BE.E4.BA.8B.E4.BB.B6
Stream<Map<dynamic, dynamic>> get onPlayerEventBroadcast => _eventStreamController.stream;
Stream<Map<dynamic, dynamic>> get onPlayerEventBroadcast =>
_eventStreamController.stream;
/// VOD player network status callback
///
/// 点播播放器网络状态回调
/// see:https://cloud.tencent.com/document/product/454/7886#.E6.92.AD.E6.94.BE.E4.BA.8B.E4.BB.B6
Stream<Map<dynamic, dynamic>> get onPlayerNetStatusBroadcast => _netStatusStreamController.stream;
Stream<Map<dynamic, dynamic>> get onPlayerNetStatusBroadcast =>
_netStatusStreamController.stream;
TXVodPlayerController({bool? onlyAudio})
: _initPlayer = Completer() {
TXVodPlayerController({bool? onlyAudio}) : _initPlayer = Completer() {
_value = TXPlayerValue.uninitialized();
_state = _value!.state;
_create(onlyAudio: onlyAudio);
......@@ -61,8 +69,10 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<void> _create({bool? onlyAudio}) async {
_playerId = await SuperPlayerPlugin.createVodPlayer(onlyAudio: onlyAudio);
_vodPlayerApi = TXFlutterVodPlayerApi(messageChannelSuffix: _playerId.toString());
TXVodPlayerFlutterAPI.setUp(this, messageChannelSuffix: _playerId.toString());
_vodPlayerApi =
TXFlutterVodPlayerApi(messageChannelSuffix: _playerId.toString());
TXVodPlayerFlutterAPI.setUp(this,
messageChannelSuffix: _playerId.toString());
_initPlayer.complete(_playerId);
}
......@@ -74,8 +84,10 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
void printVersionInfo() async {
LogUtils.d(kTag, "dart SDK version:${Platform.version}");
LogUtils.d(kTag, "liteAV SDK version:${await SuperPlayerPlugin.platformVersion}");
LogUtils.d(kTag, "superPlayer SDK version:${FPlayerPckInfo.PLAYER_VERSION}");
LogUtils.d(
kTag, "liteAV SDK version:${await SuperPlayerPlugin.platformVersion}");
LogUtils.d(
kTag, "superPlayer SDK version:${FPlayerPckInfo.PLAYER_VERSION}");
}
/// Starting from version 10.7, the method `startPlay` has been changed to `startVodPlay` for playing videos via a URL.
......@@ -188,7 +200,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<bool> isPlaying() async {
if (_isNeedDisposed) return false;
await _initPlayer.future;
BoolMsg boolMsg = await _vodPlayerApi.isPlaying(PlayerMsg()..playerId = _playerId);
BoolMsg boolMsg =
await _vodPlayerApi.isPlaying(PlayerMsg()..playerId = _playerId);
return boolMsg.value ?? false;
}
......@@ -292,7 +305,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<List?> getSupportedBitrates() async {
if (_isNeedDisposed) return [];
await _initPlayer.future;
ListMsg listMsg = await _vodPlayerApi.getSupportedBitrate(PlayerMsg()..playerId = _playerId);
ListMsg listMsg = await _vodPlayerApi
.getSupportedBitrate(PlayerMsg()..playerId = _playerId);
return listMsg.value;
}
......@@ -302,7 +316,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<int> getBitrateIndex() async {
if (_isNeedDisposed) return -1;
await _initPlayer.future;
IntMsg intMsg = await _vodPlayerApi.getBitrateIndex(PlayerMsg()..playerId = _playerId);
IntMsg intMsg =
await _vodPlayerApi.getBitrateIndex(PlayerMsg()..playerId = _playerId);
return intMsg.value ?? -1;
}
......@@ -375,7 +390,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<double> getCurrentPlaybackTime() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
DoubleMsg doubleMsg = await _vodPlayerApi.getCurrentPlaybackTime(PlayerMsg()..playerId = _playerId);
DoubleMsg doubleMsg = await _vodPlayerApi
.getCurrentPlaybackTime(PlayerMsg()..playerId = _playerId);
return doubleMsg.value ?? 0;
}
......@@ -385,7 +401,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<double> getBufferDuration() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
DoubleMsg doubleMsg = await _vodPlayerApi.getBufferDuration(PlayerMsg()..playerId = _playerId);
DoubleMsg doubleMsg = await _vodPlayerApi
.getBufferDuration(PlayerMsg()..playerId = _playerId);
return doubleMsg.value ?? 0;
}
......@@ -395,7 +412,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<double> getPlayableDuration() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
DoubleMsg doubleMsg = await _vodPlayerApi.getPlayableDuration(PlayerMsg()..playerId = _playerId);
DoubleMsg doubleMsg = await _vodPlayerApi
.getPlayableDuration(PlayerMsg()..playerId = _playerId);
return doubleMsg.value ?? 0;
}
......@@ -405,7 +423,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<int> getWidth() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
IntMsg intMsg = await _vodPlayerApi.getWidth(PlayerMsg()..playerId = _playerId);
IntMsg intMsg =
await _vodPlayerApi.getWidth(PlayerMsg()..playerId = _playerId);
return intMsg.value ?? 0;
}
......@@ -415,7 +434,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<int> getHeight() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
IntMsg intMsg = await _vodPlayerApi.getHeight(PlayerMsg()..playerId = _playerId);
IntMsg intMsg =
await _vodPlayerApi.getHeight(PlayerMsg()..playerId = _playerId);
return intMsg.value ?? 0;
}
......@@ -436,7 +456,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<bool> isLoop() async {
if (_isNeedDisposed) return false;
await _initPlayer.future;
BoolMsg boolMsg = await _vodPlayerApi.isLoop(PlayerMsg()..playerId = _playerId);
BoolMsg boolMsg =
await _vodPlayerApi.isLoop(PlayerMsg()..playerId = _playerId);
return boolMsg.value ?? false;
}
......@@ -470,15 +491,19 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// 使用系统默认图标,只支持flutter本地资源图片,传递的时候,与flutter使用图片资源一致,例如: images/back_icon.png
@override
Future<int> enterPictureInPictureMode(
{String? backIconForAndroid, String? playIconForAndroid, String? pauseIconForAndroid, String? forwardIconForAndroid}) async {
{String? backIconForAndroid,
String? playIconForAndroid,
String? pauseIconForAndroid,
String? forwardIconForAndroid}) async {
if (_isNeedDisposed) return -1;
await _initPlayer.future;
IntMsg intMsg = await _vodPlayerApi.enterPictureInPictureMode(PipParamsPlayerMsg()
..backIconForAndroid = backIconForAndroid
..playIconForAndroid = playIconForAndroid
..pauseIconForAndroid = pauseIconForAndroid
..forwardIconForAndroid = forwardIconForAndroid
..playerId = _playerId);
IntMsg intMsg =
await _vodPlayerApi.enterPictureInPictureMode(PipParamsPlayerMsg()
..backIconForAndroid = backIconForAndroid
..playIconForAndroid = playIconForAndroid
..pauseIconForAndroid = pauseIconForAndroid
..forwardIconForAndroid = forwardIconForAndroid
..playerId = _playerId);
return intMsg.value ?? -1;
}
......@@ -493,9 +518,10 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<Uint8List?> getImageSprite(double time) async {
await _initPlayer.future;
UInt8ListMsg int8listMsg = await _vodPlayerApi.getImageSprite(DoublePlayerMsg()
..value = time
..playerId = _playerId);
UInt8ListMsg int8listMsg =
await _vodPlayerApi.getImageSprite(DoublePlayerMsg()
..value = time
..playerId = _playerId);
return int8listMsg.value;
}
......@@ -505,7 +531,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<double> getDuration() async {
if (_isNeedDisposed) return 0;
await _initPlayer.future;
DoubleMsg doubleMsg = await _vodPlayerApi.getDuration(PlayerMsg()..playerId = _playerId);
DoubleMsg doubleMsg =
await _vodPlayerApi.getDuration(PlayerMsg()..playerId = _playerId);
return doubleMsg.value ?? 0;
}
......@@ -516,7 +543,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<void> exitPictureInPictureMode() async {
if (_isNeedDisposed) return;
await _initPlayer.future;
await _vodPlayerApi.exitPictureInPictureMode(PlayerMsg()..playerId = _playerId);
await _vodPlayerApi
.exitPictureInPictureMode(PlayerMsg()..playerId = _playerId);
}
/// This interface is only supported by the premium version of the player (Player_Premium),
......@@ -533,10 +561,13 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
/// @param name 字幕的名字。如果添加多个字幕,字幕名称请设置为不同的名字,用于区分与其他添加的字幕,否则可能会导致字幕选择错误。
/// @param mimeType 字幕类型,仅支持VVT和SRT格式 [VOD_PLAY_MIMETYPE_TEXT_SRT] [VOD_PLAY_MIMETYPE_TEXT_VTT]
/// 后面可以通过[getSubtitleTrackInfo]返回结果中的 name 获取对应的名字
Future<void> addSubtitleSource(String url, String name, {String? mimeType}) async {
Future<void> addSubtitleSource(String url, String name,
{String? mimeType}) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
await _vodPlayerApi.addSubtitleSource(SubTitlePlayerMsg(url: url, name: name, mimeType: mimeType)..playerId = _playerId);
await _vodPlayerApi.addSubtitleSource(
SubTitlePlayerMsg(url: url, name: name, mimeType: mimeType)
..playerId = _playerId);
}
/// This interface is only supported by the premium version of the player (Player_Premium),
......@@ -548,12 +579,14 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<List<TXTrackInfo>> getSubtitleTrackInfo() async {
if (_isNeedDisposed) return [];
await _initPlayer.future;
ListMsg listMsg = await _vodPlayerApi.getSubtitleTrackInfo(PlayerMsg(playerId: _playerId));
ListMsg listMsg = await _vodPlayerApi
.getSubtitleTrackInfo(PlayerMsg(playerId: _playerId));
if (null != listMsg.value) {
List<dynamic>? transInfoData = listMsg.value!;
List<TXTrackInfo> trackInfoList = [];
for (Map<dynamic, dynamic> map in transInfoData) {
TXTrackInfo trackInfo = TXTrackInfo(map["name"], map["trackIndex"], map["trackType"]);
TXTrackInfo trackInfo =
TXTrackInfo(map["name"], map["trackIndex"], map["trackType"]);
trackInfo.isSelected = map["isSelected"] ?? false;
trackInfo.isExclusive = map["isExclusive"] ?? true;
trackInfo.isInternal = map["isInternal"] ?? true;
......@@ -573,12 +606,14 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
Future<List<TXTrackInfo>> getAudioTrackInfo() async {
if (_isNeedDisposed) return [];
await _initPlayer.future;
ListMsg listMsg = await _vodPlayerApi.getAudioTrackInfo(PlayerMsg(playerId: _playerId));
ListMsg listMsg =
await _vodPlayerApi.getAudioTrackInfo(PlayerMsg(playerId: _playerId));
if (null != listMsg.value) {
List<dynamic>? transInfoData = listMsg.value!;
List<TXTrackInfo> trackInfoList = [];
for (Map<dynamic, dynamic> map in transInfoData) {
TXTrackInfo trackInfo = TXTrackInfo(map["name"], map["trackIndex"], map["trackType"]);
TXTrackInfo trackInfo =
TXTrackInfo(map["name"], map["trackIndex"], map["trackType"]);
trackInfo.isSelected = map["isSelected"] ?? false;
trackInfo.isExclusive = map["isExclusive"] ?? true;
trackInfo.isInternal = map["isInternal"] ?? true;
......@@ -630,7 +665,7 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
..value = [value]);
}
Future<void>setPlayerView(int renderViewId) async{
Future<void> setPlayerView(int renderViewId) async {
if (_isNeedDisposed) return;
await _initPlayer.future;
await _vodPlayerApi.setPlayerView(renderViewId);
......@@ -652,6 +687,30 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
await _vodPlayerApi.reDraw();
}
///
/// only valid on Android
///
Future<String?> snapshot({
required String saveDirPath,
// 1: jpg other: png
int format = 1,
double quality = 1.0,
}) async {
final path =
'$saveDirPath/${DateTime.now().millisecondsSinceEpoch.toString()}';
final res = await _vodPlayerApi.snapshot(
snapshotSavePath: path,
format: 1,
quality: quality,
);
if (res.success != null && res.success == true) {
return path;
} else {
debugPrint('------> ${res.reason ?? '截图是吧'}');
return null;
}
}
/// release controller
///
/// 释放controller
......@@ -707,13 +766,15 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
case TXVodPlayEvent.PLAY_EVT_PLAY_LOADING:
_changeState(TXPlayerState.buffering);
break;
case TXVodPlayEvent.PLAY_EVT_CHANGE_RESOLUTION: // Downstream video resolution change.
case TXVodPlayEvent
.PLAY_EVT_CHANGE_RESOLUTION: // Downstream video resolution change.
if (defaultTargetPlatform == TargetPlatform.android) {
int? videoWidth = event[TXVodPlayEvent.EVT_VIDEO_WIDTH];
int? videoHeight = event[TXVodPlayEvent.EVT_VIDEO_HEIGHT];
videoWidth ??= event[TXVodPlayEvent.EVT_PARAM1];
videoHeight ??= event[TXVodPlayEvent.EVT_PARAM2];
if ((videoWidth != null && videoWidth > 0) && (videoHeight != null && videoHeight > 0)) {
if ((videoWidth != null && videoWidth > 0) &&
(videoHeight != null && videoHeight > 0)) {
resizeVideoWidth = videoWidth.toDouble();
resizeVideoHeight = videoHeight.toDouble();
videoLeft = event["videoLeft"] ?? 0;
......@@ -752,7 +813,8 @@ class TXVodPlayerController extends ChangeNotifier implements ValueListenable<TX
case TXVodPlayEvent.EVENT_SUBTITLE_DATA:
String subtitleDataStr = map[TXVodPlayEvent.EXTRA_SUBTITLE_DATA] ?? "";
if (subtitleDataStr != "") {
map[TXVodPlayEvent.EXTRA_SUBTITLE_DATA] = subtitleDataStr.trim().replaceAll('\\N', '\n');
map[TXVodPlayEvent.EXTRA_SUBTITLE_DATA] =
subtitleDataStr.trim().replaceAll('\\N', '\n');
}
break;
default:
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论