diff --git a/lib/utils/video_player/src/thunder_youtube_player.dart b/lib/utils/video_player/src/thunder_youtube_player.dart index 6aaad1ad0..6abab1dd8 100644 --- a/lib/utils/video_player/src/thunder_youtube_player.dart +++ b/lib/utils/video_player/src/thunder_youtube_player.dart @@ -24,17 +24,28 @@ class ThunderYoutubePlayer extends StatefulWidget { } class _ThunderYoutubePlayerState extends State with SingleTickerProviderStateMixin { + /// Whether or not the video is muted. + bool muted = false; + late YoutubePlayerController _controller; late ypf.YoutubePlayerController _ypfController; - /// Whether or not the video is muted. - bool muted = false; + @override + void dispose() { + if (Platform.isAndroid || Platform.isIOS) { + _ypfController.dispose(); + } else { + _controller.close(); + } + super.dispose(); + } @override void initState() { super.initState(); final state = context.read().state; + final timestamp = extractYouTubeTimestamp(); if (Platform.isAndroid || Platform.isIOS) { _ypfController = ypf.YoutubePlayerController( @@ -44,10 +55,12 @@ class _ThunderYoutubePlayerState extends State with Single autoPlay: autoPlayVideo(), enableCaption: false, hideControls: false, + startAt: timestamp ?? 0, loop: state.videoAutoLoop, mute: state.videoAutoMute, ), )..setPlaybackRate(state.videoDefaultPlaybackSpeed.value); + if (state.videoAutoFullscreen) _ypfController.toggleFullScreenMode(); } else { _controller = YoutubePlayerController( @@ -60,22 +73,13 @@ class _ThunderYoutubePlayerState extends State with Single ); _controller ..loadVideoById(videoId: ypf.YoutubePlayer.convertUrlToId(widget.videoUrl)!) + ..seekTo(seconds: double.parse('$timestamp')) ..setPlaybackRate(state.videoDefaultPlaybackSpeed.value); } setState(() => muted = state.videoAutoMute); } - @override - void dispose() { - if (Platform.isAndroid || Platform.isIOS) { - _ypfController.dispose(); - } else { - _controller.close(); - } - super.dispose(); - } - bool autoPlayVideo() { final state = context.read().state; final networkCubit = context.read().state; @@ -89,6 +93,40 @@ class _ThunderYoutubePlayerState extends State with Single return false; } + int? extractYouTubeTimestamp() { + final uri = Uri.parse(widget.videoUrl); + + // Check for timestamp in query parameters + String? timeParam = uri.queryParameters['t'] ?? uri.queryParameters['start']; + + // Check for embedded timestamps like t=1m30s + final regex = RegExp(r't=(\d+h)?(\d+m)?(\d+s)?'); + + if (timeParam != null) { + return _convertTimeStringToSeconds(timeParam); + } else if (regex.hasMatch(widget.videoUrl)) { + final match = regex.firstMatch(widget.videoUrl)!; + return _convertTimeComponentsToSeconds(match); + } + + return null; + } + + int _convertTimeStringToSeconds(String time) { + final regex = RegExp(r'(\d+h)?(\d+m)?(\d+s)?'); + final match = regex.firstMatch(time); + + return match != null ? _convertTimeComponentsToSeconds(match) : 0; + } + + int _convertTimeComponentsToSeconds(RegExpMatch match) { + int hours = match.group(1) != null ? int.parse(match.group(1)!.replaceAll('h', '')) : 0; + int minutes = match.group(2) != null ? int.parse(match.group(2)!.replaceAll('m', '')) : 0; + int seconds = match.group(3) != null ? int.parse(match.group(3)!.replaceAll('s', '')) : 0; + + return hours * 3600 + minutes * 60 + seconds; + } + @override Widget build(BuildContext context) { if (Platform.isAndroid || Platform.isIOS) { diff --git a/pubspec.lock b/pubspec.lock index ef4502081..e0faccb5f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -334,6 +334,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.17.3" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" dart_ping: dependency: "direct main" description: @@ -1480,23 +1488,42 @@ packages: push: dependency: "direct main" description: - path: "packages/push/push" - relative: true - source: path + name: push + sha256: "9fd6dce70b4755e525dd21dd753e4130c5c70bc155558207f39eb0a1aa2647c7" + url: "https://pub.dev" + source: hosted version: "2.3.0" + push_android: + dependency: transitive + description: + name: push_android + sha256: c7df90971ef0b8f5c7acc5eb5b8dc474742b1eaefaf921c6cbb244d0e2da6ffb + url: "https://pub.dev" + source: hosted + version: "0.6.0" push_ios: dependency: transitive description: - path: "packages/push/push_ios" - relative: true - source: path + name: push_ios + sha256: "3d8b0ce1b0a29b20a17a7ca3d17f9ae19707cdd20c168d345b27cd00a4983826" + url: "https://pub.dev" + source: hosted version: "0.5.1" + push_macos: + dependency: transitive + description: + name: push_macos + sha256: "800997d1d6ca19aa957ab237a25074a732a7e36ef917ef4546fc75ec9aa1b9da" + url: "https://pub.dev" + source: hosted + version: "0.0.1" push_platform_interface: dependency: transitive description: - path: "packages/push/push_platform_interface" - relative: true - source: path + name: push_platform_interface + sha256: "1d45f9dfa8f26251d4c8efb733740debb91f9184fc5d05e83cc359331ef1879f" + url: "https://pub.dev" + source: hosted version: "0.6.0" recase: dependency: transitive