diff --git a/lib/core/services/constants.dart b/lib/core/services/constants.dart index 2dbb1a9..182ce47 100644 --- a/lib/core/services/constants.dart +++ b/lib/core/services/constants.dart @@ -1 +1,2 @@ -const String baseUrl = 'https://7558-114-6-25-184.ngrok-free.app/'; +const String baseUrl = + 'https://ebdc-2001-448a-50a0-3463-a4de-673f-afb-724b.ngrok-free.app/'; diff --git a/lib/features/learning/modules/material/screens/material_screen.dart b/lib/features/learning/modules/material/screens/material_screen.dart index 42a8ea1..194457b 100644 --- a/lib/features/learning/modules/material/screens/material_screen.dart +++ b/lib/features/learning/modules/material/screens/material_screen.dart @@ -169,13 +169,13 @@ class _MaterialScreenState extends State ), AudioExtension( builder: (context) => AudioPlayerWidget( - key: _audioPlayerKey, + key: ValueKey('audio_${context.attributes['src']}'), audioFileName: context.attributes['src'] ?? '', ), ), VideoExtension( builder: (context) => VideoPlayerWidget( - key: _videoPlayerKey, + key: ValueKey('video_${context.attributes['src']}'), videoUrl: context.attributes['src'] ?? '', ), ), diff --git a/lib/features/learning/modules/material/widgets/video_player_widget.dart b/lib/features/learning/modules/material/widgets/video_player_widget.dart index 4bbcf5e..d2303c5 100644 --- a/lib/features/learning/modules/material/widgets/video_player_widget.dart +++ b/lib/features/learning/modules/material/widgets/video_player_widget.dart @@ -21,36 +21,60 @@ class VideoPlayerWidgetState extends State { FlickManager? _flickManager; bool _isLoading = true; String? _error; - bool _isYoutubeReady = false; String? _youtubeId; + bool _isDisposed = false; @override void initState() { super.initState(); _youtubeId = _extractYoutubeId(widget.videoUrl); - _initializeVideoPlayerWidget(); + + if (_youtubeId != null) { + _youtubeController = YoutubePlayerController( + initialVideoId: _youtubeId!, + flags: const YoutubePlayerFlags( + mute: false, + autoPlay: false, + disableDragSeek: true, + loop: false, + enableCaption: false, + hideThumbnail: false, + ), + ); + if (!_isDisposed) { + setState(() { + _isLoading = false; + }); + } + } else { + _initializeVideo(); + } + } + + Future _initializeVideo() async { + try { + await _initializeRegularVideo(); + } catch (e) { + if (!_isDisposed) { + setState(() { + _error = "Error initializing video player: $e"; + _isLoading = false; + }); + } + } } String? _extractYoutubeId(String url) { try { - RegExp regExp = RegExp( - r'^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/|shorts\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*', - caseSensitive: false, - multiLine: false, - ); - - String? videoId = YoutubePlayer.convertUrlToId(url); - if (videoId != null) return videoId; - - Match? match = regExp.firstMatch(url); - if (match != null && match.groupCount >= 1) { - return match.group(1); + if (url.contains('youtube.com/watch?v=')) { + return url.split('watch?v=')[1].split('&')[0]; + } + if (url.contains('youtube.com/embed/')) { + return url.split('youtube.com/embed/')[1].split('?')[0]; } - if (url.contains('youtu.be/')) { - return url.split('youtu.be/')[1].split(RegExp(r'[?&]'))[0]; + return url.split('youtu.be/')[1].split('?')[0]; } - return null; } catch (e) { print('Error extracting YouTube ID: $e'); @@ -60,6 +84,9 @@ class VideoPlayerWidgetState extends State { String _getPlayableUrl(String url) { if (url.contains('drive.google.com')) { + if (url.contains('/view')) { + url = url.replaceAll('/view', ''); + } final regex = RegExp(r'/d/([a-zA-Z0-9-_]+)'); final match = regex.firstMatch(url); if (match != null) { @@ -70,71 +97,38 @@ class VideoPlayerWidgetState extends State { return url; } - Future _initializeVideoPlayerWidget() async { - if (_youtubeId != null) { - try { - _youtubeController = YoutubePlayerController( - initialVideoId: _youtubeId!, - flags: const YoutubePlayerFlags( - autoPlay: false, - mute: false, - hideControls: false, - controlsVisibleAtStart: true, - enableCaption: true, - useHybridComposition: true, - forceHD: true, - ), - ); + Future _initializeRegularVideo() async { + try { + final playableUrl = _getPlayableUrl(widget.videoUrl); + _videoController = VideoPlayerController.networkUrl( + Uri.parse(playableUrl), + ); - if (mounted) { - setState(() {}); // Trigger rebuild with controller - } - } catch (e) { - if (mounted) { - setState(() { - _error = "Error initializing YouTube player: $e"; - _isLoading = false; - }); - } - } - } else { - try { - _videoController = VideoPlayerController.networkUrl( - Uri.parse(_getPlayableUrl(widget.videoUrl)), - ); - - await _videoController!.initialize(); + await _videoController!.initialize(); + if (!_isDisposed) { _flickManager = FlickManager( videoPlayerController: _videoController!, autoPlay: false, ); - if (mounted) { - setState(() { - _isLoading = false; - }); - } - } catch (e) { - if (mounted) { - setState(() { - _error = "Error initializing video player: $e"; - _isLoading = false; - }); - } + setState(() { + _isLoading = false; + }); + } + } catch (e) { + if (!_isDisposed) { + setState(() { + _error = "Error initializing video player: $e"; + _isLoading = false; + }); } - } - } - - void _youtubeListener() { - if (_youtubeController?.value.playerState == PlayerState.ended) { - _youtubeController?.seekTo(Duration.zero); - _youtubeController?.pause(); } } @override void dispose() { + _isDisposed = true; _videoController?.dispose(); _youtubeController?.dispose(); _flickManager?.dispose(); @@ -143,94 +137,76 @@ class VideoPlayerWidgetState extends State { @override Widget build(BuildContext context) { - if (_youtubeId != null && _youtubeController != null) { - return ClipRRect( - borderRadius: BorderRadius.circular(16), - child: AspectRatio( - aspectRatio: 16 / 9, - child: YoutubePlayerBuilder( - player: YoutubePlayer( - controller: _youtubeController!, - showVideoProgressIndicator: true, - progressIndicatorColor: Colors.red, - progressColors: const ProgressBarColors( - playedColor: Colors.red, - handleColor: Colors.redAccent, - ), - onReady: () { - if (mounted) { - setState(() { - _isYoutubeReady = true; - _isLoading = false; - }); - } - _youtubeController!.addListener(_youtubeListener); - }, - onEnded: (YoutubeMetaData metaData) { - _youtubeController!.seekTo(Duration.zero); - _youtubeController!.pause(); - }, - bottomActions: [ - CurrentPosition(), - ProgressBar(isExpanded: true), - RemainingDuration(), - PlaybackSpeedButton(), - ], - ), - builder: (context, player) { - return Container( - color: Colors.black, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: _isYoutubeReady - ? player - : const Center( - child: CircularProgressIndicator( - valueColor: - AlwaysStoppedAnimation(Colors.white), - ), - ), - ), - ); - }, - ), - ), - ); + if (_error != null) { + return _buildErrorContainer(); } + return ClipRRect( + borderRadius: BorderRadius.circular(16), + child: AspectRatio( + aspectRatio: 16 / 9, + child: + _youtubeId != null ? _buildYoutubePlayer() : _buildRegularVideo(), + ), + ); + } + + Widget _buildErrorContainer() { return ClipRRect( borderRadius: BorderRadius.circular(16), child: AspectRatio( aspectRatio: 16 / 9, child: Container( color: Colors.black, - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: _buildContent(), + child: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + _error!, + style: const TextStyle(color: Colors.white), + textAlign: TextAlign.center, + ), + ), ), ), ), ); } - Widget _buildContent() { - if (_error != null) { - return Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - _error!, - style: const TextStyle(color: Colors.white), - textAlign: TextAlign.center, + Widget _buildYoutubePlayer() { + if (_youtubeController == null) { + return Container( + color: Colors.black, + child: const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), ), ), ); } + return YoutubePlayer( + controller: _youtubeController!, + showVideoProgressIndicator: true, + progressIndicatorColor: Colors.red, + progressColors: const ProgressBarColors( + playedColor: Colors.red, + handleColor: Colors.redAccent, + ), + onReady: () { + print('YouTube Player Ready'); + }, + ); + } + + Widget _buildRegularVideo() { if (_isLoading) { - return const Center( - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Colors.white), + return Container( + color: Colors.black, + child: const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 820171b..58857cb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" audio_session: dependency: transitive description: @@ -85,10 +85,10 @@ packages: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" bootstrap_icons: dependency: "direct main" description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: @@ -322,18 +322,18 @@ packages: dependency: "direct main" description: name: flutter_inappwebview - sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959" + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.5" flutter_inappwebview_android: dependency: transitive description: name: flutter_inappwebview_android - sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421 + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" url: "https://pub.dev" source: hosted - version: "1.0.13" + version: "1.1.3" flutter_inappwebview_internal_annotations: dependency: transitive description: @@ -346,34 +346,42 @@ packages: dependency: transitive description: name: flutter_inappwebview_ios - sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" url: "https://pub.dev" source: hosted - version: "1.0.13" + version: "1.1.2" flutter_inappwebview_macos: dependency: transitive description: name: flutter_inappwebview_macos - sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636 + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 url: "https://pub.dev" source: hosted - version: "1.0.11" + version: "1.1.2" flutter_inappwebview_platform_interface: dependency: transitive description: name: flutter_inappwebview_platform_interface - sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 url: "https://pub.dev" source: hosted - version: "1.0.10" + version: "1.3.0+1" flutter_inappwebview_web: dependency: transitive description: name: flutter_inappwebview_web - sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07 + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_lints: dependency: "direct dev" description: @@ -676,18 +684,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: @@ -788,10 +796,10 @@ packages: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" path_parsing: dependency: transitive description: @@ -956,7 +964,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -993,10 +1001,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -1009,10 +1017,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" synchronized: dependency: transitive description: @@ -1209,10 +1217,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.3.1" wakelock_plus: dependency: transitive description: @@ -1297,10 +1305,10 @@ packages: dependency: "direct main" description: name: youtube_player_flutter - sha256: "30f84e2f7063c56e536f507e37c1e803546842707cf58e5b5a71253b9ff9b455" + sha256: "4d14aa47f9c84929b5400a87ade4dcfdab87a2ca2e0b18ecc2ef852b1440e123" url: "https://pub.dev" source: hosted - version: "9.0.4" + version: "9.1.1" sdks: dart: ">=3.6.0-122.0.dev <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index a14bc44..fd54a28 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: shared_preferences: ^2.3.2 shimmer: ^3.0.0 video_player: ^2.9.1 - youtube_player_flutter: ^9.0.4 + youtube_player_flutter: ^9.1.1 dev_dependencies: flutter_lints: ^4.0.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index ac77dfd..073795e 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a331a5f..4c5165c 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows file_selector_windows + flutter_inappwebview_windows flutter_secure_storage_windows url_launcher_windows )