Skip to content

Commit

Permalink
Add video playback settings (in-app, custom tabs, external player) (#…
Browse files Browse the repository at this point in the history
…1563)

* Video Player  preferences playing mode (InApp, InAppBrowser,External)

* update use in-built player string

* fix video player setting page formatting

* chore: minor code adjustments for better readability

---------

Co-authored-by: Hamlet Jiang Su <hamlet.jiangsu@outlook.com>
  • Loading branch information
ggichure and hjiangsu authored Nov 27, 2024
1 parent c7050a1 commit c5a4c6b
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 17 deletions.
2 changes: 2 additions & 0 deletions lib/core/enums/local_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ enum LocalSettings {
videoAutoFullscreen(name: 'video_auto_fullscreen', key: 'videoAutoFullscreen', category: LocalSettingsCategories.videoPlayer, subCategory: LocalSettingsSubCategories.videoPlayer),
videoAutoLoop(name: 'video_auto_loop', key: '', category: LocalSettingsCategories.videoPlayer, subCategory: LocalSettingsSubCategories.videoPlayer),
videoAutoPlay(name: 'video_auto_play', key: '', category: LocalSettingsCategories.videoPlayer, subCategory: LocalSettingsSubCategories.videoPlayer),
videoPlayerMode(name: 'setting_video_player_mode', key: 'videoPlayerMode', category: LocalSettingsCategories.videoPlayer, subCategory: LocalSettingsSubCategories.videoPlayer),

// Searchable settings
// The settings under this section do not correspond to settings that we persist in SharedPreferences.
Expand Down Expand Up @@ -522,6 +523,7 @@ extension LocalizationExt on AppLocalizations {
'videoAutoLoop': videoAutoLoop,
'videoAutoPlay': videoAutoPlay,
'videoDefaultPlaybackSpeed': videoDefaultPlaybackSpeed,
'videoPlayerMode': videoPlayerMode,
'userLabels': userLabels,
'accountDisplayName': displayName,
'accountProfileBio': profileBio,
Expand Down
5 changes: 5 additions & 0 deletions lib/core/enums/video_player_mode.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum VideoPlayerMode {
inApp,
customTabs,
externalPlayer,
}
12 changes: 12 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2787,6 +2787,18 @@
"@videoDefaultPlaybackSpeed": {
"description": "Option to select the default video playback Speed"
},
"videoLinkHandlingExternal": "Play video with an external app",
"@videoLinkHandlingExternal": {
"description": "Description to Open video with an external application"
},
"videoPlayerInApp": "Use Thunder built-in player",
"@videoPlayerInApp": {
"description": "Description for using in-app video player"
},
"videoPlayerMode": "Player Mode",
"@videoPlayerMode": {
"description": "Option to select video player mode (in-app or external) "
},
"viewAll": "View all",
"@viewAll": {
"description": "A title for viewing all of something (e.g., instances)"
Expand Down
32 changes: 31 additions & 1 deletion lib/settings/pages/video_player_settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/enums/video_auto_play.dart';
import 'package:thunder/core/enums/video_playback_speed.dart';
import 'package:thunder/core/enums/video_player_mode.dart';
import 'package:thunder/core/singletons/preferences.dart';
import 'package:thunder/settings/widgets/list_option.dart';
import 'package:thunder/settings/widgets/toggle_option.dart';
Expand Down Expand Up @@ -45,6 +46,9 @@ class _VideoPlayerSettingsPageState extends State<VideoPlayerSettingsPage> {
/// Option as to how fast the video playback speed should be (.25,.5 ... 2)
VideoPlayBackSpeed videoDefaultPlaybackSpeed = VideoPlayBackSpeed.normal;

/// Option to select video player mode (in-app or external)
VideoPlayerMode videoPlayerMode = VideoPlayerMode.inApp;

@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
Expand Down Expand Up @@ -94,11 +98,14 @@ class _VideoPlayerSettingsPageState extends State<VideoPlayerSettingsPage> {
await prefs.setString(LocalSettings.videoAutoPlay.name, value);
setState(() => videoAutoPlay = VideoAutoPlay.values.byName(value ?? VideoAutoPlay.never));
break;

case LocalSettings.videoDefaultPlaybackSpeed:
await prefs.setString(LocalSettings.videoDefaultPlaybackSpeed.name, value);
setState(() => videoDefaultPlaybackSpeed = VideoPlayBackSpeed.values.byName(value ?? VideoPlayBackSpeed.normal));
break;
case LocalSettings.videoPlayerMode:
await prefs.setString(LocalSettings.videoPlayerMode.name, value);
setState(() => videoPlayerMode = VideoPlayerMode.values.byName(value ?? VideoPlayerMode.inApp));
break;
default:
}

Expand All @@ -115,6 +122,7 @@ class _VideoPlayerSettingsPageState extends State<VideoPlayerSettingsPage> {
videoAutoLoop = prefs.getBool(LocalSettings.videoAutoLoop.name) ?? false;
videoAutoPlay = VideoAutoPlay.values.byName(prefs.getString(LocalSettings.videoAutoPlay.name) ?? VideoAutoPlay.never.name);
videoDefaultPlaybackSpeed = VideoPlayBackSpeed.values.byName(prefs.getString(LocalSettings.videoDefaultPlaybackSpeed.name) ?? VideoPlayBackSpeed.normal.name);
videoPlayerMode = VideoPlayerMode.values.byName(prefs.getString(LocalSettings.videoPlayerMode.name) ?? VideoPlayerMode.inApp.name);
isLoading = false;
});
}
Expand Down Expand Up @@ -202,6 +210,28 @@ class _VideoPlayerSettingsPageState extends State<VideoPlayerSettingsPage> {
setting: LocalSettings.videoDefaultPlaybackSpeed,
highlightedSetting: settingToHighlight,
),
ListOption(
description: l10n.videoPlayerMode,
value: ListPickerItem(
label: switch (videoPlayerMode) {
VideoPlayerMode.inApp => l10n.videoPlayerInApp,
VideoPlayerMode.customTabs => l10n.linkHandlingCustomTabsShort,
VideoPlayerMode.externalPlayer => l10n.linkHandlingExternalShort,
},
payload: videoPlayerMode,
capitalizeLabel: false,
),
options: [
ListPickerItem(label: l10n.videoPlayerInApp, icon: Icons.play_circle_fill, payload: VideoPlayerMode.inApp),
ListPickerItem(label: l10n.linkHandlingCustomTabs, icon: Icons.language_rounded, payload: VideoPlayerMode.customTabs),
ListPickerItem(label: l10n.videoLinkHandlingExternal, icon: Icons.open_in_browser_rounded, payload: VideoPlayerMode.externalPlayer),
],
icon: Icons.video_label_outlined,
onChanged: (value) => setPreferences(LocalSettings.videoPlayerMode, value.payload.name),
highlightKey: settingToHighlightKey,
setting: LocalSettings.videoPlayerMode,
highlightedSetting: settingToHighlight,
),
],
),
),
Expand Down
3 changes: 3 additions & 0 deletions lib/thunder/bloc/thunder_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'package:thunder/core/enums/full_name.dart';
import 'package:thunder/core/enums/image_caching_mode.dart';
import 'package:thunder/core/enums/local_settings.dart';
import 'package:thunder/core/enums/nested_comment_indicator.dart';
import 'package:thunder/core/enums/video_player_mode.dart';
import 'package:thunder/notification/enums/notification_type.dart';
import 'package:thunder/core/enums/post_body_view_type.dart';
import 'package:thunder/core/enums/swipe_action.dart';
Expand Down Expand Up @@ -261,6 +262,7 @@ class ThunderBloc extends Bloc<ThunderEvent, ThunderState> {
bool videoAutoMute = prefs.getBool(LocalSettings.videoAutoMute.name) ?? true;
VideoAutoPlay videoAutoPlay = VideoAutoPlay.values.byName(prefs.getString(LocalSettings.videoAutoPlay.name) ?? VideoAutoPlay.never.name);
VideoPlayBackSpeed videoDefaultPlaybackSpeed = VideoPlayBackSpeed.values.byName(prefs.getString(LocalSettings.videoDefaultPlaybackSpeed.name) ?? VideoPlayBackSpeed.normal.name);
VideoPlayerMode videoPlayerMode = VideoPlayerMode.values.byName(prefs.getString(LocalSettings.videoPlayerMode.name) ?? VideoPlayerMode.inApp.name);

String currentAnonymousInstance = prefs.getString(LocalSettings.currentAnonymousInstance.name) ?? 'lemmy.ml';

Expand Down Expand Up @@ -429,6 +431,7 @@ class ThunderBloc extends Bloc<ThunderEvent, ThunderState> {
videoAutoLoop: videoAutoLoop,
videoAutoPlay: videoAutoPlay,
videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed,
videoPlayerMode: videoPlayerMode,

currentAnonymousInstance: currentAnonymousInstance,
));
Expand Down
5 changes: 5 additions & 0 deletions lib/thunder/bloc/thunder_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class ThunderState extends Equatable {
this.videoAutoMute = true,
this.videoAutoPlay = VideoAutoPlay.never,
this.videoDefaultPlaybackSpeed = VideoPlayBackSpeed.normal,
this.videoPlayerMode = VideoPlayerMode.inApp,

/// -------------------------- Accessibility Related Settings --------------------------
this.reduceAnimations = false,
Expand Down Expand Up @@ -349,6 +350,7 @@ class ThunderState extends Equatable {
final bool videoAutoMute;
final VideoAutoPlay videoAutoPlay;
final VideoPlayBackSpeed videoDefaultPlaybackSpeed;
final VideoPlayerMode videoPlayerMode;

/// --------------------------------- UI Events ---------------------------------
// Expand/Close FAB event
Expand Down Expand Up @@ -522,6 +524,7 @@ class ThunderState extends Equatable {
bool? videoAutoMute,
VideoAutoPlay? videoAutoPlay,
VideoPlayBackSpeed? videoDefaultPlaybackSpeed,
VideoPlayerMode? videoPlayerMode,

/// --------------------------------- UI Events ---------------------------------
// Expand/Close FAB event
Expand Down Expand Up @@ -698,6 +701,7 @@ class ThunderState extends Equatable {
videoAutoMute: videoAutoMute ?? this.videoAutoMute,
videoAutoPlay: videoAutoPlay ?? this.videoAutoPlay,
videoDefaultPlaybackSpeed: videoDefaultPlaybackSpeed ?? this.videoDefaultPlaybackSpeed,
videoPlayerMode: videoPlayerMode ?? this.videoPlayerMode,
currentAnonymousInstance: currentAnonymousInstance,

/// ------------------ Video Player ------------------------
Expand Down Expand Up @@ -872,6 +876,7 @@ class ThunderState extends Equatable {
videoAutoMute,
videoAutoPlay,
videoDefaultPlaybackSpeed,
videoPlayerMode,

/// -------------------------- Accessibility Related Settings --------------------------
reduceAnimations,
Expand Down
31 changes: 28 additions & 3 deletions lib/utils/links.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:link_preview_generator/link_preview_generator.dart';
import 'package:share_plus/share_plus.dart';
import 'package:swipeable_page_route/swipeable_page_route.dart';
import 'package:thunder/core/enums/browser_mode.dart';
import 'package:thunder/core/enums/video_player_mode.dart';
import 'package:thunder/feed/bloc/feed_bloc.dart';
import 'package:thunder/instances.dart';
import 'package:thunder/modlog/utils/navigate_modlog.dart';
Expand Down Expand Up @@ -73,14 +74,31 @@ Future<LinkInfo> getLinkInfo(String url) async {
}
}

void _openLink(BuildContext context, {required String url}) async {
void _openLink(BuildContext context, {required String url, bool isVideo = false}) async {
ThunderState state = context.read<ThunderBloc>().state;

if (state.browserMode == BrowserMode.external || (!kIsWeb && !Platform.isAndroid && !Platform.isIOS)) {
bool launchInExternalApp = false;
bool launchInCustomTab = false;

if (isVideo && state.videoPlayerMode == VideoPlayerMode.externalPlayer) {
launchInExternalApp = true;
} else if (!isVideo && state.browserMode == BrowserMode.external) {
launchInExternalApp = true;
}

if (isVideo && state.videoPlayerMode == VideoPlayerMode.customTabs) {
launchInCustomTab = true;
} else if (!isVideo && state.browserMode == BrowserMode.customTabs) {
launchInCustomTab = true;
}

if (launchInExternalApp || (!kIsWeb && !Platform.isAndroid && !Platform.isIOS)) {
hideLoadingPage(context, delay: true);
url_launcher.launchUrl(Uri.parse(url), mode: url_launcher.LaunchMode.externalApplication);
} else if (state.browserMode == BrowserMode.customTabs) {
} else if (launchInCustomTab) {
// Launches the link within a custom tab
hideLoadingPage(context, delay: true);

launchUrl(
Uri.parse(url),
customTabsOptions: CustomTabsOptions(
Expand All @@ -105,8 +123,10 @@ void _openLink(BuildContext context, {required String url}) async {
),
);
} else if (state.browserMode == BrowserMode.inApp) {
// Launches the link within the in-app browser if possible
// Check if the scheme is not https, in which case the in-app browser can't handle it
Uri? uri = Uri.tryParse(url);

if (uri != null && uri.scheme != 'https') {
// Although a non-https scheme is an indication that this link is intended for another app,
// we actually have to change it back to https in order for the intent to be properly passed to another app.
Expand Down Expand Up @@ -254,6 +274,11 @@ void handleLink(BuildContext context, {required String url, bool forceOpenInBrow
}
}

/// A universal way of handling video links by opening them in the browser/external player
void handleVideoLink(BuildContext context, {required String url}) async {
_openLink(context, url: url, isVideo: isVideoUrl(url));
}

/// This is a helper method which helps [handleLink] determine whether a link refers to a valid Lemmy community.
/// If the passed in link is not a valid URI, then there's no point in doing any fallback, so assume it passes.
/// If the passed in [instance] is a known Lemmy instance, then it passes.
Expand Down
40 changes: 27 additions & 13 deletions lib/utils/media/video.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:thunder/core/enums/video_player_mode.dart';
import 'package:thunder/thunder/bloc/thunder_bloc.dart';
import 'package:thunder/utils/links.dart';

import 'package:youtube_player_flutter/youtube_player_flutter.dart';

Expand Down Expand Up @@ -38,17 +42,27 @@ void showVideoPlayer(BuildContext context, {String? url, int? postId}) {

String? videoId = YoutubePlayer.convertUrlToId(url);

Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
transitionDuration: const Duration(milliseconds: 100),
reverseTransitionDuration: const Duration(milliseconds: 50),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return (videoId != null) ? ThunderYoutubePlayer(videoUrl: url, postId: postId) : ThunderVideoPlayer(videoUrl: url, postId: postId);
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return Align(child: FadeTransition(opacity: animation, child: child));
},
),
);
final thunderState = context.read<ThunderBloc>().state;

switch (thunderState.videoPlayerMode) {
case VideoPlayerMode.inApp:
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
transitionDuration: const Duration(milliseconds: 100),
reverseTransitionDuration: const Duration(milliseconds: 50),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return (videoId != null) ? ThunderYoutubePlayer(videoUrl: url, postId: postId) : ThunderVideoPlayer(videoUrl: url, postId: postId);
},
transitionsBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return Align(child: FadeTransition(opacity: animation, child: child));
},
),
);
break;
case VideoPlayerMode.externalPlayer:
handleVideoLink(context, url: url);
case VideoPlayerMode.customTabs:
handleVideoLink(context, url: url);
}
}

0 comments on commit c5a4c6b

Please sign in to comment.