Skip to content

Commit

Permalink
feat: YouTube player respects timestamps startAt
Browse files Browse the repository at this point in the history
- Implement timestamp extraction for YouTube video URLs
- Automatically set video start time from URL parameters
- Support multiple timestamp formats (t=, start=, time notation)
- Add custom method to parse timestamps in seconds
  • Loading branch information
ggichure committed Dec 3, 2024
1 parent d8bb721 commit dfc9ab1
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 21 deletions.
62 changes: 50 additions & 12 deletions lib/utils/video_player/src/thunder_youtube_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,28 @@ class ThunderYoutubePlayer extends StatefulWidget {
}

class _ThunderYoutubePlayerState extends State<ThunderYoutubePlayer> 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<ThunderBloc>().state;
final timestamp = extractYouTubeTimestamp();

if (Platform.isAndroid || Platform.isIOS) {
_ypfController = ypf.YoutubePlayerController(
Expand All @@ -44,10 +55,12 @@ class _ThunderYoutubePlayerState extends State<ThunderYoutubePlayer> 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(
Expand All @@ -60,22 +73,13 @@ class _ThunderYoutubePlayerState extends State<ThunderYoutubePlayer> 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<ThunderBloc>().state;
final networkCubit = context.read<NetworkCheckerCubit>().state;
Expand All @@ -89,6 +93,40 @@ class _ThunderYoutubePlayerState extends State<ThunderYoutubePlayer> 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) {
Expand Down
45 changes: 36 additions & 9 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit dfc9ab1

Please sign in to comment.