refactor(mobile): Refactor video player page and gallery bottom app bar (#7625)
* Fixes double video auto initialize issue and placeholder for video controller
* WIP unravel stack index
* Refactors video player controller
format
fixing video
format
Working
format
* Fixes hide on pause
* Got hiding when tapped working
* Hides controls when video starts and fixes placeholder for memory card
Remove prints
* Fixes show controls with microtask
* fix LivePhotos not playing
* removes unused function callbacks and moves wakelock
* Update motion video
* Fixing motion photo playing
* Renames to isPlayingVideo
* Fixes playing video on change
* pause on dispose
* fixing issues with sync between controls
* Adds gallery app bar
* Switches to memoized
* Fixes pause
* Revert "Switches to memoized"
This reverts commit 234e6741de.
* uses stateful widget
* Fixes double video play by using provider and new chewie video player
wip
format
Fixes motion photos
format
---------
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
@@ -2,6 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'asset_stack.provider.g.dart';
|
||||
|
||||
class AssetStackNotifier extends StateNotifier<List<Asset>> {
|
||||
final Asset _asset;
|
||||
@@ -49,3 +52,8 @@ final assetStackProvider =
|
||||
.sortByFileCreatedAtDesc()
|
||||
.findAll();
|
||||
});
|
||||
|
||||
@riverpod
|
||||
int assetStackIndex(AssetStackIndexRef ref, Asset asset) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'asset_stack.provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$assetStackIndexHash() => r'0f2df55e929767c8c698bd432b5e6e351d000a16';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [assetStackIndex].
|
||||
@ProviderFor(assetStackIndex)
|
||||
const assetStackIndexProvider = AssetStackIndexFamily();
|
||||
|
||||
/// See also [assetStackIndex].
|
||||
class AssetStackIndexFamily extends Family<int> {
|
||||
/// See also [assetStackIndex].
|
||||
const AssetStackIndexFamily();
|
||||
|
||||
/// See also [assetStackIndex].
|
||||
AssetStackIndexProvider call(
|
||||
Asset asset,
|
||||
) {
|
||||
return AssetStackIndexProvider(
|
||||
asset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AssetStackIndexProvider getProviderOverride(
|
||||
covariant AssetStackIndexProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.asset,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'assetStackIndexProvider';
|
||||
}
|
||||
|
||||
/// See also [assetStackIndex].
|
||||
class AssetStackIndexProvider extends AutoDisposeProvider<int> {
|
||||
/// See also [assetStackIndex].
|
||||
AssetStackIndexProvider(
|
||||
Asset asset,
|
||||
) : this._internal(
|
||||
(ref) => assetStackIndex(
|
||||
ref as AssetStackIndexRef,
|
||||
asset,
|
||||
),
|
||||
from: assetStackIndexProvider,
|
||||
name: r'assetStackIndexProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$assetStackIndexHash,
|
||||
dependencies: AssetStackIndexFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
AssetStackIndexFamily._allTransitiveDependencies,
|
||||
asset: asset,
|
||||
);
|
||||
|
||||
AssetStackIndexProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.asset,
|
||||
}) : super.internal();
|
||||
|
||||
final Asset asset;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
int Function(AssetStackIndexRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: AssetStackIndexProvider._internal(
|
||||
(ref) => create(ref as AssetStackIndexRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
asset: asset,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<int> createElement() {
|
||||
return _AssetStackIndexProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is AssetStackIndexProvider && other.asset == asset;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, asset.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin AssetStackIndexRef on AutoDisposeProviderRef<int> {
|
||||
/// The parameter `asset` of this provider.
|
||||
Asset get asset;
|
||||
}
|
||||
|
||||
class _AssetStackIndexProviderElement extends AutoDisposeProviderElement<int>
|
||||
with AssetStackIndexRef {
|
||||
_AssetStackIndexProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Asset get asset => (origin as AssetStackIndexProvider).asset;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/models/store.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
part 'video_player_controller_provider.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<VideoPlayerController> videoPlayerController(
|
||||
VideoPlayerControllerRef ref, {
|
||||
required Asset asset,
|
||||
}) async {
|
||||
late VideoPlayerController controller;
|
||||
if (asset.isLocal && asset.livePhotoVideoId == null) {
|
||||
// Use a local file for the video player controller
|
||||
final file = await asset.local!.file;
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
controller = VideoPlayerController.file(file);
|
||||
} else {
|
||||
// Use a network URL for the video player controller
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final String videoUrl = asset.livePhotoVideoId != null
|
||||
? '$serverEndpoint/asset/file/${asset.livePhotoVideoId}'
|
||||
: '$serverEndpoint/asset/file/${asset.remoteId}';
|
||||
|
||||
final url = Uri.parse(videoUrl);
|
||||
final accessToken = Store.get(StoreKey.accessToken);
|
||||
|
||||
controller = VideoPlayerController.networkUrl(
|
||||
url,
|
||||
httpHeaders: {"x-immich-user-token": accessToken},
|
||||
);
|
||||
}
|
||||
|
||||
await controller.initialize();
|
||||
|
||||
ref.onDispose(() {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
return controller;
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'video_player_controller_provider.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$videoPlayerControllerHash() =>
|
||||
r'72b45de66542021717807655e25ec92d78d80eec';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [videoPlayerController].
|
||||
@ProviderFor(videoPlayerController)
|
||||
const videoPlayerControllerProvider = VideoPlayerControllerFamily();
|
||||
|
||||
/// See also [videoPlayerController].
|
||||
class VideoPlayerControllerFamily
|
||||
extends Family<AsyncValue<VideoPlayerController>> {
|
||||
/// See also [videoPlayerController].
|
||||
const VideoPlayerControllerFamily();
|
||||
|
||||
/// See also [videoPlayerController].
|
||||
VideoPlayerControllerProvider call({
|
||||
required Asset asset,
|
||||
}) {
|
||||
return VideoPlayerControllerProvider(
|
||||
asset: asset,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
VideoPlayerControllerProvider getProviderOverride(
|
||||
covariant VideoPlayerControllerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
asset: provider.asset,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'videoPlayerControllerProvider';
|
||||
}
|
||||
|
||||
/// See also [videoPlayerController].
|
||||
class VideoPlayerControllerProvider
|
||||
extends AutoDisposeFutureProvider<VideoPlayerController> {
|
||||
/// See also [videoPlayerController].
|
||||
VideoPlayerControllerProvider({
|
||||
required Asset asset,
|
||||
}) : this._internal(
|
||||
(ref) => videoPlayerController(
|
||||
ref as VideoPlayerControllerRef,
|
||||
asset: asset,
|
||||
),
|
||||
from: videoPlayerControllerProvider,
|
||||
name: r'videoPlayerControllerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$videoPlayerControllerHash,
|
||||
dependencies: VideoPlayerControllerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
VideoPlayerControllerFamily._allTransitiveDependencies,
|
||||
asset: asset,
|
||||
);
|
||||
|
||||
VideoPlayerControllerProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.asset,
|
||||
}) : super.internal();
|
||||
|
||||
final Asset asset;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<VideoPlayerController> Function(VideoPlayerControllerRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: VideoPlayerControllerProvider._internal(
|
||||
(ref) => create(ref as VideoPlayerControllerRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
asset: asset,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<VideoPlayerController> createElement() {
|
||||
return _VideoPlayerControllerProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is VideoPlayerControllerProvider && other.asset == asset;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, asset.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
mixin VideoPlayerControllerRef
|
||||
on AutoDisposeFutureProviderRef<VideoPlayerController> {
|
||||
/// The parameter `asset` of this provider.
|
||||
Asset get asset;
|
||||
}
|
||||
|
||||
class _VideoPlayerControllerProviderElement
|
||||
extends AutoDisposeFutureProviderElement<VideoPlayerController>
|
||||
with VideoPlayerControllerRef {
|
||||
_VideoPlayerControllerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Asset get asset => (origin as VideoPlayerControllerProvider).asset;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||
@@ -1,10 +1,15 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
class VideoPlaybackControls {
|
||||
VideoPlaybackControls({required this.position, required this.mute});
|
||||
VideoPlaybackControls({
|
||||
required this.position,
|
||||
required this.mute,
|
||||
required this.pause,
|
||||
});
|
||||
|
||||
final double position;
|
||||
final bool mute;
|
||||
final bool pause;
|
||||
}
|
||||
|
||||
final videoPlayerControlsProvider =
|
||||
@@ -17,6 +22,7 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||
: super(
|
||||
VideoPlaybackControls(
|
||||
position: 0,
|
||||
pause: false,
|
||||
mute: false,
|
||||
),
|
||||
);
|
||||
@@ -29,18 +35,62 @@ class VideoPlayerControls extends StateNotifier<VideoPlaybackControls> {
|
||||
state = value;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = VideoPlaybackControls(
|
||||
position: 0,
|
||||
pause: false,
|
||||
mute: false,
|
||||
);
|
||||
}
|
||||
|
||||
double get position => state.position;
|
||||
bool get mute => state.mute;
|
||||
|
||||
set position(double value) {
|
||||
state = VideoPlaybackControls(position: value, mute: state.mute);
|
||||
state = VideoPlaybackControls(
|
||||
position: value,
|
||||
mute: state.mute,
|
||||
pause: state.pause,
|
||||
);
|
||||
}
|
||||
|
||||
set mute(bool value) {
|
||||
state = VideoPlaybackControls(position: state.position, mute: value);
|
||||
state = VideoPlaybackControls(
|
||||
position: state.position,
|
||||
mute: value,
|
||||
pause: state.pause,
|
||||
);
|
||||
}
|
||||
|
||||
void toggleMute() {
|
||||
state = VideoPlaybackControls(position: state.position, mute: !state.mute);
|
||||
state = VideoPlaybackControls(
|
||||
position: state.position,
|
||||
mute: !state.mute,
|
||||
pause: state.pause,
|
||||
);
|
||||
}
|
||||
|
||||
void pause() {
|
||||
state = VideoPlaybackControls(
|
||||
position: state.position,
|
||||
mute: state.mute,
|
||||
pause: true,
|
||||
);
|
||||
}
|
||||
|
||||
void play() {
|
||||
state = VideoPlaybackControls(
|
||||
position: state.position,
|
||||
mute: state.mute,
|
||||
pause: false,
|
||||
);
|
||||
}
|
||||
|
||||
void togglePlay() {
|
||||
state = VideoPlaybackControls(
|
||||
position: state.position,
|
||||
mute: state.mute,
|
||||
pause: !state.pause,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,65 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
enum VideoPlaybackState {
|
||||
initializing,
|
||||
paused,
|
||||
playing,
|
||||
buffering,
|
||||
completed,
|
||||
}
|
||||
|
||||
class VideoPlaybackValue {
|
||||
VideoPlaybackValue({required this.position, required this.duration});
|
||||
|
||||
/// The current position of the video
|
||||
final Duration position;
|
||||
|
||||
/// The total duration of the video
|
||||
final Duration duration;
|
||||
|
||||
/// The current state of the video playback
|
||||
final VideoPlaybackState state;
|
||||
|
||||
/// The volume of the video
|
||||
final double volume;
|
||||
|
||||
VideoPlaybackValue({
|
||||
required this.position,
|
||||
required this.duration,
|
||||
required this.state,
|
||||
required this.volume,
|
||||
});
|
||||
|
||||
factory VideoPlaybackValue.fromController(VideoPlayerController? controller) {
|
||||
final video = controller?.value;
|
||||
late VideoPlaybackState s;
|
||||
if (video == null) {
|
||||
s = VideoPlaybackState.initializing;
|
||||
} else if (video.isCompleted) {
|
||||
s = VideoPlaybackState.completed;
|
||||
} else if (video.isPlaying) {
|
||||
s = VideoPlaybackState.playing;
|
||||
} else if (video.isBuffering) {
|
||||
s = VideoPlaybackState.buffering;
|
||||
} else {
|
||||
s = VideoPlaybackState.paused;
|
||||
}
|
||||
|
||||
return VideoPlaybackValue(
|
||||
position: video?.position ?? Duration.zero,
|
||||
duration: video?.duration ?? Duration.zero,
|
||||
state: s,
|
||||
volume: video?.volume ?? 0.0,
|
||||
);
|
||||
}
|
||||
|
||||
factory VideoPlaybackValue.uninitialized() {
|
||||
return VideoPlaybackValue(
|
||||
position: Duration.zero,
|
||||
duration: Duration.zero,
|
||||
state: VideoPlaybackState.initializing,
|
||||
volume: 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final videoPlaybackValueProvider =
|
||||
@@ -15,10 +70,7 @@ final videoPlaybackValueProvider =
|
||||
class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
||||
VideoPlaybackValueState(this.ref)
|
||||
: super(
|
||||
VideoPlaybackValue(
|
||||
position: Duration.zero,
|
||||
duration: Duration.zero,
|
||||
),
|
||||
VideoPlaybackValue.uninitialized(),
|
||||
);
|
||||
|
||||
final Ref ref;
|
||||
@@ -30,6 +82,11 @@ class VideoPlaybackValueState extends StateNotifier<VideoPlaybackValue> {
|
||||
}
|
||||
|
||||
set position(Duration value) {
|
||||
state = VideoPlaybackValue(position: value, duration: state.duration);
|
||||
state = VideoPlaybackValue(
|
||||
position: value,
|
||||
duration: state.duration,
|
||||
state: state.state,
|
||||
volume: state.volume,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user