Merge branch 'feature-pip' into alpha

This commit is contained in:
guozhigq
2023-09-07 19:26:00 +08:00
7 changed files with 196 additions and 20 deletions

View File

@ -1,3 +1,6 @@
import 'dart:io';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -19,6 +22,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
bool isShowCover = true; bool isShowCover = true;
bool isPlay = true; bool isPlay = true;
Floating? floating;
@override @override
void initState() { void initState() {
@ -32,19 +36,24 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
} }
}, },
); );
if (Platform.isAndroid) {
floating = Floating();
}
} }
@override @override
void dispose() { void dispose() {
plPlayerController!.dispose(); plPlayerController!.dispose();
if (floating != null) {
floating!.dispose();
}
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final videoHeight = MediaQuery.of(context).size.width * 9 / 16; final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
Widget childWhenDisabled = Scaffold(
return Scaffold(
primary: true, primary: true,
appBar: AppBar( appBar: AppBar(
centerTitle: false, centerTitle: false,
@ -98,6 +107,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
bottomControl: BottomControl( bottomControl: BottomControl(
controller: plPlayerController, controller: plPlayerController,
liveRoomCtr: _liveRoomController, liveRoomCtr: _liveRoomController,
floating: floating,
), ),
) )
: const SizedBox(), : const SizedBox(),
@ -123,5 +133,25 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
], ],
), ),
); );
Widget childWhenEnabled = AspectRatio(
aspectRatio: 16 / 9,
child: plPlayerController!.videoPlayerController != null
? PLVideoPlayer(
controller: plPlayerController!,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
),
)
: const SizedBox(),
);
if (Platform.isAndroid) {
return PiPSwitcher(
childWhenDisabled: childWhenDisabled,
childWhenEnabled: childWhenEnabled,
);
} else {
return childWhenDisabled;
}
} }
} }

View File

@ -1,4 +1,8 @@
import 'dart:io';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/models/video/play/url.dart';
@ -9,9 +13,11 @@ import 'package:pilipala/utils/storage.dart';
class BottomControl extends StatefulWidget implements PreferredSizeWidget { class BottomControl extends StatefulWidget implements PreferredSizeWidget {
final PlPlayerController? controller; final PlPlayerController? controller;
final LiveRoomController? liveRoomCtr; final LiveRoomController? liveRoomCtr;
final Floating? floating;
const BottomControl({ const BottomControl({
this.controller, this.controller,
this.liveRoomCtr, this.liveRoomCtr,
this.floating,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -85,6 +91,35 @@ class _BottomControlState extends State<BottomControl> {
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
if (Platform.isAndroid) ...[
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
bool canUsePiP = false;
widget.controller!.hiddenControls(false);
try {
canUsePiP = await widget.floating!.isPipAvailable;
} on PlatformException catch (_) {
canUsePiP = false;
}
if (canUsePiP) {
await widget.floating!.enable();
} else {}
},
icon: const Icon(
Icons.picture_in_picture_outlined,
size: 18,
color: Colors.white,
),
),
),
const SizedBox(width: 4),
],
ComBtn( ComBtn(
icon: const Icon( icon: const Icon(
Icons.fullscreen, Icons.fullscreen,

View File

@ -1,6 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:floating/floating.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -51,6 +54,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late Future _futureBuilderFuture; late Future _futureBuilderFuture;
// 自动退出全屏 // 自动退出全屏
late bool autoExitFullcreen; late bool autoExitFullcreen;
Floating? floating;
@override @override
void initState() { void initState() {
@ -60,6 +64,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false); setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
videoSourceInit(); videoSourceInit();
appbarStreamListen(); appbarStreamListen();
if (Platform.isAndroid) {
floating = Floating();
}
} }
// 获取视频资源,初始化播放器 // 获取视频资源,初始化播放器
@ -83,7 +90,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
} }
// 播放器状态监听 // 播放器状态监听
void playerListener(PlayerStatus? status) { void playerListener(PlayerStatus? status) async {
playerStatus = status!; playerStatus = status!;
if (status == PlayerStatus.completed) { if (status == PlayerStatus.completed) {
// 结束播放退出全屏 // 结束播放退出全屏
@ -91,9 +98,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
plPlayerController!.triggerFullScreen(status: false); plPlayerController!.triggerFullScreen(status: false);
} }
// 播放完展示控制栏 // 播放完展示控制栏
PiPStatus currentStatus = await floating!.pipStatus;
if (currentStatus == PiPStatus.disabled) {
plPlayerController!.onLockControl(false); plPlayerController!.onLockControl(false);
} }
} }
}
// 继续播放或重新播放 // 继续播放或重新播放
void continuePlay() async { void continuePlay() async {
@ -116,6 +126,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
plPlayerController!.removeStatusLister(playerListener); plPlayerController!.removeStatusLister(playerListener);
plPlayerController!.dispose(); plPlayerController!.dispose();
} }
if (floating != null) {
floating!.dispose();
}
super.dispose(); super.dispose();
} }
@ -162,7 +175,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
final videoHeight = MediaQuery.of(context).size.width * 9 / 16; final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
final double pinnedHeaderHeight = final double pinnedHeaderHeight =
statusBarHeight + kToolbarHeight + videoHeight; statusBarHeight + kToolbarHeight + videoHeight;
return SafeArea( Widget childWhenDisabled = SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: Stack( child: Stack(
@ -209,6 +222,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
plPlayerController, plPlayerController,
videoDetailCtr: videoDetailCtr:
videoDetailController, videoDetailController,
floating: floating,
), ),
danmuWidget: Obx( danmuWidget: Obx(
() => PlDanmaku( () => PlDanmaku(
@ -320,13 +334,18 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
]; ];
}, },
// pinnedHeaderSliverHeightBuilder: () {
// return playerStatus != PlayerStatus.playing
// ? statusBarHeight + kToolbarHeight
// : pinnedHeaderHeight;
// },
/// 不收回
pinnedHeaderSliverHeightBuilder: () { pinnedHeaderSliverHeightBuilder: () {
return playerStatus != PlayerStatus.playing return pinnedHeaderHeight;
? statusBarHeight + kToolbarHeight
: pinnedHeaderHeight;
}, },
onlyOneScrollInBody: true, onlyOneScrollInBody: true,
body: Container( body: Container(
key: Key(Get.arguments['heroTag']),
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: Column( child: Column(
children: [ children: [
@ -402,21 +421,60 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
), ),
), ),
/// 重新进入会刷新
// 播放完成/暂停播放 // 播放完成/暂停播放
StreamBuilder( // StreamBuilder(
stream: appbarStream.stream, // stream: appbarStream.stream,
initialData: 0, // initialData: 0,
builder: ((context, snapshot) { // builder: ((context, snapshot) {
return ScrollAppBar( // return ScrollAppBar(
snapshot.data!.toDouble(), // snapshot.data!.toDouble(),
() => continuePlay(), // () => continuePlay(),
playerStatus, // playerStatus,
null, // null,
); // );
}), // }),
) // )
], ],
), ),
); );
Widget childWhenEnabled = FutureBuilder(
key: Key(Get.arguments['heroTag']),
future: _futureBuilderFuture,
builder: ((context, snapshot) {
if (snapshot.hasData && snapshot.data['status']) {
return Obx(
() => !videoDetailController.autoPlay.value
? const SizedBox()
: PLVideoPlayer(
controller: plPlayerController!,
headerControl: HeaderControl(
controller: plPlayerController,
videoDetailCtr: videoDetailController,
),
danmuWidget: Obx(
() => PlDanmaku(
key: Key(
videoDetailController.danmakuCid.value.toString()),
cid: videoDetailController.danmakuCid.value,
playerController: plPlayerController!,
),
),
),
);
} else {
return const SizedBox();
}
}),
);
if (Platform.isAndroid) {
return PiPSwitcher(
childWhenDisabled: childWhenDisabled,
childWhenEnabled: childWhenEnabled,
);
} else {
return childWhenDisabled;
}
} }
} }

