import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:chewie/chewie.dart'; import 'package:matrix/matrix.dart'; import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; import 'package:video_player/video_player.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/blur_hash.dart'; import '../../../utils/error_reporter.dart'; import '../../widgets/mxc_image.dart'; class EventVideoPlayer extends StatefulWidget { final Event event; const EventVideoPlayer( this.event, { super.key, }); @override EventVideoPlayerState createState() => EventVideoPlayerState(); } class EventVideoPlayerState extends State { ChewieController? _chewieController; VideoPlayerController? _videoPlayerController; // The video_player package only doesn't support Windows and Linux. final _supportsVideoPlayer = !PlatformInfos.isWindows && !PlatformInfos.isLinux; void _downloadAction() async { if (!_supportsVideoPlayer) { widget.event.saveFile(context); return; } try { final videoFile = await widget.event.downloadAndDecryptAttachment(); // Dispose the controllers if we already have them. _disposeControllers(); late VideoPlayerController videoPlayerController; // Create the VideoPlayerController from the contents of videoFile. if (kIsWeb) { final blob = html.Blob([videoFile.bytes]); final networkUri = Uri.parse(html.Url.createObjectUrlFromBlob(blob)); videoPlayerController = VideoPlayerController.networkUrl(networkUri); } else { final tempDir = await getTemporaryDirectory(); final fileName = Uri.encodeComponent( widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last, ); final file = File('${tempDir.path}/${fileName}_${videoFile.name}'); if (await file.exists() == false) { await file.writeAsBytes(videoFile.bytes); } videoPlayerController = VideoPlayerController.file(file); } _videoPlayerController = videoPlayerController; await videoPlayerController.initialize(); final infoMap = widget.event.content.tryGetMap('info'); final videoWidth = infoMap?.tryGet('w') ?? 400; final videoHeight = infoMap?.tryGet('h') ?? 300; // Create a ChewieController on top. setState(() { _chewieController = ChewieController( videoPlayerController: videoPlayerController, showControlsOnInitialize: false, autoPlay: true, autoInitialize: true, looping: true, aspectRatio: videoHeight == 0 ? null : videoWidth / videoHeight, ); }); } on IOException catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(e.toLocalizedString(context)), ), ); } catch (e, s) { ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s); } } void _disposeControllers() { _chewieController?.dispose(); _videoPlayerController?.dispose(); _chewieController = null; _videoPlayerController = null; } @override void dispose() { _disposeControllers(); super.dispose(); } @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _downloadAction(); }); } static const String fallbackBlurHash = 'L5H2EC=PM+yV0g-mq.wG9c010J}I'; @override Widget build(BuildContext context) { final hasThumbnail = widget.event.hasThumbnail; final blurHash = (widget.event.infoMap as Map) .tryGet('xyz.amorgan.blurhash') ?? fallbackBlurHash; final infoMap = widget.event.content.tryGetMap('info'); final videoWidth = infoMap?.tryGet('w') ?? 400; final videoHeight = infoMap?.tryGet('h') ?? 300; final height = MediaQuery.of(context).size.height - 52; final width = videoWidth * (height / videoHeight); final chewieController = _chewieController; return chewieController != null ? Center( child: SizedBox( width: width, height: height, child: Chewie(controller: chewieController), ), ) : Stack( children: [ Center( child: Hero( tag: widget.event.eventId, child: hasThumbnail ? MxcImage( event: widget.event, isThumbnail: true, width: width, height: height, fit: BoxFit.cover, placeholder: (context) => BlurHash( blurhash: blurHash, width: width, height: height, fit: BoxFit.cover, ), ) : BlurHash( blurhash: blurHash, width: width, height: height, ), ), ), const Center(child: CircularProgressIndicator.adaptive()), ], ); } }