tencent_cloud_chat_uikit/lib/ui/widgets/video_custom_control.dart

438 lines
12 KiB
Dart

// ignore_for_file: implementation_imports, unused_element
import 'dart:async';
import 'package:chewie/chewie.dart';
import 'package:chewie/src/animated_play_pause.dart';
import 'package:chewie/src/helpers/utils.dart';
import 'package:chewie/src/material/material_progress_bar.dart';
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
import 'package:tencent_cloud_chat_uikit/theme/tui_theme.dart';
import 'package:video_player/video_player.dart';
import 'center_play_button.dart';
class VideoCustomControls extends StatefulWidget {
const VideoCustomControls({required this.downloadFn, Key? key}) : super(key: key);
final Future<void> Function() downloadFn;
@override
State<StatefulWidget> createState() {
return _VideoCustomControlsState();
}
}
class _VideoCustomControlsState extends TIMUIKitState<VideoCustomControls> with SingleTickerProviderStateMixin {
late VideoPlayerValue _latestValue;
bool _hideStuff = true;
Timer? _hideTimer;
Timer? _initTimer;
Timer? _showAfterExpandCollapseTimer;
bool _dragging = false;
bool _displayTapped = false;
bool isLoading = false;
final barHeight = 48.0;
final marginSize = 5.0;
late VideoPlayerController controller;
ChewieController? _chewieController;
// We know that _chewieController is set in didChangeDependencies
ChewieController get chewieController => _chewieController!;
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
if (_latestValue.hasError) {
return Container(
color: Colors.transparent,
child: chewieController.errorBuilder?.call(
context,
chewieController.videoPlayerController.value.errorDescription!,
) ??
const Center(
child: Icon(
Icons.error,
color: Colors.white,
size: 42,
),
),
);
}
return MouseRegion(
onHover: (_) {
_cancelAndRestartTimer();
},
child: GestureDetector(
onTap: () => _cancelAndRestartTimer(),
child: AbsorbPointer(
absorbing: _hideStuff,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
if (_latestValue.isBuffering) const Center(child: CircularProgressIndicator(color: Colors.white)) else _buildHitArea(),
Positioned(
bottom: 0,
width: MediaQuery.of(context).size.width,
child: Column(children: [_buildVideoControlBar(context), _buildBottomBar()]),
),
if (isLoading)
Container(
child: LoadingAnimationWidget.staggeredDotsWave(
size: 35,
color: Colors.white,
),
padding: const EdgeInsets.all(30),
decoration: const BoxDecoration(
color: Color(0xB22b2b2b),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
),
],
),
),
),
);
}
@override
void dispose() {
_dispose();
super.dispose();
}
void _dispose() {
controller.removeListener(_updateState);
_hideTimer?.cancel();
_initTimer?.cancel();
_showAfterExpandCollapseTimer?.cancel();
}
@override
void didChangeDependencies() {
final _oldController = _chewieController;
_chewieController = ChewieController.of(context);
controller = chewieController.videoPlayerController;
if (_oldController != chewieController) {
_dispose();
_initialize();
}
super.didChangeDependencies();
}
Widget _buildBottomBar() {
return Material(
color: Colors.transparent,
child: Container(
height: barHeight,
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Row(
children: <Widget>[
SizedBox(
width: 48,
height: 48,
child: IconButton(
icon: Image.asset(
'images/close.png',
package: 'tencent_cloud_chat_uikit',
),
iconSize: 30,
onPressed: () {
if (_latestValue.isPlaying) {
_playPause();
}
Navigator.of(context).pop();
},
),
),
Expanded(child: Container()),
SizedBox(
width: 48,
height: 48,
child: IconButton(
icon: Image.asset(
'images/download.png',
package: 'tencent_cloud_chat_uikit',
),
iconSize: 30,
onPressed: () async {
setState(() {
isLoading = true;
});
await widget.downloadFn();
Future.delayed(
const Duration(milliseconds: 200),
() {
setState(
() {
isLoading = false;
},
);
},
);
},
),
)
],
),
),
);
}
AnimatedOpacity _buildVideoControlBar(
BuildContext context,
) {
const iconColor = Colors.white;
return AnimatedOpacity(
opacity: _hideStuff ? 0.0 : 1.0,
duration: const Duration(milliseconds: 300),
child: SizedBox(
height: barHeight,
child: Row(
children: <Widget>[
_buildPlayPause(controller, iconColor),
if (chewieController.isLive) const Expanded(child: Text('LIVE')) else _buildPositionStart(iconColor),
if (chewieController.isLive) const SizedBox() else _buildProgressBar(),
if (!chewieController.isLive) _buildPositionEnd(iconColor),
],
),
),
);
}
Widget _buildHitArea() {
// final bool isFinished = _latestValue.position >= _latestValue.duration;
return GestureDetector(
onTap: () {
if (_latestValue.isPlaying) {
if (_displayTapped) {
setState(() {
_hideStuff = true;
});
} else {
_cancelAndRestartTimer();
}
} else {
_playPause();
setState(() {
_hideStuff = true;
});
}
},
child: CenterPlayButton(
isPlaying: controller.value.isPlaying,
show: !_latestValue.isPlaying && !_dragging,
onPressed: _playPause,
));
}
GestureDetector _buildPlayPause(VideoPlayerController controller, Color color) {
return GestureDetector(
onTap: _playPause,
child: Container(
height: barHeight,
color: Colors.transparent,
margin: const EdgeInsets.only(left: 8.0, right: 4.0),
padding: const EdgeInsets.only(
left: 12.0,
right: 12.0,
),
child: AnimatedPlayPause(
playing: controller.value.isPlaying,
color: color,
),
),
);
}
Widget _buildPositionStart(Color? iconColor) {
final position = _latestValue.position;
return Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Text(
formatDuration(position),
style: TextStyle(fontSize: 14.0, color: iconColor),
),
);
}
Widget _buildPositionEnd(Color? iconColor) {
final duration = _latestValue.duration;
return Padding(
padding: const EdgeInsets.only(right: 24.0),
child: Text(
formatDuration(duration),
style: TextStyle(fontSize: 14.0, color: iconColor),
),
);
}
void _cancelAndRestartTimer() {
_hideTimer?.cancel();
_startHideTimer();
setState(() {
_hideStuff = false;
_displayTapped = true;
});
}
Future<void> _initialize() async {
controller.addListener(_updateState);
_updateState();
if (controller.value.isPlaying || chewieController.autoPlay) {
_startHideTimer();
}
if (chewieController.showControlsOnInitialize) {
_initTimer = Timer(const Duration(milliseconds: 200), () {
setState(() {
_hideStuff = false;
});
});
}
}
void _onExpandCollapse() {
setState(() {
_hideStuff = true;
chewieController.toggleFullScreen();
_showAfterExpandCollapseTimer = Timer(const Duration(milliseconds: 300), () {
setState(() {
_cancelAndRestartTimer();
});
});
});
}
void _playPause() {
final isFinished = _latestValue.position >= _latestValue.duration;
setState(() {
if (controller.value.isPlaying) {
_hideStuff = false;
_hideTimer?.cancel();
controller.pause();
} else {
_cancelAndRestartTimer();
if (!controller.value.isInitialized) {
controller.initialize().then((_) {
controller.play();
});
} else {
if (isFinished) {
controller.seekTo(const Duration());
}
Timer(const Duration(milliseconds: 100), () => controller.play());
}
}
});
}
void _startHideTimer() {
_hideTimer = Timer(const Duration(seconds: 3), () {
setState(() {
_hideStuff = true;
});
});
}
void _updateState() {
if (!mounted) return;
setState(() {
_latestValue = controller.value;
});
}
Widget _buildProgressBar() {
return Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 20.0),
child: MaterialVideoProgressBar(
controller,
onDragStart: () {
setState(() {
_dragging = true;
});
_hideTimer?.cancel();
},
onDragEnd: () {
setState(() {
_dragging = false;
});
_startHideTimer();
},
colors: chewieController.materialProgressColors ?? ChewieProgressColors(playedColor: Colors.white, handleColor: Colors.white, bufferedColor: Colors.white38, backgroundColor: Colors.white24),
),
),
);
}
}
class _PlaybackSpeedDialog extends TIMUIKitStatelessWidget {
_PlaybackSpeedDialog({
Key? key,
required List<double> speeds,
required double selected,
}) : _speeds = speeds,
_selected = selected,
super(key: key);
final List<double> _speeds;
final double _selected;
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final TUITheme theme = value.theme;
final Color selectedColor = theme.primaryColor ?? Theme.of(context).primaryColor;
return ListView.builder(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: (context, index) {
final _speed = _speeds[index];
return ListTile(
dense: true,
title: Row(
children: [
if (_speed == _selected)
Icon(
Icons.check,
size: 20.0,
color: selectedColor,
)
else
Container(width: 20.0),
const SizedBox(width: 16.0),
Text(_speed.toString()),
],
),
selected: _speed == _selected,
onTap: () {
Navigator.of(context).pop(_speed);
},
);
},
itemCount: _speeds.length,
);
}
}