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

add offline download demo

上级 83d87594
......@@ -41,35 +41,6 @@ public class CommonUtil {
return param;
}
/**
* 通过宽高获得对应的缓存视频清晰度
*/
public static int getCacheVideoQuality(Integer width, Integer height) {
if (width == null || height == null) {
return TXVodDownloadDataSource.QUALITY_FLU;
}
int minValue = Math.min(width, height);
int cacheQualityIndex;
if (minValue == 240 || minValue == 180) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_FLU;
} else if (minValue == 480 || minValue == 360) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_SD;
} else if (minValue == 540) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_SD;
} else if (minValue == 720) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_HD;
} else if (minValue == 1080) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_FHD;
} else if (minValue == 1440) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_2K;
} else if (minValue == 2160) {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_4K;
} else {
cacheQualityIndex = TXVodDownloadDataSource.QUALITY_UNK;
}
return cacheQualityIndex;
}
public static int getDownloadEventByState(int mediaInfoDownloadState) {
Integer event = DOWNLOAD_STATE_MAP.get(mediaInfoDownloadState);
return null != event ? event : FTXEvent.EVENT_DOWNLOAD_ERROR;
......
......@@ -29,11 +29,12 @@ import io.flutter.plugin.common.MethodChannel;
* 下载管理,预下载、离线下载
*/
public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXVodDownloadListener {
private FlutterPlugin.FlutterPluginBinding mFlutterPluginBinding;
private final MethodChannel mMethodChannel;
private final EventChannel mEventChannel;
private final FTXPlayerEventSink mEventSink = new FTXPlayerEventSink();
private Handler mMainHandler;
private FlutterPlugin.FlutterPluginBinding mFlutterPluginBinding;
private final MethodChannel mMethodChannel;
private final EventChannel mEventChannel;
private final FTXPlayerEventSink mEventSink = new FTXPlayerEventSink();
private Handler mMainHandler;
/**
* 视频下载管理
......@@ -108,7 +109,8 @@ public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXV
String videoUrl = call.argument("url");
Integer appId = call.argument("appId");
String fileId = call.argument("fileId");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId);
String userName = call.argument("userName");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId, userName);
TXVodDownloadManager.getInstance().stopDownload(mediaInfo);
result.success(null);
} else if (call.method.equals("setDownloadHeaders")) {
......@@ -127,15 +129,20 @@ public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXV
String videoUrl = call.argument("url");
Integer appId = call.argument("appId");
String fileId = call.argument("fileId");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId);
String userName = call.argument("userName");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId, userName);
result.success(buildMapFromDownloadMediaInfo(mediaInfo));
} else if (call.method.equals("deleteDownloadMediaInfo")) {
Integer quality = call.argument("quality");
String videoUrl = call.argument("url");
Integer appId = call.argument("appId");
String fileId = call.argument("fileId");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId);
boolean deleteResult = TXVodDownloadManager.getInstance().deleteDownloadMediaInfo(mediaInfo);
String userName = call.argument("userName");
TXVodDownloadMediaInfo mediaInfo = parseMediaInfoFromInfo(quality, videoUrl, appId, fileId, userName);
boolean deleteResult = false;
if (mediaInfo != null) {
deleteResult = TXVodDownloadManager.getInstance().deleteDownloadMediaInfo(mediaInfo);
}
result.success(deleteResult);
}
}
......@@ -172,7 +179,7 @@ public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXV
TXVodDownloadManager.getInstance().setListener(null);
}
private Map<String,Object> buildMapFromDownloadMediaInfo(TXVodDownloadMediaInfo mediaInfo) {
private Map<String, Object> buildMapFromDownloadMediaInfo(TXVodDownloadMediaInfo mediaInfo) {
Map<String, Object> resultMap = new HashMap<>();
if (null != mediaInfo) {
resultMap.put("playPath", mediaInfo.getPlayPath());
......@@ -206,8 +213,8 @@ public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXV
bundle.putString("userName", mediaInfo.getUserName());
bundle.putInt("duration", mediaInfo.getDuration());
bundle.putInt("playableDuration", mediaInfo.getPlayableDuration());
bundle.putInt("size", mediaInfo.getSize());
bundle.putInt("downloadSize", mediaInfo.getDownloadSize());
bundle.putLong("size", mediaInfo.getSize());
bundle.putLong("downloadSize", mediaInfo.getDownloadSize());
if (!TextUtils.isEmpty(mediaInfo.getUrl())) {
bundle.putString("url", mediaInfo.getUrl());
}
......@@ -223,18 +230,22 @@ public class FTXDownloadManager implements MethodChannel.MethodCallHandler, ITXV
}
private TXVodDownloadMediaInfo parseMediaInfoFromInfo(Integer quality, String url, Integer appId,
String fileId) {
String fileId, String userName) {
TXVodDownloadMediaInfo mediaInfo = null;
if (!TextUtils.isEmpty(url)) {
if (null == userName) {
userName = "default";
}
if (null != appId && null != fileId) {
mediaInfo = TXVodDownloadManager.getInstance()
.getDownloadMediaInfo(appId, fileId, optQuality(quality), userName);
} else if (!TextUtils.isEmpty(url)) {
mediaInfo = TXVodDownloadManager.getInstance().getDownloadMediaInfo(url);
} else if (null != appId && null != fileId) {
mediaInfo = TXVodDownloadManager.getInstance().getDownloadMediaInfo(appId, fileId, optQuality(quality));
}
return mediaInfo;
}
private int optQuality(Integer quality) {
return quality == null ? TXVodDownloadDataSource.QUALITY_FLU : quality;
return quality == null ? TXVodDownloadDataSource.QUALITY_UNK : quality;
}
@Override
......
......@@ -1638,3 +1638,290 @@ Future<int> switchStream(String url) async;
| enableMessage | bool | 是否开启消息通道, 默认值为 true |
| enableMetaData | bool | 是否开启 MetaData 数据回调,默认值为 NO。 true:SDK 通过 EVT_PLAY_GET_METADATA 消息抛出视频流的 MetaData 数据;false:SDK 不抛出视频流的 MetaData 数据。 |
| flvSessionKey | String | 是否开启 HTTP 头信息回调,默认值为 “” |
## TXVodDownloadController类
### startPreLoad
**说明**
启动预下载。启动预下载前,请先设置好播放引擎的缓存目录[SuperPlayerPlugin.setGlobalCacheFolderPath]和缓存大小[SuperPlayerPlugin.setGlobalMaxCacheSize],这个设置是全局配置需和播放器保持一致,否则会造成播放缓存失效。
**接口**
```dart
Future<int> startPreLoad(
final String playUrl,
final int preloadSizeMB,
final int preferredResolution, {
FTXPredownlodOnCompleteListener? onCompleteListener,
FTXPredownlodOnErrorListener? onErrorListener,
}) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| playUrl | String | 要预下载的url|
| preloadSizeMB | int | 预下载的大小(单位:MB)|
| preferredResolution | int | 期望分辨率,值为高x宽。可参考如720*1080。不支持多分辨率或不需指定时,传-1|
| onCompleteListener | FTXPredownlodOnCompleteListener? | 预下载成功回调,全局|
| onErrorListener | FTXPredownlodOnErrorListener | 预下载失败回调,全局|
**返回值说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| taskId | int | 任务ID |
### stopPreLoad
**说明**
停止预下载
**接口**
```dart
Future<void> stopPreLoad(final int taskId) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| taskId | int | 任务ID|
**返回值说明**
### startDownload
**说明**
开始下载视频
**接口**
```dart
Future<void> startDownload(TXVodDownloadMediaInfo mediaInfo) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 下载任务信息|
**TXVodDownloadMediaInfo**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| playPath | String? | 缓存地址,获得到的视频缓存会有该值,启动下载可以不赋值|
| progress | double? | 缓存进度,获得到的视频缓存会有该值,启动下载可以不赋值|
| downloadState | int? | 缓存状态,获得到的视频缓存会有该值,启动下载可以不赋值|
| userName | String? | 下载账户名称,用于区分不同账户的下载,传空则为 default|
| duration | int? | 缓存视频总时长,安卓端单位为毫秒,IOS为秒,获得到的视频缓存会有该值,启动下载可以不赋值|
| playableDuration | int? | 视频已缓存时长,安卓端单位为毫秒,IOS为秒,获得到的视频缓存会有该值,启动下载可以不赋值|
| size | int? | 文件总大小,单位:byte,获得到的视频缓存会有该值,启动下载可以不赋值|
| downloadSize | int? | 文件已下载的大小,单位:byte,获得到的视频缓存会有该值,启动下载可以不赋值|
| url | String? | 需要下载的视频url,url下载必填,不支持嵌套m3u8和mp4下载|
| dataSource | TXVodDownloadDataSource? | 需要下载的视频fileId信息,url与该参数可只使用一个|
**TXVodDownloadDataSource**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| appId | int? | 下载文件对应的appId,必填|
| fileId | String? | 下载文件Id,必填|
| pSign | String? | 加密签名,加密视频必填|
| quality | int? | 清晰度ID,必传|
| token | String? | 加密token|
| userName | String? | 下载账户名称,用于区分不同账户的下载,传空则为 default|
**返回值说明**
### stopDownload
**说明**
停止下载
**接口**
```dart
Future<void> stopDownload(TXVodDownloadMediaInfo mediaInfo) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 任务信息|
**返回值说明**
### setDownloadHeaders
**说明**
设置下载任务请求头
**接口**
```dart
Future<void> setDownloadHeaders(Map<String, String> headers) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| headers | Map<String, String> | 请求头信息|
**返回值说明**
### getDownloadList
**说明**
获得所有下载任务,包括已下载、正在下载以及下载错误的任务
**接口**
```dart
Future<List<TXVodDownloadMediaInfo>> getDownloadList() async
```
**参数说明**
**返回值说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfoList | List<TXVodDownloadMediaInfo> | 任务列表,可通过对比userName来区分不同用户的下载|
### getDownloadInfo
**说明**
获得下载任务信息
**接口**
```dart
Future<TXVodDownloadMediaInfo> getDownloadInfo(TXVodDownloadMediaInfo mediaInfo) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 任务信息|
**返回值说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 缓存任务详情信息|
### setDownloadObserver
**说明**
获得下载任务信息
**接口**
```dart
void setDownloadObserver(FTXDownlodOnStateChangeListener downlodOnStateChangeListener, FTXDownlodOnErrorListener downlodOnErrorListener)
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| downlodOnStateChangeListener | FTXDownlodOnStateChangeListener | 任务下载状态回调|
| downlodOnErrorListener | FTXDownlodOnErrorListener | 任务下载错误回调|
**返回值说明**
### deleteDownloadMediaInfo
**说明**
删除下载的视频
**接口**
```dart
Future<bool> deleteDownloadMediaInfo(TXVodDownloadMediaInfo mediaInfo) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 任务下载信息|
**返回值说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| result | bool | 删除结果|
### deleteDownloadMediaInfo
**说明**
删除下载的视频
**接口**
```dart
Future<bool> deleteDownloadMediaInfo(TXVodDownloadMediaInfo mediaInfo) async
```
**参数说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| mediaInfo | TXVodDownloadMediaInfo | 任务下载信息|
**返回值说明**
| 参数名 | 类型 | 描述 |
| ------ | ------ | ------------------ |
| result | bool | 删除结果|
......@@ -523,7 +523,7 @@ _controller.onSimplePlayerEventBroadcast.listen((event) {
## 高级功能
### 1、通过fileId提前请求视频数据
### 1、通过fileId提前请求视频数据[](id:fileIdPreRequest)
可通过SuperVodDataLoader提前将视频数据请求下来,提高起播速度
......@@ -543,7 +543,7 @@ loader.getVideoData(model, (resultModel) {
```
### 2、画中画模式的使用
### 2、画中画模式的使用[](id:playerPip)
#### 1、安卓平台配置
......@@ -632,3 +632,88 @@ TXPipController.instance.setPipPlayerPage(this);
7.3 随后,当用户点击SuperPlayerView上的进入画中画按钮的时候,会调用`SuperPlayerView``_onEnterPipMode`内部方法进入画中画,也可以自行调用`SuperPlayerController``enterPictureInPictureMode`方法进入。
### 3、视频下载[](id:videoDownload)
#### 1、下载视频
1. 使用播放器组件的视频下载,首先需要把SuperPlayerModel中的`isEnableDownload`打开,该字段默认关闭
```dart
SuperPlayerModel model = SuperPlayerModel();
// 打开视频下载能力
model.isEnableDownload = true;
```
播放器组件目前只会在点播播放模式下启用下载
2. 使用`SuperPlayerController``startDownload`方法,可以直接下载当前播放器正在播放的视频,对应的是当前播放视频的清晰度。也可是使用`DownloadHelper`下载指定视频,如下:
```dart
DownloadHelper.instance.startDownloadBySize(videoModel, videoWidth, videoHeight);
```
使用`DownloadHelper`的startDownloadBySize,可下载指定分辨率的视频,如果没有该分辨率,会下载相近分辨率的视频。
除了以上接口以外,也可选择传入画质ID或者mediaInfo直接下载。
```dart
// 使用画质ID下载
DownloadHelper.instance.startDownload(videoModel, qualityId);
// 使用mediaInfo下载
DownloadHelper.instance.startDownloadOrg(mediaInfo);
```
3. 画质ID转换
点播的`CommonUtils`提供了`getDownloadQualityBySize`方法,用于将分辨率转为对应的画质id。
```dart
CommonUtils.getDownloadQualityBySize(width, height);
```
#### 2、停止下载视频
使用`DownloadHelper``stopDownload`方法可以停止对应的视频下载,示例如下:
```dart
DownloadHelper.instance.stopDownload(mediaInfo);
```
mediaInfo可通过`DownloadHelper``getMediaInfoByCurrent`方法获取,或者使用`TXVodDownloadController``getDownloadList`获得下载信息
#### 3、删除下载视频
使用`DownloadHelper``deleteDownload`方法,可以删除对应的视频
```dart
bool deleteResult = await DownloadHelper.instance.deleteDownload(downloadModel.mediaInfo);
```
deleteDownload会返回删除的结果,来判断是否删除成功
#### 4、下载状态
`DownloadHelper`提供了基本的`isDownloaded`方法判断视频是否已经下载。也可以注册监听来实时判断下载状态。
`DownloadHelper`对下载事件进行了分发,可通过如下代码进行事件注册。
```dart
// 注册下载事件监听
DownloadHelper.instance.addDownloadListener(FTXDownloadListener((event, info) {
// 下载状态变化
}, (errorCode, errorMsg, info) {
// 下载错误回调
}));
// 移除下载事件监听
DownloadHelper.instance.removeDownloadListener(listener);
```
此外,还可以通过`TXVodDownloadController.instance.getDownloadInfo(mediaInfo)`方法或者`TXVodDownloadController.instance.getDownloadList()`方法直接查询mediaInfo中的downloadState来判断下载状态。
#### 5、播放下载视频
`TXVodDownloadController.instance.getDownloadInfo(mediaInfo)``TXVodDownloadController.instance.getDownloadList()`获得到的视频信息中有个playPath字段,使用TXVodPlayerController直接播放即可
```dart
controller.startVodPlay(mediaInfo.playPath);
```
......@@ -484,16 +484,11 @@ Fileid 下载至少需要传入 AppID、 Fileid 和 qualityId。带签名视频
**注意:加密视频只能通过Fileid下载,psign参数必须填写。**
```dart
// QUALITY_OD // 原画
// QUALITY_FLU // 流畅
// QUALITY_SD // 标清
// QUALITY_HD // 高清
TXVodDownloadMedialnfo medialnfo = TXVodDownloadMedialnfo();
TXVodDownloadDataSource dataSource = TXVodDownloadDataSource();
dataSource.appId = 1252463788;
dataSource.fileId = "4564972819220421305";
dataSource.quality = DownloadQuality.QUALITY_HD;
dataSource.quality = DownloadQuality.QUALITY_480P;
dataSource.pSign = "pSignxxxx";
medialnfo.dataSource = dataSource;
TXVodDownloadController.instance.startDonwload(medialnfo);
......@@ -574,7 +569,7 @@ medialnfo.dataSource = TXVodDownloadDataSource();
medialnfo.dataSource!.appId = 1500005830;
medialnfo.dataSource!.fileId = "8602268011437356984";
// fileId下载必须传入quality。quality也可以通过CommonUtils.getDownloadQualityBySize(width, height)来获取
medialnfo.dataSource!.quality = DownloadQuality.QUALITY_HD;
medialnfo.dataSource!.quality = DownloadQuality.QUALITY_480P;
TXVodDownloadMedialnfo downloadInfo = await TXVodDownloadController.instance.getDownloadInfo(medialnfo);
int? duration = downloadInfo.duration; // 获取总时长
int? playableDuration = downloadInfo.playableDuration; // 获取已下载的可播放时长
......
{
"zh": {
"player_cache_progress_label" : "缓存进度",
"player_cache_size" : "大小",
"player_download_list" : "下载列表",
"player_tip" : "提示",
"player_delete_failed" : "删除失败",
"player_caching" : "缓存中",
"player_cache_error" : "缓存错误",
"player_cache_interrupt" : "缓存暂停",
"player_cache_complete" : "缓存结束",
"player_confirm" : "确定",
"player_cancel" : "取消",
"player_check_user_delete_video" : "确定删除视频吗"
},
"en": {}
}
\ No newline at end of file
差异被折叠。
......@@ -4,6 +4,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/demo_download_list.dart';
import 'package:super_player_example/ui/demo_inputdialog.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
import 'dart:ui';
......@@ -93,6 +94,21 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
: AppBar(
backgroundColor: Colors.transparent,
title: const Text('播放器组件'),
actions: [
InkWell(
onTap: _jumpToDownloadList,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Container(
width: 40,
height: 40,
padding: EdgeInsets.all(8),
margin: EdgeInsets.only(left: 8, right: 8),
child: Image(
image: AssetImage("images/superplayer_ic_vod_download_list.png",
package: StringResource.PKG_NAME)),
))
],
),
body: SafeArea(
child: Builder(
......@@ -108,6 +124,15 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
return !_controller.onBackPress();
}
void _jumpToDownloadList() async {
dynamic result = await Navigator.push(context, MaterialPageRoute(builder: (context) => DemoDownloadList()));
if(result is SuperPlayerModel) {
playVideo(result);
} else {
print("download list return result is not a videoModel, result is :$result");
}
}
Widget getTabRow() {
return new Container(
height: 40,
......@@ -296,6 +321,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId!.fileId = "8602268011437356984";
model.title = "云点播(fileId播放)";
model.playAction = playAction;
model.isEnableDownload = true;
models.add(model);
model = SuperPlayerModel();
......@@ -304,18 +330,22 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId!.fileId = "5285890781763144364";
model.title = "腾讯云";
model.playAction = playAction;
model.isEnableDownload = false;
models.add(model);
model = SuperPlayerModel();
model.title = "加密视频播放";
model.appId = 1254432039;
model.appId = 1500005830;
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "5285890816303742312";
model.videoId!.fileId = "243791578431393746";
model.videoId!.psign =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTI1NDQzMjAzOSwiZmlsZUlkIjoiNTI4NTg5MDgxNjMwMzc0MjMxMiIsImN1cnJlbnRUaW1lU3"
"RhbXAiOjE2MTcyNTc0ODMsInBjZmciOiJiYXNpY0RybVByZXNldCIsInVybEFjY2Vzc0luZm8iOnt9LCJkcm1MaWNlbnNlSW5mbyI6e319.2H1t9dKPpdA41"
"a8t1WwI631OWC18HGl60ccBDLylCKE";
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6MTUwMDAwNTgzMCwiZmlsZUlkIjoiMjQzNzkxNTc4NDMxMzkzNzQ2IiwiY"
"3VycmVudFRpbWVTdGFtcCI6MTY3MzQyNjIyNywiY29udGVudEluZm8iOnsiYXVkaW9WaWRlb1R5cGUiOiJQcm90ZWN0ZWRBZGFwdGl"
"2ZSIsImRybUFkYXB0aXZlSW5mbyI6eyJwcml2YXRlRW5jcnlwdGlvbkRlZmluaXRpb24iOjEyfX0sInVybEFjY2Vzc0luZm8iOnsiZ"
"G9tYWluIjoiMTUwMDAwNTgzMC52b2QyLm15cWNsb3VkLmNvbSIsInNjaGVtZSI6IkhUVFBTIn19.q34pq7Bl0ryKDwUHGyzfXKP-C"
"DI8vrm0k_y-IaxgF_U";
model.playAction = playAction;
model.isEnableDownload = true;
models.add(model);
model = SuperPlayerModel();
......@@ -323,6 +353,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "4564972819219071568";
model.playAction = playAction;
model.isEnableDownload = false;
models.add(model);
model = SuperPlayerModel();
......@@ -330,6 +361,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "4564972819219071668";
model.playAction = playAction;
model.isEnableDownload = false;
models.add(model);
model = SuperPlayerModel();
......@@ -337,6 +369,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "4564972819219071679";
model.playAction = playAction;
model.isEnableDownload = false;
models.add(model);
model = SuperPlayerModel();
......@@ -344,6 +377,7 @@ class _DemoSuperPlayerState extends State<DemoSuperPlayer> with TXPipPlayerResto
model.videoId = new SuperPlayerVideoId();
model.videoId!.fileId = "4564972819219081699";
model.playAction = playAction;
model.isEnableDownload = false;
models.add(model);
List<Future<void>> requestList = [];
......
......@@ -4,8 +4,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/demo_superplayer.dart';
import 'package:super_player_example/res/app_localization_delegate.dart';
import 'package:super_player_example/shortvideo/demo_short_video_lib.dart';
import 'package:superplayer_widget/demo_superplayer_lib.dart';
......@@ -79,6 +81,16 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizationDelegate.delegate
],
supportedLocales: [
Locale.fromSubtags(languageCode: 'en'),
Locale.fromSubtags(languageCode: 'zh'),
],
navigatorKey: navigatorKey,
home: Container(
decoration: BoxDecoration(
......
// Copyright (c) 2022 Tencent. All rights reserved.
import 'package:flutter/material.dart';
import 'package:super_player/super_player.dart';
import 'package:super_player_example/res/app_localizations.dart';
/// 文本国际化代理
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
static AppLocalizationDelegate delegate = AppLocalizationDelegate();
/// 设置语言支持
@override
bool isSupported(Locale locale) {
return ["zh", "en"].contains(locale.languageCode);
}
@override
Future<AppLocalizations> load(Locale locale) async {
final appLocalizations = AppLocalizations(locale);
await appLocalizations.loadJson();
return appLocalizations;
}
@override
bool shouldReload(covariant LocalizationsDelegate<AppLocalizations> old) {
return false;
}
}
\ No newline at end of file
// Copyright (c) 2022 Tencent. All rights reserved.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// 文本资源国际化加载
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
static AppLocalizations of(BuildContext context) {
return Localizations.of(context, AppLocalizations);
}
static Map<String, Map<String, String>> _localizedStrings = {};
Future<void> loadJson() async {
final jsonString = await rootBundle.loadString("assets/json/i18n.json");
Map<String, dynamic> map = json.decode(jsonString);
_localizedStrings = map.map((key, value) => MapEntry(key, value.cast<String, String>()));
}
String get playerCacheProgressLabel => _localizedStrings[this.locale.languageCode]!["player_cache_progress_label"]!;
String get playerDownloadList => _localizedStrings[this.locale.languageCode]!["player_download_list"]!;
String get playerTip => _localizedStrings[this.locale.languageCode]!["player_tip"]!;
String get playerDeleteFailed => _localizedStrings[this.locale.languageCode]!["player_delete_failed"]!;
String get playerCaching => _localizedStrings[this.locale.languageCode]!["player_caching"]!;
String get playerCacheError => _localizedStrings[this.locale.languageCode]!["player_cache_error"]!;
String get playerCacheInterrupt => _localizedStrings[this.locale.languageCode]!["player_cache_interrupt"]!;
String get playerCacheComplete => _localizedStrings[this.locale.languageCode]!["player_cache_complete"]!;
String get playerConfirm => _localizedStrings[this.locale.languageCode]!["player_confirm"]!;
String get playerCancel => _localizedStrings[this.locale.languageCode]!["player_cancel"]!;
String get playerCheckUserDeleteVideo => _localizedStrings[this.locale.languageCode]!["player_check_user_delete_video"]!;
String get playerCacheSize => _localizedStrings[this.locale.languageCode]!["player_cache_size"]!;
}
......@@ -38,7 +38,7 @@ class _DemoInputDialogState extends State<DemoInputDialog> {
_buildActionWidget(BuildContext context) {
List<Widget> actionWidgets = [
FlatButton(
TextButton(
child: Text("确定"),
onPressed: () {
Navigator.of(context).pop();
......@@ -52,7 +52,7 @@ class _DemoInputDialogState extends State<DemoInputDialog> {
}, // 关闭对话框
),
// Padding(padding: EdgeInsets.only(left: 15)),
FlatButton(
TextButton(
child: Text("取消"),
onPressed: (){
Navigator.of(context).pop();
......
......@@ -10,6 +10,9 @@ environment:
flutter: ">=2.0.0"
dependencies:
# 国际化
flutter_localizations:
sdk: flutter
flutter:
sdk: flutter
event_bus: ^2.0.0
......@@ -48,6 +51,7 @@ flutter:
# the material Icons class.
uses-material-design: true
assets:
- assets/json/
- images/ic_new_vod_bg.png
- images/addp.png
# To add assets to your application, add an assets section, like this:
......
......@@ -6,8 +6,6 @@
@interface CommonUtil : NSObject
+(NSNumber*)getCacheVideoQuality:(int)width height:(int)pHeight;
+(NSNumber*)getDownloadEventByState:(int)downloadState;
@end
......@@ -4,29 +4,6 @@
@implementation CommonUtil
+ (NSNumber*)getCacheVideoQuality:(int)width height:(int)pHeight{
int minValue = MIN(width, pHeight);
int cacheQualityIndex;
if (minValue == 240 || minValue == 180) {
cacheQualityIndex = TXVodQualityFLU;
} else if (minValue == 480 || minValue == 360) {
cacheQualityIndex = TXVodQualitySD;
} else if (minValue == 540) {
cacheQualityIndex = TXVodQualitySD;
} else if (minValue == 720) {
cacheQualityIndex = TXVodQualityHD;
} else if (minValue == 1080) {
cacheQualityIndex = TXVodQualityFHD;
} else if (minValue == 1440) {
cacheQualityIndex = TXVodQuality2K;
} else if (minValue == 2160) {
cacheQualityIndex = TXVodQuality4K;
} else {
cacheQualityIndex = TXVodQualityFLU;
}
return [NSNumber numberWithInt:cacheQualityIndex];
}
+ (NSNumber*)getDownloadEventByState:(int)downloadState{
int result;
switch (downloadState) {
......
......@@ -93,7 +93,8 @@
NSString *videoUrl = args[@"url"];
NSNumber *appIdNum = args[@"appId"];
NSString *fileId = args[@"fileId"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId];
NSString *userName = args[@"userName"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId name:userName];
[[TXVodDownloadManager shareInstance] stopDownload:mediaInfo];
result(nil);
} else if([@"setDownloadHeaders" isEqualToString:call.method]) {
......@@ -111,7 +112,8 @@
NSString *videoUrl = args[@"url"];
NSNumber *appIdNum = args[@"appId"];
NSString *fileId = args[@"fileId"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId];
NSString *userName = args[@"userName"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId name:userName];
NSDictionary *resultDic = [self buildMapFromDownloadMediaInfo:mediaInfo];
result(resultDic);
} else if([@"deleteDownloadMediaInfo" isEqualToString:call.method]) {
......@@ -119,9 +121,10 @@
NSString *videoUrl = args[@"url"];
NSNumber *appIdNum = args[@"appId"];
NSString *fileId = args[@"fileId"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId];
[[TXVodDownloadManager shareInstance] deleteDownloadMediaInfo:mediaInfo];
result(@(TRUE));
NSString *userName = args[@"userName"];
TXVodDownloadMediaInfo *mediaInfo = [self parseMediaInfoFromInfo:quality url:videoUrl appId:appIdNum fileId:fileId name:userName];
BOOL deleteResult = [[TXVodDownloadManager shareInstance] deleteDownloadMediaInfo:mediaInfo];
result(@(deleteResult));
}
}
......@@ -188,20 +191,25 @@
return ([NSNull null] == (NSNull *)quality || nil == quality) ? TXVodQualityFLU : [quality intValue];
}
- (TXVodDownloadMediaInfo *)parseMediaInfoFromInfo:(NSNumber *)quality url:(NSString *)videoUrl appId:(NSNumber *)pAppId fileId:(NSString *)pFileId {
- (TXVodDownloadMediaInfo *)parseMediaInfoFromInfo:(NSNumber *)quality url:(NSString *)videoUrl appId:(NSNumber *)pAppId fileId:(NSString *)pFileId name:(NSString*)name {
TXVodDownloadMediaInfo *mediaInfo = nil;
if(nil != videoUrl && [NSNull null] != (NSNull *)videoUrl) {
TXVodDownloadMediaInfo *urlInfo = [[TXVodDownloadMediaInfo alloc] init];
urlInfo.url = videoUrl;
mediaInfo = [[TXVodDownloadManager shareInstance] getDownloadMediaInfo:urlInfo];
} else if([NSNull null] != (NSNull *)pFileId && [NSNull null] != (NSNull *)pAppId) {
if(name == nil) {
name = @"default";
}
if([NSNull null] != (NSNull *)pFileId && [NSNull null] != (NSNull *)pAppId) {
TXVodDownloadMediaInfo *fileIdInfo = [[TXVodDownloadMediaInfo alloc] init];
TXVodDownloadDataSource *dataSource = [[TXVodDownloadDataSource alloc] init];
dataSource.appId = [pAppId intValue];
dataSource.fileId = pFileId;;
dataSource.quality = [self optQuality:quality];
dataSource.userName = name;
fileIdInfo.dataSource = dataSource;
mediaInfo = [[TXVodDownloadManager shareInstance] getDownloadMediaInfo:fileIdInfo];
} else if(nil != videoUrl && [NSNull null] != (NSNull *)videoUrl) {
TXVodDownloadMediaInfo *urlInfo = [[TXVodDownloadMediaInfo alloc] init];
urlInfo.url = videoUrl;
urlInfo.userName = name;
mediaInfo = [[TXVodDownloadManager shareInstance] getDownloadMediaInfo:urlInfo];
}
return mediaInfo;
}
......
......@@ -5,24 +5,21 @@ part of SuperPlayer;
class CommonUtils {
/// 通过分辨率获取下载对应的qualityId
static int getDownloadQualityBySize(int width, int height) {
if (width == null || height == null) {
return DownloadQuality.QUALITY_FLU;
}
int minValue = min(width, height);
int cacheQualityIndex;
if (minValue == 240 || minValue == 180) {
cacheQualityIndex = DownloadQuality.QUALITY_FLU;
} else if (minValue == 480 || minValue == 360) {
cacheQualityIndex = DownloadQuality.QUALITY_SD;
} else if (minValue == 540) {
cacheQualityIndex = DownloadQuality.QUALITY_SD;
} else if (minValue == 720) {
cacheQualityIndex = DownloadQuality.QUALITY_HD;
} else if (minValue == 1080) {
cacheQualityIndex = DownloadQuality.QUALITY_FHD;
} else if (minValue == 1440) {
if (minValue > 0 && minValue <= 240) {
cacheQualityIndex = DownloadQuality.QUALITY_240P;
} else if (minValue > 240 && minValue <= 480) {
cacheQualityIndex = DownloadQuality.QUALITY_480P;
} else if (minValue > 480 && minValue <= 540) {
cacheQualityIndex = DownloadQuality.QUALITY_540P;
} else if (minValue > 540 && minValue >= 720) {
cacheQualityIndex = DownloadQuality.QUALITY_720P;
} else if (minValue > 720 && minValue <= 1080) {
cacheQualityIndex = DownloadQuality.QUALITY_1080P;
} else if (minValue > 1080 && minValue <= 1440) {
cacheQualityIndex = DownloadQuality.QUALITY_2K;
} else if (minValue == 2160) {
} else if (minValue > 1440 && minValue <= 2160) {
cacheQualityIndex = DownloadQuality.QUALITY_4K;
} else {
cacheQualityIndex = DownloadQuality.QUALITY_UNK;
......
......@@ -209,14 +209,26 @@ class TXLogLevel {
}
class DownloadQuality {
@deprecated
static const QUALITY_OD = 0;
@deprecated
static const QUALITY_FLU = 1;
@deprecated
static const QUALITY_SD = 2;
@deprecated
static const QUALITY_HD = 3;
@deprecated
static const QUALITY_FHD = 4;
static const QUALITY_2K = 5;
static const QUALITY_4K = 6;
static const QUALITY_UNK = 1000;
static const int QUALITY_2K = 5;
static const int QUALITY_4K = 6;
static const int QUALITY_UNK = 1000;
static const int QUALITY_240P = 240;
static const int QUALITY_360P = 360;
static const int QUALITY_480P = 480;
static const int QUALITY_540P = 540;
static const int QUALITY_720P = 720;
static const int QUALITY_1080P = 1080;
}
class TXPlayInfoParams {
......@@ -264,7 +276,7 @@ class TXVodDownloadDataSource {
}
/// 视频下载信息
class TXVodDownloadMedialnfo {
class TXVodDownloadMediaInfo {
/// 缓存地址
String? playPath;
/// 下载进度
......@@ -291,6 +303,9 @@ class TXVodDownloadMedialnfo {
Map<String, dynamic> toJson() {
Map<String, dynamic> json = {};
if(null != dataSource) {
json.addAll(dataSource!.toJson());
}
json["url"] = url;
json["downloadState"] = downloadState;
json["progress"] = progress;
......@@ -300,9 +315,6 @@ class TXVodDownloadMedialnfo {
json["playableDuration"] = playableDuration;
json["size"] = size;
json["downloadSize"] = downloadSize;
if(null != dataSource) {
json.addAll(dataSource!.toJson());
}
return json;
}
}
......@@ -319,6 +331,6 @@ abstract class TXPlayerType {
typedef FTXPredownlodOnCompleteListener = void Function(int taskId, String url);
typedef FTXPredownlodOnErrorListener = void Function(int taskId, String url, int code, String msg);
// 视频下载时间回调Listener
typedef FTXDownlodOnStateChangeListener = void Function(int event, TXVodDownloadMedialnfo info);
typedef FTXDownlodOnErrorListener = void Function(int errorCode, String errorMsg,TXVodDownloadMedialnfo info);
typedef FTXDownlodOnStateChangeListener = void Function(int event, TXVodDownloadMediaInfo info);
typedef FTXDownlodOnErrorListener = void Function(int errorCode, String errorMsg,TXVodDownloadMediaInfo info);
......@@ -24,12 +24,12 @@ class TXVodDownloadController {
static TXVodDownloadController _sharedInstance() {
if (_instance == null) {
_instance = TXVodDownloadController._createInsatnce();
_instance = TXVodDownloadController._createInstance();
}
return _instance!;
}
TXVodDownloadController._createInsatnce() {
TXVodDownloadController._createInstance() {
EventChannel eventChannel = EventChannel("cloud.tencent.com/txvodplayer/download/event");
_downloadEventSubscription =
eventChannel.receiveBroadcastStream('event').listen(_eventHandler, onError: _errorHandler);
......@@ -65,16 +65,16 @@ class TXVodDownloadController {
}
/// 开始下载
/// videoDownloadModel: 下载构造体 [TXVodDownloadMedialnfo]
/// videoDownloadModel: 下载构造体 [TXVodDownloadMediaInfo]
/// userName: 下载用户,用来区分不同用户的下载,可不传,不传则使用默认值
Future<void> startDonwload(TXVodDownloadMedialnfo medialnfo) async {
await _methodChannel.invokeMethod("startDownload", medialnfo.toJson());
Future<void> startDownload(TXVodDownloadMediaInfo mediaInfo) async {
await _methodChannel.invokeMethod("startDownload", mediaInfo.toJson());
}
/// 停止下载
/// videoDownloadModel: 下载构造体 [TXVodDownloadMedialnfo]
Future<void> stopDownload(TXVodDownloadMedialnfo medialnfo) async {
await _methodChannel.invokeMethod("stopDownload", medialnfo.toJson());
/// videoDownloadModel: 下载构造体 [TXVodDownloadMediaInfo]
Future<void> stopDownload(TXVodDownloadMediaInfo mediaInfo) async {
await _methodChannel.invokeMethod("stopDownload", mediaInfo.toJson());
}
/// 设置下载请求头
......@@ -83,9 +83,9 @@ class TXVodDownloadController {
}
/// 获取所有视频下载列表
/// return [TXVodDownloadMedialnfo]
Future<List<TXVodDownloadMedialnfo>> getDownloadList() async {
List<TXVodDownloadMedialnfo> outputList = [];
/// return [TXVodDownloadMediaInfo]
Future<List<TXVodDownloadMediaInfo>> getDownloadList() async {
List<TXVodDownloadMediaInfo> outputList = [];
List<dynamic> donwloadOrgList = await _methodChannel.invokeMethod("getDownloadList");
for (dynamic data in donwloadOrgList) {
outputList.add(_getDownloadInfoFromMap(data));
......@@ -94,26 +94,26 @@ class TXVodDownloadController {
}
/// 获得指定视频的下载信息
/// return [TXVodDownloadMedialnfo]
Future<TXVodDownloadMedialnfo> getDownloadInfo(TXVodDownloadMedialnfo medialnfo) async {
Map<dynamic, dynamic> data = await _methodChannel.invokeMethod("getDownloadInfo", medialnfo.toJson());
/// return [TXVodDownloadMediaInfo]
Future<TXVodDownloadMediaInfo> getDownloadInfo(TXVodDownloadMediaInfo mediaInfo) async {
Map<dynamic, dynamic> data = await _methodChannel.invokeMethod("getDownloadInfo", mediaInfo.toJson());
return _getDownloadInfoFromMap(data);
}
/// 设置下载事件监听,该监听为全局下载监听配置,重复调用,listener会先后覆盖
/// 设置下载事件监听,该监听为全局下载监听配置,重复调用
void setDownloadObserver(
FTXDownlodOnStateChangeListener downlodOnStateChangeListener, FTXDownlodOnErrorListener downlodOnErrorListener) {
FTXDownlodOnStateChangeListener? downlodOnStateChangeListener, FTXDownlodOnErrorListener? downlodOnErrorListener) {
_downlodOnStateChangeListener = downlodOnStateChangeListener;
_downlodOnErrorListener = downlodOnErrorListener;
}
/// 删除下载任务
Future<bool> deleteDownloadMediaInfo(TXVodDownloadMedialnfo medialnfo) async {
return await _methodChannel.invokeMethod("deleteDownloadMediaInfo", medialnfo.toJson());
Future<bool> deleteDownloadMediaInfo(TXVodDownloadMediaInfo mediaInfo) async {
return await _methodChannel.invokeMethod("deleteDownloadMediaInfo", mediaInfo.toJson());
}
TXVodDownloadMedialnfo _getDownloadInfoFromMap(Map<dynamic, dynamic> map) {
TXVodDownloadMedialnfo medialnfo = TXVodDownloadMedialnfo();
TXVodDownloadMediaInfo _getDownloadInfoFromMap(Map<dynamic, dynamic> map) {
TXVodDownloadMediaInfo medialnfo = TXVodDownloadMediaInfo();
medialnfo.playPath = map["playPath"];
medialnfo.progress = map["progress"];
medialnfo.downloadState = map["downloadState"];
......@@ -169,7 +169,7 @@ class TXVodDownloadController {
_downlodOnStateChangeListener?.call(eventCode, _getDownloadInfoFromMap(map));
break;
case TXVodPlayEvent.EVENT_DOWNLOAD_ERROR:
TXVodDownloadMedialnfo info = _getDownloadInfoFromMap(map);
TXVodDownloadMediaInfo info = _getDownloadInfoFromMap(map);
int errorCode = map["errorCode"];
String errorMsg = map["errorMsg"];
_downlodOnErrorListener?.call(errorCode, errorMsg, info);
......
......@@ -2,9 +2,18 @@
part of demo_super_player_lib;
/// colors resource
class ColorResource {
static const COLOR_MAIN_THEME = 0xFFFF4C58;
static const COLOR_SLIDER_MAIN_THEME = 0xFFFF4C58;
static const COLOR_APP_MAIN_THEME = 0xFF14233D;
static const COLOR_GRAY = 0xFFBBBBBB;
static const COLOR_TRANS_BLACK = 0xBB000000;
static const COLOR_WHITE = 0xFFFFFFFF;
static const COLOR_TRANS_GRAY = 0x11BBBBBB;
static const COLOR_TRANS_GRAY_2 = 0x33DDDDDD;
static const COLOR_TRANS_GRAY_3 = 0xFFA3A8B1;
static const COLOR_TRANS_GRAY_4 = 0xFF787878;
static const COLOR_BTN_BULUE = 0xFF2065DA;
static const COLOR_DOWNLOAD_CACHING = 0xFFDD9322;
static const COLOR_DOWNLOAD_COMPELETE = 0xFF20C86F;
static const COLOR_DOWNLOAD_INTERUPT = 0xFFFD7657;
}
\ No newline at end of file
......@@ -9,8 +9,15 @@ class StringResource {
static const QUALITY_FSD = "全标清";
static const QUALITY_FHD = "全高清";
static const QUALITY_FHD2 = "超清";
static const QUALITY_240P = "流畅240p";
static const QUALITY_360P = "流畅360p";
static const QUALITY_480P = "标清480p";
static const QUALITY_540P = "标清540p";
static const QUALITY_720P = "高清720p";
static const QUALITY_1080P = "全高清1080p";
static const QUALITY_2K = "2K";
static const QUALITY_4K = "4k";
static const QUALITY_OD = "原画";
static const VOICE_LABEL = "声音";
static const BRIGHTNESS_LABEL = "亮度";
......@@ -22,4 +29,6 @@ class StringResource {
static const ERROR_PIP = "画中画开启失败";
static const PKG_NAME = "superplayer_widget";
static const TEST_VIDEO_TITLE = "测试视频";
}
......@@ -8,11 +8,11 @@ class ThemeResource {
return ThemeData(
sliderTheme: SliderThemeData(
trackHeight: 2,
thumbColor: Color(ColorResource.COLOR_MAIN_THEME),
thumbColor: Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4),
overlayColor: Colors.white,
overlayShape: RoundSliderOverlayShape(overlayRadius: 10),
activeTrackColor: Color(ColorResource.COLOR_MAIN_THEME),
activeTrackColor: Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
inactiveTrackColor: Color(ColorResource.COLOR_GRAY),
));
}
......@@ -22,12 +22,12 @@ class ThemeResource {
return ThemeData(
sliderTheme: SliderThemeData(
trackHeight: 1,
thumbColor: Color(ColorResource.COLOR_MAIN_THEME),
thumbColor: Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 0, disabledThumbRadius: 0, elevation: 0, pressedElevation: 0),
overlayColor: Colors.white,
overlayShape: RoundSliderOverlayShape(overlayRadius: 1),
activeTrackColor: Color(ColorResource.COLOR_MAIN_THEME),
activeTrackColor: Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
inactiveTrackColor: Color(ColorResource.COLOR_GRAY),
));
}
......@@ -37,7 +37,7 @@ class ThemeResource {
}
static TextStyle getCheckedLabelTextStyle() {
return TextStyle(fontSize: 14, color: Color(ColorResource.COLOR_MAIN_THEME));
return TextStyle(fontSize: 14, color: Color(ColorResource.COLOR_SLIDER_MAIN_THEME));
}
static TextStyle getCommonTextStyle() {
......@@ -45,6 +45,6 @@ class ThemeResource {
}
static TextStyle getCheckedTextStyle() {
return TextStyle(fontSize: 13, color: Color(ColorResource.COLOR_MAIN_THEME));
return TextStyle(fontSize: 13, color: Color(ColorResource.COLOR_SLIDER_MAIN_THEME));
}
}
......@@ -39,4 +39,5 @@ part 'ui/superplayer_video_slider.dart';
part 'common/color_resource.dart';
part 'common/string_resource.dart';
part 'common/theme_resource.dart';
part 'common/task_executors.dart';
\ No newline at end of file
part 'common/task_executors.dart';
part 'extra/download_helper.dart';
\ No newline at end of file
// Copyright (c) 2022 Tencent. All rights reserved.
part of demo_super_player_lib;
/// 下载帮助类,配合播放器组件使用,简化下载使用流程
class DownloadHelper {
static DownloadHelper? _instance;
static DownloadHelper get instance => _sharedInstance();
/// SuperPlayerPlugin单例
static DownloadHelper _sharedInstance() {
_instance ??= DownloadHelper._internal();
return _instance!;
}
final List<FTXDownloadListener> _listeners = [];
/// 初始model
DownloadHelper._internal() {
TXVodDownloadController.instance.setDownloadObserver((event, info) {
for (FTXDownloadListener listener in _listeners) {
listener.onStateChangeListener(event, info);
}
}, (errorCode, errorMsg, info) {
for (FTXDownloadListener listener in _listeners) {
listener.onErrorListener(errorCode, errorMsg, info);
}
});
}
void addDownloadListener(FTXDownloadListener listener) {
if (!_listeners.contains(listener)) {
_listeners.add(listener);
}
}
void removeDownloadListener(FTXDownloadListener listener) {
_listeners.remove(listener);
}
void clearListener() {
_listeners.clear();
}
/// 根据videoModel生成下载MediaInfo
TXVodDownloadMediaInfo getMediaInfoByCurrent(SuperPlayerModel? model, int qualityId) {
TXVodDownloadMediaInfo mediaInfo = TXVodDownloadMediaInfo();
if (null != model) {
if (model.videoId != null) {
TXVodDownloadDataSource dataSource = TXVodDownloadDataSource();
dataSource.appId = model.appId;
dataSource.fileId = model.videoId!.fileId;
dataSource.pSign = model.videoId!.psign;
dataSource.quality = qualityId;
dataSource.userName = "default";
mediaInfo.dataSource = dataSource;
} else {
mediaInfo.url = model.videoURL;
}
// 下载会按照userName区分不同用户的下载,这里给了一个默认用户
mediaInfo.userName = "default";
}
return mediaInfo;
}
Future<bool> isDownloaded(SuperPlayerModel? model, int width, int height) async {
TXVodDownloadMediaInfo mediaInfo =
getMediaInfoByCurrent(model, VideoQualityUtils.getCacheVideoQualityIndex(width, height));
TXVodDownloadMediaInfo cacheInfo = await TXVodDownloadController.instance.getDownloadInfo(mediaInfo);
return cacheInfo.downloadState == TXVodPlayEvent.EVENT_DOWNLOAD_FINISH;
}
Future<void> startDownloadBySize(SuperPlayerModel? model, int width, int height) async {
return startDownload(model, VideoQualityUtils.getCacheVideoQualityIndex(width, height));
}
Future<void> startDownload(SuperPlayerModel? model, int qualityId) async {
return TXVodDownloadController.instance.startDownload(getMediaInfoByCurrent(model, qualityId));
}
Future<void> startDownloadOrg(TXVodDownloadMediaInfo mediaInfo) async {
return TXVodDownloadController.instance.startDownload(mediaInfo);
}
Future<bool> deleteDownload(TXVodDownloadMediaInfo mediaInfo) async {
return TXVodDownloadController.instance.deleteDownloadMediaInfo(mediaInfo);
}
Future<void> stopDownload(TXVodDownloadMediaInfo mediaInfo) async {
return TXVodDownloadController.instance.stopDownload(mediaInfo);
}
void destroy() {
TXVodDownloadController.instance.setDownloadObserver(null, null);
}
}
class FTXDownloadListener {
final FTXDownlodOnStateChangeListener onStateChangeListener;
final FTXDownlodOnErrorListener onErrorListener;
FTXDownloadListener(this.onStateChangeListener, this.onErrorListener);
}
......@@ -84,6 +84,8 @@ class SuperPlayerModel {
String coverUrl = ""; // coverUrl from net
String customeCoverUrl = ""; // custome video cover image
int duration = 0; // video duration
// 是否启用下载能力,默认关闭
bool isEnableDownload = false;
// feed流视频描述
String videoDescription = "";
......
......@@ -22,7 +22,7 @@ class SuperPlayerController {
PlayInfoProtocol? _currentProtocol;
_SuperPlayerObserver? _observer;
VideoQuality? currentQuality;
List<VideoQuality>? currentQualiyList;
List<VideoQuality>? currentQualityList;
StreamController<TXPlayerHolder> playerStreamController = StreamController.broadcast();
SuperPlayerState playerState = SuperPlayerState.INIT;
SuperPlayerType playerType = SuperPlayerType.VOD;
......@@ -57,6 +57,7 @@ class SuperPlayerController {
double _seekPos = 0; // 记录切换硬解时的播放时间
/// 该值会改变新播放视频的播放开始时间点
double startPos = 0;
/// 播放器内核解析出来的视频宽高
double videoWidth = 0;
double videoHeight = 0;
......@@ -254,6 +255,18 @@ class SuperPlayerController {
}
}
Future<bool> isDownloaded() async {
if (videoWidth == 0 || videoHeight == 0) {
return false;
} else {
return await DownloadHelper.instance.isDownloaded(videoModel, videoWidth.toInt(), videoHeight.toInt());
}
}
void startDownload() {
DownloadHelper.instance.startDownloadBySize(videoModel, videoWidth.toInt(), videoHeight.toInt());
}
/// 播放视频.
/// 10.7版本开始,playWithModel变更为playWithModelNeedLicence,需要通过 {@link SuperPlayerPlugin#setGlobalLicense} 设置 Licence 后方可成功播放,
/// 否则将播放失败(黑屏),全局仅设置一次即可。直播 Licence、短视频 Licence 和视频播放 Licence 均可使用,若您暂未获取上述 Licence ,
......@@ -573,7 +586,7 @@ class SuperPlayerController {
void _updateVideoQualityList(List<VideoQuality>? qualityList, VideoQuality? defaultQuality) {
currentQuality = defaultQuality;
currentQualiyList = qualityList;
currentQualityList = qualityList;
_observer?.onVideoQualityListChange(qualityList, defaultQuality);
}
......@@ -610,10 +623,12 @@ class SuperPlayerController {
isPrepared = false;
_needToResume = false;
_needToPause = false;
videoWidth = 0;
videoHeight = 0;
currentDuration = 0;
videoDuration = 0;
currentQuality = null;
currentQualiyList?.clear();
currentQualityList?.clear();
_currentProtocol = null;
// 移除所有事件
_vodPlayEventListener?.cancel();
......
......@@ -4,6 +4,22 @@ part of demo_super_player_lib;
/// video quality utils
class VideoQualityUtils {
static const TAG = "VideoQualityUtils";
static const Map<int, String> downloadQualityMap = {
DownloadQuality.QUALITY_FLU: StringResource.QUALITY_FLU,
DownloadQuality.QUALITY_SD: StringResource.QUALITY_SD,
DownloadQuality.QUALITY_HD: StringResource.QUALITY_HD,
DownloadQuality.QUALITY_FHD: StringResource.QUALITY_FHD,
DownloadQuality.QUALITY_OD: StringResource.QUALITY_OD,
DownloadQuality.QUALITY_240P: StringResource.QUALITY_240P,
DownloadQuality.QUALITY_360P: StringResource.QUALITY_360P,
DownloadQuality.QUALITY_480P: StringResource.QUALITY_480P,
DownloadQuality.QUALITY_540P: StringResource.QUALITY_540P,
DownloadQuality.QUALITY_720P: StringResource.QUALITY_720P,
DownloadQuality.QUALITY_1080P: StringResource.QUALITY_1080P,
DownloadQuality.QUALITY_2K: StringResource.QUALITY_2K,
DownloadQuality.QUALITY_4K: StringResource.QUALITY_4K,
DownloadQuality.QUALITY_UNK: "",
};
/// convert to quality by transcodePlayList
static List<VideoQuality> convertToVideoQualityList(Map<String, PlayInfoStream> transcodePlayList) {
......@@ -18,7 +34,7 @@ class VideoQualityUtils {
/// convert to quality by PlayInfoStream
static VideoQuality convertToVideoQuality(PlayInfoStream stream) {
VideoQuality quality = new VideoQuality();
VideoQuality quality = VideoQuality();
quality.bitrate = stream.bitrate;
quality.name = stream.id;
quality.title = stream.name;
......@@ -29,41 +45,43 @@ class VideoQualityUtils {
/// convert to quality by FTXBitrateItem
static VideoQuality convertToVideoQualityByBitrate(BuildContext context, FTXBitrateItem bitrateItem) {
VideoQuality quality = new VideoQuality();
VideoQuality quality = VideoQuality();
quality.bitrate = bitrateItem.bitrate;
quality.index = bitrateItem.index;
quality.height = bitrateItem.height;
quality.width = bitrateItem.width;
formatVideoQuality(quality);
quality.title = formatVideoQuality(quality.width, quality.height);
return quality;
}
/// format quality title by size
static void formatVideoQuality(VideoQuality quality) {
int minValue = min(quality.width, quality.height);
static String formatVideoQuality(int width, int height) {
int minValue = min(width, height);
String title = " (${minValue}dp)";
if (minValue == 240 || minValue == 180) {
quality.title = "${StringResource.QUALITY_FLU} (${minValue}dp)";
} else if (minValue == 480 || minValue == 360) {
quality.title = "${StringResource.QUALITY_SD} (${minValue}dp)";
title = StringResource.QUALITY_240P;
} else if (minValue == 360) {
title = StringResource.QUALITY_360P;
} else if (minValue == 480) {
title = StringResource.QUALITY_480P;
} else if (minValue == 540) {
quality.title = "${StringResource.QUALITY_FSD} (${minValue}dp)";
title = StringResource.QUALITY_540P;
} else if (minValue == 720) {
quality.title = "${StringResource.QUALITY_HD} (${minValue}dp)";
title = StringResource.QUALITY_720P;
} else if (minValue == 1080) {
quality.title = "${StringResource.QUALITY_FHD2} (${minValue}dp)";
title = StringResource.QUALITY_1080P;
} else if (minValue == 1440) {
quality.title = "${StringResource.QUALITY_2K} (${minValue}dp)";
title = StringResource.QUALITY_2K;
} else if (minValue == 2160) {
quality.title = "${StringResource.QUALITY_4K} (${minValue}dp)";
} else {
quality.title = " (${minValue}dp)";
title = StringResource.QUALITY_4K;
}
return title;
}
/// convert to quality by FTXBitrateItem and resolutionNames
static VideoQuality convertToVideoQualityByResolution(
FTXBitrateItem bitrateItem, List<ResolutionName> resolutionNames) {
VideoQuality quality = new VideoQuality();
VideoQuality quality = VideoQuality();
quality.bitrate = bitrateItem.bitrate;
quality.index = bitrateItem.index;
bool getName = false;
......@@ -94,4 +112,16 @@ class VideoQualityUtils {
}
return qualityName;
}
/// 根据videoQuality,转化为视频下载需要用到的画质id
/// @param width 宽度
/// @param height 高度
/// @return [DownloadQuality];
static int getCacheVideoQualityIndex(int width, int height) {
return CommonUtils.getDownloadQualityBySize(width, height);
}
static String getNameByCacheQualityId(int cacheQualityId) {
return downloadQualityMap[cacheQualityId] ?? "";
}
}
......@@ -167,9 +167,9 @@ class _VideoBottomViewState extends State<VideoBottomView> {
max: _videoDuration,
value: _currentDuration,
bufferedValue: _bufferedDuration,
activeColor: const Color(ColorResource.COLOR_MAIN_THEME),
activeColor: const Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
inactiveColor: const Color(ColorResource.COLOR_GRAY),
sliderColor: const Color(ColorResource.COLOR_MAIN_THEME),
sliderColor: const Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
sliderOutterColor: Colors.white,
progressHeight: 2,
sliderRadius: 4,
......
......@@ -117,7 +117,7 @@ class _SuperPlayerMoreViewState extends State<SuperPlayerMoreView> {
style: ThemeResource.getCommonLabelTextStyle(),
),
Switch(
activeColor: const Color(ColorResource.COLOR_MAIN_THEME),
activeColor: const Color(ColorResource.COLOR_SLIDER_MAIN_THEME),
value: _isOpenAccelerate,
onChanged: _onChangeAccelerate)
],
......
......@@ -5,8 +5,11 @@ class _VideoTitleView extends StatefulWidget {
final String _title;
final _VideoTitleController _controller;
final bool initIsFullScreen;
final bool showDownload;
final bool isDownloaded;
const _VideoTitleView(this._controller, this.initIsFullScreen, this._title, GlobalKey<_VideoTitleViewState> key)
const _VideoTitleView(this._controller, this.initIsFullScreen, this._title, this.showDownload, this.isDownloaded,
GlobalKey<_VideoTitleViewState> key)
: super(key: key);
@override
......@@ -34,6 +37,7 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
fit: BoxFit.fill)),
child: Row(
children: [
// back
InkWell(
onTap: _onTapBackBtn,
child: const Image(
......@@ -42,11 +46,42 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
image: AssetImage("images/superplayer_btn_back_play.png", package: StringResource.PKG_NAME),
),
),
// video name
Text(
_title,
style: const TextStyle(fontSize: 11, color: Colors.white),
),
const Expanded(child: SizedBox()),
const Expanded(child: SizedBox()), //
// download
Visibility(
visible: _isFullScreen && widget.showDownload,
child: InkWell(
onTap: _onTapDownload,
child: Container(
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.only(left: 8, right: 8),
width: 40,
height: 40,
child: Stack(
children: [
const Image(
image: AssetImage("images/superplayer_ic_vod_download.png", package: StringResource.PKG_NAME)),
Visibility(
visible: widget.isDownloaded,
child: const Positioned(
right: 0,
bottom: 0,
child: Image(
width: 12,
height: 12,
image: AssetImage("images/superplayer_ic_vod_check_done.png",
package: StringResource.PKG_NAME))))
],
),
),
),
),
// more menu
Visibility(
visible: _isFullScreen,
child: InkWell(
......@@ -70,6 +105,12 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
widget._controller._onTapBack();
}
void _onTapDownload() {
if (!widget.isDownloaded) {
widget._controller._onTapDownload();
}
}
void updateTitle(String name) {
if (mounted) {
setState(() {
......@@ -88,6 +129,7 @@ class _VideoTitleViewState extends State<_VideoTitleView> {
class _VideoTitleController {
final Function _onTapBack;
final Function _onTapMore;
final Function _onTapDownload;
_VideoTitleController(this._onTapBack, this._onTapMore);
_VideoTitleController(this._onTapBack, this._onTapMore, this._onTapDownload);
}
......@@ -33,6 +33,8 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
bool _isShowControlView = false;
bool _isShowQualityListView = false;
bool _isShowDownload = false;
bool _isDownloaded = false;
late BottomViewController _bottomViewController;
late QualityListViewController _qualitListViewController;
......@@ -43,6 +45,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
StreamSubscription? _volumeSubscription;
StreamSubscription? _pipSubscription;
FTXDownloadListener? downloadListener;
/// init
Timer _controlViewTimer = Timer(const Duration(milliseconds: _controlViewShowTime), () {});
......@@ -66,8 +69,16 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
TXPipController.instance.exitAndReleaseCurrentPip();
_playController = widget._controller;
_superPlayerFullUIController = SuperPlayerFullScreenController(_updateState);
_titleViewController = _VideoTitleController(_onTapBack, () {
_moreViewKey.currentState?.toggleShowMoreView();
_titleViewController = _VideoTitleController(
_onTapBack,
// onTapMore
() => _moreViewKey.currentState?.toggleShowMoreView(),
// onTapDownload
() {
if (_playController.videoModel != null) {
_playController.startDownload();
EasyLoading.showToast("开始下载");
}
});
_bottomViewController =
BottomViewController(_onTapPlayControl, _onControlFullScreen, _onControlQualityListView, (value) {
......@@ -164,14 +175,15 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
}, () {
// onRcvFirstIframe
_coverViewKey.currentState?.hideCover();
_refreshDownloadStatus();
// 收到首帧事件后,先用播放器内核解析出来的分辨率对播放器大小进行调整
_calculateSize(_playController.videoWidth, _playController.videoHeight);
}, () {
// onPlayLoading
setState(() {
//预加载模式进行特殊处理
if(_playController.videoModel!.playAction == SuperPlayerModel.PLAY_ACTION_PRELOAD) {
if(_playController.callResume) {
if (_playController.videoModel!.playAction == SuperPlayerModel.PLAY_ACTION_PRELOAD) {
if (_playController.callResume) {
_isLoading = true;
}
} else {
......@@ -215,6 +227,9 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return SuperPlayerFullScreenView(_playController, _superPlayerFullUIController);
}));
if (null != downloadListener) {
DownloadHelper.instance.removeDownloadListener(downloadListener!);
}
WidgetsBinding.instance.removeObserver(this);
_playController._updatePlayerUIStatus(SuperPlayerUIStatus.FULLSCREEN_MODE);
_videoBottomKey.currentState?.updateUIStatus(SuperPlayerUIStatus.FULLSCREEN_MODE);
......@@ -229,6 +244,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
hideControlView();
});
WidgetsBinding.instance.addObserver(this);
_initDownloadStatus();
}
void _initPlayerState() {
......@@ -260,6 +276,26 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
}
}
void _initDownloadStatus() {
if (null != downloadListener) {
DownloadHelper.instance.removeDownloadListener(downloadListener!);
}
DownloadHelper.instance.addDownloadListener(downloadListener = FTXDownloadListener((event, info) {
if (event == TXVodPlayEvent.EVENT_DOWNLOAD_FINISH) {
EasyLoading.showToast("视频下载完成");
}
_refreshDownloadStatus();
}, (errorCode, errorMsg, info) {
EasyLoading.showToast("视频下载出错,code:$errorCode,msg:$errorMsg");
}));
if (null != _playController.videoModel) {
// 仅支持点播视频下载
_isShowDownload =
_playController.videoModel!.isEnableDownload && _playController.playerType == SuperPlayerType.VOD;
}
_refreshDownloadStatus();
}
void _updateState() {
// 刷新observer的绑定
_registerObserver();
......@@ -271,6 +307,10 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
}));
}
void _refreshDownloadStatus() async {
_isDownloaded = await _playController.isDownloaded();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (_isPlaying && !_isFloatingMode) {
......@@ -401,7 +441,7 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
Widget _getQualityListView() {
return Visibility(
visible: _isShowQualityListView,
child: QualityListView(_qualitListViewController, _playController.currentQualiyList,
child: QualityListView(_qualitListViewController, _playController.currentQualityList,
_playController.currentQuality, _qualityListKey),
);
}
......@@ -474,6 +514,8 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
_titleViewController,
_playController._playerUIStatus == SuperPlayerUIStatus.FULLSCREEN_MODE,
_playController._getPlayName(),
_isShowDownload,
_isDownloaded,
_videoTitleKey),
),
);
......@@ -666,6 +708,9 @@ class SuperPlayerViewState extends State<SuperPlayerView> with WidgetsBindingObs
WidgetsBinding.instance.removeObserver(this);
_pipSubscription?.cancel();
_volumeSubscription?.cancel();
if (null != downloadListener) {
DownloadHelper.instance.removeDownloadListener(downloadListener!);
}
super.dispose();
}
}
......
......@@ -51,6 +51,9 @@ flutter:
- images/superplayer_ic_vod_fullscreen.png
- images/superplayer_top_shadow.png
- images/superplayer_btn_back_play.png
- images/superplayer_ic_vod_download.png
- images/superplayer_ic_vod_check_done.png
- images/superplayer_ic_vod_download_list.png
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论