Compare commits

..

7 Commits

Author SHA1 Message Date
b689db16b5 Merge branch 'main' into feature-media_kit 2024-03-30 17:13:09 +08:00
5cb3e578a8 Merge branch 'feature-updateVideoDetailStructure' 2024-03-29 00:02:36 +08:00
8f9fbf5d41 fix: 视频标题展开 2024-03-29 00:01:17 +08:00
76784ee664 mod: seekTo 2024-03-19 23:33:32 +08:00
2dbef3fee2 mod: media_kit引入 2024-03-19 23:23:14 +08:00
3a66c8c03d Merge branch 'main' into feature-media_kit 2024-03-19 23:15:06 +08:00
6f62837495 mod: media_kit依赖 2024-02-02 23:12:04 +08:00
10 changed files with 155 additions and 86 deletions

View File

@ -34,7 +34,6 @@ void main() async {
.then((_) async { .then((_) async {
await GStrorage.init(); await GStrorage.init();
await setupServiceLocator(); await setupServiceLocator();
clearLogs();
Request(); Request();
await Request.setCookie(); await Request.setCookie();
RecommendFilter(); RecommendFilter();

View File

@ -47,23 +47,18 @@ class Vip {
this.status, this.status,
this.dueDate, this.dueDate,
this.label, this.label,
this.nicknameColor,
}); });
int? type; int? type;
int? status; int? status;
int? dueDate; int? dueDate;
Map? label; Map? label;
int? nicknameColor;
Vip.fromJson(Map<String, dynamic> json) { Vip.fromJson(Map<String, dynamic> json) {
type = json['type']; type = json['type'];
status = json['status']; status = json['status'];
dueDate = json['due_date']; dueDate = json['due_date'];
label = json['label']; label = json['label'];
nicknameColor = json['nickname_color'] == ''
? null
: int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
} }
} }

View File

@ -281,8 +281,8 @@ class _MemberPageState extends State<MemberPage>
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
Map? data = snapshot.data; Map data = snapshot.data!;
if (data != null && data['status']) { if (data['status']) {
return Obx( return Obx(
() => Stack( () => Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
@ -302,14 +302,7 @@ class _MemberPageState extends State<MemberPage>
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.titleMedium! .titleMedium!
.copyWith( .copyWith(fontWeight: FontWeight.bold),
fontWeight: FontWeight.bold,
color: _memberController.memberInfo.value
.vip!.nicknameColor !=
null
? Color(_memberController.memberInfo
.value.vip!.nicknameColor!)
: null),
)), )),
const SizedBox(width: 2), const SizedBox(width: 2),
if (_memberController.memberInfo.value.sex == '') if (_memberController.memberInfo.value.sex == '')

View File

@ -18,32 +18,45 @@ class MemberSeasonsPanel extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
MemberSeasonsList item = data!.seasonsList![index]; MemberSeasonsList item = data!.seasonsList![index];
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12, right: 4),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( Padding(
onTap: () => Get.toNamed( padding: const EdgeInsets.only(bottom: 12, left: 4),
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), child: Row(
title: Text( children: [
Text(
item.meta!.name!, item.meta!.name!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleSmall!, style: Theme.of(context).textTheme.titleSmall!,
), ),
dense: true, const SizedBox(width: 10),
leading: PBadge( PBadge(
stack: 'relative', stack: 'relative',
size: 'small', size: 'small',
text: item.meta!.total.toString(), text: item.meta!.total.toString(),
), ),
trailing: const Icon( 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, Icons.arrow_forward,
size: 20, size: 20,
), ),
), ),
const SizedBox(height: 10), )
],
),
),
LayoutBuilder( LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
return GridView.builder( return GridView.builder(

View File

@ -44,7 +44,6 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
setState(() { setState(() {
_tempValue = value as T; _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('确定'),
)
],
); );
} }
} }

View File

