init
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user