From e5a1e9724e8b6c7d8978c43103badda1d4d74f0a Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 13 Jun 2024 18:15:49 +0300 Subject: [PATCH] chore: a workaround for loading a youtube video on desktop --- .../lib/embeds/video/editor/video_embed.dart | 1 - .../lib/embeds/widgets/video_app.dart | 44 +++++++----- .../lib/embeds/widgets/youtube_video_app.dart | 71 +++++++++++++++---- 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart b/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart index 45c37f2a..ad04fd9d 100644 --- a/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart +++ b/flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart @@ -53,7 +53,6 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder { alignment: alignment, child: VideoApp( videoUrl: videoUrl, - context: context, readOnly: readOnly, onVideoInit: configurations.onVideoInit, ), diff --git a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart index 334f64ca..f6dc64fa 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart @@ -13,14 +13,16 @@ import '../../flutter_quill_extensions.dart'; class VideoApp extends StatefulWidget { const VideoApp({ required this.videoUrl, - required this.context, required this.readOnly, + @Deprecated( + 'The context is no longer required and will be removed on future releases', + ) + BuildContext? context, super.key, this.onVideoInit, }); final String videoUrl; - final BuildContext context; final bool readOnly; final void Function(GlobalKey videoContainerKey)? onVideoInit; @@ -92,29 +94,33 @@ class VideoAppState extends State { : _controller.play(); }); }, - child: Stack(alignment: Alignment.center, children: [ - Center( - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - )), - _controller.value.isPlaying - ? const SizedBox.shrink() - : Container( - color: const Color(0xfff5f5f5), - child: const Icon( - Icons.play_arrow, - size: 60, - color: Colors.blueGrey, - )) - ]), + child: Stack( + alignment: Alignment.center, + children: [ + Center( + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + )), + _controller.value.isPlaying + ? const SizedBox.shrink() + : Container( + color: const Color(0xfff5f5f5), + child: const Icon( + Icons.play_arrow, + size: 60, + color: Colors.blueGrey, + ), + ) + ], + ), ), ); } @override void dispose() { - super.dispose(); _controller.dispose(); + super.dispose(); } } diff --git a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart index 57b0c3d6..0125a3b7 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart @@ -1,9 +1,13 @@ import 'package:flutter/gestures.dart' show TapGestureRecognizer; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/flutter_quill.dart' show DefaultStyles; -import 'package:url_launcher/url_launcher.dart' show launchUrl; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'video_app.dart'; + class YoutubeVideoApp extends StatefulWidget { const YoutubeVideoApp({ required this.videoUrl, @@ -20,11 +24,19 @@ class YoutubeVideoApp extends StatefulWidget { class YoutubeVideoAppState extends State { YoutubePlayerController? _youtubeController; + late String? _videoId; + + /// On some platforms such as desktop, Webview is not supported yet + /// as a result the youtube video player package is not supported too + /// this future will be not null and fetch the video url to load it using + /// [VideoApp] + Future? _loadYoutubeVideoWithVideoPlayer; @override void initState() { super.initState(); - final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); + _videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); + final videoId = _videoId; if (videoId != null) { _youtubeController = YoutubePlayerController( initialVideoId: videoId, @@ -32,9 +44,33 @@ class YoutubeVideoAppState extends State { autoPlay: false, ), ); + if (isDesktop(supportWeb: false)) { + _loadYoutubeVideoWithVideoPlayer = + _loadYoutubeVideoWithVideoPlayerByVideoUrl(); + } } } + Future _loadYoutubeVideoWithVideoPlayerByVideoUrl() async { + final youtubeExplode = YoutubeExplode(); + final manifest = + await youtubeExplode.videos.streamsClient.getManifest(_videoId); + final streamInfo = manifest.muxed.withHighestBitrate(); + final downloadUrl = streamInfo.url; + return downloadUrl.toString(); + } + + Widget _videoLink({required DefaultStyles defaultStyles}) { + return RichText( + text: TextSpan( + text: widget.videoUrl, + style: defaultStyles.link, + recognizer: TapGestureRecognizer() + ..onTap = () => launchUrlString(widget.videoUrl), + ), + ); + } + @override Widget build(BuildContext context) { final defaultStyles = DefaultStyles.getInstance(context); @@ -42,16 +78,7 @@ class YoutubeVideoAppState extends State { if (youtubeController == null) { if (widget.readOnly) { - return RichText( - text: TextSpan( - text: widget.videoUrl, - style: defaultStyles.link, - recognizer: TapGestureRecognizer() - ..onTap = () => launchUrl( - Uri.parse(widget.videoUrl), - ), - ), - ); + return _videoLink(defaultStyles: defaultStyles); } return RichText( @@ -59,6 +86,24 @@ class YoutubeVideoAppState extends State { ); } + // Workaround as YoutubePlayer doesn't support Desktop + if (_loadYoutubeVideoWithVideoPlayer != null) { + return FutureBuilder( + future: _loadYoutubeVideoWithVideoPlayer, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator.adaptive()); + } + if (snapshot.hasError) { + return _videoLink(defaultStyles: defaultStyles); + } + return VideoApp( + videoUrl: snapshot.requireData, + readOnly: widget.readOnly, + ); + }, + ); + } return YoutubePlayerBuilder( player: YoutubePlayer( controller: youtubeController,