Merge branch 'main' into design
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import '../models/common/video_episode_type.dart';
|
import '../models/common/video_episode_type.dart';
|
||||||
|
|
||||||
@ -44,25 +45,38 @@ class EpisodeBottomSheet {
|
|||||||
title = '第${episode.title}话 ${episode.longTitle!}';
|
title = '第${episode.title}话 ${episode.longTitle!}';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return ListTile(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
SmartDialog.showToast('切换至「$title」');
|
SmartDialog.showToast('切换至「$title」');
|
||||||
changeFucCall.call(episode, index);
|
changeFucCall.call(episode, index);
|
||||||
},
|
},
|
||||||
dense: false,
|
child: Padding(
|
||||||
leading: isCurrentIndex
|
padding: const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
|
||||||
? Image.asset(
|
child: isFullScreen
|
||||||
'assets/images/live.gif',
|
? Text(
|
||||||
color: primary,
|
title,
|
||||||
height: 12,
|
maxLines: 1,
|
||||||
)
|
style: TextStyle(
|
||||||
: null,
|
fontSize: 14,
|
||||||
title: Text(
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
title,
|
),
|
||||||
style: TextStyle(
|
)
|
||||||
fontSize: 14,
|
: Row(
|
||||||
color: isCurrentIndex ? primary : onSurface,
|
children: [
|
||||||
),
|
NetworkImgLayer(width: 130, height: 75, src: episode.cover),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
maxLines: 2,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -192,22 +192,15 @@ class VideoHttp {
|
|||||||
// 视频信息 标题、简介
|
// 视频信息 标题、简介
|
||||||
static Future videoIntro({required String bvid}) async {
|
static Future videoIntro({required String bvid}) async {
|
||||||
var res = await Request().get(Api.videoIntro, data: {'bvid': bvid});
|
var res = await Request().get(Api.videoIntro, data: {'bvid': bvid});
|
||||||
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
|
if (res.data['code'] == 0) {
|
||||||
if (result.code == 0) {
|
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
|
||||||
return {'status': true, 'data': result.data!};
|
return {'status': true, 'data': result.data!};
|
||||||
} else {
|
} else {
|
||||||
Map errMap = {
|
|
||||||
-400: '请求错误',
|
|
||||||
-403: '权限不足',
|
|
||||||
-404: '视频资源失效',
|
|
||||||
62002: '稿件不可见',
|
|
||||||
62004: '稿件审核中',
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': null,
|
'data': null,
|
||||||
'code': result.code,
|
'code': res.data['code'],
|
||||||
'msg': errMap[result.code] ?? '请求异常',
|
'msg': res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ class HotVideoItemModel {
|
|||||||
this.dimension,
|
this.dimension,
|
||||||
this.shortLinkV2,
|
this.shortLinkV2,
|
||||||
this.firstFrame,
|
this.firstFrame,
|
||||||
|
this.cover,
|
||||||
this.pubLocation,
|
this.pubLocation,
|
||||||
this.seasontype,
|
this.seasontype,
|
||||||
this.isOgv,
|
this.isOgv,
|
||||||
@ -50,6 +51,7 @@ class HotVideoItemModel {
|
|||||||
Dimension? dimension;
|
Dimension? dimension;
|
||||||
String? shortLinkV2;
|
String? shortLinkV2;
|
||||||
String? firstFrame;
|
String? firstFrame;
|
||||||
|
String? cover;
|
||||||
String? pubLocation;
|
String? pubLocation;
|
||||||
int? seasontype;
|
int? seasontype;
|
||||||
bool? isOgv;
|
bool? isOgv;
|
||||||
@ -77,6 +79,7 @@ class HotVideoItemModel {
|
|||||||
dimension = Dimension.fromMap(json['dimension']);
|
dimension = Dimension.fromMap(json['dimension']);
|
||||||
shortLinkV2 = json["short_link_v2"];
|
shortLinkV2 = json["short_link_v2"];
|
||||||
firstFrame = json["first_frame"];
|
firstFrame = json["first_frame"];
|
||||||
|
cover = json["first_frame"];
|
||||||
pubLocation = json["pub_location"];
|
pubLocation = json["pub_location"];
|
||||||
seasontype = json["seasontype"];
|
seasontype = json["seasontype"];
|
||||||
isOgv = json["isOgv"];
|
isOgv = json["isOgv"];
|
||||||
|
@ -39,6 +39,14 @@ extension VideoQualityCode on VideoQuality {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int? toCode(VideoQuality quality) {
|
||||||
|
final index = VideoQuality.values.indexOf(quality);
|
||||||
|
if (index != -1 && index < _codeList.length) {
|
||||||
|
return _codeList[index];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoQualityDesc on VideoQuality {
|
extension VideoQualityDesc on VideoQuality {
|
||||||
|
@ -67,6 +67,7 @@ class VideoDetailData {
|
|||||||
String? likeIcon;
|
String? likeIcon;
|
||||||
bool? needJumpBv;
|
bool? needJumpBv;
|
||||||
String? epId;
|
String? epId;
|
||||||
|
List<Staff>? staff;
|
||||||
|
|
||||||
VideoDetailData({
|
VideoDetailData({
|
||||||
this.bvid,
|
this.bvid,
|
||||||
@ -103,6 +104,7 @@ class VideoDetailData {
|
|||||||
this.likeIcon,
|
this.likeIcon,
|
||||||
this.needJumpBv,
|
this.needJumpBv,
|
||||||
this.epId,
|
this.epId,
|
||||||
|
this.staff,
|
||||||
});
|
});
|
||||||
|
|
||||||
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
||||||
@ -155,6 +157,9 @@ class VideoDetailData {
|
|||||||
if (json['redirect_url'] != null) {
|
if (json['redirect_url'] != null) {
|
||||||
epId = resolveEpId(json['redirect_url']);
|
epId = resolveEpId(json['redirect_url']);
|
||||||
}
|
}
|
||||||
|
staff = json["staff"] != null
|
||||||
|
? List<Staff>.from(json["staff"]!.map((e) => Staff.fromJson(e)))
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String resolveEpId(url) {
|
String resolveEpId(url) {
|
||||||
@ -377,6 +382,7 @@ class Part {
|
|||||||
String? weblink;
|
String? weblink;
|
||||||
Dimension? dimension;
|
Dimension? dimension;
|
||||||
String? firstFrame;
|
String? firstFrame;
|
||||||
|
String? cover;
|
||||||
|
|
||||||
Part({
|
Part({
|
||||||
this.cid,
|
this.cid,
|
||||||
@ -388,6 +394,7 @@ class Part {
|
|||||||
this.weblink,
|
this.weblink,
|
||||||
this.dimension,
|
this.dimension,
|
||||||
this.firstFrame,
|
this.firstFrame,
|
||||||
|
this.cover,
|
||||||
});
|
});
|
||||||
|
|
||||||
fromRawJson(String str) => Part.fromJson(json.decode(str));
|
fromRawJson(String str) => Part.fromJson(json.decode(str));
|
||||||
@ -406,6 +413,7 @@ class Part {
|
|||||||
? null
|
? null
|
||||||
: Dimension.fromJson(json["dimension"]);
|
: Dimension.fromJson(json["dimension"]);
|
||||||
firstFrame = json["first_frame"];
|
firstFrame = json["first_frame"];
|
||||||
|
cover = json["first_frame"];
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
@ -629,6 +637,7 @@ class EpisodeItem {
|
|||||||
this.attribute,
|
this.attribute,
|
||||||
this.page,
|
this.page,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
|
this.cover,
|
||||||
});
|
});
|
||||||
int? seasonId;
|
int? seasonId;
|
||||||
int? sectionId;
|
int? sectionId;
|
||||||
@ -639,6 +648,7 @@ class EpisodeItem {
|
|||||||
int? attribute;
|
int? attribute;
|
||||||
Part? page;
|
Part? page;
|
||||||
String? bvid;
|
String? bvid;
|
||||||
|
String? cover;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
seasonId = json['season_id'];
|
seasonId = json['season_id'];
|
||||||
@ -650,5 +660,46 @@ class EpisodeItem {
|
|||||||
attribute = json['attribute'];
|
attribute = json['attribute'];
|
||||||
page = Part.fromJson(json['page']);
|
page = Part.fromJson(json['page']);
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
cover = json['arc']['pic'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Staff {
|
||||||
|
Staff({
|
||||||
|
this.mid,
|
||||||
|
this.title,
|
||||||
|
this.name,
|
||||||
|
this.face,
|
||||||
|
this.vip,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
String? title;
|
||||||
|
String? name;
|
||||||
|
String? face;
|
||||||
|
int? status;
|
||||||
|
Vip? vip;
|
||||||
|
|
||||||
|
Staff.fromJson(Map<String, dynamic> json) {
|
||||||
|
mid = json['mid'];
|
||||||
|
title = json['title'];
|
||||||
|
name = json['name'];
|
||||||
|
face = json['face'];
|
||||||
|
vip = Vip.fromJson(json['vip']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Vip {
|
||||||
|
Vip({
|
||||||
|
this.type,
|
||||||
|
this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? type;
|
||||||
|
int? status;
|
||||||
|
|
||||||
|
Vip.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
status = json['status'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,51 +131,37 @@ class BangumiIntroController extends GetxController {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('选择投币个数'),
|
title: const Text('选择投币个数'),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24),
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [1, 2]
|
||||||
RadioListTile(
|
.map(
|
||||||
value: 1,
|
(e) => RadioListTile(
|
||||||
title: const Text('1枚'),
|
value: e,
|
||||||
groupValue: _tempThemeValue,
|
title: Text('$e枚'),
|
||||||
onChanged: (value) {
|
groupValue: _tempThemeValue,
|
||||||
_tempThemeValue = value!;
|
onChanged: (value) async {
|
||||||
Get.appUpdate();
|
_tempThemeValue = value!;
|
||||||
},
|
setState(() {});
|
||||||
),
|
var res = await VideoHttp.coinVideo(
|
||||||
RadioListTile(
|
bvid: bvid, multiply: _tempThemeValue);
|
||||||
value: 2,
|
if (res['status']) {
|
||||||
title: const Text('2枚'),
|
SmartDialog.showToast('投币成功 👏');
|
||||||
groupValue: _tempThemeValue,
|
hasCoin.value = true;
|
||||||
onChanged: (value) {
|
bangumiDetail.value.stat!['coins'] =
|
||||||
_tempThemeValue = value!;
|
bangumiDetail.value.stat!['coins'] +
|
||||||
Get.appUpdate();
|
_tempThemeValue;
|
||||||
},
|
} else {
|
||||||
),
|
SmartDialog.showToast(res['msg']);
|
||||||
],
|
}
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
var res = await VideoHttp.coinVideo(
|
|
||||||
bvid: bvid, multiply: _tempThemeValue);
|
|
||||||
if (res['status']) {
|
|
||||||
SmartDialog.showToast('投币成功 👏');
|
|
||||||
hasCoin.value = true;
|
|
||||||
bangumiDetail.value.stat!['coins'] =
|
|
||||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
}
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -229,13 +215,15 @@ class BangumiIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改分P或番剧分集
|
// 修改分P或番剧分集
|
||||||
Future changeSeasonOrbangu(bvid, cid, aid) async {
|
Future changeSeasonOrbangu(bvid, cid, aid, cover) async {
|
||||||
// 重新获取视频资源
|
// 重新获取视频资源
|
||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid.value = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
|
videoDetailCtr.oid.value = aid;
|
||||||
|
videoDetailCtr.cover.value = cover;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
@ -294,7 +282,8 @@ class BangumiIntroController extends GetxController {
|
|||||||
int cid = episodes[nextIndex].cid!;
|
int cid = episodes[nextIndex].cid!;
|
||||||
String bvid = episodes[nextIndex].bvid!;
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
int aid = episodes[nextIndex].aid!;
|
int aid = episodes[nextIndex].aid!;
|
||||||
changeSeasonOrbangu(bvid, cid, aid);
|
String cover = episodes[nextIndex].cover!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid, cover);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放器底栏 选集 回调
|
// 播放器底栏 选集 回调
|
||||||
@ -315,7 +304,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
sheetHeight: Get.size.height,
|
sheetHeight: Get.size.height,
|
||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
changeFucCall: (item, index) {
|
changeFucCall: (item, index) {
|
||||||
changeSeasonOrbangu(item.bvid, item.cid, item.aid);
|
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
).buildShowContent(Get.context!),
|
).buildShowContent(Get.context!),
|
||||||
|
@ -322,8 +322,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
pages: widget.bangumiDetail!.episodes!,
|
pages: widget.bangumiDetail!.episodes!,
|
||||||
cid: cid! ?? widget.bangumiDetail!.episodes!.first.cid!,
|
cid: cid! ?? widget.bangumiDetail!.episodes!.first.cid!,
|
||||||
sheetHeight: sheetHeight,
|
sheetHeight: sheetHeight,
|
||||||
changeFuc: (bvid, cid, aid) =>
|
changeFuc: (bvid, cid, aid, cover) => bangumiIntroController
|
||||||
bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
|
.changeSeasonOrbangu(bvid, cid, aid, cover),
|
||||||
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
||||||
bangumiIntroController: bangumiIntroController,
|
bangumiIntroController: bangumiIntroController,
|
||||||
)
|
)
|
||||||
|
@ -84,9 +84,12 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
item.bvid,
|
item.bvid,
|
||||||
item.cid,
|
item.cid,
|
||||||
item.aid,
|
item.aid,
|
||||||
|
item.cover,
|
||||||
);
|
);
|
||||||
_bottomSheetController?.close();
|
if (_bottomSheetController != null) {
|
||||||
currentIndex = i;
|
_bottomSheetController?.close();
|
||||||
|
}
|
||||||
|
currentIndex.value = i;
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
|||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24),
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -51,7 +51,7 @@ class VideoDetailController extends GetxController
|
|||||||
/// 播放器配置 画质 音质 解码格式
|
/// 播放器配置 画质 音质 解码格式
|
||||||
late VideoQuality currentVideoQa;
|
late VideoQuality currentVideoQa;
|
||||||
AudioQuality? currentAudioQa;
|
AudioQuality? currentAudioQa;
|
||||||
late VideoDecodeFormats currentDecodeFormats;
|
VideoDecodeFormats? currentDecodeFormats;
|
||||||
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
||||||
RxBool autoPlay = true.obs;
|
RxBool autoPlay = true.obs;
|
||||||
// 视频资源是否有效
|
// 视频资源是否有效
|
||||||
@ -73,6 +73,7 @@ class VideoDetailController extends GetxController
|
|||||||
ReplyItemModel? firstFloor;
|
ReplyItemModel? firstFloor;
|
||||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
RxString bgCover = ''.obs;
|
RxString bgCover = ''.obs;
|
||||||
|
RxString cover = ''.obs;
|
||||||
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
||||||
|
|
||||||
late VideoItem firstVideo;
|
late VideoItem firstVideo;
|
||||||
@ -107,6 +108,7 @@ class VideoDetailController extends GetxController
|
|||||||
BottomControlType.fullscreen,
|
BottomControlType.fullscreen,
|
||||||
].obs;
|
].obs;
|
||||||
RxDouble sheetHeight = 0.0.obs;
|
RxDouble sheetHeight = 0.0.obs;
|
||||||
|
RxString archiveSourceType = 'dash'.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -119,10 +121,12 @@ class VideoDetailController extends GetxController
|
|||||||
var args = argMap['videoItem'];
|
var args = argMap['videoItem'];
|
||||||
if (args.pic != null && args.pic != '') {
|
if (args.pic != null && args.pic != '') {
|
||||||
videoItem['pic'] = args.pic;
|
videoItem['pic'] = args.pic;
|
||||||
|
cover.value = args.pic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.contains('pic')) {
|
if (keys.contains('pic')) {
|
||||||
videoItem['pic'] = argMap['pic'];
|
videoItem['pic'] = argMap['pic'];
|
||||||
|
cover.value = argMap['pic'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabCtr = TabController(length: 2, vsync: this);
|
tabCtr = TabController(length: 2, vsync: this);
|
||||||
@ -190,37 +194,43 @@ class VideoDetailController extends GetxController
|
|||||||
plPlayerController.isBuffering.value = false;
|
plPlayerController.isBuffering.value = false;
|
||||||
plPlayerController.buffered.value = Duration.zero;
|
plPlayerController.buffered.value = Duration.zero;
|
||||||
|
|
||||||
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
if (archiveSourceType.value == 'dash') {
|
||||||
List<VideoItem> videoList =
|
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
||||||
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
List<VideoItem> videoList =
|
||||||
try {
|
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
||||||
firstVideo = videoList
|
try {
|
||||||
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
|
firstVideo = videoList.firstWhere(
|
||||||
} catch (_) {
|
(i) => i.codecs!.startsWith(currentDecodeFormats?.code));
|
||||||
if (currentVideoQa == VideoQuality.dolbyVision) {
|
} catch (_) {
|
||||||
firstVideo = videoList.first;
|
if (currentVideoQa == VideoQuality.dolbyVision) {
|
||||||
currentDecodeFormats =
|
firstVideo = videoList.first;
|
||||||
VideoDecodeFormatsCode.fromString(videoList.first.codecs!)!;
|
currentDecodeFormats =
|
||||||
} else {
|
VideoDecodeFormatsCode.fromString(videoList.first.codecs!)!;
|
||||||
// 当前格式不可用
|
} else {
|
||||||
currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get(
|
// 当前格式不可用
|
||||||
SettingBoxKey.defaultDecode,
|
currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get(
|
||||||
defaultValue: VideoDecodeFormats.values.last.code))!;
|
SettingBoxKey.defaultDecode,
|
||||||
firstVideo = videoList
|
defaultValue: VideoDecodeFormats.values.last.code))!;
|
||||||
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
|
firstVideo = videoList.firstWhere(
|
||||||
|
(i) => i.codecs!.startsWith(currentDecodeFormats?.code));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
videoUrl = firstVideo.baseUrl!;
|
||||||
|
|
||||||
|
/// 根据currentAudioQa 重新设置audioUrl
|
||||||
|
if (currentAudioQa != null) {
|
||||||
|
final AudioItem firstAudio = data.dash!.audio!.firstWhere(
|
||||||
|
(AudioItem i) => i.id == currentAudioQa!.code,
|
||||||
|
orElse: () => data.dash!.audio!.first,
|
||||||
|
);
|
||||||
|
audioUrl = firstAudio.baseUrl ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
videoUrl = firstVideo.baseUrl!;
|
|
||||||
|
|
||||||
/// 根据currentAudioQa 重新设置audioUrl
|
if (archiveSourceType.value == 'durl') {
|
||||||
if (currentAudioQa != null) {
|
cacheVideoQa = VideoQualityCode.toCode(currentVideoQa);
|
||||||
final AudioItem firstAudio = data.dash!.audio!.firstWhere(
|
queryVideoUrl();
|
||||||
(AudioItem i) => i.id == currentAudioQa!.code,
|
|
||||||
orElse: () => data.dash!.audio!.first,
|
|
||||||
);
|
|
||||||
audioUrl = firstAudio.baseUrl ?? '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInit();
|
playerInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +283,8 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
// 视频链接
|
// 视频链接
|
||||||
Future queryVideoUrl() async {
|
Future queryVideoUrl() async {
|
||||||
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
|
var result =
|
||||||
|
await VideoHttp.videoUrl(cid: cid.value, bvid: bvid, qn: cacheVideoQa);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
data = result['data'];
|
data = result['data'];
|
||||||
if (data.acceptDesc!.isNotEmpty && data.acceptDesc!.contains('试看')) {
|
if (data.acceptDesc!.isNotEmpty && data.acceptDesc!.contains('试看')) {
|
||||||
@ -291,8 +302,22 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
if (data.durl != null) {
|
||||||
|
archiveSourceType.value = 'durl';
|
||||||
|
videoUrl = data.durl!.first.url!;
|
||||||
|
audioUrl = '';
|
||||||
|
defaultST = Duration.zero;
|
||||||
|
firstVideo = VideoItem();
|
||||||
|
currentVideoQa = VideoQualityCode.fromCode(data.quality!)!;
|
||||||
|
if (autoPlay.value) {
|
||||||
|
await playerInit();
|
||||||
|
isShowCover.value = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
final List<VideoItem> allVideosList = data.dash!.video!;
|
final List<VideoItem> allVideosList = data.dash!.video!;
|
||||||
try {
|
try {
|
||||||
|
archiveSourceType.value = 'dash';
|
||||||
// 当前可播放的最高质量视频
|
// 当前可播放的最高质量视频
|
||||||
int currentHighVideoQa = allVideosList.first.quality!.code;
|
int currentHighVideoQa = allVideosList.first.quality!.code;
|
||||||
// 预设的画质为null,则当前可用的最高质量
|
// 预设的画质为null,则当前可用的最高质量
|
||||||
@ -322,7 +347,7 @@ class VideoDetailController extends GetxController
|
|||||||
// 当前视频没有对应格式返回第一个
|
// 当前视频没有对应格式返回第一个
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
for (var i in supportDecodeFormats) {
|
for (var i in supportDecodeFormats) {
|
||||||
if (i.startsWith(currentDecodeFormats.code)) {
|
if (i.startsWith(currentDecodeFormats?.code)) {
|
||||||
flag = true;
|
flag = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,7 +361,7 @@ class VideoDetailController extends GetxController
|
|||||||
/// 取出符合当前解码格式的videoItem
|
/// 取出符合当前解码格式的videoItem
|
||||||
try {
|
try {
|
||||||
firstVideo = videosList.firstWhere(
|
firstVideo = videosList.firstWhere(
|
||||||
(e) => e.codecs!.startsWith(currentDecodeFormats.code));
|
(e) => e.codecs!.startsWith(currentDecodeFormats?.code));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
firstVideo = videosList.first;
|
firstVideo = videosList.first;
|
||||||
}
|
}
|
||||||
|
@ -219,50 +219,36 @@ class VideoIntroController extends GetxController {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('选择投币个数'),
|
title: const Text('选择投币个数'),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24),
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [1, 2]
|
||||||
RadioListTile(
|
.map(
|
||||||
value: 1,
|
(e) => RadioListTile(
|
||||||
title: const Text('1枚'),
|
value: e,
|
||||||
groupValue: _tempThemeValue,
|
title: Text('$e枚'),
|
||||||
onChanged: (value) {
|
groupValue: _tempThemeValue,
|
||||||
_tempThemeValue = value!;
|
onChanged: (value) async {
|
||||||
Get.appUpdate();
|
_tempThemeValue = value!;
|
||||||
},
|
setState(() {});
|
||||||
),
|
var res = await VideoHttp.coinVideo(
|
||||||
RadioListTile(
|
bvid: bvid, multiply: _tempThemeValue);
|
||||||
value: 2,
|
if (res['status']) {
|
||||||
title: const Text('2枚'),
|
SmartDialog.showToast('投币成功 👏');
|
||||||
groupValue: _tempThemeValue,
|
hasCoin.value = true;
|
||||||
onChanged: (value) {
|
videoDetail.value.stat!.coin =
|
||||||
_tempThemeValue = value!;
|
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||||
Get.appUpdate();
|
} else {
|
||||||
},
|
SmartDialog.showToast(res['msg']);
|
||||||
),
|
}
|
||||||
],
|
Get.back();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
actions: [
|
|
||||||
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
var res = await VideoHttp.coinVideo(
|
|
||||||
bvid: bvid, multiply: _tempThemeValue);
|
|
||||||
if (res['status']) {
|
|
||||||
SmartDialog.showToast('投币成功 👏');
|
|
||||||
hasCoin.value = true;
|
|
||||||
videoDetail.value.stat!.coin =
|
|
||||||
videoDetail.value.stat!.coin! + _tempThemeValue;
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
}
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('确定'))
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -446,7 +432,7 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 修改分P或番剧分集
|
// 修改分P或番剧分集
|
||||||
Future changeSeasonOrbangu(bvid, cid, aid) async {
|
Future changeSeasonOrbangu(bvid, cid, aid, cover) async {
|
||||||
// 重新获取视频资源
|
// 重新获取视频资源
|
||||||
final VideoDetailController videoDetailCtr =
|
final VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: heroTag);
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
@ -461,6 +447,7 @@ class VideoIntroController extends GetxController {
|
|||||||
videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid);
|
videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid);
|
||||||
videoDetailCtr.cid.value = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
|
videoDetailCtr.cover.value = cover;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
@ -508,6 +495,7 @@ class VideoIntroController extends GetxController {
|
|||||||
void nextPlay() {
|
void nextPlay() {
|
||||||
final List episodes = [];
|
final List episodes = [];
|
||||||
bool isPages = false;
|
bool isPages = false;
|
||||||
|
late String cover;
|
||||||
if (videoDetail.value.ugcSeason != null) {
|
if (videoDetail.value.ugcSeason != null) {
|
||||||
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
||||||
final List<SectionItem> sections = ugcSeason.sections!;
|
final List<SectionItem> sections = ugcSeason.sections!;
|
||||||
@ -524,6 +512,7 @@ class VideoIntroController extends GetxController {
|
|||||||
final int currentIndex =
|
final int currentIndex =
|
||||||
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
|
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
|
||||||
int nextIndex = currentIndex + 1;
|
int nextIndex = currentIndex + 1;
|
||||||
|
cover = episodes[nextIndex].cover;
|
||||||
final VideoDetailController videoDetailCtr =
|
final VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: heroTag);
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
@ -540,7 +529,7 @@ class VideoIntroController extends GetxController {
|
|||||||
final int cid = episodes[nextIndex].cid!;
|
final int cid = episodes[nextIndex].cid!;
|
||||||
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
|
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
|
||||||
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
|
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
|
||||||
changeSeasonOrbangu(rBvid, cid, rAid);
|
changeSeasonOrbangu(rBvid, cid, rAid, cover);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置关注分组
|
// 设置关注分组
|
||||||
@ -605,10 +594,11 @@ class VideoIntroController extends GetxController {
|
|||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
changeFucCall: (item, index) {
|
changeFucCall: (item, index) {
|
||||||
if (dataType == VideoEpidoesType.videoEpisode) {
|
if (dataType == VideoEpidoesType.videoEpisode) {
|
||||||
changeSeasonOrbangu(IdUtils.av2bv(item.aid), item.cid, item.aid);
|
changeSeasonOrbangu(
|
||||||
|
IdUtils.av2bv(item.aid), item.cid, item.aid, item.cover);
|
||||||
}
|
}
|
||||||
if (dataType == VideoEpidoesType.videoPart) {
|
if (dataType == VideoEpidoesType.videoPart) {
|
||||||
changeSeasonOrbangu(bvid, item.cid, null);
|
changeSeasonOrbangu(bvid, item.cid, null, item.cover);
|
||||||
}
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
|
@ -22,6 +22,7 @@ import 'widgets/fav_panel.dart';
|
|||||||
import 'widgets/intro_detail.dart';
|
import 'widgets/intro_detail.dart';
|
||||||
import 'widgets/page_panel.dart';
|
import 'widgets/page_panel.dart';
|
||||||
import 'widgets/season_panel.dart';
|
import 'widgets/season_panel.dart';
|
||||||
|
import 'widgets/staff_up_item.dart';
|
||||||
|
|
||||||
class VideoIntroPanel extends StatefulWidget {
|
class VideoIntroPanel extends StatefulWidget {
|
||||||
final String bvid;
|
final String bvid;
|
||||||
@ -382,11 +383,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
? videoIntroController.lastPlayCid.value
|
? videoIntroController.lastPlayCid.value
|
||||||
: widget.videoDetail!.pages!.first.cid,
|
: widget.videoDetail!.pages!.first.cid,
|
||||||
sheetHeight: videoDetailCtr.sheetHeight.value,
|
sheetHeight: videoDetailCtr.sheetHeight.value,
|
||||||
changeFuc: (bvid, cid, aid) =>
|
changeFuc: (bvid, cid, aid, cover) =>
|
||||||
videoIntroController.changeSeasonOrbangu(
|
videoIntroController.changeSeasonOrbangu(
|
||||||
bvid,
|
bvid,
|
||||||
cid,
|
cid,
|
||||||
aid,
|
aid,
|
||||||
|
cover,
|
||||||
),
|
),
|
||||||
videoIntroCtr: videoIntroController,
|
videoIntroCtr: videoIntroController,
|
||||||
),
|
),
|
||||||
@ -400,41 +402,46 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
pages: widget.videoDetail!.pages!,
|
pages: widget.videoDetail!.pages!,
|
||||||
cid: videoIntroController.lastPlayCid.value,
|
cid: videoIntroController.lastPlayCid.value,
|
||||||
sheetHeight: videoDetailCtr.sheetHeight.value,
|
sheetHeight: videoDetailCtr.sheetHeight.value,
|
||||||
changeFuc: (cid) => videoIntroController.changeSeasonOrbangu(
|
changeFuc: (cid, cover) =>
|
||||||
|
videoIntroController.changeSeasonOrbangu(
|
||||||
videoIntroController.bvid,
|
videoIntroController.bvid,
|
||||||
cid,
|
cid,
|
||||||
null,
|
null,
|
||||||
|
cover,
|
||||||
),
|
),
|
||||||
videoIntroCtr: videoIntroController,
|
videoIntroCtr: videoIntroController,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
GestureDetector(
|
if (widget.videoDetail!.staff == null)
|
||||||
onTap: onPushMember,
|
GestureDetector(
|
||||||
child: Container(
|
onTap: onPushMember,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
|
child: Container(
|
||||||
child: Row(
|
padding:
|
||||||
children: [
|
const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
|
||||||
NetworkImgLayer(
|
child: Row(
|
||||||
type: 'avatar',
|
children: [
|
||||||
src: widget.videoDetail!.owner!.face,
|
NetworkImgLayer(
|
||||||
width: 34,
|
type: 'avatar',
|
||||||
height: 34,
|
src: widget.videoDetail!.owner!.face,
|
||||||
fadeInDuration: Duration.zero,
|
width: 34,
|
||||||
fadeOutDuration: Duration.zero,
|
height: 34,
|
||||||
),
|
fadeInDuration: Duration.zero,
|
||||||
const SizedBox(width: 10),
|
fadeOutDuration: Duration.zero,
|
||||||
Text(owner.name, style: const TextStyle(fontSize: 13)),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
follower,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
|
||||||
color: outline,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const Spacer(),
|
Text(owner.name, style: const TextStyle(fontSize: 13)),
|
||||||
Obx(() => AnimatedOpacity(
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
follower,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||||
|
color: outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Obx(
|
||||||
|
() => AnimatedOpacity(
|
||||||
opacity:
|
opacity:
|
||||||
videoIntroController.followStatus.isEmpty ? 0 : 1,
|
videoIntroController.followStatus.isEmpty ? 0 : 1,
|
||||||
duration: const Duration(milliseconds: 50),
|
duration: const Duration(milliseconds: 50),
|
||||||
@ -474,11 +481,58 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
if (widget.videoDetail!.staff != null) ...[
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '创作团队',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall!
|
||||||
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const WidgetSpan(child: SizedBox(width: 6)),
|
||||||
|
TextSpan(
|
||||||
|
text: '${widget.videoDetail!.staff!.length}人',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: ListView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
for (int i = 0;
|
||||||
|
i < widget.videoDetail!.staff!.length;
|
||||||
|
i++) ...[
|
||||||
|
StaffUpItem(item: widget.videoDetail!.staff![i])
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@ -545,4 +599,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Widget StaffPanel(BuildContext context, videoIntroController) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
widget.changeFuc?.call(item.cid);
|
widget.changeFuc?.call(item.cid, item.cover);
|
||||||
currentIndex.value = i;
|
currentIndex.value = i;
|
||||||
_bottomSheetController?.close();
|
_bottomSheetController?.close();
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
@ -129,7 +129,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 35,
|
height: 55,
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
@ -163,7 +163,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.pages[i].pagePart!,
|
widget.pages[i].pagePart!,
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: isCurrentIndex
|
color: isCurrentIndex
|
||||||
|
@ -67,6 +67,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
IdUtils.av2bv(item.aid),
|
IdUtils.av2bv(item.aid),
|
||||||
item.cid,
|
item.cid,
|
||||||
item.aid,
|
item.aid,
|
||||||
|
item.cover,
|
||||||
);
|
);
|
||||||
currentIndex.value = i;
|
currentIndex.value = i;
|
||||||
_bottomSheetController?.close();
|
_bottomSheetController?.close();
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
class StaffUpItem extends StatelessWidget {
|
||||||
|
final Staff item;
|
||||||
|
|
||||||
|
const StaffUpItem({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final String heroTag = Utils.makeHeroTag(item.mid);
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 15),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Get.toNamed(
|
||||||
|
'/member?mid=${item.mid}',
|
||||||
|
arguments: {'face': item.face, 'heroTag': heroTag},
|
||||||
|
),
|
||||||
|
child: Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
src: item.face,
|
||||||
|
type: 'avatar',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 85,
|
||||||
|
child: Text(
|
||||||
|
item.name!,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: item.vip!.status == 1
|
||||||
|
? const Color.fromARGB(255, 251, 100, 163)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 85,
|
||||||
|
child: Text(
|
||||||
|
item.title!,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -458,11 +458,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
handlePlay();
|
handlePlay();
|
||||||
},
|
},
|
||||||
child: NetworkImgLayer(
|
child: Obx(
|
||||||
type: 'emote',
|
() => AnimatedOpacity(
|
||||||
src: vdCtr.videoItem['pic'],
|
duration: const Duration(milliseconds: 100), // 渐变动画的持续时间
|
||||||
width: Get.width,
|
opacity: 1, // 设置不透明度
|
||||||
height: videoHeight.value,
|
child: NetworkImgLayer(
|
||||||
|
type: 'emote',
|
||||||
|
src: vdCtr.cover.value,
|
||||||
|
width: Get.width,
|
||||||
|
height: videoHeight.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -180,15 +180,16 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
'当前音质 ${widget.videoDetailCtr!.currentAudioQa!.description}',
|
'当前音质 ${widget.videoDetailCtr!.currentAudioQa!.description}',
|
||||||
style: subTitleStyle),
|
style: subTitleStyle),
|
||||||
),
|
),
|
||||||
ListTile(
|
if (widget.videoDetailCtr!.currentDecodeFormats != null)
|
||||||
onTap: () => {Get.back(), showSetDecodeFormats()},
|
ListTile(
|
||||||
dense: true,
|
onTap: () => {Get.back(), showSetDecodeFormats()},
|
||||||
leading: const Icon(Icons.av_timer_outlined, size: 20),
|
dense: true,
|
||||||
title: const Text('解码格式', style: titleStyle),
|
leading: const Icon(Icons.av_timer_outlined, size: 20),
|
||||||
subtitle: Text(
|
title: const Text('解码格式', style: titleStyle),
|
||||||
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
subtitle: Text(
|
||||||
style: subTitleStyle),
|
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats!.description}',
|
||||||
),
|
style: subTitleStyle),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => {Get.back(), showSetRepeat()},
|
onTap: () => {Get.back(), showSetRepeat()},
|
||||||
dense: true,
|
dense: true,
|
||||||
@ -541,16 +542,24 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
|
|
||||||
/// 可用的质量分类
|
/// 可用的质量分类
|
||||||
int userfulQaSam = 0;
|
int userfulQaSam = 0;
|
||||||
final List<VideoItem> video = videoInfo.dash!.video!;
|
if (videoInfo.dash != null) {
|
||||||
final Set<int> idSet = {};
|
// dash格式视频一次请求会返回所有可播放的清晰度video
|
||||||
for (final VideoItem item in video) {
|
final List<VideoItem> video = videoInfo.dash!.video!;
|
||||||
final int id = item.id!;
|
final Set<int> idSet = {};
|
||||||
if (!idSet.contains(id)) {
|
for (final VideoItem item in video) {
|
||||||
idSet.add(id);
|
final int id = item.id!;
|
||||||
userfulQaSam++;
|
if (!idSet.contains(id)) {
|
||||||
|
idSet.add(id);
|
||||||
|
userfulQaSam++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (videoInfo.durl != null) {
|
||||||
|
// durl格式视频一次请求返回对应清晰度video
|
||||||
|
userfulQaSam = videoFormat.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@ -707,7 +716,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
void showSetDecodeFormats() {
|
void showSetDecodeFormats() {
|
||||||
// 当前选中的解码格式
|
// 当前选中的解码格式
|
||||||
final VideoDecodeFormats currentDecodeFormats =
|
final VideoDecodeFormats currentDecodeFormats =
|
||||||
widget.videoDetailCtr!.currentDecodeFormats;
|
widget.videoDetailCtr!.currentDecodeFormats!;
|
||||||
final VideoItem firstVideo = widget.videoDetailCtr!.firstVideo;
|
final VideoItem firstVideo = widget.videoDetailCtr!.firstVideo;
|
||||||
// 当前视频可用的解码格式
|
// 当前视频可用的解码格式
|
||||||
final List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
final List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||||
|
@ -101,7 +101,7 @@ class PlPlayerController {
|
|||||||
bool _isFirstTime = true;
|
bool _isFirstTime = true;
|
||||||
|
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
late Timer? _timerForSeek;
|
Timer? _timerForSeek;
|
||||||
Timer? _timerForVolume;
|
Timer? _timerForVolume;
|
||||||
Timer? _timerForShowingVolume;
|
Timer? _timerForShowingVolume;
|
||||||
Timer? _timerForGettingVolume;
|
Timer? _timerForGettingVolume;
|
||||||
@ -335,8 +335,10 @@ class PlPlayerController {
|
|||||||
}) {
|
}) {
|
||||||
// 如果实例尚未创建,则创建一个新实例
|
// 如果实例尚未创建,则创建一个新实例
|
||||||
_instance ??= PlPlayerController._();
|
_instance ??= PlPlayerController._();
|
||||||
_instance!._playerCount.value += 1;
|
if (videoType != 'none') {
|
||||||
_videoType.value = videoType;
|
_instance!._playerCount.value += 1;
|
||||||
|
_videoType.value = videoType;
|
||||||
|
}
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1120,9 +1122,6 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose({String type = 'single'}) async {
|
Future<void> dispose({String type = 'single'}) async {
|
||||||
print('dispose');
|
|
||||||
print('dispose: ${playerCount.value}');
|
|
||||||
|
|
||||||
// 每次减1,最后销毁
|
// 每次减1,最后销毁
|
||||||
if (type == 'single' && playerCount.value > 1) {
|
if (type == 'single' && playerCount.value > 1) {
|
||||||
_playerCount.value -= 1;
|
_playerCount.value -= 1;
|
||||||
@ -1132,7 +1131,6 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
_playerCount.value = 0;
|
_playerCount.value = 0;
|
||||||
try {
|
try {
|
||||||
print('dispose dispose ---------');
|
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timerForVolume?.cancel();
|
_timerForVolume?.cancel();
|
||||||
_timerForGettingVolume?.cancel();
|
_timerForGettingVolume?.cancel();
|
||||||
|
@ -26,7 +26,7 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
|
|||||||
static final List<MediaItem> _item = [];
|
static final List<MediaItem> _item = [];
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
bool enableBackgroundPlay = false;
|
bool enableBackgroundPlay = false;
|
||||||
PlPlayerController player = PlPlayerController.getInstance();
|
PlPlayerController player = PlPlayerController.getInstance(videoType: 'none');
|
||||||
|
|
||||||
VideoPlayerServiceHandler() {
|
VideoPlayerServiceHandler() {
|
||||||
revalidateSetting();
|
revalidateSetting();
|
||||||
|
@ -18,7 +18,7 @@ class AudioSessionHandler {
|
|||||||
session.configure(const AudioSessionConfiguration.music());
|
session.configure(const AudioSessionConfiguration.music());
|
||||||
|
|
||||||
session.interruptionEventStream.listen((event) {
|
session.interruptionEventStream.listen((event) {
|
||||||
final player = PlPlayerController.getInstance();
|
final player = PlPlayerController.getInstance(videoType: 'none');
|
||||||
if (event.begin) {
|
if (event.begin) {
|
||||||
if (!player.playerStatus.playing) return;
|
if (!player.playerStatus.playing) return;
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
@ -51,7 +51,7 @@ class AudioSessionHandler {
|
|||||||
|
|
||||||
// 耳机拔出暂停
|
// 耳机拔出暂停
|
||||||
session.becomingNoisyEventStream.listen((_) {
|
session.becomingNoisyEventStream.listen((_) {
|
||||||
final player = PlPlayerController.getInstance();
|
final player = PlPlayerController.getInstance(videoType: 'none');
|
||||||
if (player.playerStatus.playing) {
|
if (player.playerStatus.playing) {
|
||||||
player.pause();
|
player.pause();
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,8 @@ class ShutdownTimerService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SmartDialog.showToast("设置 $scheduledExitInMinutes 分钟后定时关闭");
|
SmartDialog.showToast("设置 $scheduledExitInMinutes 分钟后定时关闭");
|
||||||
_shutdownTimer = Timer(Duration(minutes: scheduledExitInMinutes),
|
_shutdownTimer = Timer(
|
||||||
() => _shutdownDecider());
|
Duration(minutes: scheduledExitInMinutes), () => _shutdownDecider());
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showTimeUpButPauseDialog() {
|
void _showTimeUpButPauseDialog() {
|
||||||
@ -59,7 +59,7 @@ class ShutdownTimerService {
|
|||||||
// Start the 10-second timer to auto close the dialog
|
// Start the 10-second timer to auto close the dialog
|
||||||
_autoCloseDialogTimer?.cancel();
|
_autoCloseDialogTimer?.cancel();
|
||||||
_autoCloseDialogTimer = Timer(const Duration(seconds: 10), () {
|
_autoCloseDialogTimer = Timer(const Duration(seconds: 10), () {
|
||||||
SmartDialog.dismiss();// Close the dialog
|
SmartDialog.dismiss(); // Close the dialog
|
||||||
_executeShutdown();
|
_executeShutdown();
|
||||||
});
|
});
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
@ -88,7 +88,8 @@ class ShutdownTimerService {
|
|||||||
_showShutdownDialog();
|
_showShutdownDialog();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
PlPlayerController plPlayerController =
|
||||||
|
PlPlayerController.getInstance(videoType: 'none');
|
||||||
if (!exitApp && !waitForPlayingCompleted) {
|
if (!exitApp && !waitForPlayingCompleted) {
|
||||||
if (!plPlayerController.playerStatus.playing) {
|
if (!plPlayerController.playerStatus.playing) {
|
||||||
//仅提示用户
|
//仅提示用户
|
||||||
@ -108,19 +109,22 @@ class ShutdownTimerService {
|
|||||||
//该方法依赖耦合实现,不够优雅
|
//该方法依赖耦合实现,不够优雅
|
||||||
isWaiting = true;
|
isWaiting = true;
|
||||||
}
|
}
|
||||||
void handleWaitingFinished(){
|
|
||||||
if(isWaiting){
|
void handleWaitingFinished() {
|
||||||
|
if (isWaiting) {
|
||||||
_showShutdownDialog();
|
_showShutdownDialog();
|
||||||
isWaiting = false;
|
isWaiting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _executeShutdown() {
|
void _executeShutdown() {
|
||||||
if (exitApp) {
|
if (exitApp) {
|
||||||
//退出app
|
//退出app
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
//暂停播放
|
//暂停播放
|
||||||
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
PlPlayerController plPlayerController =
|
||||||
|
PlPlayerController.getInstance(videoType: 'none');
|
||||||
if (plPlayerController.playerStatus.playing) {
|
if (plPlayerController.playerStatus.playing) {
|
||||||
plPlayerController.pause();
|
plPlayerController.pause();
|
||||||
waitForPlayingCompleted = true;
|
waitForPlayingCompleted = true;
|
||||||
|
Reference in New Issue
Block a user