소스 검색

[feat]键盘教程视频页,安卓平台页,增加chewie库,实现自定义播放器UI

hezihao 7 달 전
부모
커밋
fa7bb1b888

+ 2 - 41
lib/module/keyboard_tutorial/video/android/keyboard_tutorial_video_android_view.dart

@@ -1,3 +1,4 @@
+import 'package:chewie/chewie.dart';
 import 'package:flutter/Material.dart';
 import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
 import 'package:keyboard/base/base_view.dart';
@@ -130,53 +131,13 @@ class KeyboardTutorialVideoAndroidView
                 aspectRatio: controller.videoController.value.aspectRatio,
                 child: ClipRRect(
                   borderRadius: BorderRadius.circular(12.w),
-                  child: VideoPlayer(controller.videoController),
+                  child: Chewie(controller: controller.chewieController),
                 ),
               );
             }
           }),
-          // 操作按钮
-          _buildActionBtn(),
         ],
       ),
     );
   }
-
-  /// 操作按钮
-  Widget _buildActionBtn() {
-    return Obx(() {
-      return Stack(
-        children: [
-          Visibility(
-            visible: !controller.isVideoPlaying.value,
-            child: _buildPlayBtn(),
-          ),
-          Visibility(
-            visible: controller.isVideoPlaying.value,
-            child: _buildPauseBtn(),
-          ),
-        ],
-      );
-    });
-  }
-
-  /// 播放按钮
-  Widget _buildPlayBtn() {
-    return GestureDetector(
-      onTap: () {
-        controller.clickPlay();
-      },
-      child: Assets.images.iconPlay.image(width: 52.w, height: 52.w),
-    );
-  }
-
-  /// 暂停按钮
-  Widget _buildPauseBtn() {
-    return GestureDetector(
-      onTap: () {
-        controller.clickPause();
-      },
-      child: Assets.images.iconPause.image(width: 52.w, height: 52.w),
-    );
-  }
 }

+ 30 - 8
lib/module/keyboard_tutorial/video/android/keyboard_tutorial_video_android_view_controller.dart

@@ -1,9 +1,13 @@
+import 'package:chewie/chewie.dart';
+import 'package:flutter/Material.dart';
 import 'package:get/get.dart';
 import 'package:injectable/injectable.dart';
 import 'package:keyboard/base/base_controller.dart';
 import 'package:keyboard/resource/assets.gen.dart';
 import 'package:video_player/video_player.dart';
 
