Compare commits
8 Commits
fix-bangum
...
feature-li
Author | SHA1 | Date | |
---|---|---|---|
33d28f51d1 | |||
a37f3b8b5b | |||
a57f5e8b2f | |||
beb640ac83 | |||
fca0588377 | |||
da2bbeedff | |||
98aaca286b | |||
6fdfcb888d |
43
lib/models/live/quality.dart
Normal file
43
lib/models/live/quality.dart
Normal file
@ -0,0 +1,43 @@
|
||||
enum LiveQuality {
|
||||
dolby,
|
||||
super4K,
|
||||
origin,
|
||||
bluRay,
|
||||
superHD,
|
||||
smooth,
|
||||
flunt,
|
||||
}
|
||||
|
||||
extension LiveQualityCode on LiveQuality {
|
||||
static final List<int> _codeList = [
|
||||
30000,
|
||||
20000,
|
||||
10000,
|
||||
400,
|
||||
250,
|
||||
150,
|
||||
80,
|
||||
];
|
||||
int get code => _codeList[index];
|
||||
|
||||
static LiveQuality? fromCode(int code) {
|
||||
final index = _codeList.indexOf(code);
|
||||
if (index != -1) {
|
||||
return LiveQuality.values[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
extension VideoQualityDesc on LiveQuality {
|
||||
static final List<String> _descList = [
|
||||
'杜比',
|
||||
'4K',
|
||||
'原画',
|
||||
'蓝光',
|
||||
'超清',
|
||||
'高清',
|
||||
'流畅',
|
||||
];
|
||||
get description => _descList[index];
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/live.dart';
|
||||
import 'package:pilipala/models/live/quality.dart';
|
||||
import 'package:pilipala/models/live/room_info.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import '../../models/live/room_info_h5.dart';
|
||||
@ -19,10 +21,16 @@ class LiveRoomController extends GetxController {
|
||||
PlPlayerController.getInstance(videoType: 'live');
|
||||
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
|
||||
late bool enableCDN;
|
||||
late int currentQn;
|
||||
int? tempCurrentQn;
|
||||
late List<Map<String, dynamic>> acceptQnList;
|
||||
RxString currentQnDesc = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
currentQn = setting.get(SettingBoxKey.defaultLiveQa,
|
||||
defaultValue: LiveQuality.values.last.code);
|
||||
roomId = int.parse(Get.parameters['roomid']!);
|
||||
if (Get.arguments != null) {
|
||||
liveItem = Get.arguments['liveItem'];
|
||||
@ -57,11 +65,28 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
|
||||
Future queryLiveInfo() async {
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: 10000);
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: currentQn);
|
||||
if (res['status']) {
|
||||
List<CodecItem> codec =
|
||||
res['data'].playurlInfo.playurl.stream.first.format.first.codec;
|
||||
CodecItem item = codec.first;
|
||||
// 以服务端返回的码率为准
|
||||
currentQn = item.currentQn!;
|
||||
if (tempCurrentQn != null && tempCurrentQn == currentQn) {
|
||||
SmartDialog.showToast('画质切换失败,请检查登录状态');
|
||||
}
|
||||
List acceptQn = item.acceptQn!;
|
||||
acceptQnList = acceptQn.map((e) {
|
||||
return {
|
||||
'code': e,
|
||||
'desc': LiveQuality.values
|
||||
.firstWhere((element) => element.code == e)
|
||||
.description,
|
||||
};
|
||||
}).toList();
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
String videoUrl = enableCDN
|
||||
? VideoUtils.getCdnUrl(item)
|
||||
: (item.urlInfo?.first.host)! +
|
||||
@ -90,4 +115,17 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 修改画质
|
||||
void changeQn(int qn) async {
|
||||
tempCurrentQn = currentQn;
|
||||
if (currentQn == qn) {
|
||||
return;
|
||||
}
|
||||
currentQn = qn;
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
await queryLiveInfo();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/pages/live_room/index.dart';
|
||||
@ -83,6 +84,30 @@ class _BottomControlState extends State<BottomControl> {
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: PopupMenuButton<int>(
|
||||
padding: EdgeInsets.zero,
|
||||
onSelected: (value) {
|
||||
widget.liveRoomCtr!.changeQn(value);
|
||||
},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
widget.liveRoomCtr!.currentQnDesc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return widget.liveRoomCtr!.acceptQnList.map((e) {
|
||||
return PopupMenuItem<int>(
|
||||
value: e['code'],
|
||||
child: Text(e['desc']),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (Platform.isAndroid) ...[
|
||||
SizedBox(
|
||||
width: 34,
|
||||
@ -110,7 +135,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
|
@ -10,6 +10,7 @@ import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../../models/live/quality.dart';
|
||||
import 'widgets/switch_item.dart';
|
||||
|
||||
class PlaySetting extends StatefulWidget {
|
||||
@ -22,6 +23,7 @@ class PlaySetting extends StatefulWidget {
|
||||
class _PlaySettingState extends State<PlaySetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
late dynamic defaultVideoQa;
|
||||
late dynamic defaultLiveQa;
|
||||
late dynamic defaultAudioQa;
|
||||
late dynamic defaultDecode;
|
||||
late int defaultFullScreenMode;
|
||||
@ -32,6 +34,8 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
super.initState();
|
||||
defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
|
||||
defaultValue: VideoQuality.values.last.code);
|
||||
defaultLiveQa = setting.get(SettingBoxKey.defaultLiveQa,
|
||||
defaultValue: LiveQuality.values.last.code);
|
||||
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: AudioQuality.values.last.code);
|
||||
defaultDecode = setting.get(SettingBoxKey.defaultDecode,
|
||||
@ -157,9 +161,9 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
}),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认画质', style: titleStyle),
|
||||
title: Text('默认视频画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
||||
'当前默认画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
onTap: () async {
|
||||
@ -167,7 +171,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认画质',
|
||||
title: '默认视频画质',
|
||||
value: defaultVideoQa,
|
||||
values: VideoQuality.values.reversed.map((e) {
|
||||
return {'title': e.description, 'value': e.code};
|
||||
@ -181,6 +185,32 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认直播画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前默认画质${LiveQualityCode.fromCode(defaultLiveQa)!.description!}',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
onTap: () async {
|
||||
int? result = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认直播画质',
|
||||
value: defaultLiveQa,
|
||||
values: LiveQuality.values.reversed.map((e) {
|
||||
return {'title': e.description, 'value': e.code};
|
||||
}).toList());
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
defaultLiveQa = result;
|
||||
setting.put(SettingBoxKey.defaultLiveQa, result);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认音质', style: titleStyle),
|
||||
|
10
lib/plugin/pl_player/models/bottom_control_type.dart
Normal file
10
lib/plugin/pl_player/models/bottom_control_type.dart
Normal file
@ -0,0 +1,10 @@
|
||||
enum BottomControlType {
|
||||
pre,
|
||||
playOrPause,
|
||||
next,
|
||||
time,
|
||||
space,
|
||||
fit,
|
||||
speed,
|
||||
fullscreen,
|
||||
}
|
@ -8,7 +8,6 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:nil/nil.dart';
|
||||
import 'package:pilipala/models/common/gesture_mode.dart';
|
||||
import 'package:pilipala/plugin/pl_player/controller.dart';
|
||||
import 'package:pilipala/plugin/pl_player/models/duration.dart';
|
||||
@ -19,12 +18,14 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
|
||||
import '../../utils/global_data.dart';
|
||||
import 'models/bottom_control_type.dart';
|
||||
import 'models/bottom_progress_behavior.dart';
|
||||
import 'widgets/app_bar_ani.dart';
|
||||
import 'widgets/backward_seek.dart';
|
||||
import 'widgets/bottom_control.dart';
|
||||
import 'widgets/common_btn.dart';
|
||||
import 'widgets/forward_seek.dart';
|
||||
import 'widgets/play_pause_btn.dart';
|
||||
|
||||
class PLVideoPlayer extends StatefulWidget {
|
||||
const PLVideoPlayer({
|
||||
@ -32,6 +33,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
this.headerControl,
|
||||
this.bottomControl,
|
||||
this.danmuWidget,
|
||||
this.bottomList,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -39,6 +41,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
final PreferredSizeWidget? headerControl;
|
||||
final PreferredSizeWidget? bottomControl;
|
||||
final Widget? danmuWidget;
|
||||
final List<BottomControlType>? bottomList;
|
||||
|
||||
@override
|
||||
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
|
||||
@ -48,26 +51,22 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController animationController;
|
||||
late VideoController videoController;
|
||||
final PLVideoPlayerController _ctr = Get.put(PLVideoPlayerController());
|
||||
|
||||
// bool _mountSeekBackwardButton = false;
|
||||
// bool _mountSeekForwardButton = false;
|
||||
// bool _hideSeekBackwardButton = false;
|
||||
// bool _hideSeekForwardButton = false;
|
||||
final RxBool _mountSeekBackwardButton = false.obs;
|
||||
final RxBool _mountSeekForwardButton = false.obs;
|
||||
final RxBool _hideSeekBackwardButton = false.obs;
|
||||
final RxBool _hideSeekForwardButton = false.obs;
|
||||
|
||||
// double _brightnessValue = 0.0;
|
||||
// bool _brightnessIndicator = false;
|
||||
final RxDouble _brightnessValue = 0.0.obs;
|
||||
final RxBool _brightnessIndicator = false.obs;
|
||||
Timer? _brightnessTimer;
|
||||
|
||||
// double _volumeValue = 0.0;
|
||||
// bool _volumeIndicator = false;
|
||||
final RxDouble _volumeValue = 0.0.obs;
|
||||
final RxBool _volumeIndicator = false.obs;
|
||||
Timer? _volumeTimer;
|
||||
|
||||
double _distance = 0.0;
|
||||
// 初始手指落下位置
|
||||
// double _initTapPositoin = 0.0;
|
||||
|
||||
// bool _volumeInterceptEventStream = false;
|
||||
final RxDouble _distance = 0.0.obs;
|
||||
final RxBool _volumeInterceptEventStream = false.obs;
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
late FullScreenMode mode;
|
||||
@ -82,11 +81,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
DateTime? lastFullScreenToggleTime;
|
||||
|
||||
void onDoubleTapSeekBackward() {
|
||||
_ctr.onDoubleTapSeekBackward();
|
||||
_mountSeekBackwardButton.value = true;
|
||||
}
|
||||
|
||||
void onDoubleTapSeekForward() {
|
||||
_ctr.onDoubleTapSeekForward();
|
||||
_mountSeekForwardButton.value = true;
|
||||
}
|
||||
|
||||
// 双击播放、暂停
|
||||
@ -138,10 +137,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
FlutterVolumeController.updateShowSystemUI(true);
|
||||
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
||||
_volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
||||
FlutterVolumeController.addListener((double value) {
|
||||
if (mounted && !_ctr.volumeInterceptEventStream.value) {
|
||||
_ctr.volumeValue.value = value;
|
||||
if (mounted && !_volumeInterceptEventStream.value) {
|
||||
_volumeValue.value = value;
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
@ -149,10 +148,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
_ctr.brightnessValue.value = await ScreenBrightness().current;
|
||||
_brightnessValue.value = await ScreenBrightness().current;
|
||||
ScreenBrightness().onCurrentBrightnessChanged.listen((double value) {
|
||||
if (mounted) {
|
||||
_ctr.brightnessValue.value = value;
|
||||
_brightnessValue.value = value;
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
@ -164,14 +163,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
FlutterVolumeController.updateShowSystemUI(false);
|
||||
await FlutterVolumeController.setVolume(value);
|
||||
} catch (_) {}
|
||||
_ctr.volumeValue.value = value;
|
||||
_ctr.volumeIndicator.value = true;
|
||||
_ctr.volumeInterceptEventStream.value = true;
|
||||
_volumeValue.value = value;
|
||||
_volumeIndicator.value = true;
|
||||
_volumeInterceptEventStream.value = true;
|
||||
_volumeTimer?.cancel();
|
||||
_volumeTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
if (mounted) {
|
||||
_ctr.volumeIndicator.value = false;
|
||||
_ctr.volumeInterceptEventStream.value = false;
|
||||
_volumeIndicator.value = false;
|
||||
_volumeInterceptEventStream.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -180,11 +179,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
try {
|
||||
await ScreenBrightness().setScreenBrightness(value);
|
||||
} catch (_) {}
|
||||
_ctr.brightnessIndicator.value = true;
|
||||
_brightnessIndicator.value = true;
|
||||
_brightnessTimer?.cancel();
|
||||
_brightnessTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
if (mounted) {
|
||||
_ctr.brightnessIndicator.value = false;
|
||||
_brightnessIndicator.value = false;
|
||||
}
|
||||
});
|
||||
widget.controller.brightness.value = value;
|
||||
@ -197,6 +196,134 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 动态构建底部控制条
|
||||
List<Widget> buildBottomControl() {
|
||||
const TextStyle textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
final PlPlayerController _ = widget.controller;
|
||||
Map<BottomControlType, Widget> videoProgressWidgets = {
|
||||
/// 上一集
|
||||
BottomControlType.pre: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.skip_previous_outlined,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () {},
|
||||
),
|
||||
|
||||
/// 播放暂停
|
||||
BottomControlType.playOrPause: PlayOrPauseButton(
|
||||
controller: _,
|
||||
),
|
||||
|
||||
/// 下一集
|
||||
BottomControlType.next: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.last_page_outlined,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () {},
|
||||
),
|
||||
|
||||
/// 时间进度
|
||||
BottomControlType.time: Row(
|
||||
children: [
|
||||
Obx(() {
|
||||
return Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.positionSeconds.value))
|
||||
: printDuration(Duration(seconds: _.positionSeconds.value)),
|
||||
style: textStyle,
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 2),
|
||||
const Text('/', style: textStyle),
|
||||
const SizedBox(width: 2),
|
||||
Obx(
|
||||
() => Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.durationSeconds.value))
|
||||
: printDuration(Duration(seconds: _.durationSeconds.value)),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// 空白占位
|
||||
BottomControlType.space: const Spacer(),
|
||||
|
||||
/// 画面比例
|
||||
BottomControlType.fit: SizedBox(
|
||||
height: 30,
|
||||
child: TextButton(
|
||||
onPressed: () => _.toggleVideoFit(),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_.videoFitDEsc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 播放速度
|
||||
BottomControlType.speed: SizedBox(
|
||||
width: 45,
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'${_.playbackSpeed.toString()}X',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 字幕
|
||||
/// 全屏
|
||||
BottomControlType.fullscreen: ComBtn(
|
||||
icon: Obx(
|
||||
() => Icon(
|
||||
_.isFullScreen.value
|
||||
? FontAwesomeIcons.compress
|
||||
: FontAwesomeIcons.expand,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
fuc: () => _.triggerFullScreen(),
|
||||
),
|
||||
};
|
||||
final List<Widget> list = [];
|
||||
var userSpecifyItem = widget.bottomList ??
|
||||
[
|
||||
BottomControlType.playOrPause,
|
||||
BottomControlType.time,
|
||||
BottomControlType.space,
|
||||
BottomControlType.fit,
|
||||
BottomControlType.fullscreen,
|
||||
];
|
||||
for (var i = 0; i < userSpecifyItem.length; i++) {
|
||||
list.add(videoProgressWidgets[userSpecifyItem[i]]!);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PlPlayerController _ = widget.controller;
|
||||
@ -316,7 +443,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
() => Align(
|
||||
child: AnimatedOpacity(
|
||||
curve: Curves.easeInOut,
|
||||
opacity: _ctr.volumeIndicator.value ? 1.0 : 0.0,
|
||||
opacity: _volumeIndicator.value ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
@ -335,9 +462,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
width: 28.0,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Icon(
|
||||
_ctr.volumeValue.value == 0.0
|
||||
_volumeValue.value == 0.0
|
||||
? Icons.volume_off
|
||||
: _ctr.volumeValue.value < 0.5
|
||||
: _volumeValue.value < 0.5
|
||||
? Icons.volume_down
|
||||
: Icons.volume_up,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -346,7 +473,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${(_ctr.volumeValue.value * 100.0).round()}%',
|
||||
'${(_volumeValue.value * 100.0).round()}%',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 13.0,
|
||||
@ -367,7 +494,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
() => Align(
|
||||
child: AnimatedOpacity(
|
||||
curve: Curves.easeInOut,
|
||||
opacity: _ctr.brightnessIndicator.value ? 1.0 : 0.0,
|
||||
opacity: _brightnessIndicator.value ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
@ -386,9 +513,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
width: 28.0,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Icon(
|
||||
_ctr.brightnessValue.value < 1.0 / 3.0
|
||||
_brightnessValue.value < 1.0 / 3.0
|
||||
? Icons.brightness_low
|
||||
: _ctr.brightnessValue.value < 2.0 / 3.0
|
||||
: _brightnessValue.value < 2.0 / 3.0
|
||||
? Icons.brightness_medium
|
||||
: Icons.brightness_high,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -398,7 +525,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
const SizedBox(width: 2.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${(_ctr.brightnessValue.value * 100.0).round()}%',
|
||||
'${(_brightnessValue.value * 100.0).round()}%',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 13.0,
|
||||
@ -489,7 +616,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
pos.clamp(Duration.zero, _.duration.value);
|
||||
_.onUpdatedSliderProgress(result);
|
||||
_.onChangedSliderStart();
|
||||
// _initTapPositoin = tapPosition;
|
||||
},
|
||||
onHorizontalDragEnd: (DragEndDetails details) {
|
||||
if (_.videoType.value == 'live' || _.controlsLock.value) {
|
||||
@ -521,7 +647,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
: screenWidth * 9 / 16) *
|
||||
3;
|
||||
final double brightness =
|
||||
_ctr.brightnessValue.value - delta / level;
|
||||
_brightnessValue.value - delta / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
@ -530,29 +656,29 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
const double threshold = 7.0; // 滑动阈值
|
||||
final bool flag =
|
||||
fullScreenGestureMode != FullScreenGestureMode.values.last;
|
||||
if (dy > _distance && dy > threshold) {
|
||||
if (dy > _distance.value && dy > threshold) {
|
||||
if (_.isFullScreen.value ^ flag) {
|
||||
lastFullScreenToggleTime = DateTime.now();
|
||||
// 下滑退出全屏
|
||||
await widget.controller.triggerFullScreen(status: flag);
|
||||
}
|
||||
_distance = 0.0;
|
||||
} else if (dy < _distance && dy < -threshold) {
|
||||
_distance.value = 0.0;
|
||||
} else if (dy < _distance.value && dy < -threshold) {
|
||||
if (!_.isFullScreen.value ^ flag) {
|
||||
lastFullScreenToggleTime = DateTime.now();
|
||||
// 上滑进入全屏
|
||||
await widget.controller.triggerFullScreen(status: !flag);
|
||||
}
|
||||
_distance = 0.0;
|
||||
_distance.value = 0.0;
|
||||
}
|
||||
_distance = dy;
|
||||
_distance.value = dy;
|
||||
} else {
|
||||
// 右边区域 👈
|
||||
final double level = (_.isFullScreen.value
|
||||
? Get.size.height
|
||||
: screenWidth * 9 / 16) *
|
||||
3;
|
||||
final double volume = _ctr.volumeValue.value - delta / level;
|
||||
final double volume = _volumeValue.value - delta / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
}
|
||||
@ -585,9 +711,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
position: 'bottom',
|
||||
child: widget.bottomControl ??
|
||||
BottomControl(
|
||||
controller: widget.controller,
|
||||
triggerFullScreen:
|
||||
widget.controller.triggerFullScreen),
|
||||
controller: widget.controller,
|
||||
triggerFullScreen: _.triggerFullScreen,
|
||||
buildBottomControl: buildBottomControl(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -607,23 +734,23 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
}
|
||||
if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.alwaysHide.code) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.onlyShowFullScreen.code &&
|
||||
!_.isFullScreen.value) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
} else if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.onlyHideFullScreen.code &&
|
||||
_.isFullScreen.value) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
if (_.videoType.value == 'live') {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (value > max || max <= 0) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
return Positioned(
|
||||
bottom: -1.5,
|
||||
@ -720,18 +847,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
/// 点击 快进/快退
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: _ctr.mountSeekBackwardButton.value ||
|
||||
_ctr.mountSeekForwardButton.value,
|
||||
visible:
|
||||
_mountSeekBackwardButton.value || _mountSeekForwardButton.value,
|
||||
child: Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _ctr.mountSeekBackwardButton.value
|
||||
child: _mountSeekBackwardButton.value
|
||||
? TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(
|
||||
begin: 0.0,
|
||||
end:
|
||||
_ctr.hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (BuildContext context, double value,
|
||||
@ -741,17 +867,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: child,
|
||||
),
|
||||
onEnd: () {
|
||||
if (_ctr.hideSeekBackwardButton.value) {
|
||||
_ctr.hideSeekBackwardButton.value = false;
|
||||
_ctr.mountSeekBackwardButton.value = false;
|
||||
if (_hideSeekBackwardButton.value) {
|
||||
_hideSeekBackwardButton.value = false;
|
||||
_mountSeekBackwardButton.value = false;
|
||||
}
|
||||
},
|
||||
child: BackwardSeekIndicator(
|
||||
onChanged: (Duration value) {
|
||||
// _seekBarDeltaValueNotifier.value = -value;
|
||||
},
|
||||
onChanged: (Duration value) => {},
|
||||
onSubmitted: (Duration value) {
|
||||
_ctr.hideSeekBackwardButton.value = true;
|
||||
_hideSeekBackwardButton.value = true;
|
||||
final Player player =
|
||||
widget.controller.videoPlayerController!;
|
||||
Duration result = player.state.position - value;
|
||||
@ -764,7 +888,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
},
|
||||
),
|
||||
)
|
||||
: nil,
|
||||
: const SizedBox(),
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
@ -772,11 +896,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _ctr.mountSeekForwardButton.value
|
||||
child: _mountSeekForwardButton.value
|
||||
? TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(
|
||||
begin: 0.0,
|
||||
end: _ctr.hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (BuildContext context, double value,
|
||||
@ -786,17 +910,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: child,
|
||||
),
|
||||
onEnd: () {
|
||||
if (_ctr.hideSeekForwardButton.value) {
|
||||
_ctr.hideSeekForwardButton.value = false;
|
||||
_ctr.mountSeekForwardButton.value = false;
|
||||
if (_hideSeekForwardButton.value) {
|
||||
_hideSeekForwardButton.value = false;
|
||||
_mountSeekForwardButton.value = false;
|
||||
}
|
||||
},
|
||||
child: ForwardSeekIndicator(
|
||||
onChanged: (Duration value) {
|
||||
// _seekBarDeltaValueNotifier.value = value;
|
||||
},
|
||||
onChanged: (Duration value) => {},
|
||||
onSubmitted: (Duration value) {
|
||||
_ctr.hideSeekForwardButton.value = true;
|
||||
_hideSeekForwardButton.value = true;
|
||||
final Player player =
|
||||
widget.controller.videoPlayerController!;
|
||||
Duration result = player.state.position + value;
|
||||
@ -809,7 +931,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
},
|
||||
),
|
||||
)
|
||||
: nil,
|
||||
: const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -820,31 +942,3 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PLVideoPlayerController extends GetxController {
|
||||
RxBool mountSeekBackwardButton = false.obs;
|
||||
RxBool mountSeekForwardButton = false.obs;
|
||||
RxBool hideSeekBackwardButton = false.obs;
|
||||
RxBool hideSeekForwardButton = false.obs;
|
||||
|
||||
RxDouble brightnessValue = 0.0.obs;
|
||||
RxBool brightnessIndicator = false.obs;
|
||||
|
||||
RxDouble volumeValue = 0.0.obs;
|
||||
RxBool volumeIndicator = false.obs;
|
||||
|
||||
RxDouble distance = 0.0.obs;
|
||||
// 初始手指落下位置
|
||||
RxDouble initTapPositoin = 0.0.obs;
|
||||
|
||||
RxBool volumeInterceptEventStream = false.obs;
|
||||
|
||||
// 双击快进 展示样式
|
||||
void onDoubleTapSeekForward() {
|
||||
mountSeekForwardButton.value = true;
|
||||
}
|
||||
|
||||
void onDoubleTapSeekBackward() {
|
||||
mountSeekBackwardButton.value = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:nil/nil.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/widgets/play_pause_btn.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
|
||||
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
final PlPlayerController? controller;
|
||||
final Function? triggerFullScreen;
|
||||
const BottomControl({this.controller, this.triggerFullScreen, Key? key})
|
||||
: super(key: key);
|
||||
final List<Widget>? buildBottomControl;
|
||||
const BottomControl({
|
||||
this.controller,
|
||||
this.triggerFullScreen,
|
||||
this.buildBottomControl,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
@ -20,11 +23,6 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
Widget build(BuildContext context) {
|
||||
Color colorTheme = Theme.of(context).colorScheme.primary;
|
||||
final _ = controller!;
|
||||
const textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
height: 90,
|
||||
@ -71,86 +69,89 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
PlayOrPauseButton(
|
||||
controller: _,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
// 播放时间
|
||||
Obx(() {
|
||||
return Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.positionSeconds.value))
|
||||
: printDuration(
|
||||
Duration(seconds: _.positionSeconds.value)),
|
||||
style: textStyle,
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 2),
|
||||
const Text('/', style: textStyle),
|
||||
const SizedBox(width: 2),
|
||||
Obx(
|
||||
() => Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.durationSeconds.value))
|
||||
: printDuration(
|
||||
Duration(seconds: _.durationSeconds.value)),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// 倍速
|
||||
// Obx(
|
||||
// () => SizedBox(
|
||||
// width: 45,
|
||||
// height: 34,
|
||||
// child: TextButton(
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// _.togglePlaybackSpeed();
|
||||
// },
|
||||
// child: Text(
|
||||
// '${_.playbackSpeed.toString()}X',
|
||||
// style: textStyle,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: TextButton(
|
||||
onPressed: () => _.toggleVideoFit(),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_.videoFitDEsc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// 全屏
|
||||
Obx(
|
||||
() => ComBtn(
|
||||
icon: Icon(
|
||||
_.isFullScreen.value
|
||||
? FontAwesomeIcons.compress
|
||||
: FontAwesomeIcons.expand,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => triggerFullScreen!(),
|
||||
),
|
||||
),
|
||||
],
|
||||
children: [...buildBottomControl!],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// PlayOrPauseButton(
|
||||
// controller: _,
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
// // 播放时间
|
||||
// Obx(() {
|
||||
// return Text(
|
||||
// _.durationSeconds.value >= 3600
|
||||
// ? printDurationWithHours(
|
||||
// Duration(seconds: _.positionSeconds.value))
|
||||
// : printDuration(
|
||||
// Duration(seconds: _.positionSeconds.value)),
|
||||
// style: textStyle,
|
||||
// );
|
||||
// }),
|
||||
// const SizedBox(width: 2),
|
||||
// const Text('/', style: textStyle),
|
||||
// const SizedBox(width: 2),
|
||||
// Obx(
|
||||
// () => Text(
|
||||
// _.durationSeconds.value >= 3600
|
||||
// ? printDurationWithHours(
|
||||
// Duration(seconds: _.durationSeconds.value))
|
||||
// : printDuration(
|
||||
// Duration(seconds: _.durationSeconds.value)),
|
||||
// style: textStyle,
|
||||
// ),
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// // 倍速
|
||||
// // Obx(
|
||||
// // () => SizedBox(
|
||||
// // width: 45,
|
||||
// // height: 34,
|
||||
// // child: TextButton(
|
||||
// // style: ButtonStyle(
|
||||
// // padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// // ),
|
||||
// // onPressed: () {
|
||||
// // _.togglePlaybackSpeed();
|
||||
// // },
|
||||
// // child: Text(
|
||||
// // '${_.playbackSpeed.toString()}X',
|
||||
// // style: textStyle,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// child: TextButton(
|
||||
// onPressed: () => _.toggleVideoFit(),
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// child: Obx(
|
||||
// () => Text(
|
||||
// _.videoFitDEsc.value,
|
||||
// style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 10),
|
||||
// // 全屏
|
||||
// Obx(
|
||||
// () => ComBtn(
|
||||
// icon: Icon(
|
||||
// _.isFullScreen.value
|
||||
// ? FontAwesomeIcons.compress
|
||||
// : FontAwesomeIcons.expand,
|
||||
// size: 15,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// fuc: () => triggerFullScreen!(),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
|
@ -84,6 +84,7 @@ class SettingBoxKey {
|
||||
autoUpgradeEnable = 'autoUpgradeEnable',
|
||||
feedBackEnable = 'feedBackEnable',
|
||||
defaultVideoQa = 'defaultVideoQa',
|
||||
defaultLiveQa = 'defaultLiveQa',
|
||||
defaultAudioQa = 'defaultAudioQa',
|
||||
autoPlayEnable = 'autoPlayEnable',
|
||||
fullScreenMode = 'fullScreenMode',
|
||||
|
Reference in New Issue
Block a user