feat: add YoutubeVideoSupportMode

pull/1916/head
Ellet 10 months ago
parent e5d8dbd322
commit 661dbf8c16
  1. 11
      example/lib/screens/quill/my_quill_editor.dart
  2. 1
      flutter_quill_extensions/lib/embeds/video/editor/video_embed.dart
  3. 126
      flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
  4. 7
      flutter_quill_extensions/lib/models/config/video/editor/video_configurations.dart
  5. 17
      flutter_quill_extensions/lib/models/config/video/editor/youtube_video_support_mode.dart

@ -4,11 +4,13 @@ import 'package:cached_network_image/cached_network_image.dart'
show CachedNetworkImageProvider;
import 'package:desktop_drop/desktop_drop.dart' show DropTarget;
import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb;
import 'package:flutter_quill/extensions.dart'
show isAndroid, isDesktop, isIOS, isWeb;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/embeds/widgets/image.dart'
show getImageProviderByImageSource, imageFileExtensions;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:flutter_quill_extensions/models/config/video/editor/youtube_video_support_mode.dart';
import 'package:path/path.dart' as path;
import '../../extensions/scaffold_messenger.dart';
@ -128,6 +130,13 @@ class MyQuillEditor extends StatelessWidget {
);
},
),
videoEmbedConfigurations: QuillEditorVideoEmbedConfigurations(
// Loading YouTube vidoes on Desktop is not supported yet
// when using iframe platform view
youtubeVideoSupportMode: isDesktop(supportWeb: false)
? YoutubeVideoSupportMode.customPlayerWithDownloadUrl
: YoutubeVideoSupportMode.iframeView,
),
)),
TimeStampEmbedBuilderWidget(),
],

