Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: YouTube player respects timestamps startAt #1620

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'))
micahmo marked this conversation as resolved.
Show resolved Hide resolved
..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)?');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you define this RegEx just once and reuse it?

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to use RegEx groups so that one of the matches exactly matches the number without the unit (h, s, etc.).

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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do this as Duration(hours: hours, minutes: minutes, seconds: seconds).inSeconds to avoid "magic numbers".

}

@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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, all the pubspec.lock changes should be reverted unless there was an explicit update to the packages!

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
Loading