@ -1,3 +1,4 @@
import 'package:expandable/expandable.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -16,7 +17,6 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../../../http/user.dart'; import '../../../../http/user.dart';
import '../widgets/expandable_section.dart';
import 'widgets/action_item.dart'; import 'widgets/action_item.dart';
import 'widgets/fav_panel.dart'; import 'widgets/fav_panel.dart';
import 'widgets/intro_detail.dart'; import 'widgets/intro_detail.dart';
@ -140,6 +140,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late bool enableAi; late bool enableAi;
bool isProcessing = false; bool isProcessing = false;
RxBool isExpand = false.obs; RxBool isExpand = false.obs;
late ExpandableController _expandableCtr;
void Function()? handleState(Future Function() action) { void Function()? handleState(Future Function() action) {
return isProcessing return isProcessing
? null ? null
@ -163,6 +165,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
follower = Utils.numFormat(videoIntroController.userStat['follower']); follower = Utils.numFormat(videoIntroController.userStat['follower']);
followStatus = videoIntroController.followStatus; followStatus = videoIntroController.followStatus;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
} }
// 收藏 // 收藏
@ -216,6 +219,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
showIntroDetail() { showIntroDetail() {
feedBack(); feedBack();
isExpand.value = !(isExpand.value); isExpand.value = !(isExpand.value);
_expandableCtr.toggle();
} }
// 用户主页 // 用户主页
@ -239,6 +243,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
); );
} }
@override
void dispose() {
_expandableCtr.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData t = Theme.of(context); final ThemeData t = Theme.of(context);
@ -256,14 +266,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(), onTap: () => showIntroDetail(),
child: Text( child: ExpandablePanel(
controller: _expandableCtr,
collapsed: Text(
widget.videoDetail!.title!, widget.videoDetail!.title!,
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
maxLines: 2, ),
overflow: TextOverflow.ellipsis, expanded: Text(
widget.videoDetail!.title!,
softWrap: true,
maxLines: 4,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
theme: const ExpandableThemeData(
animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
),
), ),
), ),
Stack( Stack(
@ -328,12 +358,16 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
/// 视频简介 /// 视频简介
Obx( ExpandablePanel(
() => ExpandedSection( controller: _expandableCtr,
expand: isExpand.value, collapsed: const SizedBox(height: 0),
begin: 0, expanded: IntroDetail(videoDetail: widget.videoDetail!),
end: 1, theme: const ExpandableThemeData(
child: IntroDetail(videoDetail: widget.videoDetail!), animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
), ),
), ),

View File

@ -393,7 +393,13 @@ class PlPlayerController {
} }
// 配置Player 音轨、字幕等等 // 配置Player 音轨、字幕等等
_videoPlayerController = await _createVideoController( _videoPlayerController = await _createVideoController(
dataSource, _looping, enableHA, width, height); dataSource,
_looping,
enableHA,
width,
height,
seekTo,
);
// 获取视频时长 00:00 // 获取视频时长 00:00
_duration.value = duration ?? _videoPlayerController!.state.duration; _duration.value = duration ?? _videoPlayerController!.state.duration;
updateDurationSecond(); updateDurationSecond();
@ -404,7 +410,7 @@ class PlPlayerController {
if (!_listenersInitialized) { if (!_listenersInitialized) {
startListeners(); startListeners();
} }
await _initializePlayer(seekTo: seekTo, duration: _duration.value); await _initializePlayer(duration: _duration.value);
bool autoEnterFullcreen = bool autoEnterFullcreen =
setting.get(SettingBoxKey.enableAutoEnter, defaultValue: false); setting.get(SettingBoxKey.enableAutoEnter, defaultValue: false);
if (autoEnterFullcreen && _isFirstTime) { if (autoEnterFullcreen && _isFirstTime) {
@ -424,6 +430,7 @@ class PlPlayerController {
bool enableHA, bool enableHA,
double? width, double? width,
double? height, double? height,
Duration? seekTo,
) async { ) async {
// 每次配置时先移除监听 // 每次配置时先移除监听
removeListeners(); removeListeners();
@ -506,7 +513,11 @@ class PlPlayerController {
); );
} }
player.open( player.open(
Media(dataSource.videoSource!, httpHeaders: dataSource.httpHeaders), Media(
dataSource.videoSource!,
httpHeaders: dataSource.httpHeaders,
start: seekTo ?? Duration.zero,
),
play: false, play: false,
); );
// 音轨 // 音轨
@ -519,7 +530,6 @@ class PlPlayerController {
// 开始播放 // 开始播放
Future _initializePlayer({ Future _initializePlayer({
Duration seekTo = Duration.zero,
Duration? duration, Duration? duration,
}) async { }) async {
// 设置倍速 // 设置倍速
@ -537,11 +547,6 @@ class PlPlayerController {
// await setLooping(_looping); // await setLooping(_looping);
// } // }
// 跳转播放
if (seekTo != Duration.zero) {
await this.seekTo(seekTo);
}
// 自动播放 // 自动播放
if (_autoPlay) { if (_autoPlay) {
await play(duration: duration); await play(duration: duration);
@ -661,21 +666,14 @@ class PlPlayerController {
await _videoPlayerController?.stream.buffer.first; await _videoPlayerController?.stream.buffer.first;
} }
await _videoPlayerController?.seek(position); await _videoPlayerController?.seek(position);
// if (playerStatus.stopped) {
// play();
// }
} else { } else {
print('seek duration else'); print('seek duration else');
_timerForSeek?.cancel(); _timerForSeek?.cancel();
_timerForSeek = _timerForSeek =
Timer.periodic(const Duration(milliseconds: 200), (Timer t) async { Timer.periodic(const Duration(milliseconds: 200), (Timer t) async {
//_timerForSeek = null;
if (duration.value.inSeconds != 0) { if (duration.value.inSeconds != 0) {
await _videoPlayerController!.stream.buffer.first; await _videoPlayerController!.stream.buffer.first;
await _videoPlayerController?.seek(position); await _videoPlayerController?.seek(position);
// if (playerStatus.status.value == PlayerStatus.paused) {
// play();
// }
t.cancel(); t.cancel();
_timerForSeek = null; _timerForSeek = null;
} }

View File

@ -208,18 +208,16 @@ class Utils {
static int findClosestNumber(int target, List<int> numbers) { static int findClosestNumber(int target, List<int> numbers) {
int minDiff = 127; int minDiff = 127;
int closestNumber = 0; // 初始化为0表示没有找到比目标值小的整数 late int closestNumber;
try { try {
for (int number in numbers) { for (int number in numbers) {
if (number < target) { int diff = (number - target).abs();
int diff = target - number; // 计算目标值与当前整数的差值
if (diff < minDiff) { if (diff < minDiff) {
minDiff = diff; minDiff = diff;
closestNumber = number; closestNumber = number;
} }
} }
}
} catch (_) {} } catch (_) {}
return closestNumber; return closestNumber;
} }

View File

@ -433,6 +433,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.3" version: "5.0.3"
expandable:
dependency: "direct main"
description:
name: expandable
sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.1"
extended_image: extended_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -857,10 +865,11 @@ packages:
media_kit: media_kit:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit path: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a" ref: HEAD
url: "https://pub.flutter-io.cn" resolved-ref: "77a130b1d7ce733b47d2133b57563716090450d0"
source: hosted url: "https://github.com/media-kit/media-kit.git"
source: git
version: "1.1.10+1" version: "1.1.10+1"
media_kit_libs_android_video: media_kit_libs_android_video:
dependency: transitive dependency: transitive
@ -897,10 +906,11 @@ packages:
media_kit_libs_video: media_kit_libs_video:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit_libs_video path: "libs/universal/media_kit_libs_video"
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067" ref: HEAD
url: "https://pub.flutter-io.cn" resolved-ref: "77a130b1d7ce733b47d2133b57563716090450d0"
source: hosted url: "https://github.com/media-kit/media-kit.git"
source: git
version: "1.0.4" version: "1.0.4"
media_kit_libs_windows_video: media_kit_libs_windows_video:
dependency: transitive dependency: transitive
@ -921,10 +931,11 @@ packages:
media_kit_video: media_kit_video:
dependency: "direct main" dependency: "direct main"
description: description:
name: media_kit_video path: media_kit_video
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882 ref: HEAD
url: "https://pub.flutter-io.cn" resolved-ref: "77a130b1d7ce733b47d2133b57563716090450d0"
source: hosted url: "https://github.com/media-kit/media-kit.git"
source: git
version: "1.2.4" version: "1.2.4"
meta: meta:
dependency: transitive dependency: transitive

View File

@ -142,6 +142,8 @@ dependencies:
path: 1.8.3 path: 1.8.3
# 电池优化 # 电池优化
disable_battery_optimization: ^1.1.1 disable_battery_optimization: ^1.1.1
# 展开/收起
expandable: ^5.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -161,6 +163,20 @@ dev_dependencies:
hive_generator: ^2.0.0 hive_generator: ^2.0.0
build_runner: ^2.4.8 build_runner: ^2.4.8
dependency_overrides:
media_kit:
git:
url: https://github.com/media-kit/media-kit.git
path: media_kit
media_kit_video:
git:
url: https://github.com/media-kit/media-kit.git
path: media_kit_video
media_kit_libs_video:
git:
url: https://github.com/media-kit/media-kit.git
path: libs/universal/media_kit_libs_video
flutter_launcher_icons: flutter_launcher_icons:
android: true android: true
ios: true ios: true