diff --git a/images/video_tape.png b/images/video_tape.png new file mode 100644 index 00000000..f9714116 Binary files /dev/null and b/images/video_tape.png differ diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tencent_cloud_chat_message_viewer/tencent_cloud_chat_message_videoplayer.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tencent_cloud_chat_message_viewer/tencent_cloud_chat_message_videoplayer.dart new file mode 100644 index 00000000..400988f6 --- /dev/null +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tencent_cloud_chat_message_viewer/tencent_cloud_chat_message_videoplayer.dart @@ -0,0 +1,197 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:better_player_plus/better_player_plus.dart'; +import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; + +class TencentCloudChatMessageVideoPlayer extends StatefulWidget { + final V2TimMessage message; + final bool controller; + final bool isSending; + + const TencentCloudChatMessageVideoPlayer({ + super.key, + required this.message, + required this.controller, + required this.isSending, + }); + + @override + State createState() => TencentCloudChatMessageVideoPlayerState(); +} + +enum CurrentVideoType { + online, + local, +} + +class CurrentVideoInfo { + final String path; + final CurrentVideoType type; + final double aspectRatio; + + CurrentVideoInfo({ + required this.path, + required this.type, + required this.aspectRatio, + }); +} + +class TencentCloudChatMessageVideoPlayerState extends State { + final String _tag = "TencentCloudChatMessageVideoPlayer"; + + BetterPlayerController? _betterPlayerController; + + @override + void initState() { + super.initState(); + _initializePlayer(); + } + + Future _initializePlayer() async { + try { + final info = await getMessageInfo(); + if (info != null && mounted) { + BetterPlayerDataSource dataSource; + if (info.type == CurrentVideoType.online) { + dataSource = BetterPlayerDataSource( + BetterPlayerDataSourceType.network, + info.path, + ); + } else { + dataSource = BetterPlayerDataSource( + BetterPlayerDataSourceType.file, + info.path, + ); + } + + final betterPlayerConfiguration = BetterPlayerConfiguration( + aspectRatio: info.aspectRatio, + fit: BoxFit.contain, + autoPlay: true, + allowedScreenSleep: false, + fullScreenByDefault: false, + controlsConfiguration: const BetterPlayerControlsConfiguration( + enableFullscreen: false, + enablePlayPause: true, + enableProgressBar: true, + enableProgressText: true, + showControlsOnInitialize: false, + enableMute:false, + enableOverflowMenu:false, + enableSkips:false, + ), + ); + + _betterPlayerController = BetterPlayerController( + betterPlayerConfiguration, + betterPlayerDataSource: dataSource, + ); + + if (mounted) { + setState(() { + }); + } + } + } catch (e) { + debugPrint("Video initialization error: $e"); + } + } + + @override + void dispose() { + _betterPlayerController?.dispose(); + super.dispose(); + } + + Future getMessageInfo() async { + if (widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_VIDEO) { + double aspectRatio = (9 / 16); + + if (widget.isSending) { + var lp = widget.message.videoElem!.videoPath ?? ""; + if (lp.isNotEmpty) { + console("view sending message video path"); + if (File(lp).existsSync() && !kIsWeb) { + return CurrentVideoInfo(path: lp, type: CurrentVideoType.local, aspectRatio: aspectRatio); + } + } + } + + if (widget.message.videoElem!.snapshotWidth != null && widget.message.videoElem!.snapshotHeight != null) { + if (widget.message.videoElem!.snapshotHeight != 0) { + aspectRatio = (widget.message.videoElem!.snapshotWidth!) / (widget.message.videoElem!.snapshotHeight!); + } + } + + if (TencentUtils.checkString(widget.message.videoElem!.videoPath) != null) { + // 先查本地发送的视频地址 + if (File(widget.message.videoElem!.videoPath!).existsSync()) { + console("video: local video path exists"); + return CurrentVideoInfo(path: widget.message.videoElem!.videoPath!, type: CurrentVideoType.local, aspectRatio: aspectRatio); + } + } else if (TencentUtils.checkString(widget.message.videoElem!.localVideoUrl) != null) { + // 再查本地下载的视频地址 + if (File(widget.message.videoElem!.localVideoUrl!).existsSync()) { + console("video: local url exists"); + return CurrentVideoInfo(path: widget.message.videoElem!.localVideoUrl!, type: CurrentVideoType.local, aspectRatio: aspectRatio); + } + } else { + // 最后再查在线地址(todo 使用 getMessageOnlineUrl 查询) + if (widget.message.videoElem != null) { + if (widget.message.videoElem!.snapshotUrl != null) { + console("video: online url ${widget.message.videoElem!.videoUrl}"); + return CurrentVideoInfo( + path: widget.message.videoElem!.videoUrl!, + type: CurrentVideoType.online, + aspectRatio: aspectRatio, + ); + } + } + if (!kIsWeb) { + V2TimValueCallback urlres = await TencentImSDKPlugin.v2TIMManager.getMessageManager().getMessageOnlineUrl(msgID: widget.message.msgID ?? ""); + if (urlres.data != null) { + if (urlres.data?.videoElem != null) { + if (TencentUtils.checkString(urlres.data?.videoElem?.videoUrl) != null) { + console("view video online url ${urlres.data?.videoElem?.videoUrl}"); + return CurrentVideoInfo(path: urlres.data!.videoElem!.videoUrl!, type: CurrentVideoType.online, aspectRatio: aspectRatio); + } + } + } + } + } + } else { + console("The component received a non-video message parameter. please check"); + } + console("has no view video source. please check"); + return null; + } + + console(String log) { + print("$_tag, $log"); + } + + @override + Widget build(BuildContext context) { + if (widget.message.hasRiskContent == true) { + return const Center( + child: Text( + "Risk Video", + style: TextStyle(color: Colors.white), + ), + ); + } + + if (_betterPlayerController == null) { + return Container(); + } + + return AspectRatio( + aspectRatio: _betterPlayerController!.videoPlayerController?.value.aspectRatio ?? 9 / 16, + child: BetterPlayer( + controller: _betterPlayerController!, + ), + ); + } +} diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/intl_camer_picker.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/intl_camer_picker.dart deleted file mode 100644 index d876555a..00000000 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/intl_camer_picker.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:tencent_im_base/tencent_im_base.dart'; -import 'package:wechat_camera_picker/wechat_camera_picker.dart'; - -class IntlCameraPickerTextDelegate extends CameraPickerTextDelegate { - /// Confirm string for the confirm button. - /// 确认按钮的字段 - @override - String get confirm => TIM_t('确认'); - - /// Tips string above the shooting button before shooting. - /// 拍摄前确认按钮上方的提示文字 - @override - String get shootingTips => TIM_t('轻触拍照,长按摄像'); - - /// Tips string above the shooting button before shooting. - /// 拍摄前确认按钮上方的提示文字 - @override - String get shootingWithRecordingTips => TIM_t('轻触拍照,长按摄像'); - - /// Load failed string for item. - /// 资源加载失败时的字段 - @override - String get loadFailed => TIM_t('加载失败'); - - /// Default loading string for the dialog. - /// 加载中弹窗的默认文字 - @override - String get loading => TIM_t('加载中…'); - - /// Saving string for the dialog. - /// 保存中弹窗的默认文字 - @override - String get saving => TIM_t('保存中…'); - - /// Semantics fields. - /// - /// Fields below are only for semantics usage. For customizable these fields, - /// head over to [EnglishCameraPickerTextDelegate] for better understanding. - @override - String get sActionManuallyFocusHint => TIM_t('手动聚焦'); - - @override - String get sActionPreviewHint => TIM_t('预览'); - - @override - String get sActionRecordHint => TIM_t('录像'); - - @override - String get sActionShootHint => TIM_t('拍照'); - - @override - String get sActionShootingButtonTooltip => TIM_t('拍照按钮'); - - @override - String get sActionStopRecordingHint => TIM_t('停止录像'); - - @override - String sCameraLensDirectionLabel(CameraLensDirection value) { - switch (value) { - case CameraLensDirection.front: - return TIM_t('前置'); - case CameraLensDirection.back: - return TIM_t('后置'); - case CameraLensDirection.external: - return TIM_t('外置'); - } - } - - @override - String? sCameraPreviewLabel(CameraLensDirection? value) { - if (value == null) { - return null; - } - final option1 = sCameraLensDirectionLabel(value); - return TIM_t_para("{{option1}} 画面预览", "$option1 画面预览")(option1: option1); - } - - @override - String sFlashModeLabel(FlashMode mode) { - final String _modeString; - switch (mode) { - case FlashMode.off: - _modeString = TIM_t('关闭'); - break; - case FlashMode.auto: - _modeString = TIM_t('自动'); - break; - case FlashMode.always: - _modeString = TIM_t('拍照时闪光'); - break; - case FlashMode.torch: - _modeString = TIM_t('始终闪光'); - break; - } - final option2 = _modeString; - return TIM_t_para("闪光模式: {{option2}}", "闪光模式: $option2")(option2: option2); - } - - @override - String sSwitchCameraLensDirectionLabel(CameraLensDirection value) { - final option3 = sCameraLensDirectionLabel(value); - return TIM_t_para("切换至 {{option3}} 摄像头", "切换至 $option3 摄像头")( - option3: option3); - } -} diff --git a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart index ae8cbaa4..06bcd0e2 100644 --- a/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart +++ b/lib/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_more_panel.dart @@ -1,6 +1,7 @@ // ignore_for_file: unused_field, avoid_print, unused_import import 'dart:io'; +import 'package:better_player_plus/better_player_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart'; import 'package:flutter/foundation.dart'; @@ -8,11 +9,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:just_audio/just_audio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart'; import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_call_invite_list.dart'; -import 'package:wechat_camera_picker/wechat_camera_picker.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart'; import 'package:tencent_cloud_chat_uikit/business_logic/view_models/tui_chat_global_model.dart'; @@ -22,7 +23,7 @@ import 'package:tencent_cloud_chat_uikit/data_services/services_locatar.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/message.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/permission.dart'; import 'package:tencent_cloud_chat_uikit/ui/utils/platform.dart'; -import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/intl_camer_picker.dart'; +import 'package:video_player/video_player.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart'; @@ -65,8 +66,7 @@ class MorePanelItem { final Widget icon; final Function(BuildContext context)? onTap; - MorePanelItem( - {this.onTap, required this.icon, required this.id, required this.title}); + MorePanelItem({this.onTap, required this.icon, required this.id, required this.title}); } class MorePanel extends StatefulWidget { @@ -91,8 +91,7 @@ class MorePanel extends StatefulWidget { class _MorePanelState extends TIMUIKitState { final ImagePicker _picker = ImagePicker(); - final TUISelfInfoViewModel _selfInfoViewModel = - serviceLocator(); + final TUISelfInfoViewModel _selfInfoViewModel = serviceLocator(); Uint8List? fileContent; String? fileName; File? tempFile; @@ -102,6 +101,9 @@ class _MorePanelState extends TIMUIKitState { final ScrollController _scrollController = ScrollController(); final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + // 创建AudioPlayer对象 + late BetterPlayerController _betterPlayerController; + @override void initState() { super.initState(); @@ -111,6 +113,7 @@ class _MorePanelState extends TIMUIKitState { isInstallCallkit = value; }); }); + _betterPlayerController = BetterPlayerController(const BetterPlayerConfiguration()); } } @@ -119,18 +122,17 @@ class _MorePanelState extends TIMUIKitState { return [ if (PlatformUtils().isMobile) MorePanelItem( - id: "screen", - title: TIM_t("拍摄"), + id: "take_photo", + title: TIM_t("拍照"), onTap: (c) { - _onFeatureTap("screen", c, model, theme); + _onFeatureTap("take_photo", c, model, theme); }, icon: Container( height: 64, width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/screen.svg", package: 'tencent_cloud_chat_uikit', @@ -138,6 +140,26 @@ class _MorePanelState extends TIMUIKitState { width: 64, ), )), + if (PlatformUtils().isMobile) + MorePanelItem( + id: "video_tape", + title: TIM_t("录像"), + onTap: (c) { + _onFeatureTap("video_tape", c, model, theme); + }, + icon: Container( + height: 64, + width: 64, + margin: const EdgeInsets.only(bottom: 4), + decoration: const BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), + child: Image.asset( + "images/video_tape.png", + package: 'tencent_cloud_chat_uikit', + height: 64, + width: 64, + ), + )), if (!PlatformUtils().isWeb) MorePanelItem( id: "photo", @@ -155,8 +177,7 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/photo.svg", package: 'tencent_cloud_chat_uikit', @@ -181,8 +202,7 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/photo.svg", package: 'tencent_cloud_chat_uikit', @@ -207,10 +227,8 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), - child: - Icon(Icons.video_file, color: hexToColor("5c6168"), size: 26), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), + child: Icon(Icons.video_file, color: hexToColor("5c6168"), size: 26), )), MorePanelItem( id: "file", @@ -228,8 +246,7 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/file.svg", package: 'tencent_cloud_chat_uikit', @@ -254,8 +271,7 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/video-call.svg", package: 'tencent_cloud_chat_uikit', @@ -280,8 +296,7 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(5))), + color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(5))), child: SvgPicture.asset( "images/voice-call.svg", package: 'tencent_cloud_chat_uikit', @@ -291,7 +306,11 @@ class _MorePanelState extends TIMUIKitState { )), if (config.extraAction != null) ...?config.extraAction, ].where((element) { - if (element.id == "screen") { + if (element.id == "take_photo") { + return config.showCameraAction; + } + + if (element.id == "video_tape") { return config.showCameraAction; } @@ -320,28 +339,22 @@ class _MorePanelState extends TIMUIKitState { }).toList(); } - _sendVideoMessage(AssetEntity asset, int size, TUIChatSeparateViewModel model) async { + _sendVideoMessage( + String originFilePath, int duration, int size, TUIChatSeparateViewModel model) async { if (size >= MorePanelConfig.VIDEO_MAX_SIZE) { - onTIMCallback(TIMCallback( - type: TIMCallbackType.INFO, - infoRecommendText: TIM_t("文件大小超出了限制"))); + onTIMCallback(TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("文件大小超出了限制"))); return; } final plugin = FcNativeVideoThumbnail(); - final originFile = await asset.originFile; - final duration = asset.videoDuration.inSeconds; - final filePath = originFile!.path; final convID = widget.conversationID; final convType = widget.conversationType; - String tempPath = (await getTemporaryDirectory()).path + - p.basename(originFile.path) + - ".jpeg"; + String tempPath = (await getTemporaryDirectory()).path + p.basename(originFilePath) + ".jpeg"; await plugin.getVideoThumbnail( - srcFile: originFile.path, + srcFile: originFilePath, destFile: tempPath, format: 'jpeg', width: 1280, @@ -350,12 +363,15 @@ class _MorePanelState extends TIMUIKitState { ); MessageUtils.handleMessageError( model.sendVideoMessage( - videoPath: filePath, + videoPath: originFilePath, duration: duration, snapshotPath: tempPath, convID: convID, convType: convType), context); + + // 释放资源 + _betterPlayerController.dispose(); } _sendImageMessage(TUIChatSeparateViewModel model, TUITheme theme) async { @@ -415,44 +431,38 @@ class _MorePanelState extends TIMUIKitState { if (type == AssetType.image) { if (size >= MorePanelConfig.IMAGE_MAX_SIZE) { onTIMCallback(TIMCallback( - type: TIMCallbackType.INFO, - infoRecommendText: TIM_t("文件大小超出了限制"))); + type: TIMCallbackType.INFO, infoRecommendText: TIM_t("文件大小超出了限制"))); return; } MessageUtils.handleMessageError( - model.sendImageMessage( - imagePath: filePath, - convID: convID, - convType: convType), + model.sendImageMessage(imagePath: filePath, convID: convID, convType: convType), context); } if (type == AssetType.video) { - _sendVideoMessage(asset, size, model); + final originFile = await asset.originFile; + _sendVideoMessage(originFile!.path, asset.videoDuration.inSeconds, size, model); } } } } } else { - FilePickerResult? result = - await FilePicker.platform.pickFiles(type: FileType.media); + FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.media); if (result != null && result.files.isNotEmpty) { File file = File(result.files.single.path!); final String savePath = file.path; - final String type = TencentUtils.getFileType( - savePath.split(".")[savePath.split(".").length - 1]) - .split("/")[0]; + final String type = + TencentUtils.getFileType(savePath.split(".")[savePath.split(".").length - 1]) + .split("/")[0]; if (type == "image") { MessageUtils.handleMessageError( - model.sendImageMessage( - imagePath: savePath, convID: convID, convType: convType), + model.sendImageMessage(imagePath: savePath, convID: convID, convType: convType), context); } else if (type == "video") { MessageUtils.handleMessageError( - model.sendVideoMessage( - videoPath: savePath, convID: convID, convType: convType), + model.sendVideoMessage(videoPath: savePath, convID: convID, convType: convType), context); } } else { @@ -466,8 +476,9 @@ class _MorePanelState extends TIMUIKitState { _sendImageFromCamera( TUIChatSeparateViewModel model, - TUITheme theme, - ) async { + TUITheme theme, { + required bool isVideo, + }) async { try { if (!await Permissions.checkPermission( context, @@ -484,34 +495,41 @@ class _MorePanelState extends TIMUIKitState { final convID = widget.conversationID; final convType = widget.conversationType; - final pickedFile = await CameraPicker.pickFromCamera(context, - pickerConfig: CameraPickerConfig( - enableRecording: true, - textDelegate: IntlCameraPickerTextDelegate())); - final originFile = await pickedFile?.originFile; - if (originFile != null) { - final type = pickedFile!.type; - final size = await originFile!.length(); - if (type == AssetType.image) { - if (size >= MorePanelConfig.IMAGE_MAX_SIZE) { - onTIMCallback(TIMCallback( - type: TIMCallbackType.INFO, - infoRecommendText: TIM_t("文件大小超出了限制"))); - return; - } - - MessageUtils.handleMessageError( - model.sendImageMessage( - imagePath: originFile.path, - convID: convID, - convType: convType), - context); - } - if (type == AssetType.video) { - _sendVideoMessage(pickedFile, size, model); + final ImagePicker picker = ImagePicker(); + XFile? originFile; + if (isVideo) { + originFile = await picker.pickVideo(source: ImageSource.camera); + } else { + originFile = await picker.pickImage(source: ImageSource.camera); + } + final size = await originFile!.length(); + if (!isVideo) { + if (size >= MorePanelConfig.IMAGE_MAX_SIZE) { + onTIMCallback( + TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("文件大小超出了限制"))); + return; } + + MessageUtils.handleMessageError( + model.sendImageMessage(imagePath: originFile.path, convID: convID, convType: convType), + context); } else { - // Toast.showToast(ToastType.fail, TIM_t("图片不能为空"), context); + // 监听视频准备完成事件 + _betterPlayerController.addEventsListener((event) { + if (event.betterPlayerEventType == BetterPlayerEventType.initialized) { + // 获取视频时长(单位:秒) + int durationInSeconds = _betterPlayerController.videoPlayerController?.value.duration?.inSeconds ?? 0; + _sendVideoMessage(originFile!.path, durationInSeconds, size, model); + } + }); + + // 加载视频源 + _betterPlayerController.setupDataSource( + BetterPlayerDataSource( + BetterPlayerDataSourceType.file, + originFile.path, // 替换为你的视频 URL + ), + ); } } catch (error) { outputLogger.i("err: $error"); @@ -527,9 +545,8 @@ class _MorePanelState extends TIMUIKitState { fileContent = imageContent; html.Node? inputElem; - inputElem = html.document - .getElementById("__image_picker_web-file-input") - ?.querySelector("input"); + inputElem = + html.document.getElementById("__image_picker_web-file-input")?.querySelector("input"); final convID = widget.conversationID; final convType = widget.conversationType; MessageUtils.handleMessageError( @@ -561,9 +578,8 @@ class _MorePanelState extends TIMUIKitState { } html.Node? inputElem; - inputElem = html.document - .getElementById("__image_picker_web-file-input") - ?.querySelector("input"); + inputElem = + html.document.getElementById("__image_picker_web-file-input")?.querySelector("input"); final convID = widget.conversationID; final convType = widget.conversationType; MessageUtils.handleMessageError( @@ -589,31 +605,25 @@ class _MorePanelState extends TIMUIKitState { if (result != null && result.files.isNotEmpty) { if (PlatformUtils().isWeb) { html.Node? inputElem; - inputElem = html.document - .getElementById("__file_picker_web-file-input") - ?.querySelector("input"); + inputElem = + html.document.getElementById("__file_picker_web-file-input")?.querySelector("input"); fileName = result.files.single.name; MessageUtils.handleMessageError( model.sendFileMessage( - inputElement: inputElem, - fileName: fileName, - convID: convID, - convType: convType), + inputElement: inputElem, fileName: fileName, convID: convID, convType: convType), context); return; } String? option2 = result.files.single.path ?? ""; - outputLogger - .i(TIM_t_para("选择成功{{option2}}", "选择成功$option2")(option2: option2)); + outputLogger.i(TIM_t_para("选择成功{{option2}}", "选择成功$option2")(option2: option2)); File file = File(result.files.single.path!); final int size = file.lengthSync(); if (size >= MorePanelConfig.FILE_MAX_SIZE) { - onTIMCallback(TIMCallback( - type: TIMCallbackType.INFO, - infoRecommendText: TIM_t("文件大小超出了限制"))); + onTIMCallback( + TIMCallback(type: TIMCallbackType.INFO, infoRecommendText: TIM_t("文件大小超出了限制"))); return; } @@ -621,10 +631,7 @@ class _MorePanelState extends TIMUIKitState { MessageUtils.handleMessageError( model.sendFileMessage( - filePath: savePath, - size: size, - convID: convID, - convType: convType), + filePath: savePath, size: size, convID: convID, convType: convType), context); } else { throw TypeError(); @@ -644,8 +651,11 @@ class _MorePanelState extends TIMUIKitState { case "photo": _sendImageMessage(model, theme); break; - case "screen": - _sendImageFromCamera(model, theme); + case "take_photo": + _sendImageFromCamera(model, theme, isVideo: false); + break; + case "video_tape": + _sendImageFromCamera(model, theme, isVideo: true); break; case "file": _sendFile(model, theme); @@ -673,14 +683,14 @@ class _MorePanelState extends TIMUIKitState { bool hasMicrophonePermission = false; if (type == TYPE_VIDEO) { hasCameraPermission = await Permissions.checkPermission(context, Permission.camera.value); - hasMicrophonePermission = await Permissions.checkPermission( - context, Permission.microphone.value); + hasMicrophonePermission = + await Permissions.checkPermission(context, Permission.microphone.value); if (!hasCameraPermission || !hasMicrophonePermission) { return; } } else { - hasMicrophonePermission = await Permissions.checkPermission( - context, Permission.microphone.value); + hasMicrophonePermission = + await Permissions.checkPermission(context, Permission.microphone.value); if (!hasMicrophonePermission) { return; } @@ -702,9 +712,7 @@ class _MorePanelState extends TIMUIKitState { _tUICore.callService(TUICALLKIT_SERVICE_NAME, METHOD_NAME_CALL, { PARAM_NAME_TYPE: type, PARAM_NAME_USERIDS: inviteMember, - PARAM_NAME_GROUPID: widget.conversationType == ConvType.group - ? widget.conversationID - : "" + PARAM_NAME_GROUPID: widget.conversationType == ConvType.group ? widget.conversationID : "" }); } } else { @@ -719,8 +727,7 @@ class _MorePanelState extends TIMUIKitState { @override Widget tuiBuild(BuildContext context, TUIKitBuildValue value) { final TUITheme theme = value.theme; - final TUIChatSeparateViewModel model = - Provider.of(context); + final TUIChatSeparateViewModel model = Provider.of(context); final screenWidth = MediaQuery.of(context).size.width; return Container( height: 248, @@ -758,14 +765,12 @@ class _MorePanelState extends TIMUIKitState { width: 64, margin: const EdgeInsets.only(bottom: 4), decoration: const BoxDecoration( - borderRadius: - BorderRadius.all(Radius.circular(5))), + borderRadius: BorderRadius.all(Radius.circular(5))), child: item.icon, ), Text( item.title, - style: TextStyle( - fontSize: 12, color: theme.darkTextColor), + style: TextStyle(fontSize: 12, color: theme.darkTextColor), ) ], ), diff --git a/lib/ui/widgets/video_screen.dart b/lib/ui/widgets/video_screen.dart index 6aa39cea..04ee2bf1 100644 --- a/lib/ui/widgets/video_screen.dart +++ b/lib/ui/widgets/video_screen.dart @@ -20,6 +20,8 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/video_custom_control.dart'; import 'package:universal_html/html.dart' as html; import 'package:video_player/video_player.dart'; +import '../views/TIMUIKitChat/TIMUIKitMessageItem/tencent_cloud_chat_message_viewer/tencent_cloud_chat_message_videoplayer.dart'; + class VideoScreen extends StatefulWidget { const VideoScreen({required this.message, required this.heroTag, required this.videoElement, Key? key}) : super(key: key); @@ -33,11 +35,8 @@ class VideoScreen extends StatefulWidget { } class _VideoScreenState extends TIMUIKitState { - late VideoPlayerController videoPlayerController; - late ChewieController chewieController; GlobalKey slidePagekey = GlobalKey(); final TUIChatGlobalModel model = serviceLocator(); - bool isInit = false; @override initState() { @@ -266,9 +265,6 @@ class _VideoScreenState extends TIMUIKitState { return await _saveVideo(); })); setState(() { - videoPlayerController = player; - chewieController = controller; - isInit = true; }); }); } @@ -287,10 +283,6 @@ class _VideoScreenState extends TIMUIKitState { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); - if (isInit) { - videoPlayerController.dispose(); - chewieController.dispose(); - } super.dispose(); } @@ -327,31 +319,10 @@ class _VideoScreenState extends TIMUIKitState { } return null; }, - child: ExtendedImageSlidePageHandler( - child: Container( - color: Colors.black, - child: isInit - ? Chewie( - controller: chewieController, - ) - : const Center(child: CircularProgressIndicator(color: Colors.white))), - heroBuilderForSlidingPage: (Widget result) { - return Hero( - tag: widget.heroTag, - child: result, - flightShuttleBuilder: (BuildContext flightContext, - Animation animation, - HeroFlightDirection flightDirection, - BuildContext fromHeroContext, - BuildContext toHeroContext) { - final Hero hero = (flightDirection == HeroFlightDirection.pop - ? fromHeroContext.widget - : toHeroContext.widget) as Hero; - - return hero.child; - }, - ); - }, + child: TencentCloudChatMessageVideoPlayer( + message: widget.message, + controller: true, + isSending: widget.message.status == MessageStatus.V2TIM_MSG_STATUS_SENDING, )), )); })); diff --git a/pubspec.yaml b/pubspec.yaml index 76827921..bc3202c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,10 +25,11 @@ dependencies: get_it: ^7.2.0 dotted_border: ^2.0.0+2 flutter_svg: ^2.0.6 - image_picker: ^0.8.5+3 + image_picker: ^0.8.9 file_picker: ^5.3.0 tencent_super_tooltip: ^0.0.1 - video_player: ^2.9.0 + video_player: ^2.9.2 + better_player_plus: ^1.0.5 chewie: ^1.8.5 flutter_slidable_plus_plus: ^0.1.0 flutter_plugin_record_plus: ^0.0.19 @@ -41,7 +42,7 @@ dependencies: shared_preferences: ^2.0.13 scroll_to_index: ^2.1.1 wechat_assets_picker: ^9.3.3 - wechat_camera_picker: ^4.3.4 +# wechat_camera_picker: ^4.3.4 flutter_easyrefresh: ^2.2.1 extended_image: ^9.0.0 extended_text_field: ^16.0.0