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(web,mobile) Allow videos to be looped in the detail viewer #8615

Merged
merged 21 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
812325a
First version of video looping for the web
kleinMaggus Mar 21, 2024
cb6e7c7
Merge branch 'loop-videos' of https://github.com/kleinMaggus/immich i…
kleinMaggus Mar 21, 2024
d147783
Use prop for slideshow state
kleinMaggus Mar 21, 2024
32764d7
refactor asset settings and add autoloop video setting
kleinMaggus Mar 29, 2024
79d21ff
Merge remote-tracking branch 'origin/main' into loop-videos
kleinMaggus Mar 30, 2024
0ad7d48
rename variables and adjust description
kleinMaggus Mar 30, 2024
65aee63
loop videos based on user settings in gallery viewer
kleinMaggus Mar 30, 2024
935dfe7
make asset viewer setting a stateless widget
kleinMaggus Mar 30, 2024
1d8a8e1
do not update video playback value if looping is enabled
kleinMaggus Apr 5, 2024
06a8c42
add some translations
kleinMaggus Apr 5, 2024
4baf7a7
adjust description
kleinMaggus Apr 5, 2024
0933a97
Merge branch 'main' of https://github.com/kleinMaggus/immich into loo…
kleinMaggus Apr 5, 2024
4da4ad0
Merge branch 'main' of https://github.com/kleinMaggus/immich into loo…
kleinMaggus Apr 7, 2024
ed60d42
add missing id
kleinMaggus Apr 8, 2024
a8593bf
Merge branch 'main' into loop-videos
alextran1502 Apr 15, 2024
05def08
Merge branch 'main' of https://github.com/kleinMaggus/immich into loo…
kleinMaggus May 5, 2024
3230b62
Merge branch 'main' of https://github.com/immich-app/immich into loop…
kleinMaggus May 11, 2024
47571b5
WIP
jrasm91 May 13, 2024
c301ff4
chore: clean up
jrasm91 May 13, 2024
3d61a5c
Merge remote-tracking branch 'origin/main' into pr/kleinMaggus/8615
jrasm91 May 13, 2024
17c01f6
Merge branch 'main' of github.com:immich-app/immich into loop-videos
alextran1502 May 14, 2024
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
4 changes: 4 additions & 0 deletions mobile/assets/i18n/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@
"setting_image_viewer_original_title": "Original laden",
"setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer Auflösung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur die Miniaturansicht zu verwenden.",
"setting_image_viewer_preview_title": "Vorschaubild laden",
"setting_image_viewer_title": "Bilder",
"setting_languages_apply": "Anwenden",
"setting_languages_title": "Sprachen",
"setting_notifications_notify_failures_grace_period": "Benachrichtigung über Fehler bei der Hintergrundsicherung: {}",
Expand All @@ -406,6 +407,9 @@
"setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)",
"setting_notifications_total_progress_title": "Zeige Gesamtfortschritt bei der Hintergrundsicherung",
"setting_pages_app_bar_settings": "Einstellungen",
"setting_video_viewer_looping_subtitle": "Aktivieren, damit sich ein Video in der Detailansicht automatisch wiederholt.",
"setting_video_viewer_looping_title": "Wiederholen",
"setting_video_viewer_title": "Videos",
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
"share_add": "Hinzufügen",
"share_add_photos": "Fotos hinzufügen",
Expand Down
4 changes: 4 additions & 0 deletions mobile/assets/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@
"setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_title": "Images",
"setting_languages_apply": "Apply",
"setting_languages_title": "Languages",
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
Expand All @@ -406,6 +407,9 @@
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
"setting_notifications_total_progress_title": "Show background backup total progress",
"setting_pages_app_bar_settings": "Settings",
"setting_video_viewer_looping_subtitle": "Enable to automatically loop a video in the detail viewer.",
"setting_video_viewer_looping_title": "Looping",
"setting_video_viewer_title": "Videos",
"settings_require_restart": "Please restart Immich to apply this setting",
"share_add": "Add",
"share_add_photos": "Add photos",
Expand Down
1 change: 1 addition & 0 deletions mobile/lib/entities/store.entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ enum StoreKey<T> {
advancedTroubleshooting<bool>(114, type: bool),
logLevel<int>(115, type: int),
preferRemoteImage<bool>(116, type: bool),
loopVideo<bool>(117, type: bool),
// map related settings
mapShowFavoriteOnly<bool>(118, type: bool),
mapRelativeDate<int>(119, type: int),
Expand Down
4 changes: 4 additions & 0 deletions mobile/lib/pages/common/gallery_viewer.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final settings = ref.watch(appSettingsServiceProvider);
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
final shouldLoopVideo = useState(AppSettingsEnum.loopVideo.defaultValue);
final isZoomed = useState(false);
final isPlayingVideo = useState(false);
final localPosition = useState<Offset?>(null);
Expand Down Expand Up @@ -102,6 +103,8 @@ class GalleryViewerPage extends HookConsumerWidget {
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
isLoadOriginal.value =
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
shouldLoopVideo.value =
settings.getSetting<bool>(AppSettingsEnum.loopVideo);
return null;
},
[],
Expand Down Expand Up @@ -368,6 +371,7 @@ class GalleryViewerPage extends HookConsumerWidget {
key: ValueKey(a),
asset: a,
isMotionVideo: a.livePhotoVideoId != null,
loopVideo: shouldLoopVideo.value,
placeholder: Image(
image: provider,
fit: BoxFit.contain,
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/pages/common/settings.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
import 'package:immich_mobile/widgets/settings/image_viewer_quality_setting.dart';
import 'package:immich_mobile/widgets/settings/language_settings.dart';
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/preference_setting.dart';
Expand All @@ -33,7 +33,7 @@ enum SettingSection {
SettingSection.preferences => const PreferenceSetting(),
SettingSection.backup => const BackupSettings(),
SettingSection.timeline => const AssetListSettings(),
SettingSection.viewer => const ImageViewerQualitySetting(),
SettingSection.viewer => const AssetViewerSettings(),
SettingSection.advanced => const AdvancedSettings(),
};

Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/pages/common/video_viewer.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class VideoViewerPage extends HookConsumerWidget {
final Duration hideControlsTimer;
final bool showControls;
final bool showDownloadingIndicator;
final bool loopVideo;

const VideoViewerPage({
super.key,
Expand All @@ -26,6 +27,7 @@ class VideoViewerPage extends HookConsumerWidget {
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
this.showDownloadingIndicator = true,
this.loopVideo = false,
});

@override
Expand Down Expand Up @@ -73,7 +75,9 @@ class VideoViewerPage extends HookConsumerWidget {
// Also sets the error if there is an error in the playback
void updateVideoPlayback() {
final videoPlayback = VideoPlaybackValue.fromController(controller);
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
if (!loopVideo) {
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
}
final state = videoPlayback.state;

// Enable the WakeLock while the video is playing
Expand Down Expand Up @@ -153,6 +157,7 @@ class VideoViewerPage extends HookConsumerWidget {
hideControlsTimer: hideControlsTimer,
showControls: showControls,
showDownloadingIndicator: showDownloadingIndicator,
loopVideo: loopVideo,
),
),
],
Expand Down
1 change: 1 addition & 0 deletions mobile/lib/services/app_settings.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum AppSettingsEnum<T> {
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
Expand Down
5 changes: 5 additions & 0 deletions mobile/lib/utils/hooks/chewiew_controller_hook.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ChewieController useChewieController({
bool allowFullScreen = false,
bool allowedScreenSleep = false,
bool showControls = true,
bool loopVideo = false,
Widget? customControls,
Widget? placeholder,
Duration hideControlsTimer = const Duration(seconds: 1),
Expand All @@ -36,6 +37,7 @@ ChewieController useChewieController({
hideControlsTimer: hideControlsTimer,
showControlsOnInitialize: showControlsOnInitialize,
showControls: showControls,
loopVideo: loopVideo,
allowedScreenSleep: allowedScreenSleep,
onPlaying: onPlaying,
onPaused: onPaused,
Expand All @@ -53,6 +55,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
final bool allowFullScreen;
final bool allowedScreenSleep;
final bool showControls;
final bool loopVideo;
final Widget? customControls;
final Widget? placeholder;
final Duration hideControlsTimer;
Expand All @@ -71,6 +74,7 @@ class _ChewieControllerHook extends Hook<ChewieController> {
this.allowFullScreen = false,
this.allowedScreenSleep = false,
this.showControls = true,
this.loopVideo = false,
this.customControls,
this.placeholder,
this.hideControlsTimer = const Duration(seconds: 3),
Expand All @@ -94,6 +98,7 @@ class _ChewieControllerHookState
allowFullScreen: hook.allowFullScreen,
allowedScreenSleep: hook.allowedScreenSleep,
showControls: hook.showControls,
looping: hook.loopVideo,
customControls: hook.customControls,
placeholder: hook.placeholder,
hideControlsTimer: hook.hideControlsTimer,
Expand Down
3 changes: 3 additions & 0 deletions mobile/lib/widgets/asset_viewer/video_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
final Duration hideControlsTimer;
final bool showControls;
final bool showDownloadingIndicator;
final bool loopVideo;

const VideoPlayerViewer({
super.key,
Expand All @@ -21,6 +22,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
required this.hideControlsTimer,
required this.showControls,
required this.showDownloadingIndicator,
required this.loopVideo,
});

@override
Expand All @@ -36,6 +38,7 @@ class VideoPlayerViewer extends HookConsumerWidget {
),
showControls: showControls && !isMotionVideo,
hideControlsTimer: hideControlsTimer,
loopVideo: loopVideo,
);

return Chewie(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'video_viewer_settings.dart';

class AssetViewerSettings extends StatelessWidget {
const AssetViewerSettings({
super.key,
});

@override
Widget build(BuildContext context) {
final assetViewerSetting = [
const ImageViewerQualitySetting(),
const VideoViewerSettings(),
];

return SettingsSubPageScaffold(
settings: assetViewerSetting,
showDivider: true,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';

class ImageViewerQualitySetting extends HookConsumerWidget {
const ImageViewerQualitySetting({
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview);
final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal);

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSubTitle(title: "setting_image_viewer_title".tr()),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
title: Text(
'setting_image_viewer_help',
style: context.textTheme.bodyMedium,
).tr(),
),
SettingsSwitchListTile(
valueNotifier: isPreview,
title: "setting_image_viewer_preview_title".tr(),
subtitle: "setting_image_viewer_preview_subtitle".tr(),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
),
SettingsSwitchListTile(
valueNotifier: isOriginal,
title: "setting_image_viewer_original_title".tr(),
subtitle: "setting_image_viewer_original_subtitle".tr(),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';

class VideoViewerSettings extends HookConsumerWidget {
const VideoViewerSettings({
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SettingsSubTitle(title: "setting_video_viewer_title".tr()),
SettingsSwitchListTile(
valueNotifier: useLoopVideo,
title: "setting_video_viewer_looping_title".tr(),
subtitle: "setting_video_viewer_looping_subtitle".tr(),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
),
],
);
}
}
41 changes: 0 additions & 41 deletions mobile/lib/widgets/settings/image_viewer_quality_setting.dart

This file was deleted.

3 changes: 3 additions & 0 deletions web/src/lib/components/asset-viewer/asset-viewer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,7 @@
<VideoViewer
assetId={previewStackedAsset.id}
projectionType={previewStackedAsset.exifInfo?.projectionType}
loopVideo={true}
on:close={closeViewer}
on:onVideoEnded={() => navigateAsset()}
on:onVideoStarted={handleVideoStarted}
Expand All @@ -655,6 +656,7 @@
<VideoViewer
assetId={asset.livePhotoVideoId}
projectionType={asset.exifInfo?.projectionType}
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
on:close={closeViewer}
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
/>
Expand All @@ -669,6 +671,7 @@
<VideoViewer
assetId={asset.id}
projectionType={asset.exifInfo?.projectionType}
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
on:close={closeViewer}
on:onVideoEnded={() => navigateAsset()}
on:onVideoStarted={handleVideoStarted}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { videoViewerVolume, videoViewerMuted } from '$lib/stores/preferences.store';
import { loopVideo as loopVideoPreference, videoViewerVolume, videoViewerMuted } from '$lib/stores/preferences.store';
import { getAssetFileUrl, getAssetThumbnailUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { ThumbnailFormat } from '@immich/sdk';
Expand All @@ -8,6 +8,7 @@
import LoadingSpinner from '../shared-components/loading-spinner.svelte';

export let assetId: string;
export let loopVideo: boolean;

let element: HTMLVideoElement | undefined = undefined;
let isVideoLoading = true;
Expand All @@ -34,6 +35,7 @@
>
<video
bind:this={element}
loop={$loopVideoPreference && loopVideo}
autoplay
playsinline
controls
Expand Down
Loading
Loading