+import '../../../../widget/video/custom_material_controls.dart';
+
 /// 键盘教程页-视频引导页-Android平台-Controller
 @injectable
 class KeyboardTutorialVideoAndroidViewController extends BaseController {
@@ -16,6 +20,9 @@ class KeyboardTutorialVideoAndroidViewController extends BaseController {
   /// 视频控制器
   late VideoPlayerController videoController;
 
+  /// 新增 Chewie 控制器
+  late ChewieController chewieController;
+
   @override
   void onInit() {
     super.onInit();
@@ -25,8 +32,9 @@ class KeyboardTutorialVideoAndroidViewController extends BaseController {
   @override
   void onClose() {
     // 移除视频状态监听器
-    videoController.removeListener(_updatePlayingStatus);
+    chewieController.removeListener(_syncPlayStatus);
     // 释放资源
+    chewieController.dispose();
     videoController.dispose();
     super.onClose();
   }
@@ -38,18 +46,32 @@ class KeyboardTutorialVideoAndroidViewController extends BaseController {
       )
       ..initialize().then((_) {
         isVideoInitialized.value = true;
-        // 视频初始化完成后,自动播放
-        videoController.play();
+        // 初始化 Chewie 控制器
+        chewieController = ChewieController(
+          videoPlayerController: videoController,
+          // 自动开始播放
+          autoPlay: true,
+          // 设置循环播放
+          looping: true,
+          // 是否显示右上角的选项按钮
+          showOptions: false,
+          // 速度选项
+          playbackSpeeds: [0.5, 1.0, 1.5, 2.0],
+          // 自定义UI
+          customControls: CustomMaterialControls(),
+        );
+        chewieController.addListener(_syncPlayStatus);
       });
-    // 设置循环播放
+
     videoController.setLooping(true);
-    // 添加播放状态监听器
-    videoController.addListener(_updatePlayingStatus);
   }
 
   /// 更新视频的播放状态
-  void _updatePlayingStatus() {
-    isVideoPlaying.value = videoController.value.isPlaying;
+  void _syncPlayStatus() {
+    final isPlaying = chewieController.videoPlayerController.value.isPlaying;
+    if (isVideoPlaying.value != isPlaying) {
+      isVideoPlaying.value = isPlaying;
+    }
   }
 
   /// 返回

+ 733 - 0
lib/widget/video/custom_material_controls.dart

@@ -0,0 +1,733 @@
+import 'dart:async';
+
+import 'package:chewie/src/chewie_player.dart';
+import 'package:chewie/src/chewie_progress_colors.dart';
+import 'package:chewie/src/helpers/utils.dart';
+import 'package:chewie/src/material/color_compat_extensions.dart';
+import 'package:chewie/src/material/material_progress_bar.dart';
+import 'package:chewie/src/material/widgets/options_dialog.dart';
+import 'package:chewie/src/material/widgets/playback_speed_dialog.dart';
+import 'package:chewie/src/models/option_item.dart';
+import 'package:chewie/src/models/subtitle_model.dart';
+import 'package:chewie/src/notifiers/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:provider/provider.dart';
+import 'package:video_player/video_player.dart';
+
+import '../../resource/assets.gen.dart';
+
+/// 自定义播放器UI
+class CustomMaterialControls extends StatefulWidget {
+  final bool showPlayButton;
+
+  const CustomMaterialControls({super.key, this.showPlayButton = true});
+
+  @override
+  State<StatefulWidget> createState() {
+    return _CustomMaterialControlsState();
+  }
+}
+
+class _CustomMaterialControlsState extends State<CustomMaterialControls>
+    with SingleTickerProviderStateMixin {
+  late PlayerNotifier notifier;
+  late VideoPlayerValue _latestValue;
+  double? _latestVolume;
+  Timer? _hideTimer;
+  Timer? _initTimer;
+  late var _subtitlesPosition = Duration.zero;
+  bool _subtitleOn = false;
+  Timer? _showAfterExpandCollapseTimer;
+  bool _dragging = false;
+  bool _displayTapped = false;
+  Timer? _bufferingDisplayTimer;
+  bool _displayBufferingIndicator = false;
+
+  final barHeight = 48.0 * 1.5;
+  final marginSize = 5.0;
+
+  late VideoPlayerController controller;
+  ChewieController? _chewieController;
+
+  // We know that _chewieController is set in didChangeDependencies
+  ChewieController get chewieController => _chewieController!;
+
+  @override
+  void initState() {
+    super.initState();
+    notifier = Provider.of<PlayerNotifier>(context, listen: false);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (_latestValue.hasError) {
+      return 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: notifier.hideStuff,
+          child: Stack(
+            children: [
+              if (_displayBufferingIndicator)
+                _chewieController?.bufferingBuilder?.call(context) ??
+                    const Center(child: CircularProgressIndicator())
+              else
+                _buildHitArea(),
+              _buildActionBar(),
+              Column(
+                mainAxisAlignment: MainAxisAlignment.end,
+                children: <Widget>[
+                  if (_subtitleOn)
+                    Transform.translate(
+                      offset: Offset(
+                        0.0,
+                        notifier.hideStuff ? barHeight * 0.8 : 0.0,
+                      ),
+                      child: _buildSubtitles(
+                        context,
+                        chewieController.subtitle!,
+                      ),
+                    ),
+                  _buildBottomBar(context),
+                ],
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  @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 _buildActionBar() {
+    return Positioned(
+      top: 0,
+      right: 0,
+      child: SafeArea(
+        child: AnimatedOpacity(
+          opacity: notifier.hideStuff ? 0.0 : 1.0,
+          duration: const Duration(milliseconds: 250),
+          child: Row(
+            children: [
+              _buildSubtitleToggle(),
+              if (chewieController.showOptions) _buildOptionsButton(),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  List<OptionItem> _buildOptions(BuildContext context) {
+    final options = <OptionItem>[
+      OptionItem(
+        onTap: (context) async {
+          Navigator.pop(context);
+          _onSpeedButtonTap();
+        },
+        iconData: Icons.speed,
+        title:
+            chewieController.optionsTranslation?.playbackSpeedButtonText ??
+            'Playback speed',
+      ),
+    ];
+
+    if (chewieController.additionalOptions != null &&
+        chewieController.additionalOptions!(context).isNotEmpty) {
+      options.addAll(chewieController.additionalOptions!(context));
+    }
+    return options;
+  }
+
+  Widget _buildOptionsButton() {
+    return AnimatedOpacity(
+      opacity: notifier.hideStuff ? 0.0 : 1.0,
+      duration: const Duration(milliseconds: 250),
+      child: IconButton(
+        onPressed: () async {
+          _hideTimer?.cancel();
+
+          if (chewieController.optionsBuilder != null) {
+            await chewieController.optionsBuilder!(
+              context,
+              _buildOptions(context),
+            );
+          } else {
+            await showModalBottomSheet<OptionItem>(
+              context: context,
+              isScrollControlled: true,
+              useRootNavigator: chewieController.useRootNavigator,
+              builder:
+                  (context) => OptionsDialog(
+                    options: _buildOptions(context),
+                    cancelButtonText:
+                        chewieController.optionsTranslation?.cancelButtonText,
+                  ),
+            );
+          }
+
+          if (_latestValue.isPlaying) {
+            _startHideTimer();
+          }
+        },
+        icon: const Icon(Icons.more_vert, color: Colors.white),
+      ),
+    );
+  }
+
+  Widget _buildSubtitles(BuildContext context, Subtitles subtitles) {
+    if (!_subtitleOn) {
+      return const SizedBox();
+    }
+    final currentSubtitle = subtitles.getByPosition(_subtitlesPosition);
+    if (currentSubtitle.isEmpty) {
+      return const SizedBox();
+    }
+
+    if (chewieController.subtitleBuilder != null) {
+      return chewieController.subtitleBuilder!(
+        context,
+        currentSubtitle.first!.text,
+      );
+    }
+
+    return Padding(
+      padding: EdgeInsets.all(marginSize),
+      child: Container(
+        padding: const EdgeInsets.all(5),
+        decoration: BoxDecoration(
+          color: const Color(0x96000000),
+          borderRadius: BorderRadius.circular(10.0),
+        ),
+        child: Text(
+          currentSubtitle.first!.text.toString(),
+          style: const TextStyle(fontSize: 18),
+          textAlign: TextAlign.center,
+        ),
+      ),
+    );
+  }
+
+  AnimatedOpacity _buildBottomBar(BuildContext context) {
+    final iconColor = Theme.of(context).textTheme.labelLarge!.color;
+
+    return AnimatedOpacity(
+      opacity: notifier.hideStuff ? 0.0 : 1.0,
+      duration: const Duration(milliseconds: 300),
+      child: Container(
+        height: barHeight + (chewieController.isFullScreen ? 10.0 : 0),
+        padding: EdgeInsets.only(
+          left: 20,
+          right: 20,
+          bottom: !chewieController.isFullScreen ? 10.0 : 0,
+        ),
+        child: SafeArea(
+          top: false,
+          bottom: chewieController.isFullScreen,
+          minimum: chewieController.controlsSafeAreaMinimum,
+          child: Column(
+            mainAxisSize: MainAxisSize.min,
+            mainAxisAlignment: MainAxisAlignment.center,
+            children: [
+              Flexible(
+                child: Row(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  children: <Widget>[
+                    if (chewieController.isLive)
+                      const Expanded(child: Text('LIVE'))
+                    else
+                      _buildPosition(iconColor),
+                    if (chewieController.allowMuting)
+                      _buildMuteButton(controller),
+                    const Spacer(),
+                    if (chewieController.allowFullScreen) _buildExpandButton(),
+                  ],
+                ),
+              ),
+              SizedBox(height: chewieController.isFullScreen ? 15.0 : 0),
+              if (!chewieController.isLive)
+                Expanded(
+                  child: Container(
+                    // padding: const EdgeInsets.symmetric(horizontal: 20),
+                    child: Row(children: [_buildProgressBar()]),
+                  ),
+                ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  GestureDetector _buildMuteButton(VideoPlayerController controller) {
+    return GestureDetector(
+      onTap: () {
+        _cancelAndRestartTimer();
+
+        if (_latestValue.volume == 0) {
+          controller.setVolume(_latestVolume ?? 0.5);
+        } else {
+          _latestVolume = controller.value.volume;
+          controller.setVolume(0.0);
+        }
+      },
+      child: AnimatedOpacity(
+        opacity: notifier.hideStuff ? 0.0 : 1.0,
+        duration: const Duration(milliseconds: 300),
+        child: ClipRect(
+          child: Container(
+            height: barHeight,
+            padding: const EdgeInsets.only(left: 6.0),
+            child: Icon(
+              _latestValue.volume > 0 ? Icons.volume_up : Icons.volume_off,
+              color: Colors.white,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  GestureDetector _buildExpandButton() {
+    return GestureDetector(
+      onTap: _onExpandCollapse,
+      child: AnimatedOpacity(
+        opacity: notifier.hideStuff ? 0.0 : 1.0,
+        duration: const Duration(milliseconds: 300),
+        child: Container(
+          height: barHeight + (chewieController.isFullScreen ? 15.0 : 0),
+          margin: const EdgeInsets.only(right: 12.0),
+          padding: const EdgeInsets.only(left: 8.0, right: 8.0),
+          child: Center(
+            child: Icon(
+              chewieController.isFullScreen
+                  ? Icons.fullscreen_exit
+                  : Icons.fullscreen,
+              color: Colors.white,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+
+  Widget _buildHitArea() {
+    final bool isFinished =
+        (_latestValue.position >= _latestValue.duration) &&
+        _latestValue.duration.inSeconds > 0;
+    final bool showPlayButton =
+        widget.showPlayButton && !_dragging && !notifier.hideStuff;
+
+    return GestureDetector(
+      onTap: () {
+        if (_latestValue.isPlaying) {
+          if (_chewieController?.pauseOnBackgroundTap ?? false) {
+            _playPause();
+            _cancelAndRestartTimer();
+          } else {
+            if (_displayTapped) {
+              setState(() {
+                notifier.hideStuff = true;
+              });
+            } else {
+              _cancelAndRestartTimer();
+            }
+          }
+        } else {
+          _playPause();
+
+          setState(() {
+            notifier.hideStuff = true;
+          });
+        }
+      },
+      child: Container(
+        alignment: Alignment.center,
+        color: Colors.transparent,
+        // The Gesture Detector doesn't expand to the full size of the container without this; Not sure why!
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            // 快进按钮
+            // if (!isFinished && !chewieController.isLive)
+            //   CenterSeekButton(
+            //     iconData: Icons.replay_10,
+            //     backgroundColor: Colors.black54,
+            //     iconColor: Colors.white,
+            //     show: showPlayButton,
+            //     fadeDuration: chewieController.materialSeekButtonFadeDuration,
+            //     iconSize: chewieController.materialSeekButtonSize,
+            //     onPressed: _seekBackward,
+            //   ),
+
+            // 中间的播放、暂停按钮
+            Container(
+              margin: EdgeInsets.symmetric(horizontal: marginSize),
+              child: _buildActionBtn(showPlayButton),
+            ),
+
+            // 第三方库中原始的播放、暂停按钮
+            // Container(
+            //   margin: EdgeInsets.symmetric(
+            //     horizontal: marginSize,
+            //   ),
+            //   child: CenterPlayButton(
+            //     backgroundColor: Colors.black54,
+            //     iconColor: Colors.white,
+            //     isFinished: isFinished,
+            //     isPlaying: controller.value.isPlaying,
+            //     show: showPlayButton,
+            //     onPressed: _playPause,
+            //   ),
+            // ),
+
+            // 快退按钮
+            // if (!isFinished && !chewieController.isLive)
+            //   CenterSeekButton(
+            //     iconData: Icons.forward_10,
+            //     backgroundColor: Colors.black54,
+            //     iconColor: Colors.white,
+            //     show: showPlayButton,
+            //     fadeDuration: chewieController.materialSeekButtonFadeDuration,
+            //     iconSize: chewieController.materialSeekButtonSize,
+            //     onPressed: _seekForward,
+            //   ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  /// 操作按钮
+  Widget _buildActionBtn(bool showPlayButton) {
+    return Visibility(
+      // 是否显示操作按钮
+      visible: showPlayButton,
+      child: Stack(
+        children: [
+          Visibility(
+            visible: !controller.value.isPlaying,
+            child: _buildPlayBtn(),
+          ),
+          Visibility(
+            visible: controller.value.isPlaying,
+            child: _buildPauseBtn(),
+          ),
+        ],
+      ),
+    );
+  }
+
+  /// 播放按钮
+  Widget _buildPlayBtn() {
+    return GestureDetector(
+      onTap: () {
+        _playPause();
+      },
+      child: Assets.images.iconPlay.image(width: 52.w, height: 52.w),
+    );
+  }
+
+  /// 暂停按钮
+  Widget _buildPauseBtn() {
+    return GestureDetector(
+      onTap: () {
+        _playPause();
+      },
+      child: Assets.images.iconPause.image(width: 52.w, height: 52.w),
+    );
+  }
+
+  Future<void> _onSpeedButtonTap() async {
+    _hideTimer?.cancel();
+
+    final chosenSpeed = await showModalBottomSheet<double>(
+      context: context,
+      isScrollControlled: true,
+      useRootNavigator: chewieController.useRootNavigator,
+      builder:
+          (context) => PlaybackSpeedDialog(
+            speeds: chewieController.playbackSpeeds,
+            selected: _latestValue.playbackSpeed,
+          ),
+    );
+
+    if (chosenSpeed != null) {
+      controller.setPlaybackSpeed(chosenSpeed);
+    }
+
+    if (_latestValue.isPlaying) {
+      _startHideTimer();
+    }
+  }
+
+  Widget _buildPosition(Color? iconColor) {
+    final position = _latestValue.position;
+    final duration = _latestValue.duration;
+
+    return RichText(
+      text: TextSpan(
+        text: '${formatDuration(position)} ',
+        children: <InlineSpan>[
+          TextSpan(
+            text: '/ ${formatDuration(duration)}',
+            style: TextStyle(
+              fontSize: 14.0,
+              color: Colors.white.withOpacityCompat(.75),
+              fontWeight: FontWeight.normal,
+            ),
+          ),
+        ],
+        style: const TextStyle(
+          fontSize: 14.0,
+          color: Colors.white,
+          fontWeight: FontWeight.bold,
+        ),
+      ),
+    );
+  }
+
+  Widget _buildSubtitleToggle() {
+    //if don't have subtitle hiden button
+    if (chewieController.subtitle?.isEmpty ?? true) {
+      return const SizedBox();
+    }
+    return GestureDetector(
+      onTap: _onSubtitleTap,
+      child: Container(
+        height: barHeight,
+        color: Colors.transparent,
+        padding: const EdgeInsets.only(left: 12.0, right: 12.0),
+        child: Icon(
+          _subtitleOn
+              ? Icons.closed_caption
+              : Icons.closed_caption_off_outlined,
+          color: _subtitleOn ? Colors.white : Colors.grey[700],
+        ),
+      ),
+    );
+  }
+
+  void _onSubtitleTap() {
+    setState(() {
+      _subtitleOn = !_subtitleOn;
+    });
+  }
+
+  void _cancelAndRestartTimer() {
+    _hideTimer?.cancel();
+    _startHideTimer();
+
+    setState(() {
+      notifier.hideStuff = false;
+      _displayTapped = true;
+    });
+  }
+
+  Future<void> _initialize() async {
+    _subtitleOn =
+        chewieController.showSubtitles &&
+        (chewieController.subtitle?.isNotEmpty ?? false);
+    controller.addListener(_updateState);
+
+    _updateState();
+
+    if (controller.value.isPlaying || chewieController.autoPlay) {
+      _startHideTimer();
+    }
+
+    if (chewieController.showControlsOnInitialize) {
+      _initTimer = Timer(const Duration(milliseconds: 200), () {
+        setState(() {
+          notifier.hideStuff = false;
+        });
+      });
+    }
+  }
+
+  void _onExpandCollapse() {
+    setState(() {
+      notifier.hideStuff = true;
+
+      chewieController.toggleFullScreen();
+      _showAfterExpandCollapseTimer = Timer(
+        const Duration(milliseconds: 300),
+        () {
+          setState(() {
+            _cancelAndRestartTimer();
+          });
+        },
+      );
+    });
+  }
+
+  void _playPause() {
+    final bool isFinished =
+        (_latestValue.position >= _latestValue.duration) &&
+        _latestValue.duration.inSeconds > 0;
+
+    setState(() {
+      if (controller.value.isPlaying) {
+        notifier.hideStuff = false;
+        _hideTimer?.cancel();
+        controller.pause();
+      } else {
+        _cancelAndRestartTimer();
+
+        if (!controller.value.isInitialized) {
+          controller.initialize().then((_) {
+            controller.play();
+          });
+        } else {
+          if (isFinished) {
+            controller.seekTo(Duration.zero);
+          }
+          controller.play();
+        }
+      }
+    });
+  }
+
+  void _seekRelative(Duration relativeSeek) {
+    _cancelAndRestartTimer();
+    final position = _latestValue.position + relativeSeek;
+    final duration = _latestValue.duration;
+
+    if (position < Duration.zero) {
+      controller.seekTo(Duration.zero);
+    } else if (position > duration) {
+      controller.seekTo(duration);
+    } else {
+      controller.seekTo(position);
+    }
+  }
+
+  void _seekBackward() {
+    _seekRelative(const Duration(seconds: -10));
+  }
+
+  void _seekForward() {
+    _seekRelative(const Duration(seconds: 10));
+  }
+
+  void _startHideTimer() {
+    final hideControlsTimer =
+        chewieController.hideControlsTimer.isNegative
+            ? ChewieController.defaultHideControlsTimer
+            : chewieController.hideControlsTimer;
+    _hideTimer = Timer(hideControlsTimer, () {
+      setState(() {
+        notifier.hideStuff = true;
+      });
+    });
+  }
+
+  void _bufferingTimerTimeout() {
+    _displayBufferingIndicator = true;
+    if (mounted) {
+      setState(() {});
+    }
+  }
+
+  void _updateState() {
+    if (!mounted) return;
+
+    final bool buffering = getIsBuffering(controller);
+
+    // display the progress bar indicator only after the buffering delay if it has been set
+    if (chewieController.progressIndicatorDelay != null) {
+      if (buffering) {
+        _bufferingDisplayTimer ??= Timer(
+          chewieController.progressIndicatorDelay!,
+          _bufferingTimerTimeout,
+        );
+      } else {
+        _bufferingDisplayTimer?.cancel();
+        _bufferingDisplayTimer = null;
+        _displayBufferingIndicator = false;
+      }
+    } else {
+      _displayBufferingIndicator = buffering;
+    }
+
+    setState(() {
+      _latestValue = controller.value;
+      _subtitlesPosition = controller.value.position;
+    });
+  }
+
+  Widget _buildProgressBar() {
+    return Expanded(
+      child: MaterialVideoProgressBar(
+        controller,
+        onDragStart: () {
+          setState(() {
+            _dragging = true;
+          });
+
+          _hideTimer?.cancel();
+        },
+        onDragUpdate: () {
+          _hideTimer?.cancel();
+        },
+        onDragEnd: () {
+          setState(() {
+            _dragging = false;
+          });
+
+          _startHideTimer();
+        },
+        colors:
+            chewieController.materialProgressColors ??
+            ChewieProgressColors(
+              playedColor: Theme.of(context).colorScheme.secondary,
+              handleColor: Theme.of(context).colorScheme.secondary,
+              bufferedColor: Theme.of(
+                context,
+              ).colorScheme.surface.withOpacityCompat(0.5),
+              backgroundColor: Theme.of(
+                context,
+              ).disabledColor.withOpacityCompat(.5),
+            ),
+        draggableProgressBar: chewieController.draggableProgressBar,
+      ),
+    );
+  }
+}

+ 9 - 0
plugins/keyboard_android/android/src/main/kotlin/com/atmob/keyboard_android/keyboard/CustomKeyboardService.kt

@@ -11,6 +11,7 @@ import com.atmob.keyboard_android.mvvm.ViewModelManager
 import com.atmob.keyboard_android.mvvm.viewmodel.KeyboardViewModel
 import com.atmob.keyboard_android.util.ClipboardHelper
 import com.atmob.keyboard_android.util.KeyboardHolder
+import com.atmob.keyboard_android.util.LogUtil
 
 /**
  * 自定义键盘的输入法服务
@@ -43,6 +44,14 @@ class CustomKeyboardService : InputMethodLifecycleService(), ICustomKeyboardServ
 
     override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
         super.onStartInputView(info, restarting)
+        // 键盘弹起
+        LogUtil.d("CustomKeyboardService => onStartInputView()")
+    }
+
+    override fun onFinishInputView(finishingInput: Boolean) {
+        super.onFinishInputView(finishingInput)
+        // 键盘收下
+        LogUtil.d("CustomKeyboardService => onFinishInputView()")
     }
 
     override fun onDestroy() {

+ 3 - 3
pubspec.lock

@@ -241,13 +241,13 @@ packages:
     source: hosted
     version: "2.0.3"
   chewie:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: chewie
-      sha256: "0bf6f7692cb65f7b8f59a2a17025b9cbe8f75ab4251e66161a4fc86162475fb6"
+      sha256: "4d9554a8f87cc2dc6575dfd5ad20a4375015a29edd567fd6733febe6365e2566"
       url: "https://pub.dev"
     source: hosted
-    version: "1.11.0"
+    version: "1.11.3"
   cli_util:
     dependency: transitive
     description:

+ 2 - 0
pubspec.yaml

@@ -101,6 +101,8 @@ dependencies:
 
   # 视频播放
   video_player: ^2.9.5
+  # 基于video_player的封装库,能自定义UI
+  chewie: ^1.11.3
 
   #QR
   qr_flutter: ^4.1.0