@ -37,6 +37,7 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder {
return YoutubeVideoApp(
videoUrl: videoUrl,
readOnly: readOnly,
youtubeVideoSupportMode: configurations.youtubeVideoSupportMode,
);
}
final ((elementSize), margin, alignment) = getElementAttributes(

@ -1,53 +1,65 @@
import 'package:flutter/gestures.dart' show TapGestureRecognizer;
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_string.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
import 'package:youtube_player_flutter/youtube_player_flutter.dart';
import '../../models/config/video/editor/youtube_video_support_mode.dart';
import 'video_app.dart';
class YoutubeVideoApp extends StatefulWidget {
const YoutubeVideoApp({
required this.videoUrl,
required this.readOnly,
required this.youtubeVideoSupportMode,
super.key,
});
final String videoUrl;
final bool readOnly;
final YoutubeVideoSupportMode youtubeVideoSupportMode;
@override
YoutubeVideoAppState createState() => YoutubeVideoAppState();
}
class YoutubeVideoAppState extends State<YoutubeVideoApp> {
YoutubePlayerController? _youtubeController;
late String? _videoId;
YoutubePlayerController? _youtubeIframeController;
/// 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<String>? _loadYoutubeVideoWithVideoPlayer;
Future<String>? _loadYoutubeVideoByDownloadUrlFuture;
/// Null if the video URL is not a YouTube video
String? get _videoId {
return YoutubePlayer.convertUrlToId(widget.videoUrl);
}
@override
void initState() {
super.initState();
_videoId = YoutubePlayer.convertUrlToId(widget.videoUrl);
final videoId = _videoId;
if (videoId != null) {
_youtubeController = YoutubePlayerController(
initialVideoId: videoId,
flags: const YoutubePlayerFlags(
autoPlay: false,
),
);
if (isDesktop(supportWeb: false)) {
_loadYoutubeVideoWithVideoPlayer =
if (videoId == null) {
return;
}
switch (widget.youtubeVideoSupportMode) {
case YoutubeVideoSupportMode.disabled:
break;
case YoutubeVideoSupportMode.iframeView:
_youtubeIframeController = YoutubePlayerController(
initialVideoId: videoId,
flags: const YoutubePlayerFlags(
autoPlay: false,
),
);
break;
case YoutubeVideoSupportMode.customPlayerWithDownloadUrl:
_loadYoutubeVideoByDownloadUrlFuture =
_loadYoutubeVideoWithVideoPlayerByVideoUrl();
}
break;
}
}
@ -56,11 +68,11 @@ class YoutubeVideoAppState extends State<YoutubeVideoApp> {
final manifest =
await youtubeExplode.videos.streamsClient.getManifest(_videoId);
final streamInfo = manifest.muxed.withHighestBitrate();
final downloadUrl = streamInfo.url;
return downloadUrl.toString();
final videoDownloadUri = streamInfo.url;
return videoDownloadUri.toString();
}
Widget _videoLink({required DefaultStyles defaultStyles}) {
Widget _clickableVideoLinkText({required DefaultStyles defaultStyles}) {
return RichText(
text: TextSpan(
text: widget.videoUrl,
@ -74,50 +86,58 @@ class YoutubeVideoAppState extends State<YoutubeVideoApp> {
@override
Widget build(BuildContext context) {
final defaultStyles = DefaultStyles.getInstance(context);
final youtubeController = _youtubeController;
if (youtubeController == null) {
if (widget.readOnly) {
return _videoLink(defaultStyles: defaultStyles);
}
return RichText(
text: TextSpan(text: widget.videoUrl, style: defaultStyles.link),
);
}
switch (widget.youtubeVideoSupportMode) {
case YoutubeVideoSupportMode.disabled:
throw UnsupportedError('YouTube video links are not supported');
case YoutubeVideoSupportMode.iframeView:
final youtubeController = _youtubeIframeController;
// Workaround as YoutubePlayer doesn't support Desktop
if (_loadYoutubeVideoWithVideoPlayer != null) {
return FutureBuilder<String>(
future: _loadYoutubeVideoWithVideoPlayer,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator.adaptive());
if (youtubeController == null) {
if (widget.readOnly) {
return _clickableVideoLinkText(defaultStyles: defaultStyles);
}
if (snapshot.hasError) {
return _videoLink(defaultStyles: defaultStyles);
}
return VideoApp(
videoUrl: snapshot.requireData,
readOnly: widget.readOnly,
return RichText(
text: TextSpan(text: widget.videoUrl, style: defaultStyles.link),
);
},
);
}
return YoutubePlayerBuilder(
player: YoutubePlayer(
controller: youtubeController,
showVideoProgressIndicator: true,
),
builder: (context, player) {
return player;
},
);
case YoutubeVideoSupportMode.customPlayerWithDownloadUrl:
assert(
_loadYoutubeVideoByDownloadUrlFuture != null,
'The load youtube video future should not null for "${widget.youtubeVideoSupportMode}" mode',
);
return FutureBuilder<String>(
future: _loadYoutubeVideoByDownloadUrlFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator.adaptive());
}
if (snapshot.hasError) {
return _clickableVideoLinkText(defaultStyles: defaultStyles);
}
return VideoApp(
videoUrl: snapshot.requireData,
readOnly: widget.readOnly,
);
},
);
}
return YoutubePlayerBuilder(
player: YoutubePlayer(
controller: youtubeController,
showVideoProgressIndicator: true,
),
builder: (context, player) {
return player;
},
);
}
@override
void dispose() {
_youtubeController?.dispose();
_youtubeIframeController?.dispose();
super.dispose();
}
}

@ -1,10 +1,13 @@
import 'package:flutter/widgets.dart' show GlobalKey;
import 'package:meta/meta.dart' show immutable;
import 'youtube_video_support_mode.dart';
@immutable
class QuillEditorVideoEmbedConfigurations {
const QuillEditorVideoEmbedConfigurations({
this.onVideoInit,
this.youtubeVideoSupportMode = YoutubeVideoSupportMode.iframeView,
});
/// [onVideoInit] is a callback function that gets triggered when
@ -21,4 +24,8 @@ class QuillEditorVideoEmbedConfigurations {
/// // Customize other callback functions as needed
/// ```
final void Function(GlobalKey videoContainerKey)? onVideoInit;
/// Specifies how YouTube videos should be loaded if the video URL
/// is YouTube video.
final YoutubeVideoSupportMode youtubeVideoSupportMode;
}

@ -0,0 +1,17 @@
/// Enum representing the different modes for handling YouTube video support.
enum YoutubeVideoSupportMode {
/// Disable loading of YouTube videos.
disabled,
/// Load the video using the official YouTube IFrame API.
/// See [YouTube IFrame API](https://developers.google.com/youtube/iframe_api_reference) for more details.
///
/// This will use Platform View on native platforms to use WebView
/// The WebView might not be supported on Desktop and will throw an exception
iframeView,
/// Load the video using a custom video player by fetching the YouTube video URL.
/// Note: This might violate YouTube's terms of service.
/// See [YouTube Terms of Service](https://www.youtube.com/static?template=terms) for more details.
customPlayerWithDownloadUrl,
}
Loading…
Cancel
Save