Compare commits
2 Commits
design
...
feature-re
Author | SHA1 | Date | |
---|---|---|---|
c9265897d1 | |||
48b412bdbf |
@ -9,7 +9,6 @@ PODS:
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
@ -39,7 +38,7 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- permission_handler_apple (9.1.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- saver_gallery (0.0.1):
|
||||
@ -72,7 +71,7 @@ DEPENDENCIES:
|
||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
|
||||
@ -114,7 +113,7 @@ EXTERNAL SOURCES:
|
||||
auto_orientation:
|
||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
@ -167,7 +166,7 @@ SPEC CHECKSUMS:
|
||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||
connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
@ -181,7 +180,7 @@ SPEC CHECKSUMS:
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
@ -194,7 +193,7 @@ SPEC CHECKSUMS:
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||
webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
|
||||
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
||||
|
||||
|
@ -484,9 +484,6 @@ class Api {
|
||||
/// 激活buvid3
|
||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||
|
||||
/// 获取字幕配置
|
||||
static const getSubtitleConfig = '/x/player/v2';
|
||||
|
||||
/// 我的订阅
|
||||
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
||||
|
||||
|
@ -8,11 +8,9 @@ import '../models/model_rec_video_item.dart';
|
||||
import '../models/user/fav_folder.dart';
|
||||
import '../models/video/ai.dart';
|
||||
import '../models/video/play/url.dart';
|
||||
import '../models/video/subTitile/result.dart';
|
||||
import '../models/video_detail_res.dart';
|
||||
import '../utils/recommend_filter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/subtitle.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
@ -478,25 +476,6 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future getSubtitle({int? cid, String? bvid}) async {
|
||||
var res = await Request().get(Api.getSubtitleConfig, data: {
|
||||
'cid': cid,
|
||||
'bvid': bvid,
|
||||
});
|
||||
try {
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SubTitlteModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['msg']};
|
||||
}
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
}
|
||||
|
||||
// 视频排行
|
||||
static Future getRankVideoList(int rid) async {
|
||||
try {
|
||||
@ -519,12 +498,4 @@ class VideoHttp {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字幕内容
|
||||
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
||||
var res = await Request().get('https:$url');
|
||||
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
|
||||
final List body = res.data['body'];
|
||||
return {'content': content, 'body': body};
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ void main() async {
|
||||
.then((_) async {
|
||||
await GStrorage.init();
|
||||
await setupServiceLocator();
|
||||
clearLogs();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
RecommendFilter();
|
||||
|
@ -1,47 +0,0 @@
|
||||
enum SubtitleType {
|
||||
// 中文(中国)
|
||||
zhCN,
|
||||
// 中文(自动翻译)
|
||||
aizh,
|
||||
// 英语(自动生成)
|
||||
aien,
|
||||
}
|
||||
|
||||
extension SubtitleTypeExtension on SubtitleType {
|
||||
String get description {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return '中文(中国)';
|
||||
case SubtitleType.aizh:
|
||||
return '中文(自动翻译)';
|
||||
case SubtitleType.aien:
|
||||
return '英语(自动生成)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleIdExtension on SubtitleType {
|
||||
String get id {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return 'zh-CN';
|
||||
case SubtitleType.aizh:
|
||||
return 'ai-zh';
|
||||
case SubtitleType.aien:
|
||||
return 'ai-en';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleCodeExtension on SubtitleType {
|
||||
int get code {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return 1;
|
||||
case SubtitleType.aizh:
|
||||
return 2;
|
||||
case SubtitleType.aien:
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -47,23 +47,18 @@ class Vip {
|
||||
this.status,
|
||||
this.dueDate,
|
||||
this.label,
|
||||
this.nicknameColor,
|
||||
});
|
||||
|
||||
int? type;
|
||||
int? status;
|
||||
int? dueDate;
|
||||
Map? label;
|
||||
int? nicknameColor;
|
||||
|
||||
Vip.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
status = json['status'];
|
||||
dueDate = json['due_date'];
|
||||
label = json['label'];
|
||||
nicknameColor = json['nickname_color'] == ''
|
||||
? null
|
||||
: int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -437,8 +437,7 @@ class SearchArticleItemModel {
|
||||
pubTime = json['pub_time'];
|
||||
like = json['like'];
|
||||
title = Em.regTitle(json['title']);
|
||||
subTitle =
|
||||
Em.decodeHtmlEntities(json['title'].replaceAll(RegExp(r'<[^>]*>'), ''));
|
||||
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||
rankOffset = json['rank_offset'];
|
||||
mid = json['mid'];
|
||||
imageUrls = json['image_urls'];
|
||||
|
@ -1,20 +0,0 @@
|
||||
class SubTitileContentModel {
|
||||
double? from;
|
||||
double? to;
|
||||
int? location;
|
||||
String? content;
|
||||
|
||||
SubTitileContentModel({
|
||||
this.from,
|
||||
this.to,
|
||||
this.location,
|
||||
this.content,
|
||||
});
|
||||
|
||||
SubTitileContentModel.fromJson(Map<String, dynamic> json) {
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
location = json['location'];
|
||||
content = json['content'];
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../common/subtitle_type.dart';
|
||||
|
||||
class SubTitlteModel {
|
||||
SubTitlteModel({
|
||||
this.aid,
|
||||
this.bvid,
|
||||
this.cid,
|
||||
this.loginMid,
|
||||
this.loginMidHash,
|
||||
this.isOwner,
|
||||
this.name,
|
||||
this.subtitles,
|
||||
});
|
||||
|
||||
int? aid;
|
||||
String? bvid;
|
||||
int? cid;
|
||||
int? loginMid;
|
||||
String? loginMidHash;
|
||||
bool? isOwner;
|
||||
String? name;
|
||||
List<SubTitlteItemModel>? subtitles;
|
||||
|
||||
factory SubTitlteModel.fromJson(Map<String, dynamic> json) => SubTitlteModel(
|
||||
aid: json["aid"],
|
||||
bvid: json["bvid"],
|
||||
cid: json["cid"],
|
||||
loginMid: json["login_mid"],
|
||||
loginMidHash: json["login_mid_hash"],
|
||||
isOwner: json["is_owner"],
|
||||
name: json["name"],
|
||||
subtitles: json["subtitle"] != null
|
||||
? json["subtitle"]["subtitles"]
|
||||
.map<SubTitlteItemModel>((x) => SubTitlteItemModel.fromJson(x))
|
||||
.toList()
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
class SubTitlteItemModel {
|
||||
SubTitlteItemModel({
|
||||
this.id,
|
||||
this.lan,
|
||||
this.lanDoc,
|
||||
this.isLock,
|
||||
this.subtitleUrl,
|
||||
this.type,
|
||||
this.aiType,
|
||||
this.aiStatus,
|
||||
this.title,
|
||||
this.code,
|
||||
this.content,
|
||||
this.body,
|
||||
});
|
||||
|
||||
int? id;
|
||||
String? lan;
|
||||
String? lanDoc;
|
||||
bool? isLock;
|
||||
String? subtitleUrl;
|
||||
int? type;
|
||||
int? aiType;
|
||||
int? aiStatus;
|
||||
String? title;
|
||||
int? code;
|
||||
String? content;
|
||||
List? body;
|
||||
|
||||
factory SubTitlteItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
SubTitlteItemModel(
|
||||
id: json["id"],
|
||||
lan: json["lan"].replaceAll('-', ''),
|
||||
lanDoc: json["lan_doc"],
|
||||
isLock: json["is_lock"],
|
||||
subtitleUrl: json["subtitle_url"],
|
||||
type: json["type"],
|
||||
aiType: json["ai_type"],
|
||||
aiStatus: json["ai_status"],
|
||||
title: json["lan_doc"],
|
||||
code: SubtitleType.values
|
||||
.firstWhereOrNull(
|
||||
(element) => element.id.toString() == json["lan"])
|
||||
?.index ??
|
||||
-1,
|
||||
content: '',
|
||||
body: [],
|
||||
);
|
||||
}
|
@ -185,7 +185,7 @@ class HistoryItem extends StatelessWidget {
|
||||
? '已看完'
|
||||
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
|
||||
right: 6.0,
|
||||
bottom: 8.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
// 右上角
|
||||
@ -258,24 +258,6 @@ class HistoryItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 3,
|
||||
right: 3,
|
||||
bottom: 0,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft:
|
||||
Radius.circular(StyleString.imgRadius.x),
|
||||
bottomRight:
|
||||
Radius.circular(StyleString.imgRadius.x),
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
value: videoItem.progress == -1
|
||||
? 100
|
||||
: videoItem.progress / videoItem.duration,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||
|
@ -281,8 +281,8 @@ class _MemberPageState extends State<MemberPage>
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
Map data = snapshot.data!;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
@ -302,14 +302,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _memberController.memberInfo.value
|
||||
.vip!.nicknameColor !=
|
||||
null
|
||||
? Color(_memberController.memberInfo
|
||||
.value.vip!.nicknameColor!)
|
||||
: null),
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
)),
|
||||
const SizedBox(width: 2),
|
||||
if (_memberController.memberInfo.value.sex == '女')
|
||||
|
@ -18,32 +18,45 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
itemBuilder: (context, index) {
|
||||
MemberSeasonsList item = data!.seasonsList![index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.only(bottom: 12, right: 4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => Get.toNamed(
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||
title: Text(
|
||||
item.meta!.name!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleSmall!,
|
||||
),
|
||||
dense: true,
|
||||
leading: PBadge(
|
||||
stack: 'relative',
|
||||
size: 'small',
|
||||
text: item.meta!.total.toString(),
|
||||
),
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward,
|
||||
size: 20,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12, left: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
item.meta!.name!,
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.titleSmall!,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
PBadge(
|
||||
stack: 'relative',
|
||||
size: 'small',
|
||||
text: item.meta!.total.toString(),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
|
@ -9,7 +9,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
class RankController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
late RxList tabs = [].obs;
|
||||
RxInt initialIndex = 0.obs;
|
||||
RxInt initialIndex = 1.obs;
|
||||
late TabController tabController;
|
||||
late List tabsCtrList;
|
||||
late List<Widget> tabsPageList;
|
||||
@ -50,5 +50,21 @@ class RankController extends GetxController with GetTickerProviderStateMixin {
|
||||
length: tabs.length,
|
||||
vsync: this,
|
||||
);
|
||||
// 监听 tabController 切换
|
||||
if (enableGradientBg) {
|
||||
tabController.animation!.addListener(() {
|
||||
if (tabController.indexIsChanging) {
|
||||
if (initialIndex.value != tabController.index) {
|
||||
initialIndex.value = tabController.index;
|
||||
}
|
||||
} else {
|
||||
final int temp = tabController.animation!.value.round();
|
||||
if (initialIndex.value != temp) {
|
||||
initialIndex.value = temp;
|
||||
tabController.index = initialIndex.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,20 +22,15 @@ class ZonePage extends StatefulWidget {
|
||||
State<ZonePage> createState() => _ZonePageState();
|
||||
}
|
||||
|
||||
class _ZonePageState extends State<ZonePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late ZoneController _zoneController;
|
||||
class _ZonePageState extends State<ZonePage> {
|
||||
final ZoneController _zoneController = Get.put(ZoneController());
|
||||
List videoList = [];
|
||||
Future? _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_zoneController = Get.put(ZoneController(), tag: widget.rid.toString());
|
||||
_futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid);
|
||||
scrollController = _zoneController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
@ -73,7 +68,6 @@ class _ZonePageState extends State<ZonePage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
return await _zoneController.onRefresh();
|
||||
|
@ -44,7 +44,6 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
||||
setState(() {
|
||||
_tempValue = value as T;
|
||||
});
|
||||
Navigator.pop(context, _tempValue);
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -52,6 +51,19 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
||||
),
|
||||
);
|
||||
}),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, _tempValue),
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/utils/video_utils.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
|
||||
import '../../../models/video/subTitile/content.dart';
|
||||
import '../../../http/danmaku.dart';
|
||||
import '../../../utils/id_utils.dart';
|
||||
import 'widgets/header_control.dart';
|
||||
@ -94,10 +93,7 @@ class VideoDetailController extends GetxController
|
||||
late int cacheAudioQa;
|
||||
|
||||
PersistentBottomSheetController? replyReplyBottomSheetCtr;
|
||||
RxList<SubTitileContentModel> subtitleContents =
|
||||
<SubTitileContentModel>[].obs;
|
||||
late bool enableRelatedVideo;
|
||||
List subtitles = [];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -149,7 +145,6 @@ class VideoDetailController extends GetxController
|
||||
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: AudioQuality.hiRes.code);
|
||||
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
|
||||
getSubtitle();
|
||||
}
|
||||
|
||||
showReplyReplyPanel() {
|
||||
@ -256,8 +251,6 @@ class VideoDetailController extends GetxController
|
||||
|
||||
/// 开启自动全屏时,在player初始化完成后立即传入headerControl
|
||||
plPlayerController.headerControl = headerControl;
|
||||
|
||||
plPlayerController.subtitles.value = subtitles;
|
||||
}
|
||||
|
||||
// 视频链接
|
||||
@ -395,45 +388,6 @@ class VideoDetailController extends GetxController
|
||||
: print('replyReplyBottomSheetCtr is null');
|
||||
}
|
||||
|
||||
// 获取字幕配置
|
||||
Future getSubtitle() async {
|
||||
var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value);
|
||||
if (result['status']) {
|
||||
if (result['data'].subtitles.isNotEmpty) {
|
||||
subtitles = result['data'].subtitles;
|
||||
if (subtitles.isNotEmpty) {
|
||||
for (var i in subtitles) {
|
||||
final Map<String, dynamic> res = await VideoHttp.getSubtitleContent(
|
||||
i.subtitleUrl,
|
||||
);
|
||||
i.content = res['content'];
|
||||
i.body = res['body'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result['data'];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字幕内容
|
||||
// Future getSubtitleContent(String url) async {
|
||||
// var res = await Request().get('https:$url');
|
||||
// subtitleContents.value = res.data['body'].map<SubTitileContentModel>((e) {
|
||||
// return SubTitileContentModel.fromJson(e);
|
||||
// }).toList();
|
||||
// setSubtitleContent();
|
||||
// }
|
||||
|
||||
setSubtitleContent() {
|
||||
plPlayerController.subtitleContent.value = '';
|
||||
plPlayerController.subtitles.value = subtitles;
|
||||
}
|
||||
|
||||
clearSubtitleContent() {
|
||||
plPlayerController.subtitleContent.value = '';
|
||||
plPlayerController.subtitles.value = [];
|
||||
}
|
||||
|
||||
/// 发送弹幕
|
||||
void showShootDanmakuSheet() {
|
||||
final TextEditingController textController = TextEditingController();
|
||||
|
@ -15,8 +15,6 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import '../../../../http/user.dart';
|
||||
import '../widgets/expandable_section.dart';
|
||||
import 'widgets/action_item.dart';
|
||||
import 'widgets/fav_panel.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
@ -139,7 +137,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
late String memberHeroTag;
|
||||
late bool enableAi;
|
||||
bool isProcessing = false;
|
||||
RxBool isExpand = false.obs;
|
||||
void Function()? handleState(Future Function() action) {
|
||||
return isProcessing
|
||||
? null
|
||||
@ -215,7 +212,13 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 视频介绍
|
||||
showIntroDetail() {
|
||||
feedBack();
|
||||
isExpand.value = !(isExpand.value);
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
enableDrag: true,
|
||||
builder: (BuildContext context) {
|
||||
return IntroDetail(videoDetail: widget.videoDetail!);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 用户主页
|
||||
@ -327,16 +330,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
],
|
||||
),
|
||||
|
||||
/// 视频简介
|
||||
Obx(
|
||||
() => ExpandedSection(
|
||||
expand: isExpand.value,
|
||||
begin: 0,
|
||||
end: 1,
|
||||
child: IntroDetail(videoDetail: widget.videoDetail!),
|
||||
),
|
||||
),
|
||||
|
||||
/// 点赞收藏转发
|
||||
actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
@ -445,7 +438,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
primary: false,
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 5,
|
||||
@ -459,6 +451,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
text: widget.videoDetail!.stat!.like!.toString()),
|
||||
),
|
||||
// ActionItem(
|
||||
// icon: const Icon(FontAwesomeIcons.clock),
|
||||
// onTap: () => videoIntroController.actionShareVideo(),
|
||||
// selectStatus: false,
|
||||
// loadingStatus: loadingStatus,
|
||||
// text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
@ -479,14 +477,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () async {
|
||||
final res =
|
||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
selectStatus: false,
|
||||
text: '稍后看',
|
||||
text: widget.videoDetail!.stat!.reply!.toString(),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
|
@ -1,10 +1,16 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
class IntroDetail extends StatelessWidget {
|
||||
const IntroDetail({
|
||||
super.key,
|
||||
@ -14,39 +20,105 @@ class IntroDetail extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: EdgeInsets.only(
|
||||
left: 14,
|
||||
right: 14,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 4),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: videoDetail!.bvid!));
|
||||
SmartDialog.showToast('已复制');
|
||||
},
|
||||
child: Text(
|
||||
videoDetail!.bvid!,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Theme.of(context).colorScheme.primary),
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text.rich(
|
||||
style: const TextStyle(height: 1.4),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context, videoDetail!),
|
||||
],
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
videoDetail!.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoDetail!.stat!.view,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoDetail!.stat!.danmaku,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(videoDetail!.pubdate,
|
||||
formatType: 'detail'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
videoDetail!.bvid!,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text.rich(
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
// fontSize: 13,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context, videoDetail!),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
|
@ -149,16 +149,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 6, 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
'${_videoReplyController.sortTypeLabel.value}评论',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
Text(
|
||||
'${_videoReplyController.sortTypeLabel.value}评论',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
|
@ -498,7 +498,7 @@ InlineSpan buildContent(
|
||||
return str;
|
||||
});
|
||||
}
|
||||
content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
||||
// content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
||||
content.message = content.message
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
|
@ -212,7 +212,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
videoIntroController.isPaused = true;
|
||||
plPlayerController!.removeStatusLister(playerListener);
|
||||
plPlayerController!.pause();
|
||||
vdCtr.clearSubtitleContent();
|
||||
}
|
||||
setState(() => isShowing = false);
|
||||
super.didPushNext();
|
||||
@ -223,10 +222,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
void didPopNext() async {
|
||||
if (plPlayerController != null &&
|
||||
plPlayerController!.videoPlayerController != null) {
|
||||
setState(() {
|
||||
vdCtr.setSubtitleContent();
|
||||
isShowing = true;
|
||||
});
|
||||
setState(() => isShowing = true);
|
||||
}
|
||||
vdCtr.isFirstTime = false;
|
||||
final bool autoplay = autoPlayEnable;
|
||||
@ -372,13 +368,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
!(plPlayerController?.isOpenDanmu.value ??
|
||||
false);
|
||||
},
|
||||
icon: !(plPlayerController?.isOpenDanmu.value ??
|
||||
icon: (plPlayerController?.isOpenDanmu.value ??
|
||||
false)
|
||||
? SvgPicture.asset(
|
||||
'assets/images/video/danmu_close.svg',
|
||||
// ignore: deprecated_member_use
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
'assets/images/video/danmu_open.svg',
|
||||
|
@ -17,16 +17,12 @@ class ScrollAppBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
|
||||
double scrollDistance = scrollVal;
|
||||
if (scrollVal > videoHeight - kToolbarHeight) {
|
||||
scrollDistance = videoHeight - kToolbarHeight;
|
||||
}
|
||||
return Positioned(
|
||||
top: -videoHeight + scrollDistance + kToolbarHeight + 0.5,
|
||||
top: -videoHeight + scrollVal + kToolbarHeight + 0.5,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Opacity(
|
||||
opacity: scrollDistance / (videoHeight - kToolbarHeight),
|
||||
opacity: scrollVal / (videoHeight - kToolbarHeight),
|
||||
child: Container(
|
||||
height: statusBarHeight + kToolbarHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
|
@ -32,14 +32,28 @@ class _ExpandedSectionState extends State<ExpandedSection>
|
||||
_runExpandCheck();
|
||||
}
|
||||
|
||||
///Setting up the animation
|
||||
// void prepareAnimations() {
|
||||
// expandController = AnimationController(
|
||||
// vsync: this, duration: const Duration(milliseconds: 500));
|
||||
// animation = CurvedAnimation(
|
||||
// parent: expandController,
|
||||
// curve: Curves.fastOutSlowIn,
|
||||
// );
|
||||
// }
|
||||
|
||||
void prepareAnimations() {
|
||||
expandController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 400));
|
||||
Animation<double> curve = CurvedAnimation(
|
||||
parent: expandController,
|
||||
curve: Curves.linear,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
animation = Tween(begin: widget.begin, end: widget.end).animate(curve);
|
||||
// animation = CurvedAnimation(
|
||||
// parent: expandController,
|
||||
// curve: Curves.fastOutSlowIn,
|
||||
// );
|
||||
}
|
||||
|
||||
void _runExpandCheck() {
|
||||
@ -53,9 +67,7 @@ class _ExpandedSectionState extends State<ExpandedSection>
|
||||
@override
|
||||
void didUpdateWidget(ExpandedSection oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.expand != oldWidget.expand) {
|
||||
_runExpandCheck();
|
||||
}
|
||||
_runExpandCheck();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -344,56 +344,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 选择字幕
|
||||
void showSubtitleDialog() async {
|
||||
int tempThemeValue = widget.controller!.subTitleCode.value;
|
||||
int len = widget.videoDetailCtr!.subtitles.length;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('选择字幕'),
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18),
|
||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||
return len == 0
|
||||
? const SizedBox(
|
||||
height: 60,
|
||||
child: Center(
|
||||
child: Text('没有字幕'),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
RadioListTile(
|
||||
value: -1,
|
||||
title: const Text('关闭弹幕'),
|
||||
groupValue: tempThemeValue,
|
||||
onChanged: (value) {
|
||||
tempThemeValue = value!;
|
||||
widget.controller?.toggleSubtitle(value);
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
...widget.videoDetailCtr!.subtitles
|
||||
.map((e) => RadioListTile(
|
||||
value: e.code,
|
||||
title: Text(e.title),
|
||||
groupValue: tempThemeValue,
|
||||
onChanged: (value) {
|
||||
tempThemeValue = value!;
|
||||
widget.controller?.toggleSubtitle(value);
|
||||
Get.back();
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// 选择倍速
|
||||
void showSetSpeedSheet() {
|
||||
final double currentSpeed = widget.controller!.playbackSpeed;
|
||||
@ -1165,31 +1115,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
SizedBox(width: buttonSpace),
|
||||
],
|
||||
|
||||
/// 字幕
|
||||
// SizedBox(
|
||||
// width: 34,
|
||||
// height: 34,
|
||||
// child: IconButton(
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// onPressed: () => showSubtitleDialog(),
|
||||
// icon: const Icon(
|
||||
// Icons.closed_caption_off,
|
||||
// size: 22,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.closed_caption_off,
|
||||
size: 22,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => showSubtitleDialog(),
|
||||
),
|
||||
SizedBox(width: buttonSpace),
|
||||
Obx(
|
||||
() => SizedBox(
|
||||
width: 45,
|
||||
|
@ -21,8 +21,6 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
import 'package:status_bar_control/status_bar_control.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import '../../models/video/subTitile/content.dart';
|
||||
import '../../models/video/subTitile/result.dart';
|
||||
// import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
Box videoStorage = GStrorage.video;
|
||||
@ -75,8 +73,6 @@ class PlPlayerController {
|
||||
final Rx<bool> _doubleSpeedStatus = false.obs;
|
||||
final Rx<bool> _controlsLock = false.obs;
|
||||
final Rx<bool> _isFullScreen = false.obs;
|
||||
final Rx<bool> _subTitleOpen = false.obs;
|
||||
final Rx<int> _subTitleCode = (-1).obs;
|
||||
// 默认投稿视频格式
|
||||
static Rx<String> _videoType = 'archive'.obs;
|
||||
|
||||
@ -122,7 +118,6 @@ class PlPlayerController {
|
||||
PreferredSizeWidget? headerControl;
|
||||
PreferredSizeWidget? bottomControl;
|
||||
Widget? danmuWidget;
|
||||
late RxList subtitles;
|
||||
|
||||
/// 数据加载监听
|
||||
Stream<DataStatus> get onDataStatusChanged => dataStatus.status.stream;
|
||||
@ -152,11 +147,6 @@ class PlPlayerController {
|
||||
Rx<bool> get mute => _mute;
|
||||
Stream<bool> get onMuteChanged => _mute.stream;
|
||||
|
||||
/// 字幕开启状态
|
||||
Rx<bool> get subTitleOpen => _subTitleOpen;
|
||||
Rx<int> get subTitleCode => _subTitleCode;
|
||||
// Stream<bool> get onSubTitleOpenChanged => _subTitleOpen.stream;
|
||||
|
||||
/// [videoPlayerController] instace of Player
|
||||
Player? get videoPlayerController => _videoPlayerController;
|
||||
|
||||
@ -241,10 +231,6 @@ class PlPlayerController {
|
||||
// 播放顺序相关
|
||||
PlayRepeat playRepeat = PlayRepeat.pause;
|
||||
|
||||
RxList<SubTitileContentModel> subtitleContents =
|
||||
<SubTitileContentModel>[].obs;
|
||||
RxString subtitleContent = ''.obs;
|
||||
|
||||
void updateSliderPositionSecond() {
|
||||
int newSecond = _sliderPosition.value.inSeconds;
|
||||
if (sliderPositionSeconds.value != newSecond) {
|
||||
@ -364,8 +350,6 @@ class PlPlayerController {
|
||||
bool enableHeart = true,
|
||||
// 是否首次加载
|
||||
bool isFirstTime = true,
|
||||
// 是否开启字幕
|
||||
bool enableSubTitle = false,
|
||||
}) async {
|
||||
try {
|
||||
_autoPlay = autoplay;
|
||||
@ -380,9 +364,7 @@ class PlPlayerController {
|
||||
_cid = cid;
|
||||
_enableHeart = enableHeart;
|
||||
_isFirstTime = isFirstTime;
|
||||
_subTitleOpen.value = enableSubTitle;
|
||||
subtitles = [].obs;
|
||||
subtitleContent.value = '';
|
||||
|
||||
if (_videoPlayerController != null &&
|
||||
_videoPlayerController!.state.playing) {
|
||||
await pause(notify: false);
|
||||
@ -593,8 +575,6 @@ class PlPlayerController {
|
||||
_sliderPosition.value = event;
|
||||
updateSliderPositionSecond();
|
||||
}
|
||||
querySubtitleContent(
|
||||
videoPlayerController!.state.position.inSeconds.toDouble());
|
||||
|
||||
/// 触发回调事件
|
||||
for (var element in _positionListeners) {
|
||||
@ -629,10 +609,6 @@ class PlPlayerController {
|
||||
const Duration(seconds: 1),
|
||||
() => videoPlayerServiceHandler.onPositionChange(event));
|
||||
}),
|
||||
|
||||
// onSubTitleOpenChanged.listen((bool event) {
|
||||
// toggleSubtitle(event ? subTitleCode.value : -1);
|
||||
// })
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -1071,61 +1047,12 @@ class PlPlayerController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 字幕
|
||||
void toggleSubtitle(int code) {
|
||||
_subTitleOpen.value = code != -1;
|
||||
_subTitleCode.value = code;
|
||||
// if (code == -1) {
|
||||
// // 关闭字幕
|
||||
// _subTitleOpen.value = false;
|
||||
// _subTitleCode.value = code;
|
||||
// _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no());
|
||||
// return;
|
||||
// }
|
||||
// final SubTitlteItemModel? subtitle = subtitles?.firstWhereOrNull(
|
||||
// (element) => element.code == code,
|
||||
// );
|
||||
// _subTitleOpen.value = true;
|
||||
// _subTitleCode.value = code;
|
||||
// _videoPlayerController?.setSubtitleTrack(
|
||||
// SubtitleTrack.data(
|
||||
// subtitle!.content!,
|
||||
// title: subtitle.title,
|
||||
// language: subtitle.lan,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
void querySubtitleContent(double progress) {
|
||||
if (subTitleCode.value == -1) {
|
||||
subtitleContent.value = '';
|
||||
return;
|
||||
}
|
||||
if (subtitles.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull(
|
||||
(element) => element.code == subTitleCode.value,
|
||||
);
|
||||
if (subtitle != null && subtitle.body!.isNotEmpty) {
|
||||
for (var content in subtitle.body!) {
|
||||
if (progress >= content['from']! && progress <= content['to']!) {
|
||||
subtitleContent.value = content['content']!;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPlayRepeat(PlayRepeat type) {
|
||||
playRepeat = type;
|
||||
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
||||
}
|
||||
|
||||
Future<void> dispose({String type = 'single'}) async {
|
||||
print('dispose');
|
||||
print('dispose: ${playerCount.value}');
|
||||
|
||||
// 每次减1,最后销毁
|
||||
if (type == 'single' && playerCount.value > 1) {
|
||||
_playerCount.value -= 1;
|
||||
@ -1135,7 +1062,6 @@ class PlPlayerController {
|
||||
}
|
||||
_playerCount.value = 0;
|
||||
try {
|
||||
print('dispose dispose ---------');
|
||||
_timer?.cancel();
|
||||
_timerForVolume?.cancel();
|
||||
_timerForGettingVolume?.cancel();
|
||||
|
@ -580,45 +580,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
if (widget.danmuWidget != null)
|
||||
Positioned.fill(top: 4, child: widget.danmuWidget!),
|
||||
|
||||
/// 开启且有字幕时展示
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 30,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: widget.controller.subTitleCode.value != -1,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: widget.controller.subtitleContent.value != ''
|
||||
? Colors.black.withOpacity(0.6)
|
||||
: Colors.transparent,
|
||||
),
|
||||
padding: widget.controller.subTitleCode.value != -1
|
||||
? const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 4,
|
||||
)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
widget.controller.subtitleContent.value,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// 手势
|
||||
Positioned.fill(
|
||||
left: 16,
|
||||
|
@ -19,7 +19,15 @@ class Em {
|
||||
return regCate(matchStr);
|
||||
}, onNonMatch: (String str) {
|
||||
if (str != '') {
|
||||
str = decodeHtmlEntities(str);
|
||||
str = str
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(' ', " ")
|
||||
.replaceAll('&', "&");
|
||||
Map map = {'type': 'text', 'text': str};
|
||||
res.add(map);
|
||||
}
|
||||
@ -27,17 +35,4 @@ class Em {
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
static String decodeHtmlEntities(String title) {
|
||||
return title
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'")
|
||||
.replaceAll(' ', " ")
|
||||
.replaceAll('&', "&")
|
||||
.replaceAll(''', "'");
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
class SubTitleUtils {
|
||||
// 格式整理
|
||||
static String convertToWebVTT(List jsonData) {
|
||||
String webVTTContent = 'WEBVTT FILE\n\n';
|
||||
|
||||
for (int i = 0; i < jsonData.length; i++) {
|
||||
final item = jsonData[i];
|
||||
double from = item['from'] as double;
|
||||
double to = item['to'] as double;
|
||||
int sid = (item['sid'] ?? 0) as int;
|
||||
String content = item['content'] as String;
|
||||
|
||||
webVTTContent += '$sid\n';
|
||||
webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n';
|
||||
webVTTContent += '$content\n\n';
|
||||
}
|
||||
|
||||
return webVTTContent;
|
||||
}
|
||||
|
||||
static String formatTime(num seconds) {
|
||||
final String h = (seconds / 3600).floor().toString().padLeft(2, '0');
|
||||
final String m = (seconds % 3600 / 60).floor().toString().padLeft(2, '0');
|
||||
final String s = (seconds % 60).floor().toString().padLeft(2, '0');
|
||||
final String ms =
|
||||
(seconds * 1000 % 1000).floor().toString().padLeft(3, '0');
|
||||
if (h == '00') {
|
||||
return "$m:$s.$ms";
|
||||
}
|
||||
return "$h:$m:$s.$ms";
|
||||
}
|
||||
}
|
@ -208,16 +208,14 @@ class Utils {
|
||||
|
||||
static int findClosestNumber(int target, List<int> numbers) {
|
||||
int minDiff = 127;
|
||||
int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数
|
||||
late int closestNumber;
|
||||
try {
|
||||
for (int number in numbers) {
|
||||
if (number < target) {
|
||||
int diff = target - number; // 计算目标值与当前整数的差值
|
||||
int diff = (number - target).abs();
|
||||
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestNumber = number;
|
||||
}
|
||||
if (diff < minDiff) {
|
||||
minDiff = diff;
|
||||
closestNumber = number;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
Reference in New Issue
Block a user