View File

@ -1,4 +1,8 @@
import 'dart:io';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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';
@ -14,9 +18,11 @@ import 'package:pilipala/utils/storage.dart';
class HeaderControl extends StatefulWidget implements PreferredSizeWidget { class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
final PlPlayerController? controller; final PlPlayerController? controller;
final VideoDetailController? videoDetailCtr; final VideoDetailController? videoDetailCtr;
final Floating? floating;
const HeaderControl({ const HeaderControl({
this.controller, this.controller,
this.videoDetailCtr, this.videoDetailCtr,
this.floating,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -770,6 +776,39 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
if (Platform.isAndroid) ...[
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
bool canUsePiP = false;
widget.controller!.hiddenControls(false);
try {
canUsePiP = await widget.floating!.isPipAvailable;
} on PlatformException catch (_) {
canUsePiP = false;
}
if (canUsePiP) {
final aspectRatio = Rational(
widget.videoDetailCtr!.data.dash!.video!.first.width!,
widget.videoDetailCtr!.data.dash!.video!.first.height!,
);
await widget.floating!.enable(aspectRatio: aspectRatio);
} else {}
},
icon: const Icon(
Icons.picture_in_picture_outlined,
size: 19,
color: Colors.white,
),
),
),
const SizedBox(width: 4),
],
Obx( Obx(
() => SizedBox( () => SizedBox(
width: 45, width: 45,

View File

@ -746,6 +746,10 @@ class PlPlayerController {
} }
} }
void hiddenControls(bool val) {
showControls.value = val;
}
/// 设置长按倍速状态 live模式下禁用 /// 设置长按倍速状态 live模式下禁用
void setDoubleSpeedStatus(bool val) { void setDoubleSpeedStatus(bool val) {
if (videoType.value == 'live') { if (videoType.value == 'live') {

View File

@ -417,6 +417,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
floating:
dependency: "direct main"
description:
name: floating
sha256: d9d563089e34fbd714ffdcdd2df447ec41b40c9226dacae6b4f78847aef8b991
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter

View File

@ -117,6 +117,8 @@ dependencies:
status_bar_control: ^3.2.1 status_bar_control: ^3.2.1
# 代理 # 代理
system_proxy: ^0.1.0 system_proxy: ^0.1.0
# pip
floating: ^